Есть ли компилятор c, который показывает мне каждый шаг рекурсивной функции? - PullRequest
0 голосов
/ 25 декабря 2018

В настоящее время я пишу алгоритм быстрой сортировки на языке c (я новичок), и мне очень трудно понять рекурсию.Это не моя первая программа с рекурсивной функцией, поэтому я хотел бы знать, есть ли какой-нибудь компилятор для c, который показывает шаги рекурсивной функции, чтобы облегчить ее.

Ответы [ 3 ]

0 голосов
/ 25 декабря 2018

Хотя вы можете использовать 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

Сделайте что-то похожее для вашей быстрой сортировки, чтобы создать подобную трассировку рекурсии.

0 голосов
/ 25 декабря 2018

В качестве дополнения к превосходному предложению 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;
}

Для этого требуется единственный параметркомандной строки и выводит рекурсивный граф вызовов для простой рекурсивной реализации Фибоначчи.

Например, если мы скомпилируем и запустим приведенный выше dot-fibonacci.c в Linux с использованием GCC,

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()).

Текст на языке Dot, который генерирует вышеуказанноеграфик:

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, ...) только при выводе языка Dot.Это позволяет вам перенаправить стандартный вывод из вашей программы в файл, добавив > 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);
    

    который использует фактическое значение указателя в качестве источника для идентификатора узла.

  4. Направленные ребра имеют форму

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

    или

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

    В дополнение к цвету вы можете пометить стрелки с помощью taillabel, headlabel и т. Д.

Это все, что вам нужно, хотя вы можете найти дополнительные примеры и документация по всему Интернету.

0 голосов
/ 25 декабря 2018

Используйте Visual Studio IDE. Доступна бесплатная версия.Вы можете просмотреть стек вызовов с помощью этой IDE.или используйте CodeBlocks IDE

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...