算法通关村第九关——透彻理解二分查找

news2024/11/24 9:42:06

1.前言

常见的查找算法有顺序查找、二分查找、插值查找、斐波那契查找、树表查找、分块查找、哈希查找等。如果进行归类,那么二分查找、插值查找(一种查找算法)以及斐波那契查找都可以归为插值查找(大类)。而插值查找和斐波那契查找是在二分查找的基础上的优化查找算法。

这些算法中最重要的就是Hash查找二分查找

记住:凡是涉及到在排好序的地方(全局或部分)查找的都可以考虑使用二分查找来优化查找效率

插值查找使用的公式为:
x = ( k e y − a r r [ i ] ) ( r − i ) a r r [ r ] − a r r [ i ] x = \frac{(key-arr[i])(r-i)}{arr[r]-arr[i]} x=arr[r]arr[i](keyarr[i])(ri)
其中,ir分别代表数组的第一个和最后一个索引, key代表待查找的元素

分块查找是折半查找和顺序查找的一种改进方法,分块查找由于只要求索引表是有序的,对块内节点没有排序要求,因此特别适合解决节点动态变化的情况。

2.基本查找

基本查找也就是顺序查找,不需要在乎数组是否排序,缺点是效率较低

function search(arr, key) {
    for (let index = 0; index < arr.length; index++) {
        if (arr[index] === key) {
            return index;
        }
    }
}

3.二分查找与分治

分治就是把整体拆分为局部,一个复杂的问题可以拆分成很多相似的小问题。二分查找是将中间结果与目标进行比较,一次去掉一半。
在这里插入图片描述

3.1 循环方法

// 二分查找——循环方式
function binarySearch(array, low, high, target) {
	while (low < high) {
		let mid = (low + high) / 2;
		if (array[mid] < target) {
			low = mid + 1} else if (array[mid] > target) {
			high = mid - 1;
		} else {
			return mid;
		}
	}
	return -1;
}

/ 运算符效率较低,可以写成移位符 >>, 还存在一个问题,当lowhigh 过于大时,low + high可能会溢出,可以将 let mid = (low + high) / 2;改为let mid = low + ((high - low) >> 1);,只要lowhigh 没有溢出,mid就不会溢出。

最终代码如下:

function binarySearch(array, low, high, target) {
	while (low < high) {
		let mid = low + ((high - low) >> 1);
		if (array[mid] < target) {
			low = mid + 1} else if (array[mid] > target) {
			high = mid - 1;
		} else {
			return mid;
		}
	}
	return -1;
}

3.2 递归方法

按照递归三步法,代码如下:

// 二分查找——递归方式
function binarySearch(array, low, high, target) {
	// 递归终止条件
	if (low <= high) {
		let mid = low + ((high - low) >> 1);
        // 不同情况判断
		if (array[mid] === target) {
			return mid;
		} else if (array[mid] < target) {
			return binarySearch(array, mid + 1, high, target);
		} else {
			return binarySearch(array, low, mid - 1, target);
		}
	}
	// 表示没有搜索到
	return -1;
}

4.有重复元素的二分查找

在上面的基础上,如果元素存在重复,要求如果重复就找左侧第一个,比如[1, 2, 2, 2, 3, 3, 3, 4, 5, 6],要返回第一个2的索引值——1

分析:基于上面简单的二分查找,在这里我们找到target不要着急返回,而是在重复元素的这个区间继续进行查找,一直找到最左侧的重复元素,再返回最左侧元素的重复索引。在重复元素的这个区间继续进行查找方法有好几种,第一种方法比较简单,就是使用线性查找,一个一个的向左进行查找直到找到最左侧的重复元素。

// 元素中有重复的二分查找,在上面的基础上,元素存在重复,如果重复则找左侧第一个
function binarySearchOfRepeat(array, target) {
	// 特判
	if (array === null || array.length  === 0) {
		return -1;
	}

	let left = 0;
	let right = array.length - 1;

	while (left <= right) {
		let mid = left + ((right - left) >> 1);
		if (array[mid] < target) {
			left = mid + 1;
		} else if (array[mid] > target) {
			right = mid - 1;
		} else {
			// 找到目标值后,还应该继续向左侧进行线性查找,直到左侧没有重复值
			while (mid !== 0 && array[mid] === target) {
				mid--;
			}
			// 如果一直找到了开头还都是重复值,就返回开头
			if (mid === 0 && array[mid] === target) {
				return mid;
			}
			// 不然就返回mid + 1

			return mid + 1;
		}
	}
	return -1;
}

这里之所以返回mid + 1,是因为假如序列为[1, 2, 2, 2, 2, 3, 3],当target=3,当内层的while循环退出时,nums[mid]=2,因此必须返回mid + 1

第二种方法呢就是使用折半查找:

