C++ STL 优先队列 及其 霍夫曼编码应用示例

1 篇文章 0 订阅
1 篇文章 0 订阅

优先队列(priority queue)

优先队列是一种比较常用的结构,普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。

当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高进先出 (largest-in,first-out)的行为特征。

c++  priority_queue

STL  priority_queue是拥有权值观念的queue,它允许在底端添加元素、在顶端去除元素、删除元素。优先级队列内部的元素并不是按照添加的顺序排

列,而是自动依照元素的权值排列, 权值最高者排在最前面。缺省情况下,优先级队列利用一个大顶堆完成。关于二叉堆概念,参见

其实 priority_queue 调用 STL里面的 make_heap(), pop_heap(), push_heap() 算法实现,算是堆的应用的扩展形式。关于STL堆相关算法 参见

下面对这几个函数做一个简单的介绍:

make_heap():根据随机迭代器first和last指定的范围[first,last)创建一个大顶堆,有两个make_heap版本函数,第一个函数使用‘<’进行比较,第二个模板

函数使用comp仿函数进行比较。make_heap()函数原型如下。

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. default (1)   
  2. template <class RandomAccessIterator>  
  3.   void make_heap (RandomAccessIterator first, RandomAccessIterator last);  
  4.   
  5. custom (2)    
  6. template <class RandomAccessIterator, class Compare>  
  7.   void make_heap (RandomAccessIterator first, RandomAccessIterator last,  
  8.                   Compare comp );  

push_heap():新添加一个元素在末尾,然后利用 siftup()函数,上溯函数来重新调整堆序。该算法必须是在一个已经满足堆序的条件下,添加元素

和make_heap()一样有两个版本的模版函数。需要在push_heap()之前调用push_back()之类的函数在容器末尾添加一个元素。

pop_heap():把堆顶元素和数组或者是vector的末尾交换,然后end迭代器减1,执行siftdown()下溯函数来重新调整堆序。注意算法执行完毕后,最大的元素

并没有被取走,而是放于底层容器的末尾。如果要取走,则可以使用底部容器(vector)提供的pop_back()函数。和push_heap一样有两个版本的函数。

sort_heap():pop_heap可以获得堆顶的元素,我们可以迭代执行取堆顶元素,直到堆为空,这样就可以得到一个有序序列。此函数也有两个版本的模版函数。


下面先写一个用 STL 里面堆算法实现的与真正的STL里面的 priority_queue  用法相似的 priority_queue, 加深对 priority_queue 的理解。

代码如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include  <iostream>  
  2. #include <algorithm>  
  3. #include <vector>  
  4.   
  5. using namespace std;  
  6.   
  7. // 自定义优先级队列  
  8. template <class ElemType>  
  9. class priority_queue_  
  10. {  
  11. private:  
  12.     vector<ElemType> data_vec;//存放元素的容器  
  13. public:  
  14.     // 默认构造函数 **  
  15.     priority_queue_()  
  16.     {  
  17.         data_vec = vector<ElemType>();  
  18.     }  
  19.     // 带参数的构造函数  
  20.     priority_queue_(ElemType *data, const int n);  
  21.   
  22.     //判断优先队列是否为空  
  23.     bool empty()  
  24.     {  
  25.         return data_vec.empty();  
  26.     }  
  27.     //返回优先级队列大小  
  28.     size_t size()  
  29.     {  
  30.         return data_vec.size();  
  31.     }  
  32.     //获得优先队列头元素  
  33.     ElemType top()  
  34.     {  
  35.         return data_vec.front();  
  36.     }  
  37.     //添加元素  
  38.     void push(const ElemType &t)  
  39.     {  
  40.         data_vec.push_back(t);  
  41.         push_heap(data_vec.begin(), data_vec.end());  
  42.     }  
  43.     //弹出队首元素  
  44.     void pop()  
  45.     {  
  46.         pop_heap(data_vec.begin(), data_vec.end());//堆顶的元素和堆end-1(即最后一个元素)位置的元素交换,然后调整堆.  
  47.         data_vec.pop_back();//把end-1位置元素删除  
  48.     }  
  49. };  
  50. template <class ElemType>  
  51. priority_queue_<ElemType>::priority_queue_(ElemType *data, const int n)  
  52. {  
  53.      //拷贝数组data中的元素到vector中,并建立优先队列  
  54.      for(int i=0; i<n; ++i)  
  55.         push(data[i]);  
  56. }  
  57.   
  58. // 输出优先队列  
  59. template <class ElemType>  
  60. void print(priority_queue_<ElemType> &pq)  
  61. {  
  62.     while(!pq.empty())//也可以用 size()  
  63.     {  
  64.         cout<<pq.top()<<" ";  
  65.         pq.pop();  
  66.     }  
  67.     cout<<"\n";  
  68. }  
  69.   
  70. int main()  
  71. {  
  72.     int i;  
  73.     int arr[] = {1,3,-3,-7,9};  
  74.     int len = sizeof(arr)/sizeof(arr[0]);  
  75.     // test 1  
  76.     priority_queue_<int> pq1;//默认构造函数  
  77.     for(i=0; i<len; ++i)  
  78.         pq1.push(arr[i]);  
  79.     print(pq1);  
  80.     // test 2  
  81.     priority_queue_<int> pq2(arr, len);  
  82.     print(pq2);  
  83.     return 0;  
  84. }  
