DFS的题型大概分为两种,一种是令人自闭的数据型,另一种是让人同样自闭的地图型题目。
1.数据型题目,相比地图型很抽象,很难理清思路,还要在递归之间跳来跳去,状态之间不断地转化,真是头疼,但是,慢慢来,毕竟以后自闭多得是。
来这个网站体验自闭:codeup.cn/contest.php?cid=100000608 (网站名字:墓地)(好有深意,就是来了就别好着回去了)。
上题:(体验下自闭)(大佬自动忽略)
A 题是个全排列,DFS (递归)思想
代码:
#include<iostream>
#include<string>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=1005;
typedef long long ll;
int vis[maxn];
int a[maxn];
int n;
void dfs(int x)
{
int g=0;
if(x==n+1)
{
g=0;
for(int i=1;i<=n;i++)
{
if(g)
cout<<' ';
g=1;
cout<<a[i];
}
cout<<endl;
}
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
vis[i]=1;
a[x]=i;
dfs(x+1);
vis[i]=0;
}
}
}
int main()
{
cin>>n;
dfs(1);
return 0;
}
用了递归与回溯的思想,就是将某个数用 vis 数组标记后,标记的意思就是,在下一次(不是回溯的时候)就不会用这个数据,将会去寻找下一个没有被标记的那个数据,每次找到后,都会将这个没有被标记的数据加上标记,在以后的搜索中,就不会去搜这个值,然后要拿一个数组去装那些被标记了的数据,以便最后的时候输出。(我拿3 这个数据说一说)一直到最后将数据全部都标记完之后,输出这个数组中的值,然后回溯,原路返回,将标记清空(首先先是将后面两个清空标记后)再去重新标记这两个,一开始来标记的是1 2 3 的顺序,回溯的时候 先是 3 2 清空标记,再按顺序3 2 加上标记,在这个过程中,会进行 满足条件的数组的数据更新,也可以说是数据换了换位置。最后换完之后,退回到第一步,将 第一位的标记清空。进行下一步2 在第一位的操作。
B:DFS 求组合数的输出,就是直白点,就是二项式定理 Cm(n) n在 上面,m在下面 ,没有重复的一组(换换顺序一样的也是不满足的),在全排列的情况下 加上一个剪枝,就可以了,但是你得知道DFS 排的时候的原理,不然就会出错。每次往数组里面装的数据都是要比之前的大,看看代码;
#include<stdio.h>
#include<iostream>
using namespace std;
#include<string.h>
#include<algorithm>
const int maxn=100005;
typedef long long ll;
#include<bits/stdc++.h>
#include<queue>
#include<set>
int a[maxn]={0};
int vis[maxn];
int m,n;
void dfs(int x)
{
for(int i=a[x-1]+1;i<=m;i++) //在这里 也可以判断
{
if(!vis[i]/*&&i>a[x-1]*/) //剪一下枝 直接判断
{
vis[i]=1;
a[x]=i;
if(x==n)
{
for(int j=1;j<=n;j++)
{
cout<<a[j]<<' ';
}
cout<<endl;
}
dfs(x+1);
vis[i]=0;
}
}
}
int main()
{
while(cin>>m>>n)
{
memset(vis,0,sizeof(vis));
dfs(1);
}
return 0;
}
在全排列的基础上,加上剪枝判断条件, i>a[x-1],或者 i=a[x-1]+1, 就可以直接跳过不满足条件的排列,每次排列中的元素都是比前一个之大的数据,必然是递增的字典序。在数组中个数达到 这个 n值的时候,就输出当前的排列,再去递归下面的情况,在这个过程中,会不断地去标记和加上标记,然后再不断的重新覆盖掉满足条件的这个数组(就相当于交换了下位置),也是直到满足这个条件的时候,就会输出这个排列。具体的思想和 实现,就自己再VS 上调试几遍,理解理解吧。
这个组合数的剪枝 也可以在在函数内有两个参数,也可以达到剪枝的效果,这样就不用再来标记数组来记录谁选了,谁没有选,相对简单。
代码:
#include<stdio.h>
#include<iostream>
using namespace std;
#include<string.h>
#include<algorithm>
const int maxn=100005;
typedef long long ll;
#include<bits/stdc++.h>
#include<queue>
#include<set>
int a[maxn]={0};
int vis[maxn];
int m,n;
void dfs(int ans,int x)
{
if(x==n)
{
for(int j=0;j<n;j++)
{
cout<<a[j]<<' ';
}
cout<<endl;
return ;
}
for(int i=ans;i<=m;i++)
{
a[x]=i;
dfs(i+1,x+1);
}
}
int main()
{
while(cin>>m>>n)
{
memset(vis,0,sizeof(vis));
dfs(1,0);
}
return 0;
}
有很多的方法来写全排列的。
还有的题目 ,是给你特定的数据,来求其中几个的全排列代码:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int a[15],b[15],vis[15],k;
int n;
void dfs(int ans,int num)
{
if(num==n)
{
for(int i=0;i<n;i++)
cout<<b[i]<<' ';
cout<<endl;
return;
}
for(int i=ans;i<k;i++)
{
b[num]=a[i];
dfs(i+1,num+1);
}
}
int main()
{
int flag=0;
while(cin>>k>>n)
{
memset(vis,0,sizeof(vis));
for(int i=0;i<k;i++)
scanf("%d",&a[i]);
if(flag)
cout<<endl;
flag=1;
dfs(0,0);
}
return 0;
}
这种的剪枝比上面的那几个比较简便,不用再手写 a[i]>b[x-1] i>b[x-1] 等剪枝。
下面看看 二重DFS 怎么解这个题,这样就不用再写一个for循环,就直接写出两种状态,选与不选,如果有什么剪枝操作,都可以在中间过程中加入。
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int a[15],b[15],vis[15],k;
int n;
void dfs(int ans,int num)
{
if(num>k)
return ;
if(ans==n)
{
for(int i=0;i<n;i++)
cout<<b[i]<<' ';
cout<<endl;
return;
}
b[ans]=a[num];
dfs(ans+1,num+1);
dfs(ans,num+1);
}
int main()
{
int flag=0;
while(cin>>k>>n)
{
memset(vis,0,sizeof(vis));
for(int i=0;i<k;i++)
scanf("%d",&a[i]);
if(flag)
cout<<endl;
flag=1;
dfs(0,0);
}
return 0;
}
上面 这个双重DFS在一些 穷尽搜索题目 中很有用处,在一些暴力题目中也可以拿分数。
单单会写这个双重DFS 又会怎么样?有时候会有重复的 数据来限制你,我们就需要剪一下枝叶,来达到题目的要求:
就是下面这样就可以了,比如在一个 1 1 1 1 5里面输出 1 1 5 ;1 1 1 两种答案,不会再出来一个 1 1 1,就是这样 。
b[ans]=a[num];
dfs(ans+1,num+1);
while(num+1<k&&a[num]==a[num+1])
num++;
dfs(ans,num+1);
这几种方法我够我玩了。接下来就是看更多的题练手了。 写这么多,就当我以后复习吧。
https://blog.youkuaiyun.com/chenzhenyu123456/article/details/47282969 这个博客讲得很好,有很多的解法。
n位二进制数的排列
还有的题型就是一个 求出一个n位二进制的全排列,其实和DFS 的思想差不多,其实就是一样的,使用双重DFS:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1005;
int n;
int vis[maxn];
void dfs(int now)
{
if (now == n)
{
for (int i = 0; i < n; i++)
cout << vis[i] << ' ';
cout << endl;
}
else
{
vis[now] = 1;
dfs(now + 1);
vis[now] = 0;
dfs(now + 1);
}
}
int main()
{
cin >> n;
dfs(0);
return 0;
}
每一位都有1和0两种状态,就是枚举这两种状态,然后输出每种结果,就是这样。
二叉树下标(先左面后又面)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
const int maxn = 1005;
using namespace std;
int n;
int poow(int a, int b)
{
int t = 1;
while (b)
{
if (b & 1)
{
t *= a;
b--;
}
a *= a;
b >>= 1;
}
return t;
}
void dfs(int now)
{
if (now >= poow(2, n)-1 )
return;
printf("%d\n", now);
dfs(now * 2 + 1);
dfs(now * 2 + 2);
return;
}
int main()
{
cin >> n;
dfs(0);
return 0;
}
DFS递归在很多的方面有着很多的用处。