二分查找也叫折半查找,其用于在排好序的数组找到指定值,复杂度为logN。假定要在数组a[n]中寻找b,方法是先找到数组中间的值a[n/2],如果a[n/2]=b则找到,如果a[n/2]
1.没有重复值或有重复值时只需找到一个:
这种情况只需按上述描述写代码即可,方式有很多,可以递归或者非递归。下面是其中一种例子:
#include
#include
#include
int main() {
std::ios::sync_with_stdio(false);
int32_t a(0),b(0);
std::cin >> a;
std::cin >> b;
std::vector 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<
如果有重复值,且需要让我们得到最左/最右/全部值,那么上述方法我们只能找到一个,于是我们需要想到一种方法找到最左和最右值。找到全部值其实等价于找到最左值和最右值。
首先我们会想到找到一个值的时候向左和向右遍历,然而假定数组长这样:
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
#include
#include
int main() {
std::ios::sync_with_stdio(false);
int32_t a(0),b(0);
std::cin >> a;
std::cin >> b;
std::vector 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。