二分查找新的方法

目录

前言

二分查找定义

二分查找思路

代码实现(一般的版本)

模板1

模板2

模板3

新的方法

代码模板

做题思路和细节

关于新方法的常见问题

一,会不会陷入死循环?

二,这个方法能不能遍历所有元素

一道例题


前言

        相信大家都已经对二分查找的思路很了解。但是知道怎么做不等于能做出来,尤其是将思路转化为代码时往往会遇到各种细节上的问题。在初学二分查找时尤为明显。本章将介绍二分查找的基本思路及不容易记错记混的二分查找模板。

二分查找定义

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

        二分查找算法在最坏情况下是对数时间复杂度的,需要进行O(log N)次比较操作。二分查找算法使用常数空间,对于任何大小的输入数据,算法使用的空间都是一样的。除非输入数据数量很少,否则二分查找算法比线性搜索更快,但数组必须事先被排序。尽管一些特定的、为了快速搜索而设计的数据结构更有效(比如哈希表),二分查找算法应用面更广。

        二分查找算法有许多种变种。比如分散层叠可以提升在多个数组中对同一个数值的搜索的速度。分散层叠有效的解决了计算几何学和其他领域的许多搜索问题。指数搜索将二分查找算法拓宽到无边界的列表。二叉搜索树B树数据结构就是基于二分查找算法的。

————copy自维基百科

二分查找思路

        二分搜索只对有序数组有效。二分搜索先比较数组中位元素和目标值。如果目标值与中位元素相等,则返回其在数组中的位置;如果目标值小于中位元素,则搜索继续在前半部分的数组中进行。如果目标值大于中位元素,则搜索继续在数组上部分进行。由此,算法每次排除掉至少一半的待查数组。

代码实现(一般的版本)

        二分查找的代码实现一般有很多种,这里举例一部分:

模板1

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

模板2

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

模板3

int search(int nums[], int size, int target) {
    int left = 0;
    int right = size - 1;
    while (left < right)
    {
        int mid = left + right + 1 >> 1;	
        if (num[mid]>target) {
            left = mid;
        }
        else if(num[mid]<target){
            right = mid - 1;
        }
        else if(num[mid]==target){
        return mid;
    }
    return -1;
}

        具体可以参考以下文章:

【二分查找】详细图解_二分查找法流程图-优快云博客

二分查找 & 二分答案 万字详解,超多例题,带你学透二分。_c++二分答案怎么确定是l<r还是l<=r-优快云博客

新的方法

        我们可以看一下之前的代码,不难发现,上述代码while跳出循环条件,middle的取值,left等于middle+/-1,right等于middle+/-1都不一样。他们都有一个共同点,那就是:都是对二分循环体根据题目条件的不同而改变。但是或许我们可以换一个思路:不对循环体改变,而是改变check(middle)函数以及输出的数值,这样我们的循环体就可以不用改变了,也方便好记。

代码模板

bool check(int x) {
	if (condition) return 1;
	else return 0;
}
int find(int* arr, int left, int right) {
	left--;
	right++;
	while (right - left > 1) {
		int middle = left + right >> 1;
		if (check(middle)) left = middle;
		else right = middle;
	}
	return left / right;
}

        注意到,循环体是无论什么题都可以套用这个模板的,关键是要改变check函数和return的值。

做题思路和细节

        第一步,将我们的数组分为target及左侧,和target右侧两部分,为了方便讲述,我们分别标成1(满足)和0(不满足)。我们要找到合适的check函数,是的target及左边均为1,右边均为0。L和R一开时在外面是因为数组有可能是全为1或者全为0的。需要注意,无论什么时候:L都在1区,R都在0区

一开始
L,R位置LMR
是否满足11111...1(tar)0...00
下标-10123... target...n-2n-1n

        第二步,如果check(mid)为1,证明M左侧的下标都满足,于是我们可以把M换成L,反之换成R

移动后
L,R位置LR
是否满足11111...1(tar)0...00
下标-10123... target...n-2n-1n

        重复第二步的过程,直到L在R旁边,跳出循环

 

L,R位置L

R

是否满足1111000
下标-101...targettarget+1...n-1n

        注意到L始终在满足区,R始终在不满足区,故跳出循环时,L必为target,R必为target+1,接下来根据题目需要输出L或者R。

关于新方法的常见问题

一,会不会陷入死循环?

        事实上是不会的。因为mid=(left+right)/2。我们可以证明:当right-left>=2时,经过mid=left或mid=right操作后,right-left会严格单调递减。而当right-left==1时不满足循环条件,会跳出循环。

二,这个方法能不能遍历所有元素

        答案是能,因为无论何时,L左边的元素都是满足的,及已被遍历的,而R右边的元素同理。当跳出循环时,L就在R左边,L和R之间已经没有其他元素了,故能遍历所有元素。

一道例题

可能光说理论大家无法理解,现在结合一道例题方便大家理解:

题目描述

在一个单调递增的数组中,有一个target,你需要返回target的下标。输入共两行,第一行为数组大小和target。第二行为数组。输出共一行,及target的下标。数据范围:N<10000。

思路

首先,我们要定义check,我们令check为:当小于等于target输出1,其余情况则输出0。这样target也在1区,我们最后输出时应该输出L。接下来就是套用模板了。

代码

#include<iostream>
const int N = 10010;
int arr[N];
int n,t;
bool check(int mid) {
	if (arr[mid] <= t) {
		return 1;
	}
	return 0;
}
int find(){
	int l = -1;
	int r = n;
	while (r - l > 1) {
		int mid = r + l >> 1;
		if (check(mid)) l=mid;
		else r=mid;
	}
	return l;
}
int main() {
	std::cin >> n;
	std::cin >> t;
	for (int i = 0; i < n; i++) std::cin >> arr[i];
	std::cout << find();
}

提交结果:

输入 8 4
     1 1 1 4 6 7 8 9
输出 3

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值