Python 算法:学习二分法

news2025/7/11 15:08:18

二分法,一个看似简单,逻辑易懂的算法,但是初次接触可能会有很多坑!主要是边界处理的问题。

下面以一个耳熟能详的案例来展开:
案例描述:

小B从1~100之间(含边界值)任意想一个数字(目标值),然后给7次机会让小A来猜,如果小A的猜测值大于目标值,则提示“太大了”;如果猜测值小于目标值,则提示“太大了”;如果猜测值等于目标值,则提示“猜对了!”;
在7次内小A猜到了就是小A赢,猜不到就是小B赢。如果你是小A,你会怎么玩这个游戏?

这个就是二分法的经典使用场景,每次都对半猜,7次之内肯定都能猜到结果,因为2^7=128,而提供的猜测范围才100个数,就算在1~128之间来猜也没问题!

一、踩坑之路

根据对半猜的原则,我尝试自己写了一下,跑完发现没什么问题,代码如下:

def binary_serch(array, target):
    l = 0
    r = len(array)-1

    i = 1
    while l <= r:
        m = int(0.5*l+0.5*r)    # 取中间值,索引没有小数,向下取整
        guess = array[m]
        if guess > target:      # 如果猜测值大于目标值,则给右索引赋值猜测值的索引值
            r = m
            print('太大了!')   
        elif guess < target:    # 如果猜测值小于目标值,则给左索引赋值猜测值的索引值
            l = m
            print('太小了!')
        else:                   # 如果猜测值等于目标值,返回结果
            print('猜对了!值为%d,总执行%d次。' % (target, i))
            break
        i += 1                  # 计算循环基础,即猜了几次

if __name__ == '__main__':
    array = [i for i in range(1, 100)]  # 有序数组
    target = random.randint(1,100)
    binary_serch(array, target)

实际上是有问题的,一边的边界值是取不到的,但是由于是随机取数,所以取到两边边界值的概率只有2%,相对较低。可以给target赋值100,查看执行效果,会是一个死循环。因为索引值取到98和99时,int((98+99)/2)永远是98,取不到索引值99

如果进行四舍五入取值呢?会进入另外一个端点的边界值取不到,即当索引值取到0和1时,(0+1)/2永远是1,取不到索引值0

这种某一端点取不到边界值的情况,只能通过加一个判断逻辑进行处理(如下代码),但是这样的处理方式似乎不是明智之举,二者的判断有些脱离了,在变种时,比如说查找第一个或者最后一个大于或小于某值的值,便会有一些吃力,很难用原有的逻辑进行扩展。

def binary_serch(array, target):
    l = 0
    r = len(array)-1

    i = 1
    if target == array[r]:
        print('猜对了!值为%d,总执行%d次。' % (target, i))
        return 0
    while l <= r:
        m = int(0.5*l+0.5*r)    # 取中间值,索引没有小数,向下取整
        guess = array[m]
        if guess > target:      # 如果猜测值大于目标值,则给右索引赋值猜测值的索引值
            r = m
            print('太大了!')
        elif guess < target:    # 如果猜测值小于目标值,则给左索引赋值猜测值的索引值
            l = m
            print('太小了!')
        else:                   # 如果猜测值等于目标值,返回结果
            print('猜对了!值为%d,总执行%d次。' % (target, i))
            break
        i += 1                  # 计算循环基础,即猜了几次

if __name__ == '__main__':
    array = [i for i in range(1, 101)]  # 有序数组
    # target = random.randint(1,100)
    target = 100
    binary_serch(array, target)

二、解决方案

后来,在网上看到了一个很妙的处理方式,就是将开始的索引值和结束的索引值看成是指针往外多加一位,左边从-1开始,右边则是列表长度(不用减掉1),如此处理便可以取到两个边界值,代码如下:

