<tbody id="86a2i"></tbody>


<dd id="86a2i"></dd>
<progress id="86a2i"><track id="86a2i"></track></progress>

<dd id="86a2i"></dd>
<em id="86a2i"><ruby id="86a2i"><u id="86a2i"></u></ruby></em>

    <dd id="86a2i"></dd>
    WangChe

    本系列所有題目均為Acwing課的內容,發表博客既是為了學習總結,加深自己的印象,同時也是為了以后回過頭來看時,不會感嘆虛度光陰罷了,因此如果出現錯誤,歡迎大家能夠指出錯誤,我會認真改正的。同時也希望文章能夠讓你有所收獲,與君共勉!

    昨天被并查集折磨了一天,今天終于可以放松點了。那么今天就主要來學習堆的操作。

    這里的堆主要為大頂堆和小頂堆,他們都是以完全二叉樹作為數據結構的(完全二叉樹不清楚的可以自己去百度下),而完全二叉樹一般用數組模擬。接下來談談堆能干什么,我們知道每個父節點比子節點的值要大的堆是大頂堆,那么最大值也就是堆頂嘍,小頂堆同理,最小值也是堆頂,有了這樣的數據結構我們就能以\(O(1)\)的時間復雜度獲得最大值和最小值,而堆的主要操作分別是插入一個元素,刪除一個元素,修改一個元素,獲得集合中最小的元素。具體的就在下面講吧。

    模擬堆

    維護一個集合,初始時集合為空,支持如下幾種操作:

    I x,插入一個數 x;
    PM,輸出當前集合中的最小值;
    DM,刪除當前集合中的最小值(數據保證此時的最小值唯一);
    D k,刪除第 k 個插入的數;
    C k x,修改第 k 個插入的數,將其變為 x;
    現在要進行 N 次操作,對于所有第 2 個操作,輸出當前集合的最小值。

    輸入格式
    第一行包含整數 N。

    接下來 N 行,每行包含一個操作指令,操作指令為 I x,PM,DM,D k 或 C k x 中的一種。

    輸出格式
    對于每個輸出指令 PM,輸出一個結果,表示當前集合中的最小值。

    每個結果占一行。

    數據范圍
    1≤N≤105
    ?109≤x≤109
    數據保證合法。

    輸入樣例:
    8
    I -10
    PM
    I -10
    D 1
    C 2 8
    I 6
    PM
    DM
    輸出樣例:
    -10
    6

    算法原理

    初始化

    剛剛我們知道堆是用數組實現的完全二叉樹h[N],除此之外要想實現堆操作,還需要定義cnt始終指向堆中最后一個結點(其實這時的cnt就是這個元素在堆中的下標)和進來的順序m,以及表示存儲x是第幾個進來的數組hp[N]和存儲第m個進來的數在堆h[N]中所對應的下標ph[N]。(這里mcnt在數組都是從1開始,這樣初始化m=cnt=0時就要先++m,++cnt)。
    差點忘了,建立一個堆就是不斷下沉元素的過程。

    插入一個元素

    在堆中我們插入一個元素是給h[cnt] = x,同時需要記錄這個元素是第幾個進來的hp[cnt] = m以及這時進來的元素在堆中存儲的下標也要被存儲起來ph[m] = cnt,并且將這個元素上浮down(k)(因為是存儲在堆最后的位置的)來尋找這個元素應該在堆中存儲的位置。

    cin >> x;
    ++cnt,++m;
    h[cnt] = x;
    hp[cnt] = m,ph[m] = cnt;
    up(cnt);
    

    刪除任意一個元素

    眾所周知,數組刪除可以用覆蓋來代替,那么如果我們需要刪除第k個數,我們通常會用最后一個數覆蓋他(方便),這里的覆蓋就是堆交換heap_swap(k,cnt),然后調整最后一個數到它應該到的位置,即上浮下沉一遍up(k).down(k),從樹的角度就是把最后一個數看看是往上走,還是往下走。

    修改結點值

    修改跟刪除一樣h[k] = x,刪除完之后就調整修改后節點的位置,即上浮下沉一遍。

    獲得集合里的最值

    這個更簡單,下標為1的結點就是堆頂h[1],就是最大值或最小值,直接輸出就行。

    這些操作中就有堆的核心操作,上浮和下沉,還有堆交換(比較難理解),接下來就來介紹這些核心操作,以小頂堆為例。

    上?。▍凳窃卦诙阎械南聵耍?/h3>

    由小頂堆的性質我們可以知道,如果一個節點比他的父節點小,那么我們就需要把他上浮,那么怎么上浮呢,我們可以一直比較該節點與其父節點的大小,如果比它小,就進行堆交換,那么什么時候停止呢?當該節點比父節點要大時就滿足小頂堆性質了,可以停止循環,還有一種情況就是該結點就是最小值,那么它就會到這個堆的堆頂去,即當前節點的下標為1時也要停止循環。

    void up(int x){
    	while(x/2 || h[x/2] > h[x]){
    		heap_swap(x,x/2);
    		x >>= 1;
    	}
    }
    

    下沉(參數是元素在堆中的下標)

    同理,如果一個結點值比他的子節點要大,他就要下沉,由于子節點有兩個-左右兒子,因此需要比較與子節點的大小,如果滿足條件就先堆交換在下沉,不斷下沉直至到達合適的位置,當然要是不滿足條件就不必下沉。

    void down(int x){
    	int t = x;
    	if(2*x <= cnt && h[2*x] < h[t])	t=x*2;
    	if(2*x+1 <= cnt && h[2*x+1] < h[x])	t=2*x+1;
    	if(x!=t){
    		heap_swap(x,t);
    		down(t);
    	}
    }
    

    堆交換(參數是元素在堆中的下標)

    對于x,y兩個數,首先要知道他們當前在堆中的下標a,b然后才能heap_swap(a,b),那么怎么交換這兩個數在堆中的位置呢。
    首先就得要知道他們什么時候進來的hp[a],hp[b],交換這個順序在堆中存儲的下標swap(ph[hp[a]],ph[hp[b]])。
    然后交換他們進來的先后順序swap(hp[a],hp[b])。
    最后在交換在堆中的值swap(h[a],h[b])。

    void heap_swap(int a,int b){
        swap(ph[hp[a]],ph[hp[b]]);  
        swap(hp[a],hp[b]);  
        swap(h[a],h[b]);  
    }
    

    代碼實現

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N = 1000010;
    int h[N];   // 存儲堆的值
    int hp[N];  // 下標為a所對應的數--表示一種順序
    int ph[N];  // 第k個數所對應的下標a--指針
    int cnt,m;    // 最后一個數
    
    // 交換三組:
    // 1.對這兩個數的下標所對應的順序值,第幾個值(順序值)在堆中所對應的下標進行交換
    // 2.先交換存儲在ph中某兩個相對(插入的順序)的數所指向堆中的下標
    // 3.最后交換a,b這兩個位置的值
    void heap_swap(int a,int b){
        swap(ph[hp[a]],ph[hp[b]]);  
        swap(hp[a],hp[b]);  
        swap(h[a],h[b]);  
    }
    
    void down(int x){
        int t = x;
        if(x * 2 <= cnt && h[2*x] < h[t])   t = 2*x;
        if(x * 2 + 1 <= cnt && h[2*x + 1] < h[t])    t = 2*x+1;
        if(x != t){
            heap_swap(x,t);  // 交換x,t,與堆排序的不同,這里使用的是heap_swap,只需要提供下標,再函數內部堆下標所指向的數及進行排序
            down(t);
        }
    }
    
    void up(int x){
        while(x / 2 && h[x/2] > h[x]){  // x/2
            heap_swap(x,x/2);
            x >>= 1;
        }
    }
    
    int main()
    {
        int n;
        cin >> n;
        
        
        while(n--){
            string op;
            int x,k;
            cin >> op;
            if(op == "I"){
                cin >> x;
                ++cnt,++m;
                h[cnt] = x; // cnt所對應存儲在堆h中的值
                ph[m] = cnt,hp[cnt] = m;    // cnt是下標,m是第幾個數,都從1開始
                up(cnt);    // 插入位置的元素上浮
            }
            else if(op == "D"){ // 刪除:刪除第k個數,那就拿最后一個數覆蓋他,然后對這個數調整
                cin >> k;
                k = ph[k];
                heap_swap(k,cnt);
                cnt--;
                up(k);  // 不管交換后最后一個的值是大還是小,都先上浮或下浮一遍.
                down(k);
            }
            else if(op == "C"){
                cin >> k >> x;
                k = ph[k];  // 找到第k數所對應的下標
                h[k] = x;   // 將對應的位置修改值
                up(k);  // 調整修改后的數的位置
                down(k);
            }
            else if(op == "PM"){
                cout << h[1] << endl;
                continue;
            }
            else{   // "DM"刪除最小值
                heap_swap(1,cnt);
                cnt--;
                down(1);    // 只能向下調整
            }
        }
        return 0;
    }
    

    相關文章:

    免费一级a片在线播放视频|亚洲娇小性XXXX色|曰本无码毛片道毛片视频清|亚洲一级a片视频免费观看
    <tbody id="86a2i"></tbody>

    
    
    <dd id="86a2i"></dd>
    <progress id="86a2i"><track id="86a2i"></track></progress>

    <dd id="86a2i"></dd>
    <em id="86a2i"><ruby id="86a2i"><u id="86a2i"></u></ruby></em>

      <dd id="86a2i"></dd>