因博主水平確實有限,寫文章確實費時費力,如果對你有幫助,請點贊支持!感謝!感謝!感謝!
因博主水平確實有限,寫文章確實費時費力,如果對你有幫助,請點贊支持!感謝!感謝!感謝!
因博主水平確實有限,寫文章確實費時費力,如果對你有幫助,請點贊支持!感謝!感謝!感謝!
深度優先搜索(DFS):
深度優先搜索屬於圖算法的一種,英文縮寫爲DFS即Depth First Search.其過程簡要來說是對每一個可能的分支路徑深入到不能再深入爲止,而且每個節點只能訪問一次。
深度優先遍歷圖的方法是,從圖中某頂點v出發:
(1)訪問頂點v;
(2)依次從v的未被訪問的鄰接點出發,對圖進行深度優先遍歷;直至圖中和v有路徑相通的頂點都被訪問;
(3)若此時圖中尚有頂點未被訪問,則從一個未被訪問的頂點出發,重新進行深度優先遍歷,直到圖中所有頂點均被訪問過爲止。
框架展示:
例一、K個數的和
題目描述:
給定n個整數,要求選出K個數,使得選出來的K個數的和爲sum。
輸入:
第一行爲給定整數的數量n,要求選出整數的數量K,選出來的K個數的和sum
第二行依次輸入n個給定的整數
輸出:
滿足條件的方案數
思路:
對每一個數,枚舉選或者不選兩種情況,並且使用dfs來完成此枚舉過程。
此過程中用S記錄當前選擇的數值總和,k記錄選擇的數的個數,deep表示當前正在枚舉第幾個數是否選擇
對每一層,我們都有兩個選擇——選和不選。不同的選擇,都會使得搜索進入完全不同的分支繼續搜索。而每個狀態對應的子樹,都是這個狀態通過搜索可能達到的狀態。
代碼一如下:
#include<iostream>
using namespace std;
int n,k,sum,ans;
int a[40];
void dfs(int i,int cnt,int s){
//i爲當前正在選取第i個數,cnt表示選取了幾個數,s表示選取數的和
if(i==n){
if(cnt==k&&s==sum){
++ans;
}
return;
}
dfs(i+1,cnt,s);
dfs(i+1,cnt+1,s+a[i]);
}
int main(){
cin>>n>>k>>sum;
for(int i=0;i<n;++i)
cin>>a[i];
ans=0;
dfs(0,0,0);
cout<<ans<<endl;
return 0;
}
代碼一運行結果:
代碼二如下:
#include<iostream>
using namespace std;
int n,k,sum,ans;
int a[40];
bool xuan[40];
void dfs(int s,int cnt){
if(s==sum&&cnt==k){
++ans;
}
for(int i=0;i<n;++i){
if(!xuan[i]){
xuan[i]=1;
dfs(s+a[i],cnt+1);
xuan[i]=0;
}
}
}
int main(){
cin>>n>>k>>sum;
for(int i=0;i<n;++i)
cin>>a[i];
ans=0;
dfs(0,0);
cout<<ans<<endl;
return 0;
}
代碼二運行結果:
代碼二出錯原因:
這裏出現了重複!運行結果爲正確結果的K!倍。
爲什麼呢?原因在於這段代碼,將只是方案中元素排序順序不同的相同的方案重複計入方案總數。
例如:(2,3,4)(2,4,3)(3,2,4)(3,4,2)(4,2,3)(4,3,2)。
那麼如何解決呢?其實只需要將方案總數除以/K!即可。
DFS總結:
我們可以根據搜索狀態構建一張抽象的圖,圖上的一個頂點就是一個狀態,而圖上的邊就是狀態之間的轉移關係(進一步搜索或者回溯)。雖然dfs實在這張抽象的圖上進行的,但我們不必把這張圖真正地建立出來。
我們可以認爲,一次dfs實際上就是在搜索樹上完成了一次深度優先搜索。而在上節中的搜索樹裏的每一個狀態,記錄了兩個值—和值和個數。
例二、等邊三角形
題目描述:
有一些小木棍,長短不一,想用這些木棍拼出一個等邊三角形,並且每根木棍都要用到。
例如有長度爲1,2,3,3的4根木棍,可以讓長度爲1,2的木棍組成一條邊,另外2根分別組成兩條邊。拼成一個邊長爲3的等邊三角形。
輸入:
第一行輸入木棍數量n(3<=n<=10)
第二行輸入n根木棍的長度
輸出:
如果可以拼出等邊三角形,輸出“yes”,否則輸出“no”。
代碼如下:
#include<cstdio>
int n,sum=0;
int p[15];
bool f;
bool visited[15];
void dfs(int cnt,int s,int st){
//
if(f){
return;
}
if(cnt==3){
f=true;
return;
}
if(s==sum/3){
dfs(cnt+1,0,0);
return;
}
for(int i=0;i<n;++i){
if(!visited[i]){
visited[i]=1;
dfs(cnt,s+p[i],i+1);
visited[i]=0; //回溯,所有方案都要考慮到,所以要標記回來
}
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;++i){
scanf("%d",&p[i]);
sum+=p[i];
}
if(sum%3!=0){
printf("no\n");
}else{
dfs(0,0,0);
if(f){
printf("yes\n");
}else{
printf("no\n");
}
}
return 0;
}
運行結果:
例三、方程的解數
題目描述:
求解一個n元的高次方程:
輸出:
方程的整數解的個數
代碼如下:
#include<cstdio>
int k[5],p[5];
int n,M,ans;
long long poww(int x,int y){
long long ret=1;
for(int i=0;i<y;++i){
ret*=x;
}
return ret;
}
void dfs(int x,long long s){
if(x==n){
if(s==0){
++ans;
}
return;
}
for(int i=1;i<=M;++i){
dfs(x+1,s+k[x]*poww(i,p[x]));
}
}
int main(){
scanf("%d",&n);
scanf("%d",&M);
for(int i=0;i<n;++i){
scanf("%d%d",&k[i],&p[i]);
}
dfs(0,0);
printf("%d\n",ans);
return 0;
}
運行結果:
例四、數獨
題目描述:
標準數獨是由一個給予了提示數字的9*9網格組成,我們只需要將其空格天上數字,使得每一行、每一列以及每一個3*3宮都沒有重複的數字出現。
輸入:
一個 9 * 9 的數獨,數字之間用空格隔開。*表示需要填寫的數字。
輸出:
輸出一個 9 * 9 的數獨,把輸入中的*替換成需要填寫的數字即可。
代碼如下:
#include<cstdio>
bool vx[10][10],vy[10][10],vv[10][10];
//vx[i][j]表示第i行數字j已佔用
//vx[i][j]表示第i列數字j已佔用
//vv[i][j]表示區域i數字j已佔用
bool f; //是否找到一個解
char s[10][10];
void dfs(int x,int y){ //從左向右從上向下
if(f) //已找到一個解
return;
if(x==9){ //向下走出界直接輸出
f=1;
for(int i=0;i<9;++i){ //輸出避免填好一組解,遞歸嘗試下一組,改變原來正確的值
for(int j=0;j<9;++j){
if(j!=8)
printf("%c ",s[i][j]);
else
printf("%c\n",s[i][j]);
}
}
return;
}
if(y==9){ //向右走出界
dfs(x+1,0); //填下一行第一個數字
return;
}
if(s[x][y]!='*'){ //非*不用填,直接跳過
dfs(x,y+1);
return;
}
for(int i=1;i<=9;++i){ //檢查每個數字是否可填
if(!vx[x][i]&&!vy[y][i]&&!vv[x/3*3+y/3][i]){
s[x][y]='0'+i; //可填即填入
vx[x][i]=1;
vy[y][i]=1;
vv[x/3*3+y/3][i]=1;
dfs(x,y+1); //填下一個數字
vx[x][i]=0; //因爲填入的數字可能不合法,需要回溯
vy[y][i]=0;
vv[x/3*3+y/3][i]=0;
s[x][y]='*';
}
}
}
int main(){
for(int i=0;i<9;++i){ //讀入
for(int j=0;j<9;++j){
scanf(" %c",&s[i][j]); //空格喫掉空白字符
}
}
for(int i=0;i<9;++i){ //標記已使用的數字
for(int j=0;j<9;++j){
if(s[i][j]!='*'){
vx[i][s[i][j]-'0']=1;
vy[j][s[i][j]-'0']=1;
vv[i/3*3+j/3][s[i][j]-'0']=1;
}
}
}
dfs(0,0); //考慮(0,0)位置
return 0;
}
運行結果:
例五、2n皇后問題
例六、炸彈引爆
題目描述:
在一個n*n的方格地圖上,某些方格上放置着炸彈。手動引爆一個炸彈以後,炸彈會把炸彈所在的行和列上的所有炸彈引爆,被引爆的炸彈又能引爆其他炸彈,這樣連鎖下去。
現在爲了引爆地圖上的所有炸彈,需要手動引爆其中一些炸彈,爲了把危險程度降到最低,請算出最小手動引爆多少個炸彈可以把地圖上的所有炸彈引爆。
輸入:
第一行兩個整數 n , m,表示地圖行列數
接下來n行,每行輸入一個長度爲m的字符串,表示地圖信息。0表示沒有炸彈,1表示有炸彈
輸出:
輸出一個整數,表示最少需要手動引爆的炸彈數量
代碼如下:
#include<cstdio>
int n,m,cnt;
char mp[1005][1005];
bool vx[1005],vy[1005];
//xv[i]表示第i行是否被訪問,vy[i]表示第i列是否被訪問
void dfs(int x,int y){
mp[x][y]='0';
if(!vx[x]){
vx[x]=1;
for(int i=0;i<m;++i){
if(mp[x][i]=='1'){
dfs(x,i);
}
}
}
if(!vy[y]){
vy[y]=1;
for(int i=0;i<n;++i){
if(mp[i][y]=='1'){
dfs(i,y);
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)
scanf("%s",mp[i]);
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
if(mp[i][j]=='1'){
++cnt;
dfs(i,j);
}
}
}
printf("%d\n",cnt);
return 0;
}