本篇将会结合实例解析双向搜索


一、双向搜索

当给出了起点状态与终点状态时,使用普通的搜索从起点向下搜索,则效率会很低,搜索树会非常庞大;所以,可以使用双向搜索,及从起点与终点同时向中间搜索,搜索到同一个状态时,将从起点与终点搜索的值相加得到最终值的搜索;

一般给出“始态”与“终态”时,可以考虑使用双向搜索;


二、双向DFS

第一个DFS时,先搜索前一半的空间,存储所有可达的值;

第二个DFS时,再搜索后一半的空间,然后查询是否在前一半空间中出现过;


三、双向BFS

双向BFS维护两个对列,q1 ,q2 ,q1维护从起点开始搜索的状态,q2维护从终点开始搜索的状态,若在扩展状态时发现某个状态同时被q1 和q2扩展到,此时找到的即为答案;

将开始状态放入q1;
将结束状态放入q2;
将开始状态标记为1;
将结束状态标记为2;
while (q1不为空 && q2不为空) {
    if (q1大小 > q2大小) {
        取队列 q2 队首元素;
    } else {
        取队列 q1 队首元素;
    }
    循环遍历取出元素的可扩展状态 {
        if (从q1取出) {
    		标记为1,放入q1队列;
    	} else {
			标记为2,放入q2队列;
        }
        if (该状态同时被标记为1与2) {
            找到答案,搜索结束;
        }
    }
}

也可以只使用一个对列,即在队列中再开一个变量标记从何处开始搜索的即可;


四、例题

导航系统

题目:小Q来到了一个随机的国度。这个国度由n座城市和m条双向道路构成。因为这个国度崇尚随机,因此m条边是用随机选择两端点的方式生成的。充满好奇的小Q想在这里进行k次随机的旅行,每次的起点和终点也是随机选择的。在每次出发之前,他会使用导航系统计算两点间最少需要经过几条道路。请写一个程序,帮助小Q计算两点间的最短路。

输入:

第一行包含3个正整数n,m,k(2<=n<=100000,1<=m<=300000,1<=k<=10000),分别表示点数、边数和询问数。

接下来m行,每行两个正整数u_i,v_i(1<=u_i,v_i<=n),表示一条双向道路。输入数据保证不会有重边和自环。

接下来k行,每行两个正整数u_i,v_i(1<=u_i,v_i<=n),表示一次询问。

输入数据保证随机生成,且除了样例之外均满足n=100000,m=300000。

本题共3组数据。

输出:

输出k行,每行一个整数,即最少经过的边数,若无解输出-1。

思路:

此题给出一个起点与一个终点,所以考虑双向搜索,状态扩展时,扩展每个点相连的点;

使用邻接表记录图,在询问时,使用并查集判断两点是否连通;

代码如下:

#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
#define MAXN 300005
using namespace std;
int n, m, k, father[MAXN];
bool flag1[MAXN], flag2[MAXN];
vector < int > g[MAXN];
struct node {
	int x, z, f;
};
void firstset(int n) {
	for (int i = 1; i <= n; i++) {
		father[i] = i;
	}
	return;
}
int findset(int x) {
	if (x == father[x]) return x;
	return father[x] = findset(father[x]);
}
void push(int x, int y) {
	father[findset(x)] = findset(y);
	return;
}
int bfs(int s, int e) {
	queue < node > q;
	memset(flag1, false, sizeof(flag1));
	memset(flag2, false, sizeof(flag2));
	flag1[s] = true, flag2[e] = true;
	q.push( node ( { s, 0, 1 } ) );
	q.push( node ( { e, 0, 2 } ) );
	while (!q.empty()) {
		node t = q.front();
		q.pop();
		for (int i = 0; i < g[t.x].size(); i++) {
			if (t.f == 1) {
				flag1[g[t.x][i]] = true;
				q.push( node ( { g[t.x][i], t.z + 1, 1 } ) );
			} else {
				flag2[g[t.x][i]] = true;
				q.push( node ( { g[t.x][i], t.z + 1, 2 } ) );
			}
			if (flag1[g[t.x][i]] == true && flag2[g[t.x][i]] == true) {
				int ans = t.z + 1;
				if (t.f == 2) {
					while (!q.empty()) {
						node z = q.front();
						if (z.x == g[t.x][i] && z.f == 1) {
							ans += z.z;
							break;
						}
						q.pop();
					}
				} else {
					while (!q.empty()) {
						node z = q.front();
						if (z.x == g[t.x][i] && z.f == 2) {
							ans += z.z;
							break;
						}
						q.pop();
					}
				}
				return ans;
			}
		}
	}
}
int main() {
	scanf("%d %d %d", &n, &m, &k);
	firstset(n);
	for (int i = 1; i <= m; i++) {
		int x, y;
		scanf("%d %d", &x, &y);
		g[x].push_back(y);
		g[y].push_back(x);
		push(x, y);
	}
	for (int i = 1; i <= n; i++) {
		sort(g[i].begin(), g[i].end());
	}
	for (int i = 1; i <= k; i++) {
		int x, y;
		scanf("%d %d", &x, &y);
		if (x == y) printf("0\n");
		else if (findset(x) != findset(y)) printf("-1\n");
		else printf("%d\n", bfs(x, y));
	}
	return 0;
}


点赞(0)

C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:

一点编程也不会写的:零基础C语言学练课程

解决困扰你多年的C语言疑难杂症特性的C语言进阶课程

从零到写出一个爬虫的Python编程课程

只会语法写不出代码?手把手带你写100个编程真题的编程百练课程

信息学奥赛或C++选手的 必学C++课程

蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程

手把手讲解近五年真题的蓝桥杯辅导课程

Dotcpp在线编译      (登录可减少运行等待时间)