排序算法可以分為內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。
常見的內部排序算法有:插入排序、希爾排序、選擇排序、冒泡排序、歸並排序、快速排序、堆排序、基數排序等。
本文將依次介紹上述八大排序算法。
算法一:插入排序
插入排序示意圖
插入排序是一種最簡單直觀的排序算法,它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從后向前掃描,找到相應位置並插入。
算法步驟:
1)將第一待排序序列第一個元素看做一個有序序列,把第二個元素到最后一個元素當成是未排序序列。
2)從頭到尾依次掃描未排序序列,將掃描到的每個元素插入有序序列的適當位置。(如果待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的后面。)
代碼實現:
void insert_sort(int array[],unsignedint n) { int i,j; int temp; for(i = 1;i < n;i++) { temp = array[i]; for(j = i;j > 0&& array[j - 1] > temp;j--) { array[j]= array[j - 1]; } array[j] = temp; } }
算法二:希爾排序
希爾排序示意圖
希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。但希爾排序是非穩定排序算法。
希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
- 插入排序在對幾乎已經排好序的數據操作時, 效率高, 即可以達到線性排序的效率
- 但插入排序一般來說是低效的, 因為插入排序每次只能將數據移動一位
希爾排序的基本思想是:先將整個待排序的記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。
算法步驟:
1)選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2)按增量序列個數k,對序列進行k 趟排序;
3)每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1 時,整個序列作為一個表來處理,表長度即為整個序列的長度。
代碼實現:
#include<stdio.h> #include<math.h> #define MAXNUM 10 void main() { void shellSort(int array[],int n,int t);//t為排序趟數 int array[MAXNUM],i; for(i = 0;i < MAXNUM;i++) scanf("%d",&array[i]); shellSort(array,MAXNUM,int(log(MAXNUM + 1) / log(2)));//排序趟數應為log2(n+1)的整數部分 for(i = 0;i < MAXNUM;i++) printf("%d ",array[i]); printf("\n"); } //根據當前增量進行插入排序 void shellInsert(int array[],int n,int dk) { int i,j,temp; for(i = dk;i < n;i++)//分別向每組的有序區域插入 { temp = array[i]; for(j = i-dk;(j >= i % dk) && array[j] > temp;j -= dk)//比較與記錄后移同時進行 array[j + dk] = array[j]; if(j != i - dk) array[j + dk] = temp;//插入 } } //計算Hibbard增量 int dkHibbard(int t,int k) { return int(pow(2,t - k + 1) - 1); } //希爾排序 void shellSort(int array[],int n,int t) { void shellInsert(int array[],int n,int dk); int i; for(i = 1;i <= t;i++) shellInsert(array,n,dkHibbard(t,i)); } //此寫法便於理解,實際應用時應將上述三個函數寫成一個函數。
算法三:選擇排序
選擇排序示意圖
選擇排序(Selection sort)也是一種簡單直觀的排序算法。
算法步驟:
1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
2)再從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序序列的末尾。
3)重復第二步,直到所有元素均排序完畢。
代碼實現:
void select_sort(int *a,int n) { register int i,j,min,t; for(i = 0;i < n-1;i++) { min = i;//查找最小值 for(j = i + 1;j < n;j++) if(a[min] > a[j]) min = j;//交換 if(min != i) { t = a[min]; a[min] = a[i]; a[i] = t; } } }
算法四:冒泡排序
冒泡排序示意圖
冒泡排序(Bubble Sort)也是一種簡單直觀的排序算法。它重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。
算法步驟:
1)比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
2)對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。這步做完后,最后的元素會是最大的數。
3)針對所有的元素重復以上的步驟,除了最后一個。
4)持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。
代碼實現:
#include <stdio.h> #define SIZE 8void bubble_sort(int a[], int n) { int i, j, temp; for (j = 0;j < n - 1;j++) for (i = 0;i < n - 1 - j;i++) { if(a[i] > a[i + 1]) { temp = a[i]; a[i] = a[i + 1]; a[i + 1] = temp; } } } int main() { int number[SIZE] = {95, 45, 15, 78, 84, 51, 24, 12}; int i; bubble_sort(number, SIZE); for (i = 0; i < SIZE; i++) { printf("%d", number[i]); } printf("\n"); }
算法五:歸並排序
歸並排序示意圖
歸並排序(Merge sort)是建立在歸並操作上的一種有效的排序算法。該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。
算法步驟:
1. 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列
2. 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
3. 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
4. 重復步驟3直到某一指針達到序列尾
5. 將另一序列剩下的所有元素直接復制到合並序列尾
代碼實現:
#include <stdlib.h> #include <stdio.h> void Merge(int sourceArr[],int tempArr[], int startIndex, int midIndex, int endIndex) { int i = startIndex, j=midIndex+1, k = startIndex; while(i != midIndex + 1 && j != endIndex + 1) { if(sourceArr[i] >= sourceArr[j]) tempArr[k++] = sourceArr[j++]; else tempArr[k++] = sourceArr[i++]; } while(i != midIndex+1) tempArr[k++] = sourceArr[i++]; while(j != endIndex+1) tempArr[k++] = sourceArr[j++]; for(i = startIndex; i <= endIndex; i++) sourceArr[i] = tempArr[i]; } //內部使用遞歸 void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex) { int midIndex; if(startIndex < endIndex) { midIndex = (startIndex + endIndex) / 2; MergeSort(sourceArr, tempArr, startIndex, midIndex); MergeSort(sourceArr, tempArr, midIndex+1, endIndex); Merge(sourceArr, tempArr, startIndex, midIndex, endIndex); } } int main(int argc, char * argv[]) { int a[8] = {50, 10, 20, 30, 70, 40, 80, 60}; int i, b[8]; MergeSort(a, b, 0, 7); for(i=0; i<8; i++) printf("%d ", a[i]); printf("\n"); return 0; }
算法六:快速排序
快速排序示意圖
快速排序是由東尼·霍爾所發展的一種排序算法。在平均狀況下,排序 n 個項目要Ο(n log n)次比較。在最壞狀況下則需要Ο(n2)次比較,但這種狀況並不常見。事實上,快速排序通常明顯比其他Ο(n log n) 算法更快,因為它的內部循環(inner loop)可以在大部分的架構上很有效率地被實現出來。
快速排序使用分治法(Divide and conquer)策略來把一個串行(list)分為兩個子串行(sub-lists)。
算法步驟:
1 從數列中挑出一個元素,稱為 “基准”(pivot),
2 重新排序數列,所有元素比基准值小的擺放在基准前面,所有元素比基准值大的擺在基准的后面(相同的數可以到任一邊)。在這個分區退出之后,該基准就處於數列的中間位置。這個稱為分區(partition)操作。
3 遞歸地(recursive)把小於基准值元素的子數列和大於基准值元素的子數列排序。
遞歸的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞歸下去,但是這個算法總會退出,因為在每次的迭代(iteration)中,它至少會把一個元素擺到它最后的位置去。
代碼實現:
void Qsort(int a[], int low, int high) { if(low >= high) { return; } int first = low; int last = high; int key = a[first];/*用字表的第一個記錄作為樞軸*/ while(first < last) { while(first < last && a[last] >= key) { --last; } a[first] = a[last];/*將比第一個小的移到低端*/ while(first < last && a[first] <= key) { ++first; } a[last] = a[first]; /*將比第一個大的移到高端*/ } a[first] = key;/*樞軸記錄到位*/ Qsort(a, low, first-1); Qsort(a, first+1, high); }
算法七:堆排序
堆排序示意圖
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
堆排序的平均時間復雜度為Ο(nlogn) 。
算法步驟:
1)創建一個堆H[0..n-1]
2)把堆首(最大值)和堆尾互換
3)把堆的尺寸縮小1,並調用shift_down(0),目的是把新的數組頂端數據調整到相應位置
4) 重復步驟2,直到堆的尺寸為1
代碼實現:
//array是待調整的堆數組,i是待調整的數組元素的位置,nlength是數組的長度 //本函數功能是:根據數組array構建大根堆 void HeapAdjust(int array[],int i,int nLength) { int nChild; int nTemp; for(; 2 * i + 1 < nLength;i = nChild) { //子結點的位置=2*(父結點位置)+1 nChild = 2 * i + 1; //得到子結點中較大的結點 if(nChild < nLength - 1 && array[nChild + 1] > array[nChild]) ++nChild; //如果較大的子結點大於父結點那么把較大的子結點往上移動,替換它的父結點 if(array[i] < array[nChild]) { nTemp = array[i]; array[i] = array[nChild]; array[nChild] = nTemp; } else break; //否則退出循環 } } //堆排序算法 void HeapSort(int array[],int length) { int i; //調整序列的前半部分元素,調整完之后第一個元素是序列的最大的元素 //length/2-1是最后一個非葉節點,此處"/"為整除 for(i = length / 2 - 1;i >= 0;--i) HeapAdjust(array,i,length); //從最后一個元素開始對序列進行調整,不斷的縮小調整的范圍直到第一個元素 for(i = length - 1;i > 0;--i) { //把第一個元素和當前的最后一個元素交換, //保證當前的最后一個位置的元素都是在現在的這個序列之中最大的 array[i] = array[0] ^ array[i]; array[0] = array[0] ^ array[i]; array[i] = array[0] ^ array[i]; //不斷縮小調整heap的范圍,每一次調整完畢保證第一個元素是當前序列的最大值 HeapAdjust(array,0,i); } } int main() { int i; int num[]={9,8,7,6,5,4,3,2,1,0}; HeapSort(num,sizeof(num)/sizeof(int)); for(i = 0;i < sizeof(num) / sizeof(int);i++) { printf("%d ",num[i]); } printf("\nok\n"); return 0; }
算法八:基數排序
基數排序是一種非比較型整數排序算法,其原理是將整數按位數切割成不同的數字,然后按每個位數分別比較。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是只能使用於整數。
說基數排序之前,我們簡單介紹桶排序:
算法思想:是將陣列分到有限數量的桶子里。每個桶子再個別排序(有可能再使用別的排序算法或是以遞回方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種歸納結果。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並不是 比較排序,他不受到 O(n log n) 下限的影響。
簡單來說,就是把數據分組,放在一個個的桶中,然后對每個桶里面的在進行排序。
例如要對大小為[1..1000]范圍內的n個整數A[1..n]排序
首先,可以把桶設為大小為10的范圍,具體而言,設集合B[1]存儲[1..10]的整數,集合B[2]存儲 (10..20]的整數,……集合B[i]存儲( (i-1)*10, i*10]的整數,i = 1,2,..100。總共有 100個桶。
然后,對A[1..n]從頭到尾掃描一遍,把每個A[i]放入對應的桶B[j]中。 再對這100個桶中每個桶里的數字排序,這時可用冒泡,選擇,乃至快排,一般來說任 何排序法都可以。
最后,依次輸出每個桶里面的數字,且每個桶中的數字從小到大輸出,這 樣就得到所有數字排好序的一個序列了。
假設有n個數字,有m個桶,如果數字是平均分布的,則每個桶里面平均有n/m個數字。如果
對每個桶中的數字采用快速排序,那么整個算法的復雜度是
O(n + m * n/m*log(n/m)) = O(n + nlogn – nlogm)
從上式看出,當m接近n的時候,桶排序復雜度接近O(n)
當然,以上復雜度的計算是基於輸入的n個數字是平均分布這個假設的。這個假設是很強的 ,實際應用中效果並沒有這么好。如果所有的數字都落在同一個桶中,那就退化成一般的排序了。
前面說的幾大排序算法 ,大部分時間復雜度都是O(n2),也有部分排序算法時間復雜度是O(nlogn)。而桶式排序卻能實現O(n)的時間復雜度。但桶排序的缺點是:
1)首先是空間復雜度比較高,需要的額外開銷大。排序有兩個數組的空間開銷,一個存放待排序數組,一個就是所謂的桶,比如待排序值是從0到m-1,那就需要m個桶,這個桶數組就要至少m個空間。
2)其次待排序的元素都要在一定的范圍內等等。
代碼實現:
int maxbit(int data[], int n) //輔助函數,求數據的最大位數 { int d = 1; //保存最大的位數 int p = 10; for(int i = 0; i < n; ++i) { while(data[i] >= p) { p *= 10; ++d; } } return d; } void radixsort(int data[], int n) //基數排序 { int d = maxbit(data, n); int *tmp = newint[n]; int *count = newint[10]; //計數器 int i, j, k; int radix = 1; for(i = 1; i <= d; i++) //進行d次排序 { for(j = 0; j < 10; j++) count[j] = 0; //每次分配前清空計數器 for(j = 0; j < n; j++) { k = (data[j] / radix) % 10; //統計每個桶中的記錄數 count[k]++; } for(j = 1; j < 10; j++) count[j] = count[j - 1] + count[j]; //將tmp中的位置依次分配給每個桶 for(j = n - 1; j >= 0; j--) //將所有桶中記錄依次收集到tmp中 { k = (data[j] / radix) % 10; tmp[count[k] - 1] = data[j]; count[k]--; } for(j = 0; j < n; j++) //將臨時數組的內容復制到data中 data[j] = tmp[j]; radix = radix * 10; } delete[]tmp; delete[]count; }
總結
各種排序的穩定性,時間復雜度、空間復雜度、穩定性總結如下圖:
目前最受推崇的排序算法是快速排序!!!
參考資料:http://www.cricode.com/3212.html;百度百科