經典排序算法

 

 

 

 

排序算法大致可分爲兩種:html

    一種是比較排序,時間複雜度O(nlogn) ~ O(n^2),主要有:冒泡排序選擇排序希爾排序,插入排序歸併排序堆排序快速排序等。ios

    另外一種是非比較排序,時間複雜度能夠達到O(n),主要有:計數排序基數排序桶排序等。c++

 

 排序算法的穩定性:git

  排序算法穩定性的簡單形式化定義爲:若是Ai = Aj,排序前Ai在Aj以前,排序後Ai還在Aj以前,則稱這種排序算法是穩定的。通俗地講就是保證排序先後兩個相等的數的相對順序不變。算法

  對於不穩定的排序算法,只要舉出一個實例,便可說明它的不穩定性;而對於穩定的排序算法,必須對算法進行分析從而獲得穩定的特性。須要注意的是,排序算法是否爲穩定的是由具體算法決定的,不穩定的算法在某種條件下能夠變爲穩定的算法,而穩定的算法在某種條件下也能夠變爲不穩定的算法。api

  例如,對於冒泡排序,本來是穩定的排序算法,若是將記錄交換的條件改爲A[i] >= A[i + 1],則兩個相等的記錄就會交換位置,從而變成不穩定的排序算法。數組

  其次,說一下排序算法穩定性的好處。排序算法若是是穩定的,那麼從一個鍵上排序,而後再從另外一個鍵上排序,前一個鍵排序的結果能夠爲後一個鍵排序所用。基數排序就是這樣,先按低位排序,逐次按高位排序,低位排序後元素的順序在高位也相同時是不會改變的。數據結構

 

1、冒泡排序架構

 冒泡排序是一種極其簡單的排序算法,也是我所學的第一個排序算法。它重複地走訪過要排序的元素,依次比較相鄰兩個元素,若是他們的順序錯誤就把他們調換過來,直到沒有元素再須要交換,排序完成。這個算法的名字由來是由於越小(或越大)的元素會經由交換慢慢「浮」到數列的頂端。ide

儘管冒泡排序是最容易瞭解和實現的排序算法之一,但它對於少數元素以外的數列排序是很沒有效率的。

  冒泡排序算法的運做以下:

  1. 比較相鄰的元素,若是前一個比後一個大,就把它們兩個調換位置。
  2. 對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對。這步作完後,最後的元素會是最大的數。
  3. 針對全部的元素重複以上的步驟,除了最後一個。
  4. 持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 
 4 int a[]={6, 5, 3, 1, 8, 7, 2, 4};  5 
 6 void BubbleSort(int a[],int n){  7     for(int i=0;i<n-1;i++){  8         for(int j=0;j<n-1-i;j++){  9             if(a[j]>a[j+1]){ 10                 int temp=a[j]; 11                 a[j]=a[j+1]; 12                 a[j+1]=temp; 13  } 14  } 15  } 16 } 17 
18 int main(){ 19     ios::sync_with_stdio(false); 20     int n=sizeof(a)/sizeof(int);//sizeof()求的是字節數 
21     cout<<n<<endl; 22  BubbleSort(a,n); 23     for(int i=0;i<n;i++){ 24         cout<<a[i]<<" "; 25  } 26     cout<<endl; 27     return 0; 28 } 

 

2、冒泡排序的改進--雞尾酒排序

  雞尾酒排序,也叫定向冒泡排序,是冒泡排序的一種改進。此算法與冒泡排序的不一樣處在於從低到高而後從高到低,而冒泡排序則僅從低到高去比較序列裏的每一個元素。他能夠獲得比冒泡排序稍微好一點的效能。

以序列(2,3,4,5,1)爲例,雞尾酒排序只須要訪問一次序列就能夠完成排序,但若是使用冒泡排序則須要四次。可是在亂數序列的狀態下,雞尾酒排序與冒泡排序的效率都不好勁。

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 
 4 int a[]={6, 5, 3, 1, 8, 7, 2, 4};  5 
 6 void CocktailSort(int a[],int n){  7     int left=0,right=n-1;  8     while(left<right){  9         for(int i=left;i<right;i++){ 10             if(a[i]>a[i+1]){ 11                 int temp=a[i]; 12                 a[i]=a[i+1]; 13                 a[i+1]=temp; 14  } 15  } 16         right--; 17         for(int i=right;i>left;i--){ 18             if(a[i-1]>a[i]){ 19                 int temp=a[i]; 20                 a[i]=a[i-1]; 21                 a[i-1]=temp; 22  } 23  } 24         left++; 25  } 26 } 27 
28 int main(){ 29     ios::sync_with_stdio(false); 30     int n=sizeof(a)/sizeof(int);//sizeof()求的是字節數 
31     cout<<n<<endl; 32  CocktailSort(a,n); 33     for(int i=0;i<n;i++){ 34         cout<<a[i]<<" "; 35  } 36     cout<<endl; 37     return 0; 38 } 

 

3、選擇排序

  選擇排序也是一種簡單直觀的排序算法。它的工做原理很容易理解:初始時在序列中找到最小(大)元素,放到序列的起始位置做爲已排序序列;而後,再從剩餘未排序元素中繼續尋找最小(大)元素,放到已排序序列的末尾。以此類推,直到全部元素均排序完畢。

  注意選擇排序與冒泡排序的區別:冒泡排序經過依次交換相鄰兩個順序不合法的元素位置,從而將當前最小(大)元素放到合適的位置;而選擇排序每遍歷一次都記住了當前最小(大)元素的位置,最後僅需一次交換操做便可將其放到合適的位置。

 選擇排序是不穩定的排序算法,不穩定發生在最小元素與A[i]交換的時刻。

 好比序列:{ 5, 8, 5, 2, 9 },一次選擇的最小元素是2,而後把2和第一個5進行交換,從而改變了兩個元素5的相對次序。

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 
 4 int a[]={6, 5, 3, 1, 8, 7, 2, 4};  5 
 6 void SelectionSort(int a[],int n){  7     for(int i=0;i<n;i++){  8         int ans=i;  9         for(int j=i+1;j<n;j++){ 10             if(a[j]<a[ans]){ 11                 ans=j; 12  } 13  } 14         if(ans!=i){ 15             int temp=a[i]; 16             a[i]=a[ans]; 17             a[ans]=temp; 18  } 19  } 20 } 21 
22 int main(){ 23     ios::sync_with_stdio(false); 24     int n=sizeof(a)/sizeof(int);//sizeof()求的是字節數 
25     cout<<n<<endl; 26  SelectionSort(a,n); 27     for(int i=0;i<n;i++){ 28         cout<<a[i]<<" "; 29  } 30     cout<<endl; 31     return 0; 32 } 

 

4、插入排序

  插入排序是一種簡單直觀的排序算法。它的工做原理很是相似於咱們抓撲克牌

  對於未排序數據(右手抓到的牌),在已排序序列(左手已經排好序的手牌)中從後向前掃描,找到相應位置並插入。

  插入排序在實現上,一般採用in-place排序(即只需用到O(1)的額外空間的排序),於是在從後向前掃描過程當中,須要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。

  具體算法描述以下:

  1. 從第一個元素開始,該元素能夠認爲已經被排序
  2. 取出下一個元素,在已經排序的元素序列中從後向前掃描
  3. 若是該元素(已排序)大於新元素,將該元素移到下一位置
  4. 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
  5. 將新元素插入到該位置後
  6. 重複步驟2~5

 

  插入排序不適合對於數據量比較大的排序應用。可是,若是須要排序的數據量很小,好比量級小於千,那麼插入排序仍是一個不錯的選擇。 插入排序在工業級庫中也有着普遍的應用,在STL的sort算法和stdlib的qsort算法中,都將插入排序做爲快速排序的補充,用於少許元素的排序(一般爲8個或如下)。

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 
 4 int a[]={6, 5, 3, 1, 8, 7, 2, 4};  5 
 6 void InsertionSort(int a[],int n){  7     for(int i=1;i<n;i++){  8         int temp=a[i];  9         int j=i-1; 10         while(j>=0&&a[j]>temp){ 11             a[j+1]=a[j]; 12             j--; 13  } 14         a[j+1]=temp; 15  } 16 } 17 
18 int main(){ 19     ios::sync_with_stdio(false); 20     int n=sizeof(a)/sizeof(int);//sizeof()求的是字節數 
21     cout<<n<<endl; 22  InsertionSort(a,n); 23     for(int i=0;i<n;i++){ 24         cout<<a[i]<<" "; 25  } 26     cout<<endl; 27     return 0; 28 } 

 

5、插入排序的改進--二分插入排序

  對於插入排序,若是比較操做的代價比交換操做大的話,能夠採用二分查找法來減小比較操做的次數,咱們稱爲二分插入排序

  當n較大時,二分插入排序的比較次數比直接插入排序的最差狀況好得多,但比直接插入排序的最好狀況要差,所當以元素初始序列已經接近升序時,直接插入排序比二分插入排序比較次數少。二分插入排序元素移動次數與直接插入排序相同,依賴於元素初始序列。

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 
 4 int a[]={6, 5, 3, 1, 8, 7, 2, 4};  5 
 6 void InsertionSortDichotomy(int a[],int n){  7     for(int i=1;i<n;i++){  8         int temp=a[i];  9         int left=0,right=i-1; 10         while(left<=right){ 11             int mid=(left+right)/2; 12             if(a[mid]>temp) 13             right=mid-1; 14             else
15             left=mid+1; 16  } 17         for(int j=i-1;j>=left;j--){ 18             a[j+1]=a[j]; 19  } 20         a[left]=temp; 21  } 22 } 23 
24 int main(){ 25     ios::sync_with_stdio(false); 26     int n=sizeof(a)/sizeof(int);//sizeof()求的是字節數 
27     cout<<n<<endl; 28  InsertionSortDichotomy(a,n); 29     for(int i=0;i<n;i++){ 30         cout<<a[i]<<" "; 31  } 32     cout<<endl; 33     return 0; 34 } 

 

