Решение Эндрю МакКинлея - это настоящее «истинное» функциональное решение для реального списка пропусков, но оно имеет недостаток.Вы платите логарифмическое время, чтобы получить доступ к элементу, но теперь мутация за пределами головного элемента становится безнадежной.Ответ, который вы хотите, закопан в бесчисленных копиях путей!
Можем ли мы добиться большего успеха?
Отчасти проблема в том, что существует множество путей от -infinity к вашему элементу.
Но если вы продумываете алгоритмы поиска в ски-листе, вы никогда не будете использовать этот факт.
Мы можем рассматривать каждый узел в дереве как имеющий предпочтительную ссылку, самую верхнюю ссылку на негослева, который в некотором смысле можно считать «владельцем» этой записи.
Теперь мы можем рассмотреть понятие «палец» в структуре данных, которая является функциональной техникой, которая позволяет вамсосредоточиться на одном конкретном элементе и указать путь к корню.
Теперь мы можем начать с простого пропускающего списка
-inf-------------------> 16
-inf ------> 8 --------> 16
-inf -> 4 -> 8 -> 12 --> 16
Расширение его по уровню:
-inf-------------------> 16
| |
v v
-inf ------> 8 --------> 16
| | |
v v v
-inf -> 4 -> 8 -> 12 --> 16
Уберите все указатели, кроме предпочитаемых:
-inf-------------------> 16
| |
v v
-inf ------> 8 16
| | |
v v v
-inf -> 4 8 -> 12 16
Затем вы можете переместить «палец» в положение 8, отслеживая все указатели, которые вам нужно будет щелкнуть, чтобы попасть туда.
-inf ------------------> 16
^ |
| v
-inf <------ 8 16
| | |
v v v
-inf -> 4 8 -> 12 16
Оттуда можночтобы удалить 8, нажмите пальцем куда-нибудь еще, и вы можете продолжать перемещаться по структуре пальцем.
При таком взгляде мы видим, что привилегированные пути в списке пропусков образуют связующее дерево!
Перемещение пальца на 1 шаг является операцией O (1), если у вас есть только привилегированные указатели в дереве и вы используете такие узкие узлы, как этот.Если бы вы использовали толстые узлы, то движение пальца влево / вправо было бы потенциально более дорогим.
Все операции остаются O (log n), и вы можете использовать рандомизированную структуру пропускающего списка или детерминированную структуру как обычно.
Тем не менее, когда мы разбиваем пропускающий список на понятие предпочтительного пути, мы получаем, что пропускающий список - это просто дерево с некоторыми избыточными неприоритетными ссылками, которые нам не нужны для вставки / поиска /удалить, чтобы длина каждого из этих путей в верхнем правом углу была O (log n) либо с высокой вероятностью, либо с гарантией, в зависимости от стратегии обновления.
Даже без пальца вы можете поддерживать O (log n)) ожидаемое время для вставки / удаления / обновления в дереве с помощью этой формы.
Теперь ключевое слово в вашем вопросе, которое не имеет смысла, - "одновременное".Чисто функциональная структура данных не имеет понятия мутации на месте.Вы всегда производите новую вещь.В некотором смысле одновременные функциональные обновления просты.Каждый получает свой ответ!Они просто не могут видеть друг друга.