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


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 相关文章推荐
Wordpress ThickBox 点击图片显示下一张图的修改方法
Dec 11 Javascript
封装html的select标签的js操作实例
Jul 02 Javascript
jquery对象和DOM对象的区别介绍
Aug 09 Javascript
Jquery模仿Baidu、Google搜索时自动补充搜索结果提示
Dec 26 Javascript
JS使用getComputedStyle()方法获取CSS属性值
Apr 23 Javascript
vue.js内部自定义指令与全局自定义指令的实现详解(利用directive)
Jul 11 Javascript
ES6学习教程之Map的常用方法总结
Aug 03 Javascript
Vue-cli创建项目从单页面到多页面的方法
Sep 20 Javascript
vue js秒转天数小时分钟秒的实例代码
Aug 08 Javascript
微信小程序云开发实现数据添加、查询和分页
May 17 Javascript
JavaScript 作用域实例分析
Oct 02 Javascript
OpenLayer学习之自定义测量控件
Sep 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
DOTA2 6.87版本后新眼位详解攻略
2020/04/20 DOTA
php抓取https的内容的代码
2010/04/06 PHP
Yii框架引入coreseek分页功能示例
2019/02/08 PHP
准确获得页面、窗口高度及宽度的JS
2006/11/26 Javascript
javascript 字符串连接的性能问题(多浏览器)
2008/11/18 Javascript
js跨域问题之跨域iframe自适应大小实现代码
2010/07/17 Javascript
jquery获取自定义属性(attr和prop)实例介绍
2013/04/21 Javascript
Javscript删除数组中指定元素并返回新数组
2014/03/06 Javascript
javascript 寻找错误方法整理
2014/06/15 Javascript
JS实现页面超时后自动跳转到登陆页面
2015/01/19 Javascript
jQuery结合CSS制作动态的下拉菜单
2015/10/27 Javascript
关于JS中的apply,call,bind的深入解析
2016/04/05 Javascript
js显示动态时间的方法详解
2016/08/20 Javascript
原生JS版和jquery版实现checkbox的全选/全不选/点选/行内点选(Mr.Think)
2016/10/29 Javascript
Javascript计算二维数组重复值示例代码
2016/12/18 Javascript
D3.js进阶系列之CSV表格文件的读取详解
2017/06/06 Javascript
jquery实现企业定位式导航效果
2018/01/01 jQuery
React Component存在的几种形式详解
2018/11/06 Javascript
监听angularJs列表数据是否渲染完毕的方法示例
2018/11/07 Javascript
用vuex写了一个购物车H5页面的示例代码
2018/12/04 Javascript
layui多iframe页面控制定时器运行的方法
2019/09/05 Javascript
Vue-router 报错NavigationDuplicated的解决方法
2020/03/31 Javascript
Python计算时间间隔(精确到微妙)的代码实例
2019/02/26 Python
python3实现高效的端口扫描
2019/08/31 Python
Python 通过爬虫实现GitHub网页的模拟登录的示例代码
2020/08/17 Python
美国第二大连锁书店:Books-A-Million
2017/12/28 全球购物
Spotahome意大利:公寓和房间出租
2020/02/21 全球购物
经典c++面试题四
2015/05/14 面试题
请解释接口的显式实现有什么意义
2012/05/26 面试题
军校本科大学生自我评价
2014/01/14 职场文书
市场营销毕业求职信
2014/08/07 职场文书
信息技术教研组工作总结
2015/08/13 职场文书
学习经验交流会总结
2015/11/02 职场文书
zabbix agent2 监控oracle数据库的方法
2021/05/13 Oracle
Ubuntu Server 安装Tomcat并配置systemctl
2022/04/28 Servers
Win11怎么解除儿童账号限制?Win11解除微软儿童账号限制方法
2022/07/07 数码科技