heap

堆 heap


利用徹底二叉樹的結構來維護一組數據,而後進行相關操做,通常的操做進行一次的時間複雜度在 O(1)~O(logn) 之間。ios

徹底二叉樹

  • 若設二叉樹的深度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第 h 層全部的結點都連續集中在最左邊,這就是徹底二叉樹。咱們知道二叉樹能夠用數組模擬,堆天然也能夠。
  • 從圖中能夠看出,元素的父親節點數組下標是自己的1/2(只取整數部分),因此咱們很容易去模擬,也很容易證實其全部操做都爲log級別~~
堆還分爲兩種類型:大根堆、小根堆

顧名思義,就是保證根節點是全部數據中最大/小,而且盡力讓小的節點在上方
不過有一點須要注意:堆內的元素並不必定數組下標順序來排序的!!不少的初學者會錯誤的認爲大/小根堆中
下標爲1就是第一大/小,2是第二大/小……數組

咱們剛剛畫的徹底二叉樹中並無任何元素,如今讓咱們加入一組數據吧!
下標從1到9分別加入:{8,5,2,10,3,7,1,4,6}。測試

以下圖所示
spa

如今我就來介紹一下堆的幾個基本操做:

  • 上浮 shift_up;
  • 下沉 shift_down
  • 插入 push
  • 彈出 pop
  • 取頂 top
  • 堆排序 heap_sort

小根堆 爲例

從上述未處理過的數據中能夠很容易得出,根節點1元素8絕對不是最小的
可是它的一個子節點3(元素2)比它小,咱們能夠將它放到最高點,直接進行交換。
此外,子節點3的子節點7(元素1)彷佛更適合在根節點
此時,咱們沒法直接和根節點交換的,那麼就是用上浮 shift_up操做來完成。3d

操做過程以下code

從當前結點開始,和它的父親節點比較,如果比父親節點來的小,就交換,而後將當前詢問的節點下標更新爲原父親節點下標;不然退出。
blog

僞代碼以下:排序

Shift_up( i )
{
    while( i / 2 >= 1)
    {
        if( 堆數組名[ i ] < 堆數組名[ i/2 ] )
        {
            swap( 堆數組名[ i ] , 堆數組名[ i/2 ]) ;
            i = i / 2;
        }
        else break;
}

上浮操做結束後,節點3(元素8)與其子節點7(元素2)的位置並不正確。
所以,須要節點3下沉內存

節點的下沉策略以下所述

小根堆是盡力要讓小的元素在較上方的節點,而下沉與上浮同樣要以交換來不斷操做。
讓當前結點的子節點(若是存在)做比較,哪一個比較小就和它交換,並更新詢問節點的下標爲被交換的子節點下標,不然退出。
ci

僞代碼以下所示

Shift_down( i , n )    //n表示當前有n個節點
{
    while( i * 2 <= n)
    {
        T = i * 2 ;
        if( T + 1 <= n && 堆數組名[ T + 1 ] < 堆數組名[ T ])
            T++;
        if( 堆數組名[ i ] < 堆數組名[ T ] )
        {
           swap( 堆數組名[ i ] , 堆數組名[ T ] );
            i = T;
        }
        else break;
}

插入操做

  • 如何在插入的時候維護堆?

    每次進行數據插入的時候,往堆的最後插入,而後使用上浮操做。

僞代碼以下所示

Push ( x )
{
    n++;
    堆數組名[ n ] = x;
    Shift_up( n );
}

彈出操做

使用根節點元素和尾節點進行交換,而後使如今的根元素下沉。

僞代碼以下所示

Pop ( x )
{
    swap( 堆數組名[1] , 堆數組名[ n ] );
    n--;
    Shift_down( 1 );
}

取頂操做

返回節點數組[0]

堆排序

new 新數組,每次取堆頂元素放進去,而後彈掉堆頂

僞代碼以下所示

Heap_sort( a[] )
{
    k=0;
    while( size > 0 )
    {
        k++;
        a[ k ] = top();
        pop();    
    }        
}

堆排序的時間複雜度是O(nlogn)

堆操做代碼

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

#define maxn 100010   //這部分能夠本身定義堆內存多少個元素 

using namespace std;

struct Heap {
    int size, queue[maxn];
    Heap() {       //初始化
        size=0;
        for(int i=0; i<maxn; i++)
            queue[i]=0;
    }
    void shift_up(int i) { //上浮
        while(i > 1) {
            if(queue[i] < queue[i>>1]) {
                int temp = queue[i];
                queue[i] = queue[i>>1];
                queue[i>>1] = temp;
            }
            i >>= 1;
        }
    }
    void shift_down(int i) { //下沉
        while((i<<1) <= size) {
            int next = i<<1;
            if(next < size && queue[next+1] < queue[next])
                next++;
            if(queue[i] > queue[next]) {
                int temp = queue[i];
                queue[i] = queue[next];
                queue[next] = temp;
                i = next;
            } else return ;
        }
    }
    void push(int x) { //加入元素
        queue[++size] = x;
        shift_up(size);
    }
    void pop() {       //彈出操做
        int temp = queue[1];
        queue[1] = queue[size];
        queue[size] = temp;
        size--;
        shift_down(1);
    }
    int top() {
        return queue[1];
    }
    bool empty() {
        return size;
    }
    void heap_sort() {  //另外一種堆排方式,因爲難以證實其正確性
        //我就沒有在博客裏介紹了,能夠本身測試
        int m=size;
        for(int i = 1; i <= size; i++) {
            int temp = queue[m];
            queue[m] = queue[i];
            queue[i] = temp;
            m--;
            shift_down(i);
        }
    }
};

int main()
{
    Heap Q;
    int n,a,i,j,k;
    cin>>n;
    for(i = 1; i <= n; i++) {
        cin >> a;
        Q.push(a); //放入堆內
    }

    for(i = 1; i <= n; i++) {
        cout << Q.top() << " ";  //輸出堆頂元素
        Q.pop();        //彈出堆頂元素
    }
    return 0;
}
相關文章
相關標籤/搜索