Compare commits

..

10 commits

5 changed files with 654 additions and 255 deletions

325
BEDIENUNGSANLEITUNG.md Normal file
View file

@ -0,0 +1,325 @@
# Bedienungsanleitung — Sortier-Algorithmen Visualisierung
## Inhalt
1. [Was ist das?](#1-was-ist-das)
2. [Oberfläche](#2-oberfläche)
3. [Schnellstart](#3-schnellstart)
4. [Auswahlmöglichkeiten](#4-auswahlmöglichkeiten)
5. [Steuerung](#5-steuerung)
6. [Tastaturkürzel](#6-tastaturkürzel)
7. [Farbcode](#7-farbcode)
8. [Statistik-Karten](#8-statistik-karten)
9. [Speicher-Ansicht](#9-speicher-ansicht)
10. [URL-Parameter](#10-url-parameter)
11. [Warum sind Sortieralgorithmen wichtig?](#11-warum-sind-sortieralgorithmen-wichtig)
12. [Algorithmus-Übersicht](#12-algorithmus-übersicht)
---
## 1. Was ist das?
Dieses Programm visualisiert die Funktionsweise von **20 Sortieralgorithmen** — von Bubble Sort bis Fisher-Yates Shuffle. Es zeigt Schritt für Schritt, wie ein Algorithmus ein Array sortiert, macht die Datenstrukturen und Speichernutzung sichtbar und vermittelt ein intuitives Verständnis dafür, warum der eine Algorithmus schneller ist als der andere.
**Einschränkung:** Bogo Sort ist auf 8 Elemente begrenzt, da seine O(n·n!)-Komplexität bei größeren Arrays den Browser einfrieren lässt.
---
## 2. Oberfläche
```
┌──────────────────────────────────────────────────────────┐
│ Sortier-Algorithmen [Bedienung] │
├──────────────┬─────────────────────────────────────────────┤
│ │ ┌─ Phase-Label ──────────────────────┐ │
│ Algorithmus │ │ Vergleich: arr[3]=42 mit arr[4]=17 │ │
│ [Dropdown] │ └────────────────────────────────────┘ │
│ │ │
│ Daten │ ┌─────────── Canvas ─────────────────┐ │
│ [Vorlage] │ │ │ │
│ │ │ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ │ │
│ Eigene │ │ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ │ │
│ Werte │ │ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ │ │
│ │ │ │ │
│ Array-Größe │ └────────────────────────────────────┘ │
│ ─────────── │ │
│ Geschw. │ [▶][⏸][⏭][🔀][↺][🌙] │
│ ─────────── │ │
│ Legend │ Vergleiche: 128 Tausche: 64 Zeit: 1.2s │
│ │ │
│ [▶ Play] │ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ │
└──────────────┴─────────────────────────────────────────────┘
```
- **Links (Sidebar):** Algorithmus wählen, Datensatz, eigene Werte, Geschwindigkeit, Legende
- **Rechts (Hauptbereich):** Canvas mit Animation, Steuerung, Statistik-Karten
- **Unten:** Einklappbare Abschnitte „Über die Algorithmen" und „Bedienung"
---
## 3. Schnellstart
1. Öffne `sorting_visualization.html` im Browser
2. Wähle einen Algorithmus aus dem Dropdown (z.B. „Quick Sort")
3. Stelle Array-Größe und Geschwindigkeit ein
4. Drücke `Space` zum Starten — oder den Play-Button ▶
---
## 4. Auswahlmöglichkeiten
### Algorithmus
20 Algorithmen in 4 Kategorien:
| Kategorie | Algorithmen |
|---|---|
| O(n²) — Einfache Verfahren | Bubble Sort, Selection Sort, Insertion Sort, Cocktail Shaker Sort |
| O(n log n) — Effiziente Verfahren | Merge Sort, Heap Sort, Quick Sort, 3-Way Quick Sort, Dual-Pivot Quick Sort, Introsort, Shell Sort, Tree Sort, Timsort |
| O(n·k) — Linear / Nicht-vergleichsbasiert | Counting Sort, Radix Sort, Bucket Sort |
| Spezial | Pancake Sort, Cycle Sort, Bogo Sort, Fisher-Yates Shuffle |
### Datensatz-Vorlagen (Presets)
| Vorlage | Beschreibung |
|---|---|
| **Zufall** | Zufällig verteilte Werte — der Standard-Testfall |
| **Sortiert** | Bereits aufsteigend sortiert — manche Algorithmen (z.B. Quick Sort mit erstem Element als Pivot) werden dadurch extrem langsam |
| **Umgekehrt** | Absteigend sortiert — das Gegenstück und ein fairer Test für die meisten Algorithmen |
| **Fast sortiert** | Nur wenige Elemente an der falschen Stelle — adaptiven Algorithmen (Insertion Sort, Timsort) spielen hier ihre Stärke aus |
| **Duplikate** | Viele gleiche Werte — der Härtetest: klassischer Quick Sort degeneriert zu O(n²), 3-Way Quick Sort brilliert hier |
### Eigene Werte
Du kannst eigene Zahlen eingeben, getrennt durch Kommas, Leerzeichen oder Zeilenumbrüche. Beispiele:
```
5, 3, 8, 1, 9, 2
```
Über den Clipboard-Button kannst du auch direkt aus der Zwischenablage einfügen (Mehrfachpaste wird unterstützt). Die Anzahl der eingegebenen Werte wird oben rechts im Feld angezeigt.
**Wichtig:** Eigene Werte überschreiben das Preset. Sobald du eigene Werte eingibst, verwendet das Programm diese statt der Vorlage.
### Array-Größe
Schieberegler von 5 bis 200 Elemente. Wird zurückgesetzt, wenn du eigene Werte eingibst.
- **Klein (520):** Gut zum Verstehen der Abläufe
- **Mittel (3080):** Guter Kompromiss aus Übersicht und Realismus
- **Groß (100200):** Zeigt das Skalierungsverhalten
### Geschwindigkeit
Schieberegler von 1 bis 100 — er represents die Verzögerung zwischen zwei Schritten in Millisekunden:
- **1 (links):** ~1000 ms/Schritt — sehr langsam, zum Beobachten
- **50 (Mitte):** ~500 ms/Schritt — ausgewogen
- **100 (rechts):** ~10 ms/Schritt — sehr schnell
Die tatsächliche Geschwindigkeit hängt auch von der Browser-Leistung und der Array-Größe ab.
---
## 5. Steuerung
| Button | Funktion |
|---|---|
| **⏮** | Einen Schritt zurück — zeigt die vorherige Operation |
| **▶** | Animation starten — läuft bis zum Ende oder bis Pause |
| **⏸** | Animation anhalten — kann mit ▶ fortgesetzt werden |
| **⏭** | Einen Schritt vor — führt genau eine Operation aus |
| **🔀** | Mischen — Fisher-Yates Shuffle auf das aktuelle Array |
| **↺** | Reset — setzt das Array in den Ausgangszustand zurück (vor dem Start der Animation) |
| **🌙/☀** | Theme wechseln — zwischen hellem und dunklem Modus |
### Reset vs. Mischen
- **Reset** stellt das Array so her, wie es beim letzten Start der Animation war. Das kann ein frisches Zufalls-Array sein oder deine eigenen Werte.
- **Mischen** führt einen Fisher-Yates-Shuffle auf das aktuelle Array aus — nützlich, um ein sortiertes Array wieder zu mischen und einen anderen Algorithmus zu testen.
---
## 6. Tastaturkürzel
Alle Funktionen sind auch ohne Maus bedienbar:
| Taste | Funktion |
|---|---|
| `Space` / `K` | Play / Pause — Animation starten oder anhalten |
| `→` / `L` | Ein Schritt vor — nächste Operation |
| `←` / `J` | Ein Schritt zurück — vorherige Operation |
| `R` | Reset — Array zurücksetzen |
| `T` | Theme — zwischen hell und dunkel wechseln |
| `S` | Shuffle — Array mischen (Fisher-Yates) |
**Hinweis:** Tastaturkürzel funktionieren nicht, wenn ein Eingabefeld oder Dropdown den Fokus hat.
---
## 7. Farbcode
Jede Farbe hat eine feste Bedeutung:
| Farbe | Bedeutung |
|---|---|
| **Blau (Unsortiert)** | Normaler Zustand — Elemente die noch nicht sortiert sind |
| **Orange (Vergleich)** | Zwei Elemente werden gerade verglichen — ein Bogen verbindet sie |
| **Rot (Tausch)** | Zwei Elemente werden getauscht — ein roter Bogen zeigt die Tauschpartner |
| **Lila (Verschiebung)** | Ein Element wird verschoben (nicht getauscht) — z.B. bei Insertion Sort oder Merge Sort |
| **Grün (Sortiert/Fertig)** | Elemente an ihrer endgültigen Position — der Bereich wächst mit fortschreitender Sortierung |
Ein **Bogen** über zwei Balken zeigt immer eine Beziehung zwischen den beiden Elementen:
- Oranger Bogen = Vergleich
- Roter Bogen = Tausch
---
## 8. Statistik-Karten
Unter dem Canvas zeigen vier Karten Echtzeit-Statistiken:
| Karte | Beschreibung |
|---|---|
| **Zeit** | Vergangene Echtzeit seit Start der Animation in Millisekunden |
| **Schritte** | Anzahl der ausgeführten Schritte / Operationen |
| **Vergleiche** | Anzahl der Vergleichsoperationen (nur bei vergleichsbasierten Algorithmen) |
| **Tausche** | Anzahl der Tausch- oder Verschiebungsoperationen |
**Hinweis:** Nicht-vergleichsbasierte Algorithmen (Counting, Radix, Bucket) zeigen bei „Vergleiche" keine sinnvollen Werte, da sie nicht Element-für-Element vergleichen.
---
## 9. Speicher-Ansicht
Bei einigen Algorithmen wird unterhalb des Haupt-Arrays ein zusätzlicher Speicherbereich visualisiert. Das hilft zu verstehen, wie Algorithmen temporären Platz benötigen:
| Algorithmus | Speicher-Ansicht |
|---|---|
| **Merge Sort** | Zwei Hilfs-Arrays (blau und lila) — zeigt das Zusammenführen zweier sortierter Hälften |
| **Heap Sort** | Max-Heap als Baumdiagramm — zeigt die Hierarchie der Eltern-Kind-Beziehungen |
| **Tree Sort** | Binary Search Tree — zeigt die Baumstruktur die beim Sortieren aufgebaut wird |
| **Timsort** | Hilfs-Arrays — zeigt die verwalteten Runs und Merges |
| **Counting Sort** | Häufigkeitstabelle — zählt wie oft jeder Wert vorkommt |
| **Radix Sort** | Bucket-Verteilung — zeigt wie Ziffern in Buckets einsortiert werden |
| **Bucket Sort** | Bucket-Arrays — zeigt die Verteilung und Sortierung in den Buckets |
---
## 10. URL-Parameter
Der aktuelle Zustand (Algorithmus, Vorlage, Größe, Geschwindigkeit) wird automatisch in die URL geschrieben. Das bedeutet:
- Du kannst die URL kopieren und an andere weitergeben — sie sehen dieselbe Konfiguration
- Beim Neuladen der Seite bleibt die Auswahl erhalten
- Du kannst Parameter auch manuell setzen
**URL-Parameter:**
```
sorting_visualization.html?algo=quick&preset=random&size=50&speed=50
```
| Parameter | Werte | Beschreibung |
|---|---|---|
| `algo` | `bubble`, `selection`, `insertion`, `cocktail`, `merge`, `heap`, `quick`, `quick3way`, `dualpivot`, `introsort`, `shell`, `tree`, `timsort`, `counting`, `radix`, `bucket`, `pancake`, `cycle`, `bogo`, `shuffle` | Ausgewählter Algorithmus |
| `preset` | `random`, `sorted`, `reversed`, `nearly`, `duplicates` | Datensatz-Vorlage |
| `size` | 5200 | Array-Größe |
| `speed` | 1100 | Geschwindigkeit |
---
## 11. Warum sind Sortieralgorithmen wichtig?
Sortieren ist eines der am intensivsten erforschten Probleme der Informatik — nicht aus akademischer Überheblichkeit, sondern weil es in der Praxis überall vorkommt:
- **Datenbanken** müssen Abfrageergebnisse sortieren — bei Millionen von Datensätzen entscheidet der Algorithmus über Antwortzeiten
- **Betriebssysteme** verwalten Prioritätswarteschlangen und müssen Prozesse nach Dringlichkeit ordnen
- **Compiler** nutzen Sortieralgorithmen für die Optimierung (z.B. Register-Allokation)
- **Internetsuchen** beginnen oft mit sortierten Indices (z.B. B-Bäume in Datenbanken)
- **Empfehlungssysteme** sortieren Ergebnisse nach Relevanz
### Der fundamentale Kompromiss
Jeder Algorithmus repräsentiert einen anderen Kompromiss zwischen:
- **Geschwindigkeit** — Wie viele Operationen braucht der Algorithmus?
- **Speicher** — Wie viel zusätzlichen Platz benötigt er?
- **Stabilität** — Behalten gleiche Werte ihre Reihenfolge?
- **Adaptivität** — Nutzt er vorhandene Ordnung aus?
- **Einfachheit** — Wie verständlich ist der Code?
Wer diese Kompromisse versteht, kann für jede Situation die richtige Wahl treffen — sei es beim Sortieren von 100 Datensätzen auf einem Microcontroller oder 100 Millionen in einer Datenbank.
### Was diese Visualisierung lehrt
- **Bubble Sort** bei sortierten Daten: O(n) statt O(n²) — plötzliche Effizienz durch die „swapped"-Optimierung
- **Quick Sort** bei sortierten Daten mit erstem Pivot: O(n²) — warum naive Pivot-Wahl katastrophal sein kann
- **Timsort** bei fast-sortierten Daten: adaptiv und schnell — warum er in Python und Java Standard ist
- **3-Way Quick Sort** bei Duplikaten: O(n) statt O(n²) — warum Duplikate-Handling entscheidend ist
- **Fisher-Yates Shuffle**: Mischen ist O(n), Sortieren ist O(n log n) — ein Shuffle ist immer schneller als jede Sortierung
---
## 12. Algorithmus-Übersicht
### Eigenschaften
| Symbol | Bedeutung |
|---|---|
| **Stabil** | Gleiche Werte behalten ihre ursprüngliche Reihenfolge bei |
| **Nicht stabil** | Die Reihenfolge gleicher Werte kann sich ändern |
| **In-place** | Sortiert im vorhandenen Array, braucht nur O(1) zusätzlichen Speicher |
| **O(n) Speicher** | Benötigt ein zusätzliches Hilfsarray proportional zur Eingabegröße |
| **Adaptiv** | Nutzt vorhandene Ordnung aus — bei fast sortierten Daten schneller |
| **Hybrid** | Kombiniert mehrere Strategien und wechselt je nach Situation |
### Komplexitäts-Kategorien
**O(n²) — Quadratisch** (einfache Verfahren)
Diese Algorithmen sind leicht zu verstehen, aber skalieren schlecht. Geeignet für kleine Datenmengen oder Lehrzwecke.
- Bubble Sort — Stabil, In-place, adaptiv bei sortierten Daten
- Selection Sort — Nicht stabil, In-place, min. Schreiboperationen
- Insertion Sort — Stabil, In-place, adaptiv, der beste O(n²)
- Cocktail Shaker Sort — Stabil, In-place, löst das Schildkröten-Problem
**O(n log n) — Quasi-linear** (effiziente Verfahren)
Die Standardwahl für allgemeine Sortierprobleme. Garantieren gute Leistung auch im schlimmsten Fall.
- Merge Sort — Stabil, O(n) Speicher, garantiert O(n log n)
- Heap Sort — Nicht stabil, In-place, garantiert O(n log n)
- Quick Sort — Nicht stabil, In-place, O(n log n) im Durchschnitt
- 3-Way Quick Sort — Nicht stabil, In-place, O(n) bei vielen Duplikaten
- Dual-Pivot Quick Sort — Nicht stabil, In-place, Java Standard (JDK)
- Introsort — Nicht stabil, In-place, wechselt bei Rekursionstiefe zu Heap Sort
- Shell Sort — Nicht stabil, In-place, schneller als O(n²) durch Gap-Sequenz
- Tree Sort — Stabil (bei BST), O(n) Speicher, abhängig von Baumhöhe
- Timsort — Stabil, adaptiv, Hybrid aus Merge Sort und Insertion Sort, Python/Java Standard
**O(n·k) — Linear** (nicht-vergleichsbasiert)
Diese Algorithmen nutzen spezielle Eigenschaften der Daten (z.B. Ganzzahl-Bereich) und können schneller sein als O(n log n). Funktionieren nicht mit beliebigen Vergleichbaren Werten.
- Counting Sort — Stabil, O(n+k) mit k = Wertbereich, nur für Ganzzahlen
- Radix Sort — Stabil, O(n·k) mit k = Anzahl Ziffern, nur für Ganzzahlen
- Bucket Sort — Stabil, O(n+k) im Durchschnitt, adaptiv je nach Verteilung
**Spezial**
Algorithmen mit besonderen Eigenschaften oder Anwendungsfällen.
- Pancake Sort — Nicht stabil, In-place, nur Präfix-Flip-Operationen erlaubt
- Cycle Sort — Nicht stabil, In-place, min. Schreiboperationen (für Flash-Speicher)
- Bogo Sort — Nicht stabil, In-place, O(n·n!) erwartet, **auf 8 Elemente begrenzt**
- Fisher-Yates Shuffle — Stabil, In-place, O(n), uniform zufällig
---
## Lizenz
Dieses Projekt ist eine Open-Source-Lehr- und Lernressource.
---
## Autor
© 2026 Dieter Schlüter <dieter(dot)schlueter(at)linix(dot)de>

89
README.md Normal file
View file

@ -0,0 +1,89 @@
# Sortier-Algorithmen Visualisierung
Interaktive Visualisierung von 20 Sortieralgorithmen mit Schritt-für-Schritt-Darstellung, Speicher-Ansicht und Keyboard-Steuerung.
**Datei:** `sorting_visualization.html` — Single-File, keine Abhängigkeiten, läuft direkt im Browser.
[Online ansehen](sorting_visualization.html) · [Tests bestehen: 198/198](test_algorithms.js)
---
## Algorithmus-Übersicht
| Kategorie | Algorithmen |
|---|---|
| O(n²) | Bubble Sort, Selection Sort, Insertion Sort, Cocktail Shaker Sort |
| O(n log n) | Merge Sort, Heap Sort, Quick Sort, 3-Way Quick Sort, Dual-Pivot Quick Sort, Introsort, Shell Sort, Tree Sort, Timsort |
| Linear | Counting Sort, Radix Sort, Bucket Sort |
| Spezial | Pancake Sort, Cycle Sort, Bogo Sort, Fisher-Yates Shuffle |
---
## Features
- **20 Algorithmen** — vom klassischen Bubble Sort bis zum Fisher-Yates Shuffle
- **Speicher-Ansicht** — zeigt Hilfsdatenstrukturen bei Merge Sort, Heap Sort, Timsort, Counting/Radix/Bucket Sort
- **5 Datensatz-Presets** — Zufällig, Sortiert, Umgekehrt, Fast sortiert, Duplikate
- **Schritt-Navigation** — vor, zurück, Play/Pause
- **Keyboard-Steuerung** — alle Funktionen auch ohne Maus bedienbar
- **URL-State** — Auswahl bleibt beim Neuladen erhalten
- **Mobile optimiert** — Touch-Support, zoom-resistent
- **Hell/Dunkel-Modus** — per Button oder `T`
- **Barrierefreiheit** — ARIA-Labels, Screen-Reader-Ankündigungen
---
## Schnellstart
1. `sorting_visualization.html` im Browser öffnen
2. Algorithmus wählen → Start mit `Space`
3. Oder: Play-Button drücken
---
## Tastaturkürzel
| Taste | Funktion |
|---|---|
| `Space` / `K` | Play / Pause |
| `→` / `L` | Ein Schritt vor |
| `←` / `J` | Ein Schritt zurück |
| `R` | Reset |
| `T` | Theme wechseln |
| `S` | Mischen (Fisher-Yates) |
---
## Tests
```bash
node test_algorithms.js
```
Alle 198 Tests müssen bestehen.
---
## Dateien
```
sorting_visualization.html # Hauptprogramm (Single-File)
test_algorithms.js # Node.js Test-Suite
.gitignore # Git-Konfiguration
README.md # Dieses Dokument
```
---
## Entwicklung
- **Kein Build-Schritt** — pure HTML/JS/CSS
- **Keine externen Abhängigkeiten** — läuft offline
- **Externe CDN** (optional, für Icons): Lucide Icons, Tailwind CSS
- **Version** — wird automatisch via `pre-commit`-Hook erhöht
---
## Autor
© 2026 Dieter Schlüter <dieter(dot)schlueter(at)linix(dot)de>

View file

@ -1,23 +0,0 @@
import js from '@eslint/js';
import globals from 'globals';
export default [
js.configs.recommended,
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
ecmaVersion: 2022,
sourceType: 'module',
},
rules: {
'no-var': 'error',
'no-unused-vars': ['warn', { args: 'none' }],
'semi': ['error', 'always'],
'no-console': 'off',
'no-constant-condition': 'off',
},
},
];

View file

@ -2,7 +2,8 @@
<html lang="de" class="dark"> <html lang="de" class="dark">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="theme-color" content="#0b0d14">
<title>Sortier-Algorithmen Visualisierung</title> <title>Sortier-Algorithmen Visualisierung</title>
<!-- Tailwind Play CDN --> <!-- Tailwind Play CDN -->
@ -57,7 +58,12 @@
} }
/* ── Viewport-relative Fonts ── */ /* ── Viewport-relative Fonts ── */
body { font-size: clamp(14px, 1.6vw, 16px); } html, body {
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
overscroll-behavior: contain;
}
body { font-size: clamp(14px, 1.6vw, 16px); overflow-x: hidden; }
.title-text { font-size: clamp(1.25rem, 3.5vw, 2.25rem); } .title-text { font-size: clamp(1.25rem, 3.5vw, 2.25rem); }
.stat-value { font-size: clamp(1.5rem, 4vw, 2.25rem); } .stat-value { font-size: clamp(1.5rem, 4vw, 2.25rem); }
.label-text { font-size: clamp(9px, 1.2vw, 11px); } .label-text { font-size: clamp(9px, 1.2vw, 11px); }
@ -73,6 +79,7 @@
cursor: pointer; cursor: pointer;
touch-action: none; touch-action: none;
} }
input[type="range"]::-webkit-slider-runnable-track { input[type="range"]::-webkit-slider-runnable-track {
height: 6px; height: 6px;
background: var(--c-surface2); background: var(--c-surface2);
@ -197,7 +204,7 @@
<div class="flex-1"></div> <div class="flex-1"></div>
<div class="text-center"> <div class="text-center">
<h1 class="title-text font-bold tracking-tight" style="color: var(--c-text); text-shadow: 0 0 40px rgba(74,124,255,0.3);"> <h1 class="title-text font-bold tracking-tight" style="color: var(--c-text); text-shadow: 0 0 40px rgba(74,124,255,0.3);">
Sortier-Algorithmen <span style="font-size: 0.45em; font-weight: 400; opacity: 0.5; vertical-align: middle;">v0.2.8</span> Sortier-Algorithmen <span style="font-size: 0.45em; font-weight: 400; opacity: 0.5; vertical-align: middle;">v0.2.18</span>
</h1> </h1>
<p class="text-muted text-sm mt-0.5">Interaktive Visualisierung mit schrittweiser Animation</p> <p class="text-muted text-sm mt-0.5">Interaktive Visualisierung mit schrittweiser Animation</p>
</div> </div>
@ -240,9 +247,6 @@
<option value="tree">Tree Sort (BST)</option> <option value="tree">Tree Sort (BST)</option>
<option value="timsort">Timsort</option> <option value="timsort">Timsort</option>
</optgroup> </optgroup>
<optgroup label="Parallel — Netzwerk-Verfahren">
<option value="bitonic">Bitonic Sort</option>
</optgroup>
<optgroup label="O(n·k) — Nicht-vergleichsbasiert"> <optgroup label="O(n·k) — Nicht-vergleichsbasiert">
<option value="counting">Counting Sort</option> <option value="counting">Counting Sort</option>
<option value="radix">Radix Sort (LSD)</option> <option value="radix">Radix Sort (LSD)</option>
@ -252,6 +256,7 @@
<option value="pancake">Pancake Sort</option> <option value="pancake">Pancake Sort</option>
<option value="cycle">Cycle Sort</option> <option value="cycle">Cycle Sort</option>
<option value="bogo">Bogo Sort</option> <option value="bogo">Bogo Sort</option>
<option value="shuffle">Fisher-Yates Shuffle</option>
</optgroup> </optgroup>
</select> </select>
</div> </div>
@ -275,10 +280,14 @@
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="customArray" class="label-text uppercase tracking-wider text-muted font-medium flex items-center gap-1.5"> <label for="customArray" class="label-text uppercase tracking-wider text-muted font-medium flex items-center gap-1.5">
<i data-lucide="pencil" class="w-3.5 h-3.5"></i> Eigene Werte <i data-lucide="pencil" class="w-3.5 h-3.5"></i> Eigene Werte
<span id="customArrayCount" class="text-[10px] text-muted ml-auto normal-case tracking-normal font-mono"></span>
</label> </label>
<div class="flex gap-1.5"> <div class="flex gap-1.5">
<input type="text" id="customArray" placeholder="z.B. 5, 3, 8, 1" <input type="text" id="customArray" placeholder="z.B. 5, 3, 8, 1 oder Mehrfachpaste"
class="flex-1 min-w-0 bg-surface2 border border-border rounded-lg px-2 py-2 text-sm outline-none focus:border-accent focus:ring-1 focus:ring-accent/30 transition" style="color: var(--c-text);"> class="flex-1 min-w-0 bg-surface2 border border-border rounded-lg px-2 py-2 text-sm outline-none focus:border-accent focus:ring-1 focus:ring-accent/30 transition" style="color: var(--c-text);">
<button id="btnPasteCustom" class="px-2.5 min-w-[36px] flex items-center justify-center rounded-lg border border-border bg-surface2 hover:bg-accent hover:border-accent hover:text-white transition-colors" style="color: var(--c-text);" title="Aus Zwischenablage einfügen" aria-label="Aus Zwischenablage einfügen">
<i data-lucide="clipboard-paste" class="w-4 h-4"></i>
</button>
<button id="btnClearCustom" class="px-2.5 min-w-[36px] flex items-center justify-center rounded-lg border border-border bg-surface2 hover:bg-accent hover:border-accent hover:text-white transition-colors" style="color: var(--c-text);" title="Eigene Werte leeren"> <button id="btnClearCustom" class="px-2.5 min-w-[36px] flex items-center justify-center rounded-lg border border-border bg-surface2 hover:bg-accent hover:border-accent hover:text-white transition-colors" style="color: var(--c-text);" title="Eigene Werte leeren">
<i data-lucide="x" class="w-4 h-4"></i> <i data-lucide="x" class="w-4 h-4"></i>
</button> </button>
@ -296,16 +305,6 @@
</label> </label>
<input type="range" id="sizeSlider" min="5" max="200" value="50" class="w-full h-6"> <input type="range" id="sizeSlider" min="5" max="200" value="50" class="w-full h-6">
</div> </div>
<!-- Threads -->
<div class="space-y-1.5">
<label for="threadSlider" class="label-text uppercase tracking-wider text-muted font-medium flex items-center gap-1.5">
<i data-lucide="cpu" class="w-3.5 h-3.5"></i> Threads
<span id="threadVal" class="text-accent font-mono ml-auto">1</span>
</label>
<input type="range" id="threadSlider" min="1" max="1" value="1" class="w-full h-6" disabled>
</div>
<!-- Speed --> <!-- Speed -->
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="speedSlider" class="label-text uppercase tracking-wider text-muted font-medium flex items-center gap-1.5"> <label for="speedSlider" class="label-text uppercase tracking-wider text-muted font-medium flex items-center gap-1.5">
@ -318,7 +317,7 @@
<!-- Transport Buttons --> <!-- Transport Buttons -->
<section class="bg-surface border border-border rounded-xl p-4"> <section class="bg-surface border border-border rounded-xl p-4">
<div class="grid grid-cols-4 sm:grid-cols-5 gap-2"> <div class="grid grid-cols-5 gap-2">
<button id="btnStepBack" <button id="btnStepBack"
class="flex items-center justify-center bg-surface2 border border-border text-muted rounded-lg py-2.5 text-sm font-medium hover:bg-accent hover:border-accent hover:text-white active:scale-95 transition-all min-h-[44px]" class="flex items-center justify-center bg-surface2 border border-border text-muted rounded-lg py-2.5 text-sm font-medium hover:bg-accent hover:border-accent hover:text-white active:scale-95 transition-all min-h-[44px]"
aria-label="Schritt zurück" title="Schritt zurück"> aria-label="Schritt zurück" title="Schritt zurück">
@ -335,6 +334,11 @@
aria-label="Einzelschritt vorwärts" title="Einzelschritt"> aria-label="Einzelschritt vorwärts" title="Einzelschritt">
<i data-lucide="skip-forward" class="w-4 h-4"></i> <i data-lucide="skip-forward" class="w-4 h-4"></i>
</button> </button>
<button id="btnShuffle"
class="flex items-center justify-center bg-surface2 border border-border text-muted rounded-lg py-2.5 text-sm font-medium hover:bg-accent hover:border-accent hover:text-white active:scale-95 transition-all min-h-[44px]"
aria-label="Mischen" title="Mischen (Fisher-Yates)">
<i data-lucide="shuffle" class="w-4 h-4"></i>
</button>
<button id="btnReset" <button id="btnReset"
class="flex items-center justify-center bg-surface2 border border-border text-muted rounded-lg py-2.5 text-sm font-medium hover:bg-accent hover:border-accent hover:text-white active:scale-95 transition-all min-h-[44px]" class="flex items-center justify-center bg-surface2 border border-border text-muted rounded-lg py-2.5 text-sm font-medium hover:bg-accent hover:border-accent hover:text-white active:scale-95 transition-all min-h-[44px]"
aria-label="Zurücksetzen" title="Reset"> aria-label="Zurücksetzen" title="Reset">
@ -435,7 +439,7 @@
</summary> </summary>
<div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-0 space-y-5"> <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-0 space-y-5">
<p class="text-xs text-muted leading-relaxed"><strong class="text-gray-300">Speicher-Ansicht:</strong> Bei Tree Sort, Merge Sort, Timsort, Heap Sort, Counting Sort, Radix Sort, Bucket Sort und Bitonic Sort wird der Arbeitsspeicher unterhalb des Arrays visualisiert. Sie sehen genau, wo Daten zwischengespeichert werden &mdash; ob als Baumstruktur (BST, Heap), Hilfs-Arrays (Merge), H&auml;ufigkeitstabelle (Counting), Bucket-Verteilung (Radix, Bucket) oder Sortiernetz (Bitonic).</p> <p class="text-xs text-muted leading-relaxed"><strong class="text-gray-300">Speicher-Ansicht:</strong> Bei Tree Sort, Merge Sort, Timsort, Heap Sort, Counting Sort, Radix Sort und Bucket Sort wird der Arbeitsspeicher unterhalb des Arrays visualisiert. Sie sehen genau, wo Daten zwischengespeichert werden &mdash; ob als Baumstruktur (BST, Heap), Hilfs-Arrays (Merge), H&auml;ufigkeitstabelle (Counting) oder Bucket-Verteilung (Radix, Bucket).</p>
<!-- Legende --> <!-- Legende -->
<div class="bg-surface2/50 rounded-lg p-3 border border-border/50"> <div class="bg-surface2/50 rounded-lg p-3 border border-border/50">
@ -646,26 +650,6 @@
</div> </div>
<!-- Parallel --> <!-- Parallel -->
<div>
<h3 class="text-xs uppercase tracking-wider text-muted font-medium mb-3">Parallel — Netzwerk-Verfahren</h3>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
<div class="bg-surface2 rounded-lg p-4 info-card">
<h3 class="text-sm font-semibold text-accent mb-2">Bitonic Sort</h3>
<div class="flex flex-wrap gap-2 mb-2">
<span class="text-[10px] px-2 py-0.5 rounded-full bg-cSwap/10 text-cSwap border border-cSwap/20">Nicht stabil</span>
<span class="text-[10px] px-2 py-0.5 rounded-full bg-accent/10 text-accent border border-accent/20">In-place</span>
<span class="text-[10px] px-2 py-0.5 rounded-full bg-accent/10 text-accent border border-accent/20">Parallelisierbar</span>
<span class="text-[10px] px-2 py-0.5 rounded-full bg-surface border border-border text-gray-400">O(n log²n)</span>
</div>
<p class="text-xs text-muted leading-relaxed mb-2"><strong class="text-gray-300">Funktionsweise:</strong> Basiert auf dem Konzept der „bitonischen Sequenz" — einer Folge, die erst steigt und dann fällt (oder umgekehrt). Der Algorithmus arbeitet in Phasen: Zuerst werden Paare zu bitonischen Sequenzen der Länge 2 sortiert (abwechselnd aufsteigend/absteigend). Dann werden diese zu bitonischen Sequenzen der Länge 4 gemergt, dann 8, usw. Der „Bitonic Merge" vergleicht dabei Elemente mit festem Abstand und tauscht sie so, dass eine sortierte Sequenz entsteht. Das Vergleichsmuster ist datenunabhängig — es steht vor der Ausführung fest.</p>
<p class="text-xs text-muted leading-relaxed mb-2"><strong class="text-gray-300">Darstellung:</strong> Im Haupt-Canvas verbinden orange Bögen die verglichenen Positionen — sie springen in einem streng regelmäßigen Muster durch das Array, das sich von den Daten völlig unbeeindruckt zeigt; rote Bögen zeigen Tausche. Unterhalb erscheint das vollständige <strong class="text-gray-300">Sortiernetz</strong>: n horizontale Drähte repräsentieren die Array-Positionen, senkrechte Komparatoren verbinden jeweils die verglichenen Positionen. Vergangene Phasen leuchten gedimmt blau, die aktuelle orange mit Leuchteffekt, künftige Phasen sind abgedimmt — so ist das vollständige, datenunabhängige Vergleichsprogramm auf einen Blick sichtbar.</p>
<p class="text-xs text-muted leading-relaxed"><strong class="text-gray-300">Wann einsetzen:</strong> Der Algorithmus der Wahl für GPU-Sortierung und Hardware-Implementierungen (FPGAs, ASICs). Da das Vergleichsnetzwerk vollständig vorab feststeht und keine Datenabhängigkeiten hat, können alle Vergleiche einer Phase echt parallel ausgeführt werden. Auf einer GPU mit p Prozessoren erreicht Bitonic Sort O(n/p · log²n). Wird in CUDA-Bibliotheken, Grafik-Pipelines und überall dort eingesetzt, wo massiv parallele Hardware zur Verfügung steht. Die Einschränkung: Array-Größe muss eine Zweierpotenz sein (wird intern aufgefüllt).</p>
</div>
</div>
</div>
<!-- O(n·k) --> <!-- O(n·k) -->
<div> <div>
<h3 class="text-xs uppercase tracking-wider text-muted font-medium mb-3">O(n·k) — Nicht-vergleichsbasiert</h3> <h3 class="text-xs uppercase tracking-wider text-muted font-medium mb-3">O(n·k) — Nicht-vergleichsbasiert</h3>
@ -749,11 +733,114 @@
<span class="text-[10px] px-2 py-0.5 rounded-full bg-surface border border-border text-gray-400">O(n · n!) erwartet</span> <span class="text-[10px] px-2 py-0.5 rounded-full bg-surface border border-border text-gray-400">O(n · n!) erwartet</span>
</div> </div>
<p class="text-xs text-muted leading-relaxed mb-2"><strong class="text-gray-300">Funktionsweise:</strong> Der „dümmste" Sortieralgorithmus: Das Array wird zufällig gemischt (Fisher-Yates-Shuffle), dann wird geprüft, ob es jetzt zufällig sortiert ist. Wenn nicht, wird erneut gemischt. Das wird wiederholt, bis das Array durch Zufall in der richtigen Reihenfolge landet. Es gibt keine Garantie, dass das jemals passiert — im Erwartungswert braucht man n! Versuche (bei n = 8 sind das 40.320 Mischvorgänge).</p> <p class="text-xs text-muted leading-relaxed mb-2"><strong class="text-gray-300">Funktionsweise:</strong> Der „dümmste" Sortieralgorithmus: Das Array wird zufällig gemischt (Fisher-Yates-Shuffle), dann wird geprüft, ob es jetzt zufällig sortiert ist. Wenn nicht, wird erneut gemischt. Das wird wiederholt, bis das Array durch Zufall in der richtigen Reihenfolge landet. Es gibt keine Garantie, dass das jemals passiert — im Erwartungswert braucht man n! Versuche (bei n = 8 sind das 40.320 Mischvorgänge).</p>
<p class="text-xs text-muted leading-relaxed mb-2"><strong class="text-gray-300">Darstellung:</strong> Bogo Sort erzeugt ein hektisches Blitz-Muster: Das gesamte Array blinkt rot auf (Shuffle), dann wandern kurze orange Bögen von links nach rechts durch das Array (Sortiertheitscheck). Scheitert die Prüfung, folgt sofort der nächste rote Shuffle. Die kleinen Wertemarken unter den Balken zeigen bei jedem Shuffle-Versuch eine neue zufällige Anordnung. Der Fortschrittsbalken bewegt sich kaum — tausende Schritte bei nur 8 Elementen zeigen eindrücklich, wie nutzlos rein zufälliges Ausprobieren ist. Die Array-Größe ist automatisch auf 8 begrenzt.</p> <p class="text-xs text-muted leading-relaxed mb-2"><strong class="text-gray-300">Darstellung:</strong> Bogo Sort erzeugt ein hektisches Blitz-Muster: Das gesamte Array blinkt rot auf (Shuffle), dann wandern kurze orange Bögen von links nach rechts durch das Array (Sortiertheitscheck). Scheitert die Prüfung, folgt sofort der nächste rote Shuffle. Die kleinen Wertemarken unter den Balken zeigen bei jedem Shuffle-Versuch eine neue zufällige Anordnung. Der Fortschrittsbalken bewegt sich kaum — tausende Schritte bei nur 8 Elementen zeigen eindrücklich, wie nutzlos rein zufälliges Probieren ist. Die Array-Größe ist automatisch auf 8 begrenzt.</p>
<p class="text-xs text-muted leading-relaxed"><strong class="text-gray-300">Wann einsetzen:</strong> Niemals in der Praxis. Bogo Sort dient als Lehr- und Abschreckungsbeispiel: Er zeigt eindrücklich, warum zufälliges Probieren exponentiell schlecht skaliert. Bei nur 8 Elementen braucht er im Schnitt ~40.000 Versuche, bei 10 Elementen schon ~3,6 Millionen. Der Name ist ein Wortspiel auf „bogus" (falsch/wertlos). In der theoretischen Informatik dient er als Referenz für die schlechteste denkbare Komplexitätsklasse. Die Array-Größe wird hier automatisch auf 8 begrenzt, um den Browser nicht einzufrieren.</p> <p class="text-xs text-muted leading-relaxed"><strong class="text-gray-300">Wann einsetzen:</strong> Niemals in der Praxis. Bogo Sort dient als Lehr- und Abschreckungsbeispiel: Er zeigt eindrücklich, warum zufälliges Probieren exponentiell schlecht skaliert. Bei nur 8 Elementen braucht er im Schnitt ~40.000 Versuche, bei 10 Elementen schon ~3,6 Millionen. Der Name ist ein Wortspiel auf „bogus" (falsch/wertlos). In der theoretischen Informatik dient er als Referenz für die schlechteste denkbare Komplexitätsklasse. Die Array-Größe wird hier automatisch auf 8 begrenzt, um den Browser nicht einzufrieren.</p>
</div> </div>
<div class="bg-surface2 rounded-lg p-4 info-card">
<h3 class="text-sm font-semibold text-accent mb-2">Fisher-Yates Shuffle</h3>
<div class="flex flex-wrap gap-2 mb-2">
<span class="text-[10px] px-2 py-0.5 rounded-full bg-accent/10 text-accent border border-accent/20">Stabil</span>
<span class="text-[10px] px-2 py-0.5 rounded-full bg-accent/10 text-accent border border-accent/20">In-place</span>
<span class="text-[10px] px-2 py-0.5 rounded-full bg-surface border border-border text-gray-400">O(n) Zeit · O(1) Speicher</span>
</div> </div>
<p class="text-xs text-muted leading-relaxed mb-2"><strong class="text-gray-300">Funktionsweise:</strong> Der Fisher-Yates-Algorithmus (auch Knuth-Shuffle) mischt ein Array uniform zufällig. Er läuft rückwärts: Für jede Position i wird ein zufälliger Index j zwischen 0 und i gewählt und die beiden Elemente getauscht. Jede der n! Permutationen wird mit gleicher Wahrscheinlichkeit erzeugt.</p>
<p class="text-xs text-muted leading-relaxed mb-2"><strong class="text-gray-300">Darstellung:</strong> Rote Bögen zeigen jeden Tausch — keine Vergleiche, nur Tausche. Die Anzahl der Tausche ist immer n-1 (bei n Elementen). Das macht die Effizienz des Mischens im Vergleich zum Sortieren direkt sichtbar.</p>
<p class="text-xs text-muted leading-relaxed"><strong class="text-gray-300">Wann einsetzen:</strong> Zum Erzeugen von Testdaten, zum „Nochmal-Mischen" eines sortierten Arrays, oder als Gegenbeweis: Ein einziger Shuffle macht jede Sortierung rückgängig. Mischen ist O(n), Sortieren ist O(n log n) — also ist Mischen immer schneller als Sortieren.</p>
</div>
</div>
</div>
</div>
</details>
<!-- Bedienung (collapsible) -->
<details class="bg-surface border border-border rounded-xl group">
<summary class="p-4 sm:p-5 cursor-pointer select-none flex items-center gap-2 text-sm font-semibold text-gray-200 hover:text-white transition-colors">
<i data-lucide="gamepad-2" class="w-4 h-4 text-accent"></i>
Bedienung
<i data-lucide="chevron-down" class="w-4 h-4 text-muted ml-auto chevron-icon"></i>
</summary>
<div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-0 space-y-5">
<p class="text-xs text-muted leading-relaxed"><strong class="text-gray-300">Was ist das?</strong> Dieses Programm visualisiert die Funktionsweise von 20 Sortieralgorithmen — von Bubble Sort bis Fisher-Yates Shuffle. Es zeigt Schritt für Schritt, wie ein Algorithmus ein Array sortiert, macht die Datenstrukturen und Speichernutzung sichtbar und vermittelt ein intuitives Verständnis dafür, warum der eine Algorithmus schneller ist als der andere.</p>
<!-- Quick-Start -->
<div class="bg-surface2/50 rounded-lg p-3 border border-border/50">
<p class="text-[11px] uppercase tracking-wider text-muted font-medium mb-2">Quick-Start</p>
<ol class="text-xs text-muted leading-relaxed space-y-1.5">
<li class="flex gap-2"><span class="text-accent font-bold shrink-0">1.</span> <span>Wähle einen Algorithmus aus dem Dropdown-Menü.</span></li>
<li class="flex gap-2"><span class="text-accent font-bold shrink-0">2.</span> <span>Stelle die Array-Größe und Geschwindigkeit ein.</span></li>
<li class="flex gap-2"><span class="text-accent font-bold shrink-0">3.</span> <span>Drücke <span class="inline-block px-1.5 py-0.5 rounded bg-accent/10 text-accent border border-accent/20 text-[10px] font-mono">Space</span> zum Starten — oder nutze die Schritt-für-Schritt-Navigation.</span></li>
</ol>
</div>
<!-- Steuerung -->
<div class="bg-surface2/50 rounded-lg p-3 border border-border/50">
<p class="text-[11px] uppercase tracking-wider text-muted font-medium mb-2">Steuerung</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-2 text-xs text-muted leading-relaxed">
<div class="flex items-center gap-2">
<span class="inline-block px-1.5 py-0.5 rounded bg-accent/10 text-accent border border-accent/20 text-[10px] font-mono shrink-0"></span>
<span>Play / Pause — Animation starten oder anhalten</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-block px-1.5 py-0.5 rounded bg-accent/10 text-accent border border-accent/20 text-[10px] font-mono shrink-0"></span>
<span>Ein Schritt vor — nächste Operation</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-block px-1.5 py-0.5 rounded bg-accent/10 text-accent border border-accent/20 text-[10px] font-mono shrink-0"></span>
<span>Ein Schritt zurück — vorherige Operation</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-block px-1.5 py-0.5 rounded bg-accent/10 text-accent border border-accent/20 text-[10px] font-mono shrink-0"></span>
<span>Reset — Array in Ausgangszustand zurücksetzen</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-block px-1.5 py-0.5 rounded bg-accent/10 text-accent border border-accent/20 text-[10px] font-mono shrink-0">🔀</span>
<span>Mischen — Fisher-Yates Shuffle auf aktuelles Array</span>
</div>
<div class="flex items-center gap-2">
<span class="inline-block px-1.5 py-0.5 rounded bg-accent/10 text-accent border border-accent/20 text-[10px] font-mono shrink-0">🌙/☀</span>
<span>Theme — zwischen hellem und dunklem Modus wechseln</span>
</div>
</div>
</div>
<!-- Tastaturkürzel -->
<div class="bg-surface2/50 rounded-lg p-3 border border-border/50">
<p class="text-[11px] uppercase tracking-wider text-muted font-medium mb-2">Tastaturkürzel</p>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-x-4 gap-y-1.5 text-xs text-muted leading-relaxed">
<div class="flex items-center gap-1.5"><kbd class="px-1.5 py-0.5 rounded bg-surface border border-border text-[10px] font-mono text-gray-300">Space</kbd> <span>Play/Pause</span></div>
<div class="flex items-center gap-1.5"><kbd class="px-1.5 py-0.5 rounded bg-surface border border-border text-[10px] font-mono text-gray-300">K</kbd> <span>Play/Pause</span></div>
<div class="flex items-center gap-1.5"><kbd class="px-1.5 py-0.5 rounded bg-surface border border-border text-[10px] font-mono text-gray-300"></kbd> <span>Schritt zurück</span></div>
<div class="flex items-center gap-1.5"><kbd class="px-1.5 py-0.5 rounded bg-surface border border-border text-[10px] font-mono text-gray-300">J</kbd> <span>Schritt zurück</span></div>
<div class="flex items-center gap-1.5"><kbd class="px-1.5 py-0.5 rounded bg-surface border border-border text-[10px] font-mono text-gray-300"></kbd> <span>Schritt vor</span></div>
<div class="flex items-center gap-1.5"><kbd class="px-1.5 py-0.5 rounded bg-surface border border-border text-[10px] font-mono text-gray-300">L</kbd> <span>Schritt vor</span></div>
<div class="flex items-center gap-1.5"><kbd class="px-1.5 py-0.5 rounded bg-surface border border-border text-[10px] font-mono text-gray-300">R</kbd> <span>Reset</span></div>
<div class="flex items-center gap-1.5"><kbd class="px-1.5 py-0.5 rounded bg-surface border border-border text-[10px] font-mono text-gray-300">T</kbd> <span>Theme</span></div>
<div class="flex items-center gap-1.5"><kbd class="px-1.5 py-0.5 rounded bg-surface border border-border text-[10px] font-mono text-gray-300">S</kbd> <span>Shuffle</span></div>
</div>
</div>
<!-- Presets -->
<div class="bg-surface2/50 rounded-lg p-3 border border-border/50">
<p class="text-[11px] uppercase tracking-wider text-muted font-medium mb-2">Datensätze</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-1.5 text-xs text-muted leading-relaxed">
<div><span class="text-gray-300 font-medium">Zufällig</span> — zufällig verteilte Werte, der Standard-Testfall.</div>
<div><span class="text-gray-300 font-medium">Sortiert</span> — bereits in aufsteigender Reihenfolge. Manche Algorithmen (z.B. Quick Sort mit erstem Element als Pivot) werden dadurch extrem langsam.</div>
<div><span class="text-gray-300 font-medium">Umgekehrt</span> — absteigend sortiert. Das Gegenstück zu „Sortiert" und ein公平er Test für die meisten Algorithmen.</div>
<div><span class="text-gray-300 font-medium">Fast sortiert</span> — nur wenige Elemente sind an der falschen Stelle. Adaptiven Algorithmen (Insertion Sort, Timsort) spielen hier ihre Stärke aus.</div>
<div><span class="text-gray-300 font-medium">Duplikate</span> — viele gleiche Werte. Das ist der Härtetest für Algorithmen wie Quick Sort (degeneriert zu O(n²)), während 3-Way Quick Sort hier brilliert.</div>
</div>
</div>
<!-- Warum Sortieralgorithmen? -->
<div class="bg-surface2/50 rounded-lg p-3 border border-border/50">
<p class="text-[11px] uppercase tracking-wider text-muted font-medium mb-2">Warum sind Sortieralgorithmen wichtig?</p>
<p class="text-xs text-muted leading-relaxed mb-2">Sortieren ist eines der am intensivsten erforschten Probleme der Informatik — nicht aus akademischer Überheblichkeit, sondern weil es in der Praxis überall vorkommt: Datenbankabfragen müssen Ergebnisse sortieren, Betriebssysteme verwalten Prioritätswarteschlangen, Compiler nutzen Sortieralgorithmen für die Optimierung. Die Wahl des richtigen Algorithmus kann den Unterschied ausmachen zwischen einer Antwort in Millisekunden und in Minuten.</p>
<p class="text-xs text-muted leading-relaxed mb-2">Jeder Algorithmus repräsentiert einen grundlegenden Kompromiss: Manche sind einfach zu verstehen (Bubble Sort), andere extrem schnell im Durchschnitt (Quick Sort), wieder andere garantieren auch im schlimmsten Fall gute Leistung (Heap Sort). Wer diese Kompromisse versteht, kann für jede Situation die richtige Wahl treffen — sei es beim Sortieren von 100 Datensätzen oder 100 Millionen.</p>
<p class="text-xs text-muted leading-relaxed">Dieses Programm hilft dabei, ein intuitives Gespür für diese Kompromisse zu entwickeln. Beobachten Sie, wie Bubble Sort bei sortierten Daten plötzlich in O(n) abschließt, während er bei umgekehrt sortierten Daten genauso langsam ist wie immer. Oder wie Timsort die Ordnung in fast-sortierten Daten sofort erkennt und ausnutzt. Visuelles Lernen macht sichtbar, was Paper und Pseudocode nur schwer vermitteln.</p>
</div> </div>
</div> </div>
@ -809,12 +896,12 @@ const $algoSelect = document.getElementById('algoSelect');
const $presetSelect = document.getElementById('presetSelect'); const $presetSelect = document.getElementById('presetSelect');
const $customArray = document.getElementById('customArray'); const $customArray = document.getElementById('customArray');
const $btnClearCustom = document.getElementById('btnClearCustom'); const $btnClearCustom = document.getElementById('btnClearCustom');
const $btnPasteCustom = document.getElementById('btnPasteCustom');
const $customArrayCount = document.getElementById('customArrayCount');
const $sizeSlider = document.getElementById('sizeSlider'); const $sizeSlider = document.getElementById('sizeSlider');
const $speedSlider = document.getElementById('speedSlider'); const $speedSlider = document.getElementById('speedSlider');
const $threadSlider = document.getElementById('threadSlider');
const $sizeVal = document.getElementById('sizeVal'); const $sizeVal = document.getElementById('sizeVal');
const $speedVal = document.getElementById('speedVal'); const $speedVal = document.getElementById('speedVal');
const $threadVal = document.getElementById('threadVal');
const $statComp = document.getElementById('statCompares'); const $statComp = document.getElementById('statCompares');
const $statSwap = document.getElementById('statSwaps'); const $statSwap = document.getElementById('statSwaps');
const $statMove = document.getElementById('statMoves'); const $statMove = document.getElementById('statMoves');
@ -824,6 +911,7 @@ const $btnPlayPause = document.getElementById('btnPlayPause');
const $btnStepBack = document.getElementById('btnStepBack'); const $btnStepBack = document.getElementById('btnStepBack');
const $btnStep = document.getElementById('btnStep'); const $btnStep = document.getElementById('btnStep');
const $btnReset = document.getElementById('btnReset'); const $btnReset = document.getElementById('btnReset');
const $btnShuffle = document.getElementById('btnShuffle');
const $btnTheme = document.getElementById('btnTheme'); const $btnTheme = document.getElementById('btnTheme');
const $progressFill = document.getElementById('progressFill'); const $progressFill = document.getElementById('progressFill');
const $stepCounter = document.getElementById('stepCounter'); const $stepCounter = document.getElementById('stepCounter');
@ -862,24 +950,10 @@ function updateSizeLabel() {
$sizeVal.textContent = $sizeSlider.value; $sizeVal.textContent = $sizeSlider.value;
} }
// Parallele Algorithmen: Bitonic Sort // ================================================================
const PARALLEL_ALGOS = new Set(['bitonic']);
// Algorithmen mit Memory-Visualisierung // Algorithmen mit Memory-Visualisierung
// ================================================================
const MEM_ALGOS = new Set(['tree', 'merge', 'timsort', 'heap', 'counting', 'radix', 'bucket']); const MEM_ALGOS = new Set(['tree', 'merge', 'timsort', 'heap', 'counting', 'radix', 'bucket']);
const MAX_THREADS = navigator.hardwareConcurrency || 4;
function updateThreadSlider() {
const isParallel = PARALLEL_ALGOS.has($algoSelect.value);
$threadSlider.disabled = !isParallel;
if (isParallel) {
$threadSlider.max = String(MAX_THREADS);
} else {
$threadSlider.max = '1';
$threadSlider.value = '1';
}
$threadVal.textContent = $threadSlider.value;
}
function setStats(compares, swaps, moves, step) { function setStats(compares, swaps, moves, step) {
$statComp.textContent = String(compares); $statComp.textContent = String(compares);
@ -1747,97 +1821,6 @@ function buildSteps(algoName) {
break; break;
} }
case 'bitonic': {
const n = arr.length;
// Arbeite auf separatem Array mit Zweierpotenz-Länge
let size = 1;
while (size < n) size <<= 1;
// Padding: eindeutige Werte oberhalb des Maximums (alle > origMax → Sortierung korrekt,
// aber unterschiedlich → Balken visuell unterscheidbar wenn sie in sichtbare Positionen geraten)
let origMax = 0;
for (let i = 0; i < n; i++) { if (arr[i] > origMax) origMax = arr[i]; }
const bArr = arr.slice();
let padLo = origMax * 1.05 + 1;
let padHi = origMax * 1.25 + 1;
let padCount = size - n;
for (let i = n; i < size; i++) {
bArr.push(padCount > 1 ? padLo + (padHi - padLo) * (i - n) / (padCount - 1) : padLo);
}
// Vorberechnung des Sortiernetzes (datenunabhängig)
const allStages = [];
function collectMergeStages(lo, cnt) {
if (cnt <= 1) return;
let half = cnt >> 1;
let stage = [];
for (let i = lo; i < lo + half; i++) {
if (i < n && (i + half) < n) stage.push([i, i + half]);
}
if (stage.length > 0) allStages.push(stage);
collectMergeStages(lo, half);
collectMergeStages(lo + half, half);
}
function collectSortStages(lo, cnt) {
if (cnt <= 1) return;
let half = cnt >> 1;
collectSortStages(lo, half);
collectSortStages(lo + half, half);
collectMergeStages(lo, cnt);
}
collectSortStages(0, size);
// Stage-Index: synchron mit bitonicMerge hochgezählt
let snapStageId = 0;
// Snap-Wrapper: zeigt nur die ersten n Elemente + Netz-Zustand
function bSnap(type, indices) {
let visIdx = indices.filter(function(idx) { return idx < n; });
for (let k = 0; k < n; k++) arr[k] = bArr[k];
out.push({
type: type, indices: visIdx, array: arr.slice(),
compares: compares, swaps: swaps, moves: moves,
mem: { type: 'bitonic_network', stages: allStages, currentStage: snapStageId, n: n }
});
}
function bitonicCompareSwap(i, j, dir) {
if (i < n && j < n) {
compares++;
bSnap('compare', [i, j]);
}
if ((bArr[i] > bArr[j]) === dir) {
let tmp = bArr[i]; bArr[i] = bArr[j]; bArr[j] = tmp;
if (i < n && j < n) {
swaps++;
bSnap('swap', [i, j]);
}
}
}
function bitonicMerge(lo, cnt, dir) {
if (cnt <= 1) return;
let half = cnt >> 1;
let hasVisible = false;
for (let i = lo; i < lo + half; i++) {
if (i < n && (i + half) < n) hasVisible = true;
bitonicCompareSwap(i, i + half, dir);
}
if (hasVisible) snapStageId++; // nächste Phase
bitonicMerge(lo, half, dir);
bitonicMerge(lo + half, half, dir);
}
function bitonicSortRec(lo, cnt, dir) {
if (cnt <= 1) return;
let half = cnt >> 1;
bitonicSortRec(lo, half, true);
bitonicSortRec(lo + half, half, false);
bitonicMerge(lo, cnt, dir);
}
bitonicSortRec(0, size, true);
// Ergebnis zurückkopieren (nur die ersten n Elemente, Padding verwerfen)
for (let i = 0; i < n; i++) arr[i] = bArr[i];
break;
}
case 'bucket': { case 'bucket': {
const n = arr.length; const n = arr.length;
if (n === 0) break; if (n === 0) break;
@ -1883,6 +1866,21 @@ function buildSteps(algoName) {
break; break;
} }
case 'shuffle': {
const n = arr.length;
if (n === 0) break;
// Fisher-Yates Shuffle (Knuth variant)
for (let i = n - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
if (j !== i) {
[arr[i], arr[j]] = [arr[j], arr[i]];
swaps++;
snap('swap', [i, j]);
}
}
break;
}
case 'pancake': { case 'pancake': {
const n = arr.length; const n = arr.length;
function flip(end) { function flip(end) {
@ -2260,7 +2258,6 @@ function drawMemory(state) {
else if (state.type === 'counting') drawCountingArray(state, W, H); else if (state.type === 'counting') drawCountingArray(state, W, H);
else if (state.type === 'radix') drawRadixBuckets(state, W, H); else if (state.type === 'radix') drawRadixBuckets(state, W, H);
else if (state.type === 'buckets') drawBuckets(state, W, H); else if (state.type === 'buckets') drawBuckets(state, W, H);
else if (state.type === 'bitonic_network') drawBitonicNetwork(state, W, H);
} }
function drawBST(state, W, H) { function drawBST(state, W, H) {
@ -2526,67 +2523,6 @@ function drawBuckets(state, W, H) {
} }
} }
function drawBitonicNetwork(state, W, H) {
let stages = state.stages, cur = state.currentStage, n = state.n;
let numS = stages.length;
if (numS === 0 || n < 2) return;
let PAD_L = 24, PAD_R = 8, PAD_T = 10, PAD_B = 10;
let wireSpacing = (H - PAD_T - PAD_B) / Math.max(n - 1, 1);
let stageW = (W - PAD_L - PAD_R) / numS;
// Horizontale Drähte
for (let w = 0; w < n; w++) {
let wy = PAD_T + w * wireSpacing;
memCtx.strokeStyle = 'rgba(100,120,160,0.3)';
memCtx.lineWidth = 1;
memCtx.beginPath();
memCtx.moveTo(PAD_L, wy);
memCtx.lineTo(W - PAD_R, wy);
memCtx.stroke();
}
// Index-Labels links
memCtx.fillStyle = 'rgba(140,160,190,0.5)';
memCtx.font = '500 9px Inter, sans-serif';
memCtx.textAlign = 'right';
memCtx.textBaseline = 'middle';
for (let w = 0; w < n; w++) {
memCtx.fillText(String(w), PAD_L - 4, PAD_T + w * wireSpacing);
}
// Komparatoren
for (let s = 0; s < numS; s++) {
let cx = PAD_L + (s + 0.5) * stageW;
let isCur = (s === cur);
let isPast = (s < cur);
let color = isCur ? COLORS.compare
: isPast ? 'rgba(60,160,255,0.35)'
: 'rgba(150,170,200,0.15)';
let lw = isCur ? 2 : 1;
let R = isCur ? 3 : 2;
for (let ci = 0; ci < stages[s].length; ci++) {
let a = stages[s][ci][0], b = stages[s][ci][1];
let y1 = PAD_T + a * wireSpacing, y2 = PAD_T + b * wireSpacing;
if (isCur) {
memCtx.save();
memCtx.shadowColor = COLORS.compare;
memCtx.shadowBlur = 8;
memCtx.strokeStyle = color; memCtx.lineWidth = lw;
memCtx.beginPath(); memCtx.moveTo(cx, y1); memCtx.lineTo(cx, y2); memCtx.stroke();
memCtx.restore();
} else {
memCtx.strokeStyle = color; memCtx.lineWidth = lw;
memCtx.beginPath(); memCtx.moveTo(cx, y1); memCtx.lineTo(cx, y2); memCtx.stroke();
}
memCtx.fillStyle = color;
memCtx.beginPath(); memCtx.arc(cx, y1, R, 0, Math.PI * 2); memCtx.fill();
memCtx.beginPath(); memCtx.arc(cx, y2, R, 0, Math.PI * 2); memCtx.fill();
}
}
}
// ================================================================ // ================================================================
// Memory View: Label + Resize (Label nur bei Typwechsel, Resize immer) // Memory View: Label + Resize (Label nur bei Typwechsel, Resize immer)
// ================================================================ // ================================================================
@ -2614,8 +2550,6 @@ function updateMemView(memState) {
$memLabel.innerHTML = '<i data-lucide="layers" class="w-3.5 h-3.5"></i> Arbeitsspeicher: Radix-Buckets (10 Ziffern)'; $memLabel.innerHTML = '<i data-lucide="layers" class="w-3.5 h-3.5"></i> Arbeitsspeicher: Radix-Buckets (10 Ziffern)';
} else if (memState.type === 'buckets') { } else if (memState.type === 'buckets') {
$memLabel.innerHTML = '<i data-lucide="inbox" class="w-3.5 h-3.5"></i> Arbeitsspeicher: Bucket-Verteilung'; $memLabel.innerHTML = '<i data-lucide="inbox" class="w-3.5 h-3.5"></i> Arbeitsspeicher: Bucket-Verteilung';
} else if (memState.type === 'bitonic_network') {
$memLabel.innerHTML = '<i data-lucide="network" class="w-3.5 h-3.5"></i> Sortiernetz: ' + memState.stages.length + ' Phasen \u00b7 ' + memState.n + ' Dr\u00e4hte';
} }
lucide.createIcons({ nodes: [$memLabel] }); lucide.createIcons({ nodes: [$memLabel] });
} }
@ -2626,7 +2560,6 @@ function updateMemView(memState) {
else if (memState.type === 'counting') resizeCanvas(memCanvas, memCtx, 0.25, 250, 160); else if (memState.type === 'counting') resizeCanvas(memCanvas, memCtx, 0.25, 250, 160);
else if (memState.type === 'radix') resizeCanvas(memCanvas, memCtx, 0.3, 300, 160); else if (memState.type === 'radix') resizeCanvas(memCanvas, memCtx, 0.3, 300, 160);
else if (memState.type === 'buckets') resizeCanvas(memCanvas, memCtx, 0.3, 300, 160); else if (memState.type === 'buckets') resizeCanvas(memCanvas, memCtx, 0.3, 300, 160);
else if (memState.type === 'bitonic_network') resizeCanvas(memCanvas, memCtx, 0.35, 300, 140);
drawMemory(memState); drawMemory(memState);
} }
@ -2669,14 +2602,6 @@ function scheduleNext() {
if (animTimer !== null) clearInterval(animTimer); if (animTimer !== null) clearInterval(animTimer);
let delay = getDelay(); let delay = getDelay();
// FIX: Thread-Slider hat nun einen funktionalen Effekt auf die Geschwindigkeit
if (PARALLEL_ALGOS.has($algoSelect.value)) {
const threads = parseInt($threadSlider.value, 10);
if (threads > 1) {
delay = Math.max(1, Math.round(delay / threads));
}
}
// Start timing on first tick // Start timing on first tick
if (startTime === 0) startTime = performance.now(); if (startTime === 0) startTime = performance.now();
animTimer = window.setInterval(tick, delay); animTimer = window.setInterval(tick, delay);
@ -2806,6 +2731,28 @@ $btnStepBack.addEventListener('click', function() {
$btnReset.addEventListener('click', doReset); $btnReset.addEventListener('click', doReset);
$btnShuffle.addEventListener('click', function() {
stopTimer();
isRunning = false;
isPaused = false;
// Fisher-Yates Shuffle on current array
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
if (j !== i) {
[array[i], array[j]] = [array[j], array[i]];
}
}
steps = [];
stepIndex = 0;
startTime = 0;
elapsedMs = 0;
setStats(0, 0, 0, 0);
$statTime.textContent = '0 ms';
renderCurrent();
updateButtonStates();
$stepExplanation.textContent = 'Array gemischt \u2713';
});
// ================================================================ // ================================================================
// Slider & Input Events // Slider & Input Events
// ================================================================ // ================================================================
@ -2823,16 +2770,10 @@ $sizeSlider.addEventListener('input', function() {
}); });
$algoSelect.addEventListener('change', function() { $algoSelect.addEventListener('change', function() {
updateThreadSlider();
if (!isRunning) doReset(); if (!isRunning) doReset();
writeUrlState(); writeUrlState();
}); });
$threadSlider.addEventListener('input', function() {
$threadVal.textContent = $threadSlider.value;
if (isRunning && !isPaused) scheduleNext();
});
$presetSelect.addEventListener('change', function() { $presetSelect.addEventListener('change', function() {
if (!isRunning) doReset(); if (!isRunning) doReset();
writeUrlState(); writeUrlState();
@ -2840,14 +2781,41 @@ $presetSelect.addEventListener('change', function() {
$customArray.addEventListener('change', function() { $customArray.addEventListener('change', function() {
if (!isRunning) doReset(); if (!isRunning) doReset();
updateCustomArrayCount();
}); });
$customArray.addEventListener('input', function() {
updateCustomArrayCount();
});
function updateCustomArrayCount() {
const text = $customArray.value.trim();
if (!text) {
$customArrayCount.textContent = '';
return;
}
const values = text.split(/[,;\s\n\t]+/).filter(function(v) { return v !== '' && !isNaN(parseFloat(v)); });
$customArrayCount.textContent = values.length + ' Wert' + (values.length === 1 ? '' : 'e');
}
$btnClearCustom.addEventListener('click', function() { $btnClearCustom.addEventListener('click', function() {
$customArray.value = ''; $customArray.value = '';
$customArrayCount.textContent = '';
$sizeSlider.disabled = false; $sizeSlider.disabled = false;
if (!isRunning) doReset(); if (!isRunning) doReset();
}); });
$btnPasteCustom.addEventListener('click', async function() {
try {
const text = await navigator.clipboard.readText();
$customArray.value = text.replace(/[\r\n]+/g, ', ').replace(/,/g, ', ');
updateCustomArrayCount();
$customArray.dispatchEvent(new Event('change'));
} catch (e) {
// Clipboard API not available or permission denied
}
});
$btnTheme.addEventListener('click', function() { $btnTheme.addEventListener('click', function() {
const isDark = document.documentElement.classList.contains('dark'); const isDark = document.documentElement.classList.contains('dark');
const newTheme = !isDark; const newTheme = !isDark;
@ -2886,6 +2854,10 @@ document.addEventListener('keydown', function(e) {
e.preventDefault(); e.preventDefault();
$btnTheme.click(); $btnTheme.click();
break; break;
case 's':
e.preventDefault();
$btnShuffle.click();
break;
} }
}); });
@ -2920,7 +2892,6 @@ function makeTouchSlider(slider) {
makeTouchSlider($sizeSlider); makeTouchSlider($sizeSlider);
makeTouchSlider($speedSlider); makeTouchSlider($speedSlider);
makeTouchSlider($threadSlider);
// ================================================================ // ================================================================
// URL State Persistence // URL State Persistence
@ -2984,7 +2955,6 @@ readUrlState();
refreshColors(); refreshColors();
updateSpeedLabel(); updateSpeedLabel();
updateSizeLabel(); updateSizeLabel();
updateThreadSlider();
resizeCanvas(); resizeCanvas();
generateArray(); generateArray();
arrayFresh = true; arrayFresh = true;

View file

@ -233,9 +233,8 @@ const ALGORITHMS = [
'bubble', 'selection', 'insertion', 'cocktail', 'bubble', 'selection', 'insertion', 'cocktail',
'merge', 'heap', 'quick', 'quick3way', 'dualpivot', 'introsort', 'merge', 'heap', 'quick', 'quick3way', 'dualpivot', 'introsort',
'shell', 'tree', 'timsort', 'shell', 'tree', 'timsort',
'bitonic',
'counting', 'radix', 'bucket', 'counting', 'radix', 'bucket',
'pancake', 'cycle', 'bogo', 'pancake', 'cycle', 'bogo', 'shuffle',
]; ];
const Bogo_MAX_SIZE = 6; const Bogo_MAX_SIZE = 6;
@ -332,6 +331,7 @@ console.log('═'.repeat(50));
const NORMAL_SIZES = [10, 50]; const NORMAL_SIZES = [10, 50];
for (const algo of ALGORITHMS) { for (const algo of ALGORITHMS) {
if (algo === 'shuffle') continue;
if (verbose) console.log(`\n📦 ${algo}:`); if (verbose) console.log(`\n📦 ${algo}:`);
for (const preset of PRESETS) { for (const preset of PRESETS) {
for (const size of NORMAL_SIZES) { for (const size of NORMAL_SIZES) {
@ -341,6 +341,44 @@ for (const algo of ALGORITHMS) {
} }
} }
// ── Shuffle-Tests (speziell: nicht sortiert, nur permutiert) ──
if (verbose) console.log('\n📦 shuffle:');
for (const preset of PRESETS) {
for (const size of NORMAL_SIZES) {
totalTests++;
const arr = generatePresetArray(preset, size);
const sortedArr = arr.slice().sort((a, b) => a - b);
context.array = arr.slice();
try {
const steps = buildSteps('shuffle');
const lastStep = steps[steps.length - 1];
if (lastStep.type !== 'done') {
throw new Error(`Letzter Step type '${lastStep.type}', erwartet 'done'`);
}
if (lastStep.array.length !== arr.length) {
throw new Error(`Array-Länge geändert: ${lastStep.array.length} statt ${arr.length}`);
}
const sortedResult = lastStep.array.slice().sort((a, b) => a - b);
if (!arraysEqual(sortedResult, sortedArr)) {
throw new Error(`Werte stimmen nicht überein nach Shuffle`);
}
passed++;
if (verbose) {
console.log(` ✅ shuffle + ${preset} (${size} Elemente, ${steps.length} Steps)`);
}
} catch (e) {
failed++;
failures.push({ algo: 'shuffle', preset, size, error: e.message });
console.log(` ❌ shuffle + ${preset} (${size} Elemente): ${e.message}`);
}
}
}
// Edge Cases // Edge Cases
if (verbose) console.log('\n📦 Edge Cases:'); if (verbose) console.log('\n📦 Edge Cases:');