一、前言
大多数人在小时候应该都玩过数字扫雷。比如在1-100中任意选择一个数字作为雷,比如82,第一个人从50开始查找,裁判会说“数字小了”,第二个人就得说一个50-100的数字,比如75,裁判宣布“太小了”。第三个人从75-100选择一个数,90,“太大了”,第四个人在75-90之中选择一个数字88,“太大了”。第五个人75-88选择80,“太小了”,直到第X人猜测的数字等于那个数字,裁判就宣布“第X个人踩雷了”。这就是一个简单的二分查找。
二、正文
(一)什么是二分查找?
二分查找(Binary Search),又叫折半查找,顾名思义就是每一次折掉一半进行查找
线性查找(Linear Search),这种查找在以前我们经常用
二分查找只能在有顺序、可随机访问的容器中使用,set
容器和map
容器就被淘汰了。虽然功能受限,但时间复杂度低,只有
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)
线性查找适用于所有容器,但时间复杂度高, O ( n ) O(n) O(n)
(二)二分查找如何查找?
获得查找数组 A A A,数组项数 n n n,查找内容 x x x( A A A数组从第一项开始存数,且保证数组已经从小到大排完了序)
- 首先定义两个变量 l e f t = 1 , r i g h t = n left=1, right=n left=1,right=n
- 在 l e f t ≤ r i g h t left ≤ right left≤right的情况下执行下面代码:
- 获得中间位置 m i d = ( l e f t + r i g h t ) ÷ 2 mid=(left+right) \div 2 mid=(left+right)÷2
- 如果中心位置的值 A m i d > x A_{mid} > x Amid>x,说明太大了,就得减小: l e f t = m i d − 1 left = mid-1 left=mid−1
- 如果中心位置的值 A m i d < x A_{mid} < x Amid<x,说明太小了,就得增大: r i g h t = m i d + 1 right = mid+1 right=mid+1
- 如果中心位置的值 A m i d = x A_{mid} = x Amid=x,说明查找到了,就输出: m i d mid mid,并结束程序
- 重复3-6步骤,直到退出循环或找到数字
- 如果退出了
while
循环,说明找不到 x x x(结束程序除外)
代码是这样的
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int a[n+1];
for(int i=1; i<=n; i++)
cin >> a[i];
int l=1, r=n, x;
cin >> x;
while(l<=r) {
int m = (l+r) >> 1;
//在C++中一个数右移n位 = 那个数除以2的n次方
//同样,一个数左移n位 = 那个数乘2的n次方
if(a[m] > x) //太大了
r = m - 1; //缩小
if(a[m] < x) //太小了
l = m + 1; //增大
if(a[m] == x) { //猜对了
cout << m;
return 0;
}
}
cout << -1;
return 0;
}
(三)时间复杂度
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)
为什么呢?要知道
l
o
g
2
n
log_2n
log2n和
2
n
2^n
2n两个有着互逆的关系
n n n | 2 n 2^n 2n |
---|---|
0 0 0 | 1 1 1 |
1 1 1 | 2 2 2 |
2 2 2 | 4 4 4 |
3 3 3 | 8 8 8 |
4 4 4 | 16 16 16 |
5 5 5 | 32 32 32 |
… |
你会发现 2 n = 2 n − 1 × 2 2^n = 2^{n-1} \times 2 2n=2n−1×2,刚好是折半的关系,而 l o g 2 log_2 log2又互逆,所以时间复杂度就是 O ( l o g 2 n ) O(log_2n) O(log2n)了
(四)相关函数
位于#include <algorithm>
库中
binary_search(begit, endit, num)
:第一项和第二项填起始和结尾的指针,第三项填要查找的数,返回类型bool
,如果数组中确实存在数字num,那么返回true
,否则返回false
lower_bound(begit, endit, num)
:一样的参数,返回类型指针,返回第一个大于等于num的值的指针,减去数组起始指针后等于所在项数upper_bound(begit, endit, num)
:一样的参数,返回类型指针,返回第一个大于num的值的指针,减去数组起始指针后等于所在项数
因此使用函数可以这么写
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n;
int a[n+1];
for(int i=1; i<=n; i++)
cin >> a[i];
int x;
cin >> x;
if(binary_search(a+1, a+n+1, x)==0)
cout << -1;
else
cout << lower_bound(a+1, a+n+1, x) - a;
return 0;
}
三、结语
预览