一、什么是二叉树?

1.1 定义

二叉树,是度为二的树,二叉树的每一个节点最多只有二个子节点,且两个子节点有序。

1.2 二叉树的重要特性

(1)二叉树的第 i 层上节点数最多为 2n-1

(2)高度为 k 的二叉树中,最多有 2k-1个节点;

(3)在任意一棵二叉树中,如果终端节点的度为 n,度为 2 的节点数为 m,则 n=m+1;

(4)二叉树的子树有左右之分,顺序不能颠倒。

1.3 特殊二叉树

斜树:所有的结点都只有左子树的二叉树叫左斜树,有结点都是只有右子树的二叉树叫右斜树。

满二叉树:如果所有分支结点都存在左子树和右子树,并且所有的叶子都在同一层上,就称为满二叉树。

完全二叉树:如果二叉树的深度为 k,则除第 k 层外其余所有层节点的度都为 2,且叶子节点从左到右依次存在,将满二叉树的最后一层从左到右依次删除若干节点就得到满二叉树。满二叉树是一棵特殊的完全二叉树,但完全二叉树不一定是满二叉树。

因为二叉树的顺序存储结构缺点很明显,不能反应逻辑关系;对于特殊的二叉树(左斜树、右斜树),浪费存储空间。所以二叉树顺序存储结构一般只用于完全二叉树,因此这里只重点介绍二叉树的链式存储结构。

二、什么是链式二叉树?

链式二叉树:采用链式存储结构的二叉树。二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是比较自然的想法。

以下是链式二叉树的结点结构定义:

/* 二叉树结点结构 */
typedef struct BiTNode
{
ElemType data; // 结点数据
struct BiTNode *lchild, *rchild; // 左右孩子指针
}BiTNode, BiTree;

结构示意图如下图所示:

三、具体实现

3.1 二叉树的遍历

二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

二叉树的遍历次序不同于线性结构,最多也就是从头至尾、循环、双向等简单的遍历方式。树的结点之间不存在唯一的前驱和后继关系,在访问一个结点后,下一个被访问的结点面临着不同的选择。

二叉树的遍历方式可以很多,如果我们限制了从左到右的习惯方式,那么主要就分为四种:

1. 前序遍历

规则是若二叉树为空,则返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。如下图所示,遍历顺序为:根节点-->左孩子-->右孩子,结果为:ABDGHCEIF。

二叉树的定义是用递归的方式,所以,实现遍历算法也可以采用递归,而且极其简洁明了。实现代码如下:

// 前序遍历(递归方式)
void preOrderTraverse(BiTree *T)
{
// 判断二叉树是否存在
if (T == NULL)
return; printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
preOrderTraverse(T->lchild); // 再前序遍历左子树
preOrderTraverse(T->rchild); // 最后前序遍历右子树
}

对于结点 B,只会执行左子树遍历函数;到了结点 D,先执行左子树遍历函数,再执行右子树遍历函数;至此结点 A 的左子树递归完毕,然后就会回到结点 A,开始执行结点 A 的右子树遍历函数。

2. 中序遍历

规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。如下图所示,遍历顺序为:左孩子-->根节点-->右孩子,结果为:GDHBAEICF。

那么二叉树的中序遍历算法是如何呢?哈哈,别以为很复杂,它和前序遍历算法仅仅只是代码的顺序上的差异。实现代码如下:

// 中序遍历(递归方式)
void inOrderTraverse(BiTree *T)
{
// 判断二叉树是否存在
if (T == NULL)
return; inOrderTraverse(T->lchild); // 中序遍历左子树
printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
inOrderTraverse(T->rchild); // 最后中序遍历右子树
}

第一轮遍历到结点 D,接着执行左子树遍历函数,T指向结点 G,才到第一次执行 printf 的时候;然后回到结点 D,显示其数据,执行右子树遍历函数,显示结点 H 的数据。

3. 后序遍历

规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。如下图所示,遍历顺序为:左孩子-->右孩子-->根节点,结果为:GHDBIEFCA。

实现代码如下:

// 后序遍历(递归方式)
void postOrderTraverse(BiTree *T)
{
// 判断二叉树是否存在
if (T == NULL)
return; postOrderTraverse(T->lchild); // 先后序遍历左子树
postOrderTraverse(T->rchild); // 再后序遍历右子树
printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
}

4. 层序遍历

规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。如下图所示,结果为:ABCDEFGHI。

有同学会说,研究这么多遍历的方法干什么呢?

