快速排序主要採用分治的思想。假設對一個數組arr[l…r]進行快速排序,採用分治思想可以分爲三分:
分解:選取一個主元,假設它的索引位置爲q,將arr[l…r]以q爲分割點分割爲兩個(可能爲空)子數組arr[l…q-1]和arr[q+1…r],使得arr[l…q-1]這部分的元素都小於arr[q],而arr[q+1…r]這部分的元素都大於arr[q],同時在劃分的過程中也要計算出分割點q。
解決:通過遞歸調用快速排序,對子數組arr[l..q-1]和arr[q+1…r]進行快速排序。
合併:因爲子數組都是原址排序,所以不需要合併,因爲數組arr[l…r]已經有序。
下面是實現代碼:
public static void quickSort1(int[] nums){
quickSort1(nums, 0, nums.length - 1);
}
private static void quickSort1(int[] nums, int start, int end) {
if (start < end){
int mid = partition(nums,start,end);
quickSort1(nums, start, mid - 1);
quickSort1(nums, mid + 1, end);
}
}
private static int partition(int[] nums, int start, int end) {
int i = start-1;
for (int j = start; j < end; j++) {
if (nums[j] < nums[end]){
i++;
swap(nums,i,j);
}
}
swap(nums,i+1,end);
return i+1;
}
// 交換元素
private static void swap(int[] nums, int i, int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
優化1
我們知道快速排序在劃分子數組的時候最理想的狀況就是將劃分出來的兩個子數組的長度都相等。那我們就很容易想到遍歷數組來找到一個主元使得劃分的兩個子數組的長度相等,但是遍歷數組要消耗大量的時候,所以這樣做並不會節省運行時間。那麼我們能否找到一種簡便的方案是我們不必要遍歷數組呢?很容易想到假如我們取start,mid,end這三個位置的中間值作爲主元。下面是實現代碼:
// 目標使得 nums[start] < nums[end] < nums[mid]
private static void threeMedian(int[] nums, int start, int end) {
int mid = (start+end) >> 1;
// 使得 nums[start] < nums[mid]
if (nums[start] > nums[mid])
swap(nums,start,mid);
// 使得 nums[start] < nums[end]
if (nums[start] > nums[end])
swap(nums,start,end);
// 使得 nums[end] < nums[mid]
if (nums[end] > nums[mid])
swap(nums,mid,end);
}
private static void quickSort2(int[] nums, int start, int end) {
if (start < end){
// 三數取中
threeMedian(nums,start,end);
int mid = partition(nums,start,end);
quickSort2(nums, start, mid - 1);
quickSort2(nums, mid + 1, end);
}
}
優化2
爲了避免劃分數組出現最壞的情況(此情況出現在數組是有序數組,所以子數組劃分就會出現一個子數組長度爲0,另一個長度爲end-start),此時快速排序的時間複雜度爲O(n*n),我們在長度小於一定長度的時候使用插入排序(插入排序數組完全有序的時候時間複雜度爲O(n))。下面是實現代碼:
// 使用插入排序
private static void insertSort(int[] nums,int start,int end){
int j;
for (int i = start+1; i <= end; i++) {
int tmp = nums[i];
for (j = i; j > start && tmp < nums[j-1]; j--)
nums[j] = nums[j-1];
nums[j] = tmp;
}
}
private static void quickSort3(int[] nums, int start, int end) {
//當數組的長度小於10調用插入排序
if (end - start + 1 < 10){
insertSort(nums,start,end);
return;
}
if (start < end){
// 三數取中
threeMedian(nums,start,end);
int mid = partition(nums,start,end);
quickSort3(nums, start, mid - 1);
quickSort3(nums, mid + 1, end);
}
}
單鏈表的快速排序
首先我們來看一下partition的實現代碼:
private static ListNode partition(ListNode start, ListNode end) {
// 防止劃分的子鏈表爲null
if (start == null || end == null)
return null;
int key = start.val;
ListNode p = start;
ListNode q = start.next;
while (q != null){
if (q.val < key){
p = p.next;
swap(p,q);
}
q = q.next;
}
swap(start,p);
return p;
}
和數組的快速排序的思路一樣,利用主元節點將鏈表劃分爲兩個部分。下面是實現代碼:
public static void quickSortList(ListNode head){
if (head == null || head.next == null)
return;
ListNode tail = head;
while (tail.next != null)
tail = tail.next;
quickSortList(head,tail);
}
private static void quickSortList(ListNode start, ListNode end) {
if (start != end){
ListNode q = partition(start,end);
if (q == null)
return;
quickSortList(start,q);
quickSortList(q.next,end);
}
}
private static void swap(ListNode p, ListNode q) {
int tmp = p.val;
p.val = q.val;
q.val = tmp;
}