堆--优先级队列--topK问题

       堆是一个用途很广泛的数据结构,是实现topK问题、堆排序以及优先级队列等问题的必备工具。深刻理解堆这种数据结构,掌握实现堆的技能是学习数据结构非常重要和必备的
一环。

         堆的本质是:
                        vector + 向上调整 和 向下调整
           人们在想象中把它抽象为一棵每个父亲节点都比它两个子节点 大/小 的二叉树
           并用二叉树的下标表示方法构建和调整这棵树。实际上所有的结点都在vector中
   顺序存储。
                        
向下调整:
               (_v.size() - 2) >> 2 找到最后一个父亲结点 若其比儿子中的某一个小 则将两个孩子中较大/小的结点与其父亲结点互换 之后 parent = child ;  child= parent*2 +1;(向下继续处理)Pop() 和 构造堆要用到。

向上调整:
              Push()时要用到 这时原树已经是堆了 在其最后加一个结点然后 查看新加结点是否比父亲大/小 如果是交换它和其父亲 之后 child = parent; parent= (child - 1)>>1.

以下是实现一个堆的代码:
“堆.cpp”
#pragma once
#include <iostream>
#include <string.h>
#include <vector>
#include <assert.h>
using namespace std;
//利用仿函数代替比较运算符
//Compare()(_v[child], _v[]parent) 其实是Compare( ) 创建一个临时对象 调用其operator()
template < class T >
struct Greater
{
      bool operator ( )( const T & l, const T & r)
     {
           return l > r;
     }
};
template < class T >
struct Less
{
      bool operator ( )( const T & l, const T & r)
     {
           return l < r;
     }
};
template < class T , class Compare = Greater < T >>
class Heap
{
public :
      Heap ()
     {}
      bool Empty ()
     {
           return _v. empty ();
     }
      size_t Size ()
     {
           return _v. size ();
     }
      Heap ( const T * arr, int n)
     {
          _v. reserve (n); //为了提高效率 一次性申请够空间 防止插入时空间不够再次申请。
           for ( int i = 0; i < n; ++i)
          {
              _v.push_back(arr[i]); //一般和reserve()函数配合使用
          }
           for ( int i = ( int )(_v. size () - 2) / 2; i >= 0; --i)
          {
               AdjustDown (i); //请注意向下调整是被循环调用的 最早从树的最后一个父亲结点开始
               //目的是为了将原本vector中无规律的数变为符合堆的规律。
               //每一次调用完AdjustDown(i)只保证 i下子树是堆
          }
     }
      void Pop () //移除堆顶方法
     {
           swap (_v[_v. size () - 1], _v[0]); //先将堆顶结点和堆最后结点互换位置
          _v. pop_back (); //之后移除原本是对顶的最后一个结点
           //之所以这么做是为了让原对顶的左右子树保持堆的性质。
           AdjustDown (0); //把原来是最后结点的现堆顶搞一次向下调整。
     }
      void Push ( const T & data) //新插入结点
     {
          _v.push_back(data); //先将新结点权且放在最后以保证堆内其他结点符合堆的性质
           AdjustUp (_v. size () - 1); //注意调用向上调整时只调一次 向上调整是为了Push而存在的
     }
    const T & Top ()
     {
           return _v[0];
     }
      void Printheap ()
     {
           for ( size_t i = 0; i < _v. size (); ++i){
              cout << _v[i] << " " ;
          }
          cout << endl ;
     }
private :
      void AdjustDown ( int root) //向下调整
     {
           int parent = root; //已有最后一个父亲结点
           int child = parent * 2 + 1; //默认找到其左孩子
           while (child < _v. size ()){
               if (child + 1 < _v. size () && Compare ()(_v[child + 1], _v[child]))
              {
                   child++; //这里是为了找到两个孩子里较大的准备和父亲换
              }
               else if ( Compare ()(_v[child], _v[parent])){
                    swap (_v[child], _v[parent]); //如果大孩子比父亲大 则换
                   parent = child;
                   child = parent * 2 + 1; //向下继续
              }
               else {
                    break ; //大孩子比父亲小 次树是堆出循环 
              }
          }
     }
      void AdjustUp ( int index) //向上调整
     { //注意此时除index外其余结点符合堆的性质
           int child = index;
           int parent = (child - 1) / 2;
           while (child > 0){
               if ( Compare ()(_v[child], _v[parent])){
                    swap (_v[child], _v[parent]); //比父亲大和父亲换
                   child = parent;
                   parent = (child - 1) / 2; //向上调整
              }
               else {
                    break ; //比父亲小了 出循环
              }
          }
     }
      vector < int > _v;
};
topk问题(也是海量数据处理问题):
问题:需要从十亿个数据中找出最大的前k个数。
分析思路:
思路:不能使用排序,因为使用排序的话,内存就必须可以容纳十亿个数据,但是这很明显不可能,所以不能使用排序。
             我们可以先从十亿个数据中取出前k(假如为100)个数据,使用这k个数据来建一个小堆,那么堆顶就是这个堆中最小的数据,
然后我们就从十亿减k个数据中取出一个数据赖和堆顶数据来进行比较,如果比堆大,那么就拿这个数据来替换堆顶数据。
然后采用向下调整算法,使之堆顶又是这个堆中最小的数据。依次比较。。替换。。。调整。。。
               最终,堆中的前k个数据就是十亿个数据中最大的k个数据。
这里应该可以想到:最大的前k个数据一定会进堆。–>因为每次都是比较堆顶的数据和从剩余的数据进行比较,
而这个堆顶数据又是这个堆中最小的数据。
注意:不能建大堆,如果建大堆,然后从剩余的十亿数据中取数据,拿取到的数据和堆顶数据进行比较,
               如果这个数据大于堆顶的数据,那么就交换两者的数据。最终只能找出十亿数据中最大的一个数据。—->不符合。

以下是关于对的优先级队列的使用 及topK问题的解决:


#include "堆.cpp"
#define N 1000
#define K 20
//优先级队列为堆的简单封装
template < class T >
class PriorityQueue
{
public :
      PriorityQueue ()
     {}
      void Push ( const T & data)
     {
          hp. Push (data);
     }
     
      void Pop ()
     {
          hp. Pop ();
     }
      const T & Top ()
     {
           return hp. Top ();
     }
      size_t Size ()
     {
           return hp. Size ();
     }
      bool Empty ()
     {
           return hp. Empty ();
     }
private :
      Heap < int , Greater < int >> hp;
};
void Test ()
{
      int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
      Heap < int , Greater < int > > h(array, sizeof (array) / sizeof ( int ));
     h. Push (80);
     h. Pop ();
}
void TestPriorityQueue ()//测试优先级队列
{
      int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
      size_t len = sizeof (array) / sizeof ( int );
      PriorityQueue < int > p;
      for ( size_t index = 0; index < len; ++index)
     {
          p. Push (array[index]);
     }
     cout << p. Top () << endl ;
     p. Pop ();
     cout << p. Top () << endl ;
}
//TopK问题
void TopK ()
{
      int arr[ N ];
      int brr[ K ];
      for ( size_t i = 0; i < N ; ++i){
          arr[i] = rand () % N ;
     }
      for ( size_t i = 0; i < K ; ++i){
          brr[i] = arr[i];
     }
      Heap < int , Less < int >> hp(brr, sizeof (brr) / sizeof ( int ));
      for ( size_t i = K; i <= N ; ++i){
           if (arr[i] > hp. Top ()){
              hp. Pop ();
              hp. Push (arr[i]);
          }
     }
     hp. Printheap ();
}
int main ()
{
      /*Test();
     TestQueue();*/
      TopK ();
      system ( "pause" );
      return 0;
}







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值