二分查找
二分查找:每次能够排除掉一半的数据,查找的效率非常高,但是局限性比较⼤,必须是有序序列才可以使⽤二分查找
要求: 查找的序列必须是有序序列
精髓: 掐头截尾取中间
实例:
让用户输入一个数字n,判断n是否在给定的列表lst中出现,如果出现请返回n所在的位置.
# 不使用递归(需要理解和掌握的)-->不改变列表,通过改变左右边界索引去缩小查找范围 lst = [1, 3, 15, 26, 77, 99, 456, 765, 1234] num = int(input('请输入你要查找的元素:')) left = 0 # 左边界 right = len(lst) - 1 # 右边界 while left <= right: # 中间值要放在循环里面计算,因为每次循环中间值都是要根据左右边界而变化的 mid = (left + right) // 2 # 使用地板除是因为索引只取整数 if num > lst[mid]: # 比中间值大,在中间值右边,缩小取值范围-->左边界右移到中间值的后面一个元素的位置 left = mid + 1 # 之所以要+1,是因为中间值已经比较过了 elif num < lst[mid]: # 比中间值小,在中间值的左边,缩小取值范围-->右边界左移到中间值的前面一个元素的位置 right = mid - 1 # 同理,之所以要-1,是因为中间值已经比较过了 else: #num == lst[mid] print(f'找到了,在索引位置{mid}') break else: # 即 left > right 而又没有被break,说明遍历完了整个数据集都没有找到 print('不存在')
# 使用递归 法二(需要理解和掌握的) 不改变列表,通过改变左右边界索引去缩小查找范围 def func(num, lst, left, right): if left <= right: mid = (left + right) // 2 if num > lst[mid]: left = mid + 1 return func(num, lst, left, right) # 递归如果有返回值,所有调用递归的地方必须写return,否则接收到的永远是None elif num < lst[mid]: right = mid - 1 return func(num, lst, left, right) else: print(f'找到了,在索引位置{mid}') return mid # 上面递归函数的前面没有return,这里的值无法被接收到 else: print('不存在') return -1 # 比如find函数查询不到就返回-1,是有道理的 num = int(input('请输入你要查找的元素:')) index = func(num, lst, 0, len(lst) - 1) print(index)
# 使用递归 通过改变列表去缩小查找范围 lst = [1, 3, 15, 26, 77, 99, 456, 765, 1234] def func(num, lst): left = 0 right = len(lst) - 1 if lst != []: # 与left <= right 是等价的,只要列表非空,就满足这个等式 mid = (left + right) // 2 # 列表非空再去比较,再计算mid 这个放在if的里面和外面不影响结果,逻辑上放在里面更好 if num > lst[mid]: # 大于中间值,对列表做切片,取中间值的右侧数据作为新的查找范围 lst = lst[mid+1:] func(num, lst) # 调用函数自身,再次执行重复的操作 elif num < lst[mid]: # 小于中间值,对列表做切片,取中间值的左侧数据作为新的查找范围 lst = lst[:mid] # 切片的语法,mid取不到,所以不需要传入 mid-1 func(num, lst) # 调用函数自身,再次执行重复的操作 else: print('找到了') # 这里改变了原来的查找对象lst,就不能再输出所查找的元素的具体位置了,如果存在的话,最后一定是索引0位置 return # 为了结束函数,就本题而言写和不写是等价的(因为函数中没有接在这个位置后面要执行的代码了),但是还是写吧,代表函数的结束(也是递归的出口),其他时候(比如函数中这个位置的下面还有代码)就是一定要写才能结束函数的执行了. else: # 遍历完了,不存在 print('不存在') return # 结束函数,理由同上 num = int(input('请输入你要查找的元素:')) func(num, lst)
拓展:
# 最高效的查找-->将要查找的元素作为索引,去新列表中找对应的值,看值是不是1,是就存在,不是就不存在 lst = [1, 23, 115, 26, 7, 99] # 不需要是有序的 # 准备工作:构建一个新列表,长度是旧列表的最大值,旧列表的元素作为索引所在位置的元素是1,其他位置都是0 # 创建空列表 new_lst = [] # print(max(lst)) # 获取列表元素的最大值 # 添加max(lst)个元素,都是0 for i in range(max(lst) + 1): # 不+1的话最大值取不到 new_lst.append(0) # 添加进去列表最大值个0---> 即构建一个新列表,里面的元素都是0,元素个数是旧列表的最大值 # 遍历旧列表,将旧列表的元素作为索引,将新列表该索引对应位置的元素改成1 for i in lst: new_lst[i] = 1 print(new_lst) # 准备工作完成,下面要开始查找了 n = int(input('请输入要查找的数据:')) # 将要查找的元素作为索引,如果新列表该索引对应位置的元素是1,则要查找的元素存在,否则(是0)不存在 if new_lst[n] == 1: print('找到了') # 只需要查找一次 else: print('不存在') # 上面这个的时间复杂度是1,空间复杂度也很低,因为新列表的元素都是0和1,所占用的内存是很小的