《图解算法》python实现的基本算法之二分法

引入

情景一: 假设要在电话簿中找一个名字以K打头的人,可以从头开始翻页,直到进入以K打头的部分。但你很可能不这样做,而是从中间开始,因为你知道以K打头的名字在电话簿中间。

情景二: 假设要在字典中找一个以O打头的单词,你也将从中间附近开始。

情景三: 假设你登录QQ。当你这样做时,QQ必须核实你是否有其网站的账户,因此必须在其数据库中查找你的用户名。如果你的用户名为kkk,QQ可从以A打头的部分开始查找,但更合乎逻辑的做法是从中间开始查找。

总结: 以上查找是一个算法问题,几乎每个类似的情景都可以使用按顺序查找的方式,但是大多数情况下,这是不合理的,由此可以引申出二分或者说折半查找的算法。

二分查找

二分查找是一种算法,其输入是一个有序的元素列表(必须是有序的)。如果要查找的元素包含在列表中,二分查找返回其位置;否则返回null。

举一个猜数字的例子

1. 给你一个数组,长度为100,从1开始到100结束:

《图解算法》python实现的基本算法之二分法_第1张图片
2. 系统随机产生一个范围在1到100的正整数

3. 用户来猜这个数字的大小,大了程序返回猜大了,反之返回猜小了,知道用户猜中,程序结束。

参考代码:

import random as r

def guess():
    n = 0
    rmin = 1
    rmax = 100
    ranNum = r.randint(rmin,rmax)
    while True:
        n += 1
        userNum = int(input(f'请猜一个范围[{rmin},{rmax}]的整数:'))
        if ranNum == userNum:
            print(f'猜对了!<{userNum}>,猜了{n}次')
            break
        elif userNum > ranNum:
            print(f'猜大了!<{userNum}>')
        elif userNum < ranNum:
            print(f'猜小了!<{userNum}>')
            
if __name__ == '__main__':
    guess()

一种做法:
《图解算法》python实现的基本算法之二分法_第2张图片

像上图中这样从小到大猜100次,这显然是一种糟糕的方法。

如果我们采用二分法思想呢,每次都猜中间数。

《图解算法》python实现的基本算法之二分法_第3张图片

《图解算法》python实现的基本算法之二分法_第4张图片
《图解算法》python实现的基本算法之二分法_第5张图片

《图解算法》python实现的基本算法之二分法_第6张图片
不难发现,如果使用二分查找,每次查找的元素个数都会减一半,大大提高了效率,猜数字1到100,最多不超过7次,就能猜中答案!

优化后的猜数字代码:

import random as r

def guess():
    n = 0
    rmin = 1
    rmax = 100
    ranNum = r.randint(rmin,rmax)
    while True:
        n += 1
        #机器算法,折半查找
        userNum = (rmin + rmax) // 2
        if ranNum == userNum:
            print(f'猜对了!<{userNum}>,猜了{n}次')
            break
        elif userNum > ranNum:
            rmax = userNum
            print(f'猜大了!<{userNum}>')
        elif userNum < ranNum:
            rmin = userNum
            print(f'猜小了!<{userNum}>')

if __name__ == '__main__':
    guess()

二分法例题

输入:一个有序列表,一个待查找数
输出:位置索引或者none

def binary_search(list,item):
    low = 0#low和high用于跟踪要在其中查找的列表部分
    high = len(list) - 1
    while low <= high:
        mid = (low + high) // 2 #只要范围没有缩小到只包含一个元素,就检查中间的元素
        guess = list[mid] 
        if guess == item:#找到了元素
            return mid
        if guess > item:#猜的数字大了
            high = mid - 1 #向下放缩范围,因为索引从零开始,如果不-1就会产生死循环
        else:#猜的数字小了
            low = mid + 1#向上放缩范围
    return None#没有指定的元素

if __name__ == '__main__':
    my_list = [1, 3, 5, 7, 9]
    print (binary_search(my_list, 3)) # => 1
    print (binary_search(my_list, 9)) # => 4
    print (binary_search(my_list, -1)) # => None


简单查找和二分查找的对比

《图解算法》python实现的基本算法之二分法_第7张图片

二分法缺点

  • 必须有序,我们很难保证我们的数组都是有序的。
  • 它必须是数组,数组读取效率是O(1),可是它的插入和删除某个元素的效率却是O(n)。

参考书籍《图解算法》

你可能感兴趣的:(算法,二分法,算法,python,数据结构)