深度优先搜索(DFS)
深度优先搜索在搜索过程中访问某个顶点后,需要递归地访问此顶点的所有未访问过的相邻顶点。
初始条件下所有节点为白色,选择一个作为起始顶点,按照如下步骤遍历:
a. 选择起始顶点涂成灰色,表示还未访问
b. 从该顶点的邻接顶点中选择一个,继续这个过程(即再寻找邻接结点的邻接结点),一直深入下去,直到一个顶点没有邻接结点了,涂黑它,表示访问过了
c. 回溯到这个涂黑顶点的上一层顶点,再找这个上一层顶点的其余邻接结点,继续如上操作,如果所有邻接结点往下都访问过了,就把自己涂黑,再回溯到更上一层。
d. 上一层继续做如上操作,知道所有顶点都访问过。
用图可以更清楚的表达这个过程:(注意:图画错了,请将3->4这条路径理解成4->3)
1.初始状态,从顶点1开始
2.依次访问过顶点1,2,3后,终止于顶点3(注意,是4->3)
3.从顶点3回溯到顶点2,继续访问顶点5,并且终止于顶点5
4.从顶点5回溯到顶点2,并且终止于顶点2
5.从顶点2回溯到顶点1,并终止于顶点1
6.从顶点4开始访问,并终止于顶点4
从顶点1开始做深度搜索:
初始状态,从顶点1开始
依次访问过顶点1,2,3后,终止于顶点3(再次提醒,4->3)
从顶点3回溯到顶点2,继续访问顶点5,并且终止于顶点5
从顶点5回溯到顶点2,并且终止于顶点2
从顶点2回溯到顶点1,并终止于顶点1
从顶点4开始访问,并终止于顶点4
模板
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> using namespace std; char map[1000][1000];//用来存储图的信息 /*标记是否搜索过,本题目没有使用,因为是一次访问,直接将访问过的修改即可*/ int logo[1000][1000]; int m,n,sum; /*表示八个方向,四个方向时,将后面四组删掉就可以了*/ int dir[8][2]= {0,1, 0,-1, 1,0, -1,0, 1,1, 1,-1, -1,1, -1,-1}; void DFS(int x,int y) { if(x>=0&&y>=0&&x<n&&y<m)//这里是判断是否越界,根据题目要求改写 { if(map[x][y]=='W')//如果符合条件就继续递归。 { map[x][y]='.';//标记为‘.’防止多次访问 for(int i=0; i<8; i++)//因为八个方向,所以循环八次。 DFS(x+dir[i][0],y+dir[i][1]); } } } int main() { while(~scanf("%d%d",&n,&m)) { sum=0; memset(logo,0,sizeof(map)); getchar(); for(int i=0; i<n; i++) { for(int j=0; j<m; j++) { cin>>map[i][j]; } getchar(); } for(int i=0; i<n; i++) { for(int j=0; j<m; j++) if(map[i][j]=='W') { DFS(i,j); sum++;//计数 } } cout<<sum<<endl; } }
广度优先搜索(BFS)
广度优先搜索在进一步遍历图中顶点之前,先访问当前顶点的所有邻接结点。
a .首先选择一个顶点作为起始结点,并将其染成灰色,其余结点为白色。
b. 将起始结点放入队列中。
c. 从队列首部选出一个顶点,并找出所有与之邻接的结点,将找到的邻接结点放入队列尾部,将已访问过结点涂成黑色,没访问过的结点是白色。如果顶点的颜色是灰色,表示已经发现并且放入了队列,如果顶点的颜色是白色,表示还没有发现。
d. 按照同样的方法处理队列中的下一个结点。
基本就是出队的顶点变成黑色,在队列里的是灰色,还没入队的是白色。
用一副图来表达这个流程如下:
1.初始状态,从顶点1开始,队列={1}
2.访问1的邻接顶点,1出队变黑,2,3入队,队列={2,3,}
3.访问2的邻接结点,2出队,4入队,队列={3,4}
4.访问3的邻接结点,3出队,队列={4}
5.访问4的邻接结点,4出队,队列={ 空}
从顶点1开始进行广度优先搜索:
初始状态,从顶点1开始,队列={1}
访问1的邻接顶点,1出队变黑,2,3入队,队列={2,3,}
访问2的邻接结点,2出队,4入队,队列={3,4}
访问3的邻接结点,3出队,队列={4}
访问4的邻接结点,4出队,队列={ 空}
结点5对于1来说不可达。
模板
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #define MAX 0x3f3f3f3f using namespace std; int map[305][305];//存节点信息 int vis[305][305];//标记数组 int dir[4][2]= {-1,0, 1,0, 0,1, 0,-1};//上下左右四个方向 int end; struct node { int x,y;//两点表示节点位置 int time; } start;//入队列使用 queue<node> q;//队列,自己维护用来存储节点信息 int bfs(int x,int y) { memset(vis,0,sizeof(vis)); start.x=x,start.y=y,start.time=0;//将传递过来的0.0节点放入结构体 vis[x][y]=1;//标记为已搜过 q.push(start);//入队列 while(!q.empty()) { node now=q.front();//取队头元素 q.pop(); if(map[now.x][now.y]==MAX) { return now.time;//如果符合条件,返回;根据题意自己写符合的条件。 } for(int i=0; i<4; i++)//四个方向入队列 { start.x=now.x+dir[i][0],start.y=now.y+dir[i][1];//将第一个方向的入队列 start.time=now.time+1; if(start.x>=0&&start.y>=0&&vis[start.x][start.y]==0&&start.time<map[start.x][start.y])//判断是否越界 { vis[start.x][start.y]=1; q.push(start); } } } return -1; } int main() { int n; while(~scanf("%d",&n)) { memset(map,MAX,sizeof(map)); for(int j=0; j<n; j++) { int x,y,time; scanf("%d%d%d",&x,&y,&time); if(map[x][y]>time) map[x][y]=time; for(int i=0; i<4; i++)//自己建图过程,一般不需要自己建图 { int cx,cy; cx=x+dir[i][0],cy=y+dir[i][1]; if(cx>=0&&cy>=0) if(map[cx][cy]>time) map[cx][cy]=time; } } int ans=bfs(0,0);//从00点开始广搜,根据题目要求具体定 cout<<ans<<endl; } }
【eg】跳蚱蜢
如图 所示:
有9只盘子,排成1个圆圈。
其中8只盘子内装着8只蚱蜢,有一个是空盘。
我们把这些蚱蜢顺时针编号为 1~8
每只蚱蜢都可以跳到相邻的空盘中,
也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。
请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列,
并且保持空盘的位置不变(也就是1-8换位,2-7换位,...),至少要经过多少次跳跃?
注意:要求提交的是一个整数,请不要填写任何多余内容或说明文字。
固定套路模式的方法 运行3s左右
#include<iostream> #include<string> #include<set> #include<queue> //青蛙跳格子,采用裸广搜的方法,几秒可以出答案,但是有时间限制就不行了 //将青蛙跳看作是,圆盘跳动,这样就只有一个变量在变化了 //将圆盘看成是0,初始序列用012345678表示,在广搜的时候用set判一下重 using namespace std; struct node { string str;//局面字符串 int pos;//0的位置也就是空盘子 int step;//到达这个局面的步数 node(string str, int pos, int step) : str(str), pos(pos), step(step) {} }; int N = 9; set<string> visited;//已经搜索过的局面 queue<node> q;//用户来广搜的队列 void insertq(node no, int i)//node为新的局面,i为移动方式 { string s = no.str; swap(s[no.pos], s[(no.pos + i + 9) % 9]);//将0和目标位置数字交换 //取模是为了模拟循环的数组 //if (visited.count(s) == 0)//如果没有搜索过这个局面 if(visited.end() == visited.find(s)) { visited.insert(s); node n(s, (no.pos + i + 9) % 9, no.step + 1); q.push(n); } } int main() { //初始 0空盘在0位置 走过步数0 node first("012345678", 0, 0); q.push(first); while (!q.empty()) { node temp = q.front(); //去第一个 因为之前的pop出去了 if (temp.str == "087654321") { //结束 cout << temp.step; break; } else { //四种跳法 相当于有四个邻居 insertq(temp, 1); insertq(temp, -1); insertq(temp, 2); insertq(temp, -2); q.pop(); //四个邻居添加完之后 就可以滚蛋了 } } }
优化方法,为了节省内存,使用 int 来存储数据
#include <iostream> #include <queue> #define Maxn 1000000000 using namespace std; int s = 123456789,t = 876543219; int di[4] = {-2,-1,1,2},a[10]; bool index[Maxn]; int get_val(int *a) { int sum=0; for(int i=0; i<9; i++) { sum*=10; sum+=a[i]; } return sum; } void bfs() { int find = 0; queue<int> q; queue<char> qu; //计算步数 q.push(s); index[s] = 1; qu.push(1); while(find != 1) { int x = q.front(),cnt=8,now; int count = qu.front(); while(x>0) //将数据存入数组,方便换位置 { if(x%10==9)now=cnt; a[cnt--]=x%10; x/=10; } for(int i = 0; i<4; i++) { swap(a[now],a[(now+di[i]+9)%9]); int num = get_val(a); if(!index[num]) //判重 { if(num == t) { find = 1; cout<<count; } index[num] = 1; q.push(num); qu.push(count+1); } swap(a[now],a[(now+di[i]+9)%9]); } q.pop(); qu.pop(); } } int main() { bfs(); return 0; }