快速排序
程序代碼
package com.uplooking.bigdata.datastructure;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] arr = {8, -2, 3, 9, 0, 1, 7, 6};
System.out.println("排序前:" + Arrays.toString(arr));
quickSort(arr);
System.out.println("排序後:" + Arrays.toString(arr));
}
public static void quickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
/**
* 快速排序
* 是一種分而治之的思想,以某一個基準元素爲標準,將集合中比該基準元素小的都放到其左側,反之放到其右側。
* 這樣我們就能夠找到該基準元素在該集合中所處的位置。
* 同理,我們就能夠找到每一個元素在集合中恰當的位置。
* 有點類似於二分查找。
* 一般這個基準元素就是第一個元素。
* 設置兩個指針,一個設置在開頭,一個設置末尾,從末尾開始找,找到一個比基本元素小的便停下來,然後
* 從左側開始找,直到找到一個比基準元素大的元素,停下來,二者元素進行交換,直到兩個指針碰頭,本次循環結束
* 指針指向的位置就是該基準元素在集合中應該所處的位置
* eg
* {8, -2, 3, 9, 0, 1, 7, 6}
* benchmark
* 第一個bm=8
* end = length - 1 = 7
* start=0
* end--,我們發現6比8小,end指針停下來了,當前索引爲j=7
* start++,直到元素爲9的位置停下來,當前索引i=3
* 將i和j所對應的元素進行交換
* {8, -2, 3, [6], 0, 1, 7, [9]}
* i j
* 循環繼續
* end--,到索引爲6的位置又停下來了
* start++,直到和end碰頭都沒有再找到比8大的元素,所以我們就能斷定,碰頭的這個位置就應該是8在該集合中應該在的位置
* 交換8的索引和碰頭的索引
* {8, -2, 3, 6, 0, 1, 7, 9}
* i=j=6
* 交換:{7, -2, 3, 6, 0, 1, 8, 9}
* 同理,我們可以在8左側重複上述操作,8的右側也可以重複上述操作
* 使用遞歸調用的方式來完成集合的排序
* @param arr
*/
public static void quickSort(int[] arr, int low, int high) {
if(low > high) {
return;
}
// 默認以[low, high]中的arr[low]作爲基準值
int index = arr[low];
// 定義左指針
int start = low;
// 定義右指針
int end = high;
// 開始向基準值掃描,當start < end條件不滿足時
// 說明指針碰頭,則需要將基準值與start進行交換
// 即該基準值在整個元素集合中的位置已經確定
// 其左邊的值比基準值小,其右邊的值比基準值大
while (start < end) {
// 按照前面算法的設計,先從右邊開始掃描,直到找到比基準值小的數再停下來
// (下面循環的意義就是,在start < end的前提下,如果end位置的值比index值大或等於,就繼續往左邊找)
while (start < end && arr[end] >= index) {
end--;
}
// 財從左邊開始掃描,直到找到比基準值大的數再停下來
// (下面循環的意義就是,在start < end的前提下,如果start位置的值比index值小或等於,就繼續往右邊找)
while (start < end && arr[start] <= index) {
start++;
}
if (start < end) {
// 前面的循環結束後,如果start還是小於end
// 那麼就交換arr[start]和arr[end]的值
swap(arr, start, end);
}
}
// 上面的循環結束後,start指針和end指針碰頭,交換arr[start]和基準值
arr[low] = arr[start];
arr[start] = index;
// 交換後,arr[start]左邊比它小,右邊比它大,然後再以它爲基準
// 左邊和右邊進行相同的操作
quickSort(arr, low, start - 1);
quickSort(arr, start + 1, high);
}
/**
* 位操作
* 按位與(&)
* 1&1=1
* 1&0=0
* 0&1=0
* 0&0=0
* 異或(^)
* (值不同)就取真(1),否則爲(0)
* 1&1=0
* 1&0=1
* 0&1=1
* 0&0=0
* 非
*
* @param arr
* @param i
* @param j
*/
private static void swap(int[] arr, int i, int j) {
/*int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;*/
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
}
/*
交換a=3和b=5,不用第三方變量,效率最高
方法一:
a = a + b = 8
b = a - b = (8 - 5) = 3
a = a - b = (8 - 3) = 5
異或的方式
a=3-->低8爲0000 0011
b=5-->低8爲0000 0101
a = a ^ b
0000 0011
^ 0000 0101
---------------
0000 0110 --->6
b = a ^ b
0000 0110
^ 0000 0101
--------------
0000 0011--->3
a = a ^ b
0000 0110
^ 0000 0011
---------------
0000 0101--->5
*/
測試
執行結果如下:
排序前:[8, -2, 3, 9, 0, 1, 7, 6]
排序後:[-2, 0, 1, 3, 6, 7, 8, 9]
時間複雜度分析
1.在最快及平均情況下,時間複雜度爲O(nlog2n)。
最壞情況就是每次挑中的中間值不是最大就是最小,其時間複雜度爲O(n^2)。
說明:
平均的時間複雜度記住就好了。
但是最壞的情況確實是可以算出來的,
假設每次選的基準值都是最大的,那麼對於end的操作,一開始就找到比index值小的數,
而對於start的操作,則要一直循環n-1次才能讓循環停下來。
交換一次index值後,選擇的基準值又是最大的,那麼start的操作就是n-2次......
以此類推,直到排序完成,1 + 2 + ... + n - 1,所以數量級爲n^2,這意味着,數據如果是倒序的,使用上面的實現,那麼此時的時間複雜度就爲O(n^2).
2.快速排序法不是穩定排序法。
可以考慮,當基準值index與arr[start]值進行交換時,假設arr[start - 1] == index時,那麼可以看到,其確實是不穩定的。
3.快速排序法是平均運行時間最快的排序法。