Szybsze łączenie struktur danych podobnych do pułapek o mniej więcej tym samym rozmiarze

16

Biorąc pod uwagę dwa drzewa AVL i T 2 oraz wartość t r taką, że x T 1 , y T 2 , x < t r < y , łatwo jest zbudować nowe drzewo AVL zawierające t r i wartości w T 1 i T 2 w czasie O ( 1 + | h ( T 1 ) - h ( TT1T2trxT1,yT2,x<tr<ytrT1T2 , gdzie h ( T ) oznacza wysokość drzewa T (o ile drzewa przechowują swoją wysokość).O(1+|h(T1)h(T2)|)h(T)T

Jest to również możliwe w przypadku drzew czerwono-czarnych i zakładam również wiele innych rodzajów drzew zrównoważonych.

Czy jest to możliwe w przypadku pułapek lub struktur danych podobnych do pułapek? Co jeśli pominiemy ?tr

Papier treaps w Algorytmice pokazuje, jak to zrobić w oczekiwany czas. Jeśli istnieje sposób na wykonanie O (1) oczekiwanego łączenia na pułapkach (lub strukturach podobnych do pułapek) o mniej więcej tym samym rozmiarze (lub priorytecie root), myślę, że można użyć sztuczki Kaplana i Tarjana podczas ładowania grzbiety w celu tworzenia pułapek (lub struktur danych podobnych do pułapek) z podwójnie logarytmicznym złączeniem.O(min(h(T1),h(T2)))

jbapple
źródło
Oto kod Haskella, który napisałem, pokazujący szybkie łączenie drzew AVL o mniej więcej tym samym rozmiarze: haskell.pastebin.com/nfGV8Ffz
jbapple
Wątpię, czy jest to możliwe, ponieważ wydaje się (bez dowodu), że oczekiwana głębokość nowego węzła (zawierająca wartość t_r) jest większa niż stała nawet w przypadku, gdy h (T_1) = h (T_2).
Tsuyoshi Ito
Tsuyoshi Ito: Zgadzam się, jeśli przypisujesz nowy węzeł priorytetowi w taki sam sposób, jak przypisujesz priorytety innym węzłom. Co się stanie, jeśli nadasz mu priorytet gwarantowany jako wyższy niż priorytet węzłów głównych? To niszczy charakter priorytetów IID, ale co, jeśli następnie oznaczysz inne priorytety jako przesunięte, w jakiś sposób, jak ścieżki w trwałych czerwono-czarnych drzewach zostaną oznaczone w punktach końcowych? A co jeśli ktoś przechowuje wartości tylko w liściach pułapki i wykonuje łączenie bez t_r?
jbapple
Węzły w pułapkach z n potomkami mają i pozostawiłem potomków z prawdopodobieństwem 1 / n. Może to przyczynić się do długich czasów scalania, nawet dla pułapek o jednakowej wielkości - wybranie nowego korzenia wymaga nawigacji do niego, co, ponieważ średnia głębokość drzewa to Theta (lg n), zajmuje również czas Theta (lg n). Co jeśli węzeł treap z n potomkami opuścił dzieci z prawdopodobieństwem (n wybierz i) / 2 ^ n, a wartości są przechowywane tylko w liściach, jak w drzewie B +. Następnie połączenie dwóch jednakowych rozmiarów redystrybuuje niewielką liczbę elementów z jednego drzewa do drugiego w oczekiwaniu.
jbapple 17.01.11
Jeśli moje obliczenia są poprawne, oczekiwana liczba redystrybuowanych elementów to Theta (sqrt n), co przy założeniu, że wszystko inne można wypracować (podobnie jak właściwość wyszukiwania palcem), nadal wymagałoby czasu Theta (lg n). Co powiesz na zastosowanie jeszcze ściślejszej dystrybucji?
jbapple 17.01.11

Odpowiedzi:

3

Nie, nie można tego zrobić za pomocą zwykłych pułapek, jeśli priorytety są losowe.

Dokładna roszczenie Zrobię to, że do wykonania takiego scalenia dwóch jednakowo wielkości treaps z przypadkowymi priorytetów wymaga aktualizacji wskaźniki w oczekiwaniu.Θ(logn)

Oto szkic wstępny:

Rozważ liczbę wskaźników, które musisz zmienić w oczekiwaniu na wykonanie operacji. Łatwiej jest udowodnić ograniczenie jeśli nie wstawimy t r, ale po prostu połączymy T 1 i T 2 . Rozważ prawy kręgosłup S 1 z T 1 i lewy kręgosłup S 2 z T 2 . Pokoloruj elementy S 1 na czerwono i S 2 na niebiesko. Zamów S 1S 2Θ(logn)trT1T2S1T1S2T2S1S2S1S2według priorytetu. Musimy zmieniać wskaźnik za każdym razem, gdy zmienia się kolor w tej sekwencji. Ponieważ oba kolce mają rozmiar z dużym prawdopodobieństwem, a priorytety są przypadkowe, to nie jest zbyt trudne, aby zobaczyć, że # zmian kolorów w sekwencji jest Θ ( log n ) . Musimy więc zaktualizować wskaźniki Θ ( log n ) dla scalenia (bez dodawania t r ).Θ(logn)Θ(logn)Θ(logn)tr

Teraz dodanie podczas scalania niewiele pomaga. Liczbę zmian wskaźnika w tym przypadku można ograniczyć w następujący sposób: Kolejność S 1S 2{ t r } według priorytetu. Usuń wszystko mniej niż t r w sekwencji. Wówczas # zmian kolorów w wynikowej sekwencji jest naszą dolną granicą. Ponieważ t r ma losowy priorytet, oczekiwana wysokość poddrzewa zakorzenionego w nim w końcowej pułapce wynosi O ( 1 ) , więc ma tylko węzły O ( 1 ) S 1trS1S2{tr}trtrO(1)O(1) z niższym priorytetem niż oczekiwany, dlategopodczas dodawania t r straciliśmy tylkozmiany wskaźnika O ( 1 ) w naszej dolnej granicy.S1S2O(1)tr

To powiedziawszy, prawdopodobnie istnieje sposób na uzyskanie struktury danych przypominającej pułapki, która pozwala na ciągłe łączenie oczekiwanego czasu.


źródło
Tak, szukam struktury danych przypominającej pułapki. Chociaż wspomniałem tyle samo w komentarzach i mojej nieistniejącej odpowiedzi, nie umieściłem tego w tytule ani pytaniu.
jbapple 14.01.11
Dziękuję za odpowiedź. Zmieniłem tytuł i tekst pytania, aby były mniej dwuznaczne.
jbapple 14.01.11
1

Aktualizacja: zobacz poniżej aktualizację dotyczącą nieprawidłowości tej operacji łączenia

Oto bardzo przybliżony szkic możliwego rozwiązania:

Myślę, że mogę rozwiązać ten problem, używając pewnego rodzaju losowo zrównoważonego drzewa B +. Podobnie jak pułapki, drzewa te mają unikalną reprezentację. W przeciwieństwie do pułapek przechowują niektóre klucze wiele razy. Można to naprawić za pomocą sztuczki z „Biased Search Trees” Benta i innych, polegającej na przechowywaniu każdego klucza tylko na najwyższym (tj. Najbliższym rootowi) poziomie, na którym się pojawia)

Drzewo dla uporządkowanego zestawu unikalnych wartości jest tworzone przez pierwsze skojarzenie każdej wartości ze strumieniem bitów, podobnie do sposobu, w jaki każda wartość w pułapce jest powiązana z priorytetem. Każdy węzeł w drzewie zawiera zarówno strumień klucza, jak i strumień bitów. Węzły inne niż liście zawierają ponadto liczbę naturalną wskazującą wysokość drzewa zakorzenionego w tym węźle. Wewnętrzne węzły mogą mieć dowolną niezerową liczbę dzieci. Podobnie jak drzewa B +, każda nie przecinająca się ścieżka od korzenia do liścia ma tę samą długość.

vkivki+1vvja1vja

Aby utworzyć drzewo z posortowanej listy kluczy ze skojarzonymi strumieniami bitów, najpierw zbierz klucze do ciągłych grup na podstawie pierwszego bitu w ich strumieniach. Dla każdej z tych grup utwórz element nadrzędny ze strumieniem klucza i bitów największego klucza w grupie, ale z pominięciem pierwszego bitu strumienia. Teraz wykonaj tę samą procedurę grupowania nowych rodziców, aby utworzyć dziadków. Kontynuuj, aż pozostanie tylko jeden węzeł; to jest korzeń drzewa.

Poniższa lista kluczy i (bitów) strumieni bitów jest reprezentowana przez drzewo poniżej. W prefiksach strumienia bitów „.” znaczy trochę Oznacza to, że dowolny strumień bitów dla klucza A z 0 na pierwszym miejscu daje takie samo drzewo jak każdy inny, zakładając, że strumień bitów innego klucza nie jest inny.

A 0...
B 00..
C 10..
D 0...
E 0011
F 1...
G 110.
H 0001


        ____H____
       /         \
      E           H
      |          / \
    __E__       G   H
   /  |  \      |   |
  B   C   E     G   H
 / \  |  / \   / \  |
A   B C D   E F   G H

Każde dziecko określonego węzła wewnętrznego ma ten sam bit na pierwszym miejscu swojego strumienia bitów. Nazywa się to „kolorem” rodzica - 0 to czerwony, 1 to zielony. Dziecko ma „smak” w zależności od pierwszego kawałka strumienia bitów - 0 to wiśnia, 1 to mięta. Liście mają smaki, ale nie mają koloru. Z definicji węzeł wiśniowy nie może mieć zielonego elementu nadrzędnego, a węzeł mięty nie może mieć czerwonego elementu nadrzędnego.

n2)1-n (n-1ja-1) a oczekiwana wartość to (n+1)/2). Dla wszystkichn2), to jest 3)4n, więc oczekiwana wysokość drzewa wynosi O(lgn).

