Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)——无非是在传统遍历过程中修改叶子结点加入后继结点信息(传统是stack记录),然后再删除恢复
-
先看看线索二叉树

概念
本文主要解决一个问题,如何实现二叉树的前中后序遍历,有两个要求:
1. O(1)空间复杂度,即只能使用常数空间;
2. 二叉树的形状不能被破坏(中间过程允许改变其形状)。
通常,实现二叉树的前序(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只提供了中序遍历的方法,在中序遍历的基础上稍加修改可以实现前序,而后续就要再费点心思了。所以先从中序开始介绍。
首先定义在这篇文章中使用的二叉树节点结构,即由val,left和right组成:
1 struct TreeNode {
2 int val;
3 TreeNode *left;
4 TreeNode *right;
5 TreeNode(int x) : val(x), left(NULL), right(NULL) {}
6 };
一、中序遍历--cur结点为遍历结点路径,在遍历过程中找到其前驱结点,加入后继信息,如果发现后继信息已经加入,说明当前结点是第二次访问了。这和传统的stack方式本质上一样,因为结点也会二次访问,第一次是在入stack,第二次是pop stack!
步骤:
1. 如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。——无前驱结点!
2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。——有前驱则找前驱结点!
a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。——后继信息还没有加入,则加入后继信息!然后遍历顺序为left结点。
b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。——后继信息已经加入,删除原有后继信息,然后遍历顺序为right结点。
3. 重复以上1、2直到当前节点为空。
图示:
下图为每一步迭代的结果(从左至右,从上到下),cur代表当前节点,深色节点表示该节点已输出。

代码:

1 void inorderMorrisTraversal(TreeNode *root) {
2 TreeNode *cur = root, *prev = NULL;
3 while (cur != NULL)
4 {
5 if (cur->left == NULL) // 1.
6 {
7 printf("%d ", cur->val);
8 cur = cur->right;
9 }
10 else
11 {
12 // find predecessor
13 prev = cur->left;
14 while (prev->right != NULL && prev->right != cur)
15 prev = prev->right;
16
17 if (prev->right == NULL) // 2.a)
18 {
19 prev->right = cur;
20 cur = cur->left;
21 }
22 else // 2.b)
23 {
24 prev->right = NULL;
25 printf("%d ", cur->val);
26 cur = cur->right;
27 }
28 }
29 }
30 }

复杂度分析:
空间复杂度: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)。

