關於哈夫曼樹的理解

關於哈弗曼樹的理解

今天我們就來梳理一下哈夫曼樹。哈夫曼樹的思想我覺得可以歸結成,由小到大,逐步合併。爲了更好地理解,我們來看一個實際問題吧:

我們知道,在我們使用26鍵拼音輸入時,每一個字母都會有一個使用頻率。現在假定 (A:3;B:0.2;C:0.1;D:4),其中這四個字母有各自的權值。當我們將這四個字母進行二進制編碼時,我們會引發思考。假設我們固定編碼長度,讓計算機按固定的二進制位數(如本例子爲兩位)來進行識別,是一種可行的方法 (A:00 ,B:01 ,C:10 ,D:11)計算機每次取兩位來進行識別。

但是這樣運行效率就不高了,比如當字母數量增加到26個字母時候,計算機爲了準確識別,只能增加識別二進制的位數。我們不禁思考,如果讓頻繁出現的 D字母能更快的識別,就能增加效率了。此時,哈弗曼樹便因運而生。先看看上述例子的哈弗曼樹構建過程。

在這裏插入圖片描述

可以看到每個字母就是各個葉子,如果按照左0右1的規則,那麼可以編寫成:D :1 , A:01,C:001,B:000 。(這裏是因爲)我們可以發現,這樣編寫的好處是識別的唯一性。比如一段二進制信息(101000),我們發現只有唯一一個結果:DAB。不存在因爲編碼的重合導致識別錯誤。

我先給出哈夫曼樹及其編碼,然後再進行分析

#include<iostream>
#include<cstring>
using namespace std;

#define leaves 8              //葉子數目 
#define pointSum leaves*2-1    //節點總數
#define MaxValue 10000         //最大權值 

typedef struct{              //構造哈弗曼樹的節點類型 
	char ch;
	double weight;
	int parent;
	int Lchild,Rchild;
}Htreetype;

typedef struct{              //構建哈弗曼樹碼的類型 
	int bit[leaves];
	int start;
	char ch;
}Hcodetype;

void select(Htreetype t[],int k,int *p1,int *p2)  //找最小節點的點 
{
	*p1 = *p2 = 0;
	int smallOne,smallTwo;
	smallOne = smallTwo = MaxValue;
	int i;
	for(i=0; i<k; i++)
	{
		if(t[i].parent == -1)
		{
			if(t[i].weight < smallOne)
			{
				smallTwo = smallOne;
				smallOne = t[i].weight;
				*p2 = *p1;
				*p1 = i;
			}
			else if(t[i].weight <smallTwo)
			{
				smallTwo = t[i].weight;
				*p2 = i;
			}
		}
	}
}

void HffmanTree(Htreetype t[])
{
	int i,p1,p2;
	double value;
	p1 = p2 = 0;
	for(i=0; i<pointSum; i++)       //初始化 
	{
	    t[i].weight = 0;
	    t[i].parent = -1;
	    t[i].Lchild = -1;
	    t[i].Rchild = -1;
    }
    cout<<"-----------請輸入8字母的權值-----------"<<endl;
    
    for(i=0;i<leaves;i++)          //輸入權值 
    {
		cin>>t[i].ch;
		cin>>value;
		t[i].weight = value;
	}
	
	for(i=leaves; i<pointSum;i++)     //建立哈弗曼樹數組 
    {
		select(t,i,&p1,&p2);
		t[p1].parent = i;             //找出權重最小的兩個並且合併,合併後共同parent爲i 
        t[p2].parent = i;
        t[i].Lchild = p1;
        t[i].Rchild = p2;
        t[i].weight = t[p1].weight + t[p2].weight;  //合併後parent的權重爲左右孩子的權值之和 
        
	}
}

void HffmanCode(Hcodetype code[],Htreetype t[])
{
	int i,c,p;
	Hcodetype cd;      //緩衝變量,暫時存儲
	HffmanTree(t);
    for (i = 0; i < leaves; i++)
    {
        cd.start = leaves;  
        cd.ch = t[i].ch;
        c = i;                  //從葉子結點向上
        p = t[i].parent;       //t[p]是t[i]的parent
        while (p != -1)        //判斷的條件是直到不爲頂端 
        {  
            cd.start--;        //用來記錄該葉子在第幾層 
            if (t[p].Lchild == c)
                cd.bit[cd.start] = 0;           //左子樹編爲0
            else
                cd.bit[cd.start] = 1;          //右子樹編爲1
            c = p;                              //再向上移動,繼續尋親之旅 
            p = t[c].parent;
        }
        code[i] = cd;                           //第i+1個字符的編碼存入code
    } 
}

void show(Htreetype t[], Hcodetype code[])
{
    int i, j;
    for (i = 0; i<leaves; i++)
    {
        cout<<code[i].ch<<" ";
        for (j = code[i].start; j<leaves; j++)    //編碼的長度等於總層數減去所在層數,即從頂層到葉子 
            cout<<code[i].bit[j]<<" ";            // 所要經歷的層數 
        cout<<endl;
    }
}

