我一直在想着这个事,早晨起来五六点,躺在床上冥想。突然悟解了,真如某些书上写的,大道不过三言两语,说破一文不值。
还是按照老方法,把问题最大程度的精简,现在求集合A={a,b}的幂集,只有两个元素,应该有{a,b},{a},{b},{x}四种可能。如果把这两个元素弄清楚了,其余的也都一样, 仅仅是数量增大了一些。
现在用两个数组,A是原集,B存放子集。关键是约束条件,就是边界如何界定。先这样考虑,先在B集合中放入一个元素,再放入一个元素,已经不能再放了,因为A集只有两个元素,就像是手中只有两个球,都拿出去就没有了,所以这里把约束条件定为,只要拿出去的数量>=实际数量就返回。
如图所示,这里用的是下标,所以第一次拿出a时,i=0,(以后类同),再放一个,i=1,企图再放一个i=2时已经触到边界,因为拿出去的数量已经最大。到此返回,打印结果,于是得到一个集合{a,b}。
接着,出现了转移。假如把第二个字符b收回(相当于没放),虽然没放东西,但是经过了一步,于是接着得到i=2,又触到边界,打印结果返回,结果得到集合{a}。
这就类似于树的先序遍历,如果是先序遍历的思路,剩下的就容易理解,因为先序的返回顺序是简易的,现在i=1的情况已经穷尽,也就是左右子树都已经返回,下一步就转移到根结点,也就是i=0时的情况。
同样,由于是递归,所以把a也舍去,然后遍历根的右子树,进行i=1计算。
void f(int i,char A[],int n)
{
char x;
int k;
if(i>=n)
{
if(B[])
cout<<'{'<<B<<'}'<<endl;
else
cout<<'x'<<endl;
}
else
{
x=A[i];
k=strlen(B);
B[k]=x;
f(i+,A,);
B[k]=;
f(i+,A,);
}
}
i=1的计算如下:
由于舍去a,相当于集合B没有放入一个元素,长度为0,因此,进入函数f(1)时,把A集合的第二个元素,也就是b放了B[k],也就是B[0]中。
再进入,i=2时,触到边界返回。这只是f(1)的左子树,接着进入右子树,同样,把b舍去,也即b[k]=0,(b[0]=0),进入f(2),由于集合B完全为空,所以最后打印空集'x'。
到这时,f(0)所调用的函数,左子树和右子树都已经返回,函数结束,打印的顺序为,ab,a,b,x。
这种回溯算法,初次接触会有些绕,不要用一大堆数据,把它简化成最简易的形式,递归调用步数少的演示一番,就容易看的明白。
这题的关键有两条,第一,划定边界,也就是什么条件下递归结束。第二,在递归过程中间,返回的时候需要处理什么事,也就是所谓的回溯!比如这段代码中的b[k]=0,是最关键的一步,也就是舍去某些元素!