Hogyan Működnek Az Algoritmusok
Chapter 3 Sorting Algorithms

3. fejezet: Rendezési algoritmusok

A rendezés olyan folyamat, amely egy adatsorozat elemeinek átrendezésével logikus sorrendet hoz létre. Például a hitelkártya-számlád a tranzakciókat dátum szerint rendezi, és a könyveidet a szerzők és címek betűrendjében helyezed el a könyvespolcodon. A rendezés alapvető művelet a számítástudományban, és kritikus szerepet játszik számos alkalmazásban. Különböző klasszikus rendezési algoritmusok testesítik meg a probléma megoldásának különböző megközelítéseit.

Ebben a fejezetben több klasszikus rendezési módszert és egy fontos adatszerkezetet, a prioritási sort tárgyaljuk. Először néhány elemi rendezési módszerről, köztük a kiválasztásos rendezésről, a beszúrásos rendezésről és a Shellsort-ról beszélünk. Ezek a módszerek sok alkalmazásban megfelelőek, de nagy problémák esetén a rekurzív rendezési algoritmusokhoz, a egyesítéses rendezéshez és a gyorsrendezéshez fordulunk, amelyek segítségével hatalmas mennyiségű elemet lehet rendezni. Végül a prioritási sorokról és azok rendezésben és más alkalmazásokban való felhasználásáról beszélünk.

Elemi rendezések

A legegyszerűbb rendezési algoritmusok a következő műveleteket végzik:

  • Kiválasztásos rendezés: Megkeressük a legkisebb elemet, és kicseréljük az első bejegyzéssel, majd megkeressük a második legkisebb elemet, és kicseréljük a második bejegyzéssel, és így tovább.
  • Beszúrásos rendezés: Sorra vesszük az elemeket, és beillesztjük őket a már rendezett részbe a megfelelő helyre.

Ezek a műveletek tükrözik, ahogyan az emberek általában végzik a rendezési feladatokat, és kis problémaméret esetén hatékonyak. Azonban nem skálázódnak jól, és nagy tömbök rendezése esetén nem gyakorlati megoldások.

Kiválasztásos rendezés

A kiválasztásos rendezés egy egyszerű rendezési algoritmus, amely a következőképpen működik: Először megkeressük a legkisebb elemet a tömbben, és kicseréljük az első bejegyzéssel (ha az első bejegyzés már a legkisebb, akkor önmagával cseréljük ki). Ezután megkeressük a következő legkisebb elemet, és kicseréljük a második bejegyzéssel. Ezt a folyamatot folytatjuk, amíg a teljes tömb rendezett nem lesz.

A kiválasztásos rendezés belső ciklusaHere is the Hungarian translation of the provided markdown file, with the code comments translated:

A rendezés használata a a[i..N-1] rendezetlen részhalmazban található minimális elem megkereséséhez. A minimális elem indexe min-ben van tárolva. Ezután a[i] eleme kicserélésre kerül a[min]-nel, ami a minimális elemet a végleges helyére teszi. Ahogy az i index balról jobbra halad, a baloldalon lévő elemek már rendezettek a tömbben, és nem lesznek újra érintve.

Itt egy példa a szelekciós rendezés Java-ban való implementációjára:

public static void selectionSort(Comparable[] a) {
    // A tömb hossza
    int N = a.length;
    // Végigmegyünk a tömbön
    for (int i = 0; i < N; i++) {
        // A minimális elem indexe
        int min = i;
        // Megkeressük a minimális elemet a rendezetlen részhalmazban
        for (int j = i+1; j < N; j++) {
            if (less(a[j], a[min])) min = j;
        }
        // Kicseréljük a minimális elemet a jelenlegi elemmel
        exch(a, i, min);
    }
}

A szelekciós rendezés körülbelül ~N^2/2 összehasonlítást és N cserét használ egy N hosszúságú tömb rendezéséhez. A futási idő nem érzékeny a bemenetre - körülbelül ugyanannyi ideig tart a szelekciós rendezés egy már rendezett tömbön vagy egy egyenlő kulcsokkal rendelkező tömbön, mint egy véletlenszerűen rendezett tömbön.

