資料結構之常見排序演算法
阿新 • • 發佈:2019-01-04
#include "stdafx.h" #include <memory> #define MAXSIZE 10 typedef struct { int r[MAXSIZE+1];//用於儲存要排序陣列,r[0]用作哨兵或臨時變數 int nlength;//用於記錄順序表長度 }SqList; //交換L中i,j位置的資料 void swap(SqList *L, int i, int j) { int temp = L->r[i]; L->r[i] = L->r[j]; L->r[j] = temp; } /************************************************************************/ /* 一、交換排序 */ /************************************************************************/ //1.1氣泡排序 穩定性:穩定 時間複雜度:O(n^2) 空間複雜度:O(1) //基本思路:兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄為止 //對順序表L做氣泡排序 void BubbleSort(SqList *L) { int i,j; bool bchange;//判斷是否發生交換 for (i=1;i<L->nlength;i++) { bchange=false; for (j=L->nlength-1;j>=i;j--) { if (L->r[j]>L->r[j+1]) { swap(L,j,j+1); bchange=true; } } if (!bchange)//如果沒有發生交換說明已經是排好序了 return; } } //1.2快速排序 穩定性:不穩定 時間複雜度:平均O(nlogn) 有序時最差O(n^2) 空間複雜度:O(logn) //基本思路:通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小 //則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序的目的 //交換順序表L中子表的記錄,使樞軸到位,並返回其所在位置,此時它前面的值均不大於它,後面的值均不小於它 int Partition(SqList *L, int low, int high) { int pivotkey = L->r[low];//用子表的第一個記錄作樞軸記錄 while(low<high)//從表的兩端交替向中間掃描 { while(low<high && L->r[high]>=pivotkey) high--; swap(L,low,high);//將比樞軸記錄小的記錄交換到低端 while(low<high && L->r[low]<=pivotkey) low++; swap(L,low,high);//將比樞軸記錄大的記錄交換到高階 } return low;//返回樞軸位置 } //對順序表L中的子序列L->r[low.....high]做快速排序 void QSort(SqList *L, int low, int high) { int pivot; if (low<high) { pivot = Partition(L,low,high);//將L->r[low.....high]一分為二,算出樞軸值pivot QSort(L,low,pivot-1);//對低子表做遞迴排序 QSort(L,pivot+1,high);//對高子表做遞迴排序 } } //對順序表L做快速排序 void QuickSort(SqList *L) { QSort(L,1,L->nlength); } //快排的優化策略 //1.優化選擇樞軸:三數取中,九數取中 //2.優化不必要的交換:修改Partition()將替換改為交換 high--,r[low]=r[high]; low++,r[high]=r[low]; //3.優化小陣列時的排序方案:修改QSort()當陣列個數不大於某個常數(7?50?)時用直接插入排序 //4.優化遞迴操作:每次分割槽後先處理較短的部分 /************************************************************************/ /* 二、選擇排序 */ /************************************************************************/ //2.1簡單選擇排序 穩定性:不穩定 時間複雜度:O(n^2) 相比於氣泡排序效能更優,因為交換次數少 空間複雜度:O(1) //基本思路:通過n-i次關鍵字間的比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和第i個記錄交換之 //對順序表L做簡單選擇排序 void SelectSort(SqList *L) { int i,j,min; for (i=1;i<L->nlength;i++) { min=i;//將當前下標定義為最小值下標 for (j=i+1;j<=L->nlength;j++)//迴圈比較n-i次 { if (L->r[min]>L->r[j]) min=j;//找出n-i+1個記錄中的最小值 } if (min!=i)//若min!=i,則交換 swap(L,i,min); } } //2.2堆排序 穩定性:不穩定 時間複雜度:O(nlogn) 空間複雜度:O(1) //基本思路:利用堆進行排序,重複構造大頂錐,將根結點與末尾元素交換,再將剩餘的元素重新構造大頂錐 //本函式調整L->r[s]的關鍵字,使L->r[s.....m]成為一個大頂錐 void HeapAdjust(SqList *L, int s, int m) { int temp,j; temp=L->r[s]; //s為子根結點 for (j=s*2;j<=m;j*=2)//沿關鍵字較大的孩子結點向下篩選 { if (j<m && L->r[j]<L->r[j+1]) j++;//j為關鍵字中較大的記錄的下標 if (temp<L->r[j]) { L->r[s]=L->r[j];//更新L->r[s]為最大值 s=j; } } L->r[s]=temp;//交換 } //對順序表L做堆排序 void HeapSort(SqList *L) { int i; for (i=L->nlength/2;i>0;i--)//把L中的r構造成一個大頂錐 此時的i都為子根結點 { HeapAdjust(L,i,L->nlength); } for (i=L->nlength;i>1;i--) { swap(L,1,i);//將大頂錐的根結點與末尾元素交換 HeapAdjust(L,1,i-1);//將剩餘的結點重新構造成一個大頂錐 } } /************************************************************************/ /* 三、插入排序 */ /************************************************************************/ //3.1直接插入排序 穩定性:穩定 時間複雜度:O(N^2) 是簡單排序中效能最好的 空間複雜度:O(1) //基本思路:將一個記錄插入到已經排好序的有序表中,從而得到一個新的,記錄數增1的有序表 //對順序表L做直接插入排序 void InsertSort(SqList *L) { int i,j; for (i=2;i<=L->nlength;i++) { if (L->r[i]<L->r[i-1])//如果當前元素小於之前的元素,則需要把它插入到前面 { L->r[0]=L->r[i];//設定哨兵 for (j=i-1;L->r[j]>L->r[0];j--)//與前面的元素分別比較,將大的記錄往後移 { L->r[j+1]=L->r[j];//將記錄後移 } L->r[j+1]=L->r[0];//將元素插入到正確的位置 } } } //3.2希爾排序 穩定性:不穩定(跳躍式的移動) 時間複雜度:O(N^(3/2)) 空間複雜度:O(1) //基本思路:將原本具有大量記錄的序列,通過相隔某個增量的記錄組成子序列,對子序列進行直接插入排序 //從而使整個序列基本有序,最後對整個序列做直接插入排序 //對順序表L做希爾排序 void ShellSort(SqList *L) { int i,j,increment; increment=L->nlength; do { increment=increment/3+1;//增量序列 for (i=increment+1;i<=L->nlength;i++) { if (L->r[i]<L->r[i-increment])//如果當前元素小於之前的元素,則需要把它插入到前面 { L->r[0]=L->r[i];//設定哨兵 for (j=i-increment;j>0&&L->r[j]>L->r[0];j-=increment)//與前面的元素分別比較,將大的記錄往後移 { L->r[j+increment]=L->r[j];//將記錄後移 } L->r[j+increment]=L->r[0];//將元素插入到正確的位置 } } } while (increment>1); } /************************************************************************/ /* 四、歸併排序 穩定性:穩定 時間複雜度:O(nlogn) 空間複雜度O(n) */ /************************************************************************/ //基本思路:兩兩合併,倒置的完全二叉樹 //將有序的SR[i...m]和SR[m+1...n]歸併為有序的TR[i...n] void Merge(int SR[], int TR[], int i, int m, int n) { int j,k,l; for (j=m+1,k=i;j<=n&&i<=m;k++)//將SR中記錄由小到大歸併入TR { if (SR[i]<SR[j]) TR[k]=SR[i++]; else TR[k]=SR[j++]; } if (i<=m) { for (l=0;l<=m-i;l++) TR[k+l]=SR[i+l];//將剩餘的SR[i...m]複製到TR } if (j<=n) { for (l=0;l<=n-j;l++) TR[k+l]=SR[j+l];//將剩餘的SR[j...n]複製到TR } } //將SR[]中相鄰長度為s的子序列兩兩歸併到TR[] void MergePass(int SR[], int TR[], int s, int n) { int i=1; int j; while (i<=n-s*2+1) { Merge(SR,TR,i,i+s-1,i+2*s-1);//兩兩合併 i=i+s*2; } if (i<n-s+1)//歸併<最後>兩個序列(只在最後一次出現) Merge(SR,TR,i,i+s-1,n); else//若最後只剩下單個子序列 for (j=i;j<=n;j++) TR[j]=SR[j]; } //對順序表L做歸併排序 void MergeSort(SqList *L) { int k=1; int *TR=(int*)malloc(sizeof(int)*L->nlength);//申請額外空間 while (k<L->nlength) { MergePass(TR,L->r,k,L->nlength); k=k*2;//子序列長度加倍 MergePass(L->r,TR,k,L->nlength); k=k*2;//子序列長度加倍 } } /************************************************************************/ /* 五、總結 */ /************************************************************************/ //5.1時間複雜度來說: //(1)平方階(O(n2))排序:各類簡單排序:直接插入、簡單選擇和氣泡排序; //(2)線性對數階(O(nlog2n))排序:快速排序、堆排序和歸併排序; //(3)O(n^(1+§)))排序(§是介於0和1之間的常數):希爾排序 //(4)線性階(O(n))排序:基數排序,此外還有桶、箱排序。 //說明: //當原表有序或基本有序時,直接插入排序和氣泡排序將大大減少比較次數和移動記錄的次數,時間複雜度可降至O(n) //而快速排序則相反,當原表基本有序時,將蛻化為氣泡排序,時間複雜度提高為O(n2) //原表是否有序,對簡單選擇排序、堆排序、歸併排序和基數排序的時間複雜度影響不大 //5.2穩定性來說: //排序演算法的穩定性:若待排序的序列中,存在多個具有相同關鍵字的記錄,經過排序, 這些記錄的相對次序保持不變,則稱該演算法是穩定的 // 若經排序後,記錄的相對 次序發生了改變,則稱該演算法是不穩定的。 //穩定性的好處:排序演算法如果是穩定的,那麼從一個鍵上排序,然後再從另一個鍵上排序, // 第一個鍵排序的結果可以為第二個鍵排序所用。基數排序就是這樣,先按低位排序, // 逐次按高位排序,低位相同的元素其順序再高位也相同時是不會改變的。另外,如果排序演算法穩定, // 可以避免多餘的比較; //穩定的排序演算法:氣泡排序、直接插入排序、歸併排序和基數排序 //不穩定的排序演算法:簡單選擇排序、快速排序、希爾排序、堆排序