嗯,编程语言支持程序的递归表达的原因是它通常比显式迭代形式更简单。所以你的问题几乎是自我回答的。
但是,确实有一种方法可以将递归形式转换为始终有效的迭代形式。拥有goto 的语言会有所帮助,而Java 没有。
首先让我们清理一下java。我们希望使用最少数量的参数和局部变量,因为剩下的必须放在我们的显式堆栈上。开始了。除了 i 和 prefix 之外,我们将删除所有内容。
class CombinationLister {
private final int[] counts;
private final int length;
CombinationLister(int[] counts) {
this.counts = counts.clone();
this.length = Arrays.stream(counts).sum();
}
private void list(String prefix) {
if (prefix.length() == length) {
System.out.println(prefix);
}
for (int i = 0; i < counts.length; i++) {
if (counts[i] > 0) {
--counts[i];
list(prefix + (char) ('a' + i));
++counts[i];
}
}
}
void run() {
list("");
}
}
现在让我们转写成 C,它有 goto。在这里,通过添加全局字符串缓冲区,甚至可以轻松消除 prefix。
#include <stdio.h>
char str[100];
int counts[] = { 1, 2, 3 };
int n_counts = 3;
int total_count = 6;
int len = 0;
void list(void) {
if (len == total_count) printf("%.*s\n", total_count, str);
for (int i = 0; i < n_counts; i++) {
if (counts[i] > 0) {
str[len] = 'a' + i;
--counts[i];
++len;
list();
--len;
++counts[i];
}
}
}
现在,规则是:
- 为每个局部变量和参数构建一个包含一个字段的记录堆栈。这里我们只剩下
i,所以我们甚至不需要记录。一堆ints 就可以了。
- 将递归调用站点替换为
- 将
push 放到堆栈上,然后
- 将参数重置为新值(这里我们没有),然后
- 跳转到函数的开头,然后
- 跳转后立即贴上标签
rtn:。
- 在函数的最后,添加一个检查堆栈是否为空的结语。如果没有,它会弹出堆栈并跳转到
rtn:。
这些规则本质上是模仿编译器为处理递归调用而生成的代码。
综上所述,我们有:
int stk[100];
int p = 0; // stack pointer
void list(void) {
int i;
start:
if (len == total_count) printf("%.*s\n", total_count, str);
for (i = 0; i < n_counts; i++) {
if (counts[i] > 0) {
str[len] = 'a' + i;
--counts[i];
++len;
stk[p++] = i; // push i on stack
goto start;
rtn:
--len;
++counts[i];
}
}
// epilog
if (p > 0) {
i = stk[--p]; // restore i from stack
goto rtn;
}
}
如果您仔细按照步骤操作,您的代码每次都会先运行一次尝试。唯一的附加提示是,当有多个递归调用站点时,您需要为每个 rtn1:、rtn2: 等提供一个返回标签,并在堆栈中额外添加一个 int 字段来表示返回站点, 在结语中使用 switch 语句跳转到正确的语句。
当然,这不是漂亮的代码。我们想摆脱gotos。事实证明,通过“代数”将gotos 转换为循环,这始终是可能的。有几十种转换技术……在这里无法描述。这很像在数学课上简化方程。有时需要添加布尔标志。但是,在这种情况下,我们不需要。我完成了这个:
void list(void) {
for (int i = 0;;) {
while (i < n_counts && counts[i] == 0) i++;
if (i < n_counts) {
--counts[i];
str[len] = 'a' + i;
stk[p++] = i;
if (++len == total_count) printf("%.*s\n", total_count, str);
i = 0;
} else if (p > 0) {
i = stk[--p];
--len;
++counts[i++];
}
else break;
}
}
只是为了好玩,回到 Java:
class CombinationLister {
private final int[] counts;
private final char[] str;
private final int[] stk;
private int p = 0;
private int len = 0;
CombinationLister(int[] counts) {
this.counts = counts.clone();
this.str = new char[Arrays.stream(counts).sum()];
this.stk = new int[str.length];
}
void run() {
for (int i = 0;;) {
while (i < counts.length && counts[i] == 0) i++;
if (i < counts.length) {
--counts[i];
str[len] = (char) ('a' + i);
stk[p++] = i;
if (++len == str.length) System.out.println(str);
i = 0;
} else if (p > 0) {
i = stk[--p];
--len;
++counts[i++];
} else break;
}
}
}