1. 程式人生 > >二叉樹(四)

二叉樹(四)

idt -- 圖片 names oid 層級 align 根節點 define

哈夫曼編碼

哈夫曼編碼其實就是根據每個字符出現的頻率制定其對應的01編碼

哈夫曼編碼滿足同一套編碼中沒有任何一串編碼是另一串的前綴(否則有多種翻譯方式)

例如這樣一串字符串:accbac

我們可以得到如下的表格

字符 出現次數 哈夫曼編碼
a 2 01
b 1 10
c 3 1

在這套哈夫曼編碼中,沒有任何一串編碼為另一串編碼的前綴

而且有一個重要的特點:出現頻率越高,編碼長度越小

很容易就能想出這是為了節省空間

但如何構造一套符合這個特點的哈夫曼編碼呢?

構造哈夫曼樹

首先,哈夫曼樹並不完全是二叉樹,哈夫曼樹的叉根據編碼的進制而定

我們在此研究的是二叉的哈夫曼樹,哈夫曼編碼也就是二進制下的字符串(01串)

那麽如何構造一棵哈夫曼樹呢?

我還是以accbac這個字符串舉例

在這個字符串裏,a出現了2次,b出現了1次,c出現了3次

我們先把所有的字符都變成一棵棵只有根結點的樹

現在我們有了一個森林,森林裏共三棵樹:2,1,3

我們為了讓出現頻率高的字符對應的編碼盡量短,最後構造出來的樹中肯定出現頻率高的字符層級靠上,出現頻率低的字符層級靠下

所以我們從下往上構造樹

接下來的算法很像合並果子

首先找出根節點最小的兩棵2,1

將它們分別作為新樹的左孩子與右孩子

新樹的根是他們之和,也就是3

現在我們還剩兩棵樹,一棵是根節點為3,左孩子為1,右孩子為2的樹,另一棵是只有根節點(3)的樹

我們再選出根節點最小的兩棵樹合並

於是我們就得到了這樣一棵哈夫曼樹

技術分享圖片

而它們對應的哈夫曼編碼又是什麽呢?

我們從根節點開始向下走

往左子樹走一步的路徑權值為1,往右子樹走一步的路徑權值為0

技術分享圖片

於是這樣的一套哈夫曼編碼就構造完啦

那如何論證正確性呢?(論證為何符合沒有任一編碼為另一編碼的前綴)

其實這很簡單

因為每個字符在最終的哈夫曼樹中都是葉子節點

所以它們不可能有左右孩子

而且從根節點到每個葉子節點的路徑都是唯一的

所以不存在某條從根節點到一個葉子節點的路徑在另一條從根節點到另一個葉子節點的路徑上

而哈夫曼樹左叉為1,右叉為0,不可能出現兩條不一樣的路徑,編碼卻一樣的情況

那也就不會有一個字符串是另一個的前綴了


蒟蒻從《算法競賽寶典》搬來了一段代碼(懶得寫)

//哈夫曼編碼 
#include <iostream>
using namespace std;
#define MAXN 1000
#define MAXLEAF 30
#define MAXNODE MAXLEAF*2 -1
typedef struct //編碼結構體
{
  int bit[100];//保存哈夫曼編碼 
  int start;//編碼的開始位置 
}HCode;   
typedef struct//結點結構體
{
  int weight;
  int parent;
  int lchild;
  int rchild;
}HNode;        
HNode HuffNode[MAXNODE];//定義一個結點結構體數組
HCode HuffCode[MAXLEAF];//定義一個編碼結構體數組
void CreatHuffmanTree(HNode HNode[MAXNODE],int n)//構造一顆哈夫曼樹
{ 
    int i,j;
    int m1,m2;//構造樹過程中兩個最小權值結點的權值
    int x1,x2;//構造樹過程中兩個最小權值結點在數組中的序號
    for(i=0;i<n-1;i++)//循環構造 Huffmantree
    {
        m1=m2=10000;//m1、m2中存放兩個無父結點且結點權值最小的兩個結點
        x1=x2=0;
        for(j=0;j<n+i;j++)//找出所有結點中權值最小、無父結點的兩個結點,並合並之為一顆二叉樹
        {
            if(HNode[j].weight<m1&&HNode[j].parent==-1)
            {
                m2=m1; 
                x2=x1; 
                m1=HNode[j].weight;
                x1=j;
            }
            else if(HNode[j].weight<m2&&HNode[j].parent==-1)
            {
                m2=HNode[j].weight;
                x2=j;
            }
        }
        //設置找到的兩個子結點 x1、x2 的父結點信息
        HNode[x1].parent=n+i;
        HNode[x2].parent=n+i;
        HNode[n+i].weight=HNode[x1].weight + HNode[x2].weight;
        HNode[n+i].lchild=x1;
        HNode[n+i].rchild=x2;
    }
}
void CreatHuffmanCode(HNode HuffNode[MAXNODE],int n)
{
    HCode cd;//定義一個臨時變量來存放求解編碼時的信息   
    for(int i=0;i<n;i++)
    {
        cd.start=n-1;
        int c=i;
        int p=HuffNode[c].parent;
        while(p!=-1)   // 父結點存在 
        {
            if(HuffNode[p].lchild==c)
                cd.bit[cd.start]=0;
            else
                cd.bit[cd.start]=1;
            cd.start--;        // 求編碼的低一位 
            c=p;                    
            p=HuffNode[c].parent;    // 設置下一循環條件 
        } 
        for(int j=cd.start+1;j<n;j++)//保存求出的每個葉結點的哈夫曼編碼和編碼的起始位
        { 
            HuffCode[i].bit[j]=cd.bit[j];
        }
        HuffCode[i].start=cd.start;
    }                  
} 
void init(HNode HNode[],int n)//初始化
{
    for(int i=0;i<2*n-1;i++) 
    {
        HNode[i].weight= 0;
        HNode[i].parent=-1;
        HNode[i].lchild=-1;
        HNode[i].lchild=-1;
    }
    for(int i=0;i<n;i++)
    {
        cout<<"請輸入第"<<i<<"個結點的權重";  
        cin>>HNode[i].weight;
    }    
}
int main()
{
    int i,j,n;
    cout<<"請輸入字符個數n:\n";
    cin>>n;
    init(HuffNode,n);//初始化 
    CreatHuffmanTree(HuffNode, n);//建立哈夫曼樹 
    CreatHuffmanCode(HuffNode, n);//生成哈夫曼編碼  
    for(i=0;i<n;i++)//輸出哈夫曼編碼
    {
        cout<<i<<"的哈夫曼編碼為:";
        for(j=HuffCode[i].start+1;j<n;j++)
          cout<<HuffCode[i].bit[j];
        cout<<endl;
    }
    return 0;
}

二叉樹(四)