堆的實現及用途

2022-08-29 18:03:15 字數 1871 閱讀 9878

大頂堆:每個結點的值都大於等於其左右孩子結點的值。

小頂堆:每個結點的值都小於等於其左右孩子結點的值。

注:本節以小頂堆為例,記堆的大小為n

首先定義乙個堆。

class heap 

void push(int x);

void pop();

int top();

int size();

bool empty();

};

從當前結點開始,和它的父節點比較:

void shift_up(int i) 

}

當前節點與其左右孩子(如果有的話)中較小者作比較:

void shift_down(int i) 

}

向陣列末尾插入新節點,然後使它上浮。

void push(int x)
用尾節點覆蓋根節點,堆大小減一,然後讓新的根節點下沉。

void pop()
返回陣列第乙個元素。

int top()
堆排序的基本思想是:將待排序序列構造成乙個大頂堆,此時整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就為最大值。然後將剩餘 n-1 個元素重新構造成乙個堆,這樣會得到 n 個元素的次小值。如此反覆執行,便能得到乙個有序序列。

顯然,大頂堆得到公升序序列,小頂堆得到降序序列。

演算法步驟為:

構造初始堆。從最後乙個非葉子結點arr[n/2]開始,自下而上進行下沉操作;

將堆頂元素與末尾元素交換,此時的末尾元素從堆中排除,然後再次下沉根節點;

反覆執行步驟 2,直到整個序列有序。

void shift_down(int* arr, int i, int n) 

}void heap_sort(int* arr, int n)

}

堆排序是一種選擇排序,整體主要由構建初始堆+交換堆頂元素和末尾元素並重建堆兩部分組成。其中構建初始堆經推導複雜度為 \(o(n)\),在交換並重建堆的過程中,需交換 \(n-1\) 次,而重建堆的過程中,根據完全二叉樹的性質, \([log2(n-1),log2(n-2)...1]\) 逐步遞減,近似為 \(nlogn\) 。所以堆排序時間複雜度一般認為就是 \(o(nlogn)\) 級。

用乙個大根堆實時維護陣列的前 \(k\) 小值。首先將前 \(k\) 個數插入大根堆中,隨後從第 \(k+1\) 個數開始遍歷,如果當前遍歷到的數比大根堆的堆頂的數要小,就把堆頂的數彈出,再插入當前遍歷到的數。最後將大根堆裡的數存入陣列返回即可。

反之,利用小根堆可以得到最大的 k 個數。

c++ 中的優先佇列本質上就是由堆實現的,且預設是大根堆。而 python 中的堆為小根堆,因此我們要對陣列中所有的數取其相反數,才能使用小根堆維護前 \(k\) 小值。

這個問題如果直接對所有鍊錶一起排序,複雜度為 \(o(nlogn)\),其中 \(n\) 為 \(k\) 個鍊錶所有元素的總數。

然而我們應該充分利用鍊錶本身是有序的條件,並通過堆來解決這個問題,其步驟是:

把每個鍊錶第乙個元素插入到最小堆;

從堆中取出最小的元素新增到結果列表中;

再從拿出去的元素所在的那個鍊錶中取出下乙個元素放到堆中;

重複第 2 步跟第 3 步,我們可以保證所有元素新增到了結果列表中且有序。

這種解法的時間複雜度可以達到 \(o(nlogk)\),而空間複雜度為 \(o(k)\)。

說說反射的用途及實現

反射的核心是jvm在執行時才動態載入類或呼叫方法 訪問屬性,它不需要事先 寫 的時候或編譯期 知道執行物件是誰。一 j a反射框架主要提供以下功能 1.在執行時判斷任意乙個物件所屬的類 2.在執行時構造任意乙個類的物件 3.在執行時判斷任意乙個類所具有的成員變數和方法 通過反射甚至可以呼叫priva...

堆的實現(堆的建立及push pop元素)

堆資料結構是一種陣列物件,它可以被視為一棵完全二叉樹結構。堆結構的二叉樹儲存 大堆 每個父節點的都大於孩子節點 小堆 每個父節點的都小於孩子節點。建堆 由於堆被視為完全二叉樹,故在h 1層找到第乙個 從後往前找 非葉子結點,進行堆的下調 建大堆時,從下往上依次判斷並調整堆,使該結點的左右子樹都滿足大...

堆的實現及堆排序

前兩天刷筆試題,判斷乙個陣列的序列可以構成堆。仔細想了想,腦海裡幾乎已經遺忘了堆的知識,今天又重新去看書,把堆的知識總結一下。首先堆是一種陣列物件,它可以被看成乙個完全二叉樹。在我們常見的堆中有大堆和小堆。對大堆來說,每個父節點都大於孩子結點 小堆恰好相反。而且,大堆 小堆的每個子樹也是乙個大堆 小...