【数据结构】-树与森林

一 树的定义

   树(Tree)是n(n≧0)个结点的有限集合T,若n=0时称为空树,否则:

⑴  有且只有一个特殊的称为树的根(Root)结点;

⑵  若n>1时,其余的结点被分为m(m>0)个互不相交的子集T1, T2,T3…Tm,其中每个子集本身又是一棵树,称其为根的子树(Subtree)。

这是树的递归定义,即用树来定义树,而只有一个结点的树必定仅由根组成

 

二 树的基本术语

1结点(node):一个数据元素及其若干指向其子树的分支。

2结点的度(degree) :结点所拥有的子树的棵数称为结点的度。

3树的度:树中结点度的最大值。   

 

4 叶子(left)结点

非叶子结点:树中度为0的结点称为叶子结点(或终端结点)。

度不为0的结点:称为非叶子结点(或非终端结点或分支结点)。

除根结点外,分支结点又称为内部结点。

 

5 孩子结点、双亲结点、兄弟结点

一个结点的子树的根称为该结点的孩子结点(child)或子结点;相应地,该结点是其孩子结点的双亲结点(parent)或父结点。

 

6 层次、堂兄弟结点

规定树中根结点的层次为1,其余结点的层次等于其双亲结点的层次加1。

 若某结点在第l(l≧1)层,则其子结点在第l+1层。

双亲结点在同一层上的所有结点互称为堂兄弟结点

 

7 结点的层次路径、祖先、子孙

    从根结点开始,到达某结点p所经过的所有结点成为结点p的层次路径(有且只有一条)。

    结点p的层次路径上的所有结点(p除外)称为p的祖先(ancester) 。

    以某一结点为根的子树中的任意结点称为该结点的子孙结点(descent)。

 

8 树的深度(depth):树中结点的最大层次值,又称为树的高度.

 

9有序树和无序树对于一棵树,若其中每一个结点的子树(若有)具有一定的次序,则该树称为有序树,否则称为无序树。

 

10 森林(forest)是m(m≧0)棵互不相交的树的集合。显然,若将一棵树的根结点删除,剩余的子树就构成了森林。

 

2.1 树抽象数据类型

ADT Tree{

数据对象D:D是具有相同数据类型的数据元素的集合。

数据关系R:若D为空集,则称为空树;

……

基本操作:

……

} ADT Tree

 

树的逻辑表示

 

2.2 二叉树

   二叉树(Binarytree)是n(n≥0)个结点的有限集合。若n=0时称为空树

⑵    有且只有一个根(Root)结点;

⑵ 若n>1时,其余的结点被分成为二个互不相交的子集T1,T2,分别称之为左、右子树,并且左、右子树又都是二叉树。

 

2.2.1二叉树的形态

ADT Tree{

数据对象D:D是具有相同数据类型的数据元素的集合。

数据关系R:若D为空集,则称为空树;

基本操作: ……

} ADTTree。

 

 

2.2.2 二叉树的性质

性质1:在非空二叉树中,第i层上至多有2i-1个结点(i≧1)。

性质2:深度为k的二叉树至多有2k-1个结点(k≧1)

性质3: 对任何一棵二叉树,若其叶子结点数为n0,度为2的结点数为n2,则n0=n2+1。

二叉树中度为1的结点数为n1

二叉树中总结点数为N。

二叉树中所有结点均小于或等于2,则有:N=n0+n1+n2

 

除根结点外,其余每个结点都有唯一的一个进入分支,而所有这些分支都是由度为1和2的结点射出的。设B为二叉树中的分支总数,则有:      N=B+1

∴     B=n1+2´n2         

∴     N=B+1=n1+2´n2+1

∴     n0+n1+n2=n1+2´n2+1

         n0=n2+1                                                 

 

满二叉树和完全二叉树。

   一棵深度为k且有2k-1个结点的二叉树称为满二叉树(Full Binary Tree)。

 

满二叉树的特点

◆ 基本特点是每一层上的结点数总是最大结点数。

◆ 满二叉树的所有的支结点都有左、右子树。

