1. 树和二叉树的定义
1.1 树的定义
- 树(Tree): n ( n ≥ 0 )个结点的有限集
- 空树:n = 0
- 非空树 T :
- 有且只有一个称之为根的结点
- 除根结点外的其余结点可分为 m ( m > 0)个互不相交的有限集 T_1 ,T_2 ,··· ,T_m
- 其中每个集合本身又是一颗树,称为根的子树(SubTree)
1.2 树的基本术语
- 结点:树种的一个独立单元,包含一个数据元素及若干指向其子树的分支
- 结点的度:结点拥有的子树数量
- 树的度:树内各结点度的最大值
- 叶子(终端结点):度为 0 的结点
- 非终端结点(分支结点):度不为 0 的结点;除根节点外,非终端结点也称为内部结点
- 双亲和孩子:
- 结点的子树的根称为该结点的孩子
- 该结点称为孩子的双亲
- 兄弟:同一个双亲的孩子之间互称兄弟
- 祖先:从根到该结点所经分支上的所有结点
- 子孙:以某结点为根的子树种的任一结点
- 层次:从根开始定义起,根为第一层,根的孩子为第二层,依次类推
- 堂兄弟:双亲在同一层的结点互为堂兄弟
- 树的深度(高度):树中结点的最大层次
- 有序树和无序树:
- 有序树:树中结点的各子树是从左至右有次序的(不能互换)
- 第一个孩子:最左边的子树的根
- 最后一个孩子:最右边的子树的根
- 无序树:与有序树相反
- 有序树:树中结点的各子树是从左至右有次序的(不能互换)
- 森林:m ( m ≥ 0 )棵互不相交的树的集合
1.3 二叉树的定义
-
二叉树(Binary Tree):n ( n ≥ 0 )个结点所构成的集合
- 空树:n = 0
- 非空树 T :
- 有且只有一个称之为根的结点
- 除根结点外的其余结点分为两个互不相交的子集 T_1 和 T_2 ,分别称为 T 的左子树和右子树
- T_1 和 T_2 本身又都是二叉树
-
二叉树和树的区别
- 二叉树每个结点至多只有两棵子树
- 二叉树中不存在度大于 2 的结点
- 二叉树的子树有左右之分,其次序不能任意颠倒
- 二叉树每个结点至多只有两棵子树
-
二叉树的 5 种基本形态
- 空二叉树
- 仅有根结点的二叉树
- 右子树为空的二叉树
- 左、右子树均非空的二叉树
- 左子树为空的二叉树
2. 二叉树的性质和存储过程
2.1 二叉树的性质
- 满二叉树:深度为 k 且含有 2^k - 1 个结点
- 每一层上的结点数都是最大的结点数
- 每一层 i 的结点数都具有最大值 2^(i-1)
- 每一层上的结点数都是最大的结点数
- 完全二叉树:深度为 k 的,有 n 个结点的二叉树,每一个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应
- 叶子结点只可能是在层次最大的两层上出现
- 对任一结点,若其右分支下的子孙的最大层次为 l ,则其左分支下的子孙的最大层次必为 l 或 l+1
-
重要性质
-
性质 1 :在二叉树的第 i 层上至多有 2^(i-1) 个结点(i ≥ 1)
-
性质 2 :深度为 k 的二叉树至多有 2^k - 1 个结点(k ≥ 1)
-
性质 3 :对任何一颗二叉树 T ,如果其终端结点数为 n_0 ,度为 2 的结点数为 n_2 ,则 n_0 = n_2 + 1
n = n 0 + n 1 + n 2 n = n_0 + n_1 + n_2 n=n0+n1+n2n = ( 1 n 1 + 2 n 2 ) + 1 n = (1n_1 + 2n_2) + 1 n=(1n1+2n2)+1
n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1
-
性质 4 :具有 n 个结点的完全二叉树的深度为 ⌊ log_2 (n) ⌋ + 1
-
性质 5 :如果对一棵有 n 个结点的完全二叉树(其深度为 ⌊ log_2 (n) ⌋ + 1 )的结点按层序编号(从第 1 层到第 ⌊ log_2 (n) ⌋ + 1 层,每层从左到右 ),则对任一结点 i ( 1 ≤ i ≤ n )
- i = 1 :结点 i 是二叉树的根,无双亲
- i > 1 :其双亲是结点 ⌊ i/2 ⌋
- 2i > n :结点 i 无左孩子(结点 i 为叶子结点)或左孩子为结点 2i
- 2i + 1 > n :结点 i 无右孩子或右孩子为结点 2i+1
-
2.2 二叉树的存储结构
2.2.1 顺序存储结构
#define MAXSIZE 10 // 二叉树的最大结点数
typedef TElemType SqBiTree[MAXSIZE]; // 0 号单元存储根结点
SqBiTree bt;
- 对于完全二叉树:从根起按层序存储,依次自上而下、从左至右存储结点元素
- 将完全二叉树上编号为 i 的结点元素存储在如上定义的一维数组中下标为 i-1 的分量中
- 对于一般二叉树:将其每个结点与完全二叉树上的结点相对照,存储在一维数组的相应分量中
2.2.2 链式存储结构
- 构成
- 数据域
- 左指针域
- 右指针域
- 指向双亲结点的指针域
- 在含有 n 个结点的二叉链表中有 n+1 个空链域
typedef struct BiTNode {
ElemType elem; // 结点数据域
struct BiTNode *lchild; // 左孩子指针
struct BiTNode *rchild; // 右孩子指针
} BiTNode, *BiTree;
3. 遍历二叉树和线索二叉树
3.1 遍历二叉树
-
遍历二叉树(traversing ):按某条搜索路径巡防树种每个结点,使得每个结点均被访问一次,且仅被访问一次
-
先序遍历二叉树
- 访问根节点
- 先序遍历左子树
- 先序遍历右子树
-
中序遍历二叉树
- 中序遍历左子树
- 访问根节点
- 中序遍历右子树
-
后序遍历二叉树
- 后序遍历左子树
- 后序遍历右子树
- 访问根节点
-
先序遍历:从上至下,从左至右
-
中序遍历:从左至右,从上至下
-
后序遍历:从下至上,从左至右
3.1.1 中序遍历的递归算法
void InOrderTraverse(BiTree T) {
if (T) {
InOrderTraverse(T->lchild); // 中序遍历左子树
printf("value = %d\n", T->value); // 访问根节点
InOrderTraverse(T->rchild); // 中序遍历右子树
}
}
3.1.2 中序遍历的非递归算法
- 步骤
- 初始化一个空栈 S ,指针 p 指向根结点
- 申请一个结点空间 q ,用来存放栈顶弹出的元素
- 当 p 非空或栈 S 非空时,循环执行以下操作
- p 非空,p 进栈,p 指向该结点的左孩子
- p 为空,弹出栈顶元素并访问,将 p 指向该结点的右孩子
void InOrderTraverse(BiTree T) {
BiTree* p = T;
BiNode q = (BiNode*)malloc(sizeof(BiNode));
InitStack(S);
while (p || StackEmpty(S)) {
if (p) { // p 非空
Push(S, p); // 根指针进栈
p = p->lchild; // 遍历左子树
} else {
Pop(S, q); // 退栈
printf("value = %d\n", T->value); // 访问根节点
p = q->rchild; // 遍历右子树
}
}
}
3.1.3 先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree* T) {
int value;
printf("Enter : ");
scanf("%d", &value);
if (value == 0) {
(*T) = NULL; // 递归结束,创建空树
}
else {
(*T) = (BiTNode*)malloc(sizeof(BiTNode)); // 生成根结点
(*T)->value = value; // 根结点数据域置为 value
CreateBiTree(&((*T)->lchild)); // 递归创建左子树
CreateBiTree(&((*T)->rchild)); // 递归创建右子树
}
}
3.1.4 复制二叉树
void Copy(BiTree T, BiTree* NewT) {
if (T == NULL)
{
*NewT = NULL; // 递归结束
}
else {
*NewT = (BiTNode*)malloc(sizeof(BiTNode));
(*NewT)->value = T->value; // 复制根结点
printf("copy value = %d\n", T->value);
Copy(T->lchild, &(*NewT)->lchild); // 递归复制左子树
Copy(T->rchild, &(*NewT)->rchild); // 递归复制右子树
}
}
3.1.5 计算二叉树的深度
int Depth(BiTree T) {
if (T)
{
int i = Depth(T->lchild); // 递归计算左子树的深度记为 i
int j = Depth(T->rchild); // 递归计算右子树的深度记为 j
return (i > j) ? i + 1 : j + 1;
}
else
{
return 0; // 递归结束,深度为 0
}
}
3.1.6 统计二叉树中结点的个数
int NodeCount(BiTree T) {
return T ? NodeCount(T->lchild) + NodeCount(T->rchild) + 1 : 0;
// T 不为空 : 结点个数 = 左子树的结点个数 + 右子树结点个数 + 1
// T 为空 : 结点个数为 0 ,递归结束
}
测试代码
#include <stdio.h>
#include <stdlib.h>
void InOrderTraverse(BiTree);
void CreateBiTree(BiTree);
void Copy(BiTree, BiTree);
int Depth(BiTree);
int NodeCount(BiTree);
typedef struct BiTNode {
int value;
struct BiTNode* lchild;
struct BiTNode* rchild;
} BiTNode, *BiTree;
int main() {
BiTree tree1;
BiTree tree2;
CreateBiTree(&tree1);
printf("Create Success\n");
printf("****************\n");
InOrderTraverse(tree1);
printf("Traverse Success\n");
printf("****************\n");
Copy(tree1, &tree2);
printf("Copy Success\n");
printf("****************\n");
InOrderTraverse(tree2);
printf("Traverse Success\n");
printf("****************\n");
int depth = Depth(tree1);
printf("depth = %d\n", depth);
printf("****************\n");
int count = NodeCount(tree1);
printf("count = %d\n", count);
printf("****************\n");
}
void InOrderTraverse(BiTree T) {
if (T) {
InOrderTraverse(T->lchild);
printf("value = %d\n", T->value);
InOrderTraverse(T->rchild);
}
}
void CreateBiTree(BiTree* T) {
int value;
printf("Enter : ");
scanf("%d", &value);
if (value == 0) {
*T = NULL;
}
else {
*T = (BiTNode*)malloc(sizeof(BiTNode));
(*T)->value = value;
CreateBiTree(&((*T)->lchild));
CreateBiTree(&((*T)->rchild));
}
}
void Copy(BiTree T, BiTree* NewT) {
if (T == NULL)
{
*NewT = NULL;
}
else {
*NewT = (BiTNode*)malloc(sizeof(BiTNode));
(*NewT)->value = T->value;
printf("copy value = %d\n", T->value);
Copy(T->lchild, &(*NewT)->lchild);
Copy(T->rchild, &(*NewT)->rchild);
}
}
int Depth(BiTree T) {
if (T)
{
int i = Depth(T->lchild);
int j = Depth(T->rchild);
return (i > j) ? i + 1 : j + 1;
}
else
{
return 0;
}
}
int NodeCount(BiTree T) {
return T ? NodeCount(T->lchild) + NodeCount(T->rchild) + 1 : 0;
}
3.2 线索二叉树
- 线索链表:以特殊结点结构构成的二叉链表作为二叉树的存储结构
- 线索:指向结点前驱和后继的指针
- 线索二叉树(Thread Binary Tree):带有线索的二叉树
- 线索化:对二叉树以某种次序遍历使其变为线索二叉树的过程
- 结点形式:
- 左孩子
- 左标志
- 0 :指向结点的左孩子
- 1 :指向结点的前驱
- 数据
- 右标志
- 0 :指向结点的右孩子
- 1 :指向结点的后继
- 右孩子
typedef struct BiThrNode {
ElemType elem;
struct BiThrNode *lchild, *rchild; // 左右孩子指针
int LTag, RTag; // 左右标志
} BiThrNode, *BiThrTree;
3.2.1 以结点 p 为根的子树中序线索化
- 步骤
- p 非空,左子树递归线索化
- p 的左孩子为空,给 p 加上左线索,将 LTag 置为 1 ,让 p 的左孩子指针指向 pre (前驱)
- 非空,将 p 的 LTag 置为 0
- pre 的右孩子为空,给 pre 加上右线索,将 RTag 置为 1 ,让 pre 的左孩子指针指向 p (后继)
- 非空,将 pre 的 RTag 置为 0
- 将 pre 指向刚访问过的结点
- pre = p
- 右子树递归序列化
void InThreading(BiThrTree p) {
if (p) {
InThreading(p->lchild); // 左子树递归线索化
if (!p->lchild) { // p 的左孩子为空
p->LTag = 1; // 给 p 加上左线索
p->lchild = pre; // 让 p 的左孩子指针指向 pre (前驱)
}
else {
p->LTag = 0;
}
if (!pre->rchild) { // pre 的右孩子为空
pre->RTag = 1; // 给 pre 加上右线索
pre->rchild = p; // 让 pre 的左孩子指针指向 p (后继)
}
else {
pre->RTag = 0;
}
pre = p; // 保持 pre 指向 p 的前驱
InThreading(p->rchild); // 右子树递归序列化
}
}
3.2.2 带头结点的二叉树中序线索化
void InOrderThreading(BiThrTree* p, BiThrTree T) {
*p = (BiThrNode*)malloc(sizeof(BiThrNode)); // 建立头结点
(*p)->LTag = 0; // 头结点有左孩子,若树非空,则其左孩子为树根
(*p)->RTag = 1; // 头结点的右孩子指针为右线索
(*p)->rchild = *p; // 初始化时右指针指向自己
if (!T)
{
(*p)->lchild = *p; // 若树为空,则左指针也指向自己
}
else
{
(*p)->lchild = T; // 头结点的左孩子指向根
pre = *p; // pre 初值指向头结点
InThreading(T); // 对以 T 为根的二叉树进行中序线索化
printf("InThreading Success\n");
pre->rchild = *p; // pre 为最右结点,pre 的右线索指向头结点
pre->RTag = 1;
(*p)->rchild = pre; // 头结点的右线索指向 pre
}
}
3.2.3 遍历线索二叉树
-
在中序线索二叉树中查找
- 查找 p 指针所指结点的前驱
- p->LTag = 1:p 的左链指示其前驱
- p->LTag = 0:p 有左子树,结点的前驱是遍历左子树时最后访问的一个结点(左子树中最右下的结点)
- 查找 p 指针所指结点的后继
- p->RTag = 1:p 的右链指示其后继
- p->RTag = 0:p 有右子树,结点的后继是遍历右子树时访问的第一个结点(右子树中最左下的结点)
- 查找 p 指针所指结点的前驱
-
在先序线索二叉树中查找
- 查找 p 指针所指结点的前驱
- p->LTag = 1:p 的左链指示其前驱
- p->LTag = 0:p 有左子树
- 结点的前驱
- *p 是其双亲的左孩子,则其前驱为其双亲结点
- *p 是其双亲的右孩子,则其前驱为其双亲左子树上先序遍历最后访问二点结点
- 结点的前驱
- 查找 p 指针所指结点的后继
- p->RTag = 1:p 的右链指示其后继
- p->RTag = 0:p 有右子树,*p 的后继是其左子树根(若存在)或右子树根
- 查找 p 指针所指结点的前驱
-
在后序线索二叉树中查找
-
查找 p 指针所指结点的前驱
- p->LTag = 1:p 的左链指示其前驱
- p->LTag = 0:
- p->RTag = 1:p 的左链指示其前驱
- p->RTag = 0:p 的右链指示其前驱
-
查找 p 指针所指结点的后继
-
*p 是二叉树的根:后继为空
-
*p 是其双亲的右孩子:后继为双亲结点
-
*p 是其双亲的左孩子:
- *p 没有右兄弟:后继为双亲结点
- *p 有右兄弟:后继为双亲的右子树上按后序遍历出的第一个结点(右子树中最左下的叶结点)
-
-
遍历中序线索二叉树
- 步骤
- 指针 p 指向根结点
- p 为非空树或遍历未结束时,循环执行以下操作
- 沿左孩子向下,到达最左下结点 *p ,它是中序的第一个结点
- 访问 *p
- 沿右线索反复查找当前结点 *p 的后继结点并访问后继结点,直至右线索为 0 或者遍历结束
- 转向 p 的右子树
void InOrderThraverse_Thr(BiThrTree T)
{
pre = T->lchild; // p 指向根结点
while (pre != T) // 空树或遍历结束时,pre == T
{
while (pre->LTag == 0)
{
pre = pre->lchild; // 沿左孩子向下
}
printf("value = %d\n", pre->value); // 访问其左子树为空的结点
while (pre->RTag == 1 && pre->rchild != T)
{
pre = pre->rchild; // 沿右线索
printf("value = %d\n", pre->value); // 访问后继结点
}
pre = pre->rchild; // 转向 p 的右子树
}
}
测试代码
#include <stdio.h>
#include <stdlib.h>
void CreateBiThrTree(BiThrTree);
void InThreading(BiThrTree);
void InOrderThreading(BiThrTree, BiThrTree);
void InOrderThraverse_Thr(BiThrTree);
typedef struct BiThrNode {
int value;
struct BiThrNode* lchild, * rchild;
int LTag, RTag;
} BiThrNode, *BiThrTree;
BiThrTree pre = NULL;
int main() {
pre = (BiThrNode*)malloc(sizeof(BiThrNode));
pre->rchild = NULL;
BiThrTree tree1;
BiThrTree tree2;
CreateBiThrTree(&tree1);
printf("Create Success\n");
printf("****************\n");
InOrderThreading(&tree2, tree1);
printf("InOrderThreading Success\n");
printf("****************\n");
InOrderThraverse_Thr(tree2);
printf("InOrderThraverse_Thr Success\n");
printf("****************\n");
}
void CreateBiThrTree(BiThrTree* p) {
int value;
printf("Enter : ");
scanf("%d", &value);
if (value == 0) {
*p = NULL;
}
else {
*p = (BiThrNode*)malloc(sizeof(BiThrNode));
(*p)->value = value;
CreateBiThrTree(&((*p)->lchild));
CreateBiThrTree(&((*p)->rchild));
}
}
void InThreading(BiThrTree p) {
if (p) {
InThreading(p->lchild);
if (!p->lchild) {
p->LTag = 1;
p->lchild = pre;
}
else {
p->LTag = 0;
}
if (!pre->rchild) {
pre->RTag = 1;
pre->rchild = p;
}
else {
pre->RTag = 0;
}
pre = p;
InThreading(p->rchild);
}
}
void InOrderThreading(BiThrTree* p, BiThrTree T) {
*p = (BiThrNode*)malloc(sizeof(BiThrNode));
(*p)->LTag = 0;
(*p)->RTag = 1;
(*p)->rchild = *p;
if (!T)
{
(*p)->lchild = *p;
}
else
{
(*p)->lchild = T;
pre = *p;
InThreading(T);
printf("InThreading Success\n");
pre->rchild = *p;
pre->RTag = 1;
(*p)->rchild = pre;
}
}
void InOrderThraverse_Thr(BiThrTree T)
{
pre = T->lchild;
while (pre != T)
{
while (pre->LTag == 0)
{
pre = pre->lchild;
}
printf("value = %d\n", pre->value);
while (pre->RTag == 1 && pre->rchild != T)
{
pre = pre->rchild;
printf("value = %d\n", pre->value);
}
pre = pre->rchild;
}
}
4. 树和森林
4.1 树的存储结构
4.1.1 双亲表示法
- 以一组连续的存储单元存储树的结点,每个结点除了数据域 data 外,还附设一个 parent 域用以表示其双亲结点的位置
- 构成
- 数据域
- 双亲域
4.1.2 孩子表示法
- 构成
- 数据域
- 孩子域
- 与双亲表示法结合:带双亲的孩子链表
4.1.3 孩子兄弟表示法
-
孩子兄弟表示法:二叉树表示法、二叉链表表示法
-
构成
- 第一个孩子结点
- 数据域
- 下一个兄弟结点
typedef struct CSNode {
ElemType elem;
struct CSNode *firstchild;
struct CSNode *nextsibling;
} CSNode, *CSTree;
4.2 森林与二叉树的转换
4.2.1 森林转换成二叉树
- F = { T_1 ,T_2 ,··· ,T_m } 为森林,转化成一颗二叉树 B = { root ,LB ,RB }
- F 为空(m = 0):B 为空树
- F 非空(m ≠ 0):
- B 的根 root :森林中第一棵树的根 ROOT( T_1 )
- B 的左子树 LB :从 T_1 中根结点的子树森林 F_1 = { T_11 ,T_12 ,··· ,T_1m } 转换而成的二叉树
- B 的右子树 RB :从森林 F’ = { T_2 ,T_3 ,··· ,T_m } 转换而成的二叉树
4.2.2 二叉树转换成森林
- B = { root ,LB ,RB } 为一颗二叉树,转化成森林 F = { T_1 ,T_2 ,··· ,T_m }
- B 为空:F 为空
- B 非空:
- F 中第一棵树 T_1 的根 ROOT( T_1 ):B 的根 root
- T_1 中根结点的子树森林 F_1:B 的左子树 LB 转换而成的森林
- 除 T_1 之外其余树组成的森林 F’ = { T_2 ,T_3 ,··· ,T_m } :B 的右子树 RB 转换而成的森林
4.3 树和森林的遍历
4.3.1 树的遍历
- 先根遍历:先访问树的根结点,然后依次先根遍历每棵子树
- 后根遍历:先依次后根遍历每棵子树,然后访问根结点
4.3.2 森林的遍历
-
先序遍历森林
- 访问森林中第一棵树的根结点
- 先序遍历第一棵树的根结点的子树森林
- 先序遍历除去第一棵树之后剩余的树构成的森林
-
中序遍历森林
-
中序遍历第一棵树的根结点的子树森林
-
访问森林中第一棵树的根结点
-
中序遍历除去第一棵树之后剩余的树构成的森林
-
5. 哈夫曼树及其应用
5.1 哈夫曼树的基本概念
5.1.1 哈夫曼树的定义
- 哈夫曼树(Huffman Tree)(最优树):一类带权路径长度最短的树
5.1.2 哈夫曼树的基本术语
-
路径:从树中的一个结点到另一个结点之间的分支构成这两个结点之间的路径
-
路径长度:路径上的分支数目
-
树的路径长度:从树根到每一结点的路径长度之和
-
权:对实体的数值化描述
- 结点权
- 边权
-
结点的带权路径长度:从该结点到树根之间的路径长度与结点上全的乘积
-
树的带权路径长度:树中所有叶子结点的带权路径长度之和,记作
W P L = ∑ k = 1 n w k l k WPL = \sum_{k=1}^{n}{w_kl_k} WPL=k=1∑nwklk -
哈夫曼树:m 个权值 { w_1 ,w_2 ,··· ,w_m },可以构造一棵含 n 个叶子结点的二叉树,每个叶子结点的权为 w_i ,则其中带权路径长度 WPL 最小的二叉树称作最优二叉树或哈夫曼树
5.2 哈夫曼树的构造算法
5.2.1 哈夫曼树的构造过程
- 根据给定的 n 个权值 { w_1 ,w_2 ,··· ,w_n },构造 n 棵只有根结点的二叉树,这 n 棵二叉树构成一个森林 F
- 在森林 F 中选取两棵根结点的权值最小的数作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右树上根结点的权值之和
- 在森林 F 中删除这两棵树,同时将新得到的二叉树加入 F 中
- 重复第二、三步骤,直到 F 只含一棵树为止
5.2.2 哈夫曼算法的实现
-
哈夫曼树中没有度为 1 的结点,则一棵有 n 个叶子结点的哈夫曼树共有 2n-1 个结点,可以存储在一个大小为 2n-1 的一维数组中
- 数组的 0 号单元不使用,从 1 号单元开始使用,所以数组大小为 2n
- 将叶子结点集中存储在前面部分 1~n 个位置,而后面的 n-1 个位置存储其余非叶子结点
- 数组的 0 号单元不使用,从 1 号单元开始使用,所以数组大小为 2n
-
构成
- 权值
- 双亲
- 左孩子
- 右孩子
typedef struct {
int weight; // 结点的权值
int parent; // 结点的双亲的下标
int lchild; // 结点的左孩子的下标
int rchild; // 结点的右孩子的下标
} HTNode, *HuffmanTree;
- 构造哈夫曼树
void CreateHuffanTree(HuffmanTree* tree, int n) {
if (n <= 1) {
return 0;
}
int m = 2 * n - 1;
*tree = (HTNode*)malloc(sizeof(HTNode));
// 0 号单元未用,所以需要动态分配 m+1 单元,tree[m] 表示根结点
for (int i = 1; i <= m; i++) { // 将 1~m 号单元中的双亲、左孩子,右孩子的下标都初始化为 0
tree[i].parent = 0;
tree[i].lchild = 0;
tree[i].rchild = 0;
}
for (int i = 1; i <= m; i++) { // 输入前 n 个单元中叶子结点的权值
scanf("%d", &tree[i].weight);
}
for (int i = n + 1; i <= m; i++) {
// 通过 n-1 次的选择、删除、合并来创建哈夫曼树
Select(tree, i-1, s1, s2);
// 在 tree[k] (1 ≤ k ≤ i-1) 中选择两个其双亲域为 0 且权值最小的结点,并返回它们在 tree 中的序号 s1 和 s2
tree[s1].parent = i;
tree[s2].parent = i;
// 得到新结点 i ,从森林中删除 s1 , s2 ,将 s1 和 s2 的双亲域由 0 改为 1
tree[i].lchild = s1;
tree[i].rchild = s2;
// s1 , s2 分别作为 i 的左右孩子
tree[i].weight = tree[s1].weight + tree[s2].weight;
// i 的权值为左右孩子权值之和
}
}
5.3 哈夫曼编码
-
编码
- 前缀编码
- 哈夫曼编码
-
哈夫曼编码性质
- 哈夫曼编码是前缀编码
- 哈夫曼编码是最优前缀编码