本人大一新生,将在这里写下自己学习算法过程中遇到的一些问题,希望有更多和我一样的人在一起交流。
由于本人学习的是c++,所以将用c++编译的代码呈现给大家
二分法
首先,二分查找法只适用于顺序储存的有序表(也可说它是线性表中的元素按值非递减排列),实际上有序就可以,无论它是从大到小还是从小到大。
时间复杂度(二分法)
二分查找是一种十分高效的算法,因为他是折半查找的,每次查找都会将数据减半。
如在一本牛津字典中查一个单词,我们会先从字典中间打开查找(一页一页翻真的很累),如果你发现该单词首字母所在的页数要小于中间值则你会把中间那部分放下(即中间部分变为区间的右端),然后继续对半分,直到找到单词。反之大于中间值也是一样的。
对于在给定区间中单调的函数,采用的方法就是二分法,一般的方法就是,假设答案就在区间[l,r]中,取中间值mid=l+r>>1,根据f(mid)来判断在[l,mid)查找,还是在(mid,r]查找,查找的次数为O(log(r-l),所用的时间复杂度为logn,log函数的增长速率是很慢的,可想而知这比遍历数组要快多少。(此处省略2,在计算机中如果你不输入值,对数的底数默认为2)。
模板(模板虽好用,理解仍重要)
int findx(int x)//定义的二分查找函数,x为要查找的数,首先做的就是要把所有数值存入数组
{
int l=0,r=n;//数组范围是从0-n的
while(l<r)
{
int mid=l+r>>1;
if(a[mid]>=x)
r=mid;
else
l=mid+1;
}
return a[l]==x?l:-1;//找到了就打出下标,没找到就返回-1;要善用三目运算符
}
int findx(int x)
{
int l=0,r=n-1;//其实原理是一样的,停止条件不同
while(l<=r)
{
int mid=l+r>>1;
if(a[mid]==x)
return mid;
if(x>a[mid])
l=mid+1;
else
r=mid-1;
}
return -1;
}
当时因为这个区间闭开问题而苦恼,其实画在草纸上就理解了
上习题(二分查找)
P2249 【深基13.例1】查找 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
该题显然易见需用二分查找,符合单调
#include<iostream>
int find(int x);//如果不定义全局变量,会报错未定义
const int maxn=1e6+3;//根据题意要求大一些
int a[maxn];
int n,m,q,x;
using namespace std;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++)
{
scanf("%d",&x);
printf("%d",find(x));
printf(" ");
}
}
int find(int x)
{
int l=1,r=n,mid;
while(l<r)
{
mid=(l+r)>>1;//主要原因为加法可能会有溢出风险,位运算符则不用考虑
if(a[mid]>=x)//模板
{
r=mid;
}
else
{
l=mid+1;
}
}
return a[l]==x?l:-1;
}
二分查找(一) - 题库 - 计蒜客 (jisuanke.com)
该题的类型题
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+6;
int a[MAXN] = {};
int binary_search(int A[], int l, int r, int val) {
while (l<=r) {
int mid = (r+l)>>1;
if (A[mid]==val) {
return mid;
} else if(A[mid] < val) {
l = mid+1;
} else {
r = mid-1;
}
}
return -1;
}
int main() {
int n,m;
scanf("%d%d", &n, &m);
int i;
for (i=0; i<n; i++) {
scanf("%d", &a[i]);
}
sort(a, a+n);
for (i=0; i<m; i++) {
int x;
scanf("%d", &x);
if (-1==binary_search(a, 0, n-1, x)) {
printf("NO\n");
} else {
printf("YES\n");
}
}
return 0;
}
P1102 A-B 数对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
难度增加
分析:核心思想就是遍历所有的B,然后用二分法找能使等式成立的A。
用到了stl库函数,lower_bound(),upper_bound()和sort()
sort(a,a+n)
sort(a+1,a+1+n)
upper_bound(a,a+n,x)-a //下标从0开始
upper_bound(a+1,a+n+1,x)-a //下标从1开始
lower_bound(a,a+n,x)-a //下标从0开始
lower_bound(a+1,a+n+1,x)-a //下标从1开始
#include<bits/stdc++.h>
using namespace std;
long a[1000000];//数组定义大一些
long N,C,ans;//ans为计数器
int main()
{
cin>>N>>C;
for(int i=1;i<=N;i++)
{
cin>>a[i];
}
sort(a+1,a+N+1);//进行排序,不能保证测试用例都是有序的
for(int i=1;i<=N;i++)//换成a=b+c;找有几个a,下面语句的意思是,找到一个大于b+c的数,减去那个大于等于b+c的数(如果等于了,那不就等于找到a了嘛),所以作差就是找在这个B下是否能找到a,找到就加一,没找到就是0;
{
ans+=((upper_bound(a+1,a+N+1,a[i]+C)-a)-(lower_bound(a+1,a+N+1,a[i]+C)-a));
}//这里用到了c++stl库函数,upper_bound和lower_bound
cout<<ans;
return 0;
}
二分答案
刚开始我对二分答案的概念是很模糊的,经查阅大量资料和做了很多题之后思路才逐渐清晰起来
那么,什么是二分答案,就是将答案二分呗。
适用范围
1.答案有确定的范围,且有序(即如果mid满足答案,则(mid, r]一定都满足答案,相对的[0, mid - 1]一定不满足答案)
2.如果题目规定了有“最大值最小”或者“最小值最大”的东西,那么这个东西应该就满足二分答案的有界性。
3.其次要注意,二分答案类似穷举法,需要进行验证,添加一个judge()函数来判断就显得很重要。
实践
P2678 [NOIP2015 提高组] 跳石头 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这是二分答案的典型题
分析,该题满足适用范围中的第二条,用二分答案法
#include<bits/stdc++.h>
using namespace std;
int L,N,M,ans;
const int O=1e6+5;
int a[O];
bool judge(int x)//必要的检查函数
{
int tot=0;//用来计数(挪走的石头数量)
int i=0;//初始的石头位置,即我现在站在起点准备跳的那个位置
int now=0;//跳完之后所在的石头位置,起点
while(i<N+1)
{
i++;//不算起始点
if(a[i]-a[now]<x)//如果两块石头之间的距离小于最短距离,则证明不合格,要挪走
{
tot=tot+1;//挪走
}
else
now=i;//合格我就跳上去,继续下一块,一直找到所有不合格的石头
}
if(tot>M)//如果挪走的石头比我限定的多,那么你这个最短距离就不合格
return false;
else
return true;
}
int main()
{
cin>>L>>N>>M;
for(int i=1;i<=N;i++)
{
cin>>a[i];
}
a[N+1]=L;//因为终点没有存入数组
sort(a+1,a+N+1);//排序,还是因为不知道测试用例是否有序
int l=1,r=L;
while(l<=r)//这不比数组实际长度多一个嘛,所以大于r的时候停止,目的是找最短距离
{
int mid=l+r>>1;//防止加法运算的溢出
if(judge(mid))//判断是否合格,不合格就继续找
{
ans=mid;
l=mid+1;
}
else
{
r=mid-1;
}
}
cout<<ans<<endl;
return 0;
}
P1873 [COCI 2011/2012 #5] EKO / 砍树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
和跳石头问题相似
#include<bits/stdc++.h>
using namespace std;
long long n,m,l,r,mid,maxn=-10000,q,a[1000005];
long long judge(long long x)
{
long long ans=0;
for(int i=1;i<=n;i++)
{
if(a[i]>x)
{
ans=ans+a[i]-x;
}
}
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i]>maxn)
{
maxn=a[i];
}
}
sort(a+1,a+1+n);
l=1,r=maxn;
while(l<=r)
{
mid=r+l>>1;
q=judge(mid);
if(q<m)
{
r=mid-1;
}
else
l=mid+1;
}
cout<<r;
return 0;
}