Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Ejercicios: Recursividad y Divide y Vencerás

Universidad Nacional de Río Negro

Ejercicios de Recursividad y Divide y Vencerás

Prerrequisitos y Entorno de Ejecución Requerido

Para compilar y verificar las soluciones de este módulo bajo el estándar C11 estricto de cátedra, se requiere:

Objetivos Pedagógicos y Competencias (Taxonomía de Bloom)

Capítulos de Apunte Correspondientes

Cuestiones de Estilo Aplicables


Fundamentos de Recursividad

Ejercicio 4.07.01 - Factorial con Tipos de Rango Amplio ⭐⭐☆☆☆

```c #include <stdio.h> #include <assert.h> unsigned long long factorial_avanzado(unsigned int n) { if (n <= 1) { return 1ULL; } return (unsigned long long)n * factorial_avanzado(n - 1); } int main(void) { assert(factorial_avanzado(0) == 1ULL); assert(factorial_avanzado(1) == 1ULL); assert(factorial_avanzado(5) == 120ULL); assert(factorial_avanzado(12) == 479001600ULL); return 0; } ```

(ej_b4_c07_02)=
### Ejercicio 4.07.02 - Suma de Enteros por Desplazamiento Unitario ⭐⭐☆☆☆

:::{exercise}
:label: ej_b4_c07_02_suma
:enumerator: rec-adv-2

Implementá la suma recursiva sin utilizar el operador binario `+` en el llamado recursivo, decrementando `b` e incrementando `a` mediante aritmética sucesiva.

**Nivel de Bloom:** Nivel 3 (Aplicación).  
**Conceptos requeridos:** Recursión lineal, invariante de suma ($a + b = \text{cte}$), caso base en cero.  
**Techo conceptual:** Prohibido el uso de lazos iterativos y sumas compuestas directas.

#### Contrato de la Función
- **Firma:** `int suma_recursiva(int a, int b);`
- **Precondición:** `b >= 0` (el decremento recursivo converge monótonamente hacia 0).
- **Postcondición:** Retorna el valor exacto de la suma matemática $a + b$.
- **Caso base:** $b = 0 \implies a$.

#### Tabla de Vectores de Prueba Obligatorios

| Tipo de Caso | Operandos (`a`, `b`) | Salida Esperada | Justificación Técnica |
| :--- | :--- | :--- | :--- |
| **Neutro Derecho** | `a = 15, b = 0` | `15` | Caso base inmediato, 0 llamadas recursivas |
| **Neutro Izquierdo** | `a = 0, b = 25` | `25` | Desenrollado recursivo completo de 25 pasos |
| **Caso General** | `a = 10, b = 30` | `40` | Preservación del invariante $(10+k) + (30-k) = 40$ |
```c #include <stdio.h> #include <assert.h> int suma_recursiva(int a, int b) { if (b == 0) { return a; } return suma_recursiva(a + 1, b - 1); } int main(void) { assert(suma_recursiva(15, 0) == 15); assert(suma_recursiva(0, 25) == 25); assert(suma_recursiva(10, 30) == 40); return 0; } ```

(ej_b4_c07_03)=
### Ejercicio 4.07.03 - Producto por Sumas Recursivas ⭐⭐☆☆☆

:::{exercise}
:label: ej_b4_c07_03_producto

Implementá la multiplicación entera recursiva en tiempo $O(b)$.

```c
long long producto_recursivo(long long a, unsigned int b);
```

**Tabla de Vectores de Prueba:**

| Operandos (`a`, `b`) | Salida Esperada |
| :--- | :--- |
| `a = 50, b = 0` | `0LL` |
| `a = 7, b = 8` | `56LL` |
| `a = -12, b = 4` | `-48LL` |

::::{solution}
```c
#include <stdio.h>
#include <assert.h>

long long producto_recursivo(long long a, unsigned int b) {
    if (b == 0) {
        return 0LL;
    }
    return a + producto_recursivo(a, b - 1);
}

int main(void) {
    assert(producto_recursivo(50, 0) == 0LL);
    assert(producto_recursivo(7, 8) == 56LL);
    assert(producto_recursivo(-12, 4) == -48LL);
    return 0;
}
```

(ej_b4_c07_04)=
### Ejercicio 4.07.04 - Exponenciación Rápida (Divide y Vencerás) ⭐⭐☆☆☆

