1. 程式人生 > >python常見排序算法解析

python常見排序算法解析

發生 適合 我會 由於 是我 一位 一次 優化 所有

算法是程序員的靈魂。
下面的博文是我整理的感覺還不錯的算法實現
原理的理解是最重要的,我會常回來看看,並堅持每天刷leetcode
本篇主要實現九(八)大排序算法,分別是冒泡排序,插入排序,選擇排序,希爾排序,歸並排序,快速排序,堆排序,計數排序。希望大家回顧知識的時候也能從我的這篇文章得到幫助。

概述

十種常見排序算法可以分為兩大類:

  1. 非線性時間比較類排序:通過比較來決定元素間的相對次序,由於其時間復雜度不能突破O(nlogn),因此稱為非線性時間比較類排序。
  2. 線性時間非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排序的時間下界,以線性時間運行,因此稱為線性時間非比較類排序。

基礎定義

  • 穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。
  • 不穩定:如果a原本在b的前面,而a=b,排序之後 a 可能會出現在 b 的後面。
  • 時間復雜度:對排序數據的總的操作次數。反映當n變化時,操作次數呈現什麽規律。
  • 空間復雜度:是指算法在計算機內執行時所需存儲空間的度量,它也是數據規模n的函數。

圖示
技術分享圖片

為了防止誤導讀者,本文所有概念性內容均截取自對應Wiki。

冒泡排序

原理

冒泡排序(Bubble Sort)是一種簡單的排序算法。它重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。
技術分享圖片

步驟

冒泡排序算法的運作如下:

  1. 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
  2. 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
  3. 針對所有的元素重復以上的步驟,除了最後一個。
  4. 持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。

代碼

def bubble_sort(list):
    length = len(list)
    # 第一級遍歷
    for index in range(length):
        # 第二級遍歷
        for j in range(1, length - index):
            
if list[j - 1] > list[j]: # 交換兩者數據,這裏沒用temp是因為python 特性元組。 list[j - 1], list[j] = list[j], list[j - 1] return list

這種排序其實還可以稍微優化一下,添加一個標記,在排序已完成時,停止排序。

def bubble_sort_flag(list):
    length = len(list)
    for index in range(length):
        # 標誌位
        flag = True
        for j in range(1, length - index):
            if list[j - 1] > list[j]:
                list[j - 1], list[j] = list[j], list[j - 1]
                flag = False
        if flag:
            # 沒有發生交換,直接返回list
            return list
    return list

選擇排序

原理

選擇排序(Selection sort)是一種簡單直觀的排序算法。它的工作原理大致是將後面的元素最小元素一個個取出然後按順序放置。
技術分享圖片

步驟

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,
  2. 再從剩余未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。
  3. 重復第二步,直到所有元素均排序完畢。

代碼

def selection_sort(list):
    n=len(list)
    for i in range (0,n):
        min = i
        for j in range(i+1,n):
            if list[j]<list[min]:
                min=j
                list[min],list[i]=list[i],list[min]
    return list

插入排序

原理

插入排序(Insertion Sort)是一種簡單直觀的排序算法。它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。
技術分享圖片

步驟

  1. 從第一個元素開始,該元素可以認為已經被排序
  2. 取出下一個元素,在已經排序的元素序列中從後向前掃描
  3. 如果該元素(已排序)大於新元素,將該元素移到下一位置
  4. 重復步驟3,直到找到已排序的元素小於或者等於新元素的位置
  5. 將新元素插入到該位置後
  6. 重復步驟2~5

代碼

def insert_sort(list):
    n = len(list)
    for i in range(1, n):
        # 後一個元素和前一個元素比較
        # 如果比前一個小
        if list[i] < list[i - 1]:
            # 將這個數取出
            temp = list[i]
            # 保存下標
            index = i
            # 從後往前依次比較每個元素
            for j in range(i - 1, -1, -1):
                # 和比取出元素大的元素交換
                if list[j] > temp:
                    list[j + 1] = list[j]
                    index = j
                else:
                    break
            # 插入元素
            list[index] = temp
    return list

希爾排序

原理

希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序算法。
希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
插入排序在對幾乎已經排好序的數據操作時,效率高,即可以達到線性排序的效率
但插入排序一般來說是低效的,因為插入排序每次只能將數據移動一位。
技術分享圖片

步驟

每次以一定步長(就是跳過等距的數)進行排序,直至步長為1.

代碼

def shell_sort(list):
    n = len(list)
    # 初始步長
    gap = n // 2
    while gap > 0:
        for i in range(gap, n):
            # 每個步長進行插入排序
            temp = list[i]
            j = i
            # 插入排序
            while j >= gap and list[j - gap] > temp:
                list[j] = list[j - gap]
                j -= gap
            list[j] = temp
        # 得到新的步長
        gap = gap // 2
    return list

步長使用的是Donald Shell的建議,另外步長還可以使用Sedgewick提出的(1, 5, 19, 41, 109,…)。
也可以使用斐波那契數列除去0和1將剩余的數以黃金分區比的兩倍的冪進行運算得到的數列。

歸並排序

原理

歸並操作(歸並算法),指的是將兩個已經排序的序列合並成一個序列的操作。歸並排序算法依賴歸並操作。
技術分享圖片

步驟

1.叠代法

  1. 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並後的序列
  2. 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
    3.比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
  3. 重復步驟3直到某一指針到達序列尾
  4. 將另一序列剩下的所有元素直接復制到合並序列尾

