接着上次的话题。这次我们要讨论,二叉查找树的中序遍历和后序遍历(递归和非递归),另外还有先序遍历(非递归)

1.中序遍历(递归)

static void __in_order(struct bnode_info *bnode,void (*todo)(struct bnode_info *bnode))
{if (bnode != NULL) {  __in_order(bnode->lchild, todo);todo(bnode);__in_order(bnode->rchild, todo);}
}static void btree_in_order(struct btree_info *info,void (*todo)(struct bnode_info *bnode))
{assert(todo != NULL && info != NULL);__in_order(info->root, todo);
}

其实很简单了,就是把语句的顺序换一换就可以了。

--in_order--

5 16 24 26 50 80

这是运行的结果。有没有发现,数字是按从小到大排序的! 如果一棵二叉查找树 的节点值是数值,那么中序遍历的结果为升序排列的数组。

2.中序遍历-非递归

static void btree_in_order_norecur(struct btree_info *info,void (*todo)(struct bnode_info *bnode))
{assert(info != NULL && todo != NULL);if (btree_is_empty(info)) return ;//栈空间的创建struct stack_info *stack = (struct stack_info *)malloc(sizeof(struct stack_info));assert(stack != NULL);//栈初始化stack_init(stack);struct bnode_info *cur = info->root;while (!stack->is_empty(stack) || cur != NULL) {if (cur != NULL) {stack->push(stack, &cur, sizeof(cur));cur = cur->lchild;}else{stack->pop(stack, &cur, sizeof(cur));todo(cur);cur = cur->rchild;}}stack_destroy(stack);free(stack);
}

思路是创建一个栈,顺着树根,一直往左边的节点走,一路压栈,走到最左边的那个节点,也压栈。这时候当前节点为NULL,开始出栈,弹出的这个元素就是子树的树根,todo(cur), 然后遍历右子树。对于右子树,也是同样的方法,一路向左,依次压栈......

3.先序遍历-非递归

static void btree_pre_order_norecur(struct btree_info *info,void (*todo)(struct bnode_info *bnode))
{assert(info != NULL && todo != NULL);if (btree_is_empty(info)) {return ;}//栈空间的创建struct stack_info *stack = (struct stack_info *)malloc(sizeof(struct stack_info));assert(stack != NULL);//栈初始化stack_init(stack);struct bnode_info *cur = info->root;while (!stack->is_empty(stack) || cur != NULL) {if (cur != NULL) {todo(cur);stack->push(stack, &cur, sizeof(cur));cur = cur->lchild;}else{stack->pop(stack, &cur, sizeof(cur));cur = cur->rchild;}}stack_destroy(stack);free(stack);
}

其实和上面的代码类似,也是用了栈。首先cur指向树根,既然是先序,直接todo(cur); 之后要把当前节点压栈,因为后面还要利用这个节点找到它的右子树,再然后 cur = cur->lchild, 也就是遍历左子树,一直向左,直到左子树为空,这时候就遍历右子树,于是出栈一个节点,得到他的右孩子,进入下一轮循环。

4.后序遍历--非递归

后序遍历的非递归思路应该是所有顺序中最难的一个了。

错误的思路介绍:

1.首先一路向左,把从树根开始,到树根的左孩子,再到左孩子的左孩子,全部压栈;

2.接着,取栈顶的元素,看看他有没有右孩子,转到3或者4;

3.如果没有,就弹出这个元素,并且打印(也可以是别的操作)这个元素,然后回到2;

4.如果有,就把右孩子作为新的树根,回到1;

以上的思路是错误的,为什么呢?我们结合代码来说明,下面请看错误的代码。

static void btree_post_order_norecur_wrong(struct btree_info *info,void (*todo)(struct bnode_info *bnode))
{assert(info != NULL && todo != NULL);if (btree_is_empty(info)) {return ;}//栈空间的创建struct stack_info *stack = (struct stack_info *)malloc(sizeof(struct stack_info));assert(stack != NULL);//栈初始化stack_init(stack);struct bnode_info *cur = info->root;struct bnode_info *pre = NULL;while (!stack->is_empty(stack) || cur != NULL) {if (cur != NULL) {stack->push(stack, &cur, sizeof(cur));todo(cur);printf("in stack\n");cur = cur->lchild;}else{stack->top(stack, &cur, sizeof(cur));todo(cur);printf("get top  stack\n");if (cur->rchild != NULL){todo(cur);printf("his rchild not null\n");cur = cur->rchild;}else{stack->pop(stack, &cur, sizeof(cur));todo(cur);//这个是真正的打印,其他地方只是为了打印日志。printf(" -----ok \n");cur = NULL;//for out stack continue}}}stack_destroy(stack);free(stack);}

为了说明清楚,我加了很多打印的地方。运行结果如下(括号里面的是我的解释)

----wrong:post_order_norecur---
50 in stack
24 in stack
16 in stack
5 in stack
5 get top  stack
5  ok               (这个是对的)
16 get top  stack
16  ok             (这个是对的)
24 get top  stack  (24第一次 get top)
24 his rchild not null
26 in stack
26 get top  stack
26  ok            (这个是对的)
24 get top  stack  (24第二次 get top, 其实他的右子树已经遍历了,可是后面发现又开始遍历右子树)
24 his rchild not null
26 in stack
26 get top  stack
26  ok          
24 get top  stack(24第三次 get top,  后面再次遍历右子树)
24 his rchild not null

……

……

后面的日志太长了,因为陷入了死循环。程序就是反复地 取栈顶元素24,遍历他的右子树

分析到这里,我们看出了症结,第一次取栈顶元素24没有错,是为了遍历他的右子树。第二次取的时候,右子树已经遍历过了,这时候就应该让24出栈,并且打印。

可是怎么知道栈顶元素的右子树是否已经遍历过了?方法有很多,这里我们采用这样一种方法:根据后序遍历的特性,如果24是第一次取(就是get top 的操作)且右孩子不为空,那么最近遍历的元素,一定不是24的右孩子(因为还没有遍历);如果24是第二次取,也就是说他的右子树遍历过了,那么最近遍历的那个元素,一定是24的右孩子。

所以,我们把上面的错误思路改一下:

1.从树根开始,到树根的左孩子,再到左孩子的左孩子,全部压栈;

2.接着,取栈顶的元素,看看他有没有右孩子,没有就转到3,有就转到4;

3.弹出这个元素,并且打印(也可以是别的操作)这个元素,还要记录这个元素作为最近打印的元素,然后回到2;

4.再看看他的右孩子是否等于最近打印的元素,等于转到5 , 不等于转到6;

5.说明他的右子树已经遍历了,转到3;

6. 把右孩子作为新的树根,回到1;

看看正确的代码吧:

static void btree_post_order_norecur(struct btree_info *info,void (*todo)(struct bnode_info *bnode))
{assert(info != NULL && todo != NULL);if (btree_is_empty(info)) return ;//栈空间的创建struct stack_info *stack = (struct stack_info *)malloc(sizeof(struct stack_info));assert(stack != NULL);//栈初始化stack_init(stack);struct bnode_info *cur = info->root;struct bnode_info *pre = NULL; // 为了记录最近打印的节点while (!stack->is_empty(stack) || cur != NULL){if (cur != NULL) {stack->push(stack, &cur, sizeof(cur));cur = cur->lchild; //一路向左压栈}else{stack->top(stack, &cur, sizeof(cur));if ((cur->rchild != NULL) && (cur->rchild != pre)) //存在右子树,且右子树没有遍历{cur = cur->rchild; //遍历右子树}else // 没有右孩子或者右子树已经遍历的情况{stack->pop(stack, &cur, sizeof(cur));todo(cur);pre = cur;//更新最近打印的节点cur = NULL;}}}stack_destroy(stack);free(stack);
}

运行后得到正确的结果:

5 16 26 24 80 50

===============================================================================================================================

5.后序遍历--递归

这个很简单,其实递归遍历思路都一样,先序中序后序的区别就是在于 todo(bnode) 语句的位置不一样。直接上代码:

static void __post_order(struct bnode_info *bnode,void (*todo)(struct bnode_info *bnode))
{if (bnode != NULL) {__post_order(bnode->lchild, todo);__post_order(bnode->rchild, todo);todo(bnode);}
}static void btree_post_order(struct btree_info *info,void (*todo)(struct bnode_info *bnode))
{__post_order(info->root, todo);
}

今天就说到这里,还有一些内容,下次再见!

二叉查找树的C语言实现(二)相关推荐

  1. R语言绘制二维密度图

    R语言绘制二维密度图 二维密度图显示了两个数值变量之间的关系,一个在x轴上表示,另一个在Y轴上表示,与散点图类似,然后计算二维空间中特定区域内的观测数,并用颜色梯度表示.二维密度图有几种类型,以下主要 ...

  2. 国二c语言改错题答案,c语言国二考试编程题答案

    <c语言国二考试编程题答案>由会员分享,可在线阅读,更多相关<c语言国二考试编程题答案(65页珍藏版)>请在人人文库网上搜索. 1.1m个人的成绩存放在score数组中,请编写 ...

  3. go语言定义二维数组

    使用go语言二维数组 go语言不用管理内存,很多地方使用起来确实很方便,但是在算法方面确实没有C++优秀,特别是缺少像STL一样优秀模板,定义一个二维数组需要进行如下复杂的操作: go语言的二维数组定 ...

  4. C语言在二叉搜索树找到第k个最小元素(附完整源码)

    C语言在二叉搜索树找到第k个最小元素 C语言在二叉搜索树找到第k个最小元素完整源码(定义,实现,main函数测试) C语言在二叉搜索树找到第k个最小元素完整源码(定义,实现,main函数测试) #in ...

  5. c语言调用二维数组作为函数参数传递,C++ 二维数组作为形参传递使用实例

    在线代码编辑器: http://codepad.org/ 1.*指针 void display(int *arr, const int row, const int col) { for(int i= ...

  6. [GO语言基础] 二.编译运行、语法规范、注释转义及API标准库知识普及

    作为网络安全初学者,会遇到采用Go语言开发的恶意样本.因此从今天开始从零讲解Golang编程语言,一方面是督促自己不断前行且学习新知识:另一方面是分享与读者,希望大家一起进步.前文介绍了什么是GO语言 ...

  7. java二维数组水平翻转,C 语言 利用二维数组实现对输入的数组进行翻转

    C 语言 利用二维数组实现对输入的数组进行翻转(帮助理解对图像翻转编辑原理) /* ?输入几行几列数字和翻转方式,如: 3 4 0即代表3行4列,左右翻转: 6 5 1即代表6行5列,上下翻转. 输入 ...

  8. 循环队列及C语言实现二

    在我的上一篇博文中已经讲到循环队列的特点作用以及C语言实现,当然实现和操作的方式比较简单,在实际项目应用中略显粗糙.因此,这一篇提供一个进阶篇的实现与操作接口.具体函数作用可以参见我的注释部分,使用的 ...

  9. html语言标示,HTML语言剖析(二) HTML标记一览

    HTML语言剖析(二) HTML标记一览 2008-04-11 00:00:00 分享到: 摘要 :标记 类型 译名或意义 作 用 备注 文件标记 ● 文件声明 标记 类型 译名或意义 作 用 备注 ...

  10. Swift语言指南(二)--语言基础之注释和分号

    Swift语言指南(二)--语言基础之注释和分号 原文:Swift语言指南(二)--语言基础之注释和分号 注释 通过注释向自己的代码中注入不可执行的文本,作为你自己的笔记或提示.Swift编译器运行时 ...

最新文章

  1. java hashmap 重复_java HashMap插入重复Key值问题
  2. 英文linux学习app,Linux应用软件,Linux Application Software,音标,读音,翻译,英文例句,英语词典...
  3. 计算机病毒不可能侵入rom,2008年职称计算机考试计算机基础试题7
  4. 第一章 关于python
  5. 机器学习(二十三)——Beam Search, NLP机器翻译常用评价度量, 模型驱动 vs 数据驱动
  6. cordova 实现网页缓存_如何解决ionic,cordova混合开发的app缓存大的问题
  7. ScanTailor-ScanTailor 强大的多方位的满足处理扫描图片的需求
  8. 单词接龙(洛谷-P1019)
  9. matlab的图形绘制实验,(完整版)Matlab实验7图形绘制
  10. Cypress自动化测试系列之三
  11. OSPFv3中LSA详解(九)——Prefix三元组详解
  12. 一文看懂微服务,阿里云原生资深专家李国强独家分享
  13. JavaScript编写的《人生不纠结模拟器》
  14. 2013八大免费杀毒软件排行榜
  15. 计算机网络英语词汇,计算机网络英语词汇
  16. java毕业设计户籍管理系统(附源码、数据库)
  17. SQLite 使用(针对Android)
  18. python知道章节答案_智慧树知道Python数据分析与数据可视化答案,章节期末教程考试网课答案...
  19. mysql导vertica_vertica使用vsql导数据
  20. SAP HANA XS ODATA的写法

热门文章

  1. NYOJ 228 士兵杀敌(五)
  2. 360笔试第一题----最强的不一定是最后的赢家
  3. 神经网络neural network简单理解
  4. MVC中Spring.net 对基类控制器无效 过滤器控制器无效
  5. codeforces 721E Road to Home
  6. jdbc调用mysql存储过程实现代码带有输入和输出
  7. 复习--3--对于第三堂课的总结--将两个页面相互用超链接链接到一起
  8. poj 3077Rounders(模拟)
  9. shell 编程学习笔记(一)
  10. R学习笔记:文档间函数调用