JAVA十大排序算法之快速排序詳解
快速排序
快速排序是對冒泡排序的一種改進,也是采用分治法的一個典型的應用。JDK中Arrays的sort()方法,具體的排序細節(jié)就是使用快速排序實現(xiàn)的。
從數(shù)組中任意選取一個數(shù)據(jù)(比如數(shù)組的第一個數(shù)或最后一個數(shù))作為關鍵數(shù)據(jù),我們稱為基準數(shù)(pivot,或中軸數(shù)),然后將所有比它小的數(shù)都放到它前面,所有比它大的數(shù)都放到它后面,這個過程稱為一趟快速排序,也稱為分區(qū)(partition)操作。
問題
若給定一個無序數(shù)組 [8, 5, 6, 4, 3, 1, 7, 2],并指定一個數(shù)為基準,拆分數(shù)組使得左側的數(shù)都小于等于它 ,右側的數(shù)都大于它。
基準的選取最優(yōu)的情況是基準值剛好取在無序區(qū)數(shù)值的中位數(shù),這樣能夠最大效率地讓兩邊排序,同時最大地減少遞歸劃分的次數(shù),但是一般很難做到最優(yōu)?;鶞实倪x取一般有三種方式:
- 選取數(shù)組的第一個元素
- 選取數(shù)組的最后一個元素
- 以及選取第一個、最后一個以及中間的元素的中位數(shù)(如4 5 6 7, 第一個4, 最后一個7, 中間的為5, 這三個數(shù)的中位數(shù)為5, 所以選擇5作為基準)。
思路
- 隨機選擇數(shù)組的一個元素,比如 6 為基準,拆分數(shù)組同時引入一個初始指針,也叫分區(qū)指示器,初始指針指向 -1
- 將數(shù)組中的元素和基準數(shù)遍歷比較
- 若當前元素大于基準數(shù),不做任何變化
- 若當前元素小于等于基準數(shù)時,分割指示器右移一位,同時
- 當前元素下標小于等于分區(qū)指示器時,當前元素保持不動
- 當前元素下標大于分區(qū)指示器時,當前元素和分區(qū)指示器所指元素交換

荷蘭國旗問題
荷蘭的國旗是由紅白藍三種顏色構成,如圖:

若現(xiàn)在給一個隨機的圖形,如下:

把這些條紋按照顏色排好,紅色的在上半部分,白色的在中間部分,藍色的在下半部分,這類問題稱作荷蘭國旗問題。
對應leetcode:顏色分類
給定一個包含紅色、白色和藍色,一共 n 個元素的數(shù)組,原地對它們進行排序,使得相同顏色的元素相鄰,并按照紅色、白色、藍色順序排列。
分析:
假如給定一個數(shù)組[8, 3, 6, 2, 5, 1, 7, 5],做如下操作:
1.隨機選擇數(shù)組的一個元素,比如 5 為基準,拆分數(shù)組同時引入一個左分區(qū)指示器,指向 -1,右分區(qū)指示器指向基準數(shù)(注:此時的基準數(shù)為尾元素)
2.若當前元素大于基準數(shù),右分區(qū)指示器左移一位,當前元素和右分區(qū)指示器所指元素交換,
索引保持不變
3.若當前元素小于等于基準數(shù)時,左分區(qū)指示器右移一位,索引右移
- 當前元素大于等于左分區(qū)指示器所指元素,當前元素保持不動
- 當前元素小于左分區(qū)指示器所指元素,交換
簡單來說就是,左分區(qū)指示器向右移動的過程中,如果遇到大于或等于基準數(shù)時,則停止移動,右分區(qū)指示器向左移動的過程中,如果遇到小于或等于主元的元素則停止移動。這種操作也叫雙向快速排序。

