說到排序算法,就不得不提時間複雜度和穩定性!
其實一直對穩定性不是很理解,今天研究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)