是否有C编译器向我显示递归函数的每个步骤?

时间:2018-12-24 21:23:55

标签: c recursion

目前,我正在用c(我是初学者)编写快速排序算法,这对我来说很难理解递归。这不是我的第一个具有递归函数的程序,所以我想知道是否有用于c的编译器能够显示递归函数的步骤,以使其变得更容易。

3 个答案:

答案 0 :(得分:1)

使用Visual Studio IDE,有一个免费版本。 您可以使用此IDE查看调用堆栈。 或使用CodeBlocks IDE

答案 1 :(得分:1)

虽然您可以使用IDE逐步执行递归,但对学生来说,最有启发性的是在递归函数开始时使用printf。 (实际上,最有启发性的任务是使用纸和笔手动执行递归功能,但是对于深度递归而言,它很快就会变老!)

示例:

int fibonacci(int n) {
    printf("fibonacci(%d)\n", n);
    if (n == 0 || n == 1)
    return n;
  else
    return (fibonacci(n-1) + fibonacci(n-2));
}

这将产生以下递归跟踪。不幸的是,在使用双重递归的情况下,这并不能弄清楚什么叫什么:

fib 4
fibonacci(4)
fibonacci(3)
fibonacci(2)
fibonacci(1)
fibonacci(0)
fibonacci(1)
fibonacci(2)
fibonacci(1)
fibonacci(0)
fibonacci(4)=3

如果您不介意在递归函数中添加递归计数,则可以使用以下代码获得缩进的输出(例如):

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

void traceResursion(const char *funcName, int n, int recursionLevel)
{ 
 int i;
 if (recursionLevel > 0) {
    for(i=0; i<recursionLevel; i++)
       printf("   ");
    printf("-->"); 
 }
 printf("%s(%d)\n", funcName, n);
} 


int fibonacci(int n, int recursionLevel) {
    traceResursion("fibonacci", n, recursionLevel);
    if (n == 0 || n == 1)
    return n;
  else
    return (fibonacci(n-1, recursionLevel+1) + fibonacci(n-2, recursionLevel+1));
}  

int main(int argc, char **argv){
      int n = 2;

      /* Get n from the command line if provided */
      if (argc > 1)
         n = atoi(argv[1]);

      printf("fibonacci(%d)=%d\n", n, fibonacci(n,0));
}

这将接受命令行号并计算斐波那契并显示递归。例如,如果将其编译为可执行文件fib,则使用以下命令:

fib 5

产生以下显示递归的输出。

> fib 5 
fibonacci(5)
   -->fibonacci(4)
      -->fibonacci(3)
         -->fibonacci(2)
            -->fibonacci(1)
            -->fibonacci(0)
         -->fibonacci(1)
      -->fibonacci(2)
         -->fibonacci(1)
         -->fibonacci(0)
   -->fibonacci(3)
      -->fibonacci(2)
         -->fibonacci(1)
         -->fibonacci(0)
      -->fibonacci(1)
fibonacci(5)=5

对您的QuickSort执行类似操作,以创建类似的递归跟踪。

答案 2 :(得分:1)

作为ScottK的出色建议的扩展,我建议使用Graphviz来辅助可视化。它适用于所有操作系统,并且完全免费。它以人类可读的文本(点语言)作为输入,并提供相当不错的图形作为输出。

请考虑以下斐波那契示例程序:

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

static inline int  unique_id(void)
{
    static int  id = 0;
    return ++id;
}

static int  dot_fibonacci_recursive(int n, int parent_id, FILE *out)
{
    const int  id = unique_id();

    if (n < 2) {
        printf("    \"%d\" [ label=< %d. F<sub>%d</sub> = <b>1</b> > ];\n", id, id, n);
        printf("    \"%d\" -> \"%d\";\n", id, parent_id);

        return 1;
    } else {
        int result1, result2;

        result1 = dot_fibonacci_recursive(n - 1, id, out);
        result2 = dot_fibonacci_recursive(n - 2, id, out);

        printf("    \"%d\" [ label=< %d. F<sub>%d</sub> = <b>%d</b> > ];\n", id, id, n, result1 + result2);
        printf("    \"%d\" -> \"%d\";\n", id, parent_id);

        return result1 + result2;
    }
}

int  fibonacci(int n)
{
    const int  id = unique_id();
    int        result;

    printf("digraph {\n");

    result = dot_fibonacci_recursive(n, id, stdout);

    printf("    \"%d\" [ label=< %d. F<sub>%d</sub> = <b>%d</b> > ];\n", id, id, n, result);
    printf("}\n");
    fflush(stdout);

    return result;
}

