Adaptiver Sortieralgorithmus
# Adaptiver Sortieralgorithmus

Adaptive Sortieralgorithmen sind Sortieralgorithmen, deren Kontrollfluss von den Eingabedaten abhängt. Insbesondere sind adaptive Sortieralgorithmen von Interesse, die auf Eingaben, die bereits über eine gewisse Ordnung verfügen, geringere Laufzeiten erzielen, als auf Eingaben ohne Struktur. Viele der bekannten adaptiven Sortieralgorithmen sind durch die Abwandlung bereits bekannter Algorithmen entstanden.

Da es in der Praxis oft vorkommt, dass Eingaben bereits beinahe sortiert sind, ist man an Algorithmen interessiert, die in diesen Fällen besonders schnell arbeiten. Das Ziel dabei ist es, die untere Schranke $O(n\log n)$ für den Average Case bei vergleichsbasierten Sortieralgorithmen zu unterbieten.

Bei der Laufzeitanalyse der adaptiven Verfahren wird neben der Eingabegröße auch ein Maß für die bereits vorhandene Ordnung in der Eingabe einbezogen – im Gegensatz zu klassischen vergleichsbasierten Sortierverfahren, bei denen nur zwischen Best Case, Average Case und Worst Case unterschieden wird, das Verhalten der Algorithmen zwischen diesen Fällen aber nicht weiter untersucht wird.

## Maße der Ordnung

Damit die bereits vorhandene Ordnung in der Eingabe in die Analyse des Algorithmus einfließen kann, muss diese Ordnung gemessen werden. Dafür haben sich mehrere verschiedene Maße etabliert.

Ein häufig verwendetes Maß ist die Anzahl der Fehlstände, also die Anzahl der Paare in falscher Ordnung, in der Eingabe $X=(x_{1},\dots ,x_{n})$:

    $\operatorname {Inv} (X)=|\left\{(i,j)\mid 1\leq i<j\leq n~{\text{und}}~x_{i}>x_{j}\right\}|$.

Weitere oft genutzte Maße sind:

* die Anzahl von Serien aufeinanderfolgender, ansteigender Elemente, sogenannter „runs“:

    $\operatorname {Runs} (X)=|\{\,i\,\mid 1\leq i<n~{\text{und}}~x_{i}>x_{i+1)\}|+1$
* die Anzahl von Elementen, die mindestens entfernt werden müssen, um eine sortierte Folge zu erhalten:

    $\operatorname {Rem} (X)=n-\max\{\,k\,\mid X~{\text{hat eine sortierte Teilfolge der Länge}}~k\}$
* die minimale Anzahl von Vertauschungen je zweier Elemente, um die Eingabe zu sortieren: $\operatorname {Exc} (X)$

### Beispiele

| Eingabe $X$ | $\operatorname {Inv} (X)$ | $\operatorname {Runs} (X)$ | $\operatorname {Rem} (X)$ | $\operatorname {Exc} (X)$ |
| - | - | - | - | - |
| $(1,2,3,4,5)$ | 0 | 1 | 0 | 0 |
| $(2,3,4,5,1)$ | 4 | 2 | 1 | 4 |
| $(4,3,2,1,5)$ | 6 | 4 | 3 | 2 |
| $(5,4,3,2,1)$ | 10 | 5 | 4 | 2 |

Verschiedene Maße führen nicht nur zu verschiedenen Werten, sondern auch zu unterschiedlichen Einschätzungen, welche Eingaben geordneter sind als andere.

Weitere Maße sind zum Beispiel zu finden in.

## Übersicht

Nicht bei allen der hier aufgeführten Algorithmen ist die Laufzeit als Funktion des Ordnungsmaßes angegeben. Allerdings hat man die Hoffnung, dass in Fällen von großer Ordnung in der Eingabe die Laufzeit dieser Algorithmen noch nahe am Best-Case ist.

