Вопрос

Пропустить списки (Pugh, 1990) предоставить отсортированные словари с операциями логарифмического времени, как поисковые деревья, но Списки пропусков гораздо более поддаются одновременному обновлениям.

Можно ли создать эффективный чисто функциональный параллельный список SKIP? Если нет, возможно ли создать какой-либо эффективный чисто функциональный параллельный сортированный словарь?

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

Решение

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

В частности, пропущенные списки состоят из структур, которые выглядят так:

NODE1 ---------------------> NODE2 ---------...
  |                           |
  V                           V
NODE1a --> NODE1b ---------> NODE2a --> NODE2b --> NODE2c --- ...

Теперь, если у вас есть обновление, скажем, удаляет NODE2b или NODE1b, вы можете позаботиться об этом очень локально: вы просто указываете 2a к 2c или 1a к 2a соответственно, и вы закончили. К сожалению, потому что узел листьев всей точки к другому, это не хорошая структура для функционального (неизменного) обновления.

Таким образом, древовидные структуры лучше для неизменности (поскольку ущерб всегда локально ограничен - просто узел, который вы заботитесь о том, и его прямые родители до корня дерева).

Одновременные обновления не работают хорошо с неизменными структурами данных. Если вы думаете об этом, любое функциональное решение имеет обновление A в виде f(A). Отказ Если вы хотите два обновления, один дан f и один дан g, ты в значительной степени должен сделать f(g(A)) или g(f(A)), или вы должны перехватить запросы и создать новую операцию h = f,g Что вы можете применить все в одном Go (или вам нужно сделать различные другие очень умные вещи).

Тем не менее, одновременное чтение работает фантастически с неизменными структурами данных, поскольку вам гарантированно не имеют никакого изменения государства. Если вы не предполагаете, что вы можете иметь цикл чтения / записи, которая решает, прежде чем любая другая запись может прерываться, то вам никогда не придется заблокировать чтение.

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

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

Решение Andrew Mckinlay является настоящим «истинным» функциональным решением для реального пропуска здесь, но у него есть недостаток. Вы платите логарифмическое время для доступа к элементу, но теперь мутация за пределами элемента головы становится безнадежной. Ответ, который вы хотите, хватает в бесчисленных случаях!

Можем ли мы сделать лучше?

Часть проблемы Существуют, что есть несколько путей от -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) Ожидаемое время на вставку / удаление / обновление в дереве с этой формой.

Теперь ключевое слово в вашем вопросе, который не имеет смысла, является «одновременно». Чисто функциональная структура данных не имеет понятия мутации в месте. Вы всегда производите новую вещь. В некотором смысле одновременные функциональные обновления легко. Все получают свой ответ! Они просто не могут видеть друг друга.

Не пропустить список, но, похоже, соответствует описанию проблемы: настойчивые черные черные деревья Clojure (см. Persistenttreeemap.java). Источник содержит это уведомление:

/**
 * Persistent Red Black Tree
 * Note that instances of this class are constant values
 * i.e. add/remove etc return new values
 * <p/>
 * See Okasaki, Kahrs, Larsen et al
 */

Эти деревья поддерживают порядок элементов и являются «настойчивыми» в том смысле, в котором богатый Hickey использует слово (неизменно и способно поддерживать свои гарантии производительности, поскольку обновленные версии).

Если вы хотите играть с ними, вы можете построить экземпляры в коде Clojure с функцией sorted-map.

Если вам нужно только минусы на передней панели списка пропусков, то должно быть возможно сделать постоянную неизменной версию.

Преимущество этого списка пропуска будет «случайным» доступом. Например, вы можете получить доступ к N'-элементу быстрее, чем вы можете в обычном одностороннем списке.

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