int main(int argc, char *argv[])
{
    int  n, fn;
    char dummy;

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s N > graph.dot\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program will compute the N'th Fibonacci number\n");
        fprintf(stderr, "using a recursive function, and output the call graph\n");
        fprintf(stderr, "as a dot language directed graph.\n");
        fprintf(stderr, "\n");
        fprintf(stderr, "Use e.g. 'dot' from the Graphviz package to generate\n");
        fprintf(stderr, "an image, or display the graph interactively. For example:\n");
        fprintf(stderr, "       dot -Tsvg graph.dot > graph.svg\n");
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    if (sscanf(argv[1], " %d %c", &n, &dummy) != 1 || n < 0) {
        fprintf(stderr, "%s: Invalid N.\n", argv[1]);
        return EXIT_FAILURE;
    }

    fn = fibonacci(n);

    fprintf(stderr, "%d'th Fibonacci number is %d.\n", n, fn);

    return EXIT_SUCCESS;
}

在命令行上使用单个参数,并输出用于简单递归Fibonacci实现的递归调用图。

例如,如果我们使用GCC在Linux中编译并运行上述 dot-fibonacci.c

gcc -Wall -O2 dot-fibonacci.c -o dot-fibonacci
./dot-fibonacci 4 | dot -Tx11

我们看到了调用图 4th Fibonacci number call graph 前面的数字标识对递归函数(dot_fibonacci())的调用,最外面的fibonacci()调用位于第一个。因为我的fibonacci()只是一个不计算的包装函数,所以根节点始终与第二个节点相同(第一次实际调用dot_fibonacci())。

生成上述图形的点语言文字为

digraph {
    "5" [ label=< 5. F<sub>1</sub> = <b>1</b> > ];
    "5" -> "4";
    "6" [ label=< 6. F<sub>0</sub> = <b>1</b> > ];
    "6" -> "4";
    "4" [ label=< 4. F<sub>2</sub> = <b>2</b> > ];
    "4" -> "3";
    "7" [ label=< 7. F<sub>1</sub> = <b>1</b> > ];
    "7" -> "3";
    "3" [ label=< 3. F<sub>3</sub> = <b>3</b> > ];
    "3" -> "2";
    "9" [ label=< 9. F<sub>1</sub> = <b>1</b> > ];
    "9" -> "8";
    "10" [ label=< 10. F<sub>0</sub> = <b>1</b> > ];
    "10" -> "8";
    "8" [ label=< 8. F<sub>2</sub> = <b>2</b> > ];
    "8" -> "2";
    "2" [ label=< 2. F<sub>4</sub> = <b>5</b> > ];
    "2" -> "1";
    "1" [ label=< 1. F<sub>4</sub> = <b>5</b> > ];
}

请注意,缩进线的顺序并不重要。您可以根据需要重新排列它们,以便于理解。

  • 引用的部分是节点标识符。

  • ->创建一个从一个节点到另一个节点的箭头。

  • label=< ... >将标签内的文本格式设置为HTML。您也可以将label="string"用于简单字符串,并将shape="record", label="thing | { foo | bar } | baz"用于结构化标签。您也可以将箭头指向此类子字段。

如您所见,基础非常简单; dot(或Graphviz软件包中的其他可视化工具之一)确实在选择如何表示数据方面很困难。

要在自己的程序中实现此类点图输出:

  1. 确保所有信息输出均达到标准错误;使用

     fprintf(stderr, "Information ...\n");
    

    而不是printf()。输出点语言内容时,仅使用printf(...)fprintf(stdout, ...)。这样,您可以通过将> filename.dot附加到命令行将标准输出从程序重定向到文件。

  2. 使用

    开始创建方向图
     printf("digraph {\n");
    

    并以

    结束
     printf("}\n");
    

    边缘和节点位于两者之间。他们的顺序无关紧要。

    (如果要使用无方向图,请使用graph {,并使用--作为边。)

  3. 使用"id" [ ... ];定义一个节点,其中id是用于寻址该节点的标识符,而...是用逗号分隔的属性列表。

    您需要的典型属性是shape="record", label="foo | bar"用于结构化标签; label=< HTML >shape="none", label=< HTML >用于HTML格式的标签,label="foo"用于简单的文本标签。如果省略标签(或整个节点规范),则将id用作标签。

    例如可视化树或列表或任何使用指针的东西,请使用

     printf(" \"%p\" [ ... ];\n", (void *)ptr);
    

    使用实际指针值作为节点ID的来源。

  4. 有向边的形状

    "source-id" -> "target-id";
    

    "source-id" -> "target-id" [ color="#rrggbb" ];
    

    除了颜色之外,您还可以使用taillabelheadlabel等标记箭头。

这就是您所需要的,尽管您可以在网络上找到其他examplesdocumentation