什么是二叉树
我们了解了什么是树(一对多的逻辑结构),那么对于二叉树简单地理解,满足以下两个条件的树就是二叉树:
- 本身是有序树;
- 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2;
例如,图 1a) 就是一棵二叉树,而图 1b) 则不是。
二叉树的性质
经过前人的总结,二叉树具有以下几个性质:
- 二叉树中,第 i 层最多有 2i-1 个结点。
- 如果二叉树的深度为 K,那么此二叉树最多有 2K-1 个结点。
- 二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1。
- 性质 3 的计算方法为:对于一个二叉树来说,除了度为 0 的叶子结点和度为 2 的结点,剩下的就是度为 1 的结点(设为 n1),那么总结点 n=n0+n1+n2。
同时,对于每一个结点来说都是由其父结点分支表示的,假设树中分枝数为 B,那么总结点数 n=B+1。而分枝数是可以通过 n1 和 n2 表示的,即 B=n1+2n2。所以,n 用另外一种方式表示为 n=n1+2n2+1。
两种方式得到的 n 值组成一个方程组,就可以得出 n0=n2+1。
二叉树还可以继续分类,衍生出满二叉树和完全二叉树。
满二叉树
如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。
如图 2 所示就是一棵满二叉树。
满二叉树除了满足普通二叉树的性质,还具有以下性质:
- 满二叉树中第 i 层的节点数为 2n-1 个。
- 深度为 k 的满二叉树必有 2k-1 个节点 ,叶子数为 2k-1。
- 满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。
- 具有 n 个节点的满二叉树的深度为 log2(n+1)。
完全二叉树
如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。
如图 3a) 所示是一棵完全二叉树,图 3b) 由于最后一层的节点没有按照从左向右分布,因此只能算作是普通的二叉树。
完全二叉树除了具有普通二叉树的性质,它自身也具有一些独特的性质,比如说,n 个结点的完全二叉树的深度为 ⌊log2n⌋+1。
- ⌊log2n⌋ 表示取小于 log2n 的最大整数。例如,⌊log24⌋ = 2,而 ⌊log25⌋ 结果也是 2。
对于任意一个完全二叉树来说,如果将含有的结点按照层次从左到右依次标号(如图 3a)),对于任意一个结点 i ,完全二叉树还有以下几个结论成立:
- 当 i>1 时,父亲结点为结点 [i/2] 。(i=1 时,表示的是根结点,无父亲结点)
- 如果 2i>n(总结点的个数) ,则结点 i 肯定没有左孩子(为叶子结点);否则其左孩子是结点 2i 。
- 如果 2i+1>n ,则结点 i 肯定没有右孩子;否则右孩子是结点 2i+1 。
顺序存储(数组)实现二叉树
二叉树的顺序存储,指的是使用顺序表(数组)存储二叉树。需要注意的是,顺序存储只适用于完全二叉树。换句话说,只有完全二叉树才可以使用顺序表存储。因此,如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树。
- 满二叉树也可以使用顺序存储。要知道,满二叉树也是完全二叉树,因为它满足完全二叉树的所有特征。
普通二叉树转完全二叉树的方法很简单,只需给二叉树额外添加一些节点,将其"拼凑"成完全二叉树即可。如图所示:
解决了二叉树的转化问题,接下来学习如何顺序存储完全(满)二叉树。
完全二叉树的顺序存储,仅需从根节点开始,按照层次依次将树中节点存储到数组即可。例如,存储所示的完全二叉树:
其存储状态如下图所示:
由此,我们就实现了完全二叉树的顺序存储。
不仅如此,从顺序表中还原完全二叉树也很简单。我们知道,完全二叉树具有这样的性质,将树中节点按照层次并从左到右依次标号(1,2,3,...),若节点 i 有左右孩子,则其左孩子节点为 2i,右孩子节点为 2i+1。此性质可用于还原数组中存储的完全二叉树。
实现代码如下:
定义二叉树结构
#define MAX_NODE_SIZE 100 // 二叉树的最大结点数
typedef int ElemType;//树结点的数据类型
typedef ElemType HjBiTree[MAX_NODE_SIZE];//定义树
ElemType Nil = 0;//定义一个空值,为空则没有节点
typedef struct {
int level;//层级
int order;//在当前层的序号
} Position;
1、初始化二叉树
void initBinaryTree(HjBiTree biTree){
for (int i = 0; i < MAX_NODE_SIZE; i++) {
//每个节点置空
biTree[i] = Nil;
}
}
2、按层序给二叉树的节点赋值
Status createBinaryTree(HjBiTree biTree){
int i = 0;
for (; i < 10; i++) {
biTree[i] = i + 1;
if (i != 0 && biTree[i] != Nil) {
if (biTree[(i + 1) / 2 - 1] == Nil) {
return ERROR;
}
}
}
while (i < MAX_NODE_SIZE) {
biTree[i] = Nil;
I++;
}
return OK;
}
3、层序遍历
void printfNode(ElemType data){
printf("%d ",data);
}
void levelOrderTraverse(HjBiTree biTree){
printf("层序遍历:");
for (int i = 0; i < MAX_NODE_SIZE; i++) {
ElemType data = biTree[I];
if (data != Nil) {
printfNode(data);
}
}
printf("\n");
}
4、前序遍历
void preTraverse(HjBiTree biTree, int index){
if (index >= MAX_NODE_SIZE || index < 0) {
return;
}
ElemType data = biTree[index];
if (data != Nil) {
printfNode(data);
preTraverse(biTree, index * 2 + 1);
preTraverse(biTree, index * 2 + 2);
}
}
void preOrderTraverse(HjBiTree biTree){
printf("前序遍历:");
preTraverse(biTree, 0);
printf("\n");
}
5、中序遍历
void inTraverse(HjBiTree biTree, int index){
if (index >= MAX_NODE_SIZE || index < 0) {
return;
}
ElemType data = biTree[index];
if (data != Nil) {
inTraverse(biTree, index * 2 + 1);
printfNode(data);
inTraverse(biTree, index * 2 + 2);
}
}
void inOrderTraverse(HjBiTree biTree){
printf("中序遍历:");
inTraverse(biTree, 0);
printf("\n");
}
6、后序遍历
void postTraverse(HjBiTree biTree, int index){
if (index >= MAX_NODE_SIZE || index < 0) {
return;
}
ElemType data = biTree[index];
if (data != Nil) {
postTraverse(biTree, index * 2 + 1);
postTraverse(biTree, index * 2 + 2);
printfNode(data);
}
}
void postOrderTraverse(HjBiTree biTree){
printf("后序遍历:");
postTraverse(biTree, 0);
printf("\n");
}
7、判断树是否是空树
Status isBiTreeEmpty(HjBiTree biTree){
//根结点为空,则二叉树为空
return biTree[0] == Nil;
}
8、获取二叉树的深度
int getBiTreeDepth(HjBiTree biTree){
int i = MAX_NODE_SIZE - 1;
for (; i >= 0; i --) {
if (biTree[i] != Nil) {
break;;
}
}
int j = -1;
do {
j++;
} while (pow(2, j) <= i);
return j;
}
9 返回处于位置pos的结点值
//(层从1开始,序号从1开始)
ElemType getNodeValue(HjBiTree biTree,Position pos){
int index = pow(2, pos.level - 1) - 2;
index += pos.order;
if (index >= MAX_NODE_SIZE || index < 0) {
return Nil;
}
return biTree[index];
}
10、获取二叉树根结点的值
Status getRootNode(HjBiTree biTree, ElemType *data){
if (isBiTreeEmpty(biTree)) {
return ERROR;
}
*data = biTree[0];
return OK;
}
11、给处于位置pos的结点赋值
Status setValueForPos(HjBiTree biTree,Position pos,ElemType data){
int index = pow(2, pos.level - 1) - 2;
index += pos.order;
if (index >= MAX_NODE_SIZE || index < 0) {
return ERROR;
}
biTree[index] = data;
return OK;
}
12、获取节点的双亲的值
ElemType getParentValue(HjBiTree biTree, ElemType data){
if (biTree[0] == Nil) {
return Nil;
}
for (int i = 1 ; i < MAX_NODE_SIZE; i++) {
if (biTree[i] == data) {
return biTree[ (i + 1) / 2 - 1];
}
}
return Nil;
}
13、获取某个结点的左孩子的值
ElemType getLeftChild(HjBiTree biTree,ElemType data){
if (biTree[0] == Nil) {
return Nil;
}
for (int i = 0; i < MAX_NODE_SIZE; i++) {
if (biTree[i] == data) {
int leftChildIndex = i * 2 + 1;
if (leftChildIndex < MAX_NODE_SIZE) {
return biTree[leftChildIndex];
}
}
}
return Nil;
}
14、获取某个结点的右孩子的值
ElemType getRightChild(HjBiTree biTree,ElemType data){
if (biTree[0] == Nil) {
return Nil;
}
for (int i = 0; i < MAX_NODE_SIZE; i++) {
if (biTree[i] == data) {
int rightChildIndex = i * 2 + 2;
if (rightChildIndex < MAX_NODE_SIZE) {
return biTree[rightChildIndex];
}
}
}
return Nil;
}
其它辅助代码
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
int main(int argc, const char * argv[]) {
printf("---二叉树顺序存储结构实现!---\n");
Status iStatus;
Position p;
ElemType e;
HjBiTree biTree;
initBinaryTree(biTree);
createBinaryTree(biTree);
printf("建立二叉树后,树空否?%d(1:是 0:否) \n",isBiTreeEmpty(biTree));
printf("树的深度 = %d\n",getBiTreeDepth(biTree));
levelOrderTraverse(biTree);
preOrderTraverse(biTree);
inOrderTraverse(biTree);
postOrderTraverse(biTree);
p.level = 3;
p.order = 2;
e = getNodeValue(biTree, p);
printf("第%d层第%d个结点的值: %d\n",p.level,p.order,e);
iStatus = getRootNode(biTree, &e);
if (iStatus) {
printf("二叉树的根为:%d\n",e);
}
else {
printf("树为空,无根!\n");
}
//向树中3层第2个结点位置上结点赋值99
e = 99;
setValueForPos(biTree, p, e);
//获取树中3层第2个结点位置结点的值是多少:
e = getNodeValue(biTree,p);
printf("第%d层第%d个结点的值: %d\n",p.level,p.order,e);
//找到e这个结点的双亲;
printf("结点%d的双亲为:%d ",e,getParentValue(biTree, e));
//找到e这个结点的左右孩子;
printf("左右孩子分别为:%d,%d\n",getLeftChild(biTree, e),getRightChild(biTree, e));
printf("\n");
return 0;
}
输出结果
链式存储(链表)实现二叉树
如图所示,此为一棵普通的二叉树,若将其采用链式存储,则只需从树的根节点开始,将各个节点及其左右孩子使用链表存储即可。
对应的链式存储结构如下图所示:
由图可知,采用链式存储二叉树时,其节点结构由 3 部分构成:
- 指向左孩子节点的指针(Lchild);
- 节点存储的数据(data);
-
指向右孩子节点的指针(Rchild);
实现代码如下:
定义二叉树结构
typedef char ElemType;
ElemType Nil = ' ';
typedef struct BiTreeNode {
ElemType data;
struct BiTreeNode *leftChild, *rightChild;
}HjTreeNode, *HjBiTree;
1、初始化二叉树
void initBinaryTree(HjBiTree *biTree){
*biTree = NULL;
}
2、创建二叉树
void createBinaryTree(HjBiTree *biTree, String str, int *index){
ElemType data = str[*index];
*index = *index + 1;
//判断是不是空节点
if (data == '#') {
*biTree = NULL;
}
else {
*biTree = malloc(sizeof(HjTreeNode));
if (!*biTree) {
exit(OVERFLOW);
}
(*biTree)->data = data;
(*biTree)->leftChild = NULL;
(*biTree)->rightChild = NULL;
//构造左子树
createBinaryTree(&(*biTree)->leftChild, str, &(*index));
//构造右子树
createBinaryTree(&(*biTree)->rightChild, str, &(*index));
}
}
3、销毁二叉树
void destoryBinaryTree(HjBiTree *biTree){
if (!*biTree) {
return;
}
//销毁左子树
if ((*biTree)->leftChild) {
destoryBinaryTree(&(*biTree)->leftChild);
}
//销毁右子树
if ((*biTree)->rightChild) {
destoryBinaryTree(&(*biTree)->rightChild);
}
free(*biTree);
*biTree = NULL;
}
4、获取二叉树的深度
int getBinaryTreeDepth(HjBiTree biTree){
if (!biTree) {
return 0;
}
int i = 0;
int j = 0;
if (biTree->leftChild) {
i = getBinaryTreeDepth(biTree->leftChild);
}
if (biTree->rightChild) {
j = getBinaryTreeDepth(biTree->rightChild);
}
return i > j ? i + 1 : j + 1;
}
5、前序遍历
void preTraverse(HjBiTree biTree){
if (!biTree) {
return;
}
ElemType data = biTree->data;
if (data != Nil) {
printfNode(data);
preTraverse(biTree->leftChild);
preTraverse(biTree->rightChild);
}
}
6、中序遍历
void inTraverse(HjBiTree biTree){
if (!biTree) {
return;
}
ElemType data = biTree->data;
if (data != Nil) {
inTraverse(biTree->leftChild);
printfNode(data);
inTraverse(biTree->rightChild);
}
}
7、后序遍历
void postTraverse(HjBiTree biTree){
if (!biTree) {
return;
}
ElemType data = biTree->data;
if (data != Nil) {
postTraverse(biTree->leftChild);
postTraverse(biTree->rightChild);
printfNode(data);
}
}
其它辅助代码
#include "stdlib.h"
#include "math.h"
#include "time.h"
#include "string.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
//状态码
typedef int Status;
#define MAX_SIZE 100
//定义二叉树字符串,0号单元存长度
typedef char String[MAX_SIZE];
Status assignString(String str, char *chars){
long length = strlen(chars);
if (length > MAX_SIZE) {
return ERROR;
}
str[0] = length;
for (int i = 1; i <= length; i++) {
// str[i] = chars[i - 1];
str[i] = *(chars + i - 1);
}
return OK;
}
int main(int argc, const char * argv[]) {
printf("---二叉树链式存储结构实现!---\n");
HjBiTree biTree;
String str;
int index = 1;
initBinaryTree(&biTree);
assignString(str,"ABDH#K###E##CFI###G#J##");
printf("创建二叉树:%s",str);
createBinaryTree(&biTree, str, &index);
printf("\n二叉树的深度:%d",getBinaryTreeDepth(biTree));
printf("\n前序遍历二叉树:");
preTraverse(biTree);
printf("\n中序遍历二叉树:");
inTraverse(biTree);
printf("\n后序遍历二叉树:");
postTraverse(biTree);
printf("\n");
return 0;
}
输出结果
如有不对的地方,请指正,谢谢您的阅读~