我们用图形的方式来表现树的结构,应该说是非常直观和容易理解,但是对于计算机来说,它只有循环、判断等方式来处理,也就是说,它只会处理线性序列,而我们刚才提到的四种遍历方法,其实都是在把树中的结点变成某种意义的线性序列,这就给程序的实现带来了好处。另外不同的遍历提供了对结点依次处理的不同方式,可以在遍历过程中对结点进行各种处理。

3.2 二叉树的建立

如果我们要在内存中建立一个如下图左这样的树,为了能让每个结点确认是否有左右孩子,我们对它进行了扩展,变成下图右的样子,也就是将二叉树中每个结点的空指针引出一个虚结点,其值为一特定值,比如 “#”。我们称这种处理后的二叉树为原二叉树的扩展二叉树。扩展二叉树就可以做到一个遍历序列确定一棵二叉树了。比如下图的前序遍历序列就为 AB#D##C##。

有了这样的准备,我们就可以来看看如何生成一棵二叉树了。假设二叉树的结点均为一个字符,我们把先前的前序遍历序列 ABDG##H###CE#I##F## 赋给一个字符数组。实现代码如下:

// 建立二叉树 [按前序输入二叉树中结点的值(一个字符)]
void createBiTree(BiTree **T)
{
ElemType ch; // str是全局数组,指向 'length' + "ABDG##H###CE#I##F##" 字符串
ch = str[index++]; if (ch != '#')
{
*T = (BiTree *)malloc(sizeof(BiTNode));
if (!*T)
return;
(*T)->data = ch; // 生成根结点 createBiTree(&(*T)->lchild); // 构造左子树
createBiTree(&(*T)->rchild); // 构造右子树
}
else
{
*T = NULL; // 值为#表示空树
}
}

其实建立二叉树,也是利用了递归的原理。只不过在原来应该是打印结点的地 方,改成了生成结点、给结点赋值的操作而已。所以大家理解了前面的遍历的话,对于这段代码就不难理解了。

当然,你完全也可以用中序或后序遍历的方式实现二叉树的建立,只不过代码里生成结点和构造左右子树的代码顺序交换一下。另外,输入的字符也要做相应的更改。比如上图的扩展二叉树的中序遍历字符串就应该为 #B#D#A#C#,而后序字符串应该为 ###DB##CA。

