大话数据结构之树(中)

225 阅读6分钟

一、二叉树的顺序存储结构

二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等

在这里插入图片描述

将上图存储在一维数组中,如下图所示:

在这里插入图片描述

数组的下标代表结点的位置,比如E结点的数组下标是5,即代表它的位置是5。



如果将如下的非完全二叉树存在数组中,该如何表示呢?

在这里插入图片描述

上图是一棵非完全二叉树,其中该二叉树只存在ABCEGJ(即蓝色部分)结点,

为了方便在数组中存储,需要将其补成完全二叉树,用^表示不存在的结点,如下图所示:

在这里插入图片描述



再考虑一种极端情况,如果是一棵深度为k的右斜树,只有k个结点,但是要分配2 ^ k - 1个存储单元空间,明显造成空间的浪费,如下图所示:

在这里插入图片描述 在这里插入图片描述

因此我们可以推出顺序存储结构只适合于完全二叉树



二、二叉链表

二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域

在这里插入图片描述

其中data是数据域,lchild是存放左孩子的指针域,rchild是存放右孩子的指针域

//二叉树的二叉链表结点结构定义
typedef struct BiTNode {
    TElemType data;//结点数据
    struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;

在这里插入图片描述

三、遍历二叉树

1、二叉树的遍历原理

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

2、二叉树的遍历方法

2.1 前序遍历(根左右)

规则是若二叉树为空,则空操作返回,否则闲访问根结点,然后前序遍历左子树,再前序遍历右子树

下图的遍历顺序是 ABDGHCEIF 在这里插入图片描述

2.2 中序遍历(左根右)

规则是若二叉树为空,则不操作,直接返回;否则,从根结点开始(注意不是先访问根结点),中序遍历根结点的左子树,再访问根结点,最后中序遍历右子树

下图的遍历顺序是:GDHBAEICF

在这里插入图片描述

2.3 后序遍历(左右根)

规则是若树为空,若二叉树为空,则不操作,直接返回;

否则,从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点,

下图是遍历顺序是 GHDBIEFCA 在这里插入图片描述

2.4 层序遍历

若二叉树为空,则不操作,直接返回;否则,从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问

下图的遍历顺序是 ABCDEFGHI

在这里插入图片描述

3、前序遍历算法

二叉树的定义是用递归的方式,所以遍历算法也可以采用递归来遍历一棵二叉树

//二叉树的前序遍历递归算法
void PreOrderTraverse(BiTree T)
{ 
	if(T==NULL)
            return;
	printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
	PreOrderTraverse(T->lchild); /* 再先序遍历左子树 */
	PreOrderTraverse(T->rchild); /* 最后先序遍历右子树 */
}

4、中序遍历算法

其实前序遍历算法和中序遍历算法很像,只是部分代码的顺序不同而已!

中序遍历算法的代码如下:

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

5、后序遍历算法

//二叉树的中序遍历递归算法
void PostOrderTraverse(BiTree T)
{
	if(T==NULL)
            return;
	PostOrderTraverse(T->lchild); /* 先后序遍历左子树  */
	PostOrderTraverse(T->rchild); /* 再后序遍历右子树  */
	printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
}

6、推导遍历结果

已知一颗二叉树的前序遍历序列为ABCDEF,中序遍历序列为CBAEDF,请问这颗二叉树的后序遍历结果是多少?

三种遍历都是从根结点开始的,前序遍历是先打印再递归左和右。所以前序遍历序列为ABCDEF,第一个字母是A被打印出来,就说明A是根结点的数据。再由中序遍历序列是CBAEDF,可以知道C和B是A的左子树的结点,E、D、F是A的右子树的结点

在这里插入图片描述

然后我们看前序中的C和B,它的顺序是ABCDEF,是先打印B后打印C,所以B应该是A的左孩子,而C就只能是B的孩子,此时是左还是右孩子还不确定。

在这里插入图片描述

再看前序中的E、D、F,它的顺序是ABCDEF,那就意味着D是A结点的右孩子,E和F是D的子孙,注意,它们中有一个不一定是孩子,还有可能是孙子的。 再来看中序序列是CBAEDF,由于E在D的左侧,而F在右侧,所以可以确定E是D的左孩子,F是D的右孩子

在这里插入图片描述

已知前序遍历序列和中序遍历序列,可以唯一确定一颗二叉树 已知后序遍历序列和中序遍历序列,可以唯一确定一颗二叉树 但已知前序和后序,不能确定一颗二叉树, 比如前序是ABC,后序是CBA,可以确定A是根结点,但是无法知道哪个结点是左子树,哪个结点是右子树 在这里插入图片描述

7、二叉树的建立

如果我们要建立下面一棵二叉树,为了方便,我们对它进行了扩展。

变成下下图的样子,也就是将二叉树中每个结点的空指针引出一个虚结点,其值为一特定值,比如“#”。 我们把这种处理后的二叉树为原二叉树的扩展二叉树。此时下下图的前序遍历结果为AB#D##C##

在这里插入图片描述 在这里插入图片描述

这时,我们来看看如何生成一棵二叉树。假设二叉树的结点均为一个字符,我们把刚才前序遍历序列AB#D##C##用键盘挨个输入。

/* 按前序输入二叉树中结点的值(一个字符) */
/* #表示空树,构造二叉链表表示二叉树T。 */
void CreateBiTree(BiTree *T)
{ 
	TElemType ch;
	
	/* scanf("%c",&ch); */
	ch=str[index++];
 
	if(ch=='#') 
		*T=NULL;
	else
	{
		*T=(BiTree)malloc(sizeof(BiTNode));
		if(!*T)
			exit(OVERFLOW);
		(*T)->data=ch; /* 生成根结点 */
		CreateBiTree(&(*T)->lchild); /* 构造左子树 */
		CreateBiTree(&(*T)->rchild); /* 构造右子树 */
	}
}

在建立二叉树的时候,也是用了递归的原理,只不过在原来应该打印节点的地方改成了生成结点、给结点赋值的操作而已。

(当然,也可以用中序或者后序遍历的方式实现二叉树的建立,只不过代码里生成结点和构造左右子树的代码顺序交换一下)