Оптимальная структура данных для специального словаря - PullRequest
10 голосов
/ 23 сентября 2011

Какая структура данных является наилучшей с точки зрения вычислительной сложности для реализации словаря элементов (key, val), который должен поддерживать только следующие команды:

  1. Insert(key)- добавляет элемент (ключ, значение) с val = 1
  2. Increment(key) - увеличивает значение существующего значения (ключ, значение)
  3. Find(key) - возвращает значение (ключ, значение))
  4. Select(part_of_key) - возвращает список всех элементов (key, val), если strstr(key,part_of_key)!=NULL в виде нового словаря того же типа (без выделения новой памяти, если это возможно);например, если словарь: {(красный, 3), (синий, 4), (зеленый, 1)}, то выберите (re) = {(красный, 3), (зеленый, 1)}
  5. Max(i) - возвращает элемент, который имеет i-е максимальное значение среди всех элементов;например, если словарь имеет вид {(красный, 3), (синий, 4), (зеленый, 1)}, то Max (1) = синий, Max (2) = красный, Max (3) = зеленый

Ключи являются строками, а значения являются натуральными числами.Ожидается, что количество элементов в словаре будет очень большим.

Я думаю, что это должен быть синтез двух разных структур данных.Но должно ли это быть хеш-таблица + двоичное дерево или trie + отсортированный массив или что-то еще?

Ответы [ 5 ]

6 голосов
/ 23 сентября 2011

Комбинация сбалансированного дерева (такого как красно-черное дерево) и дерева суффиксов (или массива суффиксов).

  • Сбалансированное дерево: операции 1, 2 (реализовано как удаление + вставка), 3 и 5.
  • Дерево суффиксов: операция 4.

ПРИМЕЧАНИЕ. Хэш-таблица не сможет эффективно поддерживать операцию 5.

Я думаю, вам будет трудно реализовать дерево суффиксов. Возможно, вы могли бы использовать Марк Нельсона для реализации алгоритма Укконена на языке C ++, но он имеет утечки памяти и, по сути, является одноэлементным, поэтому вам придется очистить его перед тем, как приступить к работе. Даже после того, как вы исправите это, вам нужно будет настроить его так, чтобы он работал с вашей «другой» структурой данных (которая в моем предложении является сбалансированным деревом) вместо одной большой простой строки.

Если вы выполняете операцию 1 чаще, чем операцию 4, и / или вы можете жить с линейной операцией 4, я рекомендую пропустить всю сложность с деревом суффиксов и просто пройти по структуре данных линейно.

4 голосов
/ 23 сентября 2011

Для первых трех операций, хэш-таблица может быть хорошей идеей.

Для 4-й операции (выберите часть ключа) вам, возможно, придется написать хеш-функцию по-другому. Да, хеш-функция, которая использовалась для поиска / вычисления хеш-значения по заданному ключу. Так как вы хотите поддерживать частичное совпадение, а ваш ключ - строка, вы можете использовать Suffix-tree или trie.

Для 5-й операции (элемент ih max) вы можете захотеть сохранить кучу или отсортированный связанный список (или список пропуска), который взаимодействует с хэш-таблицей.

Вам также придется просмотреть различные варианты использования и определить, какая операция должна быть оптимизирована. Например: если у вас много запросов на операцию part_of_key, вы должны использовать структуру типа Suffix-tree / LC-trie, и это даст хорошие результаты. Однако ваша операция поиска может быть не быстрой, так как она потребует O (logN) вместо постоянного поиска.

Подводя итог, вам нужно интегрировать хеш-таблицу, кучу и дерево суффиксов для выполнения всех операций.

3 голосов
/ 23 сентября 2011

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

1 голос
/ 26 сентября 2011

Я думаю, что эффективная база данных будет модифицирована trie , с двунаправленными ссылками [может идти от листьев к корню и восстанавливать слово], и каждый конечный узел будет иметь дополнительное «значение» поле.
Вам также понадобится мультикарта [т.е. карта, в которой каждое значение представляет собой набор адресов].
Ключи будут расположены в виде дерева, а набор значений будет основан на хэше. [в джаве, это должно быть что-то вроде TreeMap<Integer,HashSet<Node>>]

Псевдокод: [ну, очень псевдо… он просто показывает общие идеи для каждой операции].

Insert(key):
1. Insert a word into the trie
2. add the value `1` to the terminating node.
3. add the terminating field to the multimap [i.e. map.add(1,terminating_node_address)]
Increment(key):
1. read the word in the trie, and store the value from the terminating node as temp.
2. delete the entree (temp,terminating_node_address) from the multimap.
3. insert the entree (temp+1,terminating_node_address) to the multimap.
4. increase the value of the terminating node's address by 1.
Find(key):
1. find the terminating node for the key in the trie, return its value.
Select(part_of_key):
1. go to the last node you can reach with part_of_key.
2. from this node: do BFS and retrieve all possible words [store them in an empty set you will later return].
Max(i):
1. find the i'th biggest element key in the multimap.
2. choose an arbitrary address, and return get the relevant terminating word node.
3. build the string by following the uplinks to the root, and return it.

Сложность:
пусть n будет количеством строк, k будет максимальным значением и S длиной строки.
Вставка: O (S) [Три вставки O (S)]. Наименьший элемент (1) в карте может быть кэширован, и, следовательно, доступ к нему можно получить в точке O (1).
Инкремент: O (S + logk) : найти строку в дереве O (S), найти соответствующий ключ O (logk), удалить элемент из хеш-набора O ( 1), нахождение следующего элемента в дереве также O (logk) [фактически может быть O (1), для skip-list based map], и вставка значения - O (1). Обратите внимание, что, связав узел с соответствующим ключом на карте, сложность может быть даже улучшена до O (S) , поскольку поиск фактически не требуется.
Найти: O (S) : просто найти в дереве и вернуть значение.
Выберите: O (S * n) : в худшем случае вам нужно извлечь все элементы, что заставляет вас повторять весь набор, чтобы найти все слова.
Макс .: O (logk + S) : найти ключ в отсортированной карте, восстановить слово.

РЕДАКТИРОВАТЬ: Обратите внимание, что здесь, я предполагаю, для find (i) вы хотите, чтобы i * i отдельный самый большой элемент. Если это не так, вам нужно будет немного изменить набор и использовать мультикарту, которая позволяет дублировать ключи [вместо набора в качестве значения]. Это сделает все операции на основе дерева O (logn) вместо O (logk), по-прежнему эффективными, на мой взгляд ...

1 голос
/ 23 сентября 2011

Я думаю, что лучше всего использовать три ярлыка.

1-3 уже поддерживаются деревом как можно быстрее (длина ключа).

Для 4 я бы добавил дополнительную таблицу из 256 элементов ко всем узлам trie, которые могут быть введены из этого символа. Таким образом, я могу выполнять быстрый поиск частей строки, даже не создавая явно и не сохраняя суффиксы, как в дереве суффиксов.

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

Сложность памяти не меняется от дерева с этими дополнениями, так как она ограничена числом узлов дерева. Однако время вставки может измениться из-за неэффективной сортировки связанного списка.

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

...