Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<table>
<tr>
<td width="25%" valign="top">

### 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

</td>
<td width="25%" valign="top">
Expand Down Expand Up @@ -72,7 +72,7 @@ N-Queens · Sudoku Solver · Maze Pathfinding
<td width="25%" valign="top">

### Divide & Conquer
Tower of Hanoi
Tower of Hanoi · Binary Exponentiation

</td>
<td width="25%" valign="top">
Expand Down
6 changes: 3 additions & 3 deletions README_ES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<table>
<tr>
<td width="25%" valign="top">

### 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

</td>
<td width="25%" valign="top">
Expand Down Expand Up @@ -72,7 +72,7 @@ N-Queens · Sudoku Solver · Maze Pathfinding
<td width="25%" valign="top">

### Divide y vencerás
Torre de Hanói
Torre de Hanói · Exponenciación Binaria

</td>
<td width="25%" valign="top">
Expand Down
48 changes: 48 additions & 0 deletions src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
},
},

Expand Down Expand Up @@ -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`,
},
},
}
Expand Down
232 changes: 231 additions & 1 deletion src/lib/algorithms/divide-and-conquer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
3 changes: 2 additions & 1 deletion src/lib/algorithms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -109,6 +109,7 @@ export const algorithms: Algorithm[] = [
mazePathfinding,
// Divide and Conquer
towerOfHanoi,
binaryExponentiation,
]

export const categories: Category[] = [
Expand Down