Está en la página 1de 38

TÉCNICAS DE DISEÑO DE ALGORITMOS

Karim Guevara Puente de la Vega


Agenda
 Problemas
 Fuerza Bruta
 Recursividad
 Divide y Vencerás
 Programación Dinámica
Problemas
 De Decisión:
 ¿183287 es primo?
 ¿Cuál es el mínimo de la secuencia [7,8,32]?

 De optimización:
 Hallar el camino más corto

entre Buenos Aires y


Beijing
 Ordenar la secuencia:

[1,9,34,-2,6,28]

Elegir o proponer una solución.


Fuerza Bruta
 Probar todas las opciones.
 Se recorre todo el universo de soluciones posibles.

for cand in generarCandidatos()


if esSolucion(cand)
return cand;
Recursividad
 Cuando el problema se puede resolver a través de otros sub
problemas del mismo tipo y estructura.
 P.e. calcular xn
public static float elevar (float x, int n) {
if ( n == 0){
return 1;
else
return x * elevar(x, n-1);
}

public static float elevar (float x, int n) {


if ( n == 0){
return 1;
elseif (n es impar )
return x * elevar(x, n-1);
else return elevar(x*x, n/2);
}
Recursividad
 Torres de Hanoi

public class TorresDeHanoi {


static void Hanoi( int n, int a, int b, int c ) {
if( n>0 ) {
Hanoi( n-1, a, c, b );
System.out.println( a + " --> " + c );
Hanoi( n-1, b, a, c );
}
}
public static void main( String[] args ) {
Hanoi(Integer.parseInt(args[0]), 1, 2, 3 );
}
}
Recursividad
 No todo problema de naturaleza recursiva debe ser resuelto
con un algoritmo recursivo.
 Tales programas son fácilmente transformables en una versión
no recursiva.
 P.e. cuando no usar recursividad: cálculo de los números de
Fibonacci
public static int Fibonacci(int n){
if (n < 2)
return n;
else
return Fibonacci(n-1) + Fibonacci(n-2);
}

 El programa es extremadamente ineficiente.


Recursividad
 Versión iterativa
int FibonacciIterativo(int n)
{
int f = 0, i = 1;
for(j = 1; j <= n; j++){
f = f + i;
i = f - i;
}
return f;
}

 El programa tiene una complejidad O(n) y de espacio O(1).


Recursividad
 Usa una pila para almacenar los datos usados en cada
llamada a un procedimiento que aún no termino.
 Todos los datos no globales van a la pila registrando el
estado actual del cálculo
 Cuando una activación anterior prosigue, los datos de la pila
son recuperados
Recursividad
 Debemos evitar el uso de recursividad cuando existe solución
obvia por iteración
 Comparación de versiones recursivas e iterativas:

N 20 30 50 100
Recursiva 1 seg 2 min 21 días 109 años
Iterativa 1/3 mseg ½ mseg ¾ mseg 1.5 mseg
Divide y Vencerás (Divide para Reinar)
 Consiste en dividir el problema en partes menores, encontrar
soluciones para las partes y combinarlas en una solución
global.

DV(x){
if (x es suficientemente pequeño) {
return algoritmo_específico(x);
}
else {
descomponer x en {xi, …, xk}
for i = 1 to k
yi ← DV(xi)
y ← recombinar (yi, …, yk)
return y;
}
}
Divide y Vencerás (Divide para Reinar)
 P.e.: Multiplicación de número grandes X, Y de n dígitos
 Método tradicional : O(n2)
 Divide y reinarás?....

X = Xi * 10n/2 + Xd
Y = Yi * 10n/2 + Yd

X * Y = (Xi * 10n/2 + Xd) * (Yi * 10n/2 + Yd)

X * Y = Xi * Yi * 10n + (Xi * Yd + Xd * Yi) *


10n/2 + Xd * Yd
Multiplicación de enteros grandes
 P.e.: A = 0981 B=1234

Divide y Venceras

Multplicar Desplazar Resultado


1) 09 12 4 108....
2) 09 34 2 306..
3) 81 12 2 972..
4) 81 34 0 2754
1210554

 Dados A*B, A es el multiplicando y B el multiplicador:


 n/2 digitosizq(A) * n/2 digitosizq(B) y desplazar_Izq(n digitosB)
 n/2 digitosizq(A) * n/2 digitosder(B) y desplazar_Izq(n/2 digitosB)
 n/2 digitosder(B) * n/2 digitosizq(A) y desplazar_Izq(n/2 digitosB)
 n/2 digitosder(B) * n/2 digitosder(B)
 Sumar
