从二分查找算法来看如何写出正确的程序

利用循环不变式写出正确的二分查找及其衍生算法 一.你能准确写出二分查找吗?先看看定义 二分查找的搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则查找成功;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。 思路很简单以至于大多数人都能讲出来,但是有多少人能一次写出bug-free的 阅读详情

平时我们写程序的时候,总是莫名其妙的出现Bug,一鼓作气的写完一个接口后,经常被队友喷「你怎么一回事?结果与预期不一致啊」

老脸一红,然后一个人藏在角落调试半天,才发现原来是某一个边界条件没处理好,导致Bug重重,今天我们从二分查找算法,来聊聊如何写出正确的程序

像我们熟知的二分查找算法,在1946年就被提出来了,但是到了1962年才出现了完全没有Bug的二分查找法

二分查找法的思想并不复杂,我们很多人张口就能来,但是为什么间隔了辣么多年,才诞生完全没有Bug的二分查找算法呢?

二分查找算法,难就难在边界条件的处理上,我们经常写出Bug,往往也是出现在边界条件没处理好而导致

因为学习一个算法的思想是很简单的,但是让思想落地,写出完全没有Bug的二分查找,却并不是一件容易的事情

所以我们在写程序时,首先就需要明确的边界的意义,在程序内部的实现时,就是要不断的维护这个边界的意义

二分查找法的思想在这简单复述一下

1.在一个有序的数组中,查询一个目标值target,

2.如果目标值target 比数组的中间值要大,那么我们就往数组中间值后面的范围内去查询

3.如果目标值target 比数组的中间值要小,我们就往数组中间值前面的范围内查询

4.如此往复的执行2,3步,直到查询到目标值target 等于其数组范围内的某一个值,然后返回其值对应的索引位置,又或者数组中压根就没有我们想要找的目标值target

好了,说完了算法的思想,我们就来动手实践一下把。在这里我就用Go语言来实现了,给你们宣传了一个多月让你们学Go语言,还给你们找了那么多学习资料,有从小白到中高级的Go语言视频,也有从小白到大牛的书籍资料。你们要现在一点都没看,那可就枉费我一片苦心了

想学Golang,但还没有头绪的童鞋,看完文章后直接在公众号后台回复「Go」,即可领取全部学习资源,别忘了回来点个在看啊

把算法思想落地

我们先定义一个二分查找的函数MyBinarySearch,之所以叫这名字-,-是因为Go有个binary包,里面有叫BinarySearch的函数,咱们需要跟它区分开来,否则编译器会提示我们在瞎搞(仿佛在跟我说:你是不是傻,有现成的不用,偏要自己搞)

func MyBinarySearch(arr []int, target int) (middle int) {

}

我们传入一个数组,以及一个要查询的目标值target,如果target 存在于数组中,我们则返回它对应的索引位置

二分查找,容易出问题的地方就是边界问题,那咱们先定义先设定一个边界,我们需要在边界范围内去查询

 // 明确边界的意义,在 [left,right] 中寻找target
left, right := 0, len(arr)-1

我们要铭记,我们在程序中,每一个变量都是有意义的,我们需要明确每一个变量的意义,我们的查询范围,就是在[left,right]中去查询目标值target ,看清楚了,我在这定义的是一个闭区间,也就包含了left 和right 本身所在的位置

明确边界变量的意义后,我们在后面的查找过程中,还需要不断的维护这个意义

我们来循环判断,边界是否有意义,如果这个边界的范围区间包含了有效的整数,则代表这个边界是有意义的,所以当边界存在意义时,我们循环判断此时的中间值是否等于目标值target

for left <= right {
 // 在 [left,right] 中寻找target
middle = (left + right) / 2 // 中间值的索引位置
}

在这可能有人不解,为什么要用<=呢?,比如闭区间[8,8] ,那么这个区间依然是有意义的,因为有一个整数8 ,如果去掉=,那就变成了[8,8),而此时这个区间范围内,是没任何整数的

现在我们就需要来判断,目标值target 于现在查询范围内中间值的关系,看它是等于,还是小于又或者是大于中间值

如果相等,那就好办了,这不就是我们要找的嘛~直接返回中间值的索引位置就好了

if arr[middle] == target {
	return
}

那如果不满足,我们就只好再来判断,目标值是小于中间值还是大于中间值了

如果目标值target 小于中间值,那么我们就需要缩小查询的区间范围了

