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.youkuaiyun.com/ohmygirl/article/details/6924229。比较经典,不需过多解释。