最全【C++】二分查找--超详细图解(小白一看就懂,2024年最新成功收获美团,2024年最新小米offer

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

题目分析:

二分法的思想很简单,因为整个数组是有序的,数组默认是递增的。

  • 首先选择数组中间的数字和需要查找的目标值比较
  • 如果相等最好,就可以直接返回答案了
  • 如果不相等

1. 如果中间的数字大于目标值,则中间数字向右所有数字都大于目标值,全部排除

2. 如果中间的数字小于目标值,则中间数字向左所有数字都小于目标值,全部排除

二分法就是按照这种方式进行快速排除查找的

tips:

不用去纠结数组的长度是奇数或者偶数的时候,怎么取长度的一半,以下说明,可以跳过。

⚡当数组的长度为奇数的时候****⚡

是奇数的情况很简单,指向中间的数字很容易理解,如果需要查找的数字为29

因为29大于中间的数字大于11,所以左边的所有数字全部排除

 当数组的长度为偶数的时候⚡

这个时候 中间的数字两边的数字数量就不一样了(刚开始学习二分法的时候我经常纠结这个问题,和另外一个 长度除2得到的是最中间的数吗的问题,我相信不止我一个人纠结过……但其实这是同一个问题,每次长度除2,如果长度为奇数,得到的中间的数字两边数字数量相同,如果长度为偶数就为上图中间的数字两边的相差为 1)

但是千万不要一直纠结中间的数字两边的数字数量不一样这个问题,因为:

  • 两边数量不一样是一定会出现的情况
  • 但是这种情况并不影响我们对中间数字和目标数字大小关系的判断
    • 只要中间数字大于目标数字,就排除右边的
    • 只要中间数字小于目标数字,就排除左边的

所以数组长度是偶数还是奇数这个真的不重要,不影响怎么排除的问题,无非是多排除一个数字或者少排除一个数字

真正影响的是中间那个数字到底该不该加入下一次的查找中,也就是边界问题

✨写法1:左闭右闭

二分法最重要的两个点:

  • while循环中 left 和 right 的关系,到底是 left <= right 还是 left < right
  • 迭代过程中 middle 和 right 的关系,到底是 right = middle - 1 还是 right = middle
🥝正确的写法(正确演示)

第一种写法:每次查找的区间在 [left, right](左闭右闭区间),根据查找区间的定义(左闭右闭区间),就决定了后续的代码应该怎么写才能对。因为定义 target 在**[left, right]**区间,所以有如下两点:

  • 循环条件要使用 while(left <= right),因为当 (left == right) 这种情况发生的时候,得到的结果是有意义的
  • if(nums[middle] > target) , right 要赋值为 middle - 1, 因为当前的 nums[middle] 一定不是 target ,需要把这个 middle 位置上面的数字丢弃,那么接下来需要查找范围就是 [left, middle - 1]

代码如下:

class Solution {
public:
	int search(vector<int>& nums, int target)
	{
		int left = 0;                                 //下标从0开始
		int right = nums.size() - 1;                  //定义target在左闭右闭的区间里[left,right]   nums.size()求数组中元素个数
		while (left <= right)                         //当left == right时,区间[left, right]仍然有效
		{
			int mid = left + ((right - left) / 2);    // 防止溢出 等同于(left + right)/2;int会向下取整
			if (nums[mid] > target)
			{
				right = mid - 1;                      // target 在左区间,所以[left, middle - 1]
			}
			else if (nums[mid] < target)              // target 在右区间,所以[middle + 1, right]
			{
				left = mid + 1;
			}
			else                                      // nums[middle] == target
			{
				return mid;                           // 数组中找到目标值,直接返回下标
			}
		}
		return -1;                                    //未找到目标值
	}
};


图例解析:

首先看一个数组,需要对这个数组进行操作。需要对33进行查找的操作,那么target 的值就是33

  • 首先,对 left 的值和 right 的值进行初始化,然后计算 middle 的值
    • left = 0, right = size - 1
    • middle = (left + (right - left) / 2 )

比较 nums[middle] 的值和 target 的值大小关系

  • if (nums[middle] > target),代表middle向右所有的数字大于**target**
  • if (nums[middle] < target),代表middle向左所有的数字小于**target**
  • 既不大于也不小于就是找到了相等的值

nums[middle] = 13 < target = 33,left = middle + 1

见下图:

  • 循环条件为 while (left <= right)
  • 此时,left = 6 <= right = 11,则继续进行循环
  • 当前,middle = left + ((right - left) / 2),计算出 middle 的值

计算出 middle 的值后,比较 nums[middle] 和 target 的值,发现:

  • nums[middle] = 33 == target = 33,找到目标
🍍错误的写法(错误演示)

对应第一种正向的写法,我们把循环条件修改看看会发生什么事

  • 原查找区间 [left, right]
  • 原循环条件是 while (left <= right)

修改后题目对应的条件:

  • 查找区间不变,仍然是[left, right]
  • 查找数字为27 (target = 27)
  • 循环条件修改为while (left < right)

代码如下:

class Solution {
public:
	int search(vector<int>& nums, int target)
	{
		int left = 0;                                 //下标从0开始
		int right = nums.size() - 1;                  //定义target在左闭右闭的区间里[left,right]   nums.size()求数组中元素个数
         // 错误写法
		while (left < right)                         //当left == right时,区间[left, right]仍然有效
		{
			int mid = left + ((right - left) / 2);    // 防止溢出 等同于(left + right)/2;int会向下取整
			if (nums[mid] > target)
			{
				right = mid - 1;                      // target 在左区间,所以[left, middle - 1]
			}
			else if (nums[mid] < target)              // target 在右区间,所以[middle + 1, right]
			{
				left = mid + 1;
			}
			else                                      // nums[middle] == target
			{
				return mid;                           // 数组中找到目标值,直接返回下标
			}
		}
		return -1;                                    //未找到目标值
	}
};

好了,现在开始用图片模拟过程

  • 初始化一个数组,计算 middle 的值

  • 根据计算的 middle 值确定 nums[middle]

  • 因为nums[middle] = 13 < target = 27,所以left = middle + 1

  • 继续计算 middle 的值

  • 因为 nums[middle] = 33 > target = 27,所以 right = middle - 1

  • 接着计算 middle 的值

  • 因为 nums[middle] = 22 < target = 27,此时 left = middle + 1,此时 left = right,而循环条件为while (left < right),所以还未找到27 的情况下算法就跳出了循环,返回 -1

✨写法2:左闭右开

🍎正确的写法(正确演示)

第二种写法:每次查找的区间在 [left, right),(左闭右开区间), 根据区间的定义,条件控制应该如下:

  • 循环条件使用 while (left < right)
  • if (nums[middle] > target), right = middle,因为当前的 nums[middle] 是大于 target 的,不符合条件,不能取到 middle,并且区间的定义是 [left, right),刚好区间上的定义就取不到 right, 所以 right 赋值为 middle。

代码如下:

class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int left = 0;
        int right = nums.size();                           // 定义target在左闭右开的区间里,即:[left, right)
        while (left < right)                               // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
        { 
            int mid = left + ((right - left) >> 1);        // >>右移运算符  向右移动一位相当于除2
            if (nums[mid] > target) 
            {
                right = mid;                               // target 在左区间,在[left, middle)中
            }
            else if (nums[mid] < target) 
            {
                left = mid + 1;                            // target 在右区间,在[middle + 1, right)中
            }
            else                                           // nums[middle] == target
            { 
                return mid;                                // 数组中找到目标值,直接返回下标
            }
        }
        return -1;                                         // 未找到目标值
    }
};

