数的全排列和 避免重复运,top之前不入栈,还有求n的数子集,不是全排列了,而是每个数选或不选的问题

输入一个数n,下面输入n个数,求n个数的全排列;

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define Max 50
int  book[Max],tt[Max];
int a[Max],n;

void dfs(long long x )
{
	if(x>n)
	{
		for(int i=1;i<=n;i++)
		{
			if(i==n) printf("%d\n",tt[i]);
			else printf("%d ",tt[i]);
		}
		return ;
	}
    for(int i = 1; i<=n; i++)
    {
        if(!book[i])
        {
            book[i] = 1;
            tt[x] = a[i];
            dfs(x+1);
            book[i] = 0;
        }
    }
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        memset(book,0,sizeof(book));
        dfs(1);
    }
    return 0;
}

也可以直接用c++的函数库中的全排列函数 next_permutaton(a,a+n,cmp);

cmp,可加可不加,不加就是全排列,加上,就是自己定义一个cmp函数,按照自己定义的函数里的return条件,所排列;


下面一个n,输入n个数,求n的数的子集

思路:可以用全排列求:但像1,2,3 和 3,2,1 和2,1,3 ...,这些只要其中的一组就行了

所以可以在存到数组中时,加上一个条件,代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define Max 50
int  book[Max],tt[Max];
int a[Max],n;

void dfs(long long x )
{

    for(int i = 1; i<=n; i++)
    {
        if(!book[i]&&tt[x-1]<=a[i])
        {
            book[i] = 1;
            tt[x] = a[i];
            for(int j = 1;j <=x;j++)
            {
            	if(j==x) printf("%d\n",tt[j]);
		else printf("%d ",tt[j]); 
            }
            dfs(x+1);
            book[i] = 0;
        }
    }
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        memset(book,0,sizeof(book));
        dfs(1);
    }
    return 0;
}

加上一个条件 a[x-1]<=a[i] ,这个条件使得tt[] 数组中存的数是递增的;这样就避免重复子集的发生;


也可以这样 定义 一个 top 在 top 之前的数,都不在进入栈中这样可以避免重复

和上一个条件的区别就是 上一个输出的数单调递增的,而这个是取决于输入n的数的顺序,在top之前的数是不可以在进栈,不可在进入tt[] 数组中

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define Max 50
int  book[Max],tt[Max];
int a[Max],n;

void dfs(int top,int x )
{

    for(int i = top; i<=n; i++)
    {
        if(!book[i])
        {
            book[i] = 1;
            tt[x] = a[i];
            for(int j = 1;j <=x;j++)
            {
            	if(j==x) printf("%d\n",tt[j]);
				else printf("%d ",tt[j]); 
            }
            dfs(i+1,x+1);
            book[i] = 0;
        }
    }
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        memset(book,0,sizeof(book));
        dfs(1,1);
    }
    return 0;
}


以后避免像1,2,3 或3,2,1或2,1,3等这6种排列,只需要其中的一种就行了,以后就这样,简化上一个代码,仔细想想,top 之前的都不会再入栈了,那么book数组加不加是不是不起作用了,避免重复最简代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define Max 50
int  book[Max],tt[Max],num;
int a[Max],n;

void dfs(int top,int x)    // 为了避免重复,定义了一个top,在top之前的都不在入栈; 
{                          //  那每个数还用不用标记了,答案是不用了,根本遍历不到在它之前的数,所以根本就不用book[]数组标记了 
    for(int i = top; i<=n; i++)
    {
        tt[x] = a[i];
        for(int j = 1;j <=x;j++)
        {
      		if(j==x) printf("%d\n",tt[j]);
			else printf("%d ",tt[j]); 
        }
        dfs(i+1,x+1); 
    }
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        memset(book,0,sizeof(book));
        dfs(1,1);
    }
    return 0;
}




可以仔细想想, 求子集用不用在每一栈中都遍历n个数,假设这n个数都满足条件,这不是每一栈都要递归n次后,才能结束吗,这时间复杂度太大了, 其实 求子集无非就是这个数选于不选的问题,定义一个光标top,当移动到这数时,这个数就两种情况  选或者不选,每个数的两种情况懂遍历完,不就可以了吗;

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define Max 50
int  book[Max];
int a[Max],n;

void dfs(int top)    // 这个top可以比作为一个光标,这一行前后移动, 
{					 // 移动到这个数,这个数就有两种情况 选或者不选; 
	if(top>n)
	{
		int f =0;
		for(int j = 1;j <=n;j++)
    	{
    		if(book[j])
    		{
    			if(!f)
				{
					printf("%d",a[j]);
					f = 1; 
				} 
				else printf(" %d",a[j]);
    		}	
		}
		if(f) printf("\n");
		return ;	
	}
	
	book[top] = 1;  //这个数标记一下,选了 
	dfs(top+1);
    book[top] = 0;   //这个数变为不选; 
    dfs(top+1);

}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        memset(book,0,sizeof(book));
        dfs(1);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值