一、树的定义
树是由N个节点组成的有限集.
只有一个根节点也可以称之为树.
例如:
仅有一个A节点, 这也是树结构.
一般情况下, 树结构都是类似这个样子:
这个树中一共有11个节点, A B C D E F G H I J K. A为这个树的根节点.
1.1、树结构中基本术语
- 节点的度: 节点的度是指节点下拥有的子树. 例如: A度为3, B度为2 C度为1
- 树叶子: 把树中度为0的节点就称只为叶子. 例如: J K
- 双亲节点: 一个节点的上一个节点就为双亲节点. 例如: C的双亲节点为A
- 兄弟节点: 指的是同属于一个双亲节点的节点. 例如: EF就是兄弟节点
还有节点的高度、深度、层:
- 高度: 指的是节点到叶子节点最长的边树. 例如: A到J最长为3, 记A的高度为3
- 深度: 指从根节点到这里经历过几个子节点. 例如: A是根节点, A->A深度为0. B节点中, A->B经历1个节点 所以B深度为1
- 层: 从根节点开始, 第几代子节点就为第几层. 例如: C是第二代, 层就为2
二、二叉树
一般的树中, 每一个节点可以拥有个无数个子节点.
二叉树是一个中特殊的树:
- 非空二叉树有且只有一个根节点
- 除根节点, 其余节点都是互不相交的子集
- 每个节点, 至多只有两个子树
- 二叉树中有左右之分. 例如B与C的意义是不一样的
2.1、特殊的二叉树
斜树: 所有的节点都是靠同一边的.
左斜树:
完全二叉树: 一个具备N个节点的二叉树按照层序编号一个接一个排列.
满二叉树: 所有的分支节点都有两个子节点, 所有叶子处于同一层. 呈一种左右对称形态. 是一个特殊的完全二叉树
三、二叉树的顺序存储结构
可以用一个数组来对树的节点依次存储. 由于树的各种形态很多.所以我们需要把某一个树按照完全二叉树的形态建立一个数组.
创建一个如下数组存储节点:
| 下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| 节点 | A | B | C | D | E | F | G | H | I |
假如不是完全二叉树, 但是还是得按照完全二叉树的形态创建数组存储:
创建一个如下数组存储节点:
| 下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| 节点 | A | B | C | D | \ | \ | G | H | I |
没有E F 节点, 就用特殊符号标记这个位置是一个空的.
3.1、代码实现顺序存储二叉树
定义一个树
typedef int Status;//返回状态
typedef int ElementType;//节点数据
typedef ElementType BinaryTree[MAX_TREE_SIZE];//树
ElementType NULLNODE = -1;//空节点
3.2、初始化
创建一个空树, 不存在任何节点
//初始化
Status InitBinaryTree(BinaryTree T) {
for (int i = 0; i < MAX_TREE_SIZE; i++) {
T[i] = NULLNODE;
}
return 1;
}
3.3、创建一个有数据的树
需要注意每一个非根节点, 必定有双亲节点
//创建数据
Status CreateBinaryTree(BinaryTree T, ElementType *V, int elementCount) {
int i = 0;
while (i < elementCount) {
T[i] = V[i];
if (i != 0 && T[(i + 1) / 2 - 1] == NULLNODE) {
//出现了无双亲的非根节点
return -1;
}
i++;
}
return 1;
}
3.4、树的清空与判断空
清空操作实际上就是对树的初始化操作. 直接调用初始化就可以
//清空
#define ClearBinaryTree InitBinaryTree
当一个树有根节点就为非空树
//二叉树是否为空
Status isEmptyBinaryTree(BinaryTree T) {
return T[0] == NULLNODE ? 1 : 0;
}
3.5、获取树的深度
树的深度即为根节点到达最后一个叶节点的长度. 可以对一个树进行倒序遍历, 找到的第一个非空节点即为最后一个叶节点.
通过观察树结构得知: 树的每一层节点个数为: 1 2 4 6 8 .... 2^j(j为层数)
我们找到了最后一个节点所在的下标i之后, 只需要找到 2^j < i < 2^(j+1)求出j
//获取深度
int BinaryTreeDepth(BinaryTree T) {
int j = -1;
int i;
for (i = MAX_TREE_SIZE - 1; i >= 0; i--) {
if (T[i] != NULLNODE) {
break;
}
}
do {
j++;
} while (pow(2, j) <= i);
return j;
}
3.6、查询与修改某个位置的值
树的每一层节点个数为: 1 2 4 6 8 .... 2^j(j为层数)
树的每一层的第一个节点的位置为 0 1 3 7 .... 2^(j-1)-1 (j为层数)
通过这个规律, 用层数获取当前层的首个节点的下标, 加上本层序号就可以得到想要的位置的下标. 需要注意的是数组是从0下标开始的, 所以本层序号也要减一
typedef struct {
int level;//节点层号
int order;//本层序号(满二叉树序号)
} Position;
//查询某个位置的值
ElementType Value(BinaryTree T, Position e) {
return T[(int)pow(2, e.level - 1) + e.order - 2];
}
修改某个位置的值与查询类似, 但是需要注意的是修改的时候要注意这个节点是否有双亲节点, 不存在则无法修改
//修改某个位置的值
Status Assign(BinaryTree T, Position e, ElementType value) {
int i = (int)pow(2, e.level - 1) + e.order - 2;
if (value != NULLNODE && T[(i + 1) / 2 - 1] == NULLNODE) {
return -1;
}
T[i] = value;
return 1;
}
3.7、获取子节点与双亲节点
通过分析可以知道, 满二叉树中一层节点个数必定是下一层节点数的一半, 既然知道了一个节点在某一层, 就可以知道这个节点的子节点一定是下一层的. 一个节点对应两个子节点, 很容易得知子节点的位置.
需要注意传入的位置一定要是非空节点
//获取节点左孩子右孩子
ElementType leftChild(BinaryTree T, Position e) {
if (Value(T, e) == NULLNODE) {
return NULLNODE;
}
return T[(int)pow(2, e.level) + (e.order - 1) * 2 - 1];
}
ElementType rightChild(BinaryTree T, Position e) {
if (Value(T, e) == NULLNODE) {
return NULLNODE;
}
return T[(int)pow(2, e.level) + (e.order - 1) * 2];
}
需要注意的是, 根节点没有双亲节点. 查找思路与查找子节点相似
//获取双亲节点
ElementType superNode(BinaryTree T, Position e) {
if (e.level == 1) {
return -1;
}
return T[(int)pow(2, e.level - 2) - 1 + (e.order - 1) / 2];
}
3.8、层序遍历
顾名思义一层一层遍历, 只要是非空节点打印即可
//层序遍历
void Traverse(BinaryTree T) {
for (int i = 0; i < MAX_TREE_SIZE; i++) {
if (T[i] != NULLNODE) {
printf("%d ", T[i]);
}
}
}
3.9、前序遍历
前序遍历路径是, 左节点优先于右节点遍历
//前序遍历
void PreTraverse(BinaryTree T, int e) {
printf("%d", T[e]);
if (T[2 * e + 1] != NULLNODE) {
PreTraverse(T, 2 * e + 1);
}
if (T[2 * e + 2] != NULLNODE) {
PreTraverse(T, 2 * e + 2);
}
}
void PreOrderTraverse(BinaryTree T) {
if (!isEmptyBinaryTree(T)) {
PreTraverse(T, 0);
}
printf("\n");
}
3.10、中序遍历
中序遍历是先到根节点的最左的子树, 然后向上遍历到根节点, 再遍历根节点的右边
先来到H节点:
//中序遍历
void InTraverse(BinaryTree T, int e) {
if (T[2 * e + 1] != NULLNODE) {
InTraverse(T, 2 * e + 1);
}
printf("%d", T[e]);
if (T[2 * e + 2] != NULLNODE) {
InTraverse(T, 2 * e + 2);
}
}
void InOrderTraverse(BinaryTree T) {
if (!isEmptyBinaryTree(T)) {
InTraverse(T, 0);
}
printf("\n");
}
3.11、后序遍历
后序遍历是从左到右, 先叶子节点再节点的方式遍历
//后序遍历
void PostTraverse(BinaryTree T, int e) {
if (T[2 * e + 1] != NULLNODE) {
PostTraverse(T, 2 * e + 1);
}
if (T[2 * e + 2] != NULLNODE) {
PostTraverse(T, 2 * e + 2);
}
printf("%d", T[e]);
}
void PostOrderTraverse(BinaryTree T) {
if (!isEmptyBinaryTree(T)) {
PostTraverse(T, 0);
}
printf("\n");
}
其实不难发现, 在右斜树的情况下, 会造成很多空间的浪费. 所以还有一种链表的方式来实现树的存储
四、二叉树的链式存储结构
用一个结构体来存储树:
typedef struct BiTNode {
ElementType data;//数据
struct BiTNode *lChild, *rChild;//左右孩子
} BiTNode, *BiTree;
4.1、初始化
创建一个空树, 不存在任何节点
//初始化
Status InitListBinaryTree(BiTree *T) {
*T = NULL;
return 1;
}
4.2、创建一个树
因为定义的这个树结构, 是以孩子节点查找的, 所以这里传入数据的时候使用层序序列会造成很大的困扰. 这里就使用前序遍历序列来传入.
//创建
//S为前序遍历序列, 例如AB#D##C## index为从S的哪个位置开始,为下面递归提供下标
void CreateListBinaryTree(BiTree *T, const char *S, int *index) {
int *i = index;
char el = S[i[0]];
*i = *i + 1;
if (el == '#') {
*T = NULL;
} else {
*T = (BiTree)malloc(sizeof(BiTNode));
if (!*T) {
exit(OVERFLOW);
}
(*T)->data = el;
printf("%c ", el);
CreateListBinaryTree(&(*T)->lChild, S, i);
CreateListBinaryTree(&(*T)->rChild, S, i);
}
}
4.3、树判断空
只需要判断根节点是否为空
//是否为空
Status isEmptyListBinaryTree(BiTree T) {
return T == NULL ? 1 : 0;
}
4.4.、获取深度
依次向下查找子节点, 每找到一个子节点就记录+1
//获取深度
int ListBinaryTreeDepth(BiTree T) {
if (!T) {
return 0;
}
int i, j;
if (T->lChild) {
i = ListBinaryTreeDepth(T->lChild);
} else {
i = 0;
}
if (T->rChild) {
j = ListBinaryTreeDepth(T->rChild);
} else {
j = 0;
}
return i > j ? i + 1 : j + 1;
}
4.5.、前序遍历
//前序遍历
void PreOrderTraverseList(BiTree T) {
if (T == NULL) {
return;
}
printf("%c ", T->data);
PreOrderTraverseList(T->lChild);
PreOrderTraverseList(T->RChild);
}
4.6、中序遍历
//中序遍历
void InOrderTraverseList(BiTree T) {
if (T == NULL) {
return;
}
InOrderTraverseList(T->lChild);
printf("%c ", T->data);
InOrderTraverseList(T->RChild);
}
4.7、后序遍历
//后序遍历
void PostOrderTraverseList(BiTree T) {
if (T == NULL) {
return;
}
PostOrderTraverseList(T->lChild);
PostOrderTraverseList(T->RChild);
printf("%c ", T->data);
}