冒泡排序和选择排序区别_你以为只是简单的排序?(一)
一直在猶豫要不要寫排序的文章,因為真的爛大街了。可是一旦細看,還真是很多值的思考的地方,所以還是選擇記錄一下
以下完整代碼,均可從這里獲取
https://github.com/Rain-Life/data-struct-by-go/tree/master/sort排序算法效率分析
了解如何分析一個排序算法,可以幫助我們在實際工作場景中選擇合適的排序算法,比如,如果排序的數據比較少,可以選擇冒泡或插入排序,如果排序的數據量較大,選擇歸并或快速排序,雖然它們兩兩的時間復雜度是相同的,但是還是有很大的區別的,下邊會對它們做對比
排序算法執行效率
一般分析一個排序算法的復雜度,我們都是去分析它的時間復雜度,時間復雜度反應的是數據規模n很大的時候的一個增長趨勢。所以,通常在分析時間復雜度的時候會忽略到「系數、常數、低階」。但是,在實際開發場景中,可能我們排序的數據并不多,因此,在對排序算法進行分析的時候,還是需要將系數、常數、低階也考慮進來
分析一個排序算法的時間復雜度的時候,通常會分析它的「最好情況、最壞情況以及平均情況下的時間復雜度」。因為對于要排序的數據,它的有序度,對排序算法的執行時間是有影響的,所以,要想選擇最合適的排序算法,這些情況的時間復雜度都應該考慮到(其實不光是排序,在實現任何一個算法的時候,當有多種方式可供選擇的時候,都應該分析多重情況下的時間復雜度)
下邊要分享的三個排序算法都是基于比較的排序算法,基于比較的排序算法的在執行過程中,一般涉及兩種操作,一個是比較大小,一個是數據交換。因此,在對比這幾種排序算法的時候,「比較次數和移動次數」也應該考慮進去。這也是為什么基于比較排序的算法,我們通常不會選擇冒泡排序,而選擇插入排序
排序算法內存消耗
「算法的內存消耗可以通過空間復雜度來衡量」。針對排序算法的空間復雜度,有一個新的概念是「原地排序」。原地排序算法,就是特指空間復雜度是O(1)的排序算法。下邊要分享的三種排序算法,都是原地排序算法
排序算法穩定性
只靠執行效率和內存消耗來衡量排序算法的好壞是不夠的。針對排序算法,還有一個重要的度量指標,「穩定性」。意思是,「如果待排序的序列中存在值相等的元素,經過排序之后,相等元素之間原有的先后順序不變」
如:1、8、6、5、5、7、2、3,按照大小排序之后是:1、2、3、5、5、6、7、8
這組數據里有兩個5。經過某種排序算法排序之后,如果兩個5的前后順序沒有改變,那我們就把這種排序算法叫作「穩定的排序算法」;如果前后順序發生變化,那對應的排序算法就叫作「不穩定的排序算法」
冒泡排序
冒泡排序優化
冒泡排序的實現思想,相信大家都非常的熟悉了
每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關系要求。如果不滿足就讓它倆互換。「一次冒泡會讓至少一個元素移動到它應該在的位置」,重復n次,就完成了n個數據的排序工作
實際上,冒泡過程還可以優化。當某次冒泡操作已經沒有數據交換時,說明已經達到完全有序,不用再繼續執行后續的冒泡操作。如圖
下邊是優化后的代碼:
func BubbleSort(arr []int) {flag := falsen := len(arr)for i := 0; i < n; i++ {flag = false//如果某一次冒泡,沒有出現數據交換,說明已經有序,不用再繼續冒泡了for j := 0; j < n-i-1; j++ {if arr[j] > arr[j+1] {tmp := arr[j]arr[j] = arr[j+1]arr[j+1] = tmpflag = true}}if !flag {break}}for _, v := range arr {fmt.Printf("%vt", v)} }冒泡排序算法分析
首先「冒泡排序是一個原地排序算法」,因為冒泡排序只涉及相鄰數據的交換,需要常量級的臨時空間,所以空間復雜度是O(1)
在冒泡排序中,只有交換才可以改變兩個元素的前后順序。為了保證冒泡排序算法的穩定性,當有相鄰的兩個元素大小相等的時候,我們不做交換,相同大小的數據在排序前后不會改變順序,所以「冒泡排序是穩定的排序算法」
在最好的情況下,也就是待排數據是完全有序的,那只需要進行一次冒泡操作即可,所以「最好情況下的時間復雜度是O(n)」
最壞情況下是待排數據是完全無序的,這個時候就需要n次冒泡,所以「最壞情況下的時間復雜度是O(n^2)」
平均情況下的時間復雜度的分析涉及比較復雜的推導,不是這里的重點(我也不會,手動狗頭。如果你想了解,可以看這里),冒泡排序算法的平均時間復雜度是O(n^2)
插入排序
插入排序思想
插入排序是如何來的?假設現在有一個有序的數組,讓你往里邊插入一個數據之后,保持數組是有序的。我們都會想到通過遍歷來找到待插入數據的位置,然后進行數據的移動。通過這種方式就可以保證這個數組有序。借鑒上邊這種插入方法,于是就有了插入排序
插入排序的思想是:「將待排序的數組分成兩個區間,有序區和無序區。剛開始的時候,有序區只有第一個元素。插入排序的過程就是每次從無序區中取出一個元素,放入到有序區中對應的位置,保證插入到有序區中之后,有序區依然是有序的。不斷的重復這個過程,直到無序區為空」
文字描述比較抽象,見下圖(從小到大排序)
插入排序也是包含兩種操作,一種是比較,一種是移動。下邊是代碼實現
func InsertSort(arr []int) {if len(arr) <= 0 {fmt.Println("待排數據不合法")}n := len(arr)for i := 1; i < n; i++ {//i是待排區的元素value := arr[i]j := i-1for ; j >= 0; j-- { //j遍歷的是已排區的每一個元素if arr[j] > value {arr[j+1] = arr[j] //如果滿足條件,將前一個值賦給后邊這個} else {break}}arr[j+1] = value}for _, v := range arr {fmt.Printf("%vt", v)} }插入排序算法分析
插入排序也不需要額外的存儲空間,空間復雜度是O(1),所以它是「原地排序算法」
在插入排序中,對于值相同的元素,我們可以選擇將后面出現的元素,插入到前面出現元素的后面,這樣就可以保持原有的前后順序不變,所以插入排序是「穩定的排序算法」
如果待排序的數據是完全有序的,并不需要搬移任何數據。如果從尾到頭在有序數據組里面查找插入位置,每次只需要比較一個數據就能確定插入的位置。所以這種情況下,最好是時間復雜度為O(n)。注意,「這里是從尾到頭遍歷已經有序的數據」
如果數組是倒序的,每次插入都相當于在數組的第一個位置插入新的數據,所以需要移動大量的數據,所以「最壞情況時間復雜度為O(n^2)」。「平均時間復雜度也是O(n^2)」
選擇排序
選擇排序思想
選擇排序的思想和插入排序的思想有些類似,選擇排序是每次從無序區中選擇一個最小的元素放入到有序區中,具體如圖:
代碼實現如下
func SelectionSort(arr []int) {n := len(arr)if n <= 0 {fmt.Println("待排數據不合法")}for i := 0; i < n - 1; i++ {for j := i+1; j < n ; j++ {if arr[i] > arr[j] {arr[i],arr[j] = arr[j], arr[i]}}}for _, v := range arr {fmt.Printf("%vt", v)} }選擇排序算法分析
選擇排序的空間復雜度也是O(1),是原地排序算法。選擇排序的最好情況和最壞情況的時間復雜度都是O(n^2),這個很簡單,看一下它的執行過程就知道了
「選擇排序不是一個穩定排序」,選擇排序每次都要找剩余未排序元素中的最小值,并和前面的元素交換位置,這樣破壞了穩定性
比如7,3,5,7,1,9 這樣一組數據,使用選擇排序算法來排序的話,第一次找到最小元素1,與第一個7交換位置,那第一個7和中間的7順序就變了,所以就不穩定了
這樣一看,選擇排序和前邊兩個排序算法相比就差一些了
三種排序算法對比
這里就先不提選擇排序了,因為和前兩種相比,它明顯遜色一些
冒泡排序不管怎么優化,元素交換的次數是一個固定值。插入排序是同樣的,不管怎么優化,元素移動的次數也是一個固定值。但是,從冒泡和插入排序的代碼上看,冒泡排序的數據交換次數比插入排序要復雜,冒泡排序需要3個賦值操作,而插入排序只需要1個
冒泡排序的交換操作 if arr[j] > arr[j+1] {tmp := arr[j]arr[j] = arr[j+1]arr[j+1] = tmpflag = true }選擇排序的交換操作 if arr[j] > value {arr[j+1] = arr[j] }假設把一次賦值操作的時間算作一個單位時間,那冒泡排序的交換操作需要3個單位時間,而插入排序只需要1個單位時間
如果需要排序的數據比較少,可能這樣的差別可以忽略不計,但是數據多起來之后,插入排序節省的時間還是比較明顯的
這三種時間復雜度為 O(n2) 的排序算法中,冒泡排序、選擇排序,可能就純粹停留在理論的層面了,學習的目的也只是為了開拓思維,實際開發中應用并不多,但是插入排序還是挺有用的。有些編程語言中的排序函數的實現原理會用到插入排序算法
總結
以上是生活随笔為你收集整理的冒泡排序和选择排序区别_你以为只是简单的排序?(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用python开启相机_如何用Pytho
- 下一篇: lstm原始论文_有序的神经元——ON-