def binary_serch(array, target):
    l = -1
    r = len(array)

    i = 1
    while l + 1 != r:         # 左右索引相差1时跳出循环
        m = int(0.5*l+0.5*r)
        guess = array[m]
        if guess > target:
            r = m
            print('太大了!%d:%d' % (m, guess))  # 打印对应的索引和元素
        elif guess < target:
            l = m
            print('太小了!%d:%d' % (m, guess))  # 打印对应的索引和元素
        else:
            print('猜对了!值为%d,总执行%d次。' % (target, i))
            break
        i += 1

if __name__ == '__main__':
    array = [i for i in range(1, 101)]  # 有序数组
    binary_serch(array, 1)
    binary_serch(array, 100)

该代码和我一开是写的代码有三句有一定的差别,分别是第2行、第3行和第6行:
image.png
一开始我的那个代码就是因为取不到一边的边界值,所以该解决方案在两边多加一位,如此一来便可以取到整个列表的所有值。当然,在单独看索引值的取值的情况下,该方案也是取不到索引值100的,还是会出现一开始的那个问题int((99+100)/2)=99,永远取不到100,但是我们的取值范围在0~99,所以不用取到两个索引的边界值-1和100。而取到0和99的时候,-1和0、100和99都相差1,所以引出了while循环的条件l + 1 != r,即当左右索引值二者相差1的时候跳出循环。

理论上,该条件还可以防止出现溢出现象(即:IndexError: list index out of range)导致报错。其实在猜测值等于目标值的时候,便会break,所以在该逻辑下不会存在溢出现象。所以循环的条件稍微取大一些也不会对结果造成影响,如while l < r:while l <= r:

在该逻辑下,l < rl + 1 != r多了一种可能性:l + 1 == r,而l <= rl < r也多了一种可能性l == r。由于二分法在左右相差1(即l + 1 == r)之前就已经可以取到所有的值并会触发break,所以判断条件并不会执行到l + 1 == rl == r
尽管如此,还是推荐使用l + 1 != r,因为在需求变种时很有效。

注意点:由于题目一开始就限定了输入的目标值是在数组范围内,所以没有做判断大于数组最大值和小于数组最小值的情况,实际应用或需要,如下需求变种便需要考虑,以避免取值时出现溢出现象。

三、需求变种

需求1:查找第一个大于某值的值
当左索引和有索引相差1时,跳出循环,取右索引值对应的元素。

def binary_serch(array, target):
    l = -1
    r = len(array)

    while l + 1 != r:         # 左右索引相差1时跳出循环
        m = int(0.5*l+0.5*r)
        guess = array[m]
        if guess > target:
            r = m
            print('较大,往左边移动。%d:%d' % (m, guess))  # 打印对应的索引和元素
        elif guess < target:
            l = m
            print('较小,往右边移动。%d:%d' % (m, guess))  # 打印对应的索引和元素
        else:
            l = m  # 取第一个大于某值的值,当相等的时候,需要继续往右边取,所以m赋值给左索引
            print('相等,继续往右边取值。%d:%d' % (m, guess))  # 打印对应的索引和元素
    # 判断不小于数组最大值
    if target >= max(array):
        print('无符合值,目标值不小于数组内最大值。')
    else:
        print(array[r])

if __name__ == '__main__':
    array = [1, 3, 5, 5, 5, 7, 9]  # 有序数组
    # 分别测试数组内的值
    # for i in array:
    #     binary_serch(array, i)
    #     print('原来的值%d' % i)
    binary_serch(array, 0)   # 预期结果1
    binary_serch(array, 10)  # 预期结果无符合值

需求2:查找最后一个小于某值的值
当左索引和有索引相差1时,跳出循环,取左索引值对应的元素。

def binary_serch(array, target):
    l = -1
    r = len(array)

    while l + 1 != r:         # 左右索引相差1时跳出循环
        m = int(0.5*l+0.5*r)
        guess = array[m]
        if guess > target:
            r = m
            print('较大,往左边移动。%d:%d' % (m, guess))  # 打印对应的索引和元素
        elif guess < target:
            l = m
            print('较小,往右边移动。%d:%d' % (m, guess))  # 打印对应的索引和元素
        else:
            r = m  # 取最后一个小于某值的值,当相等的时候,需要继续往左边取,所以m赋值给右索引
            print('相等,继续往左边取值。%d:%d' % (m, guess))  # 打印对应的索引和元素

    if target <= min(array):
        print('无符合值,目标值不大于数组内最小值。')
    else:
        print(array[l])

