Известна ли реализация индексированного связанного списка? - PullRequest
14 голосов
/ 11 ноября 2009

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

Известен ли способ эффективной реализации списка «лучших из двух миров», который обеспечивает произвольный доступ по индексу и O (1) вставке / удалению, как связанный список?

Я предвижу два возможных исхода: «Нет, это невозможно, по следующим очевидным причинам ...» или «Э-э, да, это было сделано; см. Здесь, здесь и здесь».

Ответы [ 8 ]

14 голосов
/ 11 ноября 2009

Я не верю, что будет возможно получить O(1) как для вставки, так и для поиска. В ту минуту, когда вы добавляете массив (или даже причудливые, разделяемые векторы), вставка становится O(n).

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

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

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

Решением, которое я однажды реализовал, был 2D-список. Под этим я подразумеваю:

        +-----------+    +-----------+    +-----------+
List -> | count = 7 | -> | Count = 4 | -> | Count = 6 | -> null
        +-----------+    +-----------+    +-----------+
              |                |                |
              V                V                V
        +-----------+    +-----------+    +-----------+
        |    [0]    |    |    [7]    |    |   [11]    |
        +-----------+    +-----------+    +-----------+
              |                |                |
              V                V                V
        +-----------+    +-----------+    +-----------+
        |    [1]    |    |    [8]    |    |   [12]    |
        +-----------+    +-----------+    +-----------+
              |                |                |
              :                :                :
              :                :                :
              |                |                |
              V                V                V
        +-----------+    +-----------+    +-----------+
        |    [6]    |    |   [10]    |    |   [16]    |
        +-----------+    +-----------+    +-----------+
              |                |                |
              V                V                V
             null             null             null

В то время как это делало вставку и поиск O (n), баланс был правильным. В чистом решении для массивов поиск равен O(1), а вставка - O(n). Для чистого связанного списка вставка - это O(1) (как только вы найдете точку вставки, конечно, сама операция O(n)), а поиск - O(n).

2D список O(n) для обоих, но с меньшим коэффициентом. Если вы хотите вставить, вы можете найти правильный столбец, просто изучив первый ряд каждого столбца. Затем вы пересекаете сам столбец, ища правильный ряд. Затем элемент вставляется и счет для этого столбца увеличивается. Аналогично для удалений, хотя в этом случае счет уменьшается, и весь столбец удаляется, когда его счет достигает нуля.

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

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

4 голосов
/ 11 ноября 2009

Если вы считаете, что O (log N) == O (1) ,
проверить:

2 голосов
/ 11 ноября 2009

Когда я реализовывал связанный список в классе, я думал об оптимизации времени доступа, сохраняя 3 дополнительных поля: узел в середине списка, индекс самого последнего доступного узла и самый последний доступный узел.

Чтобы получить узел по индексу, я сначала посмотрел бы все доступные пути для достижения узла по данному индексу, а затем выбрал самый дешевый способ сделать это. Пути были бы просто:

  1. Переход от начала к нужному индексу
  2. Переход от последнего доступного узла к нужному индексу (вперед)
  3. Переход от последнего доступного узла к нужному индексу (назад)
  4. Переход от среднего узла к нужному индексу (вперед)
  5. Переход от среднего узла к нужному индексу (назад)
  6. Переход от конца узла к нужному индексу

Путь с наименьшей разницей в нашем желаемом индексе и нашем начальном индексе будет самым дешевым вариантом. Если ни один узел еще не был доступен, недавно доступный узел мог бы быть установлен как средний узел. Конечно, при четном количестве элементов фактической середины нет, поэтому я бы просто выбрал этаж n / 2.

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

1 голос
/ 12 ноября 2009

Как насчет хеш-таблицы? Вы получаете O (1) произвольный доступ по ключу и O (1) вставку / удаление. Подвох в том, что записи не упорядочены.

Для эффективной реализации упорядоченных последовательностей проверьте finger tree . Они дают вам O (1) доступ к head и last и O (log n) произвольный доступ к внутренним узлам. Вставьте или удалите с любого конца в O (1). Примечательно, что обращение пальца занимает постоянное время.

1 голос
/ 11 ноября 2009

У вас все в порядке с этим.

Связанные списки - это O (1) вставка / удаление, потому что операция, которую вы выполняете для вставки или удаления чего-либо, это просто переключение пары указателей (один или два на объекте, который вы вставляете, и один или два на один или два другие объекты). Очевидно, это не зависит от размера списка.

Список пропуска даст вам O (logn) поиск, но так как вы поддерживаете индекс, это также означает вставку / удаление O (logn), потому что этот индекс необходимо поддерживать в актуальном состоянии.

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

Имеете ли вы в виду конкретную проблему, которую пытаетесь решить?

Например, вы можете получить O (n) вставку, удаление и поиск, если можете гарантировать идеальный хеш. Но вам нужно знать кое-что о ваших данных заранее, чтобы это работало.

0 голосов
/ 11 ноября 2009

Хотя я не думаю, что вы можете получить целочисленную индексацию, хеш-таблица поддержки может работать, если вы используете 'ссылочные' типы.

0 голосов
/ 11 ноября 2009

Java LinkedList имеет O (n) доступ для индексированных запросов. LinkedList расширяет AbstractSequentialList, чтобы показать, что он не предлагает O (1), индексированный получает.

Я бы посоветовал взглянуть на Apache TreeList . Он предлагает O (log n) вставок / удалений и O (1) индексированных поисков.

0 голосов
/ 11 ноября 2009

Я не знаю точного BigO при вставке (поскольку это будет зависеть от размера выборки и роста), но Java 1001 * сразу придет на ум.

http://java.sun.com/j2se/1.5.0/docs/api/java/util/LinkedList.html

РЕДАКТИРОВАТЬ: да, по-видимому, под ним все еще истинный связанный список, и в качестве таких индексированных получает может быть O (n / 2), который, конечно, формально O (n).

Вы всегда можете потратить целую кучу места и реализовать реализацию List, которая содержит параллельный связанный список и массив с отложенной вставкой / удалением.

...