Поддержание эффективного упорядочения, в котором вы можете вставить элементы «между» любые два других элемента в упорядочении?

cs.stackexchange https://cs.stackexchange.com/questions/14708

Вопрос

Представьте, что у меня есть заказ на кучу таких элементов:

enter image description here

Где стрелка $ x LeateRrow y $ означает $ x <y $. Это также транзитивно: $ left (x <y right) wedge left (y <z rugh) подразумевает left (x <z ruight) $.

Для эффективного ответа на запросы, такие как $ a stackrel {?} {<} D $, требуется какая -то маркировка или структура данных. Например, вы можете числа узлы слева направо, и, таким образом, вы можете просто сделать целочисленное сравнение, чтобы ответить на запрос: $ a stackrel {?} {<} D Повышение 1 <4 подразумевает t $. Это выглядело бы примерно так:

enter image description here

Где номер является заказом, а письмо - просто имя.

Но что, если вам нужно вставить элементы »между« двумя другими элементами в упорядочении, как так:

enter image description here

enter image description here

enter image description here

Как вы можете поддерживать такой заказ? С простой нумерацией вы сталкиваетесь с проблемой, что нет целых чисел »между« 2,3 $ ».

Это было полезно?

Решение

Это известно как Проблема «обслуживание заказа». Анкет Существует относительно простое решение с использованием времени $ O (1) $ амортизированного как для запросов, так и для вставки. Теперь, под «относительно простым», я имею в виду, что вы должны понимать некоторые строительные блоки, но, как только вы получите их, остальное не сложно увидеть.

http://courses.csail.mit.edu/6.851/spring12/lectures/l08.html

Основная идея-это двухуровневая структура данных. Верхний уровень похож на решение AVL Tree Slaw Slaw, но

  • Узлы непосредственно маркированы битовыми строками длины $ O ( lg n) $ с заказом, который соответствует их заказу на дереве. Сравнение, таким образом, занимает постоянное время

  • Используется дерево с меньшим количеством вращений, чем дерево AVL, например, дерево отпущения отпущения или дерево, сбалансированное весом, поэтому перераспределения случаются реже.

Нижний уровень - листья дерева. Этот уровень использует одинаковую длину метки, $ o ( lg n) $, но содержит только $ o ( lg n) $ предметы в каждом листе в простом связанном списке. Это дает вам достаточно дополнительных кусочков, чтобы агрессивно Relabel.

Листья становятся слишком большими или слишком малы ) Амортизированный, это всего лишь $ O (1) $.

Существуют гораздо более сложные структуры для выполнения обновлений в $ O (1) $ худшее время.

Другие советы

Вместо простой нумерации вы можете распространять числа по большему (постоянному) диапазону, такому как целочисленное минимум и максимум целого числа ЦП. Тогда вы можете продолжать размещать цифры «между», усредняя два окружающих числа. Если цифры становятся слишком многолюдными (например, у вас есть два смежных целых числа, и между ними нет числа), вы можете сделать одноразовое перенуметирование всего упорядочения, равномерно перераспределяя числа по всему диапазону.

Конечно, вы можете столкнуться с ограничением, что используются все числа в диапазоне большой константы. Во-первых, это, как правило, не является проблемой, так как целый ровный размер на машине достаточно велик, так что если у вас есть больше элементов, он, вероятно, не вписывался в память. Но если это проблема, вы можете просто перенервно их переносительно с большим целочисленным диапазоном.

Если порядок ввода не является патологическим, этот метод может амортизировать перенимерование.

Отвечая на запросы

Простое целочисленное сравнение может ответить на запрос $ left (x stackrel {?} {<} Y right) $.

Время запроса было бы очень быстро ($ mathcal {o} Left (1 right) $), если использование целых чисел машины, так как это простое целочисленное сравнение. Использование большего диапазона потребует больших целых чисел, а сравнение потребует $ mathcal {o} Left ( log {| Integer |} right) $.

Вставка

Во -первых, вы будете поддерживать связанный список упорядочения, продемонстрированный в вопросе. Вставка здесь, учитывая узлы для размещения нового элемента между ними, будет $ mathcal {o} left (1 right) $.

Маркировка нового элемента обычно будет быстрой $ mathcal {o} Left (1 right) $, потому что вы легко рассчитываете новое число, усредняя окружающие числа. Время от времени у вас могут исходить числа «между», что запускает $ mathcal {o} left (n right) $ Процедура перенумерования.

Избегая перенумерования

Вы можете использовать поплавки вместо целых чисел, поэтому, когда вы получаете два «соседних» целых числа, они Можно быть усредненным. Таким образом, вы можете избежать переносимости при столкновении с двумя целыми поплавками: просто разделите их пополам. Тем не менее, в конечном итоге тип плавающей запятой закончится, и два «адасированных» поплавка не смогут усреднены (среднее значение окружающих чисел, вероятно, будет равным одному из окружающих чисел).

Аналогичным образом вы можете использовать целое число «десятичное место», где вы поддерживаете два целых числа для элемента; Один для числа и один для десятичного. Таким образом, вы можете избежать переносимости. Тем не менее, десятичное целое число в конечном итоге переполнится.

