Add Fisher-Yates Shuffle (algorithmus + shuffle button)
This commit is contained in:
parent
a2e01443e6
commit
4db41a147c
2 changed files with 99 additions and 4 deletions
|
|
@ -204,7 +204,7 @@
|
|||
<div class="flex-1"></div>
|
||||
<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);">
|
||||
Sortier-Algorithmen <span style="font-size: 0.45em; font-weight: 400; opacity: 0.5; vertical-align: middle;">v0.2.11</span>
|
||||
Sortier-Algorithmen <span style="font-size: 0.45em; font-weight: 400; opacity: 0.5; vertical-align: middle;">v0.2.12</span>
|
||||
</h1>
|
||||
<p class="text-muted text-sm mt-0.5">Interaktive Visualisierung mit schrittweiser Animation</p>
|
||||
</div>
|
||||
|
|
@ -256,6 +256,7 @@
|
|||
<option value="pancake">Pancake Sort</option>
|
||||
<option value="cycle">Cycle Sort</option>
|
||||
<option value="bogo">Bogo Sort</option>
|
||||
<option value="shuffle">Fisher-Yates Shuffle</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -316,7 +317,7 @@
|
|||
|
||||
<!-- Transport Buttons -->
|
||||
<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"
|
||||
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">
|
||||
|
|
@ -333,6 +334,11 @@
|
|||
aria-label="Einzelschritt vorwärts" title="Einzelschritt">
|
||||
<i data-lucide="skip-forward" class="w-4 h-4"></i>
|
||||
</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"
|
||||
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">
|
||||
|
|
@ -727,10 +733,22 @@
|
|||
<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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
|
|
@ -802,6 +820,7 @@ const $btnPlayPause = document.getElementById('btnPlayPause');
|
|||
const $btnStepBack = document.getElementById('btnStepBack');
|
||||
const $btnStep = document.getElementById('btnStep');
|
||||
const $btnReset = document.getElementById('btnReset');
|
||||
const $btnShuffle = document.getElementById('btnShuffle');
|
||||
const $btnTheme = document.getElementById('btnTheme');
|
||||
const $progressFill = document.getElementById('progressFill');
|
||||
const $stepCounter = document.getElementById('stepCounter');
|
||||
|
|
@ -1756,6 +1775,21 @@ function buildSteps(algoName) {
|
|||
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': {
|
||||
const n = arr.length;
|
||||
function flip(end) {
|
||||
|
|
@ -2606,6 +2640,28 @@ $btnStepBack.addEventListener('click', function() {
|
|||
|
||||
$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
|
||||
// ================================================================
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ const ALGORITHMS = [
|
|||
'merge', 'heap', 'quick', 'quick3way', 'dualpivot', 'introsort',
|
||||
'shell', 'tree', 'timsort',
|
||||
'counting', 'radix', 'bucket',
|
||||
'pancake', 'cycle', 'bogo',
|
||||
'pancake', 'cycle', 'bogo', 'shuffle',
|
||||
];
|
||||
|
||||
const Bogo_MAX_SIZE = 6;
|
||||
|
|
@ -331,6 +331,7 @@ console.log('═'.repeat(50));
|
|||
const NORMAL_SIZES = [10, 50];
|
||||
|
||||
for (const algo of ALGORITHMS) {
|
||||
if (algo === 'shuffle') continue;
|
||||
if (verbose) console.log(`\n📦 ${algo}:`);
|
||||
for (const preset of PRESETS) {
|
||||
for (const size of NORMAL_SIZES) {
|
||||
|
|
@ -340,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
|
||||
if (verbose) console.log('\n📦 Edge Cases:');
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue