Rozszerzone RB-Trees

by Jerry Sky

2020-04-06



Goal

Mając dynamiczny zbiór danych (dane będą usuwane i dodawane) chcemy szybko wyznaczyć iitą statystykę (iity najmniejszy element) oraz zwracać stat. poz. zadanego elementu.

Steps

  1. Używamy RB-Trees, jako że chcemy osiągnąć złoż. oblicz. O(log(n))O(\log(n)).

  2. Do definicji węzła dodajemy size\text{size} gdzie dla węzłów przechowujących wartość size=1\text{size}=1 a dla liści NIL\text{NIL} mamy size=0\text{size}=0.

    Wówczas dla węzła xx w RB-Tree mamy x.size=x.left.size+x.right.size+1x.\text{size} = x.\text{left}.\text{size} + x.\text{right}.\text{size} + 1 jeśli xx nie jest NIL\text{NIL}.

  3. Wykonując operacje dodawania/ usuwania węzłów RB-Tree musimy pamiętać o aktualizowaniu rozmiarów pod-drzew, których rozmiar ulega zmianie (zwiększanie/ zmniejszanie size\text{size} pod-drzewa jeszcze przed rekonstrukcją drzewa).

    Dodatkowo musimy zapewnić, że rekonstrukcja drzewa nadal będzie wykonywała się w asymptotycznie tej samej złożoności obliczeniowej. Rekonstrukcja RB-Tree przywracająca jego własności polega na wykonywaniu procedur Recolour(x,colour)\text{Recolour}(x, \text{colour}), Rotateright(T,x)\mathrm{Rotate_{right}}(T,x) oraz Rotateleft(T,x)\mathrm{Rotate_{left}}(T,x) odpowiednią liczbę razy.
    W standardowym RB-Tree procedury te mają złożoność O(1)O(1). Zatem, aby wzbogacona struktura danych zachowała asymptotycznie złożoności struktury podstawowej, musimy pokazać, że procedury te możemy wykonać w złożoności O(1)O(1) zachowując odpowiednie wartości pól size\mathrm{size} w węzłach drzewa.

    1. Recolour(x,colour)\mathrm{Recolour}(x, \mathrm{colour}) — procedura ta zmienia kolor węzła, ale nie modyfikuje rozmiaru pod-drzewa zaczepionego w węźle xx, więc nie jest wymagana aktualizacja poza size\mathrm{size} w żadnym węźle drzewa.
    2. Rotateright(T,x)\mathrm{Rotate_{right}}(T,x), Rotateleft(T,x)\mathrm{Rotate_{left}}(T,x) — rotacja nie zmienia rozmiaru pod-drzewa zaczepionego w korzeniu rotowanego pod-drzewa, ale może zmieniać rozmiar nowo powstałego pod-drzewa. Zauważmy, że musimy obliczyć rozmiar jednego nowo-powstałego pod-drzewa (powstaje ono przez przeniesienie węzła xx do prawego/ lewego pod-drzewa i przypięcie reszty pod-drzew pod nim) i możemy do tego wykorzystać wzór: x.size=x.left.size+x.right.size+1x.\mathrm{size} = x.\mathrm{left}.\mathrm{size} + x.\mathrm{right}.\mathrm{size} + 1.
      Ważne jest, że zmiana rozmiaru pod-drzewa odbywa się lokalnie i nie trzeba uaktualniać rozmiarów pod drzewa w węzłach oddalonych od rotowanego węzła. Złożoność obliczeniowa tak zmodyfikowanej operacji rotacji pozostaje O(1)O(1).
  4. Dodajemy nowe operacje:

OS-Select

OS-Select(root,i)(\mathrm{root}, i) — operacja zwracająca iity najmniejszy element zbioru zawartego w RB-Tree zaczepionym w węźle root\mathrm{root}.

OS-Select(x, i)
  k = x.left.size + 1
  if i == k then return x
  if i < k then return OS-Select(x.left, i)
  else return OS-Select(x.right, i-k)

OS-Select działa analogicznie jak operacja Search na BSTs tylko porównuje liczbę elementów mniejszych równych od węzła w którym się znajduje.
OS-Select działa w czasie O(h)=O(logn)O(h) = O(\log n)

OS-Rank

OS-Rank(root,x)(\mathrm{root}, x) — operacja zwracająca statystykę pozycyjną węzła xx w RB-Tree zaczepionym w węźle root\mathrm{root}.

OS-Rank(root, x)
  r = x.left.size + 1
  y = x
  while y != root:
    if y == y.parent.right:
      r += y.parent.left.size + 1
    y = y.parent
  return r

OS-Rank jest swoistym odwróceniem OS-Select – zliczamy liczbę elementów mniejszych (po lewej stronie w drzewie) na drodze od zadanego węzła do korzenia. OS-Rank działa w czasie O(h)=O(logn)O(h) = O(\log n).

Final thoughts

Zauważmy, że jeśli w węzłach drzewa chcielibyśmy przechowywać po prostu statystykę pozycyjną węzła to operacje OS-Select oraz OS-Rank miałyby złożoność O(1)O(1), ale złożoność obliczeniowa operacji modyfikujących RB-Tree byłaby asymptotycznie równa O(n)O(n).