算法设计与分析 二叉树的Morris遍历

简介: 算法设计与分析 二叉树的Morris遍历

Morris遍历

概述

  • Morris遍历:一种遍历二叉树的方式,并且时间复杂度O(N),空间复杂度O(1)
  • 常规的遍历无论递归还是非递归,空间复杂度都达不到常数级别(二叉树的高度 O(logN))。Morris遍历,通过利用原树中的大量空闲指针的方式,达到节省空间的目地
  • morris遍历的实现原则
    记作当前节点为cur。
    1 如果cur无左孩子,cur向右移动(cur=cur.right)
    2 如果cur有左孩子,找到cur左子树上最右的节点,记为mostright
    (1)如果mostright的right指针指向空,让其指向cur,cur向左移动(cur=cur.left)
    (2)如果mostright的right指针指向cur,让其指向空,cur向右移动(cur=cur.right)
    3 cur为空时遍历结束
  • Morris的应用:因为Morris遍历解决了最基本的遍历问题,所以很多二叉树的问题都可以使用Morris进行优化

实现思路

image.png

  1. cur:1,mostright:5;将5的右指针指向1,cur移动到2
    image.png
  2. cur:2,mostright:4;将4的右指针指向2,cur移动到4
    image.png
  3. cur:4,cur目前没有左孩子(不设置mostright),将cur移动到2
    image.png
  4. cur:2,mostright:4;将4的右指针指向null,cur移动到5
    image.png
  5. cur:5,cur无左孩子,将cur移动到1

image.png

  1. cur:1,mostright:5;将5的右指针指向null,cur移动到3
    image.png
  2. cur:3,mostright:6,将6的右指针指向3,cur移动6
    image.png
  3. cur:6,cur无左孩子,将cur移动到3
    image.png
  4. cur:3,mostright:6;将6的右指针指向null,cur移动到7
    image.png
  5. cur:7,cur无左孩子,将cur移动到null,遍历结束
    image.png

Morris(cur)序列:1,2,4,2,5,1,3,6,3,7

可以发现:若节点有左孩子,会在Morris序列中出现2次,若无,就是一次

  • 解析:
    (1)常规的递归遍历方法,通过栈的数据结构记录头节点,实现树的遍历。每一个树的节点在整个遍历的过程中,都会经过三次
    (2)Morris利用的是底层线索化,若存在左子树就是通过左子树最右的节点的右指针与头节点建立连系,即通过左子树的最右节点回到头节点。所以可以做到树的遍历,含左子树的节点经过两次,不含左子树的只经过一次
    (3)如何判断节点是第几次访问:若访问该节点时mostRight.right = null,说明第一次访问,若mostRight.right = 当前节点,第二次访问

代码实现

    public static class Node{
        int value;
        Node left;
        Node right;
        public Node(int date){
            this.value = date;
        }
    }
    public static 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.right;
            }
            else {
                //cur存在左孩子
                while (mostRight.right != null && mostRight.right != cur){
                    //找到最右端
                    //因为:mostRight.right可能以及进行了修改指向了头结点,要进行判断
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null){
                    //将最右端的右指针与头节点相连
                    //第一次访问
                    mostRight.right = cur;
                    //cur左移
                    cur = cur.left;
                } else {
                    //mostRight.right == cur
                    //第二次访问
                    mostRight.right = null;
                    cur = cur.right;
                }
            }
        }
    }

使用Morris遍历得到前,中,后序序列

  • 先序序列
    1 只到达一次的节点直接打印
    2 到达两次的只打印第一次
    public static class Node{
        int value;
        Node left;
        Node right;
        public Node(int date){
            this.value = date;
        }
    }
    public static void morrisPre(Node head){
        if (head == null){
            return;
        }
        Node cur = head; //当前节点
        Node mostRight = null; //左孩子的最右端
        while (cur != null){
            mostRight = cur.left;
            if (mostRight == null){
                //无左子树
                //只访问一次的节点直接打印
                System.out.println(cur.value);
                cur = cur.right;
            }
            else {
                //cur存在左孩子
                while (mostRight.right != null && mostRight.right != cur){
                    //找到最右端
                    //因为:mostRight.right可能以及进行了修改指向了头结点,要进行判断
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null){
                    //将最右端的右指针与头节点相连
                    mostRight.right = cur;
                    //cur第一次访问
                    System.out.println(cur.value);
                    //cur左移
                    cur = cur.left;
                } else {
                    //mostRight.right == cur;
                    mostRight.right = null;
                    //cur第二次访问,不进行操作
                    cur = cur.right;
                }
            }
        }
    }
  • 中序遍历
    1 只到达一次的节点直接打印
    2 到达两次的只打印第二次
    public static class Node{
        int value;
        Node left;
        Node right;
        public Node(int date){
            this.value = date;
        }
    }
    public static void morrisIn(Node head){
        if (head == null){
            return;
        }
        Node cur = head; //当前节点
        Node mostRight = null; //左孩子的最右端
        while (cur != null){
            mostRight = cur.left;
            if (mostRight == null){
                //无左子树
                //只访问一次的节点直接打印
                System.out.println(cur.value);
                cur = cur.right;
            }
            else {
                //cur存在左孩子
                while (mostRight.right != null && mostRight.right != cur){
                    //找到最右端
                    //因为:mostRight.right可能以及进行了修改指向了头结点,要进行判断
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null){
                    //将最右端的右指针与头节点相连
                    mostRight.right = cur;
                    //cur第一次访问,不操作
                    //cur左移
                    cur = cur.left;
                } else {
                    //mostRight.right == cur;
                    mostRight.right = null;
                    //cur第二次访问打印
                    System.out.println(cur.value);
                    cur = cur.right;
                }
            }
        }
    }
  • 后序遍历
    1 第一次访问的不做任何操作
    2 第二次访问时要求逆序打印左树的右边界
    3 整个遍历结束后,逆序打印整个树的右边界
    4 问题解决:任何逆序打印右边界,空间复杂度为O(1),将右边界看为:节点只含右指针的单列表。将整个列表逆序,访问,再逆序恢复
    public static class Node{
        int value;
        Node left;
        Node right;
        public Node(int date){
            this.value = date;
        }
    }
    public static 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.right;
            }
            else {
                //cur存在左孩子
                while (mostRight.right != null && mostRight.right != cur){
                    //找到最右端
                    //因为:mostRight.right可能以及进行了修改指向了头结点,要进行判断
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null){
                    //将最右端的右指针与头节点相连
                    mostRight.right = cur;
                    //cur第一次访问,不操作
                    //cur左移
                    cur = cur.left;
                } else {
                    //mostRight.right == cur;
                    mostRight.right = null;
                    //cur第二次访问逆序打印左子树的右边界
                    printEdge(cur.left);
                    cur = cur.right;
                }
            }
        }
        //整个遍历结束后,打印整个树的右边界
        printEdge(head);
    }
    public static void printEdge(Node x){
        //打印X的右边界
        Node temp = reverseEdge(x); //逆序后的头节点
        Node cur = temp;
        while (cur != null){
            //逆序打印
            System.out.println(cur.value);
            cur = cur.right;
        }
        reverseEdge(temp); //再次逆序
    }
    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应用判断是否为BST

  • 只需再中序遍历的打印位置进行修改即可,判断上一个值是否比当前值小
    public static class Node{
        int value;
        Node left;
        Node right;
        public Node(int date){
            this.value = date;
        }
    }
    public static Boolean isBST(Node head){
        if (head == null){
            return true;
        }
        Node cur = head; //当前节点
        Node mostRight = null; //左孩子的最右端
        int preValue = Integer.MIN_VALUE;
        while (cur != null){
            mostRight = cur.left;
            if (mostRight == null){
                //无左子树
                if (preValue < cur.value){
                    preValue = cur.value;
                }else {
                    return false;
                }
                cur = cur.right;
            }
            else {
                //cur存在左孩子
                while (mostRight.right != null && mostRight.right != cur){
                    //找到最右端
                    //因为:mostRight.right可能以及进行了修改指向了头结点,要进行判断
                    mostRight = mostRight.right;
                }
                if (mostRight.right == null){
                    //将最右端的右指针与头节点相连
                    mostRight.right = cur;
                    //cur第一次访问,不操作
                    //cur左移
                    cur = cur.left;
                } else {
                    //mostRight.right == cur;
                    mostRight.right = null;
                    if (preValue < cur.value){
                        preValue = cur.value;
                    }else {
                        return false;
                    }
                    cur = cur.right;
                }
            }
        }
        return true;
    }


