2

一天一个算法,边回想算法细节,边捡回C++,试验性程序,留作记念。

哈夫曼树概念

哈夫曼(Huffman)树又称最优二叉树。它是n个带权叶子结点构成的二叉树中,带权路径长度WPL最小的二叉树。因为构造这种树的算法是最早由哈夫曼于1952年提出的,所以被称之为哈夫曼树。

二叉树的性质

二叉树中有五点性质非常重要,需要记住。
性质1:在二叉树的第 i 层上至多有2^(i-1)个结点
性质2:深度为k的二叉树至多有2^k-1个结点
性质3:对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1
性质4:具有n个结点的完全二叉树的深度为[log(n)]+1([x]表示不大于x的最大整数)
性质5:如果对一棵有n个结点的完全二叉树(其深度为[log(n)]+1)的结点按层序编号(从第1层到第[log(n)]+1层,每层从左到右),对任一结点i(1<=i<=n)有:
(1).如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]
(2).如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i
(3).如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1

基本术语

结点的权
“权”就相当于“重要度”,我们形象的用一个具体的数字来表示,然后通过数字的大小来决定谁重要,谁不重要。
路径
树中从“一个结点”到“另一个结点”之间的分支。
路径长度
一个路径上的分支数量。
树的路径长度
从树的根节点到每个节点的路径长度之和。
节点的带权路径路径长度
其实也就是该节点到根结点的路径长度乘以该节点的权。
树的带权路径长度
树中各个叶节点的路径长度*该叶节点的权的和,常用WPL(Weight Path Length)表示。

构建方法

图片描述
第一步: 我们将所有的节点都作为独根结点。
第二步: 我们将最小的C和A组建为一个新的二叉树,权值为左右结点之和。
第三步: 将上一步组建的新节点加入到剩下的节点中,排除上一步组建过的左右子树,我们选中B组建新的二叉树,然后取权值。
第四步: 同上。

哈夫曼树的结构

    typedef struct HuffmanNode
    {
        int weight;
        int lChild, rChild, parent;
    } *pNode;

哈夫曼树的各种操作

构建哈夫曼树用到的获取最小两个权值下标函数

void HuffmanTree::SelectNode(int bg, int * min1, int * min2){
    int index = bg - _n;
    //每次进入实际上都向后移了一位
    for (int i = 0; i < bg; i++){
        if (hf[index] == huffman[i].weight){
            *min1 = i;
            break;
        }
    }
    for (int i = 0; i < bg; i++){
        //注意有可能合并后的值会与权值重复
        if (*min1 != i && hf[index + 1] == huffman[i].weight){
            *min2 = i;
            break;
        }
    }
    int newNode = hf[index] + hf[index + 1];
    int i = index + 2;
    //将新产生的权值插入到排序数组合适的位置
    for (; i < _n; i++){
        if (newNode > hf[i])
            hf[i - 1] = hf[i];
        else
            break;
    }
    hf[i - 1] = newNode;
}

哈夫曼树的构建

HuffmanTree::HuffmanTree(int * keys, int N)
{
    using namespace std;
    this->_n = N;
    //总的节点数 
    this->_m = 2 * N - 1;
    huffman = new HuffmanNode[_m];
    hf = new int[N];
    for (int i = 0; i < _m; i++){   //初始化
        if (i < N){     //前N个为权重叶子节点
            huffman[i].weight = keys[i];
            if (i == 0)
                hf[0] = keys[0];
            else{
                int j = i - 1;
                //从小到大插入到排序数组中
                for (; j >= 0; j--){
                    if (keys[i] < hf[j]){
                        hf[j + 1] = hf[j];
                    }
                    else
                        break;
                }
                hf[j + 1] = keys[i];
            }
        }
        else        //后面的为辅助结点,非叶子节点
            huffman[i].weight = -1;
        huffman[i].lChild = -1;
        huffman[i].rChild = -1;
        huffman[i].parent = -1;
    }
    //创建,从第一个辅助节点开始
    for (int i = _n; i < _m; i++){
        int min1;
        int min2;
        SelectNode(i, &min1, &min2);
        huffman[min1].parent = i;
        huffman[min2].parent = i;
        huffman[i].lChild = min1;
        huffman[i].rChild = min2;
        huffman[i].weight = huffman[min1].weight + huffman[min2].weight;
    }
}

生成哈夫曼编码模版数组

vector<string> HuffmanTree::HuffmanCoding(int len){
    int cur = 0;
    int parent = 0;

    vector<string> hfcode = vector<string>(len);

    for (int i = 0; i < len; i++){
        string tmp = "";
        cur = i;
        parent = huffman[cur].parent;
        while (parent>=0)
        {
            if (cur == huffman[parent].lChild){
                tmp += "0";
            }
            else
            {
                tmp += "1";
            }
            cur = parent;
            parent = huffman[parent].parent;
        }
        reverse(tmp.begin(), tmp.end());
        hfcode[i] = tmp;
    }
    return hfcode;
}

哈夫曼编码

string HuffmanTree::Encode(vector<string> hfCode, vector<char> alphabet, string str){
    string encodeStr = "";
    for (int i = 0; i<str.length(); i++) {
        for (int j = 0; j < alphabet.size(); j++)
            if (str.at(i) == alphabet[j])
                encodeStr += hfCode[j];
    }
    return encodeStr;
}

哈夫曼解码

string HuffmanTree::Decode(int len, vector<char> alphabet, string str){
    //最后一个点实际上为哈夫曼树的根节点
    int m = 2 * len - 1;
    string decodeStr = "";
    for (int i = 0; i<str.length(); ) {
        int j;
        //从根开始解码,一直找到叶子节点为止
        for (j = m - 1; (huffman[j].lChild >= 0 || huffman[j].rChild >= 0);){
            if (str.at(i) == '0')
                j = huffman[j].lChild;
            else
                j = huffman[j].rChild;
            i++;
        }
        decodeStr += alphabet[j];
    }
    return decodeStr;
}

类定义(头文件)

#pragma once

#include <string>
#include <vector>

using namespace std;

class HuffmanTree
{
private:
    typedef struct HuffmanNode
    {
        int weight;
        int lChild, rChild, parent;
    } *pNode;
    pNode huffman;
    int * hf;   //用来排序
    int _n,_m;
    void SelectNode(int i,int *min1,int *min2);
public:
    HuffmanTree(int * keys, int N);
    vector<string> HuffmanCoding(int len);
    string Encode(vector<string> hfCode, vector<char> alphabet, string str);
    string Decode(int len, vector<char> alphabet, string str);
    ~HuffmanTree();
};

用法示例

#include <conio.h>
#include "HuffmanTree.h"

int main()
{
    int arrs[] = { 5, 7, 2, 13 };
    vector<char> alphabet = { 'A', 'B', 'C', 'D' };
    int len = sizeof(arrs) / sizeof(arrs[0]);
    HuffmanTree hf = HuffmanTree(arrs, len);
    string testCode = "DBDBDABDCDADBDADBDADACDBDBD";
    string encodeStr = hf.Encode(hf.HuffmanCoding(len), alphabet, testCode);
    printf("%s\n", encodeStr.c_str());
    string decodeStr = hf.Decode(len, alphabet, encodeStr);
    printf("%s\n", decodeStr.c_str());
    getch();
    return 0;
}

zhoutk
2.6k 声望1.2k 粉丝

自由程序员,技术路线c,delphi,c++,c#,java,php,node.js,python,golang,typescript;超喜欢react.js的设计思路,全栈开发成为我的终极目标。开发机器macbook pro,或装ubuntu、fedora的机器,编程用vim...