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]],
+ ],
+ ];
+ }
+}