6、插入排序更高效的改進--希爾排序

希爾排序,也叫遞減增量排序,是插入排序的一種更高效的改進版本。希爾排序是不穩定的排序算法。

  希爾排序是基於插入排序的如下兩點性質而提出改進方法的:

  • 插入排序在對幾乎已經排好序的數據操做時,效率高,便可以達到線性排序的效率
  • 但插入排序通常來講是低效的,由於插入排序每次只能將數據移動一位

  希爾排序經過將比較的所有元素分爲幾個區域來提高插入排序的性能。這樣可讓一個元素能夠一次性地朝最終位置前進一大步。而後算法再取愈來愈小的步長進行排序,算法的最後一步就是普通的插入排序,可是到了這步,需排序的數據幾乎是已排好的了(此時插入排序較快)。
  假設有一個很小的數據在一個已按升序排好序的數組的末端。若是用複雜度爲O(n^2)的排序(冒泡排序或直接插入排序),可能會進行n次的比較和交換才能將該數據移至正確位置。而希爾排序會用較大的步長移動數據,因此小數據只需進行少數比較和交換便可到正確位置。

  先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,具體算法描述:

  • 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • 按增量序列個數k,對序列進行k 趟排序;
  • 每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列做爲一個表來處理,表長度即爲整個序列的長度。

 希爾排序是不穩定的排序算法,雖然一次插入排序是穩定的,不會改變相同元素的相對順序,但在不一樣的插入排序過程當中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂。

  好比序列:{ 3, 5, 10, 8, 7, 2, 8, 1, 20, 6 },h=2時分紅兩個子序列 { 3, 10, 7, 8, 20 } 和  { 5, 8, 2, 1, 6 } ,未排序以前第二個子序列中的8在前面,如今對兩個子序列進行插入排序,獲得 { 3, 7, 8, 10, 20 } 和 { 1, 2, 5, 6, 8 } ,即 { 3, 1, 7, 2, 8, 5, 10, 6, 20, 8 } ,兩個8的相對次序發生了改變。

 

 

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 
 4 int a[]={6, 5, 3, 1, 8, 7, 2, 4};  5 
 6 void ShellSort(int a[],int n){  7     int h=0;  8     while(h<=n){  9         h=h*3+1; 10  } 11     while(h>=1){ 12         for(int i=h;i<n;i++){ 13             int j=i-h; 14             int temp=a[i]; 15             while(j>=0&&a[j]>temp){ 16                 a[j+h]=a[j]; 17                 j-=h; 18  } 19             a[j+h]=temp; 20  } 21         h=(h-1)/3; 22  } 23 } 24 
25 int main(){ 26     ios::sync_with_stdio(false); 27     int n=sizeof(a)/sizeof(int);//sizeof()求的是字節數 
28     cout<<n<<endl; 29  ShellSort(a,n); 30     for(int i=0;i<n;i++){ 31         cout<<a[i]<<" "; 32  } 33     cout<<endl; 34     return 0; 35 } 

 

7、歸併排序

     歸併排序是建立在歸併操做上的一種有效的排序算法,效率爲O(nlogn),1945年由馮·諾伊曼首次提出。

  歸併排序的實現分爲遞歸實現非遞歸(迭代)實現。遞歸實現的歸併排序是算法設計中分治策略的典型應用,咱們將一個大問題分割成小問題分別解決,而後用全部小問題的答案來解決整個大問題。非遞歸(迭代)實現的歸併排序首先進行是兩兩歸併,而後四四歸併,而後是八八歸併,一直下去直到歸併了整個數組。

  歸併排序算法主要依賴歸併(Merge)操做。歸併操做指的是將兩個已經排序的序列合併成一個序列的操做,歸併操做步驟以下:

  1. 申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列
  2. 設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置
  3. 比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置
  4. 重複步驟3直到某一指針到達序列尾
  5. 將另外一序列剩下的全部元素直接複製到合併序列尾

