1. 程式人生 > >關於小根堆的看法

關於小根堆的看法

  最近在複習小根堆,看了好多部落格,一些思想記錄一下。

  早上自己團隊在比賽的時候,第一道題爆零,老師講是用小根堆解決,所以好好複習了一下小根堆;

首先,小根堆其實就是二叉樹。當然,最出名的是一個叫做堆排序的東東,它的時間複雜度為O(nlogn)。足夠的小吧,此外它還有一個別名叫做二叉樹排序。

贈送團隊第一題的連結:

劍與魔法

唔,博主寫這題的時候的直接想法是DFS,當然這樣是解決不了的,雖然博主不知道為什麼解決不了,但是還是將思路留在這裡:

  我是這麼想的,首先輸出“-1”的條件是,事件中所有戰役事件加起來並沒有達到穿越回去事件的RP值,那麼老師不僅拿不到金幣還回不到過去,這個時候就應該輸出“-1”,

然後,就是成立的條件一個“else”,那麼老師可以拿到的金幣就是各個戰役的RP++,首先,因為題面要求老師必須在最後一個返回事件返回,所以無論前面有多少個事件都不能觸發,

然而根據題意,穿越回去事件的觸發是被動的,只有參與的戰役數達到RP時才能穿越,所以搜尋所有的戰役事件,並且所進行的戰役不能超過前面所有的“EOF”事件的RP值,

這就導致了我們要將所有的嘗試值儲存下來,然後進行對比“Max(dfs(1),dfs(2))”,這樣就可以求得每個“EOF”事件之前所獲得的金幣最大值。

(當然博主不知道為啥一直除錯不出來,希望能有位大佬幫助一下蒟蒻(逃)

  其次,使用優先佇列做法(這個是我們團隊的大佬寫的,不敢copy過來,但是可以偷偷看一下

他的程式碼,恕我吐槽一句他的程式碼風真的很小清新)

 運用優先佇列儲存“RP”值與金幣數,運用sum進行判斷,優先佇列的頭頂元素存為最大值,這樣子“ans”所得的結果就是最大值。

  最後,是運用小根堆來解題,當然我並不是很清楚思路,似乎是使用堆排序,把最大值放到樹的前端,然後呼叫來著(弱弱的我,打算溜走)

  給上題目裡的標程,希望對大家的理解有幫助:

#include<cstdio>
#include<algorithm>

using namespace std;

const int N = 201013;
int c[N],d[N],h[N];
int tt,n,cnt; char op[N]; bool cmph(const int i, const int j) { return c[i] > c[j]; } int main() { //freopen("dragons.in","r",stdin); //freopen("dragons.out","w",stdout); scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%s%d", op + i, c + i); tt = 0; for (int i = 1; i < n; ++i) if (op[i] == 'e') { while (tt && c[d[tt]] >= c[i]) --tt; d[++tt] = i; } d[tt + 1] = n; tt = 0; for (int i = 1, j = 1; i < n; ++i) if (op[i] == 'c') { h[++tt] = i; push_heap(h + 1, h + tt + 1, cmph); } else if (i == d[j]) { while (tt >= c[i]) pop_heap(h + 1, h + tt-- + 1, cmph); ++j; } if (tt >= c[n]) { int ret = 0; for (int i = 1; i <= tt; ++i) ret += c[h[i]]; printf("%d\n", ret); } else puts("-1"); return 0; }

 

好啦,我還是總結個人的理解吧;

我的理解學習來自這幾個部落格主:ganggexiongqi山代王kiu000

堆的定義:

n個關鍵字序列L[1…n]稱為堆,當且僅當該序列滿足:
1. L(i)<=L(2i)且L(i)<=L(2i+1)
2. L(i)>=L(2i)且L(i)>=L(2i+1)
滿足第一個條件的成為小根堆(即每個結點值小於它的左右孩子結點值),滿足第二個新增的成為大根堆(即每個結點值大於它的左右孩子結點值)。

關於小根堆的建立:

唔,應該是類似於建立樹。

1. 複製堆陣列

2. 找到最初的調整位置,即找到最後一個分支結點

3.1自底向上逐步擴大形成堆

3.2 向前交換一個分支結點

小根堆的插入:


1. 將待插入元素插入已建成堆的最後面
2. 沿著出入位置所在的分支逐步向上調整

小根堆的刪除:


1. 將堆頂元素刪除

2. 將陣列中最後一個元素放到堆頂

堆的操作:

heapify(heap,i):若節點heap[i]左子樹和右子樹都滿足最小堆的性質,而heap[i]節點不滿足最小堆性質,

即heap[i]>heap[i*2]或者heap[i]>heap[2*i+1],則操作heapify(heap,i)調整heap[i]的位置來保持堆的性質。這是堆的基本操作。

heapinsert(heap,val):往堆裡面插入值val,新增節點heap[n+1]=val,並比較新增節點和其父節點大小,不斷調整新增節點的位置,保持最小堆的性質。

heappop(heap):彈出堆頂元素,並令heap[0]=heap[n],堆大小減一,之後執行heapify(heap,0)來維持堆的性質。

ganggexiongqi那裡有一幅圖有助於理解:

 

大佬程式碼:

public static int[] heapSort(int[] A, int n, int k) {  
        if(A == null || A.length == 0 || n < k){  
            return null;  
        }  
        int[] heap = new int[k];  
        for(int i = 0; i < k; i++){  
            heap[i] = A[i];  
        }  
        buildMinHeap(heap,k);//先建立一個小堆  
        for(int i = k; i < n; i++){  
            A[i-k] = heap[0];//難處堆頂最小元素  
            heap[0] = A[i];  
            adjust(heap,0,k);  
        }  
        for(int i = n-k;i < n; i++){  
            A[i] = heap[0];  
            heap[0] = heap[k-1];  
            adjust(heap,0,--k);//縮小調整的範圍  
        }  
        return A;  
    }  
    //建立一個小根堆  
    private static void buildMinHeap(int[] a, int len) {  
        for(int i = (len-1) / 2; i >= 0; i--){  
            adjust(a,i,len);  
        }  
    }  
    //往下調整,使得重新複合小根堆的性質  
    private static void adjust(int[] a, int k, int len) {  
        int temp = a[k];  
        for(int i = 2 * k + 1; i < len; i = i * 2 + 1){  
            if(i < len - 1 && a[i+1] < a[i])//如果有右孩子結點,並且右孩子結點值小於左海子結點值  
                i++;//取K較小的子節點的下標  
            if(temp <= a[i]) break;//篩選結束,不用往下調整了  
            else{//需要往下調整  
                a[k] = a[i];  
                k = i;//k指向需要調整的新的結點  
            }  
        }  
        a[k] = temp;//本趟需要調整的值最終放到最後一個需要調整的結點處  
    }  

堆排序和優先佇列:

由上述堆的基本操作基本可以實現堆排序和優先佇列。

堆排序:1,線上演算法,不斷heapinsert接受所有的資料後,heappop輸出所有資料

                2,離線演算法,利用heapify操作建立最小堆,heappop輸出所有資料

優先佇列:heapinsert插入優先佇列,heappop彈出優先佇列