快速排序
顾名思义:快速排序是基于比较的排序算法中最快的一种–复杂度会更趋近于O(nlogn)
执行流程
- 从序列中选择一个轴点(pivot)–假设每次选择0位置的元素为轴点元素
- 利用pivotj将序列分割成2个子序列
- 将小于pivot的元素放在pivot前面(左侧)
- 将大于pivot的元素放在pivot后面(右侧)
- 等于pivot的元素放那边都可以
就是帮第一个元素找到他对应的位置
- 对子序列进行1, 2 操作
- 递归操作
- 直到不能再分割(子序列中只剩下一个元素)递归结束条件
轴点构造
- 第一步: 将第一个元素存储(用它来构造轴点)
- 从右开始比较,如果
大于
轴点元素则end
指针向前移动一位,如果小于等于
则用end指针的元素将begin位置覆盖并调转方向,开始移动begin
- 类似第二步,移动begin
- 当
begin == end
时,说明已经移动完成,该位置就为轴点元素的存放位置,此时将第一步保存的轴点元素存在在此
代码
/**
* 对 [begin, end) 范围的元素进行快速排序
* @param begin
* @param end
*/
private void sort(int begin, int end) {
if (end - begin < 2) return; // 递归的结束条件
// 确定轴点位置 O(n)
int mid = pivotIndex(begin, end);
// 对子序列进行快速排序
sort(begin, mid);
sort(mid + 1, end);
}
/**
* 构造出 [begin, end) 范围的轴点元素
* @return 轴点元素的最终位置
*/
private int pivotIndex(int begin, int end) {
// 随机选择一个元素跟begin位置进行交换
swap(begin, begin + (int)(Math.random() * (end - begin)));
// 备份begin位置的元素
T pivot = array[begin];
// end指向最后一个元素
end--;
while (begin < end) {
while (begin < end) {
if (cmp(pivot, array[end]) < 0) { // 右边元素 > 轴点元素
end--;
} else { // 右边元素 <= 轴点元素
array[begin++] = array[end];
break;
}
}
while (begin < end) {
if (cmp(pivot, array[begin]) > 0) { // 左边元素 < 轴点元素
begin++;
} else { // 左边元素 >= 轴点元素
array[end--] = array[begin];
break;
}
}
}
// 将轴点元素放入最终的位置
array[begin] = pivot;
// 返回轴点元素的位置
return begin;
}
注意点:
// 随机选择一个元素跟begin位置进行交换
swap(begin, begin + (int)(Math.random() * (end - begin)));
可以直接使用第一个元素进行轴点构造,但是使用随机可以使用使构造更均匀–可以减小时间复杂度 (后面分析)
while () {
while() {
}
while() {
}
}
// 嵌套循环可以实现类似开关的作用
时间复杂度
- 在轴点左右元素数量比较均匀的情况下,同时也是最好的情况
T(n) = 2 * T(n / 2) + O(n) = O(nlogn)
- 如果轴点数量极度不均匀,最坏情况
T(n) = T(n - 1) + O(n) = O(n^2)
- 为了降低最坏情况的出现概率,一般采取的做法是
随机选择轴点元素
- 最好,平均时间复杂度: O(nlogn)
- 最坏时间复杂度:O(n^2)
- 由于递归调用的缘故,空间复杂度O(logn)
- 属于不稳定排序
补充
关于递归算法的复杂度分析建议阅读《算法导论》中关于递归地章节,确实很枯燥但是学完感觉对分析有很好的认识,里面包含的主方法,递归树,公式等的详细分析