Countingsort

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche

Countingsort (von engl. count „zählen“) ist ein stabiles Sortierverfahren, das eine gegebene Folge von n natürlichen Zahlen aus einem beschränkten Intervall mit k Elementen mit linearem Zeitaufwand (Problemkomplexität \textstyle O(n + k)) sortiert. Der Algorithmus arbeitet nicht vergleichsbasiert, sondern zählt die Zahlen der Eingabe - er arbeitet also adressbasiert. Im Vergleich zum vergleichenden Sortieren mit der bestmöglichen Komplexität \textstyle O(n \log n) ergibt sich ein Vorteil, wenn die Intervalllänge k sehr klein ist gegenüber der Anzahl der zu sortierenden Element n.

Algorithmus[Bearbeiten]

Countingsort setzt voraus, dass die zu sortierenden Werte der Eingabe in einem beschränkten Intervall \{0,\ldots,k\} liegen (oder sich einfach darauf abbilden lassen). Der Algorithmus zählt, wie oft jeder dieser Werte in der Eingabe vorkommt. Diese Anzahlen speichert er in einem zusätzlichen Array mit k+1 Elementen ab. Mit Hilfe dieses Arrays wird für jedes Element der Eingabe die Zielposition in der Ausgabe berechnet.

Eingabe: Array A[1 .. n] mit A[j] \in \{0,\ldots, k\} für alle j=1,\ldots,n und die rechte Intervallgrenze k.

Ausgabe: Feld B[1 .. n] mit Inhalt von A in sortierter Reihenfolge.

Zusätzlicher Speicher: Während der Sortierung benötigt der Algorithmus ein Hilfs-Array C[0 .. k].

Angabe des Algorithmus in Pseudocode:

countingsort(A, k)
{
  C = array(0,k)

  // initialisiere das Array C mit Nullen
  for (m=0; m<=k; m++)
    C[m] = 0
  // end for

  // Zähle
  for (j=1; j<=A.size; j++)
    C[key(A[j])] += 1
  // end for
  // Nun steht in C[m] wie häufig der Wert m in A vorkommt.

  // Adressrechnung
  for (m=1; m<=k; m++)
    C[m] += C[m-1]
  // end for

  B = array(1, A.size)

  // kopieren auf jeweilige Zieladresse
  for (j=A.size; j>0; j--)
  {
    B[C[key(A[j])]] = A[j]
    C[key(A[j])] -= 1        // Zielposition um 1 herunterzaehlen
  } // end for

  return B
}

Die Funktion key gibt den Sortierschlüssel des Array-Elements der Eingabe zurück. Wenn ein Array-Element nur aus dem Sortierschlüssel besteht und keine weitere Komponenten enthält, dann ist \text{key}(x) = x.
In der ersten for-Schleife wird das Hilfs-Array C initialisiert.
Die zweite for-Schleife iteriert über die Eingabe und inkrementiert für jeden Sortierschlüssel das zugeordnete Array-Element C[key(A[j])]. Es wird also gezählt, wie oft der Schlüsselwert m in A vorkommt, und diese Anzahl steht dann in C[m].
In der dritten for-Schleife werden die Zähler in C akkumuliert, so dass nach dem Ende der Schleife C[x] die letzte Position von der Zahl x im Ausgabe-Array bezeichnet. Somit enthält C[m] jetzt nicht mehr „Anzahl der ‚m-Elemente‘ in A“, sondern C[m] enthält nun die „Adresse des ‚m-Abschnitts‘ für B“ (allerdings die Endadresse, nicht die Startadresse).
Die letzte for-Schleife durchläuft die Eingabe von rechts nach links (aufgrund der Endadressen in C), und sortiert den jeweiligen Eintrag dabei ein: Ist die Schleife in Iteration j, so hat A[j] den Schlüsselwert m_j (= \text{key}(A[j])). Dieser muss also einsortiert werden an Adresse C[m_j]. Anschließend muss die Zieladresse C[m_j] um 1 heruntergezählt werden, damit das nächste Element in A, das den Schlüssel m_j besitzt, eine Position weiter vorne einsortiert wird.
Durch die sprungfreie Iteration wird die relative Reihenfolge von mehreren Elementen der Eingabe mit dem gleichen Sortierschlüssel auch in der Ausgabe nicht verändert, d. h. Countingsort sortiert stabil.

Vereinfachter Algorithmus[Bearbeiten]

Wenn die Eingabe ein einfaches Zahlen-Array ist, dann kann Countingsort ohne eine Akkumulationsphase dargestellt werden:

countingsort(A, k)
{
  C = array(0, k)
  for (m=0; m<=k; m=m+1)
    C[m] = 0
  // end for
  for (j=1; j<=A.size; j=j+1)
    C[A[j]] = C[A[j]] + 1
  // end for
  i=0
  B = array(1, A.size)
  for (m=0; m<=k; m=m+1)
    while ( C[m]>0 )
    { 
      B[i] = m
      i = i+1
      C[m]=C[m]-1
    } // end while
  // end for
  return B
}

