二分查找_day1

二分查找

一、二分查找算法介绍

「二分查找算法(Binary Search Algorithm)」:也叫做 「折半查找算法」「对数查找算法」。是一种在有序数组中查找某一特定元素的搜索算法。

基本算法思想:先确定待查找元素所在的区间范围,在逐步缩小范围,直到找到元素或找不到该元素为止。

二分查找算法的过程如下所示:

  1. 每次查找时从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;
  2. 如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。
  3. 如果在某一步骤数组为空,则代表找不到。

举个例子来说,给定一个有序数组 [0, 1, 2, 3, 4, 5, 6, 7, 8]。如果我们希望查找 5 是否在这个数组中。

  1. 第一次区间为整个数组 [0, 1, 2, 3, 4, 5, 6, 7, 8],中位数是 4,因为 4 小于 5,所以如果 5 存在在这个数组中,那么 5 一定在 4 右边的这一半区间中。于是我们的查找范围变成了 [4, 5, 6, 7, 8]
  2. 第二次区间为 [4, 5, 6, 7, 8],中位数是 6,因为 5 小于 6,所以如果 5 存在在这个数组中,那么 5 一定在 6 左边的这一半区间中。于是我们的查找范围变成了 [4, 5, 6]
  3. 第三次区间为 [4, 5, 6],中位数是 5,正好是我们需要查找的数字。

于是我们发现,对于一个长度为 9 的有序数组,我们只进行了 3 次查找就找到了我们需要查找的数字。而如果是按顺序依次遍历数组,则最坏情况下,我们需要查找 9 次。

二分查找过程的示意图如下所示:

二分查找_day1_第1张图片

二、二分查找算法思想

二分查找算法是经典的 「减而治之」 的思想。

这里的 「减」 是减少问题规模的意思,「治」 是解决问题的意思。「减」「治」 结合起来的意思就是 「排除法解决问题」。即:每一次查找,排除掉一定不存在目标元素的区间,在剩下可能存在目标元素的区间中继续查找。

每一次通过一些条件判断,将待搜索的区间逐渐缩小,以达到「减少问题规模」的目的。而于问题的规模是有限的,经过有限次的查找,最终会查找到目标元素或者查找失败。

三、二分查找的步骤

1.首先,明确区间的定义非常重要。在二分查找算法中,通常将数组的第一个元素视为区间左边界,最后一个元素视为区间右边界。因此,区间的定义有以下两种常见的方式:

  • 左闭右闭区间:即包含数组第一个元素和最后一个元素,例如数组[1, 2, 3, 4, 5]的区间定义为[1, 5]
  • 左闭右开区间:即包含数组第一个元素但不包含最后一个元素,例如数组[1, 2, 3, 4, 5]的区间定义为[1, 5)

​ 无论是哪种区间定义,都需要先明确区间的边界,以便在算法中正确地处理边界条件。

2.对于区间的不一样的定义会对我们的算法产生不一样的影响,因此我们要根据区间的定义去做一个一个理性化的分析。例如,对于左闭右闭区间,当leftright都等于数组的第一个元素时,我们仍然可以进行二分查找,因为此时区间[1, 1]仍然包含数组的第一个元素。但是,当left等于数组的第一个元素,而right等于数组的最后一个元素时,区间[1, 5]就不再包含数组的最后一个元素,此时我们就无法进行二分查找了。

3.坚持循环不变量。循环不变量是指在循环过程中,始终保持不变的东西。在这里我们的“不变量”是区间的定义,我们在每一个更新边界条件时候,都要保持循环“不变量”。在整个循环过程中,我们始终保证leftright的值满足循环不变量,这样我们才能正确地完成二分查找算法。

  • 对于左闭右闭区间,初始时left等于0,right等于数组长度减1,即[0, n-1]
  • 对于左闭右开区间,初始时left等于0,right等于数组长度,即[0, n)
四、二分查找模板

对于整数二分有两种模板,分别是 LBS,和 RBS

// 检查x是否满足某种性质  
private static boolean check(int x) {  
   /* ... */  
}  

// 区间[left, right]被划分成[left, mid]和[mid + 1, right]时使用: 
// 或者称之为左二分查询,查找左侧第一个满足条件的数
private static int leftBinarySearch(int[] arr, int left, int right) {  
   while (left < right) {  
      int mid = arr[left + right >> 1];  
      if (check(mid)) {  
         right = mid;    // check()判断mid是否满足性质  
      } else {  
         left = mid + 1;  
      }  
   }  
   return left;  
}  

// 区间[left, right]被划分成[left, mid - 1]和[mid, right]时使用:  
// 或者称之为右二分查询,查找右侧最后一个满足条件的数
private static int rightBinarySearch(int[] arr, int left, int right) {  
   while (left < right) {  
      int mid = arr[left + right + 1 >> 1];  
      if (check(mid)) {  
         left = mid;    // check()判断mid是否满足性质  
      } else {  
         right = mid - 1;  // 有加必有减
      }  
   }  
   return left;  
}
五、力扣704题目演练

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。

代码题解

//左闭右闭区间

public class Solution {
 public int search(int[] nums, int target) {
     int left = 0;
     int right = nums.length - 1;

     while (left < right) {
         int mid = left + ((right - left) >> 1);
         //之所以这样定义 mid 是因为防止,数组中间值正好为 0 时候,mid 不会自动更新
         //使用位运算的目的是,位运算是底层运算,防止溢出

         if (nums[mid] == target) {
             right = mid;
         } else if (nums[mid] < target) {
             left = mid + 1;
         } else {
             right = mid - 1;
         }
     }

     return nums[left] == target ? left : -1;
   }
}
//左闭右闭区间

public class Solution {
 public int search(int[] nums, int target) {
     int left = 0;
     int right = nums.length - 1;

     while (left < right) {
         int mid = left + (right - left) / 2;
         //之所以这样定义 mid 是因为防止,数组中间值正好为 0 时候,mid 不会自动更新
         //使用位运算的目的是,位运算是底层运算,防止溢出

         if (nums[mid] == target) {
             right = mid;
         } else if (nums[mid] < target) {
             left = mid + 1;
         } else {
             right = mid ;
         }
     }

     return nums[left] == target ? left : -1;
   }
}

你可能感兴趣的:(算法学习,java,算法,数据结构)