目录
相关文章
|
23天前
|
人工智能 算法 BI
第一周算法设计与分析 D : 两面包夹芝士
这篇文章介绍了解决算法问题"两面包夹芝士"的方法,通过找出两个数组中的最大最小值,计算这两个值之间的整数个数,包括特判不存在整数的情况。
|
15天前
|
算法 数据可视化
基于SSA奇异谱分析算法的时间序列趋势线提取matlab仿真
奇异谱分析(SSA)是一种基于奇异值分解(SVD)和轨迹矩阵的非线性、非参数时间序列分析方法,适用于提取趋势、周期性和噪声成分。本项目使用MATLAB 2022a版本实现从强干扰序列中提取趋势线,并通过可视化展示了原时间序列与提取的趋势分量。代码实现了滑动窗口下的奇异值分解和分组重构,适用于非线性和非平稳时间序列分析。此方法在气候变化、金融市场和生物医学信号处理等领域有广泛应用。
|
1天前
|
机器学习/深度学习 存储 人工智能
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计
使用Python作为开发语言,基于文本数据集(一个积极的xls文本格式和一个消极的xls文本格式文件),使用Word2vec对文本进行处理。通过支持向量机SVM算法训练情绪分类模型。实现对文本消极情感和文本积极情感的识别。并基于Django框架开发网页平台实现对用户的可视化操作和数据存储。
11 0
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计
|
23天前
|
算法
算法设计与分析作业
这篇文章是关于算法设计与分析的作业,其中包含了两个算法实现:一个是使用分治算法实现的十进制大整数相乘(包括加法、减法和乘法函数),并进行了正确性和健壮性测试;另一个是使用快速排序思想实现的分治查找第K小元素的程序,并分析了其平均和最坏时间复杂度。
算法设计与分析作业
|
19天前
|
编解码 算法 图形学
同一路RTSP|RTMP流如何同时回调YUV和RGB数据实现渲染和算法分析
我们播放RTSP|RTMP流,如果需要同时做渲染和算法分析的话,特别是渲染在上层实现(比如Unity),算法是python这种情况,拉两路流,更耗费带宽和性能,拉一路流,同时回调YUV和RGB数据也可以,但是更灵活的是本文提到的按需转算法期望的RGB数据,然后做算法处理
|
23天前
|
人工智能 算法
第一周算法设计与分析:C : 200和整数对之间的情缘
这篇文章介绍了解决算法问题"200和整数对之间的情缘"的方法,通过统计数组中每个数模200的余数,并计算每个同余类中数的组合数来找出所有满足条件的整数对(i, j),使得\( A_i - A_j \)是200的整数倍。
|
23天前
|
人工智能 算法
第一周算法设计与分析 G : 排队援救
这篇文章介绍了解决算法问题"排队援救"的方法,通过使用队列和映射来模拟救援点的排队过程,并确定最终得到救援的人的顺序和编号。
|
23天前
|
算法 C++
第一周算法设计与分析 E : 构造回文串
这篇文章介绍了解决算法问题"构造回文串"的方法,即判断给定的整数N(视为字符串)是否可以通过在前面添加任意个0(或不添加)来构造一个回文串,并给出了相应的C++代码实现。
|
23天前
|
算法 C++
第一周算法设计与分析 H : 括号匹配
这篇文章介绍了解决算法问题"括号匹配"的方法,通过使用栈来检查给定字符串中的括号是否合法匹配,并提供了相应的C++代码实现。
|
23天前
|
算法 C++
第一周算法设计与分析 F : 模拟计算器
该文章 "第一周算法设计与分析 F : 模拟计算器" 的摘要或讨论。这篇文章介绍了如何设计一个程序来模拟一个基本的计算器,处理包含加、减、乘运算的表达式,并给出了相应的C++代码实现