常见的边界问题如何处理,以快排为例子
快速排序
我们先来模拟一下快速排序的流程
- 先寻找一个基值
- 将大于这个值的数放在它后面,小于的放在前面
- 递归调用前后两个已经分好的区间重新快排
GIF如下

实现过程 (y总方法)
1 | // 闭区间使用右指针作为分界点 |
1.我们先要确定怎么取一个基值作为对比
a[l]a[l+r >> 1]a[r]- a中的随机数
2.如何将小于和大于x的数分别位于两边 ( 交换位置 )
比如说我们可以定义两个数组,分别比较每一个数和x的关系,如果大于则放入一个大于数组,小于放入一个小于数组.
1 | int greater[],less[]; |
使用数组会占用额外的空间,我们可以直接使用双指针,在原数组的基础上修改数据.
定义一个左指针q和一个右指针p 分别从两边向中间迭代,如果遇到a[q] > x或者a[p] < x就停止迭代并交换两指针数据
1 | while(q < p){ |
这里有几个坑需要注意
为什么是a[q] < x 而不是<=
我们要考虑到一种情况,比如说 基值选到最大值 的话,数组内所有元素都是小于等于基值的,指针可能会发生越界问题,或者死循环.
例如这组数据
1 | 9 5 7 2 4 8 3 1 |
如果基值一开始取到了9,那么左指针会不断移动,直到最后一个数据之后.或者再递归还是调用的原数组,会死循环下去
为什么交换时要判断q < p
通过每一次指针的移动,最后一次while循环的时候q和p有可能会移动到对方的遍历过的区间,此时再交换已经失去了原本的含义
比如
1 | 1 4 3 2 5 6 9 7 |
执行最后一次while后a[p] = 2 , a[q] = 5此时交换还有意义吗?
这时候指针不需要交换且执行完后会直接停止.
关于while循环的问题
上述代码其实是一段错误的代码
正常思维一拍脑袋想到的其实就是这样,为什么是错误的代码?
我们模拟一下指针的变化,如果出现相同数据的话,此处又会陷入一个死循环
例如这种情况
1 | 5 2 5 4 2 5 3 8 |
如果基值选择的是5的话
最后执行到a[q] = 5, a[p] = 5的时候, p q指针都会停止移动,并且不断重复swap的操作,就会陷入一个死循环.
要避免这种操作只需要让两个指针先移动再判断
1 | while(q < p){ |
边界问题
首先要搞清楚取中间值边界问题
假设一个数组a[10]
1 | l = 0, r = 10 |
也就是说 如果用数组内的大小来表示r的话, 除于二的值会取到中间靠右的位置
反之会靠左
极限情况是只有两个数的时候,会取到左边那个还是右边那个这种情况要考虑清楚
解决了这个问题我们来看下一个问题
1 | quick_sort(a, l, ?); |
我们要考虑?中需要填写什么参数
最后一个问题也是最终要的一个边界问题如这种情况
1 | 1 8 7 3 5 4 2 9 |
如果基值选择a[l]
那么就会有一个问题, 指针停下来的时候q = 0, p = 0
下一次调用左区间为 [ l ,0 ) ,直接返回,而右区间[ 0 , r]又是原来相同的区间,
1 | quick_sort(a, l, p); |
所以 右指针移动到最左边的情况时 下一次递归使用右指针作为边界判断的条件时, 要+1
1 | quick_sort(a, l, p + 1); |
如果使用左指针划分区间的话 在递归时不需要-1
因为我们使用开区间进行递归,所以包含左边界但是不包含有边界.
极限情况下右边界会自动剔除
比如指针取到最后一个数下标为7
1 | quick_sort(a, l, q); // 区间为 [ l , q ) |
第一个区间是默认不包含最后一个数的,所以不需要-1的操作

