Скажем, у меня есть график, где узлы хранятся в отсортированном списке. Теперь я хочу, чтобы топологический сортировать этот график, сохраняя исходный порядок, где топологический порядок undefined. Есть ли хорошие алгоритмы для этого?
Стабильная топологическая сортировка
Ответ 1
Одна из возможностей заключается в вычислении лексикографически наименее топологического порядка. Алгоритм состоит в том, чтобы поддерживать приоритетную очередь, содержащую узлы, чья эффективная степень (по не обрабатываемым узлам) равна нулю. Неоднократно выгружайте node с наименьшей меткой, добавляйте ее в порядок, уменьшайте эффективные в градусах своих преемников, ставьте в очередь те, которые теперь имеют нулевую степень. Это дает 1234567890 на примере btilly, но, как правило, не сводит к минимуму инверсии.
Свойства, которые мне нравятся в этом алгоритме, заключаются в том, что вывод имеет чистое определение, явно удовлетворяемое только одним порядком, и что всякий раз, когда после node y появляется инверсия (node x, хотя x < y) x наибольшая зависимость больше, чем у наибольшей зависимости, что является "оправданием" видов для инвертирования x и y. Следствием является то, что при отсутствии ограничений младший порядок lex упорядочен по порядку.
Ответ 2
Проблема двояка:
- Топологическая сортировка
- Стабильная сортировка
После многих ошибок и испытаний я придумал простой алгоритм, похожий на сортировку пузырьков, но с критериями топологического порядка.
Я тщательно протестировал алгоритм на полных графиках с полными комбинациями краев, поэтому его можно считать доказанным.
Циклические зависимости допускаются и разрешаются в соответствии с первоначальным порядком элементов в последовательности. Полученный порядок является идеальным и представляет собой максимально возможное совпадение.
Вот исходный код в С#:
static class TopologicalSort
{
/// <summary>
/// Delegate definition for dependency function.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="a">The A.</param>
/// <param name="b">The B.</param>
/// <returns>
/// Returns <c>true</c> when A depends on B. Otherwise, <c>false</c>.
/// </returns>
public delegate bool TopologicalDependencyFunction<in T>(T a, T b);
/// <summary>
/// Sorts the elements of a sequence in dependency order according to comparison function with Gapotchenko algorithm.
/// The sort is stable. Cyclic dependencies are tolerated and resolved according to original order of elements in sequence.
/// </summary>
/// <typeparam name="T">The type of the elements of source.</typeparam>
/// <param name="source">A sequence of values to order.</param>
/// <param name="dependencyFunction">The dependency function.</param>
/// <param name="equalityComparer">The equality comparer.</param>
/// <returns>The ordered sequence.</returns>
public static IEnumerable<T> StableOrder<T>(
IEnumerable<T> source,
TopologicalDependencyFunction<T> dependencyFunction,
IEqualityComparer<T> equalityComparer)
{
if (source == null)
throw new ArgumentNullException("source");
if (dependencyFunction == null)
throw new ArgumentNullException("dependencyFunction");
if (equalityComparer == null)
throw new ArgumentNullException("equalityComparer");
var graph = DependencyGraph<T>.TryCreate(source, dependencyFunction, equalityComparer);
if (graph == null)
return source;
var list = source.ToList();
int n = list.Count;
Restart:
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < i; ++j)
{
if (graph.DoesXHaveDirectDependencyOnY(list[j], list[i]))
{
bool jOnI = graph.DoesXHaveTransientDependencyOnY(list[j], list[i]);
bool iOnJ = graph.DoesXHaveTransientDependencyOnY(list[i], list[j]);
bool circularDependency = jOnI && iOnJ;
if (!circularDependency)
{
var t = list[i];
list.RemoveAt(i);
list.Insert(j, t);
goto Restart;
}
}
}
}
return list;
}
/// <summary>
/// Sorts the elements of a sequence in dependency order according to comparison function with Gapotchenko algorithm.
/// The sort is stable. Cyclic dependencies are tolerated and resolved according to original order of elements in sequence.
/// </summary>
/// <typeparam name="T">The type of the elements of source.</typeparam>
/// <param name="source">A sequence of values to order.</param>
/// <param name="dependencyFunction">The dependency function.</param>
/// <returns>The ordered sequence.</returns>
public static IEnumerable<T> StableOrder<T>(
IEnumerable<T> source,
TopologicalDependencyFunction<T> dependencyFunction)
{
return StableOrder(source, dependencyFunction, EqualityComparer<T>.Default);
}
sealed class DependencyGraph<T>
{
private DependencyGraph()
{
}
public IEqualityComparer<T> EqualityComparer
{
get;
private set;
}
public sealed class Node
{
public int Position
{
get;
set;
}
List<T> _Children = new List<T>();
public IList<T> Children
{
get
{
return _Children;
}
}
}
public IDictionary<T, Node> Nodes
{
get;
private set;
}
public static DependencyGraph<T> TryCreate(
IEnumerable<T> source,
TopologicalDependencyFunction<T> dependencyFunction,
IEqualityComparer<T> equalityComparer)
{
var list = source as IList<T>;
if (list == null)
list = source.ToArray();
int n = list.Count;
if (n < 2)
return null;
var graph = new DependencyGraph<T>();
graph.EqualityComparer = equalityComparer;
graph.Nodes = new Dictionary<T, Node>(n, equalityComparer);
bool hasDependencies = false;
for (int position = 0; position < n; ++position)
{
var element = list[position];
Node node;
if (!graph.Nodes.TryGetValue(element, out node))
{
node = new Node();
node.Position = position;
graph.Nodes.Add(element, node);
}
foreach (var anotherElement in list)
{
if (equalityComparer.Equals(element, anotherElement))
continue;
if (dependencyFunction(element, anotherElement))
{
node.Children.Add(anotherElement);
hasDependencies = true;
}
}
}
if (!hasDependencies)
return null;
return graph;
}
public bool DoesXHaveDirectDependencyOnY(T x, T y)
{
Node node;
if (Nodes.TryGetValue(x, out node))
{
if (node.Children.Contains(y, EqualityComparer))
return true;
}
return false;
}
sealed class DependencyTraverser
{
public DependencyTraverser(DependencyGraph<T> graph)
{
_Graph = graph;
_VisitedNodes = new HashSet<T>(graph.EqualityComparer);
}
DependencyGraph<T> _Graph;
HashSet<T> _VisitedNodes;
public bool DoesXHaveTransientDependencyOnY(T x, T y)
{
if (!_VisitedNodes.Add(x))
return false;
Node node;
if (_Graph.Nodes.TryGetValue(x, out node))
{
if (node.Children.Contains(y, _Graph.EqualityComparer))
return true;
foreach (var i in node.Children)
{
if (DoesXHaveTransientDependencyOnY(i, y))
return true;
}
}
return false;
}
}
public bool DoesXHaveTransientDependencyOnY(T x, T y)
{
var traverser = new DependencyTraverser(this);
return traverser.DoesXHaveTransientDependencyOnY(x, y);
}
}
}
И небольшое примерное приложение:
class Program
{
static bool DependencyFunction(char a, char b)
{
switch (a + " depends on " + b)
{
case "A depends on B":
return true;
case "B depends on D":
return true;
default:
return false;
}
}
static void Main(string[] args)
{
var source = "ABCDEF";
var result = TopologicalSort.StableOrder(source.ToCharArray(), DependencyFunction);
Console.WriteLine(string.Concat(result));
}
}
Учитывая входные элементы {A, B, C, D, E, F}, где A зависит от B и B зависит от D, выходной результат {D, B, A, C, E, F}.
UPDATE: Я написал небольшую статью о стабильной топологической задаче сортировки, алгоритме и его проверке. Надеюсь, что это дает больше объяснений и полезно для разработчиков и исследователей.
Ответ 3
У вас недостаточно критериев для указания того, что вы ищете. Например, рассмотрим граф с двумя направленными компонентами.
1 -> 2 -> 3 -> 4 -> 5
6 -> 7 -> 8 -> 9 -> 0
Какой из следующих видов вы предпочитаете?
6, 7, 8, 9, 0, 1, 2, 3, 4, 5
1, 2, 3, 4, 5, 6, 7, 8, 9, 0
Первые результаты разрыва всех связей, поместив самый низкий node как можно ближе к заголовку списка. Таким образом, 0 побед. Второй результат заключается в попытке минимизировать количество раз, когда A < B и B появляются перед A в топологическом типе. Оба являются разумными ответами. Второй, вероятно, более приятный.
Я могу легко создать алгоритм для первого. Для начала возьмите самый низкий node и выполните поиск в ширину, чтобы найти расстояние до кратчайшего корня node. Если есть галстук, определите набор узлов, которые могут отображаться на таком кратчайшем пути. Возьмите самый низкий node в этом наборе и поместите наилучший путь от него к корню, а затем поместите наилучший возможный путь из самого низкого node, с которым мы начали с него. Найдите следующий самый низкий node, который еще не находится в топологическом порядке, и продолжайте.
Создание алгоритма для более приятной версии кажется намного сложнее. См. http://en.wikipedia.org/wiki/Feedback_arc_set для связанной проблемы, которая решительно свидетельствует о том, что она фактически является NP-полной.
Ответ 4
Интерпретация "стабильной топологической сортировки" как линеаризации DAG, которая варьируется в линеаризации, где топологический порядок не имеет значения, сортируется лексикографически. Это можно решить с помощью метода DFS линеаризации с изменением того, что узлы посещаются в лексикографическом порядке.
У меня есть класс Python Digraph с методом линеаризации, который выглядит следующим образом:
def linearize_as_needed(self):
if self.islinearized:
return
# Algorithm: DFS Topological sort
# https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
temporary = set()
permanent = set()
L = [ ]
def visit(vertices):
for vertex in sorted(vertices, reverse=True):
if vertex in permanent:
pass
elif vertex in temporary:
raise NotADAG
else:
temporary.add(vertex)
if vertex in self.arrows:
visit(self.arrows[vertex])
L.append(vertex)
temporary.remove(vertex)
permanent.add(vertex)
# print('visit: {} => {}'.format(vertices, L))
visit(self.vertices)
self._linear = list(reversed(L))
self._iter = iter(self._linear)
self.islinearized = True
Здесь
self.vertices
- множество всех вершин, а
self.arrows
имеет отношение смежности в виде бита левых узлов к наборам правых узлов.
Ответ 5
Здесь простой итерационный подход к топологической сортировке: постоянно удаляйте node с степенью 0 вместе с ее ребрами.
Чтобы достичь стабильной версии, просто измените ее на: постоянно удаляйте наименьший индекс node со степенью 0 вместе с его ребрами.
В псевдо-питоне:
# N is the number of nodes, labeled 0..N-1
# edges[i] is a list of nodes j, corresponding to edges (i, j)
inDegree = [0] * N
for i in range(N):
for j in edges[i]:
inDegree[j] += 1
# Now we maintain a "frontier" of in-degree 0 nodes.
# We take the smallest one until the frontier is exhausted.
# Note: You could use a priority queue / heap instead of a list,
# giving O(NlogN) runtime. This naive implementation is
# O(N^2) worst-case (when the order is very ambiguous).
frontier = []
for i in range(N):
if inDegree[i] == 0:
frontier.append(i)
order = []
while frontier:
i = min(frontier)
frontier.remove(i)
for j in edges[i]:
inDegree[j] -= 1
if inDegree[j] == 0:
frontier.append(j)
# Done - order is now a list of the nodes in topological order,
# with ties broken by original order in the list.