内排序算法一览
文章目錄
- 1 插入排序
- 2 希爾(shell)排序
- 3 冒泡排序
- 4 快速排序
- 5 選擇排序
- 6 堆排序
- 7 歸并排序
- 8 內排序代碼一覽
- 運行結果
- 常用排序算法時間復雜度和空間復雜度一覽表
- 排序:將一組雜亂無章的數據按一定的規律順次排列起來,可以看作是線性表的一種操作。
- 內排序:排序期間元素全部存放在內存中的排序。
- 并非所有內部排序算法都基于比較操作,基數排序就不基于比較。
- 時間效率——排序速度(即排序所花費的全部比較次數)
- 空間效率——占內存輔助空間的大小
- 穩定性——若兩個記錄A和B的關鍵字值相等,但排序后A、B的先后次序保持不變,則稱這種排序算法是穩定的。
- 算法是否具有穩定性并不能衡量一個算法的優劣,它主要是對算法性質進行描述,內部排序算法的性能取決于算法的時間復雜度和空間復雜度,時間復雜度一般是由比較和移動次數決定。
基本上是,因為每趟可以確定的數據元素是呈指數增加的。
1 插入排序
插入排序的基本思想是:
每步將一個待排序的對象,按其關鍵碼大小,插入到前面已經排好序的一組對象的適當位置上,直到對象全部插入為止。
簡言之,邊插入邊排序,保證子序列中隨時都是排好序的。
在已形成的有序表中線性查找,并在適當位置插入,把原來位置上的元素向后順移。
時間效率:
因為在最壞情況下,所有元素的比較次數總和為(0+1+…+n-1)→O(n2)。
其他情況下也要考慮移動元素的次數。 故時間復雜度為O(n2)
空間效率:
僅占用1個緩沖單元——O(1)
算法的穩定性:
因為25*排序后仍然在25的后面——穩定
直接插入排序算法的實現:
void InsertSort ( int arr[],int n ) { int i,j,temp; //temp暫存待插關鍵字for ( i = 1; i <=n; i++) //依次將A[2]~A[n]插入到前面已排序的序列{ temp=arr[i]; j=i-1 ; //從i-1往前掃描while(j>=0 && temp<arr[j]){ arr[j+1]= arr[j]; //向后挪位--j ; } arr[j+1]= temp; //復制到插入元素 } }直接插入排序適用于順序存儲和鏈式存儲的線性表
2 希爾(shell)排序
基本思想:
先將整個待排記錄序列分割成若干子序列,分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行一次直接插入排序。
優點:
讓關鍵字值小的元素能很快前移,且序列若基本有序時,再用直接插入排序處理,時間效率會高很多。
void ShellSort(ElementType A[], int N) { /* 希爾增量序列 */for (D = N / 2; D > 0; D /= 2) { //D=1即直接插入排序/* 插入排序 */for (i = D; i < N; i++) {tmp = A[i];for(j = i; j >= D && A[j - D] > tmp; i -= D) {A[j] = A[j - D];}A[i] = tmp;}} }時間復雜度
1.原始數據如下共有16個數據:
1 | 9 | 2 | 10 | 3 | 11 | 4 | 12 | 5 | 13 | 6 | 14 | 7 | 15 | 8 | 16 |
2.進行 8 間隔排序后(未發生變化):
1 | 9 | 2 | 10 | 3 | 11 | 4 | 12 | 5 | 13 | 6 | 14 | 7 | 15 | 8 | 16 |
3.進行 4 間隔排序后(未發生變化):
1 | 9 | 2 | 10 | 3 | 11 | 4 | 12 | 5 | 13 | 6 | 14 | 7 | 15 | 8 | 16 |
4.進行 2 間隔排序后(未發生變化):
1 | 9 | 2 | 10 | 3 | 11 | 4 | 12 | 5 | 13 | 6 | 14 | 7 | 15 | 8 | 16 |
5.進行 1 間隔排序后(1間隔排序也就是插入排序):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
最壞情況(與插入排序一樣)
T=O(n2)T = O(n^2)T=O(n2)
結論:
那么來觀察它的間隔數 8 4 2 1,發現其間隔增量元素并不互質,則小的增量根本不起作用
穩定性:
由于不是相鄰元素的比較和交換,它是不穩定的
希爾排序僅適用于線性表為順序存儲的情況
3 冒泡排序
基本思路:
每趟不斷將記錄兩兩比較,并按“前小后大”(或“前大后小”)規則交換。
優點:
每趟結束時,不僅能擠出一個最大值到最后面位置,還能同時部分理順其他元素;一旦下趟沒有交換發生,還可以提前結束排序。
前提:順序存儲結構
冒泡排序的算法分析:
- 時間效率:O(n2) —因為要考慮最壞情況
- 空間效率:O(1) —只在交換時用到一個緩沖單元
- 穩 定 性: 穩定 —25和25*在排序前后的次序未改變
- 冒泡排序的優點:每一趟整理元素時,不僅可以完全確定一個元素的位置(擠出一個泡到表尾),還可以對前面的元素作一些整理,所以比一般的排序要快。
4 快速排序
基本思想:
從待排序列中任取一個元素 (例如取第一個) 作為中心,所有比它小的元素一律前放,所有比它大的元素一律后放,形成左右兩個子表;然后再對各子表重新選擇中心元素并依此規則調整,直到每個子表的元素只剩一個。此時便為有序序列了。
優點:
因為每趟可以確定不止一個元素的位置,而且呈指數增加,所以特別快!
前提:
順序存儲結構
快排算法分析:
- 時間效率:O(nlog2n) —因為每趟確定的元素呈指數增加
- 空間效率:O(log2n)—因為遞歸要用棧(存每層low,high和pivot)
- 穩 定 性: 不 穩 定 —因為有跳躍式交換。
5 選擇排序
選擇排序的基本思想:
每經過一趟比較就找出一個最小值,與待排序列最前面的位置互換即可。
——首先,在n個記錄中選擇最小者放到r[1]位置;然后,從剩余的n-1個記錄中選擇最小者放到r[2]位置;…如此進行下去,直到全部有序為止。
優點:實現簡單
缺點:每趟只能確定一個元素,表長為n時需要n-1趟
前提:順序存儲結構
6 堆排序
解釋:如果讓滿足以上條件的元素序列 (k1,k2,…,kn)順次排成一棵完全二叉樹,則此樹的特點是:樹中所有結點的值均大于(或小于)其左右孩子,此樹的根結點(即堆頂)必最大(或最小)。
步驟:從最后一個非終端結點開始往前逐步調整,讓每個雙親大于(或小于)子女,直到根結點為止。
堆排序算法分析:
- 時間效率: O(nlog2n)。因為整個排序過程中需要調用n-1次HeapAdjust( )算法,而算法本身耗時為log2n;
- 空間效率:O(1)。僅在第二個for循環中交換記錄時用到一個臨時變量temp。
穩定性: 不穩定。 - 優點:對小文件效果不明顯,但對大文件有效。
7 歸并排序
歸并排序算法的基本思想 :
(1) 初始無序序列看成n個有序子序列,每個子序列長度為1;
(2) 兩兩合并,得到└ n/2┘個長度為2或1的有序子序列;
(3) 重復步驟2直至得到一個長度為n的有序序列為止。
兩個有序表合并成一個有序表
合并算法:
//將有序表A[low..mid]和A[mid+1,high] 歸并為有序表B[low..high] void Merge(ElementType A[], ElementType B[], int low, int mid, int high){ i = low; j=mid+1; k=low; while (i <=mid && j <=high) { if (A[i] < A[j]) B[k++] = A[i++]; else B[k++] = A[j++]; } while (i <=mid) B[k++] = A[i++]; while (j <= high) B[k++] = A[j++]; }給定區間的歸并算法 :
//將A[low…high]中的序列歸并排序后放到B[low…high]中 void MSort(ElementType A[], ElementType B[], int low, int high){ if (low==high) B[low]=A[low]; else { mid=(low+high)/2; //將當前序列一分為二,求出分裂點mid Msort(A, S, low, mid); //對子序列A[low..mid]遞歸歸并排序結果放入S[low..mid] Msort(A, S, mid+1,high); //對子序列A[mid+1..high]遞歸歸并排序,結果放入S[mid+1..high] Merge(S, B, low, mid, high); //將S[low..mid]和S[mid+1..high]歸并到B[low..high] } }歸并排序算法實現:
void MergeSort(ElementType A[]) { //對順序表A做歸并排序 Msort(A, C, 1, A.length); }歸并排序算法分析:
- 時間效率:O(nlog2n)
- 空間效率:O(n)
- 穩 定 性:穩定
8 內排序代碼一覽
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<time.h> #include<Windows.h> typedef int keytype; typedef struct {keytype key;/*關鍵字碼*/ }RecType;int s[50][2];/*輔助棧s*/ void PrintArray(RecType R[],int n) {int i;for(i=0;i<n;i++)printf("%6d",R[i].key);printf("\n"); } void InsertSort(RecType R[],int n)/*用直接插入排序法對R[n]進行排序*/ {int i,j;RecType temp;for(i=0;i<=n-2;i++){temp=R[i+1];j=i;while (temp.key<=R[j].key && j>-1){R[j+1]=R[j];j--;}R[j+1]=temp;} } void ShellInsert(RecType *r,int dk,int n) {int i,j;keytype temp;for(i=dk; i<n; ++i )if(r[i].key <r[i-dk].key ) //將r[i]插入有序子表{temp=r[i].key;r[i].key=r[i-dk].key;for(j=i-2*dk;j>-1 && temp<r[j].key; j-=dk )r[j+dk].key=r[j].key; // 記錄后移r[j+dk].key = temp; // 插入到正確位置} } void ShellSort(RecType *L,int dlta[],int t,int n)/*希爾排序*/ { // 按增量序列 dlta[t-1] 對順序表L作希爾排序int k;for(k=0;k<t;k++)ShellInsert(L,dlta[k],n); //一趟增量為 dlta[k] 的插入排序 } // ShellSort void CreatArray(RecType R[],int n) {int i;Sleep(1000);srand( (unsigned)time( NULL ) );for(i=0;i<n;i++)R[i].key=rand()%1000; } void BubbleSort(RecType R[],int n) /*冒泡排序*/ {int i,j,flag;keytype temp;for(i=0;i<n-1;i++){flag=0;for(j=0;j<n-i-1;j++){if(R[j].key>R[j+1].key){temp=R[j].key;R[j].key=R[j+1].key;R[j+1].key=temp;flag=1;}}if(flag==0)break;} } int Hoare(RecType r[],int l,int h)/*快速排序分區處理*/ {int i,j;RecType x;i=l;j=h;x=r[i];do{while(i<j && r[j].key>=x.key)j--;if(i<j){r[i]=r[j];i++;}while(i<j && r[i].key<=x.key)i++;if (i<j){r[j]=r[i];j--;}}while(i<j);r[i]=x;return(i); } /*Hoare*/ void QuickSort1(RecType r[],int n) /*快速排序非遞歸*/ {int l=0,h=n-1,tag=1,top=0,i;do{while (l<h){i=Hoare(r,l,h);top++;s[top][0]=i+1;s[top][1]=h;h=i-1;}/*tag=0表示棧空*/if (top==0)tag=0;else{l=s[top][0];h=s[top][1];top--;}}while (tag==1); /*棧不空繼續循環*/ } /*QuickSort1*/ void SelectSort(RecType R[],int n)/*直接選擇排序*/ {int i,j,k; RecType temp;for(i=0;i<n-1;i++){k=i;/*設第i個記錄關鍵字最小*/for(j=i+1;j<n;j++)/*查找關鍵字最小的記錄*/if(R[j].key<R[k].key)k=j;/*記住最小記錄的位置*/if (k!=i) /*當最小記錄的位置不為i時進行交換*/{temp=R[k];R[k]=R[i];R[i]=temp;}} } void HeapAdjust(RecType R[],int l,int m)/*堆排序篩選*/ {/*l表示開始篩選的結點,m表示待排序的記錄個數。*/int i,j;RecType temp;i=l;j=2*i+1; /*計算R[i]的左孩子位置*/temp=R[i]; /*將R[i]保存在臨時單元中*/while(j<=m-1){if(j<m-1 && R[j].key<R[j+1].key)j++; /*選擇左右孩子中最大者,即右孩子*/if(temp.key<R[j].key)/*當前結點小于左右孩子的最小者*/{R[i]=R[j];i=j;j=2*i+1;}else /*當前結點不小于左右孩子*/break;}R[i]=temp; } void HeapSort(RecType R[],int n)/*堆排序*/ {int j;RecType temp;for(j=n/2-1;j>=0;j--) /*構建初始堆*/HeapAdjust(R,j,n);/*調用篩選算法*/for(j=n;j>1;j--) /*將堆頂記錄與堆中最后一個記錄交換*/{temp=R[0];R[0]=R[j-1];R[j-1]=temp;HeapAdjust(R,0,j-1);/*將R[0..j-1]調整為堆*/} } void Merge(RecType aa[],RecType bb[],int l,int m,int n) {/*將兩個有序子序列aa[1…m]和aa[m+1,…n]合并為一個有序序列bb[1…n]*/int i,j,k;k=l;i=l;j=m+1;/*將i,j,k分別指向aa[1…m]、aa[m+1,…n]、bb[1…n]首記錄*/while (i<=m && j<=n) /*將aa中記錄由小到大放入bb中*/if(aa[i].key<=aa[j].key)bb[k++]=aa[i++];elsebb[k++]=aa[j++];while (j<=n) /*將剩余的aa[j…n]復制到bb中*/bb[k++]=aa[j++];while (i<=m) /*將剩余的aa[i…m]復制到bb中*/bb[k++]=aa[i++]; } void MergeOne(RecType aa[],RecType bb[],int len,int n) {/*從aa[1…n]]歸并到bb[1…n],其中len是本趟歸并中有序表的長度*/int i;for(i=0;i+2*len-1<=n;i=i+2*len)Merge(aa,bb,i,i+len-1,i+2*len-1);/*對兩個長度為len的有序表合并*/if(i+len-1<n) /*當剩下的元素個數大于一個子序列長度len時*/Merge(aa,bb,i,i+len-1,n);else /*當剩下的元素個數小于或等于一個子序列長度len時*/Merge(aa,bb,i,n,n); /*復制剩下的元素到bb中*/ } void MergeSort(RecType aa[],RecType bb[],int n)//歸并排序 {int len=1; /*len是排序序列表的長度 */while (len<n){MergeOne(aa,bb,len,n-1);MergeOne(bb,aa,2*len,n-1);len=4*len;} }int main() {int n,data[3]={4,2,1},cord;RecType *r,*bb;printf("Please input number:");scanf("%d",&n);r=(RecType *)malloc(sizeof(RecType)*n);bb=(RecType *)malloc(sizeof(RecType)*n);memset(bb,0,sizeof(RecType)*n);do{printf("\n 主菜單 \n");printf("1----插入排序\n");printf("2----希爾排序\n");printf("3----冒泡排序\n");printf("4----快速排序\n");printf("5----選擇排序\n");printf("6----堆排序\n");printf("7----歸并排序\n");printf("8----退出\n");scanf("%d",&cord);switch(cord){case 1:memset(r,0,sizeof(RecType)*n);CreatArray(r,n);PrintArray(r,n);InsertSort(r,n);PrintArray(r,n);break;case 2:memset(r,0,sizeof(RecType)*n);CreatArray(r,n);PrintArray(r,n);ShellSort(r,data,3,n);PrintArray(r,n);break;case 3:memset(r,0,sizeof(RecType)*n);CreatArray(r,n);PrintArray(r,n);BubbleSort(r,n);PrintArray(r,n);break;case 4:memset(r,0,sizeof(RecType)*n);CreatArray(r,n);PrintArray(r,n);QuickSort1(r,n);PrintArray(r,n);break;case 5:memset(r,0,sizeof(RecType)*n);CreatArray(r,n);PrintArray(r,n);SelectSort(r,n);PrintArray(r,n);break;case 6:memset(r,0,sizeof(RecType)*n);CreatArray(r,n);PrintArray(r,n);HeapSort(r,n);PrintArray(r,n);break;case 7:memset(r,0,sizeof(RecType)*n);CreatArray(r,n);PrintArray(r,n);MergeSort(r,bb,n);PrintArray(r,n);break;case 8:exit(0);default:cord=0;}}while(cord<=8);return 0; }運行結果
常用排序算法時間復雜度和空間復雜度一覽表
| 冒泡排序 | O(n^2) | O(n^2) | 穩定 | O(1) |
| 快速排序 | O(n^2) | O(n*log2n) | 不穩定 | O(log2n)~O(n) |
| 選擇排序 | O(n^2) | O(n^2) | 穩定 | O(1) |
| 2-路歸并排序 | O(n*log2n) | O(n*log2n) | 是 | O(n) |
| 插入排序 | O(n^2) | O(n^2) | 穩定 | O(1) |
| 堆排序 | O(n*log2n) | O(n*log2n) | 不穩定 | O(1) |
| 希爾排序 | O | O | 不穩定 | O(1) |
總結
- 上一篇: 斯坦福大学CS520知识图谱系列课程学习
- 下一篇: 头插法和尾插法创建链表(有无头结点)