◆ 可对满二叉树的结点进行连续编号,若规定从根结点开始,按“自上而下、自左至右”的原则进行。

 

完全二叉树(Complete Binary Tree):如果深度为k,由n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应,该二叉树称为完全二叉树。

    或深度为k的满二叉树中编号从1到n的前n个结点构成了一棵深度为k的完全二叉树。

其中  2k-1 ≦n≦2k-1。

 

完全二叉树的特点。

  若完全二叉树的深度为k ,则所有的叶子结点都出现在第k层或k-1层。对于任一结点,如果其右子树的最大层次为l,则其左子树的最大层次为ll+1。

 

性质4:n1个结点的完全二叉树深度为:ë㏒2nû+1。

ëxû 表示不大于x的最大整数,向下取整。

éxù 表示不小于x的最小整数,向上取整。

2k-1-1<n≦2k-1  或   2 k-1≦n<2k

    取对数得:k-1<㏒2n<k  因为k是整数。

∴  k= ë㏒2nû+1   

性质5:若对一棵有n个结点的完全二叉树(深度为└㏒2n┘+1)的结点按层(从第1层到第ë㏒2nû +1层)序自左至右进行编号,则对于编号为i(1≦i≦n)的结点:

⑴ 若i=1:则结点i是二叉树的根,无双亲结点;否则,若i>1,则其双亲结点编号是 ëi/2û 。

⑵ 如果2i>n:则结点i为叶子结点,无左孩子;否则,其左孩子结点编号是2i。

⑶   如果2i+1>n:则结点i无右孩子;否则,其右孩子结点编号是2i+1。

 

2.2.3链式存储结构

    设计不同的结点结构可构成不同的链式存储结构。

(1)  结点的类型及其定义

① 二叉链表结点。有三个域:一个数据域,两个分别指向左右子结点的指针域,如图6-7(a)所示。

typedefstruct BTNode

ElemType data ;

struct BTNode *Lchild , *Rchild ;

}BTNode;

 

②  三叉链表结点。除二叉链表的三个域外,再增加一个指针域,用来指向结点的父结点,如图6-7(b)所示。

typedefstruct BTNode_3

ElemType data ;

struct BTNode_3  *Lchild , *Rchild , *parent ;

}BTNode_3;


2.3 遍历二叉树

DLR——先(根)序遍历。

LDR——中(根)序遍历。

LRD——后(根)序遍历。

 

 

2.3.1 二叉树的先序遍历

若二叉树为空,则遍历结束;否则

1 访问根结点;

2 先序遍历左子树(递归调用本算法);

3 先序遍历右子树(递归调用本算法);

 

伪码描述:

void  PreorderTraverse(BTNode  *T)

{  if (T!=NULL)

{  visit(T->data) ;       /* 访问根结点  */

PreorderTraverse(T->Lchild);

PreorderTraverse(T->Rchild);    

}

}

说明:visit()函数是访问结点的数据域,其要求视具体问题而定。树采用二叉链表的存储结构,用指针变量T来指向

 

2.3.2二叉树中序遍历

算法的递归定义是:

       若二叉树为空,则遍历结束;否则

1中序遍历左子树(递归调用本算法);

2访问根结点;

3中序遍历右子树(递归调用本算法)。

 

伪码

void  InorderTraverse(BTNode  *T)

{  if (T!=NULL)

InorderTraverse(T->Lchild) ;

visit(T->data) ;       /*  访问根结点   */

InorderTraverse(T->Rchild) ;

}

}  

 

2.3.3后序遍历

算法的递归定义是:

若二叉树为空,则遍历结束;否则

1后序遍历左子树(递归调用本算法);

2后序遍历右子树(递归调用本算法);

3访问根结点。

 

void  PostorderTraverse(BTNode  *T)

{  if (T!=NULL)

PostorderTraverse(T->Lchild) ;

PostorderTraverse(T->Rchild) ;

visit(T->data) ;       /* 访问根结点  */

}

}

遍历二叉树的算法中基本操作是访问结点,因此,无论是哪种次序的遍历,对有n个结点的二叉树,其时间复杂度均为O(n) 。

 

