记录一下抠二分查找细节的过程。
// 运算符
是除了之后向下取整。
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 == rightarr[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在区间里就行。
奥,因为向上取整,所以左闭右开不能这么做。怪不得向下取整的时候没有左开右闭呢。