快速排序也是分而治之的思想,是所有的内排中最好的一种。
public class FastSort {
public static int partition(int[] arr, int left, int right){
int i = left, j = right;
int pivot = arr[i];
while(i < j){
while(i < j && pivot <= arr[j]) j--;
if(i < j) arr[i++] = arr[j];
while(i < j && pivot >= arr[i]) i++;
if(i < j) arr[j--] = arr[i];
}
arr[i] = pivot;
return i;
}
// 递归实现
public static void sort(int[] arr, int left, int right){
if(left >= right) return;
int p = partition(arr, left, right);
sort(arr, left, p-1);
sort(arr, p+1, right);
}
// 非递归实现
public static void NonRecruitSort(int[] arr, int left, int right){
Stack<Integer> stack = new Stack<>();
stack.push(left);
stack.push(right);
while(!stack.isEmpty()){
int high = stack.pop();
int low = stack.pop();
int part = partition(arr, low, high);
if(low < part-1){
stack.push(low);
stack.push(part-1);
}
if(part+1 < high){
stack.push(part+1);
stack.push(high);
}
}
}
public static void main(String[] args) {
int[] arr = {12,34,11,45,25};
NonRecruitSort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
}
快排的趟数取决于递归的深度,最好的情况下是 O ( l o g 2 n ) O(log_2n) O(log2n),最差的情况下是 O ( n 2 ) O(n^2) O(n2),平均 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),这里注明一下最坏的情况是正序或者倒序,最好的情况是每次都能划分相等的两份,怎么优化这个问题,避免最坏的情况呢?这里斯坦福提出了一种选择pivot的方法:median-of-three.
选取第一个、最后一个以及中间的元素的中位数,如4 5 6 7, 第一个4, 最后一个7, 中间的为5, 这三个数的中位数为5, 所以选择5作为pivot,8 2 5 4 7, 三个元素分别为8 5 7, 中位数为7, 所以选择最后一个元素7作为pivot,其实现如下:
private static int choosePivotMedianOfThree(int[] a, int left, int right) {
int mid = 0;
if((right-left+1) % 2 == 0) mid = left + (right-left+1)/2 - 1;
else mid = left + (right-left+1)/2;
//只需要找出中位数即可,不需要交换
//有的版本也可以进行交换
if(((a[left]-a[mid]) * (a[left]-a[right])) <= 0) return left;
else if (((a[mid]-a[left]) * (a[mid]-a[right])) <= 0) return mid;
else return right;
}
然后我们在划分的时候,只需要把 choosePivotMedianOfThree 得到的下标和left进行交换相应下标位置的元素,划分依旧没有变化。类似于这样子:
int pivot_index = choosePivotMedianOfThree(a, left, right);
//始终将第一个元素作为pivot, 若不是, 则与之交换
if (pivot_index != left) {
swap(a, pivot_index, left);
}
int pivot = a[left];
举一个牛客上快排的例子,这题用堆也是可以做到的,小根堆,但是我们用快排做:
import java.util.*;
public class Finder {
public int findKth(int[] a, int n, int K) {
// write code here
int left = 0, right = n-1;
if(right <= left || n == 0) return 0;
int p = partition(a, left, right);
while(p != n-K){
if(p > n-K){
right = p-1;
p = partition(a, left, right);
}
if(p < n-K){
left = p+1;
p = partition(a, left, right);
}
}
return a[p];
}
public int partition(int[] a, int low, int high){
int i = low, j = high;
int pivot = a[low];
while(i<j){
while(j > i && pivot <= a[j]) j--;
a[i] = a[j];
while(j > i && pivot >= a[i]) i++;
a[j] = a[i];
}
a[i] = pivot;
return i;
}
}