【資料結構X.11】程式碼實現 哈夫曼樹的建立,建立,構造,實現哈夫曼編碼

甜甜圈Sweet Donut發表於2020-12-13

主題

程式碼實現哈夫曼樹的建立,建立,構造,實現哈夫曼編碼,實現思路和要點:
抓住哈夫曼樹的性質,每輪選出2個權值最小的點進行構造樹。
抓住哈夫曼編碼的性質,從根出發,向左標0,向右標1。

各種語言的實現可以看下面這個:
哈夫曼樹的各種語言java,python,c++,c,c#的實現

哈夫曼樹的構造思路:

網上說得都挺複雜的,看了半天還是看不懂,所以自己寫了個:

  1. 每輪選出2個權值最小的點,權值加和後,放到空的新位置,標註左孩子和右孩子節點。例如這裡,第一輪中,d和e點最小,權值的和為5,於是把空位置6更新為權值為5,左孩子d右孩子e。
  2. 一直這樣挑選,直到所有的點都被挑選過used=1,只剩下一個點沒有被used
  3. 利用這個得到的表去構造哈夫曼樹。這個過程其實可以放到一個哈夫曼node集陣列中,用for迴圈,從0到nodeNum遍歷,一邊遍歷,一遍把左右節點連線上,最後返回Node[NodeNum-1],也就是這個沒有被used過的節點。顯然,它是根節點。這個其實還挺巧妙的,從這個題得到的啟發:【資料結構X.8】程式碼實現和演算法解析 已知一顆樹的層次序列及每個節點的度, 編寫演算法構造這個樹的孩子兄弟連結串列
  4. 最後進行編碼。由於樹的非遞迴後序遍歷具有能儲存其全部根節點的性質,這是因為進行後序遍歷時,最後才會訪問祖先節點,所以,當訪問x節點時,其所有的祖先節點都存在棧裡。利用這個性質,用棧儲存從祖先節點,到X節點的路徑,然後訪問棧裡的祖先節點,如果是左子樹,標0,如果是右子樹,就標1
    在這裡插入圖片描述

注意:

同一組節點,可以組成多個不同形狀的哈夫曼樹,他們的共同點是WPL值相同

構造1

哈夫曼樹可以有不同的構造,例如下面的:
他們的WPL值都相同
W P L 值 = 路 徑 長 度 ∗ 權 重 WPL值=路徑長度*權重 WPL=都是 23 ∗ 2 + 11 ∗ 3 + 5 ∗ 4 + 3 ∗ 4 + 2 ∗ 29 + 3 ∗ 14 + 4 ∗ 7 + 8 ∗ 4 = 271 23*2+11*3+5*4+3*4+2*29+3*14+4*7+8*4=271 232+113+54+34+229+314+47+84=271
僅僅是左右換了下順序
在這裡插入圖片描述

在這裡插入圖片描述

構造2

這裡雖然形狀改變了:
但是 路 徑 長 度 ∗ 權 重 路徑長度*權重 沒有改變,依然滿足字首碼的性質,也是哈夫曼樹。
5 ∗ 5 + 29 ∗ 2 + 7 ∗ 4 + 8 ∗ 3 + 14 ∗ 3 + 23 ∗ 2 + 3 ∗ 5 + 11 ∗ 3 = 271 5*5+29*2+7*4+8*3+14*3+23*2+3*5+11*3=271 55+292+74+83+143+232+35+113=271
在這裡插入圖片描述

在這裡插入圖片描述

程式碼實現:

函式呼叫:

#include "HalfManTree.h"

int main()
{
    int num = 8;
    int weight[num] = {5, 29, 7, 8, 14, 23, 3, 11};
    //int weight[num] = {10, 5, 4, 2, 3, 6};
    char data[num] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
    HalfManItem item[MAX_SIZE];

    for (int i = 0; i < MAX_SIZE; i++)
    {
        item[i].data = 0;
        item[i].weight = 0;
        item[i].lchild = item[i].rchild = 0;
        item[i].used = false;
    }
    HalfManItem *ht = CreateHalfTable(item, data, weight, num);
   
    //PrintHalfManTable(ht, num);
    HalfManNode *tr = CreateHalfManTree(ht, num);
    //VisitHalfManTree(tr);
    char x = 'a';
    std::cout << "\na: ";
    GetHalfManCode(tr, x);
    std::cout << "\nb: ";
    GetHalfManCode(tr, 'b');
    std::cout << "\nc: ";
    GetHalfManCode(tr, 'c');
    std::cout << "\nd: ";
    GetHalfManCode(tr, 'd');
    std::cout << "\ne: ";
    GetHalfManCode(tr, 'e');
    std::cout << "\nf: ";
    GetHalfManCode(tr, 'f');
    std::cout << "\ng: ";
    GetHalfManCode(tr, 'g');
    std::cout << "\nh: ";
    GetHalfManCode(tr, 'h');
    //GetHalfManCode(tr,'d');
}

函式定義:

#include <iostream>
#include <stack>
#include <queue>
#include <malloc.h>
#include <string.h>
#define MAX_SIZE 200
#define MAX_INT 10000

//哈夫曼樹的構造
typedef struct HalfManNode
{
    int freq; //出現頻率
    char ch;  //儲存的字元
    HalfManNode *lchild, *rchild;
} HalfManNode;

typedef struct HalfManItem
{
    char data;          //儲存字元
    bool used;          //是否已經加入樹了
    int weight;         //權重
    int lchild, rchild; //左孩子、右孩子序號
};

bool Select2Min(HalfManItem *ht, int &nodeNum);
HalfManItem *CreateHalfTable(HalfManItem *item, char data[], int weight[], int &nodeNum);

/***
 * 建立哈夫曼樹
 * 輸入 : 資料和權重,總節點數目
 */
HalfManItem *CreateHalfTable(HalfManItem *item, char data[], int weight[], int &nodeNum)
{
    for (int i = 0; i < nodeNum; i++)
    {
        item[i].data = data[i];
        item[i].weight = weight[i];
        item[i].lchild = item[i].rchild = -1;
        item[i].used = false;
    }

    int parentNum = nodeNum - 1;
    //由於有N個節點,每次插入都選擇兩個根節點權值最小的樹作為新節點左右子樹,並且建立一個新節點
    //根據哈夫曼樹的性質,會新建N-1個新節點,一共需要選N-1次
    for (int i = nodeNum; i < nodeNum + parentNum; i++)
    {
        if (Select2Min(item, nodeNum))
        {
            std::cout << "成功新建節點,左孩子:" << item[nodeNum - 1].lchild << " 右孩子: "
                      << item[nodeNum - 1].rchild << " 權重:" << item[nodeNum - 1].weight << std::endl;
        }
        else
        {
            return item;
        }
    }
    return item;
}

/**
 * 選擇哈夫曼樹的兩個最小的節點
 * 輸入:
 * HalfManTree 樹的根節點
 * n:節點數量
 * *s1\*s2: 
 */
bool Select2Min(HalfManItem *ht, int &nodeNum)
{
    int min1 = MAX_INT, min2 = MAX_INT;
    int minId1 = 0, minId2 = 0;

    HalfManItem *p = ht;
    int i = 0;
    for (; p < ht + nodeNum; p++)
    {
        if (!p->used && p->weight < min1)
        {
            min2 = min1;
            minId2 = minId1;
            min1 = p->weight;
            minId1 = i;
        } //小於min1,min1更新了,min2也要更新
        else if (!p->used && p->weight < min2)
        {
            min2 = p->weight;
            minId2 = i;
        }
        //比min1大,但是比min2小的情況
        i++;
    }
    if (min2 == MAX_INT)
    {
        return false;
    }
    //std::cout << min1<<"|"<<min2<<std::endl;
    (ht + nodeNum)->weight = min1 + min2;
    (ht + nodeNum)->data = ' ';
    (ht + nodeNum)->lchild = minId1;
    (ht + nodeNum)->rchild = minId2;
    (ht + nodeNum)->used = false;
    (ht + minId1)->used = true;
    (ht + minId2)->used = true;
    nodeNum += 1;
    return true;
}

/**
 * 列印二叉樹全部節點
 */
void PrintHalfManTable(HalfManItem table[], int nodeNum)
{
    //std::cout << *table<<"  ---- ";
    for (int i = 0; i < nodeNum; i++)
    {
        std::cout << table[i].data << ": 左孩子:" << table[i].lchild << " 右孩子: "
                  << table[i].rchild << " 權重:" << table[i].weight << std::endl;
    }
    return;
}

/***
 * 建立哈夫曼樹,用一個陣列儲存一堆哈夫曼節點,最後返回根節點
 */
HalfManNode *CreateHalfManTree(HalfManItem *ht, int nodeNum)
{
    HalfManNode *halfTree[nodeNum];
    for (int i = 0; i < nodeNum; i++)
    {
        halfTree[i] = (HalfManNode *)malloc(sizeof(HalfManNode));
        std::cout << "訪問節點" << ht[i].data << " 權重:" << ht[i].weight << " lchild:"
                  << ht[i].lchild << " rchild:" << ht[i].rchild << std::endl;
        halfTree[i]->ch = ht[i].data;
        halfTree[i]->freq = ht[i].weight;
        if (ht[i].lchild != -1)
        {
            halfTree[i]->lchild = halfTree[ht[i].lchild];
            //std::cout << "當前節點:" << halfTree[i]->ch << " "<< "左孩子:" << halfTree[i]->lchild->ch;
        }
        else
        {
            halfTree[i]->lchild = NULL;
        }
        if (ht[i].rchild != -1)
        {
            halfTree[i]->rchild = halfTree[ht[i].rchild];
            //std::cout << "右孩子:" << halfTree[i]->rchild->ch << std::endl;
        }
        else
        {
            halfTree[i]->rchild = NULL;
        }

        //std::cout << "訪問節點" << halfTree[i]->ch << " 權重:" << halfTree[i]->freq << " " << std::endl;
    }

    return halfTree[nodeNum - 1];
}

/***
 * 訪問一下哈夫曼樹
 */
void VisitHalfManTree(HalfManNode *root)
{
    if (root)
    {
        std::cout << "訪問節點" << root->ch << " 權重: " << root->freq << " " << std::endl;
        VisitHalfManTree(root->lchild);
        VisitHalfManTree(root->rchild);
    }
}

/***
 * 已有哈夫曼樹,獲得哈夫曼編碼
 * 
 */
void GetHalfManCode(HalfManNode *tr, char ch)
{
    std::stack<HalfManNode *> s;
    HalfManNode *p = tr, *r = NULL;
    while (!s.empty() || p)
    {
        if (p)
        {
            s.push(p);
            p = p->lchild;
        }
        else
        {
            p = s.top();
            if (p->rchild && p->rchild != r)
            { //p有右孩子,且沒有被訪問過
                p = p->rchild;
                s.push(p);
                p = p->lchild;
            }
            else
            {
                //std::cout << p->freq << " ";
                //std::cout << p->ch<<" "<<ch << " "<<std::endl;
                if (ch == p->ch)
                {
                    std::stack<HalfManNode *> s1;
                    while (!s.empty())
                    {
                        s1.push(s.top());
                        s.pop();
                    }
                    while (!s1.empty())
                    {
                        p = s1.top();
                        s1.pop();
                        if (!s1.empty())
                        {
                            if (s1.top() == p->lchild)
                            {
                                std::cout << "0";
                            }
                            else if (s1.top() == p->rchild)
                            {
                                std::cout << "1";
                            }
                            else
                            {
                                std::cout << "error";
                            }
                        }
                        else
                        {
                            return;
                        }
                    }
                }
                s.pop();
                r = p;
                p = NULL;
            }
        }
    }
}

參考:

其實沒怎麼參考,感覺網上的都寫的好複雜
哈夫曼樹的c實現
哈夫曼樹的各種語言java,python,c++,c,c#的實現
OBST(最優二叉搜尋樹)

題目:

已知某系統在通訊聯絡中只可能出現8種字元,其概率分別為0.05,0.29,0.07,0.08,0.14,0.23,0.03,0.11 試著設計赫夫曼編碼。
假設權w=(5,29,7,8,14,23,3,11),n=8,則m=15,按照上述演算法構造一顆哈夫曼樹
在這裡插入圖片描述
在這裡插入圖片描述

相關文章