1 快速排序的基本思想
快速排序(Quicksort)是對冒泡排序的一種改進。
它的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。
2 快速排序的三個步驟
1) 選擇基准:在待排序列中,按照某種方式挑出一個元素,作為 “基准”(pivot);
2) 分割操作:以該基准在序列中的實際位置,把序列分成兩個子序列。此時,在基准左邊的元素都比該基准小,在基准右邊的元素都比基准大;
3) 遞歸地對兩個序列進行快速排序,直到序列為空或者只有一個元素;
3 選擇基准元的方式
對於分治算法,當每次划分時,算法若都能分成兩個等長的子序列時,那么分治算法效率會達到最大。也就是說,基准的選擇是很重要的。選擇基准的方式決定了兩個分割后兩個子序列的長度,進而對整個算法的效率產生決定性影響。
最理想的方法是,選擇的基准恰好能把待排序序列分成兩個等長的子序列。
方法1 固定基准元
如果輸入序列是隨機的,處理時間是可以接受的。如果數組已經有序時,此時的分割就是一個非常不好的分割。因為每次划分只能使待排序序列減一,此時為最壞情況,快速排序淪為冒泡排序,時間復雜度為Θ(n^2)。而且,輸入的數據是有序或部分有序的情況是相當常見的。因此,使用第一個元素作為基准元是非常糟糕的,應該立即放棄這種想法。
def swap(arr,i,j): arr[i],arr[j]=arr[j],arr[i] def partition(arr,left,right): pivot = left index = pivot + 1 # 用來指向大於基准的數字 i = index # 用於進行循環 while i<=right: if arr[i]<arr[pivot]: swap(arr,i,index) index+=1 i+=1 swap(arr,pivot,index-1) return index-1 def quick_sort(arr,left,right): if left<right: divison = partition(arr,left,right) quick_sort(arr,left,divison-1) quick_sort(arr,divison+1,right) return arr arr=[3,8,2,4,9,0,1,5,7,6] quick_sort(arr,0,len(arr)-1) print(arr)
另一種更簡單的快排如下:
#!/usr/bin/env python # coding:utf-8 def quick_sort(data): if len(data) <= 1: return data else: low_list = [] high_list = [] pivot = data[0] for item in data[1:]: if item <= pivot: low_list.append(item) else: high_list.append(item) return quick_sort(low_list)+[pivot]+quick_sort(high_list) if __name__ == "__main__": data = [1,2,5,7,9,8,0,3,4] print(quick_sort(data))
方法2 隨機基准元
這是一種相對安全的策略。由於基准元的位置是隨機的,那么產生的分割也不會總是會出現劣質的分割。在整個數組數字全相等時,仍然是最壞情況,時間復雜度是O(n^2)。實際上,隨機化快速排序得到理論最壞情況的可能性僅為1/(2^n)。所以隨機化快速排序可以對於絕大多數輸入數據達到O(nlogn)的期望時間復雜度。
方法3 三數取中
引入的原因:雖然隨機選取基准時,減少出現不好分割的幾率,但是還是最壞情況下還是O(n^2),要緩解這種情況,就引入了三數取中選取基准。
分析:最佳的划分是將待排序的序列分成等長的子序列,最佳的狀態我們可以使用序列的中間的值,也就是第N/2個數。可是,這很難算出來,並且會明顯減慢快速排序的速度。這樣的中值的估計可以通過隨機選取三個元素並用它們的中值作為基准元而得到。事實上,隨機性並沒有多大的幫助,因此一般的做法是使用左端、右端和中心位置上的三個元素的中值作為基准元。
在固定基准基礎上,使用三數取中
#!/usr/bin/env python # coding:utf-8 def quick_sort(data,left,right): if left < right: division = get_division(data,left,right) quick_sort(data,division+1,right) quick_sort(data,left,division-1) def get_division(data,left,right): #基准:三數取中 if data[left]<=data[right]: max = data[right] min = data[left] else: max = data[left] min = data[right] middle = (left+right)//2 if data[middle]>=max: target = max elif data[middle]<=min: target = min else: target = data[middle] #將基准放到最左側 if target == data[right]: data[left],data[right]=data[right],data[left] elif target == data[middle]: data[left],data[middle]=data[middle],data[left] pivot = data[left] while left < right: while left < right and data[right] >= pivot: right -= 1 data[left] = data[right] while left < right and data[left] <= pivot: left += 1 data[right] = data[left] data[left] = pivot return left if __name__ == "__main__": data = [9,8,7,6,5,4,3,200,100] left = 0 right = len(data) - 1 quick_sort(data,left,right) print(data)
4 快速排序的優化
- 優化基准選擇(三數取中)
- 優化小數組排序效率(插入排序)
- 優化交換次數
- 優化遞歸
- 優化最差情況,避免糟糕分區
- 元素聚合
- 一個細節是不是沒提到。把pivot移到最右邊,那么最好先讓i從左往右掃描,再讓j從右往左掃描。而如果把pivot放在最左邊,那又最好先讓j從右往左掃描,再讓i從左往右掃描
參考鏈接:
https://zhuanlan.zhihu.com/p/40910407
https://zhuanlan.zhihu.com/p/57436476
https://blog.csdn.net/liuyi1207164339/article/details/50827608