初學演算法----深度優先搜尋地圖型題目
Maze
描述:
Acm, a treasure-explorer, is exploring again. This time he is in a special maze, in which there are some doors (at most 5 doors, represented by 'A', 'B', 'C', 'D', 'E' respectively). In order to find the treasure, Acm may need to open doors. However, to open a door he needs to find all the door's keys (at least one) in the maze first. For example, if there are 3 keys of Door A, to open the door he should find all the 3 keys first (that's three 'a's which denote the keys of 'A' in the maze). Now make a program to tell Acm whether he can find the treasure or not. Notice that Acm can only go up, down, left and right in the maze.輸入:
輸出:
樣例輸入:
4 4 S.X. a.X. ..XG .... 3 4 S.Xa .aXB b.AG 0 0
樣例輸出:
YES NO
1.關於找到結果就回歸這件事:
像這種在圖上遍歷找某個確定的點,找到點後,可以不用再找了,直接退出整個遍歷過程輸出即可,
那麼為了使達到這種效果,我們就要設定一點套路讓他去能夠一找到就退出整個過程
bool dfs (int y,int x)
{
if (maper[y][x]=='G')
{
return true;
}
if (maper[y][x]=='X' || x<1 || y<1 || x>c || y>r || vis[y][x]==1)
{
return false;
}
if (maper[y][x]>='a' && maper[y][x]<='e')
{
keyer[maper[y][x]-'a']++;
}
if (maper[y][x]>='A' && maper[y][x]<='E')
{
if (keyer[maper[y][x]-'A']!=needkey[maper[y][x]-'A'])
{
return false;
}
}
vis[y][x]=1;
for (int i=0;i<4;i++)
{
int ny=y+dy[i],nx=x+dx[i];
if (dfs(ny,nx))//看這部操作,如果我找到了,深度優先搜尋返回正確,那一定是一直返回到最終,就可以達到那種效果了
{
return true;
}
}
vis[y][x]=0;
return false;
}
當然這種操作有很多方式,根據具體情況具體設定
2.關於如何回溯與判斷條件寫哪裡這件事:
1.bool dfs(int y, int x, int step) { if (step == r * c) { return true; } for (int i = 0; i < 8; i++) { int ny = y + dy[i]; int nx = x + dx[i]; if (nx < 1 || ny < 1 || nx > c || ny > r || vis[ny][nx] == 1) { continue; } else { vis[ny][nx] = 1; step++; maper[step].row = ny; maper[step].col = nx; if (dfs(ny, nx, step)) { return true; } step--; vis[ny][nx] = 0; } } return false; } 2.bool dfs (int y,int x)
{
if (maper[y][x]=='G')
{
return true;
}
if (maper[y][x]=='X' || x<1 || y<1 || x>c || y>r || vis[y][x]==1)
{
return false;
}
if (maper[y][x]>='a' && maper[y][x]<='e')
{
keyer[maper[y][x]-'a']++;
}
if (maper[y][x]>='A' && maper[y][x]<='E')
{
if (keyer[maper[y][x]-'A']!=needkey[maper[y][x]-'A'])
{
return false;
}
}
vis[y][x]=1;
for (int i=0;i<4;i++)
{
int ny=y+dy[i],nx=x+dx[i];
if (dfs(ny,nx))
{
return true;
}
}
vis[y][x]=0;
return false;
}
我這兩段程式碼判斷條件放的位置不一樣
他們其實都是同一個道理
接下來是上述題目了:
#include <bits/stdc++.h> using namespace std;char maper[25][25]; int r, c; int keyer[5], needkey[5], everhave[5]; int vis[25][25], dy[] = {-1, 0, 1, 0}, dx[] = {0, 1, 0, -1};
bool dfs(int y, int x) { if (maper[y][x] == 'G') { return true; } for (int i = 0; i < 4; i++) { int ny = y + dy[i], nx = x + dx[i]; if (maper[ny][nx] == 'X' || nx < 1 || ny < 1 || nx > c || ny > r || vis[ny][nx] == 1) { continue; } else if (maper[ny][nx] >= 'A' && maper[ny][nx] <= 'E') { if (keyer[maper[ny][nx] - 'A'] != needkey[maper[ny][nx] - 'A']) { continue; } } else if (maper[ny][nx] >= 'a' && maper[ny][nx] <= 'e' && everhave[maper[ny][nx] - 'a'] == 0) { keyer[maper[ny][nx] - 'a']++; everhave[maper[ny][nx] - 'a'] = 1; } vis[ny][nx] = 1; if (dfs(ny, nx)) { return true; } vis[ny][nx] = 0; } return false; }
int main() { while (1) { cin >> r >> c; if (r == 0 && c == 0) { break; } else { int sx, sy; for (int i = 1; i <= r; i++) { for (int j = 1; j <= c; j++) { cin >> maper[i][j]; if (maper[i][j] == 'S') { sy = i; sx = j; } else if ('a' <= maper[i][j] && 'e' >= maper[i][j]) { needkey[maper[i][j] - 'a']++; } } } memset(vis, 0, sizeof(vis)); memset(keyer, 0, sizeof(keyer)); memset(needkey, 0, sizeof(needkey)); memset(everhave, 0, sizeof(everhave)); vis[sy][sx] = 1; if (dfs(sy, sx)) { printf("YES\n"); } else { printf("NO\n"); } } } return 0; } 次林夢葉21:54:57
我這段程式碼肯定沒錯,但是一定超時,為什麼?因為回溯實在是太多了。 想想回溯的作用是什麼?是防止退回後,再次搜尋時,本應該要到達的點,因為上一次沒有回溯而到不了
次林夢葉21:55:43
這裡走迷宮,會超時是因為走太多次相同的點了
次林夢葉21:55:53
如果我想每個點我只走一次
次林夢葉21:56:05
那我就應該不回溯
次林夢葉21:56:28
不回溯的結果就是我不能重複走點了
次林夢葉21:56:34
但是依舊能夠回退
次林夢葉21:56:38
選擇能走的點走
次林夢葉21:56:46
這樣就可以大大減少時間了
來看下大佬的寫法: #include<iostream>
#include<cstring>
using namespace std;
char a[30][30];int c[30][30],d[30][30];
int b[5],bb[5],cx[4]={1,-1},cy[4]={0,0,1,-1};
int m,n,x1,y1,f;
void ys(int x,int y)
{
if(f)return;//看這裡,大佬找到結果後立馬退出整個過程的方法
for(int i=0;i<4;i++)
{
int x2=x+cx[i],y2=y+cy[i];
if(a[x2][y2]=='G')//如果找鑰匙時找到寶藏,做標記,然後直接結束所有過程。
{
f=1;
return;
}
if(a[x2][y2]>='a'&&a[x2][y2]<='e')
{
b[a[x2][y2]-'a']--;//每找到一個鑰匙,對應的b陣列自減,值為0時代表全部集齊
a[x2][y2]='.';//將被找到的鑰匙標記為空地
}
if(a[x2][y2]=='.'&&!c[x2][y2])
{
c[x2][y2]=1; //標記找鑰匙時走過的路
ys(x2,y2);
}//由於找鑰匙時只要把所有路徑走一遍,所以不需要回溯。
}
}
void ljq(int x,int y)
{
if(f)return;//找到寶藏後直接結束
for(int i=0;i<4;i++)
{
int x2=x+cx[i],y2=y+cy[i];
if(a[x2][y2]>='A'&&a[x2][y2]<='E'&&!b[a[x2][y2]-'A']&&bb[a[x2][y2]-'A'])
{ //找到門後,如果已經集齊全部鑰匙,則把門開啟
a[x2][y2]='.';//將被開啟的門標記為空地
memset(d,0,sizeof(d));/*d陣列紀錄找門時走過的路,由於會找到新的鑰匙,可能原來沒打
開的門可以開啟,所以將d陣列清零。*/
ys(x2,y2);//每開啟一個門,從被開啟門的位置開始找鑰匙。
}
if(a[x2][y2]=='.'&&!d[x2][y2])
{
d[x2][y2]=1;
ljq(x2,y2);
}//同上 ,不需要回溯。
}
}
int main()
{
cin>>m>>n;
while(m)
{
f=0;
memset(a,0,sizeof(a));
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
memset(b,0,sizeof(b));
memset(bb,0,sizeof(bb));
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
cin>>a[i][j];
if(a[i][j]=='S')
{
x1=i;y1=j;
a[i][j]='.';
}
if(a[i][j]>='a'&&a[i][j]<='e')
{
b[a[i][j]-'a']++;
bb[a[i][j]-'a']=1;
}//b陣列紀錄每種鑰匙的數目,bb陣列紀錄有沒有出現這種鑰匙,0下標代表a,依此類推
}
ys(x1,y1); /*次林夢葉22:00:31
先在能走的地方把鑰匙都找完
次林夢葉22:00:58
而不是邊找鑰匙找到鑰匙就立馬去開門
次林夢葉22:01:00
那樣會很麻煩
次林夢葉22:01:34
然後看一下能不能開門,如果開不了門就一定找不到寶藏*/
ljq(x1,y1);
if(f)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
cin>>m>>n;
}
}