Multiplicación de enteros grandes
 P.e.: dados X=12345678 y Y=24680135, determinar X*Y

Dividir
X = 12345678 = xi * 104 + xd xi=1234 xd=5678
Y = 24680135 = yi * 104 + yd yi=2468 yd=0135

Combinar
X * Y = (Xi * 104 + Xd) * (Yi *104 + Yd)
= Xi * Yi * 108 + (Xi * Yd + Xd * Yi) *104 + Xd * Yd
Multiplicación de enteros grandes
función multiplica (X,Y,n) {
if (P es pequeño) {
return X*Y;
}else{
Obtener xi, xd, yi, yd; // DIVIDIR
z1 = multiplica (xi, yi, n/2);
z2 = multiplica (xi, yd, n/2);
z3 = multiplica (xd, yi, n/2);
z4 = multiplica (xd, yd, n/2);
aux = suma(z2,z3); // COMBINAR
z1 = desplaza_izq(z1,n);
aux = desplaza_izq(aux,n/2);
z = suma(z1,aux);
z = suma(z,z4);
return z;
}
}
Multiplicación de enteros grandes
función multiplica (X,Y,n) { eficiencia
if (P es pequeño) { O(1)
return X*Y; O(1)
}else{
Obtener xi, xd, yi, yd; O(n)
z1 = multiplica (xi, yi, n/2); T(n/2)
z2 = multiplica (xi, yd, n/2); T(n/2)
z3 = multiplica (xd, yi, n/2); T(n/2)
z4 = multiplica (xd, yd, n/2); T(n/2)
aux = suma(z2,z3); O(n)
z1 = desplaza_izq(z1,n); O(n)
aux = desplaza_izq(aux,n/2); O(n)
z = suma(z1,aux); O(n)
z = suma(z,z4); O(n)
return z; O(1)
}
}
Multiplicación de enteros grandes
 Tiempo de ejecución:

 El cuello de botella esta en el numero de multiplicaciones de


tamaño n/2.

 ¿Se podrá mejorar la eficiencia…..?


 Reducir el numero de multiplicaciones necesario…
Multiplicación de enteros grandes
r = (Xi + Xd) * ( Yi + Yd) = Xi*Yi * (Xi*Yd + Xd*Yi) + Xd*Yd
p = Xi * Yi
q = Xd * Yd
X * Y = Xi * Yi * 10n + (Xi * Yd + Xd * Yi) * 10n/2 + Xd * Yd

X*Y = p*10n + (r-p-q)*10n/2 + q

 Entonces podemos realizar una multiplicación de tamaño n a


partir de 3 multiplicaciones de tamaño n/2.
Multiplicación de enteros grandes
función multiplicaDV (X,Y,n){
if (P es pequeño) {
return X*Y;
} else {
Obtener xi, xd, yi, yd; // DIVIDIR
s1 = suma(xi,xd);
s2 = suma(yi,yd);
p = multiplicaDV (xi, yi, n/2);
q = multiplicaDV (xd, yd, n/2);
r = multiplicaDV (s1, s2, n/2);
aux = suma(r,-p,-q); // COMBINAR
aux = desplaza_izq(aux,n/2);
p = desplaza_izq(p,n);
z = suma(p,aux,q);
return z;
}
}
Multiplicación de enteros grandes
función multiplicaDV (X,Y,n){ eficiencia
if (P es pequeño) { O(1)
return X*Y; O(1)
} else {
Obtener xi, xd, yi, yd; O(n)
s1 = suma(xi,xd); O(n)
s2 = suma(yi,yd); O(n)
p = multiplicaDV (xi, yi, n/2); T(n/2)
q = multiplicaDV (xd, yd, n/2); T(n/2)
r = multiplicaDV (s1, s2, n/2); T(n/2)
aux = suma(r,-p,-q); O(n)
aux = desplaza_izq(aux,n/2); O(n)
p = desplaza_izq(p,n); O(n)
z = suma(p,aux,q); O(n)
return z; O(1)
}
}
Multiplicación de enteros grandes
Divide y Vencerás
Aspectos de diseño
 Algoritmo recursivo
 División del problema en subproblemas y combinación eficiente
