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

1、广度优先搜索

【BFS】广度优先搜索算法

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

1.1 广搜之【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;
		}
	}
}

优化:其实完全可以用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
有关例题洛谷 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;
}

上一篇:golang 基于文件的消息队列 ---> diskqueue


下一篇:网络流 - dinic + 当前弧优化