二分查找可以大幅降低时间复杂度,但是循环条件控制有误容易陷入死循环。这里具体指两个方面:
- 右边界
r
的确定; while
循环中的条件控制;
初始化的数组:
arr_1 = [1, 3, 5, 7, 9]
arr_2 = [1, 3, 5, 7, 9, 11]
先看3
个二分查找的方法:(区别:右边界r
)
def binary_search_1(arr, target):
l = 0
r = len(arr)
while l < r:
count += 1
mid = (l + r) // 2
if arr[mid] == target:
return mid
elif arr[mid] > target:
r = mid
else:
l = mid + 1
return l
def binary_search_2(arr, target):
l = 0
r = len(arr) + 1
while l < r:
count += 1
mid = (l + r) // 2
if arr[mid] == target:
return mid
elif arr[mid] > target:
r = mid
else:
l = mid + 1
return l
def binary_search_3(arr, target):
l = 0
r = len(arr) - 1
while l < r:
count += 1
mid = (l + r) // 2
if arr[mid] == target:
return mid
elif arr[mid] > target:
r = mid
else:
l = mid + 1
return l
运行结果如下:
- 在
arr_1
中寻找数值9
Using binary_search_1 on arr_1:
The mid_1: 2
The initial l_1 and r_1: 0 5
target is on the right part!
The l_1 and r_1: 3 5
The mid_2: 4
The initial l_2 and r_2: 3 5
mid value hits the target!
4
Using binary_search_2 on arr_1:
The mid_1: 3
The initial l_1 and r_1: 0 6
target is on the right part!
The l_1 and r_1: 4 6
The mid_2: 5
The initial l_2 and r_2: 4 6
IndexError: list index out of range
Using binary_search_3 on arr_1:
The mid_1: 2
The initial l_1 and r_1: 0 4
target is on the right part!
The l_1 and r_1: 3 4
The mid_2: 3
The initial l_2 and r_2: 3 4
target is on the right part!
The l_2 and r_2: 4 4
4
可见右边界取len(arr)
与len(arr)-1
均可,注意:此时的 while
循环为 l < r
。
- 在
arr_2
中寻找数值9
Using binary_search_1 on arr_1:
The mid_1: 3
The initial l_1 and r_1: 0 6
target is on the right part!
The l_1 and r_1: 4 6
The mid_2: 5
The initial l_2 and r_2: 4 6
target is on the left part!
The l_2 and r_2: 4 5
The mid_3: 4
The initial l_3 and r_3: 4 5
mid value hits the target!
4
Using binary_search_2 on arr_1:
The mid_1: 3
The initial l_1 and r_1: 0 7
target is on the right part!
The l_1 and r_1: 4 7
The mid_2: 5
The initial l_2 and r_2: 4 7
target is on the left part!
The l_2 and r_2: 4 5
The mid_3: 4
The initial l_3 and r_3: 4 5
mid value hits the target!
4
Using binary_search_3 on arr_1:
The mid_1: 2
The initial l_1 and r_1: 0 5
target is on the right part!
The l_1 and r_1: 3 5
The mid_2: 4
The initial l_2 and r_2: 3 5
mid value hits the target!
4
可见此时右边界3
者均可,注意:此时的 while
循环为 l < r
。
总结:当while
的控制条件为l < r
时,若目标为arr
的尾部元素,则右边界存在溢出的情况。其余情况无大差别。
再来看2个二分查找的方法:(区别:while循环的条件)
def binary_search_1(arr, target):
l = 0
r = len(arr) - 1
count = 0
while l < r:
count += 1
mid = (l + r) // 2
if arr[mid] == target:
return mid
elif arr[mid] > target:
r = mid
else:
l = mid + 1
return l
def binary_search_2(arr, target):
l = 0
r = len(arr) - 1
count = 0
while l <= r:
count += 1
mid = (l + r) // 2
if arr[mid] == target:
return mid
elif arr[mid] > target:
r = mid
else:
l = mid + 1
return l
运行结果如下:
- 在
arr_1
中寻找数值9
,索引数据在arr
中。
Using binary_search_1 on arr_1:
The mid_1: 2
The initial l_1 and r_1: 0 4
target is on the right part!
The l_1 and r_1: 3 4
The mid_2: 3
The initial l_2 and r_2: 3 4
target is on the right part!
The l_2 and r_2: 4 4
4
Using binary_search_2 on arr_1:
The mid_1: 2
The initial l_1 and r_1: 0 4
target is on the right part!
The l_1 and r_1: 3 4
The mid_2: 3
The initial l_2 and r_2: 3 4
target is on the right part!
The l_2 and r_2: 4 4
The mid_3: 4
The initial l_3 and r_3: 4 4
mid value hits the target!
4
可见两种控制条件无差别。
- 在
arr_1
中寻找数值8
,索引数据不在arr
中。
Using binary_search_1 on arr_1:
The mid_1: 2
The initial l_1 and r_1: 0 4
target is on the right part!
The l_1 and r_1: 3 4
The mid_2: 3
The initial l_2 and r_2: 3 4
target is on the right part!
The l_2 and r_2: 4 4
4
Using binary_search_2 on arr_1:
The mid_1: 2
The initial l_1 and r_1: 0 4
target is on the right part!
The l_1 and r_1: 3 4
The mid_2: 3
The initial l_2 and r_2: 3 4
target is on the right part!
The l_2 and r_2: 4 4
The mid_3: 4
The initial l_3 and r_3: 4 4
target is on the left part!
The l_3 and r_3: 4 4
The mid_4: 4
The initial l_4 and r_4: 4 4
target is on the left part!
The l_4 and r_4: 4 4
endless loop
4
可见当控制条件为l <= r
时,陷入死循环。
总结:当控制条件为l <= r
时,若被检索值不在arr中,则会陷入死循环。因为arr
中不存在该值时,该值必定处于arr
的两个元素之间,l = r - 1
,mid = (l + r) // 2 = (l + l + 1) // 2 = l
,arr[mid] < target
,所以 r = mid = l
。至此,不论怎么循环,l = r = mid
,arr[mid] < target
始终成立。
另:当循环控制条件为l <= r
并且检索数值大于arr
右边界时,并不会陷入死循环。
综上所述,循环控制条件设定为:l < r
,右边界取值:len(arr) - 1
时较好。