Morris
二叉树(Binary tree)是树形结构的一个重要类型。提到二叉树,我们首先想到的就是其3种遍历方式–前序、中序和后序,对于这三种遍历方式,我们很容易通过使用递归或者迭代的方式实现,时间复杂度为O(N)。但是这两种实现方式都需要使用堆栈进行节点信息的存储,即空间复杂度也是O(N)。
但是还有一种更为巧妙的遍历方法–Morris算法,该算法的时间复杂度也是O(N),但是空间复杂度却能达到最优的O(1)
Morris遍历的实现:
记当前节点 cur ,一开始 cur 指向整棵树的头节点
- 如果cur无左子树,cur向右移动(cur=cur.right)
- 如果cur有左子树,找到cur左子树上最右的节点,记为mostright
①如果mostright的right指针指向空,让其指向cur(mostRight.ringht = cur ),cur向左移动(cur=cur.left)
②如果mostright的right指针指向cur,让其指向空(mostRight.ringht = null),cur向右移动(cur=cur.right)
对于没有左子树的节点只到达一次,对于有左子树的节点会到达两次
实例:
一个树若按层遍历的结构为{1,2,3,4,5,6,7},即该树为满二叉树,头结点值为1,左右孩子为2,3,叶节点为4,5,6,7
morris遍历来遍历该树过程:
1)首先cur来到头结点1,按照morris原则的第二条第一点,它存在左孩子,cur左子树上最右的节点为5,它的right指针指向空,所以让其指向1,cur向左移动到2。
2)2有左孩子,且它左子树最右的节点4指向空,按照morris原则的第二条第一点,让4的right指针指向2,cur向左移动到4
3)4不存在左孩子,按照morris原则的第一条,cur向右移动,在第二步中,4的right指针已经指向了2,所以cur会回到2
4)重新回到2,有左孩子,它左子树最右的节点为4,但是在第二步中,4的right指针已经指向了2,不为空。所以按照morris原则的第二条第二点,2向右移动到5,同时4的right指针重新指向空
5)5不存在左孩子,按照morris原则的第一条,cur向右移动,在第一步中,5的right指针已经指向了1,所以cur会回到1
6)cur回到1,回到头结点,左子树遍历完成,1有左孩子,左子树上最右的节点为5,它的right指针指向1,按照morris原则的第二条第二点,1向右移动到3,同时5的right指针重新指回空
……
当到达最后一个节点7时,按照流程下来,此时7无左右孩子,遍历结束。
public static void morris(Node head) {
if(head == null){
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null){
// cur 有没有左树
mostRight = cur.left;
if(mostRight != null){
// 有左树的情况下
// 找到cur左树上,真实的最右
while (mostRight.right !=null && mostRight.right != cur){
mostRight = mostRight.right;
}
//从while中出来,mostRight一定是cur左树上的最右节点
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else {
// mostRight.right != null -> mostRight.right == cur
mostRight.right = null;
}
}
cur = cur.right;
}
System.out.println();
}
前、中、后序遍历:
如上实例中,依次到达的节点为:1 2 4 2 5 1 3 6 3 7
先序遍历:第一次到达就打印
1 2 4 2 5 1 3 6 3 7
√ √ √ x √ x √ √ x √
即先序遍历结果为:1 2 4 5 3 6 7
中序遍历:对于只能到达一次的节点直接打印,对于能到达第二次的节点,第二次打印
1 2 4 2 5 1 3 6 3 7
x x √ √ √ √ x √ √ √
即先序遍历结果为:4 2 5 1 6 3 7
后序遍历:到达该节点第二次的时候打印该节点左子树的逆序右边界
打印时机:能回到自己两次且第二次回到自己的时候
但是不是打印自己