資料結構第十節(排序(下))
排序
快速排序
快速排序的思想同規定排序一樣,也是一種分而治之的思想。
不同的是,在快速排序中,分的方法如何分。
快速排序的邏輯思路
1.首先從集合中選出一個元素,把大於他的元素和小於他的元素分別分成2組,例如{1,3,5,9,6,2,4},選擇元素4為分割點,整個集合被分為了{1,3,2}∪{4}∪{5,6,9}。(我們不難發現,其實我們所選出的那個截斷點,也已經確定了他在徹底排好序的位置,在這個例子中一定是第4個元素)。
2.接著上面的例子,我們已經得到了兩個分出來的集合,遞迴的再對這兩個集合進行操作下去,就會使整個陣列有序。
那麼快速排序問題的關鍵就在於如何很快的把一個集合以一個合適的點分成2塊。首先要做的就是選取分割點,然後是根據分割點將集合劃分為2塊。
選取分割點最常規的做法是"取中",即每次對於一個集合,取他的左邊界和右邊界和中間的中位數。例如上面的例子中,左邊界為\(1\),右邊界\(4\),中間值\(9\),我們選擇這三個數字的中位數4作為分割點,整個集合被分為了{1,3,2}∪{4}∪{5,6,9}。
集合和劃分為兩塊常用的做法為,先將左邊鍵中間值右邊界調整好順序,選取中間值做分割點,並且將其於右邊界減一位置的元素交換。之後進入死迴圈,迴圈的,從左邊開始找大於等於分割點的元素low,從右邊開始找小於等於分割點的元素high,如果找到的這2個點low<high,交換他們,否則代表已經成功分割,退出迴圈。
注意到在需要排的元素不多時,快速排序的效能甚至不如插入排序,我們設定一個閾值,如果左右邊界相差大於100則進行快速排序,否則直接插入排序。
快速排序的程式碼實現
//插入排序 void Insertion_Sort(int array[], int n) { for (int i = 1; i < n; i++) { int temp = array[i]; int j = 0; for (j = i; j > 0 && array[j - 1] > temp; j--) { array[j] = array[j - 1]; } array[j] = temp; } } //交換 void Swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } //尋找分割點 int Mid(int array[],int left,int right) { int mid = (left + right) / 2; if (array[mid] < array[left]) { Swap(&array[mid], &array[left]); } if (array[right] < array[mid]) { Swap(&array[mid], &array[right]); } if (array[mid] < array[left]) { Swap(&array[mid], &array[left]); } //此時已達到array[left]<=array[mid]<=array[right] //將array[mid]和array[right-1]交換 Swap(&array[mid], &array[right-1]); return array[right - 1]; } //遞迴迭代的快速排序 void QuickSort(int array[], int left, int right) { //如果足夠大進行快排,否則直接插入排序 if (right - left >= 100) { int cutP = Mid(array, left, right); int low = left, high = right-1; while (true) { while (array[++low] < cutP); while (array[--high] > cutP); if (low < high) { Swap(&array[high], &array[low]); } else { break; } } Swap(&array[right-1], &array[low]); QuickSort(array,left,low-1); QuickSort(array, low+1,right); } else { Insertion_Sort(array+left, right - left + 1); } } //快速排序 void Quick_Sort(int array[],int n) { QuickSort(array, 0, n - 1); }
表排序
在對結構體做排序時,我們可以構建一個指標陣列,排序交換隻交換指標,排序結束再物理排序。
基數排序
桶排序
一種特殊的排序演算法,可以將排序複雜度降低至\(O(N)\),但是對資料有要求,假如給定我們一個學校4萬名學生的英語成績,我們都知道這成績不可能大於100也不可能為負的。我們可以構建一個數組,每次讀到一個數,就以該數字為下標的陣列元素+1。輸出時從下標0遍歷到100,每次輸出所儲存值的下標次數個下標數字。
我們可以發現,當給定的資料範圍過大時,該方法就不靈了,例如隨機給你100個介於0~1億之間的數排序。
次位優先的基術排序
對於所有的輸入資料,首先先去他們的個位大小進行排序,依次放到對應的桶裡,然後接著按照十位大小進行排序,依次放到對應的桶裡,直到到達包含的數字的最高位數,便實現了一次基數排序。