图列解析如下:

第一步是初始化 left 和 right 的值,然后计算 middle 的值

  • left = 0, right = size
  • 循环条件while (left < right)

因为是左闭右开区间,所以数组定义如下:

  • 计算 middle 的值

  • 比较 nums[middle] 和 target 的大小:因为 nums[middle] = 22 > target = 3
  • 所以 right = middle

  • 符合循环的条件,接着计算 middle 的值

  • 比较 nums[middle] 和 target 的大小:因为 nums[middle] = 9 > target = 3
  • 所以 right = middle

  • 符合循环的条件,继续计算 middle 的值

  • 比较 nums[middle] 和 target 的大小关系:因为nums[middle] = 0 < target = 3
  • 所以 left = middle + 1

  • 符合循环条件,接着计算 middle 的值

  • 比较 nums[middle] 和 target 的关系:nums[middle] = 3 == target = 3
  • 成功找到 target
🍐错误的写法(错误演示) ​​​​​​​​​​

对应第二种正确的写法,照样把循环条件修改,看会发生什么事

正确的写法中条件为:

  • 查找原区间 [left, right)
  • 循环条件为 while (left < right)

修改后题目对应的条件:

  • 查找区间不变,仍然是 [left, right)
  • 循环条件修改为:while (left <= right)
  • 查找的数字为26**(数组中不存在这个数字!!!)**

