算法学习-二分查找和二分答案1
事情的起因是这样的,今天下午我打算在编程课做完一道二分查找的题,实现二分查找。很快啊,我啪地一下就就写完了啊。结果我大意了,洛谷不讲武德,一个tle就让我对二分查找进行了将近一个下午的修改(同时也学习了快写与快读)。
二分查找 ===
前言
-
为什么要用二分查找?
顺序查找暴力简单,但如果一个数组很大,而我们要查找的数又在后面,或者在数组中根本不存在,则查找的时间就会很长。这时就需要效率较高的二分查找。 -
二分查找的原理是什么?
一般情况下,我们不知道待查数据的分布情况,所以采用比较机械的办法,每次检查待查数据中排在最中间的元素,然后缩小范围,在前一半或后一半内继续查找,再找前一半或后一半中间的数……然后无限套娃。
例如,对于以下一列递增的数 :12 14 18 20 21 22 24 26 28 30 33 0 1 2 3 4 5 6 7 8 9 10 要在途中找28,开始时,查找范围是0~10单元格,先从中间单元格,(0+10)/2=5号单元格里开始,记为里面的数为 arr[5] ,因为arr[5]<28,因为这一列数递增,则28在5号单元格后面,于是在(5+1=6)~10单元格查找,再次找到中间单元格8,因为arr[8] 等于 28,而28正是我们要找的数,于是查找就结束了,8 就是我们要找的数的位置。
如果我们要找23,一个不存在的数,同样去推导,最后查找范围会变成[6,5],显然不成立,于是可以下结论,23不在这列数里。
代码实现
代码实现也简单,我啪——地一下就就打完了(其实花了一个小时),很快嗷。(用的是递归)
int seek(int a,int b,int num,int arr[])//a是查找的左端点,b是查找的右端点,num是要查找的数,arr[]是查找的对象。
{
int mid=0;
mid=(a+b)/2;
if (arr[mid]==num) return mid; //找到了就返回mid,也就是num的位置
else if (arr[mid]>num&&(mid-1)>=a) return seek(a,mid-1,num,arr); //如果出现(mid-1)<=a就是没有num,最后返回'-1'
else if (arr[mid]<num&&(mid+1)<=b) return seek(mid+1,b,num,arr); //如果出现mid+1>b就是没有num,最后返回'-1'
return -1; //没有 num 就返回-1;
}
(之后打的用循环实现的:)
do{
mid=(a+b)/2;
if(arr[mid]==num) break;
else if(arr[mid]>num) b=mid-1;
else a=mid+1;
}while (a<=b); //a,b依旧是左右端点。
if (a>b) cout<<'-1'<<' ';
else cout<<mid<<' ';
深化拓展
当然这样就势必存在一个问题,如果我这个一列数虽然后一项不会比前一项小,但我可以相等啊,就比如
| 1 | 3 | 3 | 5 | 7 | 9 | 11 | 13 | 15 | 15 |
|---|---|---|---|---|---|---|---|---|---|
| 如果我们要找的是3,但是3有两个,我们输出哪个3的位置?一起输出吗? | |||||||||
| 有一个很笨的方法,找到其中一个三后,扫描其周围的的数,如果是3,将其下标一并给输出了,也可以存入数组先排序在输出。这很简单。 | |||||||||
| 不过我做的题会tle!这很重要! | |||||||||
| 题目要求是,找到这一列数中第一次出现 num 的下标。 | |||||||||
| 然后超时了,用快读和快写都无法解决,于是我认为这是原理性的问题,于是参照了一个题解,按他的循环思路打出了这个函数。 |
int seek(int a,int b,int num,int arr[])
{
int mid=0;
mid=(a+b)/2;
if (arr[mid]>=num&&mid>a) return seek(a,mid,num,arr);
else if (arr[mid]<num&&(mid+1)<b) return seek(mid+1,b,num,arr);
else if(arr[mid]>=num&&mid==a) return a;
else return b;
}
传递的变量都相同,不过返回值从“正确的下标”变成了 下标,判断是否存在交给主函数。
要点如下:
- 我们要找的是第一个 num ,所以若存在相同,我们可以将第一个 num 后面的其他相同的数按照比 num 大处理,所以第一个 if 括号里 大于 变成了 大于等于。这样对于要查找的区间 [a,b] , 仍有 arr[a]<=num<=arr[b]。
- 当如果就只有这一个 num , mid-1 就会直接跳过了,所以就不加一好了。这样对于要查找的区间 [a,b] , 仍有 arr[a]<=num<=arr[b]。
- 这种写法有一个好处,那就是运行到最后,总会出现 mid=a 或者 mid+1=b 的情况(可以自己动手尝试,这两种情况对应最后两个else), 而对于要查找的区间 [a,b] ,总有 arr[a]<=num<=arr[b]。也就是对于区间 [a,b]外的对于所有的 i ,arr[i]都不等于num。所以当区间缩小到两端相等时,记它们的值为k,要么arr[k]=num,且这个k是第一次出现num的下标,要么arr[k]!=num ,这个时候也就是 num 不存在。其所以运行到最后返回 a或 b,再检查 arr[a] 或 arr[b] 是否与 num相等就行了.
(当然,可能出现的特殊情况就是 num 不在这列数的最大值与最小值之间,不过也适用)
于是终于AC了
顺手打了 循环 版的,也是啪地一下,很快就打好了,这次是真的很快。
#include <iostream>
using namespace std;
int main()
{
int n=0,m=0,i=0,arr[1005000]={0},a=0,b=0,num=0,mid=0;
cin>>n>>m;//n是要查找对象的元素个数,m是要查找的 数 的个数
for (i=1;i<=n;++i)
{
cin>>arr[i];
}
for(i=1;i<=m;++i)
{
cin>>num;
a=1;
b=n;
//以下是二分查找
while(a!=b)
{
mid=(a+b)/2;
if (arr[mid]>=num) b=mid;
else a=mid+1;
}
if(arr[b]==num) cout<<b<<' ';
else cout<<-1<<' ';
//以上是二分查找
}
return 0;
}
(这莫不简单多了,无能狂怒)
没了。
最后
学习自:
洛谷P2249
C++程序设计|思想与方法(我的教材)
写博客好玩啊!

浙公网安备 33010602011771号