当前位置:

首页 > 编程开发 > Java Morris遍历算法在二叉树中的应用方法是什么?

Java Morris遍历算法在二叉树中的应用方法是什么?

一.Morris遍历1.什么是Morris遍历Morris遍历是一种用于二叉树遍历的算法,它可以在不使用栈或队列的情况下实现中序遍历。该算法的时间复杂度为O(n),空间复杂度为O(1)。2.基本思想Morris遍历的基本思想是,利用叶子节点的空指针来存储临时信息,以达到节省空间的目的。具体来说,对于当前遍历到的节点,如果它有左子节点,就找到左子树中最右边的节点,将其右子节点指向当前节点,然后将当前节点更新为其左子节点。如果它没有左子节点,就输出当前节点的值,并将当前节点更新为其右子节点。重复以上步骤,直到

本文目录

    一.Morris遍历

    1.什么是Morris遍历

    Morris遍历是一种用于二叉树遍历的算法,它可以在不使用栈或队列的情况下实现中序遍历。该算法的时间复杂度为O(n),空间复杂度为O(1)。

    2.基本思想

    Morris遍历的基本思想是,利用叶子节点的空指针来存储临时信息,以达到节省空间的目的。具体来说,对于当前遍历到的节点,如果它有左子节点,就找到左子树中最右边的节点,将其右子节点指向当前节点,然后将当前节点更新为其左子节点。如果它没有左子节点,就输出当前节点的值,并将当前节点更新为其右子节点。重复以上步骤,直到遍历完整棵树。

    3.Morris遍历的优点和缺点

    Morris遍历的优点是空间复杂度低O(1),但它的缺点是会改变原来的二叉树结构,因此需要在遍历完后还原二叉树。此外,该算法可能会比递归或使用栈的算法稍微慢一些。

    4.二叉树的线索化

    二叉树的线索化,主要是利用了叶子结点中的空指针域,存放了在某种遍历顺序下的前驱或者后续结点,从而达到了线索二叉树的目的

    例如,下图中序遍历结果展示如下,根据中序遍历对空指针域进行线索化

    Java Morris遍历算法在二叉树中的应用方法是什么?

    线索化的二叉树为下, 画在左边表示left结点指向,画在右边表示right指向,例如7的前驱结点为5,那么7的left指向5,7的后继节点为1,那么7的right指向1

    Java Morris遍历算法在二叉树中的应用方法是什么?

    由此,我们可能总结出这样的结论:中序遍历二叉树的指向当前结点的结点为当前结点的左子树的最右端结点,例如指向1的结点为1的左节点2的最右端结点为7,指向2结点的为2的左节点4的最右端结点4.这一点在之后的morris遍历中很重要

    具体的代码实现如下:

    public class InorderThreadedBinaryTree {
        private ThreadTreeNode pre = null;
        public void threadedNodes(ThreadTreeNode node) {
            //如果node==null,不能线索化
            if (node == null) {
                return;
            }
            //1、先线索化左子树
            threadedNodes(node.left);
            //2、线索化当前结点
            //处理当前结点的前驱结点
            //以8为例来理解
            //8结点的.left = null,8结点的.leftType = 1
            if (node.left == null) {
                //让当前结点的左指针指向前驱结点
                node.left = pre;
                //修改当前结点的左指针的类型,指向前驱结点
                node.leftType = 1;
            }
            //处理后继结点
            if (pre != null && pre.right == null) {
                //让当前结点的右指针指向当前结点
                pre.right = node;
                //修改当前结点的右指针的类型=
                pre.rightType = 1;
            }
            //每处理一个结点后,让当前结点是下一个结点的前驱结点
            pre = node;
            //3、线索化右子树
            threadedNodes(node.right);
        }
    }
    class ThreadTreeNode {
        int val;
        ThreadTreeNode left;
        //0为非线索化,1为线索化
        int leftType;
        ThreadTreeNode right;
        //0为非线索化,1为线索化
        int rightType;
        public ThreadTreeNode(int val) {
            this.val = val;
        }
    }

    但是在实现Morris遍历的时候,并不需要把结点的左节点线索化,只需要把结点的右节点进行线索化即可,具体的原因在下面进行分析.

    二.中序Morris遍历

    1.中序Morris遍历的分析

    上面我们说了Morris遍历的时候只需要线索化右节点,这里给大家进行解释.当我们在中序遍历一棵树的时候,还比如是这样一棵树,我们一步步的node.left来到了6这个结点,这个结点的left为空,所以我们打印6这个结点的值,此时我们需要返回上一个结点,如果我们是要中序递归进行遍历的话,需要返回上一个栈,而我们Morris遍历的时候无法进行递归的返回,所以这个时候我们只需要把6的right结点进行线索化,这个时候6的right指向4,我们就可以返回到4,把4这个结点进行打印,4也线索化返回了2,把2进行打印,然后进行2的right结点5,5的left结点为空,因此打印5,之后进入到5的right结点7,打印7,7的right结点也进行了线索化,进入7的right结点为1,然后打印1,进入3结点并且打印

    因为最好不要改变树的结构,所以我们在打印的时候,将线索化的结点的right结点置为空.

    Java Morris遍历算法在二叉树中的应用方法是什么?

    2.中序Morris遍历的思路

    Morris遍历是利用了线索二叉树的思想,在遍历的过程中不适用栈,从而达到了空间复杂度为O(1)

    具体的实现如下:

    1.初始化当前的结点为根结点

    2.若当前的结点的左节点为空,则输出当前结点,然后遍历当前结点的右子树,即'curr=curr.right'

    3.若当前结点的左节点不为空,则找到当前结点的前驱节点,即当前结点左节点的最右侧结点,记为'prev'

    • 如果'prev.right''为空,则将pre.right指向curr结点,然后遍历当前结点的左子树,即'curr=curr.left'

    • 如果'prev.right''不为空,说明已经遍历完了当前节点的左子树,断开 `prev.right` 的连接,即'prev.left=null',输出当前节点,然后遍历当前节点的右子树,即 `curr=curr.right`.

    3.具体的代码实现

    public class Morris {
        /**
         * 将当前根结点中序遍历的结果存储到list集合中
         * @param root  根结点
         * @return  中序遍历的结合
         */
        public List inorderTraversal(TreeNode root) {
            List res = new ArrayList<>();
            TreeNode curr = root;
            while (curr != null) {
                if (curr.left == null) { // 左子树为空,则输出当前节点,然后遍历右子树
                    res.add(curr.val);  //如果要求直接打印,直接输出System.out.println(curr.val);
                    curr = curr.right;
                } else {
                    // 找到当前节点的前驱节点
                    TreeNode prev = curr.left;
                    while (prev.right != null && prev.right != curr) {
                        prev = prev.right;
                    }
                    if (prev.right == null) {
                        // 将前驱节点的右子树连接到当前节点
                        prev.right = curr;
                        curr = curr.left;
                    } else {
                        // 前驱节点的右子树已经连接到当前节点,断开连接,输出当前节点,然后遍历右子树
                        prev.right = null;
                        res.add(curr.val);//如果要求直接打印,直接输出System.out.println(curr.val);
                        curr = curr.right;
                    }
                }
            }
            return res;
        }
    }
    class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) {
            val = x;
        }
    }

    测试:

    Java Morris遍历算法在二叉树中的应用方法是什么?

    还是这样一颗二叉树,输出如下:

    [6, 4, 2, 5, 7, 1, 3]

    三.前序Morris遍历

    1.前序Morris遍历的思路

    前序和中序的遍历很想,只不过在打印(收集结点信息的时候不同),中序遍历是在当前结点的左节点为空(curr.left==null),或者当前结点已经被线索化(prev.right==curr)的时候进行打印,仔细观察前序遍历的过程,我们通过修改打印的顺序即可.前序遍历是在当前结点的左节点为空(curr.left==null),或者当前结点没有被线索化(prev.right==null)的时候进行打印

    具体的思路如下:

    1.初始化当前的结点为根结点

    2.若当前的结点的左节点为空,则输出当前结点,然后遍历当前结点的右子树,即'curr=curr.right'

    3.若当前结点的左节点不为空,则找到当前结点的前驱节点,即当前结点左节点的最右侧结点,记为'prev'

    • 如果'prev.right''为空,输出当前节点,然后将pre.right指向curr结点,然后遍历当前结点的左子树,即'curr=curr.left'

    • 如果'prev.right''不为空,说明已经遍历完了当前节点的左子树,断开 `prev.right` 的连接,即'prev.left=null',然后遍历当前节点的右子树,即 `curr=curr.right`.

    Java Morris遍历算法在二叉树中的应用方法是什么?

    2.具体的代码实现

        public List preorderTraversal(TreeNode root) {
            List res = new ArrayList<>();
            TreeNode curr = root;
            while (curr != null) {
                if (curr.left == null) { // 左子树为空,则输出当前节点,然后遍历右子树
                    res.add(curr.val);//如果要求直接打印,直接输出System.out.println(curr.val);
                    curr = curr.right;
                } else {
                    // 找到当前节点的前驱节点
                    TreeNode prev = curr.left;
                    while (prev.right != null && prev.right != curr) {
                        prev = prev.right;
                    }
                    if (prev.right == null) {
                        res.add(curr.val);//如果要求直接打印,直接输出System.out.println(curr.val);
                        // 将前驱节点的右子树连接到当前节点
                        prev.right = curr;
                        curr = curr.left;
                    } else {
                        // 前驱节点的右子树已经连接到当前节点,断开连接,输出当前节点,然后遍历右子树
                        prev.right = null;
                        curr = curr.right;
                    }
                }
            }
            return res;
        }

    测试:

        public static void main(String[] args) {
            TreeNode root = new TreeNode(1);
            root.left = new TreeNode(2);
            root.left.right = new TreeNode(5);
            root.left.right.right = new TreeNode(7);
            root.right = new TreeNode(3);
            root.left.left = new TreeNode(4);
            root.left.left.left = new TreeNode(6);
            System.out.println(preorderTraversal(root));
        }

    还是这样一颗二叉树,输出如下:

    [1, 2, 4, 6, 5, 7, 3]

    四.后序Morris遍历

    1.后序Morris遍历的思路

    后序Morris遍历实现起来有一定的难度,但是基本代码还是不变,只是在打印的地方有略微的区别,

    具体的思路如下:

    1.初始化当前的结点为根结点

    2.若当前的结点的左节点为空,则输出当前结点,然后遍历当前结点的右子树,即'curr=curr.right'

    3.若当前结点的左节点不为空,则找到当前结点的前驱节点,即当前结点左节点的最右侧结点,记为'prev'

    • 如果'prev.right''为空,然后将pre.right指向curr结点,然后遍历当前结点的左子树,即'curr=curr.left'

    • 如果'prev.right''不为空,此时进行逆序存储,说明已经遍历完了当前节点的左子树,断开 `prev.right` 的连接,即'prev.left=null',然后遍历当前节点的右子树,即 `curr=curr.right`.

    Java Morris遍历算法在二叉树中的应用方法是什么?

    2.具体的代码实现

        public List postorderTraversal(TreeNode root) {
            List res = new ArrayList<>();
            TreeNode dump = new TreeNode(0);//建立一个临时结点
            dump.left = root;  //设置dump的左节点为root
            TreeNode curr = dump;  //当前节点为dump
            while (curr != null) {
                if (curr.left == null) { // 左子树为空,则输出当前节点,然后遍历右子树
                    curr = curr.right;
                } else {
                    // 找到当前节点的前驱节点
                    TreeNode prev = curr.left;
                    while (prev.right != null && prev.right != curr) {
                        prev = prev.right;
                    }
                    if (prev.right == null) {
                        // 将前驱节点的右子树连接到当前节点
                        prev.right = curr;
                        curr = curr.left;
                    } else {
                        reverseAddNodes(curr.left, prev, res);
                        // 前驱节点的右子树已经连接到当前节点,断开连接,输出当前节点,然后遍历右子树
                        prev.right = null;
                        curr = curr.right;
                    }
                }
            }
            return res;
        }
        private void reverseAddNodes(TreeNode begin, TreeNode end, List res) {
            reverseNodes(begin, end); //将begin到end的进行逆序连接
            TreeNode curr = end;
            while (true) {//将逆序连接后端begin到end添加
                res.add(curr.val);
                if (curr == begin)
                    break;
                curr = curr.right;
            }
            reverseNodes(end, begin);//恢复之前的连接状态
        }
        /**
         * 将begin到end的进行逆序连接
         *
         * @param begin
         * @param end
         */
        private void reverseNodes(TreeNode begin, TreeNode end) {
            TreeNode prev = begin;
            TreeNode curr = prev.right;
            TreeNode post;
            while (prev != end) {
                post = curr.right;
                curr.right = prev;
                prev = curr;
                curr = post;
            }
        }

    测试:

        public static void main(String[] args) {
            TreeNode root = new TreeNode(1);
            root.left = new TreeNode(2);
            root.left.right = new TreeNode(5);
            root.left.right.right = new TreeNode(7);
            root.right = new TreeNode(3);
            root.left.left = new TreeNode(4);
            root.left.left.left = new TreeNode(6);
            System.out.println(postorderTraversal(root));
        }

    还是这样一颗二叉树,输出如下:

    [6, 4, 7, 5, 2, 3, 1]

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。