广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java 二叉树遍历特别篇之Morris遍历
  • 809
分享到

Java 二叉树遍历特别篇之Morris遍历

2024-04-02 19:04:59 809人浏览 泡泡鱼

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

摘要

在前面,我们简单提及过二叉树的遍历方式,有递归和非递归两个版本的遍历。仔细想一想,不管是递归的,还是非递归的遍历,两种版本的遍历都是需要耗费大量的、额外的空间。比如当我们二叉树的高度

在前面,我们简单提及过二叉树的遍历方式,有递归和非递归两个版本的遍历。仔细想一想,不管是递归的,还是非递归的遍历,两种版本的遍历都是需要耗费大量的、额外的空间。比如当我们二叉树的高度有100层,那么递归时,系统就会一直压栈,最坏情况下,一直要压入100次遍历的递归函数,因为此处的空间复杂度是跟这颗二叉树的高度相关的。所以有人就在想,有没有什么方式,能够使这个空间复杂度再压缩一点呢?

img

前期文章:二叉树的非递归遍历

本期文章源码GitHub

有,肯定是有的。那就是今天的主题:Morris遍历。这种思想就能使二叉树的遍历,空间复杂度为O(1)。那我们直接开始吧!

一、Morris

在讲解之前,我们来分析一下。为什么遍历二叉树需要压栈?

image-20211004101245241

观察上面这颗二叉树,脑补一下。当我们一直往下遍历时,走到值为4的节点,打印输出4后,我该怎么回到2节点? 每个节点都是只有指向左右孩子的引用,并没有指向父节点的引用。

所以在遍历4节点的时候,会提前将2节点的所有信息,放入栈里面,只有在4节点遍历完成之后,才会弹出栈里的信息(2节点)。然后继续遍历其他的节点。

这也就是为什么遍历二叉树需要压栈的原因。

那么我们就在想啊,有没有种方法,能够不压栈,就能从4节点直接返回到2节点呢? 那就是利用4节点的右孩子引用。因为4节点是叶子节点,没有左右孩子节点的。 我们将4节点的右孩子引用,指向2节点,这样就能够从4节点直接返回到2节点了。具体的图片如下:

image-20211004102048954

上图橙色的线条,是Morris方法的作用,就是改变某些节点的右孩子引用。然后在具体的遍历过程中,我们将橙色线条擦除即可。保证跟原二叉树一模一样。这就是Morris的思想。 那么问题来了,我们该如何画这些橙色的线条呢?

前提:我们准备两个引用变量:cur和mostRight。 cur表示当前遍历的节点;mostRight表示cur节点左子树中,往右边遍历,第一个没有右孩子的节点。举个例子:上图中cur是1节点,则mostRight就是1节点左子树中,往右下角遍历,5节点就是第一个没有右孩子的节点。

Morris遍历细节:(cur初始化为根结点)

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

如果cur有左孩子,那就找到cur左子树中,最靠右的节点mostRight:

  • 如果mostRight的右孩子是null,说明是第一次遍历到mostRight。此时让mostRight的右孩子指向cur。
  • 如果mostRight的右孩子是cur,说明是第二次遍历到mostRight。此时让mostRight的右孩子等于null即可。

遍历结束条件:cur等于null时

伪代码:


public void morris(node root) {
    if (root == null) {
        return;
    }
    
    Node cur = root;
    Node mostRight = null;
    while (cur != null) {
        mostRight = cur.left;
        if (mostRight 不等于null) {
        	1、循环找到mostRight节点
            2、判断mustRight的右孩子是否为null
        }
        
        //mostRight等于null时
        cur = cur.right; //向右子树移动
    }
}

上面的伪代码就是大致的框架结构,具体的代码如下:

image-20211004104356512

值得注意的是,当第一次遍历到mostRight时,让mostRight的右孩子指向cur之后,cur 再往左子树走,然后应该再写一句continue。让代码继续往cur的左子树继续遍历。

可能有的同学分不清,什么时候是第一次遍历到mostRight,什么是第二次遍历到mostRight。简单点说,第一次就是mostRight的右孩子是null时,此时我们需要将右孩子指向cur;第二次是mostRight的右孩子指向cur的时候,此时我们需要将右孩子重新改回来,改为null。

关于前序、中序、后序遍历,都是在Morris的基础之上,添加printf即可。具体的,请往下看。

二、前序遍历

我们都知道前序遍历的顺序是:头左右。

而Morris改前序遍历,非常简单。记住两个点,你就能独自改前序遍历。

  • 如果cur节点没有左孩子,那就打印当前cur的值
  • 如果是第一次遍历到mostRight节点,那就打印当前cur的值

就以上两个点,添加到代码里面即可。

image-20211004105948598