所示的二叉树表示表达式:(a+b*(c-d)-e/f)按不同的次序遍历此二叉树,将访问的结点按先后次序排列起来的次序是:

 其先序序列为:  -+a*b-cd/ef

 其中序序列为:   a+b*c-d-e/f

 其后序序列为:   abcd-*+ef/-

2.3.4层次遍历二叉树

思想与步奏:广度遍历二叉树

若二叉树为空,则返回;否则,令p=T,p入队;

⑴    队首元素出队到p;

⑵    访问p所指向的结点;

⑶  将p所指向的结点的左、右子结点依次入队。直到队空为止。

 

伪码描述(求二叉树的深度)

#define  MAX_NODE 50

int  search_depth( BTNode  *T)

{  BTNode *Stack[MAX_NODE] ,*p=T;

int  front=0 , rear=0, depth=0, level ;

/*  level总是指向访问层的最后一个结点在队列的位置 */

if  (T!=NULL)

{  Queue[++rear]=p;    /*   根结点入队  */

level=rear;    /* 根是第1层的最后一个节点 */

while(front<rear)

     { p=Queue[++front];

         if (p->Lchild!=NULL)

               Queue[++rear]=p;    /*   左结点入队  */

         if (p->Rchild!=NULL)

               Queue[++rear]=p;    /*   左结点入队  */

          if (front==level) 

             /* 正访问的是当前层的最后一个结点  */

             { depth++ ;  level=rear ;  }

      }

}

}

 

 

 

深度遍历二叉树(求叶子节点数)伪码

#define  MAX_NODE 50

int  search_leaves( BTNode  *T)

{  BTNode *Stack[MAX_NODE] ,*p=T;

int  top=0, num=0;

if  (T!=NULL)

{  stack[++top]=p ;

while(top>0)

   {  p=stack[top--];

       if(p->Lchild==NULL&&p->Rchild==NULL)  num++ ;  

        if (p->Rchild!=NULL )

             stack[++top]=p->Rchild;

        if (p->Lchild!=NULL )

                stack[++top]=p->Lchild;

     }

}

return(num);

}

2.4二叉树的线索化

#defineMAX_NODE  50

typedefstruct BTNode

{  char data ;

structBTNode *Lchild , *Rchild ;

}BTNode;

BTNode  *Create_BTree(void)  

  /*建立链式二叉树,返回指向根结点的指针变量 */

{  BTNode *T , *p , *s[MAX_NODE] ; 

charch ; int i , j ;

while(1)

{  scanf(“%d”, &i) ;

if  (i==0) break ;   /*以编号0作为输入结束*/

else 

     { ch=getchar() ;

       p=(BTNode *)malloc(sizeof(BTNode)) ;

       p–>data=ch ;

       p–>Lchild=p–>Rchild=NULL ;  s[i]=p ;

       if (i==1)  T=p ;

       else

           { j=i/2 ;    /*    j是i的双亲结点编号  */

                 if (i%2==0)  s[j]->Lchild=p ;

               else  s[j]->Rchild=p ;

            }

   }

}

  turn(T) ;

}

 

创建二叉树:

#defineNULLKY  ‘?’

#defineMAX_NODE   50

typedefstruct BTNode

{  char data ;

structBTNode *Lchild , *Rchild ;

}BTNode;

BTNode  *Preorder_Create_BTree(BTNode  *T)

 /*   建立链式二叉树,返回指向根结点的指针变量  */

{  char ch ;

ch=getchar(); getchar();

if  (ch==NULLKY)

{   

T=NULL; return(T) ;  }

else

T=(BTNode *)malloc(sizeof(BTNode)) ;

T–>data=ch ;

Preorder_Create_BTree(T->Lchild) ;

Preorder_Create_BTree(T->Rchild) ;

return(T) ;

}

}


 

 

线索二叉树:

◆ 若结点有左孩子,则Lchild指向其左孩子,否则,指向其直接前驱;

◆ 若结点有右孩子,则Rchild指向其右孩子,否则,指向其直接后继;


这种结点结构构成的二叉树的存储结构;叫做线索链表;

