我编写了递归回溯算法来查找给定集合的所有子集。
void backtracke(int* a, int k, int n)
{
if (k == n)
{
for(int i = 1; i <=k; ++i)
{
if (a[i] == true)
{
std::cout << i << " ";
}
}
std::cout << std::endl;
return;
}
bool c[2];
c[0] = false;
c[1] = true;
++k;
for(int i = 0; i < 2; ++i)
{
a[k] = c[i];
backtracke(a, k, n);
a[k] = INT_MAX;
}
}
现在我们必须编写相同的算法,但是以迭代的形式,该怎么做?
答案 0 :(得分:10)
您可以使用二进制计数器方法。任何长度为n的唯一二进制字符串表示一组n个元素的唯一子集。如果从0开始并以2 ^ n-1结束,则覆盖所有可能的子集。计数器可以很容易地以迭代的方式实现。
Java中的代码:
public static void printAllSubsets(int[] arr) {
byte[] counter = new byte[arr.length];
while (true) {
// Print combination
for (int i = 0; i < counter.length; i++) {
if (counter[i] != 0)
System.out.print(arr[i] + " ");
}
System.out.println();
// Increment counter
int i = 0;
while (i < counter.length && counter[i] == 1)
counter[i++] = 0;
if (i == counter.length)
break;
counter[i] = 1;
}
}
请注意,在Java中,可以使用BitSet,这使得代码真的更短,但我使用了一个字节数组来更好地说明这个过程。
答案 1 :(得分:7)
有几种方法可以为此问题编写迭代算法。最常见的建议是:
从0
到2numberOfElements - 1
如果我们查看上面用于计算二进制数的变量,每个位置的数字可以被认为是一个标志,指示该集合中相应索引处的元素是否应该包含在该子集中。只需循环遍历每个位(将余数除以2,然后除以2),包括输出中的相应元素。
示例:强>
输入:{1,2,3,4,5}
。
我们开始计算0
,这是二进制的00000
,这意味着没有设置标志,所以没有包含任何元素(如果你不想要,这显然会被跳过)空子集) - 输出{}
。
然后1 = 00001
,表示只包含最后一个元素 - 输出{5}
。
然后2 = 00010
,表示只包含倒数第二个元素 - 输出{4}
。
然后3 = 00011
,表示将包含最后两个元素 - 输出{4,5}
。
依此类推,一直到31 = 11111
,表示将包含所有元素 - 输出{1,2,3,4,5}
。
*实际上在代码方面,将它转到头部会更简单 - {1}
输出00001
,考虑到第一个余数乘以2将对应于第0个元素的标志,第二个余数,第一个元素等,但为了说明的目的,上述内容更简单。
更一般地,任何递归算法都可以更改为迭代算法,如下所示:
创建一个由部分组成的循环(想想switch-statement),每个部分由函数中任意两个递归调用之间的代码组成
创建一个堆栈,其中每个元素包含函数中的每个必要的局部变量,并指示我们正忙于哪个部分
循环会弹出堆栈中的元素,执行相应的代码部分
每次递归调用都将被替换为首先将它自己的状态添加到堆栈,然后是被调用状态
将return
替换为适当的break
语句
答案 2 :(得分:3)
George算法的一点Python实现。也许它会帮助别人。
def subsets(S):
l = len(S)
for x in range(2**l):
yield {s for i,s in enumerate(S) if ((x / 2**i) % 2) // 1 == 1}
答案 3 :(得分:1)
基本上你想要的是P(S)= S_0 U S_1 U ... U S_n其中S_i是从S取i个元素所包含的所有集合的集合。换句话说,如果S = {a,b,c然后S_0 = {{}},S_1 = {{a},{b},{c}},S_2 = {{a,b},{a,c},{b,c}}和S_3 = { a,b,c}。
我们到目前为止的算法是
set P(set S) {
PS = {}
for i in [0..|S|]
PS = PS U Combination(S, i)
return PS
}
我们知道| S_i | = nCi其中| S | = n。所以基本上我们知道我们将循环nCi次。您可以稍后使用此信息来优化算法。为了生成大小为i的组合,我提出的算法如下:
假设S = {a,b,c},那么你可以将0映射到a,1到b和2到c。并且对它们的影响是(如果i = 2)0-0,0-1,0-2,1-0,1-1,1-2,2-0,2-1,2-2。要检查一个序列是否是一个组合,你检查这些数字是否都是唯一的,并且如果你对数字进行置换,序列不会出现在其他地方,这将把上面的序列过滤到0-1,0-2和1-2稍后将其映射回{a,b},{a,c},{b,c}。如何生成上面的长序列可以遵循此算法
set Combination(set S, integer l) {
CS = {}
for x in [0..2^l] {
n = {}
for i in [0..l] {
n = n U {floor(x / |S|^i) mod |S|} // get the i-th digit in x base |S|
}
CS = CS U {S[n]}
}
return filter(CS) // filtering described above
}