1. 程式人生 > >python_排序演算法(冒泡,選擇,插入,快速,歸併)

python_排序演算法(冒泡,選擇,插入,快速,歸併)

學習排序演算法的一些記錄。也希望能為大家提供幫助。演算法都用python實現。

排序演算法

大O表示法

1.講排序演算法前先提一下大O表示法.(O(n))
以下是《演算法圖解》中的一點介紹。大O表示法是一種特殊的表示法,指出了演算法的速度有多快。但表示的並非是時間,n指的是操作的次數。O(n)表示的是操作時間的增速。
大O表示法
舉個栗子。假設要在一張紙上畫一個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