【数据结构与算法】二分查找

一、什么是二分查找?

二分查找针对的是一个有序的数据集合,每次通过跟区间中间的元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间缩小为0。

二、时间复杂度分析?

1.时间复杂度
假设数据大小是n,每次查找后数据都会缩小为原来的一半,最坏的情况下,直到查找区间被缩小为空,才停止。所以,每次查找的数据大小是:n,n/2,n/4,…,n/(2k),…,这是一个等比数列。当n/(2k)=1时,k的值就是总共缩小的次数,也是查找的总次数。而每次缩小操作只涉及两个数据的大小比较,所以,经过k次区间缩小操作,时间复杂度就是O(k)。通过n/(2^k)=1,可求得k=log2n,所以时间复杂度是O(logn)。
2.认识O(logn)
①这是一种极其高效的时间复杂度,有时甚至比O(1)的算法还要高效。为什么?
②因为logn是一个非常“恐怖“的数量级,即便n非常大,对应的logn也很小。比如n等于2的32次方,也就是42亿,而logn才32。
③由此可见,O(logn)有时就是比O(1000),O(10000)快很多。

三、如何实现二分查找?

1.循环实现
代码实现:

public int binarySearch1(int[] a, int val){
int start = 0;
int end = a.length - 1;
while(start <= end){
int mid = start + (end - start) / 2;
if(a[mid] > val) end = mid - 1;
else if(a[mid] < val) start = mid + 1;
else return mid;
}
return -1;
}

注意事项:
①循环退出条件是:start<=end,而不是start<end。
②mid的取值,使用mid=start + (end - start) / 2,而不用mid=(start + end)/2,因为如果start和end比较大的话,求和可能会发生int类型的值超出最大范围。为了把性能优化到极致,可以将除以2转换成位运算,即start + ((end - start) >> 1),因为相比除法运算来说,计算机处理位运算要快得多。
③start和end的更新:start = mid - 1,end = mid + 1,若直接写成start = mid,end=mid,就可能会发生死循环。
2.递归实现

public int binarySearch(int[] a, int val){
return bSear(a, val, 0, a.length-1);
}
private int bSear(int[] a, int val, int start, int end) {
if(start > end) return -1;
int mid = start + (end - start) / 2;
if(a[mid] == val) return mid;
else if(a[mid] > val) end = mid - 1;
else start = mid + 1;
return bSear(a, val, start, end);
}

四、使用条件(应用场景的局限性)

1.二分查找依赖的是顺序表结构,即数组
2.二分查找针对的是有序数据,因此只能用在插入、删除操作不频繁,一次排序多次查找的场景中。
3.数据量太小不适合二分查找,与直接遍历相比效率提升不明显。但有一个例外,就是数据之间的比较操作非常费时,比如数组中存储的都是长度超过300的字符串,那这是还是尽量减少比较操作使用二分查找吧。
4.数据量太大也不是适合用二分查找,因为数组需要连续的空间,若数据量太大,往往找不到存储如此大规模数据的连续内存空间。

五、四种常见的二分查找变形问题

1.查找第一个值等于给定值的元素


public int bsearch(int[] a, int n, int value) {int low = 0;int high = n - 1;while (low <= high) {int mid =  low + ((high - low) >> 1);if (a[mid] > value) {high = mid - 1;} else if (a[mid] < value) {low = mid + 1;} else {if ((mid == 0) || (a[mid - 1] != value)) return mid;else high = mid - 1;}}return -1;
}

2.查找最后一个值等于给定值的元素
如果 a[mid]这个元素已经是数组中的最后一个元素了,那它肯定是我们要找的;如果 a[mid]的后一个元素 a[mid+1]不等于 value,那也说明 a[mid]就是我们要找的最后一个值等于给定值的元素。
如果我们经过检查之后,发现 a[mid]后面的一个元素 a[mid+1]也等于 value,那说明当前的这个 a[mid]并不是最后一个值等于给定值的元素。我们就更新 low=mid+1,因为要找的元素肯定出现在[mid+1, high]之间。


