比較排序算法分類
比較排序(Comparison Sort)通過對數組中的元素進行比較來實現排序。
注:關於 Memory,如果算法為 "in place" 排序,則僅需要 O(1) 內存;有時對於額外的 O(log(n)) 內存也可以稱為 "in place"。
注:Microsoft .NET Framework 中 Array.Sort 方法的實現使用了內省排序(Introspective Sort)算法。
Stable 與 Not Stable 的比較
穩定排序算法會將相等的元素值維持其相對次序。如果一個排序算法是穩定的,當有兩個有相等的元素值 R 和 S,且在原本的列表中 R 出現在 S 之前,那么在排序過的列表中 R 也將會是在 S 之前。

O(n2) 與 O(n*logn) 的比較

合並排序和堆排序在最壞情況下達到上界 O(n*logn),快速排序在平均情況下達到上界 O(n*logn)。對於比較排序算法,我們都能給出 n 個輸入的數值,使算法以 Ω(n*logn) 時間運行。
注:有關算法復雜度,可參考文章《算法復雜度分析》。有關常用數據結構的復雜度,可參考文章《常用數據結構及復雜度》。
冒泡排序(Bubble Sort)
算法描述
重復地比較要排序的數列,一次比較兩個元素,如果后者較小則與前者交換元素。
- 比較相鄰的元素,如果前者比后者大,則交換兩個元素。
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。
- 針對所有的元素重復以上的步驟,除了最后一個。

算法復雜度
冒泡排序對 n 個元素需要 O(n2) 的比較次數,且可以原地排序。冒泡排序僅適用於對於含有較少元素的數列進行排序。
- 最差時間復雜度 O(n2)
- 平均時間復雜度 O(n2)
- 最優時間復雜度 O(n)
- 最差空間復雜度 O(n),輔助空間 O(1)
示例代碼
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 }; 6 7 OptimizedBubbleSort(unsorted); 8 9 foreach (var key in unsorted) 10 { 11 Console.Write("{0} ", key); 12 } 13 14 Console.Read(); 15 } 16 17 static void BubbleSort(int[] unsorted) 18 { 19 for (int i = 0; i < unsorted.Length; i++) 20 { 21 for (int j = 0; j < unsorted.Length - 1 - i; j++) 22 { 23 if (unsorted[j] > unsorted[j + 1]) 24 { 25 int temp = unsorted[j]; 26 unsorted[j] = unsorted[j + 1]; 27 unsorted[j + 1] = temp; 28 } 29 } 30 } 31 } 32 33 static void OptimizedBubbleSort(int[] unsorted) 34 { 35 int exchange = unsorted.Length - 1; 36 while (exchange > 0) 37 { 38 int lastExchange = exchange; 39 exchange = 0; 40 41 for (int i = 0; i < lastExchange; i++) 42 { 43 if (unsorted[i] > unsorted[i + 1]) 44 { 45 int temp = unsorted[i]; 46 unsorted[i] = unsorted[i + 1]; 47 unsorted[i + 1] = temp; 48 49 exchange = i; 50 } 51 } 52 } 53 } 54 }
雞尾酒排序(Cocktail Sort)
算法描述
雞尾酒排序,也就是雙向冒泡排序(Bidirectional Bubble Sort),是冒泡排序的一種變形。此算法與冒泡排序的不同處在於排序時是以雙向在序列中進行排序。如果序列中的大部分元素已經排序好時,可以得到比冒泡排序更好的性能。

算法復雜度
- 最差時間復雜度 O(n2)
- 平均時間復雜度 O(n2)
- 最優時間復雜度 O(n)
- 最差空間復雜度 О(1)
代碼示例
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 }; 6 7 OptimizedCocktailSort(unsorted); 8 9 foreach (var key in unsorted) 10 { 11 Console.Write("{0} ", key); 12 } 13 14 Console.Read(); 15 } 16 17 static void CocktailSort(int[] unsorted) 18 { 19 for (int i = 0; i < unsorted.Length / 2; i++) 20 { 21 // move the larger to right side 22 for (int j = i; j + 1 < unsorted.Length - i; j++) 23 { 24 if (unsorted[j] > unsorted[j + 1]) 25 { 26 int temp = unsorted[j]; 27 unsorted[j] = unsorted[j + 1]; 28 unsorted[j + 1] = temp; 29 } 30 } 31 32 // move the smaller to left side 33 for (int j = unsorted.Length - i - 1; j > i; j--) 34 { 35 if (unsorted[j - 1] > unsorted[j]) 36 { 37 int temp = unsorted[j - 1]; 38 unsorted[j - 1] = unsorted[j]; 39 unsorted[j] = temp; 40 } 41 } 42 } 43 } 44 45 static void OptimizedCocktailSort(int[] unsorted) 46 { 47 bool swapped = false; 48 int start = 0; 49 int end = unsorted.Length - 1; 50 do 51 { 52 swapped = false; 53 54 // move the larger to right side 55 for (int i = start; i < end; i++) 56 { 57 if (unsorted[i] > unsorted[i + 1]) 58 { 59 int temp = unsorted[i]; 60 unsorted[i] = unsorted[i + 1]; 61 unsorted[i + 1] = temp; 62 63 swapped = true; 64 } 65 } 66 67 // we can exit the outer loop here if no swaps occurred. 68 if (!swapped) break; 69 swapped = false; 70 end = end - 1; 71 72 // move the smaller to left side 73 for (int j = end; j > start; j--) 74 { 75 if (unsorted[j - 1] > unsorted[j]) 76 { 77 int temp = unsorted[j]; 78 unsorted[j] = unsorted[j - 1]; 79 unsorted[j - 1] = temp; 80 81 swapped = true; 82 } 83 } 84 85 start = start + 1; 86 } 87 while (swapped); 88 } 89 }
奇偶排序(Odd-Even Sort)
奇偶排序通過比較數組中相鄰的(奇-偶)位置元素,如果該奇偶元素對是錯誤的順序(前者大於后者),則交換元素。然后再針對所有的(偶-奇)位置元素進行比較。如此交替進行下去。

算法復雜度
- 最差時間復雜度 O(n2)
- 平均時間復雜度 O(n2)
- 最優時間復雜度 O(n)
- 最差空間復雜度 О(1)
代碼示例
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 }; 6 7 OptimizedOddEvenSort(unsorted); 8 9 foreach (var key in unsorted) 10 { 11 Console.Write("{0} ", key); 12 } 13 14 Console.Read(); 15 } 16 17 static void OddEvenSort(int[] unsorted) 18 { 19 for (int i = 0; i < unsorted.Length; ++i) 20 { 21 if (i % 2 > 0) 22 { 23 for (int j = 2; j < unsorted.Length; j += 2) 24 { 25 if (unsorted[j] < unsorted[j - 1]) 26 { 27 int temp = unsorted[j - 1]; 28 unsorted[j - 1] = unsorted[j]; 29 unsorted[j] = temp; 30 } 31 } 32 } 33 else 34 { 35 for (int j = 1; j < unsorted.Length; j += 2) 36 { 37 if (unsorted[j] < unsorted[j - 1]) 38 { 39 int temp = unsorted[j - 1]; 40 unsorted[j - 1] = unsorted[j]; 41 unsorted[j] = temp; 42 } 43 } 44 } 45 } 46 } 47 48 static void OptimizedOddEvenSort(int[] unsorted) 49 { 50 bool swapped = true; 51 int start = 0; 52 53 while (swapped || start == 1) 54 { 55 swapped = false; 56 57 for (int i = start; i < unsorted.Length - 1; i += 2) 58 { 59 if (unsorted[i] > unsorted[i + 1]) 60 { 61 int temp = unsorted[i]; 62 unsorted[i] = unsorted[i + 1]; 63 unsorted[i + 1] = temp; 64 65 swapped = true; 66 } 67 } 68 69 if (start == 0) start = 1; 70 else start = 0; 71 } 72 } 73 }
快速排序(Quick Sort)
快速排序使用分治法(Divide-and-Conquer)策略將一個數列分成兩個子數列並使用遞歸來處理。
比如有如下這個 10 個數字,[13, 81, 92, 42, 65, 31, 57, 26, 75, 0]。

隨機選擇一個數作為中間的元素,例如選擇 65。

這樣數組就被 65 分成了兩部分,左邊的都小於 65,右邊的都大於 65。

然后分別對左右兩邊的子數組按照相同的方式進行排序,並最終排序完畢。

算法描述
- 從數列中挑出一個元素,稱為 "主元"(pivot)。
- 重新排序數列,所有元素比主元小的擺放在主元前面,所有元素比主元值大的擺在主元的后面(相同的數可以到任一邊)。這個稱為分區(partition)操作。在分區退出之后,該主元就處於數列的中間位置。
- 遞歸地(recursively)把小於主元值元素的子數列和大於主元值元素的子數列排序。
遞歸的最底部情形,是數列的大小是 0 或 1 ,也就是總是被排序好的狀況。這樣一直遞歸下去,直到算法退出。

下面的過程實現快速排序,調用 QUICKSORT(A, 1, length[A])。
1 QUICKSORT(A, p, r) 2 if p < r 3 then q <- PARTITION(A, p, r) 4 QUICKSORT(A, p, q - 1) 5 QUICKSORT(A, q + 1, r)
快速排序算法的關鍵是 Partition 過程,它對子數組進行就是重排。
1 PARTITION(A, p, r) 2 x <- A[r] 3 i <- p - 1 4 for j <- p to r - 1 5 do if A[j] <= x 6 then i <- i + 1 7 exchange A[i] <-> A[j] 8 exchange A[i + 1] <-> A[r] 9 return i + 1
算法復雜度
- 最差時間復雜度 O(n2)
- 平均時間復雜度 O(n*log n)
- 最優時間復雜度 O(n*log n)
- 最差空間復雜度 根據實現的方式不同而不同 O(n) 輔助空間 O(log n)
快速排序的運行時間與划分是否對稱有關,而后者又與選擇了哪一個元素來進行划分有關。如果划分是對稱的,那么快速排序從漸進意義上來講,就與合並算法一樣快;如果划分是不對稱的,那么從漸進意義上來講,就與插入排序一樣慢。
快速排序的平均運行時間與其最佳情況運行時間很接近,而不是非常接近於最差情況運行時間。
QUICKSORT 的運行時間是由花在過程 PARTITION 上的時間所決定的。每當 PARTITION 過程被調用時,就要選出一個 Pivot 元素。后續對 QUICKSORT 和 PARTITION 的各次遞歸調用中,都不會包含該元素。於是,在快速排序算法的整個執行過程中,至多只可能調用 PARTITION 過程 n 次。
快速排序的隨機化版本
快速排序的隨機化版本是對足夠大的輸入的理想選擇。
RANDOMIZED-QUICKSORT 的平均運行情況是 O(n lg n),如果在遞歸的每一層上,RANDOMIZED-PARTITION 所作出的划分使任意固定量的元素偏向划分的某一邊,則算法的遞歸樹深度為 Θ(lg n),且在每一層上所做的工作量都為 O(n)。
1 RANDOMIZED-PARTITION(A, p, r) 2 i <- RANDOM(p, r) 3 exchange A[r] <-> A[i] 4 return PARTITION(A, p, r)
算法比較
快速排序是二叉查找樹的一個空間優化版本。但其不是循序地把數據項插入到一個顯式的樹中,而是由快速排序組織這些數據項到一個由遞歸調用所隱含的樹中。這兩個算法完全地產生相同的比較次數,但是順序不同。
快速排序的最直接競爭者是堆排序(Heap Sort)。堆排序通常會慢於原地排序的快速排序,其最壞情況的運行時間總是 O(n log n) 。快速排序通常情況下會比較快,但仍然有最壞情況發生的機會。
快速排序也會與合並排序(Merge Sort)競爭。合並排序的特點是最壞情況有着 O(n log n) 運行時間的優勢。不像快速排序或堆排序,合並排序是一個穩定排序算法,並且非常的靈活,其設計可以應用於操作鏈表,或大型鏈式存儲等,例如磁盤存儲或網路附加存儲等。盡管快速排序也可以被重寫使用在鏈表上,但對於基准的選擇總是個問題。合並排序的主要缺點是在最佳情況下需要 O(n) 額外的空間,而快速排序的原地分區和尾部遞歸僅使用 O(log n) 的空間。
代碼示例
1 class Program 2 { 3 static bool isPrintArrayEnabled = false; 4 5 static void Main(string[] args) 6 { 7 int[] smallSeed = { 4, 1, 5, 2, 6, 3, 7, 9, 8, 0 }; 8 9 MeasureQuickSort(smallSeed, 1000000); 10 MeasureRandomizedQuickSort(smallSeed, 1000000); 11 MeasureOptimizedQuickSorts(smallSeed, 1000000); 12 13 Console.Read(); 14 } 15 16 static void MeasureQuickSort(int[] smallSeed, int arrayLength) 17 { 18 int[] unsorted = GenerateBigUnsortedArray(smallSeed, arrayLength); 19 20 Stopwatch watch = Stopwatch.StartNew(); 21 22 QuickSort(unsorted, 0, unsorted.Length - 1); 23 24 watch.Stop(); 25 26 Console.WriteLine( 27 "ArrayLength[{0}], QuickSort ElapsedMilliseconds[{1}]", 28 unsorted.Length, watch.ElapsedMilliseconds); 29 30 PrintArray(unsorted); 31 } 32 33 static void MeasureRandomizedQuickSort(int[] smallSeed, int arrayLength) 34 { 35 int[] unsorted = GenerateBigUnsortedArray(smallSeed, arrayLength); 36 37 Stopwatch watch = Stopwatch.StartNew(); 38 39 RandomizedQuickSort(unsorted, 0, unsorted.Length - 1); 40 41 watch.Stop(); 42 43 Console.WriteLine( 44 "ArrayLength[{0}], RandomizedQuickSort ElapsedMilliseconds[{1}]", 45 unsorted.Length, watch.ElapsedMilliseconds); 46 47 PrintArray(unsorted); 48 } 49 50 static void QuickSort(int[] unsorted, int left, int right) 51 { 52 // left 為子數列的最左邊元素 53 // right 為子數列的最右邊元素 54 if (!(left < right)) return; 55 56 // Partition: 57 // 所有元素比主元值小的擺放在主元的左邊, 58 // 所有元素比主元值大的擺放在主元的右邊 59 int pivotIndex = Partition(unsorted, left, right); 60 61 // Recursively: 62 // 分別排列小於主元的值和大於主元的值的子數列 63 // 主元無需參加下一次排序 64 QuickSort(unsorted, left, pivotIndex - 1); 65 QuickSort(unsorted, pivotIndex + 1, right); 66 } 67 68 static int Partition(int[] unsorted, int left, int right) 69 { 70 int pivotIndex = right; 71 72 // 哨兵 73 int sentinel = unsorted[right]; 74 75 // 子數組長度為 right - left + 1 76 int i = left - 1; 77 for (int j = left; j <= right - 1; j++) 78 { 79 if (unsorted[j] <= sentinel) 80 { 81 i++; 82 Swap(unsorted, i, j); 83 } 84 } 85 86 Swap(unsorted, i + 1, pivotIndex); 87 88 return i + 1; 89 } 90 91 static void RandomizedQuickSort(int[] unsorted, int left, int right) 92 { 93 // left 為子數列的最左邊元素 94 // right 為子數列的最右邊元素 95 if (!(left < right)) return; 96 97 // Partition: 98 // 所有元素比主元值小的擺放在主元的左邊, 99 // 所有元素比主元值大的擺放在主元的右邊 100 int pivotIndex = RandomizedPartition(unsorted, left, right); 101 102 // Recursively: 103 // 分別排列小於主元的值和大於主元的值的子數列 104 // 主元無需參加下一次排序 105 RandomizedQuickSort(unsorted, left, pivotIndex - 1); 106 RandomizedQuickSort(unsorted, pivotIndex + 1, right); 107 } 108 109 static int RandomizedPartition(int[] unsorted, int left, int right) 110 { 111 int i = random.Next(left, right); 112 Swap(unsorted, i, right); 113 return Partition(unsorted, left, right); 114 } 115 116 static void Swap(int[] unsorted, int i, int j) 117 { 118 int temp = unsorted[i]; 119 unsorted[i] = unsorted[j]; 120 unsorted[j] = temp; 121 } 122 123 static void MeasureOptimizedQuickSorts(int[] smallSeed, int arrayLength) 124 { 125 foreach (var pivotSelection in 126 Enum.GetValues(typeof(QuickSortPivotSelectionType))) 127 { 128 int[] unsorted = GenerateBigUnsortedArray(smallSeed, arrayLength); 129 130 Stopwatch watch = Stopwatch.StartNew(); 131 132 OptimizedQuickSort(unsorted, 0, unsorted.Length - 1, 133 (QuickSortPivotSelectionType)pivotSelection); 134 135 watch.Stop(); 136 137 Console.WriteLine( 138 "ArrayLength[{0}], " 139 + "QuickSortPivotSelectionType[{1}], " 140 + "ElapsedMilliseconds[{2}]", 141 unsorted.Length, 142 (QuickSortPivotSelectionType)pivotSelection, 143 watch.ElapsedMilliseconds); 144 145 PrintArray(unsorted); 146 } 147 } 148 149 static int[] GenerateBigUnsortedArray(int[] smallSeed, int arrayLength) 150 { 151 int[] bigSeed = new int[100]; 152 for (int i = 0; i < bigSeed.Length; i++) 153 { 154 bigSeed[i] = 155 smallSeed[i % smallSeed.Length] 156 + i / smallSeed.Length * 10; 157 } 158 159 int[] unsorted = new int[arrayLength]; 160 for (int i = 0; i < unsorted.Length / bigSeed.Length; i++) 161 { 162 Array.Copy(bigSeed, 0, unsorted, i * bigSeed.Length, bigSeed.Length); 163 } 164 165 return unsorted; 166 } 167 168 static void OptimizedQuickSort(int[] unsorted, int left, int right, 169 QuickSortPivotSelectionType pivotSelection) 170 { 171 // left 為子數列的最左邊元素 172 // right 為子數列的最右邊元素 173 if (!(left < right)) return; 174 175 // Partition: 176 // 所有元素比主元值小的擺放在主元的左邊, 177 // 所有元素比主元值大的擺放在主元的右邊 178 Tuple<int, int> pivotPair = 179 OptimizedPartition(unsorted, left, right, pivotSelection); 180 181 // Recursively: 182 // 分別排列小於主元的值和大於主元的值的子數列 183 // 主元無需參加下一次排序 184 OptimizedQuickSort(unsorted, left, pivotPair.Item1 - 1, pivotSelection); 185 OptimizedQuickSort(unsorted, pivotPair.Item2 + 1, right, pivotSelection); 186 } 187 188 static Tuple<int, int> OptimizedPartition( 189 int[] unsorted, int left, int right, 190 QuickSortPivotSelectionType pivotSelection) 191 { 192 int pivotIndex = SelectPivot(unsorted, left, right, pivotSelection); 193 int sentinel = unsorted[pivotIndex]; 194 195 // 子數組長度為 right - left + 1 196 int i = left - 1; 197 int j = right + 1; 198 while (true) 199 { 200 while (unsorted[++i] < sentinel) ; 201 while (unsorted[--j] > sentinel) ; 202 if (i >= j) break; 203 204 // 在主元左側找到一個大於主元值的位置 i, 205 // 在主元右側找到一個小於主元值的位置 j, 206 // 交換兩個值 207 Swap(unsorted, i, j); 208 } 209 210 return new Tuple<int, int>(i, j); 211 } 212 213 static int SelectPivot(int[] unsorted, int left, int right, 214 QuickSortPivotSelectionType pivotSelection) 215 { 216 switch (pivotSelection) 217 { 218 case QuickSortPivotSelectionType.FirstAsPivot: 219 return left; 220 case QuickSortPivotSelectionType.MiddleAsPivot: 221 return (left + right) / 2; 222 case QuickSortPivotSelectionType.LastAsPivot: 223 return right; 224 case QuickSortPivotSelectionType.RandomizedPivot: 225 { 226 // 在區間內隨機選擇位置 227 if (right - left > 1) 228 { 229 return random.Next(left, right); 230 } 231 else 232 { 233 return left; 234 } 235 } 236 case QuickSortPivotSelectionType.BalancedPivot: 237 { 238 // 選擇起始、中間和結尾位置中的中位數 239 int leftValue = unsorted[left]; 240 int middleValue = unsorted[(left + right) / 2]; 241 int rightValue = unsorted[right]; 242 243 if (leftValue < middleValue) 244 { 245 if (middleValue < rightValue) 246 { 247 return (left + right) / 2; 248 } 249 else 250 { 251 return right; 252 } 253 } 254 else 255 { 256 if (leftValue < rightValue) 257 { 258 return left; 259 } 260 else 261 { 262 return right; 263 } 264 } 265 } 266 } 267 268 return (left + right) / 2; 269 } 270 271 static void PrintArray(int[] unsorted) 272 { 273 if (!isPrintArrayEnabled) return; 274 275 foreach (var item in unsorted) 276 { 277 Console.Write("{0} ", item); 278 } 279 Console.WriteLine(); 280 } 281 282 static Random random = new Random(new Guid().GetHashCode()); 283 284 enum QuickSortPivotSelectionType 285 { 286 FirstAsPivot, 287 MiddleAsPivot, 288 LastAsPivot, 289 RandomizedPivot, 290 BalancedPivot, 291 } 292 }
代碼運行后結果

選擇排序(Selection Sort)
算法原理
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。

算法復雜度
選擇排序的交換操作介於 0 和 (n-1) 次之間。選擇排序的比較操作為 n(n-1)/2 次之間。選擇排序的賦值操作介於 0 和 3(n-1) 次之間。
比較次數 O(n2),比較次數與關鍵字的初始狀態無關,總的比較次數 N = (n-1)+(n-2)+...+1 = n*(n-1)/2。交換次數 O(n),最好情況是,已經有序,交換 0 次;最壞情況是,逆序,交換 n-1 次。交換次數比冒泡排序較少,由於交換所需 CPU 時間比比較所需的 CPU 時間多,n 值較小時,選擇排序比冒泡排序快。
原地操作幾乎是選擇排序的唯一優點,當空間復雜度(space complexity)要求較高時,可以考慮選擇排序,實際適用的場合非常罕見。
- 最差時間復雜度 О(n²)
- 平均時間復雜度 О(n²)
- 最優時間復雜度 О(n²)
- 最差空間復雜度 О(n),輔助空間 O(1)
代碼示例
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 }; 6 7 SelectionSort(unsorted); 8 9 foreach (var key in unsorted) 10 { 11 Console.Write("{0} ", key); 12 } 13 14 Console.Read(); 15 } 16 17 static void SelectionSort(int[] unsorted) 18 { 19 // advance the position through the entire array 20 // could do i < n-1 because single element is also min element 21 for (int i = 0; i < unsorted.Length - 1; i++) 22 { 23 // find the min element in the unsorted a[i .. n-1] 24 // assume the min is the first element 25 int min = i; 26 27 // test against elements after i to find the smallest 28 for (int j = i + 1; j < unsorted.Length; j++) 29 { 30 // if this element is less, then it is the new minimum 31 if (unsorted[j] < unsorted[min]) 32 { 33 // found new minimum, remember its index 34 min = j; 35 } 36 } 37 38 // swap 39 if (min != i) 40 { 41 int temp = unsorted[i]; 42 unsorted[i] = unsorted[min]; 43 unsorted[min] = temp; 44 } 45 } 46 } 47 }
插入排序(Insertion Sort)
算法原理
對於未排序數據,在已排序序列中從后向前掃描,找到相應位置,將位置后的已排序數據逐步向后挪位,將新元素插入到該位置。

算法描述
- 從第一個元素開始,該元素可以認為已經被排序;
- 取出下一個元素,在已經排序的元素序列中從后向前掃描;
- 如果該元素(已排序)大於新元素,將該元素移到下一位置;
- 重復步驟 3,直到找到已排序的元素小於或者等於新元素的位置;
- 將新元素插入到該位置后;
- 重復步驟 2~5;
算法復雜度
- In-Place 原地排序(即只需要用到 O(1) 的額外空間)
- 最差時間復雜度 O(n2)
- 平均時間復雜度 O(n2)
- 最優時間復雜度 O(n)
- 最差空間復雜度 O(n),輔助空間 O(1)
插入排序算法的內循環是緊密的,對小規模輸入來說是一個快速的原地排序算法。
示例代碼
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 }; 6 7 InsertionSort(unsorted); 8 9 foreach (var key in unsorted) 10 { 11 Console.Write("{0} ", key); 12 } 13 14 Console.Read(); 15 } 16 17 static void InsertionSort(int[] unsorted) 18 { 19 for (int i = 1; i < unsorted.Length; i++) 20 { 21 if (unsorted[i - 1] > unsorted[i]) 22 { 23 int key = unsorted[i]; 24 int j = i; 25 while (j > 0 && unsorted[j - 1] > key) 26 { 27 unsorted[j] = unsorted[j - 1]; 28 j--; 29 } 30 unsorted[j] = key; 31 } 32 } 33 } 34 }
希爾排序(Shell Sort)
希爾排序是插入排序的一種更高效的改進版本,其基於插入排序的以下兩個特點提出改進方法:
- 插入排序在對幾乎已經排序的數據操作時,效率高,可以達到線性時間。
- 插入排序一般來說是低效的,其每次只能將數據移動一位。
算法描述
希爾排序通過將比較的全部元素分為幾個區域來提升插入排序的性能。這樣可以讓一個元素一次性地朝最終位置前進一大步。然后算法再取越來越小的步長進行排序,算法的最后一步就是普通的插入排序,但是到了這步,需排序的數據幾乎是已排好的了,此時插入排序較快。
假設有一個很小的數據在一個已按升序排好序的數組的末端。如果用復雜度為 O(n2) 的排序(冒泡排序或插入排序),可能會進行 n 次的比較和交換才能將該數據移至正確位置。而希爾排序會用較大的步長移動數據,所以小數據只需進行少量比較和交換即可移到正確位置。

