参考:《算法导论》第22章 《算法图解》第6章
目录
二:闪现(类似问题http://poj.org/problem?id=3278)(bfs+输出最少步数)
五:prime path(http://poj.org/problem?id=3126)
六:Knight Moves(POJ2243 http://poj.org/problem?id=2243)输出最少步数
七:POTS(http://poj.org/problem?id=3414)
二:A Knight's Journey(http://poj.org/problem?id=2488)(dfs+输出路径)
三:Children of the Candy Corn(http://poj.org/problem?id=3083)(bfs+dfs+方向优先最短步数)
四:打冰球(http://poj.org/problem?id=3009)(dfs+剪枝)
五:棋盘问题(http://poj.org/problem?id=1321)(bfs+逐行搜索)
图G=(V,E)
- 邻接链表(鲁棒性更高)
- 邻接矩阵
广度优先搜索
广度优先算法的核心是它一定是把同一级的子节点都拉入队列,之后才会去遍历下一级节点。
结点:白色——灰色——黑色。
队列——先进先出——管理灰色结点——灰色:已被发现但是其邻接链表尚未被完全检查。
BFS(G,s):
for each vertex u ∈ G.V - {s} //所有点初始化
u.color=white
u.d=∞
u.π=NIL
s.color=gray //源结点初始化
s.d=0
s.π=NIL
Q=φ //队列初始化
enqueue(Q,s)
while Q≠φ
u=dequeue(Q)
for each v ∈ G.Adj[u]
if v.color==white
v.color=gray
v.d=u.d+1
v.π=u
enqueue(Q,v)
u.color=black
- 1-4:把每个点涂上白色,d属性设为无穷,父节点设为NIL
- 5-7:源节点s设为灰色
- 8-9:队列Q初始化,s入队
- 11:取出Q的队头结点u,并从队列中删除
- 12-17:for循环:对u的每个结点v考查:如果是白色:涂上灰色,距离加一,父节点为u,插入队尾
- 18:循环结束后u涂黑
例题:参考https://blog.csdn.net/jiange702/article/details/81365005
一:模板(POJ 3984 迷宫问题)(bfs+输出路径)
这个模板是参考了上面这个博客,感觉很好用也很好理解。
可以大体分成6部分
1.头文件段
2.坐标结构体段
3.障碍物预设段
4.已访问结点和移动坐标段
5.BFS算法函数段
6.主函数段
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
Input
一个5 × 5的二维数组,表示一个迷宫。数据保证有唯一解。
Output
左上角到右下角的最短路径,格式如样例所示。
Sample Input
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
Sample Output
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
代码:
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
//2.坐标结构体
struct mi{
int x;
int y;
} ca;
//3.障碍物预设段
int map[5][5] = {
{0, 1, 0, 0, 0},
{0, 1, 0, 1, 0},
{0, 0, 0, 0, 0},
{0, 1, 1, 1, 0},
{0, 0, 0, 1, 0}};
struct mi zhen[5][5];//记录路径子节点和父节点的对应关系
//4.已访问结点和移动坐标段
int visited[5][5]; //记录已访问结点
int xx[4] = {1, -1, 0, 0};
int yy[4] = {0, 0, -1, 1};
//5.bfs算法函数段
void bfs()
{
memset(visited, 0, sizeof(visited));//初始化数组,头文件用string.h
queue<mi> q; //初始化队列,结点类型为mi
struct mi A; //源结点初始化后入队
A.x = 0;
A.y = 0;
visited[0][0] = 1;
zhen[0][0].x = 0;
zhen[0][0].y = 0;
q.push(A);
while(!q.empty())
{
struct mi te = q.front();
q.pop();
if(te.x==4&&te.y==4) //找到答案后退出
return;
//特别注意:不一定都需要退出条件,如果题目要求我们对单节点进行移动
for (int i = 0; i < 4;i++)
{
int zx = te.x + xx[i];
int zy = te.y + yy[i];
if(zx<0||zx>4||zy<0||zy>4||map[zx][zy]||visited[zx][zy])
continue;
visited[zx][zy] = 1;
struct mi kao;
kao.x = zx;
kao.y = zy;
q.push(kao);
zhen[zx][zy].x = te.x;
zhen[zx][zy].y = te.y;
//记录最短路径,首先这个数组里存的是坐标结构体,数组下标代表着子节点坐标,
//而数组内容存着父节点坐标,这样皆可以通过循环,一级一级找上去,
//既可以知道最短路径长度,也可以打印所有经过的节点坐标
}
}
}
//6.主函数
int main(void)
{
for(int m=0;m<5;m++) //初始化迷宫地图
{
for(int n=0;n<5;n++)
{
cin >> map[m][n];
}
}
bfs();
int num[30][2];
int x=4,y=4;
int i=0;
while(1)
{ //把父子节点关系倒着取出放入数组中以便打印
int k;
k=x;
x=zhen[x][y].x;
y=zhen[k][y].y;
num[i][0]=x;
num[i][1]=y;
i++;
if(x==0&&y==0)
break;
}
for(int j=i-1;j>=0;j--)
{
cout << "(" << num[j][0] << ", " << num[j][1] << ")" << endl;//打印路径节点部分
}
cout << "(4, 4)" << endl;
system("pause");
return 0;
}//在POJ上可以AC
二:闪现(类似问题http://poj.org/problem?id=3278)(bfs+输出最少步数)
在某知名游戏中,“闪现”是一个很有用的技能,某天你在机缘巧合之下在现实生活中也学会了这个技能,
你目前处在坐标为 a的位置上,欲到坐标为 b 的地方去。你在 1 秒钟内可以移动 1 个单位的距离,
当然也可以使用“闪现”使你目前的坐标翻倍。
输入
输入的数据有多组,每组数据包含两个以空格为分隔符的非负整数 a 和 b。其中,0 ≤ a ≤ 100000
且 0 ≤ b ≤100000。
输出
输出你由 a 出发到达坐标 b 所需的最短时间,每组数据的输出占一行。
样例输入
5 17
10 20
样例输出
4
1
代码:
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int visited[100005]={0};
int xx[]={0,1,-1};
int bfs(int a,int b)
{
queue<int>q;
visited[a]=1;
q.push(a);
while(!q.empty())
{
int head=q.front();
if(head==b)return visited[head];
int zx;
q.pop();
for(int i=0;i<3;i++)
{
if(i==0)zx=head*2;
else zx=head+xx[i];
if(zx<0||zx>100000||visited[zx])continue;
q.push(zx);
visited[zx]=visited[head]+1;
}
}
return -1;
}
int main()
{
int a,b;
cin>>a>>b;
cout<<bfs(a,b)-1<<endl;
return 0;
}
三:走迷宫(牛客网)(bfs+输出最少步数)
上面北大OJ的迷宫是5*5,牛客网这个迷宫是10*10,用同样的算法代码显示超时,参考https://blog.csdn.net/m0_37923250/article/details/79831338改了一下可以AC。
#include<iostream>
#include<queue>
#include<string.h>
using namespace std;
int visited[10][10];
char map[10][10];
int xx[]={0,0,1,-1};
int yy[]={1,-1,0,0};
int bfs()
{
memset(visited,0,sizeof(visited));
queue<pair<int,int> >q;
pair<int,int>p;
int x,y;
q.push(make_pair(0,1));
while(!q.empty())
{
p=q.front();
q.pop();
x=p.first;
y=p.second;
if(x==9&&y==8)return visited[9][8];
for(int i=0;i<4;i++)
{
int zx=x+xx[i];
int zy=y+yy[i];
if(zx<0||zx>9||zy<0||zy>9||map[zx][zy]=='#'||visited[zx][zy])continue;
q.push(make_pair(zx,zy));
visited[zx][zy]=visited[x][y]+1;
}
}
return 0;
}
int main()
{
char c;
while(cin>>c)
{
map[0][0] = c;
for(int i=1;i<10;i++)
{
cin>>c;
map[0][i] = c;
}
getchar();//吃掉末尾的换行符
for(int i=1;i<10;i++)
{
for(int j=0;j<10;j++)
{
cin>>c;
map[i][j] = c;
}
getchar();//吃掉末尾的换行符
}
cout<<bfs()<<endl;
}
return 0;
}
四:油田(这个题目比上面两个更灵活)
你刚刚承包了一块大小为 M * N 的油田,并通过专业器具知道了哪些区块下面蕴藏着石油。
现在你想知道至少需要多少台机器才能将这块油田的所有石油采完。如果蕴藏着石油的区域
互相连接,那么这两块区域可以使用同一台机器。例如:若 M = 1, N = 5,其中(1, 1),
(1, 2)和(1, 4)区域有石油,那么由于(1, 1)和(1, 2)这两块区域连在一起,所以只需要两
台机器即可采完所有石油。
输入
输入的数据有多组,每组数据首先是两个正整数 M 和 N,接下来是一个 M 行N 列的矩阵,
矩阵表示油田,矩阵中有两种可能的值,一种是“*”,表示此处没有油,一种是“@”,代表此处蕴藏
着石油。若 M, N 均为 0,则表示输入结束,该组数据 不输出任何内容。
输出
对于每组数据,在一行内输出需要机器的数量。
样例输入
1 1
*
3 5
*@*@*
**@**
*@*@*
1 8
@@****@*
5 5
****@
*@@*@
*@**@
@@@*@
@@**@
0 0
样例输出
0
1
2
2
代码:
#include<iostream>
using namespace std;
#include<stdio.h>
#include<string.h>
#include<queue>
struct mi{
int x;
int y;
}ca;
//初始障碍 二维数组
char **map;
int xx[8]={1,-1,0,0,1,1,-1,-1}; //移动方向
int yy[8]={0,0,-1,1,1,-1,1,-1};
int BFS(int a,int b,int ca,int ka)
{
if(map[a][b]=='*') //初始节点*是非法节点不搜索
return 0;
queue<mi>q;
struct mi A;
A.x=a;
A.y=b;
q.push(A);
while(!q.empty())
{
struct mi te=q.front();
q.pop();
for(int i=0;i<8;i++)
{
int zx=te.x+xx[i];
int zy=te.y+yy[i];
if(zx<0||zx>=ca||zy<0||zy>=ka||map[zx][zy]=='*')
continue;
map[zx][zy]='*'; //将合法节点改为非法节点
struct mi kao;
kao.x=zx;
kao.y=zy;
q.push(kao);
}
}
return 1; //说明此初始节点属于连通区域
}
int main()
{
int a,b,k,m,key,daan;
while(1)
{
daan=0;
cin>>a>>b;
if(a==0&&b==0)
break;
map=new char*[a]; //动态数组只能是一维的,定义二维动态数组必须先定义一个动态行指针变量
for(int i=0;i<a;++i)
map[i]=new char[b];
for(int m=0;m<a;m++) //输入地图
for(int n=0;n<b;n++)
cin>>map[m][n];
for(int i=0;i<a;i++) //遍历整个地图
for(int j=0;j<b;j++)
if(BFS(i,j,a,b))
daan++;
cout<<daan<<endl;
for(int i=0;i<a;i++) //释放动态数组空间
delete(map[i]);
delete(map);
}
return 0;
}
五:prime path(http://poj.org/problem?id=3126)
输入两个质数,第一个数每次只能改变其中一位,每次改变后的数也必须是质数。最少改变几次能变成第二个数。
1033
1733
3733
3739
3779
8779
8179
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
int is_pr[10005]; //1000-9999中所有素数
int snum=0; //1000-9999中所有素数的个数
int vis[10005];
struct card
{
int data;
int num;
};
int bfs(int a,int b)
{
int ci;
int ttemp1,ttemp2,aa,bb;
queue<card>q;
card now,next;
if(a==b)
return 0;
memset(vis,0,sizeof(vis));
vis[a]=1;
now.data=a; //头结点入队
now.num=0;
q.push(now);
while (!q.empty())
{
now=q.front();
q.pop();
for (int i=0;i<snum;i++) //循环1000-9999中所有的素数
{
ci=0;
if(vis[is_pr[i]])continue;
next.data=is_pr[i];
next.num=now.num+1;
ttemp1=next.data;
ttemp2=now.data;
for (int j=0;j<4;j++) //判断两个素数有几位数字相同
{
aa=ttemp1%10;
bb=ttemp2%10;
ttemp1/=10;
ttemp2/=10;
if(aa==bb)
{
ci++;
}
}
if(ci==3)
{
if(next.data==b)
return next.num;
q.push(next);
vis[next.data]=1;
}
}
}
}
int isPrime(int n)
{
if(n<=1)
return 0;
if(n==2)
return 1;
for(int i=2;i*i<=n;i++)
if(n%i==0)
return 0;
return 1;
}
int main()
{
int n,a,b;
cin>>n;
for (int i=1000;i<=9999;i++)
{
if(isPrime(i))
{
is_pr[snum++]=i; //找出1000-9999之间的所有素数
}
}
while (n--)
{
cin>>a>>b;
cout<<bfs(a,b)<<endl;
}
return 0;
}
六:Knight Moves(POJ2243 http://poj.org/problem?id=2243)输出最少步数
代码:(main的while里面不能写1,我把while里的条件改成cin才AC)
这个题目其实挺简单,只需要注意一下输出形式就行,骑士走日字形有八种坐标可以走。
#include<iostream>
#include<queue>
#include<string.h>
using namespace std;
int visited[10][10];
int map[10][10];
int xx[]={1,1,2,2,-2,-2,-1,-1}; //走日字形
int yy[]={2,-2,1,-1,1,-1,2,-2};
int bfs(pair<int,int>start,pair<int,int>end)
{
if(end.first==start.first && end.second==start.second)return 0;
memset(visited,0,sizeof(visited));
queue<pair<int,int> >q;
pair<int,int>p;
int x,y;
q.push(start);
while(!q.empty())
{
p=q.front();
q.pop();
x=p.first;
y=p.second;
if(x==end.first&&y==end.second)return visited[end.first][end.second];
for(int i=0;i<8;i++)
{
int zx=x+xx[i];
int zy=y+yy[i];
if(zx<1||zx>8||zy<1||zy>8||visited[zx][zy])continue;
q.push(make_pair(zx,zy));
visited[zx][zy]=visited[x][y]+1;
}
}
return 0;
}
int main()
{
char c1,c2,c3,c4;
while(cin>>c1>>c2>>c3>>c4)
{
pair<int,int>p1;
pair<int,int>p2;
p1.first=c1-48-48;
p1.second=c2-48;
p2.first=c3-48-48;
p2.second=c4-48;
//广搜
cout<<"To get from "<<c1<<c2<<" to "<<c3<<c4<<" takes "<<bfs(p1,p2)<<" knight moves."<<endl;
}
return 0;
}
七:POTS(http://poj.org/problem?id=3414)
我用上面的模板AC了这个题目,看起来代码挺多,不过都是直接复制稍微修改一下就行,就是细节比较多,需要仔细修改。但是感觉别人写的(第二个代码)更简单,可参考。
#include<iostream>
#include<algorithm>
#include<queue>
#include<string.h>
#include<string> //使用string类型必须要加这个头文件
using namespace std;
int a, b, c; //pot的水容量
struct mi{
int x; //A的水容量
int y; //B的水容量
string s;
};
struct mi path[105][105];
int endx = 0, endy = 0;
int visited[105][105];
int bfs()
{
memset(visited, 0, sizeof(visited));
queue<mi> q;
struct mi A;
A.x = 0;
A.y = 0;
A.s = "";
q.push(A);
visited[0][0]=1;
path[0][0].x=0;
path[0][0].y=0;
path[0][0].s="";
while(!q.empty())
{
struct mi head = q.front();
q.pop();
endx = head.x;
endy = head.y;
if(head.x==c||head.y==c)
return visited[head.x][head.y];
int zx, zy;
string zs;
//fill(1)
if(head.x<a&&head.x>=0)
{
zx = a;
zy = head.y;
if(!visited[zx][zy])
{
visited[zx][zy] = visited[head.x][head.y]+1;
zs = "FILL(1)";
struct mi next;
next.x = zx;
next.y = zy;
q.push(next);
path[zx][zy].x = head.x;
path[zx][zy].y = head.y;
path[zx][zy].s = zs;
}
}
//fill(2)
if(head.y<b && head.y>=0)
{
zx = head.x;
zy = b;
if(!visited[zx][zy])
{
visited[zx][zy] = visited[head.x][head.y]+1;
zs = "FILL(2)";
struct mi next;
next.x = zx;
next.y = zy;
q.push(next);
path[zx][zy].x = head.x;
path[zx][zy].y = head.y;
path[zx][zy].s = zs;
}
}
//drop(1)
if(head.x>0&&head.x<=a)
{
zx = 0;
zy = head.y;
if(!visited[zx][zy])
{
zs = "DROP(1)";
struct mi next;
next.x = zx;
next.y = zy;
visited[zx][zy] = visited[head.x][head.y]+1;
q.push(next);
path[zx][zy].x = head.x;
path[zx][zy].y = head.y;
path[zx][zy].s = zs;
}
}
//drop(2)
if(head.y>0&&head.y<=b)
{
zx = head.x;
zy = 0;
if(!visited[zx][zy])
{
zs = "DROP(2)";
struct mi next;
next.x = zx;
next.y = zy;
next.s = zs;
visited[zx][zy] = visited[head.x][head.y]+1;
q.push(next);
path[zx][zy].x = head.x;
path[zx][zy].y = head.y;
path[zx][zy].s = zs;
}
}
//pour(1,2)
if(head.x>0&&head.y<b)
{
if(b-head.y<=head.x) // the pot j is full (and there may be some water left in the pot i)
{
zx = head.x-(b-head.y);
zy = b;
if(!visited[zx][zy])
{
zs = "POUR(1,2)";
struct mi next;
next.x = zx;
next.y = zy;
next.s = zs;
visited[zx][zy] = visited[head.x][head.y]+1;
q.push(next);
path[zx][zy].x = head.x;
path[zx][zy].y = head.y;
path[zx][zy].s = zs;
}
}
else // the pot i is empty (and all its contents have been moved to the pot j)
{
zx = 0;
zy = head.x+head.y;
if(!visited[zx][zy])
{
zs = "POUR(1,2)";
struct mi next;
next.x = zx;
next.y = zy;
next.s = zs;
visited[zx][zy] = visited[head.x][head.y]+1;
q.push(next);
path[zx][zy].x = head.x;
path[zx][zy].y = head.y;
path[zx][zy].s = zs;
}
}
}
//pour(2,1)
if(head.y>0&&head.x<a)
{
if(a-head.x<=head.y) // the pot j is full (and there may be some water left in the pot i)
{
zx = a;
zy = head.y-(a-head.x);
if(!visited[zx][zy])
{
zs = "POUR(2,1)";
struct mi next;
next.x = zx;
next.y = zy;
next.s = zs;
visited[zx][zy] = visited[head.x][head.y]+1;
q.push(next);
path[zx][zy].x = head.x;
path[zx][zy].y = head.y;
path[zx][zy].s = zs;
}
}
else // the pot i is empty (and all its contents have been moved to the pot j)
{
zx = head.x+head.y;
zy = 0;
if(!visited[zx][zy])
{
zs = "POUR(2,1)";
struct mi next;
next.x = zx;
next.y = zy;
next.s = zs;
visited[zx][zy] = visited[head.x][head.y]+1;
q.push(next);
path[zx][zy].x = head.x;
path[zx][zy].y = head.y;
path[zx][zy].s = zs;
}
}
}
}
return 0;
}
int main()
{
while(cin >> a >> b >> c)
{
string str[10000];
int ans = bfs()-1;
if(ans<=0) //这里我一开始写的ans==0,提交后显示答案错误,改成ans<=0就可以AC了
{
cout << "impossible" << endl;
}
else
{
cout << ans <<endl;
int i = 0;
int x=endx,y=endy;
while(1)
{
int k;
k=x;
str[i] = path[x][y].s;
i++;
x=path[x][y].x;
y=path[k][y].y;
if(x==0&&y==0)
{
break;
}
}
for (int j = i-1 ; j >= 0;--j)
{
cout << str[j]<<endl;
}
}
}
return 0;
}
#include <cstdio>
#include <iostream>
#include <string>
#include <cstring>
#include <queue>
using namespace std;
#define MAX 105
bool vis[MAX][MAX];
int A,B,C;
struct node
{
int a,b;
string path;
int step;
};
int gcd(int a,int b)
{
return b == 0 ? a : gcd(b,a%b);
}
void bfs(node &start)
{
queue<node> qu;
memset(vis,false,sizeof(vis));
qu.push(start);
while( !qu.empty() )
{
node st = qu.front();
qu.pop();
if(st.a == C || st.b == C)
{
printf("%d\n",st.step);
for(int i = 0; i < st.step; ++i)
{
switch(st.path[i])
{
case '0': printf("FILL(1)\n"); break;
case '1': printf("FILL(2)\n"); break;
case '2': printf("DROP(1)\n"); break;
case '3': printf("DROP(2)\n"); break;
case '4': printf("POUR(1,2)\n"); break;
case '5': printf("POUR(2,1)\n"); break;
default: break;
}
}
return ;
}
for(int i = 0; i < 6; ++i)
{
if(i == 0)
{
node tt = st;
tt.a = A;
if(!vis[tt.a][tt.b])
{
vis[tt.a][tt.b] = true;
tt.path += '0';
tt.step++;
qu.push(tt);
}
}
else if(i == 1)
{
node tt = st;
tt.b = B;
if(!vis[tt.a][tt.b])
{
vis[tt.a][tt.b] = true;
tt.path += '1';
tt.step++;
qu.push(tt);
}
}
else if(i == 2)
{
node tt = st;
tt.a = 0;
if(!vis[tt.a][tt.b])
{
vis[tt.a][tt.b] = true;
tt.path += '2';
tt.step++;
qu.push(tt);
}
}
else if(i == 3)
{
node tt = st;
tt. b = 0;
if(!vis[tt.a][tt.b])
{
vis[tt.a][tt.b] = true;
tt.path += '3';
tt.step++;
qu.push(tt);
}
}
else if(i == 4)
{
node tt = st;
if(tt.a > B-tt.b)
{
tt.a -= (B-tt.b);
tt.b = B;
}
else
{
tt.b += tt.a;
tt.a = 0;
}
if(!vis[tt.a][tt.b])
{
vis[tt.a][tt.b] = true;
tt.path += '4';
tt.step++;
qu.push(tt);
}
}
else if(i == 5)
{
node tt = st;
if(tt.b > A-tt.a)
{
tt.b -= (A-tt.a);
tt.a = A;
}
else
{
tt.a += tt.b;
tt.b = 0;
}
if(!vis[tt.a][tt.b])
{
vis[tt.a][tt.b] = true;
tt.path += '5';
tt.step++;
qu.push(tt);
}
}
}
}
}
int main()
{
while(scanf("%d %d %d",&A,&B,&C) != EOF)
{
if(C % gcd(A,B) != 0)
printf("impossible\n");
else
{
node start;
start.a = start.b = 0;
start.path = "";
start.step = 0;
bfs(start);
}
}
return 0;
}
八、拯救公主
多灾多难的公主又被大魔王抓走啦!国王派遣了第一勇士阿福去拯救她。
身为超级厉害的术士,同时也是阿福的好伙伴,你决定祝他一臂之力。你为阿福提供了一张大魔王根据地的地图,上面标记了阿福和公主所在的位置,以及一些不能够踏入的禁区。你还贴心地为阿福制造了一些传送门,通过一个传送门可以瞬间转移到任意一个传送门,当然阿福也可以选择不通过传送门瞬移。传送门的位置也被标记在了地图上。此外,你还查探到公主所在的地方被设下了结界,需要集齐K种宝石才能打开。当然,你在地图上也标记出了不同宝石所在的位置。
你希望阿福能够带着公主早日凯旋。于是在阿福出发之前,你还需要为阿福计算出他最快救出公主的时间。
地图用一个R×C的字符矩阵来表示。字符S表示阿福所在的位置,字符E表示公主所在的位置,字符#表示不能踏入的禁区,字符$表示传送门,字符.表示该位置安全,数字字符0至4表示了宝石的类型。阿福每次可以从当前的位置走到他上下左右四个方向上的任意一个位置,但不能走出地图边界。阿福每走一步需要花费1个单位时间,从一个传送门到达另一个传送门不需要花费时间。当阿福走到宝石所在的位置时,就视为得到了该宝石,不需要花费额外时间。
广搜,值得学习的地方是用二进制数来存储已获得的钻石种类。
深度优先搜索
颜色:初始白色——被发现后变为灰色——在其邻接链表被扫描完成后变为黑色。
DFS(G):time是全局变量
for each vertex u∈G.V //把所有结点涂成白色
u.color=white
u.π=NIL
time=0 //全局时间计数器进行复位
for each vertex u∈G.V //当一个白色结点被发现时,使用dfs对结点进行访问
if u.color==white
DFS-VISIT(G,u)
DFS-VISIT(G,u):
time=time+1
u.d=time //u.d发现时间
u.color=gray
for each v∈G:Adj[u] //对结点u的每个邻接结点进行检查,并在v是白色的情况下递归访问
if v.color==white
v.π=u
DFS-VISIT(G,v)
u.color=black
time=time+1
u.f=time //u.f完成时间
一:走迷宫(dfs+输出最少步数)
#include<iostream>
#include<string.h>
using namespace std;
int a[10][10] = {0};
char str[10][10];
int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
void dfs(int x,int y)
{
for(int i=0;i<4;i++)
{
int m = x+dir[i][0];
int n = y+dir[i][1];
if(m>=0&&m<=9&&n>=0&&n<=9&&str[m][n]!='#')
{
if((a[m][n]==0)||(a[x][y]+1)<a[m][n])
{
a[m][n] = a[x][y]+1;
dfs(m,n);
}
}
}
}
int main()
{
char c;
while(cin>>c)
{
str[0][0] = c;
for(int i=1;i<10;i++)
{
cin>>c;
str[0][i] = c;
}
getchar();
for(int i=1;i<10;i++)
{
for(int j=0;j<10;j++)
{
cin>>c;
str[i][j] = c;
}
getchar();
}
dfs(0,1);
cout<<a[9][8]<<endl;
memset(a,0,sizeof(a));
}
return 0;
}
二:A Knight's Journey(http://poj.org/problem?id=2488)(dfs+输出路径)
m*n的地图,找到一条路径使骑士经过所有的点,输出这条路径。
Sample Input
3
1 1
2 3
4 3
Sample Output
Scenario #1:
A1
Scenario #2:
impossible
Scenario #3:
A1B3C1A2B4C2A3B1C3A4B2C4
代码:
#include<iostream>
#include<cstring>
using namespace std;
int vis[100][100];
int map[100][2];
int dir[8][2]={{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}};
int p,q,flag;
void DFS(int x,int y,int num)
{
if(num==p*q)
{
flag=1;
for(int i=0;i<num;i++)
{
cout<<(char)(map[i][0]+'A')<<map[i][1]+1;
}
cout<<endl;
}
else
{
for(int i=0;i<8;i++)
{
int m=x+dir[i][0];
int n=y+dir[i][1];
if(m>=0&&m<q&&n>=0&&n<p&&!flag&&!vis[m][n])
{
vis[m][n]=1;
map[num][0]=m;
map[num][1]=n;
DFS(m,n,num+1);
vis[m][n]=0;
}
}
}
}
int main()
{
int t,ex=1;
cin>>t;
while(t--)
{
cin>>p>>q;
flag=0;
memset(vis,0,sizeof(vis));
memset(map,0,sizeof(map));
vis[0][0]=1;
cout<<"Scenario #"<<ex++<<":"<<endl;
DFS(0,0,1);
if(!flag) cout<<"impossible"<<endl;
cout<<endl;
}
return 0;
}
三:Children of the Candy Corn(http://poj.org/problem?id=3083)(bfs+dfs+方向优先最短步数)
给定一个迷宫,S是起点,E是终点,#是墙不可走,.可以走
先输出左转优先时,从S到E的步数
再输出右转优先时,从S到E的步数
最后输出S到E的最短步数
这个题目很灵活,看了很多解析,大部分都用了两个dfs,其实一个就够了,因为右转优先时,从S到E就相当于左转优先从E到S。所以从左转的方向开始顺时针旋转,用了一个while。
代码:
#include <cstring>
#include<iostream>
#include <algorithm>
#include <queue>
using namespace std;
//最短路径用bfs
//左转优先 右转优先 用dfs
// 0 1 2 3 对应 上右下左
//左转可表示为 d = (d+3)%4,右转可表示为 d = (d+1)%4
//左转优先时先右转,然后顺时针dfs;右转优先时先左转然后逆时针dfs
//右转优先从S到E相当于左转优先从E到S,所以右转是可以转化为左转,一个dfs即可
int dir[4][2] = { {-1,0},{0,1},{1,0},{0,-1} };
int vis[50][50];
int n,m;
char map[50][50];
struct node
{
int x,y;
int step;
};
queue <struct node> que;
int dfs(int x, int y,int ex, int ey,int d, int step)
{
if(x == ex && y == ey)
return step;
d = (d+3)%4; //优先左转
int xx = x+dir[d][0];
int yy = y+dir[d][1];
while( map[xx][yy] == '#') //顺时针旋转直到可以走为止
{
d = (d+1)%4;
xx = x+dir[d][0];
yy = y+dir[d][1];
}
return dfs(xx,yy,ex,ey,d,step+1);
}
int bfs(int sx,int sy,int ex,int ey)
{
memset(vis,0,sizeof(vis));
while(!que.empty()) que.pop();
vis[sx][sy] = 1;
struct node dcc;
dcc.x=sx;
dcc.y=sy;
dcc.step=1;
que.push(dcc);
while(!que.empty())
{
struct node u = que.front();
que.pop();
if(u.x == ex && u.y == ey)
return u.step;
for(int d = 0; d < 4; d++)
{
int x = u.x+dir[d][0];
int y = u.y+dir[d][1];
if(map[x][y] != '#' && !vis[x][y])
{
vis[x][y] = 1;
dcc.x=x;
dcc.y=y;
dcc.step=u.step+1;
que.push(dcc);
}
}
}
}
int main()
{
int test;
cin>>test;
while(test--)
{
int sx,sy,ex,ey;
cin>>m>>n;
memset(map,'#',sizeof(map));
for(int i = 1; i <= n; i++)
{
cin>>map[i]+1;
for(int j = 1; j <= m; j++)
{
if(map[i][j] == 'S')
{
sx = i;
sy = j;
}
if(map[i][j] == 'E')
{
ex = i;
ey = j;
}
}
}
cout<<dfs(sx,sy,ex,ey,0,1)<<" "<<dfs(ex,ey,sx,sy,0,1)<<" "<<bfs(sx,sy,ex,ey)<<endl;
}
return 0;
}
四:打冰球(http://poj.org/problem?id=3009)(dfs+剪枝)
要求把一个冰壶从起点“2”用最少的步数移动到终点“3”其中0为移动区域,1为石头区域,冰壶
一旦想着某个方向运动就不会停止,也不会改变方向(想想冰壶在冰上滑动),除非冰壶撞到
石头1 或者 到达终点 3
冰壶撞到石头后,冰壶会停在石头前面,此时(静止状态)才允许改变冰壶的运动方向,而该块
石头会破裂,石头所在的区域由1变为0. 也就是说,冰壶撞到石头后,并不会取代石头的位置。
终点是一个摩擦力很大的区域,冰壶若到达终点3,就会停止在终点的位置不再移动。
冰球出界就当输,超过10次还没将冰球打到目标位置也当输。求用最小次数将冰球打到目标位置,
或输出-1表示输了。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX_W = 20;
const int MAX_H = 20;
//输入
int W, H;
int g[MAX_H][MAX_W];
//起始
int sx, sy;
//目标
int ex, ey;
//4个方向
int d[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
//最终结果
int result;
bool inSquare(int x, int y){
return 0 <= x && x < H && 0 <= y && y < W;
}
//在(x, y)位置上的步数ans
void dfs(int x, int y, int ans){
//若到达目标点
if(x == ex && y == ey){
if(result > ans){
//若有更小值
result = ans;
}
return;
}
//若超过10步,或超过当前最短步数
if(ans == 10 || ans >= result) return;
//深度优先4个方向走
for(int i = 0; i < 4; i ++){
int nx = x + d[i][0], ny = y + d[i][1];
while(inSquare(nx, ny) && g[nx][ny] != 1){
//若此方向能走,则走到尽头,直至出场或撞墙
if(nx == ex && ny == ey){
//若在过程中到达目标点
ans ++;
if(result > ans){
result = ans;
}
return;
}
nx += d[i][0];
ny += d[i][1];
}
if((nx == x + d[i][0] && ny == y + d[i][1]) || !inSquare(nx, ny)){
//此方向不能走,或出场
continue;
}
//撞墙
g[nx][ny] = 0;
ans ++;
dfs(nx - d[i][0], ny - d[i][1], ans);
ans --;
g[nx][ny] = 1;
}
}
void solve(){
}
int main()
{
while(cin>>W>>H)
{
if(W == 0 || H == 0) break;
//输入地图
for(int i = 0; i < H; i ++)
{
for(int j = 0; j < W; j ++)
{
cin>>g[i][j];
if(g[i][j] == 2)
{
sx = i;
sy = j;
g[i][j] = 0;
}
if(g[i][j] == 3)
{
ex = i;
ey = j;
}
}
}
//初始化最终结果
result = 11;
//深度优先搜索
dfs(sx, sy, 0);
if(result == 11)
cout<<-1<<endl;
else
cout<<result<<endl;
}
return 0;
}
五:棋盘问题(http://poj.org/problem?id=1321)(bfs+逐行搜索)
Description
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能
放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
Input
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋
子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多
余的空白行或者空白列)。
Output
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
这个题因为棋盘形状不规则,所以有一定难度。参考网上解析是用逐行搜索, dfs的第一个参数是搜索到的行数,第二个参数是已摆放棋子个数。当已摆放棋子个数符合要求时status加1然后返回——当搜索的行数超过棋盘行数时返回——遍历每一列,放下棋子后,这一列的列标记改为true,调用dfs遍历下一行,棋子个数加一。
代码:
#include<iostream>
#include<cstring>
using namespace std;
bool chess[9][9];
bool vist_col[9]; //列标记,逐行搜索不需要行标记
int status; //结果
int n,k;
//逐行搜索
void DFS(int row,int num) //row为当前搜索行,num为已填充的棋子数
{
if(num==k) //棋子摆放达到k个满足要求,status+1
{
status++;
return;
}
if(row>n)
return;
for(int j=1;j<=n;j++)
if(chess[row][j] && !vist_col[j])
{
vist_col[j]=true; //放置棋子的列标记
DFS(row+1,num+1);
vist_col[j]=false; //回溯后,说明摆好棋子的状态已记录,当前的列标记还原
}
DFS(row+1,num); //这里是难点,当k<n时,row在等于n之前就可能已经把全部棋子放好
//又由于当全部棋子都放好后的某个棋盘状态已经在前面循环时记录了
//因此为了处理多余行,令当前位置先不放棋子,搜索在下一行放棋子的情况
return;
}
int main()
{
int i,j;
while(cin>>n>>k)
{
if(n==-1 && k==-1)
break;
memset(chess,false,sizeof(chess));
memset(vist_col,false,sizeof(vist_col));
status=0;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
char temp;
cin>>temp;
if(temp=='#')
chess[i][j]=true; // #表示棋盘区域
}
DFS(1,0); //从第一行开始深搜
cout<<status<<endl;
}
return 0;
}
拓扑排序
使用深搜来对有向无环图进行拓扑排序——指明事件的优先次序——对有向无环图进行线性排序
强连通分量
将有向图分解为强连通分量是深搜的一个经典应用