歸併排序除了能夠對數組進行排序,還能夠高效的求出數組小和(即單調和)以及數組中的逆序對,詳見這篇博文

 1 #include <stdio.h>
 2 #include <limits.h>
 3 
 4 // 分類 -------------- 內部比較排序  5 // 數據結構 ---------- 數組  6 // 最差時間複雜度 ---- O(nlogn)  7 // 最優時間複雜度 ---- O(nlogn)  8 // 平均時間複雜度 ---- O(nlogn)  9 // 所需輔助空間 ------ O(n) 10 // 穩定性 ------------ 穩定
11 
12 
13 void Merge(int A[], int left, int mid, int right)// 合併兩個已排好序的數組A[left...mid]和A[mid+1...right]
14 { 15     int len = right - left + 1; 16     int *temp = new int[len];       // 輔助空間O(n)
17     int index = 0; 18     int i = left;                   // 前一數組的起始元素
19     int j = mid + 1;                // 後一數組的起始元素
20     while (i <= mid && j <= right) 21  { 22         temp[index++] = A[i] <= A[j] ? A[i++] : A[j++];  // 帶等號保證歸併排序的穩定性
23  } 24     while (i <= mid) 25  { 26         temp[index++] = A[i++]; 27  } 28     while (j <= right) 29  { 30         temp[index++] = A[j++]; 31  } 32     for (int k = 0; k < len; k++) 33  { 34         A[left++] = temp[k]; 35  } 36 } 37 
38 void MergeSortRecursion(int A[], int left, int right)    // 遞歸實現的歸併排序(自頂向下)
39 { 40     if (left == right)    // 當待排序的序列長度爲1時,遞歸開始回溯,進行merge操做
41         return; 42     int mid = (left + right) / 2; 43  MergeSortRecursion(A, left, mid); 44     MergeSortRecursion(A, mid + 1, right); 45  Merge(A, left, mid, right); 46 } 47 
48 void MergeSortIteration(int A[], int len)    // 非遞歸(迭代)實現的歸併排序(自底向上)
49 { 50     int left, mid, right;// 子數組索引,前一個爲A[left...mid],後一個子數組爲A[mid+1...right]
51     for (int i = 1; i < len; i *= 2)        // 子數組的大小i初始爲1,每輪翻倍
52  { 53         left = 0; 54         while (left + i < len)              // 後一個子數組存在(須要歸併)
55  { 56             mid = left + i - 1; 57             right = mid + i < len ? mid + i : len - 1;// 後一個子數組大小可能不夠
58  Merge(A, left, mid, right); 59             left = right + 1;               // 前一個子數組索引向後移動
60  } 61  } 62 } 63 
64 int main() 65 { 66     int A1[] = { 6, 5, 3, 1, 8, 7, 2, 4 };      // 從小到大歸併排序
67     int A2[] = { 6, 5, 3, 1, 8, 7, 2, 4 }; 68     int n1 = sizeof(A1) / sizeof(int); 69     int n2 = sizeof(A2) / sizeof(int); 70     MergeSortRecursion(A1, 0, n1 - 1);          // 遞歸實現
71     MergeSortIteration(A2, n2);                 // 非遞歸實現
72     printf("遞歸實現的歸併排序結果:"); 73     for (int i = 0; i < n1; i++) 74  { 75         printf("%d ", A1[i]); 76  } 77     printf("\n"); 78     printf("非遞歸實現的歸併排序結果:"); 79     for (int i = 0; i < n2; i++) 80  { 81         printf("%d ", A2[i]); 82  } 83     printf("\n"); 84     return 0; 85 }

 

8、堆排序

 堆排序是指利用堆這種數據結構所設計的一種選擇排序算法。堆是一種近似徹底二叉樹的結構(一般堆是經過一維數組來實現的),並知足性質:以最大堆(也叫大根堆、大頂堆)爲例,其中父結點的值老是大於它的孩子節點。

  咱們能夠很容易的定義堆排序的過程:

  1. 由輸入的無序數組構造一個最大堆,做爲初始的無序區
  2. 把堆頂元素(最大值)和堆尾元素互換
  3. 把堆(無序區)的尺寸縮小1,並調用heapify(A, 0)重新的堆頂元素開始進行堆調整
  4. 重複步驟2,直到堆的尺寸爲1

堆排序是不穩定的排序算法,不穩定發生在堆頂元素與A[i]交換的時刻。

  好比序列:{ 9, 5, 7, 5 },堆頂元素是9,堆排序下一步將9和第二個5進行交換,獲得序列 { 5, 5, 7, 9 },再進行堆調整獲得{ 7, 5, 5, 9 },重複以前的操做最後獲得{ 5, 5, 7, 9 }從而改變了兩個5的相對次序。

 1 #include <stdio.h>
 2 
 3 // 分類 -------------- 內部比較排序  4 // 數據結構 ---------- 數組  5 // 最差時間複雜度 ---- O(nlogn)  6 // 最優時間複雜度 ---- O(nlogn)  7 // 平均時間複雜度 ---- O(nlogn)  8 // 所需輔助空間 ------ O(1)  9 // 穩定性 ------------ 不穩定
