Структура данных для пересечения множества?

21

Существует ли какая-либо структура данных, которая поддерживает набор множеств (конечного наземного множества), поддерживающий следующие операции? Любое сублинейное время работы будет оценено?

  1. Инициировать пустой набор.
  2. Добавить элемент в набор.
  3. Учитывая два набора, сообщают, пересекаются ли они.
Давэй Хуан
источник
1
Это очень общий вопрос, потому что любая структура данных может поддерживать эти операции с конечной областью. Не могли бы вы быть более конкретным? Например. Какая сложность вам нужна, чем вы готовы пожертвовать, чтобы получить набор операций и т. Д.
Bartosz Przybylski

Ответы:

13

Если каждый набор поддерживает запись о том, какие другие наборы существуют, и у вас есть в общей сложности наборов, вы можете легко превратить любую структуру данных для коллекции ( например, двоичные деревья поиска и т. Д. ) В ту, где вы можете получить элемент пересечения двух множеств во времени O ( log s ) .s>0O(logs)

  • Каждый набор должен иметь уникальный идентификатор из некоторого полностью упорядоченного набора. Если вы явно дадите имена своим наборам тогда идентификатор может быть просто индексом.S1,S2,

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

  • Каждый набор также поддерживает «индекс» каждого из других наборов - не их копию , а скорее структуру данных, которая индексируется метками других наборов. Этот индекс будет использоваться для поддержания, для каждого множества S к , бинарное дерево поиска всех элементов S JS K . (Два набора S j и S k совместно используют одну копию этого дерева поиска.)SjSkSjSkSjSk

инициализация

Инициализация набора состоит из O ( 1 ) операций по инициализации дерева его элементов, O ( s ) операций при инициализации (копировании из реестра) индекса для набора T и O ( s log s ) операции при обходе реестра, чтобы добавить T в индексы каждого из других наборов S j . В индексе T мы создаем деревья поиска, представляющие T S j = T=O(1)O(s)TO(slogs)TSjTTSj=для других множеств ; мы копируем тот же указатель для индекса S j .SjSj

Добавление элемента в набор T

Добавление некоторого к множеству T обычно занимает время O ( log n T ) , где n T = | T | , Мы также проверяем членство x в каждом из других наборов S 1 , S 2 , , что занимает время O ( log n S 1 + log n S 2 + ) O ( s log nxVTO(lognT)nT=|T|xS1,S2, где n = | V | это размер юниверса (или самого большого набора S j ), а s это количество наборов в реестре. Для каждого множества S J такоечто х S J ,также вставка х в индекс для множества S JT . Для каждого такого набора S j для поиска S j требуется O ( log s + log n T ) времени

O(lognS1+lognS2+)O(slogn),
n=|V|SjsSjxSjxSjTSjO(logs+lognT)Sjв индексе и вставить x в S jT ; во всех наборах S 1 , S 2 , это занимает время O ( s log s + s log n T ) . Если мы предположим, что число множеств S j намного меньше, чем размер юниверса V (то есть, если мы предположим, что s n ), общее время для вставки элемента будет равно O ( s log n )TxSjTS1,S2,O(slogs+slognT)SjVsnO(slogn),

Если вы не допускаете дубликаты в наборах, мы можем сэкономить время в том случае, когда уже за счет отказа от тестирования членства и вставок для других наборов Т . «Вставка» в случае, если x уже присутствует, занимает только время O ( log n T ) .xSTxO(lognT)

Тестирование пересечения

Индекс каждого набора поддерживается точно, чтобы позволить быструю оценку того, пересекаются ли два набора и S k . Для набора S j , просто проверив его индекс для набора S k , мы можем не только определить во времени O ( log s ) , пересекает ли S j S k , но мы также можем извлечь двоичное дерево, содержащее весь набор S jS k .SjSkSjSkO(logs)SjSkSjSk

Удаление элемента

Чтобы удалить элемент из множества T , мы удаляем его не только из дерева поиска для самого T , но и с каждого из пересечений S jT для множеств S j в его индексе. Это занимает время O ( s log n T ) , где n T = | T | ,xTTSjTSjO(slognT)nT=|T|

Установить удаление

Из-за затрат на поиск в реестре, если у вас много наборов, может быть желательно удалить наборы, когда они больше не нужны. Обходя весь реестр, мы можем удалить из индекса всех других наборов S j за время O ( s n T ) , в котором преобладает стоимость удаления дерева поиска, представляющего S jT, для каждого из других наборов S j где n T = | T | ,SSjO(snT)SjTSjnT=|T|

замечания

Если вы предполагаете только реализовать постоянное количество наборов, то приведенное выше время выполнения уменьшится до:

  • инициализация: O(1)

  • вставка элемента: O(logn)

  • проверка пересечения (и поиск пересечения): O(1)

  • удаление элемента: O(lognT)

  • установить удаление: O(nS)

где - размер наибольшего набора в реестре, а n T = | T | для набора T, над которым вы работаете.nnT=|T|T

Если вы ожидаете иметь наборы , где V - ваша вселенная, вам может потребоваться другая структура данных, если вы хотите, чтобы эти операции работали в сублинейном времени. Тем не менее, если у вас есть пары наборов, пересечение которых, как вы знаете, вы никогда не будете тестировать, вы можете уменьшить размер индекса для наборов (не включая наборы, пересечение которых вы будете тестировать) или использовать более одного реестра ( по одному на каждую коллекцию множеств, пересечение которых вы можете проверить). На самом деле, реестр полезен только в том случае, если вам нужен централизованный контроль за тем, чтобы каждая пара наборов имела записи друг друга в индексе: в некоторых случаях это может быть полезно при инициализации набора S ad hoc.O(|V|)VS просто записатькаждое новое множество входит в индексы других множеств чье пересечение с S вас интересует.TS

Ниль де Бодрап
источник
6

Существуют структуры данных, которые позволяют вам делать это менее чем за линейное время, даже для входных данных в худшем случае. См. Http://research.microsoft.com/pubs/173795/vldb11intersection.pdf (и ссылки на документы там).

Если ваши два набора S и T имеют большое пересечение, и у вас есть словарь для S, поиск элементов T в случайном порядке должен быстро дать вам общий элемент. Наиболее сложный случай, когда размер пересечения равен 0 или 1.

Расмус Паг
источник
3

Обычно ваш язык программирования поддерживает структуру данных с уникальными элементами. В целом существует три популярных подхода: деревья, хэши и битовые маски. Элементы дерева должны быть сопоставимы, элементы Hash должны быть хешируемыми, а элементы Bitmask должны иметь какой-либо способ преобразования в целые числа.

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

Хеш-набор будет поддерживать вставку в амортизированном O (1 * h), где 'h' - время выполнения алгоритма хеширования, и проверку пересечения в худшем случае O (n).

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

Карл Дамгаард Асмуссен
источник
2
Это был бы достойный ответ о переполнении стека , но здесь мы хотели бы получить некоторые подробности о том, как и почему это работает.
Рафаэль
3

Если ваш случай допускает ложные положительные ответы, я бы использовал Bloom Filter с одной хэш-функцией.

Вы можете реализовать это следующим образом:

Init пустой набор

  • Bnn

Добавить элемент в набор.

  • B[hash(element)]=1

Учитывая два набора (B1, B2), сообщите, пересекаются ли они.

  • B1 AND B2 = 0

сложность

  • nO(1)
Гриша Вайнтрауб
источник