快速排序與冒泡排序相似,也屬於交換排序,經過元素之間的比較和交換位置實現排序。不一樣的地方在於,冒泡排序在每一次循環只把一個元素冒泡到數組的一端,而快速排序在每一輪挑選一個樞紐元,並讓其餘比它大的元素移動到數組的一邊,比它小的元素移動到數組另外一邊,從而把數組拆分紅兩個部分。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
最簡單的方法就是將第一個元素做爲樞紐元。可是這種策略不可取,假設輸入的數組是預排序的,那麼樞紐元必然會選到最大或最小的元素,這種狀況下每一輪循環數組並無被分紅兩半,不能發揮分治法的優點。在這種極端狀況下,快速排序須要進行 n 輪,時間複雜度退化爲 O(n2) 。htm
一種很是安全的作法是隨機選取樞紐元。說這個策略安全,是由於隨機的樞紐元不可能在每一輪循環都產生劣質的分割(即選到了最大或最小值)。可是這個策略也有問題,一是即便是隨機選取的樞紐元,也有必定機率會選中最大或最小值,影響分治效率;二是隨機數的生成通常開銷很大,影響總體算法效率。blog
樞紐元的最好選擇是數組的中值(也叫作中位數,是第 N/2 個最大的數),可是中值難以求出,而且會明顯下降快速排序的效率。通常的作法是使用左端、右端和中間三個元素的中值做爲樞紐元。例如輸入的數組以下:排序
[8, 1, 4, 9, 6, 3, 5, 2, 7, 0]
左端元素是8,右端元素是0,中間的元素是6,所以能夠肯定樞紐元 v = 6
。顯然,使用三數中值分割法消除了預排序輸入致使的最壞情形。
選取了樞紐元以後,下一步就須要移動元素,將數組拆分紅兩個部分。有個簡便的方法是直接開三個數組,分別是 smaller
, same
,larger
,而後循環遍歷數組的每一個元素,將每一個元素與樞紐元對比,小於樞紐元的 push 進 smaller
數組,等於樞紐元的 push 進 same
數組,大於樞紐元的 push 進 larger
數組,這樣就實現了拆分。可是這樣作無疑會佔用額外空間,實際的快排(例如 JDK 的 sort 方法)都是直接對原數組進行排序的,這樣就要求直接在數組中交換元素,實現數組的分割。主要有兩種方法。
首先選定樞紐元 pivot ,而且設置兩個指針 left 和 right ,初始狀態下 left 和 right 分別位於數組最左和最右側。
接下來進行第1次循環,從 right 指針開始,讓指針所指向的元素和 pivot 進行比較,若是大於或等於 pivot ,則指針向左移動;若是小於 pivot ,則 right 指針中止移動,切換到 left 指針。輪到 left 指針行動,讓指針所指向的元素和 pivot 進行比較,若是小於或等於 pivot ,則指針向右移動;若是大於 pivot ,則 left 中止移動。
當兩個指針都中止移動時,讓 left 和 right 指針所指向的元素進行交換。
而後進行下一輪循環,以此類推。
《數據結構與算法分析(Java 語言描述)》
《漫畫算法》
圖解排序算法(五)之快速排序——三數取中法