2.1.5 Quick Sort
思想:先根據樞紐值(pivot)將資料序列分成兩個子序列, 使左邊序列的所有值都小於pivot, 且右邊都大於pivot, 然後採用同樣的方法來對每個子序列進行快速排序, 最後得到排好順序的資料.
快速排序的重點之一, 就在於選取合理的pivot, 也就是通過pivot來把整個資料序列分成兩個序列.
目前常用的方式是"三資料項取中(a.k.a. Balanced Quick Sort)", 即對資料的第一個, 中間一個及最後一個位置的資料, 找到這三者的中間項. 譬如說: 第一個為1, 中間為19, 最後一個為7, 故取三者的中間(1, 7, 19)的話, 即為7.
另一個重點, 就在於把資料分成兩個序列, 且要滿足條件(左邊小於pivot, 右邊大於pivot).
時間複雜度為: O(nlogn), 最差為O(n^2) -> 在partition的部分, 因為要將所有的元素都拿來跟pivot比過一次, 所以迭代所有元素的時間複雜度是O(n), 合併雖然會因為實作而異, 但了不起就是O(n)了; 因此重點就會是在切割的次數, 畢竟切割次數會被pivot的選擇影響到, 最好的情況下, 就是每次都切割成相同大小的子陣列, 這樣就是切割logn次. 假如運氣太差了, 即每次取pivot都取到該數列的最大或最小值(碰到完全排好的資料然後你pivot又只抓最右邊或最左邊就會變這樣), 如此一來, 變成將原本的陣列切成0跟n-1(不包含pivot), 這樣就會有n次的遞迴呼叫, 故時間複雜度最佳為O(nlogn), 最差為O(n^2).
空間複雜度: 最佳為O(nlogn), 最差為O(n^2) -> 由於每次都會把資料分成兩份子陣列, 因此會申請兩個新的子陣列記憶體空間, 對每個遞迴來說這部分的空間複雜度就是O(n), 而遞迴呼叫的最佳情況為logn次, 最差為n次, 所以空間複雜度的最佳為O(nlogn), 最差為O(n^2).
In-place版本: 這種做法可以讓我們不用為子陣列申請新的記憶體空間, 所以每次遞迴都只需要O(1)的空間複雜度(for swap function), 而通常此版本的一個特色就是僅需要做partition的操作, 不必再有merge了, 因為partition的時候也做完merge了.
以下的範例就是in-place的版本, pivot是直接取中間的元素.
package idv.carl.sorting.quicksort;
/**
* @author Carl Lu
*/
public class QuickSortByMiddlePivot {
public static void sort(int[] data) {
if (data == null || data.length == 0) {
return;
}
quickSort(data, 0, data.length - 1);
}
private static void quickSort(int[] data, int leftBound, int rightBound) {
int left = leftBound;
int right = rightBound;
int pivot = data[leftBound + ( rightBound - leftBound ) / 2];
while (left < right) {
while (data[left] < pivot) {
left++;
}
while (data[right] > pivot) {
right--;
}
if (left <= right) {
swap(data, left, right);
left++;
right--;
}
}
if (leftBound < right) {
quickSort(data, leftBound, right);
}
if (left < rightBound) {
quickSort(data, left, rightBound);
}
}
private static void swap(int[] data, int left, int right) {
int temp = data[left];
data[left] = data[right];
data[right] = temp;
}
}
原始碼點我
測試程式:
package idv.carl.sorting.quicksort;
import org.junit.Test;
import java.util.Arrays;
import static org.junit.Assert.*;
/**
* @author Carl Lu
*/
public class QuickSortByMiddlePivotTest {
@Test
public void testForNullInput() {
int[] actual = null;
QuickSortByMiddlePivot.sort(actual);
assertNull(actual);
}
@Test
public void testForEmptyInput() {
int[] actual = new int[] {};
QuickSortByMiddlePivot.sort(actual);
assertEquals(0, actual.length);
}
@Test
public void testForNormalCase() {
int[] actual = new int[] {9, -32, 102, 4, -5, 8, 0, 7, 77, 1, 30};
int[] expected = new int[] {-32, -5, 0, 1, 4, 7, 8, 9, 30, 77, 102};
QuickSortByMiddlePivot.sort(actual);
assertTrue(Arrays.equals(expected, actual));
}
@Test
public void testForDuplicateElement() {
int[] actual = new int[] {7, 7, 8, 8, 7, 7, 8, 8, 9, 9, -1, -1, 2, 2, 0};
int[] expected = new int[] {-1, -1, 0, 2, 2, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9};
QuickSortByMiddlePivot.sort(actual);
assertTrue(Arrays.equals(expected, actual));
}
}
原始碼點我
關於程式的解說, 可以看這個commit.
Last updated
Was this helpful?