Перевод статьи подготовлен в преддверии старта курса «Алгоритмы для разработчиков».
Топологическая сортировка для ориентированного ациклического графа (Directed Acyclic Graphs, далее DAG) — это линейное упорядочение вершин, для которого выполняется следующее условие — для каждого направленного ребра uv вершина u предшествует вершине v в упорядочении. Если граф не является DAG, то топологическая сортировка для него невозможна.
Например, топологическая сортировка приведенного ниже графа — «5 4 2 3 1 0». Для графа может существовать несколько топологических сортировок. Например, другая топологическая сортировка для этого же графа — «4 5 2 3 1 0». Первая вершина в топологической сортировке — это всегда вершина без входящих ребер.
Топологическая сортировка vs обход в глубину:
При обходе в глубину (Depth First Traversal, далее DFS) мы выводим вершину и затем рекурсивно вызываем DFS для смежных вершин. При топологической сортировке нам нужно вывести вершину перед ее смежными вершинами. Например, в данном графе вершина «5» должна быть выведена перед вершиной «0», и, в отличие от DFS, вершина «4» также должна быть выведена перед вершиной «0». Этим топологическая сортировка отличается от DFS. Например, DFS графа выше — «5 2 3 1 0 4», но это не топологическая сортировка.
Рекомендация: перед тем, как переходить к реализации алгоритма, попробуйте сначала разобраться с задачей на practice.
Алгоритм поиска топологической сортировки.
Для начала, рекомендуем ознакомиться с этой реализацией DFS. Мы можем модифицировать DFS так, чтобы в результате была получена топологическая сортировку графа. В DFS мы начинаем с подходящей вершины, которую сначала выводим, а затем рекурсивно вызываем DFS для ее смежных вершин. В топологической сортировке мы используем временный стек. Вершина не выводится сразу — сначала вызывается топологическая сортировка для всех смежных вершин, затем вершина помещается в стек. Только после обхода всех вершин содержимое стека выводится. Обратите внимание, что вершина помещается в стек только тогда, когда все смежные вершины (и их смежные вершины и т. д.) уже находятся в стеке.
Изображение ниже является иллюстрацией вышеупомянутого подхода:
Ниже приведены реализации топологической сортировки. Пожалуйста, ознакомьтесь с реализацией DFT для несвязного графа и обратите внимание на различия между вторым кодом, приведенным там, и кодом, приведенным ниже.
Сложность по времени: приведенный выше алгоритм это DFS с дополнительным стеком. Таким образом, сложность времени такая же, как и у DFS, которая равна O(V+E).
Примечание: также можно использовать вектор вместо стека. Если используется вектор, чтобы получить топологическую сортировку, необходимо выводить элементы в обратном порядке.
Применение:
Топологическая сортировка в основном используется для составления графика работ из заданных зависимостей между ними. В компьютерных науках применяется для планирования команд, упорядочения ячеек для вычисления формулы при повторном вычислении значений формул в электронных таблицах, логического синтеза, определения порядка задач компиляции для выполнения в make-файлах, сериализации данных и разрешения символьных зависимостей в компоновщиках [2].
Алгоритм Кана для топологической сортировки: еще один O(V+E) алгоритм.
Все топологические сортировки ориентированного ациклического графа
http://www.personal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/GraphAlgor/topoSort.htm
http://en.wikipedia.org/wiki/Topological_sorting
Пожалуйста, оставьте комментарий, если обнаружите ошибку, или если захотите поделиться дополнительной информацией по обсуждаемой теме.
Узнать подробнее о курсе.
Топологическая сортировка для ориентированного ациклического графа (Directed Acyclic Graphs, далее DAG) — это линейное упорядочение вершин, для которого выполняется следующее условие — для каждого направленного ребра uv вершина u предшествует вершине v в упорядочении. Если граф не является DAG, то топологическая сортировка для него невозможна.
Например, топологическая сортировка приведенного ниже графа — «5 4 2 3 1 0». Для графа может существовать несколько топологических сортировок. Например, другая топологическая сортировка для этого же графа — «4 5 2 3 1 0». Первая вершина в топологической сортировке — это всегда вершина без входящих ребер.
Топологическая сортировка vs обход в глубину:
При обходе в глубину (Depth First Traversal, далее DFS) мы выводим вершину и затем рекурсивно вызываем DFS для смежных вершин. При топологической сортировке нам нужно вывести вершину перед ее смежными вершинами. Например, в данном графе вершина «5» должна быть выведена перед вершиной «0», и, в отличие от DFS, вершина «4» также должна быть выведена перед вершиной «0». Этим топологическая сортировка отличается от DFS. Например, DFS графа выше — «5 2 3 1 0 4», но это не топологическая сортировка.
Рекомендация: перед тем, как переходить к реализации алгоритма, попробуйте сначала разобраться с задачей на practice.
Алгоритм поиска топологической сортировки.
Для начала, рекомендуем ознакомиться с этой реализацией DFS. Мы можем модифицировать DFS так, чтобы в результате была получена топологическая сортировку графа. В DFS мы начинаем с подходящей вершины, которую сначала выводим, а затем рекурсивно вызываем DFS для ее смежных вершин. В топологической сортировке мы используем временный стек. Вершина не выводится сразу — сначала вызывается топологическая сортировка для всех смежных вершин, затем вершина помещается в стек. Только после обхода всех вершин содержимое стека выводится. Обратите внимание, что вершина помещается в стек только тогда, когда все смежные вершины (и их смежные вершины и т. д.) уже находятся в стеке.
Изображение ниже является иллюстрацией вышеупомянутого подхода:
Текст на изображении:
Лист смежности (G)
0 >
1>
2 > 3
3 > 1
4 > 0, 1
5 > 2, 0
Стек пустой
Шаг 1:
Топологическая сортировка (0), visited[0] = true
Список пуст. Больше нет рекурсивных вызовов.
Стек 0
Шаг 2:
Топологическая сортировка (1), visited[1] = true
Список пуст. Больше нет рекурсивных вызовов.
Стек 0 1
Шаг 3:
Топологическая сортировка (2),, visited[2] = true
Топологическая сортировка (3),, visited[3] = true
Вершина 1 уже посещена. Больше нет рекурсивных вызовов
Стек 0 1 3 2
Шаг 4:
Топологическая сортировка (4),, visited[4] = true
Вершины 0 и 1 уже посещены. Больше нет рекурсивных вызовов
Стек 0 1 3 2 4
Шаг 5:
Топологическая сортировка (5), visited[5] = true
Вершины 2 и 0 уже посещены. Больше нет рекурсивных вызовов
Стек 0 1 3 2 4 5
Шаг 6:
Вывод всех элементов стека сверху вниз
Ниже приведены реализации топологической сортировки. Пожалуйста, ознакомьтесь с реализацией DFT для несвязного графа и обратите внимание на различия между вторым кодом, приведенным там, и кодом, приведенным ниже.
C++
// Программа на C++ для вывода топологической сортировки DAG
#include<iostream>
#include <list>
#include <stack>
using namespace std;
// Класс для представления графа
class Graph
{
int V; // Количество вершин
// Указатель на массив, содержащий список смежности
list<int> *adj;
// Функция, используемая topologicalSort
void topologicalSortUtil(int v, bool visited[], stack<int> &Stack);
public:
Graph(int V); // Конструктор
// Функция для добавления ребра в граф
void addEdge(int v, int w);
// Выводит топологическую сортировку графа
void topologicalSort();
};
Graph::Graph(int V)
{
this->V = V;
adj = new list<int>[V];
}
void Graph::addEdge(int v, int w)
{
adj[v].push_back(w); // Add w to v’s list.
}
// Рекурсивная функция, используемая topologicalSort
void Graph::topologicalSortUtil(int v, bool visited[],
stack<int> &Stack)
{
// Помечаем текущий узел как посещенный
visited[v] = true;
// Рекурсивно вызываем функцию для всех смежных вершин
list<int>::iterator i;
for (i = adj[v].begin(); i != adj[v].end(); ++i)
if (!visited[*i])
topologicalSortUtil(*i, visited, Stack);
// Добавляем текущую вершину в стек с результатом
Stack.push(v);
}
// Функция для поиска топологической сортировки.
// Рекурсивно использует topologicalSortUtil()
void Graph::topologicalSort()
{
stack<int> Stack;
// Помечаем все вершины как непосещенные
bool *visited = new bool[V];
for (int i = 0; i < V; i++)
visited[i] = false;
// Вызываем рекурсивную вспомогательную функцию
// для поиска топологической сортировки для каждой вершины
for (int i = 0; i < V; i++)
if (visited[i] == false)
topologicalSortUtil(i, visited, Stack);
// Выводим содержимое стека
while (Stack.empty() == false)
{
cout << Stack.top() << " ";
Stack.pop();
}
}
// Программа для тестирования
int main()
{
// Создаем граф, приведенный на диаграмме выше
Graph g(6);
g.addEdge(5, 2);
g.addEdge(5, 0);
g.addEdge(4, 0);
g.addEdge(4, 1);
g.addEdge(2, 3);
g.addEdge(3, 1);
cout << "Following is a Topological Sort of the given graph \n";
g.topologicalSort();
return 0;
}
Java
// Программа на Java для поиска топологической сортировки
import java.io.*;
import java.util.*;
// Этот класс представляет ориентированный граф с использованием списка смежности
class Graph
{
private int V; // Количество вершин
private LinkedList<Integer> adj[]; // Список смежности
// Конструктор
Graph(int v)
{
V = v;
adj = new LinkedList[v];
for (int i=0; i<v; ++i)
adj[i] = new LinkedList();
}
// Функция для добавления ребра в граф
void addEdge(int v,int w) { adj[v].add(w); }
// Рекурсивная функция, используемая topologicalSort
void topologicalSortUtil(int v, boolean visited[],
Stack stack)
{
// Помечаем текущий узел как посещенный
visited[v] = true;
Integer i;
// Рекурсивно вызываем функцию для всех смежных вершин
Iterator<Integer> it = adj[v].iterator();
while (it.hasNext())
{
i = it.next();
if (!visited[i])
topologicalSortUtil(i, visited, stack);
}
// Добавляем текущую вершину в стек с результатом
stack.push(new Integer(v));
}
// Функция для поиска топологической сортировки.
// Рекурсивно использует topologicalSortUtil()
void topologicalSort()
{
Stack stack = new Stack();
// Помечаем все вершины как непосещенные
boolean visited[] = new boolean[V];
for (int i = 0; i < V; i++)
visited[i] = false;
// Вызываем рекурсивную вспомогательную функцию
// для поиска топологической сортировки для каждой вершины
for (int i = 0; i < V; i++)
if (visited[i] == false)
topologicalSortUtil(i, visited, stack);
// Выводим содержимое стека
while (stack.empty()==false)
System.out.print(stack.pop() + " ");
}
// Программа для тестирования
public static void main(String args[])
{
// Создаем граф, приведенный на диаграмме выше
Graph g = new Graph(6);
g.addEdge(5, 2);
g.addEdge(5, 0);
g.addEdge(4, 0);
g.addEdge(4, 1);
g.addEdge(2, 3);
g.addEdge(3, 1);
System.out.println("Following is a Topological " +
"sort of the given graph");
g.topologicalSort();
}
}
// Этот код предоставлен Аакашем Хасия (Aakash Hasija)
Python
#Программа на Python для вывода результата поиска топографической сортировки DAG из коллекции import defaultdict
#Класс для представления графа
class Graph:
def __init__(self,vertices):
self.graph = defaultdict(list) #dictionary containing adjacency List
self.V = vertices #No. of vertices
# Функция для добавления ребра в граф
def addEdge(self,u,v):
self.graph[u].append(v)
# Рекурсивная функция, используемая topologicalSort
def topologicalSortUtil(self,v,visited,stack):
# Помечаем текущий узел как посещенный
visited[v] = True
# Рекурсивно вызываем функцию для всех смежных вершин
for i in self.graph[v]:
if visited[i] == False:
self.topologicalSortUtil(i,visited,stack)
# Добавляем текущую вершину в стек с результатом
stack.insert(0,v)
# Функция для поиска топологической сортировки.
# Рекурсивно использует topologicalSortUtil()
def topologicalSort(self):
# Помечаем все вершины как непосещенные
visited = [False]*self.V
stack =[]
# Вызываем рекурсивную вспомогательную функцию
# для поиска топологической сортировки для каждой вершины
for i in range(self.V):
if visited[i] == False:
self.topologicalSortUtil(i,visited,stack)
# Выводим содержимое стека
print stack
g= Graph(6)
g.addEdge(5, 2);
g.addEdge(5, 0);
g.addEdge(4, 0);
g.addEdge(4, 1);
g.addEdge(2, 3);
g.addEdge(3, 1);
print "Following is a Topological Sort of the given graph"
g.topologicalSort()
# Код предоставлен Ниламом Ядавом (Neelam Yadav)
<b>Вывод:</b>
Following is a Topological Sort of the given graph
5 4 2 3 1 0
Сложность по времени: приведенный выше алгоритм это DFS с дополнительным стеком. Таким образом, сложность времени такая же, как и у DFS, которая равна O(V+E).
Примечание: также можно использовать вектор вместо стека. Если используется вектор, чтобы получить топологическую сортировку, необходимо выводить элементы в обратном порядке.
Применение:
Топологическая сортировка в основном используется для составления графика работ из заданных зависимостей между ними. В компьютерных науках применяется для планирования команд, упорядочения ячеек для вычисления формулы при повторном вычислении значений формул в электронных таблицах, логического синтеза, определения порядка задач компиляции для выполнения в make-файлах, сериализации данных и разрешения символьных зависимостей в компоновщиках [2].
Статьи по теме
Алгоритм Кана для топологической сортировки: еще один O(V+E) алгоритм.
Все топологические сортировки ориентированного ациклического графа
Ссылки
http://www.personal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/GraphAlgor/topoSort.htm
http://en.wikipedia.org/wiki/Topological_sorting
Пожалуйста, оставьте комментарий, если обнаружите ошибку, или если захотите поделиться дополнительной информацией по обсуждаемой теме.
Узнать подробнее о курсе.
nightvich
Была однажды задача по перебсорке большого количества java пакетов, которые зависимы друг от друга. Собирать надо было а изолированном окружении за один проход. Для этого использовал топологическую сортировку по алгоритму Тарьяна. За перевод спасибо.
malkovsky
Хмм… скорее всего это ровно то, что рассказывается в статье, уверен, что это фольклор. Тарьян вообще кучу всего придумал, наиболее близкое по теме — это скорее всего алгоритм нахождения компонент сильной связности, если применить его к ациклическому графу, то он сделает на нем топологическую сортировку, а потом обнаружит, что все компоненты состоят из одной вершины.
nightvich
Да, вроде он.
Вот тут хорошее описание.