Remove Bitonic Sort and Threads slider (cleaner, simpler UI)

This commit is contained in:
Dieter Schlüter 2026-04-06 20:32:33 +02:00
commit a2e01443e6
2 changed files with 4 additions and 226 deletions

View file

@ -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.10</span>
Sortier-Algorithmen <span style="font-size: 0.45em; font-weight: 400; opacity: 0.5; vertical-align: middle;">v0.2.11</span>
</h1>
<p class="text-muted text-sm mt-0.5">Interaktive Visualisierung mit schrittweiser Animation</p>
</div>
@ -247,9 +247,6 @@
<option value="tree">Tree Sort (BST)</option>
<option value="timsort">Timsort</option>
</optgroup>
<optgroup label="Parallel — Netzwerk-Verfahren">
<option value="bitonic">Bitonic Sort</option>
</optgroup>
<optgroup label="O(n·k) — Nicht-vergleichsbasiert">
<option value="counting">Counting Sort</option>
<option value="radix">Radix Sort (LSD)</option>
@ -307,16 +304,6 @@
</label>
<input type="range" id="sizeSlider" min="5" max="200" value="50" class="w-full h-6">
</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 -->
<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">
@ -446,7 +433,7 @@
</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">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 -->
<div class="bg-surface2/50 rounded-lg p-3 border border-border/50">
@ -657,26 +644,6 @@
</div>
<!-- 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) -->
<div>
<h3 class="text-xs uppercase tracking-wider text-muted font-medium mb-3">O(n·k) — Nicht-vergleichsbasiert</h3>
@ -824,10 +791,8 @@ const $btnPasteCustom = document.getElementById('btnPasteCustom');
const $customArrayCount = document.getElementById('customArrayCount');
const $sizeSlider = document.getElementById('sizeSlider');
const $speedSlider = document.getElementById('speedSlider');
const $threadSlider = document.getElementById('threadSlider');
const $sizeVal = document.getElementById('sizeVal');
const $speedVal = document.getElementById('speedVal');
const $threadVal = document.getElementById('threadVal');
const $statComp = document.getElementById('statCompares');
const $statSwap = document.getElementById('statSwaps');
const $statMove = document.getElementById('statMoves');
@ -875,24 +840,10 @@ function updateSizeLabel() {
$sizeVal.textContent = $sizeSlider.value;
}
// Parallele Algorithmen: Bitonic Sort
const PARALLEL_ALGOS = new Set(['bitonic']);
// ================================================================
// Algorithmen mit Memory-Visualisierung
// ================================================================
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) {
$statComp.textContent = String(compares);
@ -1760,97 +1711,6 @@ function buildSteps(algoName) {
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': {
const n = arr.length;
if (n === 0) break;
@ -2273,7 +2133,6 @@ function drawMemory(state) {
else if (state.type === 'counting') drawCountingArray(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 === 'bitonic_network') drawBitonicNetwork(state, W, H);
}
function drawBST(state, W, H) {
@ -2539,67 +2398,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)
// ================================================================
@ -2627,8 +2425,6 @@ function updateMemView(memState) {
$memLabel.innerHTML = '<i data-lucide="layers" class="w-3.5 h-3.5"></i> Arbeitsspeicher: Radix-Buckets (10 Ziffern)';
} else if (memState.type === 'buckets') {
$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] });
}
@ -2639,7 +2435,6 @@ function updateMemView(memState) {
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 === 'buckets') resizeCanvas(memCanvas, memCtx, 0.3, 300, 160);
else if (memState.type === 'bitonic_network') resizeCanvas(memCanvas, memCtx, 0.35, 300, 140);
drawMemory(memState);
}
@ -2682,14 +2477,6 @@ function scheduleNext() {
if (animTimer !== null) clearInterval(animTimer);
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
if (startTime === 0) startTime = performance.now();
animTimer = window.setInterval(tick, delay);
@ -2836,16 +2623,10 @@ $sizeSlider.addEventListener('input', function() {
});
$algoSelect.addEventListener('change', function() {
updateThreadSlider();
if (!isRunning) doReset();
writeUrlState();
});
$threadSlider.addEventListener('input', function() {
$threadVal.textContent = $threadSlider.value;
if (isRunning && !isPaused) scheduleNext();
});
$presetSelect.addEventListener('change', function() {
if (!isRunning) doReset();
writeUrlState();
@ -2960,7 +2741,6 @@ function makeTouchSlider(slider) {
makeTouchSlider($sizeSlider);
makeTouchSlider($speedSlider);
makeTouchSlider($threadSlider);
// ================================================================
// URL State Persistence
@ -3024,7 +2804,6 @@ readUrlState();
refreshColors();
updateSpeedLabel();
updateSizeLabel();
updateThreadSlider();
resizeCanvas();
generateArray();
arrayFresh = true;

View file

@ -233,7 +233,6 @@ const ALGORITHMS = [
'bubble', 'selection', 'insertion', 'cocktail',
'merge', 'heap', 'quick', 'quick3way', 'dualpivot', 'introsort',
'shell', 'tree', 'timsort',
'bitonic',
'counting', 'radix', 'bucket',
'pancake', 'cycle', 'bogo',
];