DFS(自闭中...1)

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.csdn.net/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递归在很多的方面有着很多的用处。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值