Python實現經典排序算法,再也不怕面試官讓我手寫快排了!

2021-03-02 Crossin的編程教室

大家好,歡迎來到Crossin的編程教室!

算法作為程式設計師的必修課,是每位程式設計師必須掌握的基礎。本篇我們將通過Python來實現5大經典排序算法,結合例圖剖析內部實現邏輯,對比每種算法各自的優缺點和應用點。相信我,耐心看完絕對有收穫。

準備工作

大家都知道從理論上講,我們一般會使用大O表示法測量算法的運行時複雜度"大O表示法"表示程序的執行時間或佔用空間隨數據規模的增長趨勢。

但為了測算具體的時間,本篇將使用timeit模塊來衡量實現的運行時間。下面自己寫一個對算法測試時間的函數。

from random import randint
from timeit import repeat

def run_sorting_algorithm(algorithm, array):
    # 調用特定的算法對提供的數組執行。
    # 如果不是內置sort()函數,那就只引入算法函數。
    setup_code = f"from __main__ import {algorithm}" \
        if algorithm != "sorted" else ""

    stmt = f"{algorithm}({array})"

    # 十次執行代碼,並返回以秒為單位的時間
    times = repeat(setup=setup_code, stmt=stmt, repeat=3, number=10)

    # 最後,顯示算法的名稱和運行所需的最短時間
    print(f"Algorithm: {algorithm}. Minimum execution time: {min(times)}")

這裡用到了一個騷操作,通過f-strings魔術方法導入了算法名稱,不懂的可以參考:Python 格式化字符串的最佳姿勢

注意:應該找到算法每次運行的平均時間,而不是選擇單個最短時間。由於系統同時運行其他進程,因此時間測量是受影響的。最短的時間肯定是影響最小的,是這樣才使其成為算法時間最短的。

Python中的冒泡排序算法

冒泡排序是最直接的排序算法之一。它的名稱來自算法的工作方式:每經過一次新的遍歷,列表中最大的元素就會「冒泡」至正確位置。

在Python中實現冒泡排序

這是Python中冒泡排序算法的實現:

def bubble_sort(array):
     n = len(array)
 
     for i in range(n):
         # 創建一個標識,當沒有可以排序的時候就使函數終止。
         already_sorted = True
 
         # 從頭開始逐個比較相鄰元素,每一次循環的總次數減1,
         # 因為每次循環一次,最後面元素的排序就確定一個。
         for j in range(n - i - 1):
             if array[j] > array[j + 1]:
                 # 如果此時的元素大於相鄰後一個元素,那麼交換。
                 array[j], array[j + 1] = array[j + 1], array[j]
 
                 # 如果有了交換,設置already_sorted標誌為False算法不會提前停止
                 already_sorted = False
 
         # 如果最後一輪沒有交換,數據已經排序完畢,退出
         if already_sorted:
             break
 
     return array

為了正確分析算法的工作原理,看下這個列表[8, 2, 6, 4, 5]。假設使用bubble_sort()排序,下圖說明了算法每次迭代時數組的實際換件情況:

冒泡排序過程
測算冒泡算法的大O運行複雜度

冒泡排序的實現由兩個嵌套for循環組成,其中算法先執行n-1個比較,然後進行n-2個比較,依此類推,直到完成最終比較。因此,總的比較次數為(N - 1)+(N - 2)+(N - 3)+ ... + 2 + 1 = N(N-1)/ 2,也可以寫成 ½n 2 - ½n。

去掉不會隨數據規模n而變化的常量,可以將符號簡化為 n2 -n。由於 n2的增長速度快於n,因此也可以捨棄最後一項,使冒泡排序的平均和最壞情況下的時間複雜度為 O(n 2)

在算法接收到已排序的數組的情況下,運行時間複雜度將降低到更好的O(n),因為算法循環一遍沒有任何交換,標誌是true,所以循環一遍比較了N次直接退出。因此,O(n)是冒泡排序的最佳情況運行時間複雜度。但最好的情況是個例外,比較不同的算法時,應該關注平均情況。

冒泡排序的時間運行測試

使用run_sorting_algorithm()測試冒泡排序處理具有一萬個元素的數組所花費的時間。

ARRAY_LENGTH = 10000if __name__ == "__main__":
     # 生成包含「 ARRAY_LENGTH」個元素的數組,元素是介於0到999之間的隨機整數值
     array = [randint(0, 1000) for i in range(ARRAY_LENGTH)]
 
     # 使用排序算法的名稱和剛創建的數組調用該函數
     run_sorting_algorithm(algorithm="bubble_sort", array=array)

