[数据结构 - 第6章] 树之链式二叉树(C语言实现)

一、什么是二叉树?

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章》 树


posted @ 2019-01-30 15:10  fengMisaka  阅读(2343)  评论(4编辑  收藏  举报