05-樹9 Huffman Codes

zcr__發表於2020-12-05

構造哈夫曼樹

為了實現哈夫曼演算法,可以利用最小堆來儲存最優解點。因此可以通過刪除最小堆的根,並插入兩個和來實現哈夫曼演算法。因此在最小堆結構中的DATA應是指向哈夫曼樹的指標陣列。刪除最小堆的根時也應當保留刪除兩個節點指向的哈夫曼樹,並且做到在返回時左子樹和右子樹也都不為NULL。直到堆中只有一個有效元素時就說明一棵哈夫曼樹生成完成了。:happy:

最小堆與哈夫曼的結構

typedef struct HuffmanTree * TNode;
typedef struct Heap * MinHeap;
struct HuffmanTree{
    int weight;
    TNode Left;
    TNode Right;
};
struct Heap{
    TNode * Data;
    int Size;
    int MaxSize;
};

這裡是最基本的最小堆和哈夫曼結構的構建。?

注意⚠️:在後面對heap中的Data建立空間時應該首先對Data進行建立Data = (TNode *)malloc(sizeof(TNode) * (N + 1))再對Data中的每一項進行建立空間的操作Data[i] = (TNode)malloc(sizeof(struct HuffmanTree))。這應該是初學C語言才會出錯的點,但是當時寫程式的時候真的沒看出來,還是看了其他部落格上的標準程式碼才發現了錯誤。

最小堆的插入與刪除操作

插入操作

int InsertHeap(MinHeap H, int a)
{
    int i = ++H->Size;
    for(; H->Data[i/2]->weight > a; i /= 2)
        H->Data[i] = H->Data[i/2];
    H->Data[i] = (TNode)malloc(sizeof(struct HuffmanTree));
    H->Data[i]->weight = a;
    H->Data[i]->Left = H->Data[i]->Right = NULL;
    return i;
}

唯一不同的地方就是Data的型別改變了, 並且再插入操作中要設定哈夫曼根節點指向的左子樹和右子樹均為空。在後續實現哈夫曼演算法時會在其他函式中更新指向的左子樹和右子樹的值,因此需要將本來返回的null函式值要改成堆陣列的index。看到哈夫曼樹的構建就會清楚啦!

刪除操作

TNode DeleteHeapMin( MinHeap H )
{
    int Parent, Child;
    TNode MinItem = H->Data[1];
    TNode tmp = H->Data[H->Size--];
    for(Parent = 1; Parent * 2  <= H->Size; Parent = Child)
    {
        Child = Parent * 2;
        if((Child != H->Size) && (H->Data[Child]->weight > H->Data[Child + 1]->weight))
            Child++;
        if(tmp->weight <= H->Data[Child]->weight)
            break;
        else
            H->Data[Parent] = H->Data[Child];
    }
    H->Data[Parent] = tmp;
    return MinItem;
}

習慣上還是將根節點儲存在第一個元素。這樣比較好記憶和操作。

之前寫的版本竟然還是隻傳了哈夫曼樹中的weight,我怕不是個傻子。

哈夫曼樹的構建

TNode Huffman(MinHeap H)
{
    while(H->Size > 1)
    {
        TNode A, B;
        A = DeleteHeapMin(H);
        B = DeleteHeapMin(H);
        int f = InsertHeap(H, A->weight + B->weight);
        H->Data[f]->Left = A;
        H->Data[f]->Right = B;
    }
    return H->Data[1];
}

當堆只有一個有效節點的時候就說明所有哈夫曼樹的節點都整合到了一起,說明哈夫曼樹就構建好啦!

計算WSL

int WPL( TNode T, int Depth)
{
    if(T->Left == NULL && T->Right == NULL)
        return Depth * T->weight;
    else
    {
        int leftWPL = 0, rightWPL = 0;
        if(T->Left) leftWPL = WPL(T->Left, Depth + 1);
        if(T->Right) rightWPL = WPL(T->Right, Depth + 1);
        return rightWPL + leftWPL;
    }
}

利用遞迴的思想計算WSL。這學期一直在看Standard ML,感覺寫C的遞迴已經有點不適應了。

另:判斷某組編碼是否為最優編碼

某組編碼為最優編碼的條件有三個:

  1. 有最小權值
  2. 具有唯一性:只是路徑的節點不能表示為某一元素,即每一個代表編碼的節點不能有左子樹和右子樹
  3. 不能有度為1的點。

為什麼需要通過構造哈夫曼樹來判斷某組編碼是否為最優編碼呢?這是我在看浙大mooc時的一個疑問。後來我直到,這是為了得到該組編碼的WSL。WSL是首先判斷某組編碼是否為最優編碼的第一個條件。

還有另一個重要的條件就是每一個代表編碼的節點應該沒有左子樹或者右子樹,並且在找到該編碼的路徑中不能經過已經有值的節點(兩個條件實際上是等價的,然而為了解決這個問題需要在程式中考慮到這兩個問題)。第一個方面是先考慮的,先走的步長長的編碼,後一個方面是後給出的,走的步長短的編碼。所以在編寫程式的時候一定要多方謎案考慮,比如我就因為判斷的是“某節點沒有左子樹並且沒有右子樹”而出現答案錯誤。

int judge(int N, int f[], int CodeLen)
{
    int flag = 1, w = 0;
    TNode T = (TNode)malloc(sizeof(struct HuffmanTree));
    T->Left = T->Right = NULL;
    T->weight = -1;
    for(int i = 0; i < N; i++)
    {
        char c, s[N + 1];
        getchar();
        scanf("%c %s", &c, s);
        int length = strlen(s);
        if(length > N) flag = 0;
        w += length * f[i];
        int j = 0;
        TNode tmp = T;
        while(s[j] != '\0')
        {
            if(s[j] == '0')
            {
                if(!tmp->Left)
                {
                    tmp->Left = (TNode)malloc(sizeof(struct HuffmanTree));
                    tmp->Left->weight = -1;
                    tmp->Left->Left = tmp->Left->Right = NULL;
                    tmp = tmp->Left;
                }
                else
                {
                    if(tmp->Left->weight != -1) {
                        flag = 0;
                        tmp = tmp->Left;
                    }
                    else
                        tmp = tmp->Left;
                }
            }
            else
            {
                if(!tmp->Right)
                {
                    tmp->Right = (TNode)malloc(sizeof(struct HuffmanTree));
                    tmp->Right->weight = -1;
                    tmp->Right->Right = tmp->Right->Left = NULL;
                    tmp = tmp->Right;
                }
                else
                {
                    if(tmp->Right->weight != -1) {
                        //printf("**warning**\n");
                        flag = 0;
                        tmp = tmp->Right;
                    }
                    else
                        tmp = tmp->Right;
                }
            }
            j++;
        }
        tmp->weight = i;
        if(tmp->Left != NULL || tmp->Right != NULL)
            flag = 0;

    }
    if(w > CodeLen)
        flag = 0;
    free(T);
    return flag;
}

相關文章