Struktura danych dla skrzyżowania zestawu?

21

Czy jest jakaś struktura danych, która utrzymuje kolekcję zbioru (zbioru skończonego) obsługującą następujące operacje? Czy doceniony zostanie jakikolwiek podliniowy czas pracy?

  1. Zainicjuj pusty zestaw.
  2. Dodaj element do zestawu.
  3. Biorąc pod uwagę dwa zestawy, zgłoś, czy się przecinają.
Dawei Huang
źródło
1
To jest bardzo ogólne pytanie, ponieważ każda struktura danych może obsługiwać te operacje ze skończoną domeną. Czy możesz być trochę bardziej szczegółowy? Na przykład. Jakiej złożoności potrzebujesz, co chcesz poświęcić, aby rozpocząć operacje itp.
Bartosz Przybylski

Odpowiedzi:

13

Jeśli każdy zestaw zachowuje zapis istniejących zestawów i masz w sumie s>0 zestawów, możesz łatwo przekształcić dowolną strukturę danych dla kolekcji ( np. Drzewa wyszukiwania binarnego itp. ) W jedną, w której możesz pobrać element przecięcia dwóch zbiorów w czasie O(logs) .

  • Każdy zestaw powinien mieć unikalny identyfikator z całkowicie uporządkowanego zestawu. Jeśli wyraźnie określisz swoje zestawy S1,S2, identyfikatorem może być po prostu indeks.

  • Powinieneś wdrożyć „rejestr” zestawów; struktura danych, która utrzymuje kolekcję wszystkich zestawów, które zdefiniowałeś. Rejestr powinien zostać zaimplementowany jako struktura danych drzewa wyszukiwania, aby umożliwić łatwe wyszukiwanie ( np.  Jeśli chcesz usunąć zestaw) i przechodzenie zestawów w czasie liniowym.

  • Każdy zestaw utrzymuje również „indeks” z każdym z pozostałych zestawów - nie kopiowanie z nich, ale raczej strukturę danych, która jest indeksowana przez etykietach innych zestawach. Wskaźnik ten zostanie wykorzystany do utrzymania, dla każdego zestawu S k , wyszukiwanie binarne drzewo wszystkich elementów S jSjSk . (Dwa zestawy S j i S k współużytkują jedną kopię tego drzewa wyszukiwania.)SjSkSjSk

Inicjalizacja

Inicjalizacja zestawu składa się z O ( 1 ) operacje zainicjować drzewo jej elementów, O ( s ) operacje jak zainicjować (kopiowanie z rejestru) wskaźnik dla zbioru T i O ( s dziennika s ) podczas przechodzenia przez rejestr, aby dodać T do indeksów każdego z pozostałych zestawów S j . W indeksie T tworzymy drzewa wyszukiwania reprezentujące T S j = T=O(1)O(s)TO(slogs)TSjTTSj=Dla innych zestawów ; kopiujemy ten sam wskaźnik dla indeksu S j .SjSj

Dodanie elementu do zestawu T

Dodanie pewnej do zbioru T zajmuje jak zwykle czas O ( log n T ) , gdzie n T = | T | . Testujemy również członkostwo x w każdym innym zestawie S 1 , S 2 , , co zajmuje czas O ( log n S 1 + log n S 2 + ) O ( s log nxVTO(lognT)nT=|T|xS1,S2, gdzie n = | V |

O(lognS1+lognS2+)O(slogn),
n=|V|jest wielkość wszechświata (lub największy zbiór ) i s oznacza liczbę zestawów w rejestrze. Dla każdego zestawu S j takie, że x S j także wkładka X do indeksu do zbioru S jT . Dla każdego zestawu takiego S j ma to O ( LOG s + log n T ) czasu, aby wyszukać S jSjsSjxSjxSjTSjO(logs+lognT)Sj indeks i wstaw x w S jT ; we wszystkich zestawach S 1 , S 2 , TxSjTS1,S2, zajmuje to czas . Jeśli założymy, że liczba zestawów S j jest znacznie mniejszy niż rozmiar wszechświata V, (to znaczy, jeżeli założymy, Ś « n ) Całkowity czas wkładka jest wówczas O ( y log n )O(slogs+slognT)SjVsnO(slogn).

Jeśli nie dopuszczać duplikaty w zestawach, możemy zaoszczędzić czas w przypadku, gdy już o rezygnacji test członkostwa i wstawki dla innych zestawów T . „Wstawienie” w przypadku, gdy x jest już obecny, zajmuje tylko czas O ( log n T ) .xSTxO(lognT)

Testowanie skrzyżowań

Indeks każdego zestawu jest utrzymywana właśnie w celu umożliwienia szybkiej oceny, czy dwa zestawy i S k przecinać. Przez zestaw S j , po prostu przez sprawdzenie jego indeks dla zestaw S k , nie możemy określić tylko w czasie O ( log ów ) czy S J przecina S k , ale możemy także pobierać binarne drzewo zawierającą cały zestaw S jS k .SjSkSjSkO(logs)SjSkSjSk

Usuwanie elementu

Aby usunąć element ze zbioru T , usuwamy go nie tylko z drzewa wyszukiwania samego T , ale z każdego przecięcia S jT dla zbiorów S j w jego indeksie. Wymaga to czasu O ( s log n T ) , gdziexTTSjTSjO(slognT).nT=|T|

Ustaw usuwanie

Ze względu na narzut związany z przeszukiwaniem rejestru, jeśli masz wiele zestawów, pożądane może być ich usunięcie, gdy nie będą już potrzebne. Przez przejeżdżające cały rejestr, możemy usuwania z wykaz wszystkich innych zestawów S j w czasie O ( y n T ) , zdominowany przez koszt usuwania drzewa wyszukiwania odpowiadający S jT dla każdego z innych zestawów S j , gdzie n T = | T | .SSjO(snT)SjTSjnT=|T|

Uwagi

Jeśli spodziewasz się zaimplementować tylko stałą liczbę zestawów, powyższe czasy działania zmniejszają się do:

  • inicjalizacja: O(1)

  • wstawienie elementu: O(logn)

  • testowanie skrzyżowania (i odzyskanie skrzyżowania): O(1)

  • usunięcie elementu: O(lognT)

  • usunięcie zestawu: O(nS)

gdzie jest rozmiarem największego zestawu w rejestrze, a n T = | T | dla zestawu T, na którym pracujesz.nnT=|T|T

Jeśli spodziewasz się mieć zestawów , gdzie V jest twoim wszechświatem, możesz potrzebować innej struktury danych, jeśli chcesz, aby operacje te działały w czasie sublinearnym. Jeśli jednak masz pary zestawów, o których przecięciu wiesz, że nigdy nie będziesz testować, możesz być w stanie zmniejszyć rozmiar indeksu dla tych zestawów (nie włączając żadnych zestawów, których przecięcie przetestujesz) lub użyć więcej niż jednego rejestru ( jeden dla każdej kolekcji zestawów, których przecięcie można przetestować). W rzeczywistości rejestr jest przydatny tylko wtedy, gdy chcesz scentralizowanej kontroli, aby upewnić się, że każda para zestawów ma zapisy w indeksie: w niektórych przypadkach może być praktyczne, przy inicjalizacji zestawu S , po prostu rejestrowaćO(|V|)VSad hoc każdy nowy zestaw do indeksów innych zbiorów których przecięciem z S. jesteś zainteresowany.TS

Niel de Beaudrap
źródło
6

Istnieją struktury danych, które pozwalają to zrobić w czasie krótszym niż liniowy, nawet w przypadku najgorszych danych wejściowych. Zobacz http://research.microsoft.com/pubs/173795/vldb11intersection.pdf (i odnośniki do dokumentów w nim zawarte).

Jeśli twoje dwa zestawy S i T mają duże przecięcie i masz słownik dla S, wyszukiwanie elementów T w losowej kolejności powinno szybko dać ci wspólny element. Najtrudniejszy jest przypadek, gdy rozmiar przecięcia wynosi 0 lub 1.

Rasmus Pagh
źródło
3

Zwykle wybrany język programowania obsługuje strukturę danych z unikalnymi elementami. Zasadniczo istnieją trzy popularne podejścia: drzewa, skróty i maski bitowe. Elementy drzewa muszą być porównywalne, elementy skrótu muszą być mieszalne, a elementy maski bitowej muszą mieć jakiś sposób konwersji na liczby całkowite.

Zestaw drzew będzie obsługiwał wstawianie do O (log n) i testowanie skrzyżowań w najgorszym przypadku O (n log n).

Zestaw skrótów będzie obsługiwał wstawianie do amortyzacji O (1 * h), gdzie „h” oznacza czas działania algorytmu haszującego, oraz test przecięcia w najgorszym przypadku O (n).

Zestawy bitmask zwykle nie są używane jak zestawy drzewiaste i mieszające.

Karl Damgaard Asmussen
źródło
2
To byłaby przyzwoita odpowiedź na przepełnienie stosu , ale tutaj chcielibyśmy trochę szczegółów na temat tego, jak i dlaczego to działa.
Raphael
3

Jeśli twoja sprawa pozwala na fałszywie pozytywne odpowiedzi, użyłbym filtra Bloom z pojedynczą funkcją skrótu.

Możesz to zaimplementować w następujący sposób:

Zainicjuj pusty zestaw

  • b = tablica bitów n bitów, wszystkie ustawione na 0. (n należy wybrać zgodnie z liczbą możliwych elementów)

Dodaj element do zestawu.

  • b[hzash(milmimmint)]=1

Biorąc pod uwagę dwa zestawy (B1, B2), podaj, czy się przecinają.

  • sprawdź czy b1 AND B2 = 0

Complexity

  • If n is not too big, all operations are O(1).
Grisha Weintraub
źródło