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