代码:

class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int left = 0;
        int right = nums.size();                           // 定义target在左闭右开的区间里,即:[left, right)
        // 错误的写法
        while (left <= right)                               // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
        { 
            int mid = left + ((right - left) >> 1);        // >>右移运算符  向右移动一位相当于除2
            if (nums[mid] > target) 
            {
                right = mid;                               // target 在左区间,在[left, middle)中
            }
            else if (nums[mid] < target) 
            {
                left = mid + 1;                            // target 在右区间,在[middle + 1, right)中
            }
            else                                           // nums[middle] == target
            { 
                return mid;                                // 数组中找到目标值,直接返回下标
            }
        }
        return -1;                                         // 未找到目标值
    }
};

**图例详解:**以下是演示全过程:

  • 同样,开始初始化一个数组

  • 先计算 middle 的值

  • 判断 nums[middle] 和 target 的大小关系:nums[middle] = 22 < target = 26

  • left = middle + 1 (其实这里nums[left] 已经等于27,26不可能找到,接下去就看算法是否能够知道数组中不存在26并且返回-1 了)

  • 符合循环条件,计算 middle 的值

  • 判断 nums[middle] 和 target 的大小关系:nums[middle] = 57 > target = 26
  • right = middle

  • 满足循环条件,接着计算 middle 的值

  • 比较 nums[middle] 和 target 的大小关系:nums[middle] = 33 > target = 26
  • right = middle

  • 符合循环条件,继续计算 middle 的值

  • 比较 nums[middle] 和 target 大小关系,因为 nums[middle] = 27 > target = 26,
  • 所以 right = middle,自此,left 和 right 相遇,但是循环条件被我们修改成 while (left <= right) 会接着做循环

  • 接下来就是死循环
  • 因为 middle = left + ((right - left) / 2),当 left = right 的时候,middle 的值不会继续改变
  • middle 不继续改变,由于right = middle,right 也不会改变,所以三个数字自此开始不会继续改变
  • 循环条件 while (left <= right) 却仍然满足,不会跳出循环
  • 死循环……

✨ 总结

二分法最重要的两个点,就是循环条件和后续的区间赋值问题

因为两者是相互联系,相互影响的,所以就需要两者统一,如果两者不统一,就会出现问题

所以循环条件和赋值问题必须统一,也就是循环不变量。

见到数组的题,我们可以先看一下,有没有顺序,如果有,可以根据题意考虑可以合不合适使用二分查找。

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

✨ 总结

二分法最重要的两个点,就是循环条件和后续的区间赋值问题

因为两者是相互联系,相互影响的,所以就需要两者统一,如果两者不统一,就会出现问题

所以循环条件和赋值问题必须统一,也就是循环不变量。

见到数组的题,我们可以先看一下,有没有顺序,如果有,可以根据题意考虑可以合不合适使用二分查找。

[外链图片转存中…(img-PgXDpY9L-1715820813818)]
[外链图片转存中…(img-UlynEcvM-1715820813818)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值