【问题标题】:How to sort depended objects by dependency如何按依赖对依赖的对象进行排序
【发布时间】:2011-05-05 15:30:04
【问题描述】:

我有一个收藏:

List<VPair<Item, List<Item>> dependencyHierarchy;

对中的第一个项目是某个对象(项目),第二个是第一个所依赖的相同类型对象的集合。我想按依赖顺序获得List&lt;Item&gt;,所以没有依赖第一个元素的项目等等(没有循环依赖!)。

输入:

Item4 依赖于 Item3 和 Item5 Item3 依赖于 Item1 Item1 不依赖于任何一个 Item2 依赖于 Item4 Item5不依赖任何一个

结果:

项目1 项目5 第 3 项 项目4 项目2

谢谢。

解决方案:

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

example on C#, example on Java(感谢xcud 提供了很好的例子)

【问题讨论】:

标签: c# algorithm sorting dependencies topological-sort


【解决方案1】:

为此苦苦挣扎了一段时间,这是我对 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" );
    }
}

【讨论】:

  • +1 简单得多,似乎对我有用。我所做的唯一更改是使用Dictionary&lt;T, object&gt; 而不是List&lt;T&gt; 来代替visited - 对于大型集合应该更快。
  • 感谢 E M - 我已更新为使用 HashSet 来访问访问的集合。
  • +1 我在 Wikipedia 上查看了该算法的伪代码,并认为它很容易实现,但实际实现更容易!
  • 感谢 DMM!这对我有一个修改:在if( !visited.Contains( item ) ) 的末尾,我添加了类似(在Java 中)else if(!sorted.contains(item)){throw new Exception("Invalid dependency cycle!");} 来管理A->B、B->C 和C->A 的情况。跨度>
  • 非常有帮助的答案,但是,想象一下sources = {A, B}dependencies(B) = {A} 的情况。您拥有的代码会将其检测为“循环依赖”,这似乎不正确。
【解决方案2】:

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

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

它会为您提供您所需要的。

【讨论】:

【解决方案3】:

有一个很好的方法

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

要使用它,您需要创建AdjacencyGraph&lt;,&gt; 的实例,例如AdjacencyGraph&lt;String, SEdge&lt;String&gt;&gt;。然后,如果您包含适当的扩展名:

using QuickGraph.Algorithms;

您可以致电:

var sorted = myGraph.TopologicalSort();

获取已排序节点的列表。

【讨论】:

    【解决方案4】:

    我喜欢 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;
        }
    }
    

    【讨论】:

    • 这是迄今为止我见过的最紧凑的拓扑排序实现。
    • “但它假设输入节点是叶子”我不明白。你能解释一下吗?
    • @osexpert,这就是算法的工作原理:我们需要始终使用叶子——不依赖于任何其他节点的节点。此代码确保:var elem = elems.FirstOrDefault(x =&gt; x.Value.Count == 0);。特别是,x.Value.Count == 0 确保给定节点没有依赖关系。
    • 另外,这个实现很紧凑,但不是最优的。我们在每次迭代中搜索一个叶子,这使得它 O(n^2)。可以通过在while循环之前创建一个包含所有叶子的队列来改进它,然后只要它们变得“独立”,就可以向其中添加新项目。
    • 是的,上面的 IIRC 是基于我为处理编译器中数百个模块的依赖关系而编写的东西,如果执行得相当好的话。
    【解决方案5】:

    这是我自己重新实现的拓扑排序,思路基于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.")
            {            
            }
        }
    }
    

    【讨论】:

      【解决方案6】:

      我不喜欢递归方法,所以 DMM 已经过时了。 Krumelur 看起来不错但似乎占用大量内存? 制作了一种似乎可行的基于堆栈的替代方法。使用与 DMM 相同的 DFS 逻辑,我在测试时将此解决方案用作比较。

          public static IEnumerable<T> TopogicalSequenceDFS<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> deps)
          {
              HashSet<T> yielded = new HashSet<T>();
              HashSet<T> visited = new HashSet<T>();
              Stack<Tuple<T, IEnumerator<T>>> stack = new Stack<Tuple<T, IEnumerator<T>>>();
      
              foreach (T t in source)
              {
                  stack.Clear();
                  if (visited.Add(t))
                      stack.Push(new Tuple<T, IEnumerator<T>>(t, deps(t).GetEnumerator()));
      
                  while (stack.Count > 0)
                  {
                      var p = stack.Peek();
                      bool depPushed = false;
                      while (p.Item2.MoveNext())
                      {
                          var curr = p.Item2.Current;
                          if (visited.Add(curr))
                          {
                              stack.Push(new Tuple<T, IEnumerator<T>>(curr, deps(curr).GetEnumerator()));
                              depPushed = true;
                              break;
                          }
                          else if (!yielded.Contains(curr))
                              throw new Exception("cycle");
                      }
      
                      if (!depPushed)
                      {
                          p = stack.Pop();
                          if (!yielded.Add(p.Item1))
                              throw new Exception("bug");
                          yield return p.Item1;
                      }
                  }
              }
          }
      

      这也是一个更简单的基于堆栈的 BFS 变体。它将产生与上述不同的结果,但仍然有效。我不确定使用上面的 DFS 变体是否有任何优势,但创建它很有趣。

          public static IEnumerable<T> TopologicalSequenceBFS<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> dependencies)
          {
              var yielded = new HashSet<T>();
              var visited = new HashSet<T>();
              var stack = new Stack<Tuple<T, bool>>(source.Select(s => new Tuple<T, bool>(s, false))); // bool signals Add to sorted
      
              while (stack.Count > 0)
              {
                  var item = stack.Pop();
                  if (!item.Item2)
                  {
                      if (visited.Add(item.Item1))
                      {
                          stack.Push(new Tuple<T, bool>(item.Item1, true)); // To be added after processing the dependencies
                          foreach (var dep in dependencies(item.Item1))
                              stack.Push(new Tuple<T, bool>(dep, false));
                      }
                      else if (!yielded.Contains(item.Item1))
                          throw new Exception("cyclic");
                  }
                  else
                  {
                      if (!yielded.Add(item.Item1))
                          throw new Exception("bug");
                      yield return item.Item1;
                  }
              }
          }
      

      对于 .NET 4.7+,我建议用 ValueTuple 替换 Tuple 以减少内存使用。在较旧的 .NET 版本中,元组可以替换为 KeyValuePair。

      【讨论】:

        【解决方案7】:

        我会通过将 Item 的依赖项存储在 Item 本身中来简化这一点:

        public class Item
        {
            private List<Item> m_Dependencies = new List<Item>();
        
            protected AddDependency(Item _item) { m_Dependencies.Add(_item); }
        
            public Item()
            {
            }; // eo ctor
        
            public List<Item> Dependencies {get{return(m_Dependencies);};}
        } // eo class Item
        

        然后,鉴于此,您可以为 List 实现自定义 Sort 委托,该委托根据给定 Item 是否包含在其他依赖项列表中进行排序:

        int CompareItem(Item _1, Item _2)
        {
            if(_2.Dependencies.Contains(_1))
                return(-1);
            else if(_1.Dependencies.Contains(_2))
                return(1);
            else
                return(0);
        }
        

        【讨论】:

        • 订单不完整,因此无法正常工作。您需要为每个项目提供他或他的任何后代所依赖的所有项目的列表。 (即构建完整的有向无环图) 很容易找到一个反例:1 取决于 3 和 2,3 of 4。[3 4 1 2] 根据您的算法排序。但是 3 必须在 1 之后。
        • 啊,谢谢。我没有想到。非常感激。反对票来了! :)
        • Loic,您能否进一步解释一下为什么我的建议不起作用?不是说这是对的,只是为了让我学得更好。我刚刚在这里尝试过,对于 OP 的示例和您的示例,我的结果列表都是有序的。可能是运气好? :) 给定您的示例(1 取决于 3 和 2,2 取决于 4),我得到的排序是 [4, 3, 2, 1]
        • 要对列表进行排序,每个排序算法只会检查是否对任何连续元素进行了排序。在您的情况下, sorted 意味着:第二个不依赖于第一个。 [3 4 1 2] 和 [4, 3, 2, 1] 是两个可能的顺序。该算法假设传递性:如果 x
        • 感谢您花时间进一步解释这一点。在进一步的测试中,我的想法确实落空了。编程愉快!
        【解决方案8】:

        一个不同的想法,对于只有一个“父母”的情况取决于:

        您将存储父母而不是 deps。
        所以你可以很容易地判断一个问题是否是其他问题的依赖。
        然后使用Comparable&lt;T&gt;,这将声明依赖“较小”和依赖“较大”。
        然后直接拨打Collections.sort( List&lt;T&gt;, ParentComparator&lt;T&gt;);

        对于多父场景,需要进行树搜索,这会导致执行缓慢。但这可以通过 A* 排序矩阵形式的缓存来解决。

        【讨论】:

          【解决方案9】:

          我将 DMM 的想法与 Wikipedia 上的深度优先搜索算法相结合。它非常适合我的需要。

          public static class TopologicalSorter
          {
          public static List<string> LastCyclicOrder = new List<string>(); //used to see what caused the cycle
          
          sealed class ItemTag
          {
            public enum SortTag
            {
              NotMarked,
              TempMarked,
              Marked
            }
          
            public string Item { get; set; }
            public SortTag Tag { get; set; }
          
            public ItemTag(string item)
            {
              Item = item;
              Tag = SortTag.NotMarked;
            }
          }
          
          public static IEnumerable<string> TSort(this IEnumerable<string> source, Func<string, IEnumerable<string>> dependencies)
          {
            TopologicalSorter.LastCyclicOrder.Clear();
          
            List<ItemTag> allNodes = new List<ItemTag>();
            HashSet<string> sorted = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
          
            foreach (string item in source)
            {
              if (!allNodes.Where(n => string.Equals(n.Item, item, StringComparison.OrdinalIgnoreCase)).Any())
              {
                allNodes.Add(new ItemTag(item)); //don't insert duplicates
              }
              foreach (string dep in dependencies(item))
              {
                if (allNodes.Where(n => string.Equals(n.Item, dep, StringComparison.OrdinalIgnoreCase)).Any()) continue; //don't insert duplicates
                allNodes.Add(new ItemTag(dep));
              }
            }
          
            foreach (ItemTag tag in allNodes)
            {
              Visit(tag, allNodes, dependencies, sorted);
            }
          
            return sorted;
          }
          
          static void Visit(ItemTag tag, List<ItemTag> allNodes, Func<string, IEnumerable<string>> dependencies, HashSet<string> sorted)
          {
            if (tag.Tag == ItemTag.SortTag.TempMarked)
            {
              throw new GraphIsCyclicException();
            }
            else if (tag.Tag == ItemTag.SortTag.NotMarked)
            {
              tag.Tag = ItemTag.SortTag.TempMarked;
              LastCyclicOrder.Add(tag.Item);
          
              foreach (ItemTag dep in dependencies(tag.Item).Select(s => allNodes.Where(t => string.Equals(s, t.Item, StringComparison.OrdinalIgnoreCase)).First())) //get item tag which falls with used string
                Visit(dep, allNodes, dependencies, sorted);
          
              LastCyclicOrder.Remove(tag.Item);
              tag.Tag = ItemTag.SortTag.Marked;
              sorted.Add(tag.Item);
            }
          }
          }
          

          【讨论】:

            【解决方案10】:

            这是来自https://stackoverflow.com/a/9991916/4805491 的重构代码。

            // Version 1
            public static class TopologicalSorter<T> where T : class {
            
                public struct Item {
                    public readonly T Object;
                    public readonly T Dependency;
                    public Item(T @object, T dependency) {
                        Object = @object;
                        Dependency = dependency;
                    }
                }
            
            
                public static T[] Sort(T[] objects, Func<T, T, bool> isDependency) {
                    return Sort( objects.ToList(), isDependency ).ToArray();
                }
            
                public static T[] Sort(T[] objects, Item[] dependencies) {
                    return Sort( objects.ToList(), dependencies.ToList() ).ToArray();
                }
            
            
                private static List<T> Sort(List<T> objects, Func<T, T, bool> isDependency) {
                    return Sort( objects, GetDependencies( objects, isDependency ) );
                }
            
                private static List<T> Sort(List<T> objects, List<Item> dependencies) {
                    var result = new List<T>( objects.Count );
            
                    while (objects.Any()) {
                        var obj = GetIndependentObject( objects, dependencies );
                        RemoveObject( obj, objects, dependencies );
                        result.Add( obj );
                    }
            
                    return result;
                }
            
                private static List<Item> GetDependencies(List<T> objects, Func<T, T, bool> isDependency) {
                    var dependencies = new List<Item>();
            
                    for (var i = 0; i < objects.Count; i++) {
                        var obj1 = objects[i];
                        for (var j = i + 1; j < objects.Count; j++) {
                            var obj2 = objects[j];
                            if (isDependency( obj1, obj2 )) dependencies.Add( new Item( obj1, obj2 ) ); // obj2 is dependency of obj1
                            if (isDependency( obj2, obj1 )) dependencies.Add( new Item( obj2, obj1 ) ); // obj1 is dependency of obj2
                        }
                    }
            
                    return dependencies;
                }
            
            
                private static T GetIndependentObject(List<T> objects, List<Item> dependencies) {
                    foreach (var item in objects) {
                        if (!GetDependencies( item, dependencies ).Any()) return item;
                    }
                    throw new Exception( "Circular reference found" );
                }
            
                private static IEnumerable<Item> GetDependencies(T obj, List<Item> dependencies) {
                    return dependencies.Where( i => i.Object == obj );
                }
            
                private static void RemoveObject(T obj, List<T> objects, List<Item> dependencies) {
                    objects.Remove( obj );
                    dependencies.RemoveAll( i => i.Object == obj || i.Dependency == obj );
                }
            
            }
            
            
            // Version 2
            public class TopologicalSorter {
            
                public static T[] Sort<T>(T[] source, Func<T, T, bool> isDependency) {
                    var list = new LinkedList<T>( source );
                    var result = new List<T>();
            
                    while (list.Any()) {
                        var obj = GetIndependentObject( list, isDependency );
                        list.Remove( obj );
                        result.Add( obj );
                    }
            
                    return result.ToArray();
                }
            
                private static T GetIndependentObject<T>(IEnumerable<T> list, Func<T, T, bool> isDependency) {
                    return list.First( i => !GetDependencies( i, list, isDependency ).Any() );
                }
            
                private static IEnumerable<T> GetDependencies<T>(T obj, IEnumerable<T> list, Func<T, T, bool> isDependency) {
                    return list.Where( i => isDependency( obj, i ) ); // i is dependency of obj
                }
            
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-08-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-04-06
              • 2017-06-15
              • 1970-01-01
              • 2016-01-13
              相关资源
              最近更新 更多