重溫《資料結構與演算法》之堆與堆排序

2021-10-07 06:57:37 字數 2622 閱讀 9721

二叉樹可以被細分為普通二叉樹、滿二叉樹、完全二叉樹,而今天所分享的堆這種資料結構就是一種完全二叉樹。

堆中的每個結點的值要麼都大於左右子結點的值,要麼都小於左右子結點的值。前者形成的是大頂堆,即堆頂元素是最大的;後者形成的是小頂堆,即堆頂元素是最小的。如下圖所示,分表是乙個大頂堆和小頂堆。

完全二叉樹的特點是:最後一層的葉子結點靠左排列,從根結點到倒數第二層結點,是一顆滿二叉樹。通常儲存二叉樹這種資料結構的方法有鏈式儲存和順序儲存,鏈式儲存就是通過指標來維護左右子結點的關係,而順序儲存就是利用陣列來儲存。完全二叉樹最適合用陣列來儲存,即堆這種資料結構適合用陣列來儲存。

當用陣列來存放堆元素時,如果堆頂的元素存放在索引為 1 的地方(不從 0 開始),那麼左節點的索引為 2,右結點的索引為 3。也就是是說,如果父結點存放在陣列索引為 i 的地方,那麼左結點存放的位置為 2*i,右結點存放的位置為 2*i+1。

下面看下堆的常用操作:插入資料和刪除堆頂資料

1.1 插入資料

以大頂堆為例,當向堆中插入資料時,新插入的資料首先放在陣列的最後面,由於是大頂堆,而新插入的資料可能比父結點的值大,因此會出現不滿足大頂堆性質的情況,這個時候需要調整資料的位置,這個過程,叫做堆化(heapify)。(堆化分兩種:「向下堆化和向上堆化」,此時是向上堆化)

下面看下**實現。

public

class

heap

public

void

insert

(int data)

} // 交換下標a1和下標a2的資料

private

static

void

swap

(int heap, int a1, int a2)

}

1.2 刪除堆頂資料

還是以大頂堆為例,當要刪除堆中最大的元素時,其實就是刪除堆頂元素。刪除完堆頂元素後,需要從堆中找出乙個最大的元素放到堆頂。這個時候,通常是將陣列中最後乙個資料放到堆頂,由於這個資料可能不是最大的,因此也需要進行堆化,此時通常採用向下堆化的手段。**實現如下:

// 刪除最大元素

public

void

deletemax

() // 堆化

private

static

void

heapify

(int heap, int i, int count)

}

堆頂的元素要麼是最大的,要麼是最小的,如果我們每次從堆頂取乙個元素,那麼每次取到的資料就是最大或者最小,依次取完,那麼取出來的資料就是有序的了。

使用堆排序有乙個優點,就是「不需要額外的記憶體空間」,直接基於原陣列,就可以完成排序操作。

堆排序大概分為兩個步驟:「建堆和排序」。建堆是指根據指定的陣列,將其變為滿足堆這種資料結構的資料。建堆的思路有兩種,一種是從下往上建堆(前面的插入資料過程就是從下往上建堆),一種是從上往下建堆。下面以從上往下建堆為例,構建乙個大頂堆,示例**如下。

// 建堆(從上往下建堆)

public

static

int buildheap(int a) return a; }

在從上往下建堆的過程中,葉子結點是不需要堆化,因為葉子結點下面是沒有左右子結點的,無法向下堆化。

接下來是排序過程,排序過程中,由於是大頂堆,所以將堆頂元素與陣列最後的元素交換,然後刨去陣列最後乙個元素,將剩餘元素進行堆化,迴圈遍歷,這樣陣列就是變成從小到大排列的有序陣列了。示例**如下:

// 排序

public

static

void

sort

(int a)

}

堆的應用十分廣泛,例如 top k 這一系類的問題,都能用堆來解決。假如現在有 10 億資料,要取其中最大的 10 個資料,如果我們對這 10 億個資料都進行排序,肯定能得到結果,但是資料量太大,如果都進行排序就會非常耗 cpu,而且我們只需要其中的 10 個資料,對所有資料排序,顯然有點太浪費了。如果用堆來解決,我們可以建立乙個可以存放 10 個資料的「小頂堆」,在初始時,可以先一次放入 10 個資料,後面在遍歷剩下的資料時,判斷是不是比堆頂的元素大,如果大,就將現在的堆頂元素刪除,然後將當前資料放入到堆中;如果小於或者等於,則不做任何操作,這樣最終就能得到這 10 億個資料中最大的 10 個資料了。

另外,定時任務也可以用堆來實現。每個任務會在指定的時間來執行,這個時候,可以將任務達到時間最小的任務放到小頂堆的堆頂,那麼每次執行時,就取堆頂的任務執行即可。

總結一下,堆是一棵完全二叉樹,它適合採用陣列來儲存資料。大頂堆的堆頂元素是堆中最大的元素,小頂堆中的堆頂元素是堆中最小的元素。

採用堆這種資料結構,對資料進行排序時,不需要占用額外的儲存空間,效率也很高,時間複雜度為 o(nlogn)。

在文中的例子中,在使用陣列儲存堆的資料時,將陣列下標為 0 的位置,沒有儲存元素,這是為了方便計算左右子結點的位置。實際上,下標為 0 的位置也可以用來存放元素,不同的是,計算左右子結點的位置的公式變了。

資料結構 堆與堆排序

堆其實是從完全二叉樹演變過來的並且用來儲存資料的,什麼是完全二叉樹呢?完全二叉樹就是 若設二叉樹的深度為h,除第h層外,其它各層 1 h 1 的結點數都達到最大個數,第h層所有的結點都連續集中 在最左邊,這就是完全二叉樹。我們知道二叉樹可以用陣列模擬,堆自然也可以。現在讓我們來畫一棵完全二叉樹 從圖...

堆資料結構與堆排序

堆資料結構 堆 二叉堆 是近似的完全二叉樹,最底層允許不滿,堆的相關,陣列長度length,heap size有效長度 堆的有效元素,未排序的數目 heap size堆雖然很像二叉樹,但是我的理解就是用二叉樹的邏輯,但是最主要的區別在,堆的儲存是用陣列,只是用下標來幫助理解 堆的儲存結構 從上往下,...

資料結構與演算法16 堆排序

毫無疑問,排序兩個字沒必要去死磕,這裡的重點,在於排序的方式,堆排序,就是以堆的形式去排序,毫無疑問,了解堆很重要。那麼,什麼是堆呢?這裡,必須引入乙個完全二叉樹的概念,然後過渡到堆的概念。上圖,就是乙個完全二叉樹,其特點在於 從作為第一層的根開始,除了最後一層之外,第n層的元素個數都必須是2的n次...