排列组合之生成排列_(:з」∠)_

1.生成全排列


第一类:按字典序生成

         

 参考:

        http://www.cnblogs.com/pmars/archive/2013/12/04/3458289.html

    1.字典序法

通过一个已知排列,生成下一个,实质是改变尽量短的后缀,使两个排列的差尽量小。

步骤:

1.从右至左选出第一个i,p[i]<p[i+1];

2.从右至i,选出第一个j,p[i]<p[j];

3.交换p[i],p[j];

4.倒置i+1~n ;


     

#include<stdio.h>
#include<algorithm>
using namespace std;
int c[1010][1010];
int p[1010];
int n,cnt;
void Deal()
{
	cnt=0;
	for(int j=1;j<=n;j++)p[j]=j,printf("%d",p[j]),c[cnt][j]=p[j];
	int i=n-1;cnt++;
	while(1)
	{
		while(i>0&&p[i+1]<p[i])i--;
		if(i==0)break;
		int j=n;
		for(;j>i;j--)
		{
			if(p[j]>p[i])break;
		}
		swap(p[i],p[j]);
		for(i=i+1,j=n;i<=j;i++,j--)
			swap(p[i],p[j]);
		for(j=1;j<=n;j++)
		{
			printf("%d",p[j]);
			c[cnt][j]=p[j];
		}
		printf("\n");
		cnt++;
		i=n-1;
	}
}
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		Deal();
	}
	return 0;
}

2.递归法

长的排列可以通过固定一部分化归成短的排列

既然有了递推关系,就可以通过递归实现了。

#include<stdio.h>
#include<algorithm>
using namespace std;
int c[1010][1010];
int p[1010];
int cnt=0;
int n;
void Deal(int k)
{
	if(k==n)
	{
		for(int i=1;i<=n;i++)
		{
			printf("%d",p[i]);
			c[cnt][i]=p[i];
		}
		cnt++;
		printf("\n");
		return;
	}
	sort(p+k,p+n+1);
	for(int i=k;i<=n;i++)
	{
		swap(p[i],p[k]);
		Deal(k+1);
		swap(p[i],p[k]);
	}
}
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		cnt=0;
		for(int i=1;i<=n;i++)
			p[i]=i;
		Deal(1);
	}
	return 0;
}


第二类:按非字典序生成


1.邻位交换法

n个元素的排列与n-1个元素的排列有关,因为如果我们已知n-1个元素的排列情况,可以通过不断插入第n 个元素生成n个元素的排列。即(n-1)!*n=n!

比如:1 2 3    1 3 2  3 1 2      

       2 1 3   2 3 1   3 2 1

#include<stdio.h>
#include<algorithm>
using namespace std;
int n,cnt;
int c[1010][1010];
int p[1010];
void Copy()
{
	printf("cnt:%d ",cnt);
	for(int i=1;i<=n;i++)
		c[cnt][i]=p[i],printf("%d",p[i]);
	printf("\n");cnt++;
}
void Deal()
{
	cnt=0;
    int num=2;
	for(int i=3;i<n;i++)
	{
		num*=i;
	}
	for(int i=1;i<=n;i++)p[i]=i;
	num/=2;
	while(num--)//S型插入
	{
		for(int i=n;i>1;i--)
		{
			swap(p[i],p[i-1]);
			Copy();
		}
		swap(p[n],p[n-1]);Copy();//生成新的n-1排列
		for(int i=1;i<n;i++)
		{
			swap(p[i],p[i+1]);
			Copy();
		}
		swap(p[1],p[2]);Copy();//生成新的n-1排列
	}
}
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		Deal();
	}
	return 0;
}

2.逆序列倒推

由于每一个逆序列都确定唯一原数列。逆序列较原序列好枚举。所以通过逆序列倒退原数列。

设逆序列p1,p2,...,pn

0<=p1<=n-1,0<=p2<=n-2,...0<=pn-1<=1,pn=0;

p1,p2,...,pn 可以通过模拟n位(n-i)进制的方法枚举出逆序列。

#include<stdio.h>
int n,cnt=0;
int c[1010][1010];
int p[1010];
int s[1010];
void cal()
{
	for(int i=1;i<=n;i++)
	{
		int num=0;
		for(int j=1;j<=n;j++)
		{
			if(c[cnt][j]==0)num++;
			if(p[i]+1==num)
			{
				c[cnt][j]=i;break;
			}
		}
	}
	for(int i=1;i<=n;i++)
		printf("%d",c[cnt][i]);
	printf(" ");
	for(int i=1;i<=n;i++)
		printf("%d",p[i]);
	printf("\n");
	cnt++;
}
void Deal()
{
	s[n-1]=1;cnt=0;
	for(int i=1;i<=n;i++)
	{	
		c[cnt][i]=i;
		printf("%d",i);
	}
	cnt++;
	printf("\n");
	while(1)
	{
		p[n]=0;
		for(int i=n-1;i>=1;i--)
		{
			p[i]+=s[i];
			if(p[i]>n-i)
			{
				p[i-1]++;
				p[i]-=n-i+1;
			}
		}
		if(p[0]==1)break;
		cal();
	}

}
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		Deal();
	}
	return 0;
}


2.由逆序列构建原排列


前面有些许涉及,这里再详细说下。

设逆序列为b1,b2,....,bn.所对应原排列为a1,a2,...,an

设置n个空位。

从1开始放。

由于1前面至少有b1个数,所以a1前至少有b1个空位。因为1之前没有放数,所以1放在a[b1]+1处.空位为 a_1~a_b1.

下面放2.

同样,2前面至少有b2个数,因为这b2个数全部大于2,还没有放进排列,所以2前要有把b2个空位(比2小的 已经放进排列中了。)。2放在第b2+1个空位处。

……

下面放k。

k前面至少有bk个数,k放在第bk+1个空位处(比k小的已经放进排列了)。

     剩下的空位为n-k+1。bk的取值范围为0<=bk<=n-k,1<=bk+1<=n-k+1.所以一定放的下。

#include<stdio.h>
int b[10010],a[10010];
int main()
{
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		for(int i=0;i<n;i++)
			scanf("%d",&b[i]);
		for(int i=0;i<n;i++)
		{
			int cnt=0;
			for(int j=0;j<n;j++)
			{
				if(a[j]==0)cnt++;
				if(cnt==b[i]+1)
				{
					a[j]=i+1;break;
				}
			}
		}
		for(int i=0;i<n-1;i++)
		printf("%d ",a[i]);
		printf("%d\n",a[n-1]);
		}
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值