Introducció

En aquest tema, explorarem dos conceptes fonamentals en la resolució d'algorismes complexos: la recursió i la programació dinàmica. Aquests mètodes són essencials per abordar problemes que poden ser descompostos en subproblemes més petits i sovint es solapen.

Objectius del Tema

  • Comprendre els conceptes bàsics de la recursió.
  • Aprendre a identificar problemes que es poden resoldre amb recursió.
  • Introduir la programació dinàmica com una tècnica per optimitzar algorismes recursius.
  • Aplicar la programació dinàmica a problemes clàssics.

Recursió

Conceptes Bàsics

La recursió és una tècnica en la qual una funció es crida a si mateixa per resoldre subproblemes més petits del problema original. Els elements clau de la recursió són:

  1. Cas Base: La condició que atura la recursió.
  2. Cas Recursiu: La part de la funció que inclou la crida recursiva.

Exemple: Factorial d'un Nombre

El factorial d'un nombre n (denotat com n!) és el producte de tots els nombres enters positius fins a n.

Implementació Recursiva

def factorial(n):
    if n == 0:
        return 1  # Cas Base
    else:
        return n * factorial(n - 1)  # Cas Recursiu

Exercici 1: Implementar la Funció Fibonacci Recursiva

Implementa una funció recursiva per calcular el n-èssim nombre de Fibonacci. Els nombres de Fibonacci es defineixen com:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n-1) + F(n-2) per a n > 1
def fibonacci(n):
    if n == 0:
        return 0  # Cas Base
    elif n == 1:
        return 1  # Cas Base
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)  # Cas Recursiu

Solució de l'Exercici 1

def fibonacci(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

Programació Dinàmica

Conceptes Bàsics

La programació dinàmica (PD) és una tècnica d'optimització que es basa en descompondre un problema en subproblemes més petits i emmagatzemar els resultats dels subproblemes per evitar càlculs repetits. Hi ha dues tècniques principals:

  1. Memorització (Top-Down): Emmagatzemar els resultats de subproblemes en una estructura de dades (com un diccionari) per reutilitzar-los.
  2. Tabulació (Bottom-Up): Construir una taula que emmagatzema els resultats dels subproblemes de manera iterativa.

Exemple: Fibonacci amb Memorització

Implementació amb Memorització

def fibonacci_memo(n, memo={}):
    if n in memo:
        return memo[n]
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo)
        return memo[n]

Exemple: Fibonacci amb Tabulació

Implementació amb Tabulació

def fibonacci_tab(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    
    fib_table = [0] * (n + 1)
    fib_table[1] = 1
    
    for i in range(2, n + 1):
        fib_table[i] = fib_table[i - 1] + fib_table[i - 2]
    
    return fib_table[n]

Exercici 2: Implementar la Solució de la Mochila amb Programació Dinàmica

El problema de la mochila consisteix en seleccionar un conjunt d'objectes amb un pes i un valor determinats per maximitzar el valor total sense excedir un pes màxim.

Implementació

def knapsack(weights, values, W):
    n = len(weights)
    dp = [[0 for x in range(W + 1)] for y in range(n + 1)]
    
    for i in range(n + 1):
        for w in range(W + 1):
            if i == 0 or w == 0:
                dp[i][w] = 0
            elif weights[i - 1] <= w:
                dp[i][w] = max(values[i - 1] + dp[i - 1][w - weights[i - 1]], dp[i - 1][w])
            else:
                dp[i][w] = dp[i - 1][w]
    
    return dp[n][W]

Solució de l'Exercici 2

def knapsack(weights, values, W):
    n = len(weights)
    dp = [[0 for x in range(W + 1)] for y in range(n + 1)]
    
    for i in range(n + 1):
        for w in range(W + 1):
            if i == 0 or w == 0:
                dp[i][w] = 0
            elif weights[i - 1] <= w:
                dp[i][w] = max(values[i - 1] + dp[i - 1][w - weights[i - 1]], dp[i - 1][w])
            else:
                dp[i][w] = dp[i - 1][w]
    
    return dp[n][W]

Resum

En aquesta secció, hem après els conceptes bàsics de la recursió i la programació dinàmica. Hem vist com la recursió pot ser utilitzada per descompondre problemes en subproblemes més petits i com la programació dinàmica pot optimitzar aquests algorismes recursius mitjançant la memorització i la tabulació. Hem aplicat aquests conceptes a problemes clàssics com el càlcul de nombres de Fibonacci i el problema de la mochila.

© Copyright 2024. Tots els drets reservats