深度優先搜索實現方式有很多種,我學習的是藉助於遞歸來完成的,先講一個簡單的例子:給定一個整數 n,輸出n的全排列;
先用深搜實現一下:
#include<stdio.h>
int book[100] ,a[100],sum = 0; 用a數組來儲存n的排列,用book數組記錄a數組中有哪些元素
void dfs(int n,int step){
int i;
if (step==n){ //這是判斷打印的條件 即 當數組內的元素達到n時結束
sum++; //用來記錄排列的個數
for (i=0;i<step;i++)
printf("%d ",a[i]);
printf("\n");
return ;
}
for (i=1;i<=n;i++){
if (book[i] == 0){ //判斷數組a中是否有 i 元素
a[step] = i;//若沒有該元素則儲存
book[i] = 1;//用book標記
dfs(n,step+1);//繼續進行下一步的儲存
book[i] = 0;//這一步很重要 要記得將該元素取消標記,因爲回溯時要將後面的元素重新排列。
}
}
}
看了代碼是不是對深搜有所瞭解了? 深搜就是按定一種方式一隻運行下去,直到滿足某種特定條件或者走不下去時回溯,按另一條路繼續運行。
看代碼看累了?給你講個故事放鬆一下:
話說有一條可愛的狗狗,一天它去散步,走進了了一座神祕的宮殿,這座宮殿輝煌無比想一條迷宮一樣,狗狗在裏面發現了一塊誘人不對..誘狗的骨頭,它急忙跑過去,但當它撿起,,叼起來時,感覺宮殿搖晃起來,它能感覺到地面在下沉,狗狗明白了這是一個陷阱,它拼命的想要跑出這條迷宮。這個迷宮的出口離它有一定距離,並且門是關閉的,只有再那特定的一秒鐘纔開啓,當小狗停留在地板上超過一秒是它就會掉下去,所以它必須再那特定的時刻恰好到達門口。已知它的位置和這條迷宮的佈局和門開啓的時間(s),它一秒只能移動一塊地板的距離,並且有障礙物的阻攔,問它能不能逃出去?
故事聽完要做題了,就是上面那道題,這道題是HDUOJ的problem-1010。
輸入由多個測試用例組成。每個測試用例的第一行包含三個整數n、m和t(1<n,m<7;0<t<50),分別表示迷宮的大小(行數和列數)和門將打開的時間。接下來的N行給出迷宮佈局,每行包含M個字符。字符是下列之一:
'X': 障礙物; |
輸入以3個0未結束標誌,此時測試樣例不被處理。
可以逃出輸出YES,不能逃出 輸出 NO。
用深度優先搜索實現一下:
#include<stdio.h>
#include<string.h>
int book[8][8],map[8][8];//二維數組book標記該點是否走過,二維map來儲存迷宮佈局
int next[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};//記錄狗狗的下一步怎麼走:上,下,左,右
int flag = 0;//標誌狗狗是否逃出去了
void dfs(int x,int y,int n,int m,int nt,int t){//x和y記錄狗狗的位置,n,m記錄迷宮的大小,nt記錄狗狗所處時間,t代表出口關閉時間
book[x][y] = 1;
int i,tx,ty;
if (nt==t&&map[x][y]=='D'){
flag = 1;
return ;
}
if (nt>t)
return ;
for (i=0;i<4;i++){
tx = x + next[i][0];
ty = y + next[i][1];
if (book[tx][ty]==1||tx<0||tx>=n||ty<0||ty>=m||map[tx][ty]=='X')
continue;
dfs(tx,ty,n,m,nt+1,t);
book[tx][ty] = 0;
if (flag==1)
return ;
}
}
int main (){
int n,m,t,i,j,x,y;
scanf("%d%d%d",&n,&m,&t);
while (!(n==0&&m==0&&t==0)){
flag = 0;
for(i=0;i<n;i++)
for(j=0;j<m;j++){
scanf(" %c",&map[i][j]);//特別說明一下%c前面的空格是來吸收回車鍵的
if (map[i][j]=='S'){
x = i;
y = j;
}
}
dfs(x,y,n,m,0,t);
if (flag==1)
printf("YES\n");
else printf("NO\n");
memset(book,0,sizeof(int)*64);
scanf("%d%d%d",&n,&m,&t);
}
}
這樣就結束了?一提交,發現超時了,原因是執行了太多次函數了,怎麼辦?接下來講一下,深度優先搜索的奇偶剪枝。
奇偶剪枝:
看上圖假設s座標(1,1),e座標(5,5),那麼s到e的最短路徑爲:(5-1) + (5-1) = 8 是個偶數。這裏有個結論:那麼從點s到點e無論怎麼走,所走的步數肯定也是偶數。
那麼如果規定了步數k看能否恰好到達,首先他得有個前提,那就是規定步數與起點到終點的最短路徑d 奇偶性肯定是一樣的。
所以我們上題中每走一步就可以提前判定它是不是在規定步數一定不能恰好到達。
閒話少說上代碼(深度優先搜索+奇偶剪枝):
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
int book[8][8],map[8][8];
int next[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
int flag = 0;
int ex,ey;
void dfs(int x,int y,int n,int m,int nt,int t){
book[x][y] = 1;
int i,tx,ty;
if (nt==t&&map[x][y]=='D'){
flag = 1;
return ;
}
if (nt>t)
return ;
if (flag)
return ;
int tem=t-nt-abs(ex-x)-abs(ey-y);//這裏就是奇偶剪枝的核心代碼 t-nt 代表了剩餘步數,abs(ex-x)+abs(ey-y)是當前位置到終點的最短路徑;
if(tem<0||tem&1)//終止條件:剩餘步數比最短路徑少,剩餘步數與最短路徑奇偶性不同;
return;
for (i=0;i<4;i++){
tx = x + next[i][0];
ty = y + next[i][1];
if (book[tx][ty]==1||tx<0||tx>=n||ty<0||ty>=m||map[tx][ty]=='X')
continue;
dfs(tx,ty,n,m,nt+1,t);
book[tx][ty] = 0;
if (flag==1)
return ;
}
}
int main (){
int n,m,t,i,j,x,y;
scanf("%d%d%d",&n,&m,&t);
while (!(n==0&&m==0&&t==0)){
flag = 0;
for(i=0;i<n;i++)
for(j=0;j<m;j++){
scanf(" %c",&map[i][j]);
if (map[i][j]=='S'){
x = i;
y = j;
}
if (map[i][j]=='D'){
ex = i;
ey = j;
}
}
dfs(x,y,n,m,0,t);
if (flag==1)
printf("YES\n");
else printf("NO\n");
memset(book,0,sizeof(book));
scanf("%d%d%d",&n,&m,&t);
}
}