一、快速排序
算法描述
步驟為:
-
從數列中挑出一個元素,稱為"基准"(pivot),
-
重新排序數列,所有元素比基准值小的擺放在基准前面,所有元素比基准值大的擺在基准的后面(相同的數可以到任一邊)。在這個分區結束之后,該基准就處於數列的中間位置。這個稱為分區(partition)操作。
-
遞歸地(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