• No se han encontrado resultados

Cap´ıtulo 2: Paradigmas de Dise˜no de Algoritmos

N/A
N/A
Protected

Academic year: 2020

Share "Cap´ıtulo 2: Paradigmas de Dise˜no de Algoritmos"

Copied!
39
0
0

Texto completo

(1)

Cap´ıtulo 2: Paradigmas de Dise˜

no de Algoritmos

Del libro: Dise˜no de Algoritmos - Nivio Ziviani (UFMG)

C y Pascal

Setiembre - 2011

Ingenier´ıa de Software

(2)

´Indice

1 Recursividad

2 Algoritmos de prueba y error (Backtracking)

3 Divide y vencer´as

4 Balanceado

5 Programaci´on Din´amica

6 Algoritmos Voraces y aproximados

(3)

Recursividad

Recursividad

• Un m´etodo que se llama as´ı mismo directa o indirectamente es llamado recursivo.

• La recursividad permite describir algoritmos de una forma mas clara y concisa, especialmente para problemas que por su naturaleza utilizan estructuras recursivas.

• Por ejemplo, los ´arboles binarios de b´usqueda:

• Todos los registros con llaves menores se encuentran en los sub´arboles de la izquierda.

• Todos los registros con llaves mayores se encuentran en los sub´arboles de la derecha.

(4)

Recursividad

Recursividad

package cap2;

