快排它采用了一种分治的策略,通常称其为分治法
分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。
利用分治法可将快速排序的分为三步:
-
在数据集之中,选择一个元素作为”基准”(pivot)。
-
所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。这个操作称为
分区
操作,分区操作结束后,基准元素所处的位置就是最终排序后它的位置。 -
对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
function partition(arr, left, right) {
let i = left,
j = right,
temp = arr[left];
while (i < j) {
while (i < j && arr[j] > temp) {
j--;
}
if (i < j) {
arr[i] = arr[j];
i++;
}
while (i < j && arr[i] < temp) {
i++;
}
if (i < j) {
arr[j] = arr[i];
j--;
}
console.log(i, ' ', j, ' ', temp, arr);
}
arr[i] = temp;
return i;
}
function quickSort(arr, left, right) {
if (left < right) {
let index = partition(arr, left, right); // 分区,找到一个基点
quickSort(arr, left, index - 1);
quickSort(arr, index + 1, right);
}
return arr;
}
var arr = [20, 40, 30, 10, 60, 50];
console.log(quickSort(arr, 0, arr.length - 1)); // [10, 20, 30, 40, 50, 60]
阮一峰老师的错误快排,可以去知乎上去吃瓜,这边我还是照样贴出来阮老师的快排代码~
function quickSort(arr) {
if (arr.length === 0) {
return [];
}
let index = Math.floor(arr.length / 2);
let midValue = arr.splice(index, 1);
let leftArr = [],
rightArr = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < midValue) {
leftArr.push(arr[i]);
} else {
rightArr.push(arr[i]);
}
}
return quickSort(leftArr).concat(midValue, quickSort(rightArr));
}
var arr = [20, 40, 30, 10, 60, 50];
console.log(quickSort(arr));
最好的情况,时间复杂度为 O(nlog2n)
, 最差的情况,时间复杂度为 O(n²)
,平均复杂度为 O(nlog2n)
快速排序也有不足之处,比如对于元素较少或接近有序的数组来说,快速排序平均性能比插入排序差。这是因为小数组信息熵相对来说比较小(特别是经过一系列的快速排序调用以后),而插入排序在数据接近有序的情况下时间复杂度接近 O(N),再加上快速排序递归调用也会有一些性能损耗。
因此,针对于小数组的话,可以采用插入排序,如果大数组,就采用快速排序。
Java 标准库自带的排序
DualPivotQuicksort
就是这么干的,INSERTION_SORT_THRESHOLD = 47。
那有没有更好能够优化快速排序性能的方法? 双枢轴
,即将数组三切分(大于枢轴,等于枢轴,小于枢轴),为什么这样划分呢?因为对大规模数组进行排序时,数据重复的情况可能比较多,因此使用双枢轴可以有效避免相等元素之间的比较。
快排的改进主要有三种方法:小数组使用插入排序、双枢轴(快速三向切分)、划分策略优化(五取样划分)。经过优化后的快速排序算法时间复杂度可以介于 O(N) 到 O(NlogN) 之间,性能更优