常見的五類排序算法圖解和實現(交換類:冒泡排序,遞歸的快速排序)


冒泡排序算法:

總的來說就是兩兩交換,反復直到有序,第一個記錄和第二個記錄,若逆序則交換,然后比較第二個和第三個記錄,以此類推,直到第 n 個記錄和第 n-1個記錄比較完畢為止,第一趟排序,結果關鍵字最大的記錄被安排在最后一個位置。對前 n-1個記錄繼續冒泡排序,使得關鍵字次大的記錄安排在第 n-1個位置。如此重復,直到沒有需要交換的記錄為止(僅僅是第一個和第二個交換過為止)。整個一趟趟的選出最值的過程,仿佛是那水里的氣泡,咕嘟咕嘟的往上翻的過程。

遞增冒泡排序過程圖解:

一般先比較第一個元素和第二個元素,49大於38,則交換之,且需要提前取出元素49到臨時存放處

            

移動元素,實現兩個數的交換

 

繼續兩兩比較,49小於65,無需交換,65小於97,無需交換,比較97和76,交換,97和13比較交換,97和27比較交換,97和49比較交換。

第一趟排序完畢,把最大的97元素冒泡到了最后。接下來繼續第二趟排序,每次都是如此往復。每次都得到本趟次排序的最值。

代碼如下;

 1 //遞增冒泡排序算法實現
 2 void bubbleSort(int list[], int len)
 3 {
 4     int i = len;
 5     int j = 0;
 6     int k = 0;
 7     //排序終止的條件
 8     while (i > 1) {
 9         //每趟排序的過程
10         for (j = 0; j < i; j++) {
11             //核心交換算法
12             if (list[j] > list[j + 1]) {
13                 list[j] = list[j] + list[j + 1];
14                 list[j + 1] = list[j] - list[j + 1];
15                 list[j] = list[j] - list[j + 1];
16                 //交換完畢,最值移動到了末位,需要記錄下最新的位置
17                 k = j;
18             }//end of if
19         }//end of for
20         //更新每趟排序過程中的 i 值
21         i = k;
22     }//end of while
23 }
24 
25 int main(void)
26 {
27     int source[8] = {49, 38, 65, 97, 76, 13, 27, 49};
28     
29     bubbleSort(source, 8);
30     
31     for (int i = 0; i < 8; i++) {
32         printf(" %d ", source[i]);
33     }
34     
35     return 0;
36 }

13  27  38  49  49  65  76  97 Program ended with exit code: 0

一般情況下每經過一趟“起泡”,“ i 減 1”,但並不是每趟都如此。

冒泡排序算法的時間復雜度: 
最好情況(正序), 比較次數:n -1        移動次數:0        T(n) = O(n) 
最壞情況(逆序), 比較次數:       移動次數:

平均時間復雜度:T(n) = O(n2) 

空間復雜度:
S(n) = O(1) 

冒泡排序的穩定性:
穩定排序 

 

 

快速排序算法

基本思想:任選一個記錄,以它的關鍵字作為“樞軸”,凡關鍵字小於樞軸的記錄均移至樞軸之前,凡關鍵字大於樞軸的記錄均移至樞軸之后。(整個過程是交替掃描和交換的過程),這個記錄開始選取的時候,一般選取第一個記錄作為樞軸。做法是附設兩個指針 low 和 high,從 high 所指位置起向前搜索找到第一個關鍵字小於樞軸的關鍵字的記錄與樞軸記錄交換,然后從 low  所指位置起向后搜索找到第一個關鍵字大於樞軸的關鍵字的記錄與樞軸記錄交換,重復這兩步直至 low = high 為止。 

遞增排序的圖解如下:

初始狀態,第一趟排序,一般第一個記錄為樞軸,這里是52

因為是遞增排序,high 往前掃描,找第一個和52比較,小於52的記錄,即23

把樞軸52臨時存儲在 temp 處,把23交換到樞軸之前

high 掃描交換之后,馬上換到 low,low 從前面開始掃描,找到第一個和52比較,大於52的記錄,即80。

交換

轉到 high,掃描,交換14

到 low,掃描,沒有發現比52大的,直到 low 和 high 重合

最后把 temp 里的樞軸記錄,存放到重合的位置即可

這樣就完成了一趟快速排序,注意是一趟而已。

一趟快速排序代碼如下:

 1 //一次划分的快速排序算法
 2 int partitionSort(int l[], int low, int high)
 3 {
 4     //第一次快速排序,一般把第一個記錄看成是樞軸記錄,臨時存放
 5     int temp = l[low];
 6     //設置樞軸記錄
 7     int pivot = l[low];
 8     //掃描和比較終止的條件low=high
 9     while (low < high) {
10         //先從 high 開始向前掃描第一個比樞軸記錄小的記錄,交換
11         while (low < high && l[high] > pivot) {
12             //找不到就繼續掃描
13             high--;
14         }
15         //找到了,把比樞軸記錄小的,交換到低端
16         l[low] = l[high];
17         //循環交替掃描,從 low 開始向后掃描找第一個比樞軸大的記錄,交換
18         while (low < high && l[low] <= pivot) {
19             //找不到就繼續掃描
20             low++;
21         }
22         //找到了,把比樞軸記錄大的,交換到高端
23         l[high] = l[low];
24     }//end of while
25     //一次排序終止,把樞軸記錄放到終止位置
26     l[low] = temp;
27     //返回樞軸所在位置
28     return low;//返回high也可以
29 }
30 
31 int main(void)
32 {
33     int source[10] = {52, 49, 80, 36, 14, 58, 61, 97, 23, 75};
34     
35     partitionSort(source, 0, 9);
36     
37     for (int i = 0; i < 10; i++) {
38         printf(" %d ", source[i]);
39     }
40     
41     return 0;
42 }

