1. 程式人生 > >赫夫曼樹-赫夫曼編碼

赫夫曼樹-赫夫曼編碼

赫夫曼樹,又稱最優樹,是一類帶權路徑長度最短的樹,有著廣泛的應用。

設二叉樹具有n個帶權值的葉子結點,從根結點到各個葉子結點的路徑長度與對應葉子結點權值的乘積之和叫做二叉樹的帶權路徑長度。

對於一組帶有確定權值的葉子結點,帶權路徑長度最小的二叉樹稱為最優二叉樹。

構造赫夫曼樹:

1、建立n個根結點,權值{w1,w2,,,wn},得森林{T1,T2,Tn};

2、在森林中選取根結點權值最小的兩顆二叉樹歸併為新二叉樹,新二叉樹根結點權值為兩權值之和。

3、將新二叉樹加入森林,同時忽略被歸併的兩顆二叉樹。

4、重複2和3,至森林只有一顆二叉樹。該二叉樹就是赫夫曼樹。

應用:赫夫曼編碼。

以前,進行快速遠距離通訊的主要手段是電報,即將需傳送的文字轉換成由二進位制的字組成的字串。當然,在傳送電文時,希望總長儘可能地短。而赫夫曼編碼就是一種最優字首編碼。

赫夫曼樹的儲存結構定義:含n個字元則赫夫曼樹有2*n-1個結點,個數固定,編碼和解碼無增刪,故赫夫曼樹採用動態分配的順序儲存結構,構造樹時從葉子結點逐步往上走,識別字符或者說解碼時從根往下走,故結點要含雙親和左右孩子下標,當然還要有權值。

首先是輔助巨集的定義:

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW -1
#define UNDERFLOW -2
#define NULL 0
typedef int Status;
typedef char TElemType;

所有字元的Huffman編碼用含n個元素的 動態陣列表示,每個元素指向一個字串。

typedef char * *HuffmanCode;

赫夫曼樹的儲存結構定義:

//Huffman樹的儲存結構定義
typedef struct{
   unsigned int weight;//權值
   int v; //存樹的大小
   unsigned int parent,lchild,rchild;//雙親與左右孩子下標
}HTNode,*HuffmanTree;

演算法思想:構造Huffman數HT,並求Huffman編碼HC n為字元數 w為權值陣列為所有結點開闢儲存空間,初始化各葉結點和分支結點 構造Huffman樹關鍵是確定各分支結點相關結點相關資訊,求出最小的二叉樹 據此設定當前分支結點各成員的值開闢空指標陣列 開闢臨時存放單個編碼的陣列 從各葉出發逆向尋根每向上一步都將當前編碼符記錄到臨時陣列 最後一個空位置 ,待到達根 則臨時陣列中得到完整編碼後將該編碼複製到指標陣列的恰當位置即可。

演算法實現:

Status HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n){
	/*構造Huffman數HT,並求Huffman編碼HC n為字元數 w為權值陣列
      為所有結點開闢儲存空間,初始化各葉結點和分支結點
      構造Huffman樹關鍵是確定各分支結點相關結點相關資訊,求出最小的
      二叉樹 據此設定當前分支結點各成員的值
      開闢空指標陣列 開闢臨時存放單個編碼的陣列 從各葉出發逆向尋根每
      向上一步都將當前編碼符記錄到臨時陣列 最後一個空位置 ,待到達根
	  則臨時陣列中得到完整編碼後將該編碼複製到指標陣列的恰當位置即可*/
   if(n<=1)
      return ERROR;
   int m=2*n-1,i,j,f,start;//m計算Huffman的結點總數
   unsigned int s1,s2,c;
   char *cd;
   HTNode *p;
   HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//0單元不用 先存葉後分支
   if(!HT)
	   exit(OVERFLOW);
   for(p=HT+1,i=1;i<=n;i++,p++,w++){
	   //初始化葉子
       p->weight=*w;
       p->parent=0;
	   p->rchild=0;
       p->lchild=0;
	   p->v=i;
   }
   for(;i<=m;i++,p++){
	   //初始化各分支結點 不確定先寫0
       p->weight=0;
       p->parent=0;
	   p->rchild=0;
       p->lchild=0;
	   p->v=0;
   }
   for(i=n+1;i<=m;i++){
	   //建Huffman樹
       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;
	   HT[i].v=HT[s1].v; //修改樹大小
   }
   HC=(HuffmanCode)malloc((n+1)*sizeof(char *));//開闢n指標各指向一編碼串
   if(!HC)
	   exit(OVERFLOW);
   cd=(char *)malloc(n*sizeof(char));//存編碼的臨時空間 編碼最長n-1
   if(!cd)
	   exit(OVERFLOW);
   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));//為第i個字元編碼分配空間
      strcpy(HC[i],&cd[start]);//複製到HC的相應位置
   }
   free(cd);//記得釋放臨時空間
   return OK;
}

Select函式:在HT[1..i]中選擇parent為0 且權最小的兩節點 設下標分別為s1 s2

void Select(HuffmanTree HT,int i,unsigned int &s1,unsigned int &s2){
	//在HT[1..i]中選擇parent為0 且權最小的兩節點 設下標分別為s1 s2
   int flag1=0,flag2=0,t;//判斷是否找到第一個與第二個數
   for(int j=1;j<=i;j++){
       if(!HT[j].parent){
		   if(!flag1){//找第一個parent為0的數
		     s1=j;
		     flag1=1;
		  }
		   else if(flag1&&!flag2){//找第二個parent為0的數
			   if(HT[j].weight<HT[s1].weight){//將其中小的賦給s1 大的賦給s2
				s2=s1;
			    s1=j;
			 }
		     else
				 s2=j;
		     flag2=1;
		  }
	      else{
			  //如果當前權值小於最小值 修改最小值與次小值
		     if(HT[j].weight<HT[s1].weight){
			    s2=s1;
				s1=j;
			 }
			 //如果當前權值小於此小值但是大於最小值 修改次小值
		     else if(HT[j].weight<HT[s2].weight)
			    s2=j;
		  }
	   }
   }
   if(HT[s1].v<HT[s2].v){//如果s1 所在的樹大於樹s2 交換s1 s2 字典序就是儘量使字典序小的字母在左邊!!!
      t=s1;
      s1=s2;
      s2=t;
   }
}