哈夫曼樹


一、定義

哈夫曼樹,又稱最優樹,是一類帶權路徑長度最短的樹。首先有幾個概念需要清楚:

1、路徑和路徑長度

從樹中一個結點到另一個結點之間的分支構成兩個結點的路徑,路徑上的分支數目叫做路徑長度。樹的路徑長度是從樹根到每一個結點的路徑長度之和。

2、帶權路徑長度

結點的帶權路徑長度為從該結點到樹根之間的路徑長度與結點上權的乘積。樹的帶權路徑長度為樹中所有葉子結點的帶權路徑長度之和,通常記作WPL。

若有n個權值為w1,w2,...,wn的結點構成一棵有n個葉子結點的二叉樹,則樹的帶權路徑最小的二叉樹叫做哈夫曼樹或最優二叉樹。

 

在上圖中,3棵二叉樹都有4個葉子結點a、b、c、d,分別帶權7、5、2、4,則它們的帶權路徑長度為

(a)WPL = 7*2 + 5*2 + 2*2 + 4*2 = 36

(b)WPL = 4*2 + 7*3 + 5*3 + 2*1 = 46

(c)WPL = 7*1 + 5*2 + 2*3 + 4*3 = 35

其中(c)的WPL最小,可以驗證,(c)恰為哈夫曼樹。

二、哈夫曼樹操作的C++實現

1、結點表示

class Node
{
public:
    int val;        //結點的值
    Node* left;     //結點左孩子
    Node* right;    //結點右孩子
    Node* parent;   //結點的父結點

    Node(int val) :val(val), left(NULL), right(NULL), parent(NULL) {}
    Node(int val, Node* left, Node* right, Node* parent) :val(val), left(left), right(right), parent(parent) {}
};

2、操作

class HuffmanTree
{
private:
    priority_queue<Node*, vector<Node*>, Compare> nodes;    //優先隊列(小根堆)
    Node* root;
public:
    HuffmanTree();
    ~HuffmanTree();

    /*創建哈夫曼樹,返回根結點*/
    Node* create();

    /*先序遍歷*/
    void preOrder(Node* root);

    /*中序遍歷*/
    void inOrder(Node* root);

    /*后序遍歷*/
    void postOrder(Node* root);

    /*銷毀哈夫曼樹*/
    void destroyTree(Node* root);
};

3、創建哈夫曼樹

假設有n個結點,n個結點的權值分別為w1,w2,...,wn,構成的二叉樹的集合為F={T1,T2,...,Tn},則可構造一棵含有n個葉子結點的哈夫曼樹。步驟如下:

(1)從F中選取兩棵根結點權值最小的樹作為左右子樹構造一棵新的二叉樹,其新的二叉樹的權值為其左右子樹根結點權值之和;

(2)從F中刪除上一步選取的兩棵二叉樹,將新構造的樹放到F中;

(3)重復(1)(2),直到F只含一棵樹為止。

從上面的步驟可以看出,每次選取n個結點中權值最小的兩個結點,可以使用小根堆來實現,這里為了簡單起見,使用STL中的priority_queue來實現選取權值最小結點的操作。為了保持隊列中結點一直按從小到大排序,需要自定義比較函數。由於優先隊列中的數據類型為指針類型,所以不能通過重載運算符<來實現,而是要寫一個比較類,在構造優先隊列時,傳入其第3個參數(完整代碼中的類Compare)。

4、遍歷與刪除哈夫曼樹

遍歷與刪除哈夫曼樹與遍歷和刪除普通二叉樹沒有大的區別,這里只寫出代碼。

5、哈夫曼樹完整代碼

  1 #include <iostream>
  2 #include <queue>
  3 using namespace std;
  4 
  5 class Node
  6 {
  7 public:
  8     int val;        //結點的值
  9     Node* left;     //結點左孩子
 10     Node* right;    //結點右孩子
 11     Node* parent;   //結點的父結點
 12 
 13     Node(int val) :val(val), left(NULL), right(NULL), parent(NULL) {}
 14     Node(int val, Node* left, Node* right, Node* parent) :val(val), left(left), right(right), parent(parent) {}
 15 };
 16 
 17 class Compare    //比較類,用於構造Node*類型的priority_queue
 18 {
 19 public:
 20     bool operator() (Node* a, Node* b)
 21     {
 22         return a->val > b->val;        //結點的值越小越靠前
 23     }
 24 };
 25 
 26 class HuffmanTree
 27 {
 28 private:
 29     priority_queue<Node*, vector<Node*>, Compare> nodes;    //優先隊列(小根堆)
 30     Node* root;
 31 public:
 32     HuffmanTree();
 33     ~HuffmanTree();
 34 
 35     /*創建哈夫曼樹,返回根結點*/
 36     Node* create();
 37 
 38     /*先序遍歷*/
 39     void preOrder(Node* root);
 40 
 41     /*中序遍歷*/
 42     void inOrder(Node* root);
 43 
 44     /*后序遍歷*/
 45     void postOrder(Node* root);
 46 
 47     /*銷毀哈夫曼樹*/
 48     void destroyTree(Node* root);
 49 };
 50 
 51 HuffmanTree::HuffmanTree()
 52 {
 53     while (!nodes.empty()) nodes.pop();
 54     int a[] = { 4,3,5,1,2 };
 55     int len = sizeof(a) / sizeof(a[0]);
 56     for (int i = 0;i < len; i++)        //使用數組的值初始化結點
 57         nodes.push(new Node(a[i]));
 58 
 59     root = NULL;
 60 }
 61 
 62 HuffmanTree::~HuffmanTree()
 63 {
 64     destroyTree(root);
 65 }
 66 
 67 Node* HuffmanTree::create()
 68 {
 69     while (nodes.size() > 1)
 70     {
 71         Node* node1 = nodes.top();    //選取結點值最小的兩個結點
 72         nodes.pop();
 73         Node* node2 = nodes.top();
 74         nodes.pop();
 75         
 76         Node* root = new Node(node1->val + node2->val);    //新結點的值為兩個結點值之和
 77         root->left = node1;        //將新結點設為兩結點的根結點
 78         root->right = node2;
 79         node1->parent = root;
 80         node2->parent = root;
 81         nodes.push(root);
 82     }
 83 
 84     root = nodes.top();        //隊列中最后一個結點即為哈夫曼樹的根結點
 85     nodes.pop();
 86 
 87     return root;
 88 }
 89 
 90 /*先序遍歷*/
 91 void HuffmanTree::preOrder(Node* root)
 92 {
 93     if (root == NULL)
 94         return;
 95     cout << root->val << " ";
 96     preOrder(root->left);
 97     preOrder(root->right);
 98 }
 99 
100 /*中序遍歷*/
101 void HuffmanTree::inOrder(Node* root)
102 {
103     if (root == NULL)
104         return;
105     inOrder(root->left);
106     cout << root->val << " ";
107     inOrder(root->right);
108 }
109 
110 /*后序遍歷*/
111 void HuffmanTree::postOrder(Node* root)
112 {
113     if (root == NULL)
114         return;
115     postOrder(root->left);
116     postOrder(root->right);
117     cout << root->val << " ";
118 }
119 
120 /*銷毀哈夫曼樹*/
121 void HuffmanTree::destroyTree(Node* root)
122 {
123     if (root == NULL)
124         return;
125     destroyTree(root->left);
126     destroyTree(root->right);
127     delete root;
128 }
129 
130 int main()
131 {
132     /*創建哈夫曼樹*/
133     HuffmanTree* huffmanTree = new HuffmanTree();
134     Node* root = huffmanTree->create();
135 
136     cout << "先序遍歷:";
137     huffmanTree->preOrder(root);
138     cout << endl;
139 
140     cout << "中序遍歷:";
141     huffmanTree->inOrder(root);
142     cout << endl;
143 
144     cout << "后序遍歷:";
145     huffmanTree->postOrder(root);
146     cout << endl;
147 
148     return 0;
149 }

結果:

先序遍歷:15 6 3 3 1 2 9 4 5 
中序遍歷:3 6 1 3 2 15 4 9 5 
后序遍歷:3 1 2 3 6 4 5 9 15 

 三、哈夫曼編碼

我們約定左分支表示字符'0',右分支表示字符'1',在哈夫曼樹中從根結點開始,到葉子結點的路徑上分支字符組成的字符串為該葉子結點的哈夫曼編碼。上面代碼所創建的哈夫曼樹如下所示:

可以看出3被編碼為00,1為010,2為011,4為10,5為11。在這些編碼中,任何一個字符的編碼均不是另一個字符編碼的前綴。

實現代碼:

/*哈夫曼編碼*/
void HuffmanTree::encode(Node* root, string code)
{
    if (root->left == NULL&&root->right == NULL)    //葉子結點
        cout << root->val << " 被編碼為 " << code << endl;

    if (root->left != NULL)
    {
        code += "0";                //左子樹,編碼code添加'0'
        encode(root->left, code);
        code.erase(code.end()-1);   //刪除上一步添加的'0'
    }
    if (root->right != NULL)
    {
        code += "1";                //右子樹,編碼code添加'1'
        encode(root->right, code);
        code.erase(code.end()-1);   //刪除上一步添加的'1'

    }
}

完整代碼:

#include <iostream>
#include <queue>
#include <string>
#include <iomanip>
using namespace std;

class Node
{
public:
    int val;        //結點的值
    Node* left;     //結點左孩子
    Node* right;    //結點右孩子
    Node* parent;   //結點的父結點

    Node(int val) :val(val), left(NULL), right(NULL), parent(NULL) {}
    Node(int val, Node* left, Node* right, Node* parent) :val(val), left(left), right(right), parent(parent) {}
};

class Compare    //比較類,用於構造Node*類型的priority_queue
{
public:
    bool operator() (Node* a, Node* b)
    {
        return a->val > b->val;        //結點的值越小越靠前
    }
};

class HuffmanTree
{
private:
    priority_queue<Node*, vector<Node*>, Compare> nodes;    //優先隊列(小根堆)
    Node* root;
public:
    HuffmanTree();
    ~HuffmanTree();

    /*創建哈夫曼樹,返回根結點*/
    Node* create();

    /*哈夫曼編碼*/
    void encode(Node* node, string code);

    /*先序遍歷*/
    void preOrder(Node* root);

    /*中序遍歷*/
    void inOrder(Node* root);

    /*后序遍歷*/
    void postOrder(Node* root);

    /*銷毀哈夫曼樹*/
    void destroyTree(Node* root);
};

HuffmanTree::HuffmanTree()
{
    while (!nodes.empty()) nodes.pop();
    int a[] = { 4,3,5,1,2 };
    int len = sizeof(a) / sizeof(a[0]);
    for (int i = 0;i < len; i++)        //使用數組的值初始化結點
        nodes.push(new Node(a[i]));

    root = NULL;
}

HuffmanTree::~HuffmanTree()
{
    destroyTree(root);
}

Node* HuffmanTree::create()
{
    while (nodes.size() > 1)
    {
        Node* node1 = nodes.top();    //選取結點值最小的兩個結點
        nodes.pop();
        Node* node2 = nodes.top();
        nodes.pop();
        
        Node* root = new Node(node1->val + node2->val);    //新結點的值為兩個結點值之和
        root->left = node1;        //將新結點設為兩結點的根結點
        root->right = node2;
        node1->parent = root;
        node2->parent = root;
        nodes.push(root);
    }

    root = nodes.top();        //隊列中最后一個結點即為哈夫曼樹的根結點
    nodes.pop();

    return root;
}

/*哈夫曼編碼*/
void HuffmanTree::encode(Node* root, string code)
{
    if (root->left == NULL&&root->right == NULL)    //葉子結點
        cout << root->val << " 被編碼為 " << code << endl;

    if (root->left != NULL)
    {
        code += "0";                //左子樹,編碼code添加'0'
        encode(root->left, code);
        code.erase(code.end()-1);   //刪除上一步添加的'0'
    }
    if (root->right != NULL)
    {
        code += "1";                //右子樹,編碼code添加'1'
        encode(root->right, code);
        code.erase(code.end()-1);   //刪除上一步添加的'1'

    }
}

/*先序遍歷*/
void HuffmanTree::preOrder(Node* root)
{
    if (root == NULL)
        return;
    cout << root->val << " ";
    preOrder(root->left);
    preOrder(root->right);
}

/*中序遍歷*/
void HuffmanTree::inOrder(Node* root)
{
    if (root == NULL)
        return;
    inOrder(root->left);
    cout << root->val << " ";
    inOrder(root->right);
}

/*后序遍歷*/
void HuffmanTree::postOrder(Node* root)
{
    if (root == NULL)
        return;
    postOrder(root->left);
    postOrder(root->right);
    cout << root->val << " ";
}


/*銷毀哈夫曼樹*/
void HuffmanTree::destroyTree(Node* root)
{
    if (root == NULL)
        return;
    destroyTree(root->left);
    destroyTree(root->right);
    delete root;
}

int main()
{
    /*創建哈夫曼樹*/
    HuffmanTree* huffmanTree = new HuffmanTree();
    Node* root = huffmanTree->create();

    cout << "先序遍歷:";
    huffmanTree->preOrder(root);
    cout << endl;

    cout << "中序遍歷:";
    huffmanTree->inOrder(root);
    cout << endl;

    cout << "后序遍歷:";
    huffmanTree->postOrder(root);
    cout << endl;

    cout << "哈夫曼編碼:" << endl;
    huffmanTree->encode(root, "");

    return 0;
}

結果:

先序遍歷:15 6 3 3 1 2 9 4 5 
中序遍歷:3 6 1 3 2 15 4 9 5 
后序遍歷:3 1 2 3 6 4 5 9 15 
哈夫曼編碼:
3 被編碼為 00
1 被編碼為 010
2 被編碼為 011
4 被編碼為 10
5 被編碼為 11

 


免責聲明!

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



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