冒泡排序c++代码_八大排序算法(解释+代码+结果+算法优化)
》》》歡迎點贊,收藏,轉發! 評論區獲取源代碼與更多更全干貨!《《《
排序算法可以分為內部排序和外部排序,內部排序是數據記錄在內存中進行排序,不需要訪問外存便能完成. 而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。常見的內部排序算法有:插入排序、希爾排序、選擇排序、冒泡排序、歸并排序、快速排序、堆排序、基數排序等。用一張圖概括:
外部排序點擊以下圖片查看大圖:
關于時間復雜度
平方階 (O(n2)) 排序 各類簡單排序:直接插入、直接選擇和冒泡排序。
線性對數階 (O(nlog2n)) 排序 快速排序、堆排序和歸并排序;
O(n1+§)) 排序,§ 是介于 0 和 1 之間的常數。 希爾排序
線性階 (O(n)) 排序 基數排序,此外還有桶、箱排序。
關于穩定性
穩定的排序算法:冒泡排序、插入排序、歸并排序和基數排序。
不是穩定的排序算法:選擇排序、快速排序、希爾排序、堆排序。
名詞解釋:
n:數據規模
k:"桶"的個數
In-place:占用常數內存,不占用額外內存
Out-place:占用額外內存
穩定性:排序后 2 個相等鍵值的順序和排序之前它們的順序相同
1. 冒泡排序 BubbleSort
冒泡排序是一種交換排序。
什么是交換排序呢?
—— 兩兩比較待排序的關鍵字,并交換不滿足次序要求的那對數,直到整個表都滿足次序要求為止。
它重復地走訪要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。
這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端,故名冒泡排序。
動態效果示意圖:
假設有一個大小為 N 的無序序列。以升序冒泡排序為例,冒泡排序就是要每趟排序過程中通過兩兩比較相鄰元素,將小的數字放到前面,大的數字放在后面。
算法分析
1、冒泡排序算法的性能
2、時間復雜度
若文件的初始狀態是正序的,一趟掃描即可完成排序。所需的關鍵字比較次數C和記錄移動次數M均達到最小值:Cmin = N - 1, Mmin = 0。所以,冒泡排序最好時間復雜度為O(N)。
但是上述代碼,不能掃描一趟就完成排序,它會進行全掃描。所以一個改進的方法就是,當冒泡中途發現已經為正序了,便無需繼續比對下去。改進方法一會兒介紹。
若初始文件是反序的,需要進行 N -1 趟排序。每趟排序要進行 N - i 次關鍵字的比較(1 ≤ i ≤ N - 1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種情況下,比較和移動次數均達到最大值:
Cmax = N(N-1)/2 = O(N^2)
Mmax = 3N(N-1)/2 = O(N^2)
冒泡排序的最壞時間復雜度為O(N^2)。
因此,冒泡排序的平均時間復雜度為O(N^2)。
總結起來,其實就是一句話:當數據越接近正序時,冒泡排序性能越好。
3. 算法穩定性
假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱為不穩定的。
冒泡排序就是把小的元素往前調或者把大的元素往后調。是相鄰的兩個元素的比較,交換也發生在這兩個元素之間。所以相同元素的前后順序并沒有改變,所以冒泡排序是一種穩定排序算法。
4. 優化
對冒泡排序常見的改進方法是加入標志性變量exchange,用于標志某一趟排序過程中是否有數據交換。
如果進行某一趟排序時并沒有進行數據交換,則說明所有數據已經有序,可立即結束排序,避免不必要的比較過程。
2. 快速排序 Quicksort
快速排序是一種交換排序,它由C. A. R. Hoare在1962年提出??焖倥判?quick sort)的采用了分而治之(divide and conquer)的策略:將原問題分解為若干個規模更小但結構與原問題相似的子問題。遞歸地解這些子問題,然后將這些子問題的解組合為原問題的解。
1. 算法思想
快速排序的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分:分割點左邊都是比它小的數,右邊都是比它大的數。
然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。
動態效果示意圖:
詳細的圖解往往比大堆的文字更有說明力,所以直接上圖:
上圖中,演示了快速排序的處理過程:
初始狀態為一組無序的數組:2、4、5、1、3。
經過以上操作步驟后,完成了第一次的排序,得到新的數組:1、2、5、4、3。
新的數組中,以2為分割點,左邊都是比2小的數,右邊都是比2大的數。
因為2已經在數組中找到了合適的位置,所以不用再動。
2左邊的數組只有一個元素1,所以顯然不用再排序,位置也被確定。(注:這種情況時,left指針和right指針顯然是重合的。因此在代碼中,我們可以通過設置判定條件left必須小于right,如果不滿足,則不用不用不用不不用不用不用不用不用用不用排序了)。
而對于2右邊的數組5、4、3,設置left指向5,right指向3,開始繼續重復圖中的一、二、三、四步驟,對新的數組進行排序。
快排的工作過程其實比較簡單,三步走:
a. 選擇基準值 pivot 將數組分成兩個子數組:小于基準值的元素和大于基準值的元素。這個過程稱之為 partition
b. 對這兩個子數組進行快速排序。
c. 合并結果,遞歸地(recursive)把小于基準值元素的子數列和大于基準值元素的子數列排序。
2. 代碼+結果
3. 算法分析
3.1 快速排序算法的性能
3.2 時間復雜度
當數據有序時,以第一個關鍵字為基準分為兩個子序列,前一個子序列為空,此時執行效率最差。
而當數據隨機分布時,以第一個關鍵字為基準分為兩個子序列,兩個子序列的元素個數接近相等,此時執行效率最好。
所以,數據越隨機分布時,快速排序性能越好;數據越接近有序,快速排序性能越差。
3.3 時間復雜度
快速排序在每次分割的過程中,需要 1 個空間存儲基準值。而快速排序的大概需要 logN次的分割處理,所以占用空間也是 logN 個。
3.4 算法穩定性
在快速排序中,相等元素可能會因為分區而交換順序,所以它是不穩定的算法。
3. 直接插入排序 Straight Insertion Sort
直接插入排序(Insertion Sort)序是一種最簡單的插入排序。為簡化問題,我們下面只討論升序排序。
1. 算法思想
插入排序:每一趟將一個待排序的記錄,按照其關鍵字的大小插入到有序隊列的合適位置里,知道全部插入完成。
動態效果示意圖:
以上的過程,其實就是典型的直接插入排序,每次將一個新數據插入到有序隊列中的合適位置里。
很簡單吧,接下來,我們要將這個算法轉化為編程語言。
假設有一組無序序列 R0, R1, ... , RN-1。
(1) 我們先將這個序列中下標為 0 的元素視為元素個數為 1 的有序序列。
(2) 然后,我們要依次把 R1, R2, ... , RN-1 插入到這個有序序列中。所以,我們需要一個外部循環,從下標 1 掃描到 N-1 。
(3) 接下來描述插入過程。假設這是要將 Ri 插入到前面有序的序列中。由前面所述,我們可知,插入Ri時,前 i-1 個數肯定已經是有序了。
所以我們需要將Ri 和R0 ~ Ri-1 進行比較,確定要插入的合適位置。這就需要一個內部循環,我們一般是從后往前比較,即從下標 i-1 開始向 0 進行掃描。
2. 代碼+答案
3. 算法分析
3.1 直接插入排序的算法性能
3.2 時間復雜度
當數據正序時,執行效率最好,每次插入都不用移動前面的元素,時間復雜度為O(N)。
當數據反序時,執行效率最差,每次插入都要前面的元素后移,時間復雜度為O(N^2)。
所以,數據越接近正序,直接插入排序的算法性能越好。
3.3 空間復雜度
由直接插入排序算法可知,我們在排序過程中,需要一個臨時變量存儲要插入的值,所以空間復雜度為 1 。
3.4 算法穩定性
直接插入排序的過程中,不需要改變相等數值元素的位置,所以它是穩定的算法。
4. 優化
因為在一個有序序列中查找一個插入位置,以保證有序序列的序列不變,所以可以使用二分查找,減少元素比較次數提高效率。
二分查找是對于有序數組而言的,假設如果數組是升序排序的。那么,二分查找算法就是不斷對數組進行對半分割,每次拿中間元素和目標數字進行比較,如果中間元素小于目標數字,則說明目標數字應該在右側被分割的數組中,如果中間元素大于目標數字,則說明目標數字應該在左側被分割的數組中。
4. 希爾排序 Shell's Sort
希爾(Shell)排序又稱為縮小增量排序,它是一種插入排序。它是直接插入排序算法的一種威力加強版。
希爾排序,也稱遞減增量排序算法,以其設計者希爾(Donald Shell)的名字命名,該算法由 1959 年公布。
1. 算法思想
我們舉個例子來描述算法流程(以下摘自維基百科):
假設有這樣一組數 {13, 14, 94, 33, 82, 25, 59, 94, 65, 23, 45, 27, 73, 25, 39, 10},如果我們以步長為 5 開始進行排序:
13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10
然后我們對每列進行排序:
10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45
將上述四行數字,依序接在一起時我們得到:{10, 14, 73, 25, 23, 13, 27, 94, 33, 39, 25, 59, 94, 65, 82, 45},然后再以 3 為步長:
10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45
排序之后變為:
10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94
最后以 1 為步長進行排序(此時就是簡單的插入排序了)。
可想而知,步長的選擇是希爾排序的重要部分。算法最開始以一定的步長進行排序,然后會繼續以更小的步長進行排序,最終算法以步長為 1 進行排序。當步長為 1 時,算法變為直接插入排序,這就保證了數據一定會被全部排序。
下面以n/2作為步長為例進行講解。(步長自己取)
算法分析
希爾排序的算法性能
1. 時間復雜度
步長的選擇是希爾排序的重要部分,只要最終步長為1任何步長序列都可以工作。
算法最開始以一定的步長進行排序。然后會繼續以一定步長進行排序,最終算法以步長為1進行排序。當步長為1時,算法變為插入排序,這就保證了數據一定會被排序。
步長序列的不同,會導致最壞的時間復雜度情況的不同。
本文中,以N/2為步長的最壞時間復雜度為N^2。
Donald Shell 最初建議步長選擇為N/2并且對步長取半直到步長達到1。雖然這樣取可以比O(N^2)類的算法(插入排序)更好,但這樣仍然有減少平均時間和最差時間的余地。可能希爾排序最重要的地方在于當用較小步長排序后,以前用的較大步長仍然是有序的。比如,如果一個數列以步長5進行了排序然后再以步長3進行排序,那么該數列不僅是以步長3有序,而且是以步長5有序。如果不是這樣,那么算法在迭代過程中會打亂以前的順序,那就不會以如此短的時間完成排序了。
用這樣步長序列的希爾排序比插入排序要快,甚至在小數組中比快速排序和堆排序還快,但是在涉及大量數據時希爾排序還是比快速排序慢。
2. 算法穩定性
希爾排序中相等數據可能會交換位置,所以希爾排序是不穩定的算法。
3. 直接插入排序和希爾排序的比較
直接插入排序是穩定的;而希爾排序是不穩定的。
直接插入排序更適合于原始記錄基本有序的集合。
希爾排序的比較次數和移動次數都要比直接插入排序少,當N越大時,效果越明顯。
希爾排序的比較次數和移動次數都要比直接插入排序少,當N越大時,效果越明顯。
直接插入排序也適用于鏈式存儲結構;希爾排序不適用于鏈式結構。
5. 簡單選擇排序
簡單選擇排序是一種選擇排序。
選擇排序:每趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已排序的記錄序列末尾,直到全部排序結束為止。
1. 算法思想
簡單排序很簡單,它的大致處理流程為:
從待排序序列中,找到關鍵字最小的元素;
如果最小元素不是待排序序列的第一個元素,將其和第一個元素互換;
從余下的 N - 1 個元素中,找出關鍵字最小的元素,重復(1)、(2)步,直到排序結束。
動態效果示意圖:
舉例說明,處理過程示意圖如下所示:
如圖所示,每趟排序中,將當前第 i 小的元素放在位置 i 上。
2. 代碼+結果
3. 算法分析
3.1 簡單算法排序性能
其中,N2為N^2。
3.2 時間復雜度
簡單選擇排序的比較次數與序列的初始排序無關。 假設待排序的序列有 N 個元素,則比較次數總是N (N - 1) / 2。
而移動次數與序列的初始排序有關。當序列正序時,移動次數最少,為 0.
當序列反序時,移動次數最多,為3N (N - 1) / 2。
所以,綜合以上,簡單排序的時間復雜度為O(N^2)。
3.3 空間復雜度
簡單選擇排序需要占用1個臨時空間,用于保存最小值得索引。
6. 堆排序
堆排序是一種選擇排序。
選擇排序:每趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已排序的記錄序列末尾,直到全部排序結束為止。
1. 算法思想
堆排序是利用堆的性質進行的一種選擇排序。
動態效果示意圖:
堆是一棵順序存儲的完全二叉樹。
其中每個結點的關鍵字都不大于其孩子結點的關鍵字,這樣的堆稱為小根堆。
其中每個結點的關鍵字都不小于其孩子結點的關鍵字,這樣的堆稱為大根堆。
舉例來說,對于n個元素的序列{R0, R1, ... , Rn}當且僅當滿足下列關系之一時,稱之為堆:
Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)
Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)
其中i=1,2,…,n/2向下取整;
如上圖所示,序列R{3, 8, 15, 31, 25}是一個典型的小根堆。
堆中有兩個結點,元素3和元素8。
元素3在數組中以R[0]表示,它的左孩子結點是R[1],右孩子結點是R[2]。
元素8在數組中以R[1]表示,它的左孩子結點是R[3],右孩子結點是R[4],它的父結點是R[0]。可以看出,它們滿足以下規律:
設當前元素在數組中以R[i]表示,那么,
(1) 它的左孩子結點是:R[2*i+1];
(2) 它的右孩子結點是:R[2*i+2];
(3) 它的父結點是:R[(i-1)/2];
(4) R[i] <= R[2*i+1] 且 R[i] <= R[2i+2]。
首先,按堆的定義將數組R[0..n]調整為堆(這個過程稱為創建初始堆),交換R[0]和R[n];
然后,將R[0..n-1]調整為堆,交換R[0]和R[n-1];
如此反復,直到交換了R[0]和R[1]為止。
以上思想可歸納為兩個操作:
(1)根據初始數組去構造初始堆(構建一個完全二叉樹,保證所有的父結點都比它的孩子結點數值大)。
(2)每次交換第一個和最后一個元素,輸出最后一個元素(最大值),然后把剩下元素重新調整為大根堆。
當輸出完最后一個元素后,這個數組已經是按照從小到大的順序排列了。
先通過詳細的實例圖來看一下,如何構建初始堆。
設有一個無序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 }。
構造了初始堆后,我們來看一下完整的堆排序處理:
還是針對前面提到的無序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 } 來加以說明。
比如如下數組 {57, 40, 38, 11, 13, 34, 48, 75, 6, 19, 9, 7}堆排序前如下:
進行堆排序后如下:
最大堆的存儲結構如下:
接著,最后一步,堆排序,進行(n-1)次循環。
相信,通過以上兩幅圖,應該能很直觀的演示堆排序的操作處理。
看完上面所述的流程你至少有一個疑問:
如何確定最后一個非葉子結點?
其實這是有一個公式的,設二叉樹結點總數為 n,則最后一個非葉子結點是第?n/2?個。
2. 代碼+結果:
3. 算法分析
3.1 堆排序算法的總體情況
3.2 時間復雜度
首先計算建堆的時間,也就是下面的代碼,
// 循環建立初始堆
for (int i = length / 2; i >= 0; i--){
HeapAdjust(list, i, length);
}
n 個結點,從第 0 層至第logn層。對于第 i 層的2i個點如果需要往下走logn?i步,那么把走的所有步相加得:
接下來就是排序的時間,即下面的代碼:
// 進行n-1次循環,完成排序
for (int i = length - 1; i > 0; i--){
// 最后一個元素和第一元素進行交換
int temp = list[i];
list[i] = list[0];
list[0] = temp;
// 篩選 R[0] 結點,得到i-1個結點的堆
HeapAdjust(list, 0, i);
}
HeapAdjust() 耗時logn,共 n 次,故排序時間為O(nlogn)。
堆的存儲表示是順序的。因為堆所對應的二叉樹為完全二叉樹,而完全二叉樹通常采用順序存儲方式。
當想得到一個序列中第k個最小的元素之前的部分排序序列,最好采用堆排序。
3.3 算法穩定性
堆排序是一種不穩定的排序方法。
因為在堆的調整過程中,關鍵字進行比較和交換所走的是該結點到葉子結點的一條路徑,因此對于相同的關鍵字就可能出現排在后面的關鍵字被交換到前面來的情況。
7. 歸并排序 MergeSort
歸并排序是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。
1. 算法思想
該算法采用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然后遞歸求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。
動態效果示意圖:
分而治之:
1. 分階段
可以看到這種結構很像一棵完全二叉樹,本文的歸并排序我們采用遞歸去實現(也可采用迭代的方式去實現)。分階段可以理解為就是遞歸拆分子序列的過程,遞歸深度為logn。
2. 治階段
再來看看治階段,我們需要將兩個已經有序的子序列合并成一個有序序列,比如上圖中的最后一次合并,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合并為最終序列[1,2,3,4,5,6,7,8],來看下實現步驟。
3. 算法分析
3.1. 歸并排序算法的性能
其中,log2n為以2為底,n的對數。
3.2 時間復雜度
歸并排序的形式就是一棵二叉樹,它需要遍歷的次數就是二叉樹的深度,而根據完全二叉樹的可以得出它的時間復雜度是O(n*log2n)。
3.3 空間復雜度
由前面的算法說明可知,算法處理過程中,需要一個大小為n的臨時存儲空間用以保存合并序列。
3.4 算法穩定性
在歸并排序中,相等的元素的順序不會改變,所以它是穩定的算法。
3.5 歸并排序和堆排序、快速排序的比較
若從空間復雜度來考慮:首選堆排序,其次是快速排序,最后是歸并排序。
若從穩定性來考慮,應選取歸并排序,因為堆排序和快速排序都是不穩定的。
若從平均情況下的排序速度考慮,應該選擇快速排序。
8. 基數排序 RadixSort
基數排序是一種非比較型整數排序算法,其原理是將整數按位數切割成不同的數字,然后按每個位數分別比較。由于整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是只能使用于整數。
1. 算法思想
基本思想:將所有待比較數值(正整數)統一為同樣的數位長度,數位較短的數前面補零。然后,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以后,數列就變成一個有序序列。
算法步驟:
將所有待比較數值(正整數)統一為同樣的數位長度,數位較短的數前面補零。
從最低位開始,依次進行一次排序。
這樣從最低位排序一直到最高位排序完成以后, 數列就變成一個有序序列。
基數排序的方式可以采用 LSD(Least significant digital)或 MSD(Most significant digital),LSD 的排序方式由鍵值的最右邊開始,而 MSD 則相反,由鍵值的最左邊開始。
不妨通過一個具體的實例來展示一下基數排序是如何進行的。 設有一個初始序列為: R {50, 123, 543, 187, 49, 30, 0, 2, 11, 100}。
我們知道,任何一個阿拉伯數,它的各個位數上的基數都是以 0~9 來表示的,所以我們不妨把 0~9 視為 10 個桶。
我們先根據序列的個位數的數字來進行分類,將其分到指定的桶中。例如:R[0] = 50,個位數上是 0,將這個數存入編號為 0 的桶中。
分類后,我們在從各個桶中,將這些數按照從編號 0 到編號 9 的順序依次將所有數取出來。這時,得到的序列就是個位數上呈遞增趨勢的序列。
按照個位數排序: {50, 30, 0, 100, 11, 2, 123, 543, 187, 49}。
接下來,可以對十位數、百位數也按照這種方法進行排序,最后就能得到排序完成的序列。
動態效果示意圖:
3. 算法分析
3.1 基數排序的性能
其中,d代表數組元素最高為位數,n代表元素個數。
3.2 時間復雜度
這個時間復雜度比較好計算:count * length;其中 count 為數組元素最高位數,length為元素個數;所以時間復雜度:O(n * d)
3.3 空間復雜度
空間復雜度是使用了兩個臨時的數組:10 + length;所以空間復雜度:O(n)。
3.4 算法穩定性
在基數排序過程中,每次都是將當前位數上相同數值的元素統一“裝桶”,并不需要交換位置。所以基數排序是穩定的算法。
總結
以上是生活随笔為你收集整理的冒泡排序c++代码_八大排序算法(解释+代码+结果+算法优化)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python打印自动换行如何解决_解决p
- 下一篇: lisp 河道水面线计算_水面漂浮泡沫生