【算法笔记】二叉树的Morris遍历

Morris遍历: 二叉树之前的遍历方式有空间浪费的问题(递归实现也会占中栈空间)。Morris遍历时间复杂度O(N),额外空间复杂度O(1),通过利用原树中大量空闲指针的方式,达到节省空间的目的

1、Morris遍历概述

  • Morris遍历

    • 二叉树之前的遍历方式有空间浪费的问题(递归实现也会占中栈空间)
    • Morris遍历时间复杂度O(N),额外空间复杂度O(1),通过利用原树中大量空闲指针的方式,达到节省空间的目的
  • Morris遍历过程:

    • 假设来到当前节点cur,开始时cur来到头节点位置
    • 1)如果cur没有左孩子,cur向右移动(cur = cur.right)
    • 2)如果cur有左孩子,找到左子树上最右的节点mostRight:
    • 2.1)如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)
    • 2.2)如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)
    • 3)cur为空时遍历停止
  • Morris遍历的特点:

    • 1)Morris遍历的过程中,cur指针会移动到每一个节点,也就是每个节点都会被cur访问到,但是有个规律:
    • 如果一个节点有左孩子,这个节点会被cur访问2次,如何区分是第2次访问到:判断左树最右侧的节点是否指向自己,如果指向自己,说明是第2次访问到。
    • 如果一个节点没有左孩子,这个节点会被cur访问1次,
    • cur指针的指向顺序,就是Morris遍历的顺序。
    • 2)Morris遍历的过程中,存在寻找mostRight的过程,所以每一个叶子节点都会被mostRight访问到,
    • 作为父节点的左孩子的叶子节点,会在寻找父节点的左孩子的有边界的时候,被mostRight访问到,因为此时只有它一个节点,
    • 对于作为父节点的右孩子的叶子节点,会在父节点的父节点在寻找父节点的有边界的时候被mostRight访问到,
    • 要区分这个叶子节点是第一次被访问到还是第二次被访问到,同样是判断左树最右侧的节点是否指向自己,如果指向自己,说明是第2次访问到。
    • 通常所说的被访问到是指cur访问到,而判断是第一次访问到还是第二次访问到,是根据mostRight的指向来判断的。
    • 对于关注叶子节点的题目,我们要知道的是叶子节点实际上都是可以被mostRight访问到的
  • Morris遍历的应用:

    • 基于基本的Morris遍历流程,通过在不同时机执行"访问"操作(如打印节点值),可以分别实现前序、中序和后序遍历。
    • 1)前序遍历:cur第一次到达该节点时就访问,顺序为:根节点 -> 左子树 -> 右子树
    • 2)中序遍历:cur第二次到达该节点时(即从左子树返回时)才访问,顺序为:左子树 -> 根节点 -> 右子树
    • 3)后序遍历:cur第二次到达该节点时,逆序打印该节点左子树的右边界。最后,单独逆序打印整棵树的右边界

2、Morris遍历的实现

2.1、Morris遍历模板

  • Morris遍历代码模板
  • 思路:
    • 根据Morris遍历的过程,写出的代码模板

    • 1)如果cur没有左孩子,cur向右移动(cur = cur.right)

    • 2)如果cur有左孩子,找到左子树上最右的节点mostRight:

    • 2.1)如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)

    • 2.2)如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)

    • 3)cur为空时遍历停止

    • 示例:

    • 如下面这个二叉树:

    • Morris遍历的顺序为:a -> b -> d -> b -> e -> a -> c -> f -> c -> g

java 复制代码
    /**
     * Morris遍历代码模板
     * 思路:
     * 根据Morris遍历的过程,写出的代码模板
     * 1)如果cur没有左孩子,cur向右移动(cur = cur.right)
     * 2)如果cur有左孩子,找到左子树上最右的节点mostRight:
     * 2.1)如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)
     * 2.2)如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)
     * 3)cur为空时遍历停止
     * 示例:
     * 如下面这个二叉树:
     * ............a
     * ........../...\
     * .........b.....c
     * ......../.\.../.\
     * .......d..e..f...g
     * Morris遍历的顺序为:a -> b -> d -> b -> e -> a -> c -> f -> c -> g
     */
    public static void morris(Node head) {
        if (head == null) {
            return;
        }
        // cur 指针,指向当前访问的节点,初始为头节点
        Node cur = head;
        // mostRight 指针,指向左子树最右侧的节点,初始为null
        Node mostRight = null;
        // 开始遍历,对应过程的:3)cur为空时遍历停止
        while (cur != null) {
            // 先将mostRight指针指向当前节点的左孩子,这样就可以根据mostRight是否为空,判断是否有左孩子
            mostRight = cur.left;
            if (mostRight != null) {
                // 有左孩子,这个节点会被访问2次 ,
                // 依据过程 2)如果cur有左孩子,找到左子树上最右的节点mostRight
                // 找到左子树的最右侧节点,没有右子树或者上一次改成了指向自己,都要停止
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    // 第一次到达左子树的最右侧节点,对应过程2.1)
                    mostRight.right = cur;
                    cur = cur.left;
                    // 直接进行下一个 while循环
                    continue;
                } else {
                    // 第二次到达左子树的最右侧节点,对应过程2.2)
                    mostRight.right = null;
                }
            }
//            else{
//                // 没有左孩子,这个节点会被访问1次,如果要单独挑出被访问一次的节点,可以加上else在这里判断
//            }
            // 对于过程 1)和2.2),即没有左孩子或者第二次访问节点,都会执行到这里,接下来访问当前节点的右孩子
            cur = cur.right;
        }
    }

2.2、Morris遍历实现前序遍历

  • Morris遍历实现前序遍历
  • 思路:
    • cur第一次到达该节点时就访问,顺序为:根节点 -> 左子树 -> 右子树
    • 因为Morris遍历的过程中,有左子树的节点会被访问两次,没有的会被访问1次,
    • 所以要挑出第一次访问的时候,就有两个地方:
    • 1) 对于有左子树的节点,第一次访问是根据mostRight的right指针为空的时候,
    • 2) 对于没有左子树的节点,第一次访问是根据cur的左孩子为空的时候,
java 复制代码
    /**
     * Morris遍历实现前序遍历
     * 思路:
     * cur第一次到达该节点时就访问,顺序为:根节点 -> 左子树 -> 右子树
     * 因为Morris遍历的过程中,有左子树的节点会被访问两次,没有的会被访问1次,
     * 所以要挑出第一次访问的时候,就有两个地方:
     * 1) 对于有左子树的节点,第一次访问是根据mostRight的right指针为空的时候,
     * 2) 对于没有左子树的节点,第一次访问是根据cur的左孩子为空的时候,
     */
    public static void morrisPre(Node head) {
        if (head == null) {
            return;
        }
        System.out.print("morrisPre: ");
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    // 第一次到达左子树的最右侧节点,对应过程2.1)
                    System.out.print(cur.value + " ");
                    mostRight.right = cur;
                    cur = cur.left;
                    // 直接进行下一个 while循环
                    continue;
                } else {
                    // 第二次到达左子树的最右侧节点,对应过程2.2)
                    mostRight.right = null;
                }
            } else {
                // 没有左子树,第一次访问是根据cur的左孩子为空的时候,
                System.out.print(cur.value + " ");
            }
            cur = cur.right;
        }
        System.out.println();
    }

2.3、Morris遍历实现中序遍历

  • Morris遍历实现中序遍历
  • 思路:
    • cur第二次到达该节点时(即从左子树返回时)才访问,顺序为:左子树 -> 根节点 -> 右子树
    • 因为Morris遍历的过程中,有左子树的节点会被访问两次,没有的会被访问1次,
    • 对于只访问一次的节点,就是直接访问了,对于访问两次的节点,是要在第二次访问,
    • 根据Morris的遍历代码可以看出,无论是没有左树还是第二次访问,都会走到最后指向右节点的位置,
    • 所以我们只需要在指向右节点之前访问信息就可以了。
