一、常見的十種排序算法:

3.插入排序偽代碼
1 void InsertSort (ElemType A[], int n) 2 { 3 int i,j; 4 for(i=2;i<=n;i++) 5 if(A[i].key<A[i-1].key) 6 { 7 A[0]=A[i]; 8 for(j=i-1;A[0].key<A[j].key;--j) 9 A[j+i]=A[j]; 10 A[j+i]=A[0]; 11 } 12 }
4.穩定性
由於每次插入元素時總是從后往前先比較在移動,所以不會出現相同元素相對位置,發生變化的情況即直接插入排序是一個穩定的排序方法
5.時間復雜度:O(n²)
•希爾排序(縮小增量排序)
1.算法提出:
2.算法思想:
補充:操作原理時間復雜度與選取的增量序列有關且所取增量序列的函數介於O(N*logN)和O(n²)之間增量序列有很多種取法,但是使增量序列中的值沒有除1之外的公因子,並且增量序列中最后一個值必須為1。
4.希爾排序偽代碼
1 void ShellSort (ElemType A[],int n){ 2 //對順序表作希爾插入排序,基本算法和直接插入排序相比,做了以下修改: 3 //1.前后記錄位置的增量是dk,不是1 4 //2.r[0]只是暫時存儲單元,不是哨兵,當j<=0時,插入位置已到 5 for(dk=n/2;dk>=1,dk=dk/2) //步長變化 6 for(i=dk+1;i<=n;++i) 7 if(A[i].key<A[i-dk].key){ //需將A[i]插入有序增量子表 8 A[0]=A[i]; 9 for(j=i-dk;j>0&&A[0].key<A[j].key;j-=dk) 10 A[j+dk]=A[j]; //記錄后移,查找插入位置 11 A[j+dk]=A[0]; //插入 12 }//if 13 }
5.穩定性
當相同關鍵字的記錄被划分到不同的子表時,可能會改變它們之間的相對次序,因此,希爾排序是一種不穩定的排序方法。例如,表L=[3,2,2].經過一趟排序后,L=[2,2,3],最終排序序列也是L=[2,2,3],顯然2與2的相對次序已經發生了變化。
6.時間復雜度:O(N*logN)
選擇排序
•簡單選擇排序
1.算法思想
Step1:將待排序數組分為有序和無序兩組(初始情況下有序組為空)。
Step2:從左向右掃描無序組,找出最小的元素,將其放置在無序組的第一個位置。至此有序組++,無序組--;
Step3:重復Step2,直至無序組只剩下一個元素。
2.算法實現

3.選擇排序偽代碼
1 void ShellSort (ElemType A[],int n){ 2 //對表A作簡單的選擇排序,A[]從0開始放元素 3 for(i=0;i<=n-1;i++){ //一共進行n-1趟 4 min=i; //記錄最小元素位置 5 for(j=i+1;j<n;j++) //在A[i...n-1]中選擇最小的元素 6 if(A[j]<A[min]) 7 min=j; //更新最小元素位置 8 if(min!=i) swap(A[i],A[min]); //在第n個元素交換 9 10 } 11 }
4.穩定性
選擇排序的時間復雜度為O(n²),由於每次選擇僅考慮某一位置上的數據情況,可能會破壞之前數據的相對位置,因此它是一種不穩定的排序方法。 例如:序列 [9,9,1]第一次就將第一個[9]與[1]交換,導致第一個9挪動到第二個9后面。
5.時間復雜度: O(n²)
補充:簡單選擇排序的比較次數與序列的初始排序無關。假設待排序的序列有 n個元素,選擇排序的賦值操作介於0和3(n - 1次之間; 則比較次數永遠都是n(n-1)/2; 而移動次數(即:交換操作)與序列的初始排序有關,介於 0 和 (n - 1) 次之間。當序列正序時,移動次數最少,為 0。當序列反序時,移動次數最多,為n - 1 次;逆序交換n/2次。選擇排序的移動次數比冒泡排序少多了,由於交換所需CPU時間比 比較所需的CPU時間多,n值較小時,選擇排序比冒泡排序快。
•堆排序
1.引入概念
堆是一棵順序存儲的完全二叉樹。其中每個結點的關鍵字都不大於其孩子結點的關鍵字,這樣的堆稱為小根堆。
其中每個結點的關鍵字都不小於其孩子結點的關鍵字,這樣的堆稱為大根堆。
舉例來說,對於n個元素的序列{R0, R1, ... , Rn}當且僅當滿足下列關系之一時,稱之為堆: 
2.算法思想

3.算法實現(大頂堆)
注:若n為數組的個數,那么就從2/n開始,依次循環。4.堆排序偽代碼
1 void BuildMaxHeap (ElemType A[], int len) { 2 for(int i=len/2;i>0;i--) //從i=[n/2]~1,反復調整堆 3 AdjustDown(A, i, len) ; 4 5 } 6 void AdjustDown (ElemType A[], int k, int len) { 7 //函數AdjustDown將元素k向下進行調整 8 A[0]=A[k] ; //A[O]暫存 9 for (i=2*k;i<=len;i*=2){ //沿 key較大的子結點向下篩選 10 if (i<len&&A[i] <A[i+1]) 11 i++; 12 if(A[0]>=A[i]) break; // 篩選結束 13 else{ 14 A[k]=A[i]; //將A[i]調整到雙親結點上 15 k=i ; //修改k值,以便繼續向下篩選 16 } 17 }//for 18 A[k]=A[0]; //被篩選結點的值放入最終位置 19 }
下面是堆排序算法:
1 void HeapSort (ElemType A[],int len) { 2 BuildMaxHeap (A,len) ; //初始建堆 3 for(i=len;i>1;i--){ //n-1 趟的交換和建堆過程 4 Swap (A[i],A[1]) ; / /輸出堆頂元素(和堆底元素交換) 5 AdjustDown (A, 1, i-1) ;} //整理,把剩余的i-1個元素整理成堆 6 }//for 7 }
下面是向上調整堆的算法:
1 void AdjustUp (ElemType A[], int k) { 2 //參數k為向上調整的結點,也為堆的元素個數 3 A[0]=A[k] ; 4 int i=k/2; //若結點值大於雙親結點, 則將雙親結點向下調,並繼續向上比較 5 while (i>0&&A[i]<A[0]) { //循環跳出條件 6 A[k]=A[i]; //雙親結點下調 7 k=i; 8 i=k/2; //繼續向上比較 9 }//while 10 A[k]=A[0] ; //復制到最終位置 11 }
交換排序
•冒泡排序
1.算法思想
冒泡排序是一種交換排序,它的實現原理是:兩兩比較相鄰的記錄值,如果反序則交換,直到沒有反序的記錄為准。對數組中的各數據,依次比較相鄰的兩元素的大小。如果前面的數據大於后面的數據,就交換這兩個數據。經過第一輪的多次比較排序后,變可把最小的數據排好。再用同樣的方法把剩下的數據逐個進行比較,最后便可按照從小到大的順序排好數組各數據的順序。
2.算法實現

3. 冒泡排序偽代碼
1 void BubbleSort (ElemType A[],int n){ 2 //用冒泡排序將序列A中的元素按從小到大排列 3 for(i=0;i<n-1;i++){ 4 flag=false; //標示本趟冒泡是否發生交換標志 5 for(j=n-1;j>i;j--) //一趟冒泡過程 6 if(A[j-i].key>A[j].key){ //若為逆序 7 swap(A[j-1],A[j]); //交換 8 flag=true; 9 } 10 if(flag==false) 11 return; //本趟遍歷沒有發生交換,說明已經有序 12 } 13 }
4.穩定性
冒泡排序是把小的元素往前調或者把大的元素往后調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。相同元素的前后順序並沒有改變,冒泡排序是一種穩定排序算法。
5.時間復雜度: O(n²)
補充:若文件的初始狀態是正序的,一趟掃描即可完成排序。所需的關鍵字比較次數C和記錄移動次數M均達到最小值:Cmin = N - 1, Mmin = 0。所以,冒泡排序最好時間復雜度為O(N)。若初始文件是反序的,需要進行 N -1 趟排序。每趟排序要進行 N - i 次關鍵字的比較(1 ≤ i ≤ N - 1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種情況下,比較和移動次數均達到最大值:Cmax = N(N-1)/2 = O(N^2) Mmax = 3N(N-1)/2 = O(N^2)冒泡排序的最壞時間復雜度為O(N^2)。因此,冒泡排序的平均時間復雜度為O(N^2)。當數據越接近正序時,冒泡排序性能越好。
•快速排序
1.基本概念
快速排序為應用最多的排序算法,因為快速二字而聞名。快速排序和歸並排序一樣,采用的都是分治思想。快速排序可以分為:單路快速排序,雙路快速排序,三路快速排序,他們區別在於選取幾個指針來對數組進行遍歷 。
1.算法思想
快速排序是對冒泡排序的一種改進。其基本思想是基於分治法的:在待排序表L[`1...n]中任取一個元素 pivot作為基准,通過一趟排序表將待排序表划分為獨立的兩部分
L[1...k-1]和L[k+1...n],使得L[1...k-1]中所有元素小子pivot, L[k+1...n]中所有元素大於或等於pivot,則pivot放在了其最終位置L(k)上,這個過程稱作一趟快速排序。 而后分別遞歸地對兩個子表重復上述過程,直至每部分內只有一個元素或空為止,即所有元素放在了其最終位置上。
2.算法實現
注:后找小前找大
3. 快速排序偽代碼
首先假設划分算法已知,記為Partition(),返回的是上述中的k,注意到L(k)已經在最終的位置,所以可以先對表進行划分,而后對兩個表調用同樣的排序操作。因此可以遞歸地調用快速排序算法進行排序,具體的程序結構如下:
1 void QuickSort (ElemType A[],int low,int high){ 2 3 if (low<high) { //遞歸跳出的條件 4 //Partition()就是划分操作,將表low-high划分為滿足上述條件的兩個子表 5 int pivotpos-=Partition(A, low,high); //划分//依次對兩個子表進行遞歸排序 6 QuickSort MB (A, low,pivotpos-1); 7 QuickSort (A, pivotpos+1,high) ; 8 } 9 }
從上面的代碼也 不難看出快速排序算法的關鍵在於划分操作,同時快速排序算法的性能也主據要取決於划分操作的好壞。假設每次總是以當前表中第一個元素作為樞軸值 (基准)對表進行划分,則必須將表中比樞軸值大的元素向右移動,比樞軸值小的元素向左移動,使得一趟Partition()操作之后,表中的元素被樞軸值一分為二。
4.穩定性
快速排序有兩個方向,左邊的i下標一直往右走,當a[i] <= a[center_index],其中center_index是中樞元素的數f組下標,一般取為數組第0個元素。而右邊的j下標一直往左走,當a[j] > a[center_index]。如果i和j都走不動了,i <= j, 交換a[i]和a[j],重復上面的過程,直到i>j。 交換a[j]和a[center_index],完成一趟快速排序。在中樞元素和a[j]交換的時候,很有可能把前面的元素的穩定性打亂,比如序列為 5 3 3 4 3 8 9 10 11, 現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂,所以快速排序是一個不穩定的排序算法,不穩定發生在中樞元素和a[j] 交換的時刻。
5.時間平均復雜度: O(nlog2n)。
基數排序(桶排序)
1.基本概念
基數排序(Radix Sort)屬於分配式排序,又稱"桶子法"(Bucket Sort或Bin Sort),將要排序的元素分配到某些"桶"中,以達到排序的作用。基數排序屬於穩定的排序,其時間復雜度為nlog(r)m (其中r為的采取的基數,m為堆數),基數排序的效率有時候高於其它比較性排序。
2.算法思想
1. 根據輸入建立適當個數的桶,每個桶可以存放某個范圍內的元素;
2.將落在特定范圍內的所有元素放入對應的桶中;
3.對每個非空的桶中元素進行排序,可以選擇通用的排序方法,比如插入、快排;
4.按照划分的范圍順序,將桶中的元素依次取出。排序完成。
3. 算法實現
數組: int arr[] = {278,109,63,930,589,184,505,269,8,83};
個位分配,個位收集,先進后出。
十位分配,十位收集,先進后出。
百位分配,百位收集,先進后出。
4.穩定性
基數排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優 先級排序,最后的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收集,所以其是穩定的排序算法。
5.時間平均復雜度:O(d(n+r))
歸並排序(二路歸並排序)
1.基本概念
歸並排序是通過“歸並”操作完成排序的,將兩個或者多個有序子表歸並成一個子表。歸並排序是“分治法”的一個非常典型的應用,同事它也是遞歸算法的一個好的實例。它將問題分成一些小的問題然后遞歸求解,而治就是把分階段的答案拼起來。
2.算法思想
將一個大小為 n 的數組排序。歸並排序算法的排序步驟是:
1.將所有的數字放入一個無序的堆。
2.將堆分成兩部分,現在你有兩個無序的堆。
3.持續將無序的堆拆分,直到無法再拆分為止,你將得到 n 個堆,每一個堆中有一個數字。
4.現在開始將這些堆按照一定順序按對合並。每一次合並過程中,將堆中的數字放入有序的隊列。這一點很容易實現,因為每一個獨立的堆中的內容都是有序的。
3.算法實現
數組: int arr[] = {3,6,1,7,9,4,5,8,2}
二路歸並排序的過程如圖所示:

4.穩定性
歸並排序的空間復雜度O(n)。另外,歸並排序中歸並的算法並不會將相同關鍵字的元素改變相對次序,所以歸並排序是穩定的。
5.時間平均復雜度:O(nlog2n)。
后續未完...
