資料結構與演算法C 版筆記 排序 Sort 下

2022-01-31 14:14:36 字數 3848 閱讀 8920

5、堆排序(heapsort)

在接觸「堆排序」前,先回顧一下資料結構c#版筆記--樹與二叉樹 ,其中提到了「完全二叉樹」有一些重要的數學特性:

上圖就是一顆完全二叉樹,如果每個節點按從上到下,從左至右標上序號,則可以用陣列來實現順性儲存,同時其序號:

1、如果i>1,則序號為i的父結節序號為i/2(這裡/指整除) 言外之意:整個陣列前一半都是父節點,後一半則是葉節點

2、如果2*i<=n(這裡n為整顆樹的節點總數),則序號為i的左子節點序號為2*i

3、如果2*i +1 <=n,則序號為i的右子節點序號為2*i + 1

好了,再來看看"堆(heap)"是個神馬玩意兒?

其實,堆就是一顆完全二叉樹,由上面的知識點回顧可以知道,任意給定乙個陣列,我們就能將它構造成一顆完全二叉樹,也就是建立乙個「堆」--ps:還好業內標準稱它為一堆,而不是一坨 :)

其中,堆又可以分為最大堆與最小堆,下圖就是乙個最大堆:

簡言之:每個(父)節點的值,都比其子節點值大,這樣的堆就稱為最大堆;反過來類推,如果每個(父)節點的值,都比其子節點小,就叫最小堆。

下面該"堆排序"(heapsort)登場了,其思路為:

1、先將給定待排序的陣列通過一定處理,形成乙個「最大堆」

2、然後將根節點(root)與最後乙個序號的節點(lastnode)對換,這樣值最大的根節點,就「沉」到所有節點最後了(也就是墊底了),下輪處理就不用理會它了.

3、因為第2步的操作,剩下的這些節點肯定已經不滿足最大堆的定義了(因為把小值的末節點換成根節點了,它的子節點中肯定會有值比它大的),然後再類似第1步的處理,把這些剩下的節點重新排成「最大堆」

4、重複第2步的操作,將「新最大堆的根節點」與「新最大堆的末結點」(其實就是整個陣列的倒數第二個節點,因為在第一輪處理中,最大值的節點已經沉到最後了,所以新最大堆的最末節點就是整個陣列的倒數第二個節點)對換,這樣第二大的元素也沉到適當的位置了,以後就不用理它了,然後繼續把剩下的節點,重組成最大堆

5、反覆以上過程,直到最後剩下的節點只剩乙個為止(即沒辦法再繼續重組成最大堆),這時排序結束,最後剩下的節點,肯定就是值最小的

假設給定陣列new int ,要求用「堆排序演算法」從小到大排序,上面的演算法描述**為:

理解以上思路後,堆排序就拆分成了二個問題:

a、如何將陣列指定範圍的n個元素建立乙個"最大堆"?

b、如何用一定的演算法,反覆呼叫a中的"最大堆建立"方法,以處理剩下的節點,直到最終只剩乙個元素為止

建立最大堆的演算法,完全依賴於完全二叉樹的數學特性,**如下:

/// /// 建立最大堆

///

/// 待處理的陣列

/// 指定連續待處理元素範圍的下標下限

/// 指定連續待處理元素範圍的下標上限

static void createmaxheap(int arr, int low, int high)

//如果本身節點比子節點小

if (t < arr[j])

else //如果本身已經是最大值了,則說明元素i所對應的子樹,已經是最大堆,則直接跳出迴圈

}//接上面的交換值操作,將最大子節點的元素值替換為t(因為最近的一次if語句中,k=j 了,

//所以這裡的arr[k]其實就是arr[j]=t,即完成了值交換的最後一步,

//當然如果最近一次的if語句為false,根本沒進入,則這時的k仍然是i,維持原值而已)

arr[k] = t;}}

}

反覆呼叫該演算法排序的**:

/// /// 堆排序

///

///

static void heapsort(int arr)

}

點評:這是一種思維方式很獨特的排序方式,時間複雜度跟快速排序類似,也是跟二叉樹有關,為o(nlog2n),同樣它也是一種不穩定的排序。

6、歸併排序演算法(mergesort)

思路:將陣列中的每個元素看作乙個小序列,然後二二合併成乙個有序的新序列(這樣序列個數從n變成了n/2,但是每個小序列的長度從1變成2),然後繼續將這些新序列二二合併,得到n/4個序列(每個序列的長度從2變成4),如此反覆,最終得到乙個全部排列好的完整序列。這也是演算法中"分治法"的經典案例之一,即分而治之。

這裡反覆要用到將二個序列合併為新序列的處理,封裝成以下方法 :

