【转载】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)个有限节点组成一个具有 ...
随机推荐
- UML 小结(2)- 理论理解
什么是UML: UML是统一建模语言(UML是 Unified Modeling Language的缩写)是用来对软件密集系统进行可视化建模的一种语言. UML为面向对象开发系统的产品进行说明.可视化 ...
- Asp.Net生命周期系列五
如果您看了我的前四篇文章,应该知道目前Http请求已经流到了HttpModule这个程序员手中了,而且我们可以注册自己的HttpModule并且可以在里面注册一些事件来控制这个Http请求,但是到目前 ...
- iOS xcode 8 注释快捷键
单行注释:在方法的地方按 Command+/ 标注的功能,快捷键是Command + Option + / 需要在方法名的上面(空白)的地方按 Command + Option + / 才管 ...
- HTML & XML 转义字符
HTML & XML 转义字符 HTML中<, >,&等有特殊含义,(前两个字符用于链接签,&用于转义),不能直接使用.使用这三个字符时,应使用它们的转义序列,如下 ...
- 【转】android 内存泄漏相关收藏博客。
关于android内存泄漏的研究 博客建了几个月,都没有去写,一是因为当时换工作,然后又是新入职(你懂的,好好表现),比较忙:二是也因为自己没有写博客的习惯了.现在还算是比较稳定了,加上这个迭代基 ...
- VS2010常用插件介绍
今天在写JS时,写到500多行时,感觉代码已经很难看了.想到C#代码都有折叠功能,是不是JS也有呢.在选项中找了一下,没有相关了的设置功能,于是就上网找.一找可就不得了,发现了好多好用的插件.都可以在 ...
- c++ 原子操作
转载自: http://blog.csdn.net/yockie/article/details/8838686 所谓的原子操作,取的就是“原子是最小的.不可分割的最小个体”的意义,它表示在多个线程访 ...
- [nowCoder] 局部最小值位置
定义局部最小的概念.arr长度为1时,arr[0]是局部最小.arr的长度为N(N>1)时,如果arr[0]<arr[1],那么arr[0]是局部最小:如果arr[N-1]<arr[ ...
- 多线程Java Socket编程示例(转)
这篇做为学习孙卫琴<<Java网络编程精解>>的学习笔记吧.其中采用Java 5的ExecutorService来进行线程池的方式实现多线程,模拟客户端多用户向同一服务器端发送 ...
- 【Android自学之旅】 目录
[Android自学之旅] 目录 [Android自学之旅] Android开发环境的搭建