一 、首先说说非递归实现
二分查找就是从一个有序数列中查找指定的元素,其思想是:首先用序列中间位置的元素和要查找的元素进行比较,如果大于要查找的元素,说明要查找的元素位于前半部分,如果小于要查找的元素,说明位于后半部分,如果等于要查找的元素,说明找到了,返回索引位置。然后再对前半部分(或后半部分)进行同样的操作。具体如下:
假定要查找的元素所在的序列如下:
list01 = [0,1,2,3,4,5,6,7,8,9]
接下来我们定义两个变量(l,r)分别用来存储第一个元素的索引和最后一个元素的索引。如下:
l,r = 0,len(list_target)-1 # list_target为传入的列表
再定义一个变量存储中间位置(mid),如下:
mid = (l+r) // 2
接下来我们只要用mid位置的元素与要查找的元素进行比较就可以了。非递归实现的代码如下:
def binary_search2(list_target,num):
#1.定义l,r分别代表首位、末尾元素索引
l,r = 0,len(list_target) - 1
while l <= r:
mid = (l+r) // 2
#2.判断中间元素是否是要查找的num
#如果中间元素大于num,说明num存在于前半部分,否则,存在于后半部分
if list_target[mid] > num :
#存在前半部分中,则把r更新一下,向前移动一位
r = mid - 1
elif list_target[mid] < num:
#存在后半部分中,则把l更新一下,向后移动一位
l = mid + 1
elif list_target[mid] == num:
#找到了,返回索引位置
return mid
else:
#没有找到,返回0
return 0
测试:
print(binary_search(list01,6))
运行结果为:6,查找正确。
需要补充说明的是,while循环判断l小于等于r是个大前提,如果l一直增加或r一直减小到条件不满足时还没有找到元素,说明要查找的数据不存在,此时l大于r。
二、接下来说说递归实现
看到二分查找,首先我想到的是用递归实现,本来以为递归实现起来应该更简单,但却出现了一些意想不到的结果,总是返回None,后面查了python函数调用的原理,才修改过来。
首先我想到的是传入的参数代表查找的范围变化,也需要l,r分别代表首末元素的索引,原始序列数据和要查找的数据也是需要传入的,所以函数定义为:
binary_search(list_target,l,r,num)
接下来用中间位置mid = (l+r)//2 的元素与要查找的元素进行比较,如果大于或小于,则调用函数自身(此时l和r则发生变化),如果等于,则返回索引。代码如下:
def binary_search(list_target,l,r,num):
mid = (l+r) // 2
if list_target[mid] == num:
return mid
if list_target[mid] > num:
binary_search(list_target,l,mid-1,num)
elif list_target[mid] < num:
binary_search(list_target,mid+1,r,num)
进行测试如下:
list01 = [0,1,2,3,4,5,6,7,8,9]
print(binary_search(list01,0,len(list01)-1,5))
发现返回结果为None.
这个问题困扰了我好一会儿,通过调试我发现mid的值会变成要查找元素的索引“5”,感觉应该是调用那里出了问题。查找了相关资料发现,python函数调用的时候要开栈帧,调用结束栈帧就销毁了,所以在调用前加上return,实现层层返回结果,就可以了。此外,以上代码没有考虑到要查找元素不存在的情况,需要对l和r进行比较判断,修改后的完整代码如下:
def binary_search(list_target,l,r,num):
if l > r:
return 0
mid = (l+r) // 2
if list_target[mid] == num:
return mid
if list_target[mid] > num:
return binary_search(list_target,l,mid-1,num)
elif list_target[mid] < num:
return binary_search(list_target,mid+1,r,num)
总结:递归程序简洁易懂,但在调用过程中确实存在意想不到的情况,还要考虑递归的深度是否超过最大深度,如果数据序列很长时,就可能会超过调用深度,使用时还是要细心检查才好。