【哈夫曼树】哈夫曼树的实现以及哈弗曼编码

基本概念

1、路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。

2、结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。

3、树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。

4、哈夫曼树

给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

构造哈夫曼树

Huffman树构造算法:

1、由给定的n个权值{w1,w2,w3,…,wn}构造n棵只有根节点的扩充二叉树森林F={T1,T2,T3,…,Tn},其中每棵扩充二叉树Ti只有一个带权值wi的根节点,左右孩子均为空。

2、重复以下步骤,直到F中只剩下一棵树为止:
a、在F中选取两棵根节点的权值最小的扩充二叉树,作为左右子树构造一棵新的二叉树。将新二叉树的根节点的权值为其左右子树上根节点的权值之和。
b、在F中删除这两棵二叉树;
c、把新的二叉树加入到F中;

这样最后得到哈夫曼树。

这里写图片描述

这里写图片描述

结论:从上图可以看出根节点的值为构建哈夫曼树所有节点的值和16 = 7+5+3+1

取两个值最小的值,可以用堆来实现。

#pragma once

#include <iostream>
#include <assert.h>
#include <queue>
#include <vector>

template <typename T>
struct HuffmanTreeNode
{
    HuffmanTreeNode(const T &data)
    : _weight(data)
    , _pLeft(NULL)
    , _pRight(NULL)
    , _pParent(NULL)
    {}

    T       _weight;
    HuffmanTreeNode *_pLeft;
    HuffmanTreeNode *_pRight;
    HuffmanTreeNode *_pParent;
};

template <typename T>
struct greater
{
    bool operator()(const T &left, const T &right)
    {
        return left->_weight > right->_weight;
    }
};


template <typename T>
class HuffmanTree
{
public:
    HuffmanTree(const T *weight, int size, const T &invalid)
        : pRoot(NULL)
        , _invalid(invalid)
    {
        assert(NULL != weight && size >= 0);
        _Create(weight, size);
    }

    ~HuffmanTree()
    {
        _Destroy(pRoot);
    }

    void LevelTraverse()
    {
        std::queue<HuffmanTreeNode<T> *> q;
        if (NULL != pRoot)
            q.push(pRoot);

        while (!q.empty())
        {
            HuffmanTreeNode<T> *pCur = q.front();
            q.pop();
            std::cout << pCur->_weight << " ";

            if (NULL != pCur->pLeft)
                q.push(pCur->pLeft);
            if (NULL != pCur->pRight)
                q.push(pCur->pRight);
        }

        std::cout << std::endl;
    }

    HuffmanTreeNode<T> * GetRoot()
    {
        return pRoot;
    }

private:
    void _Destroy(HuffmanTreeNode<T> * &pRoot)
    {
        if (NULL != pRoot)
        {
            _Destroy(pRoot->_pLeft);
            _Destroy(pRoot->_pRight);
            delete pRoot;
            pRoot = NULL;
        }
    }

    void _Create(const T *weight, int size)
    {
        if (0 == size)
            return;
        else if (1 == size)
        {
            if (*weight != _invalid)
                pRoot = new HuffmanTreeNode<T>(*weight);
        }
        else
        {
            std::priority_queue<HuffmanTreeNode<T> *, std::vector<HuffmanTreeNode<T>* >, greater<HuffmanTreeNode<T>*> >
                heap;

            for (int i = 0; i < size; ++i)
            {
                if (weight[i] != _invalid)
                {
                    HuffmanTreeNode<T> *tmp = new HuffmanTreeNode<T>(weight[i]);
                    heap.push(tmp);
                }
            }

            HuffmanTreeNode<T> *pLeft, *pRight;
            while (heap.size() >= 2)
            {
                pLeft = heap.top();
                heap.pop();
                pRight = heap.top();
                heap.pop();

                HuffmanTreeNode<T> *pParent = new HuffmanTreeNode<T>(pLeft->_weight + pRight->_weight);
                pParent->_pLeft = pLeft;
                pParent->_pRight = pRight;
                pLeft->_pParent = pParent;
                pRight->_pParent = pParent;

                heap.push(pParent);
            }
            if (!heap.empty())
                pRoot = heap.top();
        }
    }

private:
    HuffmanTreeNode<T>  *pRoot;
    T                   _invalid; //非法值
};

哈夫曼编码

统计字符出现的个数,然后进行构建哈夫曼树;
后序遍历哈夫曼树,左0右1,对每个叶子节点

这里写图片描述

注意:在建立不等长编码时,必须是任何一个字符的编码不能是另一个字符编码的前缀,这样才能保证译码的唯一性。

任何一个字符的huffman编码都不可能是另一个字符的huffman编码的前缀。

猜你喜欢

转载自blog.csdn.net/wenqiang1208/article/details/77261906