algorithm-base/animation-simulation/二分查找及其变种/二分查找详解.md
2021-07-23 15:44:19 +00:00

150 lines
7.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

> 如果阅读时发现错误或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ,备注 github + 题目 + 问题 向我反馈
>
> 感谢支持该仓库会一直维护希望对各位有一丢丢帮助
>
> 另外希望手机阅读的同学可以来我的 <u>[**公众号袁厨的算法小屋**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步,想要和题友一起刷题,互相监督的同学,可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。
### 什么是二分
废话不多说让导演帮我们把镜头切到袁记菜馆吧
袁记菜馆内
> 店小二掌柜的您进货回来了呀今天您买这鱼挺大呀
>
> 袁厨那是这是我今天从咱们江边买的之前一直去菜市场买那里的老贵了你猜猜我今天买的多少钱一条
>
> 店小二之前的鱼30 个铜板一条今天的我猜 26 个铜板
>
> 袁厨贵了
>
> 店小二还贵呀那我猜 20 个铜板
>
> 袁厨还是贵了
>
> 店小二15 个铜板
>
> 袁厨便宜了
>
> 店小二18 个铜板
>
> 袁厨恭喜你猜对了
上面的例子就用到了我们的二分查找思想如果你玩过类似的游戏那二分查找理解起来肯定很轻松啦下面我们一起征服二分查找吧
# **完全有序**
## 二分查找
> 二分查找也称折半查找Binary Search是一种在有序数组中查找某一特定元素的搜索算法我们可以从定义可知运用二分搜索的前提是数组必须是有序的这里需要注意的是我们的输入不一定是数组也可以是数组中某一区间的起始位置和终止位置
通过上面二分查找的定义我们知道了二分查找算法的作用及要求那么该算法的具体执行过程是怎样的呢
下面我们通过一个例子来帮助我们理解我们需要在 nums 数组中查询元素 8 的索引
```java
int[ ] nums = {1,3,4,5,6,8,12,14,16}; target = 8
```
> 1我们需要定义两个指针分别指向数组的头部及尾部这是我们在整个数组中查询的情况当我们在数组
>
> 某一区间进行查询时可以输入数组起始位置终止位置进行查询
![二分查找1](https://img-blog.csdnimg.cn/img_convert/b58d55a34b32a342f652792297a1e4d7.png)
> 2找出 mid该索引为 mid =left + right/ 2但是这样写有可能溢出所以我们需要改进一下写成
>
> mid = left +right - left/ 2 或者 left + ((right - left ) >> 1) 两者作用是一样的都是为了找到两指针的中
>
> 间索引使用位运算的速度更快那么此时的 mid = 0 + (8-0) / 2 = 4
![二分查找2](https://img-blog.csdnimg.cn/img_convert/5354d4c9ea5e5bd28a77a202b4dd3445.png)
> 3此时我们的 mid = 4nums[mid] = 6 < target,那么我们需要移动我们的 left 指针 left = mid + 1下次则可以在新的 left right 区间内搜索目标值下图为移动前和移动后
![](https://img-blog.csdnimg.cn/img_convert/97c584c48d6c1c06dffb94c6481f82c6.png)
> 4我们需要在 left right 之间计算 mid mid = 5 + 8 - 5/ 2 = 6 然后将 nums[mid] target 继续比较进而决定下次移动 left 指针还是 right 指针见下图
![二分查找3](https://img-blog.csdnimg.cn/img_convert/471b4093db0233e41d4875fc6b2e4359.png)
> 5我们发现 nums[mid] > target则需要移动我们的 right 指针 right = mid - 1则移动过后我们的 left right 会重合这里是我们的一个重点大家需要注意一下后面会对此做详细叙述
![二分查找4](https://img-blog.csdnimg.cn/img_convert/2145730bf3a6373f1cf60da628bf85e6.png)
> 6我们需要在 left right 之间继续计算 mid mid = 5 +5 - 5/ 2 = 5 见下图此时我们将 nums[mid] target 比较则发现两值相等返回 mid 即可 如果不相等则跳出循环返回 -1
![二分查找6](https://img-blog.csdnimg.cn/img_convert/0aba81887cfbc25ce9a859ba8137cd4a.png)
二分查找的执行过程如下
1.从已经排好序的数组或区间中取出中间位置的元素将其与我们的目标值进行比较判断是否相等如果相等
则返回
2.如果 nums[mid] target 不相等则对 nums[mid] target 值进行比较大小通过比较结果决定是从 mid
的左半部分还是右半部分继续搜索如果 target > nums[mid] 则右半区间继续进行搜索 left = mid + 1;
target < nums[mid] 则在左半区间继续进行搜索 right = mid -1
**动图解析**
![二分查找2](https://img-blog.csdnimg.cn/img_convert/eb648f86b4ada5b32afc7a52e78d9953.gif)
下面我们来看一下二分查找的代码可以认真思考一下 if 语句的条件每个都没有简写
```java
public static int binarySearch(int[] nums,int target,int left, int right) {
//这里需要注意,循环条件
while (left <= right) {
//这里需要注意计算mid
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
}else if (nums[mid] < target) {
//这里需要注意,移动左指针
left = mid + 1;
}else if (nums[mid] > target) {
//这里需要注意,移动右指针
right = mid - 1;
}
}
//没有找到该元素,返回 -1
return -1;
}
```
二分查找的思路及代码已经理解了那么我们来看一下实现时容易出错的地方
1.计算 mid 不能使用 left + right / 2,否则有可能会导致溢出
2.while (left < = right) { } 注意括号内为 left <= right ,而不是 left < right 我们继续回顾刚才的例子如果我们设置条件为 left < right 则当我们执行到最后一步时则我们的 left right 重叠时则会跳出循环返回 -1区间内不存在该元素但是不是这样的我们的 left right 此时指向的就是我们的目标元素 但是此时 left = right 跳出循环
3. left = mid + 1,right = mid - 1 而不是 left = mid right = mid我们思考一下这种情况,见下图当我们的 target 元素为 16 然后我们此时 left = 7 right = 8mid = left + (right - left) = 7 + (8-7) = 7那如果设置 left = mid 的话则会进入死循环mid 值一直为 7
![二分查找出差](https://img-blog.csdnimg.cn/img_convert/d7ff6aba9a1e9d673ae24667823d5770.png)
下面我们来看一下二分查找的递归写法
```java
public static int binarySearch(int[] nums,int target,int left, int right) {
if (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
//查找成功
return mid;
}else if (nums[mid] > target) {
//新的区间,左半区间
return binarySearch(nums,target,left,mid-1);
}else if (nums[mid] < target) {
//新的区间,右半区间
return binarySearch(nums,target,mid+1,right);
}
}
//不存在返回-1
return -1;
}
```