先序遍历的差异代码就一行:
void preorderMorrisTraversal(TreeNode *root) {
TreeNode *cur = root, *prev = NULL;
while (cur != NULL)
{
if (cur->left == NULL)
{
printf("%d ", cur->val);
cur = cur->right;
}
else
{
prev = cur->left;
while (prev->right != NULL && prev->right != cur)
prev = prev->right;
if (prev->right == NULL)
{
printf("%d ", cur->val); // the only difference with inorder-traversal
prev->right = cur;
cur = cur->left;
}
else
{
prev->right = NULL;
cur = cur->right;
}
}
}
}
Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)——无非是在传统遍历过程中修改叶子结点加入后继结点信息(传统是stack记录),然后再删除恢复的更多相关文章
- [转载]Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)
本文主要解决一个问题,如何实现二叉树的前中后序遍历,有两个要求: 1. O(1)空间复杂度,即只能使用常数空间: 2. 二叉树的形状不能被破坏(中间过程允许改变其形状). 通常,实现二叉树的前序(pr ...
- Morris Traversal 方法遍历二叉树(非递归、不用栈,O(1)空间)
http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html
- Morris Traversal方法遍历
实现二叉树的遍历且只需要O(1)的空间. 参考:http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html
- 【LeetCode-面试算法经典-Java实现】【145-Binary Tree Postorder Traversal(二叉树非递归后序遍历)】
[145-Binary Tree Postorder Traversal(二叉树非递归后序遍历)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a bin ...
- 【LeetCode-面试算法经典-Java实现】【144-Binary Tree Preorder Traversal(二叉树非递归前序遍历)】
[144-Binary Tree Preorder Traversal(二叉树非递归前序遍历)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Given a bina ...
- c/c++二叉树的创建与遍历(非递归遍历左右中,破坏树结构)
二叉树的创建与遍历(非递归遍历左右中,破坏树结构) 创建 二叉树的递归3种遍历方式: 1,先中心,再左树,再右树 2,先左树,再中心,再右树 3,先左树,再右树,再中心 二叉树的非递归4种遍历方式: ...
- c/c++叉树的创建与遍历(非递归遍历左右中,不破坏树结构)
二叉树的创建与遍历(非递归遍历左右中,不破坏树结构) 创建 二叉树的递归3种遍历方式: 1,先中心,再左树,再右树 2,先左树,再中心,再右树 3,先左树,再右树,再中心 二叉树的非递归4种遍历方式: ...
- C++版 - LeetCode 144. Binary Tree Preorder Traversal (二叉树先根序遍历,非递归)
144. Binary Tree Preorder Traversal Difficulty: Medium Given a binary tree, return the preorder trav ...
- C++编程练习(17)----“二叉树非递归遍历的实现“
二叉树的非递归遍历 最近看书上说道要掌握二叉树遍历的6种编写方式,之前只用递归方式编写过,这次就用非递归方式编写试一试. C++编程练习(8)----“二叉树的建立以及二叉树的三种遍历方式“(前序遍历 ...
随机推荐
- HA架构
HA架构是个什么东西? 阅读文章:浅谈web应用的负载均衡.集群.高可用(HA)解决方案
- 【贪心】HDU 最少拦截系统
https://vjudge.net/contest/68966#problem/I [题解] http://www.cnblogs.com/kuangbin/archive/2012/08/03/2 ...
- android去除标题栏和状态栏(全屏)
转--http://www.eoeandroid.com/thread-66555-1-1.html 在开发中我们经常需要把我们的应用设置为全屏,这里我所知道的有俩中方法,一中是在代码中设置,另一种方 ...
- 【POJ1149&BZOJ1280】PIGS(最大流)
题意:Emmy在一个养猪场工作.这个养猪场有M个锁着的猪圈,但Emmy并没有钥匙. 顾客会到养猪场来买猪,一个接着一个.每一位顾客都会有一些猪圈的钥匙,他们会将这些猪圈打开并买走固定数目的猪. 所有顾 ...
- HTTP请求示例
HTTP请求格式 当浏览器向Web服务器发出请求时,它向服务器传递了一个数据块,也就是请求信息,HTTP请求信息由3部分组成: l 请求方法URI协议/版本 l 请求头(Request Hea ...
- Linux下增加User及添加sudo权限
运行adduser username 会默认建立同名的user,group,同时会要求输入用户密码及一些属性,完成之后OK. sudo chmod +w /etc/sudoers vi /etc/su ...
- java类中资源加载顺序
根据优先级别从高到低依次为:1.父类中的静态代码块(static);2.自身的静态代码块;3.父类中的的普通代码块;4.父类的构造方法;5.自身的普通代码块;6.自身的构造方法; 下面是一个测试 结果 ...
- 第三方APP集成微信登陆功能详解
授权后接口调用(UnionID) 通过code获取access_token 接口说明 通过code获取access_token的接口. 请求说明 http请求方式: GET https://api.w ...
- tomcat配置访问项目时不需要加项目名称
原文:http://blog.csdn.net/coolcoffee168/article/details/52582770 java web部署后,访问项目的时候,需要在地址中添加项目名称,那么如何 ...
- Angular结构简单介绍
在当前项目目录下,使用Angular-CLI生成一个组件:heroes ng generate component heroes 主界面(也就是一个主模块+多个单模块,例如我们创建的heroes(单模 ...