[算法入门笔记] 15. Morris遍历

Morris遍历用于遍历二叉树,时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( 1 ) O(1) O(1),主要利用树中空闲指针来节省空间的

1 遍历过程

遍历步骤

假设来到当前节点cur,开始时cur来到头节点位置

  1. 如果cur没有左孩子,cur向右移动
  2. 如果cur有左孩子,找到左子树上最右的节点mostRight
    • 如果mostRight的右指针指向空,让其指向cur,然后cur向左移动
    • 如果mostRight的右指针指向cur,让其指向null,然后cur向右移动
  3. cur为空时遍历停止

图解Morris

在这里插入图片描述
1.初始cur来到4节点,cur有左子树,找到左子树最右节点,节点3右指针为空,指向curcur向左移动来到节点2
在这里插入图片描述
2.cur来到节点2,cur有左子树,找到左子树最右节点,节点1右孩子空,指向curcur向左移动来到1节点
在这里插入图片描述3.cur来到1节点,cur此时没有左子树,cur向右指针方向移动,回到了节点2
在这里插入图片描述
4.cur来到节点2,cur此时有左子树,找到左子树的最右节点,发现节点1的右指针指向cur,将右指针调回null,然后cur向右指针移动,cur来到3
在这里插入图片描述
5.cur来到3节点,cur此时没有左子树,cur向右方向移动,cur回到4节点
在这里插入图片描述
6.cur来到4节点,cur此时有左子树,找到cur左子树最右侧节点,发现节点3的右指针指向cur,将其调回null,cur向右来到6节点
在这里插入图片描述
7.cur来到节点6,cur此时有左子树,找到cur左子树最右节点,发现节点5指针指向null,让其指向curcur向左移动到节点5
在这里插入图片描述
8.cur来到5节点,cur此时没有左子树,cur向右指针方向移动,cur回到了6节点
在这里插入图片描述
9.cur来到6节点,此时cur有左子树,找到左子树最右节点,发现节点5右指针指向cur,将节点5右指针调回null,cur向右来到节点7
在这里插入图片描述
10.cur来到节点7,cur此时没有左子树,cur向右指针方向移动,cur来到null
在这里插入图片描述
cur为空,过程停止
cur依次到达的节点为4、2、1、2、3、4、6、5、6、7

2 遍历实质

建立一种机制,对于没有左子树的节点只到达一次,对于有左子树的节点到达两次

避免使用栈结构,通过让底层节点指向null的空闲指针指回上层的某个节点,从而完成下层到上层的移动

在一棵二叉树中,对于有左子树的节点可以到达两次(4、2、6),对于没有左子树的节点都只会到达一次

对于任意一个只能到达一次的节点x,接下来cur跑到x的右子树上,要么返回上级

对于任意一个能到达两次的节点Y,在第一次到达Y之后的,cur都会先去Y的左子树上转一圈,第二次来到Y,接下来cur要么跑到Y的右子树上,要么就返回上级

如果Y的左子树上的最右节点的指针指向null的,那么此时cur第一次到达Y,如果指向Ycur是第二次到达Y

3 Morris遍历实现

public void morris(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight = null;
    while (cur != null) {
        mostRight = cur.left;
        if (mostRight != null) { // 如果当前cur有左子树
            // 找到cur左子树最右的节点
            while (mostRight.right != null && mostRight.right != cur) {
                mostRight = mostRight.right;
            }//while结束后mostRight就是左子树最右节点
            if (mostRight.right == null) {
                mostRight.right = cur;
                cur = cur.left;
                continue; // 回到最外层的while继续判断cur情况
            } else { // mostRight指向cur
                mostRight.right = null; // 调回null
            }
        }
        // cur如果没有左子树,cur向右移动
        // 或者cur左子树上最右节点的右指针是指向cur的,cur向右移动 第二次来,先回调null,然后右移
        cur = cur.right;
    }
}

4 应用

4.1 先序遍历

  • 对于cur只能到达一次的节点(无左子树的节点),cur到达直接打印
  • 对于cur到达两次的节点(有左子树的节点),cur第一次到达时打印,第二次到达时不打印
public void morrisPre(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight = null;
    while (cur != null) {
        mostRight = cur.left;
        if (mostRight != null) { // 如果当前cur有左子树
            // 找到cur左子树最右的节点
            while (mostRight.right != null && mostRight.right != cur) {
                mostRight = mostRight.right;
            }// while结束后mostRight就是左子树最右节点
            if (mostRight.right == null) {  // 第一次到达
                mostRight.right = cur;
                // visit
                System.out.print(cur.value + " ");
                cur = cur.left;
                continue; // 回到最外层的while继续判断cur情况
            } else { // mostRight指向cur     //第二次到达
                mostRight.right = null; // 调回null
            }
        } else { // cur没有左子树 
            System.out.println(cur.value + " ");
        }
        // cur如果没有左子树,cur向右移动
        // 或者cur左子树上最右节点的右指针是指向cur的,cur向右移动 第二次来,先回调null,然后右移
        cur = cur.right;
    }
}

4.2 中序遍历

  • 对于cur只能到达一次的节点(无左子树的节点),cur到达直接打印
  • 对于cur到达两次的节点(有左子树的节点),cur第一次到达时不打印,第二次到达时打印
public void morrisIn(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight = null;
    while (cur != null) {
        mostRight = cur.left;
        if (mostRight != null) { // 如果当前cur有左子树
            // 找到cur左子树最右的节点
            while (mostRight.right != null && mostRight.right != cur) {
                mostRight = mostRight.right;
            }// while结束后mostRight就是左子树最右节点
            if (mostRight.right == null) {  // 第一次到达
                mostRight.right = cur;
                cur = cur.left;
                continue; // 回到最外层的while继续判断cur情况
            } else { //mostRight指向cur     // 第二次到达
                mostRight.right = null; // 调回null
            }
        }
        // cur如果没有左子树,cur向右移动
        // 或者cur左子树上最右节点的右指针是指向cur的,cur向右移动 第二次来,先回调null,然后右移
        System.out.print(cur.value + " ");
        cur = cur.right;
    }
}

4.3 后序遍历

  • 对于cur只能到达一次的节点(无左子树的节点),直接跳过,没有打印行为
  • 对于cur到达两次的节点(有左子树的节点),cur第一次到达时没有打印行为,第二次到达X时,逆序打印X左子树的右边界
  • cur遍历完成后,逆序打印整个树的右边界

打印一棵树的右边界相当于在一条单链表进行操作
在这里插入图片描述
假设cur第二次到达A,并且要逆序打印cur左子树的右边界

  • 首先将E.R指向null,然后将右边界逆序调整,类似于单链表逆序操作
    在这里插入图片描述
    从节点E开始,依次通过每个节点的right指针逆序打印整个左边界,打印完B后把右边界逆序一次

逆序树的边界

public Node reverseEdge(Node from) {
    Node pre = null;
    Node next = null;
    while (from != null) {
        next = from.right;
        // 右指针指向上面的边
        from.right = pre;
        from = next;
    }
    return pre;
}

打印逆转后的边

public void printEdge(Node head) {
    // 逆转前边界的最右节点
    Node tail = reverseEdge(head);
    Node cur = tail;
    while (cur != null) {
        System.out.print(cur.value + " ");
        cur = cur.right;
    }
    reverseEdge(tail);
}

后序遍历

public void morrisPos(Node head) {
    if (head == null) {
        return;
    }
    Node cur = head;
    Node mostRight = null;
    while (cur != null) {
        mostRight = cur.left;
        if (mostRight != null) { // 如果当前cur有左子树
            // 找到cur左子树最右的节点
            while (mostRight.right != null && mostRight != cur) {
                mostRight = mostRight.right;
            }// while结束后mostRight就是左子树最右节点
            if (mostRight.right == null) {
                mostRight.right = cur;
                cur = cur.left;
                continue; // 回到最外层的while继续判断cur情况
            } else { // mostRight指向cur
                mostRight.right = null; //调回null
                printEdge(cur.left);
            }
        }
        // cur如果没有左子树,cur向右移动
        // 或者cur左子树上最右节点的右指针是指向cur的,cur向右移动 第二次来,先回调null,然后右移
        cur = cur.right;
    }
    // cur遍历完成后,逆序打印整棵树的最右边界
    printEdge(head);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyan Chau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值