代碼實現(xiàn)
public class QuickSort {
public static final int[] ARRAY = {8, 5, 6, 4, 3, 1, 7, 2};
public static final int[] ARRAY2 = {8, 3, 6, 2, 5, 1, 7, 5};
private static int[] sort(int[] array, int left, int right) {
if (array.length < 1 || left > right) return null;
//拆分
int partitionIndex = partition(array, left, right);
//遞歸
if (partitionIndex > left) {
sort(array, left, partitionIndex - 1);
}
if (partitionIndex < right) {
sort(array, partitionIndex + 1, right);
}
return array;
}
/**
* 分區(qū)快排操作
*
* @param array 原數(shù)組
* @param left 左側頭索引
* @param right 右側尾索引
* @return 分區(qū)指示器 最后指向基準數(shù)
*/
public static int partition(int[] array, int left, int right) {
//基準數(shù)下標---隨機方式取值,也就是數(shù)組的長度隨機1-8之間
int pivot = (int) (left + Math.random() * (right - left + 1));
//分區(qū)指示器索引
int partitionIndex = left - 1;
//基準數(shù)和尾部元素交換
swap(array, pivot, right);
//按照規(guī)定,如果當前元素大于基準數(shù)不做任何操作;
//小于基準數(shù),分區(qū)指示器右移,且當前元素的索引大于分區(qū)指示器,交換
for (int i = left; i <= right; i++) {
if (array[i] <= array[right]) {//當前元素小于等于基準數(shù)
partitionIndex++;
if (i > partitionIndex) {//當前元素的索引大于分區(qū)指示器
//交換
swap(array, i, partitionIndex);
}
}
}
return partitionIndex;
}
/**
* 雙向掃描排序
*/
public static int partitionTwoWay(int[] array, int left, int right) {
//基準數(shù)
int pivot = array[right];
//左分區(qū)指示器索引
int leftIndex = left - 1;
//右分區(qū)指示器索引
int rightIndex = right;
//索引
int index = left;
while (index < rightIndex) {
//若當前元素大于基準數(shù),右分區(qū)指示器左移一位,當前元素和右分區(qū)指示器所指元素交換,索引保持不變
if (array[index] > pivot) {
swap(array, index, --rightIndex);
} else if (array[index] <= pivot) {//當前元素小于等于基準數(shù)時,左分割指示器右移一位,索引右移
leftIndex++;
index++;
//當前元素小于等于左分區(qū)指示器所指元素,交換
if (array[index] < array[leftIndex]) {
swap(array, index, leftIndex);
}
}
}
//索引和 L 指向同一個元素
swap(array, right, rightIndex);
return 1;
}
//交換
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void print(int[] array) {
for (int i : array) {
System.out.print(i + " ");
}
System.out.println("");
}
public static void main(String[] args) {
print(ARRAY);
System.out.println("============================================");
print(sort(ARRAY, 0, ARRAY.length - 1));
System.out.println("====================雙向排序==================");
print(ARRAY2);
System.out.println("============================================");
print(sort(ARRAY2, 0, ARRAY2.length - 1));
}
}
時間復雜度
在拆分數(shù)組的時候可能會出現(xiàn)一種極端的情況,每次拆分的時候,基準數(shù)左邊的元素個數(shù)都為0,而右邊都為n-1個。這個時候,就需要拆分n次了。而每次拆分整理的時間復雜度為O(n),所以最壞的時間復雜度為O(n2)。什么意思?舉個簡單例子:
在不知道初始序列已經有序的情況下進行排序,第1趟排序經過n-1次比較后,將第1個元素仍然定在原來的位置上,并得到一個長度為n-1的子序列;第2趟排序經過n-2次比較后,將第2個元素確定在它原來的位置上,又得到一個長度為n-2的子序列;以此類推,最終總的比較次數(shù):
C(n) = (n-1) + (n-2) + … + 1 = n(n-1)/2
所以最壞的情況下,快速排序的時間復雜度為O(n^2)
而最好的情況就是每次拆分都能夠從數(shù)組的中間拆分,這樣拆分logn次就行了,此時的時間復雜度為O(nlogn)。
而平均時間復雜度,則是假設每次基準數(shù)隨機,最后算出來的時間復雜度為O(nlogn)
參考:快速排序的時間復雜度與空間復雜度
算法穩(wěn)定性
通過上面的分析可以知道,在隨機取基準數(shù)的時候,數(shù)據(jù)是可能會發(fā)生變化的,所以快速排序有不是穩(wěn)定的情況。
總結
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關注腳本之家的更多內容!
相關文章
java異步編程CompletableFuture使用示例詳解
這篇文章主要為大家介紹了java異步編程CompletableFuture使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
SpringBoot org.springframework.beans.factory.Unsatisfie
本文主要介紹了SpringBoot org.springframework.beans.factory.UnsatisfiedDependencyException依賴注入異常,文中通過示例代碼介紹的很詳細,具有一定的參考價值,感興趣的可以了解一下2024-02-02
解決mybatis使用foreach批量insert異常的問題
這篇文章主要介紹了解決mybatis使用foreach批量insert異常的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
JeecgBoot框架升級至Spring?Boot3的實戰(zhàn)步驟
本文主要介紹了JeecgBoot框架升級至Spring?Boot3的實戰(zhàn)步驟,從?2.7.10升級到3.1.5有以下幾個點需要注意,下面就來詳細的介紹一下,感興趣的可以了解一下2024-04-04
Spring實戰(zhàn)之抽象Bean和子Bean定義與用法示例
這篇文章主要介紹了Spring實戰(zhàn)之抽象Bean和子Bean定義與用法,結合實例形式分析了Spring抽象Bean和子Bean相關配置、定義與使用操作技巧,需要的朋友可以參考下2019-11-11
ScheduledThreadPoolExecutor巨坑解決
這篇文章主要為大家介紹了使用ScheduledThreadPoolExecutor遇到的巨坑解決示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02

