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

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。比较经典,不需过多解释。

    原文作者:Z字形编排问题
    原文地址: https://blog.csdn.net/ohmygirl/article/details/7859497
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