之前講了三種常用的經典排序。排序算法還有很多,例如選擇排序、計數排序、基數排序、插入排序、歸並排序和堆排序等等。堆排序是基於二叉樹的排序,以后再說吧。先分享一個超酷的排序算法的視頻。
再來看一個具體的例子《小哼買書》來看看三個排序在應用上的區別和局限性。 小哼的學校要建立一個圖書角,老師派小哼去找一些同學做調查,看看同學們都喜歡讀哪些書。小哼讓每個同學寫出一個自己最想讀的書的ISBN號(你知道嗎?每本書都有唯一的ISBN號,不信話你去找本書翻到背面看看)。當然有一些好書會有很多同學都喜歡,這樣就會收集到很多重復的ISBN號。小哼需要去掉其中重復的ISBN號,即每個ISBN號只保留一個,也就說同樣的書只買一本(學校真是夠摳門的)。然后再把這些ISBN號從小到大排序,小哼將按照排序好的ISBN號去書店去買書。請你協助小哼完成“去重”與“排序”的工作。

輸入有2行,第1行為一個正整數,表示有n個同學參與調查(n<=100)。第2行有n個用空格隔開的正整數,為每本圖書的ISBN號(假設圖書的ISBN號在1~1000之間)。
輸出也是2行,第1行為一個正整數k,表示需要買多少本書。第2行為k個用空格隔開的正整數,為從小到大已排好序的需要購買的圖書ISBN號。
例如輸入
10 20 40 32 67 40 20 89 300 400 15
則輸出
8 15 20 32 40 67 89 300 400
最后,程序運行的時間限制為:1秒。
解決這個問題的方法大致有兩種,第一種方法:先將這n個圖書的ISBN號去重,再進行從小到大排序並輸出。第二種方法:先從小到大排序,輸出的時候再去重。這兩種方法都可以。
先來看第一種方法。通過第一節的學習我們發現桶排序稍加改動正好可以起到去重的效果,因此我們可以使用桶排序的方法來解決此問題。
#include <stdio.h> int main() { int a[1001],n,i,t; for(i=1;i<=1000;i++) a[i]=0; //初始化 scanf("%d",&n); //讀入n for(i=1;i<=n;i++) //循環讀入n個圖書的ISBN號 { scanf("%d",&t); //把每一個ISBN號讀到變量t中 a[t]=1; //標記出現過的ISBN號 } for(i=1;i<=1000;i++) //依次判斷1~1000這個1000個桶 { if(a[i]==1)//如果這個ISBN號出現過則打印出來 printf("%d ",i); } getchar();getchar(); return 0; }
這種方法的時間復雜度是就是桶排序的時間復雜度為O(N+M)。
第二種方法我們需要先排序再去重。排序我們可以用冒泡排序或者快速排序。
20 40 32 67 40 20 89 300 400 15
將這10個數從小到大排序之后為 15 20 20 32 40 40 67 89 300 400。
接下來,要在輸出的時候去掉重復的。因為我們已經排好序,因此相同的數都會緊挨在一起。只要在輸出的時候,預先判斷一下當前這個數a[i ]與前面一個數a[i-1]是否相同。如果相同則表示這個數之前已經輸出過了,不同再次輸出。不同則表示這個數是第一次出現需要,則需要輸出這個數。
#include <stdio.h> int main() { int a[101],n,i,j,t; scanf("%d",&n); //讀入n for(i=1;i<=n;i++) //循環讀入n個圖書ISBN號 { scanf("%d",&a[i]); } //開始冒泡排序 for(i=1;i<=n-1;i++) { for(j=1;j<=n-i;j++) { if(a[j]>a[j+1]) { t=a[j]; a[j]=a[j+1]; a[j+1]=t; } } } printf("%d ",a[1]); //輸出第1個數 for(i=2;i<=n;i++) //從2循環到n { if( a[i] != a[i-1] ) //如果當前這個數是第一次出現則輸出 printf("%d ",a[i]); } getchar();getchar(); return 0; }
這種方法的時間復雜度由兩部分組成,一部分是冒泡排序時間復雜度是O(N2),另一部分是讀入和輸出都是O(N),因此整個算法的時間復雜度是O(2*N+N2)。相對於N2來說,2*N可以忽略(我們通常忽略低階),最終該方法的時間復雜度是O(N2)。
接下來我們還需要看下數據范圍。每個圖書ISBN號都是1~1000之間的整數,並且參加調查的同學人數不超過100,即n<=100。之前已經說過,在粗略計算時間復雜度的時候,我們通常認為計算機每秒鍾大約運行10億次(當然實際情況要更快)。因此以上兩種方法都可以在1秒鍾內計算出解。如果題目圖書ISBN號范圍不是在1~1000之間,而是-2147483648~2147483647之間的話,那么第一種方法就不可行了,因為你無法申請出這么大數組來標記每一個ISBN號是否出現過。另外如果n的范圍不是小於等於100而是小於等於10萬,那么第二種方法的排序部分也不能使用冒泡排序。因為題目要求的時間限制是1秒,使用冒泡排序對10萬個數的排序,計算機需要運行100億次,需要10秒鍾,需要替換為快速排序,快速排序只需要100000×log2100000≈100000×17≈170萬次,這還不到0.0017秒。是不是很神奇,同樣的問題使用不同的算法竟然有如此之大的時間差距,這就是算法的魅力!
我們來回顧一下本章三種排序算法的時間復雜度。桶排序是最快的,它的時間復雜度是O(N+M);冒泡排序是O(N2);快速排序是O(NlogN)。