Aby połączyć dwa drzewa o równej wysokości, najpierw sprawdź, czy ich korzenie są tego samego koloru. Jeśli tak, oderwij od lewego korzenia jego najbardziej potomne dziecko, a od prawego korzenia jego lewe dziecko, a następnie rekurencyjnie połącz te dwa drzewa. Rezultatem będzie drzewo o tej samej wysokości lub o jeden wyższy, ponieważ drzewa mają ten sam smak (patrz poniżej). Jeśli wynik rekurencyjnego łączenia dwóch drzew ma taką samą wysokość jak dwa odcięte dzieci, uczyń je środkowym dzieckiem korzenia z pozostałymi dziećmi lewego korzenia przed nim, a pozostałymi dziećmi prawego korzenia za nim. Jeśli jest wyższy o 1, uczyń jego dzieci środkowymi dziećmi korzenia z pozostałymi dziećmi lewego korzenia przed nim, a pozostałymi dziećmi prawego korzenia za nim. Jeśli korzenie mają różne kolory, sprawdź, czy mają ten sam smak. Jeśli to zrobią, nadaj im nowego rodzica ze strumieniem klucza i bitów odpowiedniego katalogu głównego, unikając pierwszego bitu. Jeśli nie, daj każdemu rootowi nowego rodzica ze strumieniem klucza i bitów starego roota (unikając każdego pierwszego bitu), a następnie rekurencyjnie dołącz do tych drzew.

