堆是一個用途很廣泛的數據結構,是實現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;
}