排序算法是算法學中最基礎、應用最廣的一類算法,其中最簡單的就是冒泡排序和簡單選擇排序法,然而這兩種算法的時間複雜度都在O(n^2),並不高效,這裏就對八種不一樣的排序算法進行分析。基本的排序算法分爲插入排序、選擇排序、交換排序、歸併排序、基數排序,其中插入排序分爲直接插入排序、希爾排序,選擇排序分爲簡單選擇排序和堆排序,交換排序分爲冒泡排序和快速排序,總共八種基礎的排序算法,其餘的排序算法都是在這八種方法上的組合與變種。git
直接插入法十分簡單,就是將亂序的元素逐個插入到已排序好的序列中,即將被插入元素與排好序的元素逐一比較,插入到合適的位置,時間複雜度爲O(n^2)。算法
希爾排序是在直接插入法的改進,首先構造一增量序列(遞減至1),按某個增量d分紅若干組子序列,每組中記錄的下標相差d,對每組中所有元素進行直接插入排序,而後再用增量序列中下一個較小的增量進行分組,再對每組進行直接插入排序,直至增量爲1。其效果以下:shell
示例代碼:數組
void ShellInsertSort(int a[], int n, int dk) { for(int i= dk; i<n; ++i){ if(a[i] < a[i-dk]){ int j = i-dk; int x = a[i]; a[i] = a[i-dk]; while(x < a[j]){ a[j+dk] = a[j]; j -= dk; } a[j+dk] = x; } } } void shellSort(int a[], int n){ int dk = n/2; while( dk >= 1 ){ ShellInsertSort(a, n, dk); dk = dk/2; } }
希爾排序相比於簡單直接插入排序,減小了複製的次數,緣由是當增量較大時數據項每一趟排序須要移動的個數不多,儘管數據項的個數不少;當增量減少時,每一趟須要移動的數據增多,但此時已經接近於它們排序後的最終位置,因此希爾排序的效率比插入排序高不少。希爾排序的平均時間複雜度爲O( n^1.3 ),沒有快速排序算法快,所以對規模很是大的數據排序不是最優選擇。但希爾排序在最壞的狀況下和平均狀況下執行效率相差不是不少,而快速排序在最壞的狀況下執行的效率會很是差。因此,大部分排序工做在開始時均可以用希爾排序,若在實際使用中證實它不夠快,再改爲快速排序這樣更高級的排序算法。性能
簡單選擇排序即在首次遍歷容器的過程當中找到最小或最小的元素與第一個元素交換位置,而後在第二次遍歷過程當中重複上述步驟與第二個元素交換位置,以此類推,時間複雜度爲O(n^2)。在此基礎上能夠作一些改進,好比每次搜索過程當中同時搜索最大和最小元素並分別放在容器的頭和尾,這樣遍歷次數能減小一半。ui
堆排序是一種樹形選擇排序,對一個n個元素的序列,堆的定義是:spa
堆對應一棵徹底二叉樹,且全部非葉結點的值均不大於(或不小於)其子女的值,根結點(堆頂元素)的值是最小(或最大)的。堆排序的過程就是先把數組的n個元素創建成堆結構,而後堆頂元素就是排序後數組的第一個元素,再將剩下 n-1 個元素調整爲新的堆結構,其堆頂就是排序後的第二個元素,以此類推,與簡單選擇排序有必定類似之處。指針
先說調整堆的方法:code
1)設有n個元素的堆,輸出堆頂元素後,剩下 n-1 個元素。將堆底元素送入堆頂(最後一個元素與堆頂進行交換),此時根結點不知足堆的性質。xml
2)將根結點與左、右子樹中較小元素的進行交換。
3)若與左子樹交換:若是左子樹堆被破壞,即左子樹的根結點不知足堆的性質,則重複方法 (2).
4)若與右子樹交換,若是右子樹堆被破壞,即右子樹的根結點不知足堆的性質。則重複方法 (2).
5)繼續對不知足堆性質的子樹進行上述交換操做,直到葉子結點,堆被建成。
創建堆的過程就是反覆調整的過程,從第 [n/2] 個元素開始,依次向前直到第一個元素全都重複上述(2)~(5)調整過程,一個堆就被初始化好了。
示例代碼:
void HeapAdjust(int H[],int s, int length) { int tmp = H[s]; int child = 2*s+1; 、 while (child < length) { if(child+1 <length && H[child]<H[child+1]) { ++child ; } if(H[s]<H[child]) { H[s] = H[child]; s = child; child = 2*s+1; } else { break; } H[s] = tmp; } print(H,length); } void BuildingHeap(int H[], int length) { for (int i = (length -1) / 2 ; i >= 0; --i) HeapAdjust(H,i,length); } void HeapSort(int H[],int length) { BuildingHeap(H, length); for (int i = length - 1; i > 0; --i) { int temp = H[i]; H[i] = H[0]; H[0] = temp; HeapAdjust(H,0,i); } }
堆排序的平均時間複雜度爲O(nlogn),且最差、最好時間複雜度都在這個量級。
冒泡排序是最簡單的一種交換排序,其算法是不斷遍歷數組,每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換,時間複雜度爲O(n^2)。
快速排序是另外一種交換排序,也是速度最快的一種排序算法,其算法以下:
1)選擇一個基準元素,一般選擇第一個元素或者最後一個元素,
2)經過一趟排序將待排序的記錄分割成獨立的兩部分,其中一部分記錄的元素值均比基準元素值小。另外一部分記錄的元素值比基準值大。
3)此時基準元素在其排好序後的正確位置。
4)而後分別對這兩部分記錄用一樣的方法繼續進行排序,直到整個序列有序。
具體過程如圖所示:
示例代碼:
int partition(int a[], int low, int high) { int privotKey = a[low]; while(low < high){ while(low < high && a[high] >= privotKey) --high; swap(&a[low], &a[high]); while(low < high && a[low] <= privotKey ) ++low; swap(&a[low], &a[high]); } print(a,10); return low; } void quickSort(int a[], int low, int high){ if(low < high){ int privotLoc = partition(a, low, high); quickSort(a, low, privotLoc -1); quickSort(a, privotLoc + 1, high); } }
快速排序的時間複雜度爲O(nlogn),且其平均性能是同數量級算法中最好的。但若初始序列按關鍵碼有序或基本有序時,快排反而蛻化爲冒泡排序。
歸併排序法的思想是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干有序個子序列,而後再將它們合併爲總體有序序列。1 個元素的表老是有序的,因此對n 個元素的待排序列,每一個元素可當作1 個有序子表,對子表兩兩合併生成n/2個子表,所得子表除最後一個子表長度可能爲1 外,其他子表長度均爲2。再進行兩兩合併,直到生成n 個元素按關鍵碼有序的表。效果如圖:
示例代碼:
void Merge(ElemType *r,ElemType *rf, int i, int m, int n) { int j,k; for(j=m+1,k=i; i<=m && j <=n ; ++k){ if(r[j] < r[i]) rf[k] = r[j++]; else rf[k] = r[i++]; } while(i <= m) rf[k++] = r[i++]; while(j <= n) rf[k++] = r[j++]; }
歸併排序的平均時間複雜度爲O(nlogn),且最好、最壞時間複雜度都在這個量級。
基數排序是一種多關鍵字排序,分爲最高位優先(Most Significant Digit first)法和最低位優先(Least Significant Digit first)法。設待排序列爲n個記錄,d個關鍵碼,關鍵程度從k1到kd遞減,關鍵碼的取值範圍爲radix。(關鍵碼的意義是隻要k1大,則排序高,k1相等則比較其他關鍵碼,對 k2~kd 同理)
MSD法:先按k1排序分組,同一組中記錄,關鍵碼k1相等,再對各組按k2排序分紅子組,以後,對後面的關鍵碼繼續這樣的排序分組,直到按最次位關鍵碼kd對各子組排序後。再將各組鏈接起來,便獲得一個有序序列;
排序算法選取準則: