Implementace datové struktury haldy (Max Heap) v Javě


Implementace datové struktury maximální haldy v Javě

Úvodní informace

Maximální halda, označovaná také jako max heap, je specifický typ stromové datové struktury. Uspořádání dat v této struktuře zajišťuje, že kořenový uzel vždy obsahuje největší prvek. Tato vlastnost ji činí ideální pro implementaci prioritních front, kde prvky s nejvyšší prioritou jsou zpracovávány přednostně.

Základní principy haldy

Haldy jsou formou úplných binárních stromů. To znamená, že každý uzel v haldě má maximálně dva následníky (levého a pravého potomka). Struktura haldy je navržena tak, aby hodnota každého uzlu byla větší nebo rovna hodnotám jeho potomků. Díky tomu je kořen stromu vždy největším prvkem.

Klíčové charakteristiky haldy

  • Úplnost: Halda je kompletní binární strom, kde každý uzel, s výjimkou posledního, má dva potomky.
  • Hierarchie: Hodnota každého uzlu je větší nebo rovna hodnotám jeho následníků.
  • Výšková složitost: Maximální výška haldy je úměrná logaritmu počtu uzlů (O(log n)), kde n je počet uzlů v haldě.

Základní operace nad haldou

Mezi nejčastější operace prováděné s haldou patří:

  • Vložení prvku: Přidání nového prvku do haldy při zachování jejích vlastností.
  • Odebrání maximálního prvku: Odstranění kořenového prvku (největšího v haldě) a jeho nahrazení s cílem zachovat strukturu haldy.
  • Snížení klíče: Snížení hodnoty uzlu, což může vést k narušení struktury haldy a vyžaduje její obnovení.
  • Zvýšení klíče: Operace zvýšení hodnoty uzlu je obecně zakázána, protože by porušila hierarchii haldy.

Implementace v Javě

V jazyce Java se halda obvykle implementuje pomocí pole. Každý prvek pole reprezentuje uzel haldy. Indexování pole začíná od 1.

Následující kód představuje základní implementaci maximální haldy:

public class MaxHeap {

    private int[] heap;
    private int size;

    // Konstruktor
    public MaxHeap(int capacity) {
        heap = new int[capacity + 1]; // +1 pro kořen
        size = 0;
    }

    // Vložení prvku
    public void insert(int key) {
        if (size == heap.length - 1) {
            // Halda je plná
            return;
        }

        // Vložení na konec
        heap[++size] = key;

        // Obnovení vlastností haldy
        heapifyUp(size);
    }

    // Obnovení vlastností haldy po vložení
    private void heapifyUp(int index) {
        while (index > 1) {
            int parentIndex = index / 2;
            if (heap[index] > heap[parentIndex]) {
                // Prohození
                swap(heap, index, parentIndex);
                index = parentIndex;
            } else {
                break;
            }
        }
    }

    // Odebrání kořene
    public int extractMax() {
        if (size == 0) {
            // Halda je prázdná
            return -1;
        }

        // Uložení kořene
        int max = heap[1];

        // Nahrazení kořene posledním prvkem
        heap[1] = heap[size];

        // Snížení velikosti
        size--;

        // Obnovení vlastností haldy
        heapifyDown(1);

        return max;
    }

    // Obnovení vlastností haldy po vyjmutí
    private void heapifyDown(int index) {
        while (true) {
            int leftChildIndex = index * 2;
            int rightChildIndex = index * 2 + 1;

            // Žádní potomci
            if (leftChildIndex > size) {
                break;
            }

            int maxChildIndex;
            if (rightChildIndex > size) {
                maxChildIndex = leftChildIndex;
            } else {
                if (heap[leftChildIndex] > heap[rightChildIndex]) {
                    maxChildIndex = leftChildIndex;
                } else {
                    maxChildIndex = rightChildIndex;
                }
            }

            if (heap[index] < heap[maxChildIndex]) {
                swap(heap, index, maxChildIndex);
                index = maxChildIndex;
            } else {
                break;
            }
        }
    }

    // Prohození prvků
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

Praktické využití

Maximální haldy se uplatňují v mnoha oblastech, například:

  • Implementace prioritních front.
  • Algoritmus pro třídění haldou (Heapsort).
  • Použití v grafových algoritmech.
  • Vyhledávání mediánu.

Závěr

Maximální halda je datová struktura s efektivním způsobem ukládání dat, kde největší prvek má vždy prioritu. Její rychlé operace, jako je vkládání, odstraňování a aktualizace, z ní činí ideální řešení pro různé algoritmy a aplikace. Implementace v Javě s použitím pole je poměrně jednoduchá a snadno pochopitelná.

Často kladené otázky

1. Jaká je časová náročnost vložení do haldy? Operace vložení má časovou složitost O(log n), kde n je počet prvků v haldě.
2. Jaká je časová náročnost odebrání kořene haldy? Časová složitost operace odebrání kořene haldy je rovněž O(log n), kde n je počet prvků v haldě.
3. Co se stane, když vložíme prvek větší než kořen? Pokud vložíme prvek s hodnotou větší než kořen, dojde k porušení hierarchie haldy. V takovém případě je nutné použít operaci heapifyUp pro obnovení správného uspořádání prvků.
4. Co se stane, když snížíme hodnotu uzlu, který není kořenem? Snížení hodnoty uzlu, který není kořenem, může narušit vlastnosti haldy. Po takové operaci je nutné provést heapifyUp pro obnovení struktury.
5. Proč haldu reprezentujeme polem? Reprezentace haldy polem je efektivní a jednoduchý způsob, jak uložit strukturu v paměti. Pole umožňuje snadný přístup k prvkům a rychlé vyhledávání.
6. Existují jiné možnosti implementace haldy v Javě? Ano, lze použít Java Collections Framework, které nabízí další možnosti implementace haldy.
7. Jak můžeme třídit pole čísel s pomocí haldy? Halda se používá v algoritmu heapsort, jehož časová složitost je O(n log n).
8. Jak se liší halda od binárních vyhledávacích stromů? Na rozdíl od binárních vyhledávacích stromů, které jsou optimalizované pro vyhledávání a vkládání, haldy primárně slouží k efektivnímu získání prvku s nejvyšší prioritou.