:::{exercise}
:label: ej_b4_c07_04_potencia_rapida

Implementá la función de potencia optimizada que reduzca la complejidad de
$O(n)$ a $O(\log n)$ mediante la regla de exponenciación binaria:
si $exp$ es par, $base^{exp} = (base^{exp/2})^2$; si es impar, $base \times base^{exp-1}$.

```c
long long potencia_rapida(long long base, unsigned int exp);
```

**Tabla de Vectores de Prueba:**

| Caso de Prueba | Base | Exponente | Resultado Esperado |
| :--- | :--- | :--- | :--- |
| Exponente cero | `42` | `0` | `1LL` |
| Exponente par | `2` | `10` | `1024LL` |
| Exponente impar | `3` | `5` | `243LL` |
| Base negativa par | `-2` | `4` | `16LL` |
| Base negativa impar | `-2` | `5` | `-32LL` |

::::{solution}
```c
#include <stdio.h>
#include <assert.h>

long long potencia_rapida(long long base, unsigned int exp) {
    if (exp == 0) {
        return 1LL;
    }
    if (exp % 2 == 0) {
        long long mitad = potencia_rapida(base, exp / 2);
        return mitad * mitad;
    }
    return base * potencia_rapida(base, exp - 1);
}

int main(void) {
    assert(potencia_rapida(42, 0) == 1LL);
    assert(potencia_rapida(2, 10) == 1024LL);
    assert(potencia_rapida(3, 5) == 243LL);
    assert(potencia_rapida(-2, 4) == 16LL);
    assert(potencia_rapida(-2, 5) == -32LL);
    return 0;
}
```

---

## Series Numéricas Recursivas

(ej_b4_c07_05)=
### Ejercicio 4.07.05 - Fibonacci Básico y de Cola ⭐☆☆☆☆

:::{exercise}
:label: ej_b4_c07_05_fibonacci_avanzado

Implementá Fibonacci recursivo con recursión de cola (*tail recursion*) para
permitir que el compilador optimice los marcos de pila en $O(n)$ tiempo y $O(1)$ espacio auxiliar.

```c
unsigned long long fibonacci_tail(unsigned int n, unsigned long long a, unsigned long long b);
```

**Tabla de Vectores de Prueba:**

| Índice `n` | Llamada Inicial (`a=0, b=1`) | Salida Esperada |
| :--- | :--- | :--- |
| `0` | `fibonacci_tail(0, 0, 1)` | `0ULL` |
| `1` | `fibonacci_tail(1, 0, 1)` | `1ULL` |
| `7` | `fibonacci_tail(7, 0, 1)` | `13ULL` |
| `20` | `fibonacci_tail(20, 0, 1)` | `6765ULL` |

::::{solution}
```c
#include <stdio.h>
#include <assert.h>

unsigned long long fibonacci_tail(unsigned int n, unsigned long long a, unsigned long long b) {
    if (n == 0) {
        return a;
    }
    if (n == 1) {
        return b;
    }
    return fibonacci_tail(n - 1, b, a + b);
}

static unsigned long long fibonacci(unsigned int n) {
    return fibonacci_tail(n, 0ULL, 1ULL);
}

int main(void) {
    assert(fibonacci(0) == 0ULL);
    assert(fibonacci(1) == 1ULL);
    assert(fibonacci(2) == 1ULL);
    assert(fibonacci(3) == 2ULL);
    assert(fibonacci(7) == 13ULL);
    assert(fibonacci(20) == 6765ULL);
    return 0;
}
```

---

(ej_b4_c07_06)=
### Ejercicio 4.07.06 - Búsqueda Binaria Recursiva (Divide y Vencerás) ⭐⭐☆☆☆

:::{exercise}
:label: ej_b4_c07_06_busqueda_binaria_rec

Implementá el algoritmo de búsqueda binaria sobre un arreglo ordenado de enteros utilizando recursión y la estrategia de divide y conquista:

```c
int busqueda_binaria_rec(const int *arr, int inicio, int fin, int objetivo);
```

Retorna el índice donde se encuentra `objetivo` dentro del rango `[inicio, fin]`, o `-1` si no existe.

**Tabla de Vectores de Prueba:**

