演算法導論第6章 堆排序

2021-06-28 05:08:02 字數 3020 閱讀 2692

本章開始介紹了堆的基本概念,然後引入最大堆和最小堆的概念。全章採用最大堆來介紹堆的操作,兩個重要的操作是調整最大堆和建立最大堆,接著著兩個操作引進了堆排序,最後介紹了採用堆實現優先順序佇列。

1、堆

堆給人的感覺是乙個二叉樹,但是其本質是一種陣列物件,因為對堆進行操作的時候將堆視為一顆完全二叉樹,樹種每個節點與陣列中的存放該節點值的那個元素對應。所以堆又稱為二叉堆,堆與完全二叉樹的對應關係如下圖所示:

通常給定節點i,可以根據其在陣列中的位置求出該節點的父親節點、左右孩子節點,這三個過程一般採用巨集或者內聯函式實現。書上介紹的時候,陣列的下標是從1開始的,所有可到:parent(i)=i/2  left(i) = 2*i   right(i) = 2*i+1。

根據節點數值滿足的條件,可以將分為最大堆和最小堆。最大堆的特性是:除了根節點以外的每個節點i,有a[parent(i)] >= a[i],最小堆的特性是:除了根節點以外的每個節點i,有a[parent(i)] >=a[i]。

把堆看成乙個棵樹,有如下的特性:

(1)含有n個元素的堆的高度是lgn。

(2)當用陣列表示儲存了n個元素的堆時,葉子節點的下標是n/2+1,n/2+2,……,n。

(3)在最大堆中,最大元素該子樹的根上;在最小堆中,最小元素在該子樹的根上。

2、保持堆的性質

堆個關鍵操作過程是如何保持堆的特有性質,給定乙個節點i,要保證以i為根的子樹滿足堆性質。書中以最大堆作為例子進行講解,並給出了遞迴形式的保持最大堆性的操作過程max-heapify。先從看乙個例子,操作過程如下圖所示:

從圖中可以看出,在節點i=2時,不滿足最大堆的要求,需要進行調整,選擇節點2的左右孩子中最大乙個進行交換,然後檢查交換後的節點i=4是否滿足最大堆的要求,從圖看出不滿足,接著進行調整,直到沒有交換為止。書中給出了遞迴形式的為**,我用c語言實現如下所示:

1

void adjust_max_heap_recursive(int *datas,int length,inti)2

23 }

課後習題要求給出其非遞迴的形式,我想了半天,才搞出來,領悟能力有限啊。非遞迴就要考慮要迴圈進行實現,需要考慮的是迴圈結束條件是什麼。對乙個給定的節點i,要對其進行調整使其滿足最大堆的性質。總的思想是先找出節點i的左右孩子節點,然後從三者中找到最大的節點,如果找到的最大節點就是i,說明i節點滿足堆的性質,此時迴圈就結束了。如果找到的最大節點不是節點i,那麼這個時候就要將最大的節點(設為largest)與節點i進行交換,然後從largest節點開始迴圈進行調整,直到滿足條件為止。給出非遞迴的調整堆程式如下:

1

void adjust_max_heap(int *datas,int length,inti)2

25else

26break;27

}28 }

3、建堆

建立最大堆的過程是自底向上地呼叫最大堆調整程式將乙個陣列a[1.....n]變成乙個最大堆。將陣列視為一顆完全二叉樹,從其最後乙個非葉子節點(n/2)開始調整。調整過程如下圖所示:

書中給出了建立堆的為**,我用c語言實現如下:

1

void build_max_heap(int *datas,int

length)

2

4、堆排序演算法

堆排序演算法過程為:先呼叫建立堆函式將輸入陣列a[1...n]造成乙個最大堆,使得最大的值存放在陣列第乙個位置a[1],然後用陣列最後乙個位置元素與第乙個位置進行交換,並將堆的大小減少1,並呼叫最大堆調整函式從第乙個位置調整最大堆。給出堆陣列a=進行堆排序簡單的過程如下:

(1)建立最大堆,陣列第乙個元素最大,執行後結果下圖:

(2)進行迴圈,從length(a)到2,並不斷的調整最大堆,給出乙個簡單過程如下:

書中給出了對排序為**,我用c語言實現如下所示:

1

void heap_sort(int *datas,int

length)

217 }

結合上面的調整堆和建立堆 的過程,寫個簡單測試程式連續堆排序,程式如下所示:

程式測試結果如下所示:

從結果可以看出按照最大堆進行堆排序最終使得結果是從小到大排序(非遞減的)。

堆排序演算法時間複雜度:調整堆過程滿足遞迴式t(n)<=t(2n/3)+θ(1),有master定義可以知道t(n) = o(lgn),堆排序過程中執行乙個迴圈,呼叫最大堆調整函式,總的時間複雜度為o(nlgn)。

5、問題

(1)在建立最大堆的過程中,為什麼從最後乙個非葉子節點(n/2)開始到第乙個非葉子(1)結束,而不是從第乙個非葉子節點(1)到最後乙個非葉子節點(n/2)結束呢?

我的想法是:如果是從第乙個非葉子節點開始建立堆,有可能導致建立的堆不滿足堆的性質,使得第乙個元素不是最大的。這樣做只是使得該節點的和其左右孩子節點滿足堆性質,不能確保整個樹滿足堆的性質。如果最大的節點在葉子節點,那麼將可能不會出現在根節點中。例如下面的例子:

從圖中可以看出,從第乙個非葉子節點開始建立最大堆,最後得到的結果並不是最大堆。而從最後乙個非葉子節點開始建立堆時候,能夠保證該節點的子樹都滿足堆的性質,從而自底向上進行調整堆,最終使得滿足最大堆的性質。

《演算法導論》 第6章堆排序

include include using namespace std 定義結構體,其中包含陣列長alength,堆長heap size。struct dui 返回堆中元素i的父結點的下標 i 2向下取整,即i進行左移一位操作。int parent int i 返回堆中元素i的左孩子的下標 i 2 ...

《演算法導論》筆記 第6章 堆排序

第6章 堆排序 本章主要介紹了堆的基本知識和幾個基本操作過程 堆 二叉 堆資料結構是一種陣列物件,它可以被看作是一棵完全二叉樹。heap size a 即堆的大小是已知的,樹的根結點是a 1 某個i結點,它的父結點parent i 為,左兒子left i 和右兒子right i 的下標可以簡單地計算...

《演算法導論》筆記 第6章 6 4堆排序演算法

反覆將大根堆的根與最後乙個結點交換,堆的大小減一,對根結點執行max heapify維護堆的性質。最終 a 陣列按公升序排列。void heapsort 6.4 1 說明 heapsort 在陣列 a 5,13,2,25,7,17,20,8,4 上的操作過程。6.4 2 討論在使用如下迴圈不變式時,...