运行结果如下:

                        

priority_queue的模板声明带有三个参数,priority_queue<Type, Container, Functional> Type 为数据类型, Container 为保存数据的容器,Functional

为元素比较方式。其中 Container 必须是用数组实现的容器,比如 vector, deque 但不能用 list。STL里面容器默认用的是 vector. 比较方式默认用 operator< ,

所以如果你把后面俩个参数 缺省的话,优先队列就是大顶堆。

如果要用到小顶堆,则一般要把模板的三个参数都带进去。STL里面定义了一个仿函数 greater<Type>,对于基本类型可以用这个仿函数声明小顶堆。

注意

自定义类型重载 operator< 后,声明对象时就可以只带一个模板参数。此时不能像基本类型这样声明priority_queue<Node, vector<Node>, greater<Node> >;

原因是 greater<Node> 没有定义,如果想用这种方法定义则可以按如下方式(即自定义 仿函数,推荐这种方式):

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 自定义类型  
  2. typedef struct Node  
  3. {  
  4.   int key;  
  5.   int data;  
  6.    ....  
  7. }Node;  
  8. //cmp的结构用于实现自定义的比较方法  
  9. struct cmp  
  10. {  
  11.     bool operator()(Node a,Node b)//如果返回true,这两个元素就需要交换位置  
  12.     {  
  13.         if(a.key==b.key)  
  14.             return a.data>b.data;  
  15.         return a.key>b.key;  
  16.     }  
  17. };  
  18.  priority_queue<Node, vector<Node>, cmp> pq;  

优先队列实现霍夫曼编码

霍夫曼编码, 浩子大叔的blog也有讨论。

霍夫曼编码 一般采用前缀编码 -- -- 对字符集进行编码时,要求字符集中任一字符的编码都不是其它字符的编码的前缀,这种编码称为前缀(编)码。

算法思想:

构造哈夫曼树非常简单,将所有的节点放到一个队列中,用一个节点替换两个频率最低的节点,新节点的频率就是这两个节点的频率之和。这样,

新节点就是两个被替换节点的父节点了。如此循环,直到队列中只剩一个节点(树根)。 其实这就是一个贪心策略,属于贪心算法的典型应用。

具体关于证明可以参考<算法导论>,里面证明比较详细。下面给出代码。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // http://blog.csdn.net/daniel_ustc/article/details/17613359  
  2. #include <cstdio>  
  3. #include <queue>  
  4. #include <vector>  
  5. #include <algorithm>  
  6. using namespace std;  
  7.   
  8. const int M = 6;// 待编码字符个数  
  9.   
  10. typedef struct Tree  
  11. {  
  12.     int freq;//出现频率,即 权值  
  13.     char key;//字符  
  14.     struct Tree *left, *right;  
  15.     Tree(int fr=0, char k='\0',Tree *l=nullptr, Tree *r=nullptr):  
  16.         freq(fr),key(k),left(l),right(r){};  
  17. }Tree,*pTree;  
  18.   
  19. // 自定义仿函数  
  20. struct cmp  
  21. {  
  22.     bool operator() (Tree *a, Tree *b)  
  23.     {  
  24.         return a->freq>b->freq;//注意是> or < 不能用 - 和c中的qsort不同  
  25.     }  
  26. };  
  27. // 优先队列  
  28. priority_queue<pTree, vector<pTree>, cmp> pque;  
  29.   
  30. // 利用中序遍历的方法输出霍夫曼编码  
  31. //左0右1,迭代完一次st回退一个字符  
  32. void print_Code(Tree *proot, string st)  
  33. {  
  34.     if(proot == NULL)  
  35.         return ;  
  36.   
  37.     if(proot->left)  
  38.     {  
  39.         st +='0';  
  40.     }  
  41.     print_Code(proot->left, st);  
  42.     if(!proot->left && !proot->right)//叶子  
  43.     {  
  44.         printf("%c's code:", proot->key);  
  45.         for(size_t i=0; i<st.length(); ++i)  
  46.             printf("%c", st[i]);  
  47.         printf("\n");  
  48.     }  
  49.     st.pop_back();//回退一个字符  
  50.     if(proot->right)  
  51.         st+='1';  
  52.     print_Code(proot->right, st);  
  53. }  
  54.   
  55. //清空堆上分配的内存空间  
  56. void del(Tree *proot)  
  57. {  
  58.     if(proot == nullptr)  
  59.         return ;  
  60.     del(proot->left);  
  61.     del(proot->right);  
  62.     delete proot;  
  63. }  
  64. // 霍夫曼编码  
  65. void huffman()  
  66. {  
  67.     int i;  
  68.     char c;  
  69.     int fr;  
  70.     // 读入测试数据  
  71.     for(i=0; i<M; ++i)  
  72.     {  
  73.         Tree *pt= new Tree;  
  74.         scanf("%c%d", &c, &fr);  
  75.         getchar();  
  76.         pt->key = c;  
  77.         pt->freq = fr;  
  78.         pque.push(pt);  
  79.     }  
  80.     //将森林中最小的两个频度组成树,放回森林。直到森林中只有一棵树。  
  81.     while(pque.size()>1)  
  82.     {  
  83.         Tree *proot = new Tree;  
  84.         pTree pl,pr;  
  85.         pl = pque.top();  
  86.         pque.pop();  
  87.         pr = pque.top();  
  88.         pque.pop();  
  89.   
  90.         proot->freq = pl->freq + pr->freq;  
  91.         proot->left = pl;  
  92.         proot->right = pr;  
  93.         pque.push(proot);  
  94.     }  
  95.     string s;  
  96.     print_Code(pque.top(), s);  
  97.     del(pque.top());  
  98. }  
  99. int main()  
  100. {  
  101.     //freopen("in.txt", "r", stdin);  
  102.     huffman();  
  103.     return 0;  
  104. }  

输入为:

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. a 45  
  2. b 13  
  3. c 12  
  4. d 16  
  5. e 9  
  6. f 5  
输出:

                  对应的二叉树为:


 算法以freq为键值的优先队列Q用在贪心选择时有效地确定算法当前要合并的2棵具有最小频率的树。一旦2棵具有最小频率的树合并后,产生一棵新的树,

其频率为合并的2棵树的频率之和,并将新树插入优先队列Q。经过n-1次的合并后,优先队列中只剩下一棵树,即所要求的树proot。算法huffman用最

小堆实现优先队列Q。初始化优先队列需要O(n)计算时间,由于最小堆的节点删除、插入均需O(logn)时间,n-1次的合并总共需要O(nlogn)计算时间。

因此,关于n个字符的哈夫曼算法的计算时间为O(nlogn) 。

参考资料:

《算法导论》

转载自:http://blog.csdn.net/daniel_ustc/article/details/17613359
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值