二分查找及其变种大全详解

1.经典二分查找

经典的二分查找,是对一个已排序的数组,通过查找数组中间位置元素值的大小与要查找的目标值大小进行对比,从而缩小数组的搜素范围,直到最后找到目标值出现的位置,或者将数组查找完毕一直没有找到目标值直至算法结束。

其基本的算法框架为(c++版本)

int binaray_search(int[] nums, int n, int target) {
	int left = ..., right = ..., mid;
	while(...) {
		int mid = (left+right)/2;
		if (nums[mid] == target) {
			...
		} else if (nums[mid] > target) {
			right = ...
		} else if (nums[mid] < target) {
			left = ...
		}
	}
}

二分查找的基本版本以及其他各种变种,就是针对上面的框架进行相应修改。

2.经典版本二分查找

首先我们实现经典的二分查找。

int classical_version(int nums[], int n, int target) {
    int left=0, right=n-1, mid;
    while(left<=right) {
        mid = (left+right) / 2;
        if (nums[mid] > target) right=mid-1;
        else if (nums[mid] < target) left=mid+1;
        else return mid;
    }
    return -1;
}

其中,n是数组nums的长度,target是需要查找的目标数值。
注意循环退出的条件是left<=right,等号不能丢。因为如果退出条件为left<right,那么当left=right时,循环就退出了,这样将少查一个元素。如果该元素刚好为target,程序将会返回错误的结果。

3.查找target出现的第一个位置

上面的经典算法,适合数组中没有重复元素的情况。如果数组中有重复元素,而且需要查找的target刚好是重复元素,那么找到的只是其中一个元素。如果要查找指定的第一个或者最后一个出现位置,上面的写法是搞不定的。
下面我们来实现查找target出现的第一个位置。

int find_first_target(int nums[], int n, int target) {
    int left=0, right=n-1, mid;
    while(left<=right) {
        mid = (left+right)/2;
        if (nums[mid] > target) right=mid-1;
        else if (nums[mid] < target) left=mid+1;
        else {
            if (mid == 0 || nums[mid-1] != target) return mid;
            else right=mid-1;
        }
    }
    return -1;
}

注意与经典版本不一样的地方在于,经典版本在nums[mid] == target时就支持返回退出了。而如果数组中有重复元素,需要进一步进行判断。因为是要找到出现的第一个位置,所以搜素范围需要向左移动。我们将mid-1个元素与target进行对比,如果不相等,直接返回。如果相等,则将right向左移动一个位置。

4.查找target出现的最后一个位置

很显然思路与上面类似,区别就是查找第一个位置,当nums[mid]==target搜索范围向左移动,而查找最后一个位置则是向右移动。

int find_last_target(int nums[], int n, int target) {
    int left=0, right=n-1, mid;
    while(left<=right) {
        mid = (left+right)/2;
        if (nums[mid] > target) right=mid-1;
        else if (nums[mid] < target) left=mid+1;
        else {
            if (mid==right || nums[mid+1] != target) return mid;
            else left = mid+1;
        }
    }
    return -1;
}

5.查找第一个大于等于target的元素

有时候我们还需要查找第一个大于等于target的元素。先上代码,然后再针对代码分析。

int find_first_euqal_big_element(int nums[], int n, int target) {
    int left=0, right=n-1, mid;
    while(left <= right) {
        mid = (left+right) / 2;
        if(nums[mid] < target) left=mid+1;
        else {
            if (mid == 0 || nums[mid-1] < target) return mid;
            else right=mid-1;
        }
    }
    return -1;
}

上面算法几个要点:
1.因为需要找的是大于等于的第一个元素,这个元素必然出现在right一侧,left一侧无需做特殊处理,只需要跟原始版本一样即可。
2.如果mid==0且nums[mid]>=target,直接返回mid。
3.如果nums[mid]>=target,且nums[mid-1]<target,说明mid则是第一个大于等于target值的位置。否则,right侧向左移动。

6. 查找最后一个小于等于target的元素

int find_last_equal_small_element(int nums[], int n, int target) {
    int left=0, right=n-1, mid;
    while(left <= right) {
        mid = (left+right)/2;
        if(nums[mid] > target) right=mid-1;
        else {
            if (mid==n-1 || nums[mid+1] > target) return mid;
            else left=mid+1;
        }
    }
    return -1;
}

与第五部分类似的是:
1.因为查找的是小于等于target的元素,所以必然是出现在left侧,right一侧无需特殊处理。
2.如果mid==n-1且nums[mid]<=target,直接返回mid。
3.如果nums[mid]<=target,且nums[mid+1]>target,说明mid则是第一个小于等于target值的位置。否则,left侧向右移动。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值