算法漫游指北(第九篇):快速排序算法描述、動圖演示、代碼實現、過程分析、時間復雜度


一、快速排序

快速排序(英語:Quicksort),又稱划分交換排序(partition-exchange sort),通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

 

算法描述

步驟為:

  1. 從數列中挑出一個元素,稱為"基准"(pivot),

  2. 重新排序數列,所有元素比基准值小的擺放在基准前面,所有元素比基准值大的擺在基准的后面(相同的數可以到任一邊)。在這個分區結束之后,該基准就處於數列的中間位置。這個稱為分區(partition)操作。

  3. 遞歸地(recursive)把小於基准值元素的子數列和大於基准值元素的子數列排序。

遞歸的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞歸下去,但是這個算法總會結束,因為在每次的迭代(iteration)中,它至少會把一個元素擺到它最后的位置去。

 

快速排序動圖演示

注:部分,動圖不全,后續還有分別對pivot左右兩側元素組成的列表進行遞歸的快排操作

 

快速排序代碼實現

def quick_sort(alist, start, end):
    """快速排序"""
​
    # 遞歸的退出條件
    if start >= end:
        return
​
    # 設定起始元素為要尋找位置的基准元素
    mid = alist[start]
​
    # low為序列左邊的由左向右移動的游標
    low = start
​
    # high為序列右邊的由右向左移動的游標
    high = end
​
    while low < high:
        # 如果low與high未重合,high指向的元素不比基准元素小,則high向左移動
        while low < high and alist[high] >= mid:
            high -= 1
        # 將high指向的元素放到low的位置上
        alist[low] = alist[high]
​
        # 如果low與high未重合,low指向的元素比基准元素小,則low向右移動
        while low < high and alist[low] < mid:
            low += 1
        # 將low指向的元素放到high的位置上
        alist[high] = alist[low]
​
    # 退出循環后,low與high重合,此時所指位置為基准元素的正確位置
    # 將基准元素放到該位置
    alist[low] = mid
​
    # 對基准元素左邊的子序列進行快速排序
    quick_sort(alist, start, low-1)
​
    # 對基准元素右邊的子序列進行快速排序
    quick_sort(alist, low+1, end)
​
​

  

 

快速排序過程分析

 

待排序 arrli = [54,26,93,17,77,31,44,55,20],執行

def quick_sort(alist, start, end):
    ...
​
quick_sort(alist,0,len(alist)-1)

  


 

1、

①設置左右標,左標向右移動,右標向左移動,

②左標一直向右移動,碰到比中值大的就停止,右標一直向左移動,碰到比中值小的就停止,然后把左右標所指的數據項交換,low指針就是比pivot值小就一直向右移動,high指針就是比pivot值大就一直向左移動。

③繼續前述的移動過程,直到左標移到右標的右側,停止移動,這時右標所指的位置就是中值應處的位置,將中值和這個位置交換,分裂完成,左半部全比中值小,右半部都比中值大。

 

 

2、針對arrli = [54,26,93,17,77,31,44,55,20]快排的具體過程

①第一步,將最左側的第0個元素設置為起始元素,即pivot=54。

 

mid = 54,先移動右邊high指針,20<54,不符合high指針移動的條件,停住,

執行語句

    # 將high指向的元素放到low的位置上
  alist[low] = alist[high]

將第0個元素賦值為20

 

②移動左指針,20<= 54,符合條件

移動左指針到26,26<54 ,符合條件,

移動左指針到93,93>54,不符合條件,暫停移動左指針,左指針的位置為第2個元素,

執行語句

        # 將low指向的元素放到high的位置上
      alist[high] = alist[low]

將第八個元素賦值為93

 

③移動右指針,55>= 54,符合條件

繼續移動右指針,44<54,不符合條件,暫停移動右指針,右指針的位置為第6個元素

執行語句

    # 將high指向的元素放到low的位置上
  alist[low] = alist[high]

將第2個元素賦值為44

 

 

④移動左指針,17<54,符合條件,繼續移動

移動左指針,77>54,不符合條件,暫停移動左指針,左指針位置為第4個元素,

執行語句

        # 將low指向的元素放到high的位置上
      alist[high] = alist[low]

將第6個元素賦值為77

 

 

⑤移動右指針,31<54,不符合條件,暫停移動右指針,右指針位置為第5個元素

執行語句

    # 將high指向的元素放到low的位置上
  alist[low] = alist[high]

將第4個元素賦值為31

 

 

 

⑥移動左指針,左右指針重合,最外層的 while low < high條件不成立,

執行

 

    # 退出循環后,low與high重合,此時所指位置為基准元素的正確位置
  # 將基准元素放到該位置
  alist[low] = mid

將low指針所在的位置賦值為pivot

 

 

⑦54元素位置確定,繼續執行。

    # 對基准元素左邊的子序列進行快速排序
    quick_sort(alist, start, low-1)
​
    # 對基准元素右邊的子序列進行快速排序
    quick_sort(alist, low+1, end)

  

 

分別遞歸的對元素54左邊的[20,26,44,17,31]和右邊的[77,55,93]執行步驟1-6.

即,如[20,26,44,17,31],以第0個元素20為pivot,左右指針分別從20、31開始移動,執行步驟1-6,判斷是否符合條件。

 

⑧遞歸執行到最后,遇到start >= end,

即start = end,表示alist長度為1,就一個元素不用排序,就可以退出遞歸了

 

快速排序時間復雜度

 

  • 最優時間復雜度:O(nlogn)

  • 最壞時間復雜度:O(n^2)

  • 穩定性:不穩定

從一開始快速排序平均需要花費O(n log n)時間的描述並不明顯。但是不難觀察到的是分區運算,數組的元素都會在每次循環中走訪過一次,使用O(n)的時間。在使用結合(concatenation)的版本中,這項運算也是O(n)。

 

在最好的情況,每次我們運行一次分區,我們會把一個數列分為兩個幾近相等的片段。這個意思就是每次遞歸調用處理一半大小的數列。因此,在到達大小為一的數列前,我們只要作log n次嵌套的調用。這個意思就是調用樹的深度是O(log n)。但是在同一層次結構的兩個程序調用中,不會處理到原來數列的相同部分;因此,程序調用的每一層次結構總共全部僅需要O(n)的時間(每個調用有某些共同的額外耗費,但是因為在每一層次結構僅僅只有O(n)個調用,這些被歸納在O(n)系數中)。結果是這個算法僅需使用O(n log n)時間。

 

 

參考資料

 

[1]https://blog.csdn.net/weixin_36913190/java/article/details/80550347


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM