【问题标题】:Collection that maintains sort order C#维护排序顺序的集合 C#
【发布时间】:2015-07-23 13:37:04
【问题描述】:

我有一个类Foo,其中包含一个对象列表:List<Bar>。每个Bar 都有一个可以对其进行排序的属性(TimeSpan 类型,表示持续时间),Bar 是一个不可变对象——也就是说,持续时间不会随着算法的运行而改变。目前,对于每个Foo,我还维护Bar,如果要订购它,它将在列表中排在第一位(即持续时间最短的Bar)。像这样的:

public class Foo
{
    public List<Bar> AllBars { get; set; }

    public Bar FirstBar { get; set; }

    public Foo (Bar bar)
    {
        FirstBar = bar;

        AllBars = new List<Bar>() { bar };
    }

    public AddBar(Bar bar)
    {
        if(bar.Duration < FirstBar.Duration)
        {
            FirstBar = bar;
        }

        AllBars.Add(bar);
    }
}

此类Foo 用于处理性能(速度)至关重要的算法。内存很重要,但不如速度重要。有 nFoos 的列表,每个列表最多有 mBars。到目前为止,这门课一直让我受益匪浅。我现在希望为用户提供多种选择,这意味着我需要提供对列表中前几个Bars 的随机访问。

因此,我想按顺序存储我的Bars,以便我可以按顺序按索引访问它们。在我的Bar 类中,我实现了IComparable 以允许比较Bars 的持续时间,但我一直在选择合适的数据类型。我查看了System.Collections.SortedList,但(除非我错了)这似乎是按键引用元素,因为它实现了IDictionary我可以使用什么集合来维护我的对象,使它们保持排序状态,并且可以按索引顺序遍历它们?

【问题讨论】:

  • 试试SortedSet 但注意它不允许重复。
  • @AdamHouldsworth 不认为它与“性能”有关
  • 我个人会在算法的使用点订购它。维护一个标志来说明它是否需要根据自上次排序后是否有添加来进行排序,然后让调用者有责任在运行算法之前进行排序。归根结底,排序的实现是特定于该算法的,因此我认为将责任转移给它没有问题。
  • @AlexanderKozlov 我知道,但我想了解为什么在此算法之前不能使用像var sorted = list.OrderBy(_ =&gt; _).ToArray() 这样简单的东西。如果不知道发生了多少插入、列表有多大、在排序之间或 w/e 之间调用该算法的频率,我们就无法建议一个实现。
  • 这是您必须决定最佳折衷方案的事情之一。如果您想要一个排序列表,具有索引访问和插入(可能删除)项目的能力,同时保持排序顺序,那么至少其中一个操作最终会变慢,以便让其他操作更快。您需要弄清楚用法会是什么样子。你会插入很多吗?多读书?随机阅读还是按顺序阅读?名单有多大?等等。

标签: c# .net performance sorting icomparable


【解决方案1】:

我更喜欢使用SortedSet&lt;T&gt;,它是一棵二叉树,其中键和值是同一个对象。这再次意味着添加/删除/查找是对数的 - O(log n) - 但您可以获得按顺序迭代项目的能力。要使此集合生效,键入 T 必须实现 IComparable&lt;T&gt; 或者您需要提供外部 IComparer&lt;T&gt;

【讨论】:

  • 你打算如何从 SortedSet 中取出第 n 项?
  • 您可以按其顺序迭代。这可能是请求者需要的。现在为了让它更难,第 n 个元素由于其后备存储而无法被拾取。但是,您可以使用(我还没有对此进行任何性能测试)Enumerable.ElementAt().
  • 我看不出GetHashCode()SortedSet&lt;&gt; 有什么关系。它仅使用CompareTo(或更准确地说是比较器的Compare 方法)返回的int 值。
  • @JeppeStigNielsen,SortedSet SortedDictionary 的后备存储是一棵二叉树,其中键和值是相同的对象。如果我错了,请纠正我。
  • 那里的二叉树不会使用GetHashCode()?
【解决方案2】:

(根据提问者的要求从评论中提升)

如果您可以忍受毫无意义的“价值观”,只需在不使用价值部分的地方使用SortedList&lt;Bar, object&gt;

在 O(n) 时间内添加 yourSortedList.Add(yourBar, null)(列表必须在插入点之后“向上”移动所有条目)。使用 yourSortedList.Keys[i] 在 O(1) 时间内检索 ith 条目。

请参阅SortedList&lt;,&gt;.Keys property documentation 以获得上述描述正确的一些“证据”。请注意,SortedList&lt;,&gt; 实际上由一个“列表”组成(即长度为Capacity 的数组,必要时可以被更大的数组替换)。这与我认为是二叉搜索树的SortedDictionary&lt;,&gt; 不同。

但是请注意:您的SortedList&lt;,&gt; 中不能有重复项,因此列表中的两个成员不允许CompareTo 彼此返回值为零。

【讨论】:

    【解决方案3】:

    为什么不直接使用List.Insert
    插入是 O(n),可让您在特定索引处插入。
    n + n 仍然是 O(n)

    public AddBar(Bar bar)
    {
        int index = 0;    
        foreach (bar b in AllBar)
        {
           if(bar.Duration < b.Duration)
             break;
           index++;
        }
        AllBars.Insert(index, bar);
    }
    

    所以你有排序和 O(1) 索引
    花费 O(n) 添加
    当前的 Add in 也是 O(n)

    NlogN 中的 SortedList,然后您没有索引,因为键是 Duration 并且键不是唯一的

    一个 SortedSet 插入是 LogN 但一个 ToList 是 O(n) 所以你仍然是 O(n)

    调用列表上的Sort方法是NlogN

    这回答了以下问题: 我可以使用什么集合来维护我的对象,使它们保持排序状态,并且可以按索引顺序遍历它们?

    我认为你不会比 O(n) 加法更好。
    谁曾否决过它,那么什么是更好的解决方案?

    【讨论】:

    • 每次插入都是 O(N)
    • @LasseV.Karlsen 是的,我非常清楚地指出插入是 O(n)。 List.Add 是 O(n)。你投票给我了吗?它使用 O(1) 索引访问进行排序。您对上述问题有更好的解决方案吗?
    猜你喜欢
    • 1970-01-01
    • 2011-12-18
    • 2011-04-11
    • 1970-01-01
    • 2011-06-02
    • 2021-05-05
    • 1970-01-01
    • 2021-05-19
    • 1970-01-01
    相关资源
    最近更新 更多