是否有等效于 C 中的嵌套递归函数?

Posted

技术标签:

【中文标题】是否有等效于 C 中的嵌套递归函数?【英文标题】:Is there an equivalent to a nested recursive function in C? 【发布时间】:2015-04-21 15:42:13 【问题描述】:

首先,我知道 C 标准不支持嵌套函数。

但是,在其他语言中,定义一个辅助递归函数以利用外部函数提供的数据通常非常有用。

这是一个例子,用 Python 计算 N-queens problem 的解数。例如,在 Lisp、Ada 或 Fortran 中编写相同的代码很容易,它们都允许某种嵌套函数。

def queens(n):
    a = list(range(n))
    u = [True]*(2*n - 1)
    v = [True]*(2*n - 1)
    m = 0

    def sub(i):
        nonlocal m

        if i == n:
            m += 1
        else:
            for j in range(i, n):
                p = i + a[j]
                q = i + n - 1 - a[j]
                if u[p] and v[q]:
                    u[p] = v[q] = False
                    a[i], a[j] = a[j], a[i]
                    sub(i + 1)
                    u[p] = v[q] = True
                    a[i], a[j] = a[j], a[i]

    sub(0)
    return m

现在我的问题是:有没有办法在 C 中做 类似的事情?我会想到两种解决方案:使用全局变量或将数据作为参数传递,但它们看起来都不太令人满意。

还有一种方法可以把这个写成迭代程序,但是比较笨拙:其实我先在Fortran 77为Rosetta Code写了迭代解决方案,然后就想把这个烂摊子整理一下。 Fortran 77 没有递归函数。


对于那些想知道的人,该函数将 NxN 板管理为 [0, 1 ... N-1] 的排列,因此皇后在行和列上是单独的。该函数正在寻找也是问题解决方案的所有排列,开始检查第一列(实际上没有要检查的内容),然后是第二列,并且仅当第一个 i 列处于有效配置时才递归调用自身。

【问题讨论】:

一些 C 实现,例如GCC,允许嵌套函数作为扩展。但通常的做法是将数据作为参数传递。 Is there a a way to achieve closures in C的可能重复 @Barmar 我已经阅读了 GCC 的这个特性,但我不会使用这样的非标准特性。我也认为正常的方法是你建议的,但我想知道是否有更聪明的方法。 如果它更容易,您可以将数据作为(指向 a)结构的指针传递。这里的优点是美观,但可能更令您满意。 谢谢@Matt 我会看看这个。虽然可能有一个技巧,因为数据必须在某个地方,但我猜 Ada 在内部也使用了一个技巧。我必须检查程序集输出。 【参考方案1】:

当然。您需要模拟嵌套函数使用的特殊环境,作为模块级别的静态变量。在嵌套函数上方声明它们。

为了不把事情搞砸,你把整个事情放到一个单独的模块中。

【讨论】:

是的,我什至不应该问,尽管我可能错过了 C 的一个特性,因为我不是专家。闭包显然没有魔法,数据必须可以通过指针访问,在汇编程序中,该指针必须是常量(=全局变量),或者基于堆栈(=参数)或基于寄存器。 gfortran 和 gnat 使用最后一个选项。我从来没有考虑过闭包的实现,但是一旦看到汇编器的样子,就会更容易找到。 @Jean-ClaudeArbaut 好吧,对于闭包,你真的应该将所有相关的东西打包成一些结构并传递它,当然是通过引用。 解释这些其他语言隐含地在做什么。数据在递归期间散布在堆栈上,您只需将其放在堆上即可。我上面描述的是最基本的方法,顺便说一下,重构会很困难。 @Jean-ClaudeArbaut 我自己,我更多来自 Lisp 世界;完全闭包非常复杂(查看"funarg problem",这导致需要维护堆栈帧的,而不仅仅是列表)。希望您不需要完全的复杂性。【参考方案2】:

编者注:这个答案是从问题编辑的内容中移来的,它是由原始海报撰写的。

感谢大家的建议。这是使用作为参数传递的结构的解决方案。这大致相当于 gfortran 和 gnat 在内部处理嵌套函数的做法。顺便说一下,参数i 也可以在结构中传递。

内部函数声明为静态,以帮助编译器优化。如果它不是递归的,那么代码可以集成到外部函数中(在一个简单的示例中使用 GCC 进行测试),因为编译器知道该函数不会从“外部”调用。

#include <stdio.h>
#include <stdlib.h>

struct queens_data 
    int n, m, *a, *u, *v;
;

static void queens_sub(int i, struct queens_data *e) 
    if(i == e->n) 
        e->m++;
     else 
        int p, q, j;
        for(j = i; j < e->n; j++) 
            p = i + e->a[j];
            q = i + e->n - 1 - e->a[j];
            if(e->u[p] && e->v[q]) 
                int k;
                e->u[p] = e->v[q] = 0;
                k = e->a[i];
                e->a[i] = e->a[j];
                e->a[j] = k;
                queens_sub(i + 1, e);
                e->u[p] = e->v[q] = 1;
                k = e->a[i];
                e->a[i] = e->a[j];
                e->a[j] = k;
            
        
    


int queens(int n) 
    int i;
    struct queens_data s;

    s.n = n;
    s.m = 0;
    s.a = malloc((5*n - 2)*sizeof(int));
    s.u = s.a + n;
    s.v = s.u + 2*n - 1;

    for(i = 0; i < n; i++) 
        s.a[i] = i;
    

    for(i = 0; i < 2*n - 1; i++) 
        s.u[i] = s.v[i] = 1;
    

    queens_sub(0, &s);
    free(s.a);
    return s.m;


int main() 
    int n;
    for(n = 1; n <= 16; n++) 
        printf("%d %d\n", n, queens(n));
    
    return 0;

【讨论】:

以上是关于是否有等效于 C 中的嵌套递归函数?的主要内容,如果未能解决你的问题,请参考以下文章

mkdir -p 在 C 中等效,递归地创建嵌套目录

C语言函数的嵌套函数的递归递归是什么?递归两个必要条件。

递归创建决策树

c语言怎么用递归调用函数的方法求n的阶乘?

C语言:采用递归调用函数方法计算Fibonacci数列的前20项

使用递归函数获取多维php数组的新值和键