1. 程式人生 > >手寫堆

手寫堆

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;
    }
}