内容简介:递归(recursion)就是子程序(或函数)直接调用自己或通过一系列调用语句间接调用自己。该执行过程要求每一次节点中断调用其他方法,必须记录下该中断节点的环境信息,作用是为了调用结束返回结果之后原程序能够从上次中断位置起继续执行,在计算机中,通过一个但递归算法的执行效率较低,这是因为递归需要反复入栈,时间和空间的开销都比较大。(从本质上来看,递归其实是方便了程序员却难为了机器)
先序遍历 | 中序遍历 | 后序遍历 |
---|---|---|
根结点-左子树-右子树 | 左子树-根子树-右子树 | 左子树-右子树-根结点 |
递归实现:
//先序遍历 void preOrder(Btree T){ if(T){ putchar(T->data); preOrder(T->lchild); preOrder(T->rchild); } } //中序遍历 void inOrder(Btree T){ if(T){ inOrder(T->lchild); putchar(T->data); inOrder(T->rchild); } } //后序遍历 void postOrder(Btree T){ if(T){ postOrder(T->lchild); postOrder(T->rchild); putchar(T->data); } } 复制代码
非递归实现:
基于栈的递归消除:
递归(recursion)就是子程序(或函数)直接调用自己或通过一系列调用语句间接调用自己。该执行过程要求每一次节点中断调用其他方法,必须记录下该中断节点的环境信息,作用是为了调用结束返回结果之后原程序能够从上次中断位置起继续执行,在计算机中,通过一个 栈 结构来实现该效果(栈:先进后出):
但递归算法的执行效率较低,这是因为递归需要反复入栈,时间和空间的开销都比较大。
(从本质上来看,递归其实是方便了 程序员 却难为了机器)
为了避免这种开销,需要消除递归,方法主要有两种:
1.针对简单递归,利用迭代代替递归;
2. 利用栈的方式实现:利用人工栈模拟系统堆栈 。(这种方法通用性强,但是本质上还是递归的,区别是将计算机做的事情改由人工来完成)
利用栈实现的非递归过程可分为 如下步骤 :
- 设置一个工作栈,用于保存递归工作记录,包括实参、返回地址等;
- 将调用函数传递而来的参数和返回地址入栈;
- 利用循环模拟递归分解过程,逐层将递归过程的参数和返回地址入栈。当满足递归结束条件时,依次逐层退栈,并将结果返回给上一层,直至栈空为止。
非递归遍历二叉树:
先序遍历:根结点-左子树-右子树
算法思想:
从二叉树的根结点开始,将根结点设为当前结点p,执行以下操作:
- 边遍历边打印,并存入栈中(一开始当前结点p为根结点)→先打印当前结点p的数据→再将当前结点p入栈→若左孩子存在,将左孩子设为当前结点→重复执行此操作,直至到达二叉树左子树最左下角;
- 栈顶元素出栈,将栈顶元素设为当前结点p→若当前结点p的右孩子存在,将右孩子设为当前结点p(进入右子树)→回到步骤1,重复执行 1、2,直至栈空。
void preOrder(Btree T){ Bnode *p=T; Bnode *stack[MaxSize]; int top=0; if(T==NULL){ return; } while(p!=NULL||top>0){ //边遍历边打印,并存入栈中,以后需要借助这些根节点进入右子树 while(p!=NULL){ putchar(p->data); stack[top++]=p; p=p->lchild; } //当p为空时,说明根和左子树都遍历完了,该进入右子树了 if(top>0){ p=stack[--top]; p=p->rchild; } /* //另一种写法 if(p!=NULL){ putchar(p->data); stack[top++]=p; p=p->lchild; }else{ p=stack[--top]; p=p->rchild; } */ } } 复制代码
中序遍历:左子树-根子树-右子树
算法思想:
从二叉树的根结点开始,将根结点设为当前结点p,执行以下操作:
- 当前结点p入栈(一开始当前结点p为根结点)→若当前结点p存在左孩子→将左孩子设为当前结点p→重复此操作,直至到达二叉树左子树最左下角;
- 将栈顶元素出栈,将出栈元素设为当前结点p→打印该结点p的数据;
- 若当前结点p存在右孩子,则将右孩子设为当前 结点p→回到步骤1,重复执行 1、2、3,直至栈空。
void inOrder(Btree T){ Bnode *p=T; Bnode *stack[MaxSize]; int top=0; if(T==NULL){ return; } while(p!=NULL||top>0){ //重复将当前结点的左孩子入栈 while(p!=NULL){ stack[top++]=p; p=p->lchild; } //出栈 if(top>0){ p=stack[--top]; putchar(p->data); p=p->rchild; } /* if(p!=NULL){ stack[top++]=p; p=p->lchild; }else{ p=stack[--top]; putchar(p->data); p=p->rchild; } */ } } 复制代码
后序遍历:左子树-右子树-根结点
算法思想:
声明两个结点指针(用于临时存储):当前结点-pCur;上一次访问的结点-pLast,将根结点设为当前结点pCur:
-
当前结点入栈(一开始当前结点为根结点)→若当前结点存在左孩子→将左孩子设为当前结点→重复此操作,直至到达二叉树左子树最左下角;
-
取栈顶元素,并设为当前结点pCur:
- 若当前结点pCur没有右孩子or右孩子刚刚访问过了(上一次访问的结点pLast就是右孩子)→栈顶元素出栈,打印当前结点pCur的数据→将当前结点pCur赋给pLast→将当前结点置空;
- 若当前结点pCur有右孩子→将右孩子设为当前结点(遍历右子树)→回到步骤1,重复执行 1、2,直至栈空。
void postOrder(Btree T){ Bnode *pCur=T,*pLast;//pCur为当前结点,pLast为上一次访问的结点 Bnode *stack[MaxSize];//定义栈用来存放结点的指针 int top=0;//栈顶指针 if(T==NULL){ return; } while(pCur!=NULL||top>0){ //重复将当前结点的左孩子入栈,直到把pCur移动到左子树最下边 while(pCur!=NULL){ stack[top++]=pCur; pCur=pCur->lchild; } if(top>0){ pCur=stack[top-1];//取栈顶元素 if(pCur->rchild==NULL||pCur->rchild==pLast){//若当前结点没有右孩子or右孩子刚刚访问过了 putchar(pCur->data);//打印当前结点 pLast=pCur;//将当前结点赋给pLast pCur=NULL;//将当前结点置空 top--;//栈顶元素出栈 }else{ pCur=pCur->rchild; } } /* if(pCur!=NULL){ stack[top++]=pCur; pCur=pCur->lchild; }else{ pCur=stack[top-1];//取栈顶元素 if(pCur->rchild==NULL||pCur->rchild==pLast){//若当前结点没有右孩子or右孩子刚刚访问过了 putchar(pCur->data);//打印当前结点 pLast=pCur;//将当前结点赋给pLast pCur=NULL; top--; }else{ pCur=pCur->rchild; } } */ } } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- MySQL 实现树形的遍历
- flex 遍历Object对象内容的实现代码
- 【PHP 实现数据结构】遍历二叉查找树
- python 实现二叉树的深度&&广度优先遍历
- go实现区块链[3]-遍历区块链与数据库持久化
- 数组常见的遍历循环方法、数组的循环遍历的效率对比
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web开发秘方
Brian P. Hogan、Chris Warren、Mike Weber、Chris Johnson、Aaron Godin / 七印部落 / 华中科技大学出版社 / 2013-7-10 / 66.00元
猜猜硅谷的前端工程师怎么折腾JS的?想知道无限下拉的列表怎么做吗?你知道DropBox可以当Web服务器用吗?你知道怎么做出跨平台的幻灯片效果吗?不借助插件,怎样在移动设备上实现动画效果?怎样快速搭建和测试HTML电子邮箱?怎样制作跨PC和移动设备显示的应用界面?怎样利用最新的JavaScript框架(Backbone和Knockout)提高应用的响应速度?怎样有效利用CoffeeScript和S......一起来看看 《Web开发秘方》 这本书的介绍吧!