堆--优先级队列--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;
}







发布了47 篇原创文章 · 获赞 21 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章