if __name__ == '__main__':
    array = [1, 3, 5, 5, 5, 7, 9]  # 有序数组
    # 分别测试数组内的值
    for i in array:
        binary_serch(array, i)
        print('原来的值%d' % i)
    binary_serch(array, 0)   # 预期结果无符合值
    binary_serch(array, 10)  # 预期结果9

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/5623.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Security 在登录时如何添加图形验证码

前言 在前面的几篇文章中&#xff0c;登录时都是使用用户名 密码进行登录的&#xff0c;但是在实际项目当中&#xff0c;登录时&#xff0c;还需要输入图形验证码。那如何在 Spring Security 现有的认证体系中&#xff0c;加入自己的认证逻辑呢&#xff1f;这就是本文的内容&…

matplotlib简介

matplotlib是一款用于画图的软件&#xff0c;以下步骤建议在.ipynb中完成。 导包 你需要导入以下包&#xff1a; import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np一个简单案例 matplotlib 在 Figure上绘制图形&#xff0c;每一个Figure会包含…

【附源码】Python计算机毕业设计手游账号交易系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Java注解式开发

目录 1. ssm框架的搭建 1.1 maven项目的创建 1.2 导入所需的包到pom.xml文件中 1.3 导入数据库连接文件、日志文件、redis连接文件 1.3.1 数据库连接文件 1.3.2 日志文件 1.3.3 redis连接文件 1.4 导入spring-mybatis、spring-mvc、spring-base、spring-redis四种集成文…

改变自己 只需要两年

改变自己 只需要两年 https://v.douyin.com/rLDmdQK/ 可以快速浏览上面视频 今天分享的这篇文章是TED上的一篇演讲 希望对下定决心想改变的你一些帮助。 用两年时间证明你可以 两年时间不算多长&#xff0c;但与此同时&#xff0c;很多事情都能在两年内完成&#xff0c;你…

WinHex(三)

目录 一、新建简单卷 二、MBR作用与结构 一、新建简单卷 1.右键点击刚刚新建的虚拟磁盘&#xff0c;选择新建简单卷。我新建了两个一个是NTFS&#xff0c;一个是FAT32 2.我们在刚刚新建的虚拟磁盘中放入一张图片&#xff0c;打开WinHex,点击“打开磁盘”选项&#xff0c;打…

[野火]STM32 F103 HAL库开发实战指南笔记之简单外设总结

1、GPIO编程总结 使能 GPIO 端口时钟&#xff1b;初始化 GPIO 目标引脚为推挽输出模式&#xff1b;编写简单测试程序&#xff0c;控制 GPIO 引脚输出高、低电平。 这部分宏控制 LED 亮灭的操作是直接向 BSRR 寄存器写入控制指令来实现的&#xff0c;对 BSRR 低 16 位 写 1 输出…

大学生静态HTML网页源码 我的校园网页设计成品 学校班级网页制作模板 web课程设计 dreamweaver网页作业

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

从0实现线性回归

编码题&#xff1a; 按要求完成下面的内容 1请用python完成从0实现线性回归&#xff0c;尝试使用不同的训练参数&#xff08;学习率&#xff0c;迭代次数&#xff09;&#xff0c; 以及不同的评价方法&#xff08;MSE,MAE,RMSE,R2&#xff09;等。 2比较说明sklearn的线性模…

断言(assert)的用法

参考&#xff1a;https://www.runoob.com/w3cnote/c-assert.html 目录作用总结与注意事项Demo作用 assert 是个宏&#xff0c;并且作用并非"报错"。 assert() 的用法像是一种"契约式编程"&#xff0c;程序满足我的假设条件&#xff0c;才能正常良好的运作…

做视频素材资源(free视频,音频,图片)

