二叉树先序遍历的非递归算法具体实现


Posted in Javascript onJanuary 09, 2014

在前面一文,说过二叉树的递归遍历算法(二叉树先根(先序)遍历的改进),此文主要讲二叉树的非递归算法,采用栈结构

总结先根遍历得到的非递归算法思想如下:

1)入栈,主要是先头结点入栈,然后visit此结点

2)while,循环遍历当前结点,直至左孩子没有结点

3)if结点的右孩子为真,转入1)继续遍历,否则退出当前结点转入父母结点遍历转入1)

先看符合此思想的算法:

int PreOrderTraverseNonRecursiveEx(const BiTree &T, int (*VisitNode)(TElemType data))
{
 if (T == NULL)
 {
  return -1;
 }
 BiTNode *pBiNode = T;
 SqStack S;
 InitStack(&S);
 Push(&S, (SElemType)T);
 while (!IsStackEmpty(S))
 {
  while (pBiNode)
  {
   VisitNode(pBiNode->data);
   if (pBiNode != T)
   {
    Push(&S, (SElemType)pBiNode);
   }   
   pBiNode = pBiNode->lchild;
  }
  if(pBiNode == NULL)
  {
   Pop(&S, (SElemType*)&pBiNode); 
  }  
  if ( pBiNode->rchild == NULL)
  {
   Pop(&S, (SElemType*)&pBiNode); //如果此时栈已空,就有问题
  }
  pBiNode = pBiNode->rchild;
 }
 return 0;
}

注意:1)这里使用了栈结构,可参看上文顺序结构存储的栈

            2)这里在保存结点的时候,我保存的是指针也就是结点的地址,将其变为int型存储,在pop的时候里面使用的是指针,所以取的是&pBiNode,而不是pBiNode,为什么请自行思考指针的使用,最好理解的就是BiTNode *pBiNode;定义改为BiTree pBiNode就很好理解了。

上面这个算法其实是错误的!为什么呢? 这里我检查好久,期间出现还出现过无限循环,也出现过从左子树退出后右边子树不显示,最后我修改了第一个while判断条件,为什么呢?因为如果在pop之后,栈已空但是右子树还有,就无法继续了,这个在我写出后并没有进行太多验证,后面再阐述,这里并没有压入null指针,看一下压入空指针的例子,主要是左子树为空的时候才压入栈的,如下:

int PreOrderTraverseNonRecursive(const BiTree &T, int (*VisitNode)(TElemType data))
{
 if (T == NULL)
 {
  return -1;
 }
 BiTNode *pBiNode = T;
 SqStack S;
 InitStack(&S);
 Push(&S, (SElemType)T);
 while (!IsStackEmpty(S))
 {
  GetTop(S, (SElemType*)&pBiNode);
  while (pBiNode)
  {
   VisitNode(pBiNode->data);  
   pBiNode = pBiNode->lchild;
   Push(&S, (SElemType)pBiNode);
  }
  if(pBiNode == NULL)
  { 
   Pop(&S, (SElemType*)&pBiNode);
  }  
  if ( !IsStackEmpty(S))
  {
   Pop(&S, (SElemType*)&pBiNode);
   pBiNode = pBiNode->rchild;
   Push(&S, (SElemType)pBiNode);
  }
 }
 return 0;
}

这里是这样的,先压入根节点,然后判断左子树是否为空,不为空就压入栈,否则退出while循环之后就将NULL结点出栈,再判断当前栈是否为空,如果非空就出栈得到父节点然后判断右孩子,压入右孩子结点,再判断此右子树的左孩子是否为空,继续循环。

这里有两个浪费的地方:一个就是压入空孩子结点入栈,二就是频繁使用GetTop获得栈顶元素

这里返回过来再看初开始设计的算法,那里正好没有压入NULL指针或者说空的孩子结点,但是并不能输出完整,这里我们想到可以在判断栈的时候加入,当前的结点是否为NULL就可以了,这样就不会出现不会显示退出左子树结点不能显示右子树结点的尴尬了,如下:

//非递归先序遍历二叉树
int PreOrderTraverseNonRecursiveEx(const BiTree &T, 
           int (*VisitNode)(TElemType data))
{
 if (T == NULL)
 {
  return -1;
 }
 BiTNode *pBiNode = T;
 SqStack S;
 InitStack(&S);
 Push(&S, (SElemType)T);
 while ( !IsStackEmpty(S) || pBiNode)  //主要修改的就是这句
 {
  while (pBiNode)
  {
   VisitNode(pBiNode->data);
   if (pBiNode != T)
   {
    Push(&S, (SElemType)pBiNode);
   }   
   pBiNode = pBiNode->lchild;
  }
  if(pBiNode == NULL)
  {
   Pop(&S, (SElemType*)&pBiNode); 
  }  
  if ( pBiNode->rchild == NULL)
  {
   Pop(&S, (SElemType*)&pBiNode); //如果此时栈已空,就有问题
  }
  pBiNode = pBiNode->rchild;
 }
 return 0;
}

在第一个while循环加入这个之后,就可以了,测试用例与二叉树先序遍历类似。如下测试上节的二叉树例子:

二叉树先序遍历的非递归算法具体实现

此时输入的数据仍然还是 12 34 0 0 78 0 0,测试结果如下:

--- BiTree ---
Please Enter BiTree Node data:
12
Please Enter BiTree Node data:
34
Please Enter BiTree Node data:
0
Please Enter BiTree Node data:
0
Please Enter BiTree Node data:
78
Please Enter BiTree Node data:
0
Please Enter BiTree Node data:
0
12 34 78

