解决这个问题一般有以下击中方法:
1.普通递归。
2.回溯法(决策树)对每个元素决策,取还是不取,分别递归。其实还是递归
3.位图(用n位表示二进制数表示各个字符出现的可能性,1表示出现,0表示不出现)
1.递归
设计思路:
首先,递归是使用的if else结构。
然后,就是if中填条件,再在else写调用自身的函数。
详细思路,请看代码。
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int SIZE=3;
void Subset(char str[], int n)
{
if(n==0) { //控制输出
cout<<"{";
for(int i=0; i<SIZE; i++)
if(str[i] != ' '){
cout<<str[i];
}
cout<<"}"<<endl;
}else {
/*** 先递归 ***/
Subset(str, n-1);
if(n>0) {
char newstr[SIZE] = {' '};//去掉就把该位置的元素置成空
/*** 还原之前的状态 ***/
strcpy(newstr,str);
/*** 越来越少的元素 ***/
newstr[n-1]= ' ';
/*** 再次递归 ***/
Subset(newstr,n-1);
}
}
}
int main ()
{
char str[5]="abc";
Subset(str, SIZE);
return 0;
}
2.回溯法的基本思想
对于用回溯法求解的问题,首先要将问题进行适当的转化,得出状态空间树。 这棵树的每条完整路径都代表了一种解的可能。通过深度优先搜索这棵树,枚举每种可能的解的情况;从而得出结果。但是,回溯法中通过构造约束函数,可以大大 提升程序效率,因为在深度优先搜索的过程中,不断的将每个解(并不一定是完整的,事实上这也就是构造约束函数的意义所在)与约束函数进行对照从而删除一些 不可能的解,这样就不必继续把解的剩余部分列出从而节省部分时间。
回溯法中,首先需要明确下面三个概念:
(一)约束函数:约束函数是根据题意定出的。通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。因此,约束函数是对于任何状态空间树上的节点都有效、等价的。
(二)状态空间树:刚刚已经提到,状态空间树是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。
(三)扩展节点、活结点、死结点:所谓扩展节点,就是当前正在求出它的子节点的节点,在DFS中,只允许有一个扩展节点。活结点就是通过与约束函数的对照,节点本身和其父节点均满足约束函数要求的节点;死结点反之。由此很容易知道死结点是不必求出其子节点的(没有意义)。
深度优先搜索(DFS)和广度优先搜索(FIFO)
在 分支界限法中,一般用的是FIFO或最小耗费搜索;其思想是一次性将一个节点的所有子节点求出并将其放入一个待求子节点的队列。通过遍历这个队列(队列在 遍历过程中不断增长)完成搜索。而DFS的作法则是将每一条合法路径求出后再转而向上求第二条合法路径。而在回溯法中,一般都用DFS。为什么呢?这是因 为可以通过约束函数杀死一些节点从而节省时间,由于DFS是将路径逐一求出的,通过在求路径的过程中杀死节点即可省去求所有子节点所花费的时间。FIFO 理论上也是可以做到这样的,但是通过对比不难发现,DFS在以这种方法解决问题时思路要清晰非常多。
因此,回溯法可以被认为是一个有过剪枝的DFS过程。
利用回溯法解题的具体步骤
首先,要通过读题完成下面三个步骤:
(1)描述解的形式,定义一个解空间,它包含问题的所有解。
(2)构造状态空间树。
(3)构造约束函数(用于杀死节点)。
然后就要通过DFS思想完成回溯,完整过程如下:
(1)设置初始化的方案(给变量赋初值,读入已知数据等)。
(2)变换方式去试探,若全部试完则转(7)。
(3)判断此法是否成功(通过约束函数),不成功则转(2)。
(4)试探成功则前进一步再试探。
(5)正确方案还未找到则转(2)。
(6)已找到一种方案则记录并打印。
(7)退回一步(回溯),若未退到头则转(2)。
(8)已退到头则结束或打印无解。
回溯法的数据结构
回溯法的状态空间树,在计算机上的数据结构有两种表示方法。当用k表示树的节点层数,n表示节点总数,m表示解的总数时:
(一)用m个k元组表示m种不同的解。其中,每组中的元素是[1,n]中的一个元素。在Pascal中,可以这样定义变量:
var a:array[1..k,1..m]of integer;(integer可以依n的范围决定)
(二)用m个n元组表示m种不同的解。因为所有的节点都包含在每个解的表示中,每组中的元素只有两种情况,被选用和不被选用。在Pascal中,可以这样定义变量:
var a:array[1..n,1..m]of boolean;
这两种数据结构的解空间最多都含有2n个不同的元组
#include<iostream>
using namespace std;
int str[]={1,2,3,4,5,6,7};
bool bo[7];
long xnum=0;
/*
int print(int i)
{
if(i>=7) i>=7 就输出结果中的一个
{
int j,k=0;
++xnum;
cout<<"No."<<xnum<<'=';
cout<<"{";
for(j=0; j<7; j++)
if(bo[j])
{
if(k)
cout<<',';
cout<<str[j];
++k;
}
cout<<"}"<<endl;
return 0;
}
else i<7就继续下一个数
{
bo[i]=true; 第i个元素放入集合中并继续
print(i+1);
bo[i]=false; 第i个元素不放入集合中
print(i+1);
}
return 0;
}
int main( int argc, char * argv[] )
{
print(0);
return 0;
}
#include <stdio.h>
#include <string.h>
#define MAX_LENGTH 100 /*集合的最大元素个数*/
void PowerSet(char*, int, char*,int *);
int main()
{
char a[MAX_LENGTH]; /*存储输入的集合*/
char set[MAX_LENGTH]={"\0"}; /*储存集合的幂集元素*/
int NumOfPowerSet=0; /*幂集元素记数*/
printf("Input the elements:");
scanf("%s",a);
printf("----------------------------\n");
PowerSet(a,0,set,&NumOfPowerSet); /*调用递归函数*/
printf("----------------------------\n");
printf("Number of PowerSet: %d\n",NumOfPowerSet);
return 1;
}
/*
参数说明: char* a : 待求幂集的集合
int i : 当前分析到集合的第i个元素
char* set : 存储当前幂集元素状态
int* Num : 幂集元素记数
*/
void PowerSet(char* a, int i, char* set, int * Num)
{
char TempSet[MAX_LENGTH];
strcpy(TempSet,set);
if(i>=strlen(a))
{
printf("{%s}\n",set);
(*Num)++;
}
else
{
PowerSet(a,i+1,TempSet,Num); /* 当前 *a 放到 set里 */
strncat(TempSet,(a+i),1);
PowerSet(a,i+1,TempSet,Num); /* 跳过 *a */
}
}
/*
输入测试数据:ABCD,表示集合{A,B,C,D},得到输出数据如下:Input the elements:ABCD
-------------------------
{}{D}{C}{CD}{B}{BD}{BC}{BCD}{A}{AD}{AC}{ACD}{AB}{ABD}{ABC}{ABCD}
-------------------------Number of PowerSet: 16
*/