| Arreglo | Rango `[inicio, fin]` | Objetivo | Retorno Esperado |
| :--- | :--- | :--- | :--- |
| `[10, 20, 30, 40, 50]` | `[0, 4]` | `30` | `2` (Posición central) |
| `[10, 20, 30, 40, 50]` | `[0, 4]` | `10` | `0` (Extremo izquierdo) |
| `[10, 20, 30, 40, 50]` | `[0, 4]` | `50` | `4` (Extremo derecho) |
| `[10, 20, 30, 40, 50]` | `[0, 4]` | `99` | `-1` (No presente) |
| `[]` | `[0, -1]` | `5` | `-1` (Rango vacío) |

::::{solution}
```c
#include <stdio.h>
#include <assert.h>

int busqueda_binaria_rec(const int *arr, int inicio, int fin, int objetivo) {
    if (arr == NULL || inicio > fin) {
        return -1;
    }
    int medio = inicio + (fin - inicio) / 2;
    if (arr[medio] == objetivo) {
        return medio;
    }
    if (arr[medio] > objetivo) {
        return busqueda_binaria_rec(arr, inicio, medio - 1, objetivo);
    }
    return busqueda_binaria_rec(arr, medio + 1, fin, objetivo);
}

int main(void) {
    int arr[5] = {10, 20, 30, 40, 50};
    assert(busqueda_binaria_rec(arr, 0, 4, 30) == 2);
    assert(busqueda_binaria_rec(arr, 0, 4, 10) == 0);
    assert(busqueda_binaria_rec(arr, 0, 4, 50) == 4);
    assert(busqueda_binaria_rec(arr, 0, 4, 99) == -1);
    assert(busqueda_binaria_rec(arr, 0, 4, 5) == -1);
    assert(busqueda_binaria_rec(NULL, 0, 0, 10) == -1);
    assert(busqueda_binaria_rec(arr, 2, 1, 10) == -1);
    return 0;
}
```

---

(ej_b4_c07_07)=
### Ejercicio 4.07.07 - Caminos Únicos en Grilla Bidimensional ⭐⭐⭐☆☆

:::{exercise}
:label: caminos_grilla_rec
:enumerator: recursividad-adv-7

Dada una cuadrícula de $m$ filas por $n$ columnas, un robot situado en la esquina superior izquierda $(0, 0)$ necesita alcanzar la esquina inferior derecha $(m-1, n-1)$.
En cada paso, el robot solo puede desplazarse una casilla hacia la derecha o una casilla hacia abajo.

Implementá una función puramente recursiva:
```c
size_t caminos_grilla_rec(size_t m, size_t n);
```
que calcule la cantidad total de caminos únicos posibles sin utilizar estructuras iterativas ni variables estáticas.

**Nivel de Bloom:** Nivel 3 (Aplicación) y Nivel 4 (Análisis).  
**Conceptos requeridos:** Recursión ramificada, caso base de frontera unitaria ($m=1$ o $n=1$), árbol de llamadas recursivas.  
**Techo conceptual:** Prohibido el uso de lazos `for` / `while` o fórmulas cerradas analíticas con factoriales (debe demostrar descomposición por subproblemas).

#### Contrato de la Función
- **Firma:** `size_t caminos_grilla_rec(size_t m, size_t n);`
- **Precondiciones:** $m \ge 0, n \ge 0$.
- **Postcondiciones:** Si $m == 0$ o $n == 0$, retorna $0$. Si $m == 1$ o $n == 1$, retorna $1$. En caso general retorna `caminos_grilla_rec(m - 1, n) + caminos_grilla_rec(m, n - 1)`.

#### Tabla de Vectores de Prueba Obligatorios

| Dimensiones ($m \times n$) | Caminos Esperados | Justificación Técnica |
| :--- | :--- | :--- |
| $3 \times 3$ | `6` | 6 permutaciones de 2 pasos abajo y 2 derecha |
| $3 \times 2$ | `3` | Permutaciones: `{DDA, DAD, ADD}` |
| $1 \times 5$ | `1` | Solo existe la trayectoria recta hacia la derecha |
| $5 \times 1$ | `1` | Solo existe la trayectoria recta hacia abajo |
| $0 \times 4$ | `0` | Dimensión degenerada sin camino posible |

Ejercicio 4.07.08 - Conteo Recursivo de Subconjuntos con Suma Objetivo (Backtracking) ⭐⭐⭐☆☆


Ejercicio 4.07.09 - Resolución de Laberinto con Backtracking en Grilla 2D ⭐⭐⭐⭐☆