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


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 设计模式 推荐
Oct 28 Javascript
checkbox使用示例
Aug 23 Javascript
Node.js模拟浏览器文件上传示例
Mar 26 Javascript
JavaScript字符串对象的concat方法实例(用于连接两个或多个字符串)
Oct 16 Javascript
javascript实现节点(div)名称编辑
Dec 17 Javascript
浅谈jQuery构造函数分析
May 11 Javascript
JS时间特效最常用的三款
Aug 19 Javascript
简洁实用的BootStrap jQuery手风琴插件
Aug 31 Javascript
vue  自定义组件实现通讯录功能
Sep 30 Javascript
js prototype深入理解及应用实例分析
Nov 25 Javascript
实现vuex原理的示例
Oct 21 Javascript
elementui的el-popover修改样式不生效的解决
Jun 30 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将向Java靠拢
2006/10/09 PHP
SSI指令
2006/11/25 PHP
YII实现分页的方法
2014/07/09 PHP
goto语法在PHP中的使用教程
2020/09/17 PHP
jquery 常用操作整理 基础入门篇
2009/10/14 Javascript
JQuery下的Live方法和$.browser方法使用代码
2010/06/02 Javascript
新发现一个骗链接的方法(js读取cookies)
2012/01/11 Javascript
IE下JS读取xml文件示例代码
2013/08/05 Javascript
文档对象模型DOM通俗讲解
2013/11/01 Javascript
关闭页面时window.location事件未执行的原因分析及解决方案
2014/09/01 Javascript
封装属于自己的JS组件
2016/01/27 Javascript
详解JavaScript中数组和字符串的lastIndexOf()方法使用
2016/03/13 Javascript
完美实现js拖拽效果 return false用法详解
2017/07/28 Javascript
angularjs select 赋值 ng-options配置方法
2018/02/28 Javascript
Angular使用cli生成自定义文件、组件的方法
2018/09/04 Javascript
JS+CSS3实现的简易钟表效果示例
2019/04/13 Javascript
详解Python3中yield生成器的用法
2015/08/20 Python
通过python的matplotlib包将Tensorflow数据进行可视化的方法
2019/01/09 Python
python控制台实现tab补全和清屏的例子
2019/08/20 Python
pyCharm 设置调试输出窗口中文显示方式(字符码转换)
2020/06/09 Python
python对批量WAV音频进行等长分割的方法实现
2020/09/25 Python
Python实现树莓派摄像头持续录像并传送到主机的步骤
2020/11/30 Python
html5手机端页面可以向右滑动导致样式受影响的问题
2018/06/20 HTML / CSS
Html5页面点击遮罩层背景关闭遮罩层
2020/11/30 HTML / CSS
加拿大最大的书店:Indigo
2017/01/01 全球购物
历史专业个人求职信范文
2013/12/07 职场文书
幼儿园实习自我鉴定
2013/12/15 职场文书
会计出纳岗位职责
2013/12/25 职场文书
运动会广播稿60字
2014/01/15 职场文书
yy生日主持词
2014/03/20 职场文书
新学期标语
2014/06/30 职场文书
2014医学院领导干部四风对照检查材料思想汇报
2014/09/16 职场文书
教师党的群众路线教育实践活动个人整改措施
2014/11/04 职场文书
学习走群众路线心得体会
2014/11/05 职场文书
2016国庆节67周年寄语
2015/12/07 职场文书
五年级语文教学反思
2016/03/03 职场文书