数据结构与算法系列十九(二分查找2)

179 阅读5分钟

上一篇:数据结构与算法系列十八(二分查找1) 中,我们分享了二分查找算法的思想,并且通过循环递归方式都实现了一遍。二分查找的思想非常容易理解(折半查找),相信你都已经掌握了。

但是你还记得吗?我们在上一篇留下了两个思考题:

  • 二分查找的代码实现,需要注意的细节事项 (如何写出高效,且没有问题的二分查找代码)

  • 如果待查找的目标数组中,有重复的数字,二分查找该如何实现呢?

    • 比如:查找第一个值等于目标值的元素
    • 比如:查找最后一个值等于目标值的元素
    • 比如:查找第一个大于等于目标值的元素
    • 比如:查找最后一个小于等于目标值的元素
#考考你:
1.你知道如何写出高效、且没有问题的二分查找代码吗?
2.你能写出二分查找的常见变形代码吗(有重复元素)?

案例

二分查找代码实现细节

#二分查找代码实现,需要关心的细节:
1.中位数计算问题
【问题引导】:
	1.我们知道,二分查找涉及到三个数值的变化
	2.低位:low,从0开始
	3.高位:high,从n-1开始(n是数组元素个数)
	4.中位数:mid,常规写法:mid = (low + high) / 2
【问题】:
	mid = (low + high) / 2 写法有问题吗?

【解答】:
	1.有问题,假设low,high数值足够大,那么low + high有可能超出int
	的最大范围,会有溢出的风险
	2.推荐写法:mid = low + (high - low) / 2
	3.更高效的写法:mid = low + ((high - low) >> 1)。移位操作对计算机更友好,性能更高

2.下一次查找,低位、高位数变换
【问题引导】:
	1.如果本次没有找到目标值,则需要进入下一次二分查找
	2.根据需要变更低位值:low,或者高位值:high,进行折半操作
	3.你是否会习惯性的写成:low = mid;或者high = mid
	
【问题】:
	请问low = mid;或者high = mid有问题吗?
	
【解答】:
	1.有问题,low = mid,或者high = mid会引起死循环的问题
	2.正确写法:low = mid + 1;或者high = mid - 1

二分查找变形(重复元素)

查找第一个值等于目标值的元素

/**
* 查找第一个值等于目标值的元素
* @param array  目标数组
* @param n       目标数组大小
* @param target  目标值
* @return   目标值索引
*/
public static int binarySearchFirst(int[] array,int n,int target){
  // 低位、高位
  int low = 0;
  int high = n - 1;

  while(low <= high){
     // 计算中位数
     int mid = low + ((high - low) >> 1);

     if(array[mid] > target){
          high = mid - 1;
     }else if(array[mid] < target){
          low = mid + 1;
     }else{
           // 如果mid等于0,说明第一个元素就是目标值
           // 如果mid不等于0,但是array[mid-1] 不等于目标值
           // 说明此时mid也是要找的目标位置
           if(mid == 0 ||
               array[mid - 1] != target){
                 return mid;
            }

            // 否则,继续查找
            high = mid - 1;
          }
       }

       return -1;
 }

查找最后一个值等于目标值的元素

/**
* 查找最后一个值等于目标值的元素
* @param array  目标数组
* @param n       目标数组大小
* @param target  目标值
* @return   目标值索引
*/
public static int binarySearchLast(int[] array,int n,int target){
   // 低位、高位
   int low = 0;
   int high = n - 1;

   while(low <= high){
      // 计算中位数
      int mid = low + ((high - low) >> 1);

      if(array[mid] > target){
             high = mid - 1;
       }else if(array[mid] < target){
             low = mid + 1;
       }else{
            // 如果mid等于n-1,说明最后一个元素就是目标值
            // 如果mid不等于n-1,但是array[mid+1] 不等于目标值
            // 说明此时mid也是要找的目标位置
            if(mid == (n-1) ||
                array[mid + 1] != target){
                 return mid;
            }

            // 否则,继续查找
            low = mid + 1;
          }
     }

       return -1;
    }

查找第一个值大于等于目标值的元素

/**
* 查找第一个值大于等于目标值的元素
* @param array  目标数组
* @param n       目标数组大小
* @param target  目标值
* @return   目标值索引
*/
public static int binarySearchFirst_1(int[] array,int n,int target){
  // 低位、高位
  int low = 0;
  int high = n - 1;

  while(low <= high){
    // 计算中位数
    int mid = low + ((high - low) >> 1);

    if(array[mid] < target){
           low = mid + 1;
    }else {
       // 如果mid==0,则第一个元素就是查找的目标位置
       // 如果mid!=0,如果array[mid-1] < target,则mid就是要查找的目标位置
       // 否则,继续查找
       if(mid == 0 ||
           array[mid - 1] < target){
         return mid;
        }

         high = mid - 1;
       }
    }

        return -1;
    }

查找最后一个值小于等于目标值的元素

/**
* 查找最后一个值小于等于目标值的元素
* @param array  目标数组
* @param n       目标数组大小
* @param target  目标值
* @return   目标值索引
*/
public static int binarySearchLast_1(int[] array,int n,int target){
  // 低位、高位
  int low = 0;
  int high = n - 1;

  while(low <= high){
    // 计算中位数
    int mid = low + ((high - low) >> 1);

    if(array[mid] > target){
         high = mid - 1;
    }else{
         // 如果mid==n-1,则最后一个元素,是查找的目标位置
         // 如果min!=n-1,如果array[mid+1] > target,则mid是查找的目标位置
         // 否则,继续查找
         if(mid == (n - 1) ||
              array[mid + 1] > target){
             return mid;
          }

             low = mid + 1;
     }
   }

    return -1;
}

测试代码

public static void main(String[] args) {
   // 1.查找第一个值等于目标值的元素
   int[] array_1 = {2,4,5,6,7,9,9,9,10};
   System.out.println("目标数组:" + Arrays.toString(array_1));

   int target = 8;
   int index_1 = binarySearchFirst(array_1,array_1.length,target);
   System.out.println("1.查找【第一个值等于】目标值:" + target+ " 的元素:" + index_1);

    // 2.查找最后一个值等于目标值的元素
    System.out.println("----------------华丽丽分割线------------------");
    int index_2 = binarySearchLast(array_1,array_1.length,target);
    System.out.println("2.查找【最后一个值等于】目标值:" + target + " 的元素:" + index_2);

    // 3.查找第一个大于等于目标值的元素
    System.out.println("----------------华丽丽分割线------------------");
    int index_3 = binarySearchFirst_1(array_1,array_1.length,target);
    System.out.println("3.查找【第一个大于等于】目标值:" + target + " 的元素:" + index_3);

    // 4.查找最后一个小于等于目标值的元素
   System.out.println("----------------华丽丽分割线-------------------");
   int index_4 = binarySearchLast_1(array_1,array_1.length,target);
   System.out.println("4.查找【最后一个值小于等于】目标值: " + target+ " 的元素:" + index_4);

}

image.png