de las soluciones parciales.
• Los subproblemas deben tener, aproximadamente, el mismo tamaño

 Algoritmo específico
 Para resolver problemas de tamaño pequeño

 Determinación del umbral.


 Para decidir cuándo finalizar la descomposición recursiva del
problema y aplicar el algoritmo específico
Programación Dinámica
 A veces la simple recursividad no es eficiente.
 P.e. Números de Fibonacci
fn = fn-1 + fn-2 (n ≥ 2)
f0 = 0
f1 = 1
 La definición de la recurrencia conduce inmediatamente a una
solución recursiva

public static int F(int n){


if (n<= 1) return n;
else
return F(n-1)+F(n-2);
}
T(n) = T(n-1) + T(n-2) + 1 n ≥ 2
T(n) = 1 n < 2  O(2n)
Programación Dinámica
 Calcula la solución para todos los subproblemas, partiendo
de los subproblemas menores para los mayores,
almacenando los resultados en una tabla.
 Una vez que un subproblema es resuelto, la respuesta es
almacenada en una tabla y nunca más es recalculado.
public static int F(int n, int []fib){
if (n<= 1) return 1;
fib[0] = 0;
fin[1] = 1;
for( int j=2; j<=n; ++j )
fib[j] = fib[j-1] + fib[j-2];
return fib[n]; O(n)
}
Programación Dinámica
 Calcula la solución para todos los subproblemas, partiendo
de los subproblemas menores para los mayores,
almacenando los resultados en una tabla.
 Una vez que un subproblema es resuelto, la respuesta es
almacenada en una tabla y nunca más es recalculado.
public static int F(int n, int []fib){
if (n<= 1) return 1;
fib[0] = 0;
fin[1] = 1;
for( int j=2; j<=n; ++j )
fib[j] = fib[j-1] + fib[j-2];
return fib[n]; O(n)
}
Programación Dinámica
 Calcula la solución para todos los subproblemas, partiendo
de los subproblemas menores para los mayores,
almacenando los resultados en una tabla.
 Una vez que un subproblema es resuelto, la respuesta es
almacenada en una tabla y nunca más es recalculado.
public static int F(int n, int []fib){
public static int F(int n, int []fib){
if (n<= 1) return 1;
if (n<= 1) return 1;
fib[0] = 0;
if (fib[n-1] ==-1)
fin[1] = 1;
fib[n-1] = F(n-1,fib);
for( int j=2; j<=n; ++j )
if (fib[n-2] ==-1)
fib[j] = fib[j-1] + fib[j-2];
fib[n-2] = F(n-2,fib); O(n)
return fib[n];
fib[n] = fib[n-1] + fib[n-2];
}
return fib[n];
}
Programación Dinámica
 P.e. Problema de la mochila

Objeto Peso Valor


 Se dispone:
1 1 15
 Capacidad mochila = 8
2 5 10
3 3 9
4 4 5
Problema de la mochila
 Wi representa el peso del objeto i
 Ci es el costo del objeto i
 M es la capacidad de la mochila
 n es el número de objetos.
 B[i,w] es el máximo valor obtenido cuando se seleccionan
