先簡單回顧一下,今天預計分析的題目:
題目敘述:
題目的條件:
如何利用 Quick sort 進行排序
上次說到 Quick Sort 有兩種實作方法,分別為
接下來我們會分別介紹兩種方式的實作
首先,我們取正中間的元素作為 pivot,並設置 i
, j
指針指向數列左邊:
# Partition
i, j, pivot = left, left, (left + right) // 2
其中 left
為數列左側 index, right
為數列右邊 index,以 [5, 2, 3, 1]
為例, left
為 0,而 right
為 4。下圖是初始化 i, j, 和選定 pivot。
接著用迴圈將 i
指向比 pivot 大的元素;再將 j
指向比 pivot 小的元素
# When the loop stoped, nums[i] >= nums[pivot]
while(i < right and nums[i] < nums[pivot]): i += 1
# When the loop stoped, nums[j] < nums[pivot]
if j < i: j = i
while(j < right and nums[j] >= nums[pivot]): j += 1
由於目標是將小的元素換於左側,因此若比 pivot 小的元素本來就在左側,則不需要做任何處理,因此若 j < i
則直接讓 j = i
,繼續向右開始搜尋以節省時間。下圖是第一次掃描, i 停在 0,而 j 停在 1。
迴圈結束,此時 nums[i]
為比 nums[pivot]
大的元素,而 nums[j]
為比 nums[pivot]
小的元素,當 i < j
(也就是 大的數 在 小的數左邊)則將兩者交換,而若有交換到 pivot ,則需更新 pivot 的位置,並將指標們 + 1 ,繼續向右掃描。下圖是交換數值,並繼續掃描。
if i < j and j < right:
# Swap
nums[i], nums[j] = nums[j], nums[i]
if i == pivot: pivot = j
# Move on
i += 1
j += 1
用迴圈重複執行 2 ~ 3 步,直到指標掃完所有元素
while(i < right and j < right)
此時數列左側都是小於 pivot 的數,而右側都是大於 pivot 的數。由於我們第二步實作大小比較時,讓 i
指向大於或「等於」pivot 的元素,因此兩側的交界就在於 i
,若先前是讓 j
指向小於或等於 pivot 的元素,則此時交界會在於 j
。下圖是掃完、交換完一輪後,指標的最終位置。
將 Pivot 交換至正確位置,也就是上述的交界處。下圖是將 Pivot 與 i 交換,放到正確位置。而在此例中,正好 i 和 Pivot 在同一處。
# Put pivot to the correct location
nums[i], nums[pivot] = nums[pivot], nums[i]
至此,我們已經成功找到一個數的正確位置,並將數列切分為小於該數的左側子數列,和大於該數的右側子數列。此處我們用遞迴方式,再將劃分出的兩個子數列繼續進行排序。下圖是經過一輪掃描,可確定 3 的正確位置在 index = 2 處,而左右可分為兩個子數列,透過遞迴繼續進行排序。
# Recursion
self.QuickSort_Lomuto(left, i)
self.QuickSort_Lomuto(i + 1, right)
最後,設置遞迴函式的終止條件
# End Condition
if left >= right: return
當 left >= right
時停止,也就是當子數列中沒有元素時即停止。
至此,我們已經完成 Lomuto 版本的 Quick Sort ,完整的 Code 如下:
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
self.nums = nums
self.QuickSort_Lomuto(0, len(nums))
return nums
def QuickSort_Lomuto(self, left: int, right: int) -> None:
nums = self.nums
# End Condition
if left >= right: return
# Partition
i, j, pivot = left, left, (left + right) // 2
# Sort
while(i < right and j < right):
# When the loop stoped, nums[i] >= nums[pivot]
while(i < right and nums[i] < nums[pivot]): i += 1
# When the loop stoped, nums[j] < nums[pivot]
if j < i: j = i
while(j < right and nums[j] >= nums[pivot]): j += 1
if i < j and j < right:
# Swap
nums[i], nums[j] = nums[j], nums[i]
if i == pivot: pivot = j
# Move on
i += 1
j += 1
# Put pivot to the correct location
nums[i], nums[pivot] = nums[pivot], nums[i]
# Recursion
self.QuickSort_Lomuto(left, i)
self.QuickSort_Lomuto(i + 1, right)
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
# 先判斷 nums 有無資料
if not nums:
return
# 開始 Quick Sort 的 Hoare 方法
self.QuickSort_Hoare(nums, 0 , len(nums) - 1)
return nums
def QuickSort_Hoare(self, nums, start, end):
# 把 start ~ end 之間要進行排序
if start >= end:
return
# 有兩個指標,k 從左邊往右,j 從右邊往左
k, j = start, end
# pivot 為中位數
pivot = nums[(start + end) // 2]
# 當 k 與 j 會逐漸靠近,直到 k > j
while k <= j:
# 在 k 與 j 還沒相遇的情況,k 會一直往右,直到停在比 pivot 大的位置
while k <= j and nums[k] < pivot:
k += 1
# 在 k 與 j 還沒相遇的情況,j 會一直往左,直到停在比 pivot 小的位置
while k <= j and nums[j] > pivot:
j -= 1
# 在 k 與 j 還沒相遇的情況,此時 k 停在比 pivot 大的位置,j 停在比 pivot 小的位置,將兩者的值互換,並且兩個互相接近一個位置
if k <= j:
nums[k], nums[j] = nums[j], nums[k]
k += 1
j -= 1
# print(nums)
# 當 k 與 j 相遇後,位置也排完,此時 k > j,那麼 start~j 是 左邊比 pivot 小的,k ~ end 是 右邊比 pivot 大的
self.QuickSort_Hoare(nums, start, j)
self.QuickSort_Hoare(nums, k, end)