快速排序
名副其实,它在排序中以“快”而闻名。它是处理大数据最快的排序算法之一
基本思想:
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
算法的具体实现:
虽然网上介绍快速排序的文章很多,但是具体怎么实现每一步的讲解却很少,对于大多数初学者很去难弄懂里面的关键。下面我来详细介绍一下每一步的实现过程。
快速排序分成两步,①一趟排序,②递归调用
数组:arr=[5,6,22,1,7,2];
一趟排序的实现过程:
i表示左边“↑”的坐标,指左边下一次等待比较的位置
j表示右边“↑”的坐标,指右边下一次等待比较的位置
X表示基准数,当前我们选取左边第一个数5作为基准数。
目标:小于等于基准数的放在基准数的左边,大于基准数的放到基准数的右边。
完成条件:i>=j
初始状态:
排序次数 | X | 6 | 22 | 1 | 7 | 2 | 排序方向 | 基准数 | ||
---|---|---|---|---|---|---|---|---|---|---|
i=0 | ↑ | ↑ | j=5 | X=5 |
第一次:从右j开始进行比较,如果arr[j]<X=5
,则与基准数的位置进行交换;
更新j的位置*。在这里是2与X进行了交换。
排序次数 | 2 | 6 | 22 | 1 | 7 | X | 排序方向 | 基准数 | ||
---|---|---|---|---|---|---|---|---|---|---|
1 | i=0 | ↑ | ↑ | j=4 | ← | X=5 |
第二次:从左i开始进行比较,如果arr[i]>X=5
,则与基准数的位置进行交换;
更新i的位置*。在这里是X与6进行了交换。
排序次数 | 2 | X | 22 | 1 | 7 | 6 | 排序方向 | 基准数 | ||
---|---|---|---|---|---|---|---|---|---|---|
2 | i=1 | ↑ | ↑ | j=4 | → | X=5 |
第三次:从右j开始进行比较,如果arr[j]X=5
,则与基准数的位置进行交换;
更新j的位置。在这里是1与X进行了交换。
排序次数 | 2 | 1 | 22 | X | 7 | 6 | 排序方向 | 基准数 | ||
---|---|---|---|---|---|---|---|---|---|---|
3 | i=1 | ↑ | ↑ | j=3 | ← | X=5 |
第四次:从左i开始进行比较,如果arr[i]>=X=5
,则与基准数的位置进行交换;
更新i的位置。在这里是X与22进行了交换。
排序次数 | 2 | 1 | X | 22 | 7 | 6 | 排序方向 | 基准数 | ||
---|---|---|---|---|---|---|---|---|---|---|
4 | i=2 | ↑ | ↑ | j=3 | → | X=5 |
第五次:从右j开始进行比较;更新j的位置,达成判断条件i>=j。
排序次数 | 2 | 1 | X | 22 | 7 | 6 | 排序方向 | 基准数 | ||
---|---|---|---|---|---|---|---|---|---|---|
5 | i=2 | ↑↑ | j=2 | ← | X=5 |
将X替换成5,完成一趟排序*。
排序结果:
2 | 1 | 5 | 22 | 7 | 6 | ||
---|---|---|---|---|---|---|---|
i=2 | j=2 |
注释*:
i和j位置的更新规则:
如果arr[i]<=X=5
,则i++
;重复上述过程,直到arr[i]>=X=5
,或者是i>=j
;
如果arr[j]>X=5
,则j--
;重复上述过程,直到arr[j]<X=5
,或者是i>=j
;
一趟排序中基准数(X)的替换规则:
由于基准数是需要不断被替换的,所以在实际代码中,我们并不需要反复地将基准数与目标数进行替换。
在这里我们采用了挖空填数法。即将把基准数放置到X中,这样的话基准数就可以所在的位置就可以随意被填充(挖坑)。
等到一趟排序结束之后,我们再将X放回到坑中(填数),这样就提高了代码的效率。
一趟排序的代码实现:
function quickSort(a, begin, end) {
//确保开始和结束的数正确
var begin = typeof begin != 'number' ? 0 : begin,
end = typeof end != 'number' ? a.length - 1 : end;
if (end - begin <= 1) return; //结束循环的条件
var x = a[begin], //默认选择第一个数的为基准数
p1 = begin, //左边“↑”的位置
p2 = end, //右边“↑”的位置
flag, //本次比较是否发生数组交换
dr = true; //比较的方向,默认true时为从右到左开始判断
while (p1 < p2) {
flag = false; //默认数组没有发生交换
if (dr) {//从右到左开始判断
for (var i = p2; i >= p1; i--) {
if (a[i] <= x) {//如果a[i]小于等于基准数
a[p1] = a[i]; // 则将a[i]赋给a[p1]
p1++; // 从左边下一个开始比较
p2 = i; // 从右边上一个开始比较
dr = !dr; //更改比较方向
flag = true; //本次发生了数组交换
break; //退出循环
}
}
// 如果for循环没有发现满足条件的元素,则说明X的位置本身就是正确的,不需要改变
if (!flag) { //如果没有发生数组交换,则说明一趟排序已经完成
p2 = p1; // 跳出while
}
} else {//从左到右开始判断
for (var j = p1; j < p2; j++) {
if (a[j] > x) {
a[p2] = a[j];
p2--;
p1 = j;
dr = !dr;
flag = true;
break;
}
}
if (!flag) {
p1 = p2;
}
}
}
a[p1] = x;//一趟排序已经完成,将基准数代入到a[p1]中
}
递归调用
实现简述:
通过一趟排序,我们可以把数组分成两个部分,一部分小于等于基准数,另一部分大于基准数。前者我称为前基,后者我称为后基。
对于前基,我们也进行一趟排序,也可以分成两个部分……重复这个过程,直到前基每一个部分都只有一个数或者是为空,这就说明前基就已经排好;对于后基也是如此。
递归的实现:
function quickSort(a, begin, end){
//前面代码不变
a[p1] = x;//一趟排序已经完成,将基准数代入到a[p1]中
//在上行代码后插入这两行代码
quickSort(a,begin,p1-1);
quickSort(a,p1+1,end);
}
参考资料:
[1]http://blog.csdn.net/morewindows/article/details/6684558 (非常好的文章,本文就是采用了“挖坑填数+分治法”的代码实现)
小尾巴吐槽:原本以为CSDN中html编辑器中的表格不好用(边框颜色和背景颜色重叠了),结果用了Markdown中的表格之后才知道,什么才是真正难用……