【问题标题】:How to get the closest item to my key from a SortedDictionary?如何从 SortedDictionary 中获取离我的键最近的项目?
【发布时间】:2012-07-26 08:10:52
【问题描述】:

目前,我在 SortedList<T,U> 上使用二进制搜索来查找特定数字,如果它不存在,我将获得最接近的下限键项。

我看到 inserting unsorted data 的速度相当慢,我经常这样做。

有没有办法用SortedDictionary 做类似的事情,还是我应该坚持我的SortedList

【问题讨论】:

标签: c# .net performance collections


【解决方案1】:

SortedList<K, V> 在插入数据时非常慢,因为每次添加新元素时它都会移动内部数组中的<=N 元素。加法的复杂度为O(N)。不过它支持二分查找,可以在O(log N) 中找到准确的元素或其邻居。

平衡二叉树是解决问题的最佳数据结构。 您将能够执行以下具有对数复杂度的操作:

  1. O(log N)O(N)SortedList<K, V> 中添加项目
  2. 删除O(log N) 中的项目
  3. O(log N)中搜索项目或其最近的项目

在二叉树中寻找元素或其最近的下界很简单:

  1. 从根到子垂直遍历树以找到您的密钥。如果key
  2. 如果你找到了钥匙,返回
  3. 如果未找到密钥,则最近的左父级将是您正在寻找的那个(最近的下限)
  4. 如果没有左父节点,只取最后访问的节点,它是树中最小的节点。

有很多文章描述了如何实现二叉树。不过,我将使用一种 hack 重用 .NET Framework 集合:)

现在,我将向您展示SortedSet<T>,它本身就是红黑树。它有一个缺点,它无法快速找到最近的节点。但是我们知道在树中搜索的算法(在 1. 中有描述),它是在SortedSet<T>.Contains 方法中实现的(在底部反编译*)。现在我们可以使用我们的自定义比较器在遍历期间捕获从根到最后访问的节点的所有节点。之后我们可以使用上面的算法找到最近的下界节点:

public class LowerBoundSortedSet<T> : SortedSet<T> {

    private ComparerDecorator<T> _comparerDecorator;

    private class ComparerDecorator<T> : IComparer<T> {

        private IComparer<T> _comparer;

        public T LowerBound { get; private set; }

        private bool _reset = true;

        public void Reset()
        {
            _reset = true;
        }

        public ComparerDecorator(IComparer<T> comparer)
        {
            _comparer = comparer;
        }

        public int Compare(T x, T y)
        {
            int num = _comparer.Compare(x, y);
            if (_reset)
            {
                LowerBound = y;
            }
            if (num >= 0)
            {
                LowerBound = y;
                _reset = false;
            }
            return num;
        }
    }

    public LowerBoundSortedSet()
        : this(Comparer<T>.Default) {}

    public LowerBoundSortedSet(IComparer<T> comparer)
        : base(new ComparerDecorator<T>(comparer)) {
        _comparerDecorator = (ComparerDecorator<T>)this.Comparer;
    }

    public T FindLowerBound(T key)
    {
        _comparerDecorator.Reset();
        this.Contains<T>(key);
        return _comparerDecorator.LowerBound;
    }
}

你看到找到最近的节点并不比通常的搜索多,即O(log N)。因此,这是解决您问题的最快方法。这个集合在寻找最近的时候和SortedList&lt;K, V&gt;一样快,另外和SortedSet&lt;T&gt;一样快。

SortedDictionary&lt;K, V&gt; 呢?它与SortedSet&lt;T&gt; 几乎相同,除了一件事:每个键都有一个值。我希望你也能对SortedDictionary&lt;K, V&gt; 做同样的事情。

*反编译的SortedSet&lt;T&gt;.Contains方法:

public virtual bool Contains(T item)
{
  return this.FindNode(item) != null;
}

internal virtual SortedSet<T>.Node FindNode(T item)
{
  for (SortedSet<T>.Node node = this.root; node != null; {
    int num;
    node = num < 0 ? node.Left : node.Right;
  }
  )
  {
    num = this.comparer.Compare(item, node.Item);
    if (num == 0)
      return node;
  }
  return (SortedSet<T>.Node) null;
}

【讨论】:

  • 你有关于在排序列表中插入复杂性的链接吗?
  • SortedDictionary 对于未排序的数据具有更快的插入和删除操作,O(log n) 相对于 SortedList 的 O(n)。 msdn.microsoft.com/en-us/library/ms132319.aspx
  • 我不知道SortedSet,每天学习新东西。这也很完美(与 sortedlist 相比,未排序的插入速度有惊人的提升),谢谢。
  • 太棒了!我想我必须自己动手。非常聪明的解决方案。
  • 非常聪明。太糟糕了,它依赖于实现,微软可以随时打破这一点。
猜你喜欢
  • 2016-07-19
  • 1970-01-01
  • 2011-06-10
  • 1970-01-01
  • 2016-10-17
  • 1970-01-01
  • 2013-12-26
  • 2011-01-26
相关资源
最近更新 更多