今天我们继续来学习二叉树的中序遍历算法。
在学点算法(九)——二叉树中序遍历算法(递归实现)中我们已经学习了二叉树中序遍历的递归算法,今天我们继续来学习它的迭代算法。
我们先来看整个遍历的过程,如图所示(重新播放需要刷新网页):
我们可以发现对于每棵子树来说,遍历总是从子树的左下角开始,然后依次回溯到相对应的父节点,最后整体上回归到根节点,在回溯每个父节点的过程中,它又会去遍历一下它的右子树,遍历过程继续沿用该种遍历方法,最后直至所有的节点遍历完成。整体的遍历方向,如下所示:
但是我们起始位置是根节点,如何去找到最左下角的节点呢?我们只能通过不断地去查找节点的左孩子,然后查找左孩子的左孩子,一直找到它没有左孩子为止,那么最后的节点就是待遍历子树的左下角的节点了。我们可以加入一个指针来指示查找过程,指针的跳转也意味查找过程中节点的变化。
如下所示:
随后我们对该节点执行特定方法,比如说打印一下,即完成了这个节点的遍历。
遍历完这个左下角节点后,指针就回到了它的父节点,如下:
同时该节点也自成一棵左子树,那么根据定义,接下来我们就要遍历该子树的根节点,即该节点的父节点,这个很好理解,比如下面这三个节点:
2
为这棵子树的根节点,也为其左子树的根节点1
的父节点。
而这个多层的:
4
为这棵子树的根节点,也为其左子树的根节点2
的父节点。
遍历完根节点后,我们不能直接回溯到根节点的父节点,还需要遍历右子树,这时右子树只有一个节点,直接遍历它。
此时我们就进行了一轮完整的左子树-根节点-右子树的遍历,而我们要完成对整个树的遍历,就要对这个过程进行推广,如下所示:
这是更上一层的树。看到这个图,我想大家应该很快可以理解,接下来我们只需要回到原来的小子树的根节点,遍历它,然后再去遍历它的右子树即可,而每一层之间这样逐层传递遍历,最后直至到整棵树的根节点,然后再遍历整棵树的右子树即完成整棵树的遍历。
在这个逐层传递的过程中,我们需要依次回溯找到所有右子树的根节点,因为我们无法直接知道一棵树有几层,而这里根节点的特点是先遇到但是不遍历,一步步回溯的时候再遍历,所以我们可以借助栈来保存各个根节点,在每棵子树遍历完后依次取出即可。最后栈中没有元素的时候,也即表示遍历结束。
我们使用黄颜色来表示放入栈中的元素,那么整个过程指针的变化如下所示(右子树遍历过程与左子树雷同,不再展示)(重新播放需要刷新网页):
代码实现如下:
public static List<Integer> inorderTraversal(TreeNode root) {
if (root == null) {
// 根节点为null,返回空列表
return Collections.emptyList();
}
List<Integer> elems = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
// 控制权所在节点
TreeNode currentNode = root;
while (true) {
while (currentNode != null) {
// 当前节点入栈
stack.push(currentNode);
// 控制权交给左孩子
currentNode = currentNode.left;
}
if (stack.isEmpty()) {
// 栈为空,表示所有元素已经遍历结束,退出
break;
}
// 弹出栈里面保存的节点,待进一步处理
currentNode = stack.pop();
// 弹出节点后,需要立即处理节点
elems.add(currentNode.val);
// 控制权交给右孩子,遍历节点的右子树
currentNode = currentNode.right;
}
return elems;
}
我们可以沿用学点算法(九)——二叉树中序遍历算法(递归实现)的测试代码,改下方法名:
public static void main(String[] args) {
System.out.println(inorderTraversal(TreeNode.convertArray2Tree(new Integer[]{31, 32, null, 34, 41, 42, 43})));
}
输出结果如下:
[42, 34, 43, 32, 41, 31]
符合我们的预期。