采用搜索算法解決問題時,需要構造一個表明狀態特征和不同狀態之間關系的數據結構,這種數據結構稱為結點。不同的問題需要用不同的數據結構描述。
根據搜索問題所給定的條件,從一個結點出發,可以生成一個或多個新的結點,這個過程通常稱為擴展。結點之間的關系一般可以表示成一棵樹,它被稱為解答樹。搜索算法的搜索過程實際上就是根據初始條件和擴展規則構造一棵解答樹並尋找符合目標狀態的結點的過程。
深度優先搜索DFS(Depth First Search)是從初始結點開始擴展,擴展順序總是先擴展最新產生的結點。這就使得搜索沿着狀態空間某條單一的路徑進行下去,直到最后的結點不能產生新結點或者找到目標結點為止。當搜索到不能產生新的結點的時候,就沿着結點產生順序的反方向尋找可以產生新結點的結點,並擴展它,形成另一條搜索路徑。
為了便於進行搜索,要設置一個表存儲所有的結點。由於在深度優先搜索算法中,要滿足先生成的結點后擴展的原則,所以存儲結點的表一般采用棧這種數據結構。
深度優先搜索算法的搜索步驟一般是:
(1)從初始結點開始,將待擴展結點依次放到棧中。
(2)如果棧空,即所有待擴展結點已全部擴展完畢,則問題無解,退出。
(3)取棧中最新加入的結點,即棧頂結點出棧,並用相應的擴展原則擴展出所有的子結點,並按順序將這些結點放入棧中。若沒有子結點產生,則轉(2)。
(4)如果某個子結點為目標結點,則找到問題的解(這不一定是最優解),結束。如果要求得問題的最優解,或者所有解,則轉(2),繼續搜索新的目標結點。
深度優先搜索算法的框架一般為:
void DFS()
{
棧S初始化;
初始結點入棧;
置搜索成功標志flag= false;
while (棧不為空 && !flag)
{
棧頂元素出棧,賦給current;
while (current 還可以擴展)
{
由結點current擴展出新結點new;
if (new 重復於已有的結點狀態) continue;
new結點入棧;
if (new結點是目標狀態)
{
置flag= true; break;
}
}
}
if (flag) 輸出結果;
else 輸出無解信息;
}
由於深度優先搜索是一個遞歸的過程,因此通常也使用遞歸函數來實現。一般框架為:
void DFS(結點類型 current) // 從結點current出發遞歸地深度優先搜索
{
置visited[current]=true; // 表示結點current已被處理
if (current結點是目標狀態)
{
置搜索成功標志flag= false;
return ;
}
while (current 還可以擴展)
{
由current結點擴展出新結點new;
if (! visited[new]) DFS(new); // 對未處理的結點new遞歸調用DFS
}
置visited[current]=flase; // 表示結點current以后可能被處理
}
深度優先搜索中擴展結點的原則是先產生的后擴展。因此,深度優先搜索第一個找到的解,並不一定是問題的最優解,要搜索完整個狀態空間,才能確定哪個解是最優解。
【例1】黑色方塊
有一個寬為W、高為H的矩形平面,用黑色和紅色兩種顏色的方磚鋪滿。一個小朋友站在一塊黑色方塊上開始移動,規定移動方向有上、下、左、右四種,且只能在黑色方塊上移動(即不能移到紅色方塊上)。編寫一個程序,計算小朋友從起點出發可到達的所有黑色方磚的塊數(包括起點)。
例如,如圖1所示的矩形平面中,“#”表示紅色磚塊,“.”表示黑色磚塊,“@”表示小朋友的起點,則小朋友能走到的黑色方磚有28塊。
(1)編程思路。
采用非遞歸的深度優先搜索法解決這個問題。
用數組s模擬棧操作,棧頂指針為top,初始時,top=-1,表示棧空。
入棧操作為 s[++top]=t;
出棧操作為 t=s[top--] 。
程序中定義方磚的位置坐標(x,y)為Node類型,定義數組int visit[N][N]標記某方磚是否已走過,visit[i][j]=0表示坐標(i,j)處的方磚未走過,visit[i][j]=1表示坐標(i,j)處的方磚已走過。初始時visit數組的所有元素值均為0。
具體算法步驟為:
① 將出發點(startx,starty)入棧s,且置visit[startx][starty]=1,表示該處的方磚已被處理,以后不再重復搜索。
② 將棧s的棧頂元素出棧,得到一個當前方磚cur,黑色方磚計數(sum++),沿其上、下、左、右四個方向上搜索未走過的黑色方磚,將找到的黑色方磚的坐標入棧。
③ 重復執行②,直至棧s為空,則求出了所有能走過的黑色方磚數。
(2)源程序及運行結果。
#include <iostream>
using namespace std;
#define N 21
struct Node
{
int x;
int y;
};
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
char map[N][N];
int visit[N][N];
int dfs(int startx, int starty,int w,int h)
{
Node s[N*N],cur,next; // s為棧
int top,i,x,y,sum; // top為棧頂指針
top=-1; // 棧S初始化
sum=0;
cur.x=startx; cur.y=starty;
visit[startx][starty]=1;
s[++top]=cur; // 初始結點入棧;
while(top>=0) // 棧不為空
{
cur=s[top--]; // 棧頂元素出棧
sum++; // 方磚計數
for (i=0;i<4;i++)
{
x=cur.x+dx[i]; y=cur.y+dy[i];
if(x >=0 && x<h && y>=0 && y<w && map[x][y]!='#' && visit[x][y]==0)
{
visit[x][y] = 1;
next.x=x; next.y=y; // 由cur擴展出新結點next
s[++top]=next; // next結點入棧
}
}
}
return sum;
}
int main()
{
int i,j,pos_x,pos_y,w,h,sum;
while(1)
{
cin>>w>>h;
if (w==0 && h==0) break;
for(i=0;i<h;i++)
{
for(j=0;j<w;j++)
{
cin>>map[i][j];
if (map[i][j]=='@')
{
pos_x = i; pos_y = j;
}
visit[i][j] = 0;
}
}
sum=dfs(pos_x, pos_y,w,h);
cout<<sum<<endl;
}
return 0;
}
編譯並執行以上程序,可得到如下所示的結果。
8 5
......#.
..##..@#
...#....
#......#
.#....#.
28
0 0
也可以采用遞歸的方法編寫程序。
(3)深度優先搜索采用遞歸函數實現的源程序。
#include <iostream>
using namespace std;
#define N 21
char map[N][N];
int visit[N][N];
int w,h,sum;
void dfs(int x, int y)
{
if(x >=0 && x<h && y>=0 && y<w && map[x][y]!='#' && visit[x][y]==0)
{
visit[x][y] = 1;
sum++;
dfs(x+1,y); // 遞歸訪問四個方向的磚塊
dfs(x-1,y);
dfs(x,y+1);
dfs(x,y-1);
}
}
int main()
{
int i,j,pos_x,pos_y;
while(1)
{
cin>>w>>h;
if (w==0 && h==0) break;
for(i=0;i<h;i++)
{
for(j=0;j<w;j++)
{
cin>>map[i][j];
if (map[i][j]=='@')
{
pos_x = i; pos_y = j;
}
visit[i][j] = 0;
}
}
sum = 0;
dfs(pos_x, pos_y);
cout<<sum<<endl;
}
return 0;
}