其他的代码,原封不动。只添加这两句代码即可完成前序遍历。

三、中序遍历

前序遍历顺序:左头右。

中序跟前序的遍历,这里很相似,也是记住两个点即可:

  • 如果cur没有左孩子,直接打印输出cur的值
  • 如果mostRight是第二次遍历到,那就打印输出cur的值

跟前序遍历的区别只是mostRight这里,前序是第一次遍历到mostRight就打印cur,中序是第二次遍历到就打印。

image-20211004111042242

当然,这两个printf可以提取出来,写一行即可。这里只是为了让大家清晰地认识到中序遍历。

四、后序遍 历(较难)

Morris改后序遍历,稍微有一点点难,因为Morris遍历出来的左右结果,都满足一个规律:当前节点没有左子树,那么只会遍历一次这个节点;当前节点有左子树,那么会有mostRight节点会返回到cur节点,也就是说有左孩子的节点,会遍历两次。

后序遍历是每个节点,遍历到第3次的时候,才打印输出,现在可好,Morris遍历最多只能遍历到2次,那该如何是好???

不急,我们来看一张图:

image-20211004111845186

不难发现,红色的数值就是后序遍历的结果。对比下左边的二叉树,红色线条的指向,从左到右,从下到上的打印顺序,竟然跟后序遍历的结果是一样的。

那我们就以这个为切入点,当遍历cur的时候,我们就可以打印cur左子树的最靠右的那一排的节点。比如cur等于1节点的时候,我们就可以打印2、5节点。

要满足从下到上的打印顺序,最容易想到的就是搞一个栈,压栈进行,然后再依次弹出栈并打印。这样的话,空间复杂度就不是O(1)了。 大家是否还记得逆序单链表?我们将那一排的节点,进行逆序,然后打印输出之后,再逆序回来即可。


//逆序那一排节点
public Node reverseList(Node node) {
    Node pre = null;
    Node next = null;
    while (node != null) {
        next = node.right; //逆序右子树那一排节点
        node.right = pre;
        pre = node;
        node = next;
    }
    return pre;
}

逆序完成之后,打印输出即可,然后在逆序回来,保持原来的状态


public void printList(Node node) {
    Node newHead = reverseList(node); //逆序
    node = newHead; //先保存newHead,等会逆序
    
    while (newHead != null) {
        System.out.print(newHead.val + " ");
        newHead = newHead.right; //向右子树走
    }
    reverseList(node); //逆序回来,保持原状态
}

上面两个方法,就能实现打印行为。现在的问题是,怎么进行调用???

Morris遍历,有的节点会遍历到一次,而有的节点会遍历到两次。牢牢抓住这两种情况,Morris遍历就简单了。

这里的后序遍历,还是跟前序和中序差不多,只是打印函数,我们在第二次遍历到mostRight调用即可。

比如:在上图中,第二次遍历到mostRight等于4节点时,我们就调用打印函数,传递的参数是cur的左孩子。再比如:第二次遍历到mostRight等于5节点时,此时调用printList(cur.left),此时的cur是1节点。

看代码更清晰:

image-20211004114011933

最后切记,当24行~43行的循环结束后,还剩下整棵树的最靠右的那一排节点没有打印(对应上图就是:1、3、7节点还没打印),要单独调用打印函数,传递根结点即可。

好啦,以上全部就是Morris遍历二叉树的全部情况,牢牢抓住cur节点有没有左子树的情况,应该就能很好理解了。

img

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

--结束END--

