diff --git a/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php b/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php new file mode 100644 index 0000000..1448455 --- /dev/null +++ b/src/Algorithm/BinaryTree/RecursiveReversedTraversal.php @@ -0,0 +1,161 @@ + + * ⚠ Recursive function implies a growing stack trace, which has a limited size!
+ * See {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\ReversedTraversal} for iterative implementations. + * + * + *
+ * ℹ Reversed level-order is not implemented as it implies to keep track of and look at each and every node in + * order to detect the last level => Time and space complexity will be 𝑂⟮𝑛⟯ in any cases !
+ * Workaround:
+ * 1. Fetch the result of {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::levelOrder} + * or {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::levelOrderGenerator} + * 2. Either start from the end or reverse the whole list (the latter implies + * an additional 𝑂⟮𝑛⟯ time complexity though !) + * + * + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\ReversedTraversal for iterative implementations. + */ +class RecursiveReversedTraversal +{ + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal::preOrderGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function preOrder(Node $node): array + { + // ⚠ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used ! + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::preOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal::preOrderGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function inOrder(Node $node): array + { + // ⚠ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used ! + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::inOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal::inOrderGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function postOrder(Node $node): array + { + // ⚠ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used ! + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::postOrderGenerator($node), false); + } + + /** + * Reversed Pre-order: N->R->L => Node, then Right children, then Left children + * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a right child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances ! + * + * + * @return Generator + */ + public static function preOrderGenerator(Node $node): Generator + { + yield $node; + + if (null !== $node->right) { + yield from self::preOrderGenerator($node->right); + } + if (null !== $node->left) { + yield from self::preOrderGenerator($node->left); + } + } + + /** + * Reversed In-order (=Reversed DFS): R->N->L => Right children, then Node, then Left children + * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a right child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances ! + * + * + * @return Generator + */ + public static function inOrderGenerator(Node $node): Generator + { + if (null !== $node->right) { + yield from self::inOrderGenerator($node->right); + } + + yield $node; + + if (null !== $node->left) { + yield from self::inOrderGenerator($node->left); + } + } + + /** + * Reversed Post-order: R->L->N => Right children, then Left children, then Node + * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a right child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances ! + * + * + * @return Generator + */ + public static function postOrderGenerator(Node $node): Generator + { + if (null !== $node->right) { + yield from self::postOrderGenerator($node->right); + } + if (null !== $node->left) { + yield from self::postOrderGenerator($node->left); + } + yield $node; + } +} diff --git a/src/Algorithm/BinaryTree/RecursiveTraversal.php b/src/Algorithm/BinaryTree/RecursiveTraversal.php new file mode 100644 index 0000000..114033b --- /dev/null +++ b/src/Algorithm/BinaryTree/RecursiveTraversal.php @@ -0,0 +1,216 @@ + + * ⚠ Recursive function implies a growing stack trace, which has a limited size!
+ * See {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal} for iterative implementations. + * + * + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal for iterative implementations. + */ +class RecursiveTraversal +{ + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::preOrderGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function preOrder(Node $node): array + { + // ⚠ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used ! + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::preOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::preOrderGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function inOrder(Node $node): array + { + // ⚠ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used ! + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::inOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveTraversal::inOrderGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function postOrder(Node $node): array + { + // ⚠ Do not preserve keys otherwise there is conflicting keys in case "yield from" is used ! + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::postOrderGenerator($node), false); + } + + /** + * Level order (=BFS): Traverse tree level by level, from Left to Right + * + * + *
+ * 💡 Reverse the list for reversed level-order traversal ! + *
+ *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝑛⟯ - Due to the list storing every node for every level (see levelOrderHelper() function). + * + * + * @return array> key is the level, value is the list of nodes for that level + * @phpstan-return list> + */ + public static function levelOrder(Node $node): array + { + $res = []; + + self::levelOrderHelper($node, $res); + + /** + * No easy way to tell PHPStan that $res is a list in case level is not provided :/ + * @phpstan-var list> $res + */ + return $res; + } + + /** + * Pre-order: N->L->R => Node, then Left child, then Right child + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a left child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances ! + * + * + * @return Generator + */ + public static function preOrderGenerator(Node $node): Generator + { + yield $node; + + if (null !== $node->left) { + yield from self::preOrderGenerator($node->left); + } + if (null !== $node->right) { + yield from self::preOrderGenerator($node->right); + } + } + + /** + * In-order (=DFS): L->N->R => Left child, then Node, then Right child + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a left child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances ! + * + * + * @return Generator + */ + public static function inOrderGenerator(Node $node): Generator + { + if (null !== $node->left) { + yield from self::inOrderGenerator($node->left); + } + + yield $node; + + if (null !== $node->right) { + yield from self::inOrderGenerator($node->right); + } + } + + /** + * Post-order: L->R->N => Left child, then Right child, then Node + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a left child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack trace (recursive calls) + extra space for inner Generator class instances ! + * + * + * @return Generator + */ + public static function postOrderGenerator(Node $node): Generator + { + if (null !== $node->left) { + yield from self::postOrderGenerator($node->left); + } + if (null !== $node->right) { + yield from self::postOrderGenerator($node->right); + } + yield $node; + } + + /** + * Level order (=BFS): Traverse tree level by level, from Left to Right + * + * + *
+ * 💡 Reverse the list for reversed level-order traversal ! + *
+ *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a left child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮h⟯ - Due to the stack trace (recursive calls) + * + * + * @param array> $res key is the level, value is the list of nodes for that level + * @phpstan-param array> $res + */ + public static function levelOrderHelper(Node $node, array &$res, int $level = 0): void + { + $res[$level] ??= []; // In case current level hasn't been seen/managed yet + + $res[$level][] = $node; + if (null !== $node->left) { + self::levelOrderHelper($node->left, $res, $level + 1); + } + if (null !== $node->right) { + self::levelOrderHelper($node->right, $res, $level + 1); + } + } +} diff --git a/src/Algorithm/BinaryTree/ReversedTraversal.php b/src/Algorithm/BinaryTree/ReversedTraversal.php new file mode 100644 index 0000000..6428d18 --- /dev/null +++ b/src/Algorithm/BinaryTree/ReversedTraversal.php @@ -0,0 +1,251 @@ + + * ℹ Reversed level-order is not implemented as it implies to keep track of and look at each and every node in + * order to detect the last level => Time and space complexity will be 𝑂⟮𝑛⟯ in any cases !
+ * Workaround:
+ * 1. Fetch the result of {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrder} + * or {@see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrderGenerator} + * 2. Either start from the end or reverse the whole list (the latter implies + * an additional 𝑂⟮𝑛⟯ time complexity though !) + * + * + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RecursiveReversedTraversal for recursive implementations. + */ +class ReversedTraversal +{ + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::preOrderGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function preOrder(Node $node): array + { + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::preOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::inOrderGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function inOrder(Node $node): array + { + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::inOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::postOrderGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function postOrder(Node $node): array + { + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::postOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\RevervedTraversal::BFSGenerator() + * + * + * @return array + * @phpstan-return list + */ + public static function BFS(Node $node): array + { + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::BFSGenerator($node), false); + } + + /** + * Reversed Pre-order: N->R->L => Node, then Right children, then Left children + * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a right child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack storing parent nodes path up to the root node. + * + * + * @return Generator + */ + public static function preOrderGenerator(Node $node): Generator + { + /** @var SplStack $stack */ + $stack = new SplStack(); + + $stack->push($node); + while (!$stack->isEmpty()) { + $currentNode = $stack->pop(); + + yield $currentNode; + + if (null !== $currentNode->left) { + $stack->push($currentNode->left); + } + if (null !== $currentNode->right) { + $stack->push($currentNode->right); + } + } + } + + /** + * Reversed In-order (=Reversed DFS): R->N->L => Right children, then Node, then Left children + * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a right child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack storing parent nodes path up to the root node. + * + * + * @return Generator + */ + public static function inOrderGenerator(Node $node): Generator + { + /** @var SplStack $stack */ + $stack = new SplStack(); + + $currentNode = $node; + while (null !== $currentNode || !$stack->isEmpty()) { + while (null !== $currentNode) { + $stack->push($currentNode); + $currentNode = $currentNode->right; + } + + // Current node becomes the rightmost leaf found + // (or the same current node in case it doesn't have right node!) + $currentNode = $stack->pop(); + + yield $currentNode; + + // Right is now managed, let's take a look on left side + $currentNode = $currentNode->left; + } + } + + /** + * Reversed Post-order: R->L->N => Right children, then Left children, then Node + * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a right child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack storing parent nodes. + * + * + * @return Generator + */ + public static function postOrderGenerator(Node $node): Generator + { + /** @var SplStack $stack */ + $stack = new SplStack(); + + $currentNode = $node; + while (null !== $currentNode || !$stack->isEmpty()) { + while (null !== $currentNode) { + if (null !== $currentNode->left) { + $stack->push($currentNode->left); + } + $stack->push($currentNode); + $currentNode = $currentNode->right; + } + + $currentNode = $stack->pop(); + + if (!$stack->isEmpty() && $currentNode->left === $stack->top()) { + // Remove current node left child from the stack, it will become $currentNode for next iteration + $stack->pop(); + + $stack->push($currentNode); + $currentNode = $currentNode->left; + } else { + yield $currentNode; + + $currentNode = null; + } + } + } + + /** + * Reversed BFS: Traverse tree level by level, from Right to Left.
+ * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝑚 the number of nodes for the bigger level + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝑚⟯ - Due to the queue storing current level parent nodes + * + * + * @return Generator + */ + public static function BFSGenerator(Node $node): Generator + { + /** @var SplQueue $queue */ + $queue = new SplQueue(); + + $queue->enqueue($node); + while (!$queue->isEmpty()) { + $currentLevelNodeCounter = $queue->count(); + while ($currentLevelNodeCounter > 0) { + $node = $queue->dequeue(); + + yield $node; + + if (null !== $node->right) { + $queue->enqueue($node->right); + } + if (null !== $node->left) { + $queue->enqueue($node->left); + } + + --$currentLevelNodeCounter; + } + } + } +} diff --git a/src/Algorithm/BinaryTree/Traversal.php b/src/Algorithm/BinaryTree/Traversal.php new file mode 100644 index 0000000..27e41c9 --- /dev/null +++ b/src/Algorithm/BinaryTree/Traversal.php @@ -0,0 +1,314 @@ + + */ + public static function preOrder(Node $node): array + { + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::preOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::inOrderGenerator() + * + * + * @return Node[] + * @phpstan-return list + */ + public static function inOrder(Node $node): array + { + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::inOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::postOrderGenerator() + * + * + * @return Node[] + * @phpstan-return list + */ + public static function postOrder(Node $node): array + { + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::postOrderGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::BFSGenerator() + * + * + * @return Node[] + * @phpstan-return list + */ + public static function BFS(Node $node): array + { + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::BFSGenerator($node), false); + } + + /** + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrderGenerator() + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::BFS() if node level isn't useful. + * + * + * @return array> key is the level, value is the list of nodes for that level + * @phpstan-return list> + */ + public static function levelOrder(Node $node): array + { + // ⚠ Do not preserve keys in order to always return a list ! + return iterator_to_array(self::levelOrderGenerator($node), false); + } + + /** + * Pre-order: N->L->R => Node, then Left child, then Right child. + * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a left child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack storing parent nodes path up to the root node. + * + * + * @return Generator + */ + public static function preOrderGenerator(Node $node): Generator + { + /** @var SplStack $stack */ + $stack = new SplStack(); + + $stack->push($node); + + while (!$stack->isEmpty()) { + $currentNode = $stack->pop(); + + yield $currentNode; + + if (null !== $currentNode->right) { + $stack->push($currentNode->right); + } + if (null !== $currentNode->left) { + $stack->push($currentNode->left); + } + } + } + + /** + * In-order (=DFS): L->N->R => Left child, then Node, then Right child. + * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a left child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack storing parent nodes path up to the root node. + * + * @return Generator + */ + public static function inOrderGenerator(Node $node): Generator + { + /** @var SplStack $stack */ + $stack = new SplStack(); + + $currentNode = $node; + while (null !== $currentNode || !$stack->isEmpty()) { + while (null !== $currentNode) { + $stack->push($currentNode); + $currentNode = $currentNode->left; + } + + // Current node becomes the leftmost leaf found + // (or the same current node in case it doesn't have left node!) + $currentNode = $stack->pop(); + + yield $currentNode; + + // Left is now managed, let's take a look on right side + $currentNode = $currentNode->right; + } + } + + /** + * Post-order: L->R->N => Left child, then Right child, then Node. + * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝘩 the tree height, usually ㏒(𝑛) (=balanced tree).
+ * ⚠ 𝘩 = 𝑛 if every node has only a left child (skewed tree) + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝘩⟯ - Due to the stack storing parent nodes path up to the root node. + * + * + * @return Generator + */ + public static function postOrderGenerator(Node $node): Generator + { + /** @var SplStack $stack */ + $stack = new SplStack(); + + $currentNode = $node; + while (null !== $currentNode || !$stack->isEmpty()) { + while (null !== $currentNode) { + if (null !== $currentNode->right) { + $stack->push($currentNode->right); + } + $stack->push($currentNode); + $currentNode = $currentNode->left; + } + + $currentNode = $stack->pop(); + + if (!$stack->isEmpty() && $currentNode->right === $stack->top()) { + // Remove current node right child from the stack, it will become $currentNode for next iteration + $stack->pop(); + + $stack->push($currentNode); + $currentNode = $currentNode->right; + } else { + yield $currentNode; + + $currentNode = null; + } + } + } + + /** + * Level order (=BFS): Traverse tree level by level, from Left to Right + * + * + *
+ * 💡 Reverse the list for reversed level-order traversal ! + *
+ *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝑚 the number of nodes for the bigger level + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝑚⟯ - Due to the queue storing current level parent nodes, plus temporary list storing every node + * for the current level + * + * + * + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::BFSGenerator() if node level isn't useful. + * + * + * @return Generator> key is the level, value is the list of nodes for that level + * @phpstan-return Generator> + */ + public static function levelOrderGenerator(Node $node): Generator + { + /** @var SplQueue $queue */ + $queue = new SplQueue(); + + $queue->enqueue($node); + + while (!$queue->isEmpty()) { + $nodeList = []; + $currentLevelNodeCounter = $queue->count(); + while ($currentLevelNodeCounter > 0) { + $node = $queue->dequeue(); + + $nodeList[] = $node; + + if (null !== $node->left) { + $queue->enqueue($node->left); + } + if (null !== $node->right) { + $queue->enqueue($node->right); + } + + --$currentLevelNodeCounter; + } + + yield $nodeList; + } + } + + /** + * BFS: Traverse tree level by level, from Left to Right.
+ * + * + *
+ * ### Time/Space complexity + * With: + * - 𝑛 the number of node in the tree + * - 𝑚 the number of nodes for the bigger level + * + * TC: 𝑂⟮𝑛⟯ - Each node will be visited only one time + * + * SC: 𝑂⟮𝑚⟯ - Due to the queue storing current level parent nodes + * + * + * @see \Yoanm\CommonDSA\Algorithm\BinaryTree\Traversal::levelOrderGenerator() if node level must be known ! + * + * + * @return Generator + */ + public static function BFSGenerator(Node $node): Generator + { + /** @var SplQueue $queue */ + $queue = new SplQueue(); + + $queue->enqueue($node); + while (!$queue->isEmpty()) { + $currentLevelNodeCounter = $queue->count(); + while ($currentLevelNodeCounter > 0) { + $node = $queue->dequeue(); + + yield $node; + + if (null !== $node->left) { + $queue->enqueue($node->left); + } + if (null !== $node->right) { + $queue->enqueue($node->right); + } + + --$currentLevelNodeCounter; + } + } + } +} diff --git a/src/DataStructure/BinaryTree/Node.php b/src/DataStructure/BinaryTree/Node.php new file mode 100644 index 0000000..c30f146 --- /dev/null +++ b/src/DataStructure/BinaryTree/Node.php @@ -0,0 +1,18 @@ + $list + * @phpstan-param callable(TValue $v): TNode $nodeCreator + * + * @phpstan-return ?TNode + */ + public static function fromLevelOrderList(array $list, callable $nodeCreator): ?Node + { + $tailIdx = count($list) - 1; + if ($tailIdx < 0 || (0 === $tailIdx && null === $list[0])) { + return null; + } + + /** @var SplQueue $queue */ + $queue = new SplQueue(); + + $root = call_user_func($nodeCreator, $list[0]); // @phpstan-ignore argument.type + $queue->enqueue($root); + + $idx = 1; // Root value already managed, hence 1 rather 0 ! + while ($idx <= $tailIdx) { + $parentNode = $queue->dequeue(); + + // 1. Manage left node value + if (null !== $list[$idx]) { + $parentNode->left = $node = call_user_func($nodeCreator, $list[$idx]); + $queue->enqueue($node); + } + ++$idx; + + // 1. Manage right node value (if it exists !) + if ($idx <= $tailIdx && null !== $list[$idx]) { + $parentNode->right = $node = call_user_func($nodeCreator, $list[$idx]); + $queue->enqueue($node); + } + ++$idx; + } + + return $root; + } +} diff --git a/tests/Technical/Algorithm/BinaryTree/AbstractTraversalTestCase.php b/tests/Technical/Algorithm/BinaryTree/AbstractTraversalTestCase.php new file mode 100644 index 0000000..a6d0a12 --- /dev/null +++ b/tests/Technical/Algorithm/BinaryTree/AbstractTraversalTestCase.php @@ -0,0 +1,195 @@ + [ + 'treeData' => [1,2,7,3,4,null,8,null,null,5,6,9], + 'expected' => [1,2,3,4,5,6,7,8,9], + ], + 'Basic case 2' => [ + 'treeData' => [1,2,7,3,6,8,10,4,5,null,null,null,9,11,12], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + 'Skewed tree - only left' => [ + 'treeData' => [1,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9,null,10,null,11,null,12], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + 'Skewed tree - only right' => [ + 'treeData' => [1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9,null,10,null,11,null,12], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + 'Zigzag tree' => [ + 'treeData' => [1,2,null,null,3,4,null,null,5,6,null,null,7,8,null,null,9,10,null,null,11,12], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + 'Double zigzag tree' => [ + 'treeData' => [1,2,8,null,3,9,null,4,null,null,10,null,5,11,null,6,null,null,12,null,7,13], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12,13], + ], + 'Asymmetric tree - deep left nesting' => [ + 'treeData' => [1,2,6,3,null,null,7,4,null,null,null,5], + 'expected' => [1,2,3,4,5,6,7], + ], + ]; + + return self::mapTestCases($caseDataList); + } + + public static function provideReversedPreOrderTestCases(): array + { + $caseDataList = [ + 'Basic case 1' => [ + 'treeData' => [1,5,2,9,6,null,3,null,null,8,7,4], + 'expected' => [1,2,3,4,5,6,7,8,9], + ], + 'Basic case 2' => [ + 'treeData' => [1,8,2,10,9,6,3,12,11,null,null,null,7,5,4], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + ]; + + return self::mapTestCases($caseDataList); + } + + public static function provideInOrderTestCases(): array + { + $caseDataList = [ + 'Basic case 1' => [ + 'treeData' => [6,2,7,1,4,null,9,null,null,3,5,8], + 'expected' => [1,2,3,4,5,6,7,8,9], + ], + 'Basic case 2' => [ + 'treeData' => [6,4,9,2,5,7,11,1,3,null,null,null,8,10,12], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + ]; + + return self::mapTestCases($caseDataList); + } + + public static function provideReversedInOrderTestCases(): array + { + $caseDataList = [ + 'Basic case 1' => [ + 'treeData' => [4,8,3,9,6,null,1,null,null,7,5,2], + 'expected' => [1,2,3,4,5,6,7,8,9], + ], + 'Basic case 2' => [ + 'treeData' => [7,9,4,11,8,6,2,12,10,null,null,null,5,3,1], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + ]; + + return self::mapTestCases($caseDataList); + } + + public static function providePostorderTestCases(): array + { + $caseDataList = [ + 'Basic case 1' => [ + 'treeData' => [9,5,8,1,4,null,7,null,null,2,3,6], + 'expected' => [1,2,3,4,5,6,7,8,9], + ], + 'Basic case 2' => [ + 'treeData' => [12,5,11,3,4,7,10,1,2,null,null,null,6,8,9], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + ]; + + return self::mapTestCases($caseDataList); + } + + public static function provideReversedPostorderTestCases(): array + { + $caseDataList = [ + 'Basic case 1' => [ + 'treeData' => [9,8,3,7,6,null,2,null,null,5,4,1], + 'expected' => [1,2,3,4,5,6,7,8,9], + ], + 'Basic case 2' => [ + 'treeData' => [12,11,6,10,7,5,3,9,8,null,null,null,4,2,1], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + ]; + + return self::mapTestCases($caseDataList); + } + + public static function provideTestReversedBFSCases(): array + { + $caseDataList = [ + 'Basic case 1' => [ + 'treeData' => [1,3,2,6,5,null,4,null,null,9,8,7], + 'expected' => [1,2,3,4,5,6,7,8,9], + ], + 'Basic case 2' => [ + 'treeData' => [1,3,2,7,6,5,4,12,11,null,null,null,10,9,8], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12], + ], + 'Basic case 3' => [ + 'treeData' => [1,3,2,7,6,5,4,12,11,null,null,null,10,9,8,null,null,15,null,null,null,14,null,null,13,17,null,null,null,16], + 'expected' => [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17], + ], + ]; + + return self::mapTestCases($caseDataList); + } + + public static function provideTestLevelOrderCases(): array + { + $caseDataList = [ + 'Basic case 1' => [ + 'treeData' => [1,2,3,4,5,null,6,null,null,7,8,9], + 'expected' => [[1],[2,3],[4,5,6],[7,8,9]], + ], + 'Basic case 2' => [ + 'treeData' => [1,2,3,4,5,6,7,8,9,null,null,null,10,11,12], + 'expected' => [[1],[2,3],[4,5,6,7],[8,9,10,11,12]], + ], + 'Basic case 3' => [ + 'treeData' => [1,2,3,4,5,6,7,8,9,null,null,null,10,11,12,null,null,13,null,null,null,14,null,null,15,16,null,null,null,17], + 'expected' => [[1],[2,3],[4,5,6,7],[8,9,10,11,12],[13,14,15],[16,17]], + ], + ]; + + return self::mapTestCases($caseDataList); + } + + /** + * @param array $caseDataList + * + * @return array + */ + protected static function mapTestCases(array $caseDataList): array + { + return array_map( + static fn ($caseData) => [ + TreeFactory::fromLevelOrderList($caseData['treeData'], static fn(int $val) => new Node($val)), + $caseData['expected'], + ], + $caseDataList, + ); + } +} diff --git a/tests/Technical/Algorithm/BinaryTree/RecursiveReversedTraversalTest.php b/tests/Technical/Algorithm/BinaryTree/RecursiveReversedTraversalTest.php new file mode 100644 index 0000000..fa6940f --- /dev/null +++ b/tests/Technical/Algorithm/BinaryTree/RecursiveReversedTraversalTest.php @@ -0,0 +1,62 @@ + $expected + * @phpstan-param list $expected + * @param iterable $actual + */ + protected static function assertSameTreeNodeValues(array $expected, iterable $actual, Assert $assert): void + { + $newActual = []; + foreach ($actual as $node) { + $newActual[] = $node->val; + } + + $assert::assertSame($expected, $newActual); + } + + /** + * @param array> $expected + * @phpstan-param array> $expected + * @param iterable> $actual + */ + protected static function assertLevelOrderSameTreeNodeValues(array $expected, iterable $actual, Assert $assert): void + { + $newActual = []; + foreach ($actual as $nodeList) { + $tmpList = []; + foreach ($nodeList as $node) { + $tmpList[] = $node->val; + } + $newActual[] = $tmpList; + } + + $assert::assertSame($expected, $newActual); + } +} diff --git a/tests/Technical/DataStructure/BinaryTree/NodeTest.php b/tests/Technical/DataStructure/BinaryTree/NodeTest.php new file mode 100644 index 0000000..eb7728a --- /dev/null +++ b/tests/Technical/DataStructure/BinaryTree/NodeTest.php @@ -0,0 +1,25 @@ +val); + self::assertSame($leftNode, $node->left); + self::assertSame($rightNode, $node->right); + } +} diff --git a/tests/Technical/Factory/BinaryTreeFactoryTest.php b/tests/Technical/Factory/BinaryTreeFactoryTest.php new file mode 100644 index 0000000..477ecef --- /dev/null +++ b/tests/Technical/Factory/BinaryTreeFactoryTest.php @@ -0,0 +1,71 @@ + new Node($val)); + + self::assertNotNull($root); + self::assertInstanceOf(Node::class, $root); // @phpstan-ignore staticMethod.alreadyNarrowedType + self::assertLevelOrderSameTreeNodeValues($expected, Helper::levelOrder($root), $this); + } + + /** + * @dataProvider provideFromLevelOrderListWithEmptyListTestCases + */ + public function testFromLevelOrderListWithEmptyList(array $data): void + { + $root = Factory::fromLevelOrderList($data, static fn(int $val) => new Node($val)); + + self::assertNull($root); + } + + public function provideFromLevelOrderListWithEmptyListTestCases(): array + { + return [ + 'Empty list' => [ + 'data' => [], + ], + 'Only one null value' => [ + 'data' => [null], + ], + ]; + } + + public function provideFromLevelOrderListTestCases(): array + { + return [ + 'Basic case 1' => [ + 'data' => [1,2,3,4,5,null,6,null,null,7,8,9], + 'expected' => [[1],[2,3],[4,5,6],[7,8,9]], + ], + 'Basic case 2' => [ + 'data' => [1,2,3,4,5,6,7,8,9,null,null,null,10,11,12], + 'expected' => [[1],[2,3],[4,5,6,7],[8,9,10,11,12]], + ], + 'Basic case 3' => [ + 'data' => [1,2,3,4,5,6,7,8,9,null,null,null,10,11,12,null,null,13,null,null,null,14,null,null,15,16,null,null,null,17], + 'expected' => [[1],[2,3],[4,5,6,7],[8,9,10,11,12],[13,14,15],[16,17]], + ], + ]; + } +}