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


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 相关文章推荐
用于自动添加Digg This!按钮的JavaScript
Dec 23 Javascript
获取offsetTop和offsetLeft值的js代码(兼容)
Apr 16 Javascript
flash调用js中的方法,让js传递变量给flash的办法及思路
Aug 07 Javascript
javascript解析json实例详解
Nov 05 Javascript
Javascript 拖拽的一些高级的应用(逐行分析代码,让你轻松了拖拽的原理)
Jan 23 Javascript
JavaScript操作 url 中 search 部分方法函数
Jun 15 Javascript
关于动态生成dom绑定事件失效的原因及解决方法
Aug 06 Javascript
web 前端常用组件之Layer弹出层组件
Sep 22 Javascript
Bootstrap整体框架之JavaScript插件架构
Dec 15 Javascript
jQuery居中元素scrollleft计算方法示例
Jan 16 Javascript
mpvue+vuex搭建小程序详细教程(完整步骤)
Sep 30 Javascript
微信小程序视频弹幕发送功能的实现
Dec 28 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
解析php根据ip查询所在地区(非常有用,赶集网就用到)
2013/07/01 PHP
PHP 配置后台登录以及模板引入
2017/01/24 PHP
关于Laravel参数验证的一些疑与惑
2019/11/19 PHP
jquery 得到当前页面高度和宽度的两个函数
2010/02/21 Javascript
JavaScript访问样式表代码
2010/10/15 Javascript
node.js中使用node-schedule实现定时任务实例
2014/06/03 Javascript
jquery实现类似淘宝星星评分功能有截图
2014/09/15 Javascript
javascript限制用户只能输汉字中文的方法
2014/11/20 Javascript
jQuery自定义动画函数实例详解(附demo源码)
2015/12/10 Javascript
Eclipse编辑jsp、js文件时卡死现象的解决办法汇总
2016/02/02 Javascript
AngularJS 表达式详细讲解及实例代码
2016/07/26 Javascript
Javascript实现图片懒加载插件的方法
2016/10/20 Javascript
微信小程序 密码输入(源码下载)
2017/06/27 Javascript
详解node.js中的npm和webpack配置方法
2018/01/21 Javascript
layui select动态添加option的实例
2018/03/07 Javascript
Vue自定义过滤器格式化数字三位加一逗号实现代码
2018/03/23 Javascript
关于自定义Egg.js的请求级别日志详解
2018/12/12 Javascript
vue中eslintrc.js配置最详细介绍
2018/12/21 Javascript
javascript的惯性运动实现代码实例
2019/09/07 Javascript
Vue 构造选项 - 进阶使用说明
2020/08/14 Javascript
uniapp实现可以左右滑动导航栏
2020/10/21 Javascript
[00:52]黑暗之门更新 新英雄孽主驾临DOTA2
2016/08/24 DOTA
用ReactJS和Python的Flask框架编写留言板的代码示例
2015/12/19 Python
Python KMeans聚类问题分析
2018/02/23 Python
wxpython实现图书管理系统
2018/03/12 Python
python中for循环把字符串或者字典添加到列表的方法
2019/07/20 Python
python通过链接抓取网站详解
2019/11/20 Python
pytorch方法测试详解——归一化(BatchNorm2d)
2020/01/15 Python
多个版本的python共存时使用pip的正确做法
2020/10/26 Python
初三家长会邀请函
2014/01/18 职场文书
贷款委托书范本
2014/04/08 职场文书
活动总结报告范文
2014/05/04 职场文书
家庭教育的心得体会
2014/09/01 职场文书
实践论读书笔记
2015/06/29 职场文书
毕业欢送会致辞
2015/07/29 职场文书
聊聊SpringBoot自动装配的魔力
2021/11/17 Java/Android