素材资源 一、视频 Videezy &#xff1a;https://www.videezy.com/ Videovo&#xff1a;https://www.videvo.net/ mixkit&#xff1a;https://mixkit.co/&#xff0c;可以 distill&#xff1a;https://wedistill.io/ splitshire&#xff1a;https://www.splitshire.com/ pixa…

Mysql常见指令以及用法(保姆级)

文章目录基础篇通用语法及分类DDL&#xff08;数据定义语言&#xff09;数据库操作注意事项表操作DML&#xff08;数据操作语言&#xff09;添加数据注意事项更新和删除数据DQL&#xff08;数据查询语言&#xff09;基础查询条件查询聚合查询&#xff08;聚合函数&#xff09;分…

前端性能-首次加载优化70%

前言 本篇文章&#xff0c;我们来总结归纳下万恶的this以及衍生出来的call/apply/bind对this进行绑定&#xff0c;想了很久&#xff0c;决定用实例演示的方式来讲解this&#xff0c;这样才能够理解this&#xff0c;因为this确实变化莫测&#xff0c;只靠概念&#xff0c;是不能…

【JS 构造|原型|原型链|继承(圣杯模式)|ES6类语法】下篇

⌚️⌚️⌚️个人格言&#xff1a;时间是亳不留情的&#xff0c;它真使人在自己制造的镜子里照见自己的真相! &#x1f4d6;Git专栏&#xff1a;&#x1f4d1;Git篇&#x1f525;&#x1f525;&#x1f525; &#x1f4d6;JavaScript专栏&#xff1a;&#x1f4d1;js实用技巧篇…

【数据结构】带头双向循环链表基本操作的实现(C语言)

&#x1f680; 作者简介&#xff1a;一名在后端领域学习&#xff0c;并渴望能够学有所成的追梦人。 &#x1f40c; 个人主页&#xff1a;蜗牛牛啊 &#x1f525; 系列专栏&#xff1a;&#x1f6f9;初出茅庐C语言、&#x1f6f4;数据结构 &#x1f4d5; 学习格言&#xff1a;博…

峰会实录 | StarRocks PMC Chair 赵纯:数据分析的极速统一3.0 时代

作者&#xff1a;StarRocks PMC Chair 赵纯&#xff08;本文为作者在 StarRocks Summit Asia 2022 上的分享&#xff09; 一年前&#xff0c;StarRocks 源码开放&#xff0c;StarRocks 社区也正式成立。经过一年发展&#xff0c;社区已经获得了 3400 个 Star&#xff0c;7500 …

如何用windows上架ios到苹果商城

1.苹果账号 1.你需要申请苹果账号 官网有提示&#xff1a;Sign In - Apple 2.登录 登录后店家 account&#xff0c;进入account。 点击证书&#xff0c;进入。 2.开始上架步骤 1.注册标识符&#xff08;Bundle ID&#xff09; 进入这个界面后&#xff0c;点击 Identifiers …

Elasticsearch快照备份

目录 1、Repositories 1、配置路径 2、注册快照存储库 2、查看注册的库 3、创建快照 1、为全部索引创建快照 2、为指定索引创建快照 4、查看备份完成的列表 5、删除快照 6、从快照恢复 1、恢复指定索引 2、恢复所有索引&#xff08;除.开头的系统索引&#xff09; …

【Redis】 数据结构:Redis对象与编码(底层结构)对应关系详解

【Redis】 数据结构&#xff1a;Redis对象与编码(底层结构)对应关系详解 文章目录【Redis】 数据结构&#xff1a;Redis对象与编码(底层结构)对应关系详解Redis对象与编码(底层结构)对应关系引入Redis数据结构-RedisObjectredisObject数据结构Redis的编码方式五种数据结构Redis…

2022年深信服杯四川省大学生信息安全技术大赛-CTF-Reverse复现(部分)

Rush B 开始先设置一下数字以16进制格式显示 看主函数 __int64 __fastcall main(int a1, char **a2, char **a3) {int v3; // eaxsize_t v4; // raxint v5; // ecxchar v6; // alint v7; // ecxint v9; // [rsp3Ch] [rbp-404h]char s[1000]; // [rsp40h] [rbp-400h] BYREFchar …