10 
11 
12 void Swap(int A[], int i, int j) 13 { 14     int temp = A[i]; 15     A[i] = A[j]; 16     A[j] = temp; 17 } 18 
19 void Heapify(int A[], int i, int size)  // 從A[i]向下進行堆調整
20 { 21     int left_child = 2 * i + 1;         // 左孩子索引
22     int right_child = 2 * i + 2;        // 右孩子索引
23     int max = i;                        // 選出當前結點與其左右孩子三者之中的最大值
24     if (left_child < size && A[left_child] > A[max]) 25         max = left_child; 26     if (right_child < size && A[right_child] > A[max]) 27         max = right_child; 28     if (max != i) 29  { 30         Swap(A, i, max);                // 把當前結點和它的最大(直接)子節點進行交換
31         Heapify(A, max, size);          // 遞歸調用,繼續從當前結點向下進行堆調整
32  } 33 } 34 
35 int BuildHeap(int A[], int n)           // 建堆,時間複雜度O(n)
36 { 37     int heap_size = n; 38     for (int i = heap_size / 2 - 1; i >= 0; i--) // 從每個非葉結點開始向下進行堆調整
39  Heapify(A, i, heap_size); 40     return heap_size; 41 } 42 
43 void HeapSort(int A[], int n) 44 { 45     int heap_size = BuildHeap(A, n);    // 創建一個最大堆
46     while (heap_size > 1)           // 堆(無序區)元素個數大於1,未完成排序
47  { 48         // 將堆頂元素與堆的最後一個元素互換,並從堆中去掉最後一個元素 49         // 此處交換操做頗有可能把後面元素的穩定性打亂,因此堆排序是不穩定的排序算法
50         Swap(A, 0, --heap_size); 51         Heapify(A, 0, heap_size);     // 重新的堆頂元素開始向下進行堆調整,時間複雜度O(logn)
52  } 53 } 54 
55 int main() 56 { 57     int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 從小到大堆排序
58     int n = sizeof(A) / sizeof(int); 59  HeapSort(A, n); 60     printf("堆排序結果:"); 61     for (int i = 0; i < n; i++) 62  { 63         printf("%d ", A[i]); 64  } 65     printf("\n"); 66     return 0; 67 }

 

9、快速排序

     快速排序是由東尼·霍爾所發展的一種排序算法。在平均情況下,排序n個元素要O(nlogn)次比較。在最壞情況下則須要O(n^2)次比較,但這種情況並不常見。事實上,快速排序一般明顯比其餘O(nlogn)算法更快,由於它的內部循環能夠在大部分的架構上頗有效率地被實現出來。

  快速排序使用分治策略(Divide and Conquer)來把一個序列分爲兩個子序列。步驟爲:

  1. 從序列中挑出一個元素,做爲"基準"(pivot).
  2. 把全部比基準值小的元素放在基準前面,全部比基準值大的元素放在基準的後面(相同的數能夠到任一邊),這個稱爲分區(partition)操做。
  3. 對每一個分區遞歸地進行步驟1~2,遞歸的結束條件是序列的大小是0或1,這時總體已經被排好序了。

 

 快速排序是不穩定的排序算法,不穩定發生在基準元素與A[tail+1]交換的時刻。

  好比序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基準元素是5,一次劃分操做後5要和第一個8進行交換,從而改變了兩個元素8的相對次序。

 

 

 1 #include<bits/stdc++.h>
 2 // 分類 ------------ 內部比較排序  3 // 數據結構 --------- 數組  4 // 最差時間複雜度 ---- 每次選取的基準都是最大(或最小)的元素,致使每次只劃分出了一個分區,須要進行n-1次劃分才能結束遞歸,時間複雜度爲O(n^2)  5 // 最優時間複雜度 ---- 每次選取的基準都是中位數,這樣每次都均勻的劃分出兩個分區,只須要logn次劃分就能結束遞歸,時間複雜度爲O(nlogn)  6 // 平均時間複雜度 ---- O(nlogn)  7 // 所需輔助空間 ------ 主要是遞歸形成的棧空間的使用(用來保存left和right等局部變量),取決於遞歸樹的深度,通常爲O(logn),最差爲O(n)  8 // 穩定性 ---------- 不穩定
 9 
