各种排序笔记---基于非比较排序部分
在計算機科學中,排序是一門基礎的算法技術,許多算法都要以此作為基礎,不同的排序算法有著不同的時間開銷和空間開銷。排序算法有非常多種,如我們最常用的快速排序和堆排序等算法,這些算法需要對序列中的數據進行比較,因為被稱為基于比較的排序。
基于比較的排序算法是不能突破O(NlogN)的。簡單證明如下:
N個數有N!個可能的排列情況,也就是說基于比較的排序算法的判定樹有N!個葉子結點,比較次數至少為log(N!)=O(NlogN)(斯特林公式)。
而非基于比較的排序,如計數排序,桶排序,和在此基礎上的基數排序,則可以突破O(NlogN)時間下限。但要注意的是,非基于比較的排序算法的使用都是有條件限制的,例如元素的大小限制,相反,基于比較的排序則沒有這種限制(在一定范圍內)。但并非因為有條件限制就會使非基于比較的排序算法變得無用,對于特定場合有著特殊的性質數據,非基于比較的排序算法則能夠非常巧妙地解決。
基于非比較的排序算法有三種,計數排序,桶排序和基數排序。
-----------------------------我是分割線-------------------------------------------------------
1. 計數排序
計數排序(Counting sort)是一種穩定的線性時間排序算法。計數排序使用一個額外的數組C,其中第i個元素是待排序數組A中值等于i的元素的個數。然后根據數組C來將A中的元素排到正確的位置。
?特征:
當輸入的元素是n個0到k之間的整數時,它的運行時間是Θ(n?+?k)。計數排序不是比較排序,排序的速度快于任何比較排序算法。
由于用來計數的數組C的長度取決于待排序數組中數據的范圍(等于待排序數組的最大值與最小值的差加上1),這使得計數排序對于數據范圍很大的數組,需要大量時間和內存。例如:計數排序是用來排序0到100之間的數字的最好的算法,但是它不適合按字母順序排序人名。但是,計數排序可以用在基數排序算法中,能夠更有效的排序數據范圍很大的數組。
通俗地理解,例如有10個年齡不同的人,統計出有8個人的年齡比A小,那A的年齡就排在第9位,用這個方法可以得到其他每個人的位置,也就排好了序。當然,年齡有重復時需要特殊處理(保證穩定性),這就是為什么最后要反向填充目標數組,以及將每個數字的統計減去1的原因。算法的步驟如下:
java 實現:
1 public class CountingSort { 2 public static void main(String[] argv) { 3 int[] A = CountingSort.countingSort(new int[]{16, 4, 10, 14, 7, 9, 3, 2, 8, 1}); 4 Utils.print(A); 5 } 6 7 public static int[] countingSort(int[] A) { 8 int[] B = new int[A.length]; 9 // 假設A中的數據a'有,0<=a' && a' < k并且k=100 10 int k = 100; 11 countingSort(A, B, k); 12 return B; 13 } 14 15 private static void countingSort(int[] A, int[] B, int k) { 16 int[] C = new int[k]; 17 // 計數 18 for (int j = 0; j < A.length; j++) { 19 int a = A[j]; 20 C[a] += 1; 21 } 22 Utils.print(C); 23 // 求計數和 24 for (int i = 1; i < k; i++) { 25 C[i] = C[i] + C[i - 1]; 26 } 27 Utils.print(C); 28 // 整理 29 for (int j = A.length - 1; j >= 0; j--) { 30 int a = A[j]; 31 B[C[a] - 1] = a; 32 C[a] -= 1; 33 } 34 } 35 } 36 37 38 //針對c數組的大小,優化過的計數排序 39 public class CountSort{ 40 public static void main(String []args){ 41 //排序的數組 42 int a[] = {100, 93, 97, 92, 96, 99, 92, 89, 93, 97, 90, 94, 92, 95}; 43 int b[] = countSort(a); 44 for(int i : b){ 45 System.out.print(i + " "); 46 } 47 System.out.println(); 48 } 49 public static int[] countSort(int []a){ 50 int b[] = new int[a.length]; 51 int max = a[0], min = a[0]; 52 for(int i : a){ 53 if(i > max){ 54 max = i; 55 } 56 if(i < min){ 57 min = i; 58 } 59 } 60 //這里k的大小是要排序的數組中,元素大小的極值差+1 61 int k = max - min + 1; 62 int c[] = new int[k]; 63 for(int i = 0; i < a.length; ++i){ 64 c[a[i]-min] += 1;//優化過的地方,減小了數組c的大小 65 } 66 for(int i = 1; i < c.length; ++i){ 67 c[i] = c[i] + c[i-1]; 68 } 69 for(int i = a.length-1; i >= 0; --i){ 70 b[--c[a[i]-min]] = a[i];//按存取的方式取出c的元素 71 } 72 return b; 73 } 74 } count sort優化后的代碼:
1 public static void Sort(int[] A, int k) 2 { 3 Debug.Assert(k > 0); 4 Debug.Assert(A != null); 5 6 int[] C = new int[k + 1]; 7 8 for (int j = 0; j < A.Length; j++) 9 { 10 C[A[j]]++; 11 } 12 13 int z = 0; 14 15 for (int i = 0; i <= k; i++) 16 { 17 while (C[i]-- > 0) 18 { 19 A[z++] = i; 20 } 21 } 22 } View Code由于C數組下標 i 就是A 的值,所以我們不需要保留A中原來的數了,這個代碼減少了一個數組B,而且要比原來的代碼簡化了很多。
-----------------------------我是分割線------------------------------------------------------
?
2. 桶排序
可能你會發現,計數排序似乎饒了點彎子,比如當我們剛剛統計出C,C[i]可以表示A中值為i的元素的個數,此時我們直接順序地掃描C,就可以求出排序后的結果。的確是這樣,不過這種方法不再是計數排序,而是桶排序(Bucket Sort),確切地說,是桶排序的一種特殊情況。
?
無序數組有個要求,就是成員隸屬于固定(有限的)的區間,如范圍為[0-9](考試分數為1-100等)
例如待排數字[6 2 4 1 5 9]
準備10個空桶,最大數個空桶
[6 2 4 1 5 9]?????????? 待排數組
[0 0 0 0 0 0 0 0 0 0]?? 空桶
[0 1 2 3 4 5 6 7 8 9]?? 桶編號(實際不存在)
?
1,順序從待排數組中取出數字,首先6被取出,然后把6入6號桶,這個過程類似這樣:空桶[ 待排數組[ 0 ] ] = 待排數組[ 0 ]
[6?2 4 1 5 9]?????????? 待排數組
[0 0 0 0 0 0?6?0 0 0]?? 空桶
[0 1 2 3 4 5?6?7 8 9]?? 桶編號(實際不存在)
?
2,順序從待排數組中取出下一個數字,此時2被取出,將其放入2號桶,是幾就放幾號桶
[6 2?4 1 5 9]?????????? 待排數組
[0 0?2?0 0 0 6 0 0 0]?? 空桶
[0 1?2?3 4 5 6 7 8 9]?? 桶編號(實際不存在)
?
3,4,5,6省略,過程一樣,全部入桶后變成下邊這樣
[6 2 4?1 5 9]?????????? 待排數組
[0?1 2?0?4?5 6?0 0?9]?? 空桶
[0?1 2?3?4?5 6 7 8?9]?? 桶編號(實際不存在)
?
0表示空桶,跳過,順序取出即可:1 2 4 5 6 9
1 void bucketSort(int a[], int n, int max) 2 { 3 int i,j; 4 int buckets[max]; 5 6 // 將buckets中的所有數據都初始化為0。 7 memset(buckets, 0, max*sizeof(int)); 8 9 // 1. 計數 10 for(i = 0; i < n; i++) 11 buckets[a[i]]++; 12 13 // 2. 排序 14 for (i = 0, j = 0; i < max; i++) 15 { 16 while( (buckets[i]--) >0 ) 17 a[j++] = i; 18 } 19 } bucket sorting這種特殊實現的方式時間復雜度為O(N+K),空間復雜度也為O(N+K),同樣要求每個元素都要在K的范圍內。更一般的,如果我們的K很大,無法直接開出O(K)的空間該如何呢?
首先定義桶,桶為一個數據容器,每個桶存儲一個區間內的數。依然有一個待排序的整數序列A,元素的最小值不小于0,最大值不超過K。假設我們有M個桶,第i個桶Bucket[i]存儲K * (i/M) 至 k * (i+1)/M之間的數,有如下桶排序的一般方法:
對該算法簡單分析,如果數據是期望平均分布的,則每個桶中的元素平均個數為N/M。如果對每個桶中的元素排序使用的算法是快速排序,每次排序的時間復雜度為O(N/Mlog(N/M))。則總的時間復雜度為O(N)+O(M)O(N/Mlog(N/M)) = O(N+ Nlog(N/M)) =O(N + NlogN - NlogM)。當M接近于N是,桶排序的時間復雜度就可以近似認為是O(N)的。就是桶越多,時間效率就越高,而桶越多,空間卻就越大,由此可見時間和空間是一個矛盾的兩個方面。
桶中元素的順序放入和順序取出是有必要的,因為這樣可以確定桶排序是一種穩定排序算法,配合基數排序是很好用的。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <cstring> 6 using namespace std; 7 struct linklist 8 { 9 linklist *next; 10 int value; 11 linklist(int v,linklist *n):value(v),next(n){} 12 ~linklist() {if (next) delete next;} 13 }; 14 inline int cmp(const void *a,const void *b) 15 { 16 return *(int *)a-*(int *)b; 17 } 18 /* 19 為了方便,我把A中元素加入桶中時是倒序放入的,而收集取出時也是倒序放入序列的,所以不違背穩定排序。 20 */ 21 void BucketSort(int *A,int *B,int N,int K) 22 { 23 linklist *Bucket[101],*p;//建立桶 24 int i,j,k,M; 25 M=K/100; 26 memset(Bucket,0,sizeof(Bucket)); 27 for (i=1;i<=N;i++) 28 { 29 k=A[i]/M; //把A中的每個元素按照的范圍值放入對應桶中 30 Bucket[k]=new linklist(A[i],Bucket[k]); 31 } 32 for (k=j=0;k<=100;k++) 33 { 34 i=j; 35 for (p=Bucket[k];p;p=p->next) 36 B[++j]=p->value; //把桶中每個元素取出,排序并加入B 37 delete Bucket[k]; 38 qsort(B+i+1,j-i,sizeof(B[0]),cmp); 39 } 40 } 41 int main() 42 { 43 int *A,*B,N=100,K=10000,i; 44 A=new int[N+1]; 45 B=new int[N+1]; 46 for (i=1;i<=N;i++) 47 A[i]=rand()%K+1; //生成1..K的隨機數 48 BucketSort(A,B,N,K); 49 for (i=1;i<=N;i++) 50 printf("%d ",B[i]); 51 return 0; 52 } View Code?例題:
(1)sort color
Given an array with n objects colored red, white or blue, sort them so that objects of the same color are adjacent, with the colors in the order red, white and blue.
Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively.
Notice
You are not suppose to use the library's sort function for this problem.?
You should do it in-place (sort numbers in the original array).
Have you met this question in a real interview?
Yes
Example
Given [1, 0, 1, 2], sort it in-place to [0, 1, 1, 2].
次優方法 桶排序/計數排序 代碼:
1 public class Solution { 2 public void sortColors(int[] nums) { 3 int[] count = new int[3]; 4 for (int i = 0; i < nums.length; i++) { 5 count[nums[i]]++; 6 } 7 int j = 0; 8 for (int i = 0; i < nums.length;i++) { 9 if (count[j] > 0) { 10 nums[i] = j; 11 count[j]--; 12 } else { 13 j++; 14 i--; 15 } 16 } 17 } 18 } View Code由于這個題目只有3個值,也就是拿到數,可以判斷是前中后哪個部分的。所以可以用2根指針遍歷的方式去實現
最優方法 兩根指針
1 public class Solution { 2 public void sortColors(int[] nums) { 3 int red = 0, current = 0, blue = nums.length - 1; 4 while (current <= blue) { 5 if (nums[current] == 0) { 6 swap(red, current, nums); 7 red++; 8 current++; 9 } else if (nums[current] == 2) { 10 swap(current, blue, nums); 11 blue--; 12 } else { 13 current++; 14 } 15 } 16 17 18 19 } 20 private static void swap(int i, int j, int[] nums) { 21 int temp = nums[i]; 22 nums[i] = nums[j]; 23 nums[j] = temp; 24 } 25 } View Code?
?
-----------------------------我是分割線----------------------------------------------------------------------
?
?
3 基數排序(Radix Sort)
上述的基數排序和桶排序都只是在研究一個關鍵字的排序,現在我們來討論有多個關鍵字的排序問題。
假設我們有一些二元組(a,b),要對它們進行以a為首要關鍵字,b的次要關鍵字的排序。我們可以先把它們先按照首要關鍵字排序,分成首要關鍵字相同的若干堆。然后,在按照次要關鍵值分別對每一堆進行單獨排序。最后再把這些堆串連到一起,使首要關鍵字較小的一堆排在上面。按這種方式的基數排序稱為MSD(Most Significant Dight)排序。
第二種方式是從最低有效關鍵字開始排序,稱為LSD(Least Significant Dight)排序。首先對所有的數據按照次要關鍵字排序,然后對所有的數據按照首要關鍵字排序。要注意的是,使用的排序算法必須是穩定的,否則就會取消前一次排序的結果。由于不需要分堆對每堆單獨排序,LSD方法往往比MSD簡單而開銷小。下文介紹的方法全部是基于LSD的。
通常,基數排序要用到計數排序或者桶排序。使用計數排序時,需要的是Order數組。使用桶排序時,可以用鏈表的方法直接求出排序后的順序。下面是一段用桶排序對二元組基數排序的程序:
基數排序是非比較排序算法,算法的時間復雜度是O(n). 相比于快速排序的O(nlgn),從表面上看具有不小的優勢.但事實上可能有些出入,因為基數排序的n可能具有比較大的系數K.因此在具體的應用中,應首先對這個排序函數的效率進行評估.
基數排序的主要思路是,將所有待比較數值(注意,必須是正整數)統一為同樣的數位長度,數位較短的數前面補零. 然后, 從最低位開始, 依次進行一次穩定排序(我們常用上一篇blog介紹的計數排序算法, 因為每個位可能的取值范圍是固定的從0到9).這樣從最低位排序一直到最高位排序完成以后, 數列就變成一個有序序列.
比如這樣一個數列排序: 342 58 576 356, 以下描述演示了具體的排序過程(紅色字體表示正在排序的數位)
第一次排序(個位):
3 4?2
5 7?6
3 5?6
0 5?8
第二次排序(十位):
3?4?2
3?5?6
0?5?8
5?7?6
第三次排序(百位):
0?5 8
3?4 2
3?5 6
5?7 6
結果: 58 342 356 576
兩個問題:
- 為什么要從低位開始向高位排序?
??????? 如果要從高位排序, 那么次高位的排序會影響高位已經排好的大小關系. 在數學中, 數位越高,數位值對數的大小的影響就越大.從低位開始排序,就是對這種影響的排序.?數位按照影響力從低到高的順序排序, 數位影響力相同則比較數位值.
- 為什么同一數位的排序子程序要使用穩定排序?
????????穩定排序的意思是指, 待排序相同元素之間的相對前后關系,在各次排序中不會改變.比如實例中具有十位數字5的兩個數字58和356, 在十位排序之前356在58之前,在十位排序之后, 356依然在58之前.
??????? 穩定排序能保證,上一次的排序成果被保留,十位數的排序過程能保留個位數的排序成果,百位數的排序過程能保留十位數的排序成果.
-----------------------------我是分割線-----------------------------------------------------
?
轉載于:https://www.cnblogs.com/jiangchen/p/5918507.html
總結
以上是生活随笔為你收集整理的各种排序笔记---基于非比较排序部分的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java ajaxsubmit_jQue
- 下一篇: 由于开发者通过接口修改了菜单配置_开发者