function binarySearchOfRepeat(array, target) {
	// 特判
	if (array === null || array.length  === 0) {
		return -1;
	}

	let left = 0;
	let right = array.length - 1;

	while (left <= right) {
		let mid = left + ((right - left) >> 1);
		if (target < array[mid]) {
			right = mid - 1;
		} else if (target > array[mid]) {
			left = mid + 1;
		} else {
			// 如果存在重复元素,在该段重复数组区间内进行折半查找
			while (left < mid) {				
				let midLeft = left + ((mid - left) >> 1);
                  // 如果array[midLeft] === target,直接去掉一半右边的重复值
				if (array[midLeft] === target) {
					mid = midLeft;
				} 
                  // 如果array[midLeft] !== target,说明超过了重复区间,让left =  midLeft + 1,继续查找
                  else {
					left =  midLeft + 1;
				 }
			}
			return left;
		}
	}
	return -1;
}

拓展——使用递归进行有重复元素的二分查找

再拓展一下,使用递归方法。

function binarySearchOfRepeat(array, target, left, right) {
    // 不能发生边界逾矩
    if (left > right) {
        return -1;
    }
    
    let mid = left + ((right - left) >> 1);

    if (array[mid] === target) {
        // 在重复区间内使用递归来找到最左侧的位置
        let leftmostInRepeat = binarySearchOfRepeat(array, target, left, mid - 1);
        // 如果没有重复返回mid,如果有重复返回重复区间最左侧值索引
        return (leftmostInRepeat === -1) ? mid : leftmostInRepeat;
    } else if (target < array[mid]) {
        return binarySearchOfRepeat(array, target, left, mid - 1);
    } else {
        return binarySearchOfRepeat(array, target, mid + 1, right);
    }
}

function findLeftmostRepeatIndex(array, target) {
    if (array === null || array.length === 0) {
        return -1;
    }
    
    return binarySearchOfRepeat(array, target, 0, array.length - 1);
}

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

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

相关文章

macOS上编译obs-studio

前言 最近基于obs的1个二开程序&#xff0c;需要移植到macOS平台上&#xff0c;由于遇到些问题&#xff0c;本文记录下如何在macOS上配置&编译&运行obs程序完整过程。 下载 首先下载cmake-gui工具&#xff0c;下载CMAKE&#xff0c;选择对应macOS平台的cmake版本&…

过来人经验,PMP考试第七版要怎么学?

第七版没有大家说的那么难&#xff0c;根据考纲来看&#xff0c;一半内容将考查预测型项目管理方法&#xff0c;一半考查敏捷或混合型方法 &#x1f308;备考建议是&#xff1a;先学第六版和敏捷&#xff0c;再来学第七版&#xff0c;第七版其实可以理解为第六版的升级&#xf…

java八股文面试[JVM]——垃圾回收

参考&#xff1a;JVM学习笔记&#xff08;一&#xff09;_卷心菜不卷Iris的博客-CSDN博客 GC垃圾回收面试题&#xff1a; JVM内存模型以及分区&#xff0c;需要详细到每个区放什么 堆里面的分区&#xff1a;Eden&#xff0c;survival from to&#xff0c;老年代&#xff0c;各…

7-模板过滤器

一. 过滤器 过滤器: 过滤器本质就是函数 # 模板语法中过滤器: {{ 变量名| 过滤器 }} {{ 变量名| 过滤器(*args) }}二. 常见的过滤器 str 过滤器的相关操作: safe :禁用转义 capitalize: 单词的首字母大写 lower 和 upper: 大小转换 title : 一句话中的每个单词的首字母大写 r…

机器学习深度学习——NLP实战(自然语言推断——注意力机制实现)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——NLP实战&#xff08;自然语言推断——数据集&#xff09; &#x1f4da;订阅专栏&#xff1a;机器学习&…

科脉收银系统中了360勒索病毒怎么办?勒索病毒解密,数据恢复

在商超、大卖场、连锁便利店和餐饮店的收银系统当中&#xff0c;科脉绝对是一个不得不提的产品。正因为其在市场当中有着极高的占有率&#xff0c;才使得其成为了勒索病毒攻击的主要目标之一。近几日&#xff0c;云天数据恢复中心就接到好多客户的咨询&#xff0c;在这些客户当…

视频云存储/安防监控EasyCVR视频汇聚平台接入GB国标设备时,无法显示通道信息该如何解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

SpringBoot整合Quartz,实现数据库方式执行定时任务

springboot整合quartz&#xff0c;实现数据库方式执行定时任务。把定时任务信息存进数据库&#xff0c;项目启动后自动执行定时任务。 1.引入依赖包&#xff1a; <dependency> <groupId>org.springframework.boot</groupId> <ar…

牛客刷题第二弹

ASCII 定义在类中的变量是类的成员变量&#xff0c;可以不进行初始化&#xff0c;Java会自动进行初始化&#xff0c;如果是引用类型默认初始化为null,如果是基本类型例如int则会默认初始化为0 局部变量是定义在方法中的变量&#xff0c;必须要进行初始化&#xff0c;否则不同…

论文阅读及复现——《CT_ICP: Real-time Elastic LiDAR Odometry with Loop Closure》

论文阅读之——《CT_ICP: Real-time Elastic LiDAR Odometry with Loop Closure》带闭环的实时弹性激光雷达里程计 1. 主要贡献2. 相关说明3. 激光里程计3.1 里程计公式构建3.2 局部地图与健壮性 4. 回环检测与后端5. 实验结果5.1 里程计实验结果5.2 回环检测实验结果 6. 总结…

构造函数内的方法 直接写在构造函数内部 与 写在prototype上 的区别

文章目录 前言区别总结 前言 以前没注意过, 去创建一个构造函数的时候, 方法都是直接写在函数内的. 在构造函数需要多次实例化的情况下有缺点, 不过幸好以前项目里的构造函数也不需要多次实例化, 缺点没有生效. 区别 为了比较, 先在构造函数内部直接书写方法, 查看实例化结果…

问题来了!你知道你穿的防砸劳保鞋的保护包头都是什么材料

防砸劳保鞋是较为常见的一种劳保鞋&#xff0c;用于作业过程中保护工人的脚&#xff0c;减少或避免被坠落物、重物砸伤或压伤脚部的工作鞋。防砸安全鞋鞋前头装有防护包头&#xff0c;具有耐压力和抗冲击性能。主要适用于矿山、机械、建筑、钢铁、冶金、运输等行业。 你穿的防砸…

NLog 使用

环境 .net 6.0 控制台程序 第三方库 NLog.Extensions.Logging Microsoft.Extensions.DependencyInjection 创建 NLog 配置文件 记得设置始终赋值&#xff0c;生成到发布文件夹 <?xml version"1.0" encoding"utf-8" ?> <nlog xmlns"h…

骨传导运动蓝牙耳机哪个牌子好?

生命在于运动&#xff0c;每次在运动时&#xff0c;搭配上音乐&#xff0c;可以极大程度让心情更为轻松和愉悦&#xff0c;可以说骨传导蓝牙耳机是爱运动的朋友的福音&#xff0c;相较于其他耳机来说&#xff0c;佩戴稳定不用担心出现脱落的情况&#xff0c;也不会出现传统耳机…

C++ STL迭代器适配器(详解)

STL迭代器适配器 C STL迭代器适配器是什么&#xff1f; 通过学习 C STL 标准库中的容器我们知道&#xff0c;无论是序列式容器还是关联式容器&#xff08;包括哈希容器&#xff09;&#xff0c;要想遍历容器中存储的数据&#xff0c;就只能用使用该容器模板类中提供的迭代器。…

搜狗拼音占用了VSCode及微信小程序开发者工具快捷键Ctrl + Shit + K 搜狗拼音截图快捷键

修改搜狗拼音的快捷键 右键--更多设置--属性设置--按键--系统功能快捷键--系统功能快捷键设置--取消Ctrl Shit K的勾选--勾选截屏并设置为Ctrl Shit A 微信开发者工具设置快捷键 右键--Command Palette--删除行 微信开发者工具快捷键 删除行&#xff1a;Ctrl Shit K 或…

世界上第一台无人机长啥样?

2023 世界上第一台无人机长啥样&#xff1f; 01 无人机&#xff08;英文通常为drone或unmanned aerial vehicle&#xff0c;简称为UAV&#xff09;即无人驾驶的飞机&#xff0c;是相对于载人飞机而言&#xff0c;它利用无线电遥控设备和自身的程序控制装置来完成空中的飞行任务…

通讯应用目标客户匹配,电商界都在用的营销技巧

马克扎克伯格&#xff08;Mark Zuckerburg&#xff09;对通讯应用说过一句话&#xff1a;未来是私有的。他暗示&#xff0c;社交互动的未来将在私人消息应用上。本文将帮助您使用消息应用目标客户匹配在你和现有客户之间打开一个消息应用沟通渠道。 什么是目标客户匹配&#xf…

【高危】WinRAR<6.23 远程代码执行漏洞 (CVE-2023-40477)

漏洞描述 WinRAR 是一款适用于 Windows 系统的压缩包管理器&#xff0c;其恢复卷功能用于修复损坏或丢失的压缩文件数据。 WinRAR 6.23之前版本的恢复卷功能未对用户提供的数据有效验证从而导致内存越界访问&#xff0c;攻击者可诱使用户使用 WinRAR 打开恶意文件&#xff0c…

【SkyWalking】分布式服务追踪与调用链系统

1、基本介绍 SkyWalking是一个开源的观测平台&#xff0c;官网&#xff1a;Apache SkyWalking&#xff1b; 可监控&#xff1a;分布式追踪调用链 、jvm内存变化、监控报警、查看服务器基本配置信息。 2、SkyWalking架构原理 在整个skywalking的系统中&#xff0c;有三个角色&am…