【问题标题】:Maintaining a sorted list维护排序列表
【发布时间】:2013-05-13 21:21:59
【问题描述】:

我需要存储一个节点集合:

class Node
{
   int Value;
   //other info
}

我有三个要求:

  1. 需要能够有效地检索集合中具有最低值的节点
  2. 需要能够有效地将节点插入到集合中
  3. 两个节点可以有相同的值

我认为用于此的最佳集合是某种排序列表。这样,只需从排序列表中获取第一个元素,就可以有效地满足要求 #1。通过在列表中的正确位置插入一个新节点,可以有效地满足要求 #2。

但是 .Net 中的 SortedList 集合类似于 SortedDictionary 并且要求被排序的键是唯一的,这违反了要求 #3。

.Net 中似乎没有满足这些要求的集合,主要是因为确实存在的自排序集合要求排序的键是唯一的。这是什么原因?我认为这不可能是疏忽。我在这里没有抓住什么?我可以找到关于此的类似问题,但它们通常涉及有人建议 SortList,然后意识到这不起作用,然后对话在没有标准解决方案的情况下淡出。至少如果有人会说“C# 中没有用于此任务的集合,您需要一起破解一些东西”,这将是一个答案。

是否可以使用常规的List<Node> 并在添加新节点时重新排序列表?似乎这不如将节点插入到正确的位置开始那样有效。也许这就是我应该做的?手动遍历列表,直到我自己找到插入新节点的位置?

【问题讨论】:

  • 你看过C5 Collections吗?那里可能有东西。
  • 如果你最终使用了一个你自己排序的List<Node>,请务必使用List<>BinarySearch方法找到需要插入新项目的索引。如果这意味着不使用 BinarySearch,请不要“手动迭代”。
  • 从需求看来你需要一个堆,而不是一个排序列表。将一个项目添加到堆中只需 O(log n),而不是排序列表的 O(n),甚至添加然后重新排序的 O(n log n)。
  • @harold 但是如果他以一个空的List<Node>开头,并且他总是通过以下方法添加到列表中:public void Add(Node n) { var idx = innerList.BinarySearch(n); if (idx >= 0) { innerList.Insert(idx, n); else { innerList.Insert(~idx, n); } },那么List<Node>总是会自动排序,并且他永远不必致电Sort。当然,Node 应该是IComparable<Node>,或者BinarySearch 方法应该被赋予一个IComparer<Node> 作为它的第二个参数(可以使用.NET 4.5 的Comparer<Node>.Create 静态方法创建)。
  • @JeppeStigNielsen 是的,这还不错,属于我提到的 O(n) 情况,因为插入可能会移动很多东西。堆没有这个问题。

标签: c#


【解决方案1】:

如果您只需要有效地插入并快速检索具有最低值的项目,那么您不需要排序列表。你需要一个heap。查看A Generic Binary Heap Class

【讨论】:

  • 我发现 4 元堆,尽管 RemoveMin 看起来较慢,但在实践中仍然比二元堆快,即使对于 RemoveMin 也是如此。对于添加项目,它们的速度要快得多。 [这不是批评,只是我认为您可能会觉得有趣的事情]
  • @harold:感谢您提供的信息。我一直想尝试其他堆类型。我喜欢二进制堆,因为它易于实现,而且非常有效。我将不得不研究 4 元堆。
【解决方案2】:

通过添加对象 id 或其他唯一标识符使您的 list_key 唯一:ID 4 和 5,都具有值“1”将变为“1_4”和“1_5”,它们可以毫无问题地添加到排序列表中,并将按预期排序。

【讨论】:

    【解决方案3】:

    您可以使用SortedList<int, List<NodeInfo>>,将Value 放在键中,将所有其他属性放在值中:

    public class NodeList : SortedList<int, List<NodeInfo>>
    {
        public void Add(int key, NodeInfo info)
        {
            if (this.Keys.Contains(key))
            {
                this[key].Add(info);
            }
            else
            {
                this.Add(key, new List<NodeInfo>() { info } );
            }
        }
    
        public NodeInfo FirstNode()
        {
            if (this.Count == 0)
                return null;
            return this.First().Value.First();
        }
    }
    
    public class NodeInfo
    {
        public string Info { get; set; }
        // TODO: add other members
    }
    

    以下是一些示例用法:

    var list = new NodeList();
    
    // adding
    list.Add(3, new NodeInfo() { Info = "some info 3" });
    
    // inserting
    for (int i = 0; i < 100000; i++)
    {
        list.Add(1, new NodeInfo() { Info = "some info 1" });
        list.Add(2, new NodeInfo() { Info = "some info 2" });
        list.Add(1, new NodeInfo() { Info = "some info 1.1" });
    }
    
    // retrieving the first item
    var firstNodeInfo = list.FirstNode();
    
    // retrieving an item
    var someNodeInfo = list[2].First();
    

    【讨论】:

      【解决方案4】:

      在我看来,使用普通列表并在每次插入后重新排序是可以接受的。在 .NET 中排序非常有效。看到这个线程:String sorting performance degradation in VS2010 vs. VS2008

      【讨论】:

      • 当列表中有 1 亿个项目时不会。而且,是的,有些人确实使用内存很大的列表。
      • 是的。如果您要处理 1 亿个项目并希望列表始终被排序,那么您将不得不自行实现数据结构的自定义实现。
      【解决方案5】:

      您可以在Wintellect's Power Collections for .NET 中使用OrderedMultiDictionary。这正是您要寻找的。​​p>

      【讨论】:

        猜你喜欢
        • 2023-01-18
        • 2016-05-24
        • 2012-12-09
        • 2012-05-27
        • 2022-01-25
        • 1970-01-01
        • 1970-01-01
        • 2020-07-31
        • 1970-01-01
        相关资源
        最近更新 更多