int main()
{
	Htreetype t[pointSum];
    Hcodetype code[leaves];
    HffmanCode(code, t);
    show(t,code);
    return 0;
}
typedef struct{                //構造哈弗曼樹的節點類型 
	char ch;                   //存儲數據類型
	double weight;             //該數據的權值
	int parent;                //父母
	int Lchild,Rchild;         //左右孩子
}Htreetype;

先建立一個結構體,結構體中有存儲數據類型,權值,父母和左右孩子。

void HffmanTree(Htreetype t[])
{
	int i,p1,p2;
	double value;
	p1 = p2 = 0;
	for(i=0; i<pointSum; i++)       //初始化 
	{
	    t[i].weight = 0;
	    t[i].parent = -1;
	    t[i].Lchild = -1;
	    t[i].Rchild = -1;
    }
    cout<<"-----------請輸入8字母的權值-----------"<<endl;
    
    for(i=0;i<leaves;i++)          //輸入權值 
    {
		cin>>t[i].ch;
		cin>>value;
		t[i].weight = value;
	}
	
	for(i=leaves; i<pointSum;i++)     //建立哈弗曼樹數組,從最後的元素開始
    {
		select(t,i,&p1,&p2);          //注意函數的參數是傳地址,這樣就可以改變形參p1和p2
		t[p1].parent = i;             //找出權重最小的兩個並且合併,合併後共同parent爲i 
        t[p2].parent = i;
        t[i].Lchild = p1;             //將最小權值的元素所在列表的所在位置作爲左孩子
        t[i].Rchild = p2;             //將第二小權值的元素所在列表的所在位置作爲右孩子
        t[i].weight = t[p1].weight + t[p2].weight;  //合併後parent的權重爲左右孩子的權值之和 
        
	}
}

void select(Htreetype t[],int k,int *p1,int *p2)  //找最小節點的點 
{
	*p1 = *p2 = 0;
	int smallOne,smallTwo;
	smallOne = smallTwo = MaxValue;               //先將值初始化,其中smallone爲最小,smallTwo爲
	int i;                                        //第二小
	for(i=0; i<k; i++)
	{
		if(t[i].parent == -1)                     //找出並未讀取過的點
		{
			if(t[i].weight < smallOne)
			{
				smallTwo = smallOne;
				smallOne = t[i].weight;
				*p2 = *p1;                      //p2得到第二小權值的元素所在列表的所在位置
				*p1 = i;                        //p1得到最小權值的元素所在列表的所在位置i
			}
			else if(t[i].weight <smallTwo)      //這裏考慮當前元素大於第一小的p1,但小於第二小p2
			{
				smallTwo = t[i].weight;
				*p2 = i;
			}
		}
	}
}



然後是建立哈弗曼樹的過程,首先先初始化哈夫曼樹,然後找出哈弗曼樹中最小的兩個元素進行合併,並把剛剛已經使用的那兩個元素進行標記,最後可以得到如下哈夫曼樹的列表形式。

在這裏插入圖片描述

void HffmanCode(Hcodetype code[],Htreetype t[])
{
	int i,c,p;
	Hcodetype cd;      //緩衝變量,暫時存儲
	HffmanTree(t);     //調用函數,先建立哈夫曼樹
    for (i = 0; i < leaves; i++)
    {
        cd.start = leaves;      //葉子數量就是層數,可以參考我發的第一張圖,而第幾層也決定哈夫曼編
        cd.ch = t[i].ch;        //碼的長度
        c = i;                  //從葉子結點向上
        p = t[i].parent;       //t[p]是t[i]的parent
        while (p != -1)        //判斷的條件是直到不爲頂端,頂端並沒有父母,所以parent值爲-1
        {                    
            cd.start--;        //用來記錄該葉子在第幾層 
            if (t[p].Lchild == c)
                cd.bit[cd.start] = 0;           //左子樹編爲0
            else
                cd.bit[cd.start] = 1;          //右子樹編爲1
            c = p;                              //再向上移動,繼續尋親之旅 
            p = t[c].parent;
        }
        code[i] = cd;                           //第i+1個字符的編碼存入code
    } 
}

然後是哈夫曼編碼的過程,先取出葉子,然後由葉子開始向上尋親,然後逐步判斷自己是左孩子還是右孩子,根據左0右1的規則進行哈夫曼編碼。

void show(Htreetype t[], Hcodetype code[])
{
    int i, j;
    for (i = 0; i<leaves; i++)
    {
        cout<<code[i].ch<<" ";
        for (j = code[i].start; j<leaves; j++)    //編碼的長度等於總層數減去所在層數,即從頂層到葉子 
            cout<<code[i].bit[j]<<" ";            // 所要經歷的層數 
        cout<<endl;
    }
}

最後是輸出哈夫曼代碼的過程。看一下運行結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章