java 复制代码
    /**
     * Morris遍历实现中序遍历
     * 思路:
     * cur第二次到达该节点时(即从左子树返回时)才访问,顺序为:左子树 -> 根节点 -> 右子树
     * 因为Morris遍历的过程中,有左子树的节点会被访问两次,没有的会被访问1次,
     * 对于只访问一次的节点,就是直接访问了,对于访问两次的节点,是要在第二次访问,
     * 根据Morris的遍历代码可以看出,无论是没有左树还是第二次访问,都会走到最后指向右节点的位置,
     * 所以我们只需要在指向右节点之前访问信息就可以了。
     */
    public static void morrisIn(Node head) {
        if (head == null) {
            return;
        }
        System.out.print("morrisIn : ");
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    // 第一次到达左子树的最右侧节点,对应过程2.1)
                    mostRight.right = cur;
                    cur = cur.left;
                    // 直接进行下一个 while循环
                    continue;
                } else {
                    // 第二次到达左子树的最右侧节点,对应过程2.2)
                    mostRight.right = null;
                }
            }
            // 没有左孩子和第二次到达,都会走到这里
            System.out.print(cur.value + " ");
            cur = cur.right;
        }
        System.out.println();
    }

2.4、Morris遍历实现后序遍历

  • Morris遍历实现后序遍历
  • 思路:
    • cur第二次到达该节点时,逆序打印该节点左子树的右边界。最后,单独逆序打印整棵树的右边界,
    • 根据Morris遍历的过程,第二次到达该节点的位置很容易找到,
    • 麻烦的是如何逆序打印该节点左子树的右边界。
    • 我们可以将左子树的有边界看成是一个链表,先用反转链表的方式将其转过来,然后打印,完成以后再反转回来。
java 复制代码
    /**
     * Morris遍历实现后序遍历
     * 思路:
     * cur第二次到达该节点时,逆序打印该节点左子树的右边界。最后,单独逆序打印整棵树的右边界,
     * 根据Morris遍历的过程,第二次到达该节点的位置很容易找到,
     * 麻烦的是如何逆序打印该节点左子树的右边界。
     * 我们可以将左子树的有边界看成是一个链表,先用反转链表的方式将其转过来,然后打印,完成以后再反转回来。
     */
    public static void morrisPos(Node head) {
        if (head == null) {
            return;
        }
        System.out.print("morrisPos: ");
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    // 第一次到达左子树的最右侧节点,对应过程2.1)
                    mostRight.right = cur;
                    cur = cur.left;
                    // 直接进行下一个 while循环
                    continue;
                } else {
                    // 第二次到达左子树的最右侧节点,对应过程2.2)
                    mostRight.right = null;
                    // 逆序打印左子树的有边界
                    printEdge(cur.left);
                }
            }
            cur = cur.right;
        }
        // 单独打印整棵树的有边界
        printEdge(head);
        System.out.println();
    }

    /**
     * 逆序打印右边界
     */
    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);
    }

    /**
     * 逆序有边界组成的链
     */
    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;
    }

2.5、Morris遍历判断是否为二叉搜索树

  • Morris遍历判断是否为二叉搜索树
  • 思路:
    • 二叉搜索树的左右子树都要比其父节点大,
    • 我们可以用中序遍历二叉树的方式,用一个变量记录上一个访问的节点的值,
    • 如果当前节点的值小于等于上一个节点的值,说明不是二叉搜索树。
java 复制代码
    /**
     * Morris遍历判断是否为二叉搜索树
     * 思路:
     * 二叉搜索树的左右子树都要比其父节点大,
     * 我们可以用中序遍历二叉树的方式,用一个变量记录上一个访问的节点的值,
     * 如果当前节点的值小于等于上一个节点的值,说明不是二叉搜索树。
     */
    public static boolean isBST(Node head) {
        if (head == null) {
            return true;
        }
        Node cur = head;
        Node mostRight = null;
        // 前一个访问的节点的值
        Integer pre = null;
        boolean ans = true;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                }
            }
            if (pre != null && pre >= cur.value) {
                ans = false;
            }
            // 中序遍历的方式,所以只需要在这里改pre的值即可
            pre = cur.value;
            cur = cur.right;
        }
        return ans;
    }

整体代码和测试如下:

java 复制代码
/**
 * Morris遍历
 * 二叉树之前的遍历方式有空间浪费的问题(递归实现也会占中栈空间)
 * Morris遍历时间复杂度O(N),额外空间复杂度O(1),通过利用原树中大量空闲指针的方式,达到节省空间的目的
 * <br>
 * Morris遍历过程:
 * 假设来到当前节点cur,开始时cur来到头节点位置
 * 1)如果cur没有左孩子,cur向右移动(cur = cur.right)
 * 2)如果cur有左孩子,找到左子树上最右的节点mostRight:
 * 2.1)如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)
 * 2.2)如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)
 * 3)cur为空时遍历停止
 * <br>
 * Morris遍历的特点:
 * 1)Morris遍历的过程中,cur指针会移动到每一个节点,也就是每个节点都会被cur访问到,但是有个规律:
 * 如果一个节点有左孩子,这个节点会被cur访问2次,如何区分是第2次访问到:判断左树最右侧的节点是否指向自己,如果指向自己,说明是第2次访问到。
 * 如果一个节点没有左孩子,这个节点会被cur访问1次,
 * cur指针的指向顺序,就是Morris遍历的顺序。
 * 2)Morris遍历的过程中,存在寻找mostRight的过程,所以每一个叶子节点都会被mostRight访问到,
 * 作为父节点的左孩子的叶子节点,会在寻找父节点的左孩子的有边界的时候,被mostRight访问到,因为此时只有它一个节点,
 * 对于作为父节点的右孩子的叶子节点,会在父节点的父节点在寻找父节点的有边界的时候被mostRight访问到,
 * 要区分这个叶子节点是第一次被访问到还是第二次被访问到,同样是判断左树最右侧的节点是否指向自己,如果指向自己,说明是第2次访问到。
 * 通常所说的被访问到是指cur访问到,而判断是第一次访问到还是第二次访问到,是根据mostRight的指向来判断的。
 * 对于关注叶子节点的题目,我们要知道的是叶子节点实际上都是可以被mostRight访问到的
 * <br>
 * Morris遍历的应用:
 * 基于基本的Morris遍历流程,通过在不同时机执行"访问"操作(如打印节点值),可以分别实现前序、中序和后序遍历。
 * 1)前序遍历:cur第一次到达该节点时就访问,顺序为:根节点 -> 左子树 -> 右子树
 * 2)中序遍历:cur第二次到达该节点时(即从左子树返回时)才访问,顺序为:左子树 -> 根节点 -> 右子树
 * 3)后序遍历:cur第二次到达该节点时,逆序打印该节点左子树的右边界。最后,单独逆序打印整棵树的右边界
 */
public class MorrisTraversal {

