一.排列和组合:
①排列的定义:从n个不同元素中,任取m(m≤n,m与n均为自然数,下同)个元素按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列;从n个不同元素中取出m(m≤n)个元素的所有排列的个数,叫做从n个不同元素中取出m个元素的排列数,用符号 A(n,m)表示
②组合的定义:从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。用符号 C(n,m) 表示。
排列和组合问题是在暴力枚举的时候经常遇到的,一般有三种常见情况:
1)打印n个数的全排列,共n!个;
2)打印n个数中任意m个数的全排列;共n!/(n-m)!个;
3)打印n个数中任意m个数的组合,共
组合与排列的区别在于组合里每一个数都要大于前一个数。
二.全排列的定义
1.什么是全排列
从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。
2.举例
问题:打印n个数的全排列
3.代码实现:
1) STL的next_permutation()实现
3个字符{a,b,c}组成的序列,next_permutation()函数能按字典序返回6个组合:abc,acb,bac,bca,cab,cba;
next_permutation()函数的定义有两种形式:
bool next_permutation(BidrectionalIterator first,BidirectionalIterator last);
bool next_permutation(BidrectionalIterator first,BidirectionalIterator last,Compare comp);
函数返回布尔值:如果没有下一个排列组合,返回false,否则true。每执行next_permutation()函数一次,会把新的排列放到原来的空间里;
注意:函数的排列范围是[first,last),包括first,不包括last;
next_permutation()函数从当前的全排列开始,逐个输出更大的全排列,而不是输出所有的全排列
如果要得到所有的全排列,需要从最小i的全排列开始,如果初始的全排列不是最小的,先用sort()函数排序,得到最小排列后,在使用next_permutation()函数;
例如:
#inclde<bits/stdc++.h>
using namespace std;
int main()
{
string s="bca";
sort(s.begin(),s.end());//字符串内部排序,得到最小的排列abc
do{
printf("%s",s);
}while (next_permutation(s.begin(),s.end()));
return 0;
}
//分六行输出abc acb bac bca cab cba
注意:如果序列中有重复元素,next_permutation函数生成的排列会去重,例如输入序列为aab,上面代码输出{aab,aba,baa}
STL还有一个全排列prev_permutation(),即求前一个排列组合,与next_permutation函数相反,即从大到小排列;
next_permutation函数虽然很方便,但是不能输出n个数中取m个数的部分排列,自写函数可以;
2)自写排列函数(递归)
①打印全排列的方案
用b[]记录一个新的全排列,第一次进入bfs函数时,b[0]在n个数中选一个,第二次进入bfs函数时b[1]在剩下的n-1个数中选一个,......,以此类推,用vis[]记录某个数是否已经被选过,选用过的数不能继续被选。
代码可以从小到大打印全排列,前提是a[]中的数字是从小到大的,所以先对a[]排序即可
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int a[20]={1,12,3,4,15,6,17,8,9,10,11,2,23};
int vis[20];
int b[20];
int cmp (const void *a,const void *b)
{
return *(int*)a>*(int*)b;
}
void dfs(int s,int t)
{
int i;
if(s==t)//递归结束,产生一个全排列
{
for(i=0;i<t;i++)//输出一个排列
printf("%d ",b[i]);
printf("\n");
return ;
}
for(i=0;i<t;i++)
if(!vis[i])
{
vis[i]=1;
b[s]=a[i];
dfs(s+1,t);
vis[i]=0;
}
}
int main()
{
int n=3,i;//前n个数的全排列
qsort(a,n,sizeof(a[1]),cmp);
dfs(0,n);
return 0;
}
结果:
注意:全排列的算法复杂度为(O(n!))对应全排列问题不可能有复杂度小于O(n!)的算法,因为输出的排列数量就是n!;
如果需要打印n个数中任意m个数的排列,如在四个数中取任意三个数的排列,把上述代码中的第21行改为n=4.然后再dfs()函数中修改第七行,如下所示:
if(s==3){//递归结束,取三个数产生一个排列
for(i=0;i<3;i++)//打印四个数中的三个数的排列
printf("%d ",b[i]);
printf("\n");
return ;
}
注意:对于某些全排列问题,实际上并不用生成一个完整排列;可以使用剪枝优化。
②打印全排列的次数
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define swap(a,b){int temp=a;a=b;b=temp;}
int data[20]={1,12,3,4,15,6,17,8,9,10,11,2,23};
int num=0;
int cmp (const void *a,const void *b)
{
return *(int*)a>*(int*)b;
}
int Perm(int begin,int end)
{
int i;
if(begin==end)
num++;
else
for(i=begin;i<=end;i++)
{
swap(data[begin],data[i]);//把当前第一个数与后面的所有数交换位置
Perm(begin+1,end);
swap(data[begin],data[i]);//恢复,用于下一次交换
}
}
int main()
{
Perm(0,9);//糗事个数的全排列
printf("%d",num);
return 0;
}
在算法理论中,对必须要出书的元素进行的技术叫做“平凡下界”,这是程序运行所需要的最少花费
打印n个数中任意m个数的全排列,只需要在Prem中修改一个地方就可以了
if(bgin==3)
{
for(i=0;i<=3;i++)printf("%d",data[i]);
num++;
}
三.组合
题目描述
排列与组合是常用的数学方法,其中组合就是从 n 个元素中抽出 ” 个元素(不分顺序且” < n),我们可以简单地将 n 个元素理解为自然数 1,2,...,n,从中任取 r 个数。
现要求你输出所有组合
例如n = 5,r = 3,所有组合为:
123,124,125,134,135,145,234,235.245.345.
输入格式
一行两个自然数 n,r(1<n<21,0 \le r \le n)n,r(1<n<21,0≤r≤n)。
输出格式
所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。
输入输出样样例:
输入:5 3
输出:
1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5
组合与排列的区别在于组合里每一个数都要大于前一个数。
方法一:
#include<stdio.h>
int r,a[100],n;
void dfs(int k){//搜索第k个数
int i;
if(k>r){
for(i=1;i<=r;i++){
printf("%3d",a[i]);//输出,场宽为三
}
printf("\n");
return ;//回到前一层
}
for(i=a[k-1]+1;i<=n;i++){
a[k]=i;
dfs(k+1);//直接进行下一次调用
}
}
int main()
{
scanf("%d %d",&n,&r);
dfs(1);
return 0;
}
方法二:
#include<stdio.h>
int total=0;
int a[50]; //a数组存每一次选的数
int b[50];
int n,r;
void print(){ //打印函数
int i;
for(i=1; i<=r; i++)
printf("%3d",a[i]);//输出,场宽为三
printf("\n");
}
void dfs(int t)//搜索函数
{
int i;
if(t>r) { //如果搜完了就输出a数组
print();
return;
}
for(i=1; i<=n; i++){ //枚举每一个数
if(!b[i]&&i>a[t-1]||t==1)//当i木有被使用过且i必须大于前个数但除1之外
{
a[t]=i; //将i存入a数组
b[i]=1;//标记i已被使用
dfs(t+1);//继续搜索
a[t]=0;//回溯一步
b[i]=0;
}
}
}
int main(){
scanf("%d %d",&n,&r);;//输入
dfs(1);
return 0;
}