各种排序笔记---基于非比较排序部分
在計(jì)算機(jī)科學(xué)中,排序是一門基礎(chǔ)的算法技術(shù),許多算法都要以此作為基礎(chǔ),不同的排序算法有著不同的時(shí)間開(kāi)銷和空間開(kāi)銷。排序算法有非常多種,如我們最常用的快速排序和堆排序等算法,這些算法需要對(duì)序列中的數(shù)據(jù)進(jìn)行比較,因?yàn)楸环Q為基于比較的排序。
基于比較的排序算法是不能突破O(NlogN)的。簡(jiǎn)單證明如下:
N個(gè)數(shù)有N!個(gè)可能的排列情況,也就是說(shuō)基于比較的排序算法的判定樹(shù)有N!個(gè)葉子結(jié)點(diǎn),比較次數(shù)至少為log(N!)=O(NlogN)(斯特林公式)。
而非基于比較的排序,如計(jì)數(shù)排序,桶排序,和在此基礎(chǔ)上的基數(shù)排序,則可以突破O(NlogN)時(shí)間下限。但要注意的是,非基于比較的排序算法的使用都是有條件限制的,例如元素的大小限制,相反,基于比較的排序則沒(méi)有這種限制(在一定范圍內(nèi))。但并非因?yàn)橛袟l件限制就會(huì)使非基于比較的排序算法變得無(wú)用,對(duì)于特定場(chǎng)合有著特殊的性質(zhì)數(shù)據(jù),非基于比較的排序算法則能夠非常巧妙地解決。
基于非比較的排序算法有三種,計(jì)數(shù)排序,桶排序和基數(shù)排序。
-----------------------------我是分割線-------------------------------------------------------
1. 計(jì)數(shù)排序
計(jì)數(shù)排序(Counting sort)是一種穩(wěn)定的線性時(shí)間排序算法。計(jì)數(shù)排序使用一個(gè)額外的數(shù)組C,其中第i個(gè)元素是待排序數(shù)組A中值等于i的元素的個(gè)數(shù)。然后根據(jù)數(shù)組C來(lái)將A中的元素排到正確的位置。
?特征:
當(dāng)輸入的元素是n個(gè)0到k之間的整數(shù)時(shí),它的運(yùn)行時(shí)間是Θ(n?+?k)。計(jì)數(shù)排序不是比較排序,排序的速度快于任何比較排序算法。
由于用來(lái)計(jì)數(shù)的數(shù)組C的長(zhǎng)度取決于待排序數(shù)組中數(shù)據(jù)的范圍(等于待排序數(shù)組的最大值與最小值的差加上1),這使得計(jì)數(shù)排序?qū)τ跀?shù)據(jù)范圍很大的數(shù)組,需要大量時(shí)間和內(nèi)存。例如:計(jì)數(shù)排序是用來(lái)排序0到100之間的數(shù)字的最好的算法,但是它不適合按字母順序排序人名。但是,計(jì)數(shù)排序可以用在基數(shù)排序算法中,能夠更有效的排序數(shù)據(jù)范圍很大的數(shù)組。
通俗地理解,例如有10個(gè)年齡不同的人,統(tǒng)計(jì)出有8個(gè)人的年齡比A小,那A的年齡就排在第9位,用這個(gè)方法可以得到其他每個(gè)人的位置,也就排好了序。當(dāng)然,年齡有重復(fù)時(shí)需要特殊處理(保證穩(wěn)定性),這就是為什么最后要反向填充目標(biāo)數(shù)組,以及將每個(gè)數(shù)字的統(tǒng)計(jì)減去1的原因。算法的步驟如下:
java 實(shí)現(xiàn):
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 // 假設(shè)A中的數(shù)據(jù)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 // 計(jì)數(shù) 18 for (int j = 0; j < A.length; j++) { 19 int a = A[j]; 20 C[a] += 1; 21 } 22 Utils.print(C); 23 // 求計(jì)數(shù)和 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 //針對(duì)c數(shù)組的大小,優(yōu)化過(guò)的計(jì)數(shù)排序 39 public class CountSort{ 40 public static void main(String []args){ 41 //排序的數(shù)組 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的大小是要排序的數(shù)組中,元素大小的極值差+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;//優(yōu)化過(guò)的地方,減小了數(shù)組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優(yōu)化后的代碼:
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數(shù)組下標(biāo) i 就是A 的值,所以我們不需要保留A中原來(lái)的數(shù)了,這個(gè)代碼減少了一個(gè)數(shù)組B,而且要比原來(lái)的代碼簡(jiǎn)化了很多。
-----------------------------我是分割線------------------------------------------------------
?
2. 桶排序
可能你會(huì)發(fā)現(xiàn),計(jì)數(shù)排序似乎饒了點(diǎn)彎子,比如當(dāng)我們剛剛統(tǒng)計(jì)出C,C[i]可以表示A中值為i的元素的個(gè)數(shù),此時(shí)我們直接順序地掃描C,就可以求出排序后的結(jié)果。的確是這樣,不過(guò)這種方法不再是計(jì)數(shù)排序,而是桶排序(Bucket Sort),確切地說(shuō),是桶排序的一種特殊情況。
?
無(wú)序數(shù)組有個(gè)要求,就是成員隸屬于固定(有限的)的區(qū)間,如范圍為[0-9](考試分?jǐn)?shù)為1-100等)
例如待排數(shù)字[6 2 4 1 5 9]
準(zhǔn)備10個(gè)空桶,最大數(shù)個(gè)空桶
[6 2 4 1 5 9]?????????? 待排數(shù)組
[0 0 0 0 0 0 0 0 0 0]?? 空桶
[0 1 2 3 4 5 6 7 8 9]?? 桶編號(hào)(實(shí)際不存在)
?
1,順序從待排數(shù)組中取出數(shù)字,首先6被取出,然后把6入6號(hào)桶,這個(gè)過(guò)程類似這樣:空桶[ 待排數(shù)組[ 0 ] ] = 待排數(shù)組[ 0 ]
[6?2 4 1 5 9]?????????? 待排數(shù)組
[0 0 0 0 0 0?6?0 0 0]?? 空桶
[0 1 2 3 4 5?6?7 8 9]?? 桶編號(hào)(實(shí)際不存在)
?
2,順序從待排數(shù)組中取出下一個(gè)數(shù)字,此時(shí)2被取出,將其放入2號(hào)桶,是幾就放幾號(hào)桶
[6 2?4 1 5 9]?????????? 待排數(shù)組
[0 0?2?0 0 0 6 0 0 0]?? 空桶
[0 1?2?3 4 5 6 7 8 9]?? 桶編號(hào)(實(shí)際不存在)
?
3,4,5,6省略,過(guò)程一樣,全部入桶后變成下邊這樣
[6 2 4?1 5 9]?????????? 待排數(shù)組
[0?1 2?0?4?5 6?0 0?9]?? 空桶
[0?1 2?3?4?5 6 7 8?9]?? 桶編號(hào)(實(shí)際不存在)
?
0表示空桶,跳過(guò),順序取出即可: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中的所有數(shù)據(jù)都初始化為0。 7 memset(buckets, 0, max*sizeof(int)); 8 9 // 1. 計(jì)數(shù) 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這種特殊實(shí)現(xiàn)的方式時(shí)間復(fù)雜度為O(N+K),空間復(fù)雜度也為O(N+K),同樣要求每個(gè)元素都要在K的范圍內(nèi)。更一般的,如果我們的K很大,無(wú)法直接開(kāi)出O(K)的空間該如何呢?
首先定義桶,桶為一個(gè)數(shù)據(jù)容器,每個(gè)桶存儲(chǔ)一個(gè)區(qū)間內(nèi)的數(shù)。依然有一個(gè)待排序的整數(shù)序列A,元素的最小值不小于0,最大值不超過(guò)K。假設(shè)我們有M個(gè)桶,第i個(gè)桶Bucket[i]存儲(chǔ)K * (i/M) 至 k * (i+1)/M之間的數(shù),有如下桶排序的一般方法:
對(duì)該算法簡(jiǎn)單分析,如果數(shù)據(jù)是期望平均分布的,則每個(gè)桶中的元素平均個(gè)數(shù)為N/M。如果對(duì)每個(gè)桶中的元素排序使用的算法是快速排序,每次排序的時(shí)間復(fù)雜度為O(N/Mlog(N/M))。則總的時(shí)間復(fù)雜度為O(N)+O(M)O(N/Mlog(N/M)) = O(N+ Nlog(N/M)) =O(N + NlogN - NlogM)。當(dāng)M接近于N是,桶排序的時(shí)間復(fù)雜度就可以近似認(rèn)為是O(N)的。就是桶越多,時(shí)間效率就越高,而桶越多,空間卻就越大,由此可見(jiàn)時(shí)間和空間是一個(gè)矛盾的兩個(gè)方面。
桶中元素的順序放入和順序取出是有必要的,因?yàn)檫@樣可以確定桶排序是一種穩(wěn)定排序算法,配合基數(shù)排序是很好用的。
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中元素加入桶中時(shí)是倒序放入的,而收集取出時(shí)也是倒序放入序列的,所以不違背穩(wěn)定排序。 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中的每個(gè)元素按照的范圍值放入對(duì)應(yīng)桶中 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; //把桶中每個(gè)元素取出,排序并加入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的隨機(jī)數(shù) 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].
次優(yōu)方法 桶排序/計(jì)數(shù)排序 代碼:
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由于這個(gè)題目只有3個(gè)值,也就是拿到數(shù),可以判斷是前中后哪個(gè)部分的。所以可以用2根指針遍歷的方式去實(shí)現(xiàn)
最優(yōu)方法 兩根指針
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 基數(shù)排序(Radix Sort)
上述的基數(shù)排序和桶排序都只是在研究一個(gè)關(guān)鍵字的排序,現(xiàn)在我們來(lái)討論有多個(gè)關(guān)鍵字的排序問(wèn)題。
假設(shè)我們有一些二元組(a,b),要對(duì)它們進(jìn)行以a為首要關(guān)鍵字,b的次要關(guān)鍵字的排序。我們可以先把它們先按照首要關(guān)鍵字排序,分成首要關(guān)鍵字相同的若干堆。然后,在按照次要關(guān)鍵值分別對(duì)每一堆進(jìn)行單獨(dú)排序。最后再把這些堆串連到一起,使首要關(guān)鍵字較小的一堆排在上面。按這種方式的基數(shù)排序稱為MSD(Most Significant Dight)排序。
第二種方式是從最低有效關(guān)鍵字開(kāi)始排序,稱為LSD(Least Significant Dight)排序。首先對(duì)所有的數(shù)據(jù)按照次要關(guān)鍵字排序,然后對(duì)所有的數(shù)據(jù)按照首要關(guān)鍵字排序。要注意的是,使用的排序算法必須是穩(wěn)定的,否則就會(huì)取消前一次排序的結(jié)果。由于不需要分堆對(duì)每堆單獨(dú)排序,LSD方法往往比MSD簡(jiǎn)單而開(kāi)銷小。下文介紹的方法全部是基于LSD的。
通常,基數(shù)排序要用到計(jì)數(shù)排序或者桶排序。使用計(jì)數(shù)排序時(shí),需要的是Order數(shù)組。使用桶排序時(shí),可以用鏈表的方法直接求出排序后的順序。下面是一段用桶排序?qū)ΧM基數(shù)排序的程序:
基數(shù)排序是非比較排序算法,算法的時(shí)間復(fù)雜度是O(n). 相比于快速排序的O(nlgn),從表面上看具有不小的優(yōu)勢(shì).但事實(shí)上可能有些出入,因?yàn)榛鶖?shù)排序的n可能具有比較大的系數(shù)K.因此在具體的應(yīng)用中,應(yīng)首先對(duì)這個(gè)排序函數(shù)的效率進(jìn)行評(píng)估.
基數(shù)排序的主要思路是,將所有待比較數(shù)值(注意,必須是正整數(shù))統(tǒng)一為同樣的數(shù)位長(zhǎng)度,數(shù)位較短的數(shù)前面補(bǔ)零. 然后, 從最低位開(kāi)始, 依次進(jìn)行一次穩(wěn)定排序(我們常用上一篇blog介紹的計(jì)數(shù)排序算法, 因?yàn)?span style="color:#008000;">每個(gè)位可能的取值范圍是固定的從0到9).這樣從最低位排序一直到最高位排序完成以后, 數(shù)列就變成一個(gè)有序序列.
比如這樣一個(gè)數(shù)列排序: 342 58 576 356, 以下描述演示了具體的排序過(guò)程(紅色字體表示正在排序的數(shù)位)
第一次排序(個(gè)位):
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
結(jié)果: 58 342 356 576
兩個(gè)問(wèn)題:
- 為什么要從低位開(kāi)始向高位排序?
??????? 如果要從高位排序, 那么次高位的排序會(huì)影響高位已經(jīng)排好的大小關(guān)系. 在數(shù)學(xué)中, 數(shù)位越高,數(shù)位值對(duì)數(shù)的大小的影響就越大.從低位開(kāi)始排序,就是對(duì)這種影響的排序.?數(shù)位按照影響力從低到高的順序排序, 數(shù)位影響力相同則比較數(shù)位值.
- 為什么同一數(shù)位的排序子程序要使用穩(wěn)定排序?
????????穩(wěn)定排序的意思是指, 待排序相同元素之間的相對(duì)前后關(guān)系,在各次排序中不會(huì)改變.比如實(shí)例中具有十位數(shù)字5的兩個(gè)數(shù)字58和356, 在十位排序之前356在58之前,在十位排序之后, 356依然在58之前.
??????? 穩(wěn)定排序能保證,上一次的排序成果被保留,十位數(shù)的排序過(guò)程能保留個(gè)位數(shù)的排序成果,百位數(shù)的排序過(guò)程能保留十位數(shù)的排序成果.
-----------------------------我是分割線-----------------------------------------------------
?
轉(zhuǎn)載于:https://www.cnblogs.com/jiangchen/p/5918507.html
總結(jié)
以上是生活随笔為你收集整理的各种排序笔记---基于非比较排序部分的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java ajaxsubmit_jQue
- 下一篇: 由于开发者通过接口修改了菜单配置_开发者