排列组合总结

组合

1.位运算实现求组合:

在此介绍二进制转化法,即,将每一个组合与一个二进制数相应起来,枚举二进制的同一时候,枚举每一个组合。

如字符串:abcde,则有

00000---------null

00001---------a

00010 --------b

00011---------ab

00100---------c

… …

11111--------abcde

给出程序例如以下所看到的:

#include <stdio.h>
#include <string.h>

void printCombination(char* str, int i);
void combination(char* str)
{
	int len = strlen(str);
	//共同拥有(1<<len)个组合,当中有一次什么都不打印外
	for(int i=0; i< (1<<len); ++i)
	{
		printCombination(str, i);
		printf("\n");
	}
}

void printCombination(char* str, int i)
{
	int len = strlen(str);
	for(int j=0; j<len; ++j)
	{
		//看s中哪些位为
		int s = i&(1<<j);
		if(s)
			printf("%c", str[j]);
	}
}	

int main()
{
	char str[] = "abc";
	combination(str);
}

2.递归的思路解决

#include <stdio.h>
#include <string.h>

void printCombination(char* str, int len, int m, int* arr, const int M);
void combination(char* str)
{
	int len = strlen(str);
	int* arr = new int[len];
	for(int i=1; i<=len; ++i)
	{
		printCombination(str, len, i, arr, i);
	}
	delete[] arr;
}
//从n中选m个数进行组合
/**************************************
a. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,
直到从n-(m-1)个数中选取个数为止。
b. 从n个数中选取编号次小的一个数,继续运行步。直到当前可选编号最大的数为m。
******************************************/
void printCombination(char* str, int len, int m, int* arr, const int M)
{
	for(int i=len; i>=m;--i)
	{
		//依次选择编号最大的数,次大的数....
		arr[m-1] = i-1;
		if(m>1)
		{
			//选择的数目大于,从剩余的i-1个数中选取m-1个数的组合
			printCombination(str,i-1,m-1,arr,M);
		}
		else
		{
			//打印M个数字
			for(int j=M-1; j>=0; --j)
			{
				printf("%c ", str[arr[j]]);
			}
			printf("\n");
		}
	}
}	

int main()
{
	char str[] = "abcd";
	combination(str);
	return 0;
}

3.非递归实现

首先,初始化一个n个元素的数组(所有由0。1组成)。将前m个初始化为1。后面的为0。这时候就能够输出第一个组合了。为1的元素的下标所相应的数。

算法開始:从前往后找。找到第一个10组合,将其反转成01,然后将其前面的所有1,所有往左边推,即保证其前面的1都在最左边。

然后就能够依照这个01序列来输出一个组合结果了。

而假设找不到10组合,就表示说全部情况都输出了。为什么?你想,(以n=5,m=3为例)一開始是11100,最后就是00111。已经没有10组合了。

这样的将问题转换为01序列(也就是真假序列)的想法值得我们考虑和借鉴。

比如求5中选3的组合:

1 1 1 0 0 //1,2,3

1 1 0 1 0 //1,2,4

1 0 1 1 0 //1,3,4

0 1 1 1 0 //2,3,4

1 1 0 0 1 //1,2,5

1 0 1 0 1 //1,3,5

0 1 1 0 1 //2,3,5

1 0 0 1 1 //1,4,5

0 1 0 1 1 //2,4,5

0 0 1 1 1 //3,4,5

实现代码例如以下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>   

int l=0;
//function definition
void composition(const char [],int,int);
void printCompostion(const char[],const bool[],int);   

//function implementation
void printCompostion(const char source[],const bool comp[],int n){
	int i;
    for (i=0;i<n;i++)
		if (comp[i]==true) printf ("%c-",source[i]);
    printf ("\n");   

}   

void compostion(const char* source,int n,int m){   

    bool * comp = (bool*)malloc(n*sizeof(bool));   

	int i;
	for (i=0;i<m;i++) comp[i]=true;
    for (i=m;i<n;i++) comp[i]=false;   

    printCompostion(source,comp,n);
	    l++;   

	    while(true){   

			for (i=0;i<n-1;i++)
	            if (comp[i]==true&&comp[i+1]==false) break;   

			if (i==n-1) return;  //all the compostion is found out   

	        comp[i]=false;
	        comp[i+1]=true;   

			int p=0;
			while (p<i){
	            while (comp[p]==true) p++;
				while (comp[i]==false) i--;
				if (p<i) {
					comp[p]=true;
					comp[i]=false;
	            }
			}
	        printCompostion(source,comp,n);
       l++;
    }
}   

//test function
void testCompostion(){   

	char* testcase = "abcdefghijklmno";
	int n=strlen(testcase);
    int m=7;
	compostion(testcase,n,m);
}
//main function
void main(){
	testCompostion();
	printf ("total=%d\n",l);
}

全排列:

1. 递归

分治算法:这个算法利用了分而治之的思想。我们先从2个数開始,比方说4,5,他们的全排列仅仅有两个45和54。

假设在前面加个3,那么全排列就是345,354,也就是3(54),括号表示里面的数的全排列。

当然还有4(35),5(34)...写到这里,各位看官应该已经看出点门道了吧。三个数的全排列,能够分为三次计算,第一次计算3和(45)的全排列。第二次计算4和(35)的全排列.....也就是说,将序列第一个元素分别与它以及其后的每个元素代换,得到三个序列,然后对这些序列的除首元素外的子序列进行全排列。思想事实上还是挺简单的:

代码实现例如以下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>   

#define LENGTH 27   

int n=0;   

void permute(int[],int,int);
void swapint(int &a,int &b);
void printIntArray (int[],int);   

//Function Implementation   

void swapint(int &a,int &b){
	int temp;
    temp = a;
    a = b;
    b = temp;
}   

void printIntArray(int target[],int length){
    int i;
    for (i=0;i<length;i++) printf ("%d",target[i]);
    printf ("\n");
}   

void permute(int target[],int begin,int end){   

    if (begin==end) {
        printIntArray(target,end+1);
        n++;
        return;
    }
    int i;
    for (i=begin;i<=end;i++){   

        swapint(target[begin],target[i]);
        permute(target,begin+1,end);
        swapint(target[begin],target[i]);   

    }
}   

//test Functions
void testPermute(){
    int len;
    scanf ("%d",&len);   

    int *testcase =(int *)malloc(len*sizeof(int));   

    int i;
    for (i=0;i<len;i++) testcase[i]=i+1;
    permute(testcase,0,len-1);   

}   

//Main Function
void main(){
    testPermute();
    printf ("n=%d",n);
}

2. 字典序

有时候递归的效率使得我们不得不考虑除此之外的其它实现。非常多把递归算法转换到非递归形式的算法是比較难的,这个时候我们不要忘记了标准模板库已经实现的那些算法,这让我们非常轻松。

STL有一个函数next_permutation(),它的作用是假设对于一个序列。存在依照字典排序后这个排列的下一个排列,那么就返回true且产生这个排列,否则返回false。注意。为了产生全排列,这个序列要是有序的,也就是说要调用一次sort。

直接用STL上的

#include "iostream"
#include "algorithm"
using namespace std;

void permutation(char* str,int length)
{
	sort(str,str+length);	//必须得先排序
	do
	{
		for(int i=0;i<length;i++)
			cout<<str[i];
		cout<<endl;
	}while(next_permutation(str,str+length));

}
int main(void)
{
	char str[] = "acb";
	cout<<str<<"全部全排列的结果为:"<<endl;
	permutation(str,3);
	system("pause");
	return 0;
}

当中next_permutation()的实现例如以下:

template<class BidirectionlIterator>
bool next_permutation(BidirectionalIterator firt,
					  BidirectionalIterator last)

{
	if(first == last) return false;	//空区间
	BidirectionalIterator i = first;
	++i;
	if(i == last) return false;	//仅仅有一个元素
	i = last;	//i指向尾端
	--i;

	for(;;){
		BidrectionalIterator ii = i;
		--i;
		//以上,锁定一组(两个)相邻元素
		if(*i < *ii)	//假设前一个元素小于后一个元素
		{
			BidrectionalIterator j = last;	//令j指向尾端
			while(!(*i < *--j));	//由尾端往前找,直到遇上比*i大的元素
			iter_swap(i,j);			//交换i,j
			reverse(ii, last);		//将ii之后的元素所有逆向重排
			return true;
		}
		if(i == first)				//进行到最前面了
		{
			reverse(first, last);	//所有逆向重置
			return false;
		}
	}
}

自己设计函数next_permutation():

#include <stdio.h>
#include <algorithm>
#include <string.h>

using namespace std;
//反转数组
void reverse(char* str, int first, int last)
{
	if(str == NULL || *str == ‘\0‘)
		return;

	while(first < last)
	{
		char ch = str[first];
		str[first] = str[last];
		str[last] = ch;
		first++;
		last--;
	}
}
//打印数组
void printfString(char* str)
{
	for(int i=0; i<strlen(str); ++i)
	{
		printf("%c", str[i]);
	}
	printf("\n");
}
//找到下一个数组
bool next_permutation(char* str)
{
	if(str == NULL || *str==‘\0‘)
		return false;

	int len = strlen(str);
	int i = len - 2;
	int ii = i+1;
	int j = len - 1;

	//从后端開始找到第一对str[i]<str[j]的数字
	while(str[i] > str[ii])
	{
		--i;
		--ii;
		//假设i<0,则表示已经所有排列
		if(i<0)
		{
			//所有翻转
			reverse(str, i+1, len-1);
			return false;
		}
	}
	//从后面找到第一个大于str[i]的数字
	while(str[j] < str[i])
	{
		--j;
	}

	//交换
	char ch = str[i];
	str[i] = str[j];
	str[j] = ch;
	//翻转j之后的数组
	reverse(str, ii, len-1);

	return true;

}

int main()
{
	char str[] = "cab";
	int len = strlen(str);
	//必须先排序
	sort(str, str + len);
	printfString(str);
	//打印全排列
	while(next_permutation(str))
	{
		printfString(str);
	}

	return 0;
}

參见:

http://blog.csdn.net/hackbuteer1/article/details/7462447

http://xiaomage.blog.51cto.com/293990/74094

http://blog.csdn.net/hackbuteer1/article/details/6657435

时间: 2024-08-27 03:30:06

排列组合总结的相关文章

HDU--5396(区间dp+排列组合)

做这道题的时候,想到会不会是dp,然后发现dp可做,但是一直被自己坑到死. 枚举最后合并的那个位置,然后对于加减号的,分成的前后两个部分都有不同的组合方法, (a1+a2........) +  (b1,b2.............)         对于每个a,被加b的个数的阶乘次 ,对于每个b,被加a的个数的阶乘次 减法同理 乘法特殊一点 (a1+a2........) *  (b1,b2.............)  乘法分配率,直接将两部分的总和相乘即可 想到这些还远远没有结束,因为最

排列组合

(常考)错位排列 有N封信和N个信封,每封信都不装在自己信封里的排列种数记作Dn,则 D1=0,D2=1,D3=2,D4=9,D5=44,D6=265 一.相邻问题---捆绑法 不邻问题---插空法 对于某几个元素不相邻的排列问题,可先将其他元素排好,再将不相邻元素在已排好的元素之间及两端空隙中插入即可. [例题1]一张节目表上原有3个节目,如果保持这3个节目的相对顺序不变,再添进去2个新节目,有多少种安排方法? A.20 B.12 C.6 D.4 [答案]A. [解析] 以下内容需要回复才能看

