Как получить ближайший элемент к моему ключу из SortedDictionary?

В настоящее время я использую двоичный поиск по SortedList<T,U> для определенного числа,и если он не существует, я получаю ближайший ключ-элемент с нижней границей.

Я видел, что это было довольно медленно в вставка данных несортированный что я делаю много.

есть ли способ сделать что-то подобное с SortedDictionary, или я должен просто придерживаться моего SortedList?

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. если нет родителей, просто взять последний посещенный узел, это минимальный узел в дереве.

существует много статей, описывающих, как реализовать двоичное дерево. Тем не менее, я собираюсь повторно использовать коллекцию .NET Framework, используя своего рода hack :)

теперь, я собираюсь представить вам 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<K, V> в поиске ближайшей и так же быстро, как SortedSet<T> в дополнение.

насчет SortedDictionary<K, V>? Это почти то же самое как SortedSet<T> кроме одного: каждый ключ имеет значение. Надеюсь, вы сможете сделать то же самое с SortedDictionary<K, V>.

*декомпилированный SortedSet<T>.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;
}