Если каждый набор поддерживает запись о том, какие другие наборы существуют, и у вас есть в общей сложности наборов, вы можете легко превратить любую структуру данных для коллекции ( например, двоичные деревья поиска и т. Д. ) В ту, где вы можете получить элемент пересечения двух множеств во времени O ( log s ) .с > 0O ( журналс )
Каждый набор должен иметь уникальный идентификатор из некоторого полностью упорядоченного набора. Если вы явно дадите имена своим наборам тогда идентификатор может быть просто индексом.S1, S2, ...
Вы должны реализовать «реестр» наборов; структура данных, которая поддерживает коллекцию всех наборов, которые вы определили. Реестр должен быть реализован в виде структуры данных дерева поиска, чтобы обеспечить легкий поиск ( например, если вы хотите удалить набор) и обход наборов по линейному времени.
Каждый набор также поддерживает «индекс» каждого из других наборов - не их копию , а скорее структуру данных, которая индексируется метками других наборов. Этот индекс будет использоваться для поддержания, для каждого множества S к , бинарное дерево поиска всех элементов S J ∩ S K . (Два набора S j и S k совместно используют одну копию этого дерева поиска.)SJSКSJ∩ SКSJSК
инициализация
Инициализация набора состоит из O ( 1 ) операций по инициализации дерева его элементов, O ( s ) операций при инициализации (копировании из реестра) индекса для набора T и O ( s log s ) операции при обходе реестра, чтобы добавить T в индексы каждого из других наборов S j . В индексе T мы создаем деревья поиска, представляющие T ∩ S j = ∅T= ∅O ( 1 )O ( s )TO ( s log)с )TSJTT∩ SJ= ∅для других множеств ; мы копируем тот же указатель для индекса 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 nx ∈ VTO ( журналNT)NT= | T|ИксS1, S2, ... где n = | V | это размер юниверса (или самого большого набора S j ), а s это количество наборов в реестре. Для каждого множества S J такоечто х ∈ S J ,также вставка х в индекс для множества S J ∩ T . Для каждого такого набора S j для поиска S j требуется O ( log s + log n T ) времени
O ( журналNS1+ журналNS2+ ⋯ ) ⊆ O ( журнал sн ) ,
n = | В|SJsSJx ∈ SJИксSJ∩ TSJO ( журналs + logNT)SJв индексе
и вставить
x в
S j ∩ T ; во всех наборах
S 1 , S 2 , … это занимает время
O ( s log s + s log n T ) . Если мы предположим, что число множеств
S j намного меньше, чем размер юниверса
V (то есть, если мы предположим, что
s ≪ n ), общее время для вставки элемента будет равно
O ( s log n )TИксSJ∩ TS1, S2, ...O ( s log)журнал s + sNT)SJВs ≪ nO ( s log)н ),
Если вы не допускаете дубликаты в наборах, мы можем сэкономить время в том случае, когда уже за счет отказа от тестирования членства и вставок для других наборов Т . «Вставка» в случае, если x уже присутствует, занимает только время O ( log n T ) .x ∈ STИксO ( журналNT)
Тестирование пересечения
Индекс каждого набора поддерживается точно, чтобы позволить быструю оценку того, пересекаются ли два набора и S k . Для набора S j , просто проверив его индекс для набора S k , мы можем не только определить во времени O ( log s ) , пересекает ли S j S k , но мы также можем извлечь двоичное дерево, содержащее весь набор S j ∩ S k .SJSКSJSКO ( журналс )SJSКSJ∩ SК
Удаление элемента
Чтобы удалить элемент из множества T , мы удаляем его не только из дерева поиска для самого T , но и с каждого из пересечений S j ∩ T для множеств S j в его индексе. Это занимает время O ( s log n T ) , где n T = | T | ,ИксTTSJ∩ TSJO ( s log)NT)NT= | T|
Установить удаление
Из-за затрат на поиск в реестре, если у вас много наборов, может быть желательно удалить наборы, когда они больше не нужны. Обходя весь реестр, мы можем удалить из индекса всех других наборов S j за время O ( s n T ) , в котором преобладает стоимость удаления дерева поиска, представляющего S j ∩ T, для каждого из других наборов S j где n T = | T | ,SSJO ( с нT)SJ∩ TSJNT= | T|
замечания
Если вы предполагаете только реализовать постоянное количество наборов, то приведенное выше время выполнения уменьшится до:
инициализация: O ( 1 )
вставка элемента: O ( журналн )
проверка пересечения (и поиск пересечения): O ( 1 )
удаление элемента: O ( журналNT)
установить удаление: O(nS)
где - размер наибольшего набора в реестре, а n T = | T | для набора T, над которым вы работаете.nnT=|T|T
Если вы ожидаете иметь наборы , где V - ваша вселенная, вам может потребоваться другая структура данных, если вы хотите, чтобы эти операции работали в сублинейном времени. Тем не менее, если у вас есть пары наборов, пересечение которых, как вы знаете, вы никогда не будете тестировать, вы можете уменьшить размер индекса для наборов (не включая наборы, пересечение которых вы будете тестировать) или использовать более одного реестра ( по одному на каждую коллекцию множеств, пересечение которых вы можете проверить). На самом деле, реестр полезен только в том случае, если вам нужен централизованный контроль за тем, чтобы каждая пара наборов имела записи друг друга в индексе: в некоторых случаях это может быть полезно при инициализации набора S ad hoc.O(|V|)VS просто записатькаждое новое множество входит в индексы других множеств чье пересечение с S вас интересует.TS