.NET中是否有SortedList<T>类?(不是SortedList<K,V>)

26

我需要按照对象内容(实际上是根据它们的一个属性,该属性不是键,并且可能在不同的对象之间重复)对一些对象进行排序。

.NET提供了两个类(SortedDictionarySortedList),并且两者都使用二叉树来实现。它们之间唯一的区别在于:

  • SortedList使用的内存比SortedDictionary少。
  • 对于未排序的数据,SortedDictionary具有更快的插入和删除操作,O(log n)而不是SortedList的O(n)。
  • 如果列表一次性从排序数据中填充,则SortedList比SortedDictionary更快。

我可以使用List,然后使用其Sort()方法与自定义实现的IComparer来达到想要的效果,但它不是时间效率,因为每次插入新对象时我都需要对整个List进行排序,而好的SortedList只需将项目插入到正确的位置即可。

我需要一个带有RefreshPosition(int index)方法的SortedList类,以仅移动更改(或插入)的对象,而不是每次对象内部发生更改时重新排序整个列表。

我是否忽略了一些明显的东西?


10
另一个让人大跌眼镜的.Net问题。它有一些很棒的东西,但我完全震惊于它们没有为此提供一个类,也没有其他一些你认为很常见的东西。 - mpen
1
.NET现在提供了一个SortedSet<T>,但它仍然不支持重复项。他们必须有某种意识形态问题,不支持带重复项的排序列表。 - Roman Starkov
可能是.NET中是否有排序集合类型?的重复问题。 - nawfal
5个回答

11
也许我有点慢,但是这不是最简单的实现方法吗?
class SortedList<T> : List<T>
{
    public new void Add(T item)
    {
        Insert(~BinarySearch(item), item);
    }
}

http://msdn.microsoft.com/en-us/library/w4e7fxsh.aspx


不幸的是,Add 方法无法被重写,所以我必须使用 new 方法,但当你需要使用 List<T> list = new SortedList<T>; 时,这并不是一种好的方式……因此,我重新构建了整个东西……

class SortedList<T> : IList<T>
{
    private List<T> list = new List<T>();

    public int IndexOf(T item)
    {
        var index = list.BinarySearch(item);
        return index < 0 ? -1 : index;
    }

    public void Insert(int index, T item)
    {
        throw new NotImplementedException("Cannot insert at index; must preserve order.");
    }

    public void RemoveAt(int index)
    {
        list.RemoveAt(index);
    }

    public T this[int index]
    {
        get
        {
            return list[index];
        }
        set
        {
            list.RemoveAt(index);
            this.Add(value);
        }
    }

    public void Add(T item)
    {
        list.Insert(~list.BinarySearch(item), item);
    }

    public void Clear()
    {
        list.Clear();
    }

    public bool Contains(T item)
    {
        return list.BinarySearch(item) >= 0;
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        list.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return list.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(T item)
    {
        var index = list.BinarySearch(item);
        if (index < 0) return false;
        list.RemoveAt(index);
        return true;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return list.GetEnumerator();
    }
}

也许像这样的“删除”函数更为合适……
    public bool Remove(T item)
    {
        var index = list.BinarySearch(item);
        if (index < 0) return false;
        while (((IComparable)item).CompareTo((IComparable)list[index]) == 0)
        {
            if (item == list[index])
            {
                list.RemoveAt(index);
                return true;
            }
            index++;
        }
        return false;
    }

假设项目可以进行相等比较但不相等...

6
谢谢上课。有一个修改建议:Add方法应该更像下面这样,以避免ArgumentOutOfRangeException异常: public virtual void Add(T item) { int index = list_.BinarySearch(item); list_.Insert(index >= 0 ? index : ~index, item); } - Suncat2000
@Mark,你不需要编写那个Remove方法。它基本上是O(n)的。相反,你可以将比较器传递给list.BinarySearch方法。它会给你正确的索引以进行删除。在你的情况下:index = list.BinarySearch(item, Comparer<T>.Default),然后list.RemoveAt(index)。这是O(n-index)。稍微提高了一点效率 :) Suncat是对的。我会在这里提供答案,介绍这两个更改。 - nawfal
1
@Mark 但实际上,删除该项是O(n),因为它是一个列表,所以虽然您可以在少于O(n)的时间内找到要删除的项,但“Remove”仍然是O(n)。 - Servy
1
@Mark 这就是为什么排序数据结构通常是基于树的数据结构,而不是基于数组的数据结构,使得SortedList(或这些自制变体)几乎从来不是任何给定任务的适当数据结构。如果您需要在不断变异的情况下对数据进行排序,或者您想要一个普通的List,并且在完成变异后只需偶尔进行排序(如Jon的答案中所讨论的那样),通常最好使用基于树的结构。 - Servy
@MDeSchaepmeester 是的,我认为你是对的。如果这对你有用,那么很容易将 IList 替换为 ICollection 并删除一些方法。 - mpen
显示剩余6条评论

