全排列和组合(STL和C语言递归解法)

 一.排列和组合:

①排列的定义:从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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值