指向结点前驱和后继的指针叫做线索;

按照某种次序遍历,加上线索的二叉树称之为线索二叉树;

 

typedefstruct BiThrNode

{  

ElemType data;

struct BiTreeNode *Lchild , *Rchild ;

int Ltag , Rtag ;

}BiThrNode;

 


画线索二叉树时,实线表示指针,指向其左、右孩子;虚线表示线索,指向其直接前驱或直接后继。

中序线索树为例:

◆ 树中所有叶子结点的右链都是线索。右链直接指示了结点的直接后继,如结点G的直接后继是结点E。

◆ 树中所有非叶子结点的右链都是指针。根据中序遍历的规律,非叶子结点的直接后继是遍历其右子树时访问的第一个结点,即右子树中最左下的(叶子)结点。如结点C的直接后继:沿右指针找到右子树的根结点F,然后沿左链往下直到Ltag=1的结点即为C的直接后继结点H。

 

后序遍历

 ◆ 若结点是二叉树的根结点:其直接后继为空;

 ◆ 若结点是其父结点的左孩子或右孩子且其父结点没有右子树:直接后继为其父结点;

 ◆ 若结点是其父结点的左孩子且其父结点有右子树:直接后继是对其父结点的右子树按后序遍历的第一个结点。

 

 

二叉树的线索化指的是依照某种遍历次序使二叉树成为线索二叉树的过程。

    线索化的过程就是在遍历过程中修改空指针使其指向直接前驱或直接后继的过程。

    仿照线性表的存储结构,在二叉树的线索链表上也添加一个头结点head,头结点的指针域的安排是:

 ◆ Lchild域:指向二叉树的根结点;

 ◆ Rchild域:指向中序遍历时的最后一个结点;

 ◆ 二叉树中序序列中的第一个结点Lchild指针域和最后一个结点Rchild指针域均指向头结点head。

 

#define  MAX_NODE  50

typedefenmu{Link , Thread} PointerTag ;

/*  Link=0表示指针, Thread=1表示线索   */

typedefstruct BiThrNode

{  

ElemType data;

struct BiTreeNode *Lchild , *Rchild ;

PointerTag Ltag , Rtag ;

}BiThrNode;


 

 

2.4.1 先序线索化二叉树

voidpreorder_Threading(BiThrNode *T)

 { BiThrNode  *stack[MAX_NODE];

BiThrNode *last=NULL, *p ;

inttop=0 ;

if  (T!=NULL)

{  stack[++top]=T;

while(top>0)

    { p=stack[top--] ;

        if (p->Lchild!=NULL)  p->Ltag=0 ;

        else {  p->Ltag=1 ;  p->Lchild!=last ;  }

        if (last!=NULL)

            if (last->Rchild!=NULL)last->Rtag=0 ;

            else 

               {  last->Rtag=1 ; last->Rchild!=p ;  }

        last=p ;

        if (p->Rchild!=NULL)

            stack[++top]=p->Rchild ;

        if (p->Lchild!=NULL)

             stack[++top]=p->Lchild ;

   }

Last->Rtag=1;  /*   最后一个结点是叶子结点 */

}

}

 


 

2.4.2 中序线索化二叉树

voidinorder_Threading(BiThrNode *T)

 { BiThrNode  *stack[MAX_NODE];

BiThrNode *last=NULL, *p=T ;

inttop=0 ;

while  (p!=NULL||top>0)

if  (p!=NULL) {  stack[++top]=p;  p=p->Lchild;  }

else

{  p=stack[top--] ;

    if (p->Lchild!=NULL)  p->Ltag=0 ;

    else {  p->Ltag=1 ;  p->Lchild!=last ;  }

    if (last!=NULL)

        if (last->Rchild!=NULL)last->Rtag=0 ;

        else { last->Rtag=1 ; last->Rchild!=p ; }

    last=p ;

    P=p->Rchild;

}

last->Rtag=1;  /*   最后一个结点是叶子结点   */

}

}

 

2.4.3二叉树的先序遍历

