DFS(Deep First Search)深度優先搜索
深度優先遍歷(dfs)是對一個連通圖進行遍歷的算法。它的思想是從一個頂點開始,沿着一條路一直走到底,如果發現不能到達目標解,那就返回到上一個節點,然后從另一條路開始走到底,這種盡量往深處走的概念即是深度優先的概念。
簡而言之:不撞南牆不回頭
模板如下:
void dfs(int t)//t代表目前dfs的深度
{
if(滿足輸出條件||走不下去了)
{
輸出解;
return;
}
else
{
for(int i=1;i<=嘗試方法數;i++)
if(滿足進一步搜索條件)
{
為進一步搜索所需要的狀態打上標記;
dfs(t+1);
恢復到打標記前的狀態;//也就是說的{回溯一步}
}
}
}
例題一:洛谷P1219八皇后
#include<bits/stdc++.h>
using namespace std;
int a[14],b[14],c[29],d[29];//分別存橫、列、左對角線、右對角線訪問標記
int n;
int cnt=0;
void print()
{
cnt++;
if(cnt<=3)
{
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
cout<<endl;
}
}
void dfs(int k)
{
int i=k;
for(int j=1;j<=n;j++)
{
if(b[j]==0&&c[i+j]==0&&d[i-j+n]==0)//滿足未被訪問
{
a[i]=j;
b[j]=1;c[i+j]=1;d[i-j+n]=1;//分別在豎排,左對角線,右對角線打上標記
if(k<n)
dfs(k+1); //放置下一橫排的皇后
else if(k==n)
print();
b[j]=0;c[i+j]=0;d[i-j+n]=0;//回溯,標記重新置為0
}
}
}
int main()
{
cin>>n;
dfs(1);
cout<<cnt;
return 0;
}
例題二:牛客網小雨的矩陣
#include<bits/stdc++.h>
using namespace std;
long long a[52][52];
int n;
set<int> s; //set集合中數據唯一且有序
void dfs(int x,int y,int sum){
if(x==n && y==n){
s.insert(sum); //走到(n,n)則把sum插入集合s
return;
}
if(x+1<=n){
dfs(x+1,y,sum+a[x+1][y]);//向下走
}
if(y+1<=n){
dfs(x,y+1,sum+a[x][y+1]);//向右走
}
}
int main() {
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j]; //錄入矩陣
}
}
dfs(1,1,a[1][1]); //開始深搜
cout<<s.size()<<endl;//輸出集合s的大小
return 0;
}
BFS(Breath First Search)廣度優先搜索
廣度優先搜索較之深度優先搜索之不同在於,深度優先搜索旨在不管有多少條岔路,先一條路走到底,不成功就返回上一個路口然后就選擇下一條岔路,而廣度優先搜索旨在面臨一個路口時,把所有的岔路口都記下來,然后選擇其中一個進入,然后將它的分路情況記錄下來,然后再返回來進入另外一個岔路,並重復這樣的操作。
簡而言之:地毯式搜索或者說像水波紋一樣四散開來
模板如下:
//通常用隊列queue實現,或者有些時候用數組模擬隊列
void bfs()
{
初始化隊列q
q.push(起點);
標記上起點;
while(!q.empty())
{
取隊首元素u;
q.pop();//隊首元素出隊
for(int i=0;i<可以走的方向數;i++)
{
if(下一步滿足邊界內,未訪問等條件)
{
q.push();//該點入隊
標記上該點;
...
}
}
}
}
例題一:洛谷P1443馬的遍歷
此題要求馬從某點到達某點的最少要走幾步,優先用bfs
#include<bits/stdc++.h>
using namespace std;
int h[8]={-2,-1,1,2,-2,-1,1,2},z[8]={1,2,2,1,-1,-2,-2,-1};//8個方向
int vis[410][410];
int cnt[410][410];//記錄到達每個坐標點的步數
queue<pair<int,int> >q;
int n,m;
void bfs()
{
while(!q.empty())
{
int x=q.front().first;
int y=q.front().second;
q.pop();
for(int i=0;i<8;i++)
{
int xx=x+h[i];
int yy=y+z[i];
if(xx>n||xx<1||yy>m||yy<1||vis[xx][yy]==1)continue;//到達邊界或已經訪問則跳過此次循環
q.push(make_pair(xx,yy));
vis[xx][yy]=1;
cnt[xx][yy]=cnt[x][y]+1;
}
}
}
int main()
{
memset(cnt,-1,sizeof(cnt));
int x,y;
cin>>n>>m>>x>>y;
vis[x][y]=1;
cnt[x][y]=0;
q.push(make_pair(x,y));
bfs();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
printf("%-5d",cnt[i][j]);//控制格式
}
cout<<endl;
}
return 0;
}
例題二:洛谷P1162填塗顏色
此題的關鍵是通過廣搜把 1 外圍的 0 打上標記
#include<bits/stdc++.h>
using namespace std;
int h[4]={-1,0,1,0},z[4]={0,-1,0,1};
int n,a[35][35];
queue<pair<int,int> >q;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
for(int i=0;i<=n+1;i++)//在四周加0,避免在角落的0搜不過去
{
a[0][i]=0;
a[n+1][i]=0;
a[i][0]=0;
a[n+1][i]=0;
}
q.push(make_pair(0,0));
while(!q.empty())
{
int x=q.front().first;
int y=q.front().second;
q.pop();
for(int i=0;i<4;i++)
{
int x2=x+h[i];
int y2=y+z[i];
if(x2>=0&&y2>=0&&x2<=n+1&&y2<=n+1&&a[x2][y2]==0)
{
a[x2][y2]=-1;//1外圍的0標志為-1
q.push(make_pair(x2,y2));
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(a[i][j]==-1)cout<<"0 ";
else if(a[i][j]==1)cout<<"1 ";
else if(a[i][j]==0)cout<<"2 ";
}
cout<<endl;
}
return 0;
}
綜上,其實很多題dfs和bfs都可以解,但是在最短(優)路徑問題上最好用廣度優先bfs