抽象dfs:
1.抽象思想
把问题抽象为一个状态每一次往下面移动:
连接:奇怪的电梯
思路:
1.用2个参数,一个记录pos,一个记录步数;
接下来dfs如何记录:
dfs:
如果pos正好是这一层的话,就更新最小值;
每次往上面搜索,往下面搜索都试一试:
代码:
#include<cstdio>
#include<iostream>
using namespace std;
int n,a,b,ans=0x7ffffff;//ans一开始设为无限大0x7ffffff;
int to[205];
bool vis[205];
void dfs(int now,int sum)//now表示当前搜到的楼层,sum表示按钮次数
{
if(now==b) ans=min(ans,sum);//如果正正好是b,就更新ans;
if(sum>ans) return;
vis[now]=1;//该地方设为true,表示选过的;
//不越界就搜
if(now+to[now]<=n&&!vis[now+to[now]]) dfs(now+to[now],sum+1);//判断下一个位置能不能走,而且有没有走过,如果可以走没走过就走;
if(now-to[now]>=1&&!vis[now-to[now]]) dfs(now-to[now],sum+1);//同理
vis[now]=0;//回溯
}
int main()
{
scanf("%d%d%d",&n,&a,&b);
for(int i=1;i<=n;i++) scanf("%d",&to[i]);
vis[a]=1;//当前的地方设为true;
dfs(a,0);
if(ans!=0x7ff//ffff) printf("%d",ans);
else printf("-1");
return 0;
}
总结:
dfs可以抽象为解决某一个事情;
一个小技巧,dfs需要的参数,如果一个接不下去,或者不知道如果状态转移到下一个的话,就加一个参数;
比如这题
一个pos不足以思考出下一个状态转移,那就加一个表示当前按的次数;
dfs说白了就是递归:要有递归3条件:
第一:递归出口:
即pos==b这个位置
2.重复动作:
即不断向上或者向下取;
扩展:
取木棍:
有4个木棍输入每个木棍长度;
判断是否可以拼成三角形:
比如 1 2 3 3可以拼成三角形:
输出yes;
思路:
一样直接dfs
dfs(p,s,st)表示当前组合成了多少边,s表示当前的和,st表示当前考虑的地方:
代码:
#include <iostream>
using namespace std;
int n,m,sum;
int a[10010];
bool vis[10010];
bool f;
void dfs(int p,int s,int st)
{
if(f)
{
return ;
}
if(p==3)
{
f=true;
return ;
}
if(s=sum/3)//当前的边正好可以构成三角形的一个边
{
dfs(p+1,0,0);
return ;
}
for(int i=0;i<n;++i)
{
if(!vis[i])
{
vis[i]=true;
dfs(p,s+a[i],i+1);
vis[i]=false;
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;++i)
{
cin>>a[i];
sum+=a[i];
}
if(sum%3!=0)//如果不是3的倍数的话就已经明摆着不可以成功
{
cout<<"no"<<endl;
return 0;
}
else dfs(0,0,0);
if(f) cout<<"yes"<<endl;
else cout<<"no"<<endl;
return 0;
}
八皇后问题:
在8*8的棋盘上面放置8个皇后,使得皇后们互相攻击不到对方怎么做?;
思路:
我们直接dfs,每次状态设为当前考虑第r行的皇后,然后不断试一下下一个能不能放;
因为我们每次都是考虑下一个位置,所以行之间是不会冲突的
至于说列和对角线用一个数组来表示列有没有被占用,
技巧对角线的规律:一般我们是考虑行列相加和行列相减
相加是定值,相减也是定值;防止相减的负值加上一个8基础就好了;
技巧:
考虑一个东西列上,什么的会不会冲突;可以用vis数组,和普通的bool vis数组标记有没有被访问过,异曲同工之妙;
代码:
#include <iostream>
using namespace std;
int ans;
bool col[10],x1[20],x2[20];
bool check(int r,int i)
{
return !col[i]&&!x1[r+i]&&!x2[r-i+8];//列和对角线都没有访问过
}
void dfs(int r)
{
if(r==8)
{
ans++;
return ;
}
for(int i=0;i<8;++i)
{
if(check(r,i))
{
col[i]=x1[i+r]=x2[r-i+8]=true;
dfs(r+1);
col[i]=x1[i+r]=x2[r-i+8]=false;//回溯
}
}
}
int main()
{
dfs(0);
cout<<ans<<endl;
return 0;
}
2.全排列方法:
求n的全排列:
法一:
dfs2个参数,位置和数组
出口:位置考虑到最后一个了;
重复操作:每次选一个没选过数,然后考虑下一个位置;
回溯:因为要求出全部解;
代码:
#include <iostream>
using namespace std;
int a[10010],n;
bool uesd[10];
void dfs(int pos,int n)
{
if(pos==n)
{
for(int i=0;i<n;++i)
{
cout<<a[i]<<" ";
}
cout<<endl;
return ;
}
else
{
for(int i=1;i<=n;++i)
{
if(!uesd[i])//没选过的话就选择
{
a[pos]=i;//该位置选上去
uesd[i]=true;
dfs(pos+1,n);。。考虑下一个位置
uesd[i]=false;
}
}
}
}
int main()
{
cin>>n;
dfs(0,n);
}
法二:
一样的思路,只是每次选的时候,从开头遍历到当前位置pos看看这个数字有没有被选:
代码:
#include <iostream>
using namespace std;
int a[10010],n;
void dfs(int pos,int n)
{
if(pos==n)
{
for(int i=0;i<n;++i)
{
cout<<a[i]<<" ";
}
cout<<endl;
return ;
}
else
{
for(int i=1;i<=n;++i)
{
int ok=1;。。一开始假设没有重复
for(int j=0;j<pos;++j){
if(a[j]==i) ok=0;//聪我们要输出的集合最开头开始遍历
}
if(ok)
{
a[pos]=i;
dfs(pos+1,n);
}
}
}
}
int main()
{
cin>>n;
dfs(0,n);
}
生成对应集合的全排列;
比如
生成 1 3 2 4 的全排列;
代码:
#include <iostream>
using namespace std;
int a[10010],n,p[10010];
bool uesd[10];
void dfs(int pos,int n)
{
if(pos==n)
{
for(int i=0;i<n;++i)
{
cout<<a[i]<<" ";
}
cout<<endl;
return ;
}
else
{
for(int i=0;i<n;++i)
{
if(!uesd[p[i]])//没选过的话就选择
{
a[pos]=p[i];//该位置选上去
uesd[p[i]]=true;
dfs(pos+1,n);
uesd[p[i]]=false;
}
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;++i)
{
cin>>p[i];
}
dfs(0,n);
}
思路:
每次选i改为选数组就好了
代码:
#include <iostream>
using namespace std;
int a[10010],n;
int q[10010];
void dfs(int pos,int n,int q[])
{
if(pos==n)
{
for(int i=0;i<n;++i)
{
cout<<a[i]<<" ";
}
cout<<endl;
return ;
}
else
{
for(int i=0;i<n;++i)
{
int ok=1;
for(int j=0;j<pos;++j){
if(a[j]==q[i]) ok=0;
}
if(ok)
{
a[pos]=q[i];
dfs(pos+1,n,q);
}
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;++i)
{
cin>>q[i];
}
dfs(0,n,q);
}
这个代码有个问题就是输入1 1 1 什么都不输出可重复集合的话我们就不能用选没选过作为判断标准,用变量记录出现次数来判断是否可以选用:
代码:
#include <iostream>
using namespace std;
int a[10010],n;
int q[10010];
void dfs(int pos,int n,int q[])
{
if(pos==n)
{
for(int i=0;i<n;++i)
{
cout<<a[i]<<" ";
}
cout<<endl;
return ;
}
else
{
for(int i=0;i<n;++i)
{
int c1=0,c2=0;
for(int j=0;j<pos;++j){
if(a[j]==q[i]) c1++;
}
for(int t=0;t<n;++t){
if(q[i]==q[t]) c2++;
}
if(c1<c2)
{
a[pos]=q[i];
dfs(pos+1,n,q);
}
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;++i)
{
cin>>q[i];
}
dfs(0,n,q);
}
这样发现输入1 1 1的时候输出27个1 1 1又错了;
为什么呢,因为我们输入的数组有序,而选的时候数组无序,,认为第二个1和第一个1是不一样的,
技巧:dfs每次的时候应该不重复不遗漏
2种情况第一种考虑的位置正正好是第一个;
第二种考虑的位置的数和上一次,也就是i-1这个数字不一样,一样的话就是相同的数,上一次的dfs已经搜索过没必要再来一次
判断;if(不是第一个数||不是上一个数)就dfs
代码:
#include <iostream>
using namespace std;
int a[10010],n;
int q[10010];
void dfs(int pos,int n,int q[])
{
if(pos==n)
{
for(int i=0;i<n;++i)
{
cout<<a[i]<<" ";
}
cout<<endl;
return ;
}
else
{
for(int i=0;i<n;++i)
{
if(!i||q[i]!=q[i-1])//关键判断是否为第一个数字和这个数字是否和上一次选的数字相同
{
int c1=0,c2=0;
for(int j=0;j<pos;++j)
{
if(a[j]==q[i]) c1++;
}
for(int t=0;t<n;++t)
{
if(q[i]==q[t]) c2++;
}
if(c1<c2)
{
a[pos]=q[i];
dfs(pos+1,n,q);
}
}
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;++i)
{
cin>>q[i];
}
dfs(0,n,q);
}
注意输入的数组应该先排序一下;