voidinorder_Thread_bt(BiThrNode *T)

 { BiThrNode  *p ;

if  (T!=NULL)

{   p=T;

while  (p->Ltag==0 )

    p=p->Lchild;   /*  寻找最左的结点  */

while  (p!=NULL)

    {  visit(p->data) ;

         if (p->Rtag==1)   

             p=p->Rchild ;    /*  通过右线索找到后继  */

         else   /*  否则,右子树的最左结点为后继  */

            { p=p->Rchild ;

                while (p->Ltag==0 )  p=p->Lchild;

         }

    }

}

}

 

 


 

三 树与森林

3.1 树的存储结构

3.1.1 双亲表示法(顺序存储结构)


   用一组连续的存储空间来存储树的结点,同时在每个结点中附加一个指示器(整数域) ,用以指示双亲结点的位置(下标值) 。数组元素及数组的类型定义如下:

#defineMAX_SIZE  100

typedef  struct PTNode

ElemType data ;

int parent ;

}PTNode;

 

typedef  struct

PTNode Nodes[MAX_SIZE] ;

int root;    /*  根结点位置  */

int num ;   /*  结点数   */

}Ptree;

这种存储结构利用了任一结点的父结点唯一的性质。可以方便地直接找到任一结点的父结点,但求结点的子结点时需要扫描整个数组。

 

3.1.2 孩子链表结构

 

#define  MAX_NODE 100

typedef  struct listnode

{   int  childno ;    /*  孩子结点编号  */

structlistno  *next ;

}CTNode;    /*  表结点结构  */

typedef  struct

{  ElemType  data ;

CTNode  *firstchild ;

}HNode;    /*  头结点结构  */

 

typedef  struct

{  HNode  nodes[MAX_NODE] ;

int  root;   /*  根结点位置  */

int  num ;  /*  结点数   */

}CLinkList;    /*  头结点结构  */

 

3.1.3 孩子兄弟表示法

typedef  struct  CSnode

ElemType  data ;

struct  CSnode *firstchild, *nextsibing ;

}CSNode; 

 

3.2森林与二叉树的转换

◆ 从物理结构来看,树和二叉树的二叉链表是相同的,只是对指针的逻辑解释不同而已。
◆ 从树的二叉链表表示的定义可知,任何一棵和树对应的二叉树,其右子树一定为空。

 

3.2.1 树转换成二叉树

    对于一般的树,可以方便地转换成一棵唯一的二叉树与之对应。将树转换成二叉树在“孩子兄弟表示法”中已给出,其详细步骤是

⑶   加虚线。在树的每层按从“左至右”的顺序在兄弟结点之间加虚线相连。

⑵ 去连线。除最左的第一个子结点外,父结点与所有其它子结点的连线都去掉。

⑷   旋转。将树顺时针旋转450,原有的实线左斜。

⑸   整型。将旋转后树中的所有虚线改为实线,并向右斜。

 

 

这样转换后的二叉树的特点是:

 ◆ 二叉树的根结点没有右子树,只有左子树;

 ◆ 左子结点仍然是原来树中相应结点的左子结点,而所有沿右链往下的右子结点均是原来树中该结点的兄弟结点。

 

3.2.2 二叉树转换成树

对于一棵转换后的二叉树,如何还原成原来的树? 其步骤是:

⑴  加虚线。若某结点i是其父结点的左子树的根结点,则将该结点i的右子结点以及沿右子链不断地搜索所有的右子结点,将所有这些右子结点与i结点的父结点之间加虚线相连,

⑵   去连线。去掉二叉树中所有父结点与其右子结点之间的连线。

(3) 规整化。将图中各结点按层次排列且将所有的虚线变成实线。

 

当一般的树转换成二叉树后,二叉树的右子树必为空。若把森林中的第二棵树(转换成二叉树后)的根结点作为第一棵树(二叉树)的根结点的兄弟结点,则可导出森林转换成二叉树的转换算法如下:

   设F={T1, T2,⋯,Tn}是森林,则按以下规则可转换成一棵二叉树B=(root,LB,RB)

②     若n=0,则B是空树。

②  若n>0,则二叉树B的根是森林T1的根root(T1),B的左子树LB是B(T11,T12, ⋯,T1m),其中T11,T12,⋯,T1m是T1的子树(转换后),而其右子树RB是从森林F’={T2, T3,⋯,Tn}转换而成的二叉树。

 

转换步骤

②    将F={T1, T2,⋯,Tn}中的每棵树转换成二叉树。

②  按给出的森林中树的次序,从最后一棵二叉树开始,每棵二叉树作为前一棵二叉树的根结点的右子树,依次类推,则第一棵树的根结点就是转换后生成的二叉树的根结点

3.3二叉树转换成森林

若B=(root,LB,RB)是一棵二叉树,则可以将其转换成由若干棵树构成的森林:F={T1, T2,⋯,Tn}。

转换算法:

①  若B是空树,则F为空。

②  若B非空,则F中第一棵树T1的根root(T1)就是二叉树的根root, T1中根结点的子森林F1是由树B的左子树LB转换而成的森林;F中除T1外其余树组成的的森林F’={T2, T3,⋯,Tn}是由B右子树RB转换得到的森林。

 

 

步奏:

①  去连线。将二叉树B的根结点与其右子结点以及沿右子结点链方向的所有右子结点的连线全部去掉,得到若干棵孤立的二叉树,每一棵就是原来森林F中的树依次对应的二叉树

②  二叉树的还原。将各棵孤立的二叉树按二叉树还原为树的方法还原成一般的树

 

3.3.1森林与树的遍历

⑴  先序遍历:先访问根结点,然后依次先序遍历完每棵子树。

ABCDEFGIJHK

⑵ 后序遍历:先依次后序遍历完每棵子树,然后访问根结点

CDBFGIJHEKA

 

说明

◆ 树的先序遍历实质上与将树转换成二叉树后对二叉树的先序遍历相同。

◆ 树的后序遍历实质上与将树转换成二叉树后对二叉树的中序遍历相同

 

2  森林的遍历

      设F={T1, T2,⋯,Tn}是森林,对F的遍历有二种方法。

⑵   先序遍历:按先序遍历树的方式依次遍历F中的每棵树。

⑵ 中序遍历:按后序遍历树的方式依次遍历F中的每棵树。

 

3.4 赫夫曼树

基本概念

① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径。

② 路径长度:结点路径上的分支数目称为路径长度。

③   树的路径长度:从树根到每一个结点的路径长度之和。

 

④ 结点的带权路径长度:从该结点的到树的根结点之间的路径长度与结点的权(值)的乘积。

权(值):各种开销、代价、频度等的抽象称呼。

⑤ 树的带权路径长度:树中所有叶子结点的带权路径长度之和,记做:

        WPL=w1´l1+w2´l2+⋯+wn´ln=∑wi´li   (i=1,2,⋯,n)

其中:n为叶子结点的个数;wi为第i个结点的权值; li为第i个结点的路径长度。

⑥  Huffman树:具有n个叶子结点(每个结点的权值为wi)的二叉树不止一棵,但在所有的这些二叉树中,必定存在一棵WPL值最小的树,称这棵树为Huffman树(或称最优树) 。

(a)  WPL=2´2+3´2+6´2+7´2=36 ;

(b)  WPL=2´1+3´2+6´3+7´3=47 ;

(c)  WPL=7´1+6´2+2´3+3´3=34 。

 

3.4.1 Huffman树的构造

①  根据n个权值{w1, w2, ⋯,wn},构造成n棵二叉树的集合F={T1, T2, ⋯,Tn},其中每棵二叉树只有一个权值为wi的根结点,没有左、右子树;

②  在F中选取两棵根结点权值最小的树作为左、右子树构造一棵新的二叉树,且新的二叉树根结点权值为其左、右子树根结点的权值之和;

③  在F中删除这两棵树,同时将新得到的树加入F中;

④  重复②、③,直到F只含一颗树为止。

    构造Huffman树时,为了规范,规定F={T1,T2,⋯,Tn}中权值小的二叉树作为新构造的二叉树的左子树,权值大的二叉树作为新构造的二叉树的右子树;在取值相等时,深度小的二叉树作为新构造的二叉树的左子树,深度大的二叉树作为新构造的二叉树的右子树。   

 

