Morris遍历
morris遍历可以将额外空间复杂度降到O(1),而常规的递归或者非递归遍历额外空间复杂度都是O(h)。常规的递归或者非递归遍历其实也用到了栈结构,在处理完子节点之后可以回到父节点,然而对于二叉树这种结构,都是从父节点指向子节点,所以子节点指向父节点通常使用栈结构来实现。
morris遍历避免使用栈结构来实现子节点到父节点,而是利用二叉树最下面一层节点指向null节点的指针指向上面某一个节点。比如二叉树最下面一层某个节点没有右孩子,那么该节点的right指针就指向空,这些指针被叫做空闲指针,morris正是利用这些空闲指针。
具体遍历过程
假设当前节点为 cur,一开始cur来到树的根节点,开始移动cur:
- 如果cur为null,则停止移动;
- 如果cur没有左子树,则让cur向右移动,cur = cur.right;
- 如果cur有左子树,则找左子树的最右边节点,记为mostRight;
- 如果 mostRight 的 right 指针指向 null,让 right 指针指向当前节点(mostRight.right = cur),然后让 cur 向左移动(cur = cur.left);
- 如果 mostRight 的 right 指针指向 cur,让right 指针指向 null(mostRight.right = null),然后让 cur 向右移动( cur = cur.right)。
举个例子
1、cur一开始来到4,4有左子树,找左子树的最右边节点,找到3,因为3.right = null,所以让3的right指向cur,也就是3,然后cur向左移动,来到2;
2、cur来到2,2有左子树,找左子树的最右边节点,找到1,因为1.right = null,所以让1的right指向cur,也就是2,然后cur向左移动,来到1;
3、cur来到1,1没有左子树,所以向右移动,来到2;
4、cur来到2,2有左子树,找左子树的最右边节点,找到1,因为1.right = cur,所以让1.right = null,cur向右移动,来到3;
5、cur来到3,3没有左子树,所以向右移动,来到4;
6、cur来到4,4有左子树,找左子树的最右边节点,找到3,因为3.right = cur,所以让3.right = null,cur向右移动,来到6;
7、cur来到6,6有左子树,找左子树的最右边节点,找到5,因为5.right = null,所以让5.right = cur,也就是6,cur向左移动,来到5;
8、cur来到5,5没有左子树,所以向右移动,来到6;
9、cur来到6,6有左子树,找左子树的最右边节点,找到5,因为5.right = cur,所以让5.right = null,cur向右移动,来到7;
10、cur来到7,7没有左子树,所以向右移动,来到null;
11、因为cur为null,所以停止移动。
从以上遍历过程可以看出,cur依次到达的节点分别是:4、2、1、2、3、4、6、5、6、7,我们将这个序列叫 Morris 序。从到达的这些节点中可以看出,有子节点的节点4,2,6分别来到了两次,第一次来到是在左子树的最右节点指向空的时候,第二次来到是在左子树最右边节点指向自己的时候,这也是 Morris 遍历和 Morris 序的实质。
具体代码
public void morris(TreeNode node){
if(node == null){
return ;
}
TreeNode cur = null;
TreeNode mostRight = null;
while (cur != null){
mostRight = cur.left;
//有左子树的情况
if(mostRight!=null){
//一直往下找,找到左子树最右边的节点
while(mostRight.right != null &&mostRight.right != cur){
mostRight = mostRight.right;
}
//出了上面的while循环,此时mostRight已经是左子树最右边的节点了
//判断mostRight是指向自己的还是指向空的,若指向空,则让它指向自己,并左移
// 否则就是指向自己,则让它指向空
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else {
mostRight.right = null;
}
}
//没有左子树的情况
cur = cur.right;
}
}
根据Morris遍历序列,可以改造先序、中序和后序。
先序遍历代码如下
public static void morrisPre(TreeNode node){
if(node == null){
return ;
}
TreeNode cur = node;
TreeNode mostRight = null;
while (cur != null){
mostRight = cur.left;
//有左子树的情况
if(mostRight!=null){
//一直往下找,找到左子树最右边的节点
while(mostRight.right != null &&mostRight.right != cur){
mostRight = mostRight.right;
}
//出了上面的while循环,此时mostRight已经是左子树最右边的节点了
//判断mostRight是指向自己的还是指向空的,若指向空,则让它指向自己,并左移
// 否则就是指向自己,则让它指向空
if(mostRight.right == null){
mostRight.right = cur;
System.out.print(cur.value + " ");
cur = cur.left;
//回到最外层的 while,继续判断 cur 的情况
continue;
}else {
mostRight.right = null;
}
}else {
System.out.print(cur.value + " ");
}
//没有左子树的情况
cur = cur.right;
}
}
public static void main(String[] args) {
TreeNode node = new TreeNode(4);
node.left = new TreeNode(2);
node.right = new TreeNode(6);
node.left.left = new TreeNode(1);
node.left.right = new TreeNode(3);
node.right.right = new TreeNode(7);
node.right.left = new TreeNode(5);
morrisPre(node);
}
测试结果