手寫堆
堆
1.定義
堆(Heap)是電腦科學中一類特殊的資料結構的統稱。堆通常是一個可以被看做一棵完全二叉樹的陣列物件。
2.性質
1.堆中某個節點的值總是不大於或不小於其父節點的值;
2.堆總是一棵完全二叉樹。
將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。常見的堆有二叉堆、斐波那契堆等。
堆是非線性資料結構,相當於一維陣列,有兩個直接後繼。
3.應用思路(手寫堆)
以最小堆為例
先把陣列中的數儲存起來
很顯然最小的數就在堆頂,假設儲存這個堆的陣列叫做h的話,最小數就是h[1]。接下來,我們將堆頂的數刪除,並將新增加的數23放到堆頂。顯然加了新數後已經不符合最小堆的特性,我們需要將新增加的數調整到合適的位置。那如何調整呢?
我們可以將它和它的兩個兒子進行比較,將兒子小的置為堆頂即可,然後依次下調
那如果是要新新增一個數,又如何操作呢?
同前面的,我們可以將新的點加到堆尾,然後上調與它的父節點比較即可。
程式碼
堆的操作(以大根堆為例)
關於小根堆的操作放在了文章後面。
1.堆的元素下調
void shiftdownmax(int x){ int t,flag=0; while(x*2<=addmax&&flag==0){ if(maxn[x]<maxn[x*2])t=x*2; else t=x; if(x*2+1<=addmax){ if(maxn[t]<maxn[x*2+1])t=x*2+1; } if(t!=x){ swap(maxn[t],maxn[x]); x=t; }else flag=1; } }
2.堆的元素上調
void shiftdownmax(int x){ int t,flag=0; while(x*2<=addmax&&flag==0){ if(maxn[x]<maxn[x*2])t=x*2; else t=x; if(x*2+1<=addmax){ if(maxn[t]<maxn[x*2+1])t=x*2+1; } if(t!=x){ swap(maxn[t],maxn[x]); x=t; }else flag=1; } }
3.建堆
由於堆的性質,只要調整一半的元素即可。
for(int i=1;i<=n;++i){
addmax++;
maxn[addmax]=a[i];
}
for(int i=addmax/2;i>=1;--i){
shiftdownmax(i);
}
4.取出元素
取出第一個元素將最後一個元素放在第一個元素的位置,並且元素個數減1,對堆頂進行下調操作。
int y=maxn[1];
maxn[1]=maxn[addmax--];
shiftdownmax(1);
5.加入元素
在堆尾加入新元素並且對其進行上調操作
maxn[++addmax]=x;
shiftupmax(addmax);
4.例題
合併果子
題意
在一個果園裡,多多已經將所有的果子打了下來,而且按果子的不同種類分成了不同的堆。多多決定把所有的果子合成一堆。
每一次合併,多多可以把兩堆果子合併到一起,消耗的體力等於兩堆果子的重量之和。可以看出,所有的果子經過n-1次合併之後,就只剩下一堆了。多多在合併果子時總共消耗的體力等於每次合併所耗體力之和。
因為還要花大力氣把這些果子搬回家,所以多多在合併果子時要儘可能地節省體力。假定每個果子重量都為1,並且已知果子的種類數和每種果子的數目,你的任務是設計出合併的次序方案,使多多耗費的體力最少,並輸出這個最小的體力耗費值。
例如有3種果子,數目依次為1,2,9。可以先將1、2堆合併,新堆數目為3,耗費體力為3。接著,將新堆與原先的第三堆合併,又得到新的堆,數目為12,耗費體力為12。所以多多總共耗費體力=3+12=15。可以證明15為最小的體力耗費值。
思路闡述
堆的入門題,我們只要建立一個最小堆,然後依次取出堆頂2次,將其合併之後再放入堆中即可。
程式碼實現
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
int n,a[100005],add,p,q,sum,ans=0;
void shiftdown(int x){
int t,flag=0;
while(x*2<=add && flag==0){
if(a[x]>a[x*2])t=x*2;
else t=x;
if(x*2+1<=add){
if(a[t]>a[x*2+1])t=x*2+1;
}
if(t!=x){
swap(a[t],a[x]);
x=t;
}else flag=1;
}
}
int main(){
scanf("%d",&n);
add=n;
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
for(int i=n/2;i>=1;i--){
shiftdown(i);
}
for(int i=1;i<=n-1;++i){
p=a[1];
a[1]=a[add];
add--;
shiftdown(1);
q=a[1];
sum=q+p;
a[1]=sum;
shiftdown(1);
ans=ans+sum;
}
printf("%d",ans);
return 0;
}
5.習題
[TJOI2010]中位數
洛谷P1792 [國家集訓隊]種樹
NOIP 2016 蚯蚓
6.關於堆的一些操作
1.小根堆向下調整
void shiftdownmin(int x){
int t,flag=0;
while(x*2<=addmin&&flag==0){
if(minn[x]>minn[x*2])t=x*2;
else t=x;
if(x*2+1<=addmin){
if(minn[t]>minn[x*2+1])t=x*2+1;
}
if(t!=x){
swap(minn[t],minn[x]);
x=t;
}else flag=1;
}
}
2.小根堆向上調整
void shiftupmin(int x) {
int flag=0;
if(x==1) return;
while(x!=1 && flag==0){
if(minn[x]<minn[x/2]) swap(minn[x],minn[x/2]);
else flag=1;
x=x/2;
}
}
3.大根堆向下調整
void shiftdownmax(int x){
int t,flag=0;
while(x*2<=addmax&&flag==0){
if(maxn[x]<maxn[x*2])t=x*2;
else t=x;
if(x*2+1<=addmax){
if(maxn[t]<maxn[x*2+1])t=x*2+1;
}
if(t!=x){
swap(maxn[t],maxn[x]);
x=t;
}else flag=1;
}
}
4.大根堆向上調整
void shiftupmax(int x) {
int flag=0;
if(x==1) return;
while(x!=1&&flag==0){
if(maxn[x]>maxn[x/2]) swap(maxn[x],maxn[x/2]);
else flag=1;
x=x/2;
}
}