快速排序
TS 写一份原地快排,顺便解释 pivot 在左侧时为什么要从右开始遍历。
发布 2019年3月05日 标签
#algorithms
#sorting
~/posts/quick-sort $ cat post.md
写快排的文章已经很多了,这篇只想把几个细节说清楚。
- 快排的优势之一是可以原地排序,所以那些需要额外空间的”快排变体”基本就没必要用了。
- 如果 pivot 从最左侧选取,那应该从最右侧开始遍历。
- 快排不是稳定排序,重复元素的相对位置可能改变。
代码
下面是一个 TypeScript 写的原地快排:
const swap = <T>(arr: T[], left: number, right: number): void => {
const temp: T = arr[left];
arr[left] = arr[right];
arr[right] = temp;
};
const quickSort = <T>(
arr: T[],
left: number = 0,
right: number = arr.length - 1,
): void => {
if (left > right) {
return;
}
let lp: number = left;
let rp: number = right;
const pivot: T = arr[left]; // pivot 取最左
while (lp != rp) {
// 在两指针未相遇前,从右向左找下一个比 pivot 小的数
while (lp < rp && arr[rp] >= pivot) {
rp--;
}
// 在两指针未相遇前,从左向右找下一个比 pivot 大的数
while (lp < rp && arr[lp] <= pivot) {
lp++;
}
// 两个数交换:让较小的回到左侧
if (lp < rp) {
swap(arr, lp, rp);
}
}
// 把 pivot 放到正确位置
arr[left] = arr[lp];
arr[lp] = pivot;
// 递归处理左右两段
quickSort(arr, left, lp - 1);
quickSort(arr, lp + 1, right);
};
为什么”从右开始遍历”
举个简单的例子:9 10 5 7。
- 选最左侧的 pivot =
9。 - 从右向左找比
9小的数:7。 - 从左向右找比
9大的数:10,交换得9 7 5 10。 - 从右向左找比
9小的数:5。 - 从左向右找比
9大的数——这时左指针追上了右指针5,把 pivot 和当前指针位置交换得5 7 9 10,完成。
关键在第 4 步:如果反过来从左侧开始遍历,左指针会停在 10 上;再去和 pivot 交换的话,pivot 会落到一个比它大的数的位置上,整个分区就破了。pivot 选最左 ⇒ 必须从右开始,反之亦然。