Kopce binarne (min-heap, max-heap)

1. Czym jest kopiec binarny?

Kopiec binarny (binary heap) to zupełne drzewo binarne z właściwością kopca.

1.1. Właściwość kopca:

Min-Heap: - Każdy rodzic ≤ swoje dzieci - Najmniejszy element w korzeniu

Max-Heap: - Każdy rodzic ≥ swoje dzieci - Największy element w korzeniu

1.2. Wizualizacja:

Min-Heap:                Max-Heap:
       1                        100
      / \                      /  \
     3   2                   90    80
    / \  /                  / \   /
   5  4 10                 50 70 60

✅ Każdy rodzic ≤ dzieci    ✅ Każdy rodzic ≥ dzieci
✅ Min (1) w korzeniu       ✅ Max (100) w korzeniu

2. Zupełne drzewo binarne

Kluczowa właściwość: Kopiec to zupełne drzewo binarne.

2.1. Co to znaczy "zupełne"?

✅ Zupełne drzewo:
       1
      / \
     2   3
    / \  /
   4  5 6

- Wszystkie poziomy wypełnione (oprócz ostatniego)
- Ostatni poziom wypełniany od lewej
❌ NIE zupełne:
       1
      / \
     2   3
      \   \
       5   6

- Ostatni poziom nie od lewej!

2.2. Dlaczego "zupełne"?

Bo możemy reprezentować kopiec jako tablicę! 🎉


3. Reprezentacja kopca jako tablica

3.1. Mapowanie drzewo → tablica:

Drzewo:
       1
      / \
     3   2
    / \  /
   5  4 10

Tablica (indeks od 0):
[1, 3, 2, 5, 4, 10]
 0  1  2  3  4  5

Index:  0   1   2   3   4   5
Level:  0   1   1   2   2   2

3.2. Wzory na indeksy:

Dla węzła na indeksie i:

3.3. Przykład:

Tablica: [1, 3, 2, 5, 4, 10]

Węzeł i=1 (wartość 3):
- parent(1) = (1-1)//2 = 0 → wartość 1 ✅
- left_child(1) = 2*1+1 = 3 → wartość 5 ✅
- right_child(1) = 2*1+2 = 4 → wartość 4 ✅

       1 (i=0)
      / \
   3(i=1) 2(i=2)
   / \    /
5(i=3) 4(i=4) 10(i=5)

4. Implementacja Min-Heap

4.1. Klasa bazowa:


5. Operacja: Heapify Up (Bubble Up)

Użycie: Po wstawieniu nowego elementu na koniec.

5.1. Idea:

1. Wstaw element na koniec
2. Porównaj z rodzicem
3. Jeśli mniejszy od rodzica → zamień
4. Powtarzaj aż:
   - Element ≥ rodzic (OK)
   - Element jest korzeniem

5.2. Wizualizacja:

Min-Heap: [1, 3, 2, 5, 4, 10]
Wstaw: 0

Krok 1: Dodaj na koniec
[1, 3, 2, 5, 4, 10, 0]
                    ↑

Krok 2: Porównaj z rodzicem (2)
0 < 2 → ZAMIEŃ

[1, 3, 0, 5, 4, 10, 2]
       ↑

Krok 3: Porównaj z rodzicem (1)
0 < 1 → ZAMIEŃ

[0, 3, 1, 5, 4, 10, 2]
 ↑

Krok 4: Element jest korzeniem → KONIEC

5.3. Implementacja:


6. Operacja: Heapify Down (Bubble Down)

Użycie: Po usunięciu korzenia.

6.1. Idea:

1. Zamień korzeń z ostatnim elementem
2. Usuń ostatni element
3. Porównaj nowy korzeń z dziećmi
4. Zamień z mniejszym dzieckiem
5. Powtarzaj aż:
   - Element ≤ oba dzieci (OK)
   - Element jest liściem

6.2. Wizualizacja:

Min-Heap: [1, 3, 2, 5, 4, 10]
Usuń korzeń (1)

Krok 1: Zamień korzeń z ostatnim
[10, 3, 2, 5, 4, 1]
 ↑               ↑

Krok 2: Usuń ostatni
[10, 3, 2, 5, 4]
 ↑

Krok 3: Porównaj z dziećmi (3, 2)
Min(3, 2) = 2
10 > 2 → ZAMIEŃ z 2

[2, 3, 10, 5, 4]
       ↑

Krok 4: Porównaj z dziećmi (brak)
Element jest liściem → KONIEC

Wynik: [2, 3, 10, 5, 4]

6.3. Implementacja:


7. Wstawianie do kopca

Złożoność: O(log n) – wysokość drzewa


8. Usuwanie minimum (extract-min)

Złożoność: O(log n)


9. Podglądanie minimum (peek)

Złożoność: O(1) ✅


10. Budowanie kopca (heapify)

10.1. Budowanie z tablicy:

Złożoność: O(n) – nieoczywiste, ale prawda! 🎉

10.2. Dlaczego O(n)?

