上次已经介绍了递归算法以及二叉树的基本操作,最重要的就是二叉树的遍历算法。这次主要是介绍树的孩子兄弟表示法以及树和二叉树的转换。
还是老规矩:
程序在码云上可以下载。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git
本次的程序重点是实现树和二叉树的转换。虽然没有把树的全部操作都实现,但是还是要贴出树的ADT定义,以便大家了解树的定义。
ADT Tree{
数据对象D:D是具有相同特性的数据元素的集合。
数据关系R:若D为空集,则称为空树;
若D仅含有一个数据元素,则R为空集,否则R={H},H是如下二元关系:
(1)在D中存在唯一的称为根的数据元素root,它在关系H下无前驱;
(2)若D-{root}≠Φ,则存在D-{root}的一个划分D1,D2,D3, „,Dm(m>0),
对于任意j≠k(1≤j,k≤m)有Dj∩Dk=Φ,且对任意的i(1≤i≤m),
唯一存在数据元素xi∈Di有∈H;
(3)对应于D-{root}的划分,H-{,„,}有唯一的一个划分
H1,H2,„,Hm(m>0),对任意j≠k(1≤j,k≤m)有Hj∩Hk=Φ,且对任意i
(1≤i≤m),Hi是Di上的二元关系,(Di,{Hi})是一棵符合本定义的树,
称为根root的子树。
基本操作P:
InitTree(&T);
操作结果:构造空树T。
DestroyTree(&T);
初始条件:树T存在。
操作结果:销毁树T。
CreateTree(&T,definition);
初始条件:definition给出树T的定义。
操作结果:按definition构造树T。
ClearTree(&T);
初始条件:树T存在。
操作结果:将树T清为空树。
TreeEmpty(T);
初始条件:树T存在。
操作结果:若T为空树,则返回TRUE,否则返回FALSE。
TreeDepth(T);
初始条件:树T存在。
操作结果:返回T的深度。
Root(T);
初始条件:树T存在。
操作结果:返回T的根。
Value(T,cur_e);
初始条件:树T存在,cur_e是T中某个结点。
操作结果:返回cur_e的值。
Assign(T,cur_e,value);
初始条件:树T存在,cur_e是T中某个结点。
操作结果:结点cur_e赋值为value。
Parent(T,cur_e);
初始条件:树T存在,cur_e是T中某个结点。
操作结果:若cur_e是T的非根结点,则返回它的双亲,否则函数值为“空”。
LeftChild(T,cur_e);
初始条件:树T存在,cur_e是T中某个结点。
操作结果:若cur_e是T的非叶子结点,则返回它的最左孩子,否则返回“空”。
RightSibling(T,cur_e);
初始条件:树T存在,cur_e是T中某个结点。
操作结果:若cur_e有右兄弟,则返回它的右兄弟,否则返回“空”。
InsertChild(&T,&p,I,c);
初始条件:树T存在,p指向T中某个结点,1≤i≤p指结点的度+1,
非空树c与T不相交。
操作结果:插入c为T中p指结点的第i棵子树。
DeleteChild(&T,&p,i);
初始条件:树T存在,p指向T中某个结点,1≤i≤p指结点的度。
操作结果:删除T中p所指结点的第i棵子树。
TraverseTree(T,visit());
初始条件:树T存在,visit是对结点操作的应用函数。
操作结果:按某种次序对T的每个结点调用函数visit( )一次且至多一次。
一旦visit( )失败,则操作失败。
}ADT Tree
二叉树的ADT定义和实现请参考上一篇文章:
数据结构编程笔记十四:第六章 树和二叉树 二叉树基本操作及四种遍历算法的实现
http://blog.csdn.net/u014576141/article/details/77518855
树采用孩子兄弟表示法,孩子兄弟表示法使用的是二叉链表存储结构,二叉树也采用二叉链表的存储结构。所以树和二叉树的转换就是基于相同存储结构的不同翻译。树转换为二叉树需要将孩子结点翻译为二叉树的左孩子,将兄弟结点翻译成二叉树的右孩子。原理如图所示:
本次的代码用到了二叉树的二叉链表实现,顺序栈的实现和链队列的实现。和上次的程序一样,这些源文件必须在同一目录下编译。我把其他代码放在总结后面,想看的童鞋自己去看。
接下来看看树和二叉树转换的代码:
//>>>>>>>>>>>>>>>>>>>>>>>>>引入头文件<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#include //使用了标准库函数
#include //使用了动态内存分配函数
#include "BiTree.cpp" //引入二叉树的实现
//>>>>>>>>>>>>>>>>>>>>>>>自定义符号常量<<<<<<<<<<<<<<<<<<<<<<<<<<
#define OVERFLOW -2 //内存溢出错误常量
#define OK 1 //表示操作正确的常量
#define ERROR 0 //表示操作错误的常量
#define TRUE 1 //表示逻辑真的常量
#define FALSE 0 //表示逻辑假的常量
//>>>>>>>>>>>>>>>>>>>>>>>自定义数据类型<<<<<<<<<<<<<<<<<<<<<<<<<<
typedef int Status; //函数返回值状态码类型
typedef char TElemType; //树中结点元素类型
//-------------------树的孩子兄弟表示法-----------------------
typedef struct CSNode{
TElemType data; //数据域,存储结点名称
struct CSNode *firstchild, *nextsibling; //孩子指针域和兄弟指针域
} CSNode, *CSTree;
//-------------------------------树的主要操作--------------------------------
/*
函数:CreateCSTree
参数:CSTree &CT 树的引用
返回值:状态码,操作成功返回OK,否则返回ERROR
作用:按先根次序输入树中结点的值(一个字符),空格字符表示空树,
构造二叉链表表示树T
*/
Status CreateCSTree(CSTree &CT){
//ch保存从键盘接收的字符
char ch;
//从键盘接收字符
ch = getchar();
//用户输入了空格,表示空子树
if(ch == ' ') {
CT = NULL;
}//if
else{ //用户输入的不是空格,需要生成新的结点
//分配根结点空间
//if(!(CT = (CSNode *)malloc(sizeof(CSNode))))
//等效于
//CT = (CSNode *)malloc(sizeof(CSNode))
//if(!CT) <=> if(CT == NULL)
if(!(CT = (CSNode *)malloc(sizeof(CSNode)))){
printf("内存分配失败!\n");
exit(OVERFLOW);
}//if
//生成根结点
CT->data = ch;
//构建左子树
CreateCSTree(CT->firstchild);
//构建右子树
CreateCSTree(CT->nextsibling);
}//else
//操作成功
return OK;
}//CreateCSTree
/*
函数:ExchangeToBiTree
参数:CSTree &CT 树的引用
BiTree &T 二叉树的引用
返回值:状态码,操作成功返回OK,否则返回ERROR
作用:将一棵用二叉链表表示的树转换为二叉树
*/
Status ExchangeToBiTree(CSTree &CT, BiTree &T){
//若树的根结点为空,那么转换成的二叉树根结点也是空
if(!CT) { //if(CT) <=> if(CT != NULL)
T = NULL;
}//if
else{
//分配新的结点空间
//if(!(T = (BiNode *)malloc(sizeof(BiNode))))
//相当于以下两行代码
//T = (BiNode *)malloc(sizeof(BiNode));
//if(!T) <=> if(T == NULL)
if(!(T = (BiNode *)malloc(sizeof(BiNode)))){
printf("内存分配失败!\n");
exit(OVERFLOW);
}//if
//拷贝树中对应结点到二叉树
T->data = CT->data;
//将树的孩子转换为二叉树的左孩子
ExchangeToBiTree(CT->firstchild, T->lchild);
//将树的兄弟转换为二叉树的右孩子
ExchangeToBiTree(CT->nextsibling,T->rchild);
}//else
//操作成功
return OK;
}//ExchangeToBiTree
/*
函数:DestoryTree
参数:CSTree &CT 树的引用
返回值:无
作用:按照树的定义递归地销毁树
*/
void DestoryCSTree(CSTree &CT){
//非空树
if(CT){ //if(CT) <=> if(CT != NULL)
//孩子子树非空,递归的销毁孩子子树
//if(CT->firstchild) <=> if(CT->firstchild != NULL)
if(CT->firstchild) {
DestoryCSTree(CT->firstchild);
}//if
//兄弟子树非空,递归的销毁兄弟子树
//if(CT->nextsibling) <=> if(CT->nextsibling != NULL)
if(CT->nextsibling) {
DestoryCSTree(CT->nextsibling);
}//if
//释放根结点
free(CT);
//指针置空
CT = NULL;
}//if
}//DestoryTree
/*
函数:DestoryBiTree
参数:BiTree &T 二叉树的引用
返回值:无
作用:按照二叉树定义递归地销毁二叉树
*/
void DestoryBiTree(BiTree &T){
//非空树
if(T){ //if(T) <=> if(T != NULL)
//左子树非空,递归的销毁左子树
if(T->lchild) {
DestoryBiTree(T->lchild);
}//if
//右子树非空,递归的销毁右子树
if(T->rchild) {
DestoryBiTree(T->rchild);
}//if
//释放根结点
free(T);
//指针置空
T = NULL;
}//if
}//DestoryTree
/*
函数:DestoryTree
参数:CSTree &CT 树的引用
BiTree &T 二叉树的引用
返回值:无
作用:销毁树和二叉树
*/
void DestoryTree(CSTree &CT, BiTree &T){
//销毁树
DestoryCSTree(CT);
//销毁二叉树
DestoryBiTree(T);
printf("\n->生成的树和二叉树已被销毁!");
}//DestoryTree
//-----------------------------主函数-----------------------------------
int main(int argc,char *argv[]){
printf("---------------------------------- 树的应用 ----------------------------------\n");
BiTree T=NULL; //声明一棵二叉树
CSTree CT=NULL; //声明一棵普通树
printf(" ---------------------------树的建立---------------------- \n");
printf("->请按树的先根次序输入序列,如有空子树,用空格填充,完成后输入回车确认\n");
CreateCSTree(CT);
printf(" ---------------------------树的转换---------------------- \n");
printf("->正在将输入的树转换为其对应的二叉树...\n");
ExchangeToBiTree(CT,T);
printf("->转换操作执行完毕!\n");
printf("\n -------------------------二叉树的遍历-------------------- ");
printf("\n\n先序遍历递归 算法结果:"); PreOrderTraverse(T,PrintElement);
printf("\n\n中序遍历递归 算法结果:"); InOrderTraverse(T,PrintElement);
printf("\n\n后序遍历递归 算法结果:"); PostOrderTraverse(T,PrintElement);
printf("\n\n先序遍历非递归算法结果:"); PreOrderTraverse1(T,PrintElement);
printf("\n\n中序遍历非递归算法结果:"); InOrderTraverse1(T,PrintElement);
printf("\n\n后序遍历非递归算法结果:"); PostOrderTraverse1(T,PrintElement);
printf("\n\n层序遍历非递归算法结果:"); LevelOrderTraverse1(T,PrintElement);
printf("\n -------------------------二叉树的信息-------------------- ");
printf("\n该二叉树的高度:%d",BiTreeDepth(T));
LeafNodeNum(T);
printf("\n二叉树中叶子结点的个数:%d", LNM);
printf("\n二叉树总结点数:%d",NodeSubNum(T) );
printf("\n\n ------------------------- 树的销毁 -------------------- ");
DestoryTree(CT, T);
printf("\n->算法演示结束!");
system("pause");
return 0;
}//main
大家可以手工写出这棵树转换成二叉树之后的遍历结果,然后检验这个结果对不对。
以下是程序测试时的输入和输出:
---------------------------------- 树的应用 ----------------------------------
---------------------------树的建立----------------------
->请按树的先根次序输入序列,如有空子树,用空格填充,完成后输入回车确认
ABE*F**C*DGHI*J*K******↙
//说明:此处的*是空格,为方便确认输入了几个空格将空格替换成*,测试输入时请将*改回空格
↙表示回车确认 输入(可直接复制,不要复制↙):ABE F C DGHI J K ↙
---------------------------树的转换----------------------
->正在将输入的树转换为其对应的二叉树...
->转换操作执行完毕!
-------------------------二叉树的遍历--------------------
先序遍历递归 算法结果: A B E F C D G H I J K
中序遍历递归 算法结果: E F B C I J K H G D A
后序遍历递归 算法结果: F E K J I H G D C B A
先序遍历非递归算法结果: A B E F C D G H I J K
中序遍历非递归算法结果: E F B C I J K H G D A
后序遍历非递归算法结果: F E K J I H G D C B A
层序遍历非递归算法结果: A B E C F D G H I J K
-------------------------二叉树的信息--------------------
该二叉树的高度:9
二叉树总结点数:11
------------------------- 树的销毁 --------------------
->生成的树和二叉树已被销毁!
->算法演示结束!请按任意键继续. . .
总结:树和二叉树的转换其实就是基于相同存储结构的不同翻译。
下次的文章将介绍线索二叉树的实现。希望大家继续关注我的博客。再见!
附:
二叉树实现精简版。源文件:BiTree.cpp
//>>>>>>>>>>>>>>>>>>>>>>>>>自定义符号常量<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#define STACK_INIT_SIZE 50 //顺序栈存储空间初始分配量
#define STACKINCREMENT 10 //顺序栈存储空间分配增量
#define OVERFLOW -2 //内存溢出错误常量
#define OK 1 //表示操作正确的常量
#define ERROR 0 //表示操作错误的常量
#define TRUE 1 //表示逻辑真的常量
#define FALSE 0 //表示逻辑假的常量
//>>>>>>>>>>>>>>>>>>>>>>>>>自定义数据类型<<<<<<<<<<<<<<<<<<<<<<<<<<<<
typedef int Status; //状态码为int类型,用于保存操作结果(1成功0失败)
typedef char TElemType; //二叉树节点数据域的元素类型
//----------------二叉树的二叉链表存储表示--------------------
typedef struct BiNode{
TElemType data;
struct BiNode *lchild,*rchild; //孩子结点指针
}BiNode,*BiTree;
//--------引入栈和队列的实现(实际上应该放在头部,由于编译原因,只好这样了)----------------
#include "Queue.cpp" //引入队列的实现
#include "Stack.cpp" //引入栈的实现
//---------------------二叉树的主要操作--------------------------
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>1.构造二叉树<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函数:CreateBiTree
参数:BiTree &T 二叉树引用
返回值:状态码,操作成功返回OK,否则返回ERROR
作用:按先序次序输入二叉树中结点的值(一个字符),空格字符表示空树,
递归的构造二叉链表表示二叉树T
*/
Status CreateBiTree(BiTree &T){
//ch存储从键盘接收的字符
char ch;
//从键盘接收字符
ch = getchar();
//判断输入的字符是否是空格
if(ch == ' ') { //输入空格表示结点为空
T = NULL;
}//if
else{ //不是空格,按正常结点对待
//申请结点空间
//if(!(T = (BiNode *)malloc(sizeof(BiNode))))
//等效于以下两行代码
//T = (BiNode *)malloc(sizeof(BiNode));
//if(!(T = (BiNode *)malloc(sizeof(BiNode))))
if(!(T = (BiNode *)malloc(sizeof(BiNode)))){
printf("内存分配失败!\n");
exit(OVERFLOW);
}//if
//生成根结点
T->data = ch;
//递归的构建左子树
CreateBiTree(T->lchild);
//递归的构建右子树
CreateBiTree(T->rchild);
}//else
//操作成功
return OK;
}//CreateBiTree
//>>>>>>>>>>>>>>>>>>>>2.二叉树的遍历(4种方法)<<<<<<<<<<<<<<<<<<<<<<<<
/*
函数:Print
参数:TElemType e 被访问的元素
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:访问元素e的函数,通过修改该函数可以修改元素访问方式,
该函数使用时需要配合遍历函数一起使用。
*/
Status PrintElement(TElemType e) {
//采用控制台输出的方式访问元素
printf(" %c ", e);
//操作成功
return OK;
}//PrintElement
//------------------------递归算法-----------------------------
/*
函数:PreOrderTraverse
参数:BiTree T 二叉树T
Status(* Visit)(TElemType) 函数指针,指向元素访问函数
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
先序遍历二叉树T的递归算法,对每个数据元素调用函数Visit
*/
Status PreOrderTraverse(BiTree T, Status(* Visit)(TElemType)){
//根节点存在
//if(T) <=> if(T != NULL)
if(T){
//1.访问根结点
//if(Visit(T->data)) <=> if(Visit(T->data) != ERROR)
if(Visit(T->data)) {
//2.访问左孩子(左子树)
if(PreOrderTraverse(T->lchild, Visit)) {
//3.访问右孩子(访问右子树)
if(PreOrderTraverse(T->rchild, Visit)) {
return OK;
}//if
}//if
}//if
return ERROR;
}//if
else {
return OK;
}//else
}//PreOrderTraverse
/*
函数:InOrderTraverse
参数:BiTree T 二叉树T
Status(* Visit)(TElemType) 函数指针,指向元素访问函数
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
中序遍历二叉树T的递归算法,对每个数据元素调用函数Visit
*/
Status InOrderTraverse(BiTree T, Status(* Visit)(TElemType)){
//根节点存在
if(T){ //if(T) <=> if(T != NULL)
//1.访问左子树
if(InOrderTraverse(T->lchild,Visit)) {
//2.访问根节点
//if(Visit(T->data)) <=> if(Visit(T->data) != ERROR)
if(Visit(T->data)) {
//3.访问右子树
if(InOrderTraverse(T->rchild,Visit)) {
return OK;
}//if
}//if
}//if
return ERROR;
}//if
else {
return OK;
}//else
}//InOrderTraverse
/*
函数:PostOrderTraverse
参数:BiTree T 二叉树T
Status(* Visit)(TElemType) 函数指针,指向元素访问函数
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
后序遍历二叉树T的递归算法,对每个数据元素调用函数Visit
*/
Status PostOrderTraverse(BiTree T, Status(* Visit)(TElemType)){
//根结点存在
if(T){ //if(T) <=> if(T != NULL)
//1.访问左子树
if(PostOrderTraverse(T->lchild, Visit)) {
//2.访问右子树
if(PostOrderTraverse(T->rchild, Visit)) {
//3.访问根结点
//if(Visit(T->data)) <=> if(Visit(T->data) != ERROR)
if(Visit(T->data)) {
return OK;
}//if
}//if
}//if
return ERROR;
}//if
else return OK;
}//PostOrderTraverse
//-----------------------非递归遍历算法---------------------------
/*
函数:PreOrderTraverse1
参数:BiTree T 二叉树T
Status(* Visit)(TElemType) 函数指针,指向元素访问函数
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
先序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit
*/
Status PreOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){
//二叉树非递归遍历需要借用栈来保存回溯点
Stack S;
//初始化栈
InitStack(S);
//工作指针p指向二叉树根结点
BiTree p = T;
//遍历继续的条件:工作指针p不为空或栈不为空
//while(p || !(StackIsEmpty(S)))
//<=> while(p != NULL || StackIsEmpty(S) != 1)
while(p || !(StackIsEmpty(S))){
//根结点存在
if(p){
//1.访问根结点
//if(Visit(p->data)) <=> if(Visit(p->data) != ERROR)
if(!Visit(p->data)) {
return ERROR;
}//if
//根指针进栈
Push(S, p);
//2.遍历左子树
p = p->lchild;
}//if
else{
//根指针退栈
Pop(S, p);
//3.遍历右子树
p = p->rchild;
}//else
}//while
//销毁栈
DestoryStack(S);
//操作成功
return OK;
} //PreOrderTraverse1
/*
函数:InOrderTraverse1
参数:BiTree T 二叉树T
Status(* Visit)(TElemType) 函数指针,指向元素访问函数
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
中序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit
*/
Status InOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){
//二叉树非递归遍历需要借用栈来保存回溯点
Stack S;
//初始化栈
InitStack(S);
//工作指针p指向根结点
BiTree p = T;
//遍历继续的条件:工作指针p不为空或栈不为空
//while(p || !(StackIsEmpty(S)))
//<=> while(p != NULL || StackIsEmpty(S) != 1)
while(p || !(StackIsEmpty(S))) {
//根结点不为空
if(p){
//根指针进栈
Push(S, p);
//1.遍历左子树
p = p->lchild;
}//if
else{
//根指针退栈
Pop(S, p);
//2.访问根结点
//if(Visit(p->data)) <=> if(Visit(p->data) != ERROR)
if(!Visit(p->data)) {
return ERROR;
}//if
//3.遍历右子树
p = p->rchild;
}//else
}//while
//销毁栈
DestoryStack(S);
//操作成功
return OK;
} //InOrderTraverse1
/*
函数:PostOrderTraverse1
参数:BiTree T 二叉树T
Status(* Visit)(TElemType) 函数指针,指向元素访问函数
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
后序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit
*/
Status PostOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){
//p和q都是工作指针
//p指向当前遍历的结点,q指向p最近一次遍历的结点
BiTree p = T, q = NULL;
//二叉树非递归遍历需要借用栈来保存回溯点
Stack s;
//初始化栈
InitStack(s);
//遍历继续的条件:工作指针p不为空或栈不为空
//while(p || !StackIsEmpty(S))
//<=> while(p != NULL || StackIsEmpty(S) != 1)
while(p || !StackIsEmpty(s)) {
//顺着树的根,一直走左分支,直到遇到最左分支的尽头(叶子节点的左孩子)。
while(p){
//根结点入栈
Push(s, p);
//访问左子树
p = p->lchild;
}//while
//重置指针q的值为NULL
q = NULL;
//栈不为空
while(!StackIsEmpty(s)){
//p指向栈顶元素
GetTop(s, p);
//这个条件表示p指向了叶子结点或者p的左右子树均被遍历过
if(p->rchild == NULL || p->rchild == q){
//访问根结点
//if(Visit(p->data)) <=> if(Visit(p->data) != ERROR)
if(!Visit(p->data)) {
return ERROR;
}//if
if(p == T) {
return ERROR;
}//if
//q指向的是p的上一次遍历过的结点
q = p;
//根指针出栈
Pop(s, p);
}//if
else{
//访问右子树
p = p->rchild;
//退出内层循环
break;
}//else
}//while
}//while
//销毁栈
DestoryStack(s);
//操作成功
return OK;
} //PostOrderTraverse1
/*
函数:LevelOrderTraverse1
参数:BiTree T 二叉树T
Status(* Visit)(TElemType) 函数指针,指向元素访问函数
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
层序遍历二叉树T的算法,对每个数据元素调用函数Visit
*/
Status LevelOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){
//层序遍历需要用到队列
Queue Q;
//工作指针p指向根结点
BiTree p = T;
//根结点不为空
if(T){ //if(T) <=> if(T != NULL)
//初始化队列
InitQueue(Q);
//根结点入队列
EnQueue(Q, T);
//队列不空
//while(!QueueEmpty(Q)) <=> while(QueueEmpty(Q) == 0)
while(!QueueEmpty(Q)){
//根结点出队
DeQueue(Q, p);
//访问根结点
if(!Visit(p->data)) {
return ERROR;
}//if
//左孩子不为空
if(p->lchild) {
//左孩子入队列
EnQueue(Q, p->lchild);
}//if
if(p->rchild) {
//右孩子入队列
EnQueue(Q, p->rchild);
}//if
}//while
//输出换行,使显示美观
printf("\n");
//队列用完之后要销毁,释放其内存空间
DestoryQueue(Q);
}//if
//操作成功
return OK;
} //LevelOrderTraverse1
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>3.二叉树的信息<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函数:BiTreeDepth
参数:BiTree T 二叉树T
返回值:若二叉树T存在,返回T的深度(高度),否则返回0
作用:若二叉树T存在,递归地求二叉树T的深度
*/
int BiTreeDepth(BiTree T){
//Thigh是二叉树高度,leftThigh是左子树高度,rightThigh是右子树高度
int Thigh, leftThigh, rightThigh;
//根结点为空,树高为0
if(!T) {
return 0;
}//if
else{
//根结点不为空,则递归的计算树的高度
//递归的求出左子树高度
leftThigh = BiTreeDepth(T->lchild);
//递归的求出右子树高度
rightThigh = BiTreeDepth(T->rchild);
//左右子树可能高度不相等,按照树的高度定义
//应取左子树和右子树中高度较大者作为树的高度
if(leftThigh >= rightThigh) {
Thigh = leftThigh + 1;
}//if
else {
Thigh = rightThigh + 1;
}//else
}//else
//返回树的高度
return Thigh;
}//BiTreeDepth
//全局变量LNM记录了二叉树叶子节点的个数
int LNM = 0;
/*
函数:LeafNodeNum
参数:BiTree T 二叉树T
返回值:若二叉树T存在,返回T的叶子结点个数,否则返回0
作用:递归求二叉树叶子结点的个数
*/
int LeafNodeNum(BiTree T){
//叶子结点的特征是:左孩子和右孩子指针域均为NULL
if(T->lchild == NULL && T->rchild == NULL) { //当前结点是叶子结点
LNM++;
}//if
else {
//左孩子不为空
if(T->lchild != NULL) {
//递归的统计左子树中叶子结点的数目
LeafNodeNum(T->lchild);
}//if
//右孩子不为空
if(T->rchild != NULL) {
//递归的统计右子树中叶子结点的数目
LeafNodeNum(T->rchild);
}//if
}//else
}//LeafNodeNum
/*
函数:NodeSubNum
参数:BiTree T 二叉树T
返回值:若二叉树T存在,返回T的总结点个数,否则返回0
作用:统计二叉树的总结点个数
*/
int NodeSubNum(BiTree T){
if(!T) {
return 0; //空树或空子树
}//if
else {
//二叉树总结点数 = 左子树总结点数 + 右子树总结点数 + 自身(1)
return NodeSubNum(T->lchild) + NodeSubNum(T->rchild) + 1;
}
}//NodeSubNum
顺序栈的实现精简版。源文件:Stack.cpp
//-------------------栈的顺序存储表示-------------------------
typedef BiTree SElemType; //栈的元素为二叉树指针类型
typedef struct { //栈的顺序存储表示
SElemType *base; //栈底指针,在栈构造之前和销毁之后,base的值为NULL
SElemType *top; //栈顶指针
int stacksize; //当前已分配的存储空间,以元素为单位
}Stack;
//--------------------------栈的相关函数(供非递归后序遍历使用)----------------------------
/*
函数:InitStack_Sq
参数:Stack &S 顺序栈引用
返回值:状态码,OK表示操作成功
作用:构造一个空的顺序栈
*/
Status InitStack(Stack &S){
//动态申请顺序栈的内存空间,并检查内存空间是否成功分配
//if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType))))
//这句代码相当于以下两行代码:
//S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
//if(!S.base) <=> if(S.base == NULL)
if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType)))){
printf("内存分配失败,程序即将退出!\n");
exit(OVERFLOW);
}//if
//由于刚动态分配完的栈是空栈,所以栈顶指针和栈底指针都指向栈底
S.top = S.base;
//栈的大小就是栈的初始化大小参数STACK_INIT_SIZE
S.stacksize = STACK_INIT_SIZE;
//操作成功
return OK;
}//InitStack_Sq
/*
函数:DestoryStack_Sq
参数:Stack &S 顺序栈引用
返回值:状态码,OK表示操作成功
作用:释放顺序栈S所占内存空间
*/
Status DestoryStack(Stack &S){
//栈底指针保存的是顺序栈内存空间的首地址
free(S.base);
//操作成功
return OK;
}//DestoryStack_Sq
/*
函数:StackIsEmpty_Sq
参数:Stack S 顺序栈S
返回值:若顺序栈S是空栈返回1,否返回0
作用:判断顺序栈S是否为空栈
*/
Status StackIsEmpty(Stack S){
//栈顶指针和栈底指针都指向栈底表示此栈是空栈
return S.top == S.base;
}//StackIsEmpty_Sq
/*
函数:ReallocStack_Sq
参数:Stack &S 顺序栈S引用
返回值:状态码,操作成功返回OK,否则返回ERRROR
作用:将栈S扩容,每扩容一次,栈的大小增加STACKINCREMENT
*/
Status ReallocStack(Stack &S){
//为顺序栈重新分配内存(扩容),扩展的空间大小是STACKINCREMENT
/*if(!(S.base = (SElemType *)realloc(S.base,
(STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType))))
这句代码相当于:
S.base = (SElemType *)realloc(S.base,
(STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType));
if(!S.base) <=> if(S.base == NULL)
*/
if(!(S.base = (SElemType *)realloc(S.base,
(STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType)))){
printf("内存分配失败,程序即将退出!\n");
exit(OVERFLOW);
}//if
//由于扩容前栈已经满了,所以栈顶指针位置就是栈底指针+原来栈的大小
S.top = S.base + S.stacksize;
//扩容后,栈的大小增加了STACKINCREMENT
S.stacksize += STACKINCREMENT;
//操作成功
return OK;
}//ReallocStack_Sq
/*
函数:Push_Sq
参数:Stack &S 顺序栈引用
SElemType e 被插入的元素e
返回值:成功获取顺序栈S栈顶元素的值后返回OK,否则返回ERRROR
作用:(入栈、压栈)插入元素e为新的栈顶元素
*/
Status Push(Stack &S, SElemType e){
//入栈时发现栈满了,就要追加存储空间(扩容)
if(S.top - S.base >= S.stacksize) {
//调用扩容函数
ReallocStack(S);
}//if
//插入前,栈顶指针指向当前栈顶元素的下一个位置
//将e赋值给栈顶指针所指存储空间(插入元素e),栈顶指针后移
//*S.top++ = e; <=> *(S.top) = e; S.top++;
*S.top++ = e;
}//Push_Sq
/*
函数:Pop_Sq
参数:Stack &S 顺序栈引用
SElemType &e 带回被删除的元素值e
返回值:删除成功返回OK,否则返回ERRROR
作用:(出栈,弹栈)若栈不空,则删除S的栈顶元素,用e返回其值
*/
Status Pop(Stack &S, SElemType &e){
//在空栈中执行出栈操作没有意义,所以要判断栈是否为空
//注意栈是否为空和栈是否存在不是一个概念,所以不可以用
//S.base != NULL判断栈是否为空
if(StackIsEmpty(S)) {
return ERROR;
}//if
//删除前,栈顶指针指向当前栈顶元素的下一个位置
//--S.top;之后,栈顶指针刚好指向被删除元素
//栈顶指针前移,保存被删除的元素值到e
//e=*--S.top; <=> --S.top; e=*(S.top);
e = *--S.top;
//操作成功
return OK;
}//Pop_Sq
/*
函数:GetTop
参数:Stack S 顺序栈S
返回值:成功获取顺序栈S栈顶元素的值后返回OK,否则返回ERRROR
作用:用e返回栈顶元素的值,但是栈顶元素不做出栈操作
*/
Status GetTop(Stack S, SElemType &e){
//空栈没有栈顶元素,所以要先判断栈是否为空
//注意栈是否为空和栈是否存在不是一个概念,所以不可以用
//S.base != NULL判断栈是否为空
if(StackIsEmpty(S)) {
return ERROR;
}//if
//注意:栈顶指针指向栈顶元素的下一个位置
e = *(S.top - 1);
/* 注意:此处不能使用“e = *(--S.top); ”的原因
1. --S.top自减操作改变了栈顶指针本身的指向,使得该指针向前移动一位,相当于删除了原来栈中的最后一个元素(最后一个元素出栈);
2. S.top-1 仅仅表示栈顶指针的上一个位置,并没有改变S.top的值,*(S.top-1)表示取栈顶指针前一个位置的值,即栈顶元素的值
3. 这两种写法造成的结果是不同的,如果是纯代数运算,两者没有差别,但在指向数组
(顺序结构在C语言中是用一维数组描述的)的指针变量运算中,这两个表达式有特殊含义
在指针运算中,“指针变量-1 ”表示该指针变量所指位置的前一个位置,
这种做法并不改变指针变量本身的值。
--指针变量 不仅使得该指针指向原来所指位置的上一个位置, 还修改了指针变量本身的值
在栈中,栈顶指针和栈底指针所指向的位置有特殊的含义,故两者不等价。
*/
//操作成功
return OK;
}//GetTop_Sq
/*
函数:StackLength_Sq
参数:Stack S 顺序栈S
返回值:若顺序栈S是空栈返回1,否返回0
作用:判断顺序栈S是否为空栈
*/
Status StackLength(Stack S){
//栈的长度就是栈顶指针和栈底指针之间的元素个数
return (S.top - S.base);
}//StackLength_Sq
/*
函数:Print
参数:ElemType e 被访问的元素
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:访问元素e的函数,通过修改该函数可以修改元素访问方式,
该函数使用时需要配合遍历函数一起使用。
*/
Status Print_Stack(SElemType e){
printf("%5d ", e);
return OK;
}//Print
/*
函数:StackTraverse_Sq
参数:Stack S 顺序栈S
Status(* visit)(SElemType) 函数指针,指向元素访问函数。
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:调用元素访问函数按出栈顺序完成顺序栈的遍历,但并未真正执行出栈操作
*/
Status StackTraverse(Stack S, Status(* visit)(SElemType)) {
//在空栈中执行遍历操作没有意义,所以要判断栈是否为空
//注意栈是否为空和栈是否存在不是一个概念,所以不可以用
//S.base != NULL判断栈是否为空
if(StackIsEmpty(S)) {
printf("此栈是空栈");
return ERROR;
}//if
//调用元素访问函数依次访问栈中的每个元素
for(int i = 0; i < StackLength(S); ++i){
//调用元素访问函数,一旦访问失败则退出
if(!visit(S.base[i])) {
return ERROR;
}//if
}//for
//输出换行,是控制台显示美观
printf("\n");
//操作成功
return OK;
}//StackTraverse_Sq
链队列实现精简版。对应源文件:Queue.cpp
//------------------队列的链式存储表示-----------------------
typedef BiTree QElemType; //队列元素为二叉树指针类型
typedef struct QNode{ //链队列的C语言表示
QElemType data; //数据域
struct QNode * next; //指针域
}QNode,* QueuePtr;
typedef struct{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}Queue;
//--------------------------队列的相关函数(供非递归层序遍历使用)----------------------------
/*
函数:MallocQNode
参数:无
返回值:指向新申请结点的指针
作用:为链队列结点申请内存的函数
*/
QueuePtr MallocQNode(){
//工作指针p,指向新申请的结点
QueuePtr p;
//if(!(p = (QueuePtr)malloc(sizeof(QNode)))) 相当于以下两行代码:
//p = (QueuePtr)malloc(sizeof(QNode));
//if(!p) <=> if(p != NULL)
//申请结点的内存空间,若失败则提示并退出程序
if(!(p = (QueuePtr)malloc(sizeof(QNode)))){
printf("内存分配失败,程序即将退出!\n");
exit(OVERFLOW);
}//if
//返回新申请结点的地址
return p;
}//MallocQNode
/*
函数:InitQueue
参数:Queue &Q 链队列引用
返回值:状态码,操作成功返回OK
作用:构建一个空队列 Q
*/
Status InitQueue(Queue &Q) {
//申请头结点的内存空间,并使队头和队尾指针同时指向它
Q.front = Q.rear = MallocQNode();
//由于头结点刚刚初始化,后面还没有元素结点
Q.front->next = NULL;
//头结点数据域记录了链队列长度
//由于此时链队列没有数据节点,所以将头结点数据域设为0
Q.front->data = 0;
//操作成功
return OK;
}//InitQueue
/*
函数:DestoryQueue
参数:Queue &Q 链队列引用
返回值:状态码,操作成功返回OK
作用:销毁队列Q
*/
Status DestoryQueue(Queue &Q){
//从头结点开始向后逐个释放结点内存空间
while(Q.front){ //while(Q.front) <=> while(Q.front != NULL)
//队尾指针指向被删除结点的后继结点
Q.rear = Q.front->next;
//释放Q.front指向的被删除结点的空间
free(Q.front);
//队头指针后移,指向下一个待删除结点
Q.front = Q.rear;
}//while
//操作成功
return OK;
}//DestoryQueue
/*
函数:QueueEmpty
参数:Queue Q 链队列Q
返回值:状态码,若Q为空队列,则返回TRUE;否则返回FALSE
作用:判断队列Q是否为空
*/
Status QueueEmpty(Queue Q){
//队头指针和队尾指针均指向链队列头结点表示链队列为空
if(Q.rear == Q.front){
return TRUE;
}//if
else {
return FALSE;
}//else
}//QueueEmpty
/*
函数:EnQueue
参数:Queue &Q 链队列Q的引用
QElemType e 被插入的元素e
返回值:状态码,操作成功后返回OK。
作用:插入元素e为Q的新的队尾元素
*/
Status EnQueue(Queue &Q, QElemType e){
//申请一个新的结点,并使p指向这个新结点
QueuePtr p = MallocQNode();
//将待插入元素e保存到新结点数据域
p->data = e;
//由于新结点要插在队尾,后面没有其他结点,所以后继指针域的值为NULL
p->next = NULL;
//将新结点链入到队尾
//队列要求插入操作只能发生在队尾
Q.rear->next = p;
//修正队尾指针,使之指向p所指向的新插入的结点
Q.rear = p;
//由于插入一个结点,所以存储在头结点中的队列长度+1
Q.front->data++;
//插入操作成功
return OK;
}//EnQueue
/*
函数:DeQueue
参数:Queue &Q 链队列Q的引用
QElemType &e 带回被删除结点的元素e
返回值:状态码,操作成功后返回OK。
作用:若队列不空,则删除Q的队头元素,用e返回其值
*/
Status DeQueue(Queue &Q, QElemType &e){
//注意队列为空和队列不存在的区别,队列为空,头结点一定存在,
//队列不存在时头结点一定不存在
//对空队列执行出队操作没有意义,出队操作执行前要先检查队列是否为空
if(QueueEmpty(Q)) { //if(QueueEmpty(Q)) <=> if(QueueEmpty(Q) != TRUE)
return ERROR;
}//if
//工作指针p指向队头第一个结点(不是头结点,是头结点的后继)
//队列要求删除操作只能发生在队头,所以p指向的就是待删除节点
QueuePtr p = Q.front->next;
//保存被删除结点的值
e = p->data;
//在删除操作执行前修正队头指针的位置,使之在删除结点后指向新的队头结点
Q.front->next = p->next;
//若被删除结点恰好是队尾结点,那么该结点被删除后,队列将会变成空队列
//此时刚好满足空队列条件:Q.rear == Q.front,所以要修正队尾指针的位置,使之指向头结点
if(Q.rear == p) {
Q.rear = Q.front;
}//if
//在队头指针和队尾指针的位置都调整好了之后就可以
//放心地释放p指向的结点的内存空间了
free(p);
//由于从队列中删除了一个结点,头结点存储的队列长度应当-1
Q.front->data--;
//操作成功
return OK;
}//DeQueue
/*
函数:Print
参数:ElemType e 被访问的元素
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:访问元素e的函数,通过修改该函数可以修改元素访问方式,
该函数使用时需要配合遍历函数一起使用。
*/
Status Print_Queue(QElemType e){
//指定元素的访问方式是控制台打印输出
printf("%6.2f ",e);
//操作成功
return OK;
}//Print
/*
函数:QueueTraverse
参数:Queue Q 链队列Q
Status (* visit)(QElemType) 函数指针,指向元素访问函数。
返回值:状态码,操作成功返回OK,操作失败返回ERROR
作用:调用元素访问函数按出队顺序完成链队列的遍历,但并未真正执行出队操作
*/
Status QueueTraverse(Queue Q, Status (* visit)(QElemType)) {
//对空队列进行遍历操作没有意义,所以遍历操作前要先判断队列是否为空
//注意队列为空和队列不存在的区别,队列为空,头结点一定存在,
//队列不存在时头结点一定不存在
if(QueueEmpty(Q)) { //if(QueueEmpty(Q)) <=> if(QueueEmpty(Q) != TRUE)
return ERROR;
}//if
//工作指针p指向队头结点
QueuePtr p = Q.front->next;
//从队头结点开始依次访问每个结点,直到队尾
while(p) { //while(p) <=> while(p != NULL)
//调用元素访问函数
if(!visit(p->data)) {
printf("输出发生错误!\n");
return ERROR;
}//if
//工作指针p后移,指向下一个元素
p = p->next;
}//while
//输出换行,使结果清楚美观
printf("\n");
//操作成功
return OK;
}//QueueTraverse