morris算法(莫里斯算法)

Morris算法, 我们最常用于解决二叉树的遍历问题, 以及与遍历相关的一些问题(其实在二叉树系列问题中, 我们解决各种问题的时候都要涉及到一个对二叉树的遍历)

我们先来介绍几个常见的简单的二叉树遍历方式:

常见的二叉树遍历方式: ①: 递归 ②: 迭代

递归方式:

使用递归解决二叉树的遍历本质其实很简单, 我们只需要编写一个递归方法, 在其中先进行向左遍历, 遍历到最后之后开始向右遍历, 这样我们对于我们递归到的结点就会有一个打印时机的区分:(如下: )

  1. 在向左遍历之前打印:

    • 就是第一次刚刚到这个节点的时候打印, 这个时候肯定就是先序遍历
  2. 在向左遍历之后, 向右遍历之前打印:
    • 这个时候就是我们第一次遍历节点的时候并没有打印, 而是第一次回溯的时候进行了一个打印, 打印之后才去遍历右边, 所有这就是中序遍历
  3. 在向左和向右都遍历完之后打印:
    • 这个时候就是我们第一次遍历节点的时候没有打印(向左递归过程中), 第一次回溯的时候也没有打印, 第二次回溯的时候才进行了打印, 这个时候就是后序遍历

一个节点(包括叶子结点), 肯定是会经过三次, 第一次就是向左遍历或者向右遍历的过程中, 第二次和第三次就是向左和向右遍历完成之后回溯的时候各会遍历到一次, 而先序遍历就是第一次遍历到结点的时候打印, 中序遍历就是第二次遍历到结点的时候打印, 后序遍历就是第三次遍历到结点的时候打印

  • 注意: 我们一个节点在向左或者向右遍历的时候遍历到第一次, 而在回溯的过程中涉及到右两个子节点, 所以两个子节点最终都会回溯到该节点, 也就是会在回溯的过程中遍历到两次, 一共三次

递归方式中序遍历代码举例:

public List<Integer> inorderTraversal(TreeNode root) {List<Integer> resultList = new ArrayList<>();priIT(root, resultList);return resultList;
}private void priIT(TreeNode node, List<Integer> resultList) {//递归终止条件if (node == null) {return;}//递归操作priIT(node.left, resultList);resultList.add(node.val);priIT(node.right, resultList);
}

迭代方式:

使用迭代的方式完成二叉树的遍历其实是和使用递归的方式完成二叉树的遍历一样的, 在递归的方式遍历过程中我们隐式的维护了一个栈结构, 而我们如果是使用迭代的方式完成二叉树的遍历, 其实大多数的时候都是会选择显式的维护一个栈结构, 然后来完成对二叉树的遍历操作

迭代方式中序遍历代码举例:

public List<Integer> inorderTraversal(TreeNode root) {List<Integer> resultList = new ArrayList<>();//使用双端队列来模拟栈Deque<TreeNode> stack = new LinkedList<>();//一直遍历到最左下角while (root != null || !stack.isEmpty()) {while (root != null) {stack.push(root);root = root.left;}//出栈之后就相当于左子节点和当前节点都处理完了, 接下来就要对右子节点进行一个同样的操作root = stack.pop();resultList.add(root.val);root = root.right;}return resultList;
}

我们为什么要使用莫里斯算法?(莫里斯算法的好处是什么?)

莫里斯算法和线索化二叉树的思想是一样的, 都是利用了二叉树空余的指针来存储引用, 进而减少了空间复杂度, 使用递归和迭代完成二叉树遍历的时候时间复杂度和空间复杂度都是O(n), 而使用morris算法实现二叉树的遍历的时间复杂度为O(n), 但是空间复杂度则只有O(1)

  • 注意: 这里我们说的遍历二叉树指的是遍历一般的二叉树, 如果是遍历线索化二叉树则除外, 线索化二叉树遍历的空间复杂度本来就是O(1)

莫里斯算法实现思想:

