Heap sort

Třídění haldou, skripta 5.1.6

Heap sort má složitost O(n log n), kde n je počet tříděných prvků

Formální rozložení prvků ve stromu

Je zadáno pole N hodnot a[0], … a[N-1].
Hodnoty boudou stále uloženy v poli, ale představíme si je ve stromu.
Do kořene stromu umístíme a[0].
Do patra pod kořenem umístíme a[1] , a[2].
O patro níže a[3], a[4], a[5], a[6].
Následující patra budou mít 8, 16, 32, 64, … prvků.
Dolní patro nemusí být kompletní.

   .------------------------------------------.
   | a[0] |  a[1] | a[2] | a[3] | a[4] | a[5] |
   .------------------------------------------.
                    a[0]
                     |
         .-------------------------.
         |                         |
        a[1]                      a[2]
         |                         |
    .----------.              .---------.      
    |          |              |
   a[3]       a[4]           a[5]

Pod prvkem a[i] jsou prvky a[2i+1] , a[2i+2].
( Pole a indexujeme od 0. )
( Pokud v jinem jazyce indexujeme od 1, tak pod prvkem a[i] jsou prvky a[2i] , a[2i+1]. )

                    a[i]
                     |
            .-----------------.
            |                 |
         a[2i+1]           a[2i+2]

Halda

V poli si vybereme posloupnost prvku s indexy i, i+1, …, k.

Řekneme, že prvky a[i], a[i+1], …, a[k] tvoří haldu,
pokud pro všechny hrany stromu spojující prvky a[i], a[i+1], …, a[k]
je prvek na horním konci hrany větší nebo roven než prvek na dolním konci hrany.

Tj. vybereme si několik za sebou jdoucích prvků v poli ( tato posloupnost může být i jedno-prvková nebo prázdná ).
Uvažujeme jen hrany spojující prvky v této posloupnosti (hrany které začínají nebo končí mimo tuto posloupnost nás nezajímají ).

Vezmeme prvek a[t], index t je z naší posloupnosti i < = t < = k.
Pod prvkem a[t] mohou být prvky a[2t+1] , a[2t+2].
Pokud 2t+1 < = k, levý pod-prvek je součástí naší posloupnosti a požadujeme a[t] > = a[2t+1].
Pokud 2t+2 < = k, tak i pravý pod-prvek je z naší posloupnosti a požadujeme a[t] > = a[2t+2].

Vytvoření haldy

Funkce heapify pracuje s posloupností prvků a[i], … a[k].
Na vstupu do funkce, předpokládáme, že a[i+1], … a[k] již tvoří haldu.
Cheme celou posloupnost a[i], … a[k] proměnit v haldu.

Vezmeme si nejvýše položený prvek a[i], ten zatím nemusí splňovat požadavek na haldu.
Hodnotu a[i] porovnáme s hodnotami podřízených prvků (pokud podřízené prvky v rámci a[i], … a[k] existují)
Pokud je a[i] větší nebo roven než podřízené prvky, je vše v pořádku. Následující prvky již haldu tvoří, můžeme funkci ukončit.
Jinak vybereme větší z podřízených prvků, jeho hodnotu prohodíme s hodnotou nadřízeného prvku a pokračujeme stejným postupem na místě podřízeného prvku.

Obrázek http://cdn.programiz.com/sites/tutorial2program/files/heapify-base-case.jpg

Hodnota a[i] původně na vrcholu vybrané části stromu padá dolů,
až pod ní budou prvky, které ji nepřevyšují,
nebo až je na nejnižším patře vybrané části stromu.

Obrázek http://cdn.programiz.com/sites/tutorial2program/files/heapfy-root-element-when-subtrees-are-max-heaps.jpg

Cyklus od vrcholu posloupnosti dolů

Začneme s prvkem a[i], pokud půjdeme níže, budeme měnit proměnnou i.

Cyklus, který kontroluje, zda je pod a[i] alespoň levý prvek.

    while (2*i+1 <= k)

Poznamenáme si index levého prvku do nové proměnné v.

        int v = 2*i+1;

Výběr většího podřízeného prvku

Zkusíme zda existuje také pravý prvek.
Pokud existuje porovnáme s levým prvkem.
Do proměnné v uložíme index většího pod-prvku. ( a víme, že v je z uzavřeného intervalu i, … k )

        if (v+1 <= k)
            if (a[v+1] > a[v])
                v = v + 1;

Prohození prvků

Nyní porovnáme a[i] (“horní prvek”) s prvkem a[v] (“vítězný podřízený prvek”).
Pokud je a[i] menší, prohodíme hodnoty ulořené v a[i] a a[v] a pokračujeme na místě podřízeného prvku (kde je nyní hodnota padající ze shora ).

        if (a[i] < a[v])
        {
            swap (a[i], a[v]);
            i = v;
        }

Funkce swap

Funkce swap dostane jako parametry x a y odkazy (&) na dvě celá čísla, která má prohodit.
V našem případě dostane odkazy na dva prvky pole a[i] , a[v]
Klíčové slovo inline doporučuje překladači nevolat podprogram, ale potřebné instrukce vložit v místě volání funkce.

inline void swap (int & x, int & y)
{
    int t = x;
    x = y;
    y = t;
}

Parametry předávané odkazem jsem již potkali vkládaní prvku do stromu

Altertativní funce swap pracující s globálním polem a parametrem jsou indexy v poli

inline void alt_swap (int i, int k)
{
    int t = a[i];
    a[i] = a[k];
    a[k] = t;
}