步長序列(Gap Sequences)
步長的選擇是希爾排序的重要部分。只要最終步長為 1 任何步長串行都可以工作。算法最開始以一定的步長進行排序。然后會繼續以一定步長進行排序,最終算法以步長為 1 進行排序。當步長為 1 時,算法變為插入排序,這就保證了數據一定會被排序。
已知的最好步長串行是由 Sedgewick 提出的 (1, 5, 19, 41, 109,...),該步長的項來自 9 * 4^i - 9 * 2^i + 1 和 4^i - 3 * 2^i + 1 這兩個算式。這項研究也表明 "比較在希爾排序中是最主要的操作,而不是交換。" 用這樣步長串行的希爾排序比插入排序和堆排序都要快,甚至在小數組中比快速排序還快,但是在涉及大量數據時希爾排序還是比快速排序慢。
算法復雜度
- 最差時間復雜度 O(nlog2 n)
- 平均時間復雜度 依賴於步長間隔 O(nlog2 n)
- 最優時間復雜度 O(nlogn)
- 最差空間復雜度 O(n),輔助空間 O(1)
代碼示例
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 }; 6 7 ShellSort(unsorted); 8 9 foreach (var key in unsorted) 10 { 11 Console.Write("{0} ", key); 12 } 13 14 Console.Read(); 15 } 16 17 static void ShellSort(int[] unsorted) 18 { 19 int gap = (int)Math.Ceiling(unsorted.Length / 2D); 20 21 // start with the largest gap and work down to a gap of 1 22 while (gap > 0) 23 { 24 // do a gapped insertion sort for this gap size. 25 // the first gap elements a[0..gap-1] are already in gapped order 26 // keep adding one more element until the entire array is gap sorted 27 for (int i = 0; i < unsorted.Length; i++) 28 { 29 // add a[i] to the elements that have been gap sorted 30 // save a[i] in temp and make a hole at position i 31 int j = i; 32 int temp = unsorted[i]; 33 34 // shift earlier gap-sorted elements up 35 // until the correct location for a[i] is found 36 while (j >= gap && unsorted[j - gap] > temp) 37 { 38 unsorted[j] = unsorted[j - gap]; 39 j = j - gap; 40 } 41 42 // put temp (the original a[i]) in its correct location 43 unsorted[j] = temp; 44 } 45 46 // change gap 47 gap = (int)Math.Floor(0.5 + gap / 2.2); 48 } 49 } 50 }
合並排序(Merge Sort)
合並排序是分治法的典型應用。
分治法(Divide-and-Conquer):將原問題划分成 n 個規模較小而結構與原問題相似的子問題;遞歸地解決這些問題,然后再合並其結果,就得到原問題的解。
分治模式在每一層上都有三個步驟:
- 分解(Divide):將原問題分解成一系列子問題;
- 解決(Conquer):遞歸地解決各個子問題。若子問題足夠小,則直接求解;
- 合並(Combine):將子問題的結果合並成原問題的解。
合並排序算法完全依照了上述模式:
- 分解:將 n 個元素分成各含 n/2 個元素的子序列;
- 解決:用合並排序法對兩個子序列遞歸地排序;
- 合並:合並兩個已排序的子序列以得到排序結果。

算法描述
- 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列;
- 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置;
- 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置;
- 重復步驟 3 直到某一指針到達序列尾;
- 將另一序列剩下的所有元素直接復制到合並序列尾;
算法復雜度
- 最差時間復雜度 Θ(n*logn)
- 平均時間復雜度 Θ(n*logn)
- 最優時間復雜度 Θ(n)
- 最差空間復雜度 Θ(n)
合並排序有着較好的漸進運行時間 Θ(nlogn),但其中的 merge 操作不是 in-place 操作。
示例代碼
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 }; 6 7 int[] temp = new int[unsorted.Length]; 8 MergeSort(unsorted, 0, unsorted.Length, temp); 9 foreach (var key in unsorted) 10 { 11 Console.Write("{0} ", key); 12 } 13 14 Console.Read(); 15 } 16 17 static void MergeSort(int[] unsorted, int left, int right, int[] temp) 18 { 19 if (left + 1 < right) 20 { 21 // divide 22 int mid = (left + right) / 2; 23 24 // conquer 25 MergeSort(unsorted, left, mid, temp); 26 MergeSort(unsorted, mid, right, temp); 27 28 // combine 29 Merge(unsorted, left, mid, right, temp); 30 } 31 } 32 33 static void Merge(int[] unsorted, int left, int mid, int right, int[] temp) 34 { 35 int leftPosition = left; 36 int rightPosition = mid; 37 int numberOfElements = 0; 38 39 // merge two slots 40 while (leftPosition < mid && rightPosition < right) 41 { 42 if (unsorted[leftPosition] < unsorted[rightPosition]) 43 { 44 temp[numberOfElements++] = unsorted[leftPosition++]; 45 } 46 else 47 { 48 temp[numberOfElements++] = unsorted[rightPosition++]; 49 } 50 } 51 52 // add remaining 53 while (leftPosition < mid) 54 { 55 temp[numberOfElements++] = unsorted[leftPosition++]; 56 } 57 while (rightPosition < right) 58 { 59 temp[numberOfElements++] = unsorted[rightPosition++]; 60 } 61 62 // copy back 63 for (int n = 0; n < numberOfElements; n++) 64 { 65 unsorted[left + n] = temp[n]; 66 } 67 } 68 }
堆排序(Heap Sort)
堆排序(Heap Sort)是指利用堆這種數據結構所設計的一種排序算法。二叉堆數據結構是一種數組對象,它可以被視為一棵完全二叉樹。樹中每個節點與數組中存放該節點值的那個元素對應。
二叉堆有兩種,最大堆和最小堆。最大堆特性是指除了根以外的每個節點 i ,有 A(Parent(i)) ≥ A[i] ,即某個節點的值至多是和其父節點的值一樣大。最小堆特性是指除了根以外的每個節點 i ,有 A(Parent(i)) ≤ A[i] ,最小堆的最小元素在根部。
在堆排序算法中,我們使用的是最大堆。最小堆通常在構造有限隊列時使用。
堆可以被看成一棵樹,節點在堆中的高度定義為從本節點到葉子的最長簡單下降路徑上邊的數目;定義堆的高度為樹根的高度。因為具有 n 個元素的堆是基於一棵完全二叉樹,因而其高度為 Θ(lg n) 。
堆節點的訪問
通常堆是通過一維數組來實現的。在數組起始為 0 的情形中,如果 i 為當前節點的索引,則有
- 父節點在位置 floor((i-1)/2);
- 左子節點在位置 (2*i+1);
- 右子節點在位置 (2*i+2);
堆的操作
在堆的數據結構中,堆中的最大值總是位於根節點。堆中定義以下幾種操作:
- 最大堆調整(Max-Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點,保持最大堆性質的關鍵。運行時間為 O(lg n)。
- 創建最大堆(Build-Max-Heap):在無序的輸入數組基礎上構造出最大堆。運行時間為 O(n)。
- 堆排序(HeapSort):對一個數組進行原地排序,卸載位在第一個數據的根節點,並做最大堆調整的遞歸運算。運行時間為 O(n*lg n)。
- 抽取最大值(Extract-Max):相當於執行一次最大堆調整,最大值在根處。運行時間為 O(lg n)。

算法復雜度
- 最差時間復雜度 O(n*logn)
- 平均時間復雜度 Θ(n*logn)
- 最優時間復雜度 O(n*logn)
- 最差空間復雜度 O(n),輔助空間 O(1)
示例代碼
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 4, 1, 5, 2, 6, 3, 7, 9, 8 }; 6 7 HeapSort(unsorted); 8 9 foreach (var key in unsorted) 10 { 11 Console.Write("{0} ", key); 12 } 13 14 Console.Read(); 15 } 16 17 static void HeapSort(int[] unsorted) 18 { 19 // build the heap in array so that largest value is at the root 20 BuildMaxHeap(unsorted); 21 22 // swap root node and the last heap node 23 for (int i = unsorted.Length - 1; i >= 1; i--) 24 { 25 // array[0] is the root and largest value. 26 // the swap moves it in front of the sorted elements 27 int max = unsorted[0]; 28 unsorted[0] = unsorted[i]; 29 unsorted[i] = max; // now, the largest one is at the end 30 31 // the swap ruined the heap property, so restore it 32 // the heap size is reduced by one 33 MaxHeapify(unsorted, 0, i - 1); 34 } 35 } 36 37 static void BuildMaxHeap(int[] unsorted) 38 { 39 // put elements of array in heap order, in-place 40 // start is assigned the index in array of the last parent node 41 // the last element in 0-based array is at index count-1; 42 // find the parent of that element 43 for (int i = (unsorted.Length / 2) - 1; i >= 0; i--) 44 { 45 // sift down the node at index start to the proper place 46 // such that all nodes below the start index are in heap order 47 MaxHeapify(unsorted, i, unsorted.Length - 1); 48 } 49 // after sifting down the root all nodes/elements are in heap order 50 } 51 52 static void MaxHeapify(int[] unsorted, int root, int bottom) 53 { 54 int rootValue = unsorted[root]; 55 int maxChild = root * 2 + 1; // start from left child 56 57 // while the root has at least one child 58 while (maxChild <= bottom) 59 { 60 // more children 61 if (maxChild < bottom) 62 { 63 // if there is a right child and that child is greater 64 if (unsorted[maxChild] < unsorted[maxChild + 1]) 65 { 66 maxChild = maxChild + 1; 67 } 68 } 69 70 // compare roots and the older children 71 if (rootValue < unsorted[maxChild]) 72 { 73 unsorted[root] = unsorted[maxChild]; 74 root = maxChild; 75 76 // repeat to continue sifting down the child now 77 maxChild = root * 2 + 1; // continue from left child 78 } 79 else 80 { 81 maxChild = bottom + 1; 82 } 83 } 84 85 unsorted[root] = rootValue; 86 } 87 }
內省排序(Introspective Sort)
內省排序(Introsort)是由 David Musser 在 1997 年設計的排序算法。這個排序算法首先從快速排序開始,當遞歸深度超過一定深度(深度為排序元素數量的對數值)后轉為堆排序。采用這個方法,內省排序既能在常規數據集上實現快速排序的高性能,又能在最壞情況下仍保持 O(nlog n) 的時間復雜度。由於這兩種算法都屬於比較排序算法,所以內省排序也是一個比較排序算法。
在快速排序算法中,一個關鍵操作就是選擇主元(Pivot):數列將被此主元位置分開成兩部分。最簡單的主元選擇算法是使用第一個或者最后一個元素,但這在排列已部分有序的序列上性能很差。Niklaus Wirth 為此設計了一個快速排序的變體,使用處於中間的元素來防止在某些特定序列上性能退化為 O(n2) 的狀況。該 median-of-3 選擇算法從序列的第一、中間和最后一個元素取得中位數來作為主元。雖然這個算法在現實世界的數據上性能表現良好,但經過精心設計的序列仍能大幅降低此算法性能。這樣就有攻擊者精心設計序列發送到因特網服務器以進行拒絕服務(DOS:Denial of Service)攻擊的潛在可能性。
Musser 研究指出,在為 median-of-3 選擇算法精心設計的 100,000 個元素序列上,內省排序算法的運行時間是快速排序的 1/200。在 Musser 的算法中,最終較小范圍內數據的排序由 Robert Sedgewick 提出的小數據排序算法完成。
算法復雜度
- 最差時間復雜度 O(n*log n)
- 平均時間復雜度 O(n*log n)
- 最優時間復雜度 O(n*log n)
微軟 .NET 框架中 Array.Sort 方法的實現使用了內省排序(Introspective Sort)算法。
代碼示例
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 6 4, 1, 5, 2, 6, 3, 7, 9, 8, 10, 7 20, 11, 19, 12, 18, 17, 15, 16, 13, 14 8 }; 9 10 GenericQuickSorter<int>.DepthLimitedQuickSort( 11 unsorted, 0, unsorted.Length - 1, 12 GenericQuickSorter<int>.QuickSortDepthThreshold); 13 14 foreach (var key in unsorted) 15 { 16 Console.Write("{0} ", key); 17 } 18 19 Console.Read(); 20 } 21 } 22 23 internal class GenericQuickSorter<T> 24 where T : IComparable<T> 25 { 26 internal const int QuickSortDepthThreshold = 32; 27 28 internal static void DepthLimitedQuickSort(T[] keys, int left, int right, int depthLimit) 29 { 30 do 31 { 32 if (depthLimit == 0) 33 { 34 Heapsort(keys, left, right); 35 return; 36 } 37 38 int i = left; 39 int j = right; 40 41 // pre-sort the low, middle (pivot), and high values in place. 42 // this improves performance in the face of already sorted data, or 43 // data that is made up of multiple sorted runs appended together. 44 int middle = i + ((j - i) >> 1); 45 SwapIfGreater(keys, i, middle); // swap the low with the mid point 46 SwapIfGreater(keys, i, j); // swap the low with the high 47 SwapIfGreater(keys, middle, j); // swap the middle with the high 48 49 T x = keys[middle]; 50 do 51 { 52 while (keys[i].CompareTo(x) < 0) i++; 53 while (x.CompareTo(keys[j]) < 0) j--; 54 Contract.Assert(i >= left && j <= right, 55 "(i>=left && j<=right) Sort failed - Is your IComparer bogus?"); 56 57 if (i > j) break; 58 if (i < j) 59 { 60 T key = keys[i]; 61 keys[i] = keys[j]; 62 keys[j] = key; 63 } 64 i++; 65 j--; 66 } while (i <= j); 67 68 // The next iteration of the while loop is to 69 // "recursively" sort the larger half of the array and the 70 // following calls recrusively sort the smaller half. 71 // So we subtrack one from depthLimit here so 72 // both sorts see the new value. 73 depthLimit--; 74 75 if (j - left <= right - i) 76 { 77 if (left < j) DepthLimitedQuickSort(keys, left, j, depthLimit); 78 left = i; 79 } 80 else 81 { 82 if (i < right) DepthLimitedQuickSort(keys, i, right, depthLimit); 83 right = j; 84 } 85 } while (left < right); 86 } 87 88 private static void SwapIfGreater(T[] keys, int a, int b) 89 { 90 if (a != b) 91 { 92 if (keys[a] != null && keys[a].CompareTo(keys[b]) > 0) 93 { 94 T key = keys[a]; 95 keys[a] = keys[b]; 96 keys[b] = key; 97 } 98 } 99 } 100 101 private static void Heapsort(T[] keys, int lo, int hi) 102 { 103 Contract.Requires(keys != null); 104 Contract.Requires(lo >= 0); 105 Contract.Requires(hi > lo); 106 Contract.Requires(hi < keys.Length); 107 108 int n = hi - lo + 1; 109 for (int i = n / 2; i >= 1; i = i - 1) 110 { 111 DownHeap(keys, i, n, lo); 112 } 113 for (int i = n; i > 1; i = i - 1) 114 { 115 Swap(keys, lo, lo + i - 1); 116 DownHeap(keys, 1, i - 1, lo); 117 } 118 } 119 120 private static void DownHeap(T[] keys, int i, int n, int lo) 121 { 122 Contract.Requires(keys != null); 123 Contract.Requires(lo >= 0); 124 Contract.Requires(lo < keys.Length); 125 126 T d = keys[lo + i - 1]; 127 int child; 128 while (i <= n / 2) 129 { 130 child = 2 * i; 131 if (child < n 132 && (keys[lo + child - 1] == null 133 || keys[lo + child - 1].CompareTo(keys[lo + child]) < 0)) 134 { 135 child++; 136 } 137 if (keys[lo + child - 1] == null 138 || keys[lo + child - 1].CompareTo(d) < 0) 139 break; 140 keys[lo + i - 1] = keys[lo + child - 1]; 141 i = child; 142 } 143 keys[lo + i - 1] = d; 144 } 145 146 private static void Swap(T[] a, int i, int j) 147 { 148 if (i != j) 149 { 150 T t = a[i]; 151 a[i] = a[j]; 152 a[j] = t; 153 } 154 } 155 }
微軟 .NET 框架中 Array.Sort 方法的實現使用了內省排序(Introspective Sort)算法:
- 當分區大小(Partition Size)小於 16 個元素時,使用插入排序(Insertion Sort)算法。
- 當分區的數量超過 2 * LogN 時,N 是輸入數組的范圍,使用堆排序(Heapsort)算法。
- 否則,使用快速排序(Quicksort)算法。
這種實現也是不穩定的排序,也就是說,如果兩個元素相等,則並不能保證它們的順序,而相反一個穩定的排序算法則會保持相等元素原來的順序。
如果數組的長度為 n ,則該實現的平均情況為 O(n log n) ,最壞情況為 O(n2)。
注:.NET 4.5 中使用這里描述的算法,而 .NET 4.0 及以前版本使用上面描述的快速排序和堆排序組合的內省排序算法。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = { 6 4, 1, 5, 2, 6, 3, 7, 9, 8, 10, 7 20, 11, 19, 12, 18, 17, 15, 16, 13, 14 8 }; 9 10 GenericIntroSorter<int>.IntrospectiveSort(unsorted, 0, unsorted.Length); 11 12 foreach (var key in unsorted) 13 { 14 Console.Write("{0} ", key); 15 } 16 17 Console.Read(); 18 } 19 } 20 21 internal class GenericIntroSorter<T> 22 where T : IComparable<T> 23 { 24 internal const int IntrosortSwitchToInsertionSortSizeThreshold = 16; 25 26 internal static void IntrospectiveSort(T[] keys, int left, int length) 27 { 28 Contract.Requires(keys != null); 29 Contract.Requires(left >= 0); 30 Contract.Requires(length >= 0); 31 Contract.Requires(length <= keys.Length); 32 Contract.Requires(length + left <= keys.Length); 33 34 if (length < 2) 35 return; 36 37 IntroSort(keys, left, length + left - 1, 2 * FloorLog2(keys.Length)); 38 } 39 40 private static void IntroSort(T[] keys, int lo, int hi, int depthLimit) 41 { 42 Contract.Requires(keys != null); 43 Contract.Requires(lo >= 0); 44 Contract.Requires(hi < keys.Length); 45 46 while (hi > lo) 47 { 48 int partitionSize = hi - lo + 1; 49 if (partitionSize <= IntrosortSwitchToInsertionSortSizeThreshold) 50 { 51 if (partitionSize == 1) 52 { 53 return; 54 } 55 if (partitionSize == 2) 56 { 57 SwapIfGreaterWithItems(keys, lo, hi); 58 return; 59 } 60 if (partitionSize == 3) 61 { 62 SwapIfGreaterWithItems(keys, lo, hi - 1); 63 SwapIfGreaterWithItems(keys, lo, hi); 64 SwapIfGreaterWithItems(keys, hi - 1, hi); 65 return; 66 } 67 68 InsertionSort(keys, lo, hi); 69 return; 70 } 71 72 if (depthLimit == 0) 73 { 74 Heapsort(keys, lo, hi); 75 return; 76 } 77 depthLimit--; 78 79 int p = PickPivotAndPartition(keys, lo, hi); 80 81 // Note we've already partitioned around the pivot 82 // and do not have to move the pivot again. 83 IntroSort(keys, p + 1, hi, depthLimit); 84 hi = p - 1; 85 } 86 } 87 88 private static int PickPivotAndPartition(T[] keys, int lo, int hi) 89 { 90 Contract.Requires(keys != null); 91 Contract.Requires(lo >= 0); 92 Contract.Requires(hi > lo); 93 Contract.Requires(hi < keys.Length); 94 Contract.Ensures(Contract.Result<int>() >= lo && Contract.Result<int>() <= hi); 95 96 // Compute median-of-three. But also partition them. 97 int middle = lo + ((hi - lo) / 2); 98 99 // Sort lo, mid and hi appropriately, then pick mid as the pivot. 100 SwapIfGreaterWithItems(keys, lo, middle); // swap the low with the mid point 101 SwapIfGreaterWithItems(keys, lo, hi); // swap the low with the high 102 SwapIfGreaterWithItems(keys, middle, hi); // swap the middle with the high 103 104 T pivot = keys[middle]; 105 Swap(keys, middle, hi - 1); 106 107 // We already partitioned lo and hi and put the pivot in hi - 1. 108 // And we pre-increment & decrement below. 109 int left = lo, right = hi - 1; 110 111 while (left < right) 112 { 113 if (pivot == null) 114 { 115 while (left < (hi - 1) && keys[++left] == null) ; 116 while (right > lo && keys[--right] != null) ; 117 } 118 else 119 { 120 while (pivot.CompareTo(keys[++left]) > 0) ; 121 while (pivot.CompareTo(keys[--right]) < 0) ; 122 } 123 124 if (left >= right) 125 break; 126 127 Swap(keys, left, right); 128 } 129 130 // Put pivot in the right location. 131 Swap(keys, left, (hi - 1)); 132 133 return left; 134 } 135 136 private static void Heapsort(T[] keys, int lo, int hi) 137 { 138 Contract.Requires(keys != null); 139 Contract.Requires(lo >= 0); 140 Contract.Requires(hi > lo); 141 Contract.Requires(hi < keys.Length); 142 143 int n = hi - lo + 1; 144 for (int i = n / 2; i >= 1; i = i - 1) 145 { 146 DownHeap(keys, i, n, lo); 147 } 148 for (int i = n; i > 1; i = i - 1) 149 { 150 Swap(keys, lo, lo + i - 1); 151 DownHeap(keys, 1, i - 1, lo); 152 } 153 } 154 155 private static void DownHeap(T[] keys, int i, int n, int lo) 156 { 157 Contract.Requires(keys != null); 158 Contract.Requires(lo >= 0); 159 Contract.Requires(lo < keys.Length); 160 161 T d = keys[lo + i - 1]; 162 int child; 163 while (i <= n / 2) 164 { 165 child = 2 * i; 166 if (child < n 167 && (keys[lo + child - 1] == null 168 || keys[lo + child - 1].CompareTo(keys[lo + child]) < 0)) 169 { 170 child++; 171 } 172 if (keys[lo + child - 1] == null 173 || keys[lo + child - 1].CompareTo(d) < 0) 174 break; 175 keys[lo + i - 1] = keys[lo + child - 1]; 176 i = child; 177 } 178 keys[lo + i - 1] = d; 179 } 180 181 private static void InsertionSort(T[] keys, int lo, int hi) 182 { 183 Contract.Requires(keys != null); 184 Contract.Requires(lo >= 0); 185 Contract.Requires(hi >= lo); 186 Contract.Requires(hi <= keys.Length); 187 188 int i, j; 189 T t; 190 for (i = lo; i < hi; i++) 191 { 192 j = i; 193 t = keys[i + 1]; 194 while (j >= lo && (t == null || t.CompareTo(keys[j]) < 0)) 195 { 196 keys[j + 1] = keys[j]; 197 j--; 198 } 199 keys[j + 1] = t; 200 } 201 } 202 203 private static void SwapIfGreaterWithItems(T[] keys, int a, int b) 204 { 205 Contract.Requires(keys != null); 206 Contract.Requires(0 <= a && a < keys.Length); 207 Contract.Requires(0 <= b && b < keys.Length); 208 209 if (a != b) 210 { 211 if (keys[a] != null && keys[a].CompareTo(keys[b]) > 0) 212 { 213 T key = keys[a]; 214 keys[a] = keys[b]; 215 keys[b] = key; 216 } 217 } 218 } 219 220 private static void Swap(T[] a, int i, int j) 221 { 222 if (i != j) 223 { 224 T t = a[i]; 225 a[i] = a[j]; 226 a[j] = t; 227 } 228 } 229 230 private static int FloorLog2(int n) 231 { 232 int result = 0; 233 while (n >= 1) 234 { 235 result++; 236 n = n / 2; 237 } 238 return result; 239 } 240 }
參考資料
- Big O Notation
- Comparison sort Sorting Algorithms
- Part 3: Binary Trees and BSTs
- Insertion Sort Selection Sort Bubble Sort Heapsort Merge Sort
- Sorting Algorithms Codes in C#.NET
- Heap sort in c# .NET
- Bubble Sort Heap Sort Shell Sort Quick Sort Quick Sort Algorithm
- Odd-Even Transposition Sort
- Sorting Algorithms - Shellsort
- Computer Algorithms: Shell Sort
- Generic Algorithms Introsort
- Musser, David. Introspective Sorting and Selection Algorithms.
- Array.Sort<T> Method (T[])
- 快速排序里的學問:霍爾與快速排序
本篇文章《比較排序算法》由 Dennis Gao 原創發表自博客園,任何未經作者同意的爬蟲或人為轉載均為耍流氓。