public int bsearch(int[] a, int n, int value) {int low = 0;int high = n - 1;while (low <= high) {int mid =  low + ((high - low) >> 1);if (a[mid] > value) {high = mid - 1;} else if (a[mid] < value) {low = mid + 1;} else {if ((mid == n - 1) || (a[mid + 1] != value)) return mid;else low = mid + 1;}}return -1;
}

3.查找第一个大于等于给定值的元素
如果 a[mid]小于要查找的值 value,那要查找的值肯定在[mid+1, high]之间,所以,我们更新 low=mid+1。
对于 a[mid]大于等于给定值 value 的情况,我们要先看下这个 a[mid]是不是我们要找的第一个值大于等于给定值的元素。如果 a[mid]前面已经没有元素,或者前面一个元素小于要查找的值 value,那 a[mid]就是我们要找的元素。
如果 a[mid-1]也大于等于要查找的值 value,那说明要查找的元素在[low, mid-1]之间,所以,我们将 high 更新为 mid-1。


public int bsearch(int[] a, int n, int value) {int low = 0;int high = n - 1;while (low <= high) {int mid =  low + ((high - low) >> 1);if (a[mid] >= value) {if ((mid == 0) || (a[mid - 1] < value)) return mid;else high = mid - 1;} else {//肯定low = mid + 1;}}return -1;
}

4.查找最后一个小于等于给定值的元素


public int bsearch(int[] a, int n, int value) {int low = 0;int high = n - 1;while (low <= high) {int mid =  low + ((high - low) >> 1);if (a[mid] >= value) {if ((mid == 0) || (a[mid - 1] < value)) return mid;else high = mid - 1;} else {low = mid + 1;}}return -1;
}

六、适用性分析

1.凡事能用二分查找解决的,绝大部分我们更倾向于用散列表或者二叉查找树,即便二分查找在内存上更节省,但是毕竟内存如此紧缺的情况并不多。
2.求“值等于给定值”的二分查找确实不怎么用到,二分查找更适合用在”近似“查找问题上。比如上面讲几种变体。

五、思考

1.如何在1000万个整数中快速查找某个整数?
我们的内存限制是 100MB,每个数据大小是 8 字节,最简单的办法就是将数据存储在数组中,内存占用差不多是 80MB,符合内存的限制。我们可以先对这 1000 万数据从小到大排序,然后再利用二分查找算法,就可以快速地查找想要的数据了。
①1000万个整数占用存储空间为40MB,占用空间不大,所以可
以全部加载到内存中进行处理;
②用一个1000万个元素的数组存储,然后使用快排进行升序排序,时间复杂度为O(nlogn)
③在有序数组中使用二分查找算法进行查找,时间复杂度为O(logn)
2.如何编程实现“求一个数的平方根”?要求精确到小数点后6位?

public static double sqrt(double x, double precision) {
if (x < 0) {
return Double.NaN;
}
double low = 0;
double up = x;
if (x < 1 && x > 0) {
/** 小于1的时候*/
low = x;
up = 1;
}
double mid = low + (up - low)/2;
while(up - low > precision) {
if (mid * mid > x ) {//TODO mid可能会溢出 改成mid > x / mid 
up = mid;
} else if (mid * mid < x) {
low = mid;
} else {
return mid;
}
mid = low + (up - low)/2;
}
return mid;
}

3.如何快速定位出一个IP地址的归属地?
[202.102.133.0, 202.102.133.255] 山东东营市
[202.102.135.0, 202.102.136.255] 山东烟台
[202.102.156.34, 202.102.157.255] 山东青岛
[202.102.48.0, 202.102.48.255] 江苏宿迁
[202.102.49.15, 202.102.51.251] 江苏泰州
[202.102.56.0, 202.102.56.255] 江苏连云港
假设我们有 12 万条这样的 IP 区间与归属地的对应关系,如何快速定位出一个IP地址的归属地呢?
如果 IP 区间与归属地的对应关系不经常更新,我们可以先预处理这 12 万条数据,让其按照起始 IP 从小到大排序。如何来排序呢?我们知道,IP 地址可以转化为 32 位的整型数。所以,我们可以将起始地址,按照对应的整型值的大小关系,从小到大进行排序
。然后,这个问题就可以转化为我刚讲的第四种变形问题“在有序数组中,查找最后一个小于等于某个给定值的元素”了。
当我们要查询某个 IP 归属地时,我们可以先通过二分查找,找到最后一个起始 IP 小于等于这个 IP 的 IP 区间,然后,检查这个 IP 是否在这个 IP 区间内,如果在,我们就取出对应的归属地显示;如果不在,就返回未查找到。

笔记整理来源: 王争 数据结构与算法之美

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

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

相关文章

第七章 假设检验(3)

关于样本量n的取值 如果希望在控制第I类错误的情况下&#xff0c;同时限制第II类错误的&#xff0c;这个时候就需要考虑样本量。样本量越大&#xff0c;错误概率越低。使用OC曲线。 分布拟合检验 如果不知道总体服从什么类型的分布&#xff0c;就需要根据样本来检验分布的假设…

git 多用户多仓库配置

ssh全称是Secure Shell&#xff0c;即安全Shell&#xff0c;是一种可以进行安全远程登录的协议&#xff0c;在Linux中以OpenSSH为代表&#xff0c;Windows中则有Putty作为实现。ssh的会话建立阶段类似TCP协议的三次握手&#xff0c;里面涉及到利用非对称加密(RSA/DSA)和密码协商…

第二十三期:你用的Windows操作系统是不是盗版?微软知道吗

长期以来&#xff0c;一些小白对于微软操作系统的“正版”“盗版”“原版”的含义不太明确&#xff0c;从根本上说Windows原始版权属于微软&#xff0c;我们没有那个技术去开发盗版操作系统&#xff0c;很多人用的可能是二次修改的版本&#xff0c;而操作系统的激活方式&#x…

[Leetcode][第109题][JAVA][有序链表转换二叉搜索树][分治][快慢指针][中序遍历]

【问题描述】[中等] 【解答思路】 1. 分治 快慢指针 复杂度 class Solution {public TreeNode sortedListToBST(ListNode head) {return buildTree(head, null);}public TreeNode buildTree(ListNode left, ListNode right) {if (left right) {return null;}ListNode mid …

第四十六期:关于云存储的五大优势

5G时代&#xff0c;越来越多的企业开始选择在云上存储数据&#xff0c;对于IT部门来说&#xff0c;了解云存储的优势是很有必要的。今天给大家介绍云存储的五大优势&#xff0c;以及它们如何帮助用户解决一些常见IT问题。 1、可扩展性 传统存储系统以及横向扩展增加的节点&…

leetcode之回溯backtracing专题5

参考链接 http://blog.csdn.net/zhongkeli/article/details/6966805 https://leetcode.com/problems/permutation-sequence/#/description

第四十七期:毕业3年Java程序员,年薪20W,他是如何达到的?

Java架构师&#xff0c;首先要是一个高级java攻城狮&#xff0c;熟练使用各种框架&#xff0c;并知道它们实现的原理。jvm虚拟机原理、调优&#xff0c;懂得jvm能让你写出性能更好的代码;池技术&#xff0c;什么对象池&#xff0c;连接池&#xff0c;线程池…… Java架构师&…

【数据结构与算法】散列表

一、散列表的由来&#xff1f; 1.散列表来源于数组&#xff0c;它借助散列函数对数组这种数据结构进行扩展&#xff0c;利用的是数组支持按照下标随机访问元素的特性。 2.需要存储在散列表中的数据我们称为键&#xff0c;将键转化为数组下标的方法称为散列函数&#xff0c;散列…

第八章方差分析以及线性回归(1)

方差分析 方差分析是由英国统计学家Fisher在20世纪20年代提出的。  方差分析的目的是推断两个或者两个以上的总体均值是否有差异的显著性检验。 单因素方差分析 例子 保险公司为了了解某一险种在4个不同地区索赔额情况是否存在差异。收集了四个地区一年的索赔额记录。这四个…

react树状组件

最近在react项目中需要一个树状组件&#xff0c;但是又不想因为这个去引入一套UI组件&#xff0c;故自己封装了一个基于react的树状组件&#xff0c; 个人认为比较难得部分在于数据的处理&#xff0c;话不多说直接上代码&#xff1a; 下面是tree.js import React, {Component} …

第四十八期:只因写了一段爬虫,公司200多人被抓!

刚从朋友听到这个消息的时候&#xff0c;我有点不太相信&#xff0c;做为一名程序员来讲&#xff0c;谁还没有写过几段爬虫呢&#xff1f;只因写爬虫程序就被端有点夸张了吧。 作者&#xff1a;纯洁的微笑|2019-10-17 09:51 “一个程序员写了个爬虫程序&#xff0c;整个公司20…

[Leetcode][第647题][JAVA][回文子串][动态规划][中心扩展][Manacher 算法]

【问题描述】[中等] 【解答思路】 1. 暴力 首先明确如何判断一个字符串是否为回文字符串。第一个字符与最后一个字符相同&#xff0c;第二个字符与倒数第二个字符相同…关于中心位置轴对称。 本题要求一共有多少个回文子串&#xff0c;那么就需要判断&#xff0c;索引[i, j]的…

66-加一

给定表示非负整数的非空数字数组&#xff0c;加上整数的1。 存储数字使得最高有效数字位于列表的开头&#xff0c;并且数组中的每个元素包含单个数字。 您可以假设整数不包含任何前导零&#xff0c;除了数字0本身 例1&#xff1a; 输入&#xff1a; [1,2,3] 输出&#xff1a; […

玩转oracle 11g(52):Oracle导出导入表(.sql、.dmp文件)两种方法

提示&#xff1a;在导入sql和dmp文件之前&#xff0c;先建立用户&#xff0c;指明表空间。其中要注意用户名和表空间最好跟sql文件中的一样。 方法一&#xff1a;.sql文件的导出与导入 导出步骤 使用PL/SQL Developer登录你需要备份的数据库&#xff1b;选择工具->导出用…

第八章方差分析以及线性回归(2)

一元线性回归 变量间的关系 变量与变量之间的关系分为确定性关系和相关性关系。  确定性关系是指当自变量给定一个值的时候&#xff0c;就能计算出应变量的值。例如物体下落高度h与下落时间t的关系&#xff1a;h12gt2。  相关性关系是指变量之间的关系不确定&#xff0c;表…

Creating a Pulsing Circle Animation

Creating a Pulsing Circle Animation 原文 https://www.kirupa.com/animations/creating_pulsing_circle_animation.htm Outside of transitions that animate between states, we dont see a whole lot of actual animation in the many UIs we interact with. We dont have …

第四十九期:化繁为简的五种码农必备工具

如今&#xff0c;开发工具已成为了软件开发过程中必不可少的组成部分。本文将向您介绍当前软件开发市场上颇具影响力的五种化繁为简的码农必备工具。 不知您是否已经发现&#xff1a;那些以任务为中心的软件开发工作&#xff0c;会比独立的研究式开发复杂得多。针对软件产品的开…

【数据结构与算法】哈希算法

一、什么是哈希算法&#xff1f; 1.定义 将任意长度的二进制值串映射成固定长度的二进制值串&#xff0c;这个映射的规则就是哈希算法&#xff0c;而通过原始数据映射之后得到的二进制值串就是哈希值。 2.如何设计一个优秀的哈希算法&#xff1f; ①单向哈希&#xff1a; 从哈…

自然语言处理与文本检索

今天开始把翟成祥教授的文本检索课程做一下笔记。 说明&#xff1a;文章内容来源于课程视频和课程ppt。我只学习了课程没有做习题。文章不是翻译&#xff0c;是我对课程的理解。 nlp的主要内容 1 词语处理(lexical analysis part-of-speech tagging)&#xff1a;分词与词性标…

第五十期:工作强度超996,失业半年即出局,硅谷为何如此“嗜血”?

在硅谷&#xff0c;靠创业发财的人被称为中了“硅谷六合彩”&#xff0c;大多数个体的艰难挣扎&#xff0c;最终换来了硅谷长久的繁荣昌盛。 划重点 1、在硅谷&#xff0c;靠创业发财的人被称为中了“硅谷六合彩”。 2、谷歌的合同工必须比正式工早两小时到公司打卡&#xff…