常见搜索模板DFS+BFS+二分搜索【算法】

  本篇讲的是常见的搜索模板,搜索题的解法时比较固定的,只要把模板记熟,加上自己找几道习题练习体会后,相信各位下次遇到这类题一定能拿下!!

下面我将已典型的题目为例子介绍几种常见的搜索方式。

 

1.二分搜索

二分搜索代码模板:

2fa1aa5647c84624a24cf887d8ab2340.png

例题:

5d2dc3b9432041f6a7b5cc32a88e3581.png

#include<bits/stdc++.h>
using namespace std;

double n;
const double eps = 1e-12;
//二分搜索
int main() {
	int t;
	cin>>t;
	while(t--) {
		cin>>n;
		double l = 0,r = 100000,res = -1;
		while(l<=r) {
			double mid = (l+r)/2;
			if(abs(mid*mid*mid - n) <= eps) {
				res = mid;
				break;
			}
			if(mid*mid*mid>n) r = mid - 0.0001;
			else if(mid*mid*mid<n) l = mid+0.0001,res = mid;
		}
		printf("%.3lf\n",res);
	}

	return 0;
}

二分搜索是只能对有序的数据处理的方式。并且每次操作可以将未搜索的区间减少一半,因此速度十分的快。

前面说了每次搜索将搜索的区间减少一半,因此二分搜索的关键主要是将数据区间的更新,在每次操作过程中需要跟(L+R)/ 2 得到的mid 中间值去比较,如果小于mid,则R = mid-1;大于mid,则L = mid + 1;当L不小于R时,循环结束,此时mid就是想要的值。

2.三分搜素

三分搜索和二分搜索类似,模板可以参考下面的例题。

例题:

162b7ae14b6a4867bad1e56d0cf9a65b.png

#include<bits/stdc++.h>
using namespace std;
//三分法
#define double long double
int a,b,c,Q;
const double esp = 1e-9;
double check(double x){
	return a * x * x + b * x + c;
}


int main(){
	cin>>a>>b>>c>>Q;
	while(Q--){
		double l,r;
		cin>>l>>r;
		while(r - l > esp){
			double lmid = l + (r - l) / 3;
			double rmid = r - (r - l) / 3;
			if(check(lmid)>check(rmid)) r = rmid;
			else if(check(lmid)<check(rmid)) l = lmid;
			else l = lmid,r = rmid;
		}
		cout << setprecision(2) << fixed << a * l * l + b * l + c << '\n' ;
	}
	return 0;
}

 二分搜索也是有局限,它只能对有序的数据进行搜索,也就是数据必须单调。本题给出的二元一次函数,明显非单调。这就要用到三分搜索了,就是将原来的搜索区间分成三份,如下图。分别计算LMID和RMID,然后对其进行比较,若LMID大于RMID,那最大值一定在L到RMID这区间内。小于类似。

 3871b2573f9d4867a14eb2bdbfac7863.png

 3.BFS

BFS的代码模板:

575decd43ae8405089652f7c5492b6b8.png

例题:

bcd3e0071a134901969d50b990166b5c.png

#include<bits/stdc++.h>
using namespace std;


int x,y,x2,y2;
int mp[105][105];
int n,m;
bool vis[105][105];  //标记地图上的点是否访问过
int dir[4][2] = {{1,0},{-1,0},{0,1},{0,-1}}; //搜索方向
struct node{
	int a,b,c;
};  //方便计算和存入队列中

bool check(int x,int y){ //排除边界点、地图不能访问的点、以及已经访问过了的点
	if(x<1 || x> n || y<1 || y>m || !mp[x][y] || vis[x][y]) return false;
	return true;
}
int bfs(int x,int y){
	queue<node> q;
	q.push(node{x,y,0});
	vis[x][y] = 1;
	while(!q.empty()){
		node u = q.front();
		q.pop();
		if(u.a == x2 && u.b == y2) return u.c;
		for(int i = 0;i<4;i++){
			int xx = dir[i][0]+u.a;
			int yy = dir[i][1]+u.b;
			if(!check(xx,yy)) continue;
			vis[xx][yy] = 1;
			q.push(node{xx,yy,u.c+1});
		}
	}
	return -1;
}
int main()
{
	cin>>n>>m;
	for(int i = 1;i<=n;i++){
		for(int j = 1;j<=m;j++){
			cin>>mp[i][j];
		}
	}
	cin>>x>>y>>x2>>y2;
	cout<<bfs(x,y)<<endl;
	return 0;
}

BFS适用于求最短路径,该算法的搜索过程:

  • 将起始位置加入队列
  • 从队列开头取出一个元素,将这个元素四周可到达并且未被访问的点加入队列

因为队列先进先出的特性,只有当所有距离为 111的点被取出队列后,才会遍历距离为 222 的点,而距离为 111的点能到达的只有距离为 222的点,这也就保证这搜索答案的正确性。

 4.DFS+剪枝

DFS的代码模板:

a1b6db17fe9d43bcab003462747c4642.png

例题:

dc2db43c2dc74f6ba5574ba5dcc92d92.png

#include <bits/stdc++.h>
using namespace std;
int vis[17];
int m[5][5];
int cnt = 0;

bool judge() {  
    for (int i = 0; i < 4; i ++ ) { //四行四列的和
        if (m[i][0] + m[i][1] + m[i][2] + m[i][3] != 34) return 0;
        if (m[0][i] + m[1][i] + m[2][i] + m[3][i] != 34) return 0;
    }
    if (m[0][0] + m[1][1] + m[2][2] + m[3][3] != 34) return 0;//对角线
    if (m[0][3] + m[1][2] + m[2][1] + m[3][0] != 34) return 0;
    return 1;
}

void dfs(int n) {
    if (n == 16) {
        if (judge())    cnt ++;
        return;
    }
    if (n%4 == 0)  //简单剪枝:检查第一行的和是不是34
      if (m[n/4 - 1][0] + m[n/4 - 1][1] + m[n/4 - 1][2] + m[n/4 - 1][3] != 34)
         return;

    for (int i = 2; i <= 16; i ++ ) {
        if (vis[i] == 0) {
            m[n/4][n%4] = i;
            vis[i] = 1;
            dfs(n + 1);
            vis[i] = 0;
        }
    }
}

int main() {
     m[0][0] = 1;
     dfs(1);
     cout << cnt << endl;   //416
    return 0;
}

本质上是暴力把所有的路径都搜索出来,它运用了回溯,保存这次的位置并深入搜索,都搜索完便回溯回来,搜下一个位置,直到把所有最深位置都搜一遍(找到目的解返回或者全部遍历完返回一个事先定好的值)。要注意的一点是,搜索的时候有记录走过的位置,标记完后要改回来。 

这题的递归出口是所有数都填完,然判断该解是不是题目要求的解,是的话cnt++。有一个剪枝,可以降低时间复杂度,即当一行的和加起来不是34时,就一定不是我们要的解,往后就没必要再搜索下去了。

 

 

  • 7
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值