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

1.组合问题:

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

    解法一:位向量法。用一个辅助数组表示各个元素的状态。1表示在集合中,0表示不在数组中。递归地求解所有的子集。
            算法描述如下://这里的算法对空集也输出了,可修改之使得只输出非空集合。

  1. void getSubSet(int *a,int *b,int n,int k){  
  2.     if(k==n){  
  3.         for(int i = 0;i < n;i++){  
  4.             if(i == 0){  
  5.                 printf("{ ");  
  6.             }else if(i==(n-1)){  
  7.                 printf(" }\n");  
  8.             }  
  9.             if(b[i]){  
  10.                 printf("%d, ",a[i]);  
  11.             }  
  12.         }  
  13.         return ;  
  14.     }  
  15.     b[k] = 1;  
  16.     getSubSet(a,b,n,k+1);  
  17.     b[k] = 0;  
  18.     getSubSet(a,b,n,k+1);  
  19. }  


        解法二:位图的思想。思路类似与解法一位向量。用n个位来保存相应的元素是否在集合中,如果在集合中,相应位为1.否则为0;
            代码示例://注:这里用的是位数组而不是c++中的bitmap
  1. void print_subset(int n,int s){  
  2.     printf("{");  
  3.     for(int i = 0;i<n;i++){  
  4.         if(s&(1<<i)) printf("%d ",i);//或者a[i]  
  5.     }  
  6.     printf("}\n");  
  7. }  
  8.   
  9. void subset(int n){  
  10.     for(int i= 0;i<(1<<n);i++){  
  11.         print_subset(n,i);  
  12.     }  
  13. }  


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

2.。排列问题。

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

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

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. void output(int *a,int n){  
  5.     for(int i = 0;i<n;i++){  
  6.         printf("%d ",a[i]);  
  7.     }  
  8.     printf("\n");  
  9. }  
  10.   
  11. void perm(int *a,int *b,int n,int k){  
  12.     int i,j;  
  13.     if(n==k){  
  14.         output(b,n);  
  15.     }else{  
  16.         for( i = 0;i < n;i++){  
  17.             int flag = 1;  
  18.             for( j = 0;j < k;j++){  
  19.                 if(b[j] == a[i]){  
  20.                     flag = 0;  
  21.                 }  
  22.             }  
  23.             if(flag){  
  24.                 b[k] = a[i];  
  25.                 perm(a,b,n,k+1);  
  26.             }  
  27.         }  
  28.     }  
  29. }  
  30.   
  31. int main(){  
  32.     int *a =new int[3];  
  33.     int *b =new int[3];  
  34.     for(int i = 0;i<3;i++){  
  35.         a[i] = i+1;  
  36.         b[i] = i+1;  
  37.     }  
  38.     perm(a,b,3,0);  
  39. }  

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

  1. void permutation(int *a, int n,int k){  
  2.     if(n==k){  
  3.         printf("{");  
  4.         for(int i = 0;i<n;i++){  
  5.             printf("%d ",a[i]);  
  6.         }  
  7.         printf("}\n");  
  8.         return ;  
  9.     }  
  10.     for(int i = k;i<n;i++){  
  11.         swap(&a[i],&a[k]);  
  12.         permutation(a,n,k+1);  
  13.         swap(&a[i],&a[k]);  
  14.     }  
  15. }  


        解法三:c++ 中STL中next_permutation()方法。注意这种方法要得到所有的排列,需要原始数组为递增有序的,可先对其qsort()
  1. do{  
  2.     printf("{");  
  3.      for(int i = 0;i<N;i++){  
  4.         printf("%d ",a[i]);  
  5.     }  
  6.     printf("}\n");  
  7. }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对集合的笛卡尔积由此递归定义得到。

  1. <?php  
  2.            /* 
  3.             * 笛卡尔(Descartes)乘积又叫直积。设A、B是任意两个集合,在集合A中任意取一个元素x,在集合B中任意取一个元素y,组成一个有序对(x,y), 
  4.             * 把这样的有序对作为新的元素,他们的全体组成的集合称为集合A和集合B的直积,记为A×B,即A×B={(x,y)|x∈A且y∈B}。 
  5.             * @author xuzuning 
  6.             */  
  7.             function Descartes() {  
  8.                 $t = func_get_args();  
  9.                 if(func_num_args() == 1) {  
  10.                    return call_user_func_array( __FUNCTION__$t[0] );  
  11.                 }  
  12.                 $a = array_shift($t);  
  13.                 if(! is_array($a)) {  
  14.                     $a = array($a);  
  15.                 }  
  16.                 $a = array_chunk($a, 1);//目的是分解成array(a)这样的形式,便于后面的合并  
  17.                 do {  
  18.                     $r = array();  
  19.                     $b = array_shift($t);  
  20.                     if(! is_array($b)) $b = array($b);  
  21.                     foreach($a as $p)  
  22.                         foreach(array_chunk($b, 1) as $q)  
  23.                             $r[] = array_merge($p$q);  
  24.                     $a = $r;  
  25.                 }while($t);  
  26.                 return $r;  
  27.             }  
  28.   
  29.             $arr = array(  
  30.                array('a1','a2',),  
  31.                'b',  
  32.                array('c1','c2',),  
  33.                array('d1','d2','d3')  
  34.             );  
  35.             $r = Descartes( $arr );  
  36.         ?>  


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

[plain]  view plain copy print ?
  1. void backTrack(int n,int k){  
  2.     if(符合条件){  
  3.         输出;  
  4.         return;  
  5.     }  
  6.     else{  
  7.         执行代码;  
  8.         backTrack(n,k+1);  
  9.         代码回溯;  
  10.     }  
  11.            
  12.  }  

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

八皇后问题的解法:http://blog.csdn.net/ohmygirl/article/details/6924229。比较经典,不需过多解释。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值