diff --git a/README.md b/README.md index 620647f..f7f36e9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ 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 @@ -35,7 +35,7 @@ A free, interactive web tool to learn algorithms through animated step-by-step v ### 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
@@ -79,7 +79,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 c611006..7a21395 100644 --- a/README_ES.md +++ b/README_ES.md @@ -27,7 +27,7 @@ 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 @@ -35,7 +35,7 @@ Una herramienta web interactiva y gratuita para aprender algoritmos a través de ### 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
@@ -79,7 +79,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/content/algorithms/binary-exponentiation.ts b/src/content/algorithms/binary-exponentiation.ts new file mode 100644 index 0000000..243dd75 --- /dev/null +++ b/src/content/algorithms/binary-exponentiation.ts @@ -0,0 +1,58 @@ +import type { Locale } from '@i18n/translations' + +const descriptions: Record = { + en: `Binary Exponentiation + +Binary Exponentiation computes aⁿ in O(log n) time by halving the exponent at each step instead of multiplying a by itself n times. + +How it works: +1. Base case: a⁰ = 1 +2. Recursively compute half = binPow(base, floor(exp / 2)) +3. If exp is even, return half × half +4. If exp is odd, return half × half × base + +Why it is fast: + Each recursive call cuts the exponent in half, so the recursion depth is proportional to log₂ n instead of n. + +Time Complexity: + Best: O(1) + Average: O(log n) + Worst: O(log n) + +Space Complexity: O(log n) for the recursive call stack + +Applications: + - Modular exponentiation in cryptography + - Fast matrix exponentiation + - Competitive programming and number theory + +The key insight is that powers can be reused: once you know a^(n/2), you can square it to recover most of the work immediately.`, + es: `Exponenciación Binaria + +La Exponenciación Binaria calcula aⁿ en O(log n) dividiendo el exponente a la mitad en cada paso, en lugar de multiplicar a por sí mismo n veces. + +Cómo funciona: +1. Caso base: a⁰ = 1 +2. Calcular recursivamente half = binPow(base, floor(exp / 2)) +3. Si exp es par, retornar half × half +4. Si exp es impar, retornar half × half × base + +Por qué es rápida: + Cada llamada recursiva corta el exponente a la mitad, así que la profundidad de la recursión es proporcional a log₂ n en vez de n. + +Complejidad Temporal: + Mejor: O(1) + Promedio: O(log n) + Peor: O(log n) + +Complejidad Espacial: O(log n) por la pila de llamadas recursivas + +Aplicaciones: + - Exponenciación modular en criptografía + - Exponenciación rápida de matrices + - Programación competitiva y teoría de números + +La idea clave es reutilizar potencias: una vez que conoces a^(n/2), puedes elevarlo al cuadrado y recuperar la mayor parte del trabajo inmediatamente.`, +} + +export default descriptions diff --git a/src/lib/algorithms/catalog.ts b/src/lib/algorithms/catalog.ts index 419dc99..1bef987 100644 --- a/src/lib/algorithms/catalog.ts +++ b/src/lib/algorithms/catalog.ts @@ -301,6 +301,13 @@ export const algorithmCatalog: AlgorithmSummary[] = [ difficulty: 'intermediate', visualization: 'matrix', }, + { + id: 'binary-exponentiation', + name: 'Binary Exponentiation', + category: 'Divide and Conquer', + difficulty: 'intermediate', + visualization: 'concept', + }, // Math { id: 'euclidean', diff --git a/src/lib/algorithms/cpp/divide-and-conquer.ts b/src/lib/algorithms/cpp/divide-and-conquer.ts index 8dca7b5..bd81069 100644 --- a/src/lib/algorithms/cpp/divide-and-conquer.ts +++ b/src/lib/algorithms/cpp/divide-and-conquer.ts @@ -17,4 +17,14 @@ export const divideAndConquerCpp: Record = { } hanoi(3, "A", "C", "B"); //@14`), + 'binary-exponentiation': annotated(`long long binPow(long long base, long long exp) { + if (exp == 0) return 1; //@2 + long long half = binPow(base, exp / 2); //@3 + if (exp % 2 == 0) { //@4 + return half * half; //@5 + } + return half * half * base; //@7 +} + +binPow(2, 10);`), } diff --git a/src/lib/algorithms/divide-and-conquer.ts b/src/lib/algorithms/divide-and-conquer.ts index 031f0b8..fbcc9e9 100644 --- a/src/lib/algorithms/divide-and-conquer.ts +++ b/src/lib/algorithms/divide-and-conquer.ts @@ -136,4 +136,211 @@ hanoi(3, 'A', 'C', 'B');`, }, } -export { towerOfHanoi } +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 +} + +binPow(2, 10);`, + + generateSteps(locale = 'en') { + const steps: Step[] = [] + + 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.", + 'Calculemos 2¹⁰ = 1024 con exponenciación binaria. En lugar de 9 multiplicaciones, solo necesitamos 4 llamadas recursivas.', + ), + 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): divide the exponent by 2 and recurse on 5.', + 'binPow(2, 10): dividir el exponente entre 2 y recursar sobre 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): recurse on 2. Odd exponents will multiply by the base on the way back.', + 'binPow(2, 5): recursar sobre 2. Los exponentes impares multiplicarán por la base al regresar.', + ), + 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): recurse on 1. The stack depth is growing logarithmically.', + 'binPow(2, 2): recursar sobre 1. La profundidad de la pila crece logarítmicamente.', + ), + 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): recurse on 0, which will trigger the base case.', + 'binPow(2, 1): recursar sobre 0, lo que activará el caso base.', + ), + 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: return 1', state: 'base' }, + ], + }, + description: d( + locale, + 'Base case: any number to the power of 0 is 1. Now unwind the stack.', + 'Caso base: cualquier número elevado a 0 es 1. Ahora desenrollamos la pila.', + ), + 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, odd → 1×1×2 = 2', state: 'active' }, + ], + }, + description: d( + locale, + 'binPow(2, 1): odd exponent, so multiply by the base after squaring the half result.', + 'binPow(2, 1): exponente impar, así que se multiplica por la base después de elevar half al cuadrado.', + ), + 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, even → 2×2 = 4', state: 'active' }, + ], + }, + description: d( + locale, + 'binPow(2, 2): even exponent, so just square the half result.', + 'binPow(2, 2): exponente par, así que solo se eleva al cuadrado el resultado half.', + ), + 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, odd → 4×4×2 = 32', state: 'active' }, + ], + }, + description: d( + locale, + 'binPow(2, 5): odd exponent again, so square 4 and multiply by 2.', + 'binPow(2, 5): exponente impar otra vez, así que se eleva 4 al cuadrado y se multiplica por 2.', + ), + codeLine: 7, + variables: { base: 2, exp: 5, half: 4, returns: 32 }, + }) + + steps.push({ + concept: { + type: 'callStack', + frames: [ + { label: 'binPow(2, 10)', detail: 'half=32, even → 32×32 = 1024', state: 'resolved' }, + ], + }, + description: d( + locale, + 'binPow(2, 10): final step. Square 32 to get 1024.', + 'binPow(2, 10): paso final. Elevar 32 al cuadrado para obtener 1024.', + ), + codeLine: 5, + variables: { base: 2, exp: 10, half: 32, returns: 1024 }, + consoleOutput: ['1024'], + }) + + steps.push({ + concept: { type: 'callStack', frames: [] }, + description: d( + locale, + '2¹⁰ = 1024, computed with logarithmic recursion depth instead of linear repeated multiplication.', + '2¹⁰ = 1024, calculado con profundidad recursiva logarítmica en lugar de multiplicación repetida lineal.', + ), + 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 c646800..c00c0a4 100644 --- a/src/lib/algorithms/index.ts +++ b/src/lib/algorithms/index.ts @@ -47,7 +47,7 @@ import { fibonacciDp, knapsack, lcs } from '@lib/algorithms/dynamic-programming' import { nQueens, sudokuSolver, mazePathfinding } from '@lib/algorithms/backtracking' -import { towerOfHanoi } from '@lib/algorithms/divide-and-conquer' +import { towerOfHanoi, binaryExponentiation } from '@lib/algorithms/divide-and-conquer' import { euclideanAlgorithm, sieveOfEratosthenes } from '@lib/algorithms/math' @@ -115,6 +115,7 @@ export const algorithms: Algorithm[] = [ mazePathfinding, // Divide and Conquer towerOfHanoi, + binaryExponentiation, // Math euclideanAlgorithm, sieveOfEratosthenes, diff --git a/src/lib/algorithms/java/divide-and-conquer.ts b/src/lib/algorithms/java/divide-and-conquer.ts index 0c2d32c..c120c54 100644 --- a/src/lib/algorithms/java/divide-and-conquer.ts +++ b/src/lib/algorithms/java/divide-and-conquer.ts @@ -17,4 +17,14 @@ export const divideAndConquerJava: Record = { } hanoi(3, "A", "C", "B"); //@14`), + 'binary-exponentiation': annotated(`long binPow(long base, long exp) { + if (exp == 0) return 1; //@2 + long half = binPow(base, exp / 2); //@3 + if (exp % 2 == 0) { //@4 + return half * half; //@5 + } + return half * half * base; //@7 +} + +binPow(2, 10);`), } diff --git a/src/lib/algorithms/loaders.ts b/src/lib/algorithms/loaders.ts index 71f2027..8438f1b 100644 --- a/src/lib/algorithms/loaders.ts +++ b/src/lib/algorithms/loaders.ts @@ -95,6 +95,8 @@ const ALGORITHM_LOADERS: Record Promise> = { // Divide and conquer 'tower-of-hanoi': () => import('./divide-and-conquer?algorithm=towerOfHanoi').then(readDefaultAlgorithm), + 'binary-exponentiation': () => + import('./divide-and-conquer?algorithm=binaryExponentiation').then(readDefaultAlgorithm), // Math euclidean: () => import('./math?algorithm=euclideanAlgorithm').then(readDefaultAlgorithm), diff --git a/src/lib/algorithms/python/divide-and-conquer.ts b/src/lib/algorithms/python/divide-and-conquer.ts index d3c7bd0..7fc25dc 100644 --- a/src/lib/algorithms/python/divide-and-conquer.ts +++ b/src/lib/algorithms/python/divide-and-conquer.ts @@ -17,4 +17,15 @@ export const divideAndConquerPython: Record = { hanoi(3, "A", "C", "B") #@14`), + 'binary-exponentiation': annotated(`def bin_pow(base, exp): #@1 + if exp == 0: + return 1 #@2 + + half = bin_pow(base, exp // 2) #@3 + if exp % 2 == 0: #@4 + return half * half #@5 + return half * half * base #@7 + + +bin_pow(2, 10)`), } diff --git a/src/lib/algorithms/rust/divide-and-conquer.ts b/src/lib/algorithms/rust/divide-and-conquer.ts index 74fa969..a2795fd 100644 --- a/src/lib/algorithms/rust/divide-and-conquer.ts +++ b/src/lib/algorithms/rust/divide-and-conquer.ts @@ -18,4 +18,17 @@ export const divideAndConquerRust: Record = { } hanoi(3, "A", "C", "B"); //@14`), + 'binary-exponentiation': annotated(`fn bin_pow(base: i64, exp: i64) -> i64 { + if exp == 0 { + return 1; //@2 + } + + let half = bin_pow(base, exp / 2); //@3 + if exp % 2 == 0 { //@4 + return half * half; //@5 + } + half * half * base //@7 +} + +bin_pow(2, 10);`), }