天天看點

哈夫曼樹的C語言詳解

哈夫曼樹的C語言詳解

1)一些名詞的解釋:

路徑:在一棵樹中,一個結點到另一個結點之間的通路,稱為路徑。圖 1 中,從根結點到結點 a 之間的通路就是一條路徑。

路徑長度:在一條路徑中,每經過一個結點,路徑長度都要加 1 。例如在一棵樹中,規定根結點所在層數為1層,那麼從根結點到第 i 層結點的路徑長度為 i - 1 。圖 1 中從根結點到結點 c 的路徑長度為 3。

結點的權:給每一個結點賦予一個新的數值,被稱為這個結點的權。

結點的帶權路徑長度:指的是從根結點到該結點之間的路徑長度與該結點的權的乘積

2)哈夫曼樹的定義:

當用 n 個結點(都做葉子結點且都有各自的權值)試圖建構一棵樹時,如果建構的這棵樹的帶權路徑長度最小,稱這棵樹為“最優二叉樹”,有時也叫“赫夫曼樹”或者“哈夫曼樹”。

在建構哈弗曼樹時,要使樹的帶權路徑長度最小,隻需要遵循一個原則,那就是:權重越大的結點離樹根越近。在圖 1 中,因為結點 a 的權值最大,是以理應直接作為根結點的孩子結點。

3)構造過程:

對于給定的有各自權值的 n 個結點,建構哈夫曼樹有一個行之有效的辦法:

在 n 個權值中選出兩個最小的權值,對應的兩個結點組成一個新的二叉樹,且新二叉樹的根結點的權值為左右孩子權值的和;

在原有的 n 個權值中删除那兩個最小的權值,同時将新的權值加入到 n–2 個權值的行列中,以此類推;

重複 1 和 2 ,直到是以的結點建構成了一棵二叉樹為止,這棵樹就是哈夫曼樹。

4)算法實作:

建構哈夫曼樹時,需要每次根據各個結點的權重值,篩選出其中值最小的兩個結點,然後建構二叉樹。

查找權重值最小的兩個結點的思想是:從樹組起始位置開始,首先找到兩個無父結點的結點(說明還未使用其建構成樹),然後和後續無父結點的結點依次做比較,有兩種情況需要考慮:

如果比兩個結點中較小的那個還小,就保留這個結點,删除原來較大的結點;

如果介于兩個結點權重值之間,替換原來較大的結點;

#include<iostream>
#include<string.h>
#define  MAX 10000 
/*
請輸入一段文字:this*program*is*my*favourite
字元和權值資訊如下
字元:t  權值:2
字元:h  權值:1
字元:i  權值:3
字元:s  權值:2
字元:*  權值:4
字元:p  權值:1
字元:r  權值:3
字元:o  權值:2
字元:g  權值:1
字元:a  權值:2
字元:m  權值:2
字元:y  權值:1
字元:f  權值:1
字元:v  權值:1
字元:u  權值:1
字元:e  權值:1
********************************
字元編碼為:
t:1000
h:11010
i:001
s:1001
*:011
p:11011
r:010
o:1010
g:11100
a:1011
m:1100
y:11101
f:11110
v:11111
u:0000
e:0001
文字編碼為:
100011010001100101111011010101011100010101111000110011001011110011101011111101011111111010000001000110000001
********************************
譯碼:
請輸入要譯碼的二進制字元串,輸入'#'結束:100011010001100101111011010101011100010101111000110011001011110011101011111101011111111010000001000110000001#
譯碼為:
this*program*is*my*favourite
是否繼續?(Y/N):
*/
using namespace std;
typedef struct {
    char letter, *code;
    int weight;
    int parent, lchild, rchild;
}HTNode, *HuffmanTree;
int n;
char coding[100];
int Min(HuffmanTree &HT, int i)
{
    int j;
    unsigned int k = MAX;
    int flag;
    for (j = 0; j <= i; ++j)
    {
        if (HT[j].weight < k && HT[j].parent == 0)//用父結點是否為0來判斷此結點是否已經被選過  
        {
            k = HT[j].weight;
            flag = j;
        }
    }
    HT[flag].parent = 1;//作個标記,說明已經被選擇了,因為在Select函數中要選擇權值小的兩個結點  
    return flag;
}
void Select(HuffmanTree &HT, int i, int &s1, int &s2)
{
    //在HT[1...i]中選擇parent為0且權值最小的兩個結點,其序号分别為s1,s2  
    //s1 <= s2  
    s1 = Min(HT, i);
    s2 = Min(HT, i);
}
void CreateHuffmanTree(HuffmanTree &HT, char t[], int w[])
{
    int m;
    int i, s1, s2;
    if (n <= 1)
        return;
    m = 2 * n - 1; //總共需要2n-1個節點
    HT = new HTNode[m + 1];//開辟空間
    for (i = 0; i<n; i++)
    {
        HT[i].code = '\0';
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
        HT[i].letter = t[i];
        HT[i].weight = w[i];
    }
    for (i = n; i <= m; i++)
    {
        HT[i].code = '\0';
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
        HT[i].letter = ' ';
        HT[i].weight = 0;
    }
    cout << "********************************" << endl;
    for (i = n; i<m; i++)
    {
        Select(HT, i - 1, s1, s2);//在n個數中找出權值最小的兩個

        HT[s1].parent = i;
        HT[s2].parent = i;//将他們兩個的parent節點設定為i;

        HT[i].lchild = s1;
        HT[i].rchild = s2;//把這兩個分别當作左右節點
        HT[i].weight = HT[s1].weight + HT[s2].weight;//他們兩個的雙親為他們兩個的和;

    }
}
void CreatHuffmanCode(HuffmanTree HT)
{
    int start, c, f;
    int i;
    char *cd = new char[n];
    cd[n - 1] = '\0';
    cout << "字元編碼為:" << endl;
    for (i = 0; i<n; i++)
    {
        start = n - 1;
        c = i;
        f = HT[i].parent;
        while (f != 0){
            --start;
            if (HT[f].lchild == c){
                cd[start] = '0';
            }
            else{
                cd[start] = '1';
            }
            c = f;
            f = HT[f].parent;
        }
        HT[i].code = new char[n - start];
        strcpy(HT[i].code, &cd[start]);
        cout << HT[i].letter << ":" << HT[i].code << endl;
    }
    delete cd;
}
void HuffmanTreeYima(HuffmanTree HT, char cod[], int b)           //譯碼
{
    char sen[100];
    char temp[50];
    char voidstr[] = " ";       //空白字元串
    int t = 0;
    int s = 0;
    int count = 0;
    for (int i = 0; i<b; i++)
    {
        temp[t++] = cod[i];     //讀取字元
        temp[t] = '\0';        //有效字元串
        for (int j = 0; j<n; j++){        //依次與所有字元編碼開始比對
            if (!strcmp(HT[j].code, temp)){                //比對成功
                sen[s] = HT[j].letter;    //将字元儲存到sen中
                s++;
                count += t;
                strcpy(temp, voidstr);                //将TEMP置空 
                t = 0;          //t置空
                break;
            }
        }
    }
    if (t == 0){     //t如果被置空了,表示都比對出來了,列印譯碼
        sen[s] = '\0';
        cout << "譯碼為:" << endl;
        cout << sen << endl;
    }
    else{                             //t如果沒有被置空 , 源碼無法被完全比對
        cout << "二進制源碼有錯!從第" << count + 1 << "位開始" << endl;
    }
}
int main()
{
    HuffmanTree HT;
    char a[100], buff[1024], p;//a為存放字元 buff為輸入的字元串 p為輸入譯碼時的字元 
    int b[100];//存放權值資訊 
    int i, j;
    int symbol = 1, x, k; //譯碼時做判斷用的變量  
    cout << "請輸入一段文字:";
    cin >> buff;
    int len = strlen(buff);
    for (i = 0; i<len; i++)
    {
        for (j = 0; j<n; j++)
        {
            if (a[j] == buff[i])
            {
                b[j] = b[j] + 1;
                break;
            }
        }
        if (j >= n)
        {
            a[n] = buff[i];
            b[n] = 1;
            n++;
        }
    }
    cout << "字元和權值資訊如下" << endl;
    for (i = 0; i<n; i++)
    {
        cout << "字元:" << a[i] << "  權值:" << b[i] << endl;
    }
    CreateHuffmanTree(HT, a, b);
    CreatHuffmanCode(HT);
    cout << "文字編碼為:\n";
    for (int i = 0; i < len; i++)
    {
        for (int j = 0; j < n; j++)
        {
            if (buff[i] == HT[j].letter)
            {
                cout << HT[j].code;
                break;
            }
        }
    }
    cout <<endl<< "********************************" << endl;
    cout << "譯碼:" << endl;
    while (1)
    {
        cout << "請輸入要譯碼的二進制字元串,輸入'#'結束:";
        x = 1;//判斷是否有非法字元隻能是0 1 
        k = 0;//作為循環變量來使coding【k】=輸入的字元 
        symbol = 1;//判斷是否輸入結束 
        while (symbol){
            cin >> p;
            if (p != '1'&&p != '0'&&p != '#'){ //若存在其它字元,x設為0,表示輸入的不是二進制
                x = 0;
            }
            coding[k] = p;
            if (p == '#')
                symbol = 0;  //#号結束标志
            k++;
        }
        if (x == 1){
            HuffmanTreeYima(HT, coding, k - 1);        //進行譯碼
        }
        else{
            cout << "有非法字元!" << endl;
        }
        cout << "是否繼續?(Y/N):";
        cin >> p;
        if (p == 'y' || p == 'Y')
            continue;
        else
            break;
    }
    return 0;
}
           
下一篇: BFS

繼續閱讀