【转载】Morris遍历二叉树 & BST(二叉搜索树) Traverse & 空间O(1) 时间O(n)
- 因为做一道Leetcode的题目(前面博客有:link),需要用Space O(1)空间复杂度来中序遍历树,
- 看了Discuss,也上网搜了一下,发现空间O(1)可以用 Morris遍历的方法。方法介绍如下:
- http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html
其中,中序遍历和前序遍历比较方便解决:
- 通常,实现二叉树的前序(preorder)、中序(inorder)、后序(postorder)遍历有两个常用的方法:
一是递归(recursive),
二是使用栈实现的迭代版本(stack+iterative)。
这两种方法都是O(n)的空间复杂度(递归本身占用stack空间或者用户自定义的stack)。- Morris Traversal方法可以做到这两点,与前两种方法的不同在于该方法只需要O(1)空间,而且同样可以在O(n)时间内完成。
- 要使用O(1)空间进行遍历,最大的难点在于,遍历到子节点的时候怎样重新返回到父节点(假设节点中没有指向父节点的p指针),
由于不能用栈作为辅助空间。为了解决这个问题,
Morris方法用到了线索二叉树(threaded binary tree)的概念。
在Morris方法中不需要为每个节点额外分配指针指向其前驱(predecessor)和后继节点(successor),
只需要利用叶子节点中的左右空指针指向某种顺序遍历下的前驱节点或后继节点就可以了(实现中始终是右指针指向后继节点)。- 原版本,Morris只提供了中序遍历的方法,在中序遍历的基础上稍加修改可以实现前序,而后续就要再费点心思了。
中序遍历 步骤如下:
- 1. 如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
- 2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
- a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点(Cur)更新为当前节点的左孩子
注意(不是改变节点内容,而是把游标Cur继续行进到左孩子)。- b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。
- 3. 重复以上1、2直到当前节点(Cur)为空。
- 以下有图例,很容易理解。
关于时间复杂度,其实也是O(n)。分析如下:
- 空间复杂度:O(1),因为只用了两个辅助指针。
- 时间复杂度:O(n)。证明时间复杂度为O(n),最大的疑惑在于寻找中序遍历下二叉树中所有节点的前驱节点的时间复杂度是多少,即以下两行代码:
- 1 while (prev->right != NULL && prev->right != cur)
- 2 prev = prev->right;
- 直觉上,认为它的复杂度是O(nlgn),因为找单个节点的前驱节点与树的高度有关。但事实上,寻找所有节点的前驱节点只需要O(n)时间。
n个节点的二叉树中一共有n-1条边,整个过程中每条边最多只走2次,一次是为了定位到某个节点,另一次是为了寻找上面某个节点的前驱节点,
如下图所示,其中红色是为了定位到某个节点,黑色线是为了找到前驱节点。所以复杂度为O(n)。
前序遍历(相比中序遍历,只是输出当前节点的顺序稍有不同)
- 步骤:
- 1. 如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
- 2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
- a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。输出当前节点(在这里输出,这是与中序遍历唯一一点不同)。
当前节点更新为当前节点的左孩子。- b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。当前节点更新为当前节点的右孩子。
- 3. 重复以上1、2直到当前节点为空。
后序遍历(需要加入两个小的技巧,一是dump节点,二是倒序输出路径)
- 后续遍历稍显复杂,需要建立一个临时节点dump,令其左孩子是root。并且还需要一个子过程,就是倒序输出某两个节点之间路径上的各个节点。
- 步骤:
- 当前节点设置为临时节点dump。
- 1. 如果当前节点的左孩子为空,则将其右孩子作为当前节点。
- 2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
- a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
- b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。倒序输出从当前节点的左孩子到该前驱节点这条路径上的所有节点。
当前节点更新为当前节点的右孩子。- 3. 重复以上1、2直到当前节点为空。
复杂度分析:
空间复杂度同样是O(1);时间复杂度也是O(n),倒序输出过程只不过是加大了常数系数。
上面三种顺序(前序、中序、后序)遍历的方法,都可以在原文作者的github里面获取:github
- 总结:
- 以前只知道递归和栈+迭代实现二叉树遍历的方法,现在应该了解到有使用O(1)空间复杂度的方法。
【转载】Morris遍历二叉树 & BST(二叉搜索树) Traverse & 空间O(1) 时间O(n)的更多相关文章
- 第33题:LeetCode255 Verify Preorder Sequence in Binary Search Tree 验证先序遍历是否符合二叉搜索树
题目 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 考点 1.BST 二叉搜索树 2.递归 思路 1.后序 ...
- 数据结构中很常见的各种树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)
数据结构中常见的树(BST二叉搜索树.AVL平衡二叉树.RBT红黑树.B-树.B+树.B*树) 二叉排序树.平衡树.红黑树 红黑树----第四篇:一步一图一代码,一定要让你真正彻底明白红黑树 --- ...
- 二叉树系列 - 二叉搜索树 - [LeetCode] 中序遍历中利用 pre节点避免额外空间。题:Recover Binary Search Tree,Validate Binary Search Tree
二叉搜索树是常用的概念,它的定义如下: The left subtree of a node contains only nodes with keys less than the node's ke ...
- bst 二叉搜索树简单实现
//数组实现二叉树: // 1.下标为零的元素为根节点,没有父节点 // 2.节点i的左儿子是2*i+1:右儿子2*i+2:父节点(i-1)/2: // 3.下标i为奇数则该节点有有兄弟,否则又左兄弟 ...
- [LeetCode] Serialize and Deserialize BST 二叉搜索树的序列化和去序列化
Serialization is the process of converting a data structure or object into a sequence of bits so tha ...
- 数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)
树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: BST树 ...
- [LeetCode] Minimum Absolute Difference in BST 二叉搜索树的最小绝对差
Given a binary search tree with non-negative values, find the minimum absolute difference between va ...
- LeetCode #938. Range Sum of BST 二叉搜索树的范围和
https://leetcode-cn.com/problems/range-sum-of-bst/ 二叉树中序遍历 二叉搜索树性质:一个节点大于所有其左子树的节点,小于其所有右子树的节点 /** * ...
- 数据结构中的树(二叉树、二叉搜索树、AVL树)
数据结构动图展示网站 树的概念 树(英语:tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合.它是由n(n>=1)个有限节点组成一个具有 ...
随机推荐
- 高级php面试题及部分答案
在网上看到一些高级php 的面试题目.. 闲来无事,搞了一些答案...可能不是很全面,留这以后备用吧. 一. 基本知识点1.1 HTTP协议中几个状态码的含义:503 500 401 403 404 ...
- 多线程 1-pthread 和NSThread
一.基本内容介绍: 进程: 正在运行的程序就叫进程 每个进程之间是相互独立的,每个进程均运行在其专用且受保护的内存空间内. 线程: 在程序内工作的基本执行单元(每个进程至 ...
- JAVASCRIPT、ANDROID、C#分别实现普通日期转换多少小时前、多少分钟前、多少秒
貌似最近很流行这个,就写了个js函数实现之 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> ...
- js eval()执行传参函数的写法
.cs public class Message<T> { // 数据总数 public int? Total { get; set; } // 关键数据 public List<T ...
- 对于Oracle中分页排序查询语句执行效率的比较分析
转自:http://bbs.csdn.net/topics/370033478 对于Oracle中分页排序查询语句执行效率的比较分析 作者:lzgame 在工作中我们经常遇到需要在Oracle中进行分 ...
- Flash设置全屏后,放到网页中显示不正常
stage.displayState = StageDisplayState.FULL_SCREEN;//全屏,注意当设置全屏后,放到网页中显示不正常
- 1934: [Shoi2007]Vote 善意的投票 - BZOJ
Description幼儿园里有n个小朋友打算通过投票来决定睡不睡午觉.对他们来说,这个问题并不是很重要,于是他们决定发扬谦让精神.虽然每个人都有自己的主见,但是为了照顾一下自己朋友的想法,他们也可以 ...
- 使用HQL语句的按照参数名字查询数据库信息的时候 “=:”和参数之间不能存在空格,否则会报错
问题描述: 今天在使用HQL的按照参数的名字查询数据库信息的时候报错如下: org.hibernate.QueryException: Space is not allowed after param ...
- 【Asp.Net MVC】Avoid Mass Assignment in ASP.NET MVC
Mass Assignment Vulnerability in ASP.NET MVC: http://freshbrewedcode.com/joshbush/2012/03/05/mass-as ...
- 自定义nagios check_load告警阀值
自定义nagios check_load告警阀值 日期:2012-01-11 来源: heipark 分享至: - 默认check_load配置 define service{ use generi ...