python_排序演算法(冒泡,選擇,插入,快速,歸併)
學習排序演算法的一些記錄。也希望能為大家提供幫助。演算法都用python實現。
排序演算法
大O表示法
1.講排序演算法前先提一下大O表示法.(O(n))
以下是《演算法圖解》中的一點介紹。大O表示法是一種特殊的表示法,指出了演算法的速度有多快。但表示的並非是時間,n指的是操作的次數。O(n)表示的是操作時間的增速。
舉個栗子。假設要在一張紙上畫一個16格的網格。下面我們用兩種演算法來實現。
1.
2.
第一種方式我們要操作16次才能畫出16個格子,而第二種方式我們只要摺疊四次就有16個格子。用大O法表示就是:1.O(n) 16次操作16個格子呈線性關係
2.O(logn) 4次操作得到16個格子 3次8個 2的四次方16 2的三次方8 指數關係
最後補充下常見的一些大O表示方法:
假設每秒我們能操作10次畫10個格子
以上是五種較為常見的大O表示法。接下來進入排序演算法。
1.氣泡排序演算法
1.從左到右依次取兩個數比較,較大的數往後挪接著和後面的數比較
2.這樣子所有的數都比完每一輪都會冒出一個最大數,並且挪到最後那個位置
比如有個列表[7,5,8,3,4]
1.7和5先進行比較 7>5 7的索引和5的索引對換 列表變成[5,7,8,3,4]
2.接下去7和8進行比較7<8 兩個索引都不變
3.8和3進行比較 8>3往後挪 [5,7,3,8,4]
直到8到達最後個位置又從第一個數5和7開始比較,一直迴圈len(list) 次
lists = [7,5 ,9,1,8,3,4,2,6]
n = len(lists) #個數
#氣泡排序 從左到右兩個值做比較
def bubble_sort(lists,n):
for j in range(n,0,-1): #j剩餘沒得出最大值的個數
for i in range(j-1): #從左到右開始比較i為索引
if lists[i]>lists[i+1]:
lists[i],lists[i+1]=lists[i+1],lists[i] #如果前一個數大於後一個數 兩個數位置對調
return lists
print(bubble_sort(lists,n))
如果一個列表剛好是排序完的狀態[1,2,3,4,5,6,7,8,9]
只需要執行最外層的一個迴圈 走完9次就好 表示為O(n)
假設最糟糕的狀況就是[9,8,7,6,5,4,3,2,1]
裡外每一個迴圈都要走滿 9+8+7+6+5+4+3+2+1=81 表示為O(n^2)
所以氣泡排序用大O表示時間介於O(n)-O(n2)之間,不是一個穩定的值
2. 選擇排序
1.選擇排序選擇排序 即每一輪在所有數中找出最大的那個數,然後放到最後
接著找出第二大的,放在末二依次類推。
2.我們可以從第一個數開始,假設第一個數就是最大的,依次與後面的數一個個做比較。比較過程中遇到更大的數就保留,接著用更大的數去比較。
還是剛那個栗子列表[7,5,8,3,4]
假設7是最大的 先與5做比較 7>5 ,接著與下一個數8進行比較7<8,我們取較大的數接下去比較,8接下去和3,4進行比較都大於。最後就是把8的索引和最後一個數4的索引做對換 [7,5,4,3,8]。
注意了 選擇排序可能看上去和氣泡排序很像。但這不同的地方又很明顯。雖然都是兩兩進行比較,但在比較的過程中,選擇排序的索引都不會發生變化。只是選出最大的值,並記錄索引,和最後的一個值做調換。
lists = [7,5,9,1,8,3,4,2,6]
n = len(lists) #個數
#選擇排序 找出最大值放最後
def selection_sort(lists,n):
for j in range(n-1,0,-1): #j表示還需進行的次數也是最後值的索引,第一次進行時最大值應該放在最後j的位置,第二次第二大值應該放j-1位置
maxs = lists[0] #假設最大值是第一個數值
post = 0 #post記錄最大值所在的索引
for i in range(j+1):
if lists[i]>maxs: #當發現更大的值時
maxs = lists[i] #保留更大的值
post = i #並記錄位置
lists[post],lists[j] = lists[j],lists[post] #最大值放到最後
return lists
print(selection_sort(lists,n))
選擇排序 假設的數都要與每個數做比較發現最大值有n個數時n+n-1+n-2+…. 表示為O(n)
3. 插入排序法
1.假設列表中存在一個有序的列表了,然後我們把其他數字代入這個列表和列表中的數值一個個做比較。從而將這個數插入到這個有序的列表中一個合適的位置。
2.可以假設第一個數就是一個有序的列表。(因為就一個數字不存在排序問題就是一個有序的列表) 從第2個數開始分別與第一個數做比較。
3.每次要插入的這個數我們叫做key,key與有序列表的從右到左開始比較,當遇到小於key的數時,key直接放這個數後面。
在列表中[7,5,8,3,4] 假設7是一個已經排序完的有序列表了。 現在把5插入這個有序列表中,5 先與7做比較5<7
所以[5,7,8,3,4],接著將8插入[5,7]這個有序列表中 8與第一個數7比較就大於7 直接放7後面 [5,7,8,3,4]
接著將3插入[5,7,8]這個有序列表中 3<8 接著與7 和5 做比較都小於 就放最前面[3,5,7,8,4] 以此類推
lists = [7,5,9,1,8,3,4,2,6]
n = len(lists) #個數
#插入排序 跟左邊的值一個個做對比
def insertion_sort(lists,n):
for i in range(1,n): #i為有序列表的後的第一個值 ,假設索引0,第一個數為一個有序列表
key = lists[i] #所以key,要插入的值從第二個數開始
j = i #j為要插入值所在的位置 即索引
while lists[j-1]>key: #當有序列表從大到小的數依次與key做比較
lists[j] = lists[j-1] #數比key大時因為有序列表中插入了key所以比較過的數索引往後挪
j = j-1 #有序列表中從右到左一個個數與key做比較所以索引遞減
if j==0: #當j==0時打破迴圈key已經是最小的左邊沒有數了沒必要再比了
break
lists[j] = key #當key大於[j-1]位置的數時key找到合適位置索引為j
return lists
print(insertion_sort(lists,n))
插入排序法 最好的情況就是[1,2,3,4,5,6,7,8,9] 這樣只要外面的大迴圈 裡面的小迴圈while一進去就被打破 表示O(n)
最糟糕的情況就是while每次數都比較了一次 表示O(n^2)
4.快速排序
1.快速排序就是選中一個基準點稱作pivot,然後所有的數與他做比較,比他小的全歸到左邊,比他大的全部歸到右邊。
2.就形成了[小列表]+[pivot]+[大列表] 一個有序的排列 ,然後再對兩個列表重複第一步的操作
3.[[小列表]+[pivot]+[大列表] ]+[pivot]+[[小列表]+[pivot]+[大列表] ] 直到基準點前後只有一個數時 排序就完成了
重複的操作可以用遞迴來完成 所以程式碼並不複雜 貼出兩種寫法 思路不同 第一種就不詳細註釋了 可以看第二種
lists = [7,5,9,1,8,3,4,2,6]
n = len(lists) #個數
#快速排序1
def quick_sort(lists,l,r): #l為left表示左邊的第一個數的索引 r為right右邊最後一個數的索引
i = l
j = r
pivot = lists[round((l+r)/2)] #設最中間這個數為基準
while i<=j:
while lists[i]<pivot:
i+=1
while lists[j]>pivot:
j-=1
if i<=j:
lists[i],lists[j] = lists[j],lists[i]
i+=1
j-=1
if l<j:
quick_sort(lists,l,j)
if i<r:
quick_sort(lists,i,r)
return lists
print(quick_sort(lists,0,n-1))
#快速排序2
def quick_sort2(lists):
if len(lists)<2: #當列表中只有0或一個數時返回結束遞迴,即pivot左或右只有1或0個數
return lists
else:
pivot = lists[0] #就取列表的第一個數作為基準
small = [] #建立兩個列表存放大於基準和小於基準的數
big = []
for i in lists[1:]: #因為第一個數時基準從第二個數開始遍歷
if i<=pivot:
small.append(i)
else:
big.append(i)
lists = quick_sort2(small)+[pivot]+quick_sort2(big) #最後將列表拼接起來。得到的大和小的列表再放入函式中執行排序
return lists
print(quick_sort2(lists))
大O表示快速排序法為 O(nlogn)
比之前幾種排序演算法而言比較快 優化版的氣泡排序
5.歸併排序
先貼個我寫的遞迴函式。要理解歸併,感覺遞迴熟了之後還是很好理解的,也可以藉著學習遞迴鞏固下對遞迴的認識。
https://blog.csdn.net/qq_41239584/article/details/82253771
歸併排序 就像這個名字一樣,把一個個列表合併在一起。怎麼來合併呢,簡單來說就是先拆後拼。
1.先將一個列表分成兩個,兩個分四個 分到不能再分,每一個列表都只有一個元素
2.再將列表一級一級的有序的合併起來,4個一個元素的列表變成 2個有序的2元素列表 再變成一個有序的4元素列表
舉個例子 列表[4,8,9,3,6,5,2,1]
1.將列表從中分開分成
[4,8,9,3] [6,5,2,1]
2.接著分
[4,8] [9,3] [6,5] [2,1] (如果奇數個數字就2,2,2,3分沒事)
3.分到最簡
[4] [8] [9] [3] [6] [5] [2] [1]
3.最簡後開始合併,按照分解的步驟合併,但有序的合併。
[4,8] [3,9] [5,6] [1,2] (第2點怎麼分現在怎麼合,但列表數要排序)
4.按第一部分解的合併
[3,4,8,9] [1,2,5,6]
5.最後兩個列表合併 小的放前面
假設建立一個新列表A[ ]空集合
2個列表都取第一個數開始比,1和3先比1小 先丟進去——A[1]
右邊列表的指標往後移 指向2,2和3比較 2小 丟進去——A[1,2]
右邊列表的指標接著後移,指向5,5和3比較 3小 ——A[1,2,3]
左邊列表的指標向後移動,指向4, 4和5比較 4小 ——A[1,2,3,4]
左邊列表的指標向後移動,指向8, 8和5比較 5小 ——A[1,2,3,4,5]
右邊列表的指標接著後移,指向6,6和8比較 6小 ——A[1,2,3,4,5]
最後右邊列表空了,左邊列表還剩[8,9] 拼在A列表後————A[1,2,3,4,5,6,8,9]
整個過程結束
其實整個過程 就是兩個東西 一直拆,選一箇中點拆成兩邊,拆到不能拆 。有沒有很遞迴
接著一直比較,然後合併1合2 2合4 4合8 比較到數都比完 合併到沒得合
上程式碼
#歸併排序
def merge_sort(lists):
n = len(lists) #列表個數
if n<=1: #遞迴基線條件 當分解到單個數值時結束分解
return lists
mid = n//2 #中間索引取整
left = merge_sort(lists[:mid]) #遞迴分解,從中間開始分解列表 左列表和右列表
right = merge_sort(lists[mid:]) #直到分解成單個數字的列表
left_i,right_i = 0,0 #左右的指標索引設為0
ret = [] #設定一個空列表用來重組
while left_i<len(left) and right_i<len(right): #指標不能超出範圍left_i的最大索引為len(left)-1,所以left_i<len(left)滿足就可以
if left[left_i]<right[right_i]: #按一個小列表到大列表重組的過程,小的先新增進列表就在前面
ret.append(left[left_i]) #這樣每個列表排序成有序列表了
left_i+=1 #當數字新增進後,指標向後走,後面的數接著喝前面較大數比較
else:
ret.append(right[right_i])
right_i+=1
ret += left[left_i:] #撿漏! 剩下較大的未參與比較的直接補到列表後面
ret += right[right_i:] #A列表+空列表還是=A列表沒變化
return ret #返回最後的列表
ret = merge_sort(lists)
print('歸併排序:',ret)
上個手工的圖理解下:
呼叫棧的分解往下分解 最後由下到上傳回返回排序完的有序列表。
最後用大O表示下 O(nlogn) 縱向 2 4 8 。2的次方關係 橫向 n的關係 n*logn.
各個排序演算法的時間複雜度和空間複雜度彙總。最常用的還是快速排序。
寫的第一篇。希望大家能提出寶貴意見。
keep on coding