hdu 1799 (循环多少次?)(排列组合公式)

循环多少次? Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 3051    Accepted Submission(s): 1117 Problem Description 我们知道,在编程中,我们时常需要考虑到时间复杂度,特别是对于循环的部分.例如, 如果代码中出现 for(i=1;i<=n;i++) OP ; 那么做了n次OP运算

排列组合问题

一.不同元素子集问题 78. Subsets Given a set of distinct integers, nums, return all possible subsets. 给定一组非重复数字,求出所有可能的子集 解析: 例如 [1,2,3],解法: 首先放[],然后往已有的[]中放1 1. 首先放1 此时已有[ [], 1 ] 2. 然后对[ [], 1 ] 放2 于是此时有 [ [], [1], [2], [1,2] ] 3. 然后对[ [], [1], [2], [1,2] ]

排列组合问题之圆形分布

1.问题1.1 团团坐有一张圆桌,坐了A,B,C,D四个人,已知,D在A的右边,C在D的对面,请问A,B,C,D,的坐次? 解答:这个问题相对简单,我们纸上画一画,就能画出他们的可能的位置了 但是,可能还有一种解,比如我们把A,B,C,D依次右转一个位,也是满足条件的,而且只要保持他们的相对位置不变,依次右转n个位都是问题的解,而且还有个有趣的事情,当他们转了一圈(即右转4个位)后,他们右回到原位了 2.圆形分布上面这个问题就是一种圆形分布,那么他和直线分布的区别在哪里呢?又有什么联系呢?上面文

【noi 2.6_9288】&amp;【hdu 1133】Buy the Ticket(DP / 排列组合 Catalan+高精度)

题意:有m个人有一张50元的纸币,n个人有一张100元的纸币.他们要在一个原始存金为0元的售票处买一张50元的票,问一共有几种方案数. 解法:(学习了他人的推导后~) 1.Catalan数的应用7的变形.(推荐阅读:http://www.cnblogs.com/chenhuan001/p/5157133.html).P.S.不知我之前自己推出的公式“C(n,m)*C(2*m,m)/(m+1)*P(n,n)*P(m,m)”是否是正确的. (1)在不考虑m人和n人本身组内的排列时,总方案数为C(m+

用递归写排列组合问题

最近递归弄的人头疼,但是这两天看过来也稍微总结了一些不能称得上是技巧的技巧吧 问题如下,将1,2,3,4这四个数字排列组合的输出来,看网上有个很二的方法吧,就是将10000以内的数全部输出再筛选,对此有点无语,但是程序倒是挺好编的,嘿嘿 回归到正题中,用递归的思想解决 (1)采用旋转数字的方法,当步长为1时,1234还是1234,步长为2的时候,1234可以变为1243.1324...,步长为3 的时候,1234可以变为1423,诸如此类,最重要的能体现递归的就是将每次递归一次的数字还可以接着旋

LightOJ1005 Rooks(DP/排列组合)

题目是在n*n的棋盘上放k个车使其不互相攻击的方案数. 首先可以明确的是n*n最多只能合法地放n个车,即每一行都指派一个列去放车. dp[i][j]表示棋盘前i行总共放了j个车的方案数 dp[0][0]=1 转移就是从第i-1行转移到第i行,对于第i行要嘛放上一个车要嘛不放,放的话有n-j-1种方法.即dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*(n-j-1). 1 #include<cstdio> 2 #include<cstring> 3 using na

Codeforces Gym 100187D D. Holidays 排列组合

D. Holidays Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100187/problem/D Description Everyone knows that the battle of Endor is just a myth fabled by George Lucas for promotion of his movie. Actually, no battle of Endor has

hdu 4465 Candy (快速排列组合 )

Candy Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2115    Accepted Submission(s): 910 Special Judge Problem Description LazyChild is a lazy child who likes candy very much. Despite being ve