如何通过依赖关系对依赖对象进行排序

gar*_*rik 72 c# sorting algorithm dependencies topological-sort

我有一个集合:

List<VPair<Item, List<Item>> dependencyHierarchy;
Run Code Online (Sandbox Code Playgroud)

对中的第一项是某个对象(项),第二项是第一项依赖的相同类型对象的集合.我希望得到一个List<Item>依赖顺序,所以没有依赖于第一个元素的项目等等(没有循环依赖!).

输入:

Item4 depends on Item3 and Item5
Item3 depends on Item1
Item1 does not depend on any one
Item2 depends on Item4 
Item5 does not depend on any one 

结果:

Item1
Item5
Item3
Item4
Item2

谢谢.

解:

拓扑排序(感谢LoïcFévrier的想法)

例如在C#中,例如Java的 (感谢xcud伟大的例子)

Mes*_*smo 83

经过一段时间的努力,这是我尝试使用Linq风格的TSort扩展方法:

public static IEnumerable<T> TSort<T>( this IEnumerable<T> source, Func<T, IEnumerable<T>> dependencies, bool throwOnCycle = false )
{
    var sorted = new List<T>();
    var visited = new HashSet<T>();

    foreach( var item in source )
        Visit( item, visited, sorted, dependencies, throwOnCycle );

    return sorted;
}

private static void Visit<T>( T item, HashSet<T> visited, List<T> sorted, Func<T, IEnumerable<T>> dependencies, bool throwOnCycle )
{
    if( !visited.Contains( item ) )
    {
        visited.Add( item );

        foreach( var dep in dependencies( item ) )
            Visit( dep, visited, sorted, dependencies, throwOnCycle );

        sorted.Add( item );
    }
    else
    {
        if( throwOnCycle && !sorted.Contains( item ) )
            throw new Exception( "Cyclic dependency found" );
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢DMM!这对我来说只有一个修改:在`if(!visited.Contains(item))`的末尾,我添加了类似(在Java中)`else if(!sorted.contains(item)){throw new Exception ("无效的依赖循环!");}`来管理A-> B,B-> C和C-> A的情况. (17认同)
  • 添加一个如何使用代码的例子会很棒. (5认同)
  • +1更简单,似乎对我有用.我做的唯一改变是对`visited`使用`Dictionary <T,object>`而不是`List <T>` - 对于大型集合应该更快. (4认同)
  • 然而,非常有用的答案想象出`sources = {A,B}`的情况,其中`dependencies(B)= {A}`.您拥有它的代码会将其检测为"循环依赖",这似乎是不正确的. (3认同)
  • 谢谢EM - 我已经更新为访问集合使用HashSet. (2认同)
  • +1我在维基百科上查看了算法的伪代码,并认为它很容易实现,但实际实现更容易! (2认同)
  • 感谢Supericy的反馈,我刚刚更新,以更好地反映修复它的电影给出的代码. (2认同)

Loï*_*ier 41

使用拓扑排序的完美示例:

http://en.wikipedia.org/wiki/Topological_sorting

它将为您提供您所需要的.

  • 找到了一个C#impl of tsort:http://tawani.blogspot.com/2009/02/topological-sorting-and-cyclic.html (13认同)

sin*_*law 19

这有一个难题.

对于我们这些不想重新发明轮子的人:使用nuget安装QuickGraph .NET库,其中包括多种图形算法,包括拓扑排序.

要使用它,您需要创建一个AdjacencyGraph<,>这样的实例AdjacencyGraph<String, SEdge<String>>.然后,如果您包含适当的扩展名:

using QuickGraph.Algorithms;
Run Code Online (Sandbox Code Playgroud)

你可以打电话:

var sorted = myGraph.TopologicalSort();
Run Code Online (Sandbox Code Playgroud)

获取已排序节点的列表.


Kru*_*lur 11

我喜欢DMM的答案,但它假设输入节点是叶子(可能是也可能不是预期的).

我发布了一个使用LINQ的替代解决方案,但没有做出这个假设.此外,该解决方案用于yield return能够快速返回叶子(例如使用TakeWhile).

public static IEnumerable<T> TopologicalSort<T>(this IEnumerable<T> nodes, 
                                                Func<T, IEnumerable<T>> connected)
{
    var elems = nodes.ToDictionary(node => node, 
                                   node => new HashSet<T>(connected(node)));
    while (elems.Count > 0)
    {
        var elem = elems.FirstOrDefault(x => x.Value.Count == 0);
        if (elem.Key == null)
        {
            throw new ArgumentException("Cyclic connections are not allowed");
        }
        elems.Remove(elem.Key);
        foreach (var selem in elems)
        {
            selem.Value.Remove(elem.Key);
        }
        yield return elem.Key;
    }
}
Run Code Online (Sandbox Code Playgroud)


Tha*_*ran 6

这是我自己重新实现的拓扑排序,这个想法是基于http://tawani.blogspot.com/2009/02/topological-sorting-and-cyclic.html(移植的Java源代码消耗太多内存,检查50k对象的成本是50k*50k*4 = 10GB这是不可接受的.此外,它还有一些地方的Java编码约定)

using System.Collections.Generic;
using System.Diagnostics;

namespace Modules
{
    /// <summary>
    /// Provides fast-algorithm and low-memory usage to sort objects based on their dependencies. 
    /// </summary>
    /// <remarks>
    /// Definition: http://en.wikipedia.org/wiki/Topological_sorting
    /// Source code credited to: http://tawani.blogspot.com/2009/02/topological-sorting-and-cyclic.html    
    /// Original Java source code: http://www.java2s.com/Code/Java/Collections-Data-Structure/Topologicalsorting.htm
    /// </remarks>
    /// <author>ThangTran</author>
    /// <history>
    /// 2012.03.21 - ThangTran: rewritten based on <see cref="TopologicalSorter"/>.
    /// </history>
    public class DependencySorter<T>
    {
        //**************************************************
        //
        // Private members
        //
        //**************************************************

        #region Private members

        /// <summary>
        /// Gets the dependency matrix used by this instance.
        /// </summary>
        private readonly Dictionary<T, Dictionary<T, object>> _matrix = new Dictionary<T, Dictionary<T, object>>();

        #endregion


        //**************************************************
        //
        // Public methods
        //
        //**************************************************

        #region Public methods

        /// <summary>
        /// Adds a list of objects that will be sorted.
        /// </summary>
        public void AddObjects(params T[] objects)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(objects != null);
            Debug.Assert(objects.Length > 0);
            // --- End parameters checking code -------------------------------

            // add to matrix
            foreach (T obj in objects)
            {
                // add to dictionary
                _matrix.Add(obj, new Dictionary<T, object>());
            }
        }

        /// <summary>
        /// Sets dependencies of given object.
        /// This means <paramref name="obj"/> depends on these <paramref name="dependsOnObjects"/> to run.
        /// Please make sure objects given in the <paramref name="obj"/> and <paramref name="dependsOnObjects"/> are added first.
        /// </summary>
        public void SetDependencies(T obj, params T[] dependsOnObjects)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(dependsOnObjects != null);
            // --- End parameters checking code -------------------------------

            // set dependencies
            Dictionary<T, object> dependencies = _matrix[obj];
            dependencies.Clear();

            // for each depended objects, add to dependencies
            foreach (T dependsOnObject in dependsOnObjects)
            {
                dependencies.Add(dependsOnObject, null);
            }
        }

        /// <summary>
        /// Sorts objects based on this dependencies.
        /// Note: because of the nature of algorithm and memory usage efficiency, this method can be used only one time.
        /// </summary>
        public T[] Sort()
        {
            // prepare result
            List<T> result = new List<T>(_matrix.Count);

            // while there are still object to get
            while (_matrix.Count > 0)
            {
                // get an independent object
                T independentObject;
                if (!this.GetIndependentObject(out independentObject))
                {
                    // circular dependency found
                    throw new CircularReferenceException();
                }

                // add to result
                result.Add(independentObject);

                // delete processed object
                this.DeleteObject(independentObject);
            }

            // return result
            return result.ToArray();
        }

        #endregion


        //**************************************************
        //
        // Private methods
        //
        //**************************************************

        #region Private methods

        /// <summary>
        /// Returns independent object or returns NULL if no independent object is found.
        /// </summary>
        private bool GetIndependentObject(out T result)
        {
            // for each object
            foreach (KeyValuePair<T, Dictionary<T, object>> pair in _matrix)
            {
                // if the object contains any dependency
                if (pair.Value.Count > 0)
                {
                    // has dependency, skip it
                    continue;
                }

                // found
                result = pair.Key;
                return true;
            }

            // not found
            result = default(T);
            return false;
        }

        /// <summary>
        /// Deletes given object from the matrix.
        /// </summary>
        private void DeleteObject(T obj)
        {
            // delete object from matrix
            _matrix.Remove(obj);

            // for each object, remove the dependency reference
            foreach (KeyValuePair<T, Dictionary<T, object>> pair in _matrix)
            {
                // if current object depends on deleting object
                pair.Value.Remove(obj);
            }
        }


        #endregion
    }

    /// <summary>
    /// Represents a circular reference exception when sorting dependency objects.
    /// </summary>
    public class CircularReferenceException : Exception
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CircularReferenceException"/> class.
        /// </summary>
        public CircularReferenceException()
            : base("Circular reference found.")
        {            
        }
    }
}
Run Code Online (Sandbox Code Playgroud)