簡體   English   中英

是否有C編譯器向我顯示遞歸函數的每個步驟?

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

目前,我正在用c(我是初學者)編寫快速排序算法,這對我來說很難理解遞歸。 這不是我的第一個具有遞歸函數的程序,所以我想知道是否有用於c的編譯器顯示遞歸函數的步驟,以使其更容易使用。

使用Visual Studio IDE,有一個免費版本。 您可以使用此IDE查看調用堆棧。 或使用CodeBlocks IDE

雖然您可以使用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執行類似的操作以創建類似的遞歸跟蹤。

作為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

我們看到了調用圖 第4次斐波那契數字調用圖 前面的數字標識對遞歸函數的調用( 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() 輸出點語言內容時fprintf(stdout, ...)僅使用printf(...)fprintf(stdout, ...) 這樣,您可以通過在命令行后添加> filename.dot將標准輸出從程序重定向到文件。

  2. 用以下命令啟動方向圖

      printf("digraph {\\n"); 

    並以

      printf("}\\n"); 

    邊緣和節點位於兩者之間。 他們的順序無關緊要。

    (如果要使用無方向圖,請使用graph { ,和--表示邊緣。)

  3. 使用"id" [ ... ];定義節點"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標記箭頭。

盡管您可以在網上找到更多示例文檔 ,但這僅滿足您的所有需求。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM