二叉树是在树的基础上对本身的结构做了更高的限制:
- 二叉树本身是有序树。
- 二叉树中各结点的度最多是 2,可以是 0,1,2。

满二叉树和完全二叉树
如果二叉树中除了叶子结点,每个结点的度都为 2,那么此二叉树为满二叉树。例如图 1 就是一个满二叉树。
如果二叉树除了最后一层外为满二叉树,最后一层的结点依次从左到右分布,此二叉树为完全二叉树。

图 2(A)和(B)都是二叉树,但图 2(A)是完全二叉树,(B)由于最后一层不符合从左往右依次分布的要求,所以不是完全二叉树,只是一个普通的二叉树。
二叉树的性质
二叉树有以下几个性质:
- 二叉树中,第 i 层最多有
2^(i-1)
个结点。 - 如果二叉树的深度为 K,那么此二叉树最多有
2^K - 1
个结点。 - 二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则
n0 = n2 + 1
。
性质 3 的计算方法为:
对于一个二叉树来说,除了度为 0 的叶子结点和度为 2 的结点,剩下的就是度为 1 的结点(设为 n1),那么总结点
n = n0 + n1 + n2
。
同时,对于每一个结点来说都是由其父结点分支表示的,假设树中分枝数为 B,那么总结点数n = B + 1
。而分枝数是可以通过 n1 和 n2 表示的:B = n1 + 2 * n2
。
所以,n 用另外一种方式表示为:n=n1+2*n2+1
。
两种方式得到的 n 值组成一个方程组,就可以得出n0 = n2 + 1
。
完全二叉树特有的性质
n 个结点的完全二叉树的深度为[log2n]+1
。
证明过程:
对于满二叉树来说,k层总的节点个数n = 2^k - 1;则k = log2 (n + 1); 同理,对于倒数第二层以上的满二叉树的节点个数为2^(k - 1) - 1,对于完全二叉树来说,节点个数 2^(k - 1) - 1 < n <=2^k - 1, 由于n是整数,因此上式可以看成2^(k - 1) <= n < 2^k;故 k - 1 <= log2 n < k;因此,k = [log2 n](取下限) + 1;
[log2n]表示取小于log2n的最大整数。例如,[log24] = 2,而 [log25] 结果也是 2。
对于任意一个完全二叉树来说,将含有的结点按照层次从左到右依次标号(如图 2(A)),对于任意一个结点 i ,有以下几个结论:
- 当 i > 1时,父亲结点为结点 [i / 2] 。( i = 1时,表示的是根结点,无父亲结点)
- 如果 2*i > n ,则结点 i 肯定没有左孩子(为叶子结点);否则其左孩子是结点 2*i 。
- 如果 2*i +1 > n ,则结点 i 肯定没有右孩子;否则右孩子是结点 2*i +1 。
二叉树的存储结构
二叉树有两种存储结构:顺序存储结构和链式存储结构。
顺序存储
借用数组将二叉树中的数据元素存储起来。此方式只适用于完全二叉树,如果想存储普通二叉树,需要将普通二叉树转化为完全二叉树。
使用数组存储完全二叉树时,从数组的起始地址开始,按层次顺序从左往右依次存储完全二叉树中的结点。当提取时,根据完全二叉树的第 2 条性质,可以将二叉树进行还原。
例如,存储图 2(A)时,数组中存储为:
根据完全二叉树的第 2 条性质就可以根据数组中的数据重新搭建二叉树的结构。
如果普通二叉树也采取顺序存储的方式,就需要将其转化成完全二叉树,然后再存储,例如:

转化前 转化后
图 3 中,转化后的二叉树中,数据元素 0 表示此位置没有数据。将转化后的完全二叉树按照层次并从左到右的次序存储到数组中:
由此可见。深度为 K 且只有 K 个结点的单支树(树中不存在度为 2 的结点),需要 2K-1
的数组空间,浪费存储空间。所以,顺序存储方式更适用于完全二叉树。
链式存储
采用链式存储结构存储二叉树,就非常容易理解了。根据每个结点的结构,至少需要 3 部分组成:

图5 二叉链表结点构成
图 5 中,Lchild
代表指向左孩子的指针域;data
为数据域;Rchild
代表指向右孩子的指针域。使用此种结点构建的二叉树称为“二叉链表”。
结点结构代码表示:

typedef struct BiTNode{ TElemType data;//数据域 struct BiTNode *lchild,*rchild;//左右孩子指针 }BiTNode,*BiTree;
如果程序中需要频繁地访问结点的父结点,就可以使用下面这种结点结构:

图 6 三叉链表结点构成
图 6 中,Lchild
指向左孩子;Rchild
指向右孩子;data
为数据域;parent
指向父结点。使用这种结构的结点创建的树称为“三叉链表”。
结点结构代码表示:

typedef struct BiTNode{ TElemType data;//数据域 struct BiTNode *lchild,*rchild;//左右孩子指针 struct BiTNode *parent; }BiTNode,*BiTree;

图7 单支树示意图
实现代码(以二叉链表为例)

#include <stdio.h> #include <stdlib.h> #define TElemType int typedef struct BiTNode{ TElemType data;//数据域 struct BiTNode *lchild,*rchild;//左右孩子指针 }BiTNode,*BiTree; void CreateBiTree(BiTree *T){ *T=(BiTNode*)malloc(sizeof(BiTNode)); (*T)->data=1; (*T)->lchild=(BiTNode*)malloc(sizeof(BiTNode)); (*T)->rchild=NULL; (*T)->lchild->data=2; (*T)->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode)); (*T)->lchild->rchild=NULL; (*T)->lchild->lchild->data=3; (*T)->lchild->lchild->lchild=NULL; (*T)->lchild->lchild->rchild=NULL; } int main() { BiTree Tree; CreateBiTree(&Tree); printf("%d",Tree->lchild->lchild->data); return 0; }
运行结果:
总结
对于二叉树和完全二叉树的性质,需要学员在理解的情况下进行记忆。有关二叉树存储结构的选择,以及结点结构的选择,要视情况而定,基本上遵循以下两个原则:
- 如果是普通二叉树,用链式存储结构;如果是完全二叉树,用顺序存储结构。
- 如果问题中涉及到要访问某结点的父结点,就建立三叉链表;反之,使用二叉链表即可解决问题。