10 void Swap(int A[], int i, int j) 11 { 12     int temp = A[i]; 13     A[i] = A[j]; 14     A[j] = temp; 15 } 16 
17 int Partition(int A[], int left, int right,int n)  // 劃分函數
18 { 19     int pivot = A[right];               // 這裏每次都選擇最後一個元素做爲基準
20     int tail = left - 1; 21     printf("%d %d %d\n",pivot,left,right);                // tail爲小於基準的子數組最後一個元素的索引
22     for (int i = left; i < right; i++)  // 遍歷基準之外的其餘元素
23  { 24         if (A[i] <= pivot)              // 把小於等於基準的元素放到前一個子數組末尾
25         {    //printf("%d %d\n",tail,i);
26             Swap(A, ++tail, i); 27             for (int i = 0; i < n; i++) 28  { 29                 printf("%d ", A[i]); 30             }printf("\n"); 31                 
32  } 33  } 34     Swap(A, tail + 1, right);           // 最後把基準放到前一個子數組的後邊,剩下的子數組既是大於基準的子數組
35         for (int i = 0; i < n; i++) 36  { 37             printf("%d ", A[i]); 38         } printf("\n");                                   // 該操做頗有可能把後面元素的穩定性打亂,因此快速排序是不穩定的排序算法
39     return tail + 1;                    // 返回基準的索引
40 } 41 
42 void QuickSort(int A[], int left, int right,int n) 43 { 44     if (left >= right) 45         return; 46     int pivot_index = Partition(A, left, right,n); // 基準的索引
47     QuickSort(A, left, pivot_index - 1,n); 48     QuickSort(A, pivot_index + 1, right,n); 49 } 50 
51 int main() 52 { 53     int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 }; // 從小到大快速排序
54     int n = sizeof(A) / sizeof(int); 55     QuickSort(A, 0, n - 1,n); 56     printf("快速排序結果:"); 57     for (int i = 0; i < n; i++) 58  { 59         printf("%d ", A[i]); 60  } 61     printf("\n"); 62     return 0; 63 }

 

 10、計數排序

  計數排序用到一個額外的計數數組C,根據數組C來將原數組A中的元素排到正確的位置。

  通俗地理解,例若有10個年齡不一樣的人,假如統計出有8我的的年齡不比小明大(即小於等於小明的年齡,這裏也包括了小明),那麼小明的年齡就排在第8位,經過這種思想能夠肯定每一個人的位置,也就排好了序。固然,年齡同樣時須要特殊處理(保證穩定性):經過反向填充目標數組,填充完畢後將對應的數字統計遞減,能夠確保計數排序的穩定性。

  計數排序的步驟以下:

  1. 統計數組A中每一個值A[i]出現的次數,存入C[A[i]]
  2. 從前向後,使數組C中的每一個值等於其與前一項相加,這樣數組C[A[i]]就變成了表明數組A中小於等於A[i]的元素個數
  3. 反向填充目標數組B:將數組元素A[i]放在數組B的第C[A[i]]個位置(下標爲C[A[i]] - 1),每放一個元素就將C[A[i]]遞減

 

  計數排序的時間複雜度和空間複雜度與數組A的數據範圍(A中元素的最大值與最小值的差加上1)有關,所以對於數據範圍很大的數組,計數排序須要大量時間和內存。

  例如:對0到99之間的數字進行排序,計數排序是最好的算法,然而計數排序並不適合按字母順序排序人名,將計數排序用在基數排序算法中,可以更有效的排序數據範圍很大的數組。

 1 #include<iostream>
 2 using namespace std;  3 
 4 // 分類 ------------ 內部非比較排序  5 // 數據結構 --------- 數組  6 // 最差時間複雜度 ---- O(n + k)  7 // 最優時間複雜度 ---- O(n + k)  8 // 平均時間複雜度 ---- O(n + k)  9 // 所需輔助空間 ------ O(n + k) 10 // 穩定性 ----------- 穩定
11 
12 
13 const int k = 100;   // 基數爲100,排序[0,99]內的整數
14 int C[k];            // 計數數組
15 
16 void CountingSort(int A[], int n) 17 { 18     for (int i = 0; i < k; i++)   // 初始化,將數組C中的元素置0(此步驟可省略,整型數組元素默認值爲0)
19  { 20         C[i] = 0; 21  } 22     for (int i = 0; i < n; i++)   // 使C[i]保存着等於i的元素個數
23  { 24         C[A[i]]++; 25  } 26     for (int i = 1; i < k; i++)   // 使C[i]保存着小於等於i的元素個數,排序後元素i就放在第C[i]個輸出位置上
27  { 28         C[i] = C[i] + C[i - 1]; 29  } 30     int *B = (int *)malloc((n) * sizeof(int));// 分配臨時空間,長度爲n,用來暫存中間數據
31     for (int i = n - 1; i >= 0; i--)    // 從後向前掃描保證計數排序的穩定性(重複元素相對次序不變)
32  { 33         B[--C[A[i]]] = A[i];      // 把每一個元素A[i]放到它在輸出數組B中的正確位置上 34                                   // 當再遇到重複元素時會被放在當前元素的前一個位置上保證計數排序的穩定性
35  } 36     for (int i = 0; i < n; i++)   // 把臨時空間B中的數據拷貝回A
37  { 38         A[i] = B[i]; 39  } 40     free(B);    // 釋放臨時空間 
41 } 42 
43 int main() 44 { 45     int A[] = { 15, 22, 19, 46, 27, 73, 1, 19, 8 };  // 針對計數排序設計的輸入,每個元素都在[0,100]上且有重複元素
46     int n = sizeof(A) / sizeof(int); 47  CountingSort(A, n); 48     printf("計數排序結果:"); 49     for (int i = 0; i < n; i++) 50  { 51         printf("%d ", A[i]); 52  } 53     printf("\n"); 54     return 0; 55 }

 

11、桶排序

  桶排序是計數排序的升級版。它利用了函數的映射關係,高效與否的關鍵就在於這個映射函數的肯定。桶排序 (Bucket sort)的工做的原理:假設輸入數據服從均勻分佈,將數據分到有限數量的桶裏,每一個桶再分別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排)。

9.1 算法描述

  • 設置一個定量的數組看成空桶;
  • 遍歷輸入數據,而且把數據一個一個放到對應的桶裏去;
  • 對每一個不是空的桶進行排序;
  • 從不是空的桶裏把排好序的數據拼接起來。 

 

  桶排序不是比較排序,不受到O(nlogn)下限的影響,它是鴿巢排序的一種概括結果,當所要排序的數組值分散均勻的時候,桶排序擁有線性的時間複雜度。

  桶排序最好狀況下使用線性時間O(n),桶排序的時間複雜度,取決與對各個桶之間數據進行排序的時間複雜度,由於其它部分的時間複雜度都爲O(n)。很顯然,桶劃分的越小,各個桶之間的數據越少,排序所用的時間也會越少。但相應的空間消耗就會增大。

 

 1 #include<iostream>
 2 using namespace std;  3 
 4 // 分類 ------------- 內部非比較排序  5 // 數據結構 --------- 數組  6 // 最差時間複雜度 ---- O(nlogn)或O(n^2),只有一個桶,取決於桶內排序方式  7 // 最優時間複雜度 ---- O(n),每一個元素佔一個桶  8 // 平均時間複雜度 ---- O(n),保證各個桶內元素個數均勻便可  9 // 所需輔助空間 ------ O(n + bn) 10 // 穩定性 ----------- 穩定
11 
12 /* 本程序用數組模擬桶 */
13 const int bn = 5;    // 這裏排序[0,49]的元素,使用5個桶就夠了,也能夠根據輸入動態肯定桶的數量
14 int C[bn];           // 計數數組,存放桶的邊界信息
15 
16 void InsertionSort(int A[], int left, int right) 17 { 18     for (int i = left + 1; i <= right; i++)  // 從第二張牌開始抓,直到最後一張牌
19  { 20         int get = A[i]; 21         int j = i - 1; 22         while (j >= left && A[j] > get) 23  { 24             A[j + 1] = A[j]; 25             j--; 26  } 27         A[j + 1] = get; 28  } 29 } 30 
31 int MapToBucket(int x) 32 { 33     return x / 10;    // 映射函數f(x),做用至關於快排中的Partition,把大量數據分割成基本有序的數據塊
34 } 35 
36 void CountingSort(int A[], int n) 37 { 38     for (int i = 0; i < bn; i++) 39  { 40         C[i] = 0; 41  } 42     for (int i = 0; i < n; i++)     // 使C[i]保存着i號桶中元素的個數
43  { 44         C[MapToBucket(A[i])]++; 45  } 46     for (int i = 1; i < bn; i++)    // 定位桶邊界:初始時,C[i]-1爲i號桶最後一個元素的位置
47  { 48         C[i] = C[i] + C[i - 1]; 49  } 50     int *B = (int *)malloc((n) * sizeof(int)); 51     for (int i = n - 1; i >= 0; i--)// 從後向前掃描保證計數排序的穩定性(重複元素相對次序不變)
52  { 53         int b = MapToBucket(A[i]);  // 元素A[i]位於b號桶
54         B[--C[b]] = A[i];           // 把每一個元素A[i]放到它在輸出數組B中的正確位置上 55                                     // 桶的邊界被更新:C[b]爲b號桶第一個元素的位置
56  } 57     for (int i = 0; i < n; i++) 58  { 59         A[i] = B[i]; 60  } 61     free(B); 62 } 63 
64 void BucketSort(int A[], int n) 65 { 66     CountingSort(A, n);          // 利用計數排序肯定各個桶的邊界(分桶)
67     for (int i = 0; i < bn; i++) // 對每個桶中的元素應用插入排序
68  { 69         int left = C[i];         // C[i]爲i號桶第一個元素的位置
70         int right = (i == bn - 1 ? n - 1 : C[i + 1] - 1);// C[i+1]-1爲i號桶最後一個元素的位置
71         if (left < right)        // 對元素個數大於1的桶進行桶內插入排序
72  InsertionSort(A, left, right); 73  } 74 } 75 
76 int main() 77 { 78     int A[] = { 29, 25, 3, 49, 9, 37, 21, 43 };// 針對桶排序設計的輸入
79     int n = sizeof(A) / sizeof(int); 80  BucketSort(A, n); 81     printf("桶排序結果:"); 82     for (int i = 0; i < n; i++) 83  { 84         printf("%d ", A[i]); 85  } 86     printf("\n"); 87     return 0; 88 }

 

 12、基數排序

  基數排序是按照低位先排序,而後收集;再按照高位排序,而後再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序。最後的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。

10.1 算法描述

  • 取得數組中的最大數,並取得位數;
  • arr爲原始數組,從最低位開始取每一個位組成radix數組;
  • 對radix進行計數排序(利用計數排序適用於小範圍數的特色);

  基數排序的時間複雜度是O(n * dn),其中n是待排序元素個數,dn是數字位數。這個時間複雜度不必定優於O(n log n),dn的大小取決於數字位的選擇(好比比特位數),和待排序數據所屬數據類型的全集的大小;dn決定了進行多少輪處理,而n是每輪處理的操做數目。

  若是考慮和比較排序進行對照,基數排序的形式複雜度雖然不必定更小,但因爲不進行比較,所以其基本操做的代價較小,並且若是適當的選擇基數,dn通常不大於log n,因此基數排序通常要快過基於比較的排序,好比快速排序。因爲整數也能夠表達字符串(好比名字或日期)和特定格式的浮點數,因此基數排序並非只能用於整數排序。

 

 

 1 #include<iostream>
 2 using namespace std;  3 
 4 // 分類 ------------- 內部非比較排序  5 // 數據結構 ---------- 數組  6 // 最差時間複雜度 ---- O(n * dn)  7 // 最優時間複雜度 ---- O(n * dn)  8 // 平均時間複雜度 ---- O(n * dn)  9 // 所需輔助空間 ------ O(n * dn) 10 // 穩定性 ----------- 穩定
11 
12 const int dn = 3;                // 待排序的元素爲三位數及如下
13 const int k = 10;                // 基數爲10,每一位的數字都是[0,9]內的整數
14 int C[k]; 15 
16 int GetDigit(int x, int d)          // 得到元素x的第d位數字
17 { 18     int radix[] = { 1, 1, 10, 100 };// 最大爲三位數,因此這裏只要到百位就知足了
19     return (x / radix[d]) % 10; 20 } 21 
22 void CountingSort(int A[], int n, int d)// 依據元素的第d位數字,對A數組進行計數排序
23 { 24     for (int i = 0; i < k; i++) 25  { 26         C[i] = 0; 27  } 28     for (int i = 0; i < n; i++) 29  { 30         C[GetDigit(A[i], d)]++; 31  } 32     for (int i = 1; i < k; i++) 33  { 34         C[i] = C[i] + C[i - 1]; 35  } 36     int *B = (int*)malloc(n * sizeof(int)); 37     for (int i = n - 1; i >= 0; i--) 38  { 39         int dight = GetDigit(A[i], d);  // 元素A[i]當前位數字爲dight 
40         B[--C[dight]] = A[i];           // 根據當前位數字,把每一個元素A[i]放到它在輸出數組B中的正確位置上 41         // 當再遇到當前位數字同爲dight的元素時,會將其放在當前元素的前一個位置上保證計數排序的穩定性
42  } 43     for (int i = 0; i < n; i++) 44  { 45         A[i] = B[i]; 46  } 47     free(B); 48 } 49 
50 void LsdRadixSort(int A[], int n)     // 最低位優先基數排序
51 { 52     for (int d = 1; d <= dn; d++)     // 從低位到高位
53         CountingSort(A, n, d);        // 依據第d位數字對A進行計數排序
54 } 55 
56 int main() 57 { 58     int A[] = { 20, 90, 64, 289, 998, 365, 852, 123, 789, 456 };// 針對基數排序設計的輸入
59     int n = sizeof(A) / sizeof(int); 60  LsdRadixSort(A, n); 61     printf("基數排序結果:"); 62     for (int i = 0; i < n; i++) 63  { 64         printf("%d ", A[i]); 65  } 66     printf("\n"); 67     return 0; 68 }

 

 

 

 

參考資料:

http://www.cnblogs.com/eniac12/p/5329396.html

https://www.cnblogs.com/onepixel/articles/7674659.html

http://www.cnblogs.com/eniac12/p/5332117.html

相關文章
相關標籤/搜索