線性時間排序算法列表
給定含 n 個元素的輸入序列,任何比較排序在最壞情況下都需要 Ω(n log n) 次比較來進行排序。合並排序和堆排序在最壞情況下達到上界 O(n log n),它們都是漸進最優的排序算法,快速排序在平均情況下達到上界 O(n log n)。
本文介紹的三種以線性時間運行的算法:計數排序、基數排序和桶排序,都用非比較的一些操作來確定排序順序。因此,下界 Ω(n log n) 對它們是不適用的。
計數排序(Counting Sort)
計數排序(Counting Sort)假設 n 個輸入元素中的每一個都是介於 0 到 k 之間的整數,此處 k 為某個整數。
計數排序的基本思想就是對每一個輸入元素 x,確定出小於 x 的元素個數。有了這一信息,就可以把 x 直接放到它在最終輸出數組中的位置上。
例如:有 10 個年齡不同的人,統計出有 8 個人的年齡比 A 小,那 A 的年齡就排在第 9 位,用這個方法可以得到其他每個人的位置,也就排好了序。當然,年齡有重復時需要特殊處理(保證穩定性),這就是為什么最后要反向填充目標數組,以及將每個數字的統計減去 1 的原因。
算法描述
算法的步驟如下:
- 找出待排序的數組中最大和最小的元素;
- 統計數組中每個值為 i 的元素出現的次數,存入數組 C 的第 i 項;
- 對所有的計數累加(從 C 中的第一個元素開始,每一項和前一項相加);
- 反向填充目標數組,將每個元素 i 放在新數組的第 C(i) 項,每放一個元素就將 C(i) 減去 1;
算法復雜度
- 最差時間復雜度 O(n + k)
- 平均時間復雜度 O(n + k)
- 最差空間復雜度 O(n + k)
計數排序不是比較排序,排序的速度快於任何比較排序算法。
計數排序的一個重要性質就是它是穩定的:具有相同值的元素在輸出數組中的相對次序與它們在輸入數組中的次序相同。
之所以說計數排序的穩定性非常重要,還有一個原因是因為計數排序經常用作基數排序算法的一個子過程,其穩定性對於基數排序的正確性來說非常關鍵。
代碼示例
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = 6 { 7 5, 9, 3, 9, 10, 9, 2, 4, 13, 10 8 }; 9 10 OptimizedCountingSort(unsorted); 11 12 foreach (var key in unsorted) 13 { 14 Console.Write("{0} ", key); 15 } 16 17 Console.Read(); 18 } 19 20 static int[] CountingSort(int[] unsorted) 21 { 22 // find min and max value 23 int min = unsorted[0], max = unsorted[0]; 24 for (int i = 1; i < unsorted.Length; i++) 25 { 26 if (unsorted[i] < min) min = unsorted[i]; 27 else if (unsorted[i] > max) max = unsorted[i]; 28 } 29 30 // creates k buckets 31 int k = max - min + 1; 32 int[] C = new int[k]; 33 34 // calculate the histogram of key frequencies 35 for (int i = 0; i < unsorted.Length; i++) 36 { 37 C[unsorted[i] - min]++; 38 } 39 40 // recalculate 41 C[0]--; 42 for (int i = 1; i < C.Length; i++) 43 { 44 C[i] = C[i] + C[i - 1]; 45 } 46 47 // sort the array 48 int[] B = new int[unsorted.Length]; 49 for (int i = unsorted.Length - 1; i >= 0; i--) 50 { 51 // keep stable 52 B[C[unsorted[i] - min]--] = unsorted[i]; 53 } 54 55 return B; 56 } 57 58 static void OptimizedCountingSort(int[] unsorted) 59 { 60 // find min and max value 61 int min = unsorted[0], max = unsorted[0]; 62 for (int i = 1; i < unsorted.Length; i++) 63 { 64 if (unsorted[i] < min) min = unsorted[i]; 65 else if (unsorted[i] > max) max = unsorted[i]; 66 } 67 68 // creates k buckets 69 int k = max - min + 1; 70 int[] C = new int[k]; 71 72 // calculate the histogram of key frequencies 73 for (int i = 0; i < unsorted.Length; i++) 74 { 75 C[unsorted[i] - min]++; 76 } 77 78 // copy to output array, 79 // preserving order of inputs with equal keys 80 int increment = 0; 81 for (int i = min; i <= max; i++) 82 { 83 for (int j = 0; j < C[i - min]; j++) 84 { 85 // in place, may not stable if you care 86 unsorted[increment++] = i; 87 } 88 } 89 } 90 }
基數排序(Radix Sort)
基數排序(Radix Sort)是一種非比較型整數排序算法,其原理是將整數值按相同的有效位進行分組,然后在有效位區間內進行排序。
算法描述
每個元素值首先被放入一個該值的最右位所對應的桶中,桶內會保持被放入元素值最初的順序。這使得桶的數量和值的數量能夠根據其最右位建立一對一的關系。然后,通過相同的方式重復處理下一位,直到所有的位都已被處理。
- 獲得值的最右側的最小的位。
- 根據該位的值將數組內的元素值進行分組,但仍然保持元素的順序。(以此來保持算法穩定性)
- 重復上述分組過程,直到所有的位都已被處理。
上述第 2 步中通常可以使用桶排序(Bucket Sort)或計數排序(Counting Sort)算法,因為它們在元素較少時擁有更好的效率。
基數排序中可以選擇采用最低有效位基數排序(LSD Radix Sort:Least Significant Digit Radix Sort)或最高有效位基數排序(MSD Radix Sort:Most Significant Digit Radix Sort)。LSD 的排序方式由值的最低位也就是最右邊開始,而 MSD 則相反,由值的最高位也就是最左邊開始。
例如,如下這個無序的數列需要排序:
170, 45, 75, 90, 802, 2, 24, 66
使用 LSD 方式從最低位開始(個位)排序的結果是:
170, 90, 802, 2, 24, 45, 75, 66
再繼續從下一位(十位)繼續排序的結果是:
802, 2, 24, 45, 66, 170, 75, 90
再繼續從下一位(百位)繼續排序的結果是:
2, 24, 45, 66, 75, 90, 170, 802
算法復雜度
- 最差時間復雜度 O(k*n)
- 最差空間復雜度 O(k*n)
代碼示例
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = 6 { 7 15, 19, 13, 19, 10, 33, 12, 14, 13, 10, 8 }; 9 10 RadixSort(unsorted); 11 12 foreach (var key in unsorted) 13 { 14 Console.Write("{0} ", key); 15 } 16 17 Console.Read(); 18 } 19 20 static void RadixSort(int[] unsorted) 21 { 22 // our helper array 23 int[] t = new int[unsorted.Length]; 24 25 // number of bits our group will be long 26 // try to set this also to 2, 8 or 16 to see if it is quicker or not 27 int r = 4; 28 29 // number of bits of a C# int 30 int b = 32; 31 32 // counting and prefix arrays 33 // (note dimensions 2^r which is the number of 34 // all possible values of a r-bit number) 35 int[] count = new int[1 << r]; 36 int[] pref = new int[1 << r]; 37 38 // number of groups 39 int groups = (int)Math.Ceiling((double)b / (double)r); 40 41 // the mask to identify groups 42 int mask = (1 << r) - 1; 43 44 // the algorithm: 45 for (int c = 0, shift = 0; c < groups; c++, shift += r) 46 { 47 // reset count array 48 for (int j = 0; j < count.Length; j++) 49 count[j] = 0; 50 51 // counting elements of the c-th group 52 for (int i = 0; i < unsorted.Length; i++) 53 count[(unsorted[i] >> shift) & mask]++; 54 55 // calculating prefixes 56 pref[0] = 0; 57 for (int i = 1; i < count.Length; i++) 58 pref[i] = pref[i - 1] + count[i - 1]; 59 60 // from a[] to t[] elements ordered by c-th group 61 for (int i = 0; i < unsorted.Length; i++) 62 t[pref[(unsorted[i] >> shift) & mask]++] = unsorted[i]; 63 64 // a[]=t[] and start again until the last group 65 t.CopyTo(unsorted, 0); 66 } 67 // a is sorted 68 } 69 }
桶排序(Bucket Sort)
桶排序(Bucket Sort)的工作原理是將數組分解到有限數量的桶里,每個桶再分別進行排序。桶內排序有可能使用其他排序算法或是以遞歸的方式繼續使用桶排序。
算法描述
桶排序的步驟:
- 在數組中查找數值的最大值和最小值;
- 初始化一個數組當作空桶,長度為 (MaxValue - MinValue + 1)。
- 遍歷被排序數組,並把數值逐個放入對應的桶中。
- 對每個不是空的桶進行排序。
- 從不是空的桶里把數值再放回原來的數組中。
算法復雜度
- 最差時間復雜度 O(n2)
- 平均時間復雜度 O(n+k)
- 最差空間復雜度 O(n*k)
當要被排序的數組中的數值是均勻分布時,桶排序的運行時間為線性時間 Θ(n)。桶排序不是比較排序,它不受 Ω(n log n) 下界的影響。
代碼示例
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] unsorted = 6 { 7 15, 19, 13, 19, 10, 33, 12, 14, 13, 10, 8 }; 9 10 BucketSort(unsorted); 11 12 foreach (var key in unsorted) 13 { 14 Console.Write("{0} ", key); 15 } 16 17 Console.Read(); 18 } 19 20 static void BucketSort(int[] unsorted) 21 { 22 // find the maximum and minimum values in the array 23 int max = unsorted[0]; //start with first element 24 int min = unsorted[0]; 25 26 // start from index 1 27 for (int i = 1; i < unsorted.Length; i++) 28 { 29 if (unsorted[i] < min) min = unsorted[i]; 30 else if (unsorted[i] > max) max = unsorted[i]; 31 } 32 33 // create a temporary "buckets" to store the values in order 34 // each value will be stored in its corresponding index 35 // scooting everything over to the left as much as possible 36 // e.g. 34 => index at 34 - minValue 37 List<int>[] buckets = new List<int>[max - min + 1]; 38 39 // initialize the buckets 40 for (int i = 0; i < buckets.Length; i++) 41 { 42 buckets[i] = new List<int>(); 43 } 44 45 // move items to bucket 46 for (int i = 0; i < unsorted.Length; i++) 47 { 48 buckets[unsorted[i] - min].Add(unsorted[i]); 49 } 50 51 // move items in the bucket back to the original array in order 52 int k = 0; //index for original array 53 for (int i = 0; i < buckets.Length; i++) 54 { 55 if (buckets[i].Count > 0) 56 { 57 for (int j = 0; j < buckets[i].Count; j++) 58 { 59 unsorted[k] = buckets[i][j]; 60 k++; 61 } 62 } 63 } 64 } 65 }
參考資料
- Sorting Algorithms
- Counting Sort Radix Sort Bucket Sort
- C# Counting Sort Algorithm Implementation
- Radix Sorting Implementation with C#
- Algorithm Implementation/Sorting/Radix sort
- Category:Algorithm Implementation
- Counting Sort and Radix Sort
- Data Structures and Algorithms with Object-Oriented Design Patterns in C++
本篇文章《線性時間排序算法》由 Dennis Gao 發表自博客園,任何未經作者同意的爬蟲或人為轉載均為耍流氓。