二叉查找树的C语言实现(二)
接着上次的话题。这次我们要讨论,二叉查找树的中序遍历和后序遍历(递归和非递归),另外还有先序遍历(非递归)
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语言实现(二)的更多相关文章
- 使用C语言实现二维,三维绘图算法(1)-透视投影
使用C语言实现二维,三维绘图算法(1)-透视投影 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其实想想 ...
- 使用C语言实现二维,三维绘图算法(3)-简单的二维分形
使用C语言实现二维,三维绘图算法(3)-简单的二维分形 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其 ...
- 使用C语言实现二维,三维绘图算法(2)-解析曲面的显示
使用C语言实现二维,三维绘图算法(2)-解析曲面的显示 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其 ...
- Swift语言指南(二)--语言基础之注释和分号
原文:Swift语言指南(二)--语言基础之注释和分号 注释 通过注释向自己的代码中注入不可执行的文本,作为你自己的笔记或提示.Swift编译器运行时会忽略注释. Swift的注释与C语言极其相似,单 ...
- #r语言(二)笔记
#r语言(二)笔记 #早复习 #概述:R是用于统计分析.绘图的语言和操作环境 #对象: #数据类型--统称为对象 #向量(vector):用于存储数值型.字符型或逻辑型数据的一维数组. #定义向量: ...
- iOS开发之SQLite-C语言接口规范(二) —— Prepared Your SQL Statements
在<SQLite的C语言接口规范(一)>中介绍了如何去连接打开数据库,本篇博客就介绍如何操作数据库,本篇主要给出了如何执行数据库查询语句(Select), 然后遍历结果集.本篇博客就直接使 ...
- 遗传算法的C语言实现(二)-----以求解TSP问题为例
上一次我们使用遗传算法求解了一个较为复杂的多元非线性函数的极值问题,也基本了解了遗传算法的实现基本步骤.这一次,我再以经典的TSP问题为例,更加深入地说明遗传算法中选择.交叉.变异等核心步骤的实现.而 ...
- C语言 预处理二(宏定义--#define)
//#define 宏定义(宏定义一般大写) //知识点一-->#define的作用域:从#define开始,从上往下,如果遇到#undef就到#undef处结束,如果没有就是作用于当前整个文件 ...
- C语言之二维数组
二维数组 还是一个数组,只不过数组中得每一个元素又是一个数组 1). 声明语法 类型 数组名[行][列]; 例: int nums[2][3];//2行3列的二维数组,保存的数据类型是int类型 c ...
随机推荐
- Sqlserver根据某字段分隔符将一条记录拆分为多行记录
参考地址:http://blog.sina.com.cn/s/blog_b3eabfd30102wldv.html 参考语句: if object_id('[aaa]') is not null dr ...
- swift 纯代码自定义控件
1.创建自定义控件 import UIKit class CustomView: UIView { var lab:UILabel! var btn:UIButton! /************ 将 ...
- AngularJS(四)——ng-controller(控制器)
前言 上篇大概说了一下指令的应用格式以及创建自定义指令方法,本篇重点介绍一些ng-controller都有哪些小作用. 内容 通过修改控制器部分,修改显示界面. Demo <div ng-app ...
- Ready api groovy script 参数化
def token_type =context.expand ('${#Project#token_type}') def access_token = context.expand('${#Proj ...
- P1919 【模板】A*B Problem升级版(FFT快速傅里叶)
题目描述 给出两个n位10进制整数x和y,你需要计算x*y. 输入输出格式 输入格式: 第一行一个正整数n. 第二行描述一个位数为n的正整数x. 第三行描述一个位数为n的正整数y. 输出格式: 输出一 ...
- Geometry - DbGeometry的使用说明一
说明:工作中使用过但是没有详细的研究过,使用c#语言进行逻辑处理.分享出来希望各位发表意见 geometry是arcgis的空间对象 dbgeometry是微软的空间对象 geometry对象转换为d ...
- pytorch 迁移学习[摘自官网]
迁移学习包含两种:微调和特征提取器. 微调:对整个网络进行训练,更新所有参数 特征提取器:只对最后的输出层训练,其他层的权重保持不变 当然,二者的共性就是需要加载训练好的权重,比如在ImageNet上 ...
- Git的安装使用
1.什么是Git Git是一个自由和开源的分布式版本管理工具,用于有效.高速的处理任何或大或小的项目.最初由Linux Torvalds编写,用于帮助管理Linux内核开发而开发的一个开放源码的版本管 ...
- HTML的知识点讲解(HTML版本)
老男孩培训机构老师的博客 1.html 2.css http://www.cnblogs.com/yuanchenqi/articles/6856399.html 3.javas ...
- Number Sequence (KMP第一次出现位置)
Given two sequences of numbers : a[1], a[2], ...... , a[N], and b[1], b[2], ...... , b[M] (1 <= M ...