Analiza:
- Liście (połowa węzłów): 0 operacji
- Poziom nad liśćmi: max 1 swap każdy
- Poziom wyżej: max 2 swapy każdy
- ...
- Korzeń: max log(n) swapów

Suma: n/2*0 + n/4*1 + n/8*2 + ... = O(n)

11. Max-Heap

Max-Heap to odwrotność Min-Heap – największy element w korzeniu.

11.1. Implementacja:


12. Moduł heapq w Pythonie

Python ma wbudowany moduł heapq dla Min-Heap:

12.1. Max-Heap w heapq:

Python heapq to tylko Min-Heap. Dla Max-Heap używamy sztuczki:


13. Złożoność operacji kopca

Operacja Złożoność Uwagi
Insert O(log n) Heapify up
Extract-min O(log n) Heapify down
Peek (min) O(1) ✅ Tylko odczyt korzenia
Build-heap O(n) ✅ Z tablicy
Search O(n) ❌ Brak uporządkowania
Delete (any) O(n) Musi znaleźć + heapify

Kluczowe: Peek = O(1), Insert/Extract = O(log n) ✅


14. Zastosowania kopców

14.1. Kolejka priorytetowa (Priority Queue)

14.2. Heap Sort (sortowanie przez kopcowanie)

Złożoność: O(n log n) – gwarantowane!

14.3. Znajdowanie k-tego najmniejszego/największego

14.4. Scalanie k posortowanych list

14.5. Mediana ze strumienia danych


15. Porównanie: Kopiec vs inne struktury

15.1. Kopiec vs BST:

Operacja Kopiec (Min) BST (zbal.)
Znajdź min O(1) ✅ O(log n)
Extract-min O(log n) O(log n)
Insert O(log n) O(log n)
Search O(n) ❌ O(log n) ✅
In-order NIE TAK ✅

Kopiec: Dla min/max i kolejki priorytetowej BST: Dla wyszukiwania i uporządkowania

15.2. Kopiec vs tablica posortowana:

Operacja Kopiec Tablica posort.
Znajdź min O(1) ✅ O(1) ✅
Extract-min O(log n) ✅ O(n) (shift)
Insert O(log n) ✅ O(n) (shift)
Build O(n) ✅ O(n log n)

Kopiec lepszy dla dynamicznych operacji!


16. d-ary Heap (uogólnienie)

d-ary heap: Każdy węzeł ma d dzieci (nie tylko 2).

Zastosowania: - d=3 (ternary heap) – kompromis między wysokością a liczbą porównań - d=4 (quaternary heap) – jeszcze niższa wysokość


17. Podsumowanie

Kopiec binarny (Binary Heap):

  • Zupełne drzewo binarne z właściwością kopca
  • Min-Heap: Rodzic ≤ dzieci (min w korzeniu)
  • Max-Heap: Rodzic ≥ dzieci (max w korzeniu)
  • Reprezentacja: Tablica (nie potrzeba wskaźników!)

Operacje:

Operacja Złożoność Uwagi
Peek O(1) ✅ Najważniejsza zaleta!
Insert O(log n) Heapify up
Extract O(log n) Heapify down
Build O(n) ✅ Z tablicy
Search O(n) ❌ Brak uporządkowania

Wzory na indeksy:

Zalety: - ✅ Peek O(1) – szybki dostęp do min/max - ✅ Reprezentacja tablicowa – oszczędność pamięci - ✅ Build O(n) – efektywna budowa - ✅ Prostsza niż BST/AVL - ✅ Cache-friendly (tablica)

Wady: - ❌ Search O(n) – brak uporządkowania - ❌ Nie ma in-order – nie do sortowania dynamicznego - ❌ Delete(any) O(n) – musi znaleźć element

Zastosowania:

  1. Kolejki priorytetowe
  2. Scheduler w systemach operacyjnych
  3. Algorytmy grafowe (Dijkstra, Prim)

  4. Heap Sort – O(n log n) gwarantowane

  5. Znajdowanie k-tego min/max

  6. Scalanie k list

  7. Mediana ze strumienia

  8. Top-k problemy (np. k największych elementów)

Kiedy używać: - ✅ Potrzebujesz szybkiego dostępu do min/max O(1) - ✅ Kolejka priorytetowa - ✅ Dynamiczne wstawianie z dostępem do ekstremum - ✅ Heap Sort (in-place, O(n log n))

Kiedy NIE używać: - ❌ Potrzebujesz wyszukiwania dowolnego elementu (użyj BST) - ❌ Potrzebujesz uporządkowania wszystkich elementów (użyj BST) - ❌ Tylko statyczne dane (użyj posortowanej tablicy)

Kluczowa obserwacja:

Python heapq:

Co dalej warto poznać:

  • d-ary heaps – uogólnienie na d dzieci
  • Fibonacci Heap – lepsze amortyzowane złożoności (Dijkstra)
  • Binomial Heap – efektywne scalanie kopców
  • Leftist Heap – dla kolejek priorytetowych
  • Heap Sort – szczegółowa implementacja in-place