Hierbei wird ausgenutzt, dass es dann nicht notwendig ist, die Inhalte von A nach B zu kopieren - es genügt, die m-Abschnitte von B mit so vielen m-Ganzzahlen zu füllen, wie im jeweiligen C[m] vermerkt ist.

Beispiel[Bearbeiten]

Ausführung von Countingsort auf ein Eingabefeld A[1 .. 8] mit Elementen aus \{0, \ldots, 5\} mit Hilfsfeld C und sortierter Ausgabe in Feld B.

1 2 3 4 5 6 7 8
2 5 3 0 2 3 0 3
0 1 2 3 4 5
0 0 0 0 0 0
1 2 3 4 5 6 7 8
               

Darstellung untereinander: Ausgangsarray A, Hilfsarray C, dessen Länge vom Definitionsbereich der Array abhängt. In unterster Array werden die Elemente sortiert eingefügt.
Die obige Abbildung stellt die gegebene Zahlenfolge dar, wobei die erste Schleife des Algorithmus bereits abgearbeitet wurde, indem lediglich das Array C mit 0 initialisiert wird. Zweite Schleife inkrementiert für jede Ziffer deren Stelle im Array um eins.

1 2 3 4 5 6 7 8
2 5 3 0 2 3 0 3
0 1 2 3 4 5
2 0 2 3 0 1
1 2 3 4 5 6 7 8
               

Die dritte Schleife summiert das Array C auf, so dass dessen Inhalt angibt, bis zu welcher Position ein Wert in dem sortierten Array auftaucht. Zwei gleiche aufeinanderfolgende Zahlen bedeuten dabei, dass die letzte der beiden Zahlen in der Folge überhaupt nicht auftaucht, also vorher in C an dieser Position ein 0 gewesen war. In Array C stehen nun also statt der Anzahlen, wie oft ein Wert im Array A enthalten ist, stattdessen Endadressen (bzw. End-Indizes):

  • Der letzte Wert '5' aus Array A muss an Index 8 in das Zielarray B;
  • der Wert '4' kam in Array A nicht vor und hat daher dieselbe Endadresse wie der Wert '3';
  • die letzte '3' aus Array A muss an Index 7 in das Zielarray B - anschließend wird die Zieladresse C['3'] um 1 heruntergezählt, so dass die nächste '3' aus Array A den Zielindex 6 erhält und somit nach B[6] kopiert wird;
  • die letzte '2' aus Array A muss an Index 4 (also B[4]), weitere '2'er aus A müssen vor Index 4;
  • '1' kommt in A nicht vor, daher hat es dieselbe „letzte Zieladresse“ wie der Wert '0';
  • die letzte '0' aus Array A hat die Zieladresse C['0'], also 2. Sobald sie in das Array B einsortiert wurde (also nach B[2]), muss C['0'] um 1 heruntergezählt werden, damit die nächste '0', die in Array A gefunden wird, nach Index C['0']=1, also in das Feld B[1] kopiert wird.
1 2 3 4 5 6 7 8
2 5 3 0 2 3 0 3
0 1 2 3 4 5
2 2 4 7 7 8
1 2 3 4 5 6 7 8
               

Nun folgt die letzte Schleife. In dieser werden nun sukzessive die Werte aus A in das Array B übertragen und zwar genau an der Stelle im Zielarray, die das Hilfsarray C für die entsprechende Zahl angibt. Vor der Schleife ist dies immer die letzte Stelle, an der die Zahl auftauchen wird. Nach dem übertragen jeder Zahl wird zusätzlich der Wert in C[Zahl] dekrementiert. Die nächste gleiche Zahl wird deswegen eine Stelle weiter vorn im Zielarray eingefügt. Nachfolgend die 8 Schritte.

8. Schritt

1 2 3 4 5 6 7 8
2 5 3 0 2 3 0 3
0 1 2 3 4 5
0 2 2 4 7 7
1 2 3 4 5 6 7 8
0 0 2 2 3 3 3 5

Komplexität[Bearbeiten]

Laufzeitanalyse[Bearbeiten]

Wie man aus obigem Pseudocode leicht ersehen kann, hängt die Laufzeit der Funktion von n (Anzahl der Elemente des Eingabe(arrays)) und k (die Größe des Zahlenintervalls) ab. Die for-Schleifen werden jeweils n-mal oder k-mal durchlaufen. Die Zeitkomplexität von Countingsort beträgt also \mathcal{O}(n + k).

Speicherplatzbedarf[Bearbeiten]

Zusätzlich zur Ein- und Ausgabe, die jeweils n Speicherfelder benötigen (out-of-place), wird noch ein temporäres Array zur Speicherung der Häufigkeiten der Zahlenwerte angelegt. Dieses benötigt k Elemente Speicherplatz. Die Platzkomplexität von Countingsort liegt also in \mathcal{O}(n + k).

Literatur[Bearbeiten]

Weblinks[Bearbeiten]

 Wikibooks: Countingsort – Implementierungen in der Algorithmensammlung