3
我最终决定写下来:
class RealSortedList<T> : List<T>
    {
        public IComparer<T> comparer;

        public int SortItem(int index)
        {
            T item = this[index];
            this.RemoveAt(index);
            int goodposition=FindLocation(this[index], 0, this.Count);
            this.Insert(goodposition, item);
            return goodposition;
        }

        public int FindLocation(T item, int begin, int end)
        {
            if (begin==end)
                return begin;
            int middle = begin + end / 2;
            int comparisonvalue = comparer.Compare(item, this[middle]);
            if (comparisonvalue < 0)
                return FindLocation(item,begin, middle);
            else if (comparisonvalue > 0)
                return FindLocation(item,middle, end);
            else
                return middle;
        }
    }

1
继续吧,将它作为IList<T>的扩展,你知道你想要这样做! :) - Daniel Earwicker

2

请不要忘记,在由数组支持的列表中插入一个项目可能是一项昂贵的操作 - 插入一堆项目然后排序可能会更快,除非你确实需要在每个操作之后进行排序。

或者,您可以始终包装一个列表并使您的添加操作找到正确的位置并将其插入那里。


1
是的,你绝对是正确的,在大量插入的情况下,仅在插入后排序一次会更快! 不过,我认为一个UpdatePosition(int index, IComparer<T> comparer)方法会很有用,它假设列表已经使用comparer进行了排序,并相应地更新索引! - Brann

1
我之前通过编写一个扩展方法来解决这个问题,该方法在 IList 上执行二进制搜索,另一个方法进行插入。您可以查看 CLR 源代码中的正确实现,因为有一个内置版本仅适用于数组,只需将其调整为 IList 的扩展即可。
这是那种“应该已经在 BCL 中”的事情之一。

0
我需要一个SortedList类,其中包含RefreshPosition(int index)方法,以仅移动更改(或插入)的对象,而不是每次对象内部更改时重新排序整个列表。
当这样的更新使索引无效时,为什么要使用索引进行更新?实际上,我认为通过对象引用进行更新会更方便。您可以使用SortedList来完成此操作 - 只需记住,您的Key类型与从对象中提取可比数据的函数的返回类型相同。
class UpdateableSortedList<K,V> {
    private SortedList<K,V> list = new SortedList<K,V>();
    public delegate K ExtractKeyFunc(V v);
    private ExtractKeyFunc func;

    public UpdateableSortedList(ExtractKeyFunc f) { func = f; }

    public void Add(V v) {
        list[func(v)] = v;
    }
    public void Update(V v) {
        int i = list.IndexOfValue(v);
        if (i >= 0) {
            list.RemoveAt(i);
        }
        list[func(v)] = v;
    }
    public IEnumerable<T> Values { get { return list.Values; } }
}

我猜就是这样吧。


正如我在帖子中所述,不同的对象V可以具有相同的“可比值”K。 我认为SortedList不支持重复的键;或者我错了吗? - Brann
唉,我不应该在SO编辑器里花那么多时间。你知道解决方案:要么自己实现一个二叉树(以满足你的需求),要么重用框架类之一。如果框架类不起作用,那么将SortedList转换为MultiSortedList。 - Frank Krueger
是的,这就是正确的方式。太遗憾了,这个功能没有内置到 .net 框架中! - Brann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接