/// /// 歸併處理

///

/// 需要歸併處理的陣列

/// 每段小序列的長度

static void merge(int arr, int len)

else//否則,複製第2個有序表的元素到臨時表

}//經過上面的處理後,如果第1個有序表還有元素

while (i <= high1)

//經過上面的處理後,如果第2個有序表還有元素

while (j <= high2)

low1 = high2 + 1;//將low1"指標"指到「處理完的2個有序表」之後,以方便下面將剩餘未處理完的元素複製到臨時表

}i = low1;

//將尚未處理到的元素複製到臨時表

while (i < arr.length)

//將臨時表的元素複製到原陣列

for (i = 0; i < arr.length; ++i)

}

排序處理:

/// /// 歸併排序

///

///

static void mergesort(int arr)}}

點評:倆倆合併,又是跟2有關的,哈哈,計算機領域真的很2啊!所以其時間複雜度又是o(nlog2n),但是該演算法需要很多的臨時陣列,所以其空間複雜度較其它演算法都要大一些為o(n),此外它是穩定的排序方法。

排序方法小結:(原書的小結還算不錯,就懶得自己再寫了,直接從原電子書上copy過來記錄下)

排序在計算機程式設計中非常重要,上面介紹的各種排序方法各有優缺點,適用的場合也各不相同。

在選擇排序方法時應考慮的因素有:

(1)待排序記錄的數目 n 的大小;

(2)記錄本身除關鍵碼外的其它資訊量的大小;

(3)關鍵碼(即元素值)的情況;

(4)對排序穩定性的要求;

(5)語言工具的條件,輔助空間的大小等。

綜合考慮以上因素,可以得出如下結論:

(1)若排序記錄的數目 n 較小(如 n≤50)時,可採用直接插入排序或簡單選擇排序。由於直接插入排序所需的記錄移動操作較簡單選擇排序多,因而當記錄本身資訊量較大時,用簡單選擇排序比較好。

(2)若記錄的初始狀態已經按關鍵碼基本有序,可採用直接插入排序或氣泡排序。

(3)若排序記錄的數目n較大,則可採用時間複雜度為o(nlog2n)的排序方法(如快速排序、堆排序或歸併排序等)。

快速排序的平均效能最好,在待排序序列已經按關鍵碼隨機分布時,快速排序最適合。快速排序在最壞情況下的時間複雜度是o(n2),而堆排序在最壞情況下的時間複雜度不會發生變化,並且所需的輔助空間少於快速排序。但這兩種排序方法都是不穩定的排序,若需要穩定的排序方法,則可採用歸併排序。

(4)前面討論的排序演算法,都是採用順序儲存結構。在待排序的記錄非常多時,為避免花大量的時間進行記錄的移動,可以採用鏈式儲存結構。直接插入排序和歸併排序都可以非常容易地在鍊錶上實現,但快速排序和堆排序卻很難在鍊錶上實現。此時,可以提取關鍵碼建立索引表,然後對索引表進行排序。也可以引入乙個整形陣列 t[n]作為輔助表,排序前令t[i]=i,1≤i≤n。若排序演算法中要求交換記錄 r[i]和 r[j],則只須交換 t[i]和 t[j]即可。排序結束後,陣列 t[n]就存放了記錄之間的順序關係

資料結構與演算法C 版(筆記)

跳躍鍊錶 跳躍鍊錶主要是為了解決單鏈表和雙鏈表查詢複雜的問題提出的。其時間複雜度為o lgn 主要的操作時查詢和插入 一 查詢 目的 在跳躍表中查詢乙個元素x 在跳躍表中查詢乙個元素x,按照如下幾個步驟進行 i 從最上層的鏈 sh 的開頭開始 ii 假設當前位置為p,它向右指向的節點為q p與q不一...

資料結構與演算法筆記(十二)排序

就是讓一組無序資料變成有序的過程。一般預設這裡的有序都是從小到大的排列順序 衡量乙個排序演算法的優劣,主要從三個角度分析 時間複雜度 包括最好時間複雜度 最快時間複雜度 平均時間複雜度 空間複雜度 如果空間複雜度為1,也叫原地排序 穩定度 是指相等的資料物件,在排序之後,順序能否保持不變 1.原理 ...

資料結構與演算法 C 氣泡排序

1 原理 以此比較相鄰的兩個元素,每次比較完畢最大的乙個字跑到本輪的末尾。目的 按從小到大排序。方法 假設存在陣列 72,54,59,30,31,78,2,77,82,72 第一輪比較相鄰兩個元素,如果左邊元素大於右邊元素,則交換。72和54比較的結果就是,54在前,72在後 然後72和59比較的結...