记录一下抠二分查找细节的过程。
// 运算符
是除了之后向下取整。
1 | 5//2 |
对于(left+right)//2
来说,奇数个就正好是中间,偶数个会更靠近left
一些。
关于区间的讨论
左闭右闭
1 | def search(arr, x): |
输出结果:
1 | [0, 1, 2, 3, 4, 5, 6] |
因为当arr[mid] == x
的时候,break
了循环,所以最终如果要查找的数在数组中,mid
就是结果的位置,如果不在(改了一下输出):
1 | [0, 1, 2, 3, 5, 6] |
- 退出循环后,
left
指向的是x
应该在的位置 - 在退出循环的前一步,
left
和rignt
会指向同一个位置,那mid
也是- 如果
arr[mid] < x
,x
应当在当前位置的右边,移动left
,这时候left
就是x
该在的位置; - 如果
arr[mid] > x
,x
比左边的数大比右边的小,所以left
就是x
该在的位置,移动right
后退出循环;
- 如果
如果没有单独的else
即arr[mid] == x
这一分支呢?
将等于加到大于上:
1 | def search(arr, x): |
输出:
1 | [0, 2, 4, 6] |
这个好像所有的都是left
指向答案。
但如果改成
1 | if arr[mid] <= x: |
输出:
1 | [0, 2, 4, 6] |
这就像是如果在,是right
指向,如果不在,是left
指向。
。。。
。。。
我知道了!我要得有个问题先!
问题:找到第一个大于等于x
的位置
思考的步骤应该是这样的:
- 闭区间
[left,right]
,这是每一次查询的区间,循环退出的条件是区间为空,也就是left
在right
的右边; - 找到
mid
,与要寻找的x
进行比较,将区间分成两部分,一部分符合条件(大于等于x
),另一部分是不符合条件的(小于x
) - 根据比较的结果,调整
left
或者right
- 因为
mid
在比较了之后,就可以将它划分到其中的一个部分,又因为是闭区间,所以调整后的区间就不包括mid
- 根据这道题的要求,就有了一个非常重要的事情:
left
左边的一定都比x
小,right
右边的一定都大于等于x
,这两个点不确定 - 最后一定是
left == right
arr[mid] >= x
,调整right
后退出循环,left
是结果,因为right
右边的一定都大于等于x
,而整个区间已经全部划分完了,题目要求的第一个大于等于x
的位置就是right + 1
,也就是left
;arr[mid] < x
,调整left
后退出循环,left
是结果,原因和上面差不多。
代码如下:
1 | def search(arr, x): |
同样的思考过程可以写出:
1 | # 二分查找 |
开区间
1 | # 开区间 |
发现,在开区间里,符合条件的时候调整谁最后就返回谁,闭区间就相反。
左闭右开
1 | # >=x |
考虑输出什么的时候,就想
left
(或者right
)本身因为开或闭是否要算,闭不算开要算- 然后看它或者它那一边是符合条件的就输出。
关于(left + right + 1) // 2
我觉得这不重要,重要的是根据上面的步骤考虑清楚区间的范围以及left
和right
指向的情况就行,保证mid
在区间里就行。
奥,因为向上取整,所以左闭右开不能这么做。怪不得向下取整的时候没有左开右闭呢。