我有一些 SO 答案,它们展示了如何将算法的任何递归表达式转换为非递归表达式。
让我们再做一次。
我将使用 C,因为它更清楚地用于此目的:
int calc_max(NODE *node) {
int child_sum = 0;
for (int i = 0; i < node->n_children; ++i)
child_sum += calc_max(node->children[i]);
return MAX(node->value, child_sum);
}
现在使用显式堆栈、用作“返回寄存器”的变量和goto 来模拟递归调用,以模拟由call 和return 指令执行的跳转。为了清楚起见,让一些东西在伪代码中。
int calc_max(NODE *node) {
int rtn_val; // Simulated register for function return value.
start:
int child_sum = 0;
for (int i = 0; i < node->n_children; ++i) {
<<save params and locals on frame stack>>
node = node->children[i];
goto start;
rtn:
child_sum += rtn_val;
}
// Simulate using a register to return a result.
int rtn_val = MAX(node->value, child_sum);
if (<<frame stack isn't empty>>) {
<<restore params and locals from frame stack>>
goto rtn;
}
return rtn_val;
}
需要保存的参数和局部变量是node、i和child_sum。这样我们就可以填写伪代码了。
typedef struct node {
struct node *children[8];
int n_children, value;
} NODE;
#define MAX(A, B) ((A) > (B) ? A : B)
int calc_max(NODE *node) {
int i, child_sum, rtn_val, sp = 0;
struct stack_frame {
NODE *node;
int i, child_sum;
} stk[1000];
start:
child_sum = 0;
for (i = 0; i < node->n_children; ++i) {
stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
node = node->children[i];
goto start;
rtn:
child_sum += rtn_val;
}
rtn_val = MAX(node->value, child_sum);
if (sp > 0) {
--sp;
node = stk[sp].node;
i = stk[sp].i;
child_sum = stk[sp].child_sum;
goto rtn;
}
return rtn_val;
}
这应该可以正常工作。我还没有测试过。 gotos 不是很漂亮,但如果你愿意,可以通过一些代数将它们变成 while 或 for 循环。以下是步骤。
将for 循环更改为while 并将rtn 之后的语句移动到goto rtn 之前执行。那么rtn本身就可以移到while之前:
int calc_max(NODE *node) {
int i, child_sum, rtn_val, sp = 0;
struct stack_frame {
NODE *node;
int i, child_sum;
} stk[1000];
start:
i = child_sum = 0;
rtn:
while (i < node->n_children) {
stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
node = node->children[i];
goto start;
}
rtn_val = MAX(node->value, child_sum);
if (sp > 0) {
--sp;
node = stk[sp].node;
i = stk[sp].i;
child_sum = stk[sp].child_sum;
child_sum += rtn_val;
++i;
goto rtn;
}
return rtn_val;
}
现在通过复制i = child_sum = 0,我们可以将goto start的目标移动到rtn。
int calc_max(NODE *node) {
int i, child_sum, rtn_val, sp = 0;
struct stack_frame {
NODE *node;
int i, child_sum;
} stk[1000];
i = child_sum = 0;
rtn:
while (i < node->n_children) {
stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
node = node->children[i];
i = child_sum = 0;
goto rtn;
}
rtn_val = MAX(node->value, child_sum);
if (sp > 0) {
--sp;
node = stk[sp].node;
i = stk[sp].i;
child_sum = stk[sp].child_sum;
child_sum += rtn_val;
++i;
goto rtn;
}
return rtn_val;
}
现在请注意,第一个goto rtn 可以消除。 while 循环在没有它的情况下做同样的事情:
int calc_max(NODE *node) {
int i, child_sum, rtn_val, sp = 0;
struct stack_frame {
NODE *node;
int i, child_sum;
} stk[1000];
i = child_sum = 0;
rtn:
while (i < node->n_children) {
stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
node = node->children[i];
i = child_sum = 0;
}
rtn_val = MAX(node->value, child_sum);
if (sp > 0) {
--sp;
node = stk[sp].node;
i = stk[sp].i;
child_sum = stk[sp].child_sum;
child_sum += rtn_val;
++i;
goto rtn;
}
return rtn_val;
}
最后我们可以将最后一个goto rtn 替换为一个无限循环,其中我们将return 从函数中(跳出循环),除非goto rtn 会循环:
int calc_max(NODE *node) {
int i, child_sum, rtn_val, sp;
struct stack_frame {
NODE *node;
int i, child_sum;
} stk[1000];
sp = i = child_sum = 0;
while (1) {
while (i < node->n_children) {
stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
node = node->children[i];
i = child_sum = 0;
}
rtn_val = MAX(node->value, child_sum);
if (sp <= 0) return rtn_val;
node = stk[--sp].node;
i = stk[sp].i + 1;
child_sum = stk[sp].child_sum + rtn_val;
}
}
我可能很容易在此过程中犯错。我手头没有编译器来做任何测试。但是基本的想法是合理的,结果应该是相当有效的。 It compiles to 33 x86 instructions.
在你完成了其中的一些之后,摆脱goto 的代数技巧就陷入了模式。但是有通用的自动算法可以做同样的事情。构建一个可以自动进行这种转换的工具会相当简单。
现在gotos 已经消失了,您可以翻译回您选择的语言。
这个方法的好处是你不需要弄明白特定算法的操作语义。只需从递归表达式开始并模拟编译器所做的事情,然后用代数转换以获得看起来不错的形式。除了愚蠢的错误,它必须有效。