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

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

文章目录

  • 1 遍历过程
  • 2 遍历实质
  • 3 Morris遍历实现
  • 4 应用
    • 4.1 先序遍历
    • 4.2 中序遍历
    • 4.3 后序遍历

1 遍历过程

遍历步骤

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

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

图解Morris

[算法入门笔记] 15. Morris遍历_第1张图片
1.初始cur来到4节点,cur有左子树,找到左子树最右节点,节点3右指针为空,指向curcur向左移动来到节点2
[算法入门笔记] 15. Morris遍历_第2张图片
2.cur来到节点2,cur有左子树,找到左子树最右节点,节点1右孩子空,指向curcur向左移动来到1节点
[算法入门笔记] 15. Morris遍历_第3张图片3.cur来到1节点,cur此时没有左子树,cur向右指针方向移动,回到了节点2
[算法入门笔记] 15. Morris遍历_第4张图片
4.cur来到节点2,cur此时有左子树,找到左子树的最右节点,发现节点1的右指针指向cur,将右指针调回null,然后cur向右指针移动,cur来到3
[算法入门笔记] 15. Morris遍历_第5张图片
5.cur来到3节点,cur此时没有左子树,cur向右方向移动,cur回到4节点
[算法入门笔记] 15. Morris遍历_第6张图片
6.cur来到4节点,cur此时有左子树,找到cur左子树最右侧节点,发现节点3的右指针指向cur,将其调回null,cur向右来到6节点
[算法入门笔记] 15. Morris遍历_第7张图片
7.cur来到节点6,cur此时有左子树,找到cur左子树最右节点,发现节点5指针指向null,让其指向curcur向左移动到节点5
[算法入门笔记] 15. Morris遍历_第8张图片
8.cur来到5节点,cur此时没有左子树,cur向右指针方向移动,cur回到了6节点
[算法入门笔记] 15. Morris遍历_第9张图片
9.cur来到6节点,此时cur有左子树,找到左子树最右节点,发现节点5右指针指向cur,将节点5右指针调回null,cur向右来到节点7
[算法入门笔记] 15. Morris遍历_第10张图片
10.cur来到节点7,cur此时没有左子树,cur向右指针方向移动,cur来到null
[算法入门笔记] 15. Morris遍历_第11张图片
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遍历完成后,逆序打印整个树的右边界

打印一棵树的右边界相当于在一条单链表进行操作
[算法入门笔记] 15. Morris遍历_第12张图片
假设cur第二次到达A,并且要逆序打印cur左子树的右边界

  • 首先将E.R指向null,然后将右边界逆序调整,类似于单链表逆序操作
    [算法入门笔记] 15. Morris遍历_第13张图片
    从节点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);
}

你可能感兴趣的:(数据结构与算法,算法,数据结构)