排序算法:快速排序

快速排序與冒泡排序相似,也屬於交換排序,經過元素之間的比較和交換位置實現排序。不一樣的地方在於,冒泡排序在每一次循環只把一個元素冒泡到數組的一端,而快速排序在每一輪挑選一個樞紐元,並讓其餘比它大的元素移動到數組的一邊,比它小的元素移動到數組另外一邊,從而把數組拆分紅兩個部分html

例如輸入以下的數組,樞紐元選取爲6

[8, 1, 4, 9, 0, 3, 5, 2, 7, 6]算法

分割以後結果以下數組

[2, 1, 4, 5, 0, 3, 6, 8, 7, 9]安全

與歸併排序同樣,快速排序也是一種分治的遞歸算法。在分治法的思想下,原數組在每一輪都被拆分紅兩個部分,每一部分在下一輪又被拆分紅兩部分,直到不可再分爲止。數據結構

快速排序的平均時間複雜度是 O(n logn) , 最壞情形的時間複雜度爲 O(n2) ,即每一輪循環樞紐元都選到最大或最小的元素,但通過稍許努力可使這種狀況極難出現。快速排序有兩個核心的問題,樞紐元的選擇,以及元素的交換指針

選取樞紐元

樞紐元(pivot),也叫基準元素。在分治過程當中,以樞紐元爲中心,把其餘元素移動到它的左右兩邊。選取樞紐元主要有三種方法。code

1)選取第一個元素

最簡單的方法就是將第一個元素做爲樞紐元。可是這種策略不可取,假設輸入的數組是預排序的,那麼樞紐元必然會選到最大或最小的元素,這種狀況下每一輪循環數組並無被分紅兩半,不能發揮分治法的優點。在這種極端狀況下,快速排序須要進行 n 輪,時間複雜度退化爲 O(n2) 。htm

2)隨機選取元素

一種很是安全的作法是隨機選取樞紐元。說這個策略安全,是由於隨機的樞紐元不可能在每一輪循環都產生劣質的分割(即選到了最大或最小值)。可是這個策略也有問題,一是即便是隨機選取的樞紐元,也有必定機率會選中最大或最小值,影響分治效率;二是隨機數的生成通常開銷很大,影響總體算法效率。blog

3)三數中值分割法(Median-of-Three Partitioning)

樞紐元的最好選擇是數組的中值(也叫作中位數,是第 N/2 個最大的數),可是中值難以求出,而且會明顯下降快速排序的效率。通常的作法是使用左端、右端和中間三個元素的中值做爲樞紐元。例如輸入的數組以下:排序

[8, 1, 4, 9, 6, 3, 5, 2, 7, 0]

左端元素是8,右端元素是0,中間的元素是6,所以能夠肯定樞紐元 v = 6 。顯然,使用三數中值分割法消除了預排序輸入致使的最壞情形。

分割策略

選取了樞紐元以後,下一步就須要移動元素,將數組拆分紅兩個部分。有個簡便的方法是直接開三個數組,分別是 smallersamelarger ,而後循環遍歷數組的每一個元素,將每一個元素與樞紐元對比,小於樞紐元的 push 進 smaller 數組,等於樞紐元的 push 進 same 數組,大於樞紐元的 push 進 larger 數組,這樣就實現了拆分。可是這樣作無疑會佔用額外空間,實際的快排(例如 JDK 的 sort 方法)都是直接對原數組進行排序的,這樣就要求直接在數組中交換元素,實現數組的分割。主要有兩種方法。

1)雙邊循環法

首先選定樞紐元 pivot ,而且設置兩個指針 left 和 right ,初始狀態下 left 和 right 分別位於數組最左和最右側。

接下來進行第1次循環,從 right 指針開始,讓指針所指向的元素和 pivot 進行比較,若是大於或等於 pivot ,則指針向移動;若是小於 pivot ,則 right 指針中止移動,切換到 left 指針。輪到 left 指針行動,讓指針所指向的元素和 pivot 進行比較,若是小於或等於 pivot ,則指針向移動;若是大於 pivot ,則 left 中止移動。

當兩個指針都中止移動時,讓 left 和 right 指針所指向的元素進行交換

而後進行下一輪循環,以此類推。

2)單邊循環法

參考

《數據結構與算法分析(Java 語言描述)》
《漫畫算法》
圖解排序算法(五)之快速排序——三數取中法

相關文章
相關標籤/搜索