快速排序在一般情况下是所有排序算法里面效率最高的,并且属于原地排序(递归需要栈内存为logN,符合原地排序定义)
快速排序原理:
1 选择数组中一个值为基准值,把所有比基准值小的元素移到其左边,所有大于基准值的元素移到其右边
2 将左半数组和右半数组递归进行相同操作,直到数组长为1
示例程序
private static int partition(Comparable[] a, int lo, int hi) {
int i = lo, j = hi;
while (true) {
while (less(a[lo], a[j]) && j > lo) {
j--;
}
while (less(a[i], a[lo])) {
i++;
}
exch(a, i, j);
if (i >= j) {
break;
}
}
exch(a, lo, i);
return i;
}
// eliminate the dependency on input
private static void shuffle(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
exch(a, i, (int)(Math.random() * (i + 1)));
}
}
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) {
return;
}
int j = partition(a, lo, hi);
sort(a, lo, j - 1);
sort(a, j + 1, hi);
}
public static void sort(Comparable[] a) {
shuffle(a);
sort(a, 0, a.length - 1);
}
1 partition(Comparable[]a, int lo, int hi)函数:
1 设置两个变量i与j初始指向数组最左和最右元素,设置数组第一个值为基准值
2 移动j指针向左,直到发现小于基准值的第一个数
3 移动i指针向右,直到发现大于基准值第一个数
4 交换i与j位置的数
5 重复上述操作直到i == j,将基准值与此时i(或j)位置的值交换,此时实现该位置左边值均小于基准值,右边值均大于基准值
2 sort(Comparable[] a, int lo, int hi)函数:
用于递归执行partition函数
1 如果发现hi <= lo,说明该子数组只剩一个元素
2 对数组进行分区
3 递归排序分区后数组
3 sort(Comparable[] a)函数:
用户调用
1 把输入数组顺序打乱
2 调用sort
额外说明:
1 保持输入随机性:快速排序在一般情况下时间复杂度为O(NlogN),但是如果在一次切分后一部分没有把数组分为两部分,而是基准值左侧(或右侧)数组为空,快速排序时间复杂度将会降为O(N²)。如果输入数组为有序时会出现该问题。把数组初始打乱可以避免有序数组导致的效率下降。不过,在数组中出现大量重复元素时,即使打乱数组也无法避免性能降低。
2 时间复杂度:快速排序时间复杂度为O(NlogN)
证明:对于最优情况,每次拆分可以把数组对半分,此时满足分治递归Cn = 2Cn/2 + n,2Cn/2为排序子数组成本,n为切分元素和比较的成本。可以证明Cn=nlogn
尽管其实快速排序切分位置为随机,但是一般情况和最优情况区别不大
3 稳定性:快速排序不稳定,因为在切分时交换元素可能会带来大的跨越
算法改进:
1切换到插入排序:快速排序在数组规模很小时速度低于插入排序,因此在数组长度小于10时可以改用插入排序