快速排序(改進的氣泡排序)
快速排序的基本思想:從記錄中選定一個關鍵字,將待排序記錄分割成兩部分,其中一部分記錄的關鍵字小於選定關鍵字的值,另一部分記錄的關鍵字大於選定關鍵字的值;反覆對分割好的記錄進行上述操作,直到整個序列變為有序序列。
以順序表L = {0,5,1,9,8,3}為例,length = 5,r[0]不參與排序。
快速排序的程式碼如下所示:
1 //交換順序表L中的記錄,使樞軸記錄放到正確位置,並返回其所在位置 2 //交換結束後,樞軸記錄前面的記錄值小於樞軸記錄的值 3 //後面的記錄值大於樞軸記錄的值 4 int Partition(SqList* L, int low, int high) 5{ 6 int pivotkey; 7 pivotkey = L->r[low];//子表的第一個記錄當作樞軸記錄 8 9 while (low < high) 10 { 11 //從右往左,找比樞軸記錄關鍵字的值小的記錄 12 while (low < high && L->r[high] >= pivotkey) 13 high--; 14 swap(L, low, high);//將比樞軸記錄小的記錄交換到低端 15 16 //從左往右,找比樞軸記錄關鍵字的值大的記錄 17 while (low < high && L->r[low] <= pivotkey) 18 low++; 19 swap(L, low, high);//將比樞軸記錄大的記錄交換到高階 20 } 21 22 return low;//返回樞軸所在的位置 23 }
1 //對L->r[low,...,high]做快速排序 2 void QSort(SqList* L, int low, int high) 3 { 4 int pivot;5 if (low < high) 6 { 7 pivot = Partition(L, low, high);//樞軸的位置 8 9 QSort(L, low, pivot - 1);//對L->r[low,...,pivot - 1]遞迴排序 10 QSort(L, pivot + 1, high);//對L->r[pivot,...,high]遞迴排序 11 } 12 }
執行Partition函式後順序表中的記錄變化如下所示:
上述操作使樞軸記錄5到位,並返回其所在位置3,接下來,對低子表{3,1}和高子表{8,9}進行同樣的操作,最終將順序表排序為有序表。
可以從一下幾個方面對快速排序進行優化:
(1)優化樞軸的選取。樞軸記錄的選擇,其值居於待排序記錄的中間最好,然而,將子表的第一個記錄當作樞軸記錄,並不能保證其關鍵字的值居於待排序記錄的中間,所以衍生出了三數取中,九數取中等優化樞軸的選取的方法。我們選擇三數取中法來對快速排序進行優化,即取待排序記錄的左中右三個位置(也可以按其他方式取)的記錄中關鍵字的值居中的記錄作為樞軸記錄。
(2)優化不必要的交換。如上所示,選定的數軸5的最終位置為3,而我們在將樞軸記錄放到該正確的位置的過程中,不斷地調整樞軸的位置,也就是不斷地進行交換,這些交換都是不必要的,我麼可以採用替換操作來代替交換操作,當找到樞軸的位置時,將樞軸值存入該位置。
(3)優化小陣列的排序。當陣列較小時,直接插入排序的效能更好,所以可以在程式碼中加入判斷條件,當陣列較小時,選用直接插入排序;當陣列較大時,選用快速排序。
(4)優化遞迴操作。棧的大小是有限的,且每次遞迴呼叫都會佔用一定的棧空間,函式的引數越多,佔用的棧空間越大,所以可以通過減少遞迴來提高程式的效能。我們採用尾遞迴來減少遞迴次數。
優化的快速排序的程式碼如下所示:
1 //交換順序表L中的記錄,使樞軸記錄放到正確位置,並返回其所在位置 2 //交換結束後,樞軸記錄前面的記錄值小於樞軸記錄的值 3 //後面的記錄值大於樞軸記錄的值 4 int Partition(SqList* L, int low, int high) 5 { 6 int pivotkey; 7 8 /********************************************************/ 9 //優化樞紐的選取 10 int mid = (low + high) / 2; 11 if (L->r[low] > L->r[high]) 12 swap(L, low, high); 13 if (L->r[mid] > L->r[high]) 14 swap(L, high, mid); 15 if (L->r[mid] > L->r[low]) 16 swap(L, mid, low); 17 /********************************************************/ 18 19 pivotkey = L->r[low]; 20 21 /********************************************************/ 22 //優化不必要的交換 23 L->r[0] = pivotkey; 24 /********************************************************/ 25 26 while (low < high) 27 { 28 while (low < high && L->r[high] >= pivotkey) 29 high--; 30 31 /********************************************************/ 32 //優化不必要的交換 33 L->r[low] = L->r[high]; 34 /********************************************************/ 35 36 swap(L, low, high); 37 while (low < high && L->r[low] <= pivotkey) 38 low++; 39 40 /********************************************************/ 41 //優化不必要的交換 42 L -> r[high] = L->r[low]; 43 /********************************************************/ 44 45 swap(L, low, high); 46 } 47 48 /********************************************************/ 49 //優化不必要的交換 50 L->r[low] = L->r[0]; 51 /********************************************************/ 52 53 return low; 54 }
1 //對L->r[low,...,high]做快速排序 2 void QSort(SqList *L,int low,int high) 3 { 4 int pivot; 5 6 /********************************************************/ 7 //優化遞迴操作 8 while (low < high) 9 /********************************************************/ 10 { 11 pivot = Partition(L, low, high);//樞軸的位置 12 13 QSort(L, low, pivot - 1);//對L->r[low,...,pivot - 1]遞迴排序 14 15 /********************************************************/ 16 //優化遞迴操作 17 low = pivot + 1;//尾遞迴 18 /********************************************************/ 19 } 20 }
執行Partition函式後順序表中的記錄變化如下所示:
到此為止,我們介紹完了所有的排序演算法,下一次會對所有的排序演算法進行一個總結,對各種排序演算法進行比較,分析其時間複雜度以及適用情況。接下來會依次介紹查詢演算法,希望給位同儕可以多多關注,給出指導意見,多多交流!
相關連結:
氣泡排序https://www.cnblogs.com/yongjin-hou/p/13858510.html
簡單選擇排序https://www.cnblogs.com/yongjin-hou/p/13859148.html
直接插入排序https://www.cnblogs.com/yongjin-hou/p/13861458.html
希爾排序https://www.cnblogs.com/yongjin-hou/p/13866344.html
堆排序https://www.cnblogs.com/yongjin-hou/p/13873770.html
歸併排序https://www.cnblogs.com/yongjin-hou/p/13921147.html
參考書籍:程傑 著,《大話資料結構》,清華大學出版社。