算法入门经典第二版 抽象dfs

抽象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);
}

注意输入的数组应该先排序一下;

猜你喜欢

转载自blog.csdn.net/m0_51373056/article/details/109616404