JAVA算法:二分查找

二分查找(Binary Search)

简介:

二分查找也称折半查找,它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
二分查找充分利用了元素间的次序关系,采用分冶策略,可在最坏的情况下用O(logn)完成搜索任务。

原理:

1. 如果待查序列为空,那么就返回-1,并退出算法;这表示查找不到目标元素。  
2. 如果待查询序列不为空,则将它的中间元素与查找的目标元素进行匹配,看它们是否相等。  
3. 如果相等,则返回该中间元素的索引,并退出算法;此时就查找成功。  
4. 如果不相等,就再比较这两个元素的大小。  
5. 如果该中间元素大于目标元素,那么就将当前序列的前半部分作为新的待查序列。  
6. 如果该中间元素小于目标元素,那么就将当前序列的后半部分作为新的待查序列。  
二分之查找之所有快速,是因为它在匹配不成功的时候,每次都能排除剩余元素中一半的元素。因此可能包含目标元素的有效范围就收缩得很块,而不像顺序查找那样,每次仅能排除一个元素。  

二分查找原理摘自博客:https://blog.csdn.net/m0_46937429/article/details/114947535

简单来说二分查找类似于小时候玩的猜数字游戏:
1~99整数之间随意取一个正确答案,你来猜这个数,我来告诉你比正确答案大还是小。  
为了最快找到正确答案一般来说我们会取中位数50作为第一次比较的数;  
如果比50大,我们会取51~99的中位数75来做第二次比较;如果比50小,我们会取1~49的中位数25来做第二次比较;依此类推,直到找到正确答案。    

了解完二分查找原理,我们来实践一下:

来源:力扣(leetCode)第35题:搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例:

示例 1:
输入: [1,3,5,6], 5
输出: 2

示例2:
输入: [1,3,5,6], 2
输出: 1

示例 3:
输入: [1,3,5,6], 7
输出: 4

示例 4:
输入: [1,3,5,6], 0
输出: 0

投机写了个简单的解题方案:

private static int searchInsert(int[] nums, int target) {
    int i = 0;
    for (int num : nums) {
        if (target == num || target < num) {
            return i;
        }
        i++;
    }
    return i;
}

测试:

public static void main(String[] args) {
        int[] nums = new int[]{1,3,5,6};
        System.out.println("target = 5  result:" + searchInsert(nums, 5));
        System.out.println("target = 5  result:" + searchInsert(nums, 2));
        System.out.println("target = 5  result:" + searchInsert(nums, 7));
        System.out.println("target = 5  result:" + searchInsert(nums, 0));
    }

输出:

target = 5  result:2
target = 5  result:1
target = 5  result:4
target = 5  result:0

与预期结果一致^^,在leetCode上也是通过了所有测试用例,执行用时: 0 ms 内存消耗: 38.2 MB。

可是本题标签是二分查找,需要以二分查找算法来解题:

private static int binarySearchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        //处理左右边界
        if (target < nums[left]) {
            return 0;
        }
        if (target > nums[right]) {
            return nums.length;
        }
        //二分查找
        while (left <= right) {
            int mid = left + (right - left) / 2;
            //目标值在数组中,直接返回下标
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                //目标值在 nums[mid] 右边时判断是否比下标 mid 的后一位小
                if (target < nums[mid + 1]) {
                    //满足 则说明该值在下标 mid 与 mid + 1 之间 直接返回下标mid + 1
                    return mid + 1;
                }
                left = mid + 1;
            } else if (nums[mid] > target) {
                //目标值在 nums[mid] 左边时判断是否比下标 mid 的前一位大
                if (target > nums[mid - 1]) {
                    //满足 则说明该值在下标 mid - 1 与 mid 之间 直接返回下标mid(其实应该是 mid-1+1 的  也就是mid了)
                    return mid;
                }
                right = mid - 1;
            }
        }
        return -1;
    }
大概写一下解题思路:
1. 首先要处理左右边界情况,也就是 target 小于 nums 最小值或大于最大值的情况
2. 使用二分查找法来找 target 在 nums 中的下标,如果找到则直接返回数组下标
3. 如果 target 在 nums[mid] 右边时,判断 target 是否比下标为 mid 的后一位小;
    满足:则说明该值在下标 mid 与 mid + 1 之间 直接返回下标 mid + 1;
    不满足:则 left = mid + 1 继续查找
4. 如果 target 在 nums[mid] 左边时,判断 target 是否比下标为 mid 的前一位大
    满足: 则说明该值在下标 mid - 1 与 mid 之间 直接返回下标 mid(其实应该是 mid-1+1 的  也就是mid了)
    不满足:则 right = mid - 1 继续查找

二分查找中 mid 值的计算方法

建议使用这种写法:mid = left + (right - left) / 2
对于mid = (right + left) / 2 写法来说,由于left+right很容易超过int范围,所以容易溢出。

你可能感兴趣的:(数据结构及算法,算法,java,leetcode,二分查找)