这类题目属于搜索类问题,一般可采用搜索,进行暴力求解。
1 生成1~n的全排列
问题重述
输入一个自然数N(1<=N<=9),从小到大输出用1~N组成的所有排列,也就说全排列。
##代码
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n;
int visited[10]; //标记已使用过的数字
int A[10]; //存放排列好的数
int ans; //记录全排列的种数
void print_permutation(int cur)
{
if(cur==n) //边界条件
{
ans++;
for(int i=0;i<n;i++)
{
printf("%d",A[i]);
}
printf("\n");
return;
}
for(int i=1;i<=n;i++)
{
if(!visited[i]) //如果标记为0,则该数字未使用过
{
A[cur]=i;
visited[i]=1; //该数字已使用,标记为1
print_permutation(cur+1);
visited[i]=0; //很关键,一定要再次标记为0,为了进行下一次尝试
}
}
}
int main()
{
cin>>n;
print_permutation(0);
printf("%d\n",ans);
return 0;
}
2 生成无重集的排列
问题描述
输入N个自然数(1<=N<=9),从小到大输出用N个自然数组成的所有排列。例如:输入3个自然数2 6 9,输出
269
296
629
692
926
962
基于上面题解法,只需稍微改下,定义一个数组,用来存放N个自然数。
代码
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n;
int visited[10];
int A[10],As[10];
int ans;
void print_permutation(int cur)
{
if(cur==n)
{
ans++;
for(int i=0;i<n;i++)
{
printf("%d",A[i]);
}
printf("\n");
return;
}
for(int i=0;i<n;i++)
{
if(!visited[As[i]])
{
A[cur]=As[i];
visited[As[i]]=1;
print_permutation(cur+1);
visited[As[i]]=0;
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
cin>>As[i];
}
print_permutation(0);
printf("%d\n",ans);
return 0;
}
生成可重集的排列
问题描述
以上两种题都是对无重复元素的排列,但有时遇到有重复元素时该如何排列呢,比如对1 1 1全排列。
代码
#include<stdio.h>
int k=0;
void print_permutation(int n,int* p,int* A,int cur)
{
if(cur==n)//递归边界
{
++k;
for(int i=0;i<n;i++)
{
printf("%d",A[i]);
}
printf("\n");
}
else //尝试在A[cur]中填各种i
{
for(int i=0;i<n;i++)
{
if(!i||p[i]!=p[i-1])
{
int c1=0,c2=0;
for(int j=0;j<cur;j++) if(A[j]==p[i]) c1++;
for(int j=0;j<n;j++)
{
if(p[j]==p[i])
c2++;//如果i已经在A【0】~A【cur-1】中出现,则不再选
}
if(c1<c2)
{
A[cur]=p[i];
print_permutation(n,p,A,cur+1);//递归调用
}
}
}
}
}
int main()
{
int n,A[10000],cur=0,p[10000];
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&p[i]);
}
print_permutation(n,p,A,cur);
printf("%d\n",k);
return 0;
}
4 NYOJ 擅长排列的小明
该题是南阳oj上的一道搜索题,这道题只需要一个限制变量即可,解法跟第一道题相同
问题描述
小明十分聪明,而且十分擅长排列计算。比如给小明一个数字5,他能立刻给出1-5按字典序的全排列,如果你想为难他,在这5个数字中选出几个数字让他继续全排列,那么你就错了,他同样的很擅长。现在需要你写一个程序来验证擅长排列的小明到底对不对。
输入
第一行输入整数N(1<N<10)
输出
在1-n中选取m个字符进行全排列,按字典序全部输出,每种排列占一行,每组数据间不需分界。
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int n,m;
int cur=0,ans[10],book[10];
void dfs(int cur)
{
if(cur==m)
{
for(int i=0;i<m;i++)
{
printf("%d",ans[i]);
}
printf("\n");
return;
}
for(int i=1;i<=n;i++)
{
if(!book[i])
{
ans[cur]=i;
book[i]=1;
dfs(cur+1);
book[i]=0;
}
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>n>>m;
dfs(0);
}
return 0;
}
组合数
题目描述
找出从自然数1、2、… 、n(0< n<10)中任取r(0< r<=n)个数的所有组合。
输入
输入n,r
输出
按特定顺序输出所有组合。
特定顺序:每一个组合中的值从大到小排列,组合之间按逆字典序排列。
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int n,m;
int cur=0,ans[10];
void dfs(int cur,int h)
{
if(cur==m)
{
for(int i=0;i<m;i++)
{
printf("%d",ans[i]);
}
printf("\n");
return;
}
for(int i=h;i>0;i--)
{
ans[cur]=i;
dfs(cur+1,i-1);
}
}
int main()
{
cin>>n>>m;
dfs(0,n);
return 0;
}
K进制数
##问题描述
考虑包含N位数字的K-进制数. 定义一个数有效, 如果其K-进制表示不包含两连续的0.
考虑包含N位数字的K-进制数. 定义一个数有效, 如果其K-进制表示不包含两连续的0.
给定两个数N和K, 要求计算包含N位数字的有效K-进制数的总数.(2 <= K <= 10; 2 <= N; 4 <= N+K <= 18.)
例如:
1010230 是有效的7位4进制数
注意:0001235 不是7位数, 而是4位数.
输入
两个十进制整数N和K。
输出
一个十进制正整数,为包含N位数字的有效K-进制数的总数。
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int cs[10];
int n=0,k=0,ans=0;
void dfs(int cur)
{
if(cur==n)
{
ans++;
return;
}
for(int i=0;i<k;i++)
{
if((cur==0&&i==0)||(cur>0&&cs[cur-1]==0&&i==0)) continue; //这一步是关键,首先保证首位不能是0,并且相邻两位不能同是0
cs[cur]=i;
dfs(cur+1);
}
}
int main()
{
cin>>n>>k;
dfs(0);
cout<<ans<<endl;
return 0;
}
下一个排列函数next_permutation
c++的STL中提供了一个库函数next_permutation,该函数是“求下一个排列”。那该函数该如何使用呢,还是上代码来解释吧!
#include<cstdio>
#include<iostream>
#include<algorithm> //包含next_permutation,sort
using namespace std;
int main()
{
int n,p[10];
scanf("%d",&n);
for(int i=0;i<n;i++)
cin>>p[i];
sort(p,p+n); //使用该函数进行全排列条件,数组元素必须是从小到大排列
do
{
for(int i=0;i<n;i++)
{
printf("%d ",p[i]);
}
printf("\n");
} while (next_permutation(p,p+n)); //每次调用该函数,产生下一个排列
return 0;
}
上一个排列函数prev_permutation
类似的,有下一个排列函数,就有上一个排列函数,该函数与下一个排列函数使用方法相同,但该函数全排列前提条件自然就是数组元素从大到小排列了。就不展示该函数的代码了。
很好用的两个排列函数,学会了可以节省大量时间来编写排列递归函数,哈哈哈!