返回顶部
首页 > 资讯 > 后端开发 > Python >JavaMorris遍历算法及其在二叉树中的应用
  • 825
分享到

JavaMorris遍历算法及其在二叉树中的应用

JavaMorris遍历算法JavaMorris遍历 2023-05-18 08:05:02 825人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录一.Morris遍历1.什么是Morris遍历2.基本思想3.Morris遍历的优点和缺点4.二叉树的线索化二.中序Morris遍历1.中序Morris遍历的分析2.中序Morr

一.Morris遍历

1.什么是Morris遍历

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

2.基本思想

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

3.Morris遍历的优点和缺点

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

4.二叉树的线索化

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

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

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

由此,我们可能总结出这样的结论:中序遍历二叉树的指向当前结点的结点为当前结点的左子树的最右端结点,例如指向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结点置为空.

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 {
    
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> 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;
    }
}

测试:

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

[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`.

2.具体的代码实现

    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> 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`.

2.具体的代码实现

    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> 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<Integer> 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);//恢复之前的连接状态
    }
    
    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]

到此这篇关于Java Morris遍历算法及其在二叉树中的应用的文章就介绍到这了,更多相关Java Morris遍历算法内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: JavaMorris遍历算法及其在二叉树中的应用

本文链接: https://lsjlt.com/news/211836.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • JavaMorris遍历算法及其在二叉树中的应用
    目录一.Morris遍历1.什么是Morris遍历2.基本思想3.Morris遍历的优点和缺点4.二叉树的线索化二.中序Morris遍历1.中序Morris遍历的分析2.中序Morr...
    99+
    2023-05-18
    Java Morris遍历算法 Java Morris遍历
  • JavaScript二叉树及遍历算法实例分析
    这篇“JavaScript二叉树及遍历算法实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看...
    99+
    2024-04-02
  • JavaScript二叉树及各种遍历算法详情
    目录什么是二叉树满二叉树完全二叉树二叉树的存储数组存储链表存储与二叉树相关的算法深度优先遍历广度优先遍历先序遍历中序遍历后序遍历前言: 上一篇文章中介绍了树的概念、深度优先遍历和广度...
    99+
    2024-04-02
  • 二叉树的中序遍历
    解题思路: 官方题解中介绍了三种方法来完成树的中序遍历,包括: 递归 借助栈的迭代方法 莫里斯遍历 在树的深度优先遍历中(包括前序、中序、后序遍历),递归方法最为直观易懂,但考虑到效率,我们通常不推荐使用递归。 栈迭代方法虽然提高了效率,但...
    99+
    2023-10-07
    python
  • Python二叉树的定义及常用遍历算法分析
    本文实例讲述了Python二叉树的定义及常用遍历算法。分享给大家供大家参考,具体如下: 说起二叉树的遍历,大学里讲的是递归算法,大多数人首先想到也是递归算法。但作为一个有理想有追求的程序员。也应该学学非递归...
    99+
    2022-06-04
    遍历 算法 定义
  • python二叉树类以及其4种遍历方法实例
    目录前言实例代码:相关阅读内容:总结前言 之前学习过binarytree第三方库,了解了它定义的各种基本用法。 昨天在问答频道中做题时碰到一个关于二叉树的算法填空题,感觉代码不错非常...
    99+
    2024-04-02
  • JavaScritp中二叉树遍历算法的示例分析
    这篇文章主要为大家展示了“JavaScritp中二叉树遍历算法的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JavaScritp中二叉树遍历算法的示例...
    99+
    2024-04-02
  • 二叉树遍历方法——前、中、后序遍历(java)
    二叉树结构: static class TreeNode{ public char val; public TreeNode left; public TreeNode right; ...
    99+
    2023-09-20
    算法
  • Java 二叉树遍历的常用方法
    目录递归方式非递归方式层次遍历总结采用前序遍历、中序遍历、后续遍历实现时,即便采用不同的实现方式(递归方式、非递归),它们的算法结构是有很大的相似性。因而针对前三种的遍历我们会总结出...
    99+
    2024-04-02
  • C++二叉树的创建及遍历详情
    目录树的定义什么是树?非递归的中序遍历的实现二叉树的非递归的前序遍历的实现二叉树的创建以及前中后序遍历的代码总结树的定义 什么是树? 假如给我们一棵二叉树的前序遍历和中序遍历结果,我...
    99+
    2024-04-02
  • C#中怎么实现一个二叉树遍历算法
    这篇文章给大家介绍C#中怎么实现一个二叉树遍历算法,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。C#算法实现了二叉树的定义,怎么构造一颗已知的二叉树,用几种常规的算法(先序,中序,后序,层次)进行C#二叉树遍历。希望能...
    99+
    2023-06-18
  • 怎么在C++项目中遍历二叉树
    怎么在C++项目中遍历二叉树?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。在讲遍历之前,我们要先创建一个树:#include <iostream>us...
    99+
    2023-06-08
  • 如何实现数据结构中的二叉树遍历算法
    今天就跟大家聊聊有关如何实现数据结构中的二叉树遍历算法,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。今天咱们来看一种新...
    99+
    2024-04-02
  • 利用JS实现二叉树遍历算法实例代码
    目录前言 一、二叉树 1.1、遍历二叉树 1.2、用js表示二叉树 1.3、前序遍历算法 1.4、中序遍历算法 1.5、后序遍历算法 1.6、按层遍历算法 二、算法题 1.1、二叉树...
    99+
    2024-04-02
  • Java中二叉树遍历的常用方法有哪些
    这篇文章给大家分享的是有关Java中二叉树遍历的常用方法有哪些的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。采用前序遍历、中序遍历、后续遍历实现时,即便采用不同的实现方式(递归方式、非递归),它们的算法结构是有很...
    99+
    2023-06-15
  • C++实现二叉树非递归遍历算法详解
    目录一、二叉树的前序遍历二、二叉树的中序遍历三、二叉树的后序遍历3.1 方法一3.2 方法二一、二叉树的前序遍历 题目链接 我们可以把任何一棵树看成左路节点,左路节点和右子树。先访问...
    99+
    2023-05-17
    C++二叉树非递归遍历 C++非递归遍历 C++二叉树遍历
  • Python实现二叉树结构与进行二叉树遍历的方法详解
    二叉树的建立 使用类的形式定义二叉树,可读性更好 class BinaryTree: def __init__(self, root): self.key = root sel...
    99+
    2022-06-04
    二叉树 遍历 详解
  • C++实现二叉树层序遍历的方法
    今天小编给大家分享一下C++实现二叉树层序遍历的方法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。二叉树层序遍历Given ...
    99+
    2023-06-19
  • c语言二叉树的前序遍历方法
    这篇文章主要讲解了“c语言二叉树的前序遍历方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“c语言二叉树的前序遍历方法”吧!题目给定一个二叉树,返回它的 前序 遍历。示例:输入: [1,nu...
    99+
    2023-06-19
  • C++实现LeetCode(94.二叉树的中序遍历)
    [LeetCode] 94. Binary Tree Inorder Traversal 二叉树的中序遍历 Given a binary tree, return the ...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作