二叉树的前中后序递归和非递归遍历操作【代码】
“遍历”是二叉树各种操作的基础,可以在遍历过程中对节点进行各种操作,如:求节点的双亲,求节点的孩子,判断节点的层次。当然,还有一些更重要的操作,例如,依据遍历序列建立二叉树,对建立的二叉树进行线索化,等等。
二叉树的各种遍历操作必须了然于心,无论是递归的,还是非递归的。递归算法的优点是形式简单,当然,正如一句话所说“迭代是人,递归是神”。递归算法的整个详细过程还是很烧脑的,我们只有完全一遍调试下来才能懂它具体在干嘛。递归每一步都把未知当作已知,每一步又在把未知变为已知。想不到描述起来又用上了递归的概念,生活中其实有很多递归的现象,我印象最深的就是两面面对面放置的镜子,你永远也找不到最深处的自己(当然我们假设你的视力无限好同时光在反射过程中没有亮度损失),也就是说这个递归过程没有边界条件,会无限递归,永远也不会返回,这在程序中可就是死循环呀,相当可怕,CPU很快就会被掏空。所以每次设计递归算法时,首先要考虑到的就是边界返回条件。还有一个我印象很深的电影,《盗梦空间》,三层梦境,从现实中进入第一层,再从第一层梦境进入第二层,最后再从第二层进入第三层。当然电影里也说了可以无限往下递归,进入一个叫迷失域的地方,不过好在电影的设定是无论你掉入了多深的梦,你只要在梦里死了,就能返回上层梦境,也就是说,每层梦境都有一个返回条件,所以原则上是不会死循环的。。。
哎呀,扯太远了,其实我一直想写一篇关于递归的哲学文章,阐述程序和我们现实的联系,算了,不扯了。
递归形式上很简单,代码很简洁,然后时间复杂度却很大,同时还得花费一定的空间复杂度(递归栈深度),比如经典的Hanoi问题,时间复杂度是O(2^n),n为圆盘的个数,时间复杂度膨胀的很厉害。所以我们在很多时候不能依赖于递归算法,而需要去寻找相应的非递归算法,当然并非所有问题都能找到非递归算法,即便能,那估计也复杂到没人能读懂。而非递归算法形式上就比较复杂,不过它其实更接近人的思维过程,虽然过程也不简单,但毕竟代码不涉及递归过程的一层一层的概念,不存在利用未知信息求解的过程。所以只要把代码表面的逻辑弄懂了就是懂了。
在二叉树的问题上,由于树的结构很有规律,所以用递归算法形式上会比非递归简单很多,也不用去考虑很多细节问题,只要把边界条件想好就行。
下面我把前序,中序,后序遍历的递归算法和非递归算法代码给出,其实都不长,要经常复习,思考这几个过程,以达到熟练于心的程度。
1 //以下三种递归算法均对每个节点只访问一次,故时间复杂度都是O(n) 2 //递归工作栈的深度恰好是树的深度,所以最坏的情况下空间复杂度为O(n) 3 4 //递归前序遍历二叉树 5 int PreOrderTraverse_Recursive(BiPtr T) 6 { 7 if (T) 8 { 9 cout << T->data << " "; 10 PreOrderTraverse_Recursive(T->lchild); 11 PreOrderTraverse_Recursive(T->rchild); 12 } 13 return 0; 14 } 15 16 //递归中序遍历二叉树 17 int InOrderTraverse_Recursive(BiPtr T) 18 { 19 if (T) 20 { 21 InOrderTraverse_Recursive(T->lchild); 22 cout << T->data << " "; 23 InOrderTraverse_Recursive(T->rchild); 24 } 25 return 0; 26 } 27 28 //递归后序遍历二叉树 29 int PostOrderTraverse_Recursive(BiPtr T) 30 { 31 if (T) 32 { 33 PostOrderTraverse_Recursive(T->lchild); 34 PostOrderTraverse_Recursive(T->rchild); 35 cout << T->data << " "; 36 } 37 return 0; 38 }
从以上我们可以看出递归算法的简洁,而且我们发现三种顺序的遍历方式只影响了其中两行代码的顺序而已,三种递归算法形式上非常的一致和统一,这正是递归的魅力所在。
我们再看看非递归算法
1 //非递归前序遍历二叉树 2 int PreOrderTraverse_NonRecursive(BiPtr T) 3 { 4 BiPtr p = T; 5 SqStack S; 6 InitStack(S); 7 8 while (p || !EmptyStack(S)) 9 { 10 while (p) 11 { 12 cout << p->data << " "; 13 Push(S, p); 14 p = p->lchild; 15 } 16 if (!EmptyStack(S)) 17 { 18 Pop(S, p);//刚刚访问过的根节点 19 p = p->rchild;//访问该根节点的右子树,并对右子树重复上述过程 20 } 21 } 22 return 0; 23 }
1 //非递归中序遍历二叉树 方法1 2 int InOrderTraverse_NonRecursive_1(BiPtr T) 3 { 4 BiPtr p = T; 5 SqStack S; 6 InitStack(S); 7 8 Push(S, T); 9 while (!EmptyStack(S)) 10 { 11 while (GetTop(S)) 12 { 13 Push(S, p->lchild); 14 p = p->lchild;//不停地“向左下走” 15 } 16 Pop(S, p);//前面会“走过头”,也就是说最后栈顶会多出一个空指针,所以弹出多入栈的空指针 17 18 if (!EmptyStack(S)) 19 { 20 Pop(S, p); 21 cout << p->data << " "; 22 Push(S, p->rchild); 23 p = p->rchild; 24 } 25 } 26 return 0; 27 } 28 29 //非递归中序遍历二叉树 方法2 30 int InOrderTraverse_NonRecursive_2(BiPtr T) 31 { 32 BiPtr p = T;//从树根开始 33 SqStack S; 34 InitStack(S); 35 36 while (p || !EmptyStack(S))//还有未访问的结点 37 { 38 if (p)//p向左下走到底并记录下沿途的节点 39 { 40 Push(S, p); 41 p = p->lchild; 42 } 43 else//p走到了底,再依次弹出刚才路过却没有访问的节点,访问之,然后p向右走 44 { 45 Pop(S, p); 46 cout << p->data << " "; 47 p = p->rchild; 48 } 49 } 50 return 0; 51 }
1 //非递归后序遍历二叉树 2 /* 3 后序遍历是先访问左子树再访问右子树,最后访问根节点 4 由于返回根节点时有可能时从左子树返回的,也可能是从右子树返回的, 5 要区分这两种情况需借助辅助指针r,其指向最近访问过的节点 6 */ 7 int PostOrderTraverse_NonRecursive(BiPtr T) 8 { 9 BiPtr p = T,r = nullptr;//从树根开始,r用于指向最近访问过的节点 10 SqStack S; 11 InitStack(S); 12 13 while (p || !EmptyStack(S)) 14 { 15 if (p) 16 { 17 Push(S, p); 18 p = p->lchild;//走到最左下方 19 } 20 else//说明已经走到最左下了,要返回到刚刚经过的根节点,即从null的左孩子返回到它的双亲节点 21 { 22 p = GetTop(S); 23 if (p->rchild && p->rchild != r)//右孩子尚未访问,且右孩子不为空,于是,访问右孩子 24 { 25 p = p->rchild; 26 Push(S, p); 27 p = p->lchild;//走到右孩子的最左下方,即重复最开始的过程 28 } 29 else//右孩子为空,则直接输出该节点,或者右孩子刚刚被访问过了,同样直接输出该节点 30 { 31 Pop(S, p); 32 cout << p->data << " "; 33 r = p;//r指向刚刚访问过的节点 34 p = nullptr;//左右子树都已经处理完,根节点也刚处理完,返回到根节点的双亲,即栈顶元素,下次重新取栈顶元素分析 35 } 36 } 37 } 38 return 0; 39 }
从以上看出,三种顺序的非递归算法基本完全不同,因为非递归算法其实本质上是把我们人类的思维过程用一些循环判读语句表达出来了而已,人类的思维自然没有神那么高大上,所以自然得一步一步,正如我们自己在纸上思考的那样,还得考虑每一个细节。
下面我再把其他一些非关键代码也贴出来(其实我之前发的文章中已经有了,这篇文章知识做一个总结,因为最近在复习数据结构,方便以后自己忘了复习)
建立二叉树
1 //默认按前序遍历的顺序输入,尾结点用#表示 2 int Create_BiTree(BiPtr& T) 3 { 4 ElemType c; 5 //cout << "请输入当前节点元素值:" << endl; 6 cin >> c; 7 if (c == '#') T = NULL; 8 else 9 { 10 T = new BiNode;//新建一个节点 11 T->data = c; 12 Create_BiTree(T->lchild); 13 Create_BiTree(T->rchild); 14 } 15 return 0; 16 }
栈结构定义
1 //--------------------------------栈结构---------------------------------- 2 int InitStack(SqStack &S) 3 { 4 S.base = (BiPtr *)malloc(STACK_INIT_SIZE * sizeof(BiPtr)); 5 if (!S.base) 6 { 7 cout << "分配空间失败!"; 8 exit(-1); 9 } 10 S.top = S.base; 11 S.stacksize = STACK_INIT_SIZE; 12 return 0; 13 } 14 15 int Push(SqStack &S, BiPtr e) 16 { 17 if ((S.top - S.base) >= STACK_INIT_SIZE) 18 { 19 //空间不足时重新分配空间,并将原来的元素拷贝到新分配的空间中去 20 S.base = (BiPtr *)realloc(S.base, (STACK_INIT_SIZE + STACKINCREASE) * sizeof(BiPtr)); 21 if (!S.base) 22 { 23 cout << "分配空间失败!"; 24 exit(-1); 25 } 26 S.top = S.base + STACK_INIT_SIZE; 27 S.stacksize = STACK_INIT_SIZE + STACKINCREASE; 28 } 29 *(S.top) = e;//结构体 30 S.top++;//先插入元素,再后移指针,始终保证top指向栈顶元素下一个位置 31 return 0; 32 } 33 34 int Pop(SqStack &S, BiPtr &e) 35 { 36 if (S.base == S.top) 37 { 38 cout << "栈为空!"; 39 exit(0); 40 } 41 S.top--; 42 e = *(S.top); 43 return 0; 44 } 45 46 BiPtr GetTop(SqStack &S) 47 { 48 if (S.base == S.top) 49 { 50 cout << "栈为空!"; 51 return 0; 52 } 53 else return *(S.top - 1);//返回一个指针 54 } 55 56 57 int EmptyStack(SqStack &S) 58 { 59 if (S.base == S.top) return 1;//stack is empty! 60 else return 0;//stack is not empty! 61 }
结构体定义
1 #define STACK_INIT_SIZE 100 2 #define STACKINCREASE 10 3 4 typedef char ElemType; 5 typedef struct BiNode 6 { 7 ElemType data; 8 struct BiNode *lchild, *rchild; 9 }BiNode, *BiPtr; 10 11 typedef struct 12 { 13 BiPtr *base;//始终处于栈底位置 14 BiPtr *top;//始终处于栈定元素的下一个位置,当top == base时,栈为空;当base == NULL时,栈不存在 15 int stacksize;//栈当前可使用的最大容量 16 }SqStack;
转载于:https://www.cnblogs.com/journal-of-xjx/p/9315155.html
二叉树的前中后序递归和非递归遍历操作【代码】相关推荐
- 数据结构之二叉树的前中后序遍历以及层序遍历
学习目标:读完这篇博客搞定二叉树的前中后序以及层序遍历 首先:你应该明白什么是二叉树,下面这幅图就是一个完全二叉树 其实所谓的二叉树就是一个节点有小于等于二个分支的树,可以没有分支,可以有1条分支,可 ...
- 数据结构与算法(java):树-二叉树(二叉查找树(BST)、线索化二叉树、哈夫曼树、平衡二叉树【AVL】、二叉树的前中后序遍历)
二叉树 1.定义 二叉树 就是度不超过2的树(每个结点最多只有两个子结点).如图 2.特殊二叉树 满二叉树 当二叉树的每一个层的结点树都达到最大值,则这个二叉树就是满二叉树. 完全二叉树 叶结点只能出 ...
- 二叉树的前,中,后序遍历(思路分析) [Java][数据结构]
二叉树的前,中,后序遍历(思路分析) 前序遍历: 先输出父节点, 再遍历左子树和右子树 中序遍历: 先遍历左子树, 再输出父节点,再遍历右子树 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点 ...
- 深入理解二叉树的前中后序
一.问题 二叉树的前中后序遍历到底是什么,仅仅是三个顺序不同的 List 吗?为什么多叉树没有中序遍历? 二.遍历框架 所谓二叉树遍历是按某种特定规则,依次对二叉树中的节点进行相应的操作,并且每个节点 ...
- Java二叉树的前中后序遍历
Java二叉树的前中后序遍历 1.前序遍历 1.1前序遍历概念 1.2前序遍历习题 2.中序遍历 2.1中序遍历概念 2.2中序遍历习题 3.后续遍历 3.1后序遍历概念 3.2后序遍历习题 大家好, ...
- 【Java数据结构】二叉树的前中后序遍历(递归和非递归)
二叉树的遍历 递归做法 前序遍历 中序遍历 后序遍历 非递归 前序遍历 中序遍历 后序遍历 二叉树遍历是二叉树的一种重要操作 必须要掌握 二叉树的遍历可以用递归和非递归两种做法来实现 递归做法 前序遍 ...
- 二叉树的前中后序遍历之迭代法(非统一风格迭代方式)
文章目录 前言 一.前序遍历(迭代法) 二.中序遍历(迭代法) 三.后序遍历(迭代法) 总结 前言 「递归的实现就是:每一次递归调用都会把函数的局部变量.参数值和返回地址等压入调用栈中」,然后递归返回 ...
- 二叉树的前中后序遍历(考试常考)
二叉树遍历的概念 二叉树的遍历是按某种规则对二叉树的每个节点均只被访问一次,根据根节点访问位置的不同分为三种:先序遍历(根左右).中序遍历(左根右).后序遍历(左右根). 由于树是通过 ...
- 二叉树(前中后序递归非递归遍历,层次遍历
#ifndef _BITREE_H #define _BITREE_H #include "Stack.h" #include "Queue.h" te ...
最新文章
- 16s扩增子分析注意事项和经验总结Tips
- POJ 3420 Quad Tiling
- hdu 2602 Bone Collector 01背包
- Springboot 集成Springcloud gateway的入门
- C#switch语句简单测试
- wince2秒快速启动TOC分析
- React 源码剖析系列 - 解密 setState
- Leetcode —— 面试题 04.02. 最小高度树(Python)
- 教你怎样做项目开发总结报告[转]
- python123反素数_初学python之路-day01
- 小福利,excel的常用高阶函数介绍
- office2020与2016版的不同_Office 2016 各版本之间不同(要使用Access 2016必须安装Office 2016专业版)...
- Elasticsearch教程(19) 详解mapping之keyword
- 15年来 那些值得永远铭记的硬件
- 卸载计算机更新程序包,KB4343669更新包无法卸载的解决方案
- 微信公众号jsapi支付
- kotlin入门,Android快速转战Kotlin教程,重难点整理
- 《天赋》:第一章 天赋
- 书生云10亿元超融合大单的背后
- 静态HTML网页设计作品 DIV布局家乡介绍网页模板代码---(太原 10页带本地存储登录注册 js表单校验)
热门文章
- python自学多久可以找到工作-零基础如何自学Python并且找到工作,其实也就这3点,4点...
- python初学者可以做的金融小程序-Python入门 —— 用pycharm写一个简单的小程序3...
- python入门经典例题-Python入门经典练习题
- python3.8.2安装教程-在服务器上安装python3.8.2环境的教程详解
- python urllib.request 爬虫 数据处理-使用Python3.5写简单网络爬虫
- python科学计算基础教程pdf下载-用Python做科学计算 高清晰PDF
- python怎么安装matplotlib-如何安装Python绘图库Matplotlib?
- python浪漫代码-使用Python代码的程序员也浪漫
- python使用教程cmd啥意思-Python 中的cmd模块学习
- 学python编程-趣学python编程中文版 PDF 下载