Binary Heap

by Jerry Sky



Kopiec binarny (binary heap) to pełne drzewo binarne. Poziom iity ma 2i2^i elementów (liczymy od i=0i=0).

full binary tree

Liczba węzłów wówczas wynosi #węzłoˊw=i=0k12i=2k1 \#\text{węzłów} = \sum_{i=0}^{k-1}2^i = 2^k - 1

Kopiec budujemy na tablicy A=[a1,,an]A=[a_1,\dots,a_n]. Liczba elementów tablicy nn nie musi być równa 2k12^k-1. Wówczas kopiec wygląda następująco:

not full binary heap

Jak można zauważyć nn można ograniczyć: 2k1n<2k 2^{k-1} \le n < 2^k Wówczas wysokość kopca o nn elementach jest asymptotycznie równa Θ(logn)\Theta(\log n).

Własność kopca minimalnego

A[i]A[ parent(i) ] A[i] \ge A[~\mathrm{parent}(i)~]

Przykład

Weźmy A=[ 21,42,33,8,19,13,11,15,22,2010 ] A = [~\underbrace{2}_{1},\underbrace{4}_{2},\underbrace{3}_{3},\underbrace{8,19,13,11,15,22}_{\dots},\underbrace{20}_{10}~]

Wtedy mamy następujący kopiec:

example binary heap

Chcemy się poruszać po takim drzewie binarnym.

Poruszanie się po binary heap

Będąc w jakimś węźle, potrzebne są wskaźniki umożliwiające nam przejście do parent-a tego węzła oraz lewego i prawego potomka. Warto zauważyć, że mamy do czynienia z pełnym drzewem binarnym, przez co mamy pewność, że na kolejnym poziomie mamy zawsze dwa razy więcej elementów niż na poprzednim (poza ostatnim poziomem).
Wówczas, możemy używać następujących prostych makr:

parent(i)=i2left(i)=2iright(i)=2i+1 \mathrm{parent}(i) = \left\lfloor \frac{i}{2} \right\rfloor \\ \mathrm{left}(i) = 2\cdot i \\ \mathrm{right}(i) = 2\cdot i + 1

Powyższe makra są bardzo szybkie, bo możemy użyć prostych operacji na bitach.

Nie potrzebujemy już budować struktur węzłów, tak jak miało to w przypadku BST i pochodnych — wystarczą nam tablica wraz z powyższymi makrami.

Def\text {Def} wysokość węzła

Wysokością węzła nazywamy liczbę krawędzi na najdłuższej prostej ścieżce prowadzącej od tego węzła do liścia.

Heapify(A,i)(A,i)

Procedura przywracająca własność kopca dla węzła o indeksie ii w tablicy AA. Zakładamy, że kopce ukorzenione w lewym i prawym potomku węzła ii zachowuję własność kopca.

Heapify(A,i)(A,i):

  1. Sprawdź czy left(i)\mathrm{left}(i) lub right(i)\mathrm{right}(i) nie są większe niż rozmiar kopca
  2. x=x= wybierz ze zbioru {i,left(i),right(i)}\{i, \mathrm{left}(i), \mathrm{right}(i)\} indeks węzła, dla którego będzie zachowana własność kopca pomiędzy elementami {A[i],A[left(i)],A[right(i)]}\{A[i], A[\mathrm{left}(i)], A[\mathrm{right}(i)]\}
  3. if xix\neq i:
    1. swap(A[i],A[x])(A[i], A[x])
    2. Heapify(A,x)(A,x)

heapify example

BuildHeap(A)(A)

n=n = length(A)(A)

  1. for in2i\gets\left\lfloor\frac{n}{2}\right\rfloor to 11
    1. Heapify(A,i)(A,i)
    2. ii1i\gets i-1

BuildHeap example

Złożoność obliczeniowa BuildHeap

Grube oszacowanie: n2\frac{n}{2} razy wykonujemy operację Heapify o złożoności O(logn)O(\log n) czyli mamy O(nlogn)O(n \log n).

Fakt\text {Fakt} #1

W nn-elementowym kopcu binarnym występuje co najwyżej n2h+1\left\lceil\frac{n}{2^{h+1}}\right\rceil węzłów o wysokości hh.

D-d Fakt\text {Fakt}u #1

Indukcja po hh

  1. Dla h=0h = 0: n2\left\lceil\frac{n}{2}\right\rceil liści — O.K.
  2. Zał. indukcyjne k<h\forall_{k<h} występuje co najwyżej n2k+1\left\lceil\frac{n}{2^{k+1}}\right\rceil węzłów o wysokości kk

Krok indukcyjny:

z zał. ind. na wysokości h1h-1 mamy n2k1+1=n2k\le \left\lceil\frac{n}{2^{k-1+1}}\right\rceil = \left\lceil\frac{n}{2^k}\right\rceil węzłów

d-d fakt 1 1

Zatem węzłów o wysokości hh jest co najwyżej 12n2kn2h+1\frac{1}{2}\cdot \left\lceil\frac{n}{2^k}\right\rceil \le \left\lceil\frac{n}{2^{h+1}}\right\rceil.

\blacksquare

Złożoność obliczeniowa BuildHeap c.d.

h=0lgnn2h+1O(h)=O(nh=0lgnh2hograniczone przez stałą)=O(n) \sum_{h=0}^{\lg n}\left\lceil\frac{n}{2^{h+1}}\right\rceil \cdot O(h) = O\left(n\cdot \underbrace{\sum_{h=0}^{\lg n}\frac{h}{2^h}}_{\text{ograniczone przez stałą}}\right) = O(n)

Dla x<1|x| < 1: k0xk=11x  / ()  / xk0kxk=x(1x)2 \sum_{k\ge 0}x^k = \frac{1}{1-x} ~~/~ ()' ~~/~\cdot x \\ \sum_{k\le 0}k\cdot x^k = \frac{x}{(1-x)^2}

Zatem dla x=12x=\frac{1}{2}: k0k2k=12(112)2=2. \sum_{k\le 0}\frac{k}{2^k} = \frac{\frac{1}{2}}{(1-\frac{1}{2})^2} = 2.

Kopiec dd-arny

Kopiec binarny w łatwy sposób uogólnia się na kopiec dd-arny, czyli taki, w którym węzły nie będące liśćmi mają po dd potomków.

Więcej o kopcach

Chapter 19 & 20

HeapSort(A)(A)

Jednym z zastosowań kopców jest sortowanie przez kopcowanie HeapSort(A)(A). Wykorzystuje się do tego kopiec maksymalny (w kopcu maksymalnym własność kopca to: A[parent(i)]A[i]A[\mathrm{parent}(i)] \ge A[i]). Najpierw budujemy kopiec, a potem ściągamy z niego korzeń, po czym przywracamy własność kopca dla pozostałych elementów.

HeapSort(A)(A):

  1. BuildHeap(A)(A)
  2. for i=i= length(A)(A) to 22
    1. swap(A[1],A[i])(A[1], A[i])
    2. heap_size(A)(A)--
    3. Heapify(A,1)(A,1)
    4. ii--

Złożoność obliczeniowa HeapSort(A)(A)

Dla tablicy AA wielkości nn wywołanie BuildHeap ma złożoność O(n)O(n). Każde z n1n-1 wywołań Heapify ma pesymistyczną złożoność O(logn)O(\log n). Zatem w sumie złożoność wynosi: O(n)+(n1)O(logn)=O(nlogn) O(n) + (n-1)\cdot O(\log n) = O(n\log n)

More