Uploaded by Breimer Palacios

Análisis y Diseño de Algoritmos

advertisement
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
Dr. Jaime Osorio Ubaldo
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
1 / 40
Técnicas de diseño de algoritmos
El diseño de algoritmos es un área central en la Ciencia de la Computación,
existen diversas técnicas o patrones de diseño de algoritmos que son muy
útiles para la solución de diversos problemas, las principales son:
1
Divide y vencerás.
2
Programación dinámica.
3
Algoritmo Greedy ( voraz, goloso, devorador o ávido).
4
Backtracking.
5
Ramificación y poda.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
2 / 40
Divide y vencerás
Consiste en resolver el problema a partir de la solución de subproblemas
del mismo tipo que el original. Si el problema x es de tamaño n y los
tamaños de los subproblemas x1 , x2 , · · · , xk son respectivamente
n1 , n2 , · · · , nk el tiempo de ejecución T (n) está definido por
T (n) =





g (n),
k
X
n ≤ no
T (nj ) + f (n), n > no
j=1
donde
1
no y g (n) son respectivamente es el tamaño y el costo temporal del
caso base.
2
f (n) es el tiempo de descomponer en subproblemas y combinar el
resultado de estos.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
3 / 40
Procedimiento
1
Plantear el problema de forma que pueda descomponerse en k
subproblemas del mismo tipo, pero de menor tamaño y que no exista
traslape entre ellos (si hay traslape se puede usar otra técnica llamada
programación dinámica),
2
Resolver independientemente todos los subproblemas, ya sea
directamente si son elementales o bien de forma recursiva.
3
Por último, combinar las soluciones obtenidas en el paso anterior para
construir la solución del problema original.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
4 / 40
Mergesort (ordenamiento por mezcla)
Su estrategia consiste en dividir un conjunto de datos en dos subconjuntos
que sean de un tamaño tan similar como sea posible (a la mitad), dividir
estas dos partes mediante llamadas recursivas, y finalmente, al llegar al
caso base, mezclar los dos partes para obtener un arreglo ordenado.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
5 / 40
Mergesort
Pasos
1
Dividir recursivamente la primera mitad del conjunto.
2
Dividir recursivamente la segunda mitad del conjunto.
3
Mezclar los dos subarreglos para ordenarlos.
Observación. Ignoramos los casos base.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
6 / 40
Mergesort
Merge(A, B, C )
i = 1, j = 1
para k = 1 · · · n hacer
si A[i] < B[j] entonces
C [k] = A[i]
i =i +1
sino
C [k] = B[j]
j =j +1
fin si
fin para.
El costo temporal es T1 = 4n + 2.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
7 / 40
Mergesort
MergeSort(A[1, · · · , n])
si n = 1
return A
m ≈ n/2
B = MergeSort(A[1, · · · , m])
C = MergeSort(A[m + 1, · · · , n])
Merge(B, C , A0 )
return A0 .
El costo temporal es
T (n) = 2T
Jaime Osorio
n
2
+ 4n + 2
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
8 / 40
Mergesort
De la figura
n
=1
2k
luego
k = log2 n
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
9 / 40
Mergesort
T (n) ≤ 2T (n/2) + 6n
= 22 T (n/4) + 2(6n)
..
.
= 2k T (1) + k(6n)
= 2log2 n (2) + 6n log2 n
= 2n + 6n log2 n
Por lo tanto, es de orden O(n log2 n).
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
10 / 4
Programación Dinámica
“En la programación dinámica normalmente se empieza por los subcasos
más pequeños, y por tanto más sencillos. Combinando sus soluciones,
obtenemos las respuestas para subcasos de tamaños cada vez mayores,
hasta que finalmente llegamos a la solución del caso original” [Brassard
Bratley, 2008].
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
11 / 4
Programación Dinámica
La programación dinámica permite mejorar el rendimiento de algunos
algoritmos.
En ciertos casos, cuando se divide un problema en varios
subproblemas, resulta ser que éstos no son independientes entre sı́, es
decir, cuando se resuelve un subproblema traslapado con otros, esto
genera que se tenga que procesar nuevamente la solución generando
un mayor costo temporal (en algunos casos puede generar un
algoritmo de complejidad exponencial).
La programación dinámica consiste en conservar la solución de cada
subproblema que ya se ha resuelto en una tabla (puede ser un arreglo
o una matriz), para tomarla cuando ésta se requiera.
Esto reduce el tiempo necesario para obtener el resultado final, pues
evita repetir algunos de los cálculos.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
12 / 4
Pasos
1
Plantear un algoritmo de solución recursivo.
2
Organizar las soluciones parciales en una tabla.
3
Modificar el algoritmo recursivo de manera que antes de hacer el
llamado recursivo se consulte la tabla y, en caso de hallarse la
solución en ésta, ya no se hace el llamado recursivo.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
13 / 4
Aplicación: Sucesión de Fibonacci
La sucesión de Fibonacci es
1, 1, 2, 3, 5, 8, · · ·
recursivamente está definida por
1,
n = 0, 1
F (n) =
F (n − 1) + F (n − 2), n ≥ 2
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
14 / 4
Algoritmo usando Programación Dinámica
Tabla[1 · · · n] = 0
Fibonacci(n)
Si n ≤ 1
retorna 1
Si Tabla[n] = 0
Tabla[n] = Fibonacci(n − 1) + Fibonacci(n − 2)
retorna Tabla[n]
Si bien en este algoritmo hay recursión, ésta solo se lleva a cabo cuando
no hay datos en la tabla. Cada vez que se requiere un nuevo dato en la
tabla se hace la recursión, sin embargo ésta no es muy profunda, ya que
toma los datos que ya se encuentran calculados en la tabla sin necesidad
de llegar cada vez al caso base.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
15 / 4
Caracterı́sticas
1
En la programación dinámica va guardando la historia y construyendo
la solución a partir de las soluciones óptimas de problemas más
pequeños resueltos previamente.
2
En la programación dinámica el problema debe poder dividirse en
varias etapas, y la decisión en una etapa se toma después de haber
considerado los resultados de otras etapas más simples.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
16 / 4
Algoritmo Greedy
Caracterı́sticas
1 Generalmente son utilizados para resolver problemas de optimización
(maximización o minimización de la función objetivo).
2 Estos algoritmos toman decisiones basándose en la información que se
tiene disponible de forma inmediata, sin tomar en cuenta los efectos
que pudieran causar en el futuro.
3 Son fáciles de implementar.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
17 / 4
Algoritmo Greedy
Procedimiento.
1 Se define el conjunto candidatos, por ejemplo: el conjunto de monedas
disponibles, las aristas de un grafo que se pueden utilizar para
construir una ruta, el conjunto de tareas que hay que planificar, etc.
2 En cada paso se considera al candidato indicado por una función de
selección voraz, que indica cuál es el candidato más prometedor de
entre los que aún no se han considerado, es decir, los que aún no han
sido seleccionados ni rechazados.
3 Conforme avanza el algoritmo, los elementos del conjunto inicial de
candidatos pasan a formar parte de uno de dos conjuntos: el de los
candidatos que ya se consideraron y que forman parte de la solución o
el conjunto de los candidatos que ya se consideraron y se rechazaron.
4 Mediante una función de factibilidad se determina si es posible o no
completar un conjunto de candidatos que constituya una solución al
problema. Es decir, se analiza si existe un candidato compatible con la
solución parcial construida hasta el momento.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
18 / 4
El problema del cambio
1
2
3
Dado un monto n y un conjunto de denominaciones de billetes, se
desea elegir la mı́nima cantidad de billetes cuya suma sea n.
Para construir la solución con un algoritmo voraz tomaremos cada vez
el billete de mayor denominación, siempre y cuando la suma
acumulada no sea mayor a n. El algoritmo falla cuando se presenta el
caso en el que la suma de los billetes no puede igualar a n, en este
caso el algoritmo ávido no encuentra una solución.
Nótese que en este caso en particular no existe restricción en cuanto
al número de billetes que se pueden tomar, es decir, siempre es posible
elegir k billetes de una determinada denominación. Por lo tanto no es
necesario tomar en cuenta si un billete ya se consideró o no.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
19 / 4
El problema del cambio
Veamos como serı́a la implementación
#define NUM 5
int denominaciones[NUM] = {100, 20, 10, 5, 1};
int numBilletes[NUM];
for (int i = 0; i < NUM; i + +)
numBilletes[i] = 0;
int SeleccionVoraz(int resto){
int i = 0;
while(i < NUM ∧ denominaciones[i] > resto)
i + +;
return i;
};
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
20 / 4
El problema del cambio
void cambio(int monto, int ∗ numBilletes){
int suma = 0;
int iDen = 0;
while(suma < monto){
iDen = SeleccionVoraz(monto − suma);
if (iDen >= NUM){
cout << “No se encontr ó soluci ón. ”;
return;
};
numBilletes[iDen] = numBilletes[iDen] + 1;
suma = suma + denominaciones[iDen];
};
}
Los algoritmos voraces construyen la solución en etapas sucesivas,
tratando siempre de tomar la decisión óptima para cada etapa. Sin
embargo, la búsqueda de óptimos locales no tiene por qué conducir
siempre a un óptimo global.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
21 / 4
El problema del cambio
Supongamos que queremos cambiar 15 soles y el sistema monetario está
compuesto por billetes de 1, 5 y 11 soles. El algoritmo voraz tomará
primero el billete mayor, por lo que la solución que obtendrá es
15 = 11 + 1 + 1 + 1 + 1
sin embargo, la solución óptima es
15 = 5 + 5 + 5
“La estrategia de los algoritmos ávidos consiste en tratar de ganar todas
las batallas sin pensar que, como bien saben los estrategas militares y los
jugadores de ajedrez, para ganar la guerra muchas veces es necesario
perder alguna batalla.” [Guerqueta y Vallecillo, 2000].
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
22 / 4
Backtracking
1
Cuando las técnicas divide y vencerás, algoritmos greedy y
programación dinámica no son aplicables para resolver un problema,
entonces no queda más remedio que utilizar la fuerza bruta
(algoritmos exhaustivos), pero esto en algunos casos resulta inviable
debido a su alto costo temporal.
2
El backtracking (búsqueda con retroceso) se aplica a problemas que
requieren de una búsqueda exhaustiva dentro del conjunto de todas
las soluciones potenciales. El espacio a explorar se estructura de tal
forma que se puedan ir descartando bloques de soluciones que
posiblemente no son satisfactorias
3
Los algoritmos de backtracking optimizan el proceso de búsqueda de
tal forma que se puede hallar una solución de una forma más rápida
que con los algoritmos de la fuerza bruta.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
23 / 4
Pasos
Ya que las soluciones se construyen por etapas se procede de la siguiente
manera:
1 El espacio de posibles soluciones se estructura como un árbol de
exploración (cuyos nodos son denominados nodos estados), en el que
en cada nivel se toma la decisión de la etapa correspondiente.
2 En cada etapa de búsqueda en el algoritmo backtracking, el recorrido
del árbol de búsqueda se realiza en profundidad. Si una extensión de
la solución parcial actual no es posible, se retrocede para intentar por
otro camino, de la misma manera en la que se procede en un
laberinto cuando se llega a un callejón sin salida.
3 Para una determinada etapa, se elige una de las opciones dentro del
árbol de búsqueda y se analiza para determinar si esta opción es
aceptable como parte de la solución parcial. En caso de que no sea
aceptable, se procede a elegir otra opción dentro de la misma etapa
hasta encontrar una opción aceptable o bien agotar todas las
opciones. Si se encuentra una solución aceptable, ésta se guarda y se
profundiza en el árbol de búsqueda aplicando el mismo proceso a la
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
24 / 4
Pseudocódigo
Función retroceso(etapa)
iniciarOpciones()
Hacer
opcion→ seleccionarOpción
Si aceptable(opcion) entonces
guardar(opcion)
Si incompleta(solución) entonces
exito → retroceso(siguiente(etapa))
Si(exito=FALSO) entonces
retirar(opcion)
finSi
Sino
exito → VERDADERO
finSi
finSi
mientras(exito= FALSO AND opcion 6= últimaOpción)
regresar exito
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
25 / 4
El problema de las N reinas
Consiste en situar N Reinas en un tablero de ajedrez de N filas y N
columnas, sin que se ataquen entre si.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
26 / 4
El problema de las N reinas
¿Cuántas alternativas tenemos que explorar?
Por conteo, tenemos 64 casillas en un tablero de ajedrezde 8 × 8, y 8
reinas que ubicar en posiciones distintas, ası́ que hay 64
8 alternativas de
solución (tableros con las 8 reinas situadas en posiciones distintas), esto
es, 4 426 165 368. Al número de alternativas se le denomina tamaño del
espacio de búsqueda, y siempre que tengamos un problema de búsqueda
como éste de las N-Reinas es conveniente estimarlo mediante técnicas de
conteo.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
27 / 4
Algoritmo
1
Ir colocando las reinas una a una en columnas en un vector A de tal
manera que no se ataquen
2
Si las primeras k reinas colocadas no se atacan avanzar a colocar la
reina k+1. Si tenemos éxito k irá aumentando de 1 hasta n y
habremos situado las n reinas en columnas donde no se ataquen entre
sı́ y obtendremos una solución.
3
Si no tenemos exito, es necesario hacer una vuelta atrás para cambiar
la columna en la que se colocó la última reina.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
28 / 4
Usando Backtracking
El árbol de exploración es
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
29 / 4
Ramificación y poda
Es una variante de backtracking.
Se usa generalmente para resolver problemas de optimización.
Hace uso de un árbol de expansión para analizar las posibles
soluciones.
A diferencia del backtracking, permite generar nodo utilizando
distintas estrategias.
Puede recorrer el árbol no solo en profundidad sino también en
anchura o utilizando el cálculo de funciones de coste para seleccionar
el nodo más prometedor.
Utiliza cotas (superiores o inferiores) para poder podar (eliminar)
ramas del árbol que no conducen a la solución óptima.
Estos algoritmos pueden ser paralelizados.
Necesita más memoria que los algoritmos backtracking, ya que cada
nodo debe contener la información necesaria para realizar los procesos
de bifurcación.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
30 / 4
Etapas
1
Selección. Consiste en extraer un nodo entre el conjunto de nodos
vivos (nodo que no ha sido podado).
2
Ramificación. Aquı́ se construyen los nodos hijos del nodo
seleccionado en la etapa anterior.
3
Poda. En esta etapa se eliminan algunos nodos creados en la
ramificación con la finalidad de disminuir el espacio de búsqueda y
atenuar la complejidad del algoritmo.
Con aquellos nodos que no han sido podados se repite nuevamente el
proceso de selección, el algoritmo termina cuando se encuentra la solución
o se agoten las posibilidades.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
31 / 4
Pseudocódigo
Repetir
elegir el nodo más prometedor como nodo P
generar todos sus hijos
podar el nodo P
Para cada hijo hacer
Si coste(hijo) > coste(mejor solución en curso) entonces se poda
sino
si (no es solución) entonces
se pasa a la lista de nodos vivos
sino
es la mejor solución en curso y se revisa la lista de nodos vivos,
eliminando los que prometen algo peor.
fsi
fsi
fpara
hasta que la lista está vacı́a.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
32 / 4
El problema de la Planificación de Tareas
Trab/Tarea
a
b
c
d
1
11
14
11
17
2
12
15
17
14
3
18
13
19
20
4
40
22
23
28
Cota Superior. Si asignamos al trabajador a tarea 1, al trabajador b
tarea 2, al trabajador c tarea 3 y al trabajador d tarea 4, el costo serı́a
de
11 + 15 + 19 + 28 = 73.
Si tomamos la otra diagonal será
40 + 13 + 17 + 17 = 87.
Por lo tanto, nos quedamos con 73 por se el menor.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
33 / 4
El problema de la Planificación de Tareas
Cota Inferior. Tomando el menor costo de cada tarea
independientemente de quien lo realice, con lo cual el costo será de
11 + 12 + 13 + 22 = 58.
Si tomamos el menor de cada fila asumiendo que cada trabajador
debe hacer alguna de ellas se tiene
11 + 13 + 11 + 14 = 49.
Por lo tanto, elegiremos 58. Con lo cual nuestro intervalo de
búsqueda será [58, 73].
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
34 / 4
El problema de la Planificación de Tareas
Busquemos otra cota inferior usando árboles con soluciones parciales.
Asignar al trabajador a la primera tarea, luego asignemos los menores
costos de las tareas a los demás, es decir, tarea 2 a d, tarea 3 a b y
tarea 4 a b, con lo cual es costo es de
11 + 14 + 13 + 22 = 60
Analogamente si asignamos al trabajador a la segunda tarea,
tendremos
11 + 12 + 13 + 22 = 58.
Ahora la tercera tarea
18 + 11 + 14 + 22 = 65
Y la cuarta tarea
40 + 11 + 14 + 13 = 78
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
35 / 4
El problema de la Planificación de Tareas
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
36 / 4
El problema de la Planificación de Tareas
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
37 / 4
El problema de la Planificación de Tareas
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
38 / 4
El problema de la Planificación de Tareas
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
39 / 4
Temas de investigación.
Resuelva los siguientes problemas usando algoritmos greedy, backtraking y
ramificación y poda:
1
El problema de la mochila.
2
El problema de la planificación de tareas.
3
El problema del agente viajero.
Jaime Osorio
Técnicas para el Diseño de Algoritmos
Estructura de Datos y Aplicaciones
40 / 4
Related documents
Download