二叉树的遍历——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:二叉树的深度(高度) 题目:输入一棵二叉树的根结点,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度.例如:输入二叉树 ...
随机推荐
- mysql开启查询日志功能
1.开启查询日志 https://www.cnblogs.com/kerrycode/p/7130403.html MYsql 查询日志配置 mysql> show variables ...
- 50. linux下查看tomcat日志
cd tomcat/logs/ tail -f catalina.out
- SQL2014还原到2008
请使用with move选项来标识该文件的有效位置 sqlserver用命令还原数据库 restore database TT from disk='E:\test.bak' ...
- 低版本eclipse离线集成svn步骤,亲测有效!!!
1.下载svn离线版的插件: 百度云盘链接:http://pan.baidu.com/s/1eSnMoHO 密码:6oef 2.解压出来的额目录如下: 3.将features和plugins里面的ja ...
- javascript知识点积累
8年javascript知识点积累 08年毕业就开始接触javascript,当时是做asp.net发现很多功能用asp.net控件解决不了,比如checkbox单选,全选问题,自动计算总价问题, ...
- ubuntu查询可用安装包
当使用apt-get install packages时,如果不知道安装包的具体名称,可以使用关键字进行搜索,使用:apt-cache search keywords
- delphi修改QQ快捷方式的目标地址达到在启动QQ的同时也能运行自己想要启动的EXE可执行文件
delphi修改QQ快捷方式的目标地址达到在启动QQ的同时也能运行自己想要启动的EXE可执行文件. 直接上代码,自已体会 !! Unit1.pas代码如下: unit Unit1; interface ...
- docker上传镜像
已经存在镜像 docker tag conductor:ui docker.io/mhcvs2/mhc docker push docker.io/mhcvs2/mhc
- Centos7 配置ssh 免秘钥登陆
1.yum install -y openssh 2.servier1: ssh-keygen -t rsa #有提示的直接enter 3.server 2: ssh-keygen -t rsa # ...
- chrome 调试参数(鼠标事件)
1.监听鼠标事件: monitorEvents(document.body, 'mouse') 取消监听: unmonitorEvents(document.body) 原文链接: https://b ...