| 一、排序的基本概念 排序:就是將記錄按關鍵字遞增(遞減)的次序排列起來,形成新的有序序列,稱為排序。設n個記錄的序列為{R1,R2,…,Rn},其相應關鍵字序列為{K1,K2,…,Kn},需確定一種排序P1,P2,…,Pn,使其相應的關鍵字滿足遞增(升序),或遞減(降序)的關系: Kp1£ Kp2£ ...£ Kpn 或 Kp13 Kp23 … 3 Kpn 根據排序元素所在位置的不同,排序分: 內排序和外排序。 內排序:在排序過程中,所有元素調到內存中進行的排序,稱為內排序。內排序是排序的基礎。內排序效率用比較次數來衡量。按所用策略不同,內排序又可分為插入排序、選擇排序、交換排序、歸并排序及基數排序等幾大類。 外排序:在數據量大的情況下,只能分塊排序,但塊與塊間不能保證有序。外排序用讀/寫外存的次數來衡量其效率。 排序算法的穩定性:若待排序的序列中,存在多個具有相同關鍵字的記錄,經過排序,這些記錄的相對次序保持不變,則稱該算法是穩定的;若經排序后,記錄的相對次序發生了改變,則稱該算法是不穩定的。 排序算法的存儲結構通常有三種:一維數組;鏈表;輔助表(如索引表)。 二、插入排序(Insertion Sort 基本思想: 將n個元素的數列分為已有序和無序兩個部分。每次處理就是將無序數列的第一個元素與有序數列的元素從后往前逐個進行比較,找出插入位置,將該元素插入到有序數列的合適位置中。 2.1 直接插入排序(線性插入排序,穩定) ???? 基本思想:每次將一個待排序的數據元素,插入到前面已經排好序的數列中的適當位置,使數列依然有序;直到待排序數據元素全部插入完為止。 ??? 直接插入排序是一種最簡單的排序方法。具體算法步驟: ??? Step1 從有序數列{a1}和無序數列{a2,a3,…,an}開始進行排序; ??? Step2 處理第i個元素時(i=2,3,…,n),數列{a1,a2,…,ai-1}是已有序的,而數列{ai,ai+1,…,an}是無序的。用ai與ai-1、ai-2,…,a1進行比較,找出合適的位置將ai插入。 ??? Step3 重復Step2,共進行n-1的插入處理,數列全部有序。 ??? 直接插入排序的時間復雜度為O(n2),空間復雜度為O(1)。 【示例】: [初始關鍵字] [49] 38 65 97 76 13 27 49 J=2(38) [38 49] 65 97 76 13 27 49 J=3(65) [38 49 65] 97 76 13 27 49 J=4(97) [38 49 65 97] 76 13 27 49 J=5(76) [38 49 65 76 97] 13 27 49 J=6(13) [13 38 49 65 76 97] 27 49 J=7(27) [13 27 38 49 65 76 97] 49 J=8(49) [13 27 38 49 49 65 76 97] ? Procedure InsertSort(Var R : FileType); //對R[1..N]按遞增序進行插入排序, R[0]是監視哨// Begin for I := 2 To N Do //依次插入R[2],...,R[n]// begin R[0] := R[I]; J := I - 1; While R[0] < R[J] Do //查找R[I]的插入位置// begin R[J+1] := R[J]; //將大于R[I]的元素后移// J := J - 1 end R[J + 1] := R[0] ; //插入R[I] // end End; //InsertSort // 三、選擇排序(不穩定) 1. 基本思想: 每一趟從待排序的數據元素中選出最小(或最大)的一個元素,順序放在已排好序的數列的最后,直到全部待排序的數據元素排完。 2. 排序過程: 3、時間復雜度:時間復雜度為O(n2)。 【示例】: 初始關鍵字 [49 38 65 97 76 13 27 49] 第一趟排序后 13 [38 65 97 76 49 27 49] 第二趟排序后 13 27 [65 97 76 49 38 49] 第三趟排序后 13 27 38 [97 76 49 65 49] 第四趟排序后 13 27 38 49 [49 97 65 76] 第五趟排序后 13 27 38 49 49 [97 97 76] 第六趟排序后 13 27 38 49 49 76 [76 97] 第七趟排序后 13 27 38 49 49 76 76 [ 97] 最后排序結果 13 27 38 49 49 76 76 97 Procedure SelectSort(Var R : FileType); //對R[1..N]進行直接選擇排序 // Begin for I := 1 To N - 1 Do //做N - 1趟選擇排序// begin K := I; For J := I + 1 To N Do //在當前無序區R[I..N]中選最小的元素R[K]// begin If R[J] < R[K] Then K := J end; If K <> I Then //交換R[I]和R[K] // begin Temp := R[I]; R[I] := R[K]; R[K] := Temp; end; end End; //SelectSort // 四、冒泡排序(BubbleSort)(穩定) 1. 基本思想: 兩兩比較待排序數據元素的大小,發現兩個數據元素的次序相反時即進行交換,直到沒有反序的數據元素為止。 2. 排序過程: ?? step1 將關鍵字按縱向排列,然后自下而上地對每兩個相鄰的關鍵字進行比較,若餓日為逆序(即kj-1>kj),則將兩個記錄交換位置,這樣的操作反復進行,直至全部記錄都比較、交換完為止。一趟冒泡處理,就將關鍵字最小的記錄安排在第一記錄的位置上。 ?? step2 對后n-1個記錄重復同樣操作,再將次小關鍵字記錄安排在第二個記錄的位置上。 ?? Step3 重復上述過程直至沒有記錄需要交換為止。 3、算法分析:最壞情況,比較(n2-n)/2次,移動(n2-n)*3/2。算法時間復雜度O(n2)。 【示例】: 49 13 13 13 13 13 13 13 38 49 27 27 27 27 27 27 65 38 49 38 38 38 38 38 97 65 38 49 49 49 49 49 76 97 65 49 49 49 49 49 13 76 97 65 65 65 65 65 27 27 76 97 76 76 76 76 49 49 49 76 97 97 97 97 Procedure BubbleSort(Var R : FileType) //從下往上掃描的起泡排序// Begin For I := 1 To N-1 Do //做N-1趟排序// begin NoSwap := True; //置未排序的標志// For J := N - 1 DownTo 1 Do //從底部往上掃描// begin If R[J+1]< R[J] Then //交換元素// begin Temp := R[J+1]; R[J+1 := R[J]; R[J] := Temp; NoSwap := False end; end; If NoSwap Then Return//本趟排序中未發生交換,則終止算法// end End; //BubbleSort// 五、快速排序(Quick Sort)(不穩定) 1. 基本思想: 在當前無序區R[1..H]中任取一個數據元素作為比較的"基準"(不妨記為X),用此基準將當前無序區劃分為左右兩個較小的無序區:R[1..I-1]和R[I+1..H],且左邊的無序子區中數據元素均小于等于基準元素,右邊的無序子區中數據元素均大于等于基準元素,而基準X則位于最終排序的位置上,即R[1..I-1]≤X.Key≤R[I+1..H](1≤I≤H),當R[1..I-1]和R[I+1..H]均非空時,分別對它們進行上述的劃分過程,直至所有無序子區中的數據元素均已排序為止。 2. 排序過程: 3、算法分析:最壞情況的時間復雜度為O(n2),最好情況時間復雜度為O(nlog2n)。 【示例】: 初始關鍵字 [49 38 65 97 76 13 27 49] 第一次交換后 [27 38 65 97 76 13 49 49] 第二次交換后 [27 38 49 97 76 13 65 49] J向左掃描,位置不變,第三次交換后 [27 38 13 97 76 49 65 49] I向右掃描,位置不變,第四次交換后 [27 38 13 49 76 97 65 49] J向左掃描 [27 38 13 49 76 97 65 49] (一次劃分過程) 初始關鍵字 [49 38 65 97 76 13 27 49] 一趟排序之后 [27 38 13] 49 [76 97 65 49] 二趟排序之后 [13] 27 [38] 49 [49 65]76 [97] 三趟排序之后 13 27 38 49 49 [65]76 97 最后的排序結果 13 27 38 49 49 65 76 97 各趟排序之后的狀態 Procedure Parttion(Var R : FileType; L, H : Integer; Var I : Integer); //對無序區R[1,H]做劃分,I給以出本次劃分后已被定位的基準元素的位置 // Begin I := 1; J := H; X := R[I] ;//初始化,X為基準// Repeat While (R[J] >= X) And (I < J) Do begin J := J - 1 //從右向左掃描,查找第1個小于 X的元素// If I < J Then //已找到R[J] 〈X// begin R[I] := R[J]; //相當于交換R[I]和R[J]// I := I + 1 end; While (R[I] <= X) And (I < J) Do I := I + 1 //從左向右掃描,查找第1個大于 X的元素/// end; If I < J Then //已找到R[I] > X // begin R[J] := R[I]; //相當于交換R[I]和R[J]// J := J - 1 end Until I = J; R[I] := X //基準X已被最終定位// End; //Parttion // Procedure QuickSort(Var R :FileType; S,T: Integer); //對R[S..T]快速排序// Begin If S < T Then //當R[S..T]為空或只有一個元素是無需排序// begin Partion(R, S, T, I); //對R[S..T]做劃分// QuickSort(R, S, I-1);//遞歸處理左區間R[S,I-1]// QuickSort(R, I+1,T);//遞歸處理右區間R[I+1..T] // end; End; //QuickSort// 五、堆排序(Heap Sort) 1. 基本思想: 堆排序是一樹形選擇排序,在排序過程中,將R[1..N]看成是一顆完全二叉樹的順序存儲結構,利用完全二叉樹中雙親結點和孩子結點之間的內在關系來選擇最小的元素。 2. 堆的定義: N個元素的序列K1,K2,K3,...,Kn.稱為堆,當且僅當該序列滿足特性: Ki≤K2i Ki ≤K2i+1(1≤ I≤ [N/2]) 堆實質上是滿足如下性質的完全二叉樹:樹中任一非葉子結點的關鍵字均大于等于其孩子結點的關鍵字。例如序列10,15,56,25,30,70就是一個堆,它對應的完全二叉樹如上圖所示。這種堆中根結點(稱為堆頂)的關鍵字最小,我們把它稱為小根堆。反之,若完全二叉樹中任一非葉子結點的關鍵字均大于等于其孩子的關鍵字,則稱之為大根堆。 3. 排序過程: 堆排序正是利用小根堆(或大根堆)來選取當前無序區中關鍵字小(或最大)的記錄實現排序的。我們不妨利用大根堆來排序。每一趟排序的基本操作是:將當前無序區調整為一個大根堆,選取關鍵字最大的堆頂記錄,將它和無序區中的最后一個記錄交換。這樣,正好和直接選擇排序相反,有序區是在原記錄區的尾部形成并逐步向前擴大到整個記錄區。 【示例】:對關鍵字序列42,13,91,23,24,16,05,88建堆
Procedure Sift(Var R :FileType; I, M : Integer); //在數組R[I..M]中調用R[I],使得以它為完全二叉樹構成堆。事先已知其左、右子樹(2I+1 <=M時)均是堆// Begin X := R[I]; J := 2*I; //若J <=M, R[J]是R[I]的左孩子// While J <= M Do //若當前被調整結點R[I]有左孩子R[J]// begin If (J < M) And R[J].Key < R[J+1].Key Then J := J + 1 //令J指向關鍵字較大的右孩子// //J指向R[I]的左、右孩子中關鍵字較大者// If X.Key < R[J].Key Then //孩子結點關鍵字較大// begin R[I] := R[J]; //將R[J]換到雙親位置上// I := J ; J := 2*I //繼續以R[J]為當前被調整結點往下層調整// end; Else Exit//調整完畢,退出循環// end R[I] := X;//將最初被調整的結點放入正確位置// End;//Sift// Procedure HeapSort(Var R : FileType); //對R[1..N]進行堆排序// Begin For I := N Div Downto 1 Do //建立初始堆// Sift(R, I , N) For I := N Downto 2 do //進行N-1趟排序// begin T := R[1]; R[1] := R[I]; R[I] := T;//將當前堆頂記錄和堆中最后一個記錄交換// Sift(R, 1, I-1) //將R[1..I-1]重成堆// end End; //HeapSort// 六、幾種排序算法的比較和選擇 1. 選取排序方法需要考慮的因素: (1) 待排序的元素數目n; (2) 元素本身信息量的大小; (3) 關鍵字的結構及其分布情況; (4) 語言工具的條件,輔助空間的大小等。 2. 小結: (1) 若n較小(n <= 50),則可以采用直接插入排序或直接選擇排序。由于直接插入排序所需的記錄移動操作較直接選擇排序多,因而當記錄本身信息量較大時,用直接選擇排序較好。 (2) 若文件的初始狀態已按關鍵字基本有序,則選用直接插入或冒泡排序為宜。 (3) 若n較大,則應采用時間復雜度為O(nlog2n)的排序方法:快速排序、堆排序或歸并排序。 快速排序是目前基于比較的內部排序法中被認為是最好的方法。 (4) 在基于比較排序方法中,每次比較兩個關鍵字的大小之后,僅僅出現兩種可能的轉移,因此可以用一棵二叉樹來描述比較判定過程,由此可以證明:當文件的n個關鍵字隨機分布時,任何借助于"比較"的排序算法,至少需要O(nlog2n)的時間。 (5) 當記錄本身信息量較大時,為避免耗費大量時間移動記錄,可以用鏈表作為存儲結構。 七、歸并排序(穩定) ??? 歸并(Merge)排序法是將兩個(或兩個以上)有序表合并成一個新的有序表:即把待排序序列分為若干個子序列,每個子序列是有序的,然后再把有序子序列合并為整體有序序列。 ??? 將已有序的子序列合并,得到完全有序的序列:即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合并成一個有序表,稱為2-路歸并。 歸并排序具體步驟: Step1 把待排序的n個記錄看作是長度為1的有序序列。將相鄰子序列兩兩歸并為長度為2的有序序列; Step2 把得到的n/2個長度為2的有序子序列再歸并為長度為 2*2 的有序序列; Step3 按Step2的方式,重復對相鄰有序子序列進行歸并操作,直到成為一個有序序列為止。 八、希爾排序(縮小增量排序,不穩定) ??? 希爾排序是一種快速排序法,出自D.L.Shell。基本思想是: ??? 仍采用插入法,排序過程通過調換并移動數據項來實現。先取一個小于n的整數d1并作為第一個增量,將文件的全部記錄分成d1個組,所有距離為d1倍數的記錄放在同一個組中,在各組內進行直接插入排序;然后取第二個增量d2<d1,重復上述的分組和排序,直至所取的增量dt=1(dt<dt-1<...<d2<d1)為止,此時,所有的記錄放在同一組中進行直接插入排序。增量一般d按給定公式減少: di+1 =(di + 1)/2 ,直到d等于1為止。 例,有關鍵字序列{5,4,3,6,7,1,8,9} 算法實現: ????????? j:=10; ?????????? i:=1; ????????? while j>1 do ??????????? begin ????????????? j:=j div 2; ????????????? repeat ??????????????? alldone:=true; ??????????????? for index:=1 to 10-j do?? ?????????????????? begin ???????????????????? i:=index+j; ???????????????????? if a[index]<a[i] then ?????????????????????? begin ??????????????????????? temp:=a[index]; ??????????????????????? a[index]:=a[i]; ??????????????????????? a[i]:=temp; ??????????????????????? alldone:=false; ?????????????????????? end;??? ?????????????????? end; ????????????? until alldone ??????????? end;?? |