Использование списка целых чисел или битов для каждой этикетки может полностью избежать перенюмеровки; Это в основном эквивалентно использованию десятичных чисел с неограниченной длиной. Сравнение будет проведено лексико, и время сравнения увеличится до длины задействованных списков. Однако это может разоблачить маркировку; Некоторые этикетки могут потребовать только одного целого числа (без десятичных знаков), другим может иметь список длинной длины (длинные десятичные десятки). Это проблема, и перенаселение может помочь здесь, перераспределяя нумерацию (здесь списки чисел) равномерно по выбранному диапазону (диапазон здесь, возможно, означает длину списков), чтобы после такого перенюмеров Анкет


Этот метод фактически используется в Этот алгоритм (реализация,Соответствующая структура данных); В ходе алгоритма необходимо соблюдать произвольный заказ, и автор использует целые числа и перенюмерезует это для достижения.


Попытка придерживаться чисел, делает ваше ключевое пространство несколько ограниченным. Вместо этого можно использовать строки переменной длины, используя логику сравнения «<» <«ab» <"b". Еще две проблемы еще предстоит решить A. Ключи могут стать произвольно длинными B. Сравнение длинных ключей может стать дорогостоящим

Вы можете сохранить беспризорное дерево без ключа или аналогичное.

Это будет работать следующим образом: дерево поддерживает упорядочение на узлах, как обычно, дерево AVL обычно, но вместо ключа определяющего, где узел «лежит, нет ключей, и вы должны явно вставить узлы» после «Еще один узел (или, другими словами» между «двумя узлами), где« после »означает, что он поступает после его прохождения дерева в порядке. Таким образом, дерево будет поддерживать порядок для вас естественным образом, и оно также уравновесится, из -за встроенных вращений AVL. Это будет держать все равномерно распределять автоматически.

Вставка

В дополнение к регулярной вставке в список, как показано в вопросе, вы будете поддерживать отдельное дерево AVL. Вставка в сам список - $ mathcal {o} Left (1 right) $, так как у вас есть узлы «до» и «после».

Время вставки в дерево - $ mathcal {o} left ( log {n} right) $, так же, как вставка в дерево AVL. Вставка включает в себя использование ссылки на узел, который вы хотите вставить после, и вы просто вставляете новый узел в левый левый узел правого ребенка; Это место «следующее» при упорядочении дерева (оно следующее в обходе в порядке). Затем сделайте типичные вращения AVL, чтобы перебалансировать дерево. Вы можете сделать аналогичную операцию для «Вставки перед»; Это полезно, когда вам нужно вставить что -то в начало списка, а узел нет «узел».

Отвечая на запросы

Чтобы ответить на запросы $ Left (x StackRel {?} {<} Y right) $, вы просто найдете все предки $ x $ и $ y $ на дереве, и вы анализируете место, где в Дерево предки расходятся; Тот, который расходится с «левой», - это меньший из двух.

Эта процедура принимает $ mathcal {o} left ( log {n} right) $ время, поднимающееся на дерево к корню и получение списков предков. Хотя это правда, что это кажется медленнее, чем целочисленное сравнение, правда в том, что это то же самое; Только это целочисленное сравнение на ЦП ограничено большой постоянной, чтобы сделать его $ mathcal {o} left (1 right) $; Если вы переполняете эту константу, вы должны поддерживать несколько целых чисел ($ mathcal {o} Left ( log {n} right) $ integers фактически) и сделайте одинаковую $ mathcal {o} left ( log {log { n} right) $ сравнения. В качестве альтернативы, вы можете «связать» высоту дерева постоянной суммой и «обманывать» так же, как машина делает с целыми числами: теперь запросы будут казаться $ mathcal {o} left (1 right) $.

Демонстрация операции вставки

Чтобы продемонстрировать, вы можете вставить некоторые элементы с их заказом из списка в вопросе:

Шаг 1

Начните с $ D $

Список:

list step 1

Дерево:

tree step 1

Шаг 2

Вставьте $ C $, $ yampleSet <C <D $.

Список:

list step 2

Дерево:

tree step 2

Обратите внимание, что вы явно ставите $ C $ "до" $ D $, не потому, что буква C - до D, а потому, что $ C <D $ в списке.

Шаг 3

Вставьте $ a $, $ pellyset <a <c $.

Список:

list step 3

Дерево:

tree step 3 before rotation

Avl rowtation:

tree step 3 after rotation

Шаг 4

Вставьте $ B $, $ A <B <C $.

Список:

list step 4

Дерево:

tree step 4

Вращения не требуется.

Шаг 5

Вставьте $ e $, $ d <e < EmptySet $

Список:

list step 5

Дерево:

tree step 5

Шаг 6

Вставьте $ f $, $ b <f <c $

Мы просто поставим это правильно »после« $ b $ на дереве, в данном случае, просто прикрепив его к праву $ b $; Таким образом, $ f $ теперь сразу после $ b $ в проходе дерева в порядке.

Список:

list step 6

Дерево:

tree step 6 before rotation

Avl rowtation:

tree step 6 after rotation

Сравнение операции демонстрации

$ A stackrel {?} {<} F $

ancestors(A) = [C,B]
ancestors(F) = [C,B]
last_common_ancestor = B
B.left = A
B.right = F
... A < F #left is less than right

$ D stackrel {?} {<} F $

ancestors(D) = [C]
ancestors(F) = [C,B]
last_common_ancestor = C
C.left = D
C.right = B #next ancestor for F is to the right
... D < F #left is less than right

$ B stackrel {?} {<} A $

ancestors(B) = [C]
ancestors(A) = [B,C]
last_common_ancestor = B
B.left = A
... A < B #left is always less than parent

Графики источники

Лицензировано под: CC-BY-SA с атрибуция
Не связан с cs.stackexchange
scroll top