二分查找---你真的弄明白了吗

在wiki里,二分搜索算法的的解释如下:

电脑科学中,二分搜索(英语:binary search),也称折半搜索(英语:half-interval search)、对数搜索(英语:logarithmic search),是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。

 根据上一段我们就可以自己实现二分查找了,一般来说典型地二分查找有两种写法,一种是递归,一种是循环。下面以在一个升序排列的整数数组中查找某个特定的值来实现这两种算法,如果存在放回true,否则返回false。

一、循环实现:

class Solution {
    public boolean binarySearch(int[] nums, int target) {
        if(nums == null || nums.length == 0) {
            return false;
        }
        int lo = 0;
        int hi = nums.length - 1;
        while(lo <= hi) {
            int mid = (lo + hi) / 2;
            if(target > nums[mid]) {
                lo = mid + 1;
            } else if(target < nums[mid]) {
                hi = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
}

二、递归实现:

class Solution {
    public static boolean binarySearch(int[] nums, int target) {
        return binarySearchHelp(nums, 0, nums.length - 1, target);
    }
    private static boolean binarySearchHelp(int[] nums, int lo, int hi, int target) {
        if(lo > hi) return false;
        int mid = (lo + hi) / 2;
        return target > nums[mid] ? binarySearchHelp(nums, mid + 1, hi, target) :
                    target < nums[mid] ? binarySearchHelp(nums, lo, mid - 1, target) : true;
    }
}

上面只是最基础的二分查找,下面加大难度:

这次返回的不再是true or false 而是返回对应下标的索引,如果元素不存在的话,返回如果元素出现在该数组中对应的索引。例如数组【1,2,4,5,6,7】如果查找的目标是4返回对应的小标2,如果查找的目标是3也返回2,因为如果3出现在数组中的话,它的索引应该是2。

class Solution {
    public int binarySearch(int[] nums, int target) {
        if(nums == null) {
            return -1;
        }
        int lo = 0;
        int hi = nums.length - 1;
        while(lo <= hi) {
            int mid = (lo + hi) / 2;
            if(target > nums[mid]) {
                lo = mid + 1;
            } else if(target < nums[mid]) {
                hi = mid - 1;
            } else {
                return mid;
            }
        }
        return lo;  // 这里返回lo
    }
}

二分查找思想的精髓就是每次将待查找元素的整体分为两部分,根据某个条件剔除掉其中一部分,然后继续在剩余的一部分上迭代求解。这个条件可能不是向前面描述那样的大小关系了。我们看看下面几道题:

题目:一个连续有序整数数组,(a,a+1,a+2,a+3,….a+m,a+m,a+m+1,….) 大小为N,其中有一个重复的数字,编写函数实现返回重复数字。

这题使用线性搜索肯定是可以求解的,乍一看好像和二分查找没有关系。其实不然,如果我们每次将数组分成两半(左开右闭)根据对应位置数字的关系,我们是可以判断出重复数字在哪一个区间的。

例如【1,2,3,4,4,5,6,7,8,9,10】第一趟二分时:lo = 0, mid = 5, hi = 11,左半部分有5个元素,右半部分有6个元素。如果重复的元素不在左半部分的话,那么mid对应的值应该是 nums[lo] + 左边部分元素的数目 = 6,然而这里nums[mid] = 5,所以重复的元素肯定在左半部分。

public class FindDuplicationNum {
    private static int findDuplicationNum(int[] nums) {
        int lo = 0;
        int hi = nums.length;
        while(lo < hi) {
            int mid = (lo + hi) / 2;
            if(mid - lo != nums[mid] - nums[lo]) {  // 判断重复元素是否在左半部分
                hi = mid - 1;
            } else {
                lo = mid + 1;
            }
            if(mid > 0 && nums[mid] == nums[mid - 1] || mid < nums.length - 1 && nums[mid] == nums[mid + 1]) {  // 判断在mid位置处是否出现了重复元素。
                return nums[mid];
            }
        }
        throw new RuntimeException("Input Illegal");
    }


    public static void main(String[] args) {
        int[] nums = {1,2,3,4,5,6,7,7,8};
        System.out.println(findDuplicationNum(nums));
    }
}

未完待续。。。

猜你喜欢

转载自blog.csdn.net/qq_22158743/article/details/88659405