【算法笔记】有关图的搜索

1、广度优先搜索

【BFS】广度优先搜索算法

广度优先搜索BFS(Breadth First Search)也称为宽度优先搜索,它是一种先生成的结点先扩展的策略。
在广度优先搜索算法中,解答树上结点的扩展是按它们在树中的层次进行的。首先生成第一层结点,同时检查目标结点是否在所生成的结点中,如果不在,则将所有的第一层结点逐一扩展,得到第二层结点,并检查第二层结点是否包含目标结点,……,对层次为n+1的任一结点进行扩展之前,必须先考虑层次完层次为n的结点的每种可能的状态。因此,对于同一层结点来说,求解问题的价值是相同的,可以按任意顺序来扩展它们。通常采用的原则是先生成的结点先扩展。
为了便于进行搜索,要设置一个表存储所有的结点。由于在广度优先搜索算法中,要满足先生成的结点先扩展的原则,所以存储结点的表一般采用队列这种数据结构。

1.1 广搜之【SPFA】

spfa可以用来解决带负权值的单源最短路问题,因为每一节点都可以多次入队出队,不断更新最短路径。(注意要是超过N次入队列那就是存在负环了,spfa无法解决带负环问题)
算法优点
1.时间复杂度比普通的Dijkstra和Ford低。
2.能够计算负权图问题。
3.能够判断是否有负环 (即:每跑一圈,路径会减小,所以会一直循环跑下去)。
算法思想: 参考链接
采取的方法是动态逼近法:
1.设立一个先进先出的队列用来保存待优化的结点,初始只有源点一个节点。
2.优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。
3.这样不断从队列中取出结点来进行松弛操作,直至队列空为止 期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。

例题:洛谷 P1144 最短路计数
在这里插入图片描述
【题解】
这道题可以用spfa模板,只需要记录有多少个相同的到该点的最短路就OK啦

void SPFA(int x) {
	q.push(x);
	memset(vis, 0, sizeof(vis));
	memset(dis, 0x7f7f7f, sizeof(dis));
	dis[x] = 0;
	ans[x] = 1;//注意这里是1,自己到自己的路线有1条
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for (int icnt = frist[u]; icnt; icnt = road[icnt].nextt) {
			int v = road[icnt].to;
			if (dis[v] > dis[u] + 1) {
				dis[v] = dis[u] + 1, ans[v] = ans[u];
				if (!vis[v])
					q.push(v),vis[v]=1;
			}
			else if (dis[v] == dis[u] + 1)//即之前已经更新过,且与现在的线路步数一致
				ans[v] = (ans[v] + ans[u]) % mod;
		}
	}
}

总代码

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
#define N 2000007
#define mod 100003
struct node {
	int to;
	int nex;
}road[N<<1];
int vis[N], dis[N], frist[N], cnt = 0,ans[N],po[N][2];
queue<int> q;
void init() {
	memset(frist, -1, sizeof(frist));
//	memset(dis, 1007, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	memset(ans, 0, sizeof(ans));
}
void create(int u,int v) {
	road[++cnt].nex = frist[u];
	frist[u] =cnt;
	road[cnt].to = v;
}
void SPFA(int x) {
	q.push(x);
	dis[x] = 1;//dis存这个节点的深度,当前深度为1
	vis[x] = 1;
	ans[x] = 1;//当前x到x的路径只有1条
	while (!q.empty()) {
		int now = q.front();
		q.pop();
		vis[now] = 0;//
		//now 已取出,现要更新 to
		for (int i = frist[now];i; i = road[i].nex)
		{
			int to = road[i].to;
			if (dis[now] + 1 < dis[to])//若找到一条更短的路径
			{
				dis[to] = dis[now] + 1;
				ans[to] = ans[now];//当前能到达此点的路径数也应相同
				po[to][0] = now;
				if (!vis[to])
					vis[to]=1,q.push(to);
			}
			else if(dis[now]+1==dis[to]){//这种情况相当于有两条相同路径
				ans[to] = (ans[now] + ans[to]) % mod;
			}
			else {
				//此时的情况是从now到to不是最短路,则不走
			}
		}
	}
}
int main() {
	int n, m;
	cin >> n >> m;
	init();
	for (int i = 1; i <= m; i++) {
		int u, v;
		cin >> u >> v;
		create(u, v);
		create(v,u);
	}
	for (int i = 1; i <= n; i++)
		dis[i]= 200007;//!注意要填一个最大值
	SPFA(1);
	for (int i = 1; i <= n; i++)
		cout << ans[i]<<endl;
	for (int i = n; i; i = po[i][0])
		cout << i << " ";

	return 0;
}

优化:其实完全可以用BFS做。
大佬说:我们可以利用bfs来求每个点的深度。因为在所有边边权为1的时候,点的深度就是点的最短距离;这样在写法上便少了队列优化SPFA中退栈时还要把标记抹除这一操作,会大大提高算法速度
在bfs的时候,我们不仅仅要从一个点的父亲继承最短路,还要继承方案数;大佬题解链接
所以可以优化为:
(其实就是省去了spfa重复入队的步骤,由于每个边权为1,bfs搜的时候已经是最优步数,不存在再次更新最短路的情况)

void BFS(int x) {
	memset(vis, 0, sizeof(vis));
	memset(ans, 0, sizeof(ans));
	memset(dis,0x7f7f7f7f, sizeof(dis));
	q.push(x);
	dis[x] = 0;
	vis[x] = 1;
	ans[x] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		for (int i = frist[u]; i != -1; i = road[i].nextt) {
			int v = road[i].to;
			if (dis[v] == dis[u] + 1)
				ans[v] = (ans[v] + ans[u]) %mod;
			if (vis[v])continue;
			dis[v] = dis[u] + 1;
			vis[v] = 1;
			ans[v]=ans[u]% mod;
			q.push(v);
		}
	}
}
【 Dijkstra算法】

