上一篇《求集合的所有子集》中给出的两种方法虽然解决了求所有子集的问题,但是也留下了一点缺陷,没法根据字典序输出所有子集。这里利用回溯法来求解,保证了按字典序输出所有子集。大致思路:
首先得利用辅助数组index来记录所有选中元素的下标,对于辅助数组index需满足以下两个条件:
(1)index[i + 1] > index[i],即后一个元素的下标必须大于前一个元素的下标
(2)index[i] - i <= m - n (从m个元素中选取n个元素的子集,i表示子集中的第i+1个元素)
假如现有一集合set{a, b, c, d, e},求集合set中选取3个元素的所有子集。大致流程如下:
(1)选取第一个元素,此时i = 0, index[i] = 0,满足以上两个条件,扩大规模,i = 1,index[1] = index[0] + 1 = 1。按此方法继续扩大规模,得到第一组解:index[0] = 0, index[1] = 1, index[2] = 2, i = 2
(2)在该解的基础之上,计算下一个解,因为index[2]上的2调整为3和4均满足以上条件,可以得到另外两组解:
index[0] = 0, index[1] = 1, index[2] = 3, i = 3
index[0] = 0, index[1] = 1, index[2] = 4, i = 4
(3)当index[2] = 5时,不再满足第二个条件,因此需要回溯到index[1],将index[1]调整为2,继续按以上步骤继续计算,直到从index[0]再回溯时,表明已经求出所有子集。
具体代码实现如下:
#include <iostream>
using namespace std;
template<class T>
void Output(T set[], int index[], int k, int n)
{
bool flag = false;
cout << "{";
for (int i = 0; i < n; i++)
{
if (flag)
{
cout << ", ";
}
cout << set[index[i] + k];
flag = true;
}
cout << "}" << endl;
return;
}
//从集合set[k:m]中选取n个元素的集合
template<class T>
int SubSet(T set[], int k, int m, int n)
{
int i = 0;
int index[10]; //辅助数组,记录选中元素的下标
//空集单独处理
if (0 == n)
{
Output(set, index, k, n);
return 0;
}
index[i] = 0;
while (true)
{
if (index[i] - i <= m - k + 1 - n) //还可以向前试探
{
if (i == n - 1) //找到一个子集
{
Output(set, index, k, n);
index[i]++;
continue;
}
i++; //继续向前试探
index[i] = index[i - 1] + 1;
}
else //回溯
{
if (i == 0) //找完全部子集
{
return 0;
}
index[--i]++;
}
}
return 0;
}
//生成集合set[k:m]的所有子集
template<class T>
int SubSet(T set[], int k, int m)
{
for (int i = 0; i <= m - k + 1; i++)
{
SubSet(set, k, m, i);
}
return 0;
}
int main(int argc, char *argv[])
{
char str[10];
cout << "请输入一个字符数组:";
cin >> str;
SubSet(str, 0, strlen(str) - 1);
system("PAUSE");
return EXIT_SUCCESS;
}