分治法
- 分解问题
- 解决子问题
- 合并子问题的解
快速排序思想
分治法:递归
1. 选取privot(中枢):随机的选取一个元素作为中枢;
2. 分割策略:partition算法将数组进行分割,使中枢左边的数组元素均小于中枢,中枢右边的数组元素均大于中枢;
3. 这样就分解为两个子数组,分别对子数组进行递归操作。
代码实现
package sort;
import java.util.Random;
public class QuickSort {
/**
* 快速排序的递归实现
* @param data 待排序数组
* @param start
* @param end
*/
public static void quickSort(int[] data, int start, int end) {
if (data == null || data.length <= 0 || start < 0 || end < 0 || data.length <= start || data.length <= end) {
throw new IllegalArgumentException("Invalid parameters in partition method!");
}
if (start == end) {
return;
}
int index = partition(data, start, end);
if (index > start) {
quickSort(data, start, index - 1);
}
if (index < end) {
quickSort(data, index + 1, end);
}
}
/**
* 随机选取算法
* @param data
* @param start
* @param end
* @return
*/
public static int partition(int[] data, int start, int end) {
if (data == null || data.length <= 0 || start < 0 || end < 0 || data.length <= start || data.length <= end) {
throw new IllegalArgumentException("Invalid parameters in partition method!");
}
int index = new Random().nextInt(end - start + 1) + start;
swap(data, index, end);
int small = start - 1; //指向最后一个小于data[end]的元素
for (index = start; index < end; index++) {
if (data[index] < data[end]) {
small++; //data[index]待放入的位置
if (small != index) {
swap(data, small, index);
}
}
}
small++; //放置data[end]
swap(data, small, end);
return small;
}
/**
* 交换数组中两个元素
* @param data
* @param p
* @param q
*/
public static void swap(int[] data, int p, int q) {
if (data == null || data.length <= 0 || p < 0 || q < 0 || data.length <= p || data.length <= q) {
throw new IllegalArgumentException("Invalid parameters in swap method!");
}
int tmp = data[p];
data[p] = data[q];
data[q] = tmp;
}
}
性能分析:不稳定
最坏情况的划分:每一次递归,划分都是最大程度不平衡的,即T(n)=T(n-1)+T(0)+n=O(n*n)
例如:若输入的数组是已排序(升序)的,并且此时快速排序选取最后一个元素作为中枢(不是随机选取的),那么快速排序的时间复杂度就是O(n*n)。而此时插入排序的时间复杂度最小,为O(n)。
最好情况的划分:最平衡的划分,即T(n)=2T(n/2)+n=O(nlogn)
平均情况的划分:只要划分是常数比例的,运行时间总是O(nlogn)。
例如:即T(n)=T(9n/10)+T(n/10)+cn=O(nlogn),可以通过递归树证明。
应用
partition算法的特点是通过一次遍历,以中枢元素将数组分割成两部分,前部<中枢<=后部。
partition算法和递归结合可以用于解决面试题29 :数组中出现次数超过一半的数字、面试题30:最小的k个数、第k大的数字等问题。