排列组合问题(一)

 交换法全排列

#include<bits/stdc++.h>
#define MAX 100
using namespace std;
int n,a[MAX];
void permutation(int pos)
{
	if(pos==n)
	{
		for(int i=0;i<n;i++)
			cout<<a[i];
		cout<<endl;
		return ;
	}
	for(int i=pos;i<n;i++)//每次以pos为基准  i与其交换   
	{
		swap(a[i],a[pos]);
		permutation(pos+1);
		swap(a[i],a[pos]);//交换完必须要换回来  不然受上一个状态的影响 
	}
	
}
 
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i];
	permutation(0);
}

抽取法全排列

#include<bits/stdc++.h>
#define MAX 100
using namespace std;
int n,a[MAX],b[MAX],vis[MAX]={0};
void permutation(int pos)
{
	if(pos==n)
	{
		for(int i=0;i<n;i++)
			cout<<b[i];
		cout<<endl;
		return ;
	}
	for(int i=0;i<n;i++)//每次从0开始遍历  访问过就下一个 没有就递归进去 
	{
		
		if(!vis[i])
		{
			vis[i]=1;
			b[pos]=a[i];
			permutation(pos+1);
			vis[i]=0;
		}
	}
}
 
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i];
	permutation(0);
}

案例:三角形塔(堆积木)

#include<bits/stdc++.h>
#define MAX 100
using namespace std;
int n,a[MAX];
int child[2][6]={{1,3,4,6,7,8},{2,4,5,7,8,9}};
bool check()
{
	for(int i=0;i<6;i++)
	{
		if(a[child[0][i]]<a[i]||a[child[1][i]]<a[i])
			return false;
	}
	return true;
}
void permutation(int pos)
{
	if(pos==10)
	{
		if(check())
		{
			cout<<" "<<a[0]<<endl;
			cout<<" "<<a[1]<<" "<<a[2]<<endl;
			cout<<" "<<a[3]<<" "<<a[4]<<" "<<a[5]<<endl;
			cout<<a[6]<<" "<<a[7]<<" "<<a[8]<<" "<<a[9]<<endl;
		}	
		return ;
	}
	for(int i=pos;i<10;i++)//每次以pos为基准  i与其交换   
	{
		swap(a[i],a[pos]);
		permutation(pos+1);
		swap(a[i],a[pos]);//交换完必须要换回来  不然受上一个状态的影响 
	}
	
}
 
int main()
{
	for(int i=0;i<10;i++)//0-9
		a[i]=i;
	permutation(0);
}

前面两种算法都无法排除一种情况,就是有重复值出现的时候,排列也会反复列举,eg:1 3 4 4

交换法全排列改进算法:

#include<bits/stdc++.h>
#define MAX 100
using namespace std;
int n,a[MAX];
void permutation(int pos)
{
	if(pos==n)
	{
		for(int i=0;i<n;i++)
			cout<<a[i];
		cout<<endl;
		return ;
	}
	for(int i=pos;i<n;i++)//每次以pos为基准  i与其交换   
	{
		if(i>pos&&a[i]==a[i-1])//相邻的重复数字 i>pos写在前面是为了避免:1.i=0时数组越界  2.i==pos时 是第一次顺序遍历
			continue; 
		swap(a[i],a[pos]);
		permutation(pos+1);
		swap(a[i],a[pos]);//交换完必须要换回来  不然受上一个状态的影响 
	}
	
}
 
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i];
	permutation(0);
}

抽取法全排列改进算法:

#include<bits/stdc++.h>
#define MAX 100
using namespace std;
int n,a[MAX],b[MAX],vis[MAX]={0};
void permutation(int pos)
{
	if(pos==n)
	{
		for(int i=0;i<n;i++)
			cout<<b[i];
		cout<<endl;
		return ;
	}
	for(int i=0;i<n;i++)//每次从0开始遍历  访问过就下一个 没有就递归进去 
	{
		if(i!=0&&a[i]==a[i-1]&&!a[i-1])//i!=0防止数组越界 !a[i-1]表明数组没有被访问过  说明第一次顺序遍历已经过了 
			continue;
		if(!vis[i])
		{
			vis[i]=1;
			b[pos]=a[i];
			permutation(pos+1);
			vis[i]=0;
		}
	}
}
 
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i];
	permutation(0);
}

next_permutation方法获得全排列

C++ STL 中提供了 std::next_permutation std::prev_permutation 可以获取数字或者是字符的全排列,其中 std::next_permutation提供升序 std::prev_permutation提供降序
说明: next_permutation ,重新排列范围内的元素 [ 第一,最后一个)返回按照 字典序排列 的下一个值较大的排列。
返回值:如果有一个更高的排列,它重新排列元素,并返回 true ;如果这是不可能的(因为它已经在最大可能的排列),它按升序排列所有元素,并返回false

 举个栗子:李白打酒

话说大诗人李白,一生好饮。幸好他从不开车。
一天,他提着酒壶,从家里出来,酒壶中有酒 2 斗。他边走边唱:
无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。
这一路上,他一共遇到店 5 次,遇到花 10 次,已知最后一次遇到的是花,他正好把酒喝光了。
请你计算李白遇到店和花的次序,可以把遇店记为 a ,遇花记为 b 。则: babaabbabbabbbb 就是合理的次
序。像这样的答案一共有多少呢?请你计算出所有可能方案的个数(包含题目给出的)。
输出:14

 解析:

1 代表遇到花, 0 代表遇到店。最后一位固定为 1 ,前面 14 位必须有 9 1 5 0
元素不变,顺序可变,不同的排列顺序对应一种可能的方案,可以将解空间看成是 14 个数的排列问题。 但是存在重复的元素,应该采用可以处理重复元素的全排列方法。
#include<bits/stdc++.h>
#define MAX 100
using namespace std;
int n,a[MAX];
bool check()
{
	int s=2;
	for(int i=0;i<15;i++)
	{
		if(a[i]==0)
			s*=2;
		else
			s-=1;
	}
	return s==0;
}
int main()
{
	int i;
	for(i=0;i<5;i++)
		a[i]=0;
	for(i;i<15;i++)
		a[i]=1;
	i=0;
	do{
		if(check())
			i++;
	}while(next_permutation(a,a+14));
	cout<<i;
}

 举个栗子2:扑克序列

A A 2 2 3 3 4 4 , 一共 4 对扑克牌。请你把它们排成一行。
要求:两个 A 中间有 1 张牌,两个 2 之间有 2 张牌,两个 3 之间有 3 张牌,两个 4 之间有 4 张牌。
请填写出所有符合要求的排列中,字典序最小的那个。
例如: 22AA3344 A2A23344 字典序小。当然,它们都不是满足要求的答案。
请通过浏览器提交答案。 “A” 一定不要用小写字母 a ,也不要用 “1” 代替。字符间一定不要留空格。

 解析:全排列+条件判断

#include<bits/stdc++.h>
#define MAX 100
using namespace std;
int n;
bool check(char str[])
{
	string s(str,str+8);//取str数组的前8个
	int a1=s.find('A'),a2=s.rfind('A');//正向反向分别找'A'对应的下标
	int b1=s.find('2'),b2=s.rfind('2');
	int c1=s.find('3'),c2=s.rfind('3');
	int d1=s.find('4'),d2=s.rfind('4');
	if(abs(a2-a1)==2&abs(b1-b2)==3&&abs(c1-c2)==4&&abs(d1-d2)==5)
		return true;
	else
		return false;
}
int main()
{
	char str[]="223344AA";
	do{
		if(check(str))
		{
			cout<<str;
			break;
		}
	}while(next_permutation(str,str+8));
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值