二叉树的遍历——Morris
在之前的博客中,博主讨论过二叉树的经典遍历算法,包括递归和常规非递归算法,其时间复杂度和空间复杂度均为O(n)。Morris算法巧妙地利用了二叉树的线索化思路,将二叉树的遍历算法的空间复杂度降低为O(1),时间复杂度仍然为O(n)。关于该算法的讨论在网上有很多,例如:http://www.cnblogs.com/AnnieKim/archive/2013/06/15/morristraversal.html,在这里,博主且讲讲自己的理解。各位看官可以结合本随笔和上面的帖子来加深对于Morris算法的理解。
1. 中序遍历
分析:中序遍历的基本顺序为leftTree, root,rightTree,而我们总是先接触到root节点,然后再接触leftTree和rightTree,用stack可以很方便地保存已接触到的节点,但这里却不能用!只能再考虑其他思路。这里,我们抓住中序遍历的本质:要想访问root节点,必须先访问其leftTree。但如果不借助stack,在访问了leftTree之后,又如何能再次访问root呢?二叉树的线索化就给了我们一个很好的思路。我们知道,二叉树中节点的空白指针有2n-(n-1)=n+1个,这便是一个可以利用的极好条件。
从图中可以看出,我们可以在leftTree中找到root节点在中序遍历下的前驱节点pre,将该前驱节点pre的右指针指向root节点,那么,下次在访问完leftTree之后,便能通过前驱节点pre回到root节点。由此,便能不间断访问完全部节点。以下见算法:
1 void inOrderTraverse(TreeNode *root){//Morris
2 TreeNode *cur = root;
3 TreeNode *pre = nullptr;
4 while(cur){
5 if(cur->left){
6 pre = cur->left;
7 while(pre->right && pre->right != cur){
8 pre = pre->right;
9 }
10 if(!pre->right){//first touch
11 pre->right = cur;//connect root
12 cur = cur->left;
13 }else{//second touch
14 pre->right = nullptr;//disconnect root
15 visit(cur);
16 cur = cur->right;
17 }
18 }else{
19 visit(cur);
20 cur = cur->right;
21 }
22 }
23 }
2. 前序遍历
前序遍历的思路和中序遍历的思路完全相似,只是访问时机的不同。在前序遍历中,root节点需要先访问到,然后再访问其leftTree和rightTree。如图:
1 void preOrderTraverse(TreeNode *root){//Morris
2 TreeNode *cur = root;
3 TreeNode *pre = nullptr;
4 while(cur){
5 if(cur->left){
6 pre = cur->left;
7 while(pre->right && pre->right != cur){
8 pre = pre->right;
9 }
10 if(!pre->right){//first touch
11 pre->right = cur;
12 visit(cur);//visit root
13 cur = cur->left;
14 }else{
15 pre->right = nullptr;
16 cur = cur->right;
17 }
18 }else{
19 visit(cur);//visit root
20 cur = cur->right;
21 }
22 }
23 }
3. 后续遍历
分析:在之前讨论二叉树的非递归后续遍历算法中,由于后续遍历(leftTree,rightTree, root)算法的独特性,root节点需要最后才能被访问。因此,如果先将rightTree的最后访问节点指向root,实现线索,那么leftTree将不好处理,因为leftTree要先于rightTree访问。因此,需要稍微调整一下思路。同样,利用上诉线索化的思路来改造原算法。先上图:
在了解完前序和中序遍历思路后,上图应该是很好懂的。F节点为root节点的前驱节点,第一次访问时需要将其right指针指向root节点,第二次时重新置空。在上图中,对于后续遍历,访问顺序为A->B->C->D->E->F->rightTree->root(无需区分节点和子树)。那么,思路便开始清晰起来,对于leftTree,先访问A,B,C,再从root节点的前驱节点pre逆向访问再root节点的左节点,便能正常实现逆序访问,这也是正是后续遍历的正确访问顺序。要实现从前驱节点pre到root->left的逆序访问,需要一点额外的操作,先将D->F视为单链表逆转,然后访问,最后再次逆转还原即可。最后,对于root节点和rightTee,需要增加一个额外的伪root节点,来实现和(root,leftTree)相同的结构,即(preRoot,tree)。以下见代码:
1 void reverseRightTreePath(TreeNode *from, TreeNode *to){
2 TreeNode *temp = nullptr;
3 TreeNode *next = from->right;
4 from->right = nullptr;
5 while(from != to){
6 temp = next->right;
7 next->right = from;
8 from = next;
9 next = temp;
10 }
11 }
12 void visitReverseRightTreePath(TreeNode *from, TreeNode *to){
13 reverseRightTreePath(from, to);
14 TreeNode *temp = to;
15 while(temp != from){
16 visit(temp);
17 temp = temp->right;
18 }
19 visit(temp);
20 reverseRightTreePath(to, from);
21 }
22 void postOrderTraverse(TreeNode *root){
23 TreeNode *preRoot = new TreeNode(0);
24 preRoot->left = root;
25 TreeNode *cur = preRoot;
26 TreeNode *pre = nullptr;
27
28 while(cur){
29 if(cur->left){
30 pre = cur->left;
31 while(pre->right && pre->right != cur){
32 pre = pre->right;
33 }
34 if(!pre->right){
35 pre->right = cur;
36 cur = cur->left;
37 }else{
38 visitReverseRightTreePath(cur->left, pre);//Only visit in the second touch
39 pre->right = nullptr;
40 cur = cur->right;
41 }
42 }else{
43 cur = cur->right;
44 }
45 }
46 }
二叉树的遍历——Morris的更多相关文章
- 二叉树的遍历(递归,迭代,Morris遍历)
二叉树的三种遍历方法: 先序,中序,后序,这三种遍历方式每一个都可以用递归,迭代,Morris三种形式实现,其中Morris效率最高,空间复杂度为O(1). 主要参考博客: 二叉树的遍历(递归,迭代, ...
- 二叉树的遍历(递归,迭代,Morris遍历)
二叉树的遍历: 先序,中序,后序: 二叉树的遍历有三种常见的方法, 最简单的实现就是递归调用, 另外就是飞递归的迭代调用, 最后还有O(1)空间的morris遍历: 二叉树的结构定义: struct ...
- 额外空间复杂度O(1) 的二叉树遍历 → Morris Traversal,你造吗?
开心一刻 一天,有个粉丝遇到感情方面的问题,找我出出主意 粉丝:我女朋友吧,就是先天有点病,听不到人说话,也说不了话,现在我家里人又给我介绍了一个,我该怎么办 我:这个问题很难去解释,我觉得一个人活着 ...
- Morris 遍历实现二叉树的遍历
Morris 遍历实现二叉树的遍历 作者:Grey 原文地址: 博客园:Morris 遍历实现二叉树的遍历 CSDN:Morris 遍历实现二叉树的遍历 说明 Morris 遍历可以实现二叉树的先,中 ...
- 二叉树遍历 Morris
二叉树的遍历,先根遍历,不适用递归,存储空间为 O(1) 转自:http://chuansongme.com/n/100461 MorrisInOrder(): while 没有结束 如果当前节点没有 ...
- C++ 二叉树深度优先遍历和广度优先遍历
二叉树的创建代码==>C++ 创建和遍历二叉树 深度优先遍历:是沿着树的深度遍历树的节点,尽可能深的搜索树的分支. //深度优先遍历二叉树void depthFirstSearch(Tree r ...
- [Leetcode] Binary tree level order traversal二叉树层次遍历
Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, ...
- 算法与数据结构(三) 二叉树的遍历及其线索化(Swift版)
前面两篇博客介绍了线性表的顺序存储与链式存储以及对应的操作,并且还聊了栈与队列的相关内容.本篇博客我们就继续聊数据结构的相关东西,并且所涉及的相关Demo依然使用面向对象语言Swift来表示.本篇博客 ...
- C++版 - 剑指Offer 面试题39:二叉树的深度(高度)(二叉树深度优先遍历dfs的应用) 题解
剑指Offer 面试题39:二叉树的深度(高度) 题目:输入一棵二叉树的根结点,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度.例如:输入二叉树 ...
随机推荐
- PHP判断客户端是PCweb端还是移动手机端方法
/** * * 根据php的$_SERVER['HTTP_USER_AGENT'] 中各种浏览器访问时所包含各个浏览器特定的字符串来判断是属于PC还是移动端 * @author discuz3x * ...
- Spring boot 执行jar文件 方式
项目jar包名wxo.jar 清理,打包,跳过测试(不测试) mvn clean package -Dmaven.test.skip=true 后台执行(默认环境) nohup java -jar w ...
- AS3 os与version 区别 使用Capabilities类获取Flash Player的信息
AS3中flash.system.Capabilities类提供诸多静态的只读属性来描述应用程序当前所运行在的系统和运行时信息,如Flash Player,Adobe AIR,Flash Lite.通 ...
- 基于OpenGL编写一个简易的2D渲染框架-02 搭建OpenGL环境
由于没有使用GLFW库,接下来得费一番功夫. 阅读这篇文章前请看一下这个网页:https://learnopengl-cn.github.io/01%20Getting%20started/02%20 ...
- 前端开发-2-HTML
1.开发环境 市面上有很多的HTML编辑器可以选择,常见的Hbuild.Sublime Text.Dreamweare都可以用来开发HTML. 当然PyCharm也支持HTML开发. 2.文件后缀名规 ...
- 【337】Text Mining Using Twitter Streaming API and Python
Reference: An Introduction to Text Mining using Twitter Streaming API and Python Reference: How to R ...
- Python使用Threading模块创建线程
使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法: #!/usr/bin/python # -*- coding: UTF-8 ...
- 用js来实现银行家算法
Number.prototype.round = function (len) { var old = this; var a1 = Math.pow(10, len) * old; a1 = Mat ...
- keras、 tensor flow 教程
[keras]http://keras-cn.readthedocs.io/en/latest/getting_started/concepts/,[tensorflow] http://blog.c ...
- CentOS6.5下安装ActiveMQ
1.下载ActiveMQ [root@localhost softwares]# wget http://archive.apache.org/dist/activemq/apache-activem ...