    /**
     * Morris遍历代码模板
     * 思路:
     * 根据Morris遍历的过程,写出的代码模板
     * 1)如果cur没有左孩子,cur向右移动(cur = cur.right)
     * 2)如果cur有左孩子,找到左子树上最右的节点mostRight:
     * 2.1)如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)
     * 2.2)如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)
     * 3)cur为空时遍历停止
     * 示例:
     * 如下面这个二叉树:
     * ............a
     * ........../...\
     * .........b.....c
     * ......../.\.../.\
     * .......d..e..f...g
     * Morris遍历的顺序为:a -> b -> d -> b -> e -> a -> c -> f -> c -> g
     */
    public static void morris(Node head) {
        if (head == null) {
            return;
        }
        // cur 指针,指向当前访问的节点,初始为头节点
        Node cur = head;
        // mostRight 指针,指向左子树最右侧的节点,初始为null
        Node mostRight = null;
        // 开始遍历,对应过程的:3)cur为空时遍历停止
        while (cur != null) {
            // 先将mostRight指针指向当前节点的左孩子,这样就可以根据mostRight是否为空,判断是否有左孩子
            mostRight = cur.left;
            if (mostRight != null) {
                // 有左孩子,这个节点会被访问2次 ,
                // 依据过程 2)如果cur有左孩子,找到左子树上最右的节点mostRight
                // 找到左子树的最右侧节点,没有右子树或者上一次改成了指向自己,都要停止
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    // 第一次到达左子树的最右侧节点,对应过程2.1)
                    mostRight.right = cur;
                    cur = cur.left;
                    // 直接进行下一个 while循环
                    continue;
                } else {
                    // 第二次到达左子树的最右侧节点,对应过程2.2)
                    mostRight.right = null;
                }
            }
//            else{
//                // 没有左孩子,这个节点会被访问1次,如果要单独挑出被访问一次的节点,可以加上else在这里判断
//            }
            // 对于过程 1)和2.2),即没有左孩子或者第二次访问节点,都会执行到这里,接下来访问当前节点的右孩子
            cur = cur.right;
        }
    }

    /**
     * Morris遍历实现前序遍历
     * 思路:
     * cur第一次到达该节点时就访问,顺序为:根节点 -> 左子树 -> 右子树
     * 因为Morris遍历的过程中,有左子树的节点会被访问两次,没有的会被访问1次,
     * 所以要挑出第一次访问的时候,就有两个地方:
     * 1) 对于有左子树的节点,第一次访问是根据mostRight的right指针为空的时候,
     * 2) 对于没有左子树的节点,第一次访问是根据cur的左孩子为空的时候,
     */
    public static void morrisPre(Node head) {
        if (head == null) {
            return;
        }
        System.out.print("morrisPre: ");
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    // 第一次到达左子树的最右侧节点,对应过程2.1)
                    System.out.print(cur.value + " ");
                    mostRight.right = cur;
                    cur = cur.left;
                    // 直接进行下一个 while循环
                    continue;
                } else {
                    // 第二次到达左子树的最右侧节点,对应过程2.2)
                    mostRight.right = null;
                }
            } else {
                // 没有左子树,第一次访问是根据cur的左孩子为空的时候,
                System.out.print(cur.value + " ");
            }
            cur = cur.right;
        }
        System.out.println();
    }

    /**
     * Morris遍历实现中序遍历
     * 思路:
     * cur第二次到达该节点时(即从左子树返回时)才访问,顺序为:左子树 -> 根节点 -> 右子树
     * 因为Morris遍历的过程中,有左子树的节点会被访问两次,没有的会被访问1次,
     * 对于只访问一次的节点,就是直接访问了,对于访问两次的节点,是要在第二次访问,
     * 根据Morris的遍历代码可以看出,无论是没有左树还是第二次访问,都会走到最后指向右节点的位置,
     * 所以我们只需要在指向右节点之前访问信息就可以了。
     */
    public static void morrisIn(Node head) {
        if (head == null) {
            return;
        }
        System.out.print("morrisIn : ");
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    // 第一次到达左子树的最右侧节点,对应过程2.1)
                    mostRight.right = cur;
                    cur = cur.left;
                    // 直接进行下一个 while循环
                    continue;
                } else {
                    // 第二次到达左子树的最右侧节点,对应过程2.2)
                    mostRight.right = null;
                }
            }
            // 没有左孩子和第二次到达,都会走到这里
            System.out.print(cur.value + " ");
            cur = cur.right;
        }
        System.out.println();
    }

    /**
     * Morris遍历实现后序遍历
     * 思路:
     * cur第二次到达该节点时,逆序打印该节点左子树的右边界。最后,单独逆序打印整棵树的右边界,
     * 根据Morris遍历的过程,第二次到达该节点的位置很容易找到,
     * 麻烦的是如何逆序打印该节点左子树的右边界。
     * 我们可以将左子树的有边界看成是一个链表,先用反转链表的方式将其转过来,然后打印,完成以后再反转回来。
     */
    public static void morrisPos(Node head) {
        if (head == null) {
            return;
        }
        System.out.print("morrisPos: ");
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    // 第一次到达左子树的最右侧节点,对应过程2.1)
                    mostRight.right = cur;
                    cur = cur.left;
                    // 直接进行下一个 while循环
                    continue;
                } else {
                    // 第二次到达左子树的最右侧节点,对应过程2.2)
                    mostRight.right = null;
                    // 逆序打印左子树的有边界
                    printEdge(cur.left);
                }
            }
            cur = cur.right;
        }
        // 单独打印整棵树的有边界
        printEdge(head);
        System.out.println();
    }

    /**
     * 逆序打印右边界
     */
    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);
    }

    /**
     * 逆序有边界组成的链
     */
    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;
    }

    /**
     * Morris遍历判断是否为二叉搜索树
     * 思路:
     * 二叉搜索树的左右子树都要比其父节点大,
     * 我们可以用中序遍历二叉树的方式,用一个变量记录上一个访问的节点的值,
     * 如果当前节点的值小于等于上一个节点的值,说明不是二叉搜索树。
     */
    public static boolean isBST(Node head) {
        if (head == null) {
            return true;
        }
        Node cur = head;
        Node mostRight = null;
        // 前一个访问的节点的值
        Integer pre = null;
        boolean ans = true;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                }
            }
            if (pre != null && pre >= cur.value) {
                ans = false;
            }
            // 中序遍历的方式,所以只需要在这里改pre的值即可
            pre = cur.value;
            cur = cur.right;
        }
        return ans;
    }

    /**
     * 二叉树的节点定义
     */
    public static class Node {
        public int value;
        Node left;
        Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    public static void main(String[] args) {
        Node head = new Node(4);
        head.left = new Node(2);
        head.right = new Node(6);
        head.left.left = new Node(1);
        head.left.right = new Node(3);
        head.right.left = new Node(5);
        head.right.right = new Node(7);
        printTree(head);
        morrisPre(head);
        morrisIn(head);
        morrisPos(head);
        printTree(head);
        System.out.println(isBST(head));

    }

    // for test -- print tree
    public static void printTree(Node head) {
        System.out.println("Binary Tree:");
        printInOrder(head, 0, "H", 17);
        System.out.println();
    }

    public static void printInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        printInOrder(head.right, height + 1, "v", len);
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        printInOrder(head.left, height + 1, "^", len);
    }

    public static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }
}

