排序算法實現及分析

所謂排序,就是要整理文件中的記錄,使之按關鍵字遞增(或遞減)次序排列起來。其確切定義如下:
  輸入:n個記錄R1,R2,…,Rn,其相應的關鍵字分別爲K1,K2,…,Kn

  輸出:Ril,Ri2,…,Rin,使得Ki1≤Ki2≤…≤Kin。(或Ki1≥Ki2≥…≥Kin)。

    排序的時間開銷可用算法執行中的數據比較次數與數據移動次數來衡量。基本的排序算法有如下幾種:交換排序(冒泡排序、快速排序)、選擇排序(直接選擇排序、堆排序)、插入排序(直接插入排序、希爾排序)、歸併排序、分配排序(基數排序、箱排序、計數排序)。下面依次列出各種算法的代碼,並進行簡要分析。分配排序算法的代碼沒有列出。

    (1)快速排序:快速排序是C.R.A.Hoare於1962年提出的一種劃分交換排序。它採用了一種分治的策略,通常稱其爲分治法(Divide-and-ConquerMethod)。最好、平均複雜度都爲O(nlogn),最壞爲O(n^2)。

  1. void quick_sort1(int a[],int l,int r)  
  2. {  
  3.     if(l >= r)  
  4.         return;  
  5.     int i, j, p;  
  6.     i = l-1, j = l,p = a[r];  
  7.     while(j < r)  
  8.     {  
  9.         if(a[j] < p)  
  10.             swap(a[++i], a[j]);  
  11.         j++;  
  12.     }  
  13.     swap(a[++i], a[r]);  
  14.     quick_sort1(a, l, i-1);  
  15.     quick_sort1(a, i+1, r);  
  16. }  

    《算法導論》一書中,給出了這個程序的僞代碼。當數組元素相等、逆序、順序排列時,調用這個程序會導致棧溢出。因爲每次劃分都是最壞壞分。可以改進一下。上述程序每次選的劃分基準元素都是固定的,如果是隨機產生的,那麼可以大大降低出現最壞劃分的概率。

  1. void quick_sort2(int a[],int l,int r)  
  2. {  
  3.     if(l >= r)  
  4.         return;  
  5.     int i,j,p;  
  6.     i = l-1,j = l;  
  7.   
  8.     p=l + rand()%(r-l); //隨機產生[l,r)之間的數  
  9.     swap(a[p], a[r]);  
  10.     p = a[r];  
  11.     while(j < r)  
  12.     {  
  13.         if(a[j] < p)  
  14.             swap(a[++i], a[j]);  
  15.         j++;  
  16.     }  
  17.     swap(a[++i], a[r]);  
  18.     quick_sort2(a, l, i-1);  
  19.     quick_sort2(a, i+1, r);  
  20. }  

    但是,當數組元素相等時,還是出現了棧溢出。可以做如下調整。

  1. void quick_sort3(int a[],int l,int r)  
  2. {  
  3.     if(l >= r)  
  4.         return;  
  5.     int i,j,p;  
  6.     i = l-1, j = r, p = a[r];  
  7.     while(1)  
  8.     {  
  9.         do { i++; } while(a[i] < p && i < r);  
  10.         do { j--; } while(a[j] > p && j > l);  
  11.         if(i >= j)  
  12.             break;  
  13.         swap(a[i], a[j]);  
  14.     }  
  15.     swap(a[i],a[r]);  
  16.     quick_sort3(a, l, i-1);  
  17.     quick_sort3(a, i+1, r);  
  18. }  

    但是,當數組元素順序,逆序時,同樣出現了棧溢出。若將兩者結合起來,就可以儘量避免棧溢出。

  1. void quick_sort4(int a[],int l,int r)  
  2. {  
  3.     if(l >= r)  
  4.         return;  
  5.     int i,j,p;  
  6.     i = l-1, j = r;  
  7.   
  8.     p = l + rand()%(r-l);  
  9.     swap(a[p],a[r]);  
  10.     p = a[r];  
  11.     while(1)  
  12.     {  
  13.         do { i++; } while(a[i] < p && i < r);  
  14.         do { j--; } while(a[j] > p && j > l);  
  15.         if(i >= j)  
  16.             break;  
  17.         swap(a[i], a[j]);  
  18.     }  
  19.     swap(a[i], a[r]);  
  20.     quick_sort4(a, l, i-1);  
  21.     quick_sort4(a, i+1, r);  
  22. }  

    (2)冒泡排序:兩兩比較待排序記錄的關鍵字,發現兩個記錄的次序相反時即進行交換,直到沒有反序的記錄爲止。

  1. void bubble_sort1(int a[],int n)  
  2. {  
  3.     int i,j;  
  4.     for(i = 0; i < n-1; i++)  
  5.     {  
  6.         for(j = i+1; j < n; j++)  
  7.         {  
  8.             if(a[i] > a[j])  
  9.                 swap(a[i], a[j]);  
  10.         }  
  11.     }  
  12. }  

    可以稍作改進,當數組數元素順序時,時間複雜度爲O(n)。加入一個變量,如果在一遍掃描中,沒有出現交換,那麼結束排序,因爲數組已排好序了。

  1. void bubble_sort2(int a[],int n)  
  2. {  
  3.     int i,j;  
  4.     for(i = 0; i < n-1; i++)  
  5.     {  
  6.         bool exchange = false;  
  7.         for(j = i+1; j < n; j++)  
  8.         {  
  9.             if(a[i] > a[j])  
  10.             {  
  11.                 exchange = true;  
  12.                 swap(a[i], a[j]);  
  13.             }  
  14.         }  
  15.         if(exchange == false)  
  16.             break;  
  17.     }  
  18. }  

     經網友指出,上面這個冒泡排序有問題,無法得到正確結果。下面引自網友的正確寫法:

  1. void bubble_sort2(int a[],int n)  
  2. {  
  3.     int i,j;  
  4.     for(i = 0;i < n-1; i++)  
  5.     {  
  6.         bool exchange = false;  
  7.         for(j = n-1;j > i; j--)  
  8.         {  
  9.             if(a[j-1] > a[j])  
  10.             {  
  11.                 exchange = true;  
  12.                 swap(a[j-1], a[j]);  
  13.             }  
  14.         }     
  15.         if(exchange == false)  
  16.             break;  
  17.     }  
  18. }  

    (3)直接選擇排序:每一趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已排好序的子文件的最後,直到全部記錄排序完畢。

  1. void select_sort1(int a[],int n)  
  2. {  
  3.     int i,j;  
  4.     for(i = 0; i < n-1; i++)  
  5.     {  
  6.         int min = i;  
  7.         for(j = i+1; j < n; j++)  
  8.         {  
  9.             if(a[j] < a[min])  
  10.                 min = j;  
  11.         }  
  12.         if(min != i)  
  13.             swap(a[i], a[min]);  
  14.     }  
  15. }  

    (4)堆排序:根據輸入數據,利用堆的調整算法形成初始堆,然後交換根元素與尾元素,總的元素個數減1,然後從根往下調整。堆排序的最好、最壞、平均時間複雜度都爲O(nlogn)

  1. void heap_siftdown(int a[],int n,int p) //調整算法  
  2. {  
  3.     int i = p,j = i*2+1;  
  4.     int tmp = a[i];  
  5.     while(j < n)  
  6.     {  
  7.         if(j+1 < n && a[j] < a[j+1])  
  8.             j++;  
  9.         if(a[j] <= tmp)  
  10.             break;  
  11.         else  
  12.         {  
  13.             a[i] = a[j];  
  14.             i = j;j = j*2+1;  
  15.         }  
  16.     }  
  17.     a[i] = tmp;  
  18. }  
  19. void heap_sort1(int a[],int n)  
  20. {  
  21.     int i;  
  22.     for(i = (n-1)/2; i >= 0;i--)  
  23.         heap_siftdown(a, n, i);  
  24.     for(i = n-1;i >= 0; i--)  
  25.     {  
  26.         swap(a[i], a[0]);  
  27.         heap_siftdown(a, i, 0);  
  28.     }  
  29. }  

     (5)直接插入排序:每次將一個待排序的記錄,按其關鍵字大小插入到前面已經排好序的子文件中的適當位置,直到全部記錄插入完成爲止。當數組已經排好序,直接插入排序的時間複雜度爲O(n)

  1. void insert_sort1(int a[],int n)  
  2. {  
  3.     int i,j;  
  4.     for(i = 1; i < n; i++)  
  5.     {  
  6.         for(j = i; j > 0 && a[j]<a[j-1]; j--)  
  7.             swap(a[j-1], a[j]);  
  8.     }  
  9. }  

     如果將交換函數展開,可以加快排序的速度。

  1. void insert_sort2(int a[],int n)  
  2. {  
  3.     int i,j;  
  4.     for(i = 1; i < n; i++)  
  5.     {  
  6.         for(j = i; j > 0 && a[j] < a[j-1]; j--)  
  7.         {  
  8.             int t = a[j-1];  
  9.             a[j-1] = a[j];  
  10.             a[j] = t;  
  11.         }  
  12.     }  
  13. }  

     可以進一步改進,insert_sort2的算法不斷給t賦值,可以將賦值語句移到循環外面。

  1. void insert_sort3(int a[],int n)  
  2. {  
  3.     int i,j;  
  4.     for(i = 1;i < n; i++)  
  5.     {  
  6.         int t = a[i];  
  7.         for(j = i; j > 0 && a[j-1] > t; j--)  
  8.             a[j] = a[j-1];  
  9.         a[j] = t;  
  10.     }  
  11. }  

     (6)希爾排序:先取一個小於n的整數d1作爲第一個增量,把文件的全部記錄分成d1個組。所有距離爲dl的倍數的記錄放在同一個組中。先在各組內進行直接插人排序;然後,取第二個增量d2<d1重複上述的分組和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有記錄放在同一組中進行直接插入排序爲止。

    最後一遍的增量必須是1,其實是就是調用直接插入排序算法。

  1. void shell_sort1(int a[],int n)  
  2. {  
  3.     int i = n;  
  4.     do{  
  5.         i = i/3 + 1;  
  6.         shell_pass1(a, n, i);  
  7.     }while(i > 1);  
  8. }  
  9. void shell_pass1(int a[],int n,int inc)  //inc爲1時,其實就是直接插入排序  
  10. {  
  11.     int i,j;  
  12.     for(i = inc; i < n; i++)  
  13.     {  
  14.         int t=a[i];  
  15.         for(j = i;j >= inc && a[j-inc] > t; j-= inc)  
  16.             a[j] = a[j-inc];  
  17.         a[j] = t;  
  18.     }  
  19. }  

  (7)歸併排序:利用"歸併"技術來進行排序。歸併是指將若干個已排序的子文件合併成一個有序的文件。可以用於外排序。

  1. void merge_sort1(int a[],int b[],int l,int r)  
  2. {  
  3.     if(l >= r)  
  4.         return;  
  5.     int m = (l+r)/2;  
  6.     merge_sort1(a, b, l, m);  
  7.     merge_sort1(a, b, m+1, r);  
  8.     merge1(a, b, l, m, r);  
  9. }  
  10. void merge1(int a[],int b[],int l,int m,int r)  
  11. {  
  12.     int i,j,k;  
  13.     for(i = l; i <= r; i++)  
  14.         b[i] = a[i];  
  15.     i = l; j = m+1; k = l;  
  16.     while(i <= m && j <= r)  
  17.     {  
  18.         if(b[i] <= b[j]) a[k++] = b[i++];  
  19.         else a[k++] = b[j++];  
  20.     }  
  21.     while(i <= m) a[k++] = b[i++];  
  22.     while(j <= r) a[k++] = b[j++];  
  23. }  

     給出上述算法的程序的測試驅動程序及兩個輔助程序。要測試某種排序算法,只需將註釋去掉即可。

  1. #include <iostream>  
  2. #include <ctime>  
  3. using namespace std;  
  4. const int N = 100;  
  5. int a[N];  
  6. int b[N];  
  7.   
  8. int main()   
  9. {  
  10.     int i;  
  11.     srand(time(0));  
  12.     //for(i=0;i<N;i++)  
  13.     // a[i]= N-i;  
  14.     for(i = 0;i < N; i++)  
  15.         a[i]=rand()%N;  
  16.     long start,end;  
  17.     start = clock();  
  18.   
  19.     //quick_sort1(a,0,N-1);  
  20.     //quick_sort2(a,0,N-1);  
  21.     //quick_sort3(a,0,N-1);  
  22.     //quick_sort4(a,0,N-1);  
  23.     //bubble_sort1(a,N);  
  24.     //bubble_sort2(a,N);  
  25.     //merge_sort1(a,b,0,N-1);  
  26.     //heap_sort1(a,N);  
  27.     //shell_sort1(a,N);  
  28.     //select_sort1(a,N);  
  29.     //insert_sort1(a,N);  
  30.     //insert_sort2(a,N);  
  31.     //insert_sort3(a,N);  
  32.   
  33.     end = clock();  
  34.     print_array(a, N);  
  35.     cout<<"total time is : "<<(end-start)/1000.0<<'s'<<endl;  
  36.     return 0;  
  37. }  
  38.   
  39. void swap(int a[],int i,int j) //交換元素  
  40. {  
  41.     int t = a[i];  
  42.     a[i] = a[j];  
  43.     a[j] = t;  
  44. }  
  45.   
  46. void print_array(int a[],int n) //打印元素值  
  47. {  
  48.     for(int i = 0; i < n; i++)  
  49.     {  
  50.         cout<<a[i]<<' ';  
  51.         if(i%10==0 && i!=0)  
  52.             cout<<endl;  
  53.     }  
  54.     cout<<endl;  
  55. }  

     下面對排序算法做一個簡單的比較。直接選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法,而冒泡排序、插入排序、歸併排序和基數排序是穩定的排序算法。

                     最好          最壞         平均         穩定         輔助空間       

快速排序            O(nlogn)       O(n^2)     O(nlogn)        否            不要          

冒泡排序            O(n)           O(n^2)     O(n^2)          是            不要

直接選擇排序        O(n^2)        O(n^2)     O(n^2)          否            不要

堆排序              O(nlogn)      O(nlogn)  O(nlogn)        否            不要

直接插入排序        O(n)           O(n^2)     O(n^2)          是            不要

希爾排序            O(n)           O(n^2)     O(n^2)          否            不要

歸併排序            O(nlogn)       O(nlogn)   O(nlogn)        是            要

轉載 http://blog.csdn.net/wuzhekai1985


發佈了35 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章