54. Resumen de métodos computacionales

A lo largo del curso hemos aprendido un buen número de métodos para resolver problemas con un ordenador. Veamos un pequeño resumen con ejemplos sencillos.

54.1. Conjeturar y comprobar

Los métodos de guess and check engloban toda una familia de métodos, que incluyen métodos de enumeración exhaustiva, bisección, algunos métodos estocásticos, algoritmos genéticos, ...

Encontrar la raiz cuadrada de un número natural

def raiz_cuadrada(x):
    g = primera_conjetura(x)
    while True:
        if suficientemente_proximos(g*g, x):
            return g
        g = nueva_conjetura(x,g)
(g + x/g)/2

54.2. Enumeración exhaustiva

La enumeración exhaustiva o fuerza bruta se utiliza cuando el número de casos a probar no es prohibitivamente alto y la evaluación de cada caso es sencilla. Por ejemplo:

Encontrar la raiz cúbica de un número natural.

def raiz_cubica(n):
    i = 1
    while i**3 < n:
        i = i + 1
    if i**3 == n:
        return i
    return False

54.3. Búsqueda por bisección

La búsqueda por bisección se utiliza cuando las posibles soluciones están totalmente ordenadas y se puede determinar cuál es la posible solución que se encuentra entre dos dadas. Por ejemplo:

Encontrar la raiz cúbica de un número real con una precisión dada

def raiz_cubica(x, epsilon):
    low = 0.0
    high = max(1.0, x)
    r = (high + low)/2.0
    while abs(r**3 - x) >= epsilon:
        if r**3 < x: low = r
        else: high = r
        r = (high + low)/2.0
    return r

54.4. Método de Newton-Raphson

Para el problema particular de encontrar las raizes de un polinomio \(p(x) = 0\) el mátodo de Newton Raphson establece un método que converge más rápidamente que la búsqueda por bisección. Se basa en esl siguiente teorema:

Sea :math:`p(x)` un polinomio en :math:`x`. Sea :math:`g` conjetura que aproxima una raiz, es decir :math:`p(g) simeq 0`. Entonces :math:`g - p(g)/p’(g)` es mejor conjetura.

Por ejemplo:

Encontrar la raiz cúbica de un número real con una precisión dada

def raiz_cubica(k, epsilon):
    x = k/2.0
    while abs(x**3 - k) >= epsilon:
        x -= (x**3 - k)/(3*x**2)
    return x

54.5. Divide y vencerás

Divide y vencerás es un principio general para resolución de problemas que pueden ser descompuestos en problemas más pequeños. Debe cumplir un requisito:

  • Subestructura óptima. La solución del problema puede componerse a partir de soluciones a problemas más pequeños

Por ejemplo:

Encontrar la potencia n-sima de un número real

def pot(x, n):
  if n == 1: return x
  if n % 2 == 0:
    y = pot(x, n//2)
    return y*y
  else:
    y = pot(x, (n-1)//2)
    return y*y*x

54.6. Programación dinámica

Cuando la solución a un problema exige rehacer repetidamente los mismos cálculos es posible reducir el trabajo aplicando memoization (guardando los valores ya calculados). Deben cumplirse dos requisitos:

  • Subestructura óptima. La solución del problema puede componerse a partir de las soluciones a problemas más pequeños
  • Subproblemas solapados. Los problemas más pequeños son frecuentemente repetidos.

Por ejemplo:

Calcular el término n-simo de la sucesión de Fibonacci

def fib(n, mem = {}):
    if n == 0 or n == 1:
        return 1
    try:
        return mem[n]
    except KeyError:
        res = fib(n-1, mem) + fib(n-2, mem)
        mem[n] = res
        return res

54.7. Poda del espacio de búsqueda

En algunos casos la búsqueda exhaustiva no es posible por existir un número excesivo de posibles soluciones. En esos casos pueden utilizarse técnicas que reducen el espacio de búsqueda.

Una técnica muy efectiva es la propagación de restricciones. Se trata de determinar restricciones de la solución final y propagarlas en los subproblemas para obtener nuevas restricciones en la solución final. Por ejemplo, en la solución al Sudoku:

class Restricciones(object):
    def _filtrar(self):
        while self._nuevos_fijos:
            self._nuevos_fijos = False
            self._filtrarFilas()
            self._filtrarColumnas()
            self._filtrarBloques()
    #...
Next Section - 55. Apéndice: Python básico