C++二分查找

        二分查找也叫折半查找,其用于在排好序的数组找到指定值,复杂度为logN。假定要在数组a[n]中寻找b,方法是先找到数组中间的值a[n/2],如果a[n/2]=b则找到,如果a[n/2]<b则在a[n/2]的右边查找,反之则在左边。

1.没有重复值或有重复值时只需找到一个:

        这种情况只需按上述描述写代码即可,方式有很多,可以递归或者非递归。下面是其中一种例子:

#include<vector>
#include<algorithm>
#include<iostream>

int main() {
	std::ios::sync_with_stdio(false);
	int32_t a(0),b(0);
	std::cin >> a;
	std::cin >> b;
	std::vector<int32_t> array(a, 0);
    //此处使用for循环也有一样的效果,为了节省代码行数故如此写
    //lamba表达式的参数需要址传递或者引用传递,否则cin的结果会传到实参
	std::for_each(array.begin(), array.end(), [](int32_t& value) {std::cin >> value; });
    
    if ((array[0] > b) || (array[array.size() - 1] < b)) {
        //如果数组没有目标值则进行相应处理,比如输出error等,此处不输出直接return
		return 0;
	}

	int32_t head(0);
	int32_t tail(array.size() - 1);
	int32_t mid(0);
	while (head!=tail) {
        //假定此时head=1,tail=2,那么mid将等于1,因此比较后如果array[mid] < b
        //那么head必须等于mid+1,否则head,tail和mid将不变,进入死循环。
        //如果array[mid] > b那么tail可以等于mid,不会死循环。但是为了相对快一些
        //使tail = mid - 1,这样每次循环可以多排除一个
		mid = (head + tail) / 2;
		if (array[mid] > b) {
			tail = mid - 1;
			continue;
		}

		if (array[mid] < b) {
			head = mid + 1;
			continue;
		}

		if (array[mid] == b) {
            std::cout<<mid;
			return 0;
		}
	}
    //此时必须输出head或tail,假定最后一次循环head=1,tail=2,int型的mid=(1+2)/2=1。此时假定                    
    //array[mid] < b,head=1+1=2。那么此时正确的下标是2,而mid=1。
	std::cout << head;
	return 0;
}

2.有重复值的二分查找:

        如果有重复值,且需要让我们得到最左/最右/全部值,那么上述方法我们只能找到一个,于是我们需要想到一种方法找到最左和最右值。找到全部值其实等价于找到最左值和最右值。

        首先我们会想到找到一个值的时候向左和向右遍历,然而假定数组长这样:

        1,2,...n个2...,3

        那么此时我们想找2的最左值或者最右值,直接遍历会使复杂度退化为n,因此这种方法不可以。

        实际上我们在上面的问题中只需要将判定条件改一下,想找最左值就array[mid] > b改成array[mid] >= b,想找最右值就array[mid] < b改成array[mid] <= b。原理是找最左值时我们找到目标值还要向左查找,找最右值时找到目标值要向右查找。不过写代码时还要注意一些细节。

        找最左值:

//while循环外内容不变
while (head!=tail) {
	mid = (head + tail) / 2;
	if (array[mid] >= b) {
        //假如此时mid已经是最左值,那么tail=mid-1会导致越过最左值,得到错误结果
		tail = mid;
		continue;
	}

	if (array[mid] < b) {
		head = mid + 1;
		continue;
    }

}

        可以看出找最左值还是比较容易,接下来看找最右值:

//这是错误写法
while (head!=tail) {
	mid = (head + tail) / 2;
	if (array[mid] > b) {
		tail = mid - 1;
		continue;
	}

	if (array[mid] <= b) {
		head = mid;
		continue;
	}
}

        因为head=mid+1会越过边界值,所以我们采用这种写法,然而这回出问题。我们在无重复值的代码里提到过,当head=1,tail=2,那么mid将等于1,因此比较后如果array[mid] < b那么head必须等于mid+1,否则head,tail和mid将不变,进入死循环。在这里也一样适用。因此此种方法不行。

        解决的方法可能有很多,我想到的是不寻找最右值,改为寻找最右值右边的值,这样就不用担心head=mid+1会越过边界值。代码如下:

while (head!=tail) {
	mid = (head + tail) / 2;
	if (array[mid] > b) {
		tail = mid;
		continue;
	}

	if (array[mid] <= b) {
		head = mid + 1;
		continue;
	}
}
//需要判断array[head]==b
std::cout << head - 1;

        这样我们就得到了最右值。然而还有可能数组最右边就是b,那么此时head和tail指向的不是最右值的右边,而是最右值,因此我们需要判断一下,不过最好的方式是在循环前就处理好。完整代码如下:

#include<vector>
#include<algorithm>
#include<iostream>

int main() {
	std::ios::sync_with_stdio(false);
	int32_t a(0),b(0);
	std::cin >> a;
	std::cin >> b;
	std::vector<int32_t> array(a, 0);
	std::for_each(array.begin(), array.end(), [](int32_t& value) {std::cin >> value; });

    if ((array[0] > b) || (array[array.size() - 1] < b)) {
        //如果数组没有目标值则进行相应处理,比如输出error等,此处不输出直接return
		return 0;
	}

	if (array[array.size() - 1] == b) {
		std::cout << array.size() - 1;
		return 0;
	}

	int32_t head(0);
	int32_t tail(array.size() - 1);
	int32_t mid(0);
	while (head!=tail) {
		mid = (head + tail) / 2;
		if (array[mid] > b) {
			tail = mid;
			continue;
		}

		if (array[mid] <= b) {
			head = mid + 1;
			continue;
		}
	}
	std::cout << head - 1;
	return 0;
}

        找到最右值需要注意的细节相对较多。

        如果要同时找到最左值和最右值就可以开两个循环,先找最左再找最右,复杂度为2logN,根据省去常数的原则还是logN。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值