В качестве дополнения к превосходному предложению 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
мы видим график вызовов
Число впереди идентифицирует вызов рекурсивной функции (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) действительно делает сложную часть при выборе способа представления данных.
Чтобы реализовать такой вывод точечных графиков в ваших собственных программах:
Убедитесь, что весь информационный вывод идет со стандартной ошибкой;используйте
fprintf(stderr, "Information ...\n");
вместо printf()
.Используйте printf(...)
или fprintf(stdout, ...)
только при выводе языка Dot.Это позволяет вам перенаправить стандартный вывод из вашей программы в файл, добавив > filename.dot
в командную строку.
Начните свой диаграмму направленности с
printf("digraph {\n");
и завершите его
printf("}\n");
Ребра и узлы находятся между ними.Их порядок не имеет значения.
(Если вы хотите ненаправленный граф, используйте graph {
и --
для краев.)
Узел определяется с помощью "id" [ ... ];
, где id
- это идентификатор, используемый для адресации этого узла, а ...
- список атрибутов, разделенных запятыми.
Типичные атрибуты, которые вам нужны, это shape="record", label="foo | bar"
дляструктурированные этикетки;label=< HTML >
или shape="none", label=< HTML >
для меток в формате HTML и label="foo"
для простых текстовых меток.Если вы опускаете метку (или всю спецификацию узла), вместо нее используется id
.
Для визуализации, например, деревьев или списков или чего-либо, использующего указатели, используйте
printf(" \"%p\" [ ... ];\n", (void *)ptr);
который использует фактическое значение указателя в качестве источника для идентификатора узла.
Направленные ребра имеют форму
"source-id" -> "target-id";
или
"source-id" -> "target-id" [ color="#rrggbb" ];
В дополнение к цвету вы можете пометить стрелки с помощью taillabel
, headlabel
и т. Д.
Это все, что вам нужно, хотя вы можете найти дополнительные примеры и документация по всему Интернету.