一、二叉树的顺序存储结构
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等
将上图存储在一维数组中,如下图所示:
数组的下标代表结点的位置,比如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); /* 构造右子树 */
}
}
在建立二叉树的时候,也是用了递归的原理,只不过在原来应该打印节点的地方改成了生成结点、给结点赋值的操作而已。
(当然,也可以用中序或者后序遍历的方式实现二叉树的建立,只不过代码里生成结点和构造左右子树的代码顺序交换一下)