图论基础————搜索

图论基础————搜索

DFS 深度优先搜索

原理简述:搜到底为止,不撞南墙不回头。

例题

输出全排列

八皇后问题

BFS 宽度优先搜索

原理简述:层层扩展,广撒网然后慢慢扩展出去。

每次搜到的都是离当前点最近的,具有一个“最短路“的性质。

例题

迷宫

acwing845 八数码问题

在一个3×3的网格中,1~8这8个数字和一个“x”恰好不重不漏地分布在这3×3的网格中。

例如:

1
2
3
1 2 3
x 4 6
7 5 8

在游戏过程中,可以把“x”与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1
2
3
1 2 3
4 5 6
7 8 x

例如,示例中图形就可以通过让“x”先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1
2
3
1 2 3   1 2 3   1 2 3   1 2 3
x 4 6 4 x 6 4 5 6 4 5 6
7 5 8 7 5 8 7 x 8 7 8 x

现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。

输入格式

输入占一行,将3×3的初始网格描绘出来。

例如,如果初始网格如下所示:
1 2 3

x 4 6

7 5 8

则输入为:1 2 3 x 4 6 7 5 8

输出格式

输出占一行,包含一个整数,表示最少交换次数。

如果不存在解决方案,则输出”-1”。

输入样例:

1
2  3  4  1  5  x  7  6  8 

输出样例

1
19

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<iostream>
#include<algorithm>
#include<queue>
#include<unordered_map>
#include<cstring>
using namespace std;
int n,m;
int dx[]={-1,0,1,0};
int dy[]={0,1,0,-1};
int bfs(string start)
{
string end="12345678x";
queue<string> q;
unordered_map<string,int> d;//可以替换为手写哈希表
q.push(start);
while(!q.empty())
{
auto t=q.front();
q.pop();
int dis=d[t];
if(t==end)return dis;

//难点:如何状态转移
int k=t.find('x');
int x,y;
x=k/3, y=k%3;//将一维数组的下标通过运算转换为二维数组下标
for(int i=0;i<4;i++)
{
int tx=x+dx[i],ty=y+dy[i];
if(tx>=0 and ty>=0 and tx<3 and ty<3)
{
swap(t[k],t[tx*3+ty]);//把x和周围的数交换的操作
if(!d.count(t))//这个状态之前没有出现过,是一个新的状态
{
d[t]=dis+1;
q.push(t);
}
swap(t[k],t[tx*3+ty]);//一定要换回来,否则会影响后续往其他方向swap
}
}
}
return -1;//到不了终点
}
int main()
{
string start;
for(int i=1;i<=9;i++)
{
char str;
cin>>str;
start+=str;
}
cout<<bfs(start);
return 0;
}

树与图的深度优先遍历

例题

acwing846.树的重心

给定一颗树,树中包含n个结点(编号1~n)和n-1条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式

第一行包含整数n,表示树的结点数。

接下来n-1行,每行包含两个整数a和b,表示点a和点b之间存在一条边。

输出格式

输出一个整数m,表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围

1≤n≤10^5

输入样例

9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6

输出样例:

4

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=100050;
int n;
struct node
{
int fr,to,next;
}e[N*2];
int head[N*2];
int vis[N];
int cnt=0;
int ans=N;
void add(int u,int v)
{
e[++cnt].fr=u;
e[cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
int dfs(int u)//返回以u为根的子树的大小
{
vis[u]=1;
int res=0,sum=1;//sum是子树大小,res是每个点删掉之后,剩下连通块的大小的最大值
for(int i=head[u];i;i=e[i].next)
{
int to=e[i].to;
if(!vis[to])
{
int s=dfs(to);
res=max(res,s);
sum+=s;
}
}
res=max(res,n-sum);//还有一个大的连通块就是 减去它的子树的部分
ans=min(ans,res);
return sum;
}
int main()
{
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n-1;i++)
{
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
}
dfs(1);
cout<<ans;
return 0;
}

树与图的宽度优先遍历

例题

acwing847.图中点的层次

给定一个n个点m条边的有向图,图中可能存在重边和自环。

所有边的长度都是1,点的编号为1~n。

请你求出1号点到n号点的最短距离,如果从1号点无法走到n号点,输出-1。

输入格式

第一行包含两个整数n和m。

接下来m行,每行包含两个整数a和b,表示存在一条从a走到b的长度为1的边。

输出格式

输出一个整数,表示1号点到n号点的最短距离。

数据范围

1≤n,m≤10^5

输入样例:

1
2
3
4
5
6
4 5
1 2
2 3
3 4
1 3
1 4

输出样例:

1
1

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int N=100050;
int n,m;
struct node
{
int to,next;
}e[N];
int head[N],d[N],vis[N];
int cnt=0;
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void bfs()
{
queue<int> q;
q.push(1);
d[1]=0;
while(!q.empty())
{
int u=q.front();
//cout<<u<<endl;
q.pop();
for(int i=head[u];i;i=e[i].next)
{
int to=e[i].to;
if(d[to]==-1)
{
d[to]=d[u]+1;
q.push(to);
}
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a, b;
cin>>a>>b;
add(a,b);
}
memset(d,-1,sizeof(d));
bfs();
cout<<d[n];
return 0;
}

拓扑序

一些概念:

拓扑序是针对有向图而言的。

入度:有几条边指向这个点(入边); 出度:有几条出边。

如果图上有环,则不存在拓扑序。因此有向无环图才存在拓扑序,有向无环图(DAG)又称为拓扑图。一个有向无环图一定至少存在一个入度为0的点。

如何求拓扑序:

基于宽搜

  1. 在拓扑图中,所有入度为0的点都可以作为起点。因此第一步就是先将所有入度为0的点入队。
  2. 循环:枚举队头的所有出边,u–>v,删掉u–>v的这条边,即d[v]–;
  3. 如果d[v]==0,就可以入队。完成整个过程就找到了拓扑序。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int N=100050;
int n,m;
struct node
{
int to,next;
}e[N];
int head[N],d[N],vis[N];
int ans[N];
int cnt=0;
int tot=0;
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
int bfs()
{
queue<int> q;
for(int i=1;i<=n;i++)
{
if(d[i]==0)q.push(i);
}
if(q.empty())return 0;
while(!q.empty())
{
int u=q.front();
ans[++tot]=u;
q.pop();
for(int i=head[u];i;i=e[i].next)
{
int to=e[i].to;
d[to]--;
if(d[to]==0)q.push(to);
}
}
if(tot==n)return 1;
else return 0;
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
add(a,b);
d[b]++;
}
if(bfs())
{
for(int i=1;i<=n;i++)
{
cout<<ans[i]<<" ";
}
}
else
{
cout<<-1;
}
return 0;
}