DFS
深度优先搜索
一、特点
从最开始的状态出发,遍历所有能到达的地方,每个状态只会进行一次,通过递归的思想实现
模板:摘自于https://blog.csdn.net/qq_40511966/article/details/86539631
void dfs()//参数用来表示状态 ,参数可以是一个数n//表示格数
//参数也可以是两个数//r和c//n和level
{
if(到达终点状态)
{
...//根据题意添加
return;
}
if(越界或者是不合法状态)
return;
if(特殊状态)//剪枝
return ;
if(合并状态)
//根据题意添加,注意也要初始化和标记//回溯
for(扩展方式)
{
if(扩展方式所达到状态合法)
{
修改操作;//根据题意来添加
标记;
dfs();
(还原标记);
//是否还原标记根据题意
//如果加上(还原标记)就是 回溯法
}
}
}
二、例题
例一 部分和问题:
给定整数 a1、a2、…、an,判断是否可以从中选出若干数,使它们的和恰好为 k
限制条件
1 ≤ n ≤ 20
108 ≤ ai ≤ 108
108 ≤ k ≤ 108
输入
k=13
输出
Yes
思路:运用递归,每个数有两种情况,分别是加上,不加上,然后一个一个来即可,复杂度2的n次方
代码
#include<iostream> #include<algorithm> #include<cstdio> #define maxn 30 using namespace std; int n;//元素的个数 int sum;//部分和 int tmp[maxn]; int judge(int nn,int summ)//dfs到第几个数,现在的和是多少 { if(nn==n) return summ==sum;//注意循环到最后一个数,不一定返回0,需要判断它和部分和是否相等 if(judge(nn+1,summ)) return 1; if(judge(nn+1,summ+tmp[nn])) return 1; return 0; } int main() { scanf("%d",&n); for(int i=0;i<n;i++) scanf("%d",&tmp[i]); scanf("%d",&sum); if(judge(0,0)) printf("Yes\n"); else printf("No\n"); return 0; }
例题二:Lake Counting
有一个N*M的园子,雨后积水,八联通的积水被认为是连接在一起的。请求出园子里共有多少水洼?
N,M<=100
样例:
10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
输出:
3
题解:遍历有w的点,并将其改为.,遍历其八个方向,如果符合的话,再次遍历。复杂度为,8*N*M
题解:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxn 110 using namespace std; int x,y;//总行数和总列数 int ans=0; char water[maxn][maxn];//水洼的字符串 void dfs(int tx,int ty) { water[tx][ty]='.';//更新状态 for(int i=-1;i<=1;i++) { for(int j=-1;j<=1;j++) { int xx=tx+i,yy=ty+j; if(xx>=0&&xx<x&&yy>=0&&yy<y)//符合要求 { if(water[xx][yy]=='W') dfs(xx,yy); } } } } int main() { scanf("%d%d",&x,&y); for(int i=0;i<x;i++) scanf("%s",water[i]); for(int i=0;i<x;i++) for(int j=0;j<y;j++) if(water[i][j]=='W') { dfs(i,j); ans++; } printf("%d\n",ans); return 0; }
例题三、Red and Black
题目:
There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can't move on red tiles, he can move only on black tiles.
Write a program to count the number of black tiles which he can reach by repeating the moves described above.
Input
The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.
There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.
'.' - a black tile
'#' - a red tile
'@' - a man on a black tile(appears exactly once in a data set)
The end of the input is indicated by a line consisting of two zeros.
Output
For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).
Sample Input
6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
11 9
.#.........
.#.#######.
.#.#.....#.
.#.#.###.#.
.#.#..@#.#.
.#.#####.#.
.#.......#.
.#########.
...........
11 6
..#..#..#..
..#..#..#..
..#..#..###
..#..#..#@.
..#..#..#..
..#..#..#..
7 7
..#.#..
..#.#..
###.###
...@...
###.###
..#.#..
..#.#..
0 0
Sample Output
45
59
6
13
题意:一个人站在@黑色格子位置,该人只能走黑色格子(用‘.’表示),不能走红色格子(用#表示),只能上下左右移动,求出其所能走的最大黑色格子数。
题解:本题依然采用dfs进行遍历,并将每个符合条件的改为#,以保证只会遍历一次
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
int N,M,ans,a[]= {0,0,1,-1},b[]= {1,-1,0,0};//a和b是用来上下左右移动的
string s[60];
void dfs(int x,int y)
{
ans++;
s[x][y]='#';
int dx,dy;
for(int i=0; i<4; i++)
{
dx=x+a[i];
dy=y+b[i];
if(dx>=0&&dx<N&&dy>=0&&dy<M&&s[dx][dy]=='.')
dfs(dx,dy);
}
}
int main()
{
int fl=1;
while(cin>>M>>N&&M)
{
ans=0;
for(int i=0; i<N; i++)
cin>>s[i];
for(int i=0; i<N; i++)
for(int j=0; j<M; j++)
if(s[i][j]=='@')
{
fl=0;
dfs(i,j);
break;
}
cout<<ans<<endl;
}
return 0;
}
例题四、Oil Deposits
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
string s[105];
int N,M,ans;
void dfs(int x,int y)
{
s[x][y]='*';
int dx,dy;
for(int i=-1; i<=1; i++)
{
for(int j=-1; j<=1; j++)
{
dx=x+i;
dy=y+j;
if(dx>=0&&dx<N&&dy>=0&&dy<M&&s[dx][dy]=='@')
dfs(dx,dy);
}
}
}
int main()
{
while(cin>>N>>M&&M)
{
ans=0;
for(int i=0; i<N; i++)
cin>>s[i];
for(int i=0; i<N; i++)
{
for(int j=0; j<M; j++)
if(s[i][j]=='@')
{
dfs(i,j);
ans++;
}
}
cout<<ans<<endl;
}
return 0;
}
数独游戏:
你一定听说过“数独”游戏。
如下图所示,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。
在这里插入图片描述
数独的答案都是唯一的,所以,多个解也称为无解。
本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。
本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。
输出9行,每行9个数字表示数独的解。
输入:
005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700
程序应该输出:
145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764
再例如,输入:
800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400
程序应该输出:
812753649
943682175
675491283
154237896
369845721
287169534
521974368
438526917
796318452
#include<algorithm>
#include<string>
using namespace std;
string s[10];
bool check(int x,int y,int k)//用来判断是否可以插入这个数
{
//如果可以插入,满足两个条件
//一是该行并且该列无此数k
for(int i=0;i<9;i++)
{
if(s[x][i]==k+'0')
return false;
if(s[i][y]==k+'0')
return false;
}
//二是该九框没有重复得数字
//先找边界
int xmin=x/3*3;
int xmax=xmin+3;
int ymin=y/3*3;
int ymax=ymin+3;
for(int xx=xmin;xx<xmax;xx++)
{
for(int yy=ymin;yy<ymax;yy++)
if(s[xx][yy]==k+'0')
return false;
}
return true;
}
void dfs(int x,int y)
{
//退出条件只有一个:即每一级都填完了,成了第十行
//因为从0开始,所以判定条件应该为>8
if(x>8)//全部遍历完,直接输出直接输出
{
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
cout<<s[i][j];
}
cout<<endl;
}
return ;
}
//没遍历完分两种情况,
//1.不为0,直接进行下一步遍历,因为是按行遍历,所以每一行遍历完,在进行下一行
if(s[x][y]!='0')
dfs(x+(y+1)/9,(y+1)%9);
//2.为0,判断是否可以加入,注意每次加入之后还要再拿出来,因为每次相互独立
if(s[x][y]=='0')
{
for(int i=1;i<=9;i++)//从1到9进行遍历
{
if(check(x,y,i))
{
s[x][y]='0'+i;//这个地方不能用标志变量,必须将其置为0,因为如果不是0的话,会当成已填数字处理
dfs(x+(y+1)/9,(y+1)%9);
s[x][y]='0';
}
}
}
//
}
int main()
{
for(int i=0;i<9;i++)
cin>>s[i];
dfs(0,0);
return 0;
}
例题:
如上面的10个格子,填入0~9的数字,不能重复(原先已经填了一部分数字),要求:连续的两个数字不能相邻(左右,上下,对角都算相邻)。例如:数字0和1不能放在一起。
问:一共有多少种可能的填数方案?
输入
输入多组测试数据。
每组测试数据有三行,第一行三个整数,第二行四个整数,第三行三个整数,之间用空格隔开,分别代表每个空格所填的数,如果原先没有数,则填-1。
输入数据保证不重复数字,不保证连续数字不相邻。
每组测试数据输出一行。
输出表示方案数目的整数。
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
using namespace std;
int a[3][4];//图表
bool fl[10];//是否被标记
int ans,k;//个数,是否输入的标记
void scan()//输入
{
a[0][0]=-2;
a[0][1]=k;
for(int i=2;i<4;i++)
cin>>a[0][i];
for(int i=0;i<4;i++)
cin>>a[1][i];
for(int i=0;i<3;i++)
cin>>a[2][i];
a[2][3]=-2;
for(int i=0;i<3;i++)
{
for(int j=0;j<4;j++)
if(a[i][j]>=0&&a[i][j]<=9)
fl[a[i][j]]=true;
}
}
bool check(int r,int c,int k)//判断重复数字
{
//因为是相邻的,所以只需要看正上,左上,右上,左
//不用看整行整列或是对角线,因为有可能不相邻
//正上
if(r>0&&abs(a[r-1][c]-k)==1)
return false;
//左上
if(r>0&&c>0&&abs(a[r-1][c-1]-k)==1)
return false;
//右上
if(r>0&&c<3&&abs(a[r-1][c+1]-k)==1)
return false;
//左
if(c>0&&abs(a[r][c-1]-k)==1)
return false;
return true;
}
void dfs(int n)
//此时参数是1个,因为表格无顺序,只需要一个一个来,用已选择表的个数方便
{
if(n==12)//终止条件,12个全ok了
{
ans++;
return;
}
int r=n/4,c=n%4;//目标所在的行和列
int k=a[r][c];//目标表格的值
if(k==-1)
{
for(int kk=0;kk<=9;kk++)
{
if(fl[kk]==false&&check(r,c,kk))
{
fl[kk]=true;
a[r][c]=kk;
dfs(n+1);
a[r][c]=-1;
fl[kk]=false;//回溯之后不一定只有一个
}
}
}
else if(check(r,c,k))
{
dfs(n+1);
}
}
int main()
{
while(cin>>k)
{
memset(fl,false,sizeof(fl));//注意这个顺序,memset再输入前,如果放后面的话,本来有数字的地方会消失
scan();
ans=0;
dfs(0);
cout<<ans<<endl;
}
}
寒假作业
现在小学的数学题目也不是那么好玩的。看看这个寒假作业:
□ + □ = □
□ - □ = □
□ × □ = □
□ ÷ □ = □
图1.jpg
(如果显示不出来,可以参见【图1.jpg】) 每个方块代表1~13中的某一个数字,但不能重复。
比如:
6 + 7 = 13
9 - 8 = 1
3 * 4 = 12
10 / 2 = 5
以及:
7 + 6 = 13
9 - 8 = 1
3 * 4 = 12
10 / 2 = 5
就算两种解法。(加法,乘法交换律后算不同的方案)
你一共找到了多少种方案?
请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
//这个题的教训是每次改数的时候都别忘了再次初始化#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
bool fl[14];
int a[13],ans=0;
void dfs(int n)
{
if(n==13)
{
ans++;
return;
}
else if(n==3)
{
if(a[1]+a[2]>=1&&a[1]+a[2]<=13&&fl[a[1]+a[2]]==false)
{
a[3]=a[1]+a[2];
fl[a[3]]=true;
dfs(n+1);
fl[a[3]]=false;
}
else
return ;
}
else if(n==6)
{
if(a[4]-a[5]>=1&&fl[a[4]-a[5]]==false&&a[4]-a[5]<=13)
{
a[6]=a[4]-a[5];
fl[a[6]]=true;
dfs(n+1);
fl[a[6]]=false;
}
else
return;
}
else if(n==9)
{
if(a[7]*a[8]<=13&&fl[a[7]*a[8]]==false&&a[7]*a[8]>=1)
{
a[9]=a[7]*a[8];
fl[a[9]]=true;
dfs(n+1);
fl[a[9]]=false;
}
else
return;
}
else if(n==12)//终止条件
{
if(a[10]%a[11]==0)
{
a[12]=a[10]/a[11];
}
else
return;
if(a[12]>=1&&a[12]<=13&&fl[a[12]]==false)
{
fl[a[12]]=true;
dfs(n+1);
fl[a[12]]=false;
}
else
return;
}
else
for(int i=1; i<=13; i++)
{
if(fl[i]==false)
{
fl[i]=true;
a[n]=i;
dfs(n+1);
fl[i]=false;
}
}
}
int main()
{
memset(fl,false,sizeof(fl));
dfs(1);
cout<<ans;
return 0;
}
排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r < = n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。
现要求你不用递归的方法输出所有组合。
例如n = 5 ,r = 3 ,所有组合为:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
输入
一行两个自然数n、r ( 1 < n < 21,1 < = r < = n )。
输出
所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,所有的组合也按字典顺序。
//题解:此题不同于组合
//组合是相同数字不同位置也可以
//排列必须实不同的数字
//所以只需要循环就可以,也是需要回溯法,因为不同的组合数字可能重复出先
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[25];
bool fl[25];
int n,r;
void dfs(int k)
{
if(k==r)
{
for(int i=0;i<r;i++)
{
printf("%d",a[i]);
if(i!=r-1)
printf(" ");
else
printf("\n");
}
return;
}
for(int i=a[k-1]+1;i<=n;i++)//因为保证不重复,所以采用递增序,所以从前一个数加1开始
{
if(fl[i]==false)
{
fl[i]=true;
a[k]=i;
dfs(k+1);
fl[i]=false;
}
}
}
int main()
{
while(cin>>n>>r)
{
memset(fl,false,sizeof(fl));
dfs(0);
}
return 0;
}
补充变式
描述找出从自然数1、2、... 、n(0<n<10)中任取r(0<r<=n)个数的所有组合。
- 输入
- 输入n、r。
- 输出
- 按特定顺序输出所有组合。
特定顺序:每一个组合中的值从大到小排列,组合之间按逆字典序排列。 - 样例输入
-
5 3
- 样例输出
-
543 542 541 532 531 521 432 431 421 321
例题
题目描述
已知 n 个整数b1,b2,…,bn
以及一个整数 k(k<n)。
从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。
例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:
3+7+12=22 3+7+19=29 7+12+19=38 3+12+19=34。
现在,要求你计算出和为素数共有多少种。
例如上例,只有一种的和为素数:3+7+19=29。
输入
第一行两个整数:n , k (1<=n<=20,k<n)
第二行n个整数:x1,x2,…,xn (1<=xi<=5000000)
输出
一个整数(满足条件的方案数)。
样例输入 Copy
4 3
3 7 12 19
样例输出 Copy
1
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define maxn 30
using namespace std;
int a[maxn];
int n,r,ans;
int sushu(int x)
{
if(x==1)
return 0;
int q=(int)sqrt(1.0*x);
for(int i=2;i<=q;i++)
{
if(x%i==0) return 0;
}
return 1;
}
void dfs(int rr,int k,int summ)//组合的另一种形式
{
if(rr==r&&sushu(summ))
{
ans++;
return;
}
if(k>=n||rr>r)
return;
dfs(rr,k+1,summ);
dfs(rr+1,k+1,summ+a[k]);
}
int main()
{
while(cin>>n&&n)
{
ans=0;
cin>>r;
for(int i=0;i<n;i++)
cin>>a[i];
dfs(0,0,0);
cout<<ans<<endl;
}
return 0;
}
因为迭代次数很多,如果超时的化,可以将cout换成printf
浙公网安备 33010602011771号