說到排序算法,就不得不提時間復雜度和穩定性!
其實一直對穩定性不是很理解,今天研究python實現排序算法的時候突然有了新的體會,一定要記錄下來
穩定性:
穩定性指的是 當排序碰到兩個相等數的時候,他們的順序會不會發生交換。其實對於一個整數數列的排序,是否交換元素沒有任何影響。
但是: 如果有這樣一串二元組: ( 2, 5) (1 ,6 ) ( 2 , 8 )
我們要優先按照第一個元素排序,如果相同再按照第二個元素排序
我們先按照第二個元素進行排序之后再按照第一個元素進行排序,
里面有兩個元組第一個數據都是2 如果是穩定的,那么第二個元素順序不會發生改變,,如果不穩定,,那可能就悲劇了,,會讓我們的數據排出不是我們想要的結果。
時間復雜度:
教科書和網絡中流傳的概念我覺着實在是有些難理解。我來談一談自己的想法。
對於一個算法程序,我們可以把每進行一次操作的時間記為一個時間單元,即 一次加法操作是一個時間單元,一次邏輯判斷是一個時間單元
當我們一次算法的執行時間以時間單元為單位,就能得到一個時間函數 T(n)= balbala(n) ,其中n一般代表一個傳入的數據,決定了循環的次數或者遞歸次數,T是關於n的函數。balbala是我想用它來指代一個函數的算式
比如在c++語言中一個循環: count = 0; for( int i = 0; i<n ;i++ ){ count++; }
1 count = 0是一個賦值操作,算作一個單元時間
2 for循環一開始會執行int i = 0,算作一個單元時間,
3 之后顯然會執行n次循環,在每次循環當中 count++、i++和i<n 三個時間單元操作,
所以對於這個for循環 T(n)= 1 + 1 + n*(3) = 3*n+2
比如可能某個函數 T(n) = n^2 + n + 1
類似這樣的時間函數,都是 總時間T 和 不確定的決定循環或者遞歸次數的n 的關系
時間復雜度則是保留T(n)的最高階,把其他低階都忽略掉,然后用大O來表示
比如 T(n) = n^2 + n + 1 的時間復雜度是 O(n^2)
T(n) = n + 20 的時間復雜度是O(n)
為什么要只保留最高階忽略低階呢??有過初中高中數學基礎的伙伴們你們會明白,畫出函數圖像,當n從0增長到正無窮的過程中,對增長速度影響最大的是最高階,低階其實基本可以忽略的。
此時我們要對時間復雜度有更深一步的理解,時間復雜度體現的是隨着n增大的過程中算法消耗時間增加的速率,也就是隨着n增大,算法時間增大的速度
時間復雜度大的算法,函數圖像一定比時間復雜度小的算法圖像更陡
但是!!!!!時間復雜度大的算法並不一定比時間復雜度小的算法執行時間長!
當我們忽略低階的時候,可以理解成,我們忽略了循環當中的好多個單元操作的時間,只保留了循環主導了時間。
但是如果一個兩層循環里面有1個時間單元操作 時間復雜度正常情況是O(n^2)
另一個一層循環元里面10個單元操作 時間復雜度是O(n)
這兩個算法耗時完全要看循環次數了,如果都是只循環一次,很顯然里面只有1個單元操作的家伙耗時更短(它的時間復雜度是較大的)。
再次總結:時間復雜度比較的是隨着n增大,耗時增加的速度,而不是單純比較兩個算法誰耗時更大。
怎么樣!!!好像非常有道理的樣子!!!有木有被這帥氣的妖風嚇一大跳!!我聽到大神伙伴給我講這些的時候真的是感覺學到好多知識!!
謝謝我身邊的大神伙伴咯~
嘻嘻 希望對大家有所幫助吧
接下來分享一下我利用python實現的排序算法
冒泡排序:
對於一個n個數的列表,從第一個數開始,一直到第n-1個數,每個數和它挨着的下一個數比較,如果他下一個數更小,他們就交換數值,否則不交換。以上叫做一趟排序。經歷一趟排序之后,可以保證最后一個數一定是整個數列中最大的。
之后 從第一個數開始 到第n-2個數,每個數和下一個數比較(因為第n個數經歷第一趟之后一定是最大的,所以不用再用第n-1個數跟第n個數比),如果下一個數大,他們就交換數值,否則不交換。經過第二趟比較,數列的倒數兩個數一定滿足最終的條件,是倒數第二大的和最大的。
這樣一直一趟一趟的比,n個數的列表,經過n-1趟排序之后一定能保證正確的順序。
一個數列有n個數,
第一趟排序需要比較前n-1個數每個數和后面一個數,使最大的數到最后一個位置,發生n-1次比較
第二趟排序需要比較前n-2個數,每個數后面的一個數,使第二大的數到倒數第二個位置,發生n-2次比較
。。。
第i趟排序需要比較前n-i個數,每個數和后面的一個數,使第i大的數到倒數第i的位置上,發生n-i次比較
最后一次就是第一個數和第二個數比較。
經歷n-1次比較,數列一定會變成正確的順序。
最優時間復雜度: O(n)
最壞時間復雜度: O(n^2)
穩定性:穩定的排序
1 #傳入li列表
2 def bubble_sort( li ): 3 n = len(li) #數列里有n個數,下標從0到n-1
4 #一共會發生n-1趟排序過程,限制排序次數,i 從0到n -1 ,記錄當前是第i+1次排序
5 for i in range( n-1 ): 6 # 每次排序都會確定一個前面最大的數放在最后,
7 # i從0到n-1,所以經歷第i+1次排序之后最后的i+1個數是正確的,只需要把前n-(i+1) 個數進行比較挑換就可以
8 for j in range( n - i - 1): 9 #如果我后面的哥們比我數小,我倆就換數值
10 if li[j]>li[j+1]: 11 li[j] , li[j+1] = li[j+1], li[j] 12
13 if __name__ == '__main__': 14 li = [5,4,3,2,1] 15 bubble_sort(li) 16 print(li)
選擇排序:
對於一個有n個數的數列,
第一次我們從第一個數開始選出所有數中最小的,和第一個數交換數值,這樣保證第一個數是最小的
第二次我們從第二個數開始選出第一個數之后最小的數和第二個數交換數值,這樣前兩個數都在正確位置
。。。
最后一次 我們拿倒數第二個數跟最后一個數比較,把小的放在前面
第一次我們從第1個數開始一直到最后找最小的數
第二次我們從第2個數開始到最后找最小的數
。。。
第i次我們從第i個數開始向后找最小的數
最優時間復雜度:O(n^2)
最壞時間復雜度:O(n^2)
穩定性:不穩定的排序 例如: 5 8 5 2 第一趟排序5和2互換,那么兩個5順序就改變了
1 def select_sort(li): 2 n = len(li) #li列表中有n個數,下標從0到n-1
3 # i從0到n-1 ,我們每次拿下標為i的數跟后面數比較 標記最小的數
4 for i in range( n ): 5 temp = i #用temp做臨時標記,沒遇見比下標temp更小的數,就用temp標記更小的數的下表
6 # 從temp開始向后找到最后 找最小的數
7 for j in range( temp , n ): 8 #如果我們遇到比temp標記的數更小的,tamp就標記更小的數的下標
9 if li[temp] > li[j] : 10 li[temp] ,li[j] = li[j] , li[temp] 11 #這次for循環之后 temp一定標記了i之后的最小的數的下標,我們把最小的數和i位置進行呼喚
12 li[i] , li[temp] = li[temp] , li[i] 13
14 if __name__ == '__main__': 15 li = [5,4,3,2,1] 16 select_sort(li) 17 print(li)
插入排序:
對於一個n個數的數列:
拿出第二個數,跟第一個比較,如果第二個大,第二個就放在第一個后面,否則放在第一個前面,這樣前兩個數就是正確順序了
拿出第三個數,跟第二個數比較,如果比第二個數小,就放在第二個數前面,再跟第一個數比,比第一個小就放在第一個前面,這樣前三個數就是正確順序
....
拿出最后一個數,跟之前的正確順序進行比較,插入到合適的位置當中。
可以理解成: 每次拿到一個數,他前面都是一個有序的數列,然后,找到我何時的位置,我插入進去
最壞時間復雜度: O(n^2)
最優時間復雜度: O(n)
穩定性:穩定的排序
1 def select_sort(li): 2 n = len(li) #li列表中有n個數,下標從0到n-1 3 # i從0到n-1 ,我們每次拿下標為i的數跟后面數比較 標記最小的數 4 for i in range( n ): 5 temp = i #用temp做臨時標記,沒遇見比下標temp更小的數,就用temp標記更小的數的下表 6 # 從temp開始向后找到最后 找最小的數 7 for j in range( temp , n ): 8 #如果我們遇到比temp標記的數更小的,tamp就標記更小的數的下標 9 if li[temp] > li[j] : 10 temp = j 11 #這次for循環之后 temp一定標記了i之后的最小的數的下標,我們把最小的數和i位置進行互換 12 li[i] , li[temp] = li[temp] , li[i] 13 14 if __name__ == '__main__': 15 li = [5,4,3,2,1] 16 select_sort(li) 17 print(li)
希爾排序:
是對插入排序的升級的版本。(插入排序: 每次拿一個數在前面有序的數列中找到位置進行插入 )
設置一個步長,一般為數列長度的一半。
把數列按照步長分組,每組相同位置的數據進行選擇排序,
然后再把步長減小后分組,每組的相同位置元素進行選擇排序
一直步長減小到1位置,進行選擇排序
此時,數列順序就排好了
舉個例子: 對於數列 8 7 6 5 4 3 2 1
8個元素,我們按4為步長進行分組 分成了 8 7 6 5 4 3 2 1
之后 每組相同位置元素進行插入排序,即把
第一趟 8 4 (每組第一個位置)插入排序 整個數列變成: 4 7 6 5 8 3 2 1
第二趟 7 3 (每組第二個位置)插入排序 整個數列變成: 4 3 6 5 8 7 2 1
第三趟 6 2 (每組第三個位置) 進行插入排序后: 4 3 2 5 8 7 6 1
第四趟 5 1 (每組第四個位置) 進行插入排序后: 4 3 2 1 8 7 6 5
之后步長改為2 進行分組 4 3 2 1 8 7 6 5
第一趟 4 2 8 6(每組第一個數)進行插入排序后整個數列: 2 3 4 1 6 7 8 5
第二趟 3 1 7 5(每組第二個數)進行插入排序后整個數列: 2 1 4 3 6 5 8 7
上述過程是便於理解,但是對於步長為2分組后,實際上的過程是: 4 3 2 1 8 7 6 5
第一次 4 2 比 結果是 2 3 4 1 8 7 6 5
第二次 3 1 比 結果是 2 1 4 3 8 7 6 5
第三次 4 8 比 結果是 2 1 4 3 8 7 6 5
。。。。持續下去
實際上是按下標遞增的方式進行的,但是每次只與它相鄰組同位置元素進行比較,
分組之后,每組相同位置元素不是一次進行插入排序結束再進行第二個相同位置排序
比較順序 是按照下表遞增的方式進行的,但是比較的數據不是自己相鄰的,而是相鄰組相同位置的數據
之后 步長改為1 進行分組 每個元素一組 進行插入排序: 1 2 3 4 5 6 7 8
優點:設置步長,似的后邊較大的元素能快速移動到前面來,前面大元素能快速移動到后面,省去了很多次比較的過程。
時間復雜度: O(n)< x < O(n^2)
穩定性: 不穩定的排序 按步長分組的時候,如果存在兩個相等的數分在不同組里,插入排序后有可能本來在前面的數到后面去了
1 def shell_sort(li ): 2 n = len(li) #li列表中有n個數 下標從0到n-1
3 gep = n//2 #設置初始步長進行分組
4 #當步長大於等於1的時候 一直進行分組的插入排序
5 while gep >= 1 : 6 # 從gep的元素往后 一個一個元素對前面所有組跟自己處於相同位置的數進行插入排序
7 for j in range( gep , n ): 8 i = j #標記下當前j的位置給i
9 # 這層循環是為了回跳i所在位置,
10 # 當一個新元素對前組同位置元素交換后,當前元素還需要跟再往前組的同位置元素比較決定是不是要交換
11 # i - gep 能看我當前是不是第一組位置,如果是第一組位置,我前面沒有組了,就不循環了,
12 # 如果我是第三組,我會跳到前二組再跟第一組的相同位置元素進行比較
13 while i - gep >= 0 : 14 #如果當前數比前一組同位置數小,就交換數值
15 if li[i] < li[i-gep] : 16 li[i] , li[i-gep] = li[i-gep] , li[i] 17 #如果我和前一組同位置元素交換了,我需要繼續與再往前一組同位置元素比較是否需要交換
18 #這個操作是把我當前位置回跳到上一組的同位置
19 i -= gep 20 #如果沒有發生數據交換,說明前面的不用再比較了,前面的一定都比我當前數小,跳出回跳位置的循環
21 else : 22 break
23 #修改步長 縮小步長
24 gep//=2
25
26 if __name__ == '__main__': 27 li = [5,4,3,2,1] 28 shell_sort(li) 29 print(li)