【问题标题】:Sorted data structure排序数据结构
【发布时间】:2014-03-19 16:10:04
【问题描述】:

我正在寻找支持以下功能的 .Net 4.0 中的排序键控数据结构:

  1. O(n log n)时间创建结构
  2. O(log n)时间按键获取项目
  3. O(log n) 时间内找到集合中大于或等于给定参数的最小项(我们很可能使用double 对其进行键入)
  4. O(log n) 中找到小于给定参数的最大项目
  5. 对于集合中的给定项目,获取下一个和上一个项目
  6. 键在集合中必须是唯一的

我快速浏览了SortedDictionarySortedList,但它们似乎没有提供上面列表中的 (3) 和 (4)。 SortedDictionary 好像不支持(5), 我不确定SortedList 是否支持 (6)。

不幸的是,我们仅限于.Net4

【问题讨论】:

  • 听起来你需要自己构建这样的数据结构。
  • 我不知道他们是否有这样的结构,但你看过C5 Collections吗?那里有很多好处,他们也有一个 NuGet 包PM> Install-Package C5
  • 收藏只是Double的key吗?找到较小的最大项目,您的意思是在键上搜索?
  • @Blam,是的,这是正确的。 double 将是关键,但我们仍然有一些与之关联的值。
  • 我知道这对你来说可能不是新闻,但最大的项目更小,最小的项目更大是困难的部分。不能用哈希桶做到这一点。对于 O(log n),我认为这需要像索引搜索一样。你有没有像 Redis.io 这样的内存数据库?

标签: c# .net dictionary sortedlist sorteddictionary


【解决方案1】:

您将需要编写自己的集合。从概念上讲,您想要的似乎是一个基于树的结构,这就是 SortedDictionary 的实现方式。底层结构具有完成所有这些任务的潜力,.NET 实现并没有将它们全部公开,也没有提供对实现这些目标的足够底层工具的访问,迫使您从头开始。

幸运的是,构建这种基于树的结构是初级程序员的一项常见任务,因此您会发现大量的开源 3rd 方实现可供查看,它们可以实现您的目标,也可以作为起点。如果需要,您也可以考虑 grabbing the source of SortedDictionary 并重新编译您自己的版本。

【讨论】:

    【解决方案2】:

    我为您创建了一个排序字典。我希望它能满足您的需求。

    public class MyDictionary<TKey, TItem> : IDictionary<TKey, TItem>
        where TKey : IComparable<TKey>
        where TItem : IEquatable<TItem>
    {
        private readonly List<TKey> keys;
        private readonly List<TItem> items;
        private readonly ReadOnlyCollection<TKey> roKeys;
        private readonly ReadOnlyCollection<TItem> roItems;
    
        public MyDictionary()
        {
            keys = new List<TKey>();
            items = new List<TItem>();
            roKeys = new ReadOnlyCollection<TKey>(keys);
            roItems = new ReadOnlyCollection<TItem>(items);
        }
    
        public MyDictionary(int capacity)
        {
            keys = new List<TKey>(capacity);
            items = new List<TItem>(capacity);
            roKeys = new ReadOnlyCollection<TKey>(keys);
            roItems = new ReadOnlyCollection<TItem>(items);
        }
    
        public MyDictionary(TKey[] keys, TItem[] items)
        {
            if (keys == null)
                throw new ArgumentNullException("keys");
            if (items == null)
                throw new ArgumentNullException("items");
            if (keys.Length != items.Length)
                throw new ArgumentException("Arrays lengths must be equal.");
    
            TKey[] keysCopy = new TKey[keys.Length];
            keys.CopyTo(keysCopy, 0);
            TItem[] itemsCopy = new TItem[items.Length];
            items.CopyTo(itemsCopy, 0);
    
            Array.Sort(keysCopy, itemsCopy);
    
            this.keys = new List<TKey>(keysCopy);
            this.items = new List<TItem>(itemsCopy);
            roKeys = new ReadOnlyCollection<TKey>(keys);
            roItems = new ReadOnlyCollection<TItem>(items);
        }
    
        public int BinarySearch(TKey key)
        {
            return keys.BinarySearch(key);
        }
    
        public bool ContainsKey(TKey key)
        {
            return BinarySearch(key) >= 0;
        }
    
        public void Add(TKey key, TItem item)
        {
            int index = BinarySearch(key);
            if (index >= 0)
                throw new ArgumentException(String.Format("The key {0} already exists.", key), "key");
            index = ~index;
            keys.Insert(index, key);
            items.Insert(index, item);
        }
    
        public void Add(KeyValuePair<TKey, TItem> item)
        {
            Add(item.Key, item.Value);
        }
    
        public bool Remove(TKey key)
        {
            int index = BinarySearch(key);
            if (index < 0)
                return false;
            keys.RemoveAt(index);
            items.RemoveAt(index);
            return true;
        }
    
        public bool Remove(KeyValuePair<TKey, TItem> item)
        {
            int index = BinarySearch(item.Key);
            if (index < 0)
                return false;
            index = ~index;
            keys.RemoveAt(index);
            items.RemoveAt(index);
            return true;
        }
    
        public bool Contains(KeyValuePair<TKey, TItem> item)
        {
            int index = BinarySearch(item.Key);
            if (index < 0)
                return false;
            index = ~index;
            return items[index].Equals(item.Value);
        }
    
        public bool TryGetValue(TKey key, out TItem value)
        {
            int index = BinarySearch(key);
            if (index < 0)
            {
                value = default(TItem);
                return false;
            }
            value = items[index];
            return true;
        }
    
        public TItem this[TKey key]
        {
            get
            {
                int index = BinarySearch(key);
                if (index < 0)
                    throw new ArgumentException(String.Format("The key {0} not found.", key), "key");
                return items[index];
            }
            set
            {
                int index = BinarySearch(key);
                if (index < 0)
                    throw new ArgumentException(String.Format("The key {0} not found.", key), "key");
                items[index] = value;
            }
        }
    
        public ICollection<TKey> Keys
        {
            get { return roKeys; }
        }
    
        public ICollection<TItem> Values
        {
            get { return roItems; }
        }
    
        public IEnumerator<KeyValuePair<TKey, TItem>> GetEnumerator()
        {
            return keys.Select((t, i) => new KeyValuePair<TKey, TItem>(t, items[i])).GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    
        public void Clear()
        {
            keys.Clear();
            items.Clear();
        }
    
        public void CopyTo(KeyValuePair<TKey, TItem>[] array, int arrayIndex)
        {
            Array.Copy(keys.Select((t, i) => new KeyValuePair<TKey, TItem>(t, items[i])).ToArray(), 0, array, arrayIndex, Count);
        }
    
        public int Count
        {
            get { return keys.Count; } 
        }
    
        public int Capacity
        {
            get { return keys.Capacity; }
            set
            {
                if (value < 0)
                    throw new ArgumentOutOfRangeException("value");
                keys.Capacity = value;
                items.Capacity = value;
            }
        }
    
        public bool IsReadOnly
        {
            get { return false; }
        }
    
        public int GetSmallerOrEqualIndex(TKey key)
        {
            int index = BinarySearch(key);
            if (index >= 0)
                return index;
            index = ~index;
            return index - 1;
        }
    
        public int GetGreaterOrEqualIndex(TKey key)
        {
            int index = BinarySearch(key);
            if (index >= 0)
                return index;
            index = ~index;
            return index;
        }
    
        public KeyValuePair<TKey, TItem> GetItem(int index)
        {
            return new KeyValuePair<TKey, TItem>(keys[index], items[index]);
        }
    }
    

    要求:

    1. 不满意(我正在努力)。目前,通过MyDictionary(TKey[] keys, TItem[] items) 构造函数进行初始化平均是 O(n log n) 操作,在最坏的情况下是 O(n ^ 2) 操作。添加单个项目是 O(n) 操作。
    2. 满意。
    3. 满意(GetGreaterOrEqualIndex 方法)。
    4. 满意(GetSmallerOrEqualIndex 方法)。
    5. 如果我正确理解“给定项目”,则不直接满足,但对项目的索引感到满意。
    6. 满意。

    【讨论】:

    • #1 不满意。向这个数据结构中添加 N 项是一个 O(n^2) 操作。
    • 为什么?添加 1 项是 O(log N) 操作。添加 N 个项目 - O(N log N)。我错过了什么?
    • 向列表中添加 1 项是 O(n) 操作,而不是 O(log(n)) 操作。找到添加它的索引是 O(log(n)),但是一旦你有了索引,实际上添加它是 O(n)。
    • MSDN:如果Count小于Capacity,这个方法是O(1)操作。但你是对的,我应该在答案中添加这一刻。
    • 只有在添加到末尾时才如此。您没有添加到列表的末尾。您正在添加到列表的中间,这是一个 O(n) 操作,即使在不需要新的缓冲区分配 时也是如此。