23  49  14  36  52  58  61  97  80  75 Program ended with exit code: 0

 

繼續探究快速排序算法

首先對無序的記錄序列進行“一次划分”,之后分別對分割所得兩個子序列“遞歸”進行一趟快速排序。序列越是無序,快速排序的效果越好。

這里顯然有遞歸的思想在里面,完整代碼如下

 1 //一次划分的快速排序算法
 2 int partitionSort(int l[], int low, int high)
 3 {
 4     //第一次快速排序,一般把第一個記錄看成是樞軸記錄,臨時存放
 5     int temp = l[low];
 6     //設置樞軸記錄
 7     int pivot = l[low];
 8     //掃描和比較終止的條件low=high
 9     while (low < high) {
10         //先從 high 開始向前掃描第一個比樞軸記錄小的記錄,交換
11         while (low < high && l[high] > pivot) {
12             //找不到就繼續掃描
13             high--;
14         }
15         //找到了,把比樞軸記錄小的,交換到低端
16         l[low] = l[high];
17         //循環交替掃描,從 low 開始向后掃描找第一個比樞軸大的記錄,交換
18         while (low < high && l[low] <= pivot) {
19             //找不到就繼續掃描
20             low++;
21         }
22         //找到了,把比樞軸記錄大的,交換到高端
23         l[high] = l[low];
24     }//end of while
25     //一次排序終止,把樞軸記錄放到終止位置
26     l[low] = temp;
27     //返回樞軸所在位置
28     return low;//返回high也可以
29 }
30 
31 //遞歸的思想完成完整的快速排序算法
32 void quickSort(int l[], int low, int high)
33 {
34     //對長度大於1的順序表進行快速排序,先進行一次分割的划分
35     if (low < high) {
36         int pivot = partitionSort(l, low, high);
37         //遞歸的分別對子序列進行一次划分快速排序的過程
38         quickSort(l, low, pivot - 1);
39         quickSort(l, pivot + 1, high);
40     }
41 }
42 
43 int main(void)
44 {
45     int source[10] = {52, 49, 80, 36, 14, 58, 61, 97, 23, 75};
46     
47     quickSort(source, 0, 9);
48     
49     for (int i = 0; i < 10; i++) {
50         printf(" %d ", source[i]);
51     }
52     
53     return 0;
54 }

注意:第一次調用函數 quickSort時,待排序記錄序列的上、下界分別為 1 和 L.length。

 

快速排序算法時間復雜度分析

假設一次划分所得樞軸位置 i=k,則對 n 個記錄進行快排所需時間:

T(n) = Tpass(n) + T(k-1) + T(n-k),其中 Tpass(n) 為對 n 個記錄進行一次划分所需時間。

若待排序列中記錄的關鍵字是隨機分布的,則 k 取 1 至 n 中任一值的可能性相同。由此可得快速排序所需時間的平均值為:

設 Tavg(1)≤b,則可得結果:

快速排序時間復雜度結論:

快速排序的時間復雜度為 O(n log2(n))。注意:快速排序的一次划分所需時間和表長成正比,到目前為止快速排序是平均速度最大的一種排序方法。排序趟數和初試序列有關。

快速排序的空間復雜度

O(log2(n))

快速排序的蛻化特點:

在最壞的情況,即待排序序列已經按關鍵字“正序”排列的情況下,每次划分只得到一個比上一次少一個對象的子序列。這樣,必須經過 n-1 趟才能把所有對象定位,而且第 i 趟需要經過 n-i 次關鍵碼比較才能找到第 i 個對象的安放位置,快速排序將蛻化為起泡排序,其最壞的時間復雜度為 O(n2),所以快速排序適用於原始記錄排列雜亂無章的情況。

快速排序退化的例子

用第一個對象作為基准對象     

改進方法:

為避免出現快速排序蛻化為冒泡排序的情況,需在進行一次划分之前,進行“預處理”

即:先對 L(s),  L(t) 和 L[(s+t)/2],其中開始的時候,s=0,t=length,進行相互比較,然后取關鍵字的大小為中間的記錄為樞軸記錄。

 
快速排序的穩定性:
不穩定的排序,在遞歸調用時需要占據一定的存儲空間( 遞歸棧)用來保存每一層遞歸調用時的必要信息。 對於 n 較大的平均情況而言,快速排序是“快速”的,但是當 n 很小時,這種排序方法往往比其它簡單排序方法還要慢。

 

歡迎關注

 

dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM