DFS深度优先搜索(暴搜)--栈

BFS宽度优先搜索 --队列(“最短距离”-bfs第一个搜到的点)--最小步数/距离/操作次数

dfs最重要的是找顺序

递推/递归/DFS

全排列型枚举

依次枚举每个位置应该放哪个数(用状态数组存状态)

指数型枚举

从1-n依次考虑每个选或者不选

组合型枚举

依次枚举每个位置放哪个数

练习1

P1036 [NOIP 2002 普及组] 选数 - 洛谷--组合型枚举例题

注意素数判断方法

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=25;
int n,k;
int a[N];
int c[N]; 
int cnt=0;
bool check(int n) {
	if(n<2)return false; 
    for(int i = 2; i * i <= n; i++) {  // 只检查到 sqrt(n)
        if(n % i == 0) return false;
    }
    return true;
}
void dfs(int x,int start)
{
	if(x==k)
	{
		int sum=0; 
		for(int i=0;i<k;i++)
		{
			sum+=c[i];
		}
		if(check(sum))cnt++;
		return;
	}
	for(int i=start;i<n;i++)
	{
		c[x]=a[i];
		dfs(x+1,i+1);
		c[x]=0; 
	}
}
int main()
{
	cin>>n>>k;
	for(int i=0;i<n;i++)
	{
		cin>>a[i];
	}
	dfs(0,0);
	cout<<cnt<<endl;
	return 0;
} 

练习2

lP2089 烤鸡 - 洛谷

依次枚举每个位置上的调料数

#include<iostream>
using namespace std;
int n;
int a[10];
int count1=0;
void dfs1(int pos,int sum)
{
	if(sum>n)return;
	if(sum+(10-pos)*3<n)return;
	if(sum+(10-pos)*1>n)return;
	if(pos==10)
	{
		if(sum==n)count1++;
        return;
	}
	for(int i=1;i<=3;i++)
	{
		dfs1(pos+1,sum+i);
	}
}

void dfs2(int pos,int sum)
{
	if(sum>n)return;
	if(sum+(10-pos)*3<n)return;
	if(sum+(10-pos)*1>n)return;
	if(pos==10)
	{
        if(sum==n){
		for(int i=0;i<10;i++)
		{
			cout<<a[i];
			if(i<9)cout<<' ';
		}
		cout<<endl;
        }
        return;
	}
	for(int i=1;i<=3;i++)
	{
		a[pos]=i;
		dfs2(pos+1,sum+i);
	}
}
int main()
{
	cin>>n;
	dfs1(0,0);
	cout<<count1<<endl;
    if(count1!=0){
	dfs2(0,0);
    }
	return 0;
}

练习3

P1088 [NOIP 2004 普及组] 火星人 - 洛谷

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10010;
int n,m;
int a[N];
int ans[N];
bool st[N];
int res;
bool return0=false;//剪枝条件 
void dfs(int x)
{
	if(return0)return;
	if(x>n)
	{
		res++;
		if(res==m+1)
		{
			return0=true;
			for(int i=1;i<=n;i++)
			{
				cout<<ans[i]<<' ';
			}
		}
		return;
	}
	
	for(int i=1;i<=n;i++)
	{
		if(res==0)i=a[x]; 
		if(!st[i])
		{
			st[i]=true;
			ans[x]=i;
			dfs(x+1);
			st[i]=false;
			ans[x]=0;
		}
	
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	dfs(0);
	return 0;
}

练习4

P1149 [NOIP 2008 提高组] 火柴棒等式 - 洛谷

要满足两个条件--A+B=C而且A火柴+B火柴+C火柴=n-4

依次位置选数

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10010;
int n;
int a[N];
int res=0;
int nums[N]={6,2,5,5,4,5,6,3,7,6};

//计算火柴棍数量
int col(int x)
{
	if(nums[x]>0)return nums[x];
	int sumfire=0;
	while(x)
	{
		sumfire+=nums[x%10];
		x/=10;
	}
	return sumfire;
} 

void dfs(int x,int sum)
{
	if(sum>n)return; 
	if(x>3)
	{
		if(a[1]+a[2]==a[3]&&sum==n)
		{
			res++;
		}
		return;
	}
	for(int i=0;i<10010;i++)
	{
		a[x]=i;
		dfs(x+1,sum+col(i));
		a[x]=0;
	}
	
}
int main()
{
	cin>>n;
	n-=4;
	dfs(1,0);
	cout<<res<<endl;
	return 0;
}

练习5

P2036 [COCI 2008/2009 #2] PERKET - 洛谷

依次列举每个位置的调料放还是不放

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=15;
int n;
int ans=0x7f7f;
struct node{
	int s,b;
}a[N];
void dfs(int x,int s1,int b1)
{
	if(x>n)
	{
        if(s1==1&&b1==0)return;
		ans=min(ans,abs(s1-b1));
		return;
	}
	dfs(x+1,s1*a[x].s,b1+a[x].b);
    dfs(x+1,s1,b1);
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].s>>a[i].b;
	}
	dfs(1,1,0);
	cout<<ans<<endl;
	return 0;
}

练习6

1114. 棋盘问题 - AcWing题库

这个棋盘问题---依次列举每一行的位置上放不放以及放哪一列上

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10;
int n,k;
char g[N][N];
bool st[N];
int res;
int cnt=0;//记录当前放几个棋子 
//x表示枚举到当前哪一行 
void dfs(int x,int cnt)
{
	if(cnt==k)
	{
		res++;
		return;
	}
	
	if(x>=n)return;
	//枚举列 
	for(int i=0;i<n;i++)
	{
		if(!st[i]&&g[x][i]=='#')//第i列没放过棋子且是# 
		{
			st[i]=true;
			dfs(x+1,cnt+1);
			st[i]=false;
		}
	} 
	dfs(x+1,cnt); 
}

int main()
{
	while(cin>>n>>k&&n>0&&k>0){
	    for(int i=0;i<n;i++)
	    {
		    for(int j=0;j<n;j++)
		    {
			    cin>>g[i][j];
		    }
	    }
	    res=0;
	    dfs(0,0);
	    cout<<res<<endl;
	}
	return 0;
}

练习7

P1025 [NOIP 2001 提高组] 数的划分 - 洛谷

通过给的样例来看我们可以发现他很像组合型枚举(但是数是可以重复的)

于是一种方法就出来了----但是有两个测试样例会超时

通过nowsum进行剪枝

如果在当前位置nowsum加上后面位置的最大值都<n的话那么一定不会成功所以就提前剪枝

//组合型枚举 
#include<iostream>
using namespace std;
const int N=25;
int n;
int r;
int a[N];
int st[N];
int cnt=0;
void dfs(int x,int start,int nowsum)
{
	if(x>r)
	{
		if(nowsum==n)cnt++;
		return;
	}
	
	for(int i=start;nowsum+i*(r-x+1)<=n;i++)
	{
		a[x]=i;
		dfs(x+1,i,nowsum+i);
		a[x]=0;
	}
}
int main()
{
	cin>>n>>r;
	dfs(1,1,0);
	cout<<cnt;
	return 0;
 } 

练习8**

P1019 [NOIP 2000 提高组] 单词接龙(疑似错题) - 洛谷

这个题目稍微难一点用到了字符串的知识

看题目我们可以发现其实要满足两个条件

1.怎么判断一个单词能接到另一个单词的后面

这个我们可以用字符串的substr(开始位置,长度)截取字串来实现

2.怎么记录单词的使用次数(因为题目说每个单词最多只能用两次)数组used[i]表示次数

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=25;
int n;
string words[N];
int used[N];//记录每个单词的使用次数
int g[N][N];//存储第i个单词能否接到第j个单词的后面 ,g[i][j]就是两个单词重合的长度 
int res=0;
void dfs(string d,int x)
{
	res=max(res,(int)d.size());
	used[x]++;
	for(int i=0;i<n;i++)
	{
		if(g[x][i]&&used[i]<2)
		{
			dfs(d+words[i].substr(g[x][i]),i);
		}
	}
	used[x]--;
}

int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>words[i];
	} 
	char start;
	cin>>start;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			string a=words[i];
			string b=words[j];
			for(int k=1;k<min(a.size(),b.size());k++)
			{
				if(a.substr(a.size()-k,k)==b.substr(0,k))
				{
					g[i][j]=k;
					break;
				}
			}
		}
	 } 
	 
	 for(int i=0;i<n;i++)
	 {
	 	if(words[i][0]==start)
	 	{
	 		dfs(words[i],i);
		}
	  } 
	  cout<<res<<endl;
	  return 0;
 
} 