遞歸法

假設序列共有n個元素:

  1. 將序列每相鄰兩個數字進行歸並操作,形成 {\displaystyle floor(n/2)} floor(n/2)個序列,排序後每個序列包含兩個元素
  2. 將上述序列再次歸並,形成 {\displaystyle floor(n/4)} floor(n/4)個序列,每個序列包含四個元素
  3. 重復步驟2,直到所有元素排序完畢

代碼

# 遞歸法
def merge_sort(list):
    # 認為長度不大於1的數列是有序的
    if len(list) <= 1:
        return list
    # 二分列表
    middle = len(list) // 2
    left = merge_sort(list[:middle])
    right = merge_sort(list[middle:])
    # 最後一次合並
    return merge(left, right)
# 合並
def merge(left, right):
    l,r=0,0
    result=[]
    while l<len(left) and r<len(right):
        if left[l] <right[r]:
            result.append(left[l])
            l+=1
        else:
            result.append(right[r])
            r +=1
        reslut +=left[l:]
        result+=right[r:]                
    return result

鄙人不才,不知歸並排序的叠代法如何用Python實現,望指教。

快速排序

原理

快速排序使用分治法(Divide and conquer)策略來把一個序列(list)分為兩個子序列(sub-lists)。
技術分享圖片

步驟

  1. 從數列中挑出一個元素,稱為”基準”(pivot),
  2. 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區結束之後,該基準就處於數列的中間位置。這個稱為分區(partition)操作。
  3. 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。

代碼

普通版

def quick_sort(list):
    less = []
    pivotList = []
    more = []
    # 遞歸出口
    if len(list) <= 1:
        return list
    else:
        # 將第一個值做為基準
        pivot = list[0]
        for i in list:
            # 將比急轉小的值放到less數列
            if i < pivot:
                less.append(i)
            # 將比基準打的值放到more數列
            elif i > pivot:
                more.append(i)
            # 將和基準相同的值保存在基準數列
            else:
                pivotList.append(i)
        # 對less數列和more數列繼續進行排序
        less = quick_sort(less)
        more = quick_sort(more)
        return less + pivotList + more

咳咳,下面這段代碼出自《Python cookbook 第二版》傳說中的三行實現python快速排序。

def qsort(arr):
    if len(arr) <= 1:
        return arr
    else:
        pivot = arr[0]
        less = [x for x in arr[1:] if x < pivot]
        greater = [x for x in arr[1:] if x >= pivot]
        return qsort(less) + [pivot] + qsort(greater)

當然還有一行語法糖版本:

qs = lambda xs : ( (len(xs) <= 1 and [xs]) or [ qs( [x for x in xs[1:] if x < xs[0]] ) + [xs[0]] + qs( [x for x in xs[1:] if x >= xs[0]] ) ] )[0]

是不是感受到了Python的魅力?

堆排序

原理

堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
技術分享圖片

步驟

  1. 創建最大堆:將堆所有數據重新排序,使其成為最大堆
  2. 最大堆調整:作用是保持最大堆的性質,是創建最大堆的核心子程序
  3. 堆排序:移除位在第一個數據的根節點,並做最大堆調整的遞歸運算

代碼

def heap_sort(list):
    # 創建最大堆
    for start in range((len(list) - 2) // 2, -1, -1):
        sift_down(list, start, len(list) - 1)

    # 堆排序
    for end in range(len(list) - 1, 0, -1):
        list[0], list[end] = list[end], list[0]
        sift_down(list, 0, end - 1)
    return list

# 最大堆調整
def sift_down(lst, start, end):
    root = start
    while True:
        child = 2 * root + 1
        if child > end:
            break
        if child + 1 <= end and lst[child] < lst[child + 1]:
            child += 1
        if lst[root] < lst[child]:
            lst[root], lst[child] = lst[child], lst[root]
            root = child
        else:
            break

計數排序

原理

當輸入的元素是n個0到k之間的整數時,它的運行時間是Θ(n + k)。計數排序不是比較排序,排序的速度快於任何比較排序算法。

由於用來計數的數組C的長度取決於待排序數組中數據的範圍(等於待排序數組的最大值與最小值的差加上1),這使得計數排序對於數據範圍很大的數組,需要大量時間和內存。例如:計數排序是用來排序0到100之間的數字的最好的算法,但是它不適合按字母順序排序人名。但是,計數排序可以用在基數排序算法中,能夠更有效的排序數據範圍很大的數組。
技術分享圖片

步驟

  1. 找出待排序的數組中最大和最小的元素
  2. 統計數組中每個值為i的元素出現的次數,存入數組 C 的第 i 項
  3. 對所有的計數累加(從C中的第一個元素開始,每一項和前一項相加)
  4. 反向填充目標數組:將每個元素i放在新數組的第C(i)項,每放一個元素就將C(i)減去1

代碼

def count_sort(list):
    min = 2147483647
    max = 0
    # 取得最大值和最小值
    for x in list:
        if x < min:
            min = x
        if x > max:
            max = x
    # 創建數組C
    count = [0] * (max - min +1)
    for index in list:
        count[index - min] += 1
    index = 0
    # 填值
    for a in range(max - min+1):
        for c in range(count[a]):
            list[index] = a + min
            index += 1
    return list

第九種排序

None?
當然不會
自然就是系統自帶的

list.sort()

python常見排序算法解析