算法之排序——快速排序
快速排序详解Quicksort
快排(快速排序)也是递归排序中的一种,也是分而治之的思想在排序中的一个体现,另一个体现为归并排序;
相对于归并排序,快排没有使用其他数组,没有额外的空间复杂度;
快排的思想也是分组,不同于归并排序,归并排序用额外的数组来合并分组;而快排,不实用额外数组。
归并排序:递归分组,当分组到最小值(数组length<=1)时,再用一个新数组,递归合并分组。
快速排序:递归分组,选择基点(任意选择),根据基点做排序后,利用基点进行分组;依次递归。
快速排序的3个基本步骤:
- 从数组中选择一个元素作为基准点
- 分区,排序数组,所有比基准值小的元素摆放在左边区域,而大于基准值的摆放在右边区域。每次分割结束以后基准值会插入到中间去。
- 最后利用递归,将摆放在左边的数组和右边的数组在进行一次上述的1和2操作。
分区会用到两个指针,一个指针会指向比基点大的元素,一个指针指向比基点小的元素;
两个指针可以在同一个方向,也可以在不同方向;
通用部分代码:
// 交换数组元素
function switchItem(arr, a, b) {
let temp
temp = arr[b]
arr[b] = arr[a]
arr[a] = temp
}
// 快速排序
function quickSort(arr) {
return quick(arr, 0, arr.length - 1)
}
// 分组后递归排序
function quick(arr, left, right) {
if (arr.length > 1) {
// 根据分区后,获得基点索引,依次递归分区
let indext = partition(arr, left, right)
if (left < indext - 1) {
quick(arr, left, indext - 1)
}
if (indext + 1 < right) {
quick(arr, indext + 1, right)
}
}
return arr
}
针对分区时;指针放置方向不同,分区方式也不同 ,介绍两种分区方式:
方式一:指针在不同方向
步骤如下:
- 左指针逐个格子向右移动,当遇到大于或等于基点的值时,就停下来。
- 右指针逐个格子向左移动,当遇到小于或等于基点的值时,就停下来。
- 将两指针所指的值交换位置。
- 重复上述步骤,直至两指针重合,或左指针移到右指针的右边。
- 将基点与左指针所指的值交换位置。
示意图如下(选取最左边元素为基点):
代码如下:
function partition(arr, left, right) {
let point = right
while (left < right) {
// 左指针移动
while (left < right && arr[left] <= arr[point]) {
left++
}
// 右指针移动
while (left < right && arr[point] <= arr[right]) {
right--
}
// 当左右指针停下,满足条件,交换元素
if (left < right) {
switchItem(arr, left, right)
left++
right--
}
}
// 交换基点和左指针
switchItem(arr, left, point)
// 返回基点所在指针,为了后面递归分区
return left
}
方式二:指针在同方向
步骤如下(以指针方向同在左边为例):
- 两个指针分别为上下指针,初始化都指向形参为left的元素。
- 上指针依次遍历数组;当遇见小于基点的元素停下;
- 交换上指针和下指针元素;下指针元素加1;
- 循环完后,交换基点和下指针元素。
下指针指向第一个大于基点的元素;上指针总在寻找小于基点的元素。
当上下指针交换后,下指针后面的元素均小于基点;
最后循环完毕后,下指针和基点交换后,分区完成。小于基点的元素在下指针左边,大于基点的元素在下指针右边。
且基点已经放置到分区中间。
function partition(arr, left, right) {
let point = right
// 下指针
let dPoint = left
// 上指针遍历数组
for (let uPoint = dPoint; uPoint < arr.length - 1; uPoint++) {
if (arr[uPoint] < arr[point]) {
switchItem(arr, uPoint, dPoint)
dPoint++
}
}
switchItem(arr, point, dPoint)
return dPoint
}