-
-
Save srgoogleguy/94f7cd801bb21cb405d1 to your computer and use it in GitHub Desktop.
Binary Tree PHP Implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* A binary tree implementation in PHP | |
* | |
* features: | |
* - add node | |
* - delete node | |
* - find node by value | |
* - walk tree | |
* - replace node | |
* - traversable interface | |
* - custom comparison callback | |
* - translate btree to graphviz dot | |
* - translate graphviz dot to png | |
* | |
*/ | |
class BinaryTree implements IteratorAggregate | |
{ | |
protected $size = 0; | |
protected $cmpCallback; | |
public $root; | |
public function __construct(BinaryNode $rootNode = null) | |
{ | |
if ($rootNode) { | |
$this->root = $rootNode; | |
} else { | |
$this->root = new BinaryNode($this); | |
} | |
$this->cmpCallback = function($a, $b) { | |
if ($a > $b) { | |
return 1; | |
} elseif ($b > $a) { | |
return -1; | |
} else { | |
return 0; | |
} | |
}; | |
} | |
public function __get($name) | |
{ | |
if ($name === 'size') { | |
return $this->size; | |
} | |
} | |
public function add($value) | |
{ | |
$this->root->add($value); | |
$this->size++; | |
} | |
public function delete(BinaryNode $node) | |
{ | |
// copy the left and right nodes of this subtree | |
$left = $node->left; | |
$right = $node->right; | |
// if this is the parent node then its right subtree must become the parent | |
if ($node->isRoot) { | |
$this->root = $right; | |
if ($right) { | |
$right->parent = null; | |
} | |
} else { | |
// otherwise if the node is a right branch | |
if ($node->branch instanceof RightBranch) { | |
$node->parent->right = $right; | |
} elseif ($node->branch instanceof LeftBranch) { | |
// if the node is a left branch | |
$node->parent->left = $left; | |
$left = $right; // right into left | |
} | |
} | |
// Then run each element in the left subtree back through the add method | |
$this->size--; | |
$tree = $this; | |
$this->walk($left, function($nodeValue) use ($tree) { | |
if ($nodeValue !== null) { | |
$tree->add($nodeValue); | |
$this->size--; // because every node we add back was already accounted for | |
} | |
}); | |
} | |
public function search($value) | |
{ | |
return $this->find($value, $this->root); | |
} | |
protected function find($value, BinaryNode $node = null) | |
{ | |
if ($node === null) { | |
return false; // failed to find value in tree | |
} | |
$cmp = $this->getCmpCallback(); | |
$cmp = $cmp($value, $node->value); | |
if ($cmp == 0) { | |
return $node; | |
} elseif ($cmp == 1) { | |
return $this->find($value, $node->right); | |
} elseif ($cmp == -1) { | |
return $this->find($value, $node->left); | |
} | |
} | |
public function walk(BinaryNode $node = null, Callable $callback = null) | |
{ | |
if (!$callback) { $callback = function($v) { return $v; }; } | |
if ($node && $node->value !== null) { | |
$resultA = $this->walk($node->left); | |
$resultB = $node->value; | |
$resultC = $this->walk($node->right); | |
$result = [$resultA, $resultB, $resultC]; | |
$this->reduce($result, $callback); | |
return $result; | |
} | |
} | |
private function reduce($result, $callback) | |
{ | |
if (!is_array($result)) { | |
return $callback($result); | |
} | |
foreach($result as $v) { | |
if (is_array($v)) { | |
$this->reduce($v, $callback); | |
} else { | |
$callback($v); | |
} | |
} | |
} | |
public function setCmpCallback(callable $callback) | |
{ | |
$this->cmpCallback = $callback; | |
} | |
public function getCmpCallback() | |
{ | |
return $this->cmpCallback; | |
} | |
/* Returns a flat list of the tree's values */ | |
protected function toList(BinaryNode $node) | |
{ | |
$array = $this->walk($node); | |
$list = []; | |
$list = $this->flattenRecursive($array, $list); | |
return $list; | |
} | |
/* Flattens a multidimensinoal array into a flat array */ | |
protected function flattenRecursive($val, array &$list) | |
{ | |
if (is_scalar($val) && $val !== null) { | |
$list[] = $val; | |
} elseif (is_array($val)) { | |
foreach ($val as $v) { | |
$this->flattenRecursive($v, $list); | |
} | |
} elseif (!is_scalar($val) && !is_null($val)) { | |
$type = gettype($val); | |
throw new BinaryTreeException("Unexpected type '$type' in flattenRecursive!"); | |
} | |
return $list; | |
} | |
/* Iterative Binary Tree Traversal -- uses stack for in order traversal */ | |
public function inOrderIterativeTraversal() | |
{ | |
$stack = $list = []; | |
$current = $this->root; | |
while($stack || $current) { | |
if ($current) { | |
$stack[] = $current; | |
$current = $current->left; | |
} else { | |
$current = array_pop($stack); | |
$list[] = $current; | |
$current = $current->right; | |
} | |
} | |
return $list; | |
} | |
/* Graphviz Draw - Draws the binary tree using graphviz notation */ | |
public function graphVizDraw() | |
{ | |
$out = "digraph G{\ngraph [ordering=\"out\"]\n"; | |
$name = ""; | |
$stack = []; | |
$current = $this->root; | |
while($stack || $current) { | |
if ($current && $current->parent) { | |
$name = "$current->parent"; | |
} | |
$value = "$current"; | |
if ($name && $value) { | |
$out .= "$name -> $value\n"; | |
} elseif ($value && !$name && $current && !$current->parent) { | |
$out .= "$value\n"; | |
} | |
if ($current) { | |
$stack[] = $current; | |
$current = $current->left; | |
} else { | |
$current = array_pop($stack); | |
$current = $current->right; | |
} | |
} | |
$out .= "}"; | |
return $out; | |
} | |
/* Convert the GraphViz .dot notation to a PNG using GraphViz */ | |
public function toPNG($fileName) | |
{ | |
if (!extension_loaded('Imagick')) { | |
throw new BinaryTreeException("Imagick extension is not loaded. This method requires imagick..."); | |
} | |
$dotData = $this->graphVizDraw(); | |
$img = new Imagick; | |
$img->readImageBlob($dotData); | |
$img->setImageFormat("png"); | |
file_put_contents($fileName, $img->getImageBlob()); | |
return $fileName; | |
} | |
/* Iterator Interface Implementation relies on iterative binary tree traversal */ | |
public function getIterator() | |
{ | |
return new BinaryTreeIterator($this->inOrderIterativeTraversal()); | |
} | |
} | |
class BinaryTreeIterator implements Iterator | |
{ | |
private $list; | |
public function __construct(Array $list = null) | |
{ | |
$this->list = $list; | |
} | |
public function rewind() | |
{ | |
reset($this->list); | |
} | |
public function next() | |
{ | |
return next($this->list); | |
} | |
public function current() | |
{ | |
return current($this->list); | |
} | |
public function key() | |
{ | |
return key($this->list); | |
} | |
public function valid() | |
{ | |
return key($this->list) !== null; | |
} | |
} | |
class BinaryTreeException extends Exception | |
{ | |
/* Exception subtype for BinaryTree */ | |
} | |
class BranchType | |
{ | |
/* Metatype for branch */ | |
} | |
class LeftBranch extends BranchType | |
{ | |
/* Metatype for left branch */ | |
public function __toString() | |
{ | |
return "LeftBranch"; | |
} | |
} | |
class RightBranch extends BranchType | |
{ | |
/* Metatype for right branch */ | |
public function __toString() | |
{ | |
return "RightBranch"; | |
} | |
} | |
class RootBranch extends BranchType | |
{ | |
/* Metatype for root nodes with no branch */ | |
public function __toString() | |
{ | |
return "RootBranch"; | |
} | |
} | |
class BinaryNode | |
{ | |
protected $left, $right, $parent, $branch, $value, $tree; | |
private $isRoot = false; | |
public function __construct(BinaryTree $tree, BinaryNode $parent = null, | |
BranchType $branch = null, $value = null) | |
{ | |
$this->tree = $tree; | |
$this->parent = $parent; | |
$this->value = $value; | |
$this->branch = $branch; | |
} | |
public function __get($key) | |
{ | |
if ($key === 'isRoot') { | |
if ($this->parent === null) { | |
return true; | |
} else { | |
return false; | |
} | |
} elseif ($key === 'left') { | |
return $this->left; | |
} elseif ($key === 'right') { | |
return $this->right; | |
} elseif ($key === 'parent') { | |
return $this->parent; | |
} elseif ($key === 'branch') { | |
if (!$this->branch && !$this->parent) { | |
return new RootBranch; | |
} else { | |
return $this->branch; | |
} | |
} elseif ($key === 'value') { | |
return $this->value; | |
} | |
} | |
public function __set($key, $value) | |
{ | |
if ($key === 'isRoot') { | |
return; | |
} elseif ($key === 'left') { | |
if ($value instanceof BinaryNode || is_null($value)) { | |
$this->left = $value; | |
if ($value) { | |
$value->branch = new LeftBranch; | |
} | |
} else { | |
$type = gettype($value); | |
throw new BinaryNodeException("Can only assign type BinaryNode to Binary Tree. Got". | |
" type '$type' instead."); | |
} | |
} elseif ($key === 'right') { | |
if ($value instanceof BinaryNode || is_null($value)) { | |
$this->right = $value; | |
if ($value) { | |
$value->branch = new RightBranch; | |
} | |
} else { | |
$type = gettype($value); | |
throw new BinaryNodeException("Can only assign type BinaryNode to Binary Tree. Got". | |
" type '$type' instead."); | |
} | |
} elseif ($key === 'parent') { | |
if ($value instanceof BinaryNode || is_null($value)) { | |
$this->parent = $value; | |
if ($value) { | |
$value->branch = null; | |
$value->parent = null; | |
} | |
} else { | |
$type = gettype($value); | |
throw new BinaryNodeException("Can only assign type BinaryNode or NULL to Binary". | |
" Tree parent. Got type '$type' instead."); | |
} | |
} elseif ($key === 'branch') { | |
if ($value instanceof RightBranch || $value instanceof LeftBranch || is_null($value)) { | |
if (!$this->isRoot && !$value) { | |
throw new BinaryNodeException("Cannot assign null to non-root BinaryNode!"); | |
} | |
$this->branch = $value; | |
} else { | |
$type = gettype($value); | |
throw new BinaryNodeException("Can only assign type BranchType to BinaryNode". | |
" branch. Got type '$type' instead."); | |
} | |
} elseif ($key === 'value') { | |
// If the value is null we simply delete the node | |
if ($value === null) { | |
$this->getTree()->delete($this); | |
return; | |
} | |
$tree = $this->getTree(); | |
// If the value already exists in the tree then do nothing | |
if ($tree->search($value) !== false) { | |
return; | |
} | |
// Otherwise we replace this node for a new one with the given value | |
$this->delete(); | |
// Now add the new value back into the tree | |
$tree->add($value); | |
} | |
} | |
public function __toString() | |
{ | |
return (string) $this->value; | |
} | |
public function add($value) | |
{ | |
$callback = $this->getTree()->getCmpCallback(); | |
$cmp = $callback($value, $this->value); | |
if (isset($this->value) && $cmp == 1) { | |
if (!isset($this->right)) { | |
$this->right = new BinaryNode($this->tree, $this, new RightBranch, $value); | |
} else { | |
$this->right->add($value); | |
} | |
} elseif (isset($value) && $cmp == -1) { | |
if (!isset($this->left)) { | |
$this->left = new BinaryNode($this->tree, $this, new LeftBranch, $value); | |
} else { | |
$this->left->add($value); | |
} | |
} elseif (!isset($this->value)) { | |
$this->value = $value; | |
} | |
} | |
public function getTree() | |
{ | |
return $this->tree; | |
} | |
public function delete() | |
{ | |
$this->getTree()->delete($this); | |
} | |
} | |
class BinaryNodeException extends Exception | |
{ | |
/* Exception subtype for BinaryNode */ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Examples
Add Nodes
Traverse with foreach
Delete a Node
Find a Node by Value
Replace a Node
Using Custom Comparison Callback
Using BinaryTree::toPNG() Method
We can get a visual representation of the binary tree using the
toPNG
method, which relies on thegraphVizDraw
method that translates the tree into dot notation. If you have graphviz installed you can easily convert this to a PNG image with this method.This gives us a PNG file like so...