这个还不足以测试,再看如下的二叉树

二叉树先序遍历的非递归算法具体实现

此时输入数据应该为:12 34 24 0 0 50 0 0 78 37 0 0 0,测试结果如下:

--- BiTree ---
Please Enter BiTree Node data:
12
Please Enter BiTree Node data:
34
Please Enter BiTree Node data:
24
Please Enter BiTree Node data:
0
Please Enter BiTree Node data:
0
Please Enter BiTree Node data:
50
Please Enter BiTree Node data:
0
Please Enter BiTree Node data:
0
Please Enter BiTree Node data:
78
Please Enter BiTree Node data:
37
Please Enter BiTree Node data:
0
Please Enter BiTree Node data:
0
Please Enter BiTree Node data:
0
12 34 24 50 78 37

由先序遍历可知,正好是正确的,另外这些算法不光是对先序遍历的,如果想变为中序或者后序,只需将上面算法中的visit之类的先去掉,然后将它加入合适的位置,就可以了

Javascript 相关文章推荐
用Javascript实现Windows任务管理器的代码
Mar 27 Javascript
ExtJS 刷新后如何默认选中刷新前最后一次选中的节点
Apr 03 Javascript
js阻止事件追加的具体实现
Oct 15 Javascript
谈谈JavaScript中function多重理解
Aug 28 Javascript
JS实现滑动菜单效果代码(包括Tab,选项卡,横向等效果)
Sep 24 Javascript
在AngularJS中使用jQuery的zTree插件的方法
Apr 21 Javascript
Bootstrap carousel轮转图的使用实例详解
May 17 Javascript
轻松5句话解决JavaScript的作用域
Jul 15 Javascript
获取jqGrid中选择的行的数据
Nov 30 Javascript
Vue.js实战之利用vue-router实现跳转页面
Apr 01 Javascript
详谈ES6中的迭代器(Iterator)和生成器(Generator)
Jul 31 Javascript
手把手教你使用TypeScript开发Node.js应用
May 06 Javascript
IE下Ajax缓存问题的快速解决方法(get方式)
Jan 09 #Javascript
js/jquery解析json和数组格式的方法详解
Jan 09 #Javascript
JS获取节点的兄弟,父级,子级元素的方法
Jan 09 #Javascript
js与jquery获取父级元素,子级元素,兄弟元素的实现方法
Jan 09 #Javascript
js与jquery获取父元素,删除子元素的两种不同方法
Jan 09 #Javascript
浅析jQuery(function(){})与(function(){})(jQuery)之间的区别
Jan 09 #Javascript
fmt:formatDate的输出格式详解
Jan 09 #Javascript
You might like
CI框架安全类Security.php源码分析
2014/11/04 PHP
php和editplus正则表达式去除空白行
2015/04/17 PHP
PHP简单实现文本计数器的方法
2016/04/28 PHP
php 比较获取两个数组相同和不同元素的例子(交集和差集)
2019/10/18 PHP
PHP判断当前使用的是什么浏览器(推荐)
2019/10/27 PHP
laravel框架使用极光推送消息操作示例
2020/02/15 PHP
js中访问html中iframe的文档对象的代码[IE6,IE7,IE8,FF]
2011/01/08 Javascript
JavaScript中为什么null==0为false而null大于=0为true(个人研究)
2013/09/16 Javascript
Jquery ajaxStart()与ajaxStop()方法(实例讲解)
2013/12/18 Javascript
基于jquery的手风琴图片展示效果实现方法
2014/12/16 Javascript
jquery实现图片水平滚动效果代码分享
2015/08/26 Javascript
很棒的js Tab选项卡切换效果
2016/08/30 Javascript
js实现右键自定义菜单
2016/12/03 Javascript
纯JS实现只能输入数字的简单代码
2017/06/21 Javascript
收藏AngularJS中最重要的核心功能
2017/07/09 Javascript
微信小程序实践之动态控制组件的显示/隐藏功能
2018/07/18 Javascript
在小程序中使用canvas的方法示例
2018/09/17 Javascript
javascript实现遮罩层动态效果实例
2019/05/14 Javascript
JavaScript基础之this和箭头函数详析
2019/09/05 Javascript
React 条件渲染最佳实践小结(7种)
2020/09/27 Javascript
python线程池的实现实例
2013/11/18 Python
浅析Python中yield关键词的作用与用法
2016/11/29 Python
windows 10下安装搭建django1.10.3和Apache2.4的方法
2017/04/05 Python
详解Python pygame安装过程笔记
2017/06/05 Python
python实现两个经纬度点之间的距离和方位角的方法
2019/07/05 Python
python绘制随机网络图形示例
2019/11/21 Python
PyCharm2020.1.1与Python3.7.7的安装教程图文详解
2020/08/07 Python
几款主流好用的富文本编辑器(所见即所得常用编辑器)介绍
2021/03/17 Javascript
Missguided美国官网:英国时尚品牌
2018/01/18 全球购物
PHP面试题附答案
2015/11/28 面试题
List、Map、Set三个接口,存取元素时,各有什么特点?
2015/09/27 面试题
资深地理教师自我评价
2013/09/21 职场文书
经管应届生求职信
2013/11/17 职场文书
促销活动策划方案
2014/01/12 职场文书
2014年行政助理工作总结
2014/11/19 职场文书
创业计划书之韩国烧烤店
2019/09/19 职场文书