Beszúrásos rendezés

A beszúrásos rendezés egy másik egyszerű rendezési algoritmus, amely a végső rendezett tömböt egy elemet építi fel egyszerre. Sokkal kevésbé hatékony nagy tömbökön, mint a fejlettebb algoritmusok, mint a gyorsrendezés, a kupacrendezés vagy az egyesítéses rendezés, de néhány előnye van:

  • Hatékony kis adathalmazok esetén.
  • Gyakorlatban hatékonyabb, mint a szelekciós rendezés.
  • Stabil; azaz nem változtatja meg az egyenlő kulcsokkal rendelkező elemek relatív sorrendjét.
  • In-place; azaz csak konstans O(1) további memóriahelyet igényel.
  • Online; azaz képes rendezni a listát, ahogy azt megkapja.

Itt egy példa a beszúrásos rendezés Java-ban való implementációjára:

public static void insertionSort(Comparable[] a) {
    // A tömb hossza
    int N = a.length;
    // Végigmegyünk a tömbön a második elemtől kezdve
    for (int i = 1; i < N; i++) {
        // Beszúrjuk a jelenlegi elemet a megfelelő helyre
        for (int j = i; j > 0 && less(a[j], a[j-1]); j--) {
            // Kicseréljük a jelenlegi elemet az előtte lévővel
            exch(a, j, j-1);
        }
    }
}

A beszúrásos rendezés belső ciklusa a nagyobb elemeket egy pozícióval jobbra mozgatja, helyet csinálva a jelenlegi elem beszúrásához.Here is the Hungarian translation of the provided markdown file, with the code comments translated:

A beszúró rendezés futási ideje a bemeneti elemek kezdeti sorrendjétől függ. Például, ha a tömb nagy és a bejegyzések már rendezettek (vagy majdnem rendezettek), akkor a beszúró rendezés sokkal, sokkal gyorsabb, mint ha a bejegyzések véletlenszerűen vagy fordított sorrendben vannak.

Shellsort

A Shellsort a beszúró rendezés egyszerű kiterjesztése, amely a sebesség növelését azzal éri el, hogy lehetővé teszi a tömbben távol lévő bejegyzések cseréjét, hogy részlegesen rendezett tömbeket hozzon létre, amelyeket végül beszúró rendezéssel lehet hatékonyan rendezni.

Az ötlet az, hogy átrendezzük a tömböt, hogy az a tulajdonság legyen, hogy minden h-adik bejegyzés (bárhol is kezdve) egy rendezett részsorozatot ad. Ezt a tömböt h-rendezettnek nevezik. Más szóval, egy h-rendezett tömb h független rendezett részsorozat, amelyek egymásba vannak ágyazva. Néhány nagy h értékkel való h-rendezéssel a tömb elemeit nagy távolságokra mozgathatjuk, és így könnyebbé tehetjük a kisebb h értékekkel való h-rendezést. Ilyen eljárás alkalmazása bármely olyan értéksorozatra, amely 1-gyel végződik, rendezett tömböt fog eredményezni: ez a Shellsort.

Itt egy Shellsort implementáció Javában:

public class MaxPQ<Key extends Comparable<Key>> {
    private Key[] pq;
    private int N;
    
    public MaxPQ(int capacity) {
        pq = (Key[]) new Comparable[capacity+1];
    }
   
    public boolean isEmpty() {
        return N == 0;
    }
   
    public void insert(Key key) {
        pq[++N] = key;
        swim(N);
    }
   
    public Key delMax() {
        Key max = pq[1];
        exch(1, N--);
        sink(1);
        pq[N+1] = null;
        return max;
    }
   
    private void swim(int k) {
        // Felúsztatás a csúcsig
        while (k > 1 && less(k/2, k)) {
            exch(k, k/2);
            k = k/2;
        }
    }
   
    private void sink(int k) {
        // Lesüllyesztés a csúcsig
        while (2*k <= N) {
            int j = 2*k;
            if (j < N && less(j, j+1)) j++;
            if (!less(k, j)) break;
            exch(k, j);
            k = j;
        }
    }
   
    private booleaItt a magyar fordítás a megadott markdown fájlhoz. A kódban csak a kommenteket fordítottam le, a kód maga változatlan maradt.
 
```java
private boolean less(int i, int j) {
    // Visszaadja, hogy a i-edik elem kisebb-e, mint a j-edik elem
    return pq[i].compareTo(pq[j]) < 0;
}
 
private void exch(int i, int j) {
    // Kicseréli az i-edik és a j-edik elemet
    Key t = pq[i];
    pq[i] = pq[j];
    pq[j] = t;
}
}

Ez a kód egy maximális bináris kupac implementációját valósítja meg egy pq nevű tömb használatával. A beszúrás (insert()) és a maximum törlése (delMax()) műveletek a swim() és sink() segédmetódusok használatával tartják fenn a kupac tulajdonságot, azáltal, hogy kicserélik a kulcsokat, ha a szülő nagyobb, mint a gyermek, vagy a gyermek nagyobb, mint a szülő.

Stopwatch

Egy hasznosabb absztrakt adattípus egy egyszerű és hatékony stopperóra absztrakció, amely a szemközti oldalon látható. Használatához hozzon létre egy Stopwatch objektumot, amikor el akarja indítani a stopperórát, majd használja az elapsedTime() metódust a eltelt idő másodpercben való lekérdezéséhez a létrehozás óta. Az implementáció a Java System.currentTimeMillis() metódusát használja az aktuális idő milliszekundumban való lekérdezéséhez 1970. január 1. éjfél óta.

A start instance változó rögzíti a stopperóra létrehozásának idejét, és az elapsedTime() ezt használja fel az eltelt idő kiszámításához. A bemutatott ügyfél kód tipikus: végrehajt egy számítást, és egy Stopwatch segítségével méri, mennyi ideig tart a számítás. A Stopwatch adattípus hatékony absztrakció, mert elválasztja a stopperóra fogalmát (a felületet) a megvalósítástól (a Java System.currentTimeMillis() használatával). Ez a felület és megvalósítás szétválasztása az absztrakt adattípusok alapvető jellemzője, amelyet a könyv minden ADT-jében látni fogunk.

Összefoglalás

Az absztrakt adattípusok az objektum-orientált programozás lényeges elemei, amelyeket széles körben használnak a modern programozásban. Ebben a részben a következőket láttuk:

  • Absztrakt adattípus definiálása Java osztályként, instance változókkal az adattípus értékeinek meghatározására és instance metódusokkal az ezeken végzett műveletek megvalósítására.
  • Ugyanazon API többféle implementációjának kifejlesztése, különböző reprezentációkat használva ugyanahhoz az absztrakt adattípushoz.Absztrakt adattípus.
  • Az API-k, ügyfelek és implementációk megkülönböztetése egy absztrakt adattípuson belül.
  • Absztrakt adattípusok API-jának tervezése.
  • Ügyfelek és tesztügyfelek fejlesztése tesztelés és hibakeresés céljából.
  • Következtetés egy absztrakt adattípus implementációjának helyességéről, állítások használatával.
  • Különböző implementációk teljesítményének összehasonlítása ugyanazon API esetén.

Ezek a tevékenységek elengedhetetlen részei bármely Java-program fejlesztésének. Minden Java-program, amit írunk, könyvtárakból származó absztrakt adattípusok használatát fogja magában foglalni; sok közülük új absztrakt adattípusok fejlesztését is magában foglalja. A következő részben három alapvető absztrakt adattípust tárgyalunk, amelyek lényeges összetevői a programok nagy számának, és az 1.4. részben részletesen megvizsgáljuk az implementációk teljesítményjellemzőinek elemzésének folyamatát.