【数据结构与算法-Java】二叉树的Morris遍历

  • 二叉树的遍历可以选择 递归 或者 自己创建栈空间
    时间复杂度O(N),空间复杂度O(H)

  • Morris遍历可以实现空间复杂度O(1)

01 Morris遍历的过程

首先,令current = head

  • (1)当cuurrent没有左子树,current向右(current = current.right
  • (2)当cuurrent有左子树,找左子树最右mostRight
    • (2.1)
      MostRight的右指针为空(MostRight.right == null),
      该右指针指向current(MostRight.right = current),current向左移动(current = current.left
    • (2.2)
      MostRight的右指针指向current(MostRight.right == current),
      右指针指向空(MostRight.right == null),current向右移动(current = current.right
    • (2.3)
      current指向空(current == null),
      停止

具体移动过程可以参考:

02 Morris序的特点

Morris序的特点:

  • (1)节点无左树,节点来一次
  • (2)节点有左树,节点来两次。且第1次到来和第2次到来之间,完成其的左子树遍历。

Morris序列是利用底层节点右指针的指向,方便完成回到上级。(可以不利用栈结构)


怎么知道是第一次来到这个节点,还是第二次来到?

如果左子树的最右节点为空(MostRight.right == null),那一定是第一次来到;
如果左子树的最右节点指向自己,那一定是第二次来到(MostRight.right == current

如果是第一次来到自己,则会去左子树完成同样的操作;
如果是第二次来到自己,那就会开始遍历右子树。


证明时间复杂度:

每一个左子树是访问了2遍的,是常数次,因此依然是O(N)

03 Morris遍历的过程(Java实现)

public static void morris(Node head) {
	if (head== null) {
		return;
	}
	Node Cur = head;
	Node mostRight = null;
	while (cur != null) {
		mostRight = cur.left; // mostRight是左树的节点
		if (mostRight != null) {
		
			// 找到左树的最右节点
			while (mostRight.right != null && mostRight.right != cur) {
				mostRight = mostRight.right;
			}
			
			if (mostRight.right == null) {// 第一次来到current
				mostRight.right = cur; 
				cur = cur.left;
				continue;
			} else { // mostRight.right == cur 第二次来到current
				mostRight.right = null;
			}
		}
		cur = cur.right;
	}
}

04 利用Morris完成先序遍历和中序遍历

类比与递归方法完成二叉树的先序遍历、中序遍历和后续遍历,递归方法是一个节点访问了3次,分别在第一次、第二次和第三次到达时打印,从而可以实现先序遍历、中序遍历和后续遍历。


对于Morris遍历:

  • 先序遍历: 第一次到达的时候就打印
  • 中序遍历: 只能到达一次的节点,第一次打印;能到达两次的节点,第二次打印(如何知道自己能到达两次?有左子树的节点,必然会到达2次)

4.1 先序遍历

public static void morrisPre(Node head) {
	if (head== null) {
		return;
	}
	Node Cur = head;
	Node mostRight = null;
	while (cur != null) {
		mostRight = cur.left; // mostRight是左树的节点
		if (mostRight != null) {// 可以到达自己两次的节点
		
			// 找到左树的最右节点
			while (mostRight.right != null && mostRight.right != cur) {
				mostRight = mostRight.right;
			}
			
			if (mostRight.right == null) {// 第一次来到current
				System.out.println(cur.value); // 中序遍历新加行
				mostRight.right = cur; 
				cur = cur.left;
				continue;
			} else { // mostRight.right == cur 第二次来到current
				mostRight.right = null;
			}
		}else{// 只能到达自己一次的节点 // 中序遍历新加行
			System.out.println(cur.value);// 中序遍历新加行
		}// 中序遍历新加行
		cur = cur.right;
	}
}

4.2 中序遍历

public static void morrisIn(Node head) {
	if (head== null) {
		return;
	}
	Node Cur = head;
	Node mostRight = null;
	while (cur != null) {
		mostRight = cur.left; // mostRight是左树的节点
		if (mostRight != null) {// 可以到达自己两次的节点
		
			// 找到左树的最右节点
			while (mostRight.right != null && mostRight.right != cur) {
				mostRight = mostRight.right;
			}
			
			if (mostRight.right == null) {// 第一次来到current
				mostRight.right = cur; 
				cur = cur.left;
				continue;
			} else { // mostRight.right == cur 第二次来到current
				mostRight.right = null;
			}
		}
		// 对于能到达两次的节点,第一次到达的时候,会有个continue,所以无法运行到这来
		System.out.println(cur.value); // 中序遍历新加行
		cur = cur.right;
	}
}

05 后续遍历

  • 第二次到达时,逆序打印左树的右边界(什么是处理时机?第二次到达某节点时)

  • 逆序打印,如果还想保证空间复杂度O(1),就需要不额外申请数据结构

  • 怎么做?单链表结构,让每个节点的右指针指向上一个节点

public static void morrisPos(Node head) {
	if (head== null) {
		return;
	}
	Node Cur = head;
	Node mostRight = null;
	while (cur != null) {
		mostRight = cur.left; // mostRight是左树的节点
		if (mostRight != null) {// 可以到达自己两次的节点
		
			// 找到左树的最右节点
			while (mostRight.right != null && mostRight.right != cur) {
				mostRight = mostRight.right;
			}
			
			if (mostRight.right == null) {// 第一次来到current
				mostRight.right = cur; 
				cur = cur.left;
				continue;
			} else { // mostRight.right == cur 第二次来到current
				mostRight.right = null;
				printEdge(cur.left) // 后序遍历新加行
			}
		}
		cur = cur.right;
	}
	printEdge(head) // 后序遍历新加行
}

//以head为头的树,请逆序打印该树的右边界
public static void printEdge(Node head) {
	Node tail = reverseEdge(head);
	Node cur = tail;
	while (cur != null) {
		System.out.print(cur.value +”");
		cur = cur.right;
	}
	reverseEdge(tail);
}

// 从from开始,只关心right指针,并且认为right指针就是单链表指针,请把这个单链表逆序
public static Node reverseEdge(Node from) {
	Node pre = null;
	Node next = null;
	while (from != null) {
		next = from.right;
		from.right = pre;
		pre = from;
		from = next;
	}
	return pre;
}


06 参考

是左程云的讲解,感觉讲的非常好

这个up的虽然没有做成合集不方便找,但是只截出了讲课的部分,没有聊天的部分,看起来还是很顺畅的

06 leetcode相关题目

  • 99题
    import java.util.Scanner;
    import java.util.*;
    public class Main {
        public static class TreeNode {
            int val;
            TreeNode left;
            TreeNode right;
            TreeNode() {}
            TreeNode(int val) { this.val = val; }
            TreeNode(int val, TreeNode left, TreeNode right) {
                this.val = val;
                this.left = left;
                this.right = right;
            }
        }
    
        public static void main(String[] args) {
            // [10,5,15,0,8,13,20,2,-5,6,9,12,14,18,25]
            TreeNode head = new TreeNode(10);
    
            head.left = new TreeNode(5);
            head.right = new TreeNode(15);
    
            head.left.left = new TreeNode(0);
            head.left.right = new TreeNode(8);
            head.right.left = new TreeNode(13);
            head.right.right = new TreeNode(20);
    
            head.left.left.left = new TreeNode(2);
            head.left.left.right = new TreeNode(-5);
            head.left.right.left = new TreeNode(6);
            head.left.right.right = new TreeNode(9);
            head.right.left.left = new TreeNode(12);
            head.right.left.right = new TreeNode(14);
            head.right.right.left = new TreeNode(18);
            head.right.right.right = new TreeNode(25);
    
            process(head);// 按层输出
            System.out.println("");
    
            recoverTree(head);
    
            process(head);// 按层输出
        }
    
        public static void recoverTree(TreeNode root) {
            TreeNode head = root;
            TreeNode x = null, y = null, pred = null, mR = null;
            while(root != null){
                mR = root.left;
                if(mR != null){
                    while(mR.right != null && mR.right != root){
                        mR = mR.right;
                    }
                    if(mR.right == null){
                        // 搭桥
                        mR.right = root;
                        root = root.left;
                        continue;
                    }else{
                        // 拆桥
                        mR.right = null;
                    }
                }
                if(pred != null){
                    if(pred.val > root.val){
                        y = root;
                        if(x == null){
                            x = pred;
                        }else{
    //                        break;
                        }
                    }
                }
                pred = root;
                // 中序遍历
    //            System.out.print(root.val+" ");
                root = root.right;
            }
            swap(x,y);
        }
    
        public static void swap(TreeNode x, TreeNode y){
            int temp = x.val;
            x.val = y.val;
            y.val = temp;
        }
    
        // 按层输出
        public static void process(TreeNode head){
            if(head == null){
                return;
            }
            Queue<TreeNode> queue = new LinkedList<>();
            queue.add(head);
            while(!queue.isEmpty()){
                TreeNode cur = queue.poll();
                System.out.print(cur.val + " ");
                if(cur.left!=null){
                    queue.add(cur.left);
                }
                if(cur.right!=null){
                    queue.add(cur.right);
                }
            }
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值