Обход DAG на C#: полное руководство

    Ациклический ориентированный граф (DAG) - это структура данных, в которой рёбра имеют направление и отсутствуют циклы. Обход такого графа от начала до конца требуется во многих задачах: от планирования зависимостей до анализа данных. В этой статье мы разберём, как записать DAG для прохода на C#, используя топологическую сортировку, поиск в глубину (DFS) и поиск в ширину (BFS).

    Что такое DAG и зачем его обходить?

    DAG (Directed Acyclic Graph) - ориентированный граф без циклов. Обход DAG «от начала до конца» означает посещение всех узлов в порядке, который учитывает направление рёбер, начиная с истоков (узлов без входящих рёбер) и заканчивая стоками (узлами без исходящих рёбер). Это полезно для:

    • Построения порядка выполнения задач (например, в системах сборки)
    • Обработки зависимостей в компиляторах
    • Анализа данных в графовых базах

    Топологическая сортировка - основной алгоритм

    Топологическая сортировка - это линейный порядок узлов DAG, при котором для каждого ребра (u → v) узел u идёт раньше v. Реализация на C# может использовать алгоритм Кана (Kahn) или DFS с отслеживанием времени выхода.

    Алгоритм Кана (BFS-подход)

    Идея: найти узлы с нулевой входящей степенью, удалять их вместе с исходящими рёбрами, повторять до опустошения графа.

    public List<int> TopologicalSortKahn(Dictionary<int, List<int>> graph, int verticesCount){    var inDegree = new int[verticesCount];    foreach (var node in graph)        foreach (var neighbor in node.Value)            inDegree[neighbor]++;    var queue = new Queue<int>();    for (int i = 0; i < verticesCount; i++)        if (inDegree[i] == 0) queue.Enqueue(i);    var result = new List<int>();    while (queue.Count > 0)    {        int u = queue.Dequeue();        result.Add(u);        if (graph.ContainsKey(u))            foreach (var v in graph[u])                if (--inDegree[v] == 0)                    queue.Enqueue(v);    }    return result.Count == verticesCount ? result : null; // null если граф не DAG}

    Топологическая сортировка через DFS

    Альтернативный метод - использовать рекурсивный обход с временными метками.

    public List<int> TopologicalSortDFS(Dictionary<int, List<int>> graph, int verticesCount){    var visited = new bool[verticesCount];    var result = new Stack<int>();    void Dfs(int u)    {        visited[u] = true;        if (graph.ContainsKey(u))            foreach (var v in graph[u])                if (!visited[v]) Dfs(v);        result.Push(u);    }    for (int i = 0; i < verticesCount; i++)        if (!visited[i]) Dfs(i);    return result.ToList();}

    Практический пример: обход DAG от начала до конца

    Допустим, есть граф: 0 → 1, 0 → 2, 1 → 3, 2 → 3. Исток - узел 0, сток - узел 3. Топологическая сортировка даст: [0, 1, 2, 3] или [0, 2, 1, 3]. Оба варианта корректны. Для обхода «от начала до конца» достаточно выполнить топологическую сортировку и вывести узлы в полученном порядке.

    Особенности реализации на C#

    • Используйте словарь для представления графа, где ключ - узел, значение - список соседей.
    • Проверяйте на циклы: топологическая сортировка возвращает null, если граф не DAG.
    • Оптимизируйте память: для больших графов используйте массивы вместо списков.

    Альтернативные методы обхода DAG

    Если не требуется строгий порядок, можно применить обычные BFS или DFS, начиная с истоков. BFS гарантирует обход по уровням, DFS - глубокое погружение.

    Поиск в ширину (BFS) для DAG

    public void BfsFromSources(Dictionary<int, List<int>> graph, int verticesCount){    var inDegree = new int[verticesCount];    foreach (var node in graph)        foreach (var v in node.Value) inDegree[v]++;    var queue = new Queue<int>();    for (int i = 0; i < verticesCount; i++)        if (inDegree[i] == 0) queue.Enqueue(i);    while (queue.Count > 0)    {        int u = queue.Dequeue();        Console.WriteLine(u);        if (graph.ContainsKey(u))            foreach (var v in graph[u])                if (--inDegree[v] == 0) queue.Enqueue(v);    }}

    Заключение

    Обход DAG на C# проще всего реализовать через топологическую сортировку. Алгоритм Кана (на основе очереди) и DFS-подход работают за O(V+E). Выбор зависит от контекста: Кана даёт явный порядок, DFS - компактный рекурсивный код. Всегда проверяйте граф на ацикличность перед обходом.

    Часто задаваемые вопросы