Na místo původního volání funkce swap (a[i], a[v]) použijeme

    alt_swap (i, v);

Ukončení cyklu

Nastavíme proměnnou i na příliš velkou hodnotu, která zastaví while cyklus.

  else
  {
     i = k+1; 
  }

Celá funkce heapify

void heapify (int i, int k)
{
    while (2*i+1 <= k)
    {
        int v = 2*i+1;
 
        if (v+1 <= k)
            if (a[v+1] > a[v])
                v = v + 1;
 
        if (a[i] < a[v])
        {
            swap (a[i], a[v]);
            i = v;
        }
        else
        {
            i = k+1; // ukonceni cyklu
        }
    }
}

:!: Počet prováděných instrukcí je úměrný výšce stromu s vybranými prvky a[i], … a[k].

Pro ilustraci:
http://www.youtube.com/watch?v=HqPJF2L5h9U
http://www.youtube.com/watch?v=B7hVxCmfPtM (indexují od 1 )

Třídění

Nejprve o uspořádání prvků v poli nic nevíme.

Vytváříme haldu - od konce pole k prvnímu prvku

Jednoprvková posloupnost s indexy n-1, … n-1 je z definice haldou.
Přidáme jeden prvek a použijeme funkci heapify na dvojprvkovou posloupnost s indexy n-2, … n-1.
Budeme přidávat prvky na začátek, funkci heapify upravovat hodnoty, aby tvořily haldu.

    for (int i = n-1; i >= 0; i--)
        heapify (i, n-1);

Použijeme heapify (i, n-1) pro i = n-1, n-2, …, 2, 1, 0.
Pro mnoho hodnot bude posloupnost umístěna v dolním patře.
Nebo bude v dolních dvou patrech, ale nebude obsahovat hranu.
Můžete se podívat na while (2*i+1 ⇐ k), kde k=n-1 a pozměnit počáteční hodnotu for cyklu.

Odebereme první největší prvek, uložíme na konec pole

Celá posloupnost a[0], … a[n-1] tvoří haldu.
Tedy a[0] je největší prvek, uložíme ho na konec pole.
Prvek, který byl na konci pole zkusíme umístit do a[0].
Nově umístěný prvek necháme spadnout na správné místo.
Haldu od konce postupně zkracujeme, jsou tam umístěny již setříděné hodnory, které nechceme znovu zamíchat do třídění.

    for (int i = n-1; i > 0; i--) 
    {
        swap (a[0], a[i]); // odebrat hodnotu z vrcholu a umistit na soucasny konec
        heapify (0, i-1);  // zbyle hodnoty opravit, aby opet tvorily haldu
    }

Obrázek http://stackoverflow.com/questions/41212072/ascending-and-descending-heapsort
Zeleně jsou zobrazeny uložené/setříděné hodnoty.
Červeně prohazované/nově nasazené hodnoty.
Žlutě je nakreslena zmenšující se halda.

Funkce heapsort

void heapsort (int n)
{
    for (int i = n-1; i >= 0; i--)
        heapify (i, n-1);
 
    for (int i = n-1; i > 0; i--) 
    {
        swap (a[0], a[i]);
        heapify (0, i-1);
    }
}

:!: Počet instrukcí funkce heapify je úměrný výšce stromu.
Výšku stromu shora odhadneme logaritmem o základu 2 z počtu prvků (zvětšeným o 1).
Oba for cykly proběhnou n-krát.
Celkem O (n log n)

Vstupní hodnoty, tisk a kontrola hodnot

    srand (time (nullptr));
    for (int i = 0; i < N; i++)
        a[i] = rand() % 10000;
 
    heapsort (N); // trideni
 
    bool ok = true;
    for (int i = 0; i < N; i++)
    {
        cout << a[i];
        if (i < N-1 && a[i + 1] < a[i]) { ok = false; cout << " CHYBA"; }
        cout << endl;
    }

Celý program

#include <iostream>
#include <time.h>
using namespace std;
 
const int N = 1000000;
 
int a[N];
 
inline void swap (int & x, int & y)
{
    int t = x;
    x = y;
    y = t;
}
 
void heapify(int i, int k)
// vytvorit hromadu z a[i] ... a[k]
// za predpokladu, ze a[i+1] ... a[k] jiz hromadu tvori
{
    while (2 * i + 1 <= k)
    {
        int v = 2 * i + 1;
 
        if (v + 1 <= k)
            if (a[v+1] > a[v])
                v = v + 1;
 
        if (a[i] < a[v])
        {
            swap (a[i], a[v]);
            i = v;
        }
        else
        {
            i = k+1; // ukonceni cyklu
        }
    }
}
 
void heapsort (int n)
// setridit a[0] ... a[n-1]
{
    for (int i = n-1; i >= 0; i--)
        heapify (i, n-1);
 
    for (int i = n-1; i > 0; i--) 
    {
        swap (a[0], a[i]);
        heapify (0, i-1);
    }
}
 
int main()
{
    srand (time (nullptr));
    for (int i = 0; i < N; i++)
        a[i] = rand() % 10000;
 
    heapsort (N);
 
    bool ok = true;
    for (int i = 0; i < N; i++)
    {
        cout << a[i];
        if (i < N-1 && a[i + 1] < a[i]) { ok = false; cout << " CHYBA"; }
        cout << endl;
    }
 
    if (ok)
       cout << "O.K." << endl; 
}

Mini literatura: http://cs.wikipedia.org/wiki/%C5%98azen%C3%AD_haldou

 
zalg/heapsort.txt · Last modified: 2020/04/07 13:29 by 88.103.111.44
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki