Как получить ближайший элемент к моему ключу из SortedDictionary?
В настоящее время я использую двоичный поиск по SortedList<T,U>
для определенного числа,и если он не существует, я получаю ближайший ключ-элемент с нижней границей.
Я видел, что это было довольно медленно в вставка данных несортированный что я делаю много.
есть ли способ сделать что-то подобное с SortedDictionary
, или я должен просто придерживаться моего SortedList
?
1 ответов
SortedList<K, V>
очень медленно при вставке данных по мере сдвига <=N
элементы во внутреннем массиве при каждом добавлении нового элемента. Сложность сложения -O(N)
. Тем не менее он поддерживает двоичный поиск, который позволяет найти точный элемент или его соседей в O(log N)
.
сбалансированное двоичное дерево-лучшая структура данных для решения вашей проблемы. Вы сможете выполнять следующие операции с логарифмической сложностью:
- добавить элемент
O(log N)
противO(N)
наSortedList<K, V>
- удалить элемент
O(log N)
- поиск элемента или его ближайшего в
O(log N)
поиск элемента или его ближайшей нижней границы в двоичном дереве прост:
- идите вертикально через дерево от корня до ребенка, чтобы найти свой ключ. Если key
- если вы нашли ключ, вернуть
- если ключ не найден, ближайший слева родитель будет тем, кого вы ищете (ближайшая нижняя граница)
- если нет родителей, просто взять последний посещенный узел, это минимальный узел в дереве.
существует много статей, описывающих, как реализовать двоичное дерево. Тем не менее, я собираюсь повторно использовать коллекцию .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;
}