資料結構與演算法分析-排序,資料結構與演算法排序
作者:xiabodan 出處:http://blog.csdn.net/xiabodan
排序演算法(Sorting Algorithm)是電腦演算法的一個組成部分。也是程式=演算法+資料結構中的一部分(演算法)。
實驗平台:raspberry 2 B + Ubuntu Mate
插入排序外迴圈i由1到N-1,內迴圈由j由i到1,每次內迴圈都將A【j】插入到序列A【0】-A【i】的正確位置,這樣就保證了每次外迴圈之後序列A【0】-A【i】都是已經排好序的,外迴圈僅僅是將自己插入在A【0】-A【i】之間,且不影響原來的序列正確性。和冒泡演算法一樣都要經過O(N^2)次比較和交換,如
//插入排序//stable//O(N^2) comparisons and swaps//Adaptive: O(n) time when nearly sorted//Very low overheadvoid insertion(elementtype A[],int n){int p = 0 ;int j = 0 ;for(p=1;p<n;p++ ){elementtype tem = A[p] ; for(j=p;j>0&&A[j-1]>tem;j--){ A[j] = A[j-1];}A[j] = tem;}}
希爾排序希爾排序有時可以成為增量縮小排序,選擇一個大小為hk的區間,對相隔hk個元素進行插入排序,逐漸減小hk區間,直至hk變為1
//希爾排序//O(N^3/2) unstable//Adaptive: O(N.lg(N)) time when nearly sorted//Very low overheadvoid shell(elementtype A[],int n){int i,j,inc;elementtype tem;for(inc=N/2;inc>0;inc /=2){for(i=inc;i<N;i++){tem = A[i];for(j=i;j>=inc;j-=inc){if(tem<A[j-inc])A[j] = A[j-inc];elsebreak;}A[j] = tem;}}}冒泡排序冒泡排序比較暴力,外迴圈i從A【0】-A【N-1】,內迴圈從A【N-1】-A【i】,同時內迴圈從末尾開始一直向i靠近,並找出其中最小的元素冒出來到A【i+1】,故稱為冒泡排序,十分暴力,但很穩定,需要1+2+3+4+.......+N = N*(N+1) = O(N^2)次比較和交換,效率很低。但是當某次內迴圈檢測到沒有發生交換後,說明A【N-1】-A【i】都是按照順序排列的,不需要排序了。因此如果當給定資料是已經接近排序好的時候,冒泡演算法時間複雜度僅為O(N)。
//冒泡排序//O(N^2) stable//Adaptive: O(N) time when nearly sorted//Very low overheadvoid bubble(elementtype A[],int n){int flag = 1;int i,j;for(i=0;i<n;i++){flag = 0;for(j=n-1;j>i;j--){if(A[j]<A[j-1]){flag = 1;swap(A+j,A+j-1 );}}if(flag == 0) break;}}
選擇排序
選擇排序,外迴圈i從A【0】-A【N-1】,內迴圈從A【i+1】-A【N-1】,內迴圈負責找出A【i+1】-A【N-1】中比A【i】小的元素,並用一個k去標記它的位置,在內迴圈結束的時候將A【i】與A【k】互換,那麼A【i+1】-A【N-1】中比A【i】小的元素A【k】就被放在了A【i】這個位置。選擇排序的比較次數是O(N^2),但是交換次數卻只有O(N)。因為內迴圈每次僅僅是標記最小元素,並沒有即時的去交換元素。
//選擇排序//Not stable//O(1) extra space//Θ(n2) comparisons//Θ(n) swaps//Not adaptivevoid selection(elementtype A[],int n){int i,j;int k;for(i=0;i<n;i++){k = i;for(j=i+1;j<n;j++){if(A[j]<A[k]){k = j;}}swap(A+i,A+k);}}
快速排序快速排序是一個典型的分治策略,就是將很大的集合A劃分為很小的模組,逐一處理。首先需要選擇一個
樞紐元素pivot,然後將整個集合A分為兩半,一辦是都比pivot小的元素,集合Al放在左邊;一組是比pivot大的元素都放在右邊,叫做集合Ar。然後再對左右邊的子集Al與Ar分別採取同樣的方法遞迴,直至集合Al和Ar都只有一個元素,在實際應用中,一般不會讓Al和Ar都遞迴到1個元素,一種要的方法是當Al和Ar小於10(
閾值CUT)後,就不在採用快排演算法,而是換為插入排序演算法效率更高,由於本文測試中不利於大資料的排序,所以僅僅將閾值CUT設定為10。見
另外一個問題是樞紐元素pivot的選擇,選擇樞紐元素pivot的方法有很多,一種常用的就是三數中值演算法。也就是選取集合A首尾以及中位元素,然後選取這三個數的中位元作為樞紐元素pivot。
//快速排序//not Stable//O(lg(n)) extra space (see discussion)//O(n2) time, but typically O(n·lg(n)) time//Not adaptive#define CUT 3elementtype median3(elementtype A[],int left ,int right){int center = (left +right) / 2;if(A[left]>A[center])swap(&A[left],&A[center]);if(A[left]>A[right])swap(&A[left],&A[right]);if(A[center]>A[right])swap(&A[center],&A[right]);swap(&A[center],&A[right-1]);return A[right-1];}void Qsort(elementtype A[],int left, int right){int i,j;elementtype pivot;if(left + CUT<= right){pivot = median3(A,left,right); //select middle element as pivoti = left;j = right-1;for(;;){while(A[++i]<pivot){}while(A[--j]>pivot){}if(i<j)swap(&A[i],&A[j]);elsebreak;}swap(&A[i],&A[right-1]);Qsort(A,left,i-1);Qsort(A,i+1,right);}elseinsertion(A+left,right-left+1);}void quick1(elementtype A[],int n){Qsort(A,0,n-1);}
未完待續.......
總結:
冒泡和插入是慢慢找到最大或最小的放在第一個去;選擇直接找到最大或者最小放到第一個;歸併、快排都用了devide-merge-conquer(分治策略),期間還是會用到前面提到的那幾種最基本的演算法;堆排序用了選擇排序的思想;桶排序用了空間換時間的方法;萬變不離其宗。
參考:
資料結構與演算法分析-C語言描述[M],機械工業出版社
部落格園 vamei的部落格:http://www.cnblogs.com/vamei/archive/2013/03/12/2948847.html
天津城市學院一個精品課程:http://sjjp.tjuci.edu.cn/sjjg/datastructure/ds/web/paixu/paixu8.1.1.1.htm
國外一個排序網站有動畫,分析,為代碼: http://www.sorting-algorithms.com/