Lyf凤

靡不有初,鲜克有终

扩大
缩小

二分查找

二分查找:每次能够排除掉一半的数据,查找的效率非常高,但是局限性比较⼤,必须是有序序列才可以使⽤二分查找

要求: 查找的序列必须是有序序列

精髓: 掐头截尾取中间

实例: 

 让用户输入一个数字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,所占用的内存是很小的
最快的查找

 


 

 

 

 

posted on 2018-12-13 21:04  Lyf凤  阅读(204)  评论(0编辑  收藏  举报

导航

levels of contents