【數據結構】3-2哈夫曼樹的實現(數組實現)以及哈夫曼編碼


哈夫曼樹的性質

  1. 哈夫曼樹不唯一(即左孩子右孩子放的順序可以是左大右小也可以是左小右大)
  2. 哈夫曼樹的子樹也是哈夫曼樹
  3. 哈夫曼樹中無度為1的結點
  4. 有n個葉子結點的哈夫曼樹,其總結點數為2*n-1(非常重要!編程實現就要用這條性質)

總體思路

  1. 對兩個最小者的選擇為雙親下標為0的結點
  2. 對選出的兩個最小者,修改雙親下標為新結點的下標
  3. 新結點的左右孩子修改為所選的兩個新結點的下標
  4. 新結點的權值為兩個結點的權值之和

結構體定義

struct element
{
    int weight;        // 權值域
    int lchild, rchild, parent;  // 該結點的左、右、雙親結點在數組中的下標
};

哈夫曼樹類的定義

class HTree
{
private:
    element *node;
    int n, m;//n是元素個數
    stack<int> code;
    int setcode(int weight);//返回的是編碼的長度
public:
    HTree(int _n, int _weight[]);
    void select(int &s1, int &s2);
    void creatHT();
    void print();
    int search(int weight);
    void printHTcode();
};

構造函數的實現

HTree::HTree(int _n,int _weight[])
{
    n = _n;
    m = 2 * n - 1;            //n個元素需要2*n-1空間
    node = new element[m];    //開辟m個大小的element空間
    for (int i = 0; i <n; i++)
    {
        node[i].weight = _weight[i];//為每一個元素賦初始權值
    }
    for (int i = 0; i < m; i++)
    {
        node[i].lchild = node[i].rchild = node[i].parent = 0;//初始化左孩子右孩子和雙親指針,都為0
    }
    creatHT();//構造哈夫曼樹
}

選擇函數,比較結點中雙親為0的較小的兩個(在本文中s1最小)

void HTree::select(int &s1, int &s2)
{
    //對兩個最小者的選擇為雙親下標為0的結點
    for (int i = 0; i < n; i++)
    {
        if (node[i].parent == 0)
        {
            s1=i;    //先找到一個臨時比較元素進行最小值比較
            break;
        }
    }
    for (int i = 0; i <n; i++)        //該循環是找到一個最小的並賦值給s1
    {
        if ((node[s1].weight > node[i].weight) && (node[i].parent == 0))        //找雙親為0的
        {
            s1 = i;
        }
    }
    //同s1的比較方法,先找到一個雙親為0的臨時值(排除s1),然后再進行逐一比較找到第二小的值賦值給s2即可
    for (int i = 0; i <n; i++)
    {
        if ((node[i].parent == 0)&&i!=s1)        //排除s1
        {
            s2 = i;    //先找到一個臨時比較元素進行最小值比較
            break;
        }
    }
    for (int i = 0; i <n; i++)        //找到除s1以外雙親為0中最小的並賦值給s2
    {
        if ((node[s2].weight > node[i].weight) && (node[i].parent == 0) && (i != s1))
        {
            s2 = i;
        }
    }
    
}

創建哈夫曼樹

  1. 循環2*n-1 - n-1次,即進行m-n-1次比較即可
  2. 對兩個最小者的選擇為雙親下標為0的結點(調用選擇函數來實現)
  3. 對選出的兩個最小者,修改雙親下標為新結點的下標
  4. 新結點的左右孩子修改為所選的兩個新結點的下標
  5. 新結點的權值為兩個結點的權值之和
void HTree::creatHT()
{
    for (int i = n; i < m; i++)//執行的是m-n次比較
    {
        int s1, s2;//左小右大
        select(s1, s2);//選擇數組中最小且雙親為0的結點
        node[s1].parent = i;//找到后的s1,s2的雙親應該放在n-1后
        node[s2].parent = i;
        node[i].lchild = s1;    //左小
        node[i].rchild = s2;    //右大
        node[i].weight = node[s1].weight + node[s2].weight;
        n++;
    }
}

哈夫曼編碼:

原理:

  1. 輸入要編碼的權值,調用search函數遍歷查找到在數組中的地址並返回
  2. 從要編碼的位置上溯遍歷,並判斷是左孩子還是右孩子,左孩子為0右孩子為1(也可以反過來)
  3. 編碼是從上到下,而我們在遍歷的時候是從下到上,因此可以使用棧或者鏈表的頭插法進行存儲操作(我在這里使用的是棧,有點不好,輸出棧就被清空了,可能會影響時間效率)
  4. 判斷如果該結點的parent為空(即為0)停止循環即可

setcode(私有函數)

int HTree::setcode(int weight)
{
    int location, parent, k;//location來定位權值所指向的位置,parent即Location的雙親,k是移動指針
    int cont = 0;
    location = search(weight);
    k = location;
    parent = node[location].parent;
    while (node[k].parent!= 0&&k!=-1)
    {
        if (node[parent].lchild == k)
        {
            code.push(0);
        }
        else
        {
            code.push(1);
        }
        k = node[k].parent;
        parent = node[k].parent;
        cont++;
    }
    if (k == -1)
    {
        cout << "未找到!錯誤!" << endl;
    }
    return cont;
}

search函數

int HTree::search(int weight)
{
    int result = -1;
    for (int i = 0; i < m; i++)
    {
        if (node[i].weight == weight)
        {
            result = i;
        }
    }
    return result;
}

完整代碼:

/*
1.對兩個最小者的選擇為雙親下標為0的結點
2.對選出的兩個最小者,修改雙親下標為新結點的下標
3.新結點的左右孩子修改為所選的兩個新結點的下標
4.新結點的權值為兩個結點的權值之和
*/
#include<iostream>
#include<iomanip>
#include<stack>
using namespace std;
struct element
{
    int weight;        // 權值域
    int lchild, rchild, parent;  // 該結點的左、右、雙親結點在數組中的下標
};
class HTree
{
private:
    element *node;
    int n, m;//n是元素個數
    stack<int> code;
    int setcode(int weight);//返回的是編碼的長度哼哼
public:
    HTree(int _n, int _weight[]);
    void select(int &s1, int &s2);
    void creatHT();
    void print();
    int search(int weight);
    void printHTcode();
};
int HTree::search(int weight)
{
    int result = -1;
    for (int i = 0; i < m; i++)
    {
        if (node[i].weight == weight)
        {
            result = i;
        }
    }
    return result;
}
int HTree::setcode(int weight)
{
    int location, parent, k;//location來定位權值所指向的位置,parent即Location的雙親,k是移動指針
    int cont = 0;
    location = search(weight);
    k = location;
    parent = node[location].parent;
    while (node[k].parent!= 0&&k!=-1)
    {
        if (node[parent].lchild == k)
        {
            code.push(0);
        }
        else
        {
            code.push(1);
        }
        k = node[k].parent;
        parent = node[k].parent;
        cont++;
    }
    if (k == -1)
    {
        cout << "未找到!錯誤!" << endl;
    }
    return cont;
}
void HTree::printHTcode()
{
    cout << "請輸入要查詢的編碼的權值並繼續:" << endl;
    int weight, size;
    cin >> weight;
    size=setcode(weight);
    cout << "權值 " << weight << "編碼長度: " << size << endl;
    cout << weight << " 編碼結果為: ";
    for (int i = 0; i < size; i++)
    {
        cout << code.top() << " ";
        code.pop();
    }
    cout << endl;
}
HTree::HTree(int _n,int _weight[])
{
    n = _n;
    m = 2 * n - 1;            //n個元素需要2*n-1空間
    node = new element[m];    //開辟m個大小的element空間
    for (int i = 0; i <n; i++)
    {
        node[i].weight = _weight[i];//為每一個元素賦初始權值
    }
    for (int i = 0; i < m; i++)
    {
        node[i].lchild = node[i].rchild = node[i].parent = 0;//初始化左孩子右孩子和雙親指針,都為0
    }
    creatHT();//構造哈夫曼樹
}
void HTree::select(int &s1, int &s2)
{
    //對兩個最小者的選擇為雙親下標為0的結點
    for (int i = 0; i < n; i++)
    {
        if (node[i].parent == 0)
        {
            s1=i;    //先找到一個臨時比較元素進行最小值比較
            break;
        }
    }
    for (int i = 0; i <n; i++)        //該循環是找到一個最小的並賦值給s1
    {
        if ((node[s1].weight > node[i].weight) && (node[i].parent == 0))        //找雙親為0的
        {
            s1 = i;
        }
    }
    //同s1的比較方法,先找到一個雙親為0的臨時值(排除s1),然后再進行逐一比較找到第二小的值賦值給s2即可
    for (int i = 0; i <n; i++)
    {
        if ((node[i].parent == 0)&&i!=s1)        //排除s1
        {
            s2 = i;    //先找到一個臨時比較元素進行最小值比較
            break;
        }
    }
    for (int i = 0; i <n; i++)        //找到除s1以外雙親為0中最小的並賦值給s2
    {
        if ((node[s2].weight > node[i].weight) && (node[i].parent == 0) && (i != s1))
        {
            s2 = i;
        }
    }
    
}
void HTree::creatHT()
{
    for (int i = n; i < m; i++)//執行的是m-n次比較
    {
        int s1, s2;//左小右大
        select(s1, s2);//選擇數組中最小且雙親為0的結點
        node[s1].parent = i;//找到后的s1,s2的雙親應該放在n-1后
        node[s2].parent = i;
        node[i].lchild = s1;    //左小
        node[i].rchild = s2;    //右大
        node[i].weight = node[s1].weight + node[s2].weight;
        n++;
    }
}
void HTree::print()
{
    cout << "index weight parent lChild rChild" << endl;
    cout << left;    // 左對齊輸出 
    for (int i = 0; i < m; ++i)
    {
        cout << setw(5) << i << " ";
        cout << setw(6) << node[i].weight << " ";
        cout << setw(6) << node[i].parent << " ";
        cout << setw(6) << node[i].lchild << " ";
        cout << setw(6) << node[i].rchild << endl;
    }
    cout << "哈夫曼樹打印完畢!" << endl;
}
int main()
{
    cout << "請輸入要構造哈夫曼樹元素的個數: ";
    int n,*weight;
    cin >> n;
    weight = new int[n + 1];
    cout << "請輸入這" << n << "個元素的權值:" << endl;
    for (int i = 0; i < n; i++)
    {
        cin >> weight[i];
    }
    HTree test(n, weight);
    test.print();
    test.printHTcode();
    system("pause");
    return 0;
}
點我查看完整代碼

測試數據

4

5 2 4 7

4

 

結果

請輸入要構造哈夫曼樹元素的個數: 4
請輸入這4個元素的權值:
5 2 4 7
index weight parent lChild rChild
0     5      5      0      0
1     2      4      0      0
2     4      4      0      0
3     7      6      0      0
4     6      5      1      2
5     11     6      0      4
6     18     0      3      5
哈夫曼樹打印完畢!
請輸入要查詢的編碼的權值並繼續:
4
權值 4編碼長度: 3
4 編碼結果為: 1 1 1
請按任意鍵繼續. . .

特別說明

這個代碼絕對不是最優解:)

但是原理是對的,嘛畢竟自己能力有限~

只要原理懂了可以自己優化優化的~~

各位見笑了,還請多多包涵。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM