您的位置:首页 > 其它

【算法总结-排列组合与子集问题】排列组合与子集问题

2015-08-11 16:41 423 查看
1.组合问题:

问题描述:对于一组各不相同的数字,从中任意抽取1-n个数字,构成一个新的集合。求出所有的可能的集合。例如,对于集合{1,2,3},其所有子集为{1},{2},{3},{1,2},{1,3},{2,3}{1,2,3}, 给定一个数组(元素各不相同),求出数组的元素的所有非空组合(即数组的所有非空子集)

解法一:位向量法。用一个辅助数组表示各个元素的状态。1表示在集合中,0表示不在数组中。递归地求解所有的子集。

算法描述如下://这里的算法对空集也输出了,可修改之使得只输出非空集合。

[cpp] view
plaincopyprint?

void getSubSet(int *a,int *b,int n,int k){

if(k==n){

for(int i = 0;i < n;i++){

if(i == 0){

printf("{ ");

}else if(i==(n-1)){

printf(" }\n");

}

if(b[i]){

printf("%d, ",a[i]);

}

}

return ;

}

b[k] = 1;

getSubSet(a,b,n,k+1);

b[k] = 0;

getSubSet(a,b,n,k+1);

}

解法二:位图的思想。思路类似与解法一位向量。用n个位来保存相应的元素是否在集合中,如果在集合中,相应位为1.否则为0;

代码示例://注:这里用的是位数组而不是c++中的bitmap

[cpp] view
plaincopyprint?

void print_subset(int n,int s){

printf("{");

for(int i = 0;i<n;i++){

if(s&(1<<i)) printf("%d ",i);//或者a[i]

}

printf("}\n");

}

void subset(int n){

for(int i= 0;i<(1<<n);i++){

print_subset(n,i);

}

}

只需要调用subset(n)即可输出1->n个数字的所有组合。或者修改输出部分为输出一个特定集合的组合。

2.。排列问题。

给定一组不相同的数字。求出这n个数字的各种排列形式。称为排列问题。

解法一:暴力搜索,对于一个全排列问题,相当于搜索一个具有n个n-1叉数的深林。暴力搜索之,得到所有的全排列形式。代码如下:

[cpp] view
plaincopyprint?

#include <stdio.h>

#include <stdlib.h>

void output(int *a,int n){

for(int i = 0;i<n;i++){

printf("%d ",a[i]);

}

printf("\n");

}

void perm(int *a,int *b,int n,int k){

int i,j;

if(n==k){

output(b,n);

}else{

for( i = 0;i < n;i++){

int flag = 1;

for( j = 0;j < k;j++){

if(b[j] == a[i]){

flag = 0;

}

}

if(flag){

b[k] = a[i];

perm(a,b,n,k+1);

}

}

}

}

int main(){

int *a =new int[3];

int *b =new int[3];

for(int i = 0;i<3;i++){

a[i] = i+1;

b[i] = i+1;

}

perm(a,b,3,0);

}

解法二:模拟回溯法生成排列的过程,对于已知的一个序列,如果交换其中两个元素的,会得到新的序列。思路类似于生成组合问题。算法描述如下:

[cpp] view
plaincopyprint?

void permutation(int *a, int n,int k){

if(n==k){

printf("{");

for(int i = 0;i<n;i++){

printf("%d ",a[i]);

}

printf("}\n");

return ;

}

for(int i = k;i<n;i++){

swap(&a[i],&a[k]);

permutation(a,n,k+1);

swap(&a[i],&a[k]);

}

}

解法三:c++ 中STL中next_permutation()方法。注意这种方法要得到所有的排列,需要原始数组为递增有序的,可先对其qsort()

[cpp] view
plaincopyprint?

do{

printf("{");

for(int i = 0;i<N;i++){

printf("%d ",a[i]);

}

printf("}\n");

}while(next_permutation(a,a+N));

//TODO 全排列中有重复元素的算法总结

3.笛卡尔积问题。

@xuzuning

问题描述:笛卡尔(Descartes)乘积又叫直积。设A、B是任意两个集合,在集合A中任意取一个元素x,在集合B中任意取一个元素y,组成一个有序对(x,y),

把这样的有序对作为新的元素,他们的全体组成的集合称为集合A和集合B的直积,记为A×B,即A×B={(x,y)|x∈A且y∈B}。n对集合的笛卡尔积由此递归定义得到。

[php] view
plaincopyprint?

<?php

/*

* 笛卡尔(Descartes)乘积又叫直积。设A、B是任意两个集合,在集合A中任意取一个元素x,在集合B中任意取一个元素y,组成一个有序对(x,y),

* 把这样的有序对作为新的元素,他们的全体组成的集合称为集合A和集合B的直积,记为A×B,即A×B={(x,y)|x∈A且y∈B}。

* @author xuzuning

*/

function Descartes() {

$t = func_get_args();

if(func_num_args() == 1) {

return call_user_func_array( __FUNCTION__, $t[0] );

}

$a = array_shift($t);

if(! is_array($a)) {

$a = array($a);

}

$a = array_chunk($a, 1);//目的是分解成array(a)这样的形式,便于后面的合并

do {

$r = array();

$b = array_shift($t);

if(! is_array($b)) $b = array($b);

foreach($a as $p)

foreach(array_chunk($b, 1) as $q)

$r[] = array_merge($p, $q);

$a = $r;

}while($t);

return $r;

}

$arr = array(

array('a1','a2',),

'b',

array('c1','c2',),

array('d1','d2','d3')

);

$r = Descartes( $arr );

?>

上述求全排列和子集的方法,称为回溯法。对于回溯法,有一个基本的框架(模式):

[plain] view
plaincopyprint?

void backTrack(int n,int k){

if(符合条件){

输出;

return;

}

else{

执行代码;

backTrack(n,k+1);

代码回溯;

}

}

利用这个模式可以写出例如组合,全排列,子集,八皇后等问题。

八皇后问题的解法:http://blog.csdn.net/ohmygirl/article/details/6924229。比较经典,不需过多解释。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: