最优二叉树--哈夫曼树和最优前缀编码--哈夫曼编码

1.最优二叉树的定义

最优二叉树又称哈夫曼树,是一种带权路径长最短的树。树的路径长度是从树根到每一个叶子之间的路径长度之和。节点的带树路径长度为从该节点到树根之间的路径长度与该节点权(比如字符在某串中的使用频率)的乘积。

2.构造哈夫曼树

2.1贪心算法

贪心算法(又称贪婪算法)是指,在对 问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部 最优解。
在构建哈夫曼树的过程中,即每次选出两个权值最小的数据,构成左右子树。

2.2构建哈夫曼树的过程

有一堆数据0,1,2,3,4该如何构建哈夫曼树呢?

  1. 按照贪心算法每次找权值最小(即值最小)的两个数,构成孩子结点,并将这两个数据排除出这一堆数据之外

  2. 由于哈弗曼树的数据全在叶子结点,所以把上述权值最小的两个数据构成父结点,然后再将父结点的权值放回上述数据,返回第一步,重复上述过程,直到所有的数据都变成哈夫曼树的叶子结点

最优二叉树--哈夫曼树和最优前缀编码--哈夫曼编码_第1张图片

2.3用代码实现哈夫曼树

1>从构建哈夫曼树的过程我们可以看到每次要选出两个最小的数据,为了提高效率,我们将数据的结点建堆,由于要取权值最小的,所以建小堆,这样能避免因为用数据建堆可能造成重复创建结点
构建堆的代码:

[cpp] view plain copy
print ?
  1. #include   
  2. template<class T>  
  3. struct Greater  
  4. {  
  5.     bool operator()(const T& left, const T& right) const  
  6.     {  
  7.         return left > right;  
  8.     }  
  9. };  
  10.   
  11. template<typename T,class Compare=Greater>  
  12. class Heap  
  13. {  
  14. public:  
  15.     Heap()  
  16.     {}  
  17.     Heap(T* a, size_t n)  
  18.     {  
  19.         //减少多次开辟容量的代价  
  20.         v.reserve(n);  
  21.         for (size_t i = 0; i < n; i++)  
  22.         {  
  23.             v.push_back(a[i]);  
  24.         }  
  25.         //建堆  
  26.         int Size = v.size();  
  27.         for (int i = (Size - 2) / 2; i >= 0; i–)  
  28.         {  
  29.             AdjustDown(i);  
  30.         }  
  31.     }  
  32.   
  33.     //从哪个结点开始调整  
  34.     void AdjustDown(int parent)  
  35.     {  
  36.         Compare com;  
  37.         int Size = v.size();  
  38.         int child = parent * 2 + 1;  
  39.         while (child < Size)  
  40.         {  
  41.             //if (child + 1 < Size&&v[child + 1] > v[child])  
  42.             if (child + 1 < Size&&com(v[child + 1], v[child]))  
  43.             {  
  44.                 child++;  
  45.             }  
  46.             //if (v[child] > v[parent])  
  47.             if (com(v[child],v[parent]))  
  48.             {  
  49.                 swap(v[child], v[parent]);  
  50.                 parent = child;  
  51.                 child = parent * 2 + 1;  
  52.             }  
  53.             else  
  54.             {  
  55.                 break;  
  56.             }  
  57.         }  
  58.     }  
  59.     void AdjustUp(int child)  
  60.     {  
  61.         Compare com;  
  62.         int parent = (child - 1) / 2;  
  63.         while (parent >= 0)  
  64.         {  
  65.             //if (v[child] > v[parent])  
  66.             if (com(v[child], v[parent]))  
  67.             {  
  68.                 swap(v[child], v[parent]);  
  69.                 child = parent;  
  70.                 parent = (child - 1) / 2;  
  71.             }  
  72.             else  
  73.             {  
  74.                 break;  
  75.             }  
  76.         }  
  77.     }  
  78.     void Push(const T& x)  
  79.     {  
  80.         v.push_back(x);  
  81.         AdjustUp(v.size() - 1);  
  82.     }  
  83.     void  Pop()  
  84.     {  
  85.         swap(v[0], v[v.size() - 1]);  
  86.         v.pop_back();  
  87.         AdjustDown(0);  
  88.     }  
  89.     T& Top()  
  90.     {  
  91.         assert(v.size() > 0);  
  92.         return v[0];  
  93.     }  
  94.     size_t Size()  
  95.     {  
  96.         return v.size();  
  97.     }  
  98. protected:  
  99.     vector v;  
  100. };  

2> 每次取出堆顶的最小的数据之后,将堆中的存放结点删除,重复两次,再将权值相加,创建一个新的结点,并将三个结点链接,将它存放到堆中,在堆中每次删除和增加数据时,记得将堆调整为小堆,直到堆中只剩下一个结点时,哈夫曼树就构建成功了

构建哈夫曼树代码:

[cpp] view plain copy
print ?
  1. #include “Heap.hpp”  
  2. template<typename  W>  
  3. struct HuffmanNode  
  4. {  
  5.     HuffmanNode* _lchild;  
  6.     HuffmanNode* _rchild;  
  7.     HuffmanNode* _parent;  
  8.     W _w;  
  9.     HuffmanNode(const W &w)  
  10.         :_lchild(NULL)  
  11.         , _rchild(NULL)  
  12.         , _w(w)  
  13.         , _parent(NULL)  
  14.     {}  
  15.   
  16. };  
  17.   
  18. template <typename W>  
  19. class HuffmanTree  
  20. {  
  21.     typedef HuffmanNode Node;  
  22. public:  
  23.     HuffmanTree()  
  24.         :_root(NULL)  
  25.     {}  
  26.     HuffmanTree(W* a, size_t n,W &invalid)  
  27.     {  
  28.         //堆中存放的是结点,而比较大小的时候我们希望比较的是权值  
  29.         struct NodeCompare  
  30.         {  
  31.             bool operator()(const Node* left, const Node* right) const  
  32.             {  
  33.                 return left->_w < right->_w;  
  34.             }  
  35.         };  
  36.         Heap minHeap;  
  37.   
  38.         for (size_t i = 0; i < n; i++)  
  39.         {  
  40.             //此处需要重载  
  41.             if (a[i] != invalid)  
  42.             {  
  43.                 Node* cur = new Node(a[i]);  
  44.                 minHeap.Push(cur);  
  45.             }  
  46.         }  
  47.         Node* parent = NULL;  
  48.         //根据权值生成小堆,选小堆中最小的两个数据生成父节点  
  49.         while (minHeap.Size()>1)  
  50.         {  
  51.             Node* left = minHeap.Top();  
  52.             minHeap.Pop();  
  53.             Node* right = minHeap.Top();  
  54.             minHeap.Pop();  
  55.             Node* cur = new Node(left->_w + right->_w);  
  56.             minHeap.Push(cur);  
  57.             parent = cur;  
  58.             parent->_lchild = left;  
  59.             parent->_rchild = right;  
  60.             left->_parent = parent;  
  61.             right->_parent = parent;  
  62.         }  
  63.         _root = parent;  
  64.     }  
  65.   
  66.     Node* GetRoot()  
  67.     {  
  68.         return _root;  
  69.     }  
  70. protected:  
  71.     Node* _root;  
  72. };  

3.哈夫曼编码

3.1前缀编码

在一个字符集中,任何一个字符的编码都不是另一个字符编码的前缀(最左子串)。
例:
比如一组编码01,010,001,100,110就不是前缀编码,因为01是010的前缀,如果去掉01和010就是前缀编码了。好比人名张三和张三章就不是前缀编码。

3.2哈夫曼编码(最优前缀编码)

对于一棵具有叶子结点的哈弗曼树,若对树中的每个左支赋予0,右支赋予1(也可以左1右0),则从根到每个叶子结点的通路上,各分支的赋值构成一个二进制串,该二进制串称为哈夫曼编码。
最优二叉树--哈夫曼树和最优前缀编码--哈夫曼编码_第2张图片
从叶节点往根扫描,若为左子树则标记为0,为右子树则标记为1。如上图6的编码即为:00,5的编码100,等等。

3.3如何实现哈夫曼编码

  1. 统计字符出现的个数

  2. 构建哈夫曼树

  3. 生成哈夫曼编码

构建哈夫曼编码的代码:
测试用例:aaaabbbccd

[cpp] view plain copy
print ?
  1. #include “Huffman.hpp”  
  2. #include   
  3.   
  4. //字符串每个结点的信息  
  5. typedef size_t LongType;  
  6.   
  7. struct CharInfo  
  8. {  
  9.     char ch;         //字符  
  10.     LongType count;    // 字符出现个数  
  11.     string code;  
  12.     //比较字符串出现的个数是否相等  
  13.     bool operator!=(const CharInfo &x) const  
  14.     {  
  15.         return this->count != x.count;  
  16.     }  
  17.     CharInfo operator+(const CharInfo &x) const  
  18.     {  
  19.         CharInfo ret;  
  20.         ret.count = this->count + x.count;  
  21.         return ret;  
  22.     }  
  23.     bool operator<(const CharInfo &x) const  
  24.     {  
  25.         return this->count < x.count;  
  26.     }  
  27. };  
  28.   
  29.   
  30. class HuffmanCode  
  31. {  
  32.     typedef HuffmanNode Node;  
  33. public:  
  34.     HuffmanCode()  
  35.     {  
  36.         for (size_t i = 0; i < 256; i++)  
  37.         {  
  38.             _infos[i].ch = i;  
  39.             _infos[i].count = 0;  
  40.         }  
  41.     }  
  42.     void Code(const char* filename)  
  43.     {  
  44.         assert(filename);  
  45.         FILE* fout = fopen(filename, “r”);  
  46.         assert(fout);  
  47.   
  48.         //1.统计字符个数  
  49.         char ch = fgetc(fout);  
  50.         while (ch != EOF)  
  51.         {  
  52.             _infos[ch].count++;  
  53.             ch = fgetc(fout);  
  54.         }  
  55.         //2. 构建哈夫曼树  
  56.         //非法值用字符出现次数为0  
  57.         CharInfo invalid;  
  58.         invalid.count = 0;  
  59.         HuffmanTree tree(_infos, 256, invalid);  
  60.   
  61.         //3生成哈夫曼编码  
  62.         Node* root = tree.GetRoot();  
  63.         GenerateHuffmanCode(root);  
  64.     }  
  65.   
  66.     //从每个叶子结点往根节点走,规定左边标记为1,右边标记为0  
  67.     void GenerateHuffmanCode(Node* root)  
  68.     {  
  69.         if (root == NULL)  
  70.             return;  
  71.         if (root->_rchild == NULL&&root->_lchild == NULL)  
  72.         {  
  73.             Node* cur = root;  
  74.             Node* parent = NULL;  
  75.             string s;  
  76.             while (cur->_parent != NULL)  
  77.             {  
  78.                 parent = cur->_parent;  
  79.                 if (parent->_lchild == cur)  
  80.                     s.push_back(’1’);  
  81.                 else  
  82.                     s.push_back(’0’);  
  83.                 cur = parent;  
  84.             }  
  85.             //stl中提供的算法逆置,只需提供一段迭代器区间即可  
  86.             reverse(s.begin(), s.end());  
  87.             root->_w.code = s;  
  88.             //输出霍夫曼编码  
  89.             cout <<”ch:”<< root->_w.ch <<“ ”<<“count:”<_w.count<< “ ” << root->_w.code << endl;  
  90.         }  
  91.         GenerateHuffmanCode(root->_lchild);  
  92.         GenerateHuffmanCode(root->_rchild);  
  93.     }  
  94. protected:  
  95.     CharInfo _infos[256];  
  96. };  
  97.   
  98. void TestHuffmanCode()  
  99. {  
  100.     HuffmanCode fc;  
  101.     fc.Code(”Input.txt”);  
  102. }  
  103.   
  104. //test.cpp  
  105. #include   
  106. #include   
  107. #include   
  108. using namespace std;  
  109. #include “HuffmanCode.hpp”  
  110. int main()  
  111. {  
  112.     TestHuffmanCode();  
  113.     return 0;  
  114. }  

测试结果:
最优二叉树--哈夫曼树和最优前缀编码--哈夫曼编码_第3张图片
生成的哈弗曼树即为:
最优二叉树--哈夫曼树和最优前缀编码--哈夫曼编码_第4张图片

4.哈夫曼编码的应用

主要应用于信息传输和数据压缩等方面。


附:
BTree的插入和查找算法分析(详细图解过程)
http://blog.csdn.net/skyroben/article/details/73755103

红黑树(RBTree)的插入算法以及如何测试一棵树是否是红黑树?(详细图解说明)
http://blog.csdn.net/skyroben/article/details/72837890

你可能感兴趣的:(数据结构+算法)