排序算法——随机快速排序
引言
隨機快排是一個非常有意思的排序排序算法,它的算法思想用到了如遞歸、荷蘭國旗問題等諸多元素,還意外的引入了隨機性的概念。
以下將逐步總結三個版本的快速排序,由淺入深總結快速排序的經典實現過程。
荷蘭國旗問題參考:《荷蘭國旗問題》
一、快速排序1.0
在荷蘭國旗問題中,我們通過簡單的邏輯可以將一個數組分為兩個區域或三個區域,但往往需要在題目之初給定一個 target 作為目標數以此劃分。
而在快速排序算法中,這個 target 選為排序范圍上的最右邊的數——arr[R],我們以 R 上的數為 target 將R位置左側先劃分為兩塊區域 小于等于區和大于區,最后再將 R 與 大于區域的第一個數交換就可以完成整體的劃分。
通過上面的這個主過程,我們通過遞歸,將左右兩部分遞歸完成上面的過程,最終就可以實現整個數組的有序。
注意,在具體編碼時,由于遞歸每次都需要選出一個數作為基準數,依此劃分兩塊區域,在這個版本中,我們以 R 位置上的數為基準數,以此來劃分,循環完畢后,我們只實現了R位置左側的內容變為小于等于區和大于區,這時必須在循環外部額外單獨處理一次基準數,做法就是將它與小于等于區的右邊+1位置交換,并將小于等于區擴大一個位置即可。
private static int partition(int[] arr, int L, int R) {if (L > R)return -1;if (L == R)return L;int lessEqual = L - 1;int cur = L;while (cur < R) {if (arr[cur] <= arr[R])SortUtil.swap(arr, ++lessEqual, cur);// 這里必須寫在 if 外面自加,如果寫在傳參處自加,會導致大于時無法自加cur++;}SortUtil.swap(arr, ++lessEqual, R);return lessEqual;}最后通過遞歸完成劃分區域的步驟:
public static void quickSort1(int[] arr) {// 注意不要寫成 lenth == 0if (arr == null || arr.length < 2)return;process1(arr, 0, arr.length - 1);}private static void process1(int[] arr, int L, int R) {if (L >= R)return;int M = partition(arr, L, R);process1(arr, L, M - 1);process1(arr, M + 1, R);}說明一下 M,它代表一個[L, R]范圍上的 中點位置,取自 partition 方法的返回值。partition 方法將 L R 范圍上的數組劃分成兩個區域——小于等于區和大于區,M 即為上一次分區劃分的基準數。
就1.0版本的快排而言,由于每次基準數都取自 R 位置上的數,那么思考這樣一個問題,最理想的基準數和最差基準數分別是怎樣的情況呢?
如果可以將 [L, R] 范圍上的數充分等分,那么這就是最理想的基準數,如果范圍上的數都偏向基準數一側,那么就可能使算法花費更長的執行時間。
二、快速排序2.0
在1.0 中,我們選取基準數將[L, R]范圍上的數劃分為兩部分,但是對于基準數重復的情況,它可能會造成在小于等于區不連續的情況,導致可能需要重復選擇相同基準數的問題。
對于這個問題,通過荷蘭國旗問題(三塊分)的方法可以很好的優化。
/** 快排2.0*/public static void quickSort2(int[] arr) {if (arr == null || arr.length < 2)return;process2(arr, 0, arr.length - 1);}/** 迭代處理*/private static void process2(int[] arr, int L, int R) {if (L >= R)return;int[] midRange = Code2_NetherlandsFlag.netherlandsFlag(arr, L, R, arr[R]);process2(arr, L, midRange[0] - 1);process2(arr, midRange[1] + 1, R);}/*** 荷蘭國旗劃分*/public static int[] netherlandsFlag(int[] arr, int L, int R, int target) {if (L > R)return new int[]{-1, -1};if (L == R)return new int[]{L, R};int less = L - 1;int more = R + 1;int curr = L;while (curr < more) {if (arr[curr] == target) {curr++;} else if (arr[curr] < target) {SortUtil.swap(arr, curr, less + 1);less++;curr++;} else {SortUtil.swap(arr, curr, more - 1);more--;}}// 等于區域的左邊界和右邊界 [less + 1, more - 1]return new int[]{less + 1, more - 1};}三、快速排序 3.0 —— 隨機快速排序
在1.0的版本中提到,當 R 位置選取的基準數可以比較好的中分數組元素,那么遞歸函數處理兩側的時間就會大致均分,從而可以達到 O(N * logN) 時間復雜度,而為了更好的達到這一點,在 2.0 基礎之上,我們需要隨機選取數組上的一個元素,將其視為基準數,然后其他的內容基本和 2.0 沒有任何區別。
注意,在隨機快排的遞歸進行前選取基準數的時候,我們依然選取 R 位置上的數,只不過我們會先隨機一個位置,將其換到 R 位置上,在進行遞歸處理:
完整代碼如下:
/** 快排3.0*/public static void quickSort3(int[] arr) {if (arr == null || arr.length < 2)return;process3(arr, 0, arr.length - 1);}private static void process3(int[] arr, int L, int R) {if (L >= R)return;// 隨機選取1個位置上的數換到 R 位置上SortUtil.swap(arr, L + (int) Math.random() * (R - L + 1), R);int[] midRange = Code2_NetherlandsFlag.netherlandsFlag(arr, L, R, arr[R]);process2(arr, L, midRange[0] - 1);process2(arr, midRange[1] + 1, R);}public static int[] netherlandsFlag(int[] arr, int L, int R, int target) {if (L > R)return new int[]{-1, -1};if (L == R)return new int[]{L, R};int less = L - 1;int more = R + 1;int curr = L;while (curr < more) {if (arr[curr] == target) {curr++;} else if (arr[curr] < target) {SortUtil.swap(arr, curr, less + 1);less++;curr++;} else {SortUtil.swap(arr, curr, more - 1);more--;}}// 等于區域的左邊界和右邊界 [less + 1, more - 1]return new int[]{less + 1, more - 1};}四、隨機快排的時間復雜度
在每次隨機選取基準數時,每個數的選中概率是 1/N ,根據數學家的證明,隨機快排的時間復雜度會收斂于 O(N * logN),它并不代表不會出現 O(N ^ 2) 的情況,而是將最壞的情況變成了隨機概率事件。
總結
快速排序借助了幾個非常重要的算法技巧:迭代、荷蘭國旗問題、隨機概率性。
它的時間復雜度會收斂于 O(N * logN)。
?
總結
以上是生活随笔為你收集整理的排序算法——随机快速排序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: codesys com库_CODESYS
- 下一篇: updatepanel失效怎么办_【点滴