迷宫问题

DFS 解决走迷宫问题的实现步骤:

数据结构准备:

①迷宫表示:使用二维数组 maze[m][n] 存储迷宫的布局,0 或 1 表示通道或墙壁。

②标记数组:使用 visited[m][n] 标记节点是否已访问,避免重复访问。

③方向数组:使用 dx[] 和 dy[] 表示上下左右四个方向的偏移量,如 dx = {-1, 0, 1, 0},dy = {0, 1, 0, -1},表示上、右、下、左四个方向的坐标变化。

递归函数定义:

①dfs(int x, int y) 是核心递归函数,其中 x 和 y 表示当前节点的坐标。

②函数首先检查当前节点是否为终点,如果是则表示找到路径;然后检查是否越界、是否是墙壁或已访问,如果都不是,则标记为已访问,并递归调用 dfs 函数对相邻节点进行搜索。 

搜索过程:

①从起点开始调用 dfs(start_x, start_y)。

②对于当前节点 (x, y),依次尝试向四个方向移动(根据 dx 和 dy),并对满足条件(未越界、不是墙壁、未访问)的新节点调用 dfs 函数。

③如果某一方向的递归调用找到了路径,返回 true,否则回溯,将当前节点标记为未访问(即取消标记),尝试其他方向。

练习1

P1683 入门 - 洛谷

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=25;
int n,m;
char g[N][N];

int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};

int cnt=0;

void dfs(int i,int j)
{
	if(i<0||i>=n||j<0||j>=m||g[i][j]=='#')return;
	g[i][j]='#';
	cnt++;
	for(int k=0;k<4;k++)
	{
		int x=i+dx[k];
		int y=j+dy[k];
		dfs(x,y);
	}
}

int main()
{
	cin>>m>>n;
	int x=-1,y=-1;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			cin>>g[i][j];
			if(g[i][j]=='@')
			{
				x=i,y=j;
			}
		}
	}
	dfs(x,y);
	cout<<cnt<<endl;
	return 0;
	
}

练习2

P1596 [USACO10OCT] Lake Counting S - 洛谷

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
int n,m;
int cnt=0;
char g[N][N];
bool st[N][N];
int dx[8]={0,0,1,-1,1,1,-1,-1};
int dy[8]={1,-1,0,0,1,-1,1,-1};

void dfs(int i,int j)
{
	if(i<0||i>=n||j<0||j>=m||g[i][j]=='.'||st[i][j])return;
	st[i][j]=true;
	for(int k=0;k<8;k++)
	{
		int x=i+dx[k];
		int y=j+dy[k];
		dfs(x,y); 
	}
}

int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			cin>>g[i][j];
		}
	}
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			if(!st[i][j]&&g[i][j]=='W')
			{
				dfs(i,j);
				cnt++;
			}
		}
	}
	cout<<cnt<<endl;
	return 0;
	 
	 
}

练习3

P1747 好奇怪的游戏 - 洛谷

主要在于要设一个数组f[i][j]:表示(i,j)到(1,1)的距离

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=25;
int f[N][N];
int x1,y1,x2,y2;
int dx[12] = {-2, -2, -1, -1, 1, 1, 2, 2, -2, -2, 2, 2};
int dy[12] = {-1, 1, -2, 2, -2, 2, -1, 1, -2, 2, -2, 2};

void dfs(int x,int y,int sum)
{
	if(x<=0||x>20||y<=0||y>20)return;
	f[x][y]=sum;
	for(int k=0;k<12;k++)
	{
		int i=x+dx[k];
		int j=y+dy[k];
		if(f[i][j]==0||f[i][j]>sum+1)
		{
			dfs(i,j,sum+1);
		}
	}
}

int main()
{
	cin>>x1>>y1>>x2>>y2;
	dfs(x1,y1,1);
	cout<<f[1][1]-1<<endl;
	memset(f,0,sizeof f);
	dfs(x2,y2,1); 
	cout<<f[1][1]-1;
	return 0;
}

更多推荐