數據結構與算法系列研究九——排序算法的一些探討
四種排序
一.實驗內容
輸入20個整數,分別用希爾排序、快速排序、堆排序和歸並排序實現由小到大排序並輸出排序結果。
二.關鍵數據結構與核心算法
關鍵數據結構:由於是排序為了簡單起見,選用線性表中的數組作為存儲結構。
核心算法:
1.希爾排序
希爾排序的核心還是直接插入法,但是插入的位置有所講究。要把數組分為許多段,每一段的長度除了最後的有可能不同之外,其他的都相同。該段的長度即為增量,在最後一次必須為一,此時程序變成了直接插入。每次進行隔段插入,不斷地調整是的數組變得隔段有序。這樣做的目的是為了對直接插入的改進,減小時間復雜度。
具體代碼如下:
/*shell sort 主要利用插入排序的思想View Code*/ void ShellSort(int a[],int len) { int i,j,temp,increment; //增量 increment=len; //初始化為總長度 do { increment=increment/3+1; //設置每次的增量值,最後一次必為1 for (i=increment;i<len;i++) { //從增量開始遞增,若滿足後面的比前面的小,就要插入 if(a[i]<a[i-increment]) { temp=a[i]; //臨時存儲處 for (j=i-increment;j>=0&&a[j]>temp;j-=increment) { //註意插入條件j>=0,a[j]>temp a[j+increment]=a[j]; //找到插入位置進行插入 } a[j+increment]=temp; //插入到要插的地方 } } }while(increment>1); //註意當increment為1時仍在循環 }
2.快速排序
快速排序在最好的情況下時間復雜度最低,但是若本來數組就是序排列的,就會退化為冒泡排序。快速排序關鍵的就是一次排序的操作。
首先,選取數組的第一個元素作為支點,將此支點保存,此後該支點位置可以被覆蓋。 每次排序時,選用兩個指針,一個指向尾部,一個指向首部,如果首部小於尾部則進行循環,尾部指針一直向前移動直至首部等於尾部或者尾部所指節點小於支點,此時,若首部尾部相等循環結束,否則,將該尾部節點覆蓋到首部節點位置(註意:開始時首節點即為支點,可以覆蓋)。完成該操作後,首節點指針開始向後移動直至遇到1.首位節點相等或2.首節點所指節點大於支點,若為1,則循環結束,否則將該首指針所指節點覆蓋到上次的為節點空出的位置上,繼續進行尾部指針移動。重復上述過程有限此後,必將得到首指針等於尾指針的情況,此時將支點放到首尾指針所指節點位置。必然得到在支點左邊的元素都小於等於支點,在支點右邊的都大於等於該支點。然後返回該支點的位置,一次循環結束。
然後主函數中采用遞歸的方法即可。
具體代碼如下:
/**************快速排序*****************/ int Partiton(int a[],int low,int high) { int temp,pivotkey; pivotkey=a[low]; //選取支點 temp=pivotkey; //暫存支點 while (low<high) //條件,相等則結束 { while (low<high &&a[high]>=pivotkey) { high--;//左移 } a[low]=a[high];//覆蓋 while (low<high && a[low]<=pivotkey) { low++; //右移 } a[high]=a[low];//覆蓋 } a[low]=temp; //恢復 return low; //返回分界點位置 } void Qsort(int a[],int low,int high) { int pivot; if (low<high) { pivot=Partiton(a,low,high); //獲得分界點位置 Qsort(a,low,pivot-1); //排左邊 Qsort(a,pivot+1,high);//右邊 } } /******************** end qsort *******************/View Code
3.堆排序
堆排序是一個比較有意思的排序,有虛擬二叉樹的結構來進行排序,簡稱大根堆。要完成堆排序,首先要建立大根堆。首先要按照層次遍歷的方法產生完全二叉樹。然後,對該二叉樹進行調整使得遞歸的來說,每個根節點都大於等於左右兒子節點。然後,首根節點即為最大元素,將該節點和最後的節點交換。交換之後,最後的節點成為了最大節點,該節點在以後的操作中認為不在大根堆中,然後調整剩余節點使之成為新的大根堆,再交換新的大根堆的最後元素和首根結點。剔除該大根堆最後節點。如此循環下去,直至大根堆只剩一個元素,此元素即為最小元素和自身交換仍為最小元素。至此數組就變成了升序排列的了。
具體代碼:
/**********將從i開始的為頭節點的子樹調整為大根堆************/ void HAdjust(int R[], int i, int n) { //i為出發結點下標,n為堆的結點總數 int iL,iR,j;//左右子樹 while(i<=n/2-1) //葉子停止,非葉子繼續 { iL=2*i+1; //左兒子,下標從0開始 iR=iL+1; //右兒子 j=iL; //j為R[i]要交換的結點下標 if(iR<n&&R[iR]>R[iL]) { j=iR;//若右子樹存在,且該節點大於左兒子,選擇右兒子 } if(R[i]>R[j]) //頭節點最大 { break; //無需交換,則結束 } swap(R[i], R[j]);//否則交換 i=j;//標記新的待檢核節點,看是否要交換 } } void HeapSort(int R[], int n) { //初始建大根堆 int i; for(i=n/2-1; i>=0; i--) { HAdjust(R, i, n);//建立大根堆,每次都調整使每個子樹為大根堆 } for(i=1; i<n; i++) { swap(R[0], R[n-i]);//將n-i的元素和頭節點最大值交換 HAdjust(R, 0, n-i);//去除交換後的n-i元素,將其他的節點調整為大根堆 }//從而得到從小到大的序列 } /***************************heap end**************************************/View Code
4.歸並排序
歸並排序的思想是借用一個輔助數組存儲每次歸並後的節點,每次循環中都從開始進行二路歸並,將兩個相鄰的元素歸為有序集合,每次都在縮小歸並後的集合數直至最後只剩一個集合即為升序排列的數組。
具體代碼:
/***************歸並排序:將有序的SR歸並到TR中********************/ void Merge(int SR[],int TR[],int i,int m,int n) { int j,k,l; for (j=m+1,k=i;i<=m && j<=n;k++) { if (SR[i]<SR[j]) { TR[k]=SR[i++];//若前半部分小則直接加入 } else { TR[k]=SR[j++]; //否則加入後半部分 } } //結束時可能有兩種情況 if (i<=m) { /*將SR[1...m]中多余中的部分添加到TR中*/ for (l=0;l<=m-i;l++) { TR[k+l]=SR[i+l]; //添加多余部分 } } if (j<=n) { /*將SR[m+1...n]中多余中的部分添加到TR中*/ for (l=0;l<=n-j;l++) { TR[k+l]=SR[j+l]; //添加多余部分 } } } /*將SR歸並排序為TR1*/ void MSort(int SR[],int TR1[],int s,int t) { //歸並完成後t數組即為升序數組 int m=0; int TR2[100]={0}; if (s==t) { TR1[s]=SR[s];//相等則復制 } else { m=(s+t)/2; //找到中心位置 MSort(SR,TR2,s,m); //分別進行歸並復制,左邊 MSort(SR,TR2,m+1,t); //右邊 Merge(TR2,TR1,s,m,t); //歸並 } } /******************end merge********************/View Code
四.理論與測試
理論:選用相同的無序數組分別進行四種排序,將得到相同的結果。
測試:無序數組為:
a[20]={12,23,43,35,37,26,19,20,1,10,
32,27,29,89,94,93,108,246,245,17};
運行程序後結果為:
五.附錄(源代碼)
1 #include "stdio.h" 2 #include "stdlib.h" 3 #include "iostream" 4 using namespace std; 5 /*shell sort 主要利用插入排序的思想*/ 6 void ShellSort(int a[],int len) 7 { 8 int i,j,temp,increment; //增量 9 increment=len; //初始化為總長度 10 do 11 { 12 increment=increment/3+1; //設置每次的增量值,最後一次必為1 13 for (i=increment;i<len;i++) 14 { //從增量開始遞增,若滿足後面的比前面的小,就要插入 15 if(a[i]<a[i-increment]) 16 { 17 temp=a[i]; //臨時存儲處 18 for (j=i-increment;j>=0&&a[j]>temp;j-=increment) 19 { //註意插入條件j>=0,a[j]>temp 20 a[j+increment]=a[j]; //找到插入位置進行插入 21 } 22 a[j+increment]=temp; //插入到要插的地方 23 } 24 } 25 }while(increment>1); //註意當increment為1時仍在循環 26 } 27 /****************heap sort******************/ 28 void swap(int &a,int &b) 29 { //交換a與b的值 30 int temp; 31 temp = a; 32 a = b; 33 b = temp; 34 } 35 /**********將從i開始的為頭節點的子樹調整為大根堆************/ 36 void HAdjust(int R[], int i, int n) 37 { //i為出發結點下標,n為堆的結點總數 38 int iL,iR,j;//左右子樹 39 while(i<=n/2-1) //葉子停止,非葉子繼續 40 { 41 iL=2*i+1; //左兒子,下標從0開始 42 iR=iL+1; //右兒子 43 j=iL; //j為R[i]要交換的結點下標 44 if(iR<n&&R[iR]>R[iL]) 45 { 46 j=iR;//若右子樹存在,且該節點大於左兒子,選擇右兒子 47 48 } 49 if(R[i]>R[j]) //頭節點最大 50 { 51 break; //無需交換,則結束 52 } 53 swap(R[i], R[j]);//否則交換 54 i=j;//標記新的待檢核節點,看是否要交換 55 } 56 } 57 void HeapSort(int R[], int n) 58 { //初始建大根堆 59 int i; 60 for(i=n/2-1; i>=0; i--) 61 { 62 HAdjust(R, i, n);//建立大根堆,每次都調整使每個子樹為大根堆 63 } 64 for(i=1; i<n; i++) 65 { 66 swap(R[0], R[n-i]);//將n-i的元素和頭節點最大值交換 67 HAdjust(R, 0, n-i);//去除交換後的n-i元素,將其他的節點調整為大根堆 68 }//從而得到從小到大的序列 69 } 70 /***************************heap end**************************************/ 71 72 73 /***************歸並排序:將有序的SR歸並到TR中********************/ 74 void Merge(int SR[],int TR[],int i,int m,int n) 75 { 76 int j,k,l; 77 for (j=m+1,k=i;i<=m && j<=n;k++) 78 { 79 if (SR[i]<SR[j]) 80 { 81 TR[k]=SR[i++];//若前半部分小則直接加入 82 } 83 else 84 { 85 TR[k]=SR[j++]; //否則加入後半部分 86 } 87 } 88 //結束時可能有兩種情況 89 if (i<=m) 90 { /*將SR[1...m]中多余中的部分添加到TR中*/ 91 for (l=0;l<=m-i;l++) 92 { 93 TR[k+l]=SR[i+l]; //添加多余部分 94 } 95 } 96 if (j<=n) 97 { /*將SR[m+1...n]中多余中的部分添加到TR中*/ 98 for (l=0;l<=n-j;l++) 99 { 100 TR[k+l]=SR[j+l]; //添加多余部分 101 } 102 } 103 } 104 /*將SR歸並排序為TR1*/ 105 void MSort(int SR[],int TR1[],int s,int t) 106 { //歸並完成後t數組即為升序數組 107 int m=0; 108 int TR2[100]={0}; 109 if (s==t) 110 { 111 TR1[s]=SR[s];//相等則復制 112 } 113 else 114 { 115 m=(s+t)/2; //找到中心位置 116 MSort(SR,TR2,s,m); //分別進行歸並復制,左邊 117 MSort(SR,TR2,m+1,t); //右邊 118 Merge(TR2,TR1,s,m,t); //歸並 119 } 120 } 121 /******************end merge********************/ 122 123 /**************快速排序*****************/ 124 int Partiton(int a[],int low,int high) 125 { 126 int temp,pivotkey; 127 pivotkey=a[low]; //選取支點 128 temp=pivotkey; //暫存支點 129 while (low<high) //條件,相等則結束 130 { 131 while (low<high &&a[high]>=pivotkey) 132 { 133 high--;//左移 134 } 135 a[low]=a[high];//覆蓋 136 while (low<high && a[low]<=pivotkey) 137 { 138 low++; //右移 139 } 140 a[high]=a[low];//覆蓋 141 } 142 a[low]=temp; //恢復 143 return low; //返回分界點位置 144 } 145 void Qsort(int a[],int low,int high) 146 { 147 int pivot; 148 if (low<high) 149 { 150 pivot=Partiton(a,low,high); //獲得分界點位置 151 Qsort(a,low,pivot-1); //排左邊 152 Qsort(a,pivot+1,high);//右邊 153 } 154 } 155 /******************** end qsort *******************/ 156 void MainMenu() 157 {//四個相同的數組,四種不同的排序 158 int a[20]={12,23,43,35,37,26,19,20,1,10, 159 32,27,29,89,94,93,108,246,245,17}; 160 int b[20]={12,23,43,35,37,26,19,20,1,10, 161 32,27,29,89,94,93,108,246,245,17}; 162 int c[20]={12,23,43,35,37,26,19,20,1,10, 163 32,27,29,89,94,93,108,246,245,17}; 164 int d[20]={12,23,43,35,37,26,19,20,1,10, 165 32,27,29,89,94,93,108,246,245,17}; 166 int t[100],n=20,i;//t數組為歸並排序服務 167 printf("shell sort:\n"); 168 ShellSort(a,n); 169 for(i=0;i<n;i++) 170 { 171 printf("%d ", a[i]); 172 if((i+1)%10==0) 173 { 174 printf("\n"); 175 } 176 } 177 printf("\nheap sort:\n"); 178 HeapSort(b,n); 179 for(i=0;i<n;i++) 180 { 181 printf("%d ", b[i]); 182 if((i+1)%10==0) 183 { 184 printf("\n"); 185 } 186 } 187 printf("\nmerge sort:\n"); 188 MSort(c,t,0,n-1); 189 for(i=0;i<n;i++) 190 { 191 printf("%d ", t[i]); 192 if((i+1)%10==0) 193 { 194 printf("\n"); 195 } 196 } 197 printf("\nquick sort:\n"); 198 Qsort(d,0,n-1); 199 for(i=0;i<n;i++) 200 { 201 printf("%d ", d[i]); 202 if((i+1)%10==0) 203 { 204 printf("\n"); 205 } 206 } 207 printf("\n"); 208 } 209 210 int main() 211 { 212 MainMenu(); 213 return 0; 214 }
數據結構與算法系列研究九——排序算法的一些探討