快速排序
描述
快速排序借用了分治的思想, 并且基于冒泡排序做了改进。 它将数组拆分为两个子数组, 其中一个子数组的所有元素都比另一个子数组的元素小, 然后对这两个子数组再重复进行上述操作, 直到数组不可拆分, 排序完成。
基本思想
- 从数组中取出一个数,称之为基数(pivot)
- 遍历数组,将比基数大的数字放到它的右边,比基数小的数字放到它的左边
- 遍历完成后,数组被分成了左右两个区域
- 将左右两个区域视为两个数组,重复前两个步骤,直到排序完成
实现
基本框架
sortArray
:入口方法QuickSort
:递归方法,负责不停的划分,直到p
q
指针对撞partition
: 划分函数,根据pivot
划分区域,然后返回中点,中点右边的值均大于pivot
,左边的值均小于pivot
1 | // 排序函数 |
第一种写法
按照基本思想进行
- 选取一个基准点
pivot
, 定义两个指针i = p
,j = p + 1
- 移动
j
指针找到 比pivot
小的,移动i
指针,将其与i
换位 - 直到
j > q
之后跳出循环 - 最后将
p
与i
进行互换,返回i
指针
1 | function partition(arr, p, q){ |
详细过程
arr = [15, 9, 31, 21, 44, 22, 33, 18, 2]
p = 0
q = arr.length - 1 = 8
pivot = arr[0]
i = 0
j = 1
开始比对9 < 15
i++; i = 1
swap(1, 1)
arr
不变31 > 15
,21 > 15
,44 > 15
,22 > 15
,33 > 15
,18 > 15
(跳过)2 < 15
i++; i = 2
swap(2, 8)
[15, 9, 2, 21, 44, 22, 33, 18, 31]
i = 2, p = 0
swap(0, 2)
[2, 9, 15, 21, 44, 22, 33, 18, 31]
返回
i = 2
,QuickSort(arr, 0, 1)
QuickSort(arr, 3, 8)
QuickSort(arr, 0, 1)
arr = [2, 9, 15, 21, 44, 22, 33, 18, 31]
p = 0
q = 1
i = 0
j = 2
swap(0, 0)
返回i = 0
,QuickSort(arr, 0, -1)
(跳过)QuickSort(arr, 1, 1)
(跳过)QuickSort(arr, 3, 8)
=>partition(arr, 3, 8)
[..., 21, 44, 22, 33, 18, 31]
i = 3
j = 4
pivot = 21
44 > 21
,22 > 21
,33 > 21
(跳过)18 < 21
i = 4, j = 7
swap(4, 7)
[..., 21, 18, 22, 33, 44, 31]
31 > 21
(跳过),i = 4, p = 3
,swap(3, 4)
[..., 18, 21, 22, 33, 44, 31]
返回
i = 4
QuickSort(arr, 3, 3)
(跳过)QuickSort(arr, 5, 8)
=>partition(arr, 5, 8)
[..., 22, 33, 44, 31]
i = 5, j = 6
pivot = 22
(跳过)返回
i = 5
QuickSort(arr, 4, 4)
QuickSort(arr, 6, 8)
[..., 33, 44, 31]
i = 6, j = 7, pivot = 33
44 > 33
(跳过)31 < 33
i = 7, j = 8
swap(7, 8)
[..., 33, 31, 44]
i = 7, p = 6
swap(6, 7)
[..., 31, 33, 44]
返回
i = 7
QuickSort(arr, 6, 6)
(跳过)QuickSort(arr, 8, 8)
(跳过)排序完成
[2, 9, 15, 18, 21, 22, 31, 33, 44]
第二种写法
1 | var partition = (arr, p, q) => { |
详细过程
[31, 15, 18, 22, 33, 21, 44, 2, 9]
pivot = 31, i = 1, j = 8
15, 18, 22 < 31
(跳过)33 > 31
i = 4 j = 8
swap(4, 8)
[31, 15, 18, 22, 9, 21, 44, 2, 33]
j = 7
9, 21 < 31
(跳过)44 > 31
i = 6 j = 7
swap(6, 7)
[31, 15, 18, 22, 9, 21, 2, 44, 33]
j = 6
- 跳出循环
arr[6] = 2 < 31
swap(0, 6)
[2, 15, 18, 22, 9, 21, 31, 44, 33]
返回j = 6
QuickSort(arr, 0, 5)
和QuickSort(arr, 7, 8)
QuickSort(arr, 0, 5)
=>partition(arr, 0, 5)
[2, 15, 18, 22, 9, 21]
pivot = 2, i = 1, j = 5
15 > 2
swap(1, 5)
=>[2, 21, 18, 22, 9, 15]
j = 4
21 > 2
swap(1, 4)
=>[2, 9, 18, 22, 21, 15]
j = 3
9 > 2
swap(1, 3)
=>[2, 22, 18, 9, 21, 15]
j = 2
22 > 2
swap(1, 2)
=>[2, 18, 22, 9, 21, 15]
j = 1
- 跳出循环
arr[1] = 18 > 2
j--
swap(0, 0)
返回j = 0
QuickSort(arr, 0, -1)
(跳过)和QuickSort(arr, 1, 5)
QuickSort(arr, 1, 5)
=>partition(arr, 1, 5)
[18, 22, 9, 21, 15]
pivot = 18, i = 2, j = 5
22 > 18
swap(2, 5)
=>[18, 15, 9, 21, 22]
j = 4
15, 9 < 18
(跳过)21 > 18
此时i = j = 4
- 跳出循环
arr[4] = 21 > 18
j--
swap(2, 3)
返回j = 3
[9, 15, 18, 21, 22]
QuickSort(arr, 1, 2)
(跳过)和QuickSort(arr, 4, 5)
(跳过)- 回到
QuickSort(arr, 7, 8)
交换后完成排序[2, 9, 15, 18, 21, 22, 31, 33, 44]
第三种写法
1 | var partition = (arr, p, q) => { |
详细过程
[22, 2, 18, 31, 33, 9, 15, 44, 21]
pivot = 22
i = 1, j = 8
2, 18 < 22
(跳过),31 > 22
i = 3
21 < 22
j = 8
swap(3, 8)
[22, 2, 18, 21, 33, 9, 15, 44, 31]
21 < 22
(跳过)33 > 22
i = 4
31, 44 > 22
(跳过)15 < 22
j = 6
swap(4, 6)
[22, 2, 18, 21, 15, 9, 33, 44, 31]
15, 9 < 22
跳过,i = 6
j = 6
跳出循环arr[6] = 33 > 22
j--
swap(0, 5)
[9, 2, 18, 21, 15, 22, 33, 44, 31]
返回j = 5
QuickSort(arr, 0, 4)
和QuickSort(arr, 6, 8)
QuickSort(arr, 0, 4)
=>partition(arr, 0, 4)
[9, 2, 18, 21, 15]
pivot = 9, i = 1, j = 4
2 < 9
(跳过)18 > 9
i = 2
15, 21, 18 > 9
(跳过)j = 2
跳出循环arr[j] = 18 > 9
j--
swap(0, 1)
[2, 9, 18, 21, 15]
返回j = 1
QuickSort(arr, 0, 0)
(跳过) 和QuickSort(arr, 2, 4)
QuickSort(arr, 2, 4)
=>partition(arr, 2, 4)
[18, 21, 15]
pivot = 18, i = 3, j = 4
21 > 18
i = 3
15 < 18
j = 4
swap(3, 4)
[18, 15, 21]
j = 4
跳出循环arr[4] = 21 > 18
j--
swap(2, 3)
[15, 18, 21]
返回j = 3
QuickSort(arr, 2, 2)
(跳过) 和QuickSort(arr, 4, 4)
(跳过)- 回到
QuickSort(arr, 6, 8)
=>partition(arr, 6, 8)
[33, 44, 31]
pivot = 33, i = 7, j = 8
44 > 33
i = 7
31 < 33
j = 8
swap(7, 8)
[33, 31, 44]
j = 8
跳出循环arr[8] > 33
j--
swap(6, 7)
[31, 33, 44]
返回j = 7
QuickSort(arr, 6, 6)
(跳过) 和QuickSort(arr, 8, 8)
(跳过)- 返回数组
[2, 9, 15, 18, 21, 22, 31, 33, 44]
完成排序
三种方式对比
优化角度
分析上面三个版本的实现,我们可以发现,在随机化越高的情况下,快速排序所用的轮次会越少,所以一般我们可以通过打乱数组后进行排序,效率更高
1 | var swap = (arr, i, j) => { |
完整代码
1 | var sortArray = function(arr){ |
再补一个三路快排
1 | // 3路快排 [l,...lt, .. i, ... gt, r] |
算法复杂度
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|
O(nlogn) | O(nlogn) | O(n^2) | O(n) | in-place | 不稳定 |