简体   繁体   English

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

[英]Is there any c compiler that shows me each step of a recursive function?

Currently I am writing a quicksort algorithm in c (I'm a beginner) and it's really hard for me to understand the recursion. 目前,我正在用c(我是初学者)编写快速排序算法,这对我来说很难理解递归。 It is not my first program with a recursive function so I would like to know if there is any compiler for c that shows the steps of a recursive function to get it easier. 这不是我的第一个具有递归函数的程序,所以我想知道是否有用于c的编译器显示递归函数的步骤,以使其更容易使用。

Use Visual Studio IDE , There is a free version available. 使用Visual Studio IDE,有一个免费版本。 you can view the call stack using this IDE. 您可以使用此IDE查看调用堆栈。 or Use CodeBlocks IDE 或使用CodeBlocks IDE

While you can use an IDE to step through the recursion, I find the most instructive thing for students is to use printf at the start of your recursive function. 虽然您可以使用IDE逐步执行递归,但对学生来说,最有启发性的是在递归函数开始时使用printf。 (Actually THE most instructive task is to hand execute a recursive function using paper and pen, but this gets old fast for deep recursion!) (实际上,最有启发性的任务是使用纸和笔手动执行递归功能,但是对于深度递归而言,它很快就会变老!)

Example: 例:

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

This will produce the following recursion trace. 这将产生以下递归跟踪。 With double recursion, this unfortunately does not make it clear what calls what: 不幸的是,在使用双重递归时,这并不能弄清楚什么叫什么:

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

If you don't mind adding a recursion count to your recursive function, you can get a nicely indented output using the follow code (as an example): 如果您不介意在递归函数中添加递归计数,则可以使用以下代码获得缩进的输出(作为示例):

#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));
}

This accepts a command line number and computes the fibonacci and display the recursion. 这将接受命令行编号并计算斐波那契并显示递归。 For example, if you compiled this into executable fib, then the following command: 例如,如果将其编译为可执行文件fib,则使用以下命令:

fib 5

Produces the following output that shows the recursion. 产生以下显示递归的输出。

> 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

Do something similar for your QuickSort to create a similar recursion trace. 对您的QuickSort执行类似的操作以创建类似的递归跟踪。

As an extension to the excellent suggestion by ScottK , I recommend using Graphviz to aid in the visualization. 作为ScottK的出色建议的扩展 ,我建议使用Graphviz进行可视化。 It is available for all operating systems, and is completely free. 它适用于所有操作系统,并且完全免费。 It takes human-readable text (Dot language) as input, and provides rather nice graphs as output. 它以人类可读的文本(点语言)作为输入,并提供相当不错的图形作为输出。

Consider the following Fibonacci example program: 考虑下面的斐波那契示例程序:

#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;
}

It takes a single parameter on the command line, and outputs the recursive call graph for the simple recursive Fibonacci implementation. 它在命令行上使用单个参数,并输出用于简单递归Fibonacci实现的递归调用图。

For example, if we compile and run the above dot-fibonacci.c in Linux using GCC, 例如,如果我们使用GCC在Linux中编译并运行上述dot-fibonacci.c

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

we see the call graph 我们看到了调用图 第4次斐波那契数字调用图 The number in front identifies the call to the recursive function ( dot_fibonacci() ), with the outermost fibonacci() call being first. 前面的数字标识对递归函数的调用( dot_fibonacci() ),最外面的fibonacci()调用位于第一个。 Because my fibonacci() is just a wrapper function that does no calculation, the root node is always the same as the second node (first actual call to dot_fibonacci() ). 因为我的fibonacci()只是一个不执行任何计算的包装函数,所以根节点始终与第二个节点相同(第一次实际调用dot_fibonacci() )。

The Dot language text that generates the above graph is 生成以上图形的点语言文本为

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> > ];
}

Note that the order of the indented lines is not important. 注意,缩进线的顺序并不重要。 You can reorder them if you like, for easier understanding. 您可以根据需要重新排序,以方便理解。

  • The quoted parts are node identifiers. 引用的部分是节点标识符。

  • -> creates an arrow from one node to another. ->创建从一个节点到另一个节点的箭头。

  • The label=< ... > formats the text inside the label as HTML. label=< ... >将标签内的文本格式化为HTML。 You can also use label="string" for simple strings, and shape="record", label="thing | { foo | bar } | baz" for structured labels. 您还可以将label="string"用于简单的字符串,并将shape="record", label="thing | { foo | bar } | baz"用于结构化标签。 You can direct arrows to such subfields as well. 您也可以将箭头指向此类子字段。

As you can see, the basics are really simple; 如您所见,基础非常简单。 dot (or one of the other visualizers in the Graphviz package) really does the hard part of choosing how to represent the data. dot (或Graphviz程序包中的其他可视化工具之一)确实在选择如何表示数据方面很困难。

To implement such dot graph output in your own programs: 要在自己的程序中实现这样的点图输出:

  1. Make sure all informational output goes to standard error; 确保所有信息输出均达到标准错误; use 采用

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

    instead of printf() . 而不是printf() Only use printf(...) or fprintf(stdout, ...) when outputting the Dot language stuff. 输出点语言内容时fprintf(stdout, ...)仅使用printf(...)fprintf(stdout, ...) This lets you redirect the standard output from your program to a file by appending > filename.dot to the command line. 这样,您可以通过在命令行后添加> filename.dot将标准输出从程序重定向到文件。

  2. Start your directional graph with 用以下命令启动方向图

      printf("digraph {\\n"); 

    and end it with 并以

      printf("}\\n"); 

    The edges and nodes go in between. 边缘和节点位于两者之间。 Their order does not matter. 他们的顺序无关紧要。

    (If you want an non-directional graph, use graph { , and -- for the edges.) (如果要使用无方向图,请使用graph { ,和--表示边缘。)

  3. A node is defined using "id" [ ... ]; 使用"id" [ ... ];定义节点"id" [ ... ]; where id is the identifier used to address that node, and ... is a comma-separated list of attributes. 其中id是用于寻址该节点的标识符,而...是逗号分隔的属性列表。

    Typical attributes you need are shape="record", label="foo | bar" for structured labels; 您需要的典型属性是用于结构化标签的shape="record", label="foo | bar" label=< HTML > or shape="none", label=< HTML > for HTML-formatted labels, and label="foo" for simple text labels. label=< HTML >shape="none", label=< HTML >用于HTML格式的标签, label="foo"用于简单的文本标签。 If you omit the label (or the entire node specification), the id is used for the label instead. 如果省略标签(或整个节点规范),则将id用作标签。

    To visualize eg trees or lists or anything using pointers, use 要可视化例如树或列表或使用指针的任何内容,请使用

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

    which uses the actual pointer value as the source for the node ID. 它使用实际指针值作为节点ID的来源。

  4. Directed edges have form 有向边有形式

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

    or 要么

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

    In addition to color, you can label the arrows using taillabel , headlabel , and so on. 除了颜色外,还可以使用taillabelheadlabel标记箭头。

That's about all you need, although you can find additional examples and documentation all over the web. 尽管您可以在网上找到更多示例文档 ,但这仅满足您的所有需求。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM