一篇夯實一個知識點系列--python實現十大排序演算法

藥少敏發表於2020-08-09

寫在前面

排序是查詢是演算法中最重要的兩個概念,我們大多數情況下都在進行查詢和排序。科學家們窮盡努力,想使得排序和查詢能夠更加快速。本篇文章用Python實現十大排序演算法。

乾貨兒

排序演算法從不同維度可以分為好多類別,從其排序思想(排序思想一般決定了其時間複雜度的量級)來看,主要可以分為四類:

  • 雙層迴圈比較排序:平方級排序
  • 分治策略比較排序:對數級排序
  • 另闢蹊徑的非比較方式排序:線性級排序
  • 笑死人不償命的其它排序:有著天馬行空的時間複雜度,難以描述。
平方級排序
  • 氣泡排序

    1. 從陣列的第一個元素開始,比較當前元素和下一個元素,如果當前元素大於下一個元素,交換兩元素位置。
    2. 接著從第二個元素開始,重複第一步,直到當前元素為最後一個元素。此時最後一個元素為最大元素。未排序陣列為除最後一個元素之外的其它元素。
    3. 對未排序陣列不斷重複以上步驟,直到未排序陣列為空。
    def bubble_sort(arr):
        length = len(arr)
        for i in range(length):
            for j in range(length-i-1):
                if arr[j] > arr[j+1]:
                    arr[j], arr[j+1] = arr[j+1], arr[j]
        return arr
    
  • 選擇排序

    1. 選取陣列中的最小元素,和陣列中的第一個元素交換位置
    2. 選取陣列中除第一個元素外剩餘元素的最小元素,和陣列中的第二個元素交換位置。
    3. 不斷重複以上步驟,直到當前選取的元素為陣列中最後一個元素。
    def select_sort(arr):
        length = len(arr)
        for i in range(length):
            min_ix = i
            for j in range(i, length):
                if arr[j] < arr[min_ix]:
                    min_ix = j
            arr[min_ix], arr[i] = arr[i], arr[min_ix]
        return arr
    
  • 插入排序

    1. 從陣列的第一個元素開始,不斷比較當前元素和前一個元素。如果當前元素比前一個元素小,那麼就將當前元素插入到前一個元素的前面(即兩者交換位置)
    2. 從第二個元素開始,不斷重複以上步驟,直到所有元素全部經歷上述步驟。
    def insert_sort(arr):
        length = len(arr)
        for i in range(length):
            for j in range(i, 0, -1):
                if arr[j] < arr[j-1]:
                    arr[j], arr[j-1] = arr[j-1], arr[j]
        return arr
    
對數級排序
  • 希爾排序

    1. 選擇一個增量值k,分別將陣列中索引以k為間隔的元素放在同一個陣列中。
    2. 將增量值縮小為原增量值的1/2,然後重複步驟1。
    3. 直到增量值為1,使用插入排序對已經部分有序的陣列進行排序。
    def shell_sort(arr):
        n = len(arr)
        gap = int(n/2)
        while gap > 0: 
            for i in range(gap,n): 
                temp = arr[i] 
                j = i 
                while  j >= gap and arr[j-gap] >temp: 
                    arr[j] = arr[j-gap] 
                    j -= gap 
                arr[j] = temp 
            gap = int(gap/2)
        return arr
    
  • 歸併排序

    1. 以陣列中間元素為界,將陣列分為等長的兩個陣列(可能不等長,和陣列長度的奇偶性有關)。
    2. 對所有陣列執行步驟1
    3. 不斷重複以上步驟,直到將陣列分割為多個包含單個元素的陣列。
    4. 將以上陣列兩兩合併,並排序,此時為多個包含有序的兩個元素的陣列(可能包含單個元素,跟陣列長度的奇偶性有關)。
    5. 重複步驟4,直到將所有陣列合併為一個陣列
    def merge(left, right):
        i = j = 0
        res = []
        while i < len(left) and j < len(right):
            if left[i] < right[j]:
                res.append(left[i])
                i += 1
            else:
                res.append(right[j])
                j += 1
        if i == len(left):
            res.extend(right[j:])
        else:
            res.extend(left[i:])
        return res
    
    def merge_sort(arr):
        if len(arr) <= 1:
            return arr
        length = len(arr)
        i = int(length / 2)
        left = merge_sort(arr[:i])
        right = merge_sort(arr[i:])
        return merge(left, right)
    
  • 快速排序

    1. 挑選一個元素為基準
    2. 比基準大的元素作為一個陣列,比基準小或者等於基準的元素作為一個陣列。
    3. 對新分割的陣列,不斷重複以上步驟,直到分割後的陣列只含有1個或者0個元素
    4. 遞迴地合併以上陣列為有序陣列,合併方式為:[小於等於基準的元素]+[基準]+[大於基準的元素]
    def fast_sort(arr):
        if len(arr) <= 1:
            return arr
        pivot = arr.pop()
        left = [i for i in arr if i <= pivot]
        right = [i for i in arr if i > pivot]
        return fast_sort(left) + [pivot] + fast_sort(right)
    

    以上演算法需要額外的空間,如果我們將小於等於基準的元素不斷置於基準元素之前,大於基準的元素置於基準元素之後,那麼就可以實現不需要額外空間的就地排序。

    def fast_sort_on_extra_spacing(arr):
        l = 0
        h = len(arr)-1
    
        def partition(arr, l, h):
            pivot = arr[h]
            for i in range(l, h):
                if arr[i] <= pivot:
                    arr[l], arr[i] = arr[i], arr[l]
                    l += 1
            arr[h], arr[l] = arr[l], arr[h]
            return l
    
        def fast_sort(arr, l, h):
            if l < h:
                pivot = partition(arr, l, h)
                fast_sort(arr, l, pivot-1)
                fast_sort(arr, pivot+1, h)
            return arr
        return fast_sort(arr, l, h)
    
  • 堆排序

    1. 先對待排序陣列構造大根堆
    2. 將大根堆第一個元素和最後一個元素交換位置。此時最後一個元素為最大元素,待排序陣列為除最後一個元素之外的所有元素。
    3. 對待排序陣列不斷重複以上步驟,直到待排序陣列中只有一個元素。
    def heapify(arr, n, i):
        # build a max root heap
        max_ix = i
        left_i = 2 * i + 1
        right_i = 2 * i + 2
    
        if left_i < n and arr[max_ix] < arr[left_i]:
            max_ix = left_i
        if right_i < n and arr[max_ix] < arr[right_i]:
            max_ix = right_i
        if max_ix != i:
            arr[max_ix], arr[i] = arr[i], arr[max_ix] 
            heapify(arr, n, max_ix)
    
    def heap_sort(arr):
        for i in range(n-1, -1, -1):
            heapify(arr, n, i)
    
        for i in range(n-1, 0, -1):
            arr[i], arr[0] = arr[0], arr[i]
            heapify(arr, i, 0)
        return arr
    
線性級排序

此排序方法只適用於陣列元素全部為整數的情景。

  • 計數排序

    1. 找出待排序陣列中最大的元素,構造一個長度為此元素值的計數陣列。
    2. 遍歷待排序陣列元素,以當前元素為索引,將計數陣列中的對應值加1.
    3. 此時計數陣列中的索引為待排序陣列中的元素,值為出現的次數。將計數陣列中所有值非0的元素索引根據其出現次數串聯起來。
    def count_sort(arr):
        min_ix, max_ix = min(arr), max(arr)
        bucket = [0 for _ in range(max_ix+1)]
        for i in arr:
            bucket[i] += 1
        return sum([[i] * bucket[i] for i in range(len(bucket)) if bucket[i] != 0], [])
    
  • 桶排序

    1. 設定固定數量的桶(這是個技術活兒).
    2. 將待排序陣列中的元素放入對應的桶中(對應關係也是個技術活兒,下面的例子中採用整除)
    3. 將非空桶中的元素串聯起來。
    def bucket_sort(arr):
        min_ix, max_ix = min(arr), max(arr)
        bucket_range = (max_ix - min_ix) / len(arr)
        # +1 avoid for that max_ix - min_ix will raise a IndexError
        temp_bucket = [[] for i in range(len(arr) + 1)]
        for i in arr:
            temp_bucket[int((i-min_ix)//bucket_range)].append(i)
        return sum(temp_bucket, [])
    
  • 基數排序

    1. 找出待排序陣列中最大元素的位數。將所有元素補足此位數,補足方式為前面補0。
    2. 從最低位到最高位,進行多輪陣列排序。
    def radix_sort(arr):
        max_value = max(arr)
        num_digits = len(str(max_value))
        for i in range(num_digits):
            bucket = [[] for _ in range(10)]
            for j in arr:
                bucket[j//(10**i)%10].append(j)
            arr = [j for i in bucket for j in i]
        return arr
    
笑死人不償命排序
  • 睡排序

    讓多個程式(執行緒)分別睡眠待排序陣列中的元素時長,先睡醒的程式(執行緒),對應元素追加到結果陣列中。

  • 猴子排序

    不停隨機排序,然後檢查是否元素全部有序。如果你是歐皇,那麼你可以嘗試用這個排序演算法,很可能一次搞定。

排序演算法複雜度、穩定性及通用性總結
演算法 平均時間複雜度 最優時間複雜度 最壞時間複雜度 空間複雜度 是否原地排序 是否穩定 是否通用
氣泡排序 O(n2) O(n) O(n2) O(1)
選擇排序 O(n2) O(n2) O(n2) O(1)
插入排序 O(n2) O(n) O(n2) O(1)
希爾排序 O(n logn) O(n log2n) O(n log2n) O(1)
歸併排序 O(n logn) O(n logn) O(n logn) O(n)
快速排序 O(n logn) O(n logn) O(n2) O(n logn)
堆排序 O(n logn) O(n logn) O(n logn) O(1)
計數排序 O(n+k) O(n+k) O(n+k) O(k)
桶排序 O(n+k) O(n+k) O(n2) O(n+k)
基數排序 O(n*k) O(n*k) O(n*k) O(n+k)
寫在最後

排序演算法是演算法學習中的核心。掌握排序演算法及其思想是學習其它演算法的基礎。希望大家可以熟練掌握。歡迎關注個人部落格:藥少敏的部落格

相關文章