蓝桥杯实用算法——排列组合

 

1.全排列

定义:从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。

公式:全排列数f(n)=n!(定义0!=1)

以上是对全排列的简单说明,我们先来看一个简单的例子:写出“ABCDE”的全排列:

【问题分析】我们用递归的方法来尝试,那么就需要将大问题转化为相似的小问题。当前字符串的所以排列的可能情况,就是遍历字符串,将当前的字符串作为开头,加上剩余的字符全排列情况之和。换句话说就是在“ABCDE”这个字符串的全排列情况,每一个字符都可以作为开头,我们要做的就是将每一个字符拿出来作为开头,然后将剩下的所有字符进行全排列!!!这样就实现了将问题转化为相似的小问题啦!!也就是递归的形式已经显然易见了!话不多说,具体细节在代码注释中 

package demo01;

import java.util.List;
import java.util.Vector;

public class D2 {
//f方法就是用于求串str的全排列,返回的是一个list类型,存放串的所有排列情况
	static List f(String str) {
		List lst=new Vector();//首先要创建一个存放结果的list容器
		if(str.length()==1) {//这是递归结束的条件,我们不断缩小递归的规模。当串只有一个字符时,递归结束
			lst.add(str);//只有一个字符,这是全排列也就是它本身,直接添加到容器中
			return lst;//返回容器,递归结束
		}
		for(int i=0;i<str.length();i++) {
			char x=str.charAt(i);//取出串的每一个字符作为开头
			List t=f(str.substring(0, i)+str.substring(i+1));
            //将除去x字符外的所有字符的全排列的结果交给容器t
			for(int k=0;k<t.size();k++) {//将容器t中的每一个串与x进行拼接,就是str串的全排列
				lst.add(""+x+t.get(k));
			}
		}
		return lst;//返回lst容器
	}
	public static void main(String []args) {
		List lst=f("ABC");
		for(int i=0;i<lst.size();i++) {
			System.out.println(lst.get(i));
		}
	}
}

以上关于串的全排列是用Java来实现的,似乎对于c++而言,这种方法并没有太大的优势。在这里介绍最经典的全排列的递归算法,c++和Java都能轻松驾驭。为了更好的解释,我们以【1,2,3】的全排列为例

容易发现,它的全排列情况有【123】【132】【213】【231】【312】【321】

现将【123】的2/3位互换得【132】

2/3位互换恢复为【123】

将【123】的第一位分别于二三位互换分别得【213】【321】

再分别将其2/3位进行互换得【231】【312】

可能还是有点乱,总的来说,就是将某位于后面的每一位进行交换就得到了一组全排列

Java代码如下

public class D3 {
	//a是考察的数组
	//k是当前考虑的位置
	static void f(char []aa,int k) {
		if(k==aa.length-1) {//递归的出口,当进行到最后一位时,就没有可交换的元素了,直接出来吧!!
			if(aa[0]=='A') {
				System.out.println(String.valueOf(aa));
			}
			return;
		}
		for(int i=k;i<aa.length;i++) {
			{char t=aa[i];aa[i]=aa[k];aa[k]=t;}//试探//将当前位于后面每一位进行交换
			f(aa,k+1);//调用递归
			{char t=aa[i];aa[i]=aa[k];aa[k]=t;}//回溯//必须恢复交换前的位置,基于该次序的交换不能乱!
		}
	}
	public static void main(String []args) {
		f("ABCDE".toCharArray(),0);
	}
}

c++代码如下

#include <iostream>
using namespace std;

void prem(char a[],int begin,int end){
	if(begin==end-1){
		cout<<a<<endl;
		return;
	}
	for(int i=begin;i<end;i++){
		swap(a[begin],a[i]);//试探 
		prem(a,begin+1,end);
		swap(a[begin],a[i]);//回溯 
	}
} 
int main(){
	char a[5]={'A','B','C','D','E'};
	prem(a,0,5);
} 

2.组合

我们先采用循环暴力求解的办法吧!!

假设从“ABCDE”中选3个字符的组合情况

循环暴力求解嘛!第一层循环,i可能从A取到E,第二层循环j,可能从A取到E,第三层循环k,可能从A取到E。三层循环后,满足i、j、k不相等的就是满足题目的组合情况。

仔细想一想??会不会有问题???是这样吗?组合对顺序并没有要求,所以在上面的循环暴力法之下,会出现ABC、BAC都是满足条件的,也就是会让同一个结果重复出现。那么我们该怎么样避免这个问题呢??

可不可以这样,我们将i、j、k的大小定下来,这样重复的项就只能出现一次(ABC、ACB、BCA……我们要求字典顺序i<j<k,那么就只有ABC一种情况会被保留下来)。这个时候,再用暴力的方法该怎么做呢?

第一层循环,i当然还是从A取到E,第二层循环,j从i的后一个开始取到E,第三层循环,k从j的后一个开始取到E,这样得到的三个字母组合一定是没有重复项的。仔细想想,是不是这个道理哈?!

来!上代码!

#include <iostream>
using namespace std;
int main(){
	for(char i='A';i<='E';i++){
		for(char j=i+1;j<='E';j++){
			for(char k=j+1;k<='E';k++){
				cout<<i<<j<<k<<endl;
			}
		}
	}
    return 0;
} 

如果用递归呢???递归??没错!递归!

递归就是将问题转化为小规模的相似的问题~~自己做少量的工作,其余的工作交给别人来做!

从m个中取n个,怎么递归呢?我们设一个递归函数 int f(int m,int n);表示的就是从m个球中取n个球的组合数。我们就考虑抓取的第n次,对于某一个特定的球而言,这第n次抓取,要么取这个球,要么不取这个球。我们把取这个球和不取这个球的所有情况加起来不就是取n次的所有情况吗?是不是这个道理

代码如下(java实现)

public class D4 {
	//m个中取n个
	static int f(int m,int n) {
		if(m==n)return 1;
		if(n==0)return 1;
		return f(m-1,n)+f(m-1,n-1);
	}
	public static void main(String []args) {
		System.out.println(f(5,3));
	}
}

我们都知道,循环和递归之间有着很神奇的联系。对于之前的循环暴力求解法,有没有相应的递归解法呢??当然是有的,思想是一样的,只是实现的手法不一样,直接上代码吧!

还是来说一下吧…………之所以刚刚能够轻松的用循环暴力法求解,那是因为字符串是固定的(“ABCDE”中取3个),取多少个也是固定的,所以循环的层数也就能确定下来。但是如果不确定呢??

在三层循环的时候,每一层循环做的都是相似的操作——从前一个已经确定的字符的后面任取一个字符!

那么我们自然就能想到用递归来代替循环求解。设一个函数static List f(String s,int n) ,该函数的作用,就是在字符串s中取n个的组合情况。在这个函数中,我们要做的就是在字符串中任取一个字符m,然后利用递归,在该字符后的剩下的字符中取n-1个字符。大概就是这个思想,现在看代码趴!

Java版本: 

public class D5 {

	static List f(String s,int n) {
		List lst=new Vector();
		if(n==0) {
			lst.add("");//什么也不取,也是一种方法!
			return lst;
		}
		for(int i=0;i<s.length();i++) {
			char m=s.charAt(i);//取出第i位
			List t=f(s.substring(i+1),n-1);//在取出的该字符的后面选出剩下的n-1个
			for(int k=0;k<t.size();k++) {
				lst.add(""+m+t.get(k));//加入到结果容器中
			}
		}
		return lst;
	}
	public static void main(String[] args) {
		List lst=f("ABCDE",3);
		for(int i=0;i<lst.size();i++) {
			System.out.println(lst.get(i));
		}
	}
}

c++版: 

public class E2 {

	//a用来存放原来的数组
	//n位个数
	//begin为开始的位置
	//k为当前考虑的位置
	//x数组用来存放个数
	static void work(int []x) {
		for(int i=0;i<x.length;i++) {
			System.out.print(x[i]);
		}
		System.out.println();
	}
	static void f(int []a,int []x,int begin,int k,int n) {
		if(k==x.length) {
			if(n==0) {
				work(x);
			}
			return;
		}
//		if(begin==a.length) {
//			return;
//		}
		for(int i=begin;i<a.length;i++) {
			x[k]=a[i];//试探
			f(a,x,i+1,k+1,n-1);
			x[k]=0;//回溯
		}
	}
	
	public static void main(String []args) {
		int []a= {1,2,3,4,5};
		int []x=new int [3];
		f(a,x,0,0,3);
	}
}

3.有重复情况的排列组合

3.1有重复的全排列

在上面已经介绍过了,全排列的实际上就是遍历数组,将当前位置的元素与后面的元素进行交换。有重复项时,只与第一个进行交换,后面的不再进行交换。也就是多加一个判重的函数,代码如下,有一个相应的例题,就是扑克牌AA223344排序的问题

https://blog.csdn.net/jfwzy109127/article/details/87800495

#include <iostream>
#include <cstring>
using namespace std;
//第i个元素是否在前面元素[K……i-1]中出现过 
int dup(char a[],int k,int i){
	if(i>k){
		for(int j=k;j<i;j++){
			if(a[j]==a[i]){
				return 0;
			}
		}
	} 
	return 1;
}
void prem(char a[],int begin,int end){
	if(begin==end-1){
		cout<<a<<endl;
		return;
	}
	for(int i=begin;i<end;i++){
		if(dup(a,begin,i)){
			swap(a[i],a[begin]);
			prem(a,begin+1,end);
			swap(a[i],a[begin]);
		}
	}
} 
int main(){
	string s="AA223344";
	char s2[8];
	strcpy(s2,s.c_str());
	prem(s2,0,8);
}

3.2有重复项的组合问题

我还没想好怎么解释………………那就……emmm先看代码哈

通俗的将,就是将每一个元素能取到的值全部遍历一遍,满足条件的输出!

//AAABBC中任取3个
public class D62 {

	static void work(int []x) {
		for(int i=0;i<x.length;i++) {
			for(int j=0;j<x[i];j++) {
				System.out.print((char)(i+'A'));
			}
		}
		System.out.println();
	}

	//a数组存放每个元素最多重复的次数
	//x位实际上选取的每个元素的个数
	//k为当前考虑的对象
	//goal为目标的差值
	static void f(int []a,int []x,int k,int goal) {
		if(k==x.length) {
			if(goal==0) {
				work(x);
			}
			return;
		}
		for(int i=0;i<=Math.min(a[k], goal);i++) {
			x[k]=i;//试探,假设第k为元素选取i次
			f(a,x,k+1,goal-i);
			x[k]=0;//回溯
		}
	}
	public static void main(String[] args) {
		int []a= {3,2,1};
		int []x=new int [a.length];
		f(a,x,0,3);
	}
}

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值