二分查找是典型的分治法的应用,要求待查序列排好序,这里都按照从小到大排列处理,查找时间代价o(log(n)).
思路和中间的数字比较,如果相等则找到,如果<则在左边找,如果>则在右边找。
分治及许多计算机算法的核心就是将问题设法转化为
相同的形式问题而规模减小,即子问题。能够找到规模减小的子问题,意味着问题的解决。
注意分治不一定非要递归,如果每次只走一个分支,那么循环的写法也非常简单,是更好的写法。
1
def
bsearch(li, val):
2
3 start = 0
4
5 end = len(li) - 1
6
7 while (start <= end):
8
9 middle = int((start + end) / 2 )
10
11 if val == li[middle]:
12
13 return middle
14
15 if val < li[middle]:
16
17 end = middle - 1
18
19 else :
20
21 start = middle + 1
22
23
24 return - 1
2
3 start = 0
4
5 end = len(li) - 1
6
7 while (start <= end):
8
9 middle = int((start + end) / 2 )
10
11 if val == li[middle]:
12
13 return middle
14
15 if val < li[middle]:
16
17 end = middle - 1
18
19 else :
20
21 start = middle + 1
22
23
24 return - 1
二分变形1
在数组中查找val,有可
能数组中有多个相同的val,返回第一个的位置。
方法1,二分查找val,找到后向左线性扫描直到遇到更小的。
方法2,找到val以后,对左边的数组继续二分查找直到找到第一个位置
其它方法? TODO参考一下stl的lower bound
下面给出方法2的解法。
1
"""
find the first val like li avove search 3 will return 2
"""
2
3 def bsearch2(li, val):
4
5 start = 0
6
7 end = len(li) - 1
8
9 find = - 1
10
11 while (start <= end):
12
13 middle = int((start + end) / 2 )
14
15 if val == li[middle]:
16
17 end = middle - 1
18
19 find = middle
20
21 if val < li[middle]:
22
23 end = middle - 1
24
25 else :
26
27 start = middle + 1
28
29 return find
2
3 def bsearch2(li, val):
4
5 start = 0
6
7 end = len(li) - 1
8
9 find = - 1
10
11 while (start <= end):
12
13 middle = int((start + end) / 2 )
14
15 if val == li[middle]:
16
17 end = middle - 1
18
19 find = middle
20
21 if val < li[middle]:
22
23 end = middle - 1
24
25 else :
26
27 start = middle + 1
28
29 return find
二分变形2
将排好序的数组移位,而且不知道具体的移位位置,
如1 , 2, 3, 4, 5, 6, 7
移位后可能为
6 , 7, 1, 2, 3, 4, 5 //list1
3, 4, 5, 6, 7, 1, 2 //list2
这样查找过程中,要注意如何能够找到相同形式规模缩小的子问题呢?
仍然二分查找,但是每次下一步查找的区间的确定,就不仅仅由待查找的val和当前数组中间值的关系确定了,要考虑数组的头尾元素值。
例如 list1中查找 7, 那么先和2 比较发现较大, 但是下一步不能去右边区间找,因为7 > list1的首元素了,所以应该在左边区间查找。
注意区分list1,list2的不同 list1的中间值小于list1的首元素,意味着中间值在较小的序列中。
而list2的中间值大于首元素,意味着中间值在较大的序列中。
1
def
bsearch3(li, val):
2
3 start = 0
4
5 end = len(li) - 1
6
7 while (start <= end):
8
9 mid = int((start + end) / 2 )
10
11 # print '*', mid, val, li[mid]
12
13 if val == li[mid]:
14
15 return mid
16
17 if val == li[start]:
18
19 return start
20
21 if val == li[end]:
22
23 return end
24
25 if val < li[mid]:
26
27 if li[mid] < li[start]: # like 6 7 1 .2. 3 4 5
28
29 end = mid - 1 # here to find 1
30
31 else : # li[mid] >= li[start]like 3 4 5 .6. 7 1 2
32
33 if (val < li[start]): # here to find 2
34
35 start = mid + 1
36
37 else :
38
39 end = mid - 1 # here to find 4
40
41 else : # val > li[mid]
42
43 if li[mid] < li[start]: # like 6 7 1 .2. 3 4 5
44
45 if val < li[end]: # here to find 3 or 4
46
47 start = mid + 1
48
49 else :
50
51 end = mid - 1 # here to find 7
52
53 else : # like 3 4 5 .6. 7 1 2
54
55 start = mid + 1 # here to find 7
56
57 return - 1
2
3 start = 0
4
5 end = len(li) - 1
6
7 while (start <= end):
8
9 mid = int((start + end) / 2 )
10
11 # print '*', mid, val, li[mid]
12
13 if val == li[mid]:
14
15 return mid
16
17 if val == li[start]:
18
19 return start
20
21 if val == li[end]:
22
23 return end
24
25 if val < li[mid]:
26
27 if li[mid] < li[start]: # like 6 7 1 .2. 3 4 5
28
29 end = mid - 1 # here to find 1
30
31 else : # li[mid] >= li[start]like 3 4 5 .6. 7 1 2
32
33 if (val < li[start]): # here to find 2
34
35 start = mid + 1
36
37 else :
38
39 end = mid - 1 # here to find 4
40
41 else : # val > li[mid]
42
43 if li[mid] < li[start]: # like 6 7 1 .2. 3 4 5
44
45 if val < li[end]: # here to find 3 or 4
46
47 start = mid + 1
48
49 else :
50
51 end = mid - 1 # here to find 7
52
53 else : # like 3 4 5 .6. 7 1 2
54
55 start = mid + 1 # here to find 7
56
57 return - 1