当前节点cur, 一开始的cur来到整棵树的头结点位置:

  1. cur无左树, cur = cur.right

  2. cur有左树, 找到左树最右节点mostright

    1.1. mostright的右指针指向null的时候, mostright.right = cur, cur = cur.left

    1.2. mostright的右指针指向cur的时候, mostright.right = null, cur = cur.right

上面第一个大条件, 就是判断当前节点有无左树, 如果没有左树, 那么我们直接就让当前引用指向当前节点的右子节点即可, 都没有左树了, 这个时候我们肯定是指针右移

上面的第二个大条件, 就是判断如果有左树, 那么我们就要找到左树上最优的结点, 并且分为以下两种情况:

  1. 如果mostright的右指针指向了null的时候, 我们就让mostright的右指针指向cur, 之后我们就需要让引用cur指向当前节点的左子节点(注意: 这个时候就是我们第一次遍历到这个节点)
  2. 如果mostright的右指针已经指向了cur了, 这个时候我们就让mostright的右指针重新指向null, 之后我们就需要让引用cur指向当前节点的右子节点(这个时候这个cur就是第二次指向同一个结点了)
莫里斯算法会经过所有结点, 但是对于没有左子节点的结点只会到达一次, 而对于有左子节点的结点会到达两次, 通过以上思想就可以实现莫里斯算法来遍历二叉树
  • 只要是cur指针能指向的结点都是可以遍历到的结点, cur指针几次指向该结点, 我们就称之为到达了几次该结点

莫里斯算法也是先走左边, 只有左边没有的时候或者是左边走完的时候我们才会去走右边(也就是cur右移)

  • 左边走完其实也就是我们左子树的真实最右子节点的右指针已经被二次修改过的时候(也就是重新置空的时候), 这个时候就算是右边走完了

使用莫里斯算法实现二叉树的中序遍历:

public static void morrisIn(Node head) {//如果头结点为空, 直接返回if(head == null){return;}//开始的时候让cur指针指向头结点Node cur = head;//开始的时候mostright指向nullNode mostRight = null;//如果当前节点不为null, 那么就循环while (cur != null){//让mostright指向cur的左子树的根节点(也就是左子节点), 为了找左子树的最右结点mostRight = cur.left;//如果cur的左子树不为空if(mostRight != null){//如果mostright的右子树不为空, 并且右子节点不为cur, 那么循环, 一直找到最右子节点while (mostRight.right !=null && mostRight.right != cur){//mostright一直右移, 最终就会找到最右子节点mostRight = mostRight.right;}//从上面while循环出来之后mostright已经是当前节点的左子树上的最右子节点了//这个时候分了两种情况:/*情况一: mostright的右子节点为null的时候, 这个时候我们就要让mostright的右子节点指向当前节点cur, 然后让cur指向当前节点的左子节点, 然后停止本次循环即可*/if(mostRight.right == null){mostRight.right = cur;cur = cur.left;continue;/*情况二 mostright的右子节点为cur的时候, 这个时候我们让mostright的右子节点重新指向null, 然后让cur指向当前节点的右子节点, 当前节点的左边就算是执行完了*/}else {//注意: 这个时候我们让cur指向当前节点的右子节点是在外面和cur左子树为空情况合并到了一起执行的mostRight.right = null;}}//如果cur的左子树为空, 那么直接让cur指向当前节点的右子节点即可System.out.print(cur.value+" ");cur = cur.right;}System.out.println();
}

morris算法(莫里斯遍历) [数据结构与算法]相关推荐

  1. vrp 节约算法 c++_数据结构和算法(Golang实现)(8.1)基础知识-前言

    基础知识 学习数据结构和算法.我们要知道一些基础的知识. 一.什么是算法 算法(英文algorithm)这个词在中文里面博大精深,表示算账的方法,也可以表示运筹帷幄的计谋等.在计算机科技里,它表示什么 ...

  2. 【算法】常见数据结构基本算法整理

    去年11月份听了牛客网的课,当时做了纸质的笔记整理. 现在为了以后方便查找,将问题目录整理如下. 每道题只提供解题思路,不贴源代码. 可能会稍微手写一下代码(没有在IDE上测,为了试下以后面试时手写代 ...

  3. python数据结构与算法知识点_数据结构和算法基础知识点(示例代码)

    数据结构和算法基础知识点 链表 1.链表是一种由节点组成的线性数据集合,每个节点通过指针指向下一个节点.它是 一种由节点组成,并能用于表示序列的数据结构. 2.单链表:每个节点仅指向下一个节点,最后一 ...

  4. 0302Prim算法-最小生成树-图-数据结构和算法(Java)

    文章目录 1 Prim算法 1.1 概述 1.1.1 算法描述 1.1.2 数据结构 1.1.3 横切边集合维护 1.2 延时实现 1.2.1 实现代码 1.2.2 性能分析 1.3 即时实现 1.3 ...

  5. python数据结构算法优势_Python数据结构与算法(一)----- 算法效率

    一.引入 先来看一道题: 如果a+b+c=1000, 且a2+b2=c^2(a,b,c为自然数),如何求出所有a,b,c可能的组合? (1) 枚举法 import time start_time = ...

  6. 重学算法第三期|数据结构与算法001

    目录 强烈推荐一个数据结构可视化工具:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html,点击B+树即可模拟B+树的动态插入过 ...

  7. java算法概述,Java数据结构与算法基础(一)概述与线性结构

    Java数据结构与算法基础(二)递归算法 Java数据结构与算法基础(一)概述与线性结构 学习目的:为了能更顺畅的读很多底层API代码和拓宽解决问题的思路 一.数据结构概述 1.数据结构是什么?数据与 ...

  8. 【自学笔记】尚硅谷数据结构与算法Chapter 1 数据结构与算法概述

    Chapter 1 数据结构与算法概述 文章目录 Chapter 1 数据结构与算法概述 1.1.1 数据结构和算法的关系 1.2.1 线性结构 1.2.2 非线性结构 尚硅谷数据结构B站学习视频地址 ...

  9. 完全背包问题贪心算法c语言,数据结构与算法学习之路:背包问题的贪心算法和动态规划算法...

    一.背包问题描述: 有N种物品和一个重量为M的背包,第i种物品的重量是w[i],价值是p[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包重量,且价值总和最大. 二.解决方法: 1.贪心算 ...

最新文章

  1. mysql engine innodb myisam 区别
  2. iPhone浏览器性能测试
  3. ubuntu 安装redis
  4. opengl win32 nehe
  5. 1亿以内素数的个数_神奇的素数
  6. 华菱重卡仪表指示说明_仪表装置11种常见故障的解决方法
  7. mysql cascade|restrict|no action|set null__mysql 外键的几种约束
  8. iScroll4下表单元素聚焦及键盘的异常问题
  9. http请求状态码表示的意义
  10. Linux下如何从mysql数据库里导出导入数据
  11. Caffe编译 Mnist训练测试---基本参数学习
  12. 【三维路径规划】基于matlab A_star算法无人机三维路径规划【含Matlab源码 003期】
  13. LTE学习笔记五:LTE两种帧结构
  14. 特殊符号大全(无穷的想象力)
  15. EXCEL单元格换行操作
  16. 058 不定积分计算工具总结
  17. gcc: error trying to exec ‘cc1plus‘: execvp: no such file or directory
  18. 暴走的Polkadot (DOT)(上)
  19. 华为LOGO是什么意思?看完任正非的解释,再看网友的解释,笑出声
  20. The power of habits 1

热门文章

  1. 加速安装pytorch
  2. 怎么利用shell批量把图片改名(数字排序)
  3. [DEV] 陷阱技术探秘 ──动态汉化Windows技术的分析
  4. git远程分支强制覆盖本地分支
  5. 电脑会员管理系统怎么弄,电脑会员卡管理系统怎么弄
  6. 在服务器创建并进入虚拟环境
  7. zzulioj1008: 美元和人民币
  8. WinForm中使用Win10 蓝牙Ble Sdk
  9. node.js 腾讯镜像站_史上最强Sx05RE游戏整合镜像发布!FBA4ARM永久闭源并停止发布!...
  10. 计算机公共基础知识总结,记住 | 公共基础知识备考之电脑常识总结!