本文标题: Java 二叉树遍历特别篇之Morris遍历

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

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

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java 二叉树遍历特别篇之Morris遍历
    在前面,我们简单提及过二叉树的遍历方式,有递归和非递归两个版本的遍历。仔细想一想,不管是递归的,还是非递归的遍历,两种版本的遍历都是需要耗费大量的、额外的空间。比如当我们二叉树的高度...
    99+
    2022-11-12
  • 二叉树遍历方法——前、中、后序遍历(java)
    二叉树结构: static class TreeNode{ public char val; public TreeNode left; public TreeNode right; ...
    99+
    2023-09-20
    算法
  • C语言之二叉树的遍历
    目录0.写在前面1.前序遍历步骤详解代码实现2.中序遍历步骤详解代码实现3.后序遍历步骤详解代码实现0.写在前面 认识二叉树结构最简单的方式就是遍历二叉树。所谓遍历二叉树就是按照某种...
    99+
    2023-05-14
    C语言实现二叉树遍历 二叉树遍历
  • 二叉树的中序遍历
    解题思路: 官方题解中介绍了三种方法来完成树的中序遍历,包括: 递归 借助栈的迭代方法 莫里斯遍历 在树的深度优先遍历中(包括前序、中序、后序遍历),递归方法最为直观易懂,但考虑到效率,我们通常不推荐使用递归。 栈迭代方法虽然提高了效率,但...
    99+
    2023-10-07
    python
  • C++树之遍历二叉树实例详解
    在讲遍历之前,我们要先创建一个树: #include <iostream> using namespace std; typedef struct node; ty...
    99+
    2022-11-11
  • java二叉树的遍历方式详解
    目录一、前序遍历(递归和非递归)二、中序遍历(递归和非递归)三、后序遍历(递归和非递归)四、层序遍历总结一、前序遍历(递归和非递归) 前序遍历就是先遍历根再遍历左之后是右 根左右 ...
    99+
    2022-11-12
  • Java 二叉树遍历的常用方法
    目录递归方式非递归方式层次遍历总结采用前序遍历、中序遍历、后续遍历实现时,即便采用不同的实现方式(递归方式、非递归),它们的算法结构是有很大的相似性。因而针对前三种的遍历我们会总结出...
    99+
    2022-11-12
  • Java如何实现二叉树和遍历
    这篇文章主要介绍了Java如何实现二叉树和遍历,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。什么是二叉树简单理解为对于一个节点来说,最多拥有一个上级节点,同时最多具备左右两个...
    99+
    2023-06-29
  • 详解Java 二叉树的实现和遍历
    目录什么是二叉树二叉树建树前序建树中序建树后序建树二叉树的遍历什么是二叉树 简单理解为对于一个节点来说,最多拥有一个上级节点,同时最多具备左右两个下级节点的数据结构。 由于很多排序算...
    99+
    2022-11-13
  • 利用java如何实现遍历二叉树
    这篇文章给大家介绍利用java如何实现遍历二叉树,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。遍历二叉树,从上往下遍历。但是同层节点可以从左向右遍历,也可以从右向左遍历(也就是之字型遍历),其中,都需要队列进行实现。只...
    99+
    2023-05-31
    java 二叉树 遍历
  • Java二叉树的遍历方式有哪些
    本篇内容主要讲解“Java二叉树的遍历方式有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java二叉树的遍历方式有哪些”吧!二叉树的四种遍历方式:二叉树的遍历(traversing bin...
    99+
    2023-06-25
  • C语言二叉树层序遍历
    实现下面图中的二叉树层序遍历 #include <stdio.h> #include <stdlib.h> #include <stdbool.h&g...
    99+
    2022-11-13
  • python先序遍历二叉树问题
    问题 如何遍历一个二叉树 遍历二叉树就是访问二叉树的每一个节点 二叉树父结点下先左访问,先序遍历(根左右) 例如:遍历以下的二叉树 遍历结果:ABDECF Python代码示例 # !/usr/bi...
    99+
    2022-06-04
    遍历 二叉树 python
  • JavaScript实现二叉树层序遍历
    目录题目描述示例递归实现代码思路解析图示队列实现代码思路解析题目描述 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 示例 二叉树:[3...
    99+
    2023-05-14
    JavaScript二叉树遍历 JS遍历 二叉树层序遍历 JS二叉树
  • python中创建和遍历二叉树
    python创建和遍历二叉树,可以使用递归的方式,源代码如下: #!/usr/bin/python class node(): def __init__(self,k=None,l=None,r=None): self.key=...
    99+
    2023-01-31
    建和 遍历 中创
  • C++实现LeetCode(107.二叉树层序遍历之二)
    [LeetCode] 107. Binary Tree Level Order Traversal II 二叉树层序遍历之二 Given the root of ...
    99+
    2022-11-12
  • 二叉树递归迭代及morris层序前中后序遍历详解
    目录分析二叉树的前序,中序,后序的遍历步骤1.层序遍历方法一:广度优先搜索方法二:递归2.前序遍历3.中序遍历4.后序遍历递归解法前序遍历--递归中序遍历--递归后序遍历--递归迭代...
    99+
    2022-11-12
  • java怎么实现二叉树的层次遍历
    这篇文章主要介绍“java怎么实现二叉树的层次遍历”,在日常操作中,相信很多人在java怎么实现二叉树的层次遍历问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”java怎么实现二叉树的层次遍历”的疑惑有所帮助!...
    99+
    2023-06-19
  • Java二叉树的四种遍历方式详解
    二叉树的四种遍历方式: 二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被访问依次且仅被访...
    99+
    2022-11-12
  • 用C++实现二叉树层序遍历
    这篇文章主要讲解了“用C++实现二叉树层序遍历”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“用C++实现二叉树层序遍历”吧!二叉树层序遍历从底部层序遍历其实还是从顶部开始遍历,只不过最后存储...
    99+
    2023-06-20
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作