四、完整程序

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "io.h"
#include "math.h"
#include "time.h" #define TRUE 1
#define FALSE 0 #define MAXSIZE 100 /* 存储空间初始分配量 */ typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如TRUE等 */
typedef char ElemType; /* 二叉树结点结构 */
typedef struct BiTNode
{
ElemType data; // 结点数据
struct BiTNode *lchild, *rchild; // 左右孩子指针
}BiTNode, BiTree; //Status strAssign(ElemType T[24], ElemType *chars); // 构建字符串
Status initBiTree(BiTree **T); // 初始化二叉树
void createBiTree(BiTree **T); // 建立二叉树 [按前序输入二叉树中结点的值(一个字符)]
void preOrderTraverse(BiTree *T); // 前序遍历(递归方式)
void inOrderTraverse(BiTree *T); // 中序遍历(递归方式)
void postOrderTraverse(BiTree *T); // 后序遍历(递归方式)
void destroyBiTree(BiTree **T); // 销毁二叉树
Status isEmpty(BiTree *T); // 判断是否是空二叉树
int getBiTreeDepth(BiTree *T); // 获得二叉树的深度
ElemType getRoot(BiTree *T); // 获得二叉树的根
ElemType getNodeValue(BiTree *p); // 获得指定结点的值
void setValue(BiTree *p, ElemType value); // 给指定节点设置值 /* 用于构造二叉树********************************** */
int index = 1; // 因为str的头字节是字符串的长度,所以从1开始插入
ElemType str[24] = {19, 'A','B','D','G','#','#','H','#','#','#','C','E','#','I','#','#','F','#','#'}; // 初始化二叉树
Status initBiTree(BiTree **T)
{
*T = NULL; return TRUE;
} // 建立二叉树 [按前序输入二叉树中结点的值(一个字符)]
void createBiTree(BiTree **T)
{
ElemType ch; // str是全局数组,指向 'length' + "ABDG##H###CE#I##F##" 字符串
ch = str[index++]; if (ch != '#')
{
*T = (BiTree *)malloc(sizeof(BiTNode));
if (!*T)
return;
(*T)->data = ch; // 生成根结点 createBiTree(&(*T)->lchild); // 构造左子树
createBiTree(&(*T)->rchild); // 构造右子树
}
else
{
*T = NULL; // 值为#表示空树
}
} // 前序遍历(递归方式)
void preOrderTraverse(BiTree *T)
{
// 判断二叉树是否存在
if (T == NULL)
return; printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
preOrderTraverse(T->lchild); // 再前序遍历左子树
preOrderTraverse(T->rchild); // 最后前序遍历右子树
} // 中序遍历(递归方式)
void inOrderTraverse(BiTree *T)
{
// 判断二叉树是否存在
if (T == NULL)
return; inOrderTraverse(T->lchild); // 中序遍历左子树
printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
inOrderTraverse(T->rchild); // 最后中序遍历右子树
} // 后序遍历(递归方式)
void postOrderTraverse(BiTree *T)
{
// 判断二叉树是否存在
if (T == NULL)
return; postOrderTraverse(T->lchild); // 先后序遍历左子树
postOrderTraverse(T->rchild); // 再后序遍历右子树
printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
} // 销毁二叉树
void destroyBiTree(BiTree **T)
{
if (*T != NULL)
{
if ((*T)->lchild) // 有左孩子
destroyBiTree(&(*T)->lchild); // 销毁左孩子子树
if ((*T)->rchild) // 有右孩子
destroyBiTree(&(*T)->rchild); // 销毁右孩子子树 free(*T); // 释放根结点
*T = NULL; // 空指针赋NULL
}
} // 判断是否是空二叉树
Status isEmpty(BiTree *T)
{
return T == NULL ? TRUE : FALSE;
} // 获得二叉树的深度
int getBiTreeDepth(BiTree *T)
{
if (!T)
return 0; int left = getBiTreeDepth(T->lchild); // 获得左子树的深度
int right = getBiTreeDepth(T->rchild); // 获得右子树的深度 return left>right ? left + 1 : right + 1;
} // 获得二叉树的根
ElemType getRoot(BiTree *T)
{
if (isEmpty(T))
return ' ';
else
return T->data;
} // 获得指定结点的值
ElemType getNodeValue(BiTree *p)
{
return p->data;
} // 给指定节点设置值
void setValue(BiTree *p, ElemType value)
{
p->data = value;
} int main()
{
BiTree *T; // 初始化二叉树
initBiTree(&T); // 建立二叉树
createBiTree(&T); // 判断是否是空二叉树 | 获得二叉树的深度
printf("构造空二叉树后,树空否?%d(1:是 0:否) 二叉树的深度=%d\n\n", isEmpty(T), getBiTreeDepth(T)); // 获得二叉树的根
ElemType e = getRoot(T);
printf("二叉树的根为: %c\n", e); // 遍历二叉树
printf("\n前序遍历二叉树:");
preOrderTraverse(T);
printf("\n中序遍历二叉树:");
inOrderTraverse(T);
printf("\n后序遍历二叉树:");
postOrderTraverse(T);
printf("\n"); // 销毁二叉树
destroyBiTree(&T);
printf("\n销毁二叉树后,树空否?%d(1:是 0:否) 树的深度=%d\n\n", isEmpty(T), getBiTreeDepth(T)); return 0;
}

输出结果如下图所示:

参考:

《大话数据结构 - 第6章》 树

最新文章

  1. Openfire的启动过程与session管理
  2. 简单的ViewPager了解Scroller类
  3. [Asp.net]常见数据导入Excel,Excel数据导入数据库解决方案,总有一款适合你!
  4. Ubuntu16.04 安装MATALAB R2015b教程
  5. coffeeScript中类的继承[学习篇]
  6. ATI显卡添加自定义分辨率
  7. jQuery中实现自定义方法的扩展
  8. 【转】linux中的cut/tr/join/split/xargs命令
  9. AJAX原理及优缺点
  10. workerman需要的php模块posix、pcntl、sysvshm、sysvmsg缺少,怎么办
  11. css font的简写规则
  12. LDAP启动cacao提示Invalid file permission
  13. springmvc入门demo
  14. uva 11572 - Unique Snowflakes(和书略有不同)
  15. 使用Mybatis实现动态SQL(一)
  16. django之forms组件
  17. 手写LRU算法
  18. #10 Python字符串
  19. SQL中not in 和not exists
  20. ping 返回的TTL数值代表什么?

热门文章

  1. LoadRunner【第一篇】下载、安装、破解、修改端口号
  2. 实现:创建/复制/移动文件API
  3. JDBC-select练习&jdbc工具类&数据库登录案例
  4. div模拟textarea且高度自适应
  5. 【CSGRound2】逐梦者的初心(洛谷11月月赛 II & CSG Round 2 T3)
  6. python 项目实战之装饰器
  7. css选择指定元素
  8. [技术博客]react native事件监听、与原生通信——实现对通知消息的响应
  9. JavaScript初探系列(八)——DOM
  10. Javascript Date构造函数和比较 (二)