Dijkstra算法算是贪心思想实现的,首先把起点到所有点的距离存下来找个最短的,然后松弛一次再找出最短的,所谓的松弛操作就是,遍历一遍看通过刚刚找到的距离最短的点作为中转站会不会更近,如果更近了就更新距离,这样把所有的点找遍之后就存下了起点到其他所有点的最短距离(已确定是最短的点便不用当做终点站)。参考博客:点我
Dijkstra处理的是带正权值的有权图
模板:

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
using namespace std;
#define Inf 0x3f3f3f
#define N 1005
int map[N][N],vis[N],dis[N];
int n, m;
void Init() {
	memset(map, Inf, sizeof(map));
	for (int i = 1; i <= n; i++)
		map[i][i] = 0;
}
void Getmap() {//邻接表存图
	int u, v, w;
	for (int t = 1; t <= m; t++) {
		cin >> u >> v >> w;
		if (map[u][v] > w) {
			map[u][v] = w;
			map[v][u] = w;
		}
	}
}
void Dijkstra(int u) {
	memset(vis, 0, sizeof(vis));
	for (int t = 1; t <= n; t++) {
		dis[t] = map[u][t];
	}
	vis[u] = 1;
	for (int t = 1; t < n; t++) {
		int minn = Inf, temp;
		for (int i = 1; i <= n; i++) {
			if (!vis[i] && dis[i] < minn) {//求出最小路径
				minn = dis[i];
				temp = i;
			}
		}
		vis[temp] = 1;
		for (int i = 1; i <= n; i++) {
			if (vis[temp])continue;
			if (map[temp][i] + dis[temp] < dis[i])
				dis[i] = map[temp][i] + dis[temp];
		}
	}
}
int main() {
	cin >> n >> m;
	Init();
	Getmap();
	Dijkstra(1);
	cout << dis[n];
	return 0;
}

2、[DFS]深度优先搜索

深度优先搜索是暴力搜索的一种,其核心思想是递归+回溯。
思想:一直往深处走,直到找到解或者走不下去为止
例如:走迷宫的时候,在起点处,我们先任意选一个前进方向,到达新的位置后再次选择新的方向进行尝试,如果碰到死胡同,说明前进方向已无路可走,则返回一步(回溯),继续尝试其它方向是否还有路可走,如果有路可走,则沿该方向继续试探新的方向。按此原则不断搜索,直到找到新的出路或从原路返回入口处无解为止。参考女院总站
:bfs是浪费空间节省时间,dfs是浪费时间节省空间。
找最短路的时候可以用bfs,因为bfs找到的第一个解一定是最优的,这个时候可以直接return
递归回溯法算法框架:

int Search(int x)
 {
   if  (到目的地) 输出解;
   else
    for (int i = 1;i <= 算符种数;i ++)
     if  (满足条件) 
     {
        保存结果;
                     Search(x + 1);
        恢复:保存结果之前的状态{回溯一步}
     }
 }

有关例题洛谷 P1706 全排列问题
(一直往下深搜的思想,和回溯的思想。)
AC代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 10
int n;
int visit[N] = {0};//数没用过标0,数用过标1
int path[N];
//每一次递归搜索的是第 depth 个箱子中可以放什么数字 我是这么理解的。
void dfs(int depth) {
//假设有n个字符要排列,把他们依次放到n个箱子中

//先要检查数字是否被用过,手中还有什么数字,把他们放进并标记。

//放完一次要恢复初始状态,当到n+1个箱子时,一次排列已经结束

	if (depth == n+1)//判断边界
	{
		for (int i = 0; i < n; i++)
			printf("%5d", path[i]);
		cout << endl;
		return;
	}
	for (int i = 1; i <=n; i++) {//遍历查找 第depth个箱子中 ,能放什么数字
		if (visit[i] == 0)//若数字没有被用过
		{
		    path[depth-1] = i;//则可以往箱子里放这个数
			visit[i]= 1;//放完数后数字要标记为已用过
			dfs(depth + 1);//继续搜索下一个箱子
			visit[i] = 0;//回溯 数字用过后要回到没用过状态
		}
	}
}
int main() {

	cin >> n;
	dfs(1);//从第一个箱子开始
	return 0;
}

2、题目:洛谷 P1219 [USACO1.5]八皇后
题解:只是判断困难了一点。以后要记住:主对角线上各数组下标之差相等;斜对角线上各数组下标之和相等。
AC代码:

#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define N 17
bool col[N], tx[N], ty[N];
int path[N] = { 0 };
int n,ans=0;
void dfs(int depth) {
	if (depth >n) {
		ans++;
		if (ans <= 3)
		{
			for (int i = 1; i < n ; i++)
				cout << path[i] << " ";
			cout << path[n] << endl;
		}
		return;
	}
	for (int i = 1; i <= n; i++) {//试图把数字放在(depth,i)
		if (col[i] || tx[depth - i + n] || ty[depth + i])
			continue;
		else
		{
			path[depth] = i;
			col[i] = 1, tx[depth - i + n] = 1, ty[depth + i] = 1;
			dfs(depth + 1);
			col[i] = 0, tx[depth - i + n] = 0, ty[depth + i] = 0;
		}
	}
}
int main() {
	cin >> n;
	dfs(1);
	cout << ans;
	return 0;
}

除此之外,由于DFS算法是基于递归的嘛,时间复杂度较高,所以可以进行适当的剪枝操作。同参考参考女院总站请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值