1.组合问题:
问题描述:对于一组各不相同的数字,从中任意抽取1-n个数字,构成一个新的集合。求出所有的可能的集合。例如,对于集合{1,2,3},其所有子集为{1},{2},{3},{1,2},{1,3},{2,3}{1,2,3}, 给定一个数组(元素各不相同),求出数组的元素的所有非空组合(即数组的所有非空子集)
解法一:位向量法。用一个辅助数组表示各个元素的状态。1表示在集合中,0表示不在数组中。递归地求解所有的子集。
算法描述如下://这里的算法对空集也输出了,可修改之使得只输出非空集合。
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
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叉数的深林。暴力搜索之,得到所有的全排列形式。代码如下:
#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);
}
解法二:模拟回溯法生成排列的过程,对于已知的一个序列,如果交换其中两个元素的,会得到新的序列。思路类似于生成组合问题。算法描述如下:
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()
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
/*
* 笛卡尔(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 );
?>
上述求全排列和子集的方法,称为回溯法。对于回溯法,有一个基本的框架(模式):
void backTrack(int n,int k){
if(符合条件){
输出;
return;
}
else{
执行代码;
backTrack(n,k+1);
代码回溯;
}
}
利用这个模式可以写出例如组合,全排列,子集,八皇后等问题。
八皇后问题的解法:http://blog.csdn.net/ohmygirl/article/details/6924229。比较经典,不需过多解释。