| Sortierverfahren | Laufzeit |
| - | - |
| A-Sort | $O\left(n\log \left({\frac {\operatorname {Inv} (X)}{n}}\right)+n\right)$ (optimal) |
| Adaptive Heap Sort | $O\left(n\log \left({\frac {\operatorname {Inv} (X)}{n}}\right)+n\right)$ |
| AVL Sort | $O\left(n\log \left({\frac {\operatorname {Inv} (X)}{n}}\right)+n\right)$ |
| Natural Mergesort | $\Theta (n\log(\operatorname {Runs} (X)))$ |
| Patience Sorting | Best-Case: $O(n)$, bei sortierter Eingabe. Average-Case $O(n\log n)$ |
| Randomisierter Quicksort | $O\left(n\log \left({\frac {\operatorname {Inv} (X)}{n}}\right)+n\right)$ |
| Shellsort | Best-Case: $O(n\log n)$, bei sortierter Eingabe. Average-Case nicht in $O(n\log n)$. |
| Smoothsort | $O(n)$ für sortierte Eingaben. Erreicht $O\left(n\log \left({\frac {\operatorname {Inv} (X)}{n}}\right)+n\right)$ nicht. Worst-Case: $O(n\log n)$ |
| Splaysort | In Experimenten ab einer bestimmten Ordnung in der Eingabe schneller als Randomisierter Quicksort und AVL Sort. |
| Straight Insertionsort | $\Theta (n+\operatorname {Inv} (X))$ |
| Timsort | $O(n+n\log \operatorname {Runs} (X))$. Worst-Case: $O(n\log n)$ |

### A-Sort

Verwendet AVL-Bäume, in denen die Elemente abgelegt, und in sortierter Reihenfolge entnommen werden.

### Adaptive Heap Sort

Verwendet einen Kartesischen Baum, in den die Elemente eingefügt, und dann in sortierter Reihenfolge wieder entfernt werden.

### AVL Sort

Verwendet AVL-Bäume.

### Natural Mergesort

Natural Mergesort verwendet als Ausgangspunkt für die merge-Operationen nicht einelementige Teilfolgen, sondern die in der Eingabe bereits vorhandenen Runs (siehe oben). Falls das größte Element der einen Teilfolge kleiner ist als das kleinste der zweiten Teilfolge, so kann die merge-Operation durch eine entsprechende Konkatenation der beiden Teilfolgen ersetzt werden.

### Patience Sorting

Patience Sorting verläuft in zwei Phasen. In der ersten Phase werden Runs (siehe oben) erzeugt: jedes Element wird entweder dem Ende eines bereits angelegten Runs angefügt, oder, falls es keinen Run gibt, bei dem das möglich ist, bildet einen neuen Run. In der zweiten Phase wird ein k-Wege-Mischen auf die Runs angewendet, um eine sortierte Folge zu erhalten.

Beispiel:

| Schritt | Eingabe $X$ | Runs |
| - | - | - |
| 0 | $(3,5,4,2,1,7,6,8,9,10)$ | $\emptyset$ |
| 1 | $(5,4,2,1,7,6,8,9,10)$ | $(3)$ |
| 2 | $(4,2,1,7,6,8,9,10)$ | $(3,5)$ |
| 3 | $(2,1,7,6,8,9,10)$ | $(3,5),(4)$ |
| 4 | $(1,7,6,8,9,10)$ | $(3,5),(4),(2)$ |
| 5 | $(7,6,8,9,10)$ | $(3,5),(4),(2),(1)$ |
| 6 | $(6,8,9,10)$ | $(3,5,7),(4),(2),(1)$ |
| 7 | $(8,9,10)$ | $(3,5,7),(4,6),(2),(1)$ |
| 8 | $(9,10)$ | $(3,5,7,8),(4,6),(2),(1)$ |
| 9 | $(10)$ | $(3,5,7,8,9),(4,6),(2),(1)$ |
| 10 | $\emptyset$ | $(3,5,7,8,9,10),(4,6),(2),(1)$ |

### Randomisierter Quicksort

Im randomisierten Quicksort Algorithmus wird das Pivot-Element zufällig gleichverteilt gewählt.

### Shellsort

Shellsort teilt die Eingabe wiederholt in disjunkte Teilfolgen auf, deren Elemente in der Eingabe einen regelmäßigen Abstand haben, und sortiert diese Teilfolgen. Bei jeder Wiederholung wird der Abstand zwischen den Elementen verringert, die Teilfolgen werden damit länger, bis im letzten Schritt mit Abstand 1 die gesamten Daten sortiert werden. Basiert auf Insertionsort.

### Smoothsort

Benutzt Binärbäume.

### Splaysort

Benutzt Splay-Bäume.

### Straight Insertion Sort

Straight Insertion Sort ist eine Abwandlung von Insertionsort, siehe unten.

### Timsort

Timsort basiert auf Natural Mergesort und Insertionsort.

### Weitere Algorithmen mit adaptiven Eigenschaften

Bubblesort, Combsort, Insertionsort.

## Beispiel

### Straight Insertion Sort

Straight Insertion Sort ist eine Abwandlung von Insertionsort. Der Unterschied zwischen Insertion Sort und Straight Insertion Sort ist, dass bei Insertion Sort die Reihenfolge, in der das bereits Sortierte Array durchlaufen wird, beliebig gewählt werden kann, während bei Straight Insertion Sort vom größten Index zum kleinsten gesucht werden muss. Im Fall einer fast sortierten Eingabe wird so die while-Schleife selten durchlaufen.

Die Laufzeit von Straight Insertion Sort ist in $\Theta (n+\operatorname {Inv} (X))$. Damit hat Straight Insertion Sort auf einer bereits sortierten Eingabe die minimale Laufzeit $\Theta (n)$; um eine Liste auf Sortiertheit zu prüfen, muss jedes Element mindestens einmal betrachtet werden.

## Ausblick: AdaSort

Keiner der bekannten adaptiven Sortieralgorithmen ist in allen Fällen schneller als alle anderen. Die Laufzeit der Algorithmen hängt aufgrund ihrer Adaptivität stark von der Eingabe ab, und so ist in manchen Fällen der eine Algorithmus schneller, in anderen der andere. Welcher Algorithmus der schnellste für eine Eingabe ist, ist erst nach der Ausführung bekannt.

Es ist aber möglich die Laufzeiten vorab zu schätzen, und den Algorithmus auszuwählen mit der geringsten geschätzten Laufzeit, in der Hoffnung, dass dieser auch der tatsächlich schnellste Algorithmus für die Eingabe ist. Auf diese Weise kann aus den bekannten Algorithmen ein Algorithmus konstruiert werden, der für alle Eingaben, die zu einer guten Schätzung der Laufzeiten führen, so schnell sortiert, wie der jeweils schnellste Algorithmus, allerdings mit dem Zusatzaufwand für die Laufzeitschätzungen. Diese Schätzung kann mit Machine-Learning-Verfahren durchgeführt werden.

In wurde das Machine-Learning-Verfahren so trainiert, dass es in Abhängigkeit von zwei Parametern, RUNS und n, einen Sortieralgorithmus auswählt. Dabei ist

    $\operatorname {RUNS} (X)={\frac {\operatorname {Runs} (X)}{|X|}}$.

Nach erfolgtem Lernen der Entscheidungsgrenzen kann auf die Ausführung des Machine-Learning-Verfahrens für die Schätzung verzichtet werden, es genügt ein Entscheidungsbaum. Dieser komplexe Baum wurde noch weiter reduziert. Das Ergebnis von S. Majumdar, I. Jain und K. Kukreja sieht wie folgt aus:

Abschließende Experimente zeigen, dass AdaSort zuverlässig den schnellsten Algorithmus auswählt, und im Durchschnitt nur wenige Mikrosekunden langsamer ist als der jeweils schnellste Sortieralgorithmus. Diese Experimente waren allerdings an die Entscheidungsgrenzen für n im Algorithmus, also $n\in \{50,100,1000,10000,100000,500000,1000000\}$ angepasst, und auf Eingaben fast ohne Ordnung mit $\operatorname {RUNS} (X)\approx 0{,}8$ eingeschränkt. Da das Machine-Learning-Verfahren ebenfalls nur für diese n Entscheidungsgrenzen gelernt hat, ist damit nicht klar, wie gut AdaSort im Vergleich zu anderen Algorithmen für beispielsweise $n=357211,{\text{RUNS}}=0{,}001$ ist, also $\approx 357$ Runs mit durchschnittlicher Länge von $\approx 1000$. Es ist zu vermuten, dass für allgemeine $n$ und größere Ordnung in den Eingaben andere Entscheidungsgrenzen für andere Algorithmen gefunden werden können, mit denen AdaSort in diesen Fällen auch eine bessere durchschnittliche Laufzeit erzielt.