現在運行腳本來獲取bubble_sort的執行時間:

$ python sorting.py
Algorithm: bubble_sort. Minimum execution time: 73.21720498399998

分析冒泡排序的優缺點

冒泡排序算法的主要優點是它的簡單性,理解起來非常簡單。但也看到了冒泡排序的缺點是速度慢,運行時間複雜度為O(n 2)。因此,一般對大型數組進行排序的時候,不會考慮使用冒泡排序。

Python中的插入排序算法

像冒泡排序一樣,插入排序算法也易於實現和理解。但是與冒泡排序不同,它通過將每個元素與列表的其餘元素進行比較並將其插入正確的位置,來一次構建一個排序的列表元素。此「插入」過程為算法命名。

一個例子,就是對一副紙牌進行排序。將一張卡片與其餘卡片進行逐個比較,直到找到正確的位置為止,然後重複進行直到您手中的所有卡都被排序。

在Python中實現插入排序

插入排序算法的工作原理與紙牌排序完全相同,Python中的實現:

  def insertion_sort(array):
    # 從數據第二個元素開始循環,直到最後一個元素
    for i in range(1, len(array)):
        # 這個是我們想要放在正確位置的元素
        key_item = array[i]

        # 初始化變量,用於尋找元素正確位置
        j = i - 1

        # 遍曆元素左邊的列表元素,一旦key_item比被比較元素小,那麼找到正確位置插入。
        while j >= 0 and array[j] > key_item:
            # 把被檢測元素向左平移一個位置,並將j指向下一個元素(從右向左)
            array[j + 1] = array[j]
            j -= 1

        # 當完成元素位置的變換,把key_item放在正確的位置上
        array[j + 1] = key_item

    return array

下圖顯示了對數組進行排序時算法的不同迭代[8, 2, 6, 4, 5]:

插入排序過程

測量插入排序的大O時間複雜度

與冒泡排序實現類似,插入排序算法具有兩個嵌套循環,遍歷整個列表。內部循環非常有效,因為它會遍歷列表,直到找到元素的正確位置為止。

最壞的情況發生在所提供的數組以相反順序排序時。在這種情況下,內部循環必須執行每個比較,以將每個元素放置在正確的位置。這仍然給您帶來O(n2)運行時複雜性。

最好的情況是對提供的數組進行了排序。這裡,內部循環永遠不會執行,導致運行時複雜度為O(n),就像冒泡排序的最佳情況一樣。

儘管冒泡排序和插入排序具有相同的大O時間複雜度,但實際上,插入排序比冒泡排序有效得多。如果查看兩種算法的實現,就會看到插入排序是如何減少了對列表進行排序的比較次數的。

插入排序時間測算

為了證明插入排序比冒泡排序更有效,可以對插入排序算法進行計時,並將其與冒泡排序的結果進行比較。調用我們寫好的測試函數。

  if __name__ == "__main__":
     # 生成包含「 ARRAY_LENGTH」個元素的數組,元素是介於0到999之間的隨機整數值
     array = [randint(0, 1000) for i in range(ARRAY_LENGTH)]
 
     # 使用排序算法的名稱和剛創建的數組調用該函數
    run_sorting_algorithm(algorithm="insertion_sort", array=array)

執行腳本:

  $ python sorting.py
Algorithm: insertion_sort. Minimum execution time: 56.71029764299999

可以看到,插入排序比冒泡排序實現少了17秒,即使它們都是O(n 2)算法,插入排序也更加有效。

分析插入排序的優點和缺點

就像冒泡排序一樣,插入排序算法的實現也很簡單。儘管插入排序是O(n 2)算法,但在實踐中它也比其他二次實現(例如冒泡排序)更有效。

有更強大的算法,包括合併排序和快速排序,但是這些實現是遞歸的,在處理小型列表時通常無法擊敗插入排序。如果列表足夠小,可以提供更快的整體實現,則某些快速排序實現甚至在內部使用插入排序。Timsort還在內部使用插入排序對輸入數組的一小部分進行排序。

也就是說,插入排序不適用於大型陣列,這為可以更有效地擴展規模的算法打開了大門。

Python中的合併排序算法

合併排序是一種非常有效的排序算法。它基於分治法,這是一種用於解決複雜問題的強大算法技術。

