Morris遍历
Morris遍历是二叉树遍历的另一种方式,做到了空间复杂度为O(1),时间复杂度为O(N)。
1. Morris遍历的流程
当遍历指针cur遍历到某个节点:
- 该节点有左孩子,则判断该节点左子树的最右孩子mostRight的right指针
- 如果最右孩子的right指针为空,说明第一次遍历到该节点,即
mostRight.right=null
,则令最右孩子的right指针指向自己,即mostRight.right = cur;
,然后cur = cur.left;
- 如果最右孩子的right指针指向自己,说明第二次遍历到该节点,即
mostRight.right=cur
,则令最右孩子的right指针指向null
,即mostRigth.right = null;
,然后cur = cur.right;
- 如果最右孩子的right指针为空,说明第一次遍历到该节点,即
- 该节点无左孩子,
cur=cur.right;
通过上述流程,可以看出:非叶子节点,能遍历到两次。而叶子节点,只能遍历到一次。
则最终遍历到的次序为1242513637
代码实现
public static void morrisSearch(TreeNode head){
if(head == null){
return;
}
TreeNode cur = head;
TreeNode mostRight = null;
while(cur != null){
if(cur.left == null){
cur = cur.right;
}else{
mostRight = cur.left;
while(mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
//cur指针第一次来到该节点
cur = cur.left;
}else{//mostRight.right == cur,说明cur指针第二次来到该节点
mostRight.right = null;
cur = cur.right;
}
}
}
}
2. 先序遍历
代码实现
public static void morrisPre(TreeNode head){
if(head == null){
return;
}
TreeNode cur = head;
TreeNode mostRight = null;
while(cur != null){
if(cur.left == null){
System.out.print(cur.val + " ");
cur = cur.right;
}else{
mostRight = cur.left;
while(mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
//cur指针第一次来到该节点
System.out.print(cur.val + " ");
cur = cur.left;
}else{//mostRight.right == cur,说明cur指针第二次来到该节点
mostRight.right = null;
cur = cur.right;
}
}
}
}
3. 中序遍历
代码实现
public static void morrisIn(TreeNode head){
if(head == null){
return;
}
TreeNode cur = head;
TreeNode mostRight = null;
while(cur != null){
if(cur.left == null){
System.out.print(cur.val + " ");
cur = cur.right;
}else{
mostRight = cur.left;
while(mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
//cur指针第一次来到该节点
cur = cur.left;
}else{//mostRight.right == cur,说明cur指针第二次来到该节点
mostRight.right = null;
System.out.print(cur.val + " ");
cur = cur.right;
}
}
}
}
4. 后序遍历
后续遍历稍微不同
步骤:
- 当第二次遍历到非叶节点的时候,逆序打印该节点的左孩子的右边界。(不知道这样表达对不对)。
- 最后打印整棵二叉树的右边界。
为了保证空间复杂度为O(1),逆序打印的时候不能用栈,先将链表反转,打印后再反转回来。
代码实现:
public static void morrisPost(TreeNode head){
if(head == null){
return;
}
TreeNode cur = head;
TreeNode mostRight = null;
while(cur != null){
if(cur.left == null){
cur = cur.right;
}else{
mostRight = cur.left;
while(mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
//cur指针第一次来到该节点
cur = cur.left;
}else{//mostRight.right == cur,说明cur指针第二次来到该节点
mostRight.right = null;
printEdge(cur.left);
cur = cur.right;
}
}
}
printEdge(head);
}
//为了使空间复杂度不上升,逆序打印右边界,不能使用栈,而使用反转链表的方式
public static void printEdge(TreeNode head){
TreeNode tail = reverseList(head);
TreeNode cur = tail;
while(cur != null){
System.out.print(cur.val + " ");
cur = cur.right;
}
//再把链表反转回来
reverseList(tail);
}
//从单链表的头开始,逆转链表,并返回逆转后单链表的头部。
public static TreeNode reverseList(TreeNode head){
TreeNode pre = null;
TreeNode next = null;
TreeNode cur = head;
while(cur != null){
next = cur.right;
cur.right = pre;
pre = cur;
cur = next;
}
return pre;
}