排序算法汇总(转载收藏)
生活随笔
收集整理的這篇文章主要介紹了
排序算法汇总(转载收藏)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一 排序的基本概念 所謂排序,就是要整理文件中的記錄,使之按關鍵字遞增(或遞減)次序排列起來. 其確切定義如下: 輸入: n個記錄R1,R2,…,Rn,其相應的關鍵字分別為K1,K2,…,Kn. 輸出: Ril,Ri2,…,Rin,使得Ki1 ≤ Ki2 ≤ … ≤ Kin.(或Ki1 ≥ Ki2 ≥ … ≥ Kin.) 二 排序的分類 1. 按是否涉及數據的內、外存交換分為 內部排序: 內部排序(簡稱內排序),是帶排序紀錄存放在計算機內存中進行的排序過程. 細分又可為插入排序、選擇排序、交換排序、歸并排序和基數排序. 外部排序: 外部排序(簡稱外排序),是帶排序紀錄的數量很大,以至于內存一次不能容納全部紀錄,在排序過程中,只有部分數被調入內存,并借助內存調整數在外存中的存放順序的排序方法. 2. 按策略劃分內部排序方法 可以分為五類: 插入排序、選擇排序、交換排序、歸并排序和基數排序. 三 排序算法的基本操作 1. 比較兩個關鍵字的大小 2. 改變指向記錄的指針或移動記錄本身 注意: 第2種基本操作的實現依賴于待排序記錄的存儲方式. 四 排序算法的性能評價 1. 評價排序算法好壞的標準 (1)執行時間和所需的輔助空間 (2)算法本身的復雜程度 2. 排序算法的空間復雜度 若排序算法所需的輔助空間并不依賴于問題的規模n,即輔助空間是O(1),則稱之為就地排序(In-Place Sort). 非就地排序一般要求的輔助空間為O(n). 3. 排序算法的時間開銷 大多數排序算法的時間開銷主要是關鍵字之間的比較和記錄的移動. 有的排序算法其執行時間不僅依賴于問題的規模,還取決于輸入實例中數據的狀態. 1 插入排序 1.1 直接插入排序 定義: 直接插入排序(Straight Insertion Sort)是一種最簡單的排序方法. 它的基本操作是將一個記錄插入到一個長度為m(假設)的有序表中,使之仍保持有序,從而得到一個新的長度為m+1的有序表. 算法思路: 設有一組關鍵字{K1,K2,…,Kn},排序開始就認為K1是一個有序序列,讓K2 插入上述表長為1的有序序列,使之成為一個表長為2的有序序列,然后讓K3插入上述表長為2的有序序列,使之成為一個表長為3的有序序列,依次類推,最后讓Kn插入上述表長為 n-1的有序序列,得一個表長為n的有序序列. 算法時間復雜度: 此算法外循環n-1次,在一般情況下內循環平均比較次數的數量級為O(n),所以算法總時間復雜度為O(n^2). 直接插入排序的穩定性: 直接插入排序是穩定.的排序方法 具體算法: /* 比較數據函數模板 */ template<class Type> typedef bool __stdcall (*PFunCustomCompare)(const Type *Data_1, const Type *Data_2) ; // InsertSort template<class Type> void InsertSort (TypeArray[], int n, PFunCustomComparepfCompare) { ?????? int i , j ; ?????? for (i = 2 ; i <= n ; i ++) // 進行n-1趟插入 ?????? { ????????????? Array[0] = Array[i] ; // Array[0]為監視哨,也可作下邊循環結束標志 ????????????? j = i - 1 ; ????????????? while ( pfCompare(Array[j], Array[0]) ) ????????????? { ???????????????????? Array[j+1] = Array[j] ; ???????????????????? j -- ; ????????????? } ????????????? Array[j+1] = Array[0] ; // 將r[0]即原r[i]記錄內容,插到r[j]后一位置 ?????? } } // 或者:不需要監視哨 template<class Type> void __stdcall InsertSort(TypeArray[], int Num, PFunCustomComparepfCompare) { ?????? for (int i = 1 ; i < Num ; i ++) ?????? { ????????????? Typetemp = Array[i] ; ????????????? int j ; ????????????? for (j = i-1 ; j >= 0 && pfCompare(temp, Array[j]) ; j --) ???????????????????? Array[j+1] = Array[j] ; ????????????? Array[j+1] = temp ; ?????? } } 例1 設有一組關鍵字序列{55,22,44,11,33},這里n = 5,即有5個記錄. 請將其按由小到大的順序排序. 排序過程如下所示: 第一趟: ?[55] ?22 ?44 ?11 ?33 第二趟: ?[22?55]?44?11?33 第三趟: ?[22?44?55]?11?33 第四趟: ?[11?22?44?55]?33 第五趟:?[11 ?22?33?44?55] 1.2 折半插入排序 定義: 當直接插入排序進行到某一趟時,對于r[i].key來講,前邊i-1個記錄已經按關鍵字有序.此時不用直接插入排序的方法,而改為折半查找,找出r[i].key應插的位置,然后插入. 這種方法就是折半插入排序(Binary Insertion Sort). 具體算法: // BinarySort template<class T> void BinarySort(Ta[], int n) { ?????? int i , j , l , h , mid ; ?????? for (i = 2 ; i <= n ; i ++) ?????? { ????????????? a[0] = a[i] ; ????????????? l = 1 ; h = i-1 ; // 認為在a[1]和a[i-1]之間已經有序 ????????????? while (l <= h) // 對有序表進行折半查找 ????????????? { ???????????????????? mid = (l+h) / 2 ; ???????????????????? if(a[0].key < a[mid].key) ??????????????????????????? h = mid-1 ; ???????????????????? else ??????????????????????????? l = mid+1 ; ????????????? } ????????????? // 結果在mid位置 ????????????? for(j = i-1 ; j >= mid ; j --) ???????????????????? a[j+1] = a[j] ; ????????????? a[mid] = a[0] ; ?????? } } 算法時間復雜度: 折半插入排序,關鍵字的比較次數由于采用了折半查找而減少,數量級為O (n*log(n)),但是元素移動次數仍為O(n^2). 故折半插入排序時間復雜度仍為O(n^2). 折半插入排序的穩定性: 折半插入排序方法是穩定.的 1.3 2路插入排序 1.4 表插入排序 1.5 希爾排序 定義: 希爾排序(Shell Sort)是D.L.希爾(D.L.Shell)提出的“縮小增量”的排序方法. 它的作法不是每次一個元素挨一個元素的比較. 而是初期選用大跨步(增量較大)間隔比較,使記錄跳躍式接近它的排序位置,然后增量縮小,最后增量為1,這樣記錄移動次數大大減少,提高了排序效率. 希爾排序對增量序列的選擇沒有嚴格規定. 算法思路: ① 先取一個正整數d1(d1<n),把全部記錄分成d1個組,所有距離為d1的倍數的記錄看成一組,然后在各組內進行插入排序 ; ② 然后取 d2 (d2<d1) ; ③ 重復上述分組和排序操作,直到取di=1 (i>=1),即所有記錄成為一個組為止. 一般選d1約為n/2,d2為d1/2,d3為d2/2,…,di=1. 具體算法: /* 比較數據函數模板 */? template<class Type> typedef bool __stdcall (*PFunCustomCompare)(const Type *Data_1, const Type *Data_2) ; template <class Type> void __stdcall ShellSort(TypeArray[], int Num, PFunCusomCompare pfCompare) { ?????? d = Num ; ?????? do ?????? { ????????????? d = d/2 ; // 一般增量設置為數組元素個數,不斷除以2以取小 ????????????? for (int i = d+1 ; i <= Num ; ++ i) ????????????? { ???????????????????? if (pfCompare(Array[i], Array[i-d])) ???????????????????? { ??????????????????????????? Typetemp = Array[i] ; ??????????????????????????? for (int j = i-d ; j > 0 && fpCompare(temp, Array[j]) ; j = j-d) ?????????????????????????????????? Array[j-d] = Array[j] ; ??????????????????????????? Array[j+d] = temp ; ???????????????????? } ????????????? } ?????? }while (d > 1) ; } // // Example two : void ShellPass(SeqList R, int d) { ?????? //希爾排序中的一趟排序,d為當前增量 ?????? for(i = d+1 ; i <= n ; i ++) // 將R[d+1..n]分別插入各組當前的有序區 ????????????? if(R[i].key < R[i-d].key) ????????????? { ???????????????????? R[0] = R[i] ; ???????????????????? j = i-d ; //R[0]只是暫存單元,不是哨兵 ???????????????????? do ???????????????????? { ??????????????????????????? //查找R[i]的插入位置 ??????????????????????????? R[j+d] = R[j] ; //后移記錄 ??????????????????????????? j = j-d ; //查找前一記錄 ???????????????????? }while (j > 0 && R[0].key < R[j].key) ; ???????????????????? R[j+d] = R[0] ; //插入R[i]到正確的位置上 ????????????? } // endif } // ShellPass void?ShellSort(SeqList R) { ?????? int increment = n ; //增量初值,不妨設n>0 ?????? do ?????? { ????????????? increment = increment/3 + 1 ; //求下一增量 ????????????? ShellPass(R, increment) ; //一趟增量為increment的Shell插入排序 ?????? }while (increment > 1) ; } // ShellSort 注意: 當增量d=1時,ShellPass和InsertSort基本一致,只是由于沒有哨兵而在內循環中增加了一個循環判定條件"j>0",以防下標越界. 算法分析: ①???? 增量序列的選擇 Shell排序的執行時間依賴于增量序列. 好的增量序列的共同特征: 最后一個增量必須為1 ; 應該盡量避免序列中的值(尤其是相鄰的值)互為倍數的情況. 有人通過大量的實驗,給出了目前比較好的結果: 當n較大時,比較和移動的次數在n1.25至1.6n1.25之間. ②???? Shell排序的時間性能優于直接插入排序 希爾排序的時間性能優于直接插入排序的原因: 當文件初態基本有序時直接插入排序所需的比較和移動次數均較少 ; 當n值較小時,n和n2的差別也較小,即直接插入排序的最好時間復雜度O(n)和最壞時間復雜度O(n2)差別不大 ; 在希爾排序開始時增量較大,分組較多,每組的記錄數目少,故各組內直接插入較快,后來增量di逐漸縮小,分組數逐漸減少,而各組的記錄數目逐漸增多,但由于已經按di-1作為距離排過序,使文件較接近于有序狀態,所以新的一趟排序過程也較快. 因此,希爾排序在效率上較直接插入排序有較大的改進. ③???? 穩定性 希爾排序是不穩定.的 2 選擇排序 選擇排序(Selection Sort)的基本思想是: 每一趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已排好序的子文件的最后,直到全部記錄排序完畢. 2.1 簡單選擇排序 定義: 簡單選擇排序(Simple Selection Sort)也是直接選擇排序. 此方法在一些高級語言課程中做過介紹,是一種較為容易理解的方法. 算法思想: 對于一組關鍵字{K1,K2,…,Kn},首先從K1,K2,…,Kn中選擇最小值,假如它是Kz,則將Kz與K1對換,然后從K2,K3,…,Kn中選擇最小值Kz,再將Kz與K2對換. 如此進行選擇和調換n-2趟,第(n-1)趟,從Kn-1、Kn中選擇最小值Kz將Kz與Kn-1對換,最后剩下的就是該序列中的最大值,一個由小到大的有序序列就這樣形成. 即: 在要排序的一組數中,選出最小的一個數與第一個位置的數交換,然后在剩下的數當中再找最小的與第二個位置的數交換,如此循環到倒數第二個數和最后一個數比較為止. 具體算法: /* 比較數據函數模板 */ template<class Type> typedef bool __stdcall (*PFunCustomCompare)(const Type *Data_1,const Type *Data_2) ; /* 交換數據函數模板 */ template<class Type> typedef void __stdcall (*PFunCusomSwap)(const Type *Data_1,const Type *Data_2) ; template<class Type> void __stdcall SelectSort(TypeArray[], int Num, PFunCusomCompare pfCompare, PFunCusomSwap pfSwap) { ?????? for (int i = 0 ; i < Num-1 ; ++ i) ?????? { ????????????? //從i~n-1中選擇要選的數據 ????????????? int min = i ; ????????????? for (int j = i+1 ; j < Num ; ++ j) ???????????????????? if (pfCompare (Array[j], Array[min])) ??????????????????????????? min = j ; ????????????? if(min != i) ???????????????????? pfSwap(Array[j],Array[min]) ; ?????? } } 算法分析: 1.?????? 關鍵字比較次數 無論文件初始狀態如何,在第i趟排序選出最小關鍵字的記錄,需做n-i次比較,因此總的比較次數為: n(n-1)/2 = O(n2). 2.?????? 記錄的移動次數 當初始文件為正序時,移動次數為0 ; 文件初態反序時,每趟排序均要執行交換操作,中的移動次數取最大值(n-1) ; 直接選擇排序的平均時間復雜度為O(n2) . 3.?????? 直接選擇排序是一個就地排序 4.?????? 穩定性分析 直接選擇排序是不穩定的. 2.2 堆排序 定義: 樹形選擇排序(錦標賽排序),1964年威洛姆斯(J.Willioms)提出了進一步改正的排序方法,即堆排序(Heap Sort). 堆是n個元素的有限序列{K1,K2,…,Kn},它當且僅當滿足如下關系: 這是一個上小、底大的堆. 若是一個上大、底小的堆,只需把“<=”改為“>=”即可. 堆是一種數據元素之間的邏輯關系,常用向量做存儲結構. 對于滿二叉樹,當對它的結點由上而下,自左至右編號之后,編號為i的結點是編號為2i和2i+1結點的雙親. 反過來講,結點2i是結點i的左孩子,結點2i+1是結點i的右孩子. 圖9.7表示完全二叉樹和它在向量中的存儲狀態. 結點編號對應向量中的下標號. 用堆的概念分析向量中的數據,它顯然滿足(上小、底大)堆的關系. 不難看出滿足堆的邏輯關系的一組數據,可畫成二叉樹的形狀,并且它是一棵完全二叉樹樹形. 因此,也可借助完全二叉樹來描述堆的概念. 若完全二叉樹中任一非葉子結點的值小于等于(或大于等于)其左、右孩子結點的值,則從根結點開始按結點編號排列所得的結點序列就是一個小根(大根)堆. 在圖9.8中(a)、(c)是堆, (b)、(d)不是堆. 堆排序的算法思想: 堆排序利用了大根堆(或小根堆)堆頂記錄的關鍵字最大(或最小)這一特征,使得在當前無序區中選取最大(或最小)關鍵字的記錄變得簡單. (1)用大根堆排序的基本思想 ①. 先將初始文件R[1..n]建成一個大根堆,此堆為初始的無序區 ②. 再將關鍵字最大的記錄R[1](即堆頂)和無序區的最后一個記錄R[n]交換,由此得到新的無序區R[1..n-1]和有序區R[n],且滿足R[1..n-1].keys ≤ R[n].key ③. 由于交換后新的根R[1]可能違反堆性質,故應將當前無序區R[1..n-1]調整為堆. 然后再次將R[1..n-1]中關鍵字最大的記錄R[1]和該區間的最后一個記錄R[n-1]交換,由此得到新的無序區R[1..n-2]和有序區R[n-1..n],且仍滿足關系R[1..n-2].keys≤R[n-1..n].keys,同樣要將R[1..n-2]調整為堆. ④. …… ⑤. 直到無序區只有一個元素為止. (2)大根堆排序算法的基本操作: ①. 初始化操作: 將R[1..n]構造為初始堆 ; ②. 每一趟排序的基本操作: 將當前無序區的堆頂記錄R[1]和該區間的最后一個記錄交換,然后將新的無序區調整為堆(亦稱重建堆). 注意: ① 只需做n-1趟排序,選出較大的n-1個關鍵字即可以使得文件遞增有序. ② 用小根堆排序與利用大根堆類似,只不過其排序結果是遞減有序的. 堆排序和直接選擇排序相反: 在任何時刻,堆排序中無序區總是在有序區之前,且有序區是在原向量的尾部由后往前逐步擴大至整個向量為止. 具體算法: 建堆(BuildHeap)和堆化(Heapify)函數的實現: 因為構造初始堆必須使用到調整堆的操作,先討論Heapify的實現. 以R[1]為根的堆,在R[1]與R[i]交換后,新的無序區R[1..i-1]中只有R[1]的值發生了變化,故除R[1]可能違反堆性質外,其余任何結點為根的子樹均是堆. 因此,當被調整區間是R[low..high]時,只須調整以R[low]為根的樹即可. "篩選法"調整堆R[low]的左、右子樹(若存在)均已是堆,這兩棵子樹的根R[2low]和R[2low+1]分別是各自子樹中關鍵字最大的結點. 若R[low].key不小于這兩個孩子結點的關鍵字,則R[low]未違反堆性質,以R[low]為根的樹已是堆,無須調整; 否則必須將R[low]和它的兩個孩子結點中關鍵字較大者進行交換,即R[low]與R[large](R[large].key=max(R[2low].key,R[2low+1].key))交換. 交換后又可能使結點R[large]違反堆性質,同樣由于該結點的兩棵子樹(若存在)仍然是堆,故可重復上述的調整過程,對以R[large]為根的樹進行調整. 此過程直至當前被調整的結點已滿足堆性質,或者該結點已是葉子為止. 上述過程就象過篩子一樣,把較小的關鍵字逐層篩下去,而將較大的關鍵字逐層選上來. 因此,有人將此方法稱為"篩選法". BuildHeap的實現 要將初始文件R[l..n]調整為一個大根堆,就必須將它所對應的完全二叉樹中以每一結點為根的子樹都調整為堆. 顯然只有一個結點的樹是堆,而在完全二叉樹中,所有序號大于n/2的結點都是葉子,因此以這些結點為根的子樹均已是堆. 這樣,我們只需依次將以序號為n/2,…,1的結點作為根的子樹都調整為堆即可. //-------------------------------------------------------------------------------------- template <class type> static void HeapIfy (type *arry, int size, int index) ; template <class type> inline static void BuildHeap (type *arry, int size) ; template <class type> static void HeapSort (type *arry, int size) ; //-------------------------------------------------------------------------------------- template <class type> static void HeapSort (type *arry, int size) { ?????? if (size <= 1) return ; ?????? BuildHeap (arry, size) ; ?????? int count = size ; ?????? while (count >= 2) ?????? { ????????????? typetemp = arry[count-1] ; ????????????? arry[count-1] = arry[0] ; ????????????? arry[0] = temp ; ????????????? count -- ; ????????????? BuildHeap (arry, count) ; ?????? } } //-------------------------------------------------------------------------------------- template <class type>? inline static void BuildHeap (type *arry, int size) { #if _DEBUG ?????? assert (arry && size > 0) ; #endif ?????? int i = (size-1)/2 ; ?????? for ( ; i >= 0 ; i --) ????????????? HeapIfy (arry, size, i) ; }? //-------------------------------------------------------------------------------------- static void HeapIfy (type *arry, int size, int index) { ?????? // 平衡堆,參數為數組,數組長度,加入的元素下標 #if _DEBUG ?????? assert (arry && size > 0 && index >= 0 && index < size) ; #endif ?????? int m = index ; // 本身索引 ?????? int l ; ?????? int r ; ?????? do ?????? { ????????????? l = m*2 + 1 ; // 左兒子索引 ????????????? r = l + 1 ; // 右兒子索引 ????????????? if (l >= size) //無兒子 ???????????????????? return ; ????????????? else if (r >= size) ????????????? { ???????????????????? // 無右兒子 ???????????????????? if (arry[m] >= arry[l]) ??????????????????????????? return ; ???????????????????? else ???????????????????? { ??????????????????????????? typetemp = arry[m] ; ??????????????????????????? arry[m] = arry[l] ; ??????????????????????????? arry[l] = temp ; ??????????????????????????? return ; ???????????????????? } ???????????????????? if (arry[l] >= arry[r]) ???????????????????? { ??????????????????????????? if (arry[m] >= arry[l]) ?????????????????????????????????? return ; ??????????????????????????? typetemp = arry[m] ; ??????????????????????????? arry[m] = arry[l] ; ??????????????????????????? arry[l] = temp ; ??????????????????????????? m = l ; ??????????????????????????? continue ; ???????????????????? } ????????????? } ????????????? else ????????????? { ???????????????????? if (arry[m] >= arry[r]) ??????????????????????????? return ; ???????????????????? typetemp = arry[m] ; ???????????????????? arry[m] = arry[r] ; ???????????????????? arry[r] = temp ; ???????????????????? m = r ; ???????????????????? continue ; ????????????? } ?????? }while (true) ; } 算法時間復雜度: 堆排序中heap算法的時間復雜度與堆所對應的完全二叉樹的樹高度log2n相關. 而 heapsort中對heap的調用數量級為n,所以堆排序的整個時間復雜度為O(nlog2n). 并且堆排序是不穩定的. 堆排序的時間,主要由建立初始堆和反復重建堆這兩部分的時間開銷構成,它們均是通過調用Heapify實現的. 堆排序的最壞時間復雜度為O(nlog(n)),堆排序的平均性能較接近于最壞性能. 由于建初始堆所需的比較次數較多,所以堆排序不適于記錄數較少的文件. 堆排序是就地排序,輔助空間為O(1). 堆排序是不穩定的. 3 交換排序 交換排序主要是根據記錄的關鍵字的大小,將記錄交換來進行排序的. 交換排序的特點是: 將關鍵字值較大的記錄向序列的后部移動,關鍵字較小的記錄向前移動. 這里介紹兩種交換排序方法,它們是冒泡排序和快速排序. 3.1 冒泡排序 定義: 將被排序的記錄數組R[1...n]垂直排列,每個記錄R[i]看作是重量為R[i].key的氣泡. 根據輕氣泡不能在重氣泡之下的原則,從下往上掃描數組R: 凡掃描到違反本原則的輕氣泡,就使其向上"飄浮". 如此反復進行,直到最后任何兩個氣泡都是輕者在上,重者在下為止. 算法思路: (1) 讓j取n至2,將r[j].key與r[j-1].key比較,如果r[j].key < r[j-1].key,則把記錄r[j]與r[j-1]交換位置,否則不進行交換. 最后是r[2].key與r[1].key對比,關鍵字較小的記錄就換到r[1]的位置上,到此第一趟結束. 最小關鍵字的記錄就象最輕的氣泡冒到頂部一樣換到了文件的前邊. (2) 讓j取n至3,重復上述的比較對換操作,最終r[2]之中存放的是剩余n-1個記錄(r[1]除外)中關鍵字最小的記錄. (3) 讓j取n至i+1,經過一系列對聯對比交換之后,r[i]之中是剩余若干記錄中關鍵字最小的記錄. (4) 讓j取n至n-1,將r[n].key與r[n-1].key對比,把關鍵字較小的記錄交換到r[n-1]之中. 例 設有一組關鍵字序列{55,22,44,11,33},這里n=5,即有5個記錄. 請將其按由小到大的順序排序. 具體算法: template<class Type> BubbleSort(TypeArray[], int n) { ?????? int t = 1, tag, j ; ?????? Tx ; ?????? do ?????? { ????????????? tag = 0 ; ????????????? for(j = n ; j >= i ; j --) ???????????????????? if(r[j].key < r[j-1].key) ???????????????????? { ??????????????????????????? x = r[j] ; ??????????????????????????? r[j] = r[j-1] ; ??????????????????????????? r[j-1] = x ; ??????????????????????????? tag = 1 ; ???????????????????? } ????????????? i ++ ; ?????? }while (tag == 1 && i <= n) ; } // BubbleSort 算法時間復雜度: 該算法的時間復雜度為O(n2). 但是,當原始關鍵字序列已有序時,只進行一趟比較就結束,此時時間復雜度為O(n). 3.2 雞尾酒排序(冒泡排序變形) 3.3 快速排序 定義: 快速排序由霍爾(Hoare)提出,它是一種對冒泡排序的改正. 由于其排序速度快,故稱快速排序(Quick Sort). 快速排序方法的實質是將一組關鍵字[K1,K2,…,Kn]進行分區交換排序. 算法思路: ①???? 以第一個關鍵字K1為控制字,將[K1,K2,…,Kn]分成兩個子區,使左區所有關鍵字小于等于K1,右區所有關鍵字大于等于K1,最后控制字居兩個子區中間的適當位置. 在子區內數據尚處于無序狀態. ②???? 將右區首、尾指針(記錄的下標號)保存入棧,對左區進行與第①步相類似的處理,又得到它的左子區和右子區,控制字居中. ③???? 后退棧對一個個右子區進行相類似的處理,直到棧空. 由以上三步可以看出: 快速排序算法總框架是進行多趟的分區處理,而對某一特定子區,則應把它看成又是一個待排序的文件,控制字總是取子區中第一個記錄的關鍵字. 現在設計一個函數hoare,它僅對某一待排序文件進行左、右子區的劃分,使控制字居中,再設計一個主體框架函數quicksort,在這里多次調用hoare函數以實現對整個文件的排序. 快速排序算法分析: 快速排序的非遞歸算法引用了輔助棧,它的深度為log(n). 假設每一次分區處理所得的兩個子區長度相近,那么可入棧的子區長度分別為: n/(2*1),n/(2*2),n/(2*3),n/(2*4),…,n/(2*k).又因為n/2k=1,所以 k= log2(n). 分母中2的指數恰好反映出需要入棧的子區個數,它就是 log2n,也即棧的深度. 在最壞情況下,比如原文件關鍵字已經有序,每次分區處理僅能得到一個子區. 可入的子區個數接近n,此時棧的最大深度為n. 快速排序主體算法時間運算量約O(log2n),劃分子區函數運算量約O(n),所以總的時間復雜度為O(nlog2n),它顯然優于冒泡排序O(n2). 可是算法的優勢并不是絕對的. 試分析,當原文件關鍵字有序時,快速排序時間復雜度是O(n2),這種情況下快速排序不快. 而這種情況的冒泡排序是O(n),反而很快. 在原文件記錄關鍵字無序時的多種排序方法中,快速排序被認為是最好的一種排序方法. 例 試用[6,7,5(1),2,5(2),8]進行快速排序. 排序過程簡述如下 6???? 7???? 5(1) 2???? 5(2) 8???? 初始狀態 [5(2)?????? 7???? 5(1)]?????? 6???? [7??? 8] [2]?? 5(2) [5(1)]????? 6???? 7???? [8] [2??? 5(2) 5(1) 6?? 7?? 8]??? 最后狀態 從這個例子可以分析出快速排序法的穩定性問題,其中51和52表示兩個關鍵字的值相同,都是5. 5(1)表示排序之前它位于5(2)的前面. 從結果中可以看出原先位于5(1)之后的5(2)在排序之后移到了5(1)的前面,所以說快速排序是不穩定的. 具體算法: template <class Type> void __stdcall QuickSort(TypeArray[], int Num, PFunCusomCompare pfCompare, PFunCusomSwap pfSwap) { ?????? int left = 0 ; ?????? int right = Num-1 ; ?????? do ?????? { ????????????? int i = left , j = right ; ????????????? TypeMidData = Array[(left+right)/2] ; ????????????? do ????????????? { ???????????????????? while (fpCompare (MidData, Array[i]) && i < right) ???????????????????? { ??????????????????????????? // 從左掃描大于中值的數 ??????????????????????????? ++ i ; ???????????????????? } ???????????????????? while (fpCompare (Array[j], MidData) && j > left) ???????????????????? { ??????????????????????????? // 從右掃描大于中值的數 ??????????????????????????? -- j ; ???????????????????? } ???????????????????? if (i <= j) ???????????????????? { ??????????????????????????? pfSwap(Array[i], Array[j]) ; // 交換數據 ??????????????????????????? ++ i ; ??????????????????????????? -- j ; ???????????????????? } ????????????? }while (i <= j) ;// 如果兩邊掃描的下標交錯,就停止(完成一次) ????????????? if (left < j) ????????????? { ???????????????????? // 當左邊部分有值(left<j),遞歸左半邊 ???????????????????? left = left ; ???????????????????? ????????????? } ????????????? if (right > i) ????????????? { ???????????????????? //當右邊部分有值(right>i),遞歸右半邊 ???????????????????? left = i ; ???????????????????? right = right ; ????????????? } ?????? }while (left <= right) ; } 4 歸并排序 定義: 歸并排序(Merge Sort)是一類與插入排序、交換排序、選擇排序不同的另一種排序方法.歸并的含義是將兩個或兩個以上的有序表合并成一個新的有序表. 歸并排序有多路歸并排序、兩路歸并排序,可用于內排序,也可以用于外排序. 這里僅對內排序的兩路歸并方法進行討論. 兩路歸并排序算法思路: ①. 把n個記錄看成n個長度為l的有序子表 ; ②. 進行兩兩歸并使記錄關鍵字有序,得到n/2個長度為2的有序子表 ; ③. 重復第②步直到所有記錄歸并成一個長度為n的有序表為止 ; 算法實現: 此算法的實現不像圖示那樣簡單,現分三步來討論. 首先從宏觀上分析,首先讓子表表長L=1進行處理,不斷地使L=2*L,進行子表處理,直到L>=n為止,把這一過程寫成一個主體框架函數mergesort. 然后對于某確定的子表表長L,將n個記錄分成若干組子表,兩兩歸并,這里顯然要循環若干次,把這一步寫成一個函數mergepass,可由mergesort調用. 最后再看每一組(一對)子表的歸并,其原理是相同的,只是子表表長不同,換句話說,是子表的首記錄號與尾記錄號不同,把這個歸并操作作為核心算法寫成函數merge,由mergepass來調用. 具體算法: // 歸并操作 template <class type> static void Merge (typearray[], int p, int q, int r) { ?????? int i , k ; ?????? int begin1 , end1 , begin2 , end2 ; ?????? int* temp = (int*)malloc((r-p)*sizeof(int)) ; ?????? begin1 = p ; ?????? end1 = q ; ?????? begin2 = q+1 ; ?????? end2 = r ; ?????? k = 0 ; ?????? while (begin1 <= end1 && begin2 <= end2) ?????? { ????????????? if (array[begin1] < array[begin2]) ????????????? { ???????????????????? temp[k] = array[begin1] ; ???????????????????? begin1 ++ ; ????????????? } ????????????? else ????????????? { ???????????????????? temp[k] = array[begin2] ; ???????????????????? begin2 ++ ; ????????????? } ????????????? k ++ ; ?????? } ?????? while (begin1 < end1) ?????? { ????????????? temp[k++] = array[begin1++] ; ?????? } ?????? while (begin2 < end2) ?????? { ????????????? temp[k++] = array[begin2++] ; ?????? } ?????? for (i = 0 ; i < (r-p) ; i ++) ?????? { ????????????? array[p+i] = temp ; ?????? } ?????? free(temp) ; } //-------------------------------------------------------------------------------------- template <class type> void MergeSort(typearray[], unsigned int first, unsigned int last) { ?????? int mid = 0 ; ?????? if (first < last) ?????? { ????????????? mid = (first+last)/2 ; ????????????? MergeSort (array, first, mid) ; ????????????? MergeSort (array, mid+1, last) ; ????????????? Merge (array, first, mid, last) ; ?????? } } 算法分析: 1. 穩定性,歸并排序是一種穩定的排序. 2. 存儲結構要求,可用順序存儲結構,也易于在鏈表上實現. 3. 時間復雜度,對長度為n的文件,需進行log(n)趟二路歸并,每趟歸并的時間為O(n),故其時間復雜度無論是在最好情況下還是在最壞情況下均是O(nlog(n)). 4. 空間復雜度,需要一個輔助向量來暫存兩有序子文件歸并的結果,故其輔助空間復雜度為O(n),顯然它不是就地排序. 注意: 若用單鏈表做存儲結構,很容易給出就地的歸并排序. 5 分配排序 分配排序的基本思想: 排序過程無須比較關鍵字,而是通過"分配"和"收集"過程來實現排序. 它們的時間復雜度可達到線性階: O(n). 5.1桶排序 這里先介紹箱排序. 箱排序的基本思想: 箱排序也稱桶排序(Bucket Sort),其基本思想是: 設置若干個箱子,依次掃描待排序的記錄R[0],R[1],…,R[n-1],把關鍵字等于k的記錄全都裝入到第k個箱子里(分配),然后按序號依次將各非空的箱子首尾連接起來(收集). 例要將一副混洗的52張撲克牌按點數A<2<…<J<Q<K排序,需設置13個"箱子",排序時依次將每張牌按點數放入相應的箱子里,然后依次將這些箱子首尾相接,就得到了按點數遞增序排列的一副牌. 箱排序中,箱子的個數取決于關鍵字的取值范圍 若R[0..n-1]中關鍵字的取值范圍是0到m-1的整數,則必須設置m個箱子. 因此箱排序要求關鍵字的類型是有限類型,否則可能要無限個箱子. 箱子的類型應設計成鏈表為宜 一般情況下每個箱子中存放多少個關鍵字相同的記錄是無法預料的,故箱子的類型應設計成鏈表為宜. 為保證排序是穩定的,分配過程中裝箱及收集過程中的連接必須按先進先出原則進行. 實現方法一 每個箱子設為一個鏈隊列. 當一記錄裝入某箱子時,應做入隊操作將其插入該箱子尾部,而收集過程則是對箱子做出隊操作,依次將出隊的記錄放到輸出序列中. 實現方法二 若輸入的待排序記錄是以鏈表形式給出時,出隊操作可簡化為是將整個箱子鏈表鏈接到輸出鏈表的尾部. 這只需要修改輸出鏈表的尾結點中的指針域,令其指向箱子鏈表的頭,然后修改輸出鏈表的尾指針,令其指向箱子鏈表的尾即可. 算法簡析 分配過程的時間是O(n),收集過程的時間為O(m),(采用鏈表來存儲輸入的待排序記錄)或O(m+n). 因此,箱排序的時間為O(m+n). 若箱子個數m的數量級為O(n),則箱排序的時間是線性的,即O(n). 注意: 箱排序實用價值不大,僅適用于作為基數排序(下節介紹)的一個中間步驟. 箱排序的變種. 為了區別于上述的箱排序,姑且稱它為桶排序(實際上箱排序和桶排序是同義詞). 桶排序基本思想 桶排序的思想是把[0,1)劃分為n個大小相同的子區間,每一子區間是一個桶. 然后將n個記錄分配到各個桶中. 因為關鍵字序列是均勻分布在[0,1)上的,所以一般不會有很多個記錄落入同一個桶中. 由于同一桶中的記錄其關鍵字不盡相同,所以必須采用關鍵字比較的排序方法(通常用插入排序)對各個桶進行排序,然后依次將各非空桶中的記錄連接(收集)起來即可. 注意: 這種排序思想基于以下假設: 假設輸入的n個關鍵字序列是隨機分布在區間[0,1)之上.若關鍵字序列的取值范圍不是該區間,只要其取值均非負,我們總能將所有關鍵字除以某一合適的數,將關鍵字映射到該區間上. 但要保證映射后的關鍵字是均勻分布在[0,1)上的. 桶排序算法 偽代碼算法為: void BucketSon(R) { //對R[0..n-1]做桶排序,其中0 ≤ R[i].key < 1 (0 ≤ i < n) for(i = 0 ; i < n ; i ++) // 分配過程 將R[i]插入到桶B[「n(R[i].key)」]中 ; // 可插入表頭上 for(i = 0 ; i < n ; i ++) // 排序過程 當B[i]非空時用插人排序將B[i]中的記錄排序 ; for(i = 0 ; i < n ; i ++) // 收集過程 若B[i]非空,則將B[i]中的記錄依次輸出到R中 ; } 注意: 實現時需設置一個指針向量B[0..n-1]來表示n個桶. 但因為任一記錄R[i]的關鍵字滿足: 0≤R[i].key<1(0≤i≤n-1),所以必須將R[i].key映射到B的下標區間[0,n-1)上才能使R[i]裝入某個桶中,這可通過「n*(R[i].key)」來實現. 桶排序算法分析 桶排序的平均時間復雜度是線性的,即O(n). 但最壞情況仍有可能是O(n2). 箱排序只適用于關鍵字取值范圍較小的情況,否則所需箱子的數目m太多導致浪費存儲空間和計算時間. 例n=10,被排序的記錄關鍵字ki取值范圍是0到99之間的整數(36,5,16,98,95,47,32,36,48)時,要用100個箱子來做一趟箱排序. (即若m=n2時,箱排序的時間O(m+n)=O(n2)). 5.2 基數排序 基數排序(Radix Sort)是對箱排序的改進和推廣. 單關鍵字和多關鍵字 文件中任一記錄R[i]的關鍵字均由d個分量構成. 若這d個分量中每個分量都是一個獨立的關鍵字,則文件是多關鍵字的(如撲克牌有兩個關鍵字: 點數和花色),否則文件是單關鍵字的,(0≤j<d)只不過是關鍵字中其中的一位(如字符串、十進制整數等). 多關鍵字中的每個關鍵字的取值范圍一般不同. 如撲克牌的花色取值只有4種,而點數則有13種. 單關鍵字中的每位一般取值范圍相同. 基數 設單關鍵字的每個分量的取值范圍均是: C0≤kj≤Crd-1(0≤j<d), 可能的取值個數rd稱為基數. 基數的選擇和關鍵字的分解因關鍵字的類型而異: (1) 若關鍵字是十進制整數,則按個、十等位進行分解,基數rd=10,C0=0,C9=9,d為最長整數的位數; (2) 若關鍵字是小寫的英文字符串,則rd=26,Co='a',C25='z',d為字符串的最大長度. 基數排序的基本思想 基數排序的基本思想是: 從低位到高位依次對Kj(j=d-1,d-2,…,0)進行箱排序. 在d趟箱排序中,所需的箱子數就是基數rd,這就是"基數排序"名稱的由來. 算法分析 若排序文件不是以數組R形式給出,而是以單鏈表形式給出(此時稱為鏈式的基數排序),則可通過修改出隊和人隊函數使表示箱子的鏈隊列無須分配結點空間,而使用原鏈表的結點空間. 入隊出隊操作亦無需移動記錄而僅需修改指針. 雖然這樣一來節省了一定的時間和空間,但算法要復雜得多,且時空復雜度就其數量級而言并未得到改觀. 基數排序的時間是線性的(即O(n)). 基數排序所需的輔助存儲空間為O(n+rd). 基數排序是穩定的. 5.3 鴿巢排序 6 并行排序
轉載于:https://www.cnblogs.com/guoxiaowen/archive/2009/02/05/1384405.html
總結
以上是生活随笔為你收集整理的排序算法汇总(转载收藏)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多线程-- ThreadLocal
- 下一篇: VOA AGRICULTURE REPO