第八章學習心得
排序
內部排序
插入排序
一、直接插入排序
(一)方法
1.設待排序的記錄存放在陣列r[1...n]中,r[1]是一個有序序列
2.迴圈n-1次,每次使用順序查詢法,查詢r[i](i = 2,...,n)在已排好序的序列r[1...i-1]中的插入位置,然後將r[i]插入表長為i-1的有序序列r[1...i-1],直到將r[n]插入表長為n-1的有序序列r[1...n-1],最後得到一個表長為n的有序序列
(二)程式碼
void InsertSort(SqList &L) { for(i = 2;i <= L.length;++i) if(L.r[i].key < L.r[i-1View Code].key)//”<"需將r[i]插入有序子表 { L.r[0] = L.r[i]; //將插入的記錄暫存到監視哨中 L.r[0] = L.r[i-1]; //r[i-1] 後移 for(j = i - 2;L.r[0].key < L.r[0].key;--j) //從後往前尋找插入位置 L.r[j+1] = L.r[j]; //記錄逐個後移,直到找到插入位置 L.r[j+1] = L.r[0]; //將r[0]即原r[i],插入到正確位置} }
(三)時間複雜度為O(n2)
(四)空間複雜度為O(1)
(五)演算法特點
1.穩定排序
2.演算法簡便,且容易實現
3.也適用於鏈式結構,只是在單鏈表上無需移動記錄,只需修改相應的指標
4.更適合於初始記錄基本有序(正序)的情況,當初始記錄無序,n較大時,次演算法時間複雜度較高,不宜採用
二、折半插入排序
(一)方法
1.設待排序的記錄存放在陣列r[1...n]中,r[1]是一個有序序列
2.迴圈n-1次,每次使用折半查詢法,查詢r[i](i = 2,...,n)在已排好序的序列r[1...i-1]中的插入位置,然後將r[i]插入表長為i-1的有序序列r[1...i-1],直到將r[n]插入表長為n-1的有序序列r[1...n-1],最後得到一個表長為n的有序序列
(二)程式碼
void BInsertSort(SqList &L) { for(i = 2;i <= L.length;++i) { L.r[0] = L.r[i]; //將待插入的記錄暫存到監視哨中 low = 1;high = i- 1; //置查詢區間初值 while(low <= high) { m = (low + high)/2; //折半 if(L.r[0].key < L.r[m].key) high = m-1; //插入點在前一子表 else low = m + 1; //插入點在後一子表 } for(j = i-1;j >= high+1;--j) L.r[j+1] = L.r[j]; //記錄後移 L.r[high+1] = L.r[0]; //將r[0]即原r[i],插入到正確位置 } }View Code
(三)時間複雜度為O(n2)
(四)空間複雜度為O(1)
(五)演算法特點
1.穩定排序
2.只能用於順序結構,不能用於鏈式結構
3.適合初始記錄無序,n較大時的情況
三、希爾排序
(一)方法
1.第一趟取增量d1(d1<n)把全部記錄分成d1個組,所有間隔為d1的記錄分在同一組,在各個組中進行直接插入排序
2.第二趟取增量d2(d2<d1),重複上述的分組和排序
3.依次類推,直到所取的增量dt=1(dt<dt-1<...<d2<d1),所有記錄在同一組中進行直接插入排序為止
(二)程式碼
void ShellInsert(SqList &L,int dk) { for(i = dk+1;i <= L.length;++i) { if(L.r[i].key < L.r[i-dk].key) //需將L.r[i]插入有序增量子表 { L.r[0] = L.r[i]; //暫存在L.r[0] for(j = i-dk;j>0&&L.r[0].key < L.r[j].key;j-=dk) L.r[j+dk] = L.r[j]; //記錄後移,直到找到插入位置 L.r[j+dk] = L.r[0]; //將r[0]即原r[i],插入到正確位置 } } void ShellSort(SqList &L,int dt[],int t) { for(k = 0;k < t;++k) ShellInsert(L,dt[k]); //一趟增量為dt[t]的希爾插入排序 }View Code
(三)時間複雜度為n(log2n)2
(四)空間複雜度為O(1)
(五)演算法特點
1.不穩定的
2.只能用於順序結構
3.增量序列可以有各種取法,但應該使增量序列中的值沒有除1之外的公因子,並且最後一個增量值必須等於1
4.記錄總的比較次數和移動次數都比直接插入排序要少,n越大時,效果越明顯。所以適合初始記錄無序、n較大時的情況
交換排序
一、氣泡排序
(一)方法
1.設待排序的記錄存放在陣列r[1...n]中。首先將第一個記錄的關鍵字和第二個記錄的關鍵字進行比較,若為逆序(即L.r[1].key>L.r[2].key),則交換兩個記錄。然後比較第二個記錄和第三個記錄的關鍵字。以此類推,直至n-1個記錄和第n個記錄進行過比較為止。上述過程稱作第一趟起泡排序,其結果使得關鍵字最大的記錄被安置到最後一個記錄的位置上
2.然後進行第二趟起泡排序,對前n-1個記錄進行同樣操作,其結果是使關鍵字次大的記錄被安置到第n-1個記錄的位置上
3.重複上述比較和交換過程,第i趟是從L.r[1]到L.r[n-i+1]依次比較相鄰兩個記錄的關鍵字,並在“逆序”時交換相鄰記錄,其結果是這n-i+1個記錄中關鍵字最大的記錄被交換到第n-i+1的位置上。直到在某一趟排序過程中沒有進行過交換記錄的操作,說明序列已全部達到排序要求,則完成排序。
(二)程式碼
void BubbleSort(SqList &L) { m = L.length - 1; flag = 1; //flag用來標記某一趟排序是否發生交換 while((m>0)&&(flag == 1)) { flag = 0; //flag置為0,如果本趟排序沒有發生交換,則不會執行下一趟排序 for(j = 1;j <= m;j++) if(L.r[j].key > L.r[j+1].key) { flag = 1; //flag置為1,表示本趟排序發生了交換 t = L.r[j];L.r[j] = L.r[j+1];L.r[j+1] = t; //交換前後兩個記錄 } --m; } }View Code
(三)時間複雜度為O(n2)
(四)空間複雜度為O(1)
(五)演算法特點
1.穩定排序
2.可用於鏈式儲存結構
3.移動記錄次數較多,演算法平均時間效能比直接插入排序差。當初始記錄無序,n較大時,此演算法不宜採用
二、快速排序
(一)方法
1.選擇待排序表中的第一個記錄作為樞軸,將數週記錄暫存r[0]的位置上。附設兩個指標low和high,初始時分別指向表的上街和下界(第一趟時,low = 1,high = L。length)
2.從表的最右側位置依次向左搜尋,找到第一個關鍵小於數週關鍵字pivotkey的記錄,將其移到low處。具體操作:當low<high時,若high所指向的關鍵字大於等於pivotkey,則向左移動指標high(執行操作--high);否則將high所指記錄移到low所指記錄
3.然後再從表的最左側位置,依次向右搜尋找到第一個關鍵字大於pivotkey的記錄和樞軸記錄交換。具體操作時:當low<high時,若low所指記錄的關鍵字小於等於pivotkey,則向右移動指標low(執行操作++low);否則將low所指記錄與數週記錄交換
4.重複步驟2和3,直至low和high相等為止。此時low和high的位置即為樞軸在此趟排序中的最終位置,原表被分成兩個子表
(二)程式碼
int Partition(SqList &L,int low,int high) { L.r[0] = L.r[low]; //用子表的第一個記錄做樞軸記錄 pivotkey = L.r[low].key; //樞軸記錄關鍵字保持在pivotkey中 while(low<high) { while(low<high && L.r[high].key >= pivotkey) --high; L.r[low] = L.r[high]; //將比樞軸記錄小的記錄移到低端 while(low<high && L.r[low].key<=pivotkey) ++low; L.r[high] = L.r[low]; //將比樞軸記錄大的記錄移到高階 } L.r[low] = L.r[0]; //樞軸記錄到位 return low; //返回樞軸位置 } void QSort(SqList &L,int low,int high) { if(low < high) { pivotloc = Partition(L,low,high); //將L.r[low...high]一分為二,pivotloc是樞軸位置 QSort(L,low,pivotloc-1); //對左子表遞迴排序 QSort(L,pivotloc+1,high); //對右子表遞迴排序 } } void QuickSort(SqList &L) { QSort(L,1,L.length); }View Code
(三)時間複雜度為O(nlog2n)
(四)空間複雜度為O(n)
(五)演算法特點
1.不穩定排序
2.排序過程中需要定位表的上界和下界,所以適合用於順序結構,很難用於鏈式結構
3.當n較大時,在平均情況下快速排序時所有內部排序方法中速度最塊的一種,所以其適合初始記錄無序、n較大時的情況
選擇排序
一、簡單選擇排序
(一)方法
1.設待排序的記錄存放在陣列r[1...n]中。第一趟從r[1]開始,通過n-1次比較,從n個記錄中選出關鍵字最小的記錄,記為r[k],交換r[1]和r[k]
2.第二趟從r[2]開始,通過n-2次比較,從n-1個記錄中選出關鍵字最小的記錄,記為r[k],交換r[2]和r[k]
3.以此類推,第i趟從r[i]開始,通過n-i次比較,從n-i+1個記錄中選出關鍵字最小的記錄,記為r[k],交換r[i]和r[k]
4.經過n-1趟,排序完成
(二)程式碼
void SelectSort(SqList &L) { for(i = 1;i < L.length;i++) {//在L.r[i...L.length]中選擇關鍵字最小的記錄 k = i; for(j = i+1;j <= L.length;++j) if(L.r[j].key < L.r[k].key) k = j;//k指向此趟排序中關鍵字最小的記錄 if(k != i) {//交換r[i]和r[k] t = L.r[i]; L.r[i] = L.r[k]; L.r[k] = t; } } }View Code
(三)時間複雜度為O(n2)
(四)空間複雜度為O(1)
(五)演算法特點
1.穩定排序
2.可用於鏈式儲存結構
3.激動記錄次數較少,當每一記錄佔用的空間較多時,次方法比直接插入排序快
二、堆排序
(一)方法
1.按對的定義將待排序序列r[1...n]調整為大根堆(這個過程稱為建初堆),交換r[1]和r[n],則r[n]為關鍵字最大的記錄
2.將r[1...n-1]重新調整為堆,交換r[1]和r[n-1],則r[n-1]為關鍵字次大的記錄
3.迴圈n-1次,直到交換了r[1]和r[2]為止,得到了一個非遞減的有序序列r[1...n]
(二)程式碼
void HeapAdjust(SqList &L,int s,int m) { rc = L.r[s]; for(j = 2*s;j <= m;j *= 2) //沿key較大的孩子結點向下篩選 { if(j<m && L.r[j].key<L.r[j+1].key) ++j; //j為key較大的記錄的下標 if(rc.key >= L.r[j].key) break; //rc應插入在位置s上 L.r[s] = L.r[j]; s = j; } L.r[s] = rc; //插入 } void CreatHeap(SqList &L) {//把無序序列L.r[1...n]建成大根堆 n = L.length; for(i = n/2;i > 0;--i) HeapAdjust(L,i,n); //反覆呼叫HeapAdjust } void HeapSort(SqList &L) {//堆順序表L進行堆排序 CreatHeap(L); //把無序序列L.r[1...n]建成大根堆 for(i = L.length;i > 1;--i) { x = L.r[1]; //將堆頂記錄和當前未經排序子序列L.r[1...i]中最後一個記錄互換 L.r[1] = L.r[i]; L.r[i] = x; HeapAdjust(L,1,i-1); //將L.r[1...i-1]重新調整為大根堆 } }View Code
(三)時間複雜度為O(nlog2n)
(四)空間複雜度為O(1)
(五)演算法特點
1.不穩定排序
2.只能用於順序結構,不能用於鏈式結構
3.初始建堆所需的比較次數較多,因此記錄較少時不宜採用,因為當記錄較多時較快速排序更高效
歸併排序
(一)方法:
假設初始序列含有n個記錄,則可看成時n個有序的子序列,每個子序列的長度為1,然後涼涼歸併,得到[n/2]個長度為2或1的有序子序列;再兩兩歸併,……,如此重複,直至得到一個長度為n的有序序列為止
(二)程式碼
void Merge(RedType R[],RedType &T[],int low,int high) {//將有序表R[low...high]和R[mid+1...high]歸併為有序表T[low...high] i = low; j = mid + 1; k = low; while(i<=mid && j<=high) { if(R[i].key <= R[i].key) T[k] = R[i++]; else T[k] = R[j++]; } while(i <= mid) T[k++] = R[i++]; //將剩餘的R[low...mid]複製到T中 while(j <= high) T[k++] = R[j++]; //將剩餘的R[j...high]複製到T中 } void MSort(RedType R[],RedType &T[],int low,int high) {//R[low...high]歸併排序後放入T[low...high]中 if(low == high) T[low] = R[low]; else { mid = (low + high)/2; //將當前序列一分為二,求分裂點mid MSort(R,S,low,mid); //對子序列R[low...mid]遞迴歸併排序,結果放入S[low...mid] MSort(R,S,mid+1,high); //對子序列R[mid+1...high]遞迴歸併排序,結果放入S[mid+1...high] Merge(S,T,low,mid,high); //將S[low...mid]和S[mid+1...high]歸併到T[low...high] } } void MergeSort(SqList &L) { MSort(L.r,L.r,1,L.length); }View Code
(三)時間複雜度為O(nlog2n)
(四)空間複雜度為O(n)
(五)演算法特點
1.穩定排序
2.可用於鏈式結構,且不需要附加儲存空間,但遞迴是現實仍需開闢相應的遞迴工作棧
外部排序
(一)方法
1.按可用記憶體大小,將外存上含n個記錄的檔案分成若干長度為l的子檔案或段,依次讀入記憶體並利用有效的內部排序方法對它們進行排序,並將排序後得到的有序子檔案重新寫入外存,通常稱這些有序子檔案為歸併段或順串
2.對這些歸併段進行逐趟歸併,使歸併段(有序的子檔案)逐漸由小至大,直至得到整個有序檔案為止