1. 题目描述
2. 解题思路
二叉树无论是前序、中序还是后序遍历,利用递归都是可以很简单地实现并且通俗易懂的。在本道题目中使用迭代算法实现才是有挑战性的任务。递归是使用了栈,那么毫无疑问使用迭代算法时需要人为地构造栈结构,那么难点在于什么部分呢?其难点正是在于出栈入栈的规则设定,实不相瞒,昨晚想了一个多小时愣是卡住了没想出来,今早吃完个早餐突然就有了想法了,这可能就是心理学当中的酝酿效应吧。
言归正传,对于中序遍历而言,“先解决左子树,再解决自己,后解决右子树” 是它的基本规则,而根据栈先进后出的特点,压栈顺序就应该是逆着来,先压右子树,再压自己,然后处理左子树(开启新的一次迭代)。
那么我究竟是卡在了哪一个点上呢?
按照上面的逻辑,在每一轮迭代中,都应该判断当前节点是否有右子树,有则压栈;然后判断是否有左子树,有则把自己压栈,开始新一轮迭代处理左子树,若没有左子树,则把自己添加到结果中,从栈中弹一个节点在新一轮迭代中处理。
逻辑上好像没问题,但是,我怎么知道现在这一轮迭代中的节点有没有已经访问过呢?
因为倘若该节点是有左子树的话,该节点是先被访问,然后判断有左子树,然后被压栈,然后处理左子树完毕后,再弹栈把该节点取出。问题在于弹栈取出后,便会开启新的迭代,又会重新判断是否有左孩子,这不就死循环了?
因此应该在发现有左孩子后,取出左孩子,然后把该节点的左孩子赋空再进行压栈 (相当于做了一个标记表示该节点的左子树部分已经处理完毕了)。
说起来有点乱,可以了看看整体逻辑的总结版,设当前遍历的节点为root:
提交完答案瞅了一眼官解,好家伙,还有个Morris算法,是我见识太少,它的思路,是这样的:
简洁版表示,是这样的:
有点绕,不过看懂了之后发现总体思路确实很妙!
一开始不太理解predecessor
右孩子不为空的什么情况,定义不是表示左子树的最右节点吗,怎么可能不为空?
认真想了一下发现,噢!不为空的情况就是已经与x
建立了链接的情况,即下图所示:
既然不太好理解,那我改一改!本质还是在于判断该节点(x
)的左子树有没有处理完毕嘛,那么我可以在predecessor
的右孩子指向x
之后,把x
的左孩子赋空值,那就可以达到同样的效果了!
算法就改成了这个逻辑:
树结构则大致变成了这样:
3. 代码实现
3.1 递归
public List<Integer> inorderTraversal(TreeNode root) {
if(root!=null){
ArrayList<Integer> integers = new ArrayList<>();
integers.addAll(inorderTraversal(root.left));
integers.add(root.val);
integers.addAll(inorderTraversal(root.right));
return integers;
}
return new ArrayList<>();
}
3.2 迭代
public List<Integer> inorderTraversal(TreeNode root) {
if(root==null)
return new ArrayList<Integer>();
List<Integer> integers = new ArrayList<Integer>();
LinkedList<TreeNode> treeNodes = new LinkedList<TreeNode>();
treeNodes.add(new TreeNode(-101));
while (root.val != -101) {
if (root.right != null) {
treeNodes.add(root.right);
root.right = null;
}
if (root.left != null) {
TreeNode left = root.left;
root.left = null;
treeNodes.add(root);
root=left;
} else {
integers.add(root.val);
root = treeNodes.removeLast();
}
}
return integers;
}
3.3 改进Morris算法
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> integers = new ArrayList<>();
TreeNode treeNode = null;
while (root != null) {
if (root.left == null) {
integers.add(root.val);
root = root.right;
} else {
treeNode = root.left;
while (treeNode.right != null) {
treeNode = treeNode.right;
}
treeNode.right = root;
treeNode = root.left;
root.left = null;
root = treeNode;
}
}
return integers;
}
3.4 对比
三种算法的时间复杂度都是O(n),最大的区别的空间复杂度部分,递归算法和普通迭代算法的空间复杂度最坏的情况下都是O(n),在递归中是自动实现的栈结构,而在迭代中需要自行构造栈结构,但总体而言在遍历逻辑上是相同的。而著名的Morris算法利用巧妙的思想,改变了遍历所需要的空间复杂度为O(1),使其在遍历过程中不再需要依靠栈实现,本质上其实是改变了二叉树的空间结构。