objetos de 1 a i y cuyo peso máximo no sobrepasa la
capacidad de la mochila, donde 0 ≤ i ≤ n y 0 ≤ w ≤ M

B[i,w] = max(B[i-1,w],C[i] + B[i-1, w-W[i]]


Problema de la mochila

i/w 0 1 2 3 4 5 6 7 8
Objeto Peso Valor
0 0 0 0 0 0 0 0 0 0
1 1 15
1 0
2 5 10
2 0
3 3 9 3 0
4 4 5 4 0

B[i,w] = max(B[i-1,w],C[i] + B[i-1, w-W[i]]

Para i, w > 0:
si w-Wi < 0 Entonces
B[i,w] = B[i-1,w]
sino
B[i,w] = max(B[i-1,w],C[i] + B[i-1, w-W[i]]
Ejemplo- Problema de la mochila

 Para i = 1 y w = 1, el peso y costo del objeto 1 son W1 = 1 y C1 = 15.


Como w – W1 = 0 entonces:
B[1,1] = max (B[0,1], C1 + B[0,0]) = max (0,15+0) = 15
.......
 Para i = 3 y w = 4, el peso y el costo del objeto 3 son W3 = 3 y C3 = 9.
Como w – W3 = 1 > 0 entonces:
B[3,4] = max (B[2,4], C3 + B[2,1]) = max (15, 9+15) = 24
Ejemplo- Problema de la mochila
public static ProgDin(int []W, int []C, int n, int M) {
int [][] B = new int [n+1] [M+1]

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


B[i][0] = 0;
for(int i=0; i<=M; i++)
B[0][i] = 0;
for(int i=1; i<=n; i++)
for(int w=1; w<=M; w++)
if( w – W[i] < 0 )
B[i][w] = B[i-1][w];
else
if( B[i-1][w] > (C[i] + B[i-1][w-W[i]]) )
B[i][w] = B[i-1][w];
else
B[i][w] = C[i] + B[i-1][w-W[i]];
return B;
}
Problema de la mochila – ítems seleccionados

Hacer k = capacidad máxima mochila (k=8)


i=n
Si B[4,8] ≠ B[3,8]: // B[i,k] ≠ B[i-1,k]
ítem 4 debe estar en la mochila
reducir la capacidad menos el peso del ítem 4
k = k – W4 = 8 - 4 = 4
probar el ítem anterior
i = i – 1 = 4 – 1 = 3
Problema de la mochila – ítems seleccionados
 Como B[3,4]  B[2,4] entonces el item 3 en la mochila.

k = k – W3 = 4 – 3 = 1
Problema de la mochila – ítems seleccionados
 Si B[2,1] = B[1,1] entonces el item 2 no entra en la mochila.

i = i – 1 = 2 – 1 = 1
Problema de la mochila – ítems seleccionados
 Los valores de B[1,1]  B[0,1] entonces el item 1entra a la
mochila.

k = k – W1 = 1 – 1 = 0
Problema de la mochila – ítems seleccionados
 Entonces la combinación de objetos que maximizan el robo
es:
Problema de la mochila
 Para encontrar los items que fueron seleccionados hacemos
uso del algoritmo Backtrace
public static boolean[] ObjSel(int []W, int [][]B, int n, int M){
boolean X[] = new boolean[n];
int i = n; int k = M;
while (i > 0 && k > 0 ){
if ( B[i][k] != B[i-1][k] ){
X[i-1] = true; //{item i esta en la mochila};
k = k - W[i-1];
}
else
X[i-1] = false;
i--;
}
return X;
}
Pasos del diseño del algoritmo
 El diseño de un algoritmo de Programación Dinámica consta
de los siguientes pasos:
 Planteamiento de la solución como una sucesión de decisiones y
verificación de que ésta cumple el principio de óptimo.
 Definición recursiva de la solución.
 Cálculo del valor de la solución óptima mediante una tabla en
donde se almacenan soluciones a problemas parciales para
reutilizar los cálculos.
 Construcción de la solución óptima haciendo uso de la
información contenida en la tabla anterior.