堆--優先級隊列--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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章