快速排序 几种划分方法讨论
最近在复习数据结构与算法,聊聊快速排序的几种划分算法。
快速排序思路
快速排序是一种基于分治策略的排序算法。
对于待排序数组,其核心操作是:
- 选取一个基准数
pivot
- 将数组分为两块,一块小于等于基准数,另一块大于等于基准数(注意 基准数作为分界点)
- 递归地对于分出来的两个数字再次进行快速排序
不断地进行划分,递归,最后能保证整个数组有序。
基准数的选取
一般来说,基准数有几种选取方法:
- 选取第一个数或者最后一个数:这样做,如果数组已经排好序,则每次划分都是基准数和剩余数,时间复杂度升至
O(n^2)
- 随机选择一个数
- 选取中位数:这样做始终能对原数组进行等分,寻找中位数是线性时间的。
下面以最无脑的方法1为例。
几种划分方法
主要的划分方法有:
- 朴素划分
- Lomuto 划分
- Hoare 划分
中国大陆教材中经常出现的是第三种,即 Hoare 划分,也叫哨兵划分。
朴素划分
思路很无脑。直接开一个新数组,把比 pivot
小的放左边,比 pivot
大的放右边,最后把数组拷贝回到原数组。需要 O(n)
的额外空间。
1 | const int N; // 数组长度 |
Lomuto 划分
Lomuto 划分即使用单个指针,从左到右扫描数组,交换,维持划分顺序。
1 | int Lomuto_Partition(int left, int right) { |
Hoare 划分
Hoare 划分采用双指针,进行交换。
1 | int Hoare_Partition(int left, int right) { |
扫描顺序对于结果有影响。pivot
在最左边时,应该先从右往左扫描。
如果先从左往右扫,找第一个大于 pivot
的数 a,而第一个小于 pivot
的数在 a 左侧,此时进行 pivot 和 A[left]
的交换,会导致交换错误。故应该优先保证 pivot
左侧都小于等于 pivot
,即先寻找第一个小于 pivot
的数,这样交换是安全的。
快速排序算法
对于某划分函数 partition(int st, int end) -> pivot: int
有
1 | const int N; // 数组长度 |