树与二叉树
树的定义和基本术语
树的基本概念
-
树是n(n≥0)个结点的有限集合,n = 0时,称为空树,这是一种特殊情况。在任意一棵非空树中应满足:
1)有且仅有一个特定的称为根的结点。
2)当n > 1时,其余结点可分为m(m > 0)个互不相交的有限集合T1, T2,…, Tm,其中每个集合本身又是一棵树,并且称为根结点的子树。
-
∅ 空树——结点数为0的树
-
非空树的特性:
(1)有且仅有一个根节点
(2)没有后继的结点称为叶子结点(或终端结点)
(3)有后继的结点称为分支结点(或非终端结点)
(4)除了根节点外,任何一个结点都有且仅有一个前驱
(5)每个结点可以有0个或多个后继
结点之间的关系描述
- 什么是两结点路径:只能是自上而下经过的节点
- 什么是路径长度:节点间的边数
有序树 V.S 无序树
有序树——逻辑上看,树中结点的各子树从左至右是有次序的,不能互换
无序树——逻辑上看,树中结点的各子树从左至右是无次序的,可以互换
注:具体看你用树存什么,是否需要用结点的左右位置反映某些逻辑关系
树 V.S 森林
森林是m(m≥0)棵互不相交的树的集合,m可为0,空森林
树的性质
常见考点1:结点数=总度数+1
常见考点2:度为m的树、m叉树 的区别
常见考点3:
- 度为m的树第 i 层至多有 m(i-1) 个结点(i≥1)
- m叉树第 i 层至多有 mi-1 个结点(i≥1)
常见考点4:高度为h的m叉树至多有
m
h
−
1
m
−
1
\frac{m^h-1}{m-1}
m−1mh−1 个结点。
常见考点5 :高度为h的m叉树至少有 h 个结点。高度为h、度为m的树至少有 h+m-1 个结点。
常见考点6:具有n个结点的m叉树的最小高度为 logm{n(m - 1) + 1)}
二叉树的定义和基本术语
二叉树的定义
二叉树是 n(n≥0)个结点的有限集合:
① 或者为空二叉树,即n = 0。
② 或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树。
特点:①每个结点至多只有两棵子树 ②左右子树不能颠倒(二叉树是有序树)
二叉树的五种状态
特殊的二叉树
- 满二叉树:一棵高度为h,且含有2h - 1个结点的二叉树
特点:
①只有最后一层有叶子结点
②不存在度为 1 的结点
③按层序从 1 开始编号,结点 i 的左孩子为 2i,右孩子为 2i+1;结点 i 的父节点为[ i/2 ](如果有的话) - 完全二叉树:当且仅当其每个结点都与高度为h的满二叉树中编号为1~n的结点一一对应时,称为完全二叉树
如果某结点只有一个孩子,那么一定是左孩子,否则不能构成完全二叉树。
特点:
①只有最后两层可能有叶子结点
②最多只有一个度为1的结点
③按层序从 1 开始编号,结点 i 的左孩子为 2i,右孩子为 2i+1;结点 i 的父节点为[ i/2 ](如果有的话)
④ i≤ [ n / 2 ]为分支结点, i> [ n / 2 ]为叶子结点。 - 二叉排序树:一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
左子树上所有结点的关键字均小于根结点的关键字;
右子树上所有结点的关键字均大于根结点的关键字。
左子树和右子树又各是一棵二叉排序树,二叉排序树可用于元素的排序、搜索
- 平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过1,平衡二叉树能有更高的搜索效率
二叉树的性质
二叉树的常考性质
常见考点1:
设非空二叉树中度为0、1和2的结点个数分别为n0、n1和n2,则 n0 = n2 + 1(叶子结点比二分支结点多一个)
假设树中结点总数为 n,则
① n = n0 + n1 + n2
② n = n1 + 2n2 +1(树的结点数=总度数+1)
②-①可推出 n0 = n2 + 1
常见考点2:
- 二叉树第 i 层至多有 2(i-1) 个结点(i≥1)
- m叉树第 i 层至多有 mi-1 个结点(i≥1)
常见考点3:
- 高度为h的2叉树至多有 2h-1 个结点。
- 高度为h的m叉树至多有 m h − 1 m − 1 \frac{m^h-1}{m-1} m−1mh−1 个结点。
完全二叉树的常考性质
常见考点:
- 具有n个(n > 0)结点的完全二叉树的高度h为log2(n + 1)向上取整或log2n + 1 向下取整
- 完全二叉树,可以由他的结点数 n 推出度为0、1和2的结点个数为n0、n1和n2(突破点:完全二叉树最多只会有一个度为1的结点)
因为:完全二叉树最多只有一个度为1的结点,即n1=0或1,n0 = n2 + 1===>n0 + n2 一定是奇数,所以:
①若完全二叉树有2k个(偶数)个结点,则必有 n1=1, n0 = k, n2 = k-1
②若完全二叉树有2k-1个(奇数)个结点,则必有 n1=0, n0 = k, n2 = k-1
二叉树的顺序存储结构(不推荐)
二叉树的定义
#define MAXTSIZE 100
typedef int Elemtype;
struct TreeNode{
Elemtype value; //数值
bool isEmpty; //判断是否为空
};
typedef TreeNode SqBiTree [MAXTSIZE];
分析:
定义一个长度为 MaxSize 的数组 t ,按照从上至下、从左至右的顺序依次存储完全二叉树中的各个结点
二叉树结点分析
- 针对于节点i:
① i 的左孩子——2i
② i 的右孩子——2i+1
③ i 的父节点——i / 2向下取整
④ i 所在的层次log2(n + 1)向上取整或log2n + 1 向下取整 - 若完全二叉树中共有n个结点,则
①判断 i 是否有左孩子?——2i<=n
②判断 i 是否有右孩子?——2i+1<=n
③判断 i 是否是叶子/分支结点?——i>n / 2向下取整
二叉树顺序存储缺点
当存储的二叉树不是完全二叉树时,在数组中会存在众多空值的区域,浪费大量的空间,除此之外,因为空值的存在,判断i是否有左右孩子以及分支的公式不适用,无法从结点编号反映出结点间的逻辑关系,所以顺序存储方式只适合存储完全二叉树
二叉树的链式存储结构
二叉树的定义及初始化
n个结点的二叉链表共有 n+1 个空链域(可以用于构造线索二叉树)
//二叉链表
typedef int Elemtype;
typedef struct BiTNode { //也称二叉链表
Elemtype data; //数据域
struct BiTNode *lchild, *rchild; //左右孩子指针
} BiTNode, *BiTree;
//三叉链表
typedef int Elemtype;
typedef struct BiTNode { //也称三叉链表,方便找父结点
Elemtype data; //数据域
struct BiTNode *lchild, *rchild,*parent; //左右孩子、父亲点指针
} BiTNode, *BiTree;
//初始化
bool InitBiTree(BiTree &T) {
T = new BiTNode();
T->lchild = NULL;
T->rchild = NULL;
return true;
}
二叉树的先中后序遍历
//访问输出结点数据
void Visit(BiTree T) {
cout << T->data << " ";
}
//先序遍历
void PreOrder(BiTree T) {
if (T != NULL) {
Visit(T);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
//中序遍历
void InOrder(BiTree T) {
if (T != NULL) {
InOrder(T->lchild);
Visit(T);
InOrder(T->rchild);
}
}
//后序遍历
void PosOrder(BiTree T) {
if (T != NULL) {
PosOrder(T->lchild);
PosOrder(T->rchild);
Visit(T);
}
}
二叉树的层序遍历
算法思想
①初始化一个辅助队列
②根结点入队
③若队列非空,则队头结点出队,访问该结点,并将其左、右孩子插入队尾(如果有的话)
④重复③直至队列为空
//二叉树的层序遍历
void LevelOrder(BiTree T) {
queue<BiTNode *> Q; //存指针而不是结点
BiTree P; //p作为子树一直指向以队头作为根结点的树
Q.push(T); //根结点入队
while (!Q.empty()) { //队列不空则循环
cout << Q.front()->data; //输出队头值
P = Q.front(); //对头结点指针赋值P;
Q.pop(); //删除队头
if (P->lchild != NULL) { //其非空左右孩子入队
Q.push(P->lchild);
}
if (P->rchild != NULL) {
Q.push(P->rchild);
}
}
}
求二叉树深度、节点个数、复制二叉树
//求二叉树深度
int Depth(BiTree T) {
if (T == NULL) { //如果是空树,深度为0, 递归结束
return 0;
} else {
int left = Depth(T->lchild); //递归计算左子树的深度记为left
int right = Depth(T->rchild); //递归计算右子树的深度记为right
if (left > right) { // 二叉树的深度为left与right的较大者加1
return left + 1;
} else {
return right + 1;
}
}
}
//求节点的个数
int NodeCount(BiTree T) {
if (T == NULL || T->data == '#') { //T为空节点时返回0,递归结束
return 0;
} else {
int left = NodeCount(T->lchild);
int right = NodeCount(T->rchild);
return left + right + 1; // 否则结点个数为左子树的结点个数+右子树的结点个数+1
}
}
//复制二叉树
void Copy(BiTree T, BiTree &NewT) {
if (T == NULL) {
NewT = NULL;
return;
} else {
NewT = new BiTNode();
NewT->data = T->data; //复制根结点
Copy(T->lchild, NewT->lchild); //递归复制左子树
Copy(T->rchild, NewT->rchild); //递归复制右子树
}
由遍历序列构造二叉树(画图方法)
考点:
注意:
- 若只给出一棵二叉树的 前/中/后/层序遍历序列中的一种,不能唯一确定一棵二叉树
- 前序、后序、层序序列的两两组合无法唯一确定一棵二叉树
方法一:前序 + 中序遍历序列
例题1
例题2
方法二:后序 + 中序遍历序列
方法三:层序 + 中序遍历序列
由遍历序列构造二叉树(代码实现)
//先序遍历建立二叉树
void CreateBiTree(BiTree &T) {
char ch;
cin >> ch;
if (ch == '#') {
T = NULL;
} else {
T = new BiTNode();
T->data = ch;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
}
//中序遍历建立二叉树
void CreateBiTree(BiTree &T) {
char ch;
cin >> ch;
if (ch == '#') {
T = NULL;
} else {
T = new BiTNode();
CreateBiTree(T->lchild);
T->data = ch;
CreateBiTree(T->rchild);
}
}
//后序遍历建立二叉树
void CreateBiTree(BiTree &T) {
char ch;
cin >> ch;
if (ch == '#') {
T = NULL;
} else {
T = new BiTNode();
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
T->data = ch;
}
}
线索二叉树/线索链表(先/中/后序线索二叉树)
线索二叉树的概念及作用
-
概念:在每个结点中增加两个指针域来存放在遍历时得到的有关前驱和后继信息,同时用Tag表示其指针指向信息。
以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,其中指向结点前驱和后继的指针,叫做线索。加上线索的二叉树称之为线索二叉树 (Threaded Binary Tree)。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化 -
作用
方便从一个指定结点出发,找到其前驱、后继;方便遍历 -
必清概念:
中序前驱/中序后继;先序前驱/先序后继;后序前驱/后序后继 -
考点:
-
例子:
中序遍历序列:D G B E A F C,线索化后为:
先序遍历序列:A B D G E C F,线索化后为:
后序遍历序列:G D E B F C A,线索化后为:
线索二叉树的存储结构
typedef char Elemtype;
typedef struct BiThrNode { //也称二叉链表
Elemtype data; //数据域
struct BiTNode *lchild, *rchild; //左右孩子指针
int LTag,RTag; //左右标志
} BiThrNode, *BiTHrTree;
bool InitBiTree(BiThrTree &T) {
T = new BiThrNode();
T->lchild = NULL;
T->rchild = NULL;
T->LTag=0; //均表示孩子
T->RTag=0;
return true;
}
中序线索化
算法步骤
①如果q非空,左子树递归线索化。
②如果q的左孩子为空,则给q加上左线索,将其LTag置为1, 让q的左孩子指针指向pre(前驱);否则将q的LTag置为0。
③如果pre的右孩子为空,则给pre加上右线索,将其RTag置为1,让pre的右孩子指针指向q(后继);否则将pre的RTag置为0。
④将pre指向刚访问过的结点q, 即pre=q。
⑤右子树递归线索化。
图解
代更…………
代码
//以结点q为根的子树中序线索化
void InThreading(BiThrTree q, BiThrTree &pre) {
InThreading(q->lchild, pre); //左子树递归线索化
if (q->lchild == NULL) { //q的左孩子为空
q->lchild = pre; //q的左孩子指针指向pre(前驱)
q->LTag = 1; //给p加上左线索
} else {
q->LTag = 0;
}
if (pre->rchild == NULL) { //pre的右孩子为空
pre->rchild = q; //pre的右孩子指针指向q (后继)
pre->RTag = 1; // pre的右孩子为空
} else {
pre->RTag = 0;
}
pre = q; //保持pre指向q的前驱
InThreading(q->rchild, pre);
}
在线索二叉树中找前驱后继
待更新………………
树的存诸结构
双亲表示法(顺序存储)
存储方式为:
①根节点的parent为-1;
②其孩子的parent为其父结点所在的数组下标,以此类推。
#define MAX_TREE_SIZE 100
typedef int ElemType;
typedef struct { //树的结点定义
ElemType data; //元素值
int parent; //双亲位置域
} PTNode;
typedef struct {
PTNode nodes[MAX_TREE_SIZE]; //双亲表示
int n;//结点个数
} PTree;
孩子表示法(顺序+链式存储)
孩子表示法: 顺序存储各个节点,每个结点中保存孩子链表头指针
以上面的树为例:
struct CTNode {
int child;//孩子结点在数组中的位置
struct CTNode *next; //下一个孩子
};
typedef struct {
ElemType data;
struct CTNode *firstChild;/第一个孩子
} CTBox;
typedef struct {
CTBox nodes[MAX_TREE_SIZE];
int n, r;//结点数和根的位置CTree;
};
孩子兄弟表示法(链式存储)
typedef struct CSNode {
ElemType data;
struct cSNode *firstchild, *nextsibling;
} CSNode, *CSTree;
*firstchild:看做左指针,其指向的是根节点及其父亲结点的第一个左孩子
*nextsibling:看做右指针,其指向的是*firstchild的兄弟结点
-
树和二叉树的相互转化
①普通树转化为孩子兄弟法表示,转化后,B的右孩子代表B的兄弟结点,左孩子代表转化前B的第一个左孩子。
②孩子兄弟法转化为普通树
-
森林和二叉树的转换
①树转森林
②森林转树
树和森林的遍历
- 树的先/后根遍历
先根遍历:若树非空,先访问根结点,再依次对每棵子树进行先根遍历。
先根遍历为 :A (B (EK)F)(CG)(DH l J)
树的先根遍历序列与这棵树相应二叉树的先序序列相同。
//树的先根遍历
void Pre0rder(TreeNode *R){
if (R!=NULL){
visit(R);//访问根节点
while(R还有下一个子树T)
Pre0rder(T);//先根遍历下一棵子树
}
}
- 树的后根遍历
后根遍历 :若树非空,先依次对每棵子树进行后根遍历,最后再访问根结点。
后跟遍历为:((KE)F B) (G C) (H l J D)A
树的后根遍历序列与这棵树相应二叉树的中序序列相同。
//树的后根遍历
void Post0rder(TreeNode *R){
if (R!=NULL){
while(R还有下一个子树T)
Post0rder(T);//后根遍历下一棵子树visit(R);l/访问根节点
visit(R);//访问根节点
}
}
-
树的层次遍历
①若树非空,则根节点入队
②若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队
③重复②直到队列为空
-
森林的先序遍历
①若森林为非空,则按如下规则进行遍历:访问森林中第一棵树的根结点。
②先序遍历第一棵树中根结点的子树森林。
③先序遍历除去第一棵树之后剩余的树构成的森林。
先序遍历的结果为:(B(EK L) F)(C G) (D (HM) l J)
注:效果等同于依次对各个树进行先根遍历,效果也等同于依次对二叉树的先序遍历 -
森林的中序遍历
①若森林为非空,则按如下规则进行遍历:
②中序遍历森林中第一棵树的根结点的子树森林。访问第一棵树的根结点。
③中序遍历除去第一棵树之后剩余的树构成的森林。
中序遍历的结果为:((K L E ) F B)(G C) ((M H) I J D)
注:效果等同于依次对各个树进行后根遍历,效果也等同于依次对二叉树的中序遍历
二叉排序树
二叉排序树的定义
又称二叉查找树(BST,Binary Search Tree)一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
①左子树上所有结点的关键字均小于根结点的关键字;
②右子树上所有结点的关键字均大于根结点的关键字;
③左子树和右子树又各是一棵二叉排序树。
左子树结点值<根结点值<右子树结点值——>进行中序遍历,可以得到一个递增的有序序列
例如:
二叉排序树的查找
左子树结点值<根结点值<右子树结点值
①若树非空,目标值与根结点的值比较:若相等,则查找成功;
②若小于根结点,则在左子树上查找,否则在右子树上查找。
③查找成功,返回结点指针;查找失败返回NULL
typedef int ElemType;
typedef struct BSTNode {
ElemType key;
struct BSTNode *lchild, *rchild;
} BSTNode, *BSTree;
//在二叉排序树中查找值为 key 的结点,非递归
//时间复杂度O(1)
BSTNode *BST_Search(BSTree T, ElemType key) {
while (T != NULL && key != T->key) {//若树空或等于根结点值,则结束循环
if (key < T->key) { //小于,则在左子树上查找
T = T->lchild;
} else { //大于,则在右子树上查找
T = T->rchild;
}
}
return T;
};
//在二叉排序树中查找值为 key 的结点,递归;
//时间复杂度O(h)高度
BSTNode *BSTSearch(BSTree T, ElemType key) {
if (T == NULL) {
return NULL;
}
if (key == T->key) {
return T;
} else if (key < T->key) {
return BSTSearch(T->lchild, key);
} else {
return BSTSearch(T->rchild, key);
}
};
二叉排序树的插入
若原二叉排序树为空,则直接插入结点;否则,若关键字k小于根结点值,则插入到左子树,若关键字k大于根结点值,则插入到右子树
//在二叉排序树插入关键字为k的新结点(递归实现)最坏空间复杂度O(h)
bool BSTInsert(BSTree &T, ElemType key) {
if (T == NULL) { //原树为空
T = new BSTNode();
T->key = key;
T->rchild = NULL;
T->lchild = NULL;
return true;
} else if (key == T->key) {
return false;
} else if (key < T->key) { //小于根结点插入左子树
return BSTInsert(T->lchild, key);
} else { //大于根结点插入右子树
return BSTInsert(T->rchild, key);
}
}
//在二叉排序树插入关键字为k的新结点(非递归实现)最坏空间复杂度O(h)
bool BST_Insert(BSTree &T, ElemType key) {
if (T == NULL) { //如果T为空直接插入
T = new BSTNode();
T->key = key;
T->lchild = T->rchild = NULL;
return true;
} else {
BSTNode *p = T, *pre = NULL; //P指向T,pre指向父结点
while (p != NULL && key != p->key) {
pre = p; //pre指向p
if (key < p->key) {
p = p->lchild;
} else {
p = p->rchild;
}
}
//出循环后,p指向空,pre指向p的父结点
p = new BSTNode(); //P新生成一个结点存储key
p->key = key;
p->lchild = p->rchild = NULL;
if (p->key < pre->key) { //与其父结点比较判断p的位置
pre->lchild = p;
} else {
pre->rchild = p;
}
return true;
}
}
二叉排序树的构造
//按照str[]中的关键字序列建立二叉排序树
void Creat_BST(BSTree &T ,int str[] ,int n){
T=NULL;
//初始时T为空树
int i=0;
while(i<n){
//依次将每个关键字插入到二叉排序树中
BST_Insert(T,str[i] );
i++;
}
}
二叉排序树的删除
先搜索找到目标结点:
①若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。
②若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置。
③若结点z有左、右两棵子树,则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况。
注意:
此处的直接后继是指z 右子树中最小的值(最左下)
此处的直接前驱是指z 左子树中最大的值(最右下)
查找效率分析
查找长度――在查找运算中,需要对比关键字的次数称为查找长度,反映了查找操作时间复杂度
注:
①若树高h,找到最下层的一个结点需要对比h 次
②最好情况:n个结点的二叉树最小高度为(log2n+ 1)向下取整。平均查找长度=O(log2n)
③最坏情况:每个结点只有一个分支,树高h=结点数n。 子均查找长度=O(n)
查找成功的平均查找长度ASL (Average Search Length)
平衡二叉树
平衡二叉树的定义
平衡二叉树(Balanced Binary Tree),简称平衡树(AVL树)――树上任一结点的左子树和右子树的高度之差不超过1。
结点的平衡因子=左子树高 - 右子树高(只可能是-1、0、1),只要有任一结点的平衡因子绝对值大于1,就不是平衡二叉树
//平衡二叉树结点
typedef struct AVLNode {
int key; //数据域
int balance; //平衡因子
struct AVLNode *lchild, *rchild;
} AVLNode, *AVLTree;
平衡二叉树的调整
首先寻找最小不平衡子树(以平衡因子绝对值大于1的结点为根结点的子树),假设A为最小不平衡子树
-
LL:在A的左孩子的左子树中插入导致不平衡
调整:A的左孩子结点右上旋
LL平衡旋转(右单旋转) 由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。 -
RR:在A的右孩子的右子树中插入导致不平衡
调整︰A的右孩子结点左上旋
RR平衡旋转(左单旋转) 由于在结点A的右孩子(R)的右子树®上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树 -
LR:在A的左孩子的右子树中插入导致不平衡
调整︰A的左孩子的右孩子先左上旋再右上旋
LR平衡旋转(先左后右双旋转) 由于在A的左孩子(L)的右子树®上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A绿点的位置 -
RL:在A的右孩子的左子树中插入导致不平衡
调整:A的右孩子的左孩子先右上旋后左上旋
RL平衡旋转(先右后左双旋转) 由于在A的右孩子(R)的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置
查找效率分析
若树高为h,则最坏情况下,查找一个关键字最多需要对比 h次,即查找操作的时间复杂度不可能超过O(h)
平衡二叉树——树上任一结点的左子树和右子树的高度之差不超过1。假设以n,表示深度为h的平衡树中含有的最少结点数。
则有n0 = 0,n1 = 1, n2 = 2,并且有nh = nh-1 + nh-2 + 1
可以证明含有n个结点的平衡二叉树的最大深度为O(log2n),平衡二叉树的平均查找长度O(log2n)
哈夫曼树
带权路径长度
在含有n个带权叶结点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树
哈夫曼树的构造(不唯一)
哈夫曼树代码逻辑
给定n个权值分别为w1, w2…, wn,的结点,构造哈夫曼树的算法描述如下:
- 将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F。
- 构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。
- 从F中删除刚才选出的两棵树,同时将新得到的树加入F中。
- 重复步骤2)和3),直至F中只剩下一棵树为止。
哈夫曼树的性质:
- 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大
- 哈夫曼树的结点总数为2n- 1
- 哈夫曼树中不存在度为1的结点。
- 哈夫曼树并不唯一,但WPL必然相同且为最优
哈夫曼编码(不唯一)
由哈夫曼树得到哈夫曼编码—―字符集中的每个字符作为一个叶子结点,各个字符出现的频度作为结点的权值,根据之前介绍的方法构造哈夫曼树。
可变长度编码——允许对不同字符用不等长的二进制位表示
若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码(不会产生歧义)
例子,接上