3.4.2 Huffman编码方法

    以字符集C作为叶子结点,次数或频度集W作为结点的权值来构造Huffman树。规定Huffman树中左分支代表“0”,右分支代表“1” 。

    从根结点到每个叶子结点所经历的路径分支上的“0”或“1”所组成的字符串,为该结点所对应的编码,称之为Huffman编码。

    由于每个字符都是叶子结点,不可能出现在根结点到其它字符结点的路径上,所以一个字符的Huffman编码不可能是另一个字符的Huffman编码的前缀。

Huffman编码算法实现

#define  MAX_NODE 200     /*   Max_Node>2n-1  */

typedefstruct

{     unsigned int Weight ;    /*  权值域  */

unsingedint Parent , Lchild , Rchild ;

} HTNode;

 

 

 

void Create_Huffman(unsignedn, HTNode HT[ ], unsigned m)

       /* 创建一棵叶子结点数为n的Huffman树  */

{  unsigned int  w ;   int  k, j ;

for(k=1 ; k<m ; k++)

{   if (k<=n)

    {  printf(“\nPlease Input Weight : w=?”);

       scanf(“%d”, &w) ;HT[k].weight=w; 

    }       /*  输入时,所有叶子结点都有权值  */

else  HT[k].weight=0;  /*  非叶子结点没有权值 */

HT[k].Parent=HT[k].Lchild=HT[k].Rchild=0;

}     /* 初始化向量HT */

for(k=n+1; k<m ; k++)

{   unsigned w1=32767 , w2=w1 ;

      /* w1 , w2分别保存权值最小的两个权值  */

 int p1=0 , p2=0 ;

     /* p1 , p2保存两个最小权值的下标  */

for(j=1 ; j<=k-1 ; j++)

    {  if (HT[k].Parent==0)    /* 尚未合并 */

           {  if (HT[j].Weight<w1)

                     {   w2=w1 ; p2=p1 ;

                          w1=HT[j].Weight ; p1=j; 

                      }

        else if (HT[j].Weight<w2)

                  {  w2=HT[j].Weight ;  p2=j ;  }

     }   /*  找到权值最小的两个值及其下标  */

}

HT[k].Lchild=p1; HT[k].Rchild=p2 ;

HT[k].weight=w1+w2;

HT[p1].Parent=k; HT[p2].Parent=k ;

}

}

3.4.3 Huffman编码算法

   根据出现频度(权值)Weight,对叶子结点的Huffman编码有两种方式:

① 从叶子结点到根逆向处理,求得每个叶子结点对应字符的Huffman编码。

② 从根结点开始遍历整棵二叉树,求得每个叶子结点对应字符的Huffman编码。

       由Huffman树的生成知,n个叶子结点的树共有2n-1个结点,叶子结点存储在数组HT中的下标值为1∽n。

①  编码是叶子结点的编码,只需对数组HT[1…n]的n个权值进行编码;

②  每个字符的编码不同,但编码的最大长度是n。

 

算法实现

void Huff_coding(unsignedn , Hnode HT[] , unsigned m)

          /* m应为n+1,编码的最大长度n加1 */

{  int  k, sp ,fp ;

char*cd , *HC[m] ;

cd=(char*)malloc(m*sizeof(char)) ;

/*  动态分配求编码的工作空间  */

cd[n]=‘\0’    /*  编码的结束标志 */

for (k=1; k<n+1 ; k++)       /*  逐个求字符的编码    */

{  sp=n ; p=k ; fp=HT[k].parent ;

for(  ; fp!=0 ;p=fp , fp=HT[p].parent)

       /*  从叶子结点到根逆向求编码  */

      if (HT[fp].parent==p)  cd[--sp]=‘0’ ;

     else cd[--sp]=‘1’ ;

HC[k]=(char*)malloc((n-sp)*sizeof(char)) ;

          /* 为第k个字符分配保存编码的空间 */

trcpy(HC[k], &cd[sp]) ;

}

free(cd);

}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值