前言
排序算法非常多,几乎每个人学的第一个排序算法都是冒泡算法,但是冒泡算法的时间复杂度是很高的,是一种效率很低的算法。而目前来说,快速排序是相对比较好的一种算法:实现难度低,时间复杂度低。但快速排序在一些情况下也可能出现退化到和冒泡算法一样的时间复杂度,所以需要读者注意一下,下面我会讲到。那么接下来就来看看这个算法。
笔者才疏学浅,有不同观点欢迎评论区或私信讨论。如需转载请留言告知。
另外欢迎阅读笔者的个人博客一只修仙的猿的个人博客,更精美的UI,拥有更好的阅读体验。
算法思路
递归算法的思路其实很简单:
- 从数据中取出一个数,即为mid
- 比mid小的数放在左边,比mid大的数放在右边
- 最后把mid放在中间
- 对左右两边的子数组进行递归排序,直到只剩下一个元素则全部排序完成。
具体的实现思路我们举一个例子:
现在有一个数组:
这里细心的读者会发现诶第三个数怎么后面有一点,这个不是手滑打出来的。而是为了证明在排序的过程中,两个3的顺序是否会发生颠倒,从得出这是否是一个稳定的排序。
首先我们取出一个数,当成哨兵。这里我们取第一个数
5
当成哨兵。然后我们把这个数和最后一个数交换。为什么需要交换呢?这样我们就不需要去额外设置一个变量存储哨兵了。如图:接下来我们设置两个变量:
min
和max
们分别表示比mid小的数的最大的下标,和比mid大的数最小的坐标。举个例子如下图:
接下来我们从左边开始,如果
min
指向的数比哨兵小,则min = min+1
,否则则停下来。执行完之后如下图:然后从右边开始,如果
max
指向的数大于等于哨兵,则max = max-1
,否则则停下来,执行完成之后如下图:然后把min和max指向的数字进行交换:(这里可以看到两个3的顺序发生了颠倒,所以这是一个不稳定的排序)
重复3,4,5步骤直到
min==max
把下标为
min
的数字和哨兵进行交换,至此一轮的排序已经完成:对前后的子数组进行递归排序。完成排序。
快速排序的核心就是利用递归分治的思路来降低时间复杂度。如果我们每次都刚好选到中位数,那么递归树的高度就是logn
(这里的n代表元素的个数),每一层递归都需要遍历一次数组,那么时间复杂度最好的情况就是:
O(logn)
但是,如果每次都取到最小或者最大的数,那么快排的递归树高度则为n,那么他的时间复杂度将退化为:
O(n^2)
由于只需要常量空间,所以空间复杂度为:
O(1)
代码示范
下面使用java语言做一个规范。接口参数为:整型数组,数组的开始下标,数组的结束下标。(因可能是子数组所以需要下标参数)
private void fastSort(int[] nums,int start,int end) {
// 终止条件:start>=end
if(start>=end) return;
// 记录原始的下标
int startRow = start;
int endRow = end;
// 采用随机数获取下标,可以降低退化到n^2的概率
int random = new Random().nextInt(end-start)+start;
// 交换两个数
swap(nums,random,end);
// 重复上述的3,4,5步骤
while(start<end){
// 记得这里的每一步都必须判断start<end
while( nums[start]<nums[endRow] && start<end ){
start++;
}
while( nums[end]>=nums[endRow] && start<end ){
end--;
}
// 如果相同则把哨兵放到中间,排序结束
// 否则start和end交换位置,继续循环
if(start==end) swap(nums,start,endRow);
else{
swap(nums,start,end);
}
}
// 最后对子数组进行递归排序
paixu(nums,startRow,start-1);
paixu(nums,start+1,endRow);
}
// 交换数组中的两个数
private void swap(int[] nums,int one,int two){
int temp = nums[one];
nums[one] = nums[two];
nums[two] = temp;
}