前言
快速排序应该是排序中最出名的算法了,也可能是应用最广泛的排序算法了。他之所以流行,是因为实现简单,同时适用于各种数据,且一般比其他排序算法要快。
快排原理
快排是对冒泡排序的一种改进,它之所以快是因为一次交换能改变多个逆序对,而冒泡排序只能改变一个逆序对。
而快排的基本思想即是:通过一趟排序将欲排序的数据分割成两部分,其中一部分数据比另一部分所有数据都要小。接着继续对这两部分数据进行快速排序。则是分治思想的体现。
下面我们用图例来看看一趟排序
此过程如下:
- 选定数组a[lo]作为切分元素,同时令i = lo, j = hi,即对应lo、hi位置
- i指针从左向右扫描,遇见>v的数时暂停;j指针从右向左扫描,遇见<v的数时暂停。i、j指针指向数字交换;重复2流程,直至i >= j。
- 当2流程结束时,此时j指向小于切分元素的数字,进行交换。此时切分元素已位于最终位置,切分元素左侧元素均小于切分元素,右侧元素均大于切分元素
- 对却分后的子数组重复上述流程:sort(array, lo, j - 1);sort(array, j + 1, hi)。直至递归结束
代码实现
核心代码sort与partition,sort用于递归调用,partition用于切分
sort(array, lo, hi) {
if(hi <= lo ){
return ;
}
const j = this.partition(array, lo, hi);
this.sort(array, lo, j - 1);
this.sort(array, j + 1, hi);
}
partition(array, lo, hi){
let i = lo;
let j = hi + 1;
const ele = array[lo];//切分元素
while(true){
while(this.less(array[++i], ele)){//左向右扫描
if(i === hi){
break;
}
}
while(this.less(ele, array[--j])){//右向左扫描
if(j === lo){
break;
}
}
if(i >= j){
break;
}
this.exch(array, i ,j);//左右元素交换
}
this.exch(array, lo, j);//切分元素交换
return j;
}
性能特点
主要优点:
- 实现简单
- 一般情况下比其他排序算法快得多
- 长度为N的数组排序时间为NlgN
- 内循环比大多数排序算法都要短小
主要缺点:
- 脆弱,在切分不平衡时可能导致极低的性能。比如倒序变升序
总结
以上的快排是最基础的版本。针对不同的场景,其实还有很多的改进空间使得性能提升,比如:
- 对于小数组可在快排中切换至插入排序
- 对于含有大量重复元素的数组,比如生日、性别,可将数组切分为三部分,分别对应小于、等于、大于切分元素的数组元素
以上内容也主要是学习于《算法(第4版)》一书中,感兴趣的同学可以查阅。
参考资料
《算法(第4版)》
快速排序算法详解(原理、实现和时间复杂度)