一篇文章帶你快速入門 DFS抽象深度優先搜索——C++實現

因博主水平確實有限,寫文章確實費時費力,如果對你有幫助,請點贊支持!感謝!感謝!感謝!
因博主水平確實有限,寫文章確實費時費力,如果對你有幫助,請點贊支持!感謝!感謝!感謝!
因博主水平確實有限,寫文章確實費時費力,如果對你有幫助,請點贊支持!感謝!感謝!感謝!

深度優先搜索(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皇后問題

2n皇后問題——C++詳解(這篇是精髓)

例六、炸彈引爆

題目描述:

在一個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;
}

運行結果:

另附:

快速入門DFS深度優先搜索

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章