第六章 树和二叉树
DESCRIPTION
第六章 树和二叉树. 树的定义和基本术语 树的存储 二叉树 遍历二叉树和线索二叉树 树、森林与二叉树的转换 赫夫曼树及其应用. 树的定义和基本术语. 树的类型定义. 数据对象 D : D 是具有相同特性的数据元素的集合。 数据关系 R : 若 D 为空集,则称为空树 。 否则 : 在 D 中存在唯一的称为根的数据元素 root ; 当 n>1 时,其余结点可分为 m (m>0) 个互不相交的有限集 T 1 , T 2 , …, T m ,其中每一棵子集本身又是一棵符合本定义的树,称为根 root 的子树。. A. C. B. D. E. F. - PowerPoint PPT PresentationTRANSCRIPT
+
第六章 树和二叉树
+
树的定义和基本术语树的存储二叉树遍历二叉树和线索二叉树树、森林与二叉树的转换赫夫曼树及其应用
树的定义和基本术语
树的类型定义数据对象 D :
D 是具有相同特性的数据元素的集合。数据关系 R :
若 D 为空集,则称为空树 。否则 :
在 D 中存在唯一的称为根的数据元素 root ;当 n>1 时,其余结点可分为 m (m>0) 个互不相交的有限集 T1, T2, …, Tm ,其中每一棵子集本身又是一棵符合本定义的树,称为根root 的子树。
K
E F JH IG
B DC
A根子树
A( B(E, F), C(G), D(H(k), I, J))
T1 T3T2树根
树的逻辑特征:
树中只有根结点无直接前驱除了根结点,其余结点都有且仅有一个直接前驱
树中每个结点可以有零个或多个直接后继
树的表示法1. 分支图表示法
2. 嵌套集合表示法
AA
BB CC
DD
AABB CCDD 3. 广义表表示法
(A(B(D),C))
基本术语结点结点的度叶子结点或终端结点分支结点或非终端结点树的度孩子和双亲兄弟祖先和子孙结点的层次堂兄弟树的深度
A
B C D
E F G H I J
MK L
基本术语
结点 结点的度叶结点分支结点树的度
基本术语
孩子和双亲 兄弟 祖先和子孙 堂兄弟
A
B C D
E F G H I J
MK L
基本术语
结点的层次 树的深度
A
B C D
E F G H I J
MK L
1 层
2 层
4 层
3 层
height= 4
基本术语
有序树和无序树:如果将树中结点的各子树看成从左至右是有次序的,则称该树为有序树,否则称为无序树。
森林:森林是 m 棵互不相交的树的集合。
树的三种存储结构双亲表示法孩子链表表示法二叉链表表示法
0 1 2 3 4 5 6 7 8 9 10
data A B C D E F G H I J Kparrent -1 0 0 0 11 2 33 3 7
K
E F JH IG
B DC
A双亲表示法
data parent
结点结构 :
typedef struct PTNode { Elem data; int parent; // 双亲位置域 } PTNode;
#define MAX_TREE_SIZE 100
双亲表示法的类型描述
typedef struct { PTNode nodes[MAX_TREE_SIZE]; int r, n; // 根结点的位置和结点个数 } PTree;
孩子链表表示法 :
孩子结点结构 : child next
双亲结点结构 data firstchild
0 A
1 B
2 C
3 D
4 E
5 F
6 G
7 H
8 I
9 J
10
K
^
^^
^
^
^^
^
1
^
2
^
3
^
4 5
6
7 8 9
10
data firstchild
K
E F JH IG
B DC
A
孩子链表表示法 :
typedef struct CTNode { int child; struct CTNode *next; } *ChildPtr;
孩子链表类型描述 :
#define MAX_TREE_SIZE 100
typedef struct { CTBox nodes[MAX_TREE_SIZE]; int n, r; // 结点数和根结点的位置 } CTree;
typedef struct { Elemtype data; ChildPtr firstchild; // 孩子链的头指针 } CTBox;
左孩子右兄弟表示法
结点结构如下:
存放第一个孩子的地址
存放下一个兄弟的地址
firstchild data nextsibling
指针域 数据域 指针域
A
B C D
E F
G
root
C
AB
DEF
G
typedef struct CSNode{ Elem data; struct CSNode *firstchild, *nextsibling;} CSNode, *CSTree;
左孩子右兄弟表示法类型描述 :
二叉树二叉树的类型定义二叉树的性质二叉树的存储
二叉树的类型定义二叉树是 n ( n≥0 )个结点的有限集,它或者为空,或者由一个根结点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。
二叉树的特点:二叉树的度小于等于 2二叉树是一棵有序树
二叉树的五种形态:
φ
(a) 空树 (b) 仅有根 (c) 右子树空
(e) 左子树空(d) 左、右子树均在
二叉树的性质
性质 1 在二叉树的第 i 层上至多有 2i - 1
个结点。性质 2 深度为 k 的二叉树至多有 2k-1个结点 (k1) 。
性质 3 对任何一棵二叉树 T, 如果其叶结点数为 n0, 度为 2 的结点数为 n2,则 n0 = n2 + 1 。
满二叉树:深度为 k ,且有 2k-1 个结点的二叉树。
完全二叉树:深度为 k ,结点数为 n 的二叉树,当且仅当每个结点的编号都与相同深度的满二叉树中从 1 到 n 的结点一一对应时,称为完全二叉树。
两类特殊形态的二叉树
1
2 3
11
4 5
8 9 12 13
6 7
10 14 15
1
2 3
11
4 5
8 9 12
6 7
10
1
2 3
4 5
6 7
1
2 3
4 5 6
二叉树的性质性质 4 具有 n (n 0) 个结点的完全二叉树的深度为 log2(n) + 1 。
性质 5 如果对一棵有 n 个结点的完全二叉树的结点按层次顺序编号,则对任一结点有:如果 i=1 ,则结点 i 是二叉树的根,无双亲;如果 i>1 ,则其双亲是结点 i/2
如果 2i>n ,则结点 i 无左孩子;否则其左孩子是结点 2i
如果 2i+1>n ,则结点 i 无右孩子;否则其右孩子是结点 2i+1
在一棵度为 m 树中,度为 1 的结点数为 n1 ,度为 2 的结点数为 n2 ,……,度为 m 的结点数为 nm ,则该数中含有多少个叶子结点?有多少个非终端结点?
解:设度为 0 的结点(即终端结点)数目为 n0 ,树中的分支数为 B ,树中总的结点数为 N ,则有:( 1 )从结点的度考虑: N= n0+ n1+ n2+……+nm( 2 )从分支数目考虑:一棵树中只有一个根结点,其他的均为孩子结点,而孩子结点可以由分支数得到。所以有:N=B+1=0×n0+1×n1+2×n2+…+m×nm+1
由以上两式,可得n0+ n1+ n2+……+nm=0×n0+1×n1+2×n2+…+m×nm+1
从而可导出叶子结点的数目为:n0=0×n1+1×n2+…+ ( m-
1 ) ×nm+1=1+从而可以得到非终端结点的数目为
N- n0= n1+ n2+……+nm=
m
iini
2
)1(
m
iin
1
二叉树的顺序存储表示按满二叉树的结点层次编号,用一组地址连续的存储单元,依编号存放二叉树中的数据元素,结点的相对位置蕴含着结点之间的关系。
C
E
F G
D
B
A1
2 3
4 5 7
8 9 10
11
6
A B C D E \0 \0 \0 \0 \0 F G
0 1 2 3 4 5 6 7 8 8 10 11
二叉树的二叉链表存储表示结点结构
lchild data rchild
数据域左指针域 右指针域
H
D E
B C
A
GF
A
B C
F G
ED
H
^ ^
^
^ ^ ^ ^ ^^
bt
二叉链表的类型描述typedef char TElemType;
typedef struct BiNode{
TElemType data;
struct BiNode *lchild;
struct BiNode *rchild;
}BiTNode,*BiTree;
遍历二叉树和线索二叉树二叉树的遍历及递归算法二叉树遍历算法的应用二叉树遍历算法非递归实现二叉树的线索化线索二叉树的遍历
遍历二叉树定义:
按某条搜索路径巡访二叉树中的结点,使得每个结点均被访问,且仅被访问一次。
D
L R
LDR 、 LRD 、 DLRRDL 、 RLD 、 DRL
先序遍历
中序遍历
后续遍历
先序遍历二叉树
若二叉树为空,则空操作;否则访问根结点先序遍历左子树先序遍历右子树
A
D
B C
D L R
AD L R
D L R
>
B
>>
D
>>
C
D L R
先序遍历序列: A B D C
先序遍历 :
中序遍历二叉树
若二叉树为空,则空操作;否则中序遍历左子树访问根结点中序遍历右子树
A
D
B C
中序遍历序列: B D A C
中序遍历 : L D R
B
L D R
L D R
>
A
>> D
>> C
L D R
后序遍历二叉树
若二叉树为空,则空操作;否则后序遍历左子树后序遍历右子树访问根结点
A
D
B C
后序遍历序列: D B C A
后序遍历 : L R D
L R D
L R D>
A
>> D
>>
C
L R D
B
-
+ /
a *
b -
e f
c d
先序遍历:中序遍历:后序遍历:
- + a * b - cd / e f-+a *b -c d /e f
-+a *b -cd /ef
H
D E
B C
A
GF
先序序列:
ABDFCEGH
中序序列:
BFDAGEHC后序序列:
FDBGHECA
M
D E GF
B C
A
H
I
L
J K PN
思考 1 :
满足下列条件的所有二叉树:1 、先序序列和中序序列相同2 、中序序列和后序序列相同3 、先序序列和后序序列相同
思考 2 :
1 、怎样由二叉树的先序序列和中序序列构造一颗二叉树?
2 、怎样由二叉树的后序序列和中序序列构造一颗二叉树?
由二叉树的先序和中序序列建树
二叉树的先序序列二叉树的先序序列
二叉树的中序序列二叉树的中序序列 左子树
左子树 右子树
右子树
根
根
DEBA
先序序列为:中序序列为:
C E D ABD E B CA
E
C
BABD
A
先序序列为: A B D E C F H G中序序列为: D B E A H F C G构造一棵二叉树。
D E GF
B C
A
H
例如:后序序列为: D E B H F G C A
中序序列为: D B E A H F C G
画出该二叉树
二叉树遍历算法递归实现
void InOrder ( BiTNode *T ) {
if ( T != NULL ) {
InOrder ( T->lChild );
Visit( T->data);
InOrder ( T->rChild );
}
}
先序遍历中序遍历后序遍历
void PreOrder ( BiTNode *T )
{
if ( T != NULL ) {
Visit( T->data);
PreOrder ( T->lChild );
PreOrder ( T->rChild );
}
}
void PostOrder ( BiTNode * T )
{
if ( T != NULL ) {
PostOrder ( T->lChild );
PostOrder ( T->rChild );
Visit( T->data);
}
}
二叉树遍历算法的应用举例:
统计二叉树中叶子结点的个数求二叉树的深度建立二叉树的存储结构
统计二叉树中叶子结点的个数算法基本思想 :
先序 ( 或中序或后序 ) 遍历二叉树,在遍历过程中查找叶子结点,并计数。由此,需在遍历算法中增添一个“计数器”,并将算法中“访问结点”的操作改为:若是叶子,则计数器增 1 。
int CountLeaf (BiTree T)
{
if (!T) return 0;
if ((!T->lchild)&& (!T->rchild))
return 1;
return(CountLeaf( T->lchild)+
CountLeaf( T->rchild));
}
求二叉树的深度
算法基本思想 :
从二叉树深度的定义可知,二叉树的深度应为其左、右子树深度的最大值加 1 。由此,需先分别求得左、右子树的深度,算法中“访问结点”的操作为:求得左、右子树深度的最大值,最后加 1 。
A
B C
D E F
G H
I
int Depth (BiTree T ){ if ( !T ) depth= 0; else { Left = Depth( T->lchild ); Right= Depth( T->rchild ); depth=1+(Left>Right? Left:Right); } return depth;}
以带空格的先序序列建立二叉树: 根 左子树 右子树定义一棵二叉树
例如 :
以空白字符“ ”︼ 表示•空树
•只含一个根结点的二叉树
A 以字符串“ A ”︼ ︼ 表示
○
建立二叉树的存储结构
如图所示的二叉树的先序遍历顺序为A B C @ @ D E @ G @ @ F @ @ @
A
B
C D
E
G
F@ @
@
@ @
@ @
@
void CreateBiTree ( BiTree &T ) { scanf(&ch); if ( ch==‘ ’ ) T=NULL; else{ T=(BiTNode *)malloc(sizeof(BiTNode)) ; T->data = ch; CreateBiTree ( T->lChild ); CreateBiTree ( T->rChild ); }}//CreateBiTree
二叉树遍历非递归算法的实现
--借助栈实现
在高级语言编制的程序中,调用函数和被调用函数之间的链接及信息交换需通过栈来进行。
为了保证递归函数的正确执行,系统需设立一个递归工作栈作为整个递归函数运行期间使用的数据存储区。每一层递归所需信息构成一个“工作记录”,其中包括所有的实在参数、所有的局部变量以及上一层的返回地址。每进入一层递归,就产生一个新的工作记录压入栈顶,每退出一层递归,就从栈顶弹出一个工作记录,则当前执行层的工作记录必是递归工作栈栈顶的工作记录。
中序遍历二叉树 ( 非递归算法 ) 用栈实现
ba
a
b c
d e
ada a
a b入栈
b退栈访问
d入栈 d退栈访问
e 退栈
访问
ec c
栈空
a退栈访问
c e 入栈
c 退栈
访问
void InOrder ( BiTree T ) {
InitStack( &S ); Push(S,T);
while ( !StackEmpty(S) {
while(GetTop(S,p)&&p) Push(S, p->lchild);
Pop(S,p); // 空指针退栈 if ( !StackEmpty(S) ) { //栈非空
Pop(S, p); //退栈 visit(p->data); // 访问根 Push(S, p->rchild); }//if
}//while
}
void InOrder ( BiTree T ) {
InitStack( &S ); p=T;
while ( p||!StackEmpty(S)) {
while(p) {
Push(S, p); p=p->lchild;}
if ( !StackEmpty(S) ) { //栈非空 Pop(S, p); //退栈 visit(p->data); // 访问根 p=p->rchild; }
}//while
}
先序遍历二叉树 ( 非递归算法 ) 用栈实现a
c
a
b c
d e
dc c
访问a进栈c左进b
访问b进栈d左进空
退栈d访问d左进空
退栈c访问c左进e
访问e左进空退栈
结束
void PreOrder( BiTree T ) {
InitStack(S); p = T;
while ( p||!StackEmpty(S)) {
while(p) {
visit(p->data);
Push(S,p);
p=p->lchild;} if ( !StackEmpty(S) ) { Pop(S, p);
p = p->rChild; }
}
}
a
b c
d e
后序遍历二叉树 ( 非递归算法 ) 用栈实现后序遍历时使用的栈的结点定义typedef struct { BiTNode *ptr; // 结点指针 enum tag{ L, R }; // 该结点退栈标记} StackNode; 根结点的 tag = L ,表示从左子树退出 , 访问右子树。 tag = R, 表示从右子树退出 , 访问根。
ptr tag{L,R}
void PostOrder ( BiTree T ) {InitStack(S); p = T;do {
while ( p != NULL ) { //向左子树走
w.ptr = p; w.tag = L; Push (S, w); p = p->lChild; }continue = 1; // 继续循环标记
while ( continue && !StackEmpty(S) ) { Pop (S, w); p = w.ptr; switch ( w.tag ) { //判断栈顶 tag标记
case L : w.tag = R; Push (S, w); continue = 0;
p = p->rChild; break; case R : Visit( p->data);
} }} while ( p != NULL || !StackEmpty(S) );
}
二叉树的线索化
问题的提出: 当以二叉链表作为存储结构时 , 只能找到结
点的左右孩子的信息 , 而不能在结点的任一序列的前驱与后继信息 , 这种信息只有在遍历的动态过程中才能得到。
解决办法: 利用二叉链表中的空指针 增加标志域
效果: 为二叉树建立了一个双向线索链表
线索二叉树的结构 结点结构
按遍历序列分为:先序线索二叉树中序线索二叉树后序线索二叉树
ltag data rtag
lchild rchild
指向结点的左儿子
指向结点的前驱
lchild
lchildltag
,0
,1
指向结点的右儿子
指向结点的后继
rchild
rchildrtag
,0
,1
以上述结点结构构成的二叉链表称为线索链表,其中指向结点前驱和后继的指针,叫做线索。加上线索的二叉树称为线索二叉树。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。
先序线索二叉树
A
F
E D
C B
0 A 0
0 B 1 0 C 1
1 D 0 1 E 1
1 F 1先序遍历序列:ABDFCE
Null
0 A 0
0 B 1 0 C 1
1 D 0 1 E 1
1 F 1
中序线索二叉树
中序遍历序列:DFBAEC Null
Null
A
F
E D
C B
0 A 0
0 B 1 0 C 1
1 D 0 1 E 1
1 F 1
后序线索二叉树
后序遍历序列:FDBECA
Null
A
F
E D
C B
线索二叉链表的类型定义typedef struct BiThrNode{ TElemtype data; struct BiThrNode *lchild ,*rchild; int ltag,rtag;}BiThrNode,*BiThrTree;
ltag data rtag
lchild rchild
void InOrderThr(BiThrTree &Thrt,BiThrTree T){// 中序遍历二叉树 T ,并将其中序线索化 Thrt=(BiThrTree)malloc(sizeof(BiThrNode)); Thrt->ltag=0; Thrt->rtag=1; // 建立头结点 Thrt->rchild=Thrt; // 右指针回指 if (!T) Thrt->lchild =Thrt; else { Thrt->lchild=T; pre=Thrt; InThreading(T); // 中序遍历进行中序线索化 pre->rchild=Thrt; pre->rtag=1; //最后一个结点线索化 Thrt->rchild=pre; }}
void InTreading(BiThrTree p){ // 中序遍历进行中序线索化 if (p) { InThreading(p->lchild); // 左子树线索化 if (!p->lchild) // 前驱线索 { p->ltag=1; p->lchild=pre; } if (!pre->rchild) // 后继线索 { pre->rtag=1; pre->rchild=p; } pre=p; InThreading(p->rchild);// 右子树线索化 }}
中序遍历线索化二叉树void InOrderTraverse_Thr(BiThrTree T){ p=T->lchild; while(p!=T){ while(p->ltag==0) p=p->lchild; visit(p->data); while(p->rtag==1&&p->rchild!=T){ p=p->rchild; visit(p->data); } p=p->rchild; }}
树、森林与二叉树的转换
将树转换成二叉树加线:在兄弟之间加一连线抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系
旋转:以树的根结点为轴心,将整树顺时针转 45°
A
B C D
E F G H I
A
B C D
E F G H I
A
B C D
E F G H I
A
B C D
E F G H I
AB
C
D
E
F
G H
I树转换成的二叉树其右子树一定为空
将二叉树转换成树加线:若 p 结点是双亲结点的左孩子,则将 p 的右孩子,右孩子的右孩子,……沿分支找到的所有右孩子,都与 p 的双亲用线连起来
抹线:抹掉原二叉树中双亲与右孩子之间的连线
调整:将结点按层次排列,形成树结构
AB
C
D
E
F
G H
I
AB
C
D
E
F
G H
I
AB
C
D
E
F
G H
I
AB
C
D
E
F
G H
I
A
B C D
E F G H I
森林转换成二叉树将各棵树分别转换成二叉树将每棵树的根结点用线相连以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构
A
B C D
E
F
G
H I
J
A
B
CD
E
F
G
H
I
J
A
B
CD
E
F
G
H
I
J
A
B
C
D
E
FG
H
I
J
二叉树转换成森林抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
还原:将孤立的二叉树还原成树
A
B
C
D
E
FG
H
I
J
A
B
C
D
E
FG
H
I
J
A
B
CD
E
F
G
H
I
J
A
B C D
E
F
G
H I
J
树和森林的遍历树的遍历
先序遍历:先访问根结点,然后依次先根遍历根的每棵子树。
后序遍历:后根访问根的每棵子树,然后访问根结点。
K
E F JH IG
B DC
A
A B E CF G HD
先序遍历序列
K I J
K
E F JH IG
B DC
A
E F B CG K IH
后序遍历序列J D A
森林的遍历先序遍历若森林非空,则可按下述规则遍历之:访问森林中第一棵树的根结点先序遍历第一棵树中根结点的子树森林先序遍历除去第一棵树之后剩余的树构成的森林
中序遍历若森林非空,则可按下述规则遍历之:中序遍历第一棵树中根结点的子树森林访问森林中第一棵树的根结点中序遍历除去第一棵树之后剩余的树构成的森林
B DC
A
K
JH I
GE
F
A B C ED F HG
先序遍历序列
K I J
B C D FA E HK
中序遍历序列
I J G
B DC
A
K
JH I
GE
F
赫夫曼树及其应用
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的 ~
路径长度:路径上的分支数结点的权:在许多应用中,常常将树中的结点赋予一个有意义的数,称为该结点的权。
结点的带权路径长度:是指该结点到树根之间的路径长度与该结点上权的乘积。
基本术语
树的路径长度:从树根到每一个结点的路径长度之和树的带权路径长度:树中所有叶结点的带权路径长度之和,记着
n
kkklwwpl
1
基本术语
其中: wk 为权值, lk 为结点到根的路径长度
D E GF
B C
A
H
7 5
4
2
赫夫曼树( huffman 树)
在权为 w1,w2,…,wn 的 n 个叶子结点的所有二叉树中,带权路径长度 WPL最小的二叉树称为赫夫曼树或最优二叉树。
例如,有 4 个结点 a,b,c,d ,分别带权 7,5,2,4 ,由它们作为叶子结点,可以构造下列三棵不同的二叉树
a b dcd
c
ba
a
db
dc
7 5 2 47
7
5
5
2
2
4
4
Ⅰ Ⅱ
Ⅲ
它们的带权路径长度分别为:
WPL (Ⅰ)=7*2+5*2+2*2+4*2=36
WPL (Ⅱ)=7*3+5*3+2*1+4*2=46
WPL (Ⅲ)=7*1+5*2+2*3+4*3=35
假设给定 n 个权值{ w1,w2,…,wn },如何构造一棵 n 个结点的二叉树,使其为最优二叉树( huffman 树)?
构造 Huffman 树的方法—— Huffman 算法构造 Huffman 树步骤
根据给定的 n 个权值 {w1,w2,……wn} ,构造n 棵只有根结点的二叉树,令其权值为 wj
在森林中选取两棵根结点权值最小的树作左右子树,构造一棵新的二叉树,置新二叉树根结点权值为其左右子树根结点权值之和
在森林中删除这两棵树,同时将新得到的二叉树加入森林中
重复上述两步,直到森林中只含一棵树为止,这棵树即哈夫曼树
例如权值集合W={7,19,2,6,32,3,21,10 } 构造赫夫曼树的过程。
36 103219 27 21
32
5
5
32
32
5 6
11
32
5 6
11
107
17
17
107
32
5 6
11 17
107
28
28
32
5 6
11 17
1072119
40
40
2119
28
32
5 6
11 17
107
32
60
60
3228
32
5 6
11 17
107
40
2119
60
3228
32
5 6
11 17
107
100
Huffman 树应用最佳判定树赫夫曼编码
等级分数段比例
ABCDE
0~59 60~6970~7980~8990~100
0.05 0.15 0.40 0.30 0.10
a<60
a<90
a<80
a<70
E
Y
N
D
Y
N
C
Y
N
B
Y
NA
70a<80
a<60
C
Y
N
B
Y
N
D
Y
N
E
Y
NA
80a<90
60a<70
E A
D
E
C
a<80
a<70
a<60a<90
E
Y
N
D
Y
NCY
NB
Y
N
A
最佳判定树
赫夫曼编码在数据通信中,需要将传送的文字转换成由二进制字符‘ 0’ 和‘ 1’ 组成的二进制串,我们称之为编码。反之,收到电文后要将电文转换成原来的文字,称为译码。
如何构造电文总长最短的二进制编码?
例如:将文字“ ABACCDA” 转换成电文。文字中有四种字符,用 2 位二进制便可分辨。
A B C D00 01 10 11
方案 1:
则上述文字的电文为:00010010101100 共 14 位。译码时,只需每 2 位一译即可。
特点:等长等频率编码,译码容易,但电文不一定最短 .
A B C D0 00 1 01
方案2:
采用不等长编码,让出现次数多的字符用短码。则 ABACCDA文字的电文为:000011010 共 9 位
但无法译码,它即可译为 BBCCACA ,也可译为 AAAACCDA 等。
A B C D0 110 10 111
方案 3:
采用不等长编码,让出现次数多的字符用短码,且任一编码不能是另一编码的前缀。
则 ABACCDA文字的电文为:
0110010101110 共 13 位
设有 n 种字符,每种字符出现的次数为 Wi ,
其编码长度为 Li (i=1,2,... n) ,
则整个电文总长度为∑WiLi ,
要得到最短的电文,即使得∑WiLi最小。
也就是以字符出现的次数为权值,构造一棵 Huffman 树,并规定左分支编码位 0 ,右分支编码为 1 ,则字符的编码就是从根到该字符所在的叶结点的路径上的分支编号序列,称为 Huffman 编码。
赫夫曼编码的一般步骤:构造赫夫曼树:设需要编码的集合为 {d1,d2,
…,dn} ,它们在电文中出现的次数或频率集合为 {w1,w2,…,wn} ,以 d1,d2,…,dn 作为叶子结点,以 w1,w2,…,wn 作为它们的权值,构造一个赫夫曼树
设计赫夫曼编码:将树中结点引向其左孩子的分支标“ 0” ,引向其右孩子的分支标“ 1” ;则从根结点到每个叶子结点所经分支上的‘ 0’ 和‘ 1’ 的组合即为该字符所对应的编码,我们称之为赫夫曼编码。
已知某系统在通信联络中只可能出现 8种字符 A,B,C,D,E,F,G,H ,其概率分别为0.07,0.19,0.02,0.06,0.32,0.03,0.21,0.1试设计赫夫曼编码。
40
2119
60
3228
32
5 6
11 17
107
100
0 1
0 1 0 1
0 1
0 1 0 1
0 1 A
B
C
D
A : 1010
B : 00
C : 10000
D : 1001
EE : 11
F
F : 10001G
G : 01
H
H : 1011
Huffman 编码算法实现一棵有 n 个叶子结点的 Huffman 树有2n-1 个结点
采用顺序存储结构——一维数组结点类型定义
typedef struct{ int weight; int parent,lchild,rchild;}HTNode,*HuffmanTree;typedef char **HuffmanCode;
例如:已知某系统在通信联络中只可能出现 6 种字符,其概率分别为:0.22,0.13,0.07,0.08,0.23,0.27,试设计哈夫曼编码。
求哈夫曼编码的算法void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n){if(n<1) return;m=2*n-1;HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));for(p=HT+1,i=1;i<=n;i++,p++,w++) *p={*w,0,0,0};for(;i<=m;i++,p++) *p={0,0,0,0};for(i=n+1;i<=m;i++){ select(HT,i-1,s1,s2); HT[s1].parent=i; HT[s2].parent=i; HT[i].lchild=s1; HT[i].rchild=s2; HT[i].weight=HT[s1].weight+HT[s2].weight; }
HC=(HuffmanCode)malloc((n+1)*sizeof(char *));Cd=(char *)malloc(n*sizeof(char));Cd[n-1]=‘\0’;for(i=1;i<=n;i++){ start=n-1; for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent) if(HT[f].lchild==c) cd[--start]=‘0’; else Cd[--start]=‘1’; HC[i]=(char *)malloc((n-start)*sizeof(char)); strcpy(HC[i],&cd[start]); }free(cd);}