系列文章目录
【每天学一点 - 算法篇 - 排序 - 插入排序】
【每天学一点 - 算法篇 - 排序 - 希尔排序】
【每天学一点 - 算法篇 - 排序 - 堆排序】
【每天学一点 - 算法篇 - 排序 - 归并排序】
前言
新的一年,祝大家吉时吉日吉如风,丰年丰月如风增,增福增禄增长寿,寿山寿水寿长生,生财生利生贵子,子孝孙贤代代荣,荣华富贵年年有,有财有势有前程。
一、什么是快速排序
快速排序不像前面说的插入排序,缩减增量排序,堆排序,归并排序那样,排序方式就是排序名字,很好理解,快速排序在只用到比较的排序算法中就已经是非常小的时间复杂度,又保持住了空间复杂度没有扩展,所以只能说这种排序确实很快速,嗯,就是这样,快速排序
二、快速排序原理
1、思路
快速排序也是用的分治的思路,提到分治,自然最先想到归并排序,
因为都是分治,所以快速排序还是可以跟归并排序放在一起理解的。
归并是通过将数组分到最小,然后合并的时候进行排序,
而快速排序则是在将数组往小分的时候保证一定程度有序,分到最后形成有序数组。
之前说过,归并排序快是快在通过临时数组储存了每次比较得到的比较关系,
而快速排序则是在当前数组里面,去切分出左右两个数组,
这样就即储存了比较关系,又保持了空间复杂度没有变高。
快速排序还有一个比较重要的点就是如何去选取一个合适的数,
来跟每个数子比较,能让切分出来的左右两个数组尽可能大小相近,
这个数本可以是数组中任意的随机一个数,
为防止数组可能预排序过或为倒序数组,故可选取数组最中间的数作为基础值
2、示例
3、抽象
选取数组中间位置数字为基础值,
从数组左侧向右依次寻找小于基础值数字,
找到后再从数组右侧向左依次寻找大于基础值数字,
均寻找到后,两个数字进行交换,
持续进行左右数字的查找和交换,
直到左边数组为小于基础值数组,右边数组为大于基础值数组,
然后左边数组和有右边数组再分别进行快速排序,
数组无法再分,则完成整体排序
4、改进
1)基础值的选取可改进为通过选最左的数,中间的数,最右的数进行预排序,
小值放入数组首位,大值放入数组末尾,由中值作为基础值,可提升基础值的可靠性。
2)因为被选中的基础值预计就是要保存在左右两组之间,并不需要参与左右两组之间位置的交换,
所以实际使用中,一般先将基础值存放在数组末端,等完成左右分组后再放回两组的中间位置。
3)改进方向均为考虑较大数组排序情况,所以分治过程中,数组小于一定程度时,可改为较简单的插入排序快速完成排序。
三、快速排序代码
实现方法
private void quickSort(int[] a, int left, int right) {
//数组较大时用快速排序,否则用插入排序
if (right - left > CUTOFF) {
//由数组最左,最右,和中间三个数预排序,选取基础值并放在右边第二个
int base = mid(a, left, right);
int i = left, j = right - 1;
for (; ; ) {
//数组从左往右查找大于基础值的数
while (a[++i] < base) {
}
;
//数组从右往左查找小于基础值的数
while (a[--j] > base) {
}
;
if (i < j) {
swap(a, i, j);
} else {
break;
}
}
//此时以i为分界,分为左右两组
swap(a, i, right - 1);
//左右两组再分别进行快速排序
quickSort(a, left, i - 1);
quickSort(a, i + 1, right);
} else {
insertionSort(a, left, right);
}
}
预排序方法
private int mid(int[] a, int left, int right) {
int mid = (left + right) / 2;
if (a[left] > a[mid]) {
swap(a, left, mid);
}
if (a[left] > a[right]) {
swap(a, left, right);
}
if (a[mid] > a[right]) {
swap(a, mid, right);
}
swap(a, mid, right - 1);
return a[right - 1];
}
四、快速排序复杂度
老规矩,先写结论
O(NlogN)
原因上面也大概说了,时间复杂度就类比归并排序看就可以,开始排序的好处在于除了时间复杂度比较优秀,而且没有造成空间复杂度的变化
总结
继续闭关修炼。。。。。