3、题目:求二叉树的最小深度

  • 题目:求二叉树的最小深度
  • 给定一个二叉树,找出其最小深度。
  • 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
  • 说明:叶子节点是指没有子节点的节点。
  • 测试链接 : https://leetcode.cn/problems/minimum-depth-of-binary-tree

3.1、二叉树的递归套路实现方法

  • 二叉树的递归套路实现方法
  • 思路:
    • 根据二叉树的递归套路,我们需要最小的深度,那就要像左右节点要他们各自最小的深度,
    • 要到以后,取他们最小的深度,再加上1,就是以x为头的树的最小深度
    • 提交时方法名改为:minDepth
java 复制代码
    /**
     * 二叉树的递归套路实现方法
     * 思路:
     * 根据二叉树的递归套路,我们需要最小的深度,那就要像左右节点要他们各自最小的深度,
     * 要到以后,取他们最小的深度,再加上1,就是以x为头的树的最小深度
     * 提交时方法名改为:minDepth
     */
    public static int minDepth(TreeNode head) {
        if (head == null) {
            return 0;
        }
        return minDepthProcess(head);
    }

    /**
     * 递归函数的定义:返回x为头的树,最小深度是多少
     */
    public static int minDepthProcess(TreeNode x) {
        if (x.left == null && x.right == null) {
            // 叶节点,返回1
            return 1;
        }
        // 左右子树起码有一个不为空
        int leftH = Integer.MAX_VALUE;
        if (x.left != null) {
            leftH = minDepthProcess(x.left);
        }
        int rightH = Integer.MAX_VALUE;
        if (x.right != null) {
            rightH = minDepthProcess(x.right);
        }
        return 1 + Math.min(leftH, rightH);
    }

3.2、Morris遍历实现方法

  • Morris遍历实现方法
  • 思路:
    • 要判断一个数的深度,即是从根节点到叶子节点的距离,然后取一个最小值即可。
    • 根据Morris遍历我们知道,叶子节点都会被mostRight指针遍历到,所以这个是可以通过mostRight指针来判断的。
    • 我们只需要记录每一个节点的深度,就能统计出到叶子节点的距离。然后用一个变量取到他们中的最小值即可。
    • 这里有两个问题:
    • 1)根据Morris遍历,有些节点会被访问两次,而且是从叶子节点直接转到上层,深度变化如何处理?
    • 对于这个问题,因为第一次到达叶子节点的时候,我们是用了mostRight来找子节点的最右节点,可以在这个过程中统计出子节点到最右节点的距离,
    • 然后到第二次访问的时候,直接减去这个距离即可还原原来节点的深度。
    • 2)根据Morris遍历,我们访问到最后一个节点的时候,就直接退出了,所以整棵树的最右边这条边是没有统计的。
    • 所以我们在整体统计完成以后,需要单独统计一下整棵树的最右边的深度,需要的时候考虑上最小值即可。
    • 提交时方法名改为:minDepth
java 复制代码
    /**
     * Morris遍历实现方法
     * 思路:
     * 要判断一个数的深度,即是从根节点到叶子节点的距离,然后取一个最小值即可。
     * 根据Morris遍历我们知道,叶子节点都会被mostRight指针遍历到,所以这个是可以通过mostRight指针来判断的。
     * 我们只需要记录每一个节点的深度,就能统计出到叶子节点的距离。然后用一个变量取到他们中的最小值即可。
     * 这里有两个问题:
     * 1)根据Morris遍历,有些节点会被访问两次,而且是从叶子节点直接转到上层,深度变化如何处理?
     * 对于这个问题,因为第一次到达叶子节点的时候,我们是用了mostRight来找子节点的最右节点,可以在这个过程中统计出子节点到最右节点的距离,
     * 然后到第二次访问的时候,直接减去这个距离即可还原原来节点的深度。
     * 2)根据Morris遍历,我们访问到最后一个节点的时候,就直接退出了,所以整棵树的最右边这条边是没有统计的。
     * 所以我们在整体统计完成以后,需要单独统计一下整棵树的最右边的深度,需要的时候考虑上最小值即可。
     * 提交时方法名改为:minDepth
     */
    public static int minDepth(TreeNode head) {
        if (head == null) {
            return 0;
        }
        TreeNode cur = head;
        TreeNode mostRight = null;
        // 记录当前节点的深度的变量
        int curLevel = 0;
        // 记录最小高度的变量
        int minHeight = Integer.MAX_VALUE;
        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {
                // 统计到左子树最右边节点的距离
                int rightBoardSize = 1;
                while (mostRight.right != null && mostRight.right != cur) {
                    rightBoardSize++;
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null) {
                    // 第一次到达,深度加1
                    curLevel++;
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    // 第二次到达,此时的深度还没有减去右子树的深度,所以是子树最右侧节点的深度,需要统计到整体的里面
                    if (mostRight.left == null) {
                        minHeight = Math.min(minHeight, curLevel);
                    }
                    // 减去右子树的深度,还原到当前节点的深度
                    curLevel -= rightBoardSize;
                    mostRight.right = null;
                }
            } else {
                // 只有一次到达的节点,直接统计深度,深度加1
                curLevel++;
            }
            cur = cur.right;
        }
        // 单独统计整棵树的最右边的深度
        int finalRight = 1;
        cur = head;
        while (cur.right != null) {
            finalRight++;
            cur = cur.right;
        }
        // 整个树的最右节点是一个叶子节点的时候,需要将最优节点的高度加进去,
        // 如果不是叶子节点,说明有左节点,肯定比finalRight大。所以不能把finalRight加进去。
        if (cur.left == null && cur.right == null) {
            minHeight = Math.min(minHeight, finalRight);
        }
        return minHeight;
    }

后记

个人学习总结笔记,不能保证非常详细,轻喷

相关推荐
永远都不秃头的程序员(互关)2 小时前
人工智能中的深度学习:基础与实战应用
人工智能·笔记·学习
元亓亓亓2 小时前
LeetCode热题100--739. 每日温度--中等
python·算法·leetcode
思成不止于此2 小时前
【MySQL 零基础入门】DCL 核心语法全解析:用户管理与权限控制篇
数据库·笔记·sql·学习·mysql
小白程序员成长日记2 小时前
2025.12.11 力扣每日一题
数据结构·算法·leetcode
一碗白开水一2 小时前
【论文阅读】Denoising Diffusion Probabilistic Models (DDPM)详细解析及公式推导
论文阅读·人工智能·深度学习·算法·机器学习
代码游侠2 小时前
学习笔记——进程
linux·运维·笔记·学习·算法
天赐学c语言2 小时前
12.11 - 最长回文子串 && main函数是如何开始的
c++·算法·leetcode
CoovallyAIHub2 小时前
AI模型训练有哪些关键步骤与必备工具?从概念到可运行的智能模型
深度学习·算法·计算机视觉
程序员-King.2 小时前
day122—二分查找—完成旅途的最少时间(LeetCode-2187)
算法·leetcode·二分查找·双指针