要正確理解分而治之,應該首先了解遞歸的概念。遞歸涉及將問題分解成較小的子問題,直到它們足夠小以至於無法解決。在編程中,遞歸通常由調用自身的函數表示。

分而治之算法通常遵循相同的結構:

原始輸入分為幾個部分,每個部分代表一個子問題,該子問題與原始輸入相似,但更為簡單。每個子問題都遞歸解決。所有子問題的解決方案都組合成一個整體解決方案。在合併排序的情況下,分而治之方法將輸入值的集合劃分為兩個大小相等的部分,對每個一半進行遞歸排序,最後將這兩個排序的部分合併為一個排序列表。

在Python中實現合併排序

合併排序算法的實現需要兩個不同的部分:

遞歸地將輸入分成兩半的函數合併兩個半部的函數,產生一個排序數組這是合併兩個不同數組的代碼:

def merge(left, right):
  # 如果第一個數組為空,那麼不需要合併,
  # 可以直接將第二個數組返回作為結果
  if len(left) == 0:
      return right

  # 如果第二個數組為空,那麼不需要合併,
  # 可以直接將第一個數組返回作為結果
  if len(right) == 0:
      return left

  result = []
  index_left = index_right = 0

  # 查看兩個數組直到所有元素都裝進結果數組中
  while len(result) < len(left) + len(right):
      # 這些需要排序的元素要依次被裝入結果列表,因此需要決定將從
      # 第一個還是第二個數組中取下一個元素
      if left[index_left] <= right[index_right]:
          result.append(left[index_left])
          index_left += 1
      else:
          result.append(right[index_right])
          index_right += 1

      # 如果哪個數組達到了最後一個元素,那麼可以將另外一個數組的剩餘元素
      # 裝進結果列表中,然後終止循環
      if index_right == len(right):
          result += left[index_left:]
          break

      if index_left == len(left):
          result += right[index_right:]
          break

  return result

現在還缺少的部分是將輸入數組遞歸拆分為兩個並用於merge()產生最終結果的函數:

def merge_sort(array):
  # 如果輸入數組包含元素不超過兩個,那麼返回它作為函數結果
  if len(array) < 2:
      return array

  midpoint = len(array) // 2

  # 對數組遞歸地劃分為兩部分,排序每部分然後合併裝進最終結果列表
  return merge(
      left=merge_sort(array[:midpoint]),
      right=merge_sort(array[midpoint:]))

看一下合併排序將對數組進行排序的步驟[8, 2, 6, 4, 5]:

合併排序過程

該圖使用黃色箭頭表示在每個遞歸級別將數組減半。綠色箭頭表示將每個子陣列合併在一起。

衡量合併排序的大O複雜度

要分析合併排序的複雜性,可以分別查看其兩個步驟:

merge()具有線性運行時間。它接收兩個數組,它們的組合長度最多為n(原始輸入數組的長度),並且通過最多查看每個元素一次來組合兩個數組。這導致運行時複雜度為O(n)。

第二步以遞歸方式拆分輸入數組,並調用merge()每一部分。由於將數組減半直到剩下單個元素,因此此功能執行的減半運算總數為log 2 n。由於merge()每個部分都被調用,因此總運行時間為O(n log 2 n)。

對合併排序進行測算時間

同樣通過之前時間測試函數:

if __name__ == "__main__":
     # 生成包含「 ARRAY_LENGTH」個元素的數組,元素是介於0到999之間的隨機整數值
     array = [randint(0, 1000) for i in range(ARRAY_LENGTH)]
 
     # 使用排序算法的名稱和剛創建的數組調用該函數
    run_sorting_algorithm(algorithm="merge_sort", array=array)

執行時間:

$ python sorting.py
Algorithm: merge_sort. Minimum execution time: 0.6195857160000173

與冒泡排序和插入排序相比,合併排序實現非常快,可以在一秒鐘內對一萬個數組進行排序了!

分析合併排序的優點和缺點

由於其運行時複雜度為O(n log 2 n),因此合併排序是一種非常有效的算法,可以隨著輸入數組大小的增長而很好地擴展。並行化也很簡單,因為它將輸入數組分成多個塊,必要時可以並行分配和處理這些塊。

缺點是對於較小的列表,遞歸的時間成本就較高了,冒泡排序和插入排序之類的算法更快。例如,運行包含十個元素的列表的實驗的時間:

Algorithm: bubble_sort. Minimum execution time: 0.000018774999999998654
Algorithm: insertion_sort. Minimum execution time: 0.000029786000000000395
Algorithm: merge_sort. Minimum execution time: 0.00016983000000000276

合併排序的另一個缺點是,它在遞歸調用自身時會創建數組。它還在內部創建一個新列表,這使得合併排序比氣泡排序和插入排序使用更多的內存。

Python中的快速排序算法

就像合併排序一樣,快速排序算法採用分而治之的原理將輸入數組分為兩個列表,第一個包含小項目,第二個包含大項目。然後,該算法將對兩個列表進行遞歸排序,直到對結果列表進行完全排序為止。

劃分輸入列表稱為對列表進行分區。快排首先選擇一個pivot元素,然後將列表劃分為pivot,然後將每個較小的元素放入low數組,將每個較大的元素放入high數組。

將low列表中的每個元素放在列表的左側,列表中的pivot每個元素high放在右側,將其pivot精確定位在最終排序列表中的確切位置。這意味著該函數現在可以遞歸地將相同的過程應用於low,然後high對整個列表進行排序。

在Python中實現快排

這是快排的一個相當緊湊的實現:

from random import randint

def quicksort(array):
   # 如果第一個數組為空,那麼不需要合併,
   # 可以直接將第二個數組返回作為結果
    if len(array) < 2:
        return array

    low, same, high = [], [], []

    # 隨機選擇 pivot 元素
    pivot = array[randint(0, len(array) - 1)]

    for item in array:
        # 元素小於pivot元素的裝進low列表中,大於piviot元素值的裝進high列表中
        # 如果和pivot相等,則裝進same列表中
        if item < pivot:
            low.append(item)
        elif item == pivot:
            same.append(item)
        elif item > pivot:
            high.append(item)

    # 最後的結果列表包含排序的low列表、same列表、hight列表
    return quicksort(low) + same + quicksort(high)

以下是快排對數組進行排序的步驟的說明[8, 2, 6, 4, 5]:

快排排序過程

快速排序流程黃線表示陣列的劃分成三個列表:low,same,high。綠線表示排序並將這些列表放在一起。

選擇pivot元素

為什麼上面的實現會pivot隨機選擇元素?選擇輸入列表的第一個或最後一個元素會不會一樣?

由於快速排序算法的工作原理,遞歸級別的數量取決於pivot每個分區的結尾位置。在最佳情況下,算法始終選擇中值元素作為pivot。這將使每個生成的子問題恰好是前一個問題的一半,從而導致最多log 2 n級。

另一方面,如果算法始終選擇數組的最小或最大元素作為pivot,則生成的分區將儘可能不相等,從而導致n-1個遞歸級別。對於快速排序,那將是最壞的情況。

如你所見,快排的效率通常取決於pivot選擇。如果輸入數組未排序,則將第一個或最後一個元素用作,pivot將與隨機元素相同。但是,如果輸入數組已排序或幾乎已排序,則使用第一個或最後一個元素作為pivot可能導致最壞的情況。pivot隨機選擇使其更有可能使快排選擇一個接近中位數的值並更快地完成。

另一個選擇是找到數組的中值,並強制算法將其用作pivot。這可以在O(n)時間內完成。儘管該過程稍微複雜一些,但將中值用作pivot快速排序可以確保您擁有最折中的大O方案。

衡量快排的大O複雜度

使用快排,將輸入列表按線性時間O(n)進行分區,並且此過程將平均遞歸地重複log 2 n次。這導致最終複雜度為O(n log 2 n)