There are two recursive calls in this algorithm. The first is when the roots have the same color, the second is when the roots have different colors and different flavors. The roots have the same color with probability 1/2. The recursive call in this case always sees roots with the same flavor, so the second type of recursion never occurs after the first. However, the first can occur repeatedly, but each time with probability 1/2, so the expected running time is still O(1). The second recursive call happens with probability 1/4, and subsequent recursive calls are always on trees with different colors, so the same analysis applies.

Aby połączyć dwa drzewa o nierównej wysokości, najpierw przeszukaj lewy kręgosłup prawego drzewa, zakładając, że prawe drzewo jest wyższe. (Drugi przypadek jest symetryczny). Po osiągnięciu dwóch drzew o równej wysokości wykonaj operację łączenia dla dwóch drzew o równej wysokości, zmodyfikowanych w następujący sposób: Jeśli wynik ma tę samą wysokość, zastąp drzewo, które było dzieckiem, wynikiem dołączenia. Jeśli wynik jest wyższy, dołącz element nadrzędny drzewa po prawej stronie do katalogu głównego drugiego drzewa, po tym, jak zostanie on podniesiony o jeden przez dodanie elementu nadrzędnego dla katalogu głównego. Drzewo będzie prawdopodobnie tej samej wysokości1/2), więc to kończy się w O(1) spodziewany.

Update: Thanks to QuickCheck, I discovered that the above join method does not produce the same trees as the uniquely represented trees above. The problem is that parent choices near the leaves may change depending on the available siblings. To fix up those changes, join would have to traverse all the way to the leaves, which is not O(1). Here is the example QuickCheck found:

a 01110
b 110..
c 10...
d 00000

The the tree made by [a,b] has height 2, the tree made by [c,d] has height 2, and the tree made by joinEqual (tree [a,b]) (tree [c,d]) has height 3. However, the tree made by [a,b,c,d] has height 5.

Here is the code I used to find this error.

jbapple
źródło