二叉树的遍历(无论是递归还是非递归),都会用到栈结构,其中递归版本用的是系统栈,非递归版本用的是Java自带的Stack结构,空间复杂度均为递归栈的深度,即O(h),h为二叉树的高度。此外,层次遍历的空间复杂度是O(w),w为二叉树的宽度。
Moris遍历的神奇之处在于,他的空间复杂度只有O(1),核心思想是利用叶子结点的空指针,下面介绍主要流程和代码实现。
Moris遍历的流程
Moris遍历本身是没有前序、中序、后序的概念的,但是利用Moris遍历可以实现这三种形式的遍历。先来介绍Moris遍历本身的流程(给定根节点root):
首先,如果root为null,直接返回。否则令node=root,进行循环:
WHILE (node != null)
1.如果node没有左子树,node=node.right;
2.如果node有左子树,获取左子树的最右结点right_most(right!= null && right!=node),如果最右结点的right为null见3, 否则(right为node)见4 。
3. right_most.right == null : 令right_most的right指向node,node = node.left
4. right_most.right == node : 令right_most的right指向null, node= node.right
END WHILE
Moris遍历流程的含义
第一次看到这个流程可能会有点懵,实际上Moris遍历就是利用叶结点的指针进行标记,如果左子树还没遍历过,此时左子树的最右结点的right指针便指向null,这个时候便应该去遍历左子树;如果某结点的左子树遍历过了,再次来到了该节点,就会发现此时左子树的最右结点的right指向该节点,这个时候就可以去遍历右子树了,因为左子树已经完成遍历了。
就Moris遍历而言,所有的子树的遍历顺序(也就是node引用的顺序)都是:根节点 --> 左子树 --> 根节点 --> 右子树。 也就是说所有左孩子不为空的结点都会被遍历两次,若左孩子为空就只遍历一次。
那么如何利用Moris遍历进行前序、中序、后序遍历呢?从刚才的顺序 ”根节点1 --> 左子树 --> 根节点2 --> 右子树“ (1和2用来区分第一次和第二次遍历根节点)可以看出:
- 如果忽略两次重复遍历中的第一次,顺序就变成了 左子树 --> 根节点2 --> 右子树 , 也就是中序遍历。
- 如果忽略第二次,就变成了根节点1 --> 左子树 --> 右子树,也就是先序遍历。
- 至于后序遍历,不能以相似的方法直接得到,待会会介绍。
代码实现
Moris遍历
public static void moris(TreeNode head){
if (head == null)
return;
TreeNode curr = head;
while (curr != null){
if (curr.left != null){
TreeNode right_most = curr.left;
while (right_most.right != null && right_most.right!=curr)
right_most = right_most.right;
if(right_most.right == null){
// 第一次来到这个结点
right_most.right = curr;
curr = curr.left;
} else{
// 左边已经遍历完了,第二次来到这个结点