这个时候,我们查询范围就发生了改变,右侧要范围要缩短到中间值的位置,用Code来表达,就是right = middle - 1

if target < arr[middle] {
	// 如果target < 中间值 则代表我们要在左边区间查找
	right = middle - 1
}

你可能会问,为什么要-1呢?因为我们已经明确知道target < arr[middle],也就是middle所在的位置,不可能是我们要找的位置,所以我们就需要再往左侧移动一位

说到现在,我想你现在已经彻底弄懂了left与right所代表的含义,那么当target > arr[middle]时,我们的right自然也要在middle的位置上往右移动一位

if target > arr[middle] {
	// 如果target > 中间值,则代表我们要在右边区间查找
	left = middle + 1
}

上面的Code全部拼凑起来,就是如下所示,如果当目标值不在数组中时,我们就返回 -1

// MyBinarySearch 二分查找法,在有序数组中查询目标元素target,并返回元素对应的索引值
func MyBinarySearch(arr []int, target int) (middle int) {
	left, right := 0, len(arr)-1 // 明确边界的意义,在[left,right]中寻找target
	for left <= right {
		middle = (left + right) / 2
		// 当left > right 时,意味着边界不存在,则代表数组中没有目标值target
		// 所以当left <= right 时,我们就遍历数组
		if arr[middle] == target {
			return
		}
		if target < arr[middle] {
			// 如果target < 中间值 则代表我们要在左边区间查找
			right = middle - 1
		}
		if target > arr[middle] {
			// 如果target > 中间值,则代表我们要在右边区间查找
			left = middle + 1
		}
	}
	return -1
}

我还是简单说下Go语言的函数语法吧,因为我在定义函数的时候,就写明了返回值的变量名middle,给返回值声明了变量名后,执行函数时会给返回值的变量初始化为0值,return返回值也会自动指定对应的变量名

所以我在函数中没有定义middle以及把return middle直接写成了return

循环不变量

我猜很多人都是第一次听说这个专业术语,其实我上面已经多次强调这个术语所代表的含义

现在再刻意解释一下,仔细看完上面的内容的你,现在我一说,你肯定就能懂

我们在上面的二分查找中,一直在循环left <= right,这就是循环,当left <= right时,我们的循环不会终止

而不变量是什么呢?left以及right不都是变量么?你怎么说它的不变量呢?

left以及right的值虽然一直都是在改变的,但是它们所代表的含义却是一直都没有改变过,因为我们寻找的永远都是在[left,right]这个闭区间中寻找我们的目标值

程序中left以及right的变化,也只是在不断缩小这个闭区间的范围,并没有改变其声明时所代表的含义,注意我说的,是没有改变声明时的含义

所以要想写出正确的程序,在声明每一个变量时,我们都需要明确其含义,变量在改变时,我们只能改变其数值,而不能改变其变量所代表的意义

一旦声明其变量的意义,后面的程序都是在维护其意义。就像我们每一个人一生下来就有意义,而我们人生的经历,都是在为了完成人生意义而必须所拥有的铺垫

之所以我们的Bug越写越多,多半是因为对变量的含义理解的不透彻,并且经常声明一些无意义的变量所导致(可能你认为有些变量并不是无意义的变量,但事实多半如此)

你以为到这就完了?

二分查找从提出到最后无Bug实现,期间经历了16年,要这么简单的就完结了。。。怕是没办法瞒天过海16年了

我们在上面实现的二分查找还是有个Bug隐藏着,那就是middle = (left +right) / 2

如果当left和right数值足够大的时候,我们再这样求和时,int类型就越界啦~

那越界了咋搞呢?还记得我们之前有说过如何进行大整数求和吧?嗯,记得的话,那你还挺不错的,如果你用那种方式去解决它们求中间值时造成的越界…我只能说…你是不是傻啊…

其实我们稍微改一改middle 的求值方式就好了,从加法改成了减法,那么自然也就不会越界啦~

就是改成这样子middle = left + (right - left) / 2就好了~

我这几十年之后的马后炮,真酸爽

写在最后

之前说的每周算法题,说实话,停更有好长一段时间了

今天这篇继上一篇,间隔时间估摸着有好几周了…emmmm

不是我不想在这个系列上保持持续更新,而是这货你们连个在看都不点,让我没有欲望继续写下去

你仔细看看我之前的每周算法题系列,就会发现,没有一篇是只说算法的,都是夹杂着其它的思想,或者是开发实践,又或者是一些别的经验

如果只是拿着LeetCode上的题目实现一遍,然后跟你们讲解一遍,我觉得这样做是没用任何意义,因为你们完全可以去Github上看人家的solution,一次性看几十个题都没问题,也可以直接去LeetCode一天刷个几十题,何苦还需要在我这看我BB叨呢…

最后为了证明你彻底掌握循环不变量的关键思想,给你们留个问题吧

我们在之前声明时left = 0,right = len(arr) - 1,现在将right改成right = len(arr),我们后面在处理区间时,当target < arr[middle]时,left和right又该怎么赋值呢?

给你们一个提示,我们在写right = len(arr)-1时的查询范围是[left,right],而right = len(arr)时,查询的范围是[left,right),注意查询区间的改变~

之所以给你们留个问题,是因为只看不做还不思考,是很难彻底弄明白的~

在底下留言给出你的思考吧!

微信扫码关注公众号「闹闹吃鱼」,每周都有好分享,还可领取学习资源哦~不仅仅只是技术!

将算法转化成程序 很多人程序员不知道如何将算法转化为程序,通过此文档,相信你的编程功力会更进一步。 立即下载

相关推荐

如何将《数据结构》的算法转化成程序.pdf

#资源达人分享计划#

3-1 从二分查找法看如何写出正确的程序

如何写出正确的程序 设定“循环不变量”,即我们寻找的数据索引一定在该范围中:[l .. r]。 数组中可查找的数据的索引范围为:[0, n-1],前闭后闭。 设置循环结束条件:当 l <= r,区间 [l ... r] 依然是有效的,数组中仅包含一个元素 设置返回条件: arr[mid] == target,返回mid arr[mid] > target,target在 [mid+1 ... r]中 arr[mid] <target,target在 [l ....

SIONCIOSC的博客 286

“傻瓜”stata入门——熟悉操作界面

小白开始学习stata和matlab,零基础上手

nnpersistent的博客 4453

由July师兄二分查找代码及编程珠玑有感:循环不变性(断言)证明程序的正确性及发现bug

最近看到Lucida大神写的算法数据结构经历以及各种面试经历帖,偶然看到编程珠玑这本书,发现里面对二分查找的证明有着与 算法导论的循环不变性相似,但是更加make sense的证明思路,最近读了一下,有一些心得。大家可以尝试运用这个方法来证明下更加容易 出现bug的快速排序算法,以便确定自己的代码是否正确。 二分查找算法(h=n-1版本): int BinarySearch(int

richardzrc的博客搬家了--->richardzhang.info 1858

单卡也能玩转Qwen2.5-VL微调!3090实测Grounding任务调参技巧大全

本文详细介绍了如何在单张RTX 3090显卡上,通过梯度累积、BF16混合精度训练、梯度检查点等关键技术,成功微调Qwen2.5-VL多模态大模型以完成Grounding视觉定位任务。文章提供了从数据处理、参数配置到训练监控的完整实战调参技巧,为资源有限的开发者提供了可行的解决方案。

weixin_29098117的博客 424

自抗扰控制(ADRC)理论与工程实践:8.1 自适应ADRC与参数在线整定

自适应自抗扰控制(AADRC)针对传统固定参数ADRC在动态变化系统中的局限性,提出通过在线调整关键参数(如观测器带宽ω₀、控制器带宽ω_c)以适应对象动态变化。主要方法包括基于稳定性理论的自适应律设计和基于性能指标的优化调节,前者保证闭环稳定但设计复杂,后者通过梯度搜索或模糊规则实现性能优化但缺乏严格稳定性证明。当前挑战在于非线性时变系统的稳定性分析、参数变化速率限制及工程实现的计算负担。AADRC拓展了ADRC在变参数系统(如无人机、软体机器人)中的应用,是迈向自主智能控制的关键技术。

技术工作者 214

如何写出一个正确的二分查找

文章目录1.二分查找2.二分查找的变种查找第一个与key相等的元素查找最后一个与key相等的元素查找最后一个等于或者小于key的元素查找最后一个小于key的元素查找第一个等于或者大于key的元素:查找第一个大于key的元素总结 1.二分查找 注意:本文全部例子默认数组升序排序 基于分治思想的一个很简单的算法,但若想写对,却也不是那么容易 使用条件 二分查找也称折半查找(Binary Search...

star的博客 1162

Cadence 批量一次性修改title 页码标题

本文介绍如何在Cadence软件中对DSN文件进行标题栏的批量编辑操作,包括选择编辑对象、使用快捷键打开属性面板以及如何修改标题、页码等关键信息。

tianpu2320959696的博客 975

传说中的二分查找

很久以前在csdn的编码挑战栏里第一次看到“90%的程序员无法正确实现二分查找”

猪哥的白驼山 733

【云原生技术】SonarQube 生态中PMD插件和P3C插件详细介绍

4. 在项目中如何选择 / 组合 1. 小团队、对《阿里规约》认同度高 → 直接启用 **P3C**,配合 SonarJava 基本够用。 2. 追求全面 Bug/安全检测 + 额外编码规范 → **SonarJava + PMD**(必要时加 P3C 少量规则)。 3. 大型多团队,希望“一本规约”统一风格 → **P3C 为主**,再补充 SonarJava/PMD 的 Bug 与安全规则。 4. 通过 Quality Gate 控制:将 **Blocker/Critical** Bug

weixin_46453070的博客 1364

微信小程序 - 完美解决“code“:“NO_AUTH“,“message“:“商户号该产品权限未开通,请前往商户平台>产品中心检查后重试“(微信小程序报错问题排查解决)

微信小程序NO_AUTH: 商户号该产品权限未开通,请前往商户平台>产品中心检查后重试?微信小程序提示商户号该产品权限未开通,请前往商户平台>产品中心检查后重试,微信小程序"code":"NO_AUTH"错误码怎么解决,报错信息如下:httpResponseBody[{"code":"NO_AUTH","message":"商户号该产品权限未开通,请前往商户平台>产品中心检查后重试"}],微信小程序支付问题,如何解决这个问题,虚拟支付商户号,虚拟支付商户号里面没有产品支付的页面,排查商户号没问题,商户号配置

🏆 前端领域优质创作者 4444

Python 用自然语言/流程图描述算法并实现(以鸡兔同笼问题为例)

算法的定义及作用 一、导入:编程解决问题的步骤 分析问题 划分边界 设计算法 #importance 编写程序 调试测试 后期维护 二、生活中的算法问题:饮料换杯 两个杯子,一杯装可乐,一杯装雪碧,怎样才能让装可乐的杯子装雪碧,装雪碧的杯子装可乐? 再拿一个杯子。 设计算法: t=a #第一步 a=b #第二步 b=t #第三步 编写代码: >>> a="可乐" >>> b="雪碧" >>> t=a >>&g

yinaiyinai1212的博客 1万+

AFL源码分析之afl-as.c详细注释

前言 afl-as注释好了,明白了是如何进行插桩的,以及插在哪里 本文主要参考Sakura大佬文章:https://eternalsakura13.com/2020/08/23/afl/ 编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ /* Copyright 2013 Google LLC All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); y

hollk’s blog 2172

怎么将算法改成程序_从二分查找算法来看如何写出正确的程序

从二分查找算法来看如何写出正确的程序平时我们写程序的时候,总是莫名其妙的出现Bug,一鼓作气的写完一个接口后,经常被队友喷「你怎么一回事?结果与预期不一致啊」老脸一红,然后一个人藏在角落调试半天,才发现原来是某一个边界条件没处理好,导致Bug重重,今天我们从二分查找算法,来聊聊如何写出正确的程序像我们熟知的二分查找算法,在1946年就被提出来了,但是到了1962年才出现了完全没有Bug的二分查找法...

weixin_39705435的博客 205

【26美赛C题】2026美赛数学建模(MCM/ICM)思路解析及代码分享

本文为2026年美国大学生数学建模竞赛(MCM/ICM)C题的解题思路与资源分享。文章提出系统学习数学建模的重要性,强调竞赛考察的是综合建模能力而非单纯计算。针对C题常见的复杂系统评估类问题,作者提供了一套完整的建模框架,包括问题拆解、指标体系构建、数据建模、决策模型等六个阶段。订阅者可获得Python/Matlab双版本代码、论文结构解析、AI辅助建模方法等资源。内容适合准备参加各类数学建模竞赛的学生,特别是希望通过AI技术提升建模效率的参赛者。

一键难忘的博客 1681

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

漫谈二分查找-Binary Search (转)

http://duanple.blog.163.com/blog/static/709717672009049528185/  (用vim把程序缩进了一下,方便看) 历史上,Knuth在其>一书的第6.2.1节指出:尽管第一个二分搜索算法于1946年就出现,然而第一个完全正确的二分搜索算法直到1962年才出现。 而不经仔细斟酌而写出的一个二分查找经常遭遇off by one或者无限循

Pegasus (http://ningning.today) 1138

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

二分查找实现(Jon Bentley:90%程序员无法正确实现)

引言     Jon Bentley:90%以上的程序员无法正确无误的写出二分查找代码。也许很多人都早已听说过这句话,但我还是想引用《编程珠玑》上的如下几段文字:  “二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它。一开始,范围覆盖整个数组。将数组的中间项与T进行比较,可以排除一半元素,范围缩小一半。就这样反复

u012512762的专栏 797

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

重温二分查找算法(r4笔记第66天)

二分查找在学习算法的时候会涉及到,算是一个基本的分治思想,对于算法的实现大家也都是很熟悉的,但是这个时候真会犯眼高手低的毛病。不信你自己试试,看你能够在段时间内写出可运行...

杨建荣的学习笔记 191

数组中的问题

数组中的问题 关于数组 1、数组应用场景 排序:选择排序、插入排序、归并排序、快速排序 查找:二分查找 数据结构:栈、队列、堆 2、如何写出正确的程序 明确变量和含义;循环不变量;小数据量调试 举例:二分查找法:找中间,再往左或右序列中寻找。 对于有序数列,才能使用二分查找法 public static int binarySearch(Comparable[] arr, int n, Comparable target){ int l = 0, r = n - 1; // 在[l..

lucky_xy1997的博客 224

【模拟】历史检测

History Exam 历史测验 (exam.pas/cpp/in/out) Time Limit: 5 second Memory Limit: 1000K Discription 历史老师决定简化考试的过程。每个学生只需写下著名事件发生的年份就行了。老师有自己的年份表。学生得多少分取决于他写下的年份有多少是包含在老师的年份表中。 Input Format 首行

wu_yihao的专栏 440

只有10%的程序员能够实现二分法算法

有一些讲编程的图书,我会从头到尾、一字不落地反复研读;还有一些讲编程的图书,我已经看过好几遍了,但每次差不多都是只看其中的一章。乔恩·本特利(Jon Bentley)1986年的经典名著《编程珠玑》(Programming Pearls)则是少数几本能同时归入上述两类的编程图书之一。我打算最近再专门写一篇关于这本书的文章,但今天我只想就这本书中的几段话谈谈自己的想法。这几段内容有点骇人听闻

leohxj的专栏 1683

程序员编程艺术第二十五章:Jon Bentley:90%无法正确实现二分查找

第二十五章:二分查找实现(Jon Bentley:90%程序员无法正确实现)作者:July出处:结构之法算法之道引言    Jon Bentley:90%以上的程序员无法正确无误的写出二分查找代码。也许很多人都早已听说过这句话,但我还是想引用《编程珠玑》上的如下几段文字:  “二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它

结构之法 算法之道 7万+

如何写出正确的二分查找?

如何写出正确的二分查找?——利用循环不变式理解二分查找及其变体的正确性以及构造方式 转载自:http://www.cnblogs.com/wuyuegb2312/archive/2013/05/26/3090369.html# 序言   本文以经典的二分查找为例,介绍如何使用循环不变式来理解算法并利用循环不变式在原始算法的基础上根据需要产生算法的变体。谨以本文献给在理解算法思路时

梧桐下的四叶草 2143

只有10%程序员能正确实现二分查找算法

有一些讲编程的图书,我会从头到尾、一字不落地反复研读;还有一些讲编程的图书,我已经看过好几遍了,但每次差不多都是只看其中的一章。乔恩·本特利(Jon Bentley)1986年的经典名著《编程珠玑》(Programming Pearls)则是少数几本能同时归入上述两类的编程图书之一。我打算最近再专门写一篇关于这本书的文章,但今天我只想就这本书中的几段话谈谈自己的想法。这几段内容有点骇人听闻

jeffcjl的专栏 1033

很多程序员都无法写出正确的二分查找

二分查找(折半查找)

qq_47882731的博客 1166
上一篇: JS如何取得URL里的参数?
下一篇: golang的反射机制与实践(上)
闹闹吃鱼
博客等级 码龄10年 137粉丝 40原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值