From 400532d953eb4c9e146b5b941a7a64456527d790 Mon Sep 17 00:00:00 2001 From: dcq-31 <64748988+dcq-31@users.noreply.github.com> Date: Sun, 24 May 2026 20:26:58 -0400 Subject: [PATCH] feat: add binary exponentiation algorithm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Binary Exponentiation to the Divide and Conquer category. Computes aⁿ in O(log n) by halving the exponent at each recursive step, visualized through the call stack concept type. - 11-step trace of binPow(2, 10) = 1024 covering both even and odd exponent cases (10 → 5 → 2 → 1 → 0) - Bilingual step descriptions and About-tab content (en/es) - README.md and README_ES.md updated; also adds previously omitted Bucket Sort entry under Sorting --- README.md | 6 +- README_ES.md | 6 +- src/i18n/translations.ts | 48 +++++ src/lib/algorithms/divide-and-conquer.ts | 232 ++++++++++++++++++++++- src/lib/algorithms/index.ts | 3 +- 5 files changed, 287 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ce4d6a5..885dae5 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,14 @@ A free, interactive web tool to learn algorithms through animated step-by-step v - **Variable tracking** — see the state of every variable in real time - **Contextual explanation** — understand the *why* behind each operation -## 40+ algorithms across 8 categories +## 41+ algorithms across 8 categories
### Sorting -Bubble Sort · Selection Sort · Insertion Sort · Quick Sort · Merge Sort · Heap Sort · Counting Sort · Radix Sort · Shell Sort +Bubble Sort · Selection Sort · Insertion Sort · Quick Sort · Merge Sort · Heap Sort · Counting Sort · Radix Sort · Shell Sort · Bucket Sort @@ -72,7 +72,7 @@ N-Queens · Sudoku Solver · Maze Pathfinding ### Divide & Conquer -Tower of Hanoi +Tower of Hanoi · Binary Exponentiation diff --git a/README_ES.md b/README_ES.md index c154734..2b1b1c3 100644 --- a/README_ES.md +++ b/README_ES.md @@ -27,14 +27,14 @@ Una herramienta web interactiva y gratuita para aprender algoritmos a través de - **Seguimiento de variables** — ve el estado de cada variable en tiempo real - **Explicación contextual** — entiende el *porqué* de cada operación -## +40 algoritmos en 8 categorías +## +41 algoritmos en 8 categorías
### Ordenamiento -Bubble Sort · Selection Sort · Insertion Sort · Quick Sort · Merge Sort · Heap Sort · Counting Sort · Radix Sort · Shell Sort +Bubble Sort · Selection Sort · Insertion Sort · Quick Sort · Merge Sort · Heap Sort · Counting Sort · Radix Sort · Shell Sort · Bucket Sort @@ -72,7 +72,7 @@ N-Queens · Sudoku Solver · Maze Pathfinding ### Divide y vencerás -Torre de Hanói +Torre de Hanói · Exponenciación Binaria diff --git a/src/i18n/translations.ts b/src/i18n/translations.ts index 0683760..5e33c2a 100644 --- a/src/i18n/translations.ts +++ b/src/i18n/translations.ts @@ -1021,6 +1021,30 @@ Properties: - Demonstrates the power of recursion The puzzle was invented by mathematician Édouard Lucas in 1883. Legend says monks in a temple are moving 64 golden disks — completing the puzzle would mark the end of the world (requiring 18,446,744,073,709,551,615 moves).`, + + 'binary-exponentiation': `Binary Exponentiation + +Binary Exponentiation computes aⁿ in O(log n) time by halving the exponent at each recursive step, rather than multiplying a by itself n times. + +How it works: +1. Base case: a⁰ = 1 +2. Divide: recursively compute half = binPow(base, exp >> 1) +3. Conquer (even exp): return half × half +4. Conquer (odd exp): return half × half × a + +Example: 2¹⁰ uses only 4 recursive calls (10 → 5 → 2 → 1 → 0) instead of 9 naive multiplications. + +Key insight — the exponent in binary: + 10 = 1010₂ → one recursive level per bit, ⌈log₂ n⌉ levels total + Each level decides: if exp is even, just square (half × half); if odd, also multiply by base + +Time Complexity: O(log n) — exponent halves each call +Space Complexity: O(log n) — recursive call stack depth + +Applications: + - Modular exponentiation (cryptography, RSA) + - Matrix exponentiation (Fibonacci in O(log n)) + - Computing large powers in competitive programming`, }, }, @@ -1966,6 +1990,30 @@ Propiedades: - Demuestra el poder de la recursión El rompecabezas fue inventado por el matemático Édouard Lucas en 1883. La leyenda dice que monjes en un templo están moviendo 64 discos dorados — completar el rompecabezas marcaría el fin del mundo (requiriendo 18.446.744.073.709.551.615 movimientos).`, + + 'binary-exponentiation': `Exponenciación Binaria + +La Exponenciación Binaria calcula aⁿ en tiempo O(log n) dividiendo el exponente a la mitad en cada llamada recursiva, en lugar de multiplicar a por sí mismo n veces. + +Cómo funciona: +1. Caso base: a⁰ = 1 +2. Dividir: calcular recursivamente half = binPow(base, exp >> 1) +3. Conquistar (exp par): retornar half × half +4. Conquistar (exp impar): retornar half × half × a + +Ejemplo: 2¹⁰ usa solo 4 llamadas recursivas (10 → 5 → 2 → 1 → 0) en lugar de 9 multiplicaciones directas. + +Idea clave — el exponente en binario: + 10 = 1010₂ → un nivel recursivo por bit, ⌈log₂ n⌉ niveles en total + Cada nivel decide: si exp es par, solo elevar al cuadrado (half × half); si es impar, multiplicar también por la base + +Complejidad Temporal: O(log n) — el exponente se divide a la mitad en cada llamada +Complejidad Espacial: O(log n) — profundidad de la pila de llamadas recursivas + +Aplicaciones: + - Exponenciación modular (criptografía, RSA) + - Exponenciación de matrices (Fibonacci en O(log n)) + - Cálculo de grandes potencias en programación competitiva`, }, }, } diff --git a/src/lib/algorithms/divide-and-conquer.ts b/src/lib/algorithms/divide-and-conquer.ts index 828b708..09432f8 100644 --- a/src/lib/algorithms/divide-and-conquer.ts +++ b/src/lib/algorithms/divide-and-conquer.ts @@ -142,4 +142,234 @@ The minimum number of moves for n disks is 2^n - 1. For 3 disks, that's 7 moves. }, } -export { towerOfHanoi } +// ============================================================ +// BINARY EXPONENTIATION +// ============================================================ +const binaryExponentiation: Algorithm = { + id: 'binary-exponentiation', + name: 'Binary Exponentiation', + category: 'Divide and Conquer', + difficulty: 'intermediate', + visualization: 'concept', + code: `function binPow(base, exp) { + if (exp === 0) return 1 + const half = binPow(base, exp >> 1) + if (exp % 2 === 0) { + return half * half + } + return half * half * base +}`, + description: `Binary Exponentiation + +Binary Exponentiation computes aⁿ in O(log n) time by halving the exponent at each recursive step, rather than multiplying a by itself n times. + +How it works: +1. Base case: a⁰ = 1 +2. Divide: recursively compute half = binPow(base, exp >> 1) +3. Conquer (even exp): return half × half +4. Conquer (odd exp): return half × half × a + +Example: 2¹⁰ uses only 4 recursive calls (10 → 5 → 2 → 1 → 0) instead of 9 naive multiplications. + +Time Complexity: O(log n) — exponent halves each call +Space Complexity: O(log n) — recursive call stack depth`, + + generateSteps(locale = 'en') { + const steps: Step[] = [] + + // Code line references (1-indexed): + // 1: function binPow(base, exp) { + // 2: if (exp === 0) return 1 + // 3: const half = binPow(base, exp >> 1) + // 4: if (exp % 2 === 0) { + // 5: return half * half + // 6: } + // 7: return half * half * base + // 8: } + + // Example: binPow(2, 10) = 1024 + // Descent: 10 → 5 → 2 → 1 → 0 (4 recursive calls, shows both even and odd cases) + + steps.push({ + concept: { type: 'callStack', frames: [] }, + description: d( + locale, + "Let's compute 2¹⁰ = 1024 using binary exponentiation. Instead of 9 multiplications, we need only 4 recursive calls — one per bit in the exponent.", + 'Calculemos 2¹⁰ = 1024 con exponenciación binaria. En lugar de 9 multiplicaciones, solo necesitamos 4 llamadas recursivas — una por bit del exponente.', + ), + codeLine: 1, + variables: { base: 2, exp: 10 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [{ label: 'binPow(2, 10)', detail: 'exp=10 is even → call binPow(2, 5)', state: 'active' }], + }, + description: d( + locale, + 'binPow(2, 10): exp=10, not the base case. Divide: call binPow(2, 10 >> 1) = binPow(2, 5).', + 'binPow(2, 10): exp=10, no es caso base. Dividir: llamar binPow(2, 10 >> 1) = binPow(2, 5).', + ), + codeLine: 3, + variables: { base: 2, exp: 10 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [ + { label: 'binPow(2, 10)', detail: 'waiting for binPow(2, 5)…', state: 'waiting' }, + { label: 'binPow(2, 5)', detail: 'exp=5 is odd → call binPow(2, 2)', state: 'active' }, + ], + }, + description: d( + locale, + 'binPow(2, 5): exp=5, not the base case. Divide: call binPow(2, 5 >> 1) = binPow(2, 2). Stack grows.', + 'binPow(2, 5): exp=5, no es caso base. Dividir: llamar binPow(2, 5 >> 1) = binPow(2, 2). La pila crece.', + ), + codeLine: 3, + variables: { base: 2, exp: 5, stackDepth: 2 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [ + { label: 'binPow(2, 10)', detail: 'waiting for binPow(2, 5)…', state: 'waiting' }, + { label: 'binPow(2, 5)', detail: 'waiting for binPow(2, 2)…', state: 'waiting' }, + { label: 'binPow(2, 2)', detail: 'exp=2 is even → call binPow(2, 1)', state: 'active' }, + ], + }, + description: d( + locale, + 'binPow(2, 2): exp=2, not the base case. Divide: call binPow(2, 2 >> 1) = binPow(2, 1). Stack depth = 3.', + 'binPow(2, 2): exp=2, no es caso base. Dividir: llamar binPow(2, 2 >> 1) = binPow(2, 1). Profundidad = 3.', + ), + codeLine: 3, + variables: { base: 2, exp: 2, stackDepth: 3 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [ + { label: 'binPow(2, 10)', detail: 'waiting for binPow(2, 5)…', state: 'waiting' }, + { label: 'binPow(2, 5)', detail: 'waiting for binPow(2, 2)…', state: 'waiting' }, + { label: 'binPow(2, 2)', detail: 'waiting for binPow(2, 1)…', state: 'waiting' }, + { label: 'binPow(2, 1)', detail: 'exp=1 is odd → call binPow(2, 0)', state: 'active' }, + ], + }, + description: d( + locale, + 'binPow(2, 1): exp=1, not the base case. Divide: call binPow(2, 1 >> 1) = binPow(2, 0). Stack depth = 4.', + 'binPow(2, 1): exp=1, no es caso base. Dividir: llamar binPow(2, 1 >> 1) = binPow(2, 0). Profundidad = 4.', + ), + codeLine: 3, + variables: { base: 2, exp: 1, stackDepth: 4 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [ + { label: 'binPow(2, 10)', detail: 'waiting for binPow(2, 5)…', state: 'waiting' }, + { label: 'binPow(2, 5)', detail: 'waiting for binPow(2, 2)…', state: 'waiting' }, + { label: 'binPow(2, 2)', detail: 'waiting for binPow(2, 1)…', state: 'waiting' }, + { label: 'binPow(2, 1)', detail: 'waiting for binPow(2, 0)…', state: 'waiting' }, + { label: 'binPow(2, 0)', detail: 'BASE CASE: exp=0, return 1', state: 'base' }, + ], + }, + description: d( + locale, + "BASE CASE reached! exp=0, return 1. Stack depth = ⌈log₂ 10⌉ = 4 calls — that's O(log n). Now results propagate back up.", + '¡CASO BASE alcanzado! exp=0, retorna 1. Profundidad = ⌈log₂ 10⌉ = 4 llamadas — eso es O(log n). Ahora los resultados se propagan hacia arriba.', + ), + codeLine: 2, + variables: { base: 2, exp: 0, returns: 1, stackDepth: 4 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [ + { label: 'binPow(2, 10)', detail: 'waiting for binPow(2, 5)…', state: 'waiting' }, + { label: 'binPow(2, 5)', detail: 'waiting for binPow(2, 2)…', state: 'waiting' }, + { label: 'binPow(2, 2)', detail: 'waiting for binPow(2, 1)…', state: 'waiting' }, + { label: 'binPow(2, 1)', detail: 'half=1, 1 is odd → 1×1×2 = 2', state: 'active' }, + ], + }, + description: d( + locale, + 'binPow(2, 1): half=1, exp=1 is odd → return half × half × base = 1×1×2 = 2. Frame popped.', + 'binPow(2, 1): half=1, exp=1 es impar → retorna half × half × base = 1×1×2 = 2. Frame eliminado.', + ), + codeLine: 7, + variables: { base: 2, exp: 1, half: 1, returns: 2 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [ + { label: 'binPow(2, 10)', detail: 'waiting for binPow(2, 5)…', state: 'waiting' }, + { label: 'binPow(2, 5)', detail: 'waiting for binPow(2, 2)…', state: 'waiting' }, + { label: 'binPow(2, 2)', detail: 'half=2, 2 is even → 2×2 = 4', state: 'active' }, + ], + }, + description: d( + locale, + 'binPow(2, 2): half=2, exp=2 is even → return half × half = 2×2 = 4. Stack unwinding.', + 'binPow(2, 2): half=2, exp=2 es par → retorna half × half = 2×2 = 4. La pila se desenrolla.', + ), + codeLine: 5, + variables: { base: 2, exp: 2, half: 2, returns: 4 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [ + { label: 'binPow(2, 10)', detail: 'waiting for binPow(2, 5)…', state: 'waiting' }, + { label: 'binPow(2, 5)', detail: 'half=4, 5 is odd → 4×4×2 = 32', state: 'active' }, + ], + }, + description: d( + locale, + 'binPow(2, 5): half=4, exp=5 is odd → return half × half × base = 4×4×2 = 32. Almost done!', + 'binPow(2, 5): half=4, exp=5 es impar → retorna half × half × base = 4×4×2 = 32. ¡Casi listo!', + ), + codeLine: 7, + variables: { base: 2, exp: 5, half: 4, returns: 32 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [{ label: 'binPow(2, 10)', detail: 'half=32, 10 is even → 32×32 = 1024', state: 'resolved' }], + }, + description: d( + locale, + 'binPow(2, 10): half=32, exp=10 is even → return half × half = 32×32 = 1024. Final result!', + 'binPow(2, 10): half=32, exp=10 es par → retorna half × half = 32×32 = 1024. ¡Resultado final!', + ), + codeLine: 5, + variables: { base: 2, exp: 10, half: 32, returns: 1024 }, + }) + + steps.push({ + concept: { type: 'callStack', frames: [] }, + description: d( + locale, + "2¹⁰ = 1024, computed in just 4 calls instead of 9 multiplications. That's the power of O(log n).", + '2¹⁰ = 1024, calculado en solo 4 llamadas en lugar de 9 multiplicaciones. Ese es el poder de O(log n).', + ), + codeLine: 5, + variables: { result: 1024 }, + }) + + return steps + }, +} + +export { towerOfHanoi, binaryExponentiation } diff --git a/src/lib/algorithms/index.ts b/src/lib/algorithms/index.ts index 8f15f85..de99a56 100644 --- a/src/lib/algorithms/index.ts +++ b/src/lib/algorithms/index.ts @@ -59,7 +59,7 @@ import { mazePathfinding, } from '@lib/algorithms/backtracking' -import { towerOfHanoi } from '@lib/algorithms/divide-and-conquer' +import { towerOfHanoi, binaryExponentiation } from '@lib/algorithms/divide-and-conquer' export const algorithms: Algorithm[] = [ // Concepts @@ -109,6 +109,7 @@ export const algorithms: Algorithm[] = [ mazePathfinding, // Divide and Conquer towerOfHanoi, + binaryExponentiation, ] export const categories: Category[] = [