當所選擇的pivot是接近陣列的中位數,最好的情況下會發生O(n),當pivot是陣列的最小或最大的值,最差的時間複雜度為O(n 2

從理論上講,如果算法首先專注於找到中值,然後將其用作pivot元素,那麼最壞情況下的複雜度將降至O(n log 2 n)。數組的中位數可以在線性時間內找到,並將其用作pivot保證代碼的快速排序部分將在O(n log 2 n)中執行。

通過使用中值作為pivot,最終運行時間為O(n)+ O(n log 2 n)。你可以將其簡化為O(n log 2 n),因為對數部分的增長快於線性部分。

隨機選擇pivot使最壞情況發生的可能性很小。這使得隨機pivot選擇對於該算法的大多數實現都足夠好。

對快排測量運行時間

調用測試函數:

if __name__ == "__main__":
     # 生成包含「 ARRAY_LENGTH」個元素的數組,元素是介於0到999之間的隨機整數值
     array = [randint(0, 1000) for i in range(ARRAY_LENGTH)]
 
     # 使用排序算法的名稱和剛創建的數組調用該函數
    run_sorting_algorithm(algorithm="quicksort", array=array)

執行腳本:

$ python sorting.py
Algorithm: quicksort. Minimum execution time: 0.11675417600002902

快速排序不僅可以在不到一秒鐘的時間內完成,而且比合併排序(0.11幾秒鐘對0.61幾秒鐘)要快得多。如果增加ARRAY_LENGTH從10,000到數量1,000,000並再次運行腳本,合併排序最終會在97幾秒鐘內完成,而快排則僅在10幾秒鐘內對列表進行排序。

分析快排的優勢和劣勢

顧名思義,快排非常快。儘管從理論上講,它的最壞情況是O(n 2),但在實踐中,快速排序的良好實現勝過大多數其他排序實現。而且,就像合併排序一樣,快排也很容易並行化。

快排的主要缺點之一是缺乏保證達到平均運行時複雜度的保證。儘管最壞的情況很少見,但是某些應用程式不能承受性能不佳的風險,因此無論輸入如何,它們都選擇不超過O(n log 2 n)的算法。

就像合併排序一樣,快排也會在內存空間與速度之間進行權衡。這可能成為對較大列表進行排序的限制。

通過快速實驗對十個元素的列表進行排序,可以得出以下結果:

Algorithm: bubble_sort. Minimum execution time: 0.0000909000000000014
Algorithm: insertion_sort. Minimum execution time: 0.00006681900000000268
Algorithm: quicksort. Minimum execution time: 0.0001319930000000004

結果表明,當列表足夠小時,快速排序也要付出遞歸的代價,完成時間比插入排序和冒泡排序都要長。

Python中的Timsort算法

最後這個算法就有意思了!

所述Timsort算法被認為是一種混合的排序算法,因為它採用插入排序和合併排序的最佳的兩個世界級組合。Timsort與Python社區也很有緣,它是由Tim Peters於2002年創建的,被用作Python語言的標準排序算法。我們使用的內置sorted函數就是這個算法。

Timsort的主要特徵是它利用了大多數現實數據集中存在的已排序元素。這些稱為natural runs。然後,該算法會遍歷列表,將元素收集到運行中,然後將它們合併到一個排序的列表中。

在Python中實現Timsort

本篇創建一個準系統的Python實現,該實現說明Timsort算法的所有部分。如果有興趣,也可以查看Timsort的原始C實現。

實施Timsort的第一步是修改insertion_sort()的實現:

def insertion_sort(array, left=0, right=None):
    if right is None:
        right = len(array) - 1

    # 從指示的left元素循環,直到right被指示
    for i in range(left + 1, right + 1):
# 這個是我們想要放在正確位置的元素
        key_item = array[i]

  # 初始化變量,用於尋找元素正確位置
        j = i - 1
# 遍曆元素左邊的列表元素,一旦key_item比被比較元素小,那麼找到正確位置插入
        while j >= left and array[j] > key_item:
# 把被檢測元素向左平移一個位置,並將j指向下一個元素(從右向左)
            array[j + 1] = array[j]
            j -= 1

# 當完成元素位置的變換,把key_item放在正確的位置上
        array[j + 1] = key_item

    return array

此修改後的實現添加了兩個參數left和right,它們指示應該對數組的哪一部分進行排序。這使Timsort算法可以對數組的一部分進行排序。修改功能而不是創建新功能意味著可以將其同時用於插入排序和Timsort。

現在看一下Timsort的實現:

def timsort(array):
    min_run = 32
    n = len(array)

    # 開始切分、排序輸入數組的小部分,切分在`min_run`定義
    for i in range(0, n, min_run):
        insertion_sort(array, i, min((i + min_run - 1), n - 1))

    # 現在可以合併排序的切分塊了
    # 從`min_run`開始, 每次循環都加倍直到超過數組的長度
    size = min_run
    while size < n:
        # Determine the arrays that will
        # be merged together
        for start in range(0, n, size * 2):
            # 計算中點(第一個數組結束第二個數組開始的地方)和終點(第二個數組結束的地方)
            midpoint = start + size - 1
            end = min((start + size * 2 - 1), (n-1))

            # 合併兩個子數組
            # left數組應該從起點到中點+1, right數組應該從中點+1到終點+1
            merged_array = merge(
                left=array[start:midpoint + 1],
                right=array[midpoint + 1:end + 1])

            # 最後,把合併的數組放回數組
            array[start:start + len(merged_array)] = merged_array

        # 每次迭代都應該讓數據size加倍
        size *= 2

    return array

儘管實現比以前的算法要複雜一些,但是我們可以通過以下方式快速總結一下:

之前已經了解到,插入排序在小列表上速度很快,而Timsort則利用了這一優勢。Timsort使用新引入的left和right參數在insertion_sort()對列表進行適當排序,而不必像merge sort和快排那樣創建新數組。

定義min_run = 32作為值有兩個原因:

1. 使用插入排序對小數組進行排序非常快,並且min_run利用此特性的價值很小。使用min_run太大的值進行初始化將無法達到使用插入排序的目的,並使算法變慢。

2. 合併兩個平衡列表比合併不成比例的列表要有效得多。min_run在合併算法創建的所有不同運行時,選擇一個為2的冪的值可確保更好的性能。

結合以上兩個條件,可以提供幾種min_run選擇。本教程中的實現min_run = 32是其中一種可能性。

衡量Timsort的大O時間複雜性

平均而言,Timsort的複雜度為O(n log 2 n),就像合併排序和快速排序一樣。對數部分來自執行每個線性合併操作的運行大小加倍。

但是,Timsort在已排序或接近排序的列表上表現特別出色,從而導致了O(n)的最佳情況。在這種情況下,Timsort明顯勝過合併排序,並與快速排序的最佳情況相匹配。但是,對於Timsort來說,最糟糕的情況也是O(n log 2 n)。

對Timsort測量運行時間

調用時間運行測試函數:

if __name__ == "__main__":
     # 生成包含「 ARRAY_LENGTH」個元素的數組,元素是介於0到999之間的隨機整數值
     array = [randint(0, 1000) for i in range(ARRAY_LENGTH)]
 
     # 使用排序算法的名稱和剛創建的數組調用該函數
    run_sorting_algorithm(algorithm="timsort", array=array)

執行時間:

$ python sorting.py
Algorithm: timsort. Minimum execution time: 0.5121690789999998

0.51秒,Timsort比合併排序整整快0.1秒,即17%。它也比插入排序快11,000%!

現在,嘗試使用這四種算法對已經排序的列表進行排序,然後看看會發生什麼。

if __name__ == "__main__":
    # 生成包含「 ARRAY_LENGTH」個元素的數組
    array = [i for i in range(ARRAY_LENGTH)]

    # 調用每個函數
    run_sorting_algorithm(algorithm="insertion_sort", array=array)
    run_sorting_algorithm(algorithm="merge_sort", array=array)
    run_sorting_algorithm(algorithm="quicksort", array=array)
    run_sorting_algorithm(algorithm="timsort", array=array)

現在執行腳本,那麼所有算法都將運行並輸出相應的執行時間:

Algorithm: insertion_sort. Minimum execution time: 53.5485634999991
Algorithm: merge_sort. Minimum execution time: 0.372304601
Algorithm: quicksort. Minimum execution time: 0.24626494199999982
Algorithm: timsort. Minimum execution time: 0.23350277099999994

這次,Timsort的速度比合併排序快了37%,比快排快了5%,從而增強了利用已排序運行的能力。

請注意,Timsort如何從兩種算法中受益,這兩種算法單獨使用時速度要慢得多。Timsort的神奇之處在於將這些算法結合起來並發揮其優勢,以獲得令人印象深刻的結果。

分析Timsort的優勢和劣勢

Timsort的主要缺點是它的複雜性。儘管實現了原始算法的非常簡化的版本,但由於它同時依賴於insertion_sort()和merge(),因此仍需要更多代碼。

Timsort的優點之一是其能夠以O(n log 2 n)的方式執行預測,而與輸入數組的結構無關。與快排相比,快排可以降級為O(n 2)。對於小數組,Timsort也非常快,因為該算法變成了單個插入排序。

對於現實世界中的使用(通常對已經具有某些預先存在的順序的數組進行排序),Timsort是一個不錯的選擇。它的適應性使其成為排序任何長度的數組的絕佳選擇。

結論

排序是任何Pythonista工具包中必不可少的工具。了解Python中不同的排序算法以及如何最大程度地發揮它們的潛力,你就可以實現更快,更高效的應用程式和程序!


參考: 

1. https://realpython.com/sorting-algorithms-python/

2. https://blog.csdn.net/weixin_38483589/article/details/84147376

3. https://mp.weixin.qq.com/s/OHoe6TTX--Ys5G-5yR6juA

相關焦點

  • 經典 | Python實現八大排序算法,面試必備
    本文使用python實現了一些常用的排序方法。排序方法詳細介紹直接插入排序直接插入排序(Straight Insertion Sort)是一種最簡單的排序方法,它的基本操作是一個值插入到已排好序的有序表中,從而得到一個新的、記錄數增1的有序表。如下圖所示:由上圖可知若最初始的有序表即為數組的第一個元素。
  • 用Python手寫五大經典排序算法,看完這篇終於懂了!
    作為Python忠實愛好者,本篇東哥將通過Python來手撕5大經典排序算法,結合例圖剖析內部實現邏輯,對比每種算法各自的優缺點和應用點。相信我,耐心看完絕對有收穫。在Python中實現快排這是快排的一個相當緊湊的實現:from random import randintdef quicksort(array):   # 如果第一個數組為空,那麼不需要合併,
  • 面試官問我插入排序和冒泡排序哪個更牛逼?
    還有一些其他經典的排序,小鹿整理的共有十種是面試常問到的,冒泡排序、插入排序、希爾排序、選擇排序、歸併排序、快速排序、堆排序、桶排序、計數排序、基數排序。雖然我們基本知道了這些排序算法,但是在實際項目開發以及面試中往往出乎我們所料。在面試中,經常會被問到各種排序之間的比較;在實際項目中,往往排序的數據不是我們所練習的整數。
  • Python實現八大經典排序算法
    一、前言在面試題中可能會遇到排序算法,畢竟作為程式設計師內功心法,熟練掌握排序算法是很重要的,本文總結了八大經典排序算法的 Python 實現。排序算法是《數據結構與算法》中最基本的算法之一。關於穩定性:穩定的排序算法:冒泡排序、插入排序、歸併排序和基數排序。(冒插歸基)不穩定的排序算法:選擇排序、快速排序、希爾排序、堆排序。(選快希堆)二、冒泡排序冒泡排序是一種簡單的排序算法。
  • 動畫:面試官問我插入排序和冒泡排序哪個更牛逼?
    排序對於每個開發者來講,都多多少少知道幾個經典的排序算法,比如我們之前以動畫形式分享的冒泡排序,也包括今天要分享的插入排序。還有一些其他經典的排序,小鹿整理的共有十種是面試常問到的,冒泡排序、插入排序、希爾排序、選擇排序、歸併排序、快速排序、堆排序、桶排序、計數排序、基數排序。雖然我們基本知道了這些排序算法,但是在實際項目開發以及面試中往往出乎我們所料。
  • Python中排序算法的重要性,希爾排序 ShellSort,中級python技術
    Python中排序算法的重要性排序是計算機科學中最深入研究的算法之一。有許多不同的排序實現和應用程式,您可以使用它們來提高代碼的效率。該方法的基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個「增量」的元素組成的)分別進行直接插入排序,然後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因為直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。
  • 算法基礎:五大排序算法Python實戰教程
    不僅要通過編程面試,還要對程序本身有一個全面的理解。不同的排序算法很好地展示了算法設計上如何強烈的影響程序的複雜度、運行速度和效率。讓我們看一下前6種排序算法,看看如何在Python中實現它們!快速排序快速排序也是一種分而治之的算法,如歸併排序。雖然它有點複雜,但在大多數標準實現中,它的執行速度明顯快於歸併排序,並且很少達到最壞情況下的複雜度O(n) 。
  • 用Python實現常見的四種排序算法
    排序是每個軟體工程師和開發人員都需要掌握的技能。不僅要通過編程面試,還要對程序本身有一個全面的理解。不同的排序算法很好地展示了算法設計上如何強烈的影響程序的複雜度、運行速度和效率。排序有很多種實現方法,比如冒泡排序、選擇排序、歸併排序、希爾排序、快速排序、插入排序、堆排序、基數排序等,今天就給大家介紹使用Python語言實現的其中4個排序算法。1.
  • 用 python 實現各種排序算法
    我們工作到底為了什麼(這篇文章很重要)總結了一下常見集中排序的算法選擇排序選擇排序(Selection sort)是一種簡單直觀的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
  • 經典排序算法三 選擇排序(JAVA實現)
    選擇排序原理:每一趟從待排序的記錄中選出最小的元素,順序放在已排好序的序列最後,直到全部記錄排序完畢。也就是:每一趟在n-i+1(i=1,2,…n-1)個記錄中選取關鍵字最小的記錄作為有序序列中第i個記錄。基於此思想的算法主要有簡單選擇排序、樹型選擇排序和堆排序。
  • Go語言實現常用排序算法
    經過很多年的研究,出現了很多的排序算法,有快的有慢的。冒泡排序是一種極其簡單的排序算法,也是我所學的第一個排序算法。它重複地走訪過要排序的元素,依次比較相鄰兩個元素,如果他們的順序錯誤就把他們調換過來,直到沒有元素再需要交換,排序完成。這個算法的名字由來是因為越小(或越大)的元素會經由交換慢慢「浮」到數列的頂端。在面試中,也是問的最多的一種,有時候還要求手寫排序代碼。冒泡排序屬於交換類的排序算法。
  • 八大經典排序算法詳解
    >」,找工作面試的時候,算法和數據結構也是絕對不可避免的,面試官可能一言不合就讓你手寫一個排序算法。我把最經典的八大排序算法原理和代碼也都整理出來了,內容如下,希望對大家能有所幫助。插入排序•基本思想:每步將一個待排序的紀錄,按其關鍵碼值的大小插入前面已經排序的文件中適當位置上,直到全部插入完為止。•算法適用於少量數據的排序,時間複雜度為O(n^2)。是穩定的排序方法。
  • 字節大佬Leetcode刷題筆記,看完吊打問你算法的面試官
    其中算法,主要是以下幾種:基礎技巧:分治、二分、貪心排序算法:快速排序、歸併排序、計數排序搜索算法:回溯、遞歸、深度優先遍歷這個時候只有看一下數據範圍,思考下你的算法複雜度就行了。當然也不排除很多 hard 題目也可以暴力模擬,大家平時多注意數據範圍即可。以下是我列舉的經典題目:中等難度題目合集中等題目是力扣比例最大的部分,因此這部分我的題解也是最多的。
  • 算法-快排
    面試官:最後問一道簡單的算法題,快速排序你用過嗎?用到的思想是什麼?
  • C語言實現八大排序算法(一)
    註:下面的排序代碼有些我沒編譯跑過,可能有問題,但思想肯定是正確的,我用python重新編寫並驗證了一遍,代碼可參考這 python實現十大排序算法(詳解)排序相描述註:算法是否穩定並不能衡量一個算法的優劣,排序算法主要包含2種操作:比較和移動,但並不是所有的排序都基於比較操作,比如基數排序。下圖為排序算法體系結構圖:
  • 經典排序算法全攻略
    這次跳槽季的面試中有沒有被算法難倒呢?不久前,一位程式設計師就因為算法問題被面試官吐槽了:清華就這水平?咳,清華的水平小編不敢妄作評論,不過算法是真心重要!就連李開復老師都曾經說過:「算法遠遠比日新月異語言重要得多。算法是本質,是『萬變不離其宗』的東西」。算法不僅作為理論基礎,微軟造作系統的研發更新、谷歌搜索、百度地圖引導等都需要強大的算法在背後支撐。
  • Python中的插入排序算法,插入排序的優缺點,中級python技術點
    屆時,您需要將卡插入正確的位置,然後重新開始製作新卡,重複進行直到您手上的所有卡都被排序為止。在Python中實現插入排序插入排序算法的工作原理與紙牌示例完全相同。這是Python中的實現:與冒泡排序不同,此插入排序的實現通過將較小的項目向左推來構造排序列表。
  • 數據結構與算法:聊一聊在面試中被常問的那幾個基礎算法的理解
    前面我也說到:數據結構和算法是一對"相愛相殺的"組合,所以接下來要分享下我個人對於一些算法的理解和實現。本文將主要分享基礎的查找和排序(代碼基於python)既然要開始分享算法,那必然要先介紹下算法相關的一些基本術語,如下。
  • 八大排序算法的 Python 實現
  • 面試官:手寫一個冒泡排序,並對其改進
    前寫過一篇選擇排序,很多人把它和冒泡排序搞混了,這篇文章對冒泡排序進行一個分析,希望你能分清楚,也希望能在面試的時候能夠完美的回答出冒泡排序。今年的工作據說是不好找,當然運氣佔很大一部分,但是實力越強運氣的成分就會相應降低吧。