目标
从0开始一步一步地实现一个快速排序。并逐步优化成一个通用的快速排序。 (欢迎大家提供建议和帮助)
快速排序的原理
对数组的左右两端用指针标记,取出数组最左侧的元素作为参照数,将数组中小于参照数的置于左侧、大于参照数的置于右侧,此时数组被分为左右两个子数组,再分别对两个子数组进行上述排序,直到子数组长度为1或0。
代码实现
下面的排序函数只是按照最初的思路进行实现,后面会进行优化。
function quickSort(arr) {
if (arr < 2) {
return arr;
}
const length = arr.length;
let left = 0;
let right = length - 1;
const reference = arr[0];
let slotInLeft = true;
// 假设排序结果为递增数组
// 5. 最后的情况是,两个指针距离为1,一个指向待比较元素,一个指向空槽。
// 5.1 待比较元素较大,放入空槽,原始空槽指针移动到原始待比较元素位置(新空槽),两指针相遇。
// 5.2 待比较元素较小,它的指针移动到空槽,两指针相遇。
// 所以一轮排序的终止条件为,两指针相遇(即left===right)(排序中的状态:left的值应该小于right)
while (left !== right) {
// 6. 对空槽位置不同情况的判断
if (slotInLeft) {
// 1. 比较参考数和待排序元素
if (reference > arr[right]) {
// 2. 对待排序元素排序,放入空槽
arr[left] = arr[right];
// 3. 更新空槽标记,空槽从左侧变为右侧 (一开始默认空槽在左侧,但是漏掉了对空槽位置不同情况的判断[6])
slotInLeft = false;
// 4. 开始下一次比较,左侧索引left已经排好序,继续从左向右扫描(此时发现需要添加**扫描的终止条件**)
left++;
} else {
// 右侧值大,保持不动,继续从右向左扫描
right--;
}
} else {
// 7. 当前空槽在右侧,待排序元素则在左侧。此时需要特别注意,在空槽在左侧的情况没有对“与参考数相等”的情况做处理(相当于默认相等相等时,右侧元素保持不动),所以在此必须处理这种情况,左侧元素相等时将移动到右侧
if (reference <= arr[left]) {
arr[right] = arr[left];
slotInLeft = true;
right--;
} else {
left++;
}
}
}
// 8. 一轮排序结束后,left和right相遇。当前left指针指向空槽,将参照数放入空槽。
arr[left] = reference;
// 9. 以left为界限,划分两个子数组。特别注意,右子数组可能存在数组越界的问题(当left指向arr最右侧时,left+1会越界,越界长度为1)
const leftChildArr = arr.slice(0, left);
const rightChildArr = left === length - 1 ? [] : arr.slice(left + 1, length);
// 10. 分别对左子数组和右子数组排序,然后组合起来,整个数组就排好序了
return [...quickSort(leftChildArr), reference, ...quickSort(rightChildArr)];
}
优化:不再分割数组
function quickSort(arr, leftBounds = 0, rightBounds = arr.length - 1) {
// 当排序区间长度不大于1时,停止排序
if (leftBounds >= rightBounds) {
return arr;
}
const length = rightBounds - leftBounds + 1;
let left = leftBounds;
let right = rightBounds;
const reference = arr[leftBounds];
let slotInLeft = true;
while (left !== right) {
if (slotInLeft) {
if (reference > arr[right]) {
arr[left] = arr[right];
slotInLeft = false;
left++;
} else {
right--;
}
} else {
if (reference <= arr[left]) {
arr[right] = arr[left];
slotInLeft = true;
right--;
} else {
left++;
}
}
}
arr[left] = reference;
// 排序子数组,参考数不参与排序。数组越界问题在函数调用开头处理。
quickSort(arr, leftBounds, left - 1);
quickSort(arr, left + 1, rightBounds);
return arr;
}
优化:排序模式(递增、递减)
function quickSort(
arr,
leftBounds = 0,
rightBounds = arr.length - 1,
incremental = true // 是否递增
) {
if (leftBounds >= rightBounds) {
return arr;
}
const length = rightBounds - leftBounds + 1;
let left = leftBounds;
let right = rightBounds;
const reference = arr[leftBounds];
let slotInLeft = true;
while (left !== right) {
if (slotInLeft) {
// 递增 右小则交换;递减 右大则交换
if (
(incremental && reference > arr[right]) ||
(!incremental && reference < arr[right])
) {
arr[left] = arr[right];
slotInLeft = false;
left++;
} else {
right--;
}
} else {
// 递增 左大则交换;递减 左小则交换
if (
(incremental && reference <= arr[left]) ||
(!incremental && reference >= arr[left])
) {
arr[right] = arr[left];
slotInLeft = true;
right--;
} else {
left++;
}
}
}
arr[left] = reference;
quickSort(arr, leftBounds, left - 1);
quickSort(arr, left + 1, rightBounds);
return arr;
}
优化:支持对象数组类型
function quickSort(
arr,
leftBounds = 0,
rightBounds = arr.length - 1,
incremental = true,
getSortableValue = quickSort.getSortableValue //自定义取值方法,以支持Array<Object>
) {
if (leftBounds >= rightBounds) {
return arr;
}
let left = leftBounds;
let right = rightBounds;
// 保存参考值的引用,一轮排序结束需要将参考值放入空槽。
const referenceEl = arr[leftBounds];
const reference = getSortableValue(referenceEl);
let slotInLeft = true;
while (left !== right) {
if (slotInLeft) {
if (
(incremental && reference > getSortableValue(arr[right])) ||
(!incremental && reference < getSortableValue(arr[right]))
) {
arr[left] = arr[right];
slotInLeft = false;
left++;
} else {
right--;
}
} else {
if (
(incremental && reference <= getSortableValue(arr[left])) ||
(!incremental && reference >= getSortableValue(arr[left]))
) {
arr[right] = arr[left];
slotInLeft = true;
right--;
} else {
left++;
}
}
}
arr[left] = referenceEl;
quickSort(arr, leftBounds, left - 1);
quickSort(arr, left + 1, rightBounds);
return arr;
}
// 增加静态方法作为默认参数
quickSort.getSortableValue = function (arrEl) {
return arrEl;
};
优化:支持稳定排序
快速排序法不是稳定排序算法。 因为快排会打乱相同的值的原始顺序。
例如
// 期望结果为递增的例子
const arrOfWaitSort1 = [
{ value: 2 },
{ value: 1, position: 1 },
{ value: 1, position: 2 },
{ value: 3 },
];
/*
step 1:
[
->{ value: 2 },
{ value: 1, position: 1 },
{ value: 1, position: 2 },
{ value: 3 }<-
]
step 2:
{ value: 2 }
[
-> slot,
{ value: 1, position: 1 },
{ value: 1, position: 2 },
{ value: 3 }<-
]
step 3:
由于 2<3 ,右指针向左移动1格
{ value: 2 }
[
-> slot,
{ value: 1, position: 1 },
{ value: 1, position: 2 },<-
{ value: 3 }
]
step 4:
由于 2>1 且slot(空槽)在左,将较小的元素放入slot(空槽)中并产生新的slot,左指针向右移动1格
{ value: 2 }
[
{ value: 1, position: 2 },
->{ value: 1, position: 1 },
slot,<-
{ value: 3 }
]
step 5:
由于 2>1 且slot(空槽)在右,将较小的元素保持不动,左指针向右移动1格
{ value: 2 }
[
{ value: 1, position: 2 },
->{ value: 1, position: 1 },
slot,<-
{ value: 3 }
]
step 6:
此时左右指针相遇,本轮排序结束
{ value: 2 }
[
{ value: 1, position: 2 },
{ value: 1, position: 1 },
->slot,<-
{ value: 3 }
]
*/
// 期望结果为递减的例子,同理
const arrOfWaitSort2 = [
{ value: 2 },
{ value: 3, position: 1 },
{ value: 3, position: 2 },
{ value: 1 },
];