public class ArbolBinario { private static class Nodo {

Object reg; Nodo izq; Nodo der; }

(5)

Recursividad

Recursividad

• Uno de los algoritmos mas conocidos para recorrer todos los los registros de un ´arbol, es conocido como recorrido en orden, su funcionamiento es como sigue:

1 Recorrer el sub´arbol izquierdo mediante recorrido en orden.

2 Visitar la ra´ız.

3 Recorrer el sub´arbol derecho mediante recorrido en orden. • El recorrido en orden, los nodos se visitan atendiendo el orden

lexicogr´afico de las claves.

private void enOrden (Nodo p) { if (p != null) { enOrden (p.izq); System.out.println (p.reg.toString()); enOrden (p.der); } }

(6)

Recursividad

Implementaci´

on de la recursividad

• Se utiliza un pila para almacenar los datos pasados en cada llamada de un procedimiento que no ha terminado.

• Todos los datos que no son globales se guardan en la pila registrando el estado actual del procedimiento.

• Cuando un procedimiento interior termina, los datos de la pila son recuperados.

• En el caso del algoritmo b´usqueda en orden:

• En cada llamada se almacena en la pila el valor de p y la direcci´on de retorno de la llamada recursiva.

• Cuando el procedimiento en alcanza p = nill, se retorna al

procedimiento que lo llam´o utilizando la direcci´on de llamada que se encuentra encima de la pila.

(7)

Recursividad

Cuando no usar recursividad

• No todos los problemas de naturaleza recursiva deben ser resueltos utilizando recursividad.

• Calculo de los n´umeros de Fibonacci:

f0= 0, f1= 1

fn= fn−1+ fn−2, para : n ≥ 2

• Soluci´on: fn=√1 5[Φ

n− (1 − Φ)n] donde Φ = (1 +5)/2 ≈ 1, 618 es conocida como la

raz´on de oro.

• El procedimiento recursivo obtenido directamente de la sucesi´on es la siguiente: package cap2;

public class Fibonacci {

public static int fibRec (int n) { if (n < 2) return n;

else return (fibRec (n-1) + fibRec (n-2)); }

(8)

Recursividad

Cuando no usar recursividad

• El programa es extremadamente ineficiente, ya que recalcula el mismo valor varias veces.

• En este caso la complejidad de tiempo esta dado por f (n) es de orden O(Φn)

(9)

Recursividad

Versi´

on iterativa para el calculo de la secuencia de

Fibonacci

public static int fibIter (int n) { int i = 1, f = 0; for (int k = 1; k <= n; k++) { f = i + f; i = f - i; } return f; }

• El programa tiene complejidad temporal O(n)

• Debemos usar recursividad cuando existe una soluci´on obvia de manera iterativa.

(10)

Recursividad

Mas ejemplos - Factorial

public class Factorial {

public int factorial(int n){ if (n <= 1)

return (1); else

return (n*factorial(n-1)); }

• Si T(n) es la ecuaci´on de recurrencia para el algoritmo f actorial entonces: T (n) = c1, n ≤ 1 T (n) = T (n − 1) + c2, n > 1 Entonces: T (n) = T (n − 1) + c2 T (n) = (T (n − 2) + c2) + c2= T (n − 2) + 2 × c2 T (n) = (T (n − 3) + c2) + 2 × c2= T (n − 2) + 3 × c2 T (n) = T (n − k) + k × c2

(11)

Recursividad

Mas ejemplos - Torres de Hanoi

public class Hanoi {

public void hanoi(int n, int inicio, int temp, int fin){ if (n > 0){

hanoi (n-1, inicio, fin, temp );

System.out.println("mover del poste " + inicio + " al " + fin); hanoi (n-1, temp, inicio, fin);

} }

(12)

Algoritmos de prueba y error (Backtracking)

Backtracking

• Prueba y error: descomponer el proceso en un n´umero finito de subtareas parciales que deben ser exploradas exahustivamente.

• El proceso de prueba gradualmente construye y prerecorre un ´arbol de subtareas.

• Los algoritmos de prueba y error no siguen una regla fija de computaci´on:

• Los pasos en direcci´on a la soluci´on final son efectuados y realizados.

• En caso los pasos anteriores no lleven a la soluci´on final, ellos pueden ser retirados y borrados del registro.

• Cuando la b´usqueda en el ´arboles de soluciones crece r´apidamente es necesario utilizar algoritmos aproximados o heur´ısticas que no garantizan una soluci´on ´optima pero son r´apidos.

(13)

Algoritmos de prueba y error (Backtracking)

Movimiento del Caballo

• Tablero con n × n posiciones, el caballo se mueve seg´un las reglas del ajedrez.

• Problema: a partir de (x0, y0), encontrar, si existe, un recorrido del caballo que visite

todos los puntos del tablero una ´unica vez.

• Intenta el siguiente movimiento: void Intenta {

Inicializa selecci´on de movimientos; do {

selecciona el siguiente candidato para mover; if (movimiento aceptable) {

registra movimiento;

if (tablero no esta recorrido){

intenta nuevo movimiento; // llamada recursiva if ( no ha tenido exito)

borra registro anterior; }

}

} while (se produzca movimiento) o (no haya candidatos para mover); }

(14)

Algoritmos de prueba y error (Backtracking)

Ejemplo de Backtracking: Movimiento de caballo

• El tablero puede ser representado por una matriz de n × n.

• La situaci´on de cada posici´on puede ser representada por un entero para registrar la hist´oria del recorrido:

• t[x, y] = 0, posici´on < x, y > no visita.

• t[x, y] = i, posici´on < x, y > visitada en el i−´esimo movimiento, 1 ≤ i ≤ n2.

(15)

Algoritmos de prueba y error (Backtracking)

Ejemplo de Backtracking: Movimiento de caballo

package cap2;

public class RecorridoCaballo { private int n; // Tama~no del tablero private int a[], b[], t[][];

public RecorridoCaballo (int n) { this.n = n;

this.t = new int[n][n]; this.a = new int[n]; this.b = new int[n];

a[0] = 2; a[1] = 1; a[2] =-1; a[3] =-2;

b[0] = 1; b[1] = 2; b[2] = 2; b[3] = 1;

a[4] = -2; a[5] = -1; a[6] = 1; a[7] = 2; b[4] = -1; b[5] = -2; b[6] =-2; b[7] = -1; for (int i = 0; i < n; i++)

for (int j = 0; j < n; j++) t[i][j] = 0; t[0][0] = 1; // posici´on de inicio

(16)

Algoritmos de prueba y error (Backtracking)

Ejemplo de Backtracking: Movimiento de caballo

public boolean intenta (int i, int x, int y) { int u, v, k; boolean q;

k = -1; // inicializa la posici´on de movimientos do {

k = k + 1; q = false; u = x + a[k]; v = y + b[k];

/* Verifica los limites del tablero */

if ((u >= 0) && (u <= 7) && (v >= 0) && (v <= 7)) if (t[u][v] == 0) {

t[u][v] = i;

if (i < n * n) { // el tablero no se encuentra lleno q = intenta (i+1, u, v); // intenta un nuevo movimiento if (!q) t[u][v] = 0; // no hay exito, inicializa el registro }

else q = true; }

} while (!q && (k != 7)); // no hay celdas para visitar a partir de x,y return q;

(17)

Algoritmos de prueba y error (Backtracking)

Ejemplo de Backtracking: Movimiento de caballo

public void imprimeRecorrido () { for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++)

System.out.print ("\t" +this.t[i][j]); System.out.println ();

} }

public static void main (String[] args) {

RecorridoCaballo recorridoCaballo = new RecorridoCaballo (8); boolean q = recorridoCaballo.intenta (2, 0, 0);

if (q) recorridoCaballo.imprimeRecorrido(); else System.out.println ("Sin soluci´on"); }

(18)

Divide y vencer´as

Divide y vencer´

as

• Consiste en dividir o problema en partes menores, encontrar soluciones para las partes, y combinarlas en una soluci´on global.

• Ejemplo: encontrar el mayor o menor elemento de un vector de enteros, v[0 . . . n − 1], n ≥ 1

• Cada llamada de maxM in4 asigna a maxM in[0] y maxM in[1] el mayor y menor elemento en v[linf ], v[lin + 1], . . . , v[lsup]

(19)

Divide y vencer´as

Divide y vencer´

as

package cap2;

public class MaxMin4 {

public static int [] maxMin4 (int v[], int linf, int lsup) { int maxMin[] = new int[2];

if (lsup - linf <= 1) { if (v[linf] < v[lsup]) {

maxMin[0] = v[lsup]; maxMin[1] = v[linf]; }else {

maxMin[0] = v[linf]; maxMin[1] = v[lsup]; } }else {

int medio = (linf + lsup)/2; maxMin = maxMin4 (v, linf, medio); int max1 = maxMin[0], min1 = maxMin[1]; maxMin = maxMin4 (v, medio + 1, lsup); int max2 = maxMin[0], min2 = maxMin[1];

if (max1 > max2) maxMin[0] = max1; else maxMin[0] = max2; if (min1 < min2) maxMin[1] = min1; else maxMin[1] = min2; }

return maxMin; }

(20)

Divide y vencer´as

Divide y vencer´

as - An´

alisis del ejemplo

• Sea T (n) una funci´on de complejidad tal que T (n) es el n´umero de comparaciones entre los elementos de v y v contiene n elementos:

T (n) = 1 para n ≤ 2,

T (n) = T (bn/2c) + T (dn/2e) + 2 para n > 2,

• Podemos notar que n = 2jpara alg´un entero postivo j:

T (n) = 2T (n/2) + 2 T (n) = 2(2T (n/4) + 2) + 2 = 4T (n/4) + 4 + 2 T (n) = 4(2T (n/8) + 2) + 4 + 2 = 8T (n/8) + 8 + 4 + 2 Entonces: T (n) = 2kT (n/2k) +Pj=k j=12k

Tendra soluci´on cuando: n

2k= 2 entonces: k = lg n − 1 y T (n) = 2lg n−1+Plg n−1

i=1 2i

T (n) = n2 + [1−21−2lg n] − 1 =n2 − 1 + n − 1 T (n) = 3n2 − 2

(21)

Divide y vencer´as

Divide y vencer´

as: An´

alisis del ejemplo

• Conforme al teorema revisado en el cap´ıtulo 1, el algoritmo es ´optimo.

• Sin embargo, puede ser peor que los presentados en el cap´ıtulo 1, pues, por cada llamada del m´etodo se guardan los valores de linf , lsup, maxM in[0], maxM in[1], en la direcci´on de la llamada de retorno de cada llamada.

• Adem´as, una comparaci´on adicional es necesaria para cada llamada recursiva para verificar que: lsup − linf ≤ 1.

• n debe ser menor que la mitad del mayor entero que puede ser representado por el compilador, para no provocar overf low en la operaci´on lsup − linf .

(22)

Balanceado

Balanceado

• En el dise˜no de algoritmos es importante mantener el balanceado la subdivisi´on de un problema en partes menores.

• En divide y vencer´as no es la ´unica t´ecnica donde el balanceado es ´

util.

• Si consideramos el siguiente ejemplo de ordenaci´on:

• Selecciona el menor elemento del conjunto v[0 . . . n − 1] entonces intercambia este elemento con el primer elemento v[0].

• Repite el proceso con los n − 1 elementos restantes, el segundo mayor elemento es intercambiado con el segundo elemento v[1].

(23)

Balanceado

Balanceado - An´

alisis del ejemplo

• El algoritmo lleva a la siguiente ecuaci´on de recurrencia: T (n) = T (n − 1) + (n − 1), T (1) = 0, para los n´umeros de comparaciones entre elementos.

• Sustituyendo: T (n) = T (n − 1) + n − 1

T (n) = T (n − 2) + n − 2 + n − 1 = T (n − 2) + 2n − 2 T (n) = T (n − k) + kn −Pi=k

i=1i

Tiene soluci´on cuando n − k = 1, entonces k = n − 1, sustituyendo: T (n) = T (1) + (n − 1)n −Pi=n−1 i=1 i = 0 + n2− n − [ n(n+1) 2 − n] T (n) = n22 − n 2

(24)

Balanceado

Balanceado - An´

alisis del ejemplo

• El algoritmo no es eficiente para valores grandes de n.

• Para mejorar la eficiencia asint´otica, es necesario balancear: dividir el problema en dos subproblemas de tama˜nos aproximadamente iguales, en vez de uno de tama˜no 1 y otro de tama˜no n − 1.

(25)

Balanceado

Ejemplo de Balanceado - MergeSort

• Fusi´on: unir dos archivos ordenados, generando un tercero ordenado (merge).

• Copiar en el tercer archivo, el menor elemento de entre los menores de los ficheros iniciales, obviando este elemento de los pasos posteriores.

• Este proceso se debe repetir hasta que todos los elementos de los archivos de entrada se hayan copiado al tercer archivo.

• Algoritmo MergeSort:

1 Dividir recursivamente el vector a ser ordenado en dos, hasta obtener n vectores de un ´unico elemento.

2 Aplicar la fusi´on teniendo como entrada dos vectores de un elemento, formando un vector ordenado de un elemento.

3 Repetir el proceso formando vectores ordenados cada vez mayores hasta que todo el vector se encuentre ordenado.

(26)

Balanceado

Ejemplo de Balanceado - MergeSort

package cap2;

public class Ordenacion {

public static void mergeSort (int v[], int i, int j) { if (i < j) {

int m = (i + j)/2; mergeSort (v, i, m); mergeSort (v, m + 1, j);

merge (v, i, m, j); // Fusiona v[i..m] y v[m+1..j] en v[i..j] }

}

• Considere n como una potencia de 2.

• merge(v, i, m, j), recibe dos secuencias ordenadas v[i...m] y v[m + 1..j] y produce otra secuencia ordenada de los elementos anteriores.

• Como v[i...m] y v[m + 1..j] se encuentran ordenados, merge necesita como m´aximo n − 1 comparaciones.

• merge selecciona repetidamente el menor entre los menores elementos restantes en v[i...m] y v[m + 1..j]. En caso de igualdad lo retira de cualquiera de las listas.

(27)

Balanceado

An´

alisis de Ejemplo - MergeSort

• El comportamiento de mergesort puede representarse por la siguiente ecuaci´on de recurrencia:

T (n) = 2T (n/2) + n − 1, para n > 1. T (1) = 0

• La soluci´on a la ecuaci´on de recurrencia es: T (n) = n log n − n + 1. Resolver!!

(28)

Programaci´on Din´amica

Programaci´

on Din´

amica

• Cuando la suma de los tama˜nos de los subproblemas en un algoritmo recursivo es O(n), entonces es probable que el algoritmo recursivo tenga complejidad polinomial.

• Pero cuando la divisi´on de un problema de tama˜no n resulta en n subproblemas de tama˜no n − 1 entonces, es probable que el algoritmo recursivo tenga complejidad exponencial.

• En este caso, con la t´ecnica de la programaci´on din´amica se puede proponer un algoritmo m´as eficiente.

• La programaci´on di´amica calcula la soluci´on para todos los

subproblemas, partiendo de subproblemas menores hacia los mayores, almacenando los valores en una tabla.

• La ventaja es, que una vez que un subproblema es resuelto, la respuesta es almacenada en una tabla y nunca m´as es recalculado.

(29)

Programaci´on Din´amica

Programaci´

on Din´

amica - Ejemplo

• En el calculo de los n´umeros de Fibonacci, en el ejemplo anterior, se mostr´o un algoritmo recursivo de orden exponencial. Esto se debe, que se realizan calculos repetidos para obtener los valores de la sucesi´on, que habi´endose calculado previamente, no son registrados y utilizados para los calculos posteriores.

• Para el c´alculo de los n´umeros de Fibonacci, es posible dise˜nar un algoritmo que en tiempo lineal lo resuelva mediante la construcci´on de una tabla que permita ir almacenando los calculos realizados y luego reutilizarlos.

Fibonacci (n: entero): entero T[0] = 1

T[1] = 1

para i= 2,..., n hacer T[i]= T[i-1] + T[i-2] fin_para

(30)

Algoritmos Voraces y aproximados

Algoritmos voraces

• Resuelve problemas de optimizaci´on.

• Ejemplo: un algoritmo para encontrar el camino mas corto entre dos vertices de un grafo: • Selecciona la arista que parece mas promisoria en cualquier instante.

• Independientemente de lo que pueda suceder adelante y no reconsidera su decisi´on.

• No necesita evaluar alternativas, o usar procedimientos sofisticados para deshacer decisiones tomadas previamente.

• Problema general: dado un conjunto C, determinar un subconjunto S ⊆ C, tal que: • S satisface las propiedades de P , y

• S es m´ınimo (o m´aximo) en relaci´on a alg´un criterio α.

• El algoritmo voraz para resolver un problema, consiste en un proceso iterativo donde S es construido adicionando elementos de C uno a uno.

(31)

Algoritmos Voraces y aproximados

Caracter´ısticas de los algoritmos voraces

• Para construir la soluci´on ´optima existe un cojunto o lista de candidatos.

• Se elijen un conjunto de candidatos y otros se rechazan.

• Existe una funci´on que verifica si un conjunto particular de candidatos produce una soluci´on.

• Otra funci´on se encarga de verificar si la soluci´on es viable.

• Una funci´on de selecci´on, evalua en cualquier momento cual de los candidatos es el mas promisorio.

• Una funci´on objetivo, valoriza la soluci´on encontrada, como la longitud del camino construido (no aparece de manera explicita en el algoritmo voraz).

(32)

Algoritmos Voraces y aproximados

Seudoc´

odigo del algoritmo voraz

C Conjunto de candidatos S = ∅

Mientras C 6= ∅ y S no sea soluci´on

• x = selecciona (C); • C = C − x; • Si es viable (S + x) entonces S = S + x Si es soluci´on S • Retorna S Caso contrario

(33)

Algoritmos Voraces y aproximados

Caracter´ısticas de los algoritmos voraces

• La funci´on de selecci´on generalmente esta relacionada con la funci´on objetivo.

• Si el objetivo es:

• Maximizar ⇒ seleccionar´a el siguiente candidato que proporcione la mayor ganancia individual.

• Minimizar ⇒ seleccionar´a el candidato restante de menor costo. • El algoritmos nunca cambia de idea:

• Una vez que un candidato es seleccionado, es adicionado a la soluci´on y permanece para siempre.

• Una vez que el candidato es rechazado, nunca mas vuelve a ser considerado.

(34)

Algoritmos Voraces y aproximados

Algoritmos aproximados

• Los problemas que poseen algoritmos exponenciales para encontrar una soluci´on exacta, son considerados dif´ıciles.

• Los problemas considerados intratables o dif´ıciles son muy comunes, como por ejemplo, el problema del agente viajero el cual es O(n!).

• Frente a un problema dif´ıcil es com´un retirar la exigencia de que el algoritmo tenga que encontrar una soluci´on ´optima.

• En este caso utilizamos algoritmos eficientes que no garanticen tener una soluci´on optima, pero que sea lo mas pr´oxima posible a ella.

(35)

Algoritmos Voraces y aproximados

Tipos de algoritmos aproximados

• Heur´ıstica: Es un algoritmo que puede producir un buen resultado, o una soluci´on ´optima, as´ı como obtener una soluci´on que se encuentre distante de la ´optima.

• Algoritmos aproximados: Es un algoritmo que genera soluciones aproximadas dentro de un l´ımite en relaci´on a la soluci´on optima. La eficiencia del algoritmo depende de la calidad de los resultados.

(36)

Algoritmos Voraces y aproximados Ejemplos de algoritmos voraces

Dar Cambio Maquina expendedora

• Una maquina expendedora para devolver el cambio puede utilizar monedas de 2, 1, 0,50, 0,20 y 0,10 nuevos soles. El problema consiste en pagar el cambio a un cliente utilizando el menor n´umero posible de monedas.

(37)

Algoritmos Voraces y aproximados Ejemplos de algoritmos voraces

El problema de la mochila

• Dados n objetos y una mochila donde llevarlos. Para cada objeto i = 1, 2, 3, . . . , n el objeto i tiene un peso positivo wi y un valor

positivo vi. La mochila tiene una capacidad de carga W .

• El objetivo es llenar la mochila de tal manera que se maximice el valor de los objetos transportados, respetando la capacidad de la misma.

• En esta versi´on del problema suponemos que podemos partir los objetos en trozos mas peque˜nos, de tal manera que podemos llevar una fracci´on xi del objeto i (0 ≤ xi ≤ 1). En este caso el objeto xi

contribuye en xiwi al peso de la carga y en xivi al valor de la carga.

El problema se puede formular de la siguiente manera: M axPn

i=1xivi con la restricci´on

Pn

(38)

Algoritmos Voraces y aproximados Ejemplos de algoritmos voraces

El problema de la mochila

• Supongamos que tenemos los siguientes datos para resolver el problema:

(39)

Algoritmos Voraces y aproximados Ejemplos de algoritmos voraces

Planificaci´

on de tareas

• Se tiene un unico procesador que tiene que dar servicio a n clientes. El tiempo de atenci´on requerido por cada cliente se conoce de antemano, el cliente i requerir´a un tiempo ti para 1 ≤ i ≤ n. El

objetivo es minimizar el tiempo que pasan los clientes en el sistema. Es decir, se requiere minimizar:

T =Pn

i=1(tiempo en el sistema para el cliente i)

• Supongamos que tenemos 3 clientes, con t1= 5, t2 = 10 y t3= 3,

Referencias

Documento similar

b) El Tribunal Constitucional se encuadra dentro de una organiza- ción jurídico constitucional que asume la supremacía de los dere- chos fundamentales y que reconoce la separación

No conozco ninguna palabra castellana capaz de juntar cosas tan dispares como la gratitud, el favor, la belleza, la complacencia y la alegría [para nosotros, el placer que

 La enseñanza, el aprendizaje y la difusión de la ciencia son procesos que determinan el desarrollo científico y tecnológico de un país; vivimos en una sociedad donde la ciencia

Calcular los l´ımites

La primera opción como algoritmo de compresión para secuencias biológicas que sugirió la directora del proyecto fue la adaptación de los algoritmos de Lempel-Ziv al alfabeto formado

En esta sección se tratan las características que debe tener un compresor de secuencias biológicas para poder ser usado como herramienta en la construcción de los árboles de

Volviendo a la jurisprudencia del Tribunal de Justicia, conviene recor- dar que, con el tiempo, este órgano se vio en la necesidad de determinar si los actos de los Estados

Es cierto que si esa persona ha tenido éxito en algo puede que sea trabajadora, exigente consigo misma y con los demás (eso sí son virtudes que valoro en