UNIDAD I INTRODUCCIÓN A LA ALGORITMIA

Anuncio
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
López
UNIDAD I INTRODUCCIÓN A LA ALGORITMIA
1.1 INTRODUCCION
La Algoritmia es la ciencia que nos permite evaluar el efecto de los diversos factores
externos sobre los algoritmos disponibles, de tal modo que sea posible seleccionar el que
más se ajusta a nuestras circunstancias particulares; también es la ciencia que nos indica
la
forma de diseñar un nuevo algoritmo para una tarea concreta.
Problemas de la algoritmia:
Correctitud
Eficiencia
aproximación
La algoritmia es uno de los pilares de la programación y su relevancia se muestra en el
desarrollo de cualquier aplicación, más allá de la mera construcción de programas. Este es
un texto introductorio sobre análisis y diseño de algoritmos que pretende exponer al lector las
técnicas básicas para su diseño e implementación, así como presentar unas herramientas
que le permitan medir su efectividad y eficiencia.
1.2 FUNDAMENTOS
1.2.1 ALGORITMO
El matemático persa del siglo IX alKhowarizmi dio nombre a la palabra algoritmo, un
algoritmo es sencillamente un conjunto de reglas para efectuar algún cálculo, bien sea a
mano o, más frecuentemente, en una máquina.
“Un algoritmo es un conjunto de instrucciones sencillas, claramente
especificadas, que se debe seguir para resolver un problema. Una vez que
se da un algoritmo para un problema y se decide que es correcto, un paso
importante es determinar la cantidad de recursos, como tiempo o espacio,
que requerirá”
Además un algoritmo es una secuencia bien determinada de acciones elementales que
transforma los datos de entrada en datos de salida con el objetivo de resolver un
problema computacional.
DEFINICIÓN DE KNUTH
Una definición más formal de algoritmo se puede encontrar en el libro de Knuth [12]:
Definición 1.1 Un método de cálculo es una cuaterna (Q, I,W,f) donde:
Q es un conjunto que contiene a I y W, y
f:Q
Q con f(w) = w para todo w perteneciente a W.
Q es el conjunto de estados del cálculo,
1
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
I es el conjunto de estados de entrada,
W es el conjunto de estados de salida y
f es la regla de cálculo.
Definición 1.2 Una secuencia de cálculo es x0, x1, x2; . . . donde x0 I y
k 0
f(xk) = xk+1. La secuencia de cálculo acaba en n pasos si n es el menor
entero
con xn W.
Veamos cómo esta definición formal de algoritmo (el método de cálculo) cumple las
propiedades que debe de cumplir un algoritmo:
Se cumplirá el concepto de finitud si toda secuencia de cálculo a la que pueda dar
lugar el método de cálculo es finita.
Existe un conjunto de posibles entradas, que es I en la definición.
Existe un conjunto de posibles salidas, que es W en la definición.
En cuanto a la definibilidad, el algoritmo está definido con el método de cálculo, pero,
dado un método de cálculo, ¿se puede hacer un programa?,¿en qué lenguaje?, ¿en
un tiempo razonable?
El método de cálculo será eficiente si el valor de n en cada secuencia de cálculo un
valor "razonable".
1.2.2 PROBLEMA COMPUTACIONAL
Un problema computacional consiste en una caracterización de un conjunto de datos de
entrada, junto con una especificación de la salida deseada en base a cada entrada.
Un problema computacional tiene una o más instancias, valores particulares para los
datos de entrada, sobre las cuales se puede ejecutar un algoritmo para resolver el
problema.
Ejemplo 1: el problema computacional multiplicar dos números enteros tiene por ejemplo
las siguientes instancias:
multiplicar 345 por 4653,
multiplicar 2637 por 10000,
multiplicar 32341 por 1, etc..
Un problema computacional abarca a otro problema computacional si las instancias del
segundo pueden ser resueltas como instancias del primero en forma directa.
Ejemplo 2: multiplicar un entero por 352 es un problema computacional que es abarcado
por el problema multiplicar dos números enteros.
2
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
1.2.3 PASOS A SEGUIR EN LA RESOLUCIÓN DE UN PROBLEMA
En muchas situaciones reales, al enfrentarnos a un problema, no es evidente en un
primer intento conseguir la solución. La formulación del problema puede venir dada o la
podemos describir en lengua natural, el cual es un mecanismo de especificación
ambiguo, incompleto y redundante. Si uno busca una solución al problema, lo primero
que hace es eliminar la incompletitud, la redundancia y la ambigüedad. En otras
palabras, se desea una formulación del problema donde todo esté claro.
Es aquí es donde interviene el proceso de abstracción, que permite modificar el problema
a modelos equivalentes más simples, descartando detalles e información que, a priori, no
influyen en la solución y establecer un enunciado preciso del problema. En este proceso
el problema deviene más puro y abstracto, hablamos de un enunciado cerrado del
problema.
Existen variantes en la formulación de un enunciado cerrado. Estas consisten en
presentar una situación inicial (datos), una situación final (resultados esperados) y una
serie de operadores que permiten pasar de una situación factible a otra (estados). Una
solución del problema consiste en describir una serie de acciones, en términos de los
operadores, que permitan pasar de la situación inicial a la final. Es aquí donde interviene
el término solución algorítmica de un problema.
Un algoritmo es una descripción de la solución del problema mediante una secuencia
finita de acciones elementales que se supone son realizables a priori. Una acción es un
evento que dura un período de tiempo finito y produce un resultado bien definido y
previsto.
Los pasos principales a seguir en la resolución de un problema, mediante un algoritmo,
son:
a) Formulación del problema en lengua natural.
b) Especificar el problema mediante un lenguaje preciso, por ejemplo el lenguaje
matemático, eliminando así la redundancia, incompletitud y ambigüedad de la
información. En esta etapa se presenta en términos de modelos matemáticos, que
permiten simplificar el problema al hacer una abstracción de los datos innecesarios,
rescatando sólo la información realmente relevante. Se obtiene un enunciado cerrado.
c) Diseño y Análisis de una solución del problema en términos de una solución
algorítmica del enunciado cerrado.
d) Refinamiento del algoritmo hasta obtener una versión que pueda ser expresada en el
lenguaje de
programación que se haya escogido.
1.3 PARADIGMAS DE LA PROGRAMACION
Un paradigma está constituido por los supuestos teóricos generales, las leyes y las técnicas para su
aplicación que adoptan los miembros de una determinada comunidad
científica.
3
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Las leyes explícitamente establecidas y los supuestos teóricos. Por ejemplo, las leyes de
movimiento de Newton forman parte del paradigma newtoniano y las ecuaciones de
Maxwell forman parte del paradigma que constituye la teoría electromagnética clásica.
El instrumental y las técnicas instrumentales necesarios para hacer que las leyes del
paradigma se refieran al mundo real. La aplicación en astronomía del paradigma
newtoniano requiere el uso de diversos telescopios, junto con técnicas para su utilización
y diversas técnicas para corregir los datos recopilados.
Un componente adicional de los paradigmas lo constituyen algunos principios metafísicos
muy generales que guían el trabajo dentro del paradigma. Todos los paradigmas,
además, contienen prescripciones metodológicas muy generales tales como: "Hay que
intentar seriamente compaginar el paradigma con la naturaleza".
Podemos decir que, los paradigmas son marcos de referencia que imponen reglas sobre
cómo se deben hacer las cosas, indican qué es válido dentro del paradigma y qué está
fuera de sus límites. Un paradigma distinto implica nuevas reglas, elementos, límites y
maneras de pensar, o sea implica un cambio. Los paradigmas pueden ser considerados
como patrones de pensamiento para la resolución de problemas. Desde luego siempre
teniendo en cuenta los lenguajes de programación, según nuestro interés de estudio. Los
paradigmas de Programación representan un enfoque particular o filosofía para la
construcción del software. No es mejor uno que otro sino que cada uno tiene ventajas y
desventajas. También hay situaciones donde un paradigma resulta más apropiado que
otro.
Generalmente los autores clasifican los paradigmas de modos similares, siempre
destacan el imperativo, el orientado a objetos, el funcional y el lógico. Algunos autores o
profesores, mencionan paradigmas heurísticos, concurrentes, procedimentales,
declarativos y demostrativos.
1.3.1 PARADIGMA FUNCIONAL
Modelo matemático de composición funcional donde el resultado de un cálculo es la
entrada del siguiente, y así sucesivamente hasta que una composición produce el valor
deseado.
El paradigma funcional está representado por la familia de lenguajes LISP, en particular
Scheme o Haskell.
1.3.2 PARADIGMA IMPERATIVO
El paradigma imperativo es considerado el más común y está representado, por ejemplo,
por el C o por BASIC.
El Paradigmas Imperativo es un modelo abstracto que consiste en un gran
almacenamiento de memoria donde la computadora almacena una representación
4
Algoritmos Avanzados
Lic. Solange Salazar
e desarrollo
o y modelado
de software que permite
I
construir gmás n
r sistemas
g
fácilmente
complejos a partir de
a
.
componentes individuales.
m
a
S
Objetos + Mensajes =
.
i
Programa.
m
1
.
3
.
3
P
A
R
A
D
I
G
M
A
ó
n
O
n
o
f
r
e
L
ó
p
e
z
O
R
I
E
N
T
A
D
O
codificada de un
cálculo y ejecuta
una secuencia de
comandos que
modifican el
contenido de ese
almacenamiento.
A
l
g
o
r
i
t
m
o
s
A
+
D
i
s
c
i
p
l
i
n
a
E
s
t
r
u
c
t
u
r
a
d
e
O
B
J
E
T
O
S
d
e
=
i
n
g
e
n
i
e
r
í
a
P
r
d
D
a
t
o
s
Algoritmos Avanzados
L
i
c
.
UNIDAD II
S
o
l
a
n
g
e
S
a
l
a
z
a
r
I
n
g
.
S
i
m
ó
n
O
n
o
f
r
e
ANÁLISIS
DE
ALGORITM
OS
2.1. INTRODUCCIÓN
Para cada algoritmo es necesario aclarar cuales son las operaciones elementales y como
están representados los datos de entrada y de salida. Su ejecución requiere unos recursos.
Un algoritmo es mejor cuanto menos recursos consuma.
Otros criterios que se toman en cuenta son: facilidad de programarlo, corto, fácil de entender,
robusto...
Legible y bien documentado
Correcto
Programa
Ideal
EFICIENTE
Fácil de mantener y utilizar
2.2 COMPLEJIDAD DE ALGORITMOS
La eficiencia de los algoritmos se cuantifica con las medidas de complejidad:
— Complejidad temporal: tiempo de cómputo de un programa
— Complejidad espacial: memoria que utiliza un programa en su ejecución
También cuando se habla del comportamiento de los algoritmos se hace referencia al
análisis de complejidad.
Dado un algoritmo A, el tiempo de ejecución T A(n) de A es la cantidad de pasos,
operaciones o acciones elementales que debe realizar el algoritmo al ser ejecutado en una
instancia de tamaño n.
El espacio eA(n) de A es la cantidad de datos elementales que el algoritmo necesita al ser
ejecutado en una instancia de tamaño n.
La complejidad de un programa depende de
— el tamaño de los datos de entrada,
— el valor de los datos de entrada, y
— la máquina y el compilador.
Es claro que para cada algoritmo la cantidad de recursos (tiempo, memoria) insumidos
depende fuertemente de los datos de entrada. En general, la cantidad de recursos crece a
medida que crece el tamaño de la entrada.
Ejemplo 1
— para un vector su longitud,
— para un número su valor o su número de dígitos, …
6
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
2.3 TIPOS DE ANÁLISIS DE EFICIENCIA
Se definen distintos tipos de análisis:
a) Análisis en el peor caso: se considera el máximo entre las cantidades de recursos
insumidas por todas las instancias de tamaño n.
b) Análisis caso promedio: se considera el promedio de las cantidades de recursos
insumidas por todas las instancias de tamaño n.
c) Análisis probabilístico: se considera la cantidad de recursos de cada instancia de
tamaño n pesado por su probabilidad de ser ejecutada.
d) Análisis en el mejor caso: se considera el mínimo entre las cantidades de recursos
insumidas por todas las instancias de tamaño n.
Nos concentraremos en general a analizar el peor caso, debido a que: constituye
una cota superior al total de los recursos insumidos por el algoritmo.
Conocerla nos asegura que no se superar a esa cantidad. Para muchos
algoritmos, el peor caso es el que ocurre más seguido debido al uso de la
notación asintótica, el caso promedio o probabilístico es muchas veces el
mismo que el peor caso.
No se necesita conocer la distribución de probabilidades para todas las
instancias de un mismo tamaño, como será necesario en el análisis
probabilístico. Se considerará entonces que un algoritmo es más eficiente
que otro para resolver el mismo problema si su tiempo de ejecución (o
espacio) en el peor caso tiene un crecimiento menor.
2.4 EFICIENCIA
Relación entre los recursos consumidos y los productos conseguidos.
Ejemplo 2. ¿Cuántos recursos de tiempo y memoria consume el siguiente algoritmo sencillo?
i:= 0
a[n+1]:= x
repetir
i:= i + 1
hasta a[i] = x
1
1
1
Para la estructura repetitiva el Tiempo de Ejecución depende de los valores que toman x y n
Mejor caso. Se encuentra x en la 1ª posición:
Tiempo(N) = 1
Peor caso. No se encuentra x:
Tiempo(N) =·N
Considerando el contenido de los datos de entrada, se analiza.
Mejor caso. El contenido favorece una rápida ejecución.
Peor caso. La ejecución más lenta posible.
Caso promedio. Media de todos los posibles contenidos.
7
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Existen dos enfoques para medir la eficiencia:
Enfoque empírico o a posteriori: Consiste en programar todos los algoritmos candidatos e
ir probándolos en distintos casos con ayuda de una computadora.
Enfoque teórico o a priori. Que consiste en determinar matemáticamente la cantidad de
recursos necesarios para cada uno de los algoritmos como función del tamaño de los casos
considerados. Lo recursos que más nos interesan son el tiempo de computación y el espacio
de almacenamiento.
2.5 MODELO
Para analizar algoritmo en un marco formal, se necesita un modelo de computación. Nuestro
modelo es básicamente un computador normal, en el cual las instrucciones se ejecutan de
modo secuencial. El modelo tiene el repertorio estándar de instrucciones sencillas, como
adición, multiplicación, comparación y asignación, pero a diferencia de los computadores
reales, este tarda exactamente una unidad de tiempo en hacer cualquier operación sencilla.
Para ser razonable, se supondrá que, como un computador moderno, este modelo tiene
enteros de tamaño fijo y que no tienen instrucciones refinadas, como la inversión de matrices
o la clasificación, que claramente no se pueden hacer en una unidad de tiempo.
A continuación se definen algunas reglas básicas para la asignación de tiempos:
Operaciones básicas aritméticas y lógicas (+, -, *, :=,...): Una unidad de tiempo,
o alguna constante.
Operaciones de entrada salida: Otra unidad de tiempo, o una constante
diferente.
En ambos casos T(n)=1
Ejemplo 3
a) z=z+1
b) a>b
Tienen un T(n)=1, donde n es el tamaño (no conocido en este ejemplo) de la entrada
2.6 NOTACIONES PARA EXPRESAR LA COMPLEJIDAD EN TIEMPO
Una medida asintótica es un conjunto de funciones que muestran un comportamiento similar
cuando los argumentos toman valores muy grandes. Las medidas asintóticas se definen en
términos de una función de referencia f.
T(n) tiempo de ejecución de un programa con una entrada de tamaño n
Notación
que cg(n)
() Una función T(n) es
T(n) cuando n0 n
(g(n)) sí y solo sí existen unas constantes c y n 0 tales
Notación () Una función T(n) es (h(n)) sí y solo sí se cumple que existen unas constantes
positivas c1, c2 y n0 independientes de n tales que:
8
Algoritmos Avanzados
c1g(n)
T(n)
Lic. Solange Salazar
Ing. Simón Onofre
c2g(n)
n0 n
Notación O(): De manera formal se dice que una función f(n) es de orden O(g(n)) sii se
cumple que existen unas constantes positivas c y n 0 , ambas independientes tales que:
f(n)
c g(n) ,
n
n0
donde decidir que f(n) es O(g(n)) supone que cg(n) es una cota superior del tiempo de
ejecución del algoritmo
De esta manera definimos la medida asintótica de cota superior f, que notaremos O( f ) como
el conjunto de funciones
{ T | existen c Î Â +, n0 Î À tales que, para todo n ³ n0 , T(n) £ c × f(n) }
Si T Î O( f ) decimos que ―T(n) es del orden de f(n)‖ y que ―f es asintóticamente una cota
superior del crecimiento de T‖.
Las notaciones se interpretan también de la siguiente forma:
O(T): Orden de complejidad de T.
(T): Orden inferior de T, u omega de T.
(T): Orden exacto de T.
2.6.1 PROPIEDADES DE LA NOTACIÓN O()
Para cualquier par de funciones f(n) y g(n) se verifican las siguientes propiedades:
Pr1: cO(f(n)) es O(f(n)), donde c es una constante
Pr2: Regla de la suma:
O(f(n) + g(n)) es max(O(f(n)), O(g(n))
Pr3: O(f(n)) + O(g(n)) es O(f(n)+g(n))
Pr4: O(f(n))O(g(n)) es O(f(n)g(n))
Pr5: O(O(f(n))) es O(f(n))
Lo que buscamos es determinar matemáticamente la cantidad de recursos
Ejemplo 4 Determine O(g(n))
T(n)=2n es O(n), donde 2 es una constante
T(n)=n3 + n2 + log n es O(n3) puesto que n3 es el mayor
T(n)=nm+n es O(nm), si la entrada depende de n y m
O(O(n3)) es O(n3)
T(n) = 2n2/5 + 3 /2; T(n) O(n2). O T(n) e O(n)
Ejemplo 5
a) Sea el segmento de algoritmo
X=1 . . . . . .1
Z=6 . . . . . . 1
9
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
El tiempo de ejecución es T(n)= 1+1= 2 aplicando la ―regla de la suma‖ lo cual es O(1) por
la propiedad Pr3
Sea el segmento de algoritmo
For i=1 to 10 do
Begin
x=x+1 ..............1
y=y/i.................1
end
T(n)=T(for)*T(instrucciones internas del for)=10*2=12 por la propiedad de la multiplicación
y
es O(1) por Pr 2
2.6.2 ORDENES DE COMPLEJIDAD
donde:
O(1) O(log n)
O(n!)
O( n )
Ì O(n)
O(n log n)
O(n2)
O(n3)
...
O(nk)
...O(2n )
2.7 ANALISIS DE ALGORITMOS ITERATIVOS
A continuación se enumeran algunas reglas importantes para el análisis de
programas.
a) Ciclos for
El tiempo de ejecución de un ciclo for, es a lo más el tiempo de ejecución de las
instrucciones que están en el interior del ciclo for (incluyendo las condiciones), por el número
de iteraciones. Generalmente se usa el símbolo de sumatoria
Ejemplo 6
for i:=1 to n do
x:=x+1;
10
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
n
Ta (n)
T(for_i) * T(instr_in ternas)
1
n
i 1
Ciclos for anidados
Analizar de adentro hacia fuera. El tiempo de ejecución total de una proposición dentro del
grupo, de ciclos for anidados es el tiempo de ejecución de la proposición multiplicada por el
producto de los tamaños de todos los ciclos for.
T(For/nivel1)*T(For/Nivel2)* . . . *T(For/nivelN)
Ejemplo 7
for i:=1 to n do
for j:=1 to n do
for k:=1 to n do
x:=x+1;
n
Ta (n)
n
n
n
n
n
1
T(for_i)* T(for_j)* T(for_k)*1
i 1
j 1
n
k 11
i 1
n*n
n * n * n n3
i 1
j 1
que es O(n3) orden cúbico
Para If / Else
T(IF_THEN) = T(condición)+T(Then)
T(IF_THEN_ELSE) = T(condición)+max(T(Then),T(Else))
Ejemplo 8
i)
if N mod 2 = 0 then
for i:=1 to n do
x:=x+1;
Ta(n)= T(if_then)=T(condición)+T(then)
n
1
1
1
n es O(n)orden lineal
i 1
ii
) if N> 0 then
x:=x+N;
else
begin
N=N+100
Z=z+N
Endélse
Tb(n)= T(if_then_else)
= T(condición)+max(T(then),T(else))
1
max(1,2)
1
2
3
es O(1)orden lconstante
11
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
La noción de esta estructura selectiva se puede extender al uso del CASE (selección
múltiple)
While
Se realiza generalmente un análisis inductivo exhaustivo mediante una variable axiliar t cuya
misión es contar el tiempo del while. Además se realiza el análisi del peor de los casos de
entrada al bucle.
Ejemplo 9
¿Cuántas multiplicaciones realiza el siguiente algoritmo para calcular potencias, en el peor
de los casos?
Funcion potencia1(y : num; z : nat): nat
p := 1
1
mientras z > 0 hacer
p := p * y
1
1 z+1
z := z –1
fmientras
función
Analizando solamente la sentencia mientras se tiene
t=0
mientras z > 0 hacer
z = z -1
t = t +1
z
4
3
2
1
0
t
0
1
2
3
4
El tiempo del mientras (while) es:
z +1
la ctte 1 se adiciona debido a la
última pregunta de condición del
while
Finalmente:
Tpotencia1(z) = 1 + (z+1)2 = 1 + 2z + 2 = 3 + 2z que es O(z) orden lineal
2.8 LLAMADAS A PROCEDIMIENTOS Y FUNCIONES
Si se tiene un programa con procedimientos no recursivos es posible calcular el tiempo de
ejecución de los distintos procedimientos, uno a la vez partiendo de aquellos que no llaman a
otros.
Las siguientes formulas serán útiles para la resolución de ejercicios y algunas serán usadas
en esta sección
12
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
n
a)
1
n
i 1
n
b) 1 (n a 1).
i a
n
c)
i
i 1
n
d)
i2
n(n 1)
.
2
n(n
1)(2n
6
i 1
n
nr
e)
ir
i 1
(r
1)
1)
1
p r (n)
2.9 EJERCICIOS RESUELTOS
Mediante la notación asintótica, obténganse los tiempos de ejecución del peor caso supuesto
para cada uno de los procedimientos siguientes como una función de n
Ejercicio 1
i:=1;
while I<= n do
begin
x:=x+1;
I:=I+1;
End
Añadiendo una variable t que controlará el número de veces que se ingresa a la estructura
while
T=0
i:=1;
while i<= n do
i:=i+1;
t = t +1
i
1
2
3
4
5
t
0
1
2
3
4
n
4
El tiempo del while es:
n +1
Finalmente realizamos el análisis del segmento de algoritmo del Ejercicio 1:
T(n) = 1 + T(while)*2 = 1+(n+1)*2 = 1 + 2n + 2 = 3 + 2n es O(n) orden lineal
Ejercicio 2
for i:=1 to n do
for j:=1 to i do
x:=x+1;
13
Algoritmos Avanzados
n
Lic. Solange Salazar
Ing. Simón Onofre
n
i
T (n)
1
i 1
j 1
i
i 1
n(n 1)
2
12
n
2
1
n es O(n2) orden cuadrático
2
Ejercicio 3
procedure pord_mat(n: integer);
var
I,j,k:integer;
Begin
For i:=:1 to n do
For j:=1 to n do
begin
C[i,j]:=0; - - - - - - - - - - - - - - 1
For k:=1 to n do
C[i,j]:=C[i,j]+A[i,k] * B[k,j]: - - 1
End;
End;
nn
n
Tprod_mat(n) =
( (1+ 1)) = n ( n (1+ n )) = n (n+n2) = n2 + n3
I=1 j=1
k=1
Que es O(n3) orden cúbico.
La simplificación de las sumatorias se realiza directamente, porque no existen variables
dependientes.
Ejercicio 4
procedure misterio1(n: integer);
var
i,j,k:integer;
begin
for i:=:1 to n-1 do
for j:=i+1 to n do
for k:=1 to j do
{alguna proposición que requiera tiempo O(1) }
end;
14
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
n 1 n
Tmisterio1 (n)
(
j
n 1 n
( 1))
i 1j i 1k 1
n1
Tmisterio1 (n)
i
Tmisterio1 (n)
(n
n 1 n
n(n 1)
(
2
1
1)n(n
2
(
i 1j i 1
i(i
j)
(
i 1j 1
1)
2
1) 1 n 1
( i2
2i 1 i
i
j
j)
j 1
n1
)
(
i 1
n 1
n(n 1)
)
2
(n 2
i)
1
1n
2i
n)(n
2
1 2
1
(i
1)
i)
1 (n 1)n( 2n 1)
(
2
6
(n 1)n
)
2
es O(n3) orden cúbico
Ejercicio 5
procedure misterio2(n: integer);
var
x, cuenta:integer;
begin
cuenta:=0;
x:=2;
while x<n do
begin
x:=2*x;
cuenta:=cuenta+1;
end;
writeln (cuenta)
end;
Primero analizamos la sentencia WHILE
x
2
4
8
16
32
t=0
x=2
while x<n do
x=2*x
t=t+1
T
0
1
2
3
4
n
31 El tiempo del mientras (while) es:
t+1
t+1
2=x
de la condición del while 2 < n
despejando t se tiene
t <log2n –1
para que se de una equivalencia entre ambos términos
t +a log2n –1 t log2n –1 –a +1
se adiciona la ctte 1 debido a la última pregunta que se
realiza en la sentencia while
Finalmente:
Es O(log n) orden logarítmico
Tincognita 2 (n)
2
2 log2 n
2a
15
Algoritmos Avanzados
Tincognita 2 (n)
1
1
Lic. Solange Salazar
Ing. Simón Onofre
(log 2 n
1
a
1)(1
1)
2
(log 2 n
a)2
Ejercicio 6
Se tiene un fragmento de programa sencillo para calcular
i3 a continuación:
Function suma(n:intertger)
var
i,suma_parcial: integer
begin
(1) suma_parcial=0;
(2) for i:=1 to n do
(3) suma_parcial:= suma_parcial +i*i*i;
end for
(4) suma:=suma_parcial
end suma
Calcular el tiempo de ejecución en el peor de los casos
Sol. El tiempo de ejecución para (1), (3) y (4) es 1, en el caso del for es una sumatoria de la
siguiente forma:
n
Tsuma(n) = 1+ 1 + 1
i=1
Tsuma(n) = 1+ n + 1 = 2+n es O(n) orden lineal
2.10 RESOLUCION DE ECUACIONES DE RECURRENCIA
Existen tres enfoques distintos para resolver ecuaciones de recurrencia:
1.
Expansión de recurrencias
Utilizar la recurrencia misma para sustituir m<n por cualquier T(m) en la derecha, hasta que
todo, los términos T(m) par m>1 se hayan reemplazado por formulas que impliquen solo T(1)
como T( 1) siempre es constante, so tiene una formula para T(n) en función de n y de
algunas constantes
2.-
Suposición de una solución
Suponer una solución f(n) y usar la recurrencia para mostrar que T(n)<=f(n). Algunas veces
solo se supone la forma f(n) dejando algunos parámetros sin especificar. y deduciendo
valores adecuados para los parámetros al intentar demostrar que T(n)<=f(n) para todo n.
3.-
Solución general para una clase grande de problemas
Emplear Ia solución general para ciertas ecuaciones de recurrencias de tipos comunes.
16
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Si T(n) es el tiempo para resolver un problema de tamaño n. Se tiene
1
n= 1
( *)T(n) =
aT(n/b)+d(n) n>=2
Donde
a: es el número de llamadas recursivas.
h: es el número de partes en que se divide la entrada.
d(n): función motriz
Para resolver (*) se aplica la técnica de sustituciones repetidas para T en el lado derecho.
Una vez que se resuelve por expansion de recurrencias, se obtiene los siguientes tres casos:
CASO I
CASO II
CASO Ill:
Si ak >d(b)k => O(ak)
Si
ak
<d(b)k
k
k
k
Si a = d(b)
=> O(d(b) k)
=>
O(d(b)k)
2.11 EJERCICIOS RESUELTOS
Ejercicio 1 Sea el siguiente programa recursivo para calcular factoriales, encontrar el tiempo
de ejecución en el peor de los casos.
int Factorial(int n)
{
if(n<=1) return 1;
else
return Factorial(n-1)*n;
}
So I
Para este ejercicio se tiene la siguiente ecuación característica que representa el tiempo:
Primera forma
Segunda forma
c
n=1
TFactorial (n) =
1
n=1
TFactorial (n) =
T ( n-1) + d n>1
T ( n-1) + 1 n>1
Aplicando la técnica de Expansión de recurrencia:
T(n)=T(n-1) + d = T(n-2) + d + d = T(n-3)+ d + d + d
Generalizando, obtenemos el Patron de Recurrencia:
17
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
T(n) = T(n-k) + kd
(*)
Por último, el caso base se presenta cuando el argumento de la función T(n) es 1 es decir
para T(1), entonces:
n-k=1 => k=n –1
luego se reemplaza k en (*)
T(n) = T(n –(n-1)) + (n-1)*d = T(1) + (n-1)*d
T(n) = c + dn – d que es O(n) orden lineal
Ejercicio 2. Obténgase la cota O rnayúscula de la siguiente ecuación:
d
T(n)=
n= 1
2T(n/2)+ cn
n<1
Para aplicar eI método expansión de recurrencia se realizan las siguientes operaciones
auxiliares
n
2
n
n
T(n) 2T
2
2
n
n
T
2T 2
2
2
n
cn
2
n
c
2
Realizando repetidos reemplazos en T(n)
T 2
2T 3
c 2
n
2
T(n)
2
n
n
n
n
n
n
n
2
2
2
2
2
2
2
2T
ncn
n
2
n
2
3
n
2
n
3
n
T(n) 2 2T 2
2
2
T(n)
22
T (n)
2k T
2T
k
c
cn
22 T
2
2c
cn
22 T
2
2c
cn
22 T
3
3cn
2 18
3
c
kcn
2
c
cn
2T
3
2c
2
2cn
2T
2
2cn
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
El caso base se da cuando:
n
2
k
1
n
2k
k
log n n
Reemplazando el valor de k en el patron de recurrencia:
T (n)
2 log 2 n T 1
T (n)
nT (1)
cn log 2 n
cn log 2 n
nd
cn log 2 n
es O(nlogn)orden cuasilineal
Ejercicio 3. Obténgase Ia cota 0 mayüscula de la siguiente recurrencia:
T(n)=
2T( n/2 )+2 n> 2
1
n= 2
0
n=I
T ( n )= 2T ( n/2)+2
Para n/2
T( n/2 )=2T( n/4 )+2
T(n) = 2[2T(n/4)+2]+2
para n/4
T(n/4) = 2T(n/8)+2
T(n)=2*2T(n/8)+8+4+2
El patron de recurrencia será
k
T (n) 2 k T ( n
2 )
2 i 2’
k
i 1
Si n=2t
T (n) 2 k T (2
k
t
2 )
2i
k
i 1
Por últirno cuando k=t-1, se obtiene
T (n) 2t 1T (2
t 1
t
2t 1 )
2
i 1
i
2t
2 T (2)
t 1
2i
i 1
19
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Debemos resolver la siguiente sumatoria, por inducción:
t 1
S
2i
2t
2
i 1
luego reemplazando S en el Patrón de recurrencia y con n=2t
T(n)=n/2 + 2t-2 0 n/2 +n-2=3/2n-2 es O(n)
Ejercicio 4
C
n=1
2T(n/2) +
n3 e.o.c.
T(n) =
Respuesta:
Mediante la técnica de "análisis de expansión" o ‖Expansión de Recurrencias―,
reemplazamos sobre T(n) de la siguiente forma:
T (n)
T (n)
n
2T ( )
2
nn
2 2T
n3
3
n3
2
2
2
22 2T
22 T
3
nn
T (n)
3
3
2
3
2
2
2
n
n3
2
n
n
2
n3
2
2
2
3
23T
3
2
n
22
2
3
2
2
n
2
n3
n
Generalizando llegamos al Patrón de Recurrencia
T (n)
n
2T k
2 i0 2
kk
k 1
2
n
i
2
i
3
2T
n
k
3
k 1
2
i0
i
n
2i 3
n
2T k
2
k3
k 1
n
i 02
2i
i3
Primero resolveremos la siguiente sumatoria:
20
Algoritmos Avanzados
k 1
2
3
i0
i
k 1i
2
2
i 0
Lic. Solange Salazar
Ing. Simón Onofre
1
2S
1 1
1 1
2
......... k 2
4 4
4 4
1 1
1 1
2
......... k 1
4 4
4 4
1
1
S (1 ) 1 k
4 4
1
S 4
3 3.4 K 1
S 1
1S
4
k 1
k
La determinación de O se deja como ejercicio
Ejercicio 5 Resuelva la siguiente recurrencia por el método Solución General para una clase
grande de recurrencias
1
n= 2
T(n)=
4T( n/2 )+n3 n> 1
Sc tiene a=4 v b=2
d(n)=n función Motriz. d(2)=8
Abora Comparemos con los tres casos se tiene que
a>d( 2) => 4<8
=>
(Caso II)
T(n)= O(8logn)
Por propiedades de
log se tiene O(n3)
A
l
g
UNIDAD
o
r
i
t
m
o
s
III
A
v
a
n
z
a
d
o
s
2
1
Lic. Solange
Salazar
I
n
g
ESTRUCTURAS
.
Simón Onofre López
DE DATOS
3.1 CONCEPTOS FUNDAMENTALES
3.1.1 TIPOS DE DATOS
Cualquier lenguaje suministra una serie de tipos de datos simples, como son los números
enteros, caracteres, números reales. En realidad suministra un subconjunto de éstos,
pues la memoria del ordenador es finita. Los punteros (si los tiene) son también un tipo
de datos. El tamaño de todos los tipos de datos depende de la máquina y del compilador
sobre los que se trabaja.
En principio, conocer la representación interna de estos tipos de datos no es necesaria
para realizar un programa, pero sí puede afectar en algunos casos al rendimiento.
Por otra parte es posible definir otros tipos de estructuras de datos compuestos, entre las
cuales se encuentran los arreglos, registros y otros.
Los lenguajes de programación (LP) proporcionan herramientas para manejar distintos
tipos de datos.
Tipos predefinidos:
proporcionados por el LP
sólo nos preocupamos de su uso
Tipos definidos por el usuario:
Se puede elegir y definir una estructura de datos de las proporcionadas por el
LP para su representación
Se debe elegir y definir operaciones de manipulación
Se debe garantizar el correcto comportamiento del tipo
3.1.2 ESTRUCTURA DE DATOS
Se trata de un conjunto de variables de un determinado tipo agrupadas y organizadas de
alguna manera para representar un comportamiento. Lo que se pretende con las
estructuras de datos es facilitar un esquema lógico para manipular los datos en función
del problema que haya que tratar y el algoritmo para resolverlo. En algunos casos la
dificultad para resolver un problema radica en escoger la estructura de datos adecuada.
Y, en general, la elección del algoritmo y de las estructuras de datos que manipulará
estarán muy relacionadas.
Según su comportamiento durante la ejecución del programa distinguimos estructuras de
datos:
o
o
Estáticas: su tamaño en memoria es fijo. Ejemplo: arrays.
Dinámicas: su tamaño en memoria es variable. Ejemplo: listas enlazadas con
punteros, ficheros, etc.
Las estructuras de datos que trataremos aquí son los arrays, las pilas y las colas, los
árboles, y algunas variantes de estas estructuras.
22
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
3.1.3 TIPOS ABSTRACTOS DE DATOS
Los tipos abstractos de datos (TAD) permiten describir una estructura de datos en
función de las operaciones que pueden efectuar, dejando a un lado su implementación.
Los TAD mezclan estructuras de datos junto a una serie de operaciones de
manipulación. Incluyen una especificación, que es lo que verá el usuario, y una
implementación (algoritmos de operaciones sobre las estructuras de datos y su
representación en un lenguaje de programación), que el usuario no tiene necesariamente
que conocer para manipular correctamente los tipos abstractos de datos.
Se caracterizan por el encapsulamiento. Es como una caja negra que funciona
simplemente conectándole unos cables. Esto permite aumentar la complejidad de los
programas pero manteniendo una claridad suficiente que no desborde a los
desarrolladores. Además, en caso de que algo falle será más fácil determinar si lo que
falla es la caja negra o son los cables.
Por último, indicar que un TAD puede definir a otro TAD. Por ejemplo, en próximos
apartados se indicará como construir pilas, colas y árboles a partir de arrays y listas
enlazadas. De hecho, las listas enlazadas también pueden construirse a partir de arrays
y viceversa.
3.2 RECURSIVIDAD
La recursión es una técnica para resolver problemas. Muchas veces resulta más fácil
desarrollar un algoritmo recursivo que uno iterativo.
Definición: Una función f es recursiva si en su cuerpo contiene
una aplicación de f, es decir, si se puede activarse a si misma.
Si la llamada sucede dentro de la propia función se dice que es directamente recursiva. En
cambio si la función llama a otra y esta a su vez llama a la primera se dice que es recursión
indirecta.
El objetivo del programa recursivo, es realizar una serie de llamadas hasta que la secuencia
se define en un punto.
Las directrices para una función recursiva son:
Cada vez que se hace una llamada recursiva en una función, el programa deberá comprobar
que se satisface una condición básica con un determinado parámetro puesto al valor mínimo.
Cada vez que se hace la llamada a la función, los parámetros enviados a la misma, deberán
ser de algún modo más ―simple‖, es decir, su valor debe tender a la condición básica.
Además por lo general en las funciones recursivas se suelen identificar dos casos:
Caso base, que implica el caso de parada de la recursión
Caso base, que implica realizar las llamadas recursivas
23
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Ejemplo
La función Factorial puede ser desarrollada iterativamente o recursivamente 1.
Matemáticamente de define como:
n!
1
n(n
si n 0 ó 1
1) si n 1
Análogamente su expresión funcional será la siguiente:
fac(n)
1
si n
n * fac(n
0ó1
1) si n
1
Codificación
Como función la codificación es la siguiente:
Private Function factorial(ByVal n As Integer) As
Integer
If n = 1 Then
factorial = 1
Else
factorial = n * factorial(n - 1)
End If
End Function
Prueba
Para N=5
Llamadas recursiva
Factorial(5)=factorial(4)*5
Factorial(4)=factorial(3)*4
Factorial(3)=factorial(2)*3
Factorial(2)=factorial(1)*2
Factorial(1)=1
Evaluación de resultados
Factorial(1)=1
Factorial(2)=factorial(1)*2 = 1*2 =2
Factorial(3)=factorial(2)*3 = 2*3 =6
Factorial(4)=factorial(3)*4 = 6*4 =24
Factorial(5)=factorial(4)*5 = 24*5 =120
24
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Como procedimiento se tiene la siguiente codificación:
Private Sub fac(ByVal n As Integer, ByRef resul As Integer)
Dim resp_aux As Integer
If n = 1 OR n = 0 Then
resul = 1
Else
fac n - 1, resp_aux
resul = resp_aux * n
End If
End Sub
Ejemplo: Otro ejemplo clásico es la secuencia de fibonacci cuyos términos son 1, 1, 2, 3, 5,
8, ... los cuales se determinan por la función:
Expresión funcional
fib(n)
1
fib(n
si n
1)
1ó2
fib(n
2) si n
2
Codificación
Private Function
Integer
If n = 0 Or n =
fibonacci =
Else
fibonacci =
End If
End Function
fibonacci(ByVal n As Integer) As
1 Then
1
fibonacci(n - 1) + fibonacci(n - 2)
Prueba
Para fib(5) se tienen las siguientes entradas y salidas.
Llamadas recursiva
Fibonacci(5)=fibonacci(4)+ fibonacci(3)
Fibonacci(4)=fibonacci(3)+ fibonacci(2)
Fibonacci(3)=fibonacci(2)+ fibonacci(1)
Fibonacci(2)=1
Fibonacci(1)=1
Evaluación de resultados
Fibonacci(1)=1
Fibonacci(2)=1
Fibonacci(3)=fibonacci(2)+ fibonacci(1)=1+1=2
Fibonacci(4)=fibonacci(3)+ fibonacci(2)=2+1=3
Fibonacci(5)=fibonacci(4)+ fibonacci(3)=3+2=5
25
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
En este caso se puede observar que la solución planteada de fibonacci es impracticable para
valores de n grandes. Cada fibonacci() realiza dos llamadas, por lo que el número de
llamadas aumenta en proporción geométrica.
¿ Conviene usar recursión siempre?¿ Qué pasa con la memoria?
Veamos el caso de implementar la misma secuencia de fibonacci iterativamente:
Private Function fiboRec(ByVal n As Integer) As Integer
Dim a As Integer, b As Integer, c As Integer, i As
Integer
a = 0
b = 1
c = 1
Print c
For i = 2 To n
c = a + b
Print c
a = b
b = c
Next i
fiboRec = c
End Function
EL lector podrá verificar la eficiencia de la versión recursiva con respecto de la iterativa
3.2.1 EJERCICIOS RESUELTOS
Ejercicio 1 Multiplicar dos números por sumas sucesivas
Solución
Como es sabido, ―la multiplicación es una suma abreviada‖, en tal sentido se puede realizar
la operación de la siguiente forma:
A*B = A+A+A+ . . . . . . . . . +A , Es decir sumar A, B veces
Entonces para el caso general:
B
A * B Multi( A, B)
i 1
B 1
A
i 1
A A
Es decir A*B se puede expresar como la sumatoria desde 1 hasta B de A, lo cual si se saca
de la expresión al último termino, el resto continua siendo sumatoria.
26
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
El caso base es mucho más simple, nos preguntamos cuál es el valor más qequeño que
puede toma B, para que el resultado sea trivial, es posible elegir entre las siguientes
alternativas
O
A*B=A cuando B=1
A*B=0 cuando B=0
Cualquiera de estas expresiones puede representar al caso base.
Expresión funcional
Multi( A, B)
A
si B
1
Multi( A, B 1) A
si B
1
Algoritmo
Private Function multiplica(ByVal A As Integer, ByVal
B As Integer) As Integer
If B = 1 Then
multiplica = A
Else
multiplica = multiplica(A, B - 1) + A
End If
End Function
27
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Codificación completa
Option Explicit
Dim num As Integer
Dim num2 As Integer
Private Function multiplica(ByVal A As Integer, ByVal B As Integer)
As Integer
If B = 1 Then
multiplica = A
Else
multiplica = multiplica(A, B - 1) + A
End If
End Function
Private Sub Command1_Click()
num = Val(Text1.Text)
num2 = Val(Text2.Text)
MsgBox "El resulatdo es " & multiplica(num, num2), vbCritical,
"RESULTADO"
End Sub
Private Sub Command2_Click()
End
End Sub
Private Sub Text1_KeyPress(KeyAscii As Integer)
Select Case KeyAscii
Case 13
Text2.SetFocus
Case Asc(0) To Asc(9)
Case Else
KeyAscii = 0
End Select
End Sub
28
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Private Sub Text2_KeyPress(KeyAscii As Integer)
Select Case KeyAscii
Case 13
Command1.SetFocus
Case Asc(0) To Asc(9)
Case Else
KeyAscii = 0
End Select
End Sub
Ejercicio 2 intercalar los dígitos de dos números, los números tiene la misma longitud.
Solución
La función modulo nos permite sacar el último digito de cada número y componer un nuevo
numero, cuando numA y numB sean de un solo dígito, es decir menores a 10 se habrá
llegado al caso base.
Expresión funcional
Intercala( A, B)
A *10
B
si A
10 ó B
10
Intercala( Adiv10, Bdiv10) *100
( A mod10) *10
(B mod10) eoc
Algoritmo
Private Function intercala(ByVal numA As Integer, ByVal numB As
Integer) As Double
Dim ma As Integer, mb As Integer, nuevoNum As Integer
If numA < 10 Then
intercala = numA * 10 + numB
Else
ma = numA Mod 10
mb = numB Mod 10
nuevoNum = ma * 10 + mb
intercala = intercala(numA \ 10, numB \ 10) * 100 + nuevoNum
End If
End Function
Prueba
Intercala(345,796) = Intercala(34,79)*100 + 56 =3749*100 + 56 = 374956
Intercala(34,79)
Intercala(3,7)
= Intercala(3,7)*100 + 49 = 37*100 +49 = 3749
= 37
29
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Ejercicio 3 Hallar la sumatoria de los N primero números pares
Solución
La sumatoria de los n primero números pares está dada por:
SumaPares(n) = 2 + 4 + 6 + . . . . . .+ 2*(n-1) + 2*n
Sacando el ultimo elemento de la sumatoria, logramos identificar el caso general:
SumaPares(n) = SumaPares(n-1)+ 2*n
Para el caso general, se considera que valor debe tomar n para generar el primer numero
par.
Algoritmo
Private Function sumaPares(n As Integer)
If n = 1 Then
sumaPares = 2
Else
sumaPares = sumaPares(n - 1) + 2 * n
End If
End Function
Codificación completa
Option Explicit
Dim num As Integer
Private Function sumaPares(ByVal n As Integer) As Integer
If n = 1 Then
sumaPares = 2
List1.AddItem 2
Else
sumaPares = sumaPares(n - 1) + 2 * n
List1.AddItem (2 * n)
End If
End Function
30
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Private Sub Form_Load()
Option1.Value = False
Option2.Value = True
End Sub
Private Sub Option1_Click()
num = Val(Text1.Text)
MsgBox sumaPares(num), vbExclamation
End Sub
Private Sub Option2_Click()
Text1 = ""
List1.Clear
End Sub
Private Sub Option3_Click()
End
End Sub
Ejercicio 4
naturales.
Ejemplo:
N=1
N=2
N=3
Dado un número entero generar otro número solo compuesto por los números
genera
genera
genera
1
12
123
Expresión funcional
Genera(n)
1
Genera(n
1) *10
si n 1
n si n 1
Algoritmo
Prueba
Private Function genera(ByVal n
Integer) As Double
If n = 1 Then
genera = 1
Else
genera = genera(n - 1) * 10 + n
End If
End Function
As
Para n=5
31
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Llamadas recursiva
genera(5) = genera(4) * 10 +
5
genera(4) = genera(3) * 10 +
4
genera(3) = genera(2) * 10 +
3
genera(2) = genera(1) * 10 +
2
genera(1) = 1
Evaluación de resultados
genera(1) = 1
genera(2) = genera(1) * 10 + 2 = 1*10+2 = 12
genera(3) = genera(2) * 10 + 3 = 12*10+3=123
genera(4) = genera(3) * 10 + 4 = 123*10+4=1234
genera(5) = genera(4) * 10 + 5 = 1234*10+5=
.....................12345
Ejercicio 5 Invertir un vector
Solución
ini
fin
1234567
La solución consiste en cambiar el primer V(ini) y último V(fin) elementos usando un auxiliar:
aux = v(ini)
v(ini) = v(fin)
v(fin) = aux
luego realizar la llamada recursiva de manera de cambiar el segundo V(ini+1) con el
penúltimo V(fin-1) elemento:
Invierte ini + 1, fin - 1
el proceso se repite mientras ini sea menor o igual que fin.
Algoritmo
Private Sub Invierte(ByVal ini As Integer,
ByVal fin As Integer)
Dim aux As Integer
If ini < fin Then
aux = v(ini)
v(ini) = v(fin)
v(fin) = aux
Invierte ini + 1, fin - 1
End If
End Sub
Codificación completa
32
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Dim tam As Integer
Dim v(1 To 10) As String
Private Sub LlenaVector(ByVal n As Integer)
Dim i As Integer
List1.Clear
For i = 1 To n
v(i) = InputBox("Ingrese el elemento V[" & i & "] =>", "Ingreso de
datos al Vector", 0)
List1.AddItem v(i)
Next
End Sub
Private Sub MostrarLista2(ByVal n As Integer)
Dim i As Integer
List2.Clear
For i = 1 To n
List2.AddItem v(i)
Next
End Sub
Private Sub Invierte(ByVal ini As Integer, ByVal fin As Integer)
Dim aux As String
If ini < fin Then
aux = v(ini)
v(ini) = v(fin)
v(fin) = aux
Invierte ini + 1, fin - 1
End If
End Sub
Private Sub Command1_Click()
tam = Val(InputBox("Ingrese tamaño del vector ", "TAMAÑO DEL VECTOR",
0))
LlenaVector tam
End Sub
33
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Private Sub Command2_Click()
Invierte 1, tam
MostrarLista2 tam
End Sub
Private Sub Command3_Click()
End
End Sub
3.3 ARREGLOS
Supongamos que nos enfrentamos a un problema como este: “Una empresa que cuenta con
150 empleados, desea establecer una estadística sobre los salarios de sus empleados, y
quiere saber cual es el salario promedio, y también cuantos de sus empleados gana entre
$1250.00 y $2500.00”.
Si tomamos la decisión de tratar este tipo de problemas con datos simples, pronto nos
percataríamos del enorme desperdicio de tiempo, almacenamiento y velocidad. Es por eso
que para situaciones de este tipo la mejor solución son los datos estructurados.
Un arreglo puede definirse como un grupo o una colección finita, homogénea y ordenada de
elementos. Los arreglos pueden ser de los siguientes tipos:
o
o
o
De una dimensión.
De dos dimensiones.
De tres o más dimensiones
3.4 PILAS
Una pila (stack) es una colección ordenada de elementos en la cual se
pueden insertar nuevos elementos por un extremo y se pueden retirar
otros por el mismo extremo; ese estremos se llama ``la parte superior'' de
la pila.
34
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Si tenemos un par de elementos en la pila, uno de ellos debe estar en la parte superior de la
pila, que se considera ``el más alto'' en la pila que el otro. En la figura 3.1 el elemento F es el
más alto de todos los elementos que están en la pila. El elemento D es el más alto de los
elementos A,B,C, pero es menor que los elementos E y F.
Figura 3.1: Pila con 6 elementos
Para describir cómo funciona esta estructura, debemos agregar un nuevo elemento, el
elemento G. Después de haber agregado el elemento G a la pila, la nueva configuración es
la que se muestra en la figura 3.2
Figura 3.2: Operación de insertar el elemento G en la pila P
De acuerdo con la definición, existe solamente un lugar en donde cualquier elemento puede
ser agregado a la pila. Después de haber insertado el nuevo elemento, G ahora es el
elemento en la cima. Debemos aclarar en qué pila deseamos insertar elementos, puesto que
es posible tener más de una pila al mismo tiempo.
Cuando se desea retirar un elemento de la pila, solo basta ordenar que sea retirado un
elemento; no podemos decir ``retira C de la pila'', porque C no está en la cima de la pila y
solamente podemos retirar el elemento que está en la cima. Para que la sentencia ``retira C
de la pila'' tenga sentido, debemos replantear las órdenes a algo como:
Retira de la pila hasta que el elemento retirado sea C.
Y solamente se puede sacar un elemento a la vez.
Siguiendo nuestro ejemplo, ahora deseamos retirar de la pila P. La configuración global de la
pila es como se muestra en la figura 3.3
35
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Figura 3.3: Operación de retirar de la pila P
El concepto de pila es muy importante en computación y en especial en teoría de lenguajes
de programación. En lenguajes procedurales como Pascal o C, la pila es una estructura
indispensable, debido a las llamadas a función.
Resulta que el flujo de instrucciones va de arriba hacia abajo, y cuando ocurre una llamada a
alguna función, el estado global del sistema se almacena en un registro y éste en una pila.
Así que la pila va a contener todas las llamadas a procedimientos que se hagan.
Cuando se termina de ejecutar algún procedimiento, se recupera el registro que está en la
cima de la pila. En ese registro están los valores de las variables como estaban antes de la
llamada a la función, o algunas pueden haber cambiado si valor, dependiendo del ámbito de
las variables.
Cada elemento en la pila que es retirado, significa que se ha terminado de ejecutar alguna
función. Cuando se termina de ejecutar el programa, la pila de llamadas a subprogramas
debe haber quedado en 0 también, de otro modo podría causar algun tipo de error.
La manera en cómo entran los datos a la estructura de datos y cómo salen, se denomina fifo,
que viene del ingés first in first out (primero en entrar, primero en salir).
3.5 COLAS
Las colas son una estructura de datos similar a las pilas. Recordemos que las pilas
funcionan en un depósito en donde se insertan y se retiran elementos por el mismo
extremo. En las colas sucede algo diferente, se insertan elementos por un extremo
y se retiran elementos por el otro extremo. De hecho a este tipo de dispositivos se
les conoce como dispositivos ``fifo'' (first in, first out) porque funcionan como una
tubería, lo que entra primero por un extremo, sale primero por el otro extremo.
En una cola hay dos extremos, uno es llamado la parte delantera y el otro extremo se llama
la parte trasera de la cola. En una cola, los elementos se retiran por la parte delantera y se
agregan por la parte trasera.
36
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Figura 3.4: Dinámica de una cola.
a) estado actual con una cola con tres
elementos a,b,c;
b) estado de la cola cuando se agrega el
elemento d;
c) estado de la cola cuando se elimina el
elemento a del frente de la cola
En la figura 3.4 se muestra una actividad típica
de la cola, en donde se muestra que se agregan
datos por la parte trasera de la cola y se eliminana datos por el frente de la cola.
Si Q es una cola y x es un elemento, se pueden hacer tres operaciones básicas con las
colas:
o
insert(Q,x), que inserta el elemento x en la parte trasera de la cola Q.
o
x=remove(Q), que almacena en x el valor del elemento retirado de la parte
frontal de la cola Q.
o
empty(Q), que es un predicado de valor booleano, y es true cuando la cola Q
tiene 0 elementos, y es false cuando la cola Q tiene al menos un elemento, en
cuyo caso, ese único elemento es la parte frontal y la parte trasera de la cola
al mismo tiempo.
Teóricamente no hay límite para el tamaño de la cola, asi que siempre se debería poder
insertar elementos a una cola, sin embargo, al igual que las pilas, normalmente se deja un
espacio de memoria para trabajar con esta estructura. Por el contrario, la operación remove
solamente se puede hacer si la cola no está vacía.
3.6 LISTAS
Una lista es una estructura de datos secuencial.
Una manera de clasificarlas es por la forma de acceder al siguiente elemento:
o
o
lista densa: la propia estructura determina cuál es el siguiente elemento de la lista.
Ejemplo: un array.
lista enlazada: la posición del siguiente elemento de la estructura la determina el
elemento actual. Es necesario almacenar al menos la posición de memoria del primer
elemento. Además es dinámica, es decir, su tamaño cambia durante la ejecución del
programa.
37
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
Una lista enlazada o encadenada es una colección de elementos ó
nodos, en donde cada uno contiene datos y un enlace o liga.
Un nodo es una secuencia de caracteres en memoria dividida en campos (de cualquier
tipo). Un nodo siempre contiene la dirección de memoria del siguiente nodo de
información si este existe.
Un apuntador es la dirección de memoria de un nodo
La figura siguiente muestra la estructura de un nodo:
El campo liga, que es de tipo puntero, es el que se usa para establecer la liga con el
siguiente nodo de la lista. Si el nodo fuera el último, este campo recibe como valor
NIL (vacío).
A continuación se muestra el esquema de una lista :
3.7 ÁRBOLES
3.7.1 ÁRBOLES BINARIOS
A los árboles ordenados de grado dos se les conoce como árboles binarios ya que cada
nodo del árbol no tendrá más de dos descendientes directos. Las aplicaciones de los
árboles binarios son muy variadas ya que se les puede utilizar para representar una
estructura en la cual es posible tomar decisiones con dos opciones en distintos puntos.
3.7.1.1 Representación
La representación gráfica de un árbol binario es la siguiente:
Hay dos formas tradicionales de representar un árbol binario en memoria:
38
Algoritmos Avanzados
o
o
Lic. Solange Salazar
Ing. Simón Onofre López
Por medio de datos tipo punteros también conocidos como variables
dinámicas o listas.
Por medio de arreglos.
Sin embargo la más utilizada es la primera, puesto que es la más natural para tratar
este tipo de estructuras. Los nodos del árbol binario serán representados como
registros que contendrán como mínimo tres campos. En un campo se almacenará la
información del nodo. Los dos restantes se utilizarán para apuntar al subarbol
izquierdo y derecho del subarbol en cuestión.
Cada nodo se representa gráficamente de la siguiente manera:
3.7.1.2 Clasificación de Árboles Binarios
Existen cuatro tipos de árbol binario:.
a.
b.
c.
d.
B. Distinto.
B. Similares.
B. Equivalentes.
B. Completos.
A continuación se hará una breve descripción de los diferentes tipos de árbol binario
así como un ejemplo de cada uno de ellos.
a. A. B. DISTINTO
Se dice que dos árboles binarios son distintos cuando sus estructuras son
diferentes. Ejemplo:
b. A. B. SIMILARES
Dos árboles binarios son similares cuando sus estructuras son idénticas, pero la
información que contienen sus nodos es diferente. Ejemplo:
39
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
c. A. B. EQUIVALENTES
Son aquellos árboles que son similares y que además los nodos contienen la
misma información. Ejemplo:
d. A. B. COMPLETOS
Son aquellos árboles en los que todos sus nodos excepto los del ultimo nivel, tiene dos
hijos; el subárbol izquierdo y el subárbol derecho.
3.7.1.3 Recorrido de un Arbol Binario
Hay tres manera de recorrer un árbol : en inorden, preorden y postorden. Cada una
de ellas tiene una secuencia distinta para analizar el árbol como se puede ver a
continuación:
INORDEN
Recorrer el subarbol izquierdo en inorden.
Examinar la raíz.
Recorrer el subarbol derecho en inorden.
PREORDEN
Examinar la raíz.
Recorrer el subarbol izquierdo en preorden.
recorrer el subarbol derecho en preorden.
POSTORDEN
Recorrer el subarbol izquierdo en postorden.
Recorrer el subarbol derecho en postorden.
Examinar la raíz.
A continuación se muestra un ejemplo de los diferentes recorridos en un árbol
binario.
Inorden: GDBHEIACJKF
Preorden: ABDGEHICFJK
Postorden: GDHIEBKJFCA
40
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
3.7.1.4 Árboles Enhebrados
Existe un tipo especial de árbol binario llamado enhebrado, el cual contiene hebras
que pueden estar a la derecha o a la izquierda. El siguiente ejemplo es un árbol
binario enhebrado a la derecha.
ARBOL ENHEBRADO A LA DERECHA. Este tipo de árbol tiene un apuntador a la
derecha que apunta a un nodo antecesor.
ARBOL ENHEBRADO A LA IZQUIERDA. Estos árboles tienen un apuntador a la
izquierda que apunta al nodo antecesor en orden.
3 .7.1.5 Árboles binarios de búsqueda
Un árbol de búsqueda binaria es una estructura apropiada para muchas de las
aplicaciones que se han discutido anteriormente con listas. La ventaja especial de
utilizar un árbol es que se facilita la búsqueda.
Un árbol binario de búsqueda es aquel en el que el hijo de la izquierda (si existe) de
cualquier nodo contiene un valor más pequeño que el nodo padre, y el hijo de la
derecha (si existe) contiene un valor más grande que el nodo padre.
Un ejemplo de árbol binario de búsqueda es el siguiente:
41
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
3.7.2 ÁRBOLES GENERALES
Los árboles representan las estructuras no lineales y dinámicas de datos más
importantes en computación . Dinámicas porque las estructuras de árbol pueden
cambiar durante la ejecución de un programa. No lineales, puesto que a cada
elemento del árbol pueden seguirle varios elementos.
Los árboles pueden ser construidos con estructuras estáticas y dinámicas. Las
estáticas son arreglos, registros y conjuntos, mientras que las dinámicas están
representadas por listas.
La definición de árbol es la siguiente:
es una estructura jerárquica aplicada sobre una colección de elementos
u objetos llamados nodos; uno de los cuales es conocido como raíz.
además se crea una relación o parentesco entre los nodos dando lugar a
términos como padre, hijo, hermano, antecesor, sucesor, ansestro, etc..
Formalmente se define un árbol de tipo T como una estructura
homogénea que es la concatenación de un elemento de tipo T junto con
un número finito de arboles disjuntos, llamados subarboles. Una forma
particular de árbol puede ser la estructura vacía.
La figura siguiente representa a un árbol general.
Se utiliza la recursión para definir un árbol porque representa la forma más apropiada y
porque además es una característica inherente de los mismos.
Los árboles tienen una gran variedad de aplicaciones. Por ejemplo, se pueden utilizar
para representar fórmulas matemáticas, para organizar adecuadamente la información,
para construir un árbol genealógico, para el análisis de circuitos eléctricos y para numerar
los capítulos y secciones de un libro.
3.7.2.1 Terminología
La terminología que por lo regular se utiliza para el manejo de arboles es la siguiente:
42
Algoritmos Avanzados
o
o
o
o
o
o
o
o
o
o
o
Lic. Solange Salazar
Ing. Simón Onofre López
HIJO. X es hijo de Y, sí y solo sí el nodo X es apuntado por Y. También se dice
que X es descendiente directo de Y.
PADRE. X es padre de Y sí y solo sí el nodo X apunta a Y. También se dice que
X es antecesor de Y.
HERMANO. Dos nodos serán hermanos si son descendientes directos de un
mismo nodo.
HOJA. Se le llama hoja o terminal a aquellos nodos que no tienen ramificaciones
(hijos).
NODO INTERIOR. Es un nodo que no es raíz ni terminal.
GRADO. Es el número de descendientes directos de un determinado nodo.
GRADO DEL ARBOL Es el máximo grado de todos los nodos del árbol.
NIVEL. Es el número de arcos que deben ser recorridos para llegar a un
determinado nodo. Por definición la raíz tiene nivel 1.
ALTURA. Es el máximo número de niveles de todos los nodos del árbol.
PESO. Es el número de nodos del árbol sin contar la raíz.
LONGITUD DE CAMINO. Es el número de arcos que deben ser recorridos para
llegar desde la raíz al nodo X. Por definición la raíz tiene longitud de camino 1, y
sus descendientes directos longitud de camino 2 y así sucesivamente.
3.7.2.2 Transformación de un Árbol Gral. en un Árbol Binario.
En esta sección estableceremos los mecanismos necesarios para convertir un árbol
general en un árbol binario. Para esto, debemos seguir los pasos que se describen a
continuación:
o
o
o
Enlazar los hijos de cada nodo en forma horizontal (los hermanos).
Enlazar en forma vertical el nodo padre con el nodo hijo que se encuentra más a
la izquierda. Además, debe eliminarse el vínculo de ese padre con el resto de sus
hijos.
Rotar el diagrama resultante aproximadamente 45 grados hacia la izquierda, y así
se obtendrá el árbol binario correspondiente
3.8 GRAFOS
Un grafo dirigido G consiste en un conjunto de vértices V y un conjunto de arcos o aristas A.
Los vertice se denominan también nodos o puntos.
Un arco, es un par ordenado de vértices(V,W) donde V es el vértice inicial y W es el vértice
terminal del arco. Un arco se expresa como: V-->W y se representa de la siguiente manera:
Los vértice de un grafo pueden usarse para representar objetos. Los arcos se utilizan para
representar relaciones entre estos objetos.
Las aplicaciones más importantes de los grafos son las siguientes:
o
o
o
Rutas entre ciudades.
Determinar tiempos máximos y mínimos en un proceso.
Flujo y control en un programa.
43
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
3.8.1 TERMINOLOGIA
o La terminología que manejaremos regularmente para el uso de grafos es la siguiente:
o CAMINO.Es una secuencia de vértices V1, V2, V3, ... , Vn, tal que cada uno de estos
V1-&gtV2, V2-&gtV3, V1-&gtV3.
o LONGITUD DE CAMINO. Es el número de arcos en ese camino.
o CAMINO SIMPLE. Es cuando todos sus vértices, excepto tal vez el primero y el
último son distintos.
o CICLO SIMPLE. Es un camino simple de longitud por lo menos de uno que empieza
y termina en el mismo vértice.
o ARISTAS PARALELAS. Es cuando hay más de una arista con un vértice inicial y uno
terminal dados.
o GRAFO CICLICO. Se dice que un grafo es cíclico cuando contiene por lo menos un
ciclo.
o GRAFO ACICLICO. Se dice que un grafo es aciclíco cuando no contiene ciclos.
o GRAFO CONEXO. Un grafo G es conexo, si y solo si existe un camino simple en
cualesquiera dos nodos de G.
o GRAFO COMPLETO ó FUERTEMENTE CONEXO.Un grafo dirigido G es completo si
para cada par de nodos (V,W) existe un camino de V a W y de W a V (forzosamente
tendrán que cumplirse ambas condiciones), es decir que cada nodo G es adyacente a
todos los demás nodos de G.
o GRAFO UNILATERALMENTE CONEXO.Un grafo G es unilateralmente conexo si
para cada par de nodos (V,W) de G hay un camino de V a W o un camino de W a V.
o GRAFO PESADO ó ETIQUETADO. Un grafo es pesado cuando sus aristas
contienen datos (etiquetas). Una etiqueta puede ser un nombre, costo ó un valor de
cualquier tipo de dato. También a este grafo se le denomina red de actividades, y el
número asociado al arco se le denomina factor de peso.
o VERTICE ADYACENTE. Un nodo o vértice V es adyacente al nodo W si existe un
arco de m a n.
o GRADO DE SALIDA.El grado de salida de un nodo V de un grafo G, es el número de
arcos o aristas que empiezan en V.
o GRADO DE ENTRADA.El grado de entrada de un nodo V de un grafo G, es el
número de aristas que terminan en V.
o NODO FUENTE.Se le llama así a los nodos que tienen grado de salida positivo y un
grado de entrada nulo.
o NODO SUMIDERO.Se le llama sumidero al nodo que tiene grado de salida nulo y un
grado de entrada positivo.
3.8.2 EJEMPLOS DE GRAFOS
081.- Grafo regular: Aquel con el mismo grado en todos los vértices. Si ese grado es k lo
llamaremos k-regular.
Por ejemplo, el primero de los siguientes grafos es 3-regular, el segundo es 2-regular
y el tercero no es regular
44
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre López
2.- Grafo bipartito: Es aquel con cuyos vértices pueden formarse dos conjuntos
disjuntos de modo que no haya adyacencias entre vértices pertenecientes al mismo conjunto
Ejemplo.- de los dos grafos siguientes el primero es bipartito y el segundo no lo es
3.- Grafo completo: Aquel con una arista entre cada par de vértices. Un grafo completo con n
vértices se denota Kn.
A continuación pueden verse los dibujos de K3, K4, K5 y K6
Todo grafo completo es regular porque cada vértice tiene grado |V|-1 al estar conectado con
todos los otros vértices.
Un grafo regular no tiene por qué ser completo.
4.- Un grafo bipartido regular se denota Km,n donde m, n es el grado de cada conjunto
disjunto de vértices.
A continuación ponemos los dibujos de K1,2, K3,3, y K2,5
45
Algoritmos Avanzados
Lic. Solange Salazar
I
n
g
.
S
i
m
ó
n
O
n
o
f
r
e
L
ó
p
e
z
3.8.3 REPRESENTACION DE GRAFOS
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
UNIDAD IV TECNICAS DE DISEÑO DE ALGORITMOS
4.1 DIVIDE Y VENCERÁS
Consiste en descomponer el caso que hay que resolver en subcasos más pequeños,
resolver independientemente los subcasos y por último combinar las soluciones de los
subcasos para obtener la solución del caso original.
4.1.1 ALGORITMO GENERAL
Consideremos un problema arbitrario, y sea A un algoritmo sencillo capaz de resolver
el problema. A debe ser eficiente para casos pequeños y lo denominamos
subalgoritmo básico.
El caso general de los algoritmos de divide y vencerás es como sigue:
función DV(x)
si x es suficientemente pequeño o sencillo entonces
devolver A(x)
descomponer x en casos más pequeños x1, x2, x3 , ... , xl
para i ← 1 hasta l hacer
yi ← DV(xi)
recombinar los yi para obtener una solución y de x
devolver y
El número de subejemplares l suele ser pequeño e independiente del caso particular
que haya que resolverse.
Para aplicar la estrategia ―divide y vencerás‖ es necesario que se cumplan tres
condiciones:
Tiene que ser posible descomponer el caso en subcasos y recomponer las
soluciones parciales de forma eficiente.
Los subcasos deben ser en lo posible aproximadamente del mismo tamaño.
En la mayoría de los algoritmos de DV el tamaño de los l subcasos es
aproximadamente m/b, para alguna constante b, en donde m es el tamaño del
caso (o subcaso) original (cada subproblema es aproximadamente del tamaño
1/b del problema original).
4.1.2 COMPLEJIDAD
El análisis de tiempos de ejecución para estos algoritmos es el siguiente:
Sea g(n) el tiempo requerido por DV en casos de tamaño n, sin contar el tiempo
necesario para llamadas recursivas. El tiempo total t(n) requerido por este algoritmo de
divide y vencerás es parecido a:
t(n) = l t(n/b) + nk para 1 ≥ l y 2 ≥ b
47
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
siempre que n sea suficientemente grande. Si existe un entero k ≥ 0. Donde además:
l: Número de llamadas recursivas
b: En cuanto se divide el problema
4.1.3. EJEMPLOS
Ejemplo 1. BÚSQUEDA DEL MÁXIMO Y MÍNIMO
Estudiaremos el ejemplo del cálculo del máximo y mínimo de los elementos
almacenados
en un array. Este ejemplo es muy sencillo y sólo tiene interés para hacer un estudio
detallado de los tiempos de ejecución.
a) Método directo
Un algoritmo para resolver el problema sin aplicar la técnica divide y vencerás puede
ser:
Cálculo del máximo y mínimo, método directo, compilado en Delphi 5
PROCEDURE MaxMin(a:TVector ;VAR max,min:integer);
VAR
i:INTEGER;
BEGIN
max := a[1];
min := a[1];
FOR i := 2 TO n DO
BEGIN
IF a[i] < min THEN
min := a[i];
IF a[i] > max THEN
max := a[i];
END;
END;
Analizando el algoritmo, observamos que el número de comparaciones es
TMaxMin(n) = T(for)*(T(if-then)+ T(if-then))
= (n-1)*(2+2)
48
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
= 4n-4
Entonces es: O(n) de orden lineal
b) Aplicando Divide y Vencerás
dividimos el vector en 2 hasta llegar a un solo
elemento que se considera el caso base
PROCEDURE
maxminDV(v:TVector;i,j:INTEGER;VAR
may,min:INTEGER);
VAR
med,amay,bmay,amin,bmin:INTEGER;
BEGIN
IF i=j THEN
BEGIN
may:=v[i];
min:=v[i];
END
ELSE
BEGIN
med:=(i+j)DIV 2;
maxminDV(V,i,med,amay,amin);
maxminDV(V,med+1,j,bmay,bmin);
IF amay>bmay THEN
may:=amay
ELSE
may:=bmay;
IF amin<bmin THEN
min:=amin
ELSE
min:=bmin;
END;//fin de else
END;//fin de maxminDV
El programa en ejecución se verá de la siguiente forma:
Analizando el algoritmo DV, observamos que el número de comparaciones es
49
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
T(n) = 3 cuando n=1, en el caso base
T(n) = 2T(n/2)+ 7 cuando n>1
El problema se divide en dos
2 llamadas recursivas
aplicando la fórmula general tenemos:
l=2
b=2
k=0
donde 2>20
T(n) es
entonces
log 2
(n )
2
(n)
Ejemplo 2. BÚSQUEDA BINARIA
Sea T[1..n] un arreglo ordenado por orden no decreciente, esto es, T[i]
que 1 i j n.
T[ j], siempre
Sea x un elemento que no necesariamente se encuentra en T. El problema consiste en
buscar en que posición se encuentra x en el arreglo T, si es que se encuentra.
Formalmente, se desea encontrar el índice i tal que 1 ≤ i ≤ n+1
La aproximación evidente es examinar secuencialmente el arreglo hasta que se
encuentre el elemento buscado o hasta llegar al final del arreglo, sin embargo el lector
podrá observar que esta es la alternativa más costosa, puesto que en el peor de los
casos.
Indudablemente la Búsqueda Binaria es más beneficiosa. En la siguiente figura
buscamos el número 25
50
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Ini
fin
medio x
1
5
5
6
8
8
6
6
4
6
6
25
A continuación se presenta el algoritmo codificado en Vbasic
Function BusquedaBinaria(ByVal x As Integer) As Integer
Dim Ini, Fin, Centro As Integer
Ini = 1
Fin = Val(Text1)
Centro = Int((Ini + Fin) / 2)
Do While Ini <= Fin And Vec(Centro) <> x
If x < Vec(Centro) Then
Fin = Centro - 1
Else
Ini = Centro + 1
End If
If Fin < 1 Or Ini > Val(Text1) Then Exit Do
Centro = Int((Ini + Fin) / 2)
Loop „fin del do while
If x = Vec(Centro) Then
Busqueda=Centro
Else
Busqueda= -1
End If
End Function
La ejecución del algoritmo será de la siguiente forma:
51
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
El algoritmo DV es el siguiente:
Private Function BusquedaBinariaDV(ByVal x As Integer, ini As Integer, _
fin As Integer) As Integer
Dim Centro As Integer
If ini = fin Then
If x = V(ini) Then
BusquedaBinariaDV = ini
Else
BusquedaBinariaDV = -1
End If
Else
Centro = Int((ini + fin) / 2)
If V(Centro) = x Then
BusquedaBinariaDV = Centro
Else
If V(Centro) < x Then
BusquedaBinariaDV = BusquedaBinariaDV(x, Centro + 1, fin)
Else
BusquedaBinariaDV = BusquedaBinariaDV(x, ini, Centro - 1)
End If
End If
End If
End Function
Análisis
¿Es diferente el comportamiento del algoritmo recursivo vs. el iterativo?
NO en el contexto general del tiempo de ejecución…
La complejidad de tiempo es diferente, pero por un valor constante
Por lo tanto, el orden es el mismo: O(log n), veamos:
52
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
T(n) = 3 cuando n=1, en el caso base
T(n) = 1T(n/2)+ d cuando n>1, d es ctte
El problema se divide en dos
1 llamadas recursiva, puesto que se ejecuta solamente la
llamada then o la llamada else
aplicando la fórmula general tenemos:
l=1
b=2
k=0
donde 1=20
T(n) es
entonces
(n 0 log n)
(log n)
Ejemplo 3 MATRIZ BOOLEANA
El problema consiste en generar una matriz de la siguiente forma:
Algoritmo
void BooleDV(int m[][20], int ini, int fin, int col)
{int medio,i;
if(ini==fin-1) { m[ini][col]=0; m[fin][col]=1;}
else
53
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
{
medio=(ini+fin)/2;
for(i=ini;i<=medio;i++) m[i][col]=0;
for(i=medio+1;i<=fin;i++) m[i][col]=1;
BooleDV(m,ini,medio,col+1);
BooleDV(m,medio+1,fin,col+1);
}
}
Ejemplo 4 ORDENACIÓN
Sea T[1..n] un arreglo de n elementos. El problema consiste en ordenar estos
elementos por orden ascendente.
Posibles soluciones:
Ordenación por selección
Ordenación por inserción
Los algoritmos que ordenan por selección o por inserción requieren un tiempo que
está en (n2) para el caso peor.
Dos algoritmos de ordenación que siguen la estrategia ―divide y vencerás‖ son la
ordenación por fusión (mergesort) y la ordenación rápida (quicksort).
a) ORDENACIÓN POR FUSIÓN
Se divide el arreglo T en dos partes de tamaño aproximadamente igual, se ordenan
recursivamente las dos partes y después se fusionan las dos partes ordenadas
cuidando de mantener el orden.
En la siguiente figura se puede apreciar el proceso para ordenar un vector T
54
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Algoritmo
void OrdDV(int v[],int ini,int fin)
{
int m,a[20],b[20],ta,tb;
//ta. Tamaño del vector a
//tb.Tamaño del vector b
if(ini==fin) return;
else
{
medio=(ini+fin)/2;
ta=m;
tb=fin-m;
copiav(v,ini,medio,a);
copiav(v,medio+1,fin,b);
OrdDV(a,1,ta);
OrdDV(b,1,tb);
Merge(a,ta,b,tb,v);
}
}
Se necesita un algoritmo eficiente para fusionar dos arreglos ordenados U y V en un
único arreglo T cuya longitud es la longitud de la suma de las longitudes de U y V.
void Merge(int a[], int ta,int b[], int tb,int c[])
{int i=1,j=1,k=1,h;
while((i<=ta)&&(j<=tb))
55
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
{
if(a[i]<b[j]) { c[k]=a[i]; i++; k++; }
else { c[k]=b[j]; j++; k++;}
}
if(i<=ta) for(h=i;h<=ta;h++)
c[k]=a[h]; k++;
else for(h=j;h<=tb;h++)
c[k]=b[h]; k++;
}
Para realizar la copia en los subvectores
void copiav(int a[],int ini,int fin,int b[])
{int j=ini;
for(int i=1;i<=(fin-ini)+1;i++)
{
b[i]=a[j];
j++;
}
}
Análisis
¿Porqué no es un análisis ―every-case‖?–El algoritmo Merge tiene comportamiento
distinto dependiendo del caso. En el peor de los casos se realizar ta + tb = n
comparaciones
Para el algoritmo copiaVec, se copia primero una mitad en a más la otra mitad en b,
entonces se tiene n/2+n/2
Entonces tenemos:
T(n) = T(n/2) + T(n/2) + n/2+n/2+n
Tiempo para
ordenar
el primer
subarreglo
Tiempo para
Tiempo para
ordenar
copiar en los
Para:
el segundo
subvectores A
subarreglo
yB
T(n) = 2T(n/2)+ 2n + d cuando n>1, d es ctte
Tiempo para
ejecutar el
modulo UNE
El problema se divide en dos
2 llamadas recursiva
aplicando la fórmula general tenemos:
l=2
b=2
k=1
donde 2=21
entonces
56
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
T(n) es
(n1 log n)
(n log n)
b) ORDENACIÓN RÁPIDA (QUICKSORT)
A diferencia de la ordenación por fusión, la mayor parte del trabajo no recursivo se
invierte en construir los subcasos, y no en combinar sus soluciones.
Como primer paso, el algoritmo selecciona como ―pivote‖ a uno de los elementos del
arreglo que hay que ordenar.
A continuación, el arreglo se parte a ambos lados del pivote: se desplazan los
elementos de tal manera que los que sean mayores que el pivote queden a su
derecha, mientras que los demás (menores o iguales) quedan a su izquierda.
Enseguida, las partes del arreglo a ambos lados del pivote se ordenan
independientemente mediante llamadas recursivas del algoritmo. El resultado final es
un arreglo ordenado. Idealmente, para que los dos subcasos tuvieran
aproximadamente el mismo tamaño, el pivote debería ser la ―mediana‖ del arreglo. Sin
embargo, encontrar la mediana requiere un tiempo excesivo, por lo que nos
limitaremos a utilizar como pivote un elemento arbitrario del arreglo.
Supongamos que se descompondrá el subarreglo T[i..j] usando como pivote a p=T[i]
(el primer elemento). Para hacerlo se recorre el arreglo una sola vez empezando por
los
extremos. Los punteros k y l se inicializan en i y j+1, respectivamente. A continuación,
se incrementa el puntero k hasta que T[k] > p (el primer elemento mayor que el pivote)
y se decrementa el puntero l hasta que T[l] ≤ p (el último elemento no mayor que el
pivote). Después se intercambian T[k] y T[l]. Este proceso continúa mientras k < l.
Finalmente se intercambian T[i] y T[l] para poner el pivote en su posición correcta.
fuente: Apuntes de Roman Martinez
El algoritmo de ordenación será.
57
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Análisis
La partición del arreglo es variable, depende del pivote.
¿Cuál es el peor caso si la operación de comparación (al hacer la Partición) es la que
determina la complejidad del algoritmo?
Cuando la partición genera un subarreglo vacío y el otro con n-1 datos
Se hacen n - 1 comparaciones
·Sea T(n) el peor tiempo para hacer el Quick Sort a un arreglo de n elementos…
T(n)
Para:
Tiempo para
ordenar
el subarreglo
vacío = 0
=
T(0)
+
T(n-1)
Tiempo para
ordenar
el segundo
subarreglo
+
n-1
Tiempo para
ejecutar el
módulo
PARTICIÓN
58
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
T(n) = T(n-1) + n-1 es O(n2)
EJEMPLO 5: ALGORITMO DE STRASSEN PARA MULTIPLICAR MATRICES
·El análisis matemático de Strassen, descubrió que para:
c11
c12
c21
Existen los valores:
c22
a11
b11
X
a12
b12
a21
b21
a22
b22
m1 = (a11 + a22) * (b11 + b22)
m2 = (a21 + a22) * b11
m3 = a11 * (b12 - b22)
m4 = a22 * (b21 - b11)
m5 = (a11 + a12) * b22
m6 = (a21 - a11) * (b11 + b12)
m7 = (a12 - a22) * (b21 + b22)
=
Tales que
c11 = m1 + m4 - m5 + m7
c12 = m3 + m5
c21 = m2 + m4
c22 = m1 + m3 - m2 + m6
Dividir cada una de las matrices en 4 submatrices, y resolver por el método de
Strassen el problema
4.1.4
1
PROBLEMAS RESUELTOS
Realizar un algoritmo que utilizando la técnica de divide y vencerás
encuentre el mayor y segundo mayor elemento de un array.
Solución:
59
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Haremos un procedimiento al que mandaremos los índices del array sobre los que
estamos trabajando, y dos valores max1 y max2 que contendrán el máximo y el
segundo mayor.
M y m serán variables pues hay que devolverlos al procedimiento que los llamó
para
combinarlos:
procedure maximos(ini, fin: enteros; var ;max1, max2: datos)
var
medio: entero
amax1,bmax1,amax2,bmax2:datos
begin
si ini = fin
max1 = a[ini]
max2 = en otro caso si fin - ini = 1
si a[ini] > a[fin]
max1 = a[ini]
max2 = a[fin]
en otro caso
max1 = a[fin]
max2 = a[ini]
fin_si
en otro caso // No es suficientemente pequeño
medio = (ini + fin) div 2
maximos(ini, medio, amax1;amax2)
maximos(medio + 1, fin, bmax1;bmax2)
/*Se combinan los resultados:*/
si M1 > M2 M = M1
si M2 > m1
m = M2
en otro caso
m = m1
fin_si
en otro caso
M = M2
si M1 > m2
m = M1
en otro caso
m = m2
fin_si
fin_si
fin_si
fin_maximos
La primera llamada se hará con maximos(1, n, a, b) estando los datos en un array de
índices de 1 a n.
60
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
El Problema consiste en que dos amigos se ponen como juego para matar el tiempo,
ver si uno de ellos adivina el número que estas pensando el otro preguntando si el
número que ha pensado es menor.
Solución al problema: ¿A ver si adivinas el numero que estoy pensando?
El Problema consiste en que dos amigos se ponen como juego para matar el tiempo,
ver si uno de ellos adivina el número que estas pensando el otro preguntando si el
número que ha pensado es menor.
Primero se debe determinar un rango de trabajo para el problema, Ejemplo: Juan le
pide a Pedro que piense un número entre el 1 y el 100. este 1 será nuestro inicio(ini),
y el 10 nuestro final (fin). Supongamos que Pedro tiene es su mente el número 2.
Luego debemos de encontrar la mitad del problema, que para nuestro ejemplo sería
el 5:
Inicio(Ini)
Final(fin)
1
Medio
2
3
4
5
medio
6
7
(ini
8
9
10
fin)
2
Algoritmo
// Algoritmo desarrollado por Heber Huanca y Felicidad Silva
// para la materia de algoritmos avanzados
void BuscarNumero( int ini,int fin, int *res )
{int medio,x,n,r1=0,r2=0,sol;
medio=(fin+ini)/2;
61
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
cout<<"el numero es=> "<<medio<<"?";
cout<<"(presionamos 1=si y 2=no)";
n=leer(n);
if(n==1)
cout<<"\n el numero que pensaste es el=>"<<medio;
else
{ cout<<"es menor (1=si y 2 =no)=>";
x=leer(x);
if(x==1)
{ BuscarNumero (ini,medio,&r1); }
else
{ BuscarNumero (medio+1,fin,&r2); }
}
}
Entonces para determinar el tiempo de ejecución (T(n)) tenemos el siguiente resultado:
T(n) = 2T(n/2) + 4
n>1 en el peor caso
Entonces
l=1
b=2
k=0
donde 1=20
T(n) es
entonces
(n 0 log n)
(log n)
4.2 ALGORITMOS VORACES
Los algoritmos voraces o de avance rápido (greedy en inglés) se utilizan normalmente
para obtener soluciones iniciales aproximadas en problemas de optimización,
realizándose después una búsqueda local para obtener una solución óptima. Se trata
de obtener una solución que debe cumplir unas ciertas condiciones dadas por
ecuaciones y minimizar o maximizar una función de coste.
Una solución se obtiene tomando de entre una serie de entradas unas determinadas
en un orden determinado, y esto se hace en varios pasos, decidiendo en cada uno de
los pasos la inclusión en la solución de una entrada, teniendo en cuenta que esta
inclusión no debe hacer que la nueva solución parcial lleve a transgredir las
condiciones que debe cumplir una solución
62
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
4.2.1 ALGORITMO GENERAL
procedimiento voraz (C: conjunto):
//C es el conjunto de candidatos
S
// se construye la solución en el conjunto S
mientras C
y no solución(S) hacer
x seleccionar(C) // x es el mejor candidato
C C \{x}
si factible ( S {x}) entonces
S S {x}
Fin_mientras
si solución(S) entonces
devolver S
sino
devolver ..no hay soluciones..
Fin_si
Fin_Procedimiento
63
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
En cada paso tenemos los siguientes conjuntos:
 Candidatos seleccionados para la solución S.
 Candidatos seleccionados pero rechazados luego.
 Candidatos pendientes de seleccionar C.
4.2.2 CARACTERÍSTICAS GENERALES DE LOS ALGORITMOS VORACES
Para construir la solución del problema se dispone de un conjunto (o una lista)
de candidatos.
A medida que avanza el algoritmo se van acumulando dos conjuntos: uno
contiene candidatos que ya han sido considerados y seleccionados, y el otro
contiene candidatos que han sido considerados y rechazados.
Existe una función que comprueba si un cierto conjunto de candidatos
constituye una solución del problema, ignorando por el momento si es o no
óptima.
Existe otra función que comprueba si un cierto conjunto de candidatos es
factible, esto es, si es posible o no completar el conjunto añadiendo otros
candidatos para obtener al menos una solución del problema.
Existe una función de selección que indica en cualquier momento cuál es el
más prometedor de los candidatos restantes, que no han sido seleccionados ni
rechazados.
Existe una función objetivo que da el valor de la solución que se ha encontrado.
Algunos algoritmos voraces pueden no tener alguna de estas características
(explícitamente).
Generalmente se identifican las siguientes funciones:
solución (S). Comprueba si un conjunto de candidatos es una solución
(independientemente de que sea óptima o no)
seleccionar (C). Devuelve el elemento más ―prometedor‖ del conjunto de
candidatos pendientes (no seleccionados ni rechazados).
factible (C). Indica si a partir del conjunto de candidatos C es posible construir
una solución (posiblemente añadiendo otros elementos).
Insertar un elemento en la solución. Además de la inserción, puede ser
necesario hacer otras cosas.
Función objetivo (S). Dada una solución devuelve el coste asociado a la
misma (resultado del problema de optimización).
64
Algoritmos Avanzados
Lic Solange Salazar o
Ing. Simón Onofre
4.2.4 EJEMPLOS
Ejemplo 1. PROBLEMA DEL CAMBIO DE MONEDAS.
Disponemos de monedas de 5 Bs, 2 Bs, 1Bs, 0.50 ctvs, 0.20 ctvs y 0.10 ctvs
Construir un algoritmo que dada una cantidad P devuelva esa cantidad con
monedas de estos tipos, usando un número mínimo de monedas.
Por ejemplo: para devolver 18.70Bs un posible conjunto solución sería.: 3 monedas
de 5Bs, 1 moneda de 2Bs, 1 moneda de 1Bs, 1 moneda de 0,50 ctvs y 1 moneda de
0.20 ctvs es decir S1={3,1,1,1,1,0}
Podemos aplicar la técnica voraz y en cada paso añadir una moneda nueva a la
solución actual, hasta que el valor llegue a P.
Candidatos iniciales: todos los tipos de monedas disponibles. Supondremos que C
contiene la cantidad de elementos
Solución: conjunto de monedas que suman la cantidad P.
Una solución será de la forma (x1, x2, x3, x4, x5, x6, x7, x8), donde xi es el número
de monedas de tipo i. Suponemos que la moneda i vale ci.
Funciones:
o solución. El valor actual será solución si xici=P
o objetivo. La función a minimizar es xi, el número de monedas resultante.
o seleccionar. Elegir en cada paso la moneda de valor más alto posible, pero
menor que el valor que queda por devolver.
o factible. Valdrá siempre verdad si xi+S <= P
65
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Algoritmo
función DevolverCambio (n): conjunto de monedas
//Da el cambio de n unidades utilizando el menor número posible
//de monedas. La constante C especifica las monedas
disponibles
const C = {100, 25, 10, 5, 1}
S ← //el conjunto S contendrá la solución
s ←0 //s es la suma de los elementos de S
mientras s ≠ n hacer
x ← el mayor elemento de C tal que s + x n
si no existe ese elemento entonces
devolver ..no encuentro la solución..
S ← S {una moneda de valor x}
s←s+x
fin_mientras
devolver S
fin_función
Ejemplo 2.EL PROBLEMA DE LA MOCHILA
Se tienen n objetos y una mochila. Para i = 1, 2, ...,
n, el objeto i tienen un peso positivo wi y un valor
positivo vi. La mochila puede llevar un peso que no
sobrepase W.
El objetivo es llenar la mochila de manera que se
maximice el valor de los objetos transportados sin
pasar del peso máximo W.
20
Kg.
Por ejemplo : n = 3; M = 20
Donde n es el número de elementos y M la capacidad de la mochila
w = (18, 15, 10)
v = (25, 24, 15)
Solución 1: S = (1, 2/15, 0)
Valor total = 25 + 24*2/15 = 28.2
Solución 2: S = (0, 2/3, 1)
Valor total = 15 + 24*2/3 = 31
66
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Diseño de la solución. Utilizando un algoritmo voraz para resolver el problema.
Supondremos que los objetos se pueden partir en trozos más pequeños de manera
que se pueda seleccionar un fracción xi del objeto i, con 0 xi . 1
El objeto i contribuye en xiwi al peso total de la mochila, y en xivi al valor de la carga.
La solución se podrá plantear de la siguiente forma.
n
x i vi
Función objetivo
i 1
n
x i wi
Restricción
M
i 1
donde vi > 0, wi > 0 y 0
xi . 1 para 0
i
n
Los candidatos son los objetos disponibles.
Una solución es un vector S= (x1, ... , xn ) que indica qué fracción de cada
objeto hay que incluir.
Una solución es factible si se respetan las restricciones mencionadas.
La función objetivo es el valor total de los objetos en la mochila.
n
Si
wi
W lo óptimo es meter todos los objetos en la mochila.
i 1
n
El problema es más interesante cuando
wi
W
i 1
La solución óptima debe llenar exactamente la mochila, pues de lo contrario se podría
añadir una fracción de alguno de los objetos restantes e incrementar el valor total. Por
n
lo tanto, en una solución óptima tenemos
wi
W
i 1
Para encontrar la solución la estrategia será:
Seleccionar cada objeto por turno en algún orden adecuado.
Poner la mayor fracción posible del objeto seleccionado en la mochila.
Detenerse cuando la mochila esté llena.
67
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Algoritmo
El siguiente representa un algoritmo general, donde aún no s dtrminó con exactitud la
función de selección
función mochila ( w[1..n], v[1..n], W ) : arreglo[1..n]
// Inicialización del vector solución
para i ←1 hasta n hacer
x[i] ←0
peso ← 0
// bucle voraz
mientras peso < W hacer
i ← el mejor objeto restante
si peso + w[i] . W entonces
x[i] ←1
peso ← peso + w[i]
sino
x[i] ← (W - peso)/w[i]
peso ← W
fin_si
fin_mientras
devolver x
fin_mochila
¿Cómo seleccionar el candidato más prometedor?
Hay tres posibilidades:
1) Seleccionar el objeto más valioso
2) Seleccionar el objeto más ligero
3) Seleccionar el objeto cuyo valor por unidad de peso sea el mayor posible
Por ejemplo
W = 100
w
v
v/w
1
2
3
4
5
10
20
2.0
20
30
1.5
30
66
2.2
40
40
1.0
50
60
1.2
Si se seleccionan por orden decreciente de valor, la solución es x = [0, 0, 1,
0.5, 1]. Valor total: 66 + (0.5)40 + 60 = 146
Si se seleccionan por orden creciente de peso, la solución es x = [1, 1, 1, 1, 0].
Valor total: 20 + 30 + 66 + 40 = 156.
Si se seleccionan por orden decreciente de vi / wi, la solución es x = [1, 1, 1,0,
0.8]. Valor total: 20 + 30 + 66 + (0.8)60 = 164.
68
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Teorema
Si se seleccionan los objetos por orden decreciente de vi / wi ,
entonces el algoritmo de la mochila encuentra una solución óptima.
Análisis del Algoritmo
La ordenación por orden decreciente de vi / wi se encuentra en Θ(n log n) y el bucle
voraz está en Θ(n). Por lo tanto el algoritmo está en Θ(n log n).
4.2.5 ÁRBOLES DE RECUBRIMIENTO MÍNIMO
Sea G = <N, A> un grafo conexo no dirigido en donde N es el conjunto de nodos y A
es el conjunto de aristas. Cada arista posee una longitud no negativa.
El problema consiste en hallar un subconjunto T de las aristas de G tal que utilizando
solamente las aristas de T, todos los nodos deben quedar conectados (sin formar
ciclos), y además, la suma de las longitudes de las aristas de T debe ser tan pequeña
como sea posible.
Dado que G es conexo, debe existir al menos una solución. Sin embargo, puede haber
más de una solución (donde la suma mínima de longitudes sea igual).
En lugar de longitudes, también podemos hablar de costo. El problema consiste
entonces en hallar un subconjunto T de aristas con costo total mínimo.
Si n es el número de nodos en N (del grafo G), entonces, si G es conexo, el número
mínimo de aristas en G es n .1. Por lo tanto, este es el número de aristas en T. De
hecho, si un grafo tiene más de n-1 aristas, entonces tiene por lo menos un ciclo.
Sea G’ = <N, T>. El grafo G’ se denomina árbol de recubrimiento mínimo (o árbol de
cobertura mínimo) para el grafo G. Este problema tiene muchas aplicaciones. Por
ejemplo, supongamos que los nodos de G representan ciudades, y sea el costo de una
arista {a, b} el costo de tender una línea telefónica desde a hasta b. Entonces un árbol
de recubrimiento mínimo representa la red más barata posible para dar servicio a
todas las ciudades.
Para resolver este tipo de problemas con algoritmos voraces:
El conjunto de candidatos son las aristas de G.
Un conjunto de aristas es una solución si constituye un árbol de recubrimiento
para los nodos de N.
69
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Un conjunto de aristas es factible si no contiene ningún ciclo.
La función de selección dependerá del algoritmo.
La función objetivo que hay que minimizar es la longitud total de las aristas de
la solución.
Definición 1
Se dice que un conjunto de aristas factible es prometedor si se puede extender para
producir una solución óptima.
El conjunto vacío siempre es prometedor (ya que siempre existe una solución óptima);
además, si un conjunto de aristas prometedor ya es una solución, la extensión
requerida es irrelevante, y esta solución debe ser óptima.
Definición 2
Se dice que una arista sale de un conjunto de nodos dado si esa arista tiene
exactamente un extremo en el conjunto de nodos. Una arista puede no salir de un
conjunto de nodos ya sea porque ninguno de sus extremos esté en el conjunto, o bien
porque sus dos extremos se encuentre en el conjunto.
Lema:
Sean:
G = <N, A> un grafo conexo no dirigido en el cual las aristas tienen longitudes
no negativas
B N un subconjunto propio de los nodos de G,
T A un conjunto prometedor de aristas tal que no hay ninguna arista de T que
salga de B y
v la arista más corta que salga de B (o una de las más cortas, si hay más de
una).
Entonces T
{v} es prometedor.
El conjunto de aristas T está vacío inicialmente y se le van añadiendo aristas a medida
que progresa el algoritmo. Mientras no se haya encontrado una solución, el grafo
parcial formado por los nodos de G y las aristas de T consta de varias componentes
conexas (un nodo aislado es una componente conexa).
Los elementos de T que se incluyen en una componente conexa dada forman un árbol
de recubrimiento mínimo para los nodos de esa componente.
Al final del algoritmo, sólo queda una componente conexa, así que T es un árbol de
recubrimiento mínimo para todos los nodos de G. Para construir componentes
70
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
conexas cada vez más grandes, se examinan las aristas de G por orden creciente de
longitudes. Si una arista une a dos nodos de componentes conexas distintas, se añade
a T, y las dos componentes conexas forman ahora una única componente. En caso
contrario, se rechaza la arista (porque une a dos nodos de la misma componente
conexa formando un ciclo).
El algoritmo se detiene cuando sólo tiene una componente conexa.
Teorema
El algoritmo de Kruskal halla un árbol de recubrimiento mínimo.
Las componentes conexas se manejan como conjuntos de nodos. Se necesitan dos
operaciones: buscar(x) que indica en qué componente conexa se encuentra el nodo x,
y fusionar(A, B), para fusionar dos componentes conexas.
Algoritmo
Ejemplo:
Ordenar las aristas por orden no decreciente de
longitud:
{1,2}, {2,3}, {4,5}, {6,7}, {1,4}, {2,5}, {4,7}, {3,5},
{2,4}, {3,6},
{5,7} y {5,6}.
71
Algoritmos Avanzados
Paso Arista
Inicialización
1
2
3
4
5
6
7
Lic. Solange Salazar
Ing. Simón Onofre
considerada
{1,2}
{2,3}
{4,5}
{6,7}
{1,4}
{2,5}
{4,7}
Componentes conexas
{1},{2},{3},{4},{5},{6},{7}
{1,2},{3},{4},{5},{6},{7}
{1,2,3},{4},{5},{6},{7}
{1,2,3},{4,5},{6},{7}
{1,2,3},{4,5},{6,7}
{1,2,3,4,5},{6,7}
rechazado (forma ciclo)
{1,2,3,4,5,6,7}
Análisis del Algoritmo
Sea n el número de nodos y a el número de aristas. Se requiere un tiempo en el orden
de
Θ(a log a) para ordenar las aristas, lo cual es equivalente a
Θ(a log n), porque n -1 a n(n- 1)/2 y cuando n → ∞, log a = log nk,
donde 1 k . 2 (y log nk = k log n)
Θ(n) para iniciar los n conjuntos disjuntos
Θ(aα) para todas las operación buscar y fusionar, donde α es
aproximadamente constante (función de crecimiento lento) cuando se utilizan
estructuras de partición (de hecho, Θ(aα) Θ(log n) )
Ο(a) para las operaciones restantes en el caso peor
Por lo tanto, el tiempo total para el algoritmo está en Θ(a log n).
4.2.5.1 ALGORITMO DE PRIM
En el algoritmo de Kruskal, la función de selección escoge las aristas por orden
creciente de longitud, sin preocuparse por la conexión con las aristas seleccionadas
anteriormente (sólo cuidando que no se forme un ciclo). Por esto mismo, se crea un
bosque de árboles hasta que todas las componentes del bosque se fusionan en un
sólo árbol.
En el algoritmo de Prim, el árbol de recubrimiento mínimo crece de forma natural,
comenzando por una raíz arbitraria. En cada fase se añade una nueva rama al árbol
ya construido.El algoritmo se detiene cuando se han alcanzado todos los nodos. Sea B
un conjunto de nodos, y sea T un conjunto de aristas. Inicialmente B contiene un único
nodo arbitrario y T está vacío.
En cada paso se busca la arista más corta posible {u, v} tal que u B y v N \ B.
Entonces se añade v a B y {u, v} a T. Así, las aristas de T forman en todo momento un
árbol de recubrimiento mínimo para los nodos de B. El algoritmo termina cuando B =
N.
72
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Algoritmo
Teorema
El algoritmo de Prim halla un árbol de recubrimiento mínimo.
Otra versión del algoritmo de Prim que se puede implementar más fácilmente en una
computadora es la siguiente:
Supongamos que los nodos de G están numerados de 1 a n.
Supongamos también que hay una matriz L que da la longitud de todas las
aristas con L[i, j] = ∞ si no existe la arista correspondiente.
Se utilizarán dos arreglos: para todo nodo i N \ B, más_próximo[i]
proporciona el nodo de B que está más próximo a i, y distmin[i] da la distancia
desde i hasta este nodo más próximo. Para un nodo i B, se hace distmin[i] =
.1 (así podemos saber si un nodo está o no en B ).
73
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Ejemplo:
Seleccionar el nodo 1 el como inicial
Paso
Arista considerada
Inicialización
1
2
3
4
5
6
{1,2}
{2,3}
{1,4}
{4,5}
{4,7}
{7,6}
Componentes
Conexas
{1}
{1,2}
{1,2,3}
{1,2,3,4}
{1,2,3, 4,5}
{1,2,3,4,5,7}
{1,2,3,4,5,6,7}
T = {{1,2}, {2,3}, {1,4}, {4,5}, {4,7}, {7,6}}
4.2.5.2 ALGORITMO DE DIJKSTRA
(Implementado por Ruth Chirinos y Wilson Poma)
ANÁLISIS
Suponga que en su andar cotidiano tiene que ir a 4 lugares diferentes durante 4 días
de la semana, es decir:
DIA
Lunes
Martes
Jueves
Viernes
ACTIVIDAD
Ir al Cine
Estreno de la Película “MATRIX RECARGADO”
CINE MONJE CAMPERO – EL PRADO
Ir al Stadium
Clásico Bolivar vs. The Strtongest
Cita con el dentista
Plaza San Francisco, Perez Velasco
Ira pagar el consumo del teléfono a COTEL
Av. Mcal. Santa Cruz
Tomando como ejemplo la actividad del día Lunes. ¿Qué camino tomarías tú para
llegar a tu destino?
Las flechas representan las calles que pueden llevarte a tu destino.
Suponiendo que la cantidad de minutos desde su casa hacia cada lugar y de
cualquier lugar a otro, respetando las calles (flechas) de dirección, es la siguiente:
74
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
10
5
0
10
0
3
0
1
0
2
0
5
5
0
Un buen observador se habrá dado cuenta que el gráfico anterior se
parece a un grafo
CASA CINE
STADIUM DENTISTA COTEL
CASA
0
50
30
100
10
CINE
0
0
0
0
0
STADIUM
0
5
0
0
0
DENTISTA
0
20
50
0
0
COTEL
0
0
0
10
0
¿Qué caminos tomarías tú para legar más rápido desde tu casa a los diferentes
lugares?. A simple vista podemos decir que:
De tu casa al cine sólo 35 minutos, realizando un desvío por el
Stadium.
De tu casa al Stadium sólo 30 minutos. En ruta directa
De tu casa al dentista 20 minutos, realizando un desvío por COTEL.
De tu casa a COTEL sólo 10 minutos. En ruta directa
Este ejemplo es muy sencillo pero, ¿que sucedería si estuviéramos a cargo una
empresa de transporte?, los destinos son más de 64 y las calles o avenidas para llegar
a esos destinos son el doble de 64 o más.
75
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
¿Entonces cómo nos ayuda el Algoritmo Vorás de Dijkstra?
―El Algoritmo de Dijkstra halla los caminos más cortos desde un único origen
hasta los demás nodos del grafo.‖
Más arriba dijimos que la gráfica se parecía a un grafo, pues sí, los problemas de
caminos se solucionan más frecuentemente con grafos. Por ello nosotros
manejaremos grafos para el desarrollo del algoritmo.
¿Cómo resolverlo?
En primer lugar debemos recordar en que consiste un Algoritmo Voráz,
Es como un glotón que va devorando todo lo va encontrando, claro mientras exista
algo que comer.
En nuestro caso el algoritmo analizará todos los caminos uno a uno, mientras exista
caminos que analizar.
Entonces podemos tener 2 grupos:
o
o
Caminos Seleccionados
Caminos no Seleccionados
En el primer paso de inicialización se tendrá:
o
Lugares Seleccionados: por default (por defecto), conjunto ―S‖
TU CASA, de donde partirás a los demás lugares. En este conjunto contendrá
aquellos lugares que ya han sido seleccionados. La distancia mínima desde el
origen ya es conocida para todos los lugares de ―S‖
o
Lugares No Seleccionados (candidatos), conjunto ―C‖
LUGARES donde quieres llegar, para ellos su distancia mínima desde tu casa
hacia ellos aún no ha sido hallada y son candidatos a ser seleccionados
posteriormente.
Conjunto “D”
En otro conjunto “D”, se irán almacenando los minutos que se tarda para llegar desde
tu casa hacia otro lugar. Llamado camino especial, porque todos los lugares
76
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
intermedios a lo largo del camino pertenecen al conjunto de los lugares seleccionados
(conjunto S).
Por ejemplo : Supongamos que el Stadium ya pertenece al conjunto de
lugares seleccionados, entonces el camino más corto de tu casa al Cine es a
través de un desvío por el Stadium.
30
5
D= ―Duración‖= 35 minutos.
¿Cuándo se detiene el algoritmo?
Cuando todos los lugares (nodos) que se quieren llegar se encuentran en el conjunto
de lugares seleccionados(S), por tanto todos los caminos desde el origen hasta algún
otro nodo son especiales. Consiguientemente los valores que hay en D (conjunto de
cantidad de minutos), dan la solución del problema de caminos mínimos.
Observaciones
No solamente se pueden aplicar a caminos con respecto a sus minutos de viaje, sino a
la cantidad de Km.
PSEUDOCÓDIGO
77
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Por inducción:
o
o
Si un nodo i 6= 1 está en S , D [ i ] es la longitud del camino más corto desde el
origen a i .
Si un nodo i no está en S , D [ i ] es la longitud del camino especial más corto
desde el origen a i .
Base: Sólo 1 está en S .
o
o
se cumple trivialmente y
se cumplen por cómo se inicializa D .(conjunto de cant. de minutos)
Hipótesis de inducción: (a) y (b) son ciertos justo antes de añadir v (candidato lugar)
aS.
Paso inductivo (a): Hay que probar que después de añadir v sigue
cumpliéndose (a).
Para los nodos que ya estaban, no cambia nada .
Hay que comprobar que D [ v ] es el camino más corto a v (candidato lugar).
Vamos a ver que no hay otro camino a v de longitud menor .
78
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
d (1; v )= d (1; x )+ d ( x ; v )
= D [ x ]+ d ( x ; v )¸ D [ v ]
Paso inductivo (b): Cuando v se añade a S o bien el camino especial más
corto a w no cambia, o bien ahora pasa por v (candidato lugar)
El algoritmo compara D [ w ] con D [ v ] + G [ v ; w ] y se queda con el menor .
79
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
CODIFICACION
La siguiente codificación está hecha en Visual Basic 6.0.
¿Por qué llenamos estos
vectores desde 2 hasta
tam (cantidad de lugares
o nodos)?, Porque al
inicializar el programa
en S se almacena 1, y en
C, D, P se almacenan los
demás lugares
¿Por qué Do While es
hasta n-2?
El Comportamiento de C
y D es:
C
D
{2,3,4,5} {50,30,100,10}
{2,3,4} {50,30,20,10}
{2,3} {40,30,20,10}
{2} {35,30,20,10}
Claramente D no
cambiaría si se
hiciera una iteración
más para eliminar el
último elemento de C
¿Porqué el for toma esos
parámetros? Porque v toma un lugar
de C y utiliza una variable
auxiliar w para que vaya
recorriendo todos los lugares
posibles mínimos, claro, la
condición se ve en el if que está
dentro de el For.
Function dijkstra()
Dim i,w, v As Integer
For i=2 To tam
c(i)=i
d(i)=g(1,i)
p(i)=1
Next
Do While(n-2>0)
v=c(n)
n=n-1
For w=v To w<=2 Step -1
If(((d(v)+g(v,w))<d(w)) And (g(v,w)<>0) And v<> w)
Then
d(w)=d(v)+g(v,w)
p(w)=v
End If
Next
Loop
End Function
¿Qué hace el If?
If interactúa con D
y con P (para saber
por dónde pasarán
los caminos, estos
se almacenan en P)
Pregunta si D mas
la distancia del
nodo seleccionado
es mayor a D, y
además la distancia
del nodo es
diferente de 0, y
además si el lugar
elegido “v” es
diferente a “W”,
que es el nodo que
estamos revisando.
80
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Las pantallas anteriormente mostradas son las salidas que dio el programa, los resultados
son los caminos mínimos desde el Origen hasta cada uno de los demás nodos. Entonces
llegamos a la conclusión de que:
30
5
35 minutos para llegar al cine, con un desvío por el
Stadium.
30
30 minutos para llegar al Stadium, ruta directa.
10
10
20 minutos para llegar al Dentista, con desvío por
COTEL.
10
10 minutos para llegar a COTEL, con
ruta directa.
81
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
4.3 PROGRAMACIÓN DINÁMICA
En algunos problemas que se resuelven con llamadas recursivas a subproblemas de menor
tamaño se repiten llamadas a problemas idénticos, con lo que se repiten cálculos
innecesariamente pues se podrían haber guardado las soluciones obtenidas anteriormente.
Esto ocurre por ejemplo si calculamos los números de Fibonacci por la ecuación f(n) = f(n 1)
+ f(n 2). Con la técnica de la programación dinámica se trata de evitar estos cálculos
repetidos guardando una tabla de resultados parciales, es por tanto un método ascendente,
donde partimos de problemas de tamaño mínimo y vamos obteniendo resultados de
problemas de tamaño cada vez mayor hasta llegar al tamaño del problema a resolver. Se
diferencia de la técnica divide y vencerás en que este método es descendente.
Se aplica a problemas de optimización en los que una solución viene dada como una
secuencia de decisiones (en el problema de la mochila podría ser decidir si se incluye un
elemento o no, o qué elemento se incluye primero) y se utiliza el principio de optimalidad que
dice que cuando se ha tomado una secuencia óptima de decisiones cada subsecuencia debe
ser óptima, por lo que si se han tomado una serie de decisiones la siguiente subsecuencia
de decisiones debe ser óptima para el problema en que nos encontremos en ese momento.
El principio de optimalidad no siempre se cumple por lo que, para aplicar este método,
deberemos comprobar que síí se puede aplicar en nuestro problema. Por ejemplo, en el
problema de calcular el camino mínimo entre las ciudades A y D con el mapa de carreteras
de la figura:
una primera decisión sería tomar dirección a B o C. En este momento es óptima A B, pero
tomar esta decisión nos lleva a una solución no óptima. Da la impresión de que se cumple el
principio de optimalidad ya que en la solución A B D las dos decisiones que se han tomado
son óptimas en un cierto sentido: la decisión de tomar A B es óptima pues es la menor de las
dos posibilidades en ese momento, y la decisión de tomar B D es óptima pues es la única
posibilidad en ese momento.
Pero sin embargo no llegamos a una solución óptima por lo que no puede cumplirse el
principio de optimalidad.
Si enfocamos el problema considerando que una solución es óptima si se compone de dos
decisiones óptimas en el sentido: la primera solución es óptima para pasar de A a B o C y la
segunda decisión es óptima para pasar de B o C a D; es cierto que una solución compuesta
de dos soluciones óptimas de los subproblemas es una solución óptima global, pero no
podemos asegurar que una solución óptima se pueda descomponer en soluciones óptimas
de subproblemas con el tipo de subproblemas que hemos considerado, por lo que no se
cumple el principio de optimalidad. Pero sí se cumple el principio de optimalidad si
consideramos que una solución óptima para ir de A a D debe estar compuesta de dos
soluciones óptimas para ir de A a B o para ir de A a C y para ir de B a D o para ir de C a D.
De esta manera se ve que para resolver algunos problemas es necesario resolver
subproblemas de menor tamaño y que tengamos que guardar las soluciones de estos
subproblemas para poder reconstruir a partir de ellas la solución del problema inicial. Como
no sabemos a priori cuáles de estas soluciones óptimas de subproblemas van a dar lugar a
soluciones óptimas del problema global tenemos que guardar toda la información, lo que
82
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
hace que se necesite mucha memoria y que se hagan cálculos que a la postre se van a
revelar como innecesarios.
4.3.1 CARACTERISTICAS
La programación dinámica se suele utilizar en problemas de optimización, donde una
solución está formada por una serie de decisiones.
Igual que la técnica divide y vencerás, resuelve el problema original combinando las
soluciones para subproblemas más pequeños.
Sin embargo, la programación dinámica no utiliza recursividad, sino que almacena los
resultados de los subproblemas en una tabla, calculando primero las soluciones para los
problemas pequeños.
Con esto se pretende evitar el problema de divide y vencerás de calcular varias veces la
solución para problemas pequeños.
4.3.2 CARACTERÍSTICAS COMUNES
Hay un conjunto de características que son comunes a estos dos ejemplos y a todos los
problemas de programación dinámica.
El problema se puede dividir en niveles, se requiere una decisión en cada nivel.
En el problema de inversión de capital los niveles fueron las asignaciones para cada planta,
la decisión fue cuánto gastar. En el problema de la ruta más corta, los niveles se definieron
siguiendo la estructura del grafo, la decisión fue ir al siguiente.
Cada nivel tiene un conjunto de estados asociados a él. Los estados para el problema de
asignación de capital corresponden a la cantidad gastada en ese punto en ese instante de
tiempo. Los estados de la ruta más corta fueron los nodos visitados.
La decisión en un nivel transfoma un estado en un estado en el siguiente nivel.
En asignación de capital : la decisión de cuánto gastar dado una cantidad total gastada en el
siguiente nivel. En la ruta más corta: la decisión de donde seguir dado la llegada en el
siguiente nivel.
Dado el estado actual, la decisión óptima para cada uno de los estados que faltan no
depende de los estados o de decisiones previas.
En asignación de capital : no es necesario conocer cómo se gastó el dinero en los niveles
previos, sólo cuánto se gastó. En la ruta más corta: no fue necesario conocer cómo se llegó
al nodo, sólo se necesitaba saber que se llegó a ese nodo.
Hay una relación de recursividad que identifica la decisión óptima para el nivel j, dado que el
nivel j+1 ya fue resuelto.
El nivel final debe resolverse por si mismo.
Las dos últimas propiedades obedecen a las relaciones de recursividad dadas anteriormente.
La potencialidad de la programación dinámica, y el arte involucrado, es tomar un problema y
determinar sus niveles y estados, tal que las características mencionadas se cumplan. Si
esto es posible, entonces la relación de recursividad permite encontrar los valores en una
forma relativamente fácil. La dificultad está en la determinación de los niveles y de los
estados, para visualizar este aspecto se recomienda estudiar y analizar los siguientes
ejemplos.
83
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
4.3.4 EJEMPLOS
Ejemplo 1. CÁLCULO DE LOS NÚMEROS DE FIBONACCI.
Con divide y vencerás
Fibonacci (n: integer)
Si n<2 Devolver n
Sino Devolver Fibonacci (n-1) + Fibonacci (n-2)
Problema: Muchos cálculos están repetidos, tiempo de ejec. exponencial.
Solución: Calcular los valores de menor a mayor empezando por 0, e ir guardando los
resultados en una tabla.
Con programación dinámica.
Fibonacci (n: integer)
T[0]:= 0; T[1]:= 1;
for i:= 2 to n do
T[i]:= T[i-1] + T[i-2];
Devolver T[n];
Se utiliza la misma fórmula que en la versión anterior, pero de forma más inteligente. El
tiempo de ejecución es (n).
Ejemplo 2. CÁLCULO DEL COEFICIENTE BINOMIAL
Para calcularlo directamente:
funcion C(n,k)
si k=0 o k=n entonces devolver 1
sino devolver(C(n-1,k-1)+C(n-1,k))
fin_funcion
Para calcular C(5,3):
C(5,3) = C(4,2)+C(4,3) = C(3,1)+C(3,2)+C(3,2)+C(3,3) =
= C(2,0)+C(2,1)+C(2,1)+C(2,2)+C(2,1)+C(2,2) =
= 1+C(1,0)+C(1,1)+1+C(1,0)+C(1,1)+1+1 = 10
84
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Muchos valores C(i,j) i<n , j<n se calculan muchas veces C(2,2) o C(2,1)
La repetición de cálculos tiene orden exponencial
Se puede mejorar el algoritmo utilizando una tabla de resultados intermedios (triángulo de
Pascal), alcanzándose (n·k)
Ejemplo 3 EL PROBLEMA DE LA MOCHILA
Planteamiento
Consideramos en este caso el problema de la mochila 0/1 (no se pueden introducir en la
mochila porciones de elementos) con lo que cada xi es 0 ó 1 dependiendo de que no se
introduzca o sí se introduzca el elemento en la mochila.
En principio tenemos n objetos numerados de 1 a n y una capacidad de la mochila M, y
podemos llamar a este problemaMochila(1; n;M). Para resolverlo tendremos que resolver
problemas más pequeños que llamaremos Mochila(i; j; Y ), que corresponden al problema de
la mochila 0/1 con los objetos numerados de i a j y con capacidad de la mochila Y , por lo
que Mochila(i; j; Y ) será maximizar
Pj
k=i pkxk sujeto a
Pj
k=i wkxk _
Y y xk = 0 ó xk = 1 8k; i _ k _ j.
Para resolver el problema suponemos que hay que tomar n decisiones correspondientes
a si se mete o no un elemento en la mochila. Por lo tanto, una solución será
una secuencia de n ceros y unos, y la solución óptima se podr_á encontrar por búsqueda
exhaustiva, tal como se puede ver en la _gura:
85
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
correspondiente al ejemplo n = 3, p = (1; 2; 5), w = (2; 3; 4), M = 6. En este árbol cada nivel
corresponde a una decisión sobre si incluir o no un elemento, y los pares de los nodos
corresponden al peso y bene_cio acumulado, los nodos terminales corresponden la
soluciones y la solución óptima será la correspondiente al nodo con peso seis y mayor
bene_cio. En este ejemplo el bene_cio máximo es 6 y corresponde a incluir en la mochila los
elementos 1 y 3.
Solución por programación dinámica
Existe otra forma de formular el problema de la mochila. Esto ilustrará cuán arbitraria puede
ser la definición de niveles, estado y decisiones. Además, da una visión de la flexibilidad de
las reglas en programación dinámica. Se va a trabajar con una recursividad forward (hacia
adelante). Para el problema de la mochila se indexarán los niveles por w, el peso que se
lleva. La decisión es determinar el último item agregado para llegar al peso w. Luego, hay
sólo un estado por nivel. Sea g(w) el máximo beneficio que se puede ganar de una mochila
con un peso w. Continuamos usando
y
como el peso y beneficio, respectivamente,
para el item j. Lo siguiente relaciona g(w) con valores de g calculados previamente:
Intuitivamente, para tener una mochila con un peso de w, se debe terminar agregando algún
item. Si se agrega el item j, terminaremos con una mochila de tamaño
disponible
para ser llenada.
Ejemplo 4 EL PROBLEMA DEL VENDEDOR VIAJERO
El problema del vendedor viajero es visitar un conjunto de ciudades con una mínima
distancia. Por ejemplo, un político comienza en New York y tiene que visitar Miami, Dallas, y
Chicago antes de volver a New York. ¿Cómo podría minimizar la distancia recorrida?. Las
distancias se muestran en la Tabla siguiente.
86
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Un problema del vendedor viajero (TSP).
La dificultad está una vez más en definir los niveles, estados y decisiones. Una forma natural
es que el nivel t represente las ciudades t visitadas, y la decisión es donde ir a continuación.
¿Cuáles son los estados?. Supongamos que elegimos la ciudad en la que estamos como un
estado. No se podría tomar la decisión de donde seguir a continuación ya que no sabemos lo
que se ha realizado anteriormente. En consecuencia, el estado debe incluir información
acerca de todas las ciudades visitadas, más la ciudad a donde queremos terminar. Luego,
un estado estará representado por el par (i,S) donde S es el conjunto de t ciudades ya
visitadas e i es la última ciudad visitada ( así i debe estar en S). Usando recursividad se
tiene:
Los cálculos en el nivel 3 son:
Para los otros niveles, la recursión es:
Se recomienda realizar los cálculos. Un aspecto importante de este problema es la NPcompletitud. El espacio de búsqueda es tan grande que llega a ser imposible encontrar una
solución, aún para problemas de tamaño pequeño. Por ejemplo suponga un problema con 20
ciudades, el número de estados en el 10-ésimo nivel es más de un millón. Para 30 ciudades,
el número de estados en el nivel 15 es más de un billón. Y para 100 ciudades, el número de
estados en el nivel 50 es más de 5,000,000,000,000,000,000,000,000,000,000. Este no es
un problema fácil de resolver aún con una configuración computacional ideal.
Ejemplo 6 REEMPLAZO DE EQUIPAMIENTO
Suponga que un negocio necesita tener una máquina en los próximos 5 años. Cada máquina
nueva tiene un costo $1000. El costo de mantener la máquina durante el año i-ésimo de
operación es:
,
,y
. Una máquina se puede usar por
tres años y luego ser rematada. El valor de remate de la máquina después de i años
es
,
,
y
.
¿Cómo podría minimizar los costos el dueño del negocio sobre un período de 5 años?.
Los niveles serán asociados a cada año. El estado será la edad de la máquina en ese año.
Las decisiones son ya sea mantener la máquina o rematarla y reemplazarla por una nueva.
Sea
el mínimo costo desde el instante t al 5, dado que la máquina tiene x años de
antiguedad en el instante t.
87
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Ya que se debe rematar en el instante 5,
Ahora se consideran los otros períodos de tiempo. Si se tiene en el instante t una máquina
con 3 años de antiguedad, ésta se debe rematar en:
Si tiene dos ñaos de antiguedad, se puede rematar o mantenerla:
Costo remate
.
Costo Mantenerla
.
Así, la mejor decisión con una máquina que tiene dos años de antiguedad es el mínimo de
los dos costos.
Análogamente,
Finalmente, en el instante inicial, se debe comprar
Usando una recursividad backward (hacia atrás) se tiene:
Nivel 5.
Nivel 4.
Nivel 3.
88
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Nivel 2.
Nivel 1.
Nivel 0.
El costo es de 1280, y una solución es rematarla en los años 1 y 2. Existen otras soluciones
óptimas, se recomienda determinarlas.
4.3.5 PROBLEMAS RESUELTOS
1. Veamos un problema simple de inversión de capital. Una corporación tiene $5 millones
para invertir en sus tres plantas para una posible expansión. Cada planta ha presentado un
número de propuestas sobre como pretende gastar el dinero. Cada propuesta entrega el
costo de la expansión (c) y la ganancia esperada (r). La siguiente tabla resume las
propuestas:
89
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Posibilidades de Inversión
Cada planta sólo podrá realizar un de sus propuestas. El objetivo es maximizar el retorno de
la firma dada su inversión de $5 millones. Se supondrá que si no se gastan los $5 millones
completamente, ese dinero se perderá.
Una forma de resolver este problema es intentar todas las posibilidades y elegir la mejor. En
ese caso, hay solo
formas de invertir el dinero. Muchas de estas son
infactibles (por ejemplo, propuestas 3, 4 y 1 para las tres plantas cuesta $6 millones). Otras
propuestas son factibles, pero son muy pobres en retorno (como propuestas 1, 1 y 2, con un
retorno de sólo $4 millones.)
Desventajas de una enumeración completa:
Para problemas de gran tamaño la enumeración de todas las posibles soluciones puede no
ser factible computacionalmente.
Las combinaciones infactibles no pueden ser detectadas
a priori, llevando a una
ineficiencia.
Información sobre combinaciones previamente investigadas no se usan para eliminar otras
combinaciones menos buenas, o infactibles.
Cabe hacer notar que este problema no puede ser formulado como un problema de
programación lineal, porque los retornos no son funciones lineales.
Un método para calcular la solución es:
Dividamos el problema en 3 niveles: cada nivel representa el dinero asignado a una única
planta. Asi el nivel 1 representa el dinero asignado a la planta 1. Artificialmente se dará un
orden a los niveles, asumiendo que primero se asignará a la planta 1, luego a la planta 2 y
finalmente a la planta 3.
Cada nivel está dividido en estados. Un estado guarda la información requerida para ir
desde un nivel al siguiente nivel.
En este caso los estados por nivel 1, 2 y 3 son:
{0,1,2,3,4,5}: cantidad de dinero gastado en la planta 1, representado como
,
{0,1,2,3,4,5}: cantidad de dinero gastado en las plantas 1 y 2 (
), y
{5}: cantidad de dinero gastado en las plantas 1, 2, y 3 (
).
Es necesario notar que diferentemente a lo que es programación lineal, las
no
representan variables de decisión: ellas son simplemente representaciones de un estado
genérico
en
el
nivel.
Un retorno es asociado a cada estado. Se debe notar que para tomar una decisión en el
estado 3, es sólo necesario conocer cuanto fue gastado en las plantas 1 y 2, no cómo esto
fue gastado. También note que se desea que
sea 5 Determinando los retornos asociados
a cada estado, lo más fácil es en el nivel 1, los estados
Tabla entrega el retorno
asociado con
.
90
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Cálculos en el nivel 1
Ahora se pueden calcular para el nivel 2. En este caso, deseamos encontrar la mejor
solución tanto para la planta 1 como para la planta 2. Si se desea calcular el mejor retorno
para un
dado,simplemente se analizarán todas las propuestas de la planta 2, asignando la
cantidad requerida a la planta 2 y usar la tabla anterior para ver cómo la planta 1 gastará el
excedente. Por ejemplo, supongamos que deseamos determinar la mejor asignación para el
estado
. En el nivel 2 se puede hacer una de las siguientes propuestas:
Propuesta 1 da un retorno de 0, deja 4 para el nivel 1, el cual retorna 6. Total: 6.
Propuesta 2 da un retorno de 8, deja 2 para el nivel 1, el cual retorna 6. Total: 14.
Propuesta 3 da un retorno de 9, deja 1 para el nivel 1, el cual retorna 5. Total: 14.
Propuesta 4 da un retorno de 12, deja 0 para el nivel 1, el cual retorna 0. Total: 12.
Lo mejor que se puede hacer con 4 unidades es la propuesta 1 para la planta 2 y la
propuesta 2 para la planta 1, con un retorno de 14, o la propuesta 2 para la planta 2 y la
propuesta 1 para la planta 1, también con un retorno de 14. En cualquier caso, el retorno
para este nivel es
es 14.
El resto de la tabla se puede interpretar análogamente. .
Cálculos en el nivel 2 .
Ahora se puede analizar el nivel 3. El único valor en el que estamos interesados es
.
Nuevamente, se analizrá todas las propuestas para este nivel, determinando la cantidad de
dinero remanente y usando la última tabla para decidir el valor de los niveles previos. Asi se
puede realizar lo siguiente en la planta 3:
91
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Propuesta1 da un retorno 0, deja 5. Niveles previos dan 17. Total: 17.
Propuesta 2 da un retorno 4, deja 4. Niveles previos dan 14. Total: 18.
De esta manera, la solución óptima es implementar la propuesta 2 de la planta 3, propuesta
2 o 3 en la planta 2 y la propuesta 3 o 2 (respectivamente) en la planta 1. Esto da un retorno
de 18.
Si se estudia este procedimiento, uno puede observar que los cálculos son efectuados
recursivamente. Los cálculos en el nivel 2 están basados en el nivel 1, el nivel 3 sólo en el
nivel 2. A su vez, estando en un estado, todas las futuras decisiones son tomadas
independientemente de como se llegó a ese estado. Este es el prinicipio de optimalidad en el
cual se sustenta la programación dinámica.
Se pueden resumir estos cálculos en las siguientes fórmulas:
Sea
el retorno para la propuesta
correspondiente. Sea
siguientes cálculos:
el retorno del stado
en el estado
j,y por
el costo
en el nivel j. Luego se harán los
y
Los cálculos fueron llevados a cabo por un procedimiento forward. Sin embargo, también se
calcularon "cosas" desde el último nivel hacia el primero.
Se define:
= cantidad asignada a los niveles 1, 2, y 3,
= cantidad asignada a los niveles 2 y 3, y
= cantidad asignada al nivel 3.
Esto define una recursividad backward. Gráficamente esto se ilustra en la Figura 1.
92
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Forward vs. Backward Recursividad
Las fórmulas correspondientes son:
Sea
el retorno óptimo para el nivel 3, dado por
,
es el retorno óptimo para los niveles 2 y 3, dados por
es el retorno óptimo para los estados 1, 2 y 3 dado por
Las fórmulas de recursividad son:
,y
.
y
Si lleva a cabo todos estos cálculos llegará al mismo resultado.
Aunque la recursividad forward parezca más natural, se introdujo una recursividad hacia
backward. En este caso particular l orden de los niveles no es relevante. En otros casos
puede haber ventajas computacionales en la elección uno versus otro orden. En general, la
recursividad backward es más efectiva.
4.3.6 PROBLEMAS PROPUESTOS
Aplicar el algoritmo de programación dinámica para el problema del cambio de monedas
sobre el siguiente ejemplo: n = 3, P = 9, c = (1, 3, 4). ¿Qué ocurre si multiplicamos P y c por
un valor constante, por ejemplo por 100? ¿Ocurre lo mismo con el algoritmo voraz? ¿Cómo
se podría solucionar?
93
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
n
El número de combinaciones de m objetos entre un conjunto de n, denotado por m , para n
1 y 0 m n, se puede definir recursivamente por:
n
m
n
m
=1
Si m = 0 ó m = n
n 1
=
m
n 1
+
m
Si 0 < m < n
1
Conociendo que el resultado puede ser calculado también con la fórmula:
n!/(m!·(n-m)!)
Dar una función recursiva para calcular
n
m , usando la primera de las definiciones. ¿Cuál
será el orden de complejidad de este algoritmo? Sugerencia: la respuesta es inmediata.
n
Diseñar un algoritmo de programación dinámica para calcular
m
. Nota: la tabla construida
por el algoritmo es conocida como ―el triángulo de Pascal‖. ¿Cuál será el tiempo de ejecución
en este caso?
Una variante del problema de la mochila es la siguiente. Tenemos un conjunto de enteros
(positivos) A = {a1, a2, ..., an} y un entero K. El objetivo es encontrar si existe algún
subconjunto de A cuya suma sea exactamente K.
Desarrollar un algoritmo para resolver este problema, utilizando programación dinámica.
¿Cuál es el orden de complejidad del algoritmo?
Mostrar cómo se puede obtener el conjunto de objetos resultantes (en caso de existir
solución) a partir de las tablas utilizadas por el algoritmo.
Aplicar el algoritmo sobre el siguiente ejemplo A = {2, 3, 5, 2}, K= 7. ¿Cómo se puede
comprobar que la solución no es única?
Considerar el problema del cambio de monedas. Tenemos monedas de n tipos distintos
(cada uno con valor ci), y queremos devolver una cantidad P. Dar un algoritmo, con
programación dinámica, para calcular el número de formas diferentes de devolver la cantidad
P (independientemente del número de monedas que se use). ¿Cuál es el orden de
complejidad de este algoritmo?
Aplicar el algoritmo sobre el siguiente ejemplo: n= 4, c= {1, 3, 4, 7}, P= 7.
En el problema del cambio de monedas, en lugar de utilizar la ecuación de recurrencia:
Cambio (i, Q) = min (Cambio(i-1, Q), Cambio(i, Q - ci)+1)
Decidimos usar la siguiente:
Cambio (i, Q) = mink=0, ..., Q/c[i] { k + Cambio (i - 1, Q - k·c[i]) }
¿Es correcta esta ecuación de recurrencia para encontrar la solución? Explicar cuál es el
significado de esta fórmula.
Suponiendo que modificamos el algoritmo de programación dinámica para usar la segunda
fórmula, mostrar el resultado del algoritmo para el siguiente ejemplo: n= 4, c= {1, 3, 4}, P= 7.
94
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Estimar el orden de complejidad del algoritmo. Compararlo con el algoritmo visto en clase.
* Resolver con programación dinámica el problema de minimizar el número de monedas a
devolver para dar una cantidad C si tenemos monedas de n tipos, estando los tipos de las
monedas en un array tipos: array[1,..,n] of integer, y teniendo de cada tipo una cierta
cantidad de monedas, estando estas cantidades almacenadas en un array cantidad:
array[1,..,n] of integer (de la moneda de tipo tipos[i] podemos dar una cantidad entre 0 y
cantidad[i]). No habrá que programar la resolución, pero sí habrá que dar la ecuación
recurrente con la que se resuelve el problema, indicar qué tablas se utilizan y cómo se
rellenan, cómo se recompone la solución, cuáles son y qué valores tienen los casos base, y
estudiar el tiempo de ejecución.
Una agencia de turismo realiza planificaciones de viajes aéreos. Para ir de una ciudad A a B
puede ser necesario coger varios vuelos distintos. El tiempo de un vuelo directo de I a J será
T[I, J] (que puede ser distinto de T[J, I]). Hay que tener en cuenta que si cogemos un vuelo
(de A a B) y después otro (de B a C) será necesario esperar un tiempo de ―escala‖ adicional
en el aeropuerto (almacenado en E[A, B, C]).
Diseñar una solución para resolver este problema utilizando programación dinámica. Explicar
cómo, a partir de las tablas, se puede obtener el conjunto de vuelos necesarios para hacer
un viaje concreto.
Mostrar la ejecución del algoritmo sobre la siguiente matriz T, suponiendo que todos los E[A,
B, C] tienen valor 1. ¿Cuál es el orden de complejidad del algoritmo?
T[i, j]
A
B
C
D
A
7
2
3
B
2
2
4
C
1
9
8
D
3
2
1
-
* Supongamos una serie de n trabajos denominados a, b, c, ... y una tabla B[1..n, 1..n], en la
que cada posición B[i, j] almacena el beneficio de ejecutar el trabajo i y a continuación el
trabajo j. Se quiere encontrar la sucesión de m trabajos que dé un beneficio óptimo. No hay
límite en el número de veces que se puede ejecutar un trabajo concreto.
Idear un algoritmo por programación dinámica que resuelva el problema. Para ello, definir un
subproblema (que permita realizar la combinación de problemas pequeños para resolver
problemas grandes), especifica la ecuación de recurrencia para el mismo (con sus casos
base) y después describe las tablas necesarias y cómo son rellenadas.
Ejecutar el algoritmo sobre la siguiente tabla, suponiendo que m= 5.
B[i, j]
A
B
C
a
2
4
3
b
2
1
2
c
5
3
2
Estimar el tiempo de ejecución del algoritmo. El tiempo estimado ¿es un orden exacto o una
cota superior del peor caso?
95
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
En una cierta aplicación del problema de la mochila 0/1, los pesos de los objetos están
definidos como valores reales. Por ejemplo, tenemos 5 objetos con pesos w = (3.32, 2.15,
2.17, 3.21, /2) y beneficios v = (10.2, 9.2, 8.3, 9.1, 6.5) y capacidad de la mochila M = 7.7.
¿Qué problema ocurre al intentar aplicar el algoritmo de programación dinámica? Intenta
resolverlo de alguna forma y muestra el resultado. ¿La solución encontrada es la óptima?
Dada una tabla de tamaño nxn de números naturales, se pretende resolver el problema de
obtener el camino de la casilla (1, 1) a la casilla (n, n) que minimice la suma de los valores de
las casillas por las que pasa. En cada casilla (i, j) habrán sólo dos posibles movimientos: ir
hacia abajo (i+1, j), o hacia la derecha (i, j+1).
Resolver el problema utilizando programación dinámica. Indica la ecuación de recurrencia
usada, con los casos base necesarios, las tablas para llegar a la solución óptima y para
recomponer el camino correspondiente a esa solución óptima.
Mostrar la ejecución del algoritmo sobre la siguiente entrada.
2
5
1
3
8
3
2
4
3
4
2
6
4
5
1
5
Formular el principio de optimalidad de Bellman sobre este problema y comprobar si se
cumple.
Los algoritmos de divide y vencerás y los de programación dinámica se basan en la
resolución de un problema en base a subproblemas. Usando las mismas ecuaciones de
recurrencia de los problemas vistos en el tema 5 (cambio de monedas, mochila 0/1 y
multiplicación encadenada de matrices), diseñar algoritmos que resuelvan esos problemas
pero con divide y vencerás. Compara la complejidad obtenida con los dos tipos de
algoritmos.
En los dos primeros casos, ¿por qué los algoritmos no son comparables (al menos de forma
directa) con los algoritmos voraces correspondientes?
En el algoritmo para el cambio de monedas visto en clase, ¿es posible que al acabar de
ejecutarse obtengamos que D[n, P] = + ? En caso afirmativo, ¿qué indica esta situación?
Muéstralo con un ejemplo. En caso contrario, ¿por qué no es posible esa situación?
* Considerar el siguiente problema: dado un conjunto de números enteros X = {x 1, x2, ..., xn} y
otro entero P, queremos encontrar si existe algún subconjunto {y 1, ..., yk} de X, tal que P =
y1*y2*...*yk.
Resolver el problema utilizando programación dinámica. No es necesario programar el
algoritmo, habrá que dar una ecuación recurrente para resolver el problema, con los casos
base, indicar cómo son las tablas que se deben utilizar y la forma de rellenarlas.
A partir de las tablas, mostrar cómo podemos saber si existe tal conjunto o no, y en caso de
existir cómo se puede obtener el conjunto solución {y1, y2, ..., yk}.
Hacer una estimación del orden de complejidad del algoritmo.
Ejecutar sobre el siguiente ejemplo: X= {2, 4, 3, 9, 10}, P= 18.
Nota: tener en cuenta que el problema no es de optimización, sino de encontrar si existe una
solución o no.
96
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
En el problema de la mochila (igual que en el problema del cambio de monedas) puede
existir en general más de una solución óptima para unas entradas determinadas. ¿Cómo se
puede comprobar si una solución óptima es única o no, suponiendo que hemos resuelto el
problema utilizando programación dinámica? Dar un algoritmo para que, a partir de las tablas
resultantes del problema de la mochila, muestre todas las soluciones óptimas existentes.
En el problema de la multiplicación encadenada de matrices, la tabla debía ser rellenada por
diagonales, empezando por la diagonal 1 hasta la n. Especificar de forma más detallada (en
notación Pascal) cómo se podría implementar el rellenado de la tabla para que los cálculos
fueran realizados en este orden. Ejecutar sobre el siguiente ejemplo, n = 6, tamaños d = (23,
40, 2, 20, 6, 40, 12).
Un play off por el descenso se juega al mejor de 2n-1 partidos. Los dos equipos juegan
varios partidos hasta que uno gane n encuentros. Ese equipo será el que permanece y el
otro desciende a 3ª regional. Supongamos que se enfrentan Real Murcia y Las Palas. Si en
un momento dado el Real Murcia ha ganado i partidos y perdido j, queremos calcular la
probabilidad de que permanezca que llamaremos P(i, j). Suponer que la probabilidad de que
el Real Murcia gane un partido es 0.3 y la probabilidad de que pierda 0.7 (no posibilidad de
empate).
Aplicarla técnica de programación dinámica para resolver este problema. Para ello, definir la
ecuación de recurrencia de P(i, j) (con sus casos base), la estructura de las tablas utilizadas
y la forma de rellenarlas. Idea: dada la situación (i partidos ganados, j partidos perdidos)
considerar qué puede ocurrir en el siguiente partido. Tener en cuenta que si uno llega a n
entonces se acaba la competición.
* Usando la fórmula recursiva para el problema de la mochila 0/1 (vista en clase, en
programación dinámica), escribe un procedimiento que resuelva el problema pero con divide
y vencerás. El cuerpo del procedimiento debe ser:
Mochila(i: entero; M: entero; v, w: array[1..n] of entero):entero;
Siendo:
i = Número de objetos a usar (desde 1 hasta i). M = Capacidad de la mochila.
v, w = Beneficio y peso de los objetos. Valor devuelto = Beneficio óptimo.
* Un sistema dispone de m procesadores (P1, P2, ..., Pm), que deben ejecutar un conjunto de
n tareas distintas (T1, T2, ..., Tn), disponibles en el instante inicial. De cada tarea se conoce el
número de instrucciones que ejecuta ti (se supone que todas las instrucciones requieren el
mismo tiempo), y de cada procesador se tiene su velocidad de procesamiento vi, en número
de instrucciones por segundo. Se supone que cada procesador ejecuta las tareas de manera
secuencial, es decir sin partirlas.
El objetivo consiste en dar una planificación que minimice el tiempo medio de finalización de
las tareas. Una planificación consistirá en asignar cada tarea a un procesador, y en un orden
determinado. El tiempo medio de finalización será la media de los tiempos que cada tarea
tiene que esperar, desde el instante inicial hasta que acaba de ejecutarse.
Dar una buena solución para el problema, usando la técnica que creas más adecuada de
entre las siguientes: divide y vencerás, algoritmos voraces o programación dinámica. Se pide
explicar el funcionamiento del algoritmo y dar un esquema en pseudocódigo de su
estructura.
Ejecutar el algoritmo diseñado sobre el siguiente ejemplo: m= 3, n= 6, t= (35, 40, 20, 25,
10, 50), v= (5, 1, 10).
97
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Hacer una estimación del orden de complejidad del algoritmo.
El algoritmo diseñado, ¿obtiene siempre la solución óptima?
* Un sistema de ecuaciones sobredeterminado (con muchas más ecuaciones m que
incógnitas n), puede expresarse matricialmente como: A·X = B, siendo:
A (mxn): matriz de coeficientes,
X (nx1): vector de incógnitas,
B (mx1): vector de términos independientes.
La solución de mínimos cuadrados puede calcularse con:
X = (AT·A)-1·AT·B
Encuentra todos los posibles órdenes en los que se pueden hacer las multiplicaciones
matriciales para calcular X. Para cada uno de ellos, estima el número de productos escalares
necesario, en función de m y de n. Se supone que se usa el método clásico de multiplicación
de matrices (y no el método de Strassen). No es necesario contar las operaciones
necesarias para el cálculo de la inversa o de la transpuesta.
¿Cuál es el orden óptimo para calcular X? Compara el valor anterior con el número de
multiplicaciones requeridas en otro orden que no sea óptimo. ¿Qué se puede decir, en
cuanto al tiempo de ejecución y al orden de complejidad (el orden O(..) y la o-pequeña)?
4.4 BACKTRACKING - VUELTA ATRÁS
4.4.1 INTRODUCCIÓN
Los algoritmos de vuelta atrás se utilizan para encontrar soluciones a un problema. No
siguen unas reglas para la búsqueda de la solución, simplemente una búsqueda sistemática,
que más o menos viene a significar que hay que probar todo lo posible hasta encontrar la
solución o encontrar que no existe solución al problema. Para conseguir este propósito, se
separa la búsqueda en varias búsquedas parciales o subtareas. Asimismo, estas subtareas
suelen incluir más subtareas, por lo que el tratamiento general de estos algoritmos es de
naturaleza recursiva.
¿Por qué se llaman algoritmos de vuelta atrás?. Porque en el caso de no encontrar una
solución en una subtarea se retrocede a la subtarea original y se prueba otra cosa distinta
(una nueva subtarea distinta a las probadas anteriormente).
Puesto que a veces nos interesa conocer múltiples soluciones de un problema, estos
algoritmos se pueden modificar fácilmente para obtener una única solución (si existe) o todas
las soluciones posibles (si existe más de una) al problema dado.
Los algoritmos de vuelta atrás tienen un esquema genérico, según se busque una o todas
las soluciones, y puede adaptarse fácilmente según las necesidades de cada problema
98
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
4.4.2 ESQUEMA PARA UNA SOLUCIÓN
procedimiento ensayar (paso : TipoPaso)
repetir
seleccionar_candidato
if factible then
begin
anotar_candidato
if solucion_incompleta then
begin
ensayar(paso_siguiente)
if no acertado then borrar_candidato
end
else begin
anotar_solucion
acertado <- cierto;
end
hasta que (acertado = cierto) o
(candidatos_agotados)
fin procedimiento
4.4.3 ESQUEMA PARA TODAS LAS SOLUCIONES
procedimiento ensayar (paso : TipoPaso)
para cada candidato hacer
seleccionar candidato
if factible then
begin
anotar_candidato
if solucion_incompleta then
ensayar(paso_siguiente)
else
almacenar_solucion
borrar_candidato
end
hasta que candidatos_agotados
fin procedimiento
Por último, se exponen una serie de problemas típicos que se pueden resolver fácilmente
con las técnicas de vuelta atrás. El primero que se expone es muy conocido. Se trata de la
vuelta del caballo. Muchos problemas de los pasatiempos de los periódicos pueden
resolverse con la ayuda de un ordenador y en esta web se muestran algunos de ellos.
99
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
4.4.4 EJEMPLOS
Ejemplo 1. LA VUELTA DEL CABALLO
Se dispone de un tablero rectangular, por ejemplo el tablero de ajedrez, y de un caballo, que
se mueve según las reglas de este juego. El objetivo es encontrar una manera de recorrer
todo el tablero partiendo de una casilla determinada, de tal forma que el caballo pase una
sola vez por cada casilla. Una variante es obligar al caballo a volver a la posición de partida
en el último movimiento. Por último se estudiará como encontrar todas las soluciones
posibles partiendo de una misma casilla.
Para resolver el problema hay que realizar todos los movimientos posibles hasta que ya no
se pueda avanzar, en cuyo caso hay que dar marcha atrás, o bien hasta que se cubra el
tablero. Además, es necesario determinar la organización de los datos para implementar el
algoritmo.
¿Cómo se mueve un caballo?. Para aquellos que no sepan
jugar al ajedrez se muestra un gráfico con los ocho movimientos
que puede realizar. Estos movimientos serán los ocho
candidatos.
Con las coordenadas en las que se encuentre el caballo y las
ocho coordenadas relativas se determina el siguiente
movimiento.
Las coordenas relativas se guardan en dos arrays:
ejex = [2, 1, -1, -2, -2, -1, 1, 2]
ejey = [1, 2, 2, 1, -1, -2, -2, -1]
El tablero, del tamaño que sea, se representará mediante un array bidimensional de números
enteros. A continuación se muestra un gráfico con un tablero de tamaño 5x5 con todo el
recorrido partiendo de la esquina superior izquierda.
Cuando se encuentra una solución, una variable que se pasa
por referencia es puesta a 1 (cierto). Puede hacerse una
variable de alcance global y simplificar un poco el código, pero
esto no siempre es recomendable.
Para codificar el programa, es necesario considerar algunos
aspectos más, entre otras cosas no salirse de los límites del
tablero y no pisar una casilla ya cubierta (selección del
candidato). Se determina que hay solución cuando ya no hay
más casillas que recorrer.
A continuación se expone un código completo en C, que
recubre un tablero cuadrado de lado N partiendo de la
posición (0,0).
#include <stdio.h>
#define N 5
#define ncuad N*N
void mover(int tablero[][N], int i, int pos_x, int pos_y, int *q);
100
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
const int ejex[8] = { -1,-2,-2,-1, 1, 2, 2, 1 },
ejey[8] = { -2,-1, 1, 2, 2, 1,-1,-2 };
int main(void)
{
int tablero[N][N]; /* tablero del caballo. */
int i,j,q;
/* inicializa el tablero a cero */
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
tablero[i][j] = 0;
/* pone el primer movimiento */
tablero[0][0] = 1;
mover(tablero,2,0,0,&q);
if (q) { /* hay solucion: la muestra. */
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++)
printf("%3d ", tablero[i][j]);
putchar('\n');
}
}
else
printf("\nNo existe solucion\n");
return 0;
}
void mover(int tablero[][N],int i, int pos_x, int pos_y, int *q)
{
int k, u, v;
k = 0;
*q = 0;
do {
u = pos_x + ejex[k]; v = pos_y + ejey[k]; /* seleccionar candidato */
if (u >= 0 && u < N && v >= 0 && v < N) { /* esta dentro de los limites? */
if (tablero[u][v] == 0) { /* es valido? */
tablero[u][v] = i; /* anota el candidato */
if (i < ncuad) { /* llega al final del recorrido? */
mover(tablero,i+1,u,v,q);
if (!*q) tablero[u][v] = 0; /* borra el candidato */
}
else *q = 1; /* hay solucion */
}
}
k++;
} while (!*q && k < 8);
101
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
}
Cambiando el valor de N puede obtenerse una solución para un tablero cuadrado de tamaño
N.
A continuación, se muestra una adaptación del procedimiento que muestra todas las
soluciones. Si se ejecuta para N = 5 se encuentra que hay 304 soluciones partiendo de la
esquina superior izquierda. Cuando se encuentra una solución se llama a un procedimiento
(no se ha codificado aquí) que imprime todo el tablero.
void mover(int tablero[][N],int i, int pos_x, int pos_y)
{
int k, u, v;
for (k = 0; k < 8; k++) {
u = pos_x + ejex[k]; v = pos_y + ejey[k];
if (u >= 0 && u < N && v >= 0 && v < N) { /* esta dentro de los limites */
if (tablero[u][v] == 0) {
tablero[u][v] = i;
if (i < ncuad)
mover(tablero,i+1,u,v);
else imprimir_solucion(tablero);
tablero[u][v] = 0;
}
}
}
}
Ejemplo 2.EL PROBLEMA DE LAS OCHO REINAS
Continuamos con problemas relacionados con el ajedrez. El problema que ahora se plantea
es claramente, como se verá, de vuelta atrás. Se recomienda intentar resolverlo a mano.
Se trata de colocar ocho reinas sobre un tablero de ajedrez, de tal forma que ninguna
amenace (pueda comerse) a otra. Para los que no sepan ajedrez deben saber que una reina
amenaza a otra pieza que esté en la misma columna, fila o cualquiera de las cuatro
diagonales.
La dificultad que plantea este problema es la representación de los datos. Se puede utilizar
un array bidimensional de tamaño 8x8, pero las operaciones para encontrar una reina que
amenace a otra son algo engorrosas y hay un truco para evitarlas. La solución aquí expuesta
vuelve a ser tomada de Wirth
Es lógico que cada reina debe ir en una fila distinta. Por tanto, en un array se guarda la
posición de cada reina en la columna que se encuentre. Ejemplo: si en la tercera fila hay una
reina situada en la quinta columna, entonces la tercera posición del array guardará un 5. A
este array se le llamará col. Hace falta otro array que determine si hay puesta una reina en la
fila j-ésima. A este array se le llamará fila. Por último se utilizan dos arrays más para
determinar las diagonales libres, y se llamarán diagb y diagc.
Para poner una reina se utiliza esta instrucción:
col[i] = j ; fila[j] = diagb[i+j] = diagc[7+i-j] = FALSE;
102
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
Para quitar una reina esta otra:
fila[j] = diagb[i+j] = diagc[7+i-j] = TRUE;
Se considera válida la posición para este caso:
if (fila[j] && diagb[i+j] && diagc[7+i-j]) entonces proceder ...
A continuación se expone el código completo en C. Se han utilizado tipos enumerados para
representar los valores booleanos.
#include <stdio.h>
enum bool {FALSE, TRUE};
typedef enum bool boolean;
void ensayar(int i, boolean *q, int col[], boolean fila[], boolean diagb[], boolean diagc[]);
int main(void)
{
int i;
boolean q;
int col[8];
boolean fila[8],diagb[15], diagc[15];
for (i = 0; i < 8; i++) fila[i] = TRUE;
for (i = 0; i < 15; i++) diagb[i] = diagc[i] = TRUE;
ensayar(0,&q,col,fila,diagb,diagc);
if (q) {
printf("\nSolucion:");
for (i = 0; i < 8; i++) printf(" %d", col[i]);
} else printf("\nNo hay solucion");
return 0;
}
void ensayar(int i, boolean *q, int col[], boolean fila[], boolean diagb[], boolean diagc[])
{
int j;
j = 0;
*q = FALSE;
do {
if (fila[j] && diagb[i+j] && diagc[7+i-j]) {
col[i] = j; fila[j] = diagb[i+j] = diagc[7+i-j] = FALSE;
if (i < 7) { /* encuentra solucion? */
ensayar(i+1,q,col,fila,diagb,diagc);
if (!*q)
103
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
fila[j] = diagb[i+j] = diagc[7+i-j] = TRUE;
} else *q = TRUE; /* encuentra la solucion */
}
j++;
} while (!*q && j < 8);
}
Por último, se deja al lector que implemente un procedimiento que encuentre todas las
soluciones. Si se desea complicar más entonces se puede pedir que encuentre todas las
soluciones distintas, es decir, aquellas que no sean rotaciones o inversiones de otras
soluciones.
Ahora que se conoce el método general, puede hacerse extensible a múltiples piezas
simultáneamente.
Ejemplo 3 EL PROBLEMA DE LA MOCHILA (SELECCIÓN ÓPTIMA)
Con anterioridad se ha estudiado la posibilidad de encontrar una única solución a un
problema y la posibilidad de encontrarlas todas. Pues bien, ahora se trata de encontrar la
mejor solución, la solución óptima, de entre todas las soluciones.
Partiendo del esquema que genera todas las soluciones expuesto anteriormente se puede
obtener la mejor solución (la solución óptima, seleccionada entre todas las soluciones) si se
modifica la instrucción almacenar_solucion por esta otra: si f(solución) > f(optimo) entonces
optimo <- solución siendo f(s) función positiva, optimo es la mejor solución encontrada hasta
el momento, y solución es una solución que se está probando.
El problema de la mochila consiste en llenar una mochila con una serie de objetos que tienen
una serie de pesos con un valor asociado. Es decir, se dispone de n tipos de objetos y que
no hay un número limitado de cada tipo de objeto (si fuera limitado no cambia mucho el
problema). Cada tipo i de objeto tiene un peso wi positivo y un valor vi positivo asociados. La
mochila tiene una capacidad de peso igual a W. Se trata de llenar la mochila de tal manera
que se maximice el valor de los objetos incluidos pero respetando al mismo tiempo la
restricción de capacidad. Notar que no es obligatorio que una solución óptima llegue al límite
de capacidad de la mochila.
Ejemplo: se supondrá:
n=4
W=8
w() = 2, 3, 4, 5
v() = 3, 5, 6, 10
Es decir, hay 4 tipos de objetos y la mochila tiene una capacidad de 8. Los pesos varían
entre 2 y 5, y los valores relacionados varían entre 3 y 10.
Una solución no óptima de valor 12 se obtiene introduciendo cuatro objetos de peso 2, o 2 de
peso 4. Otra solución no óptima de valor 13 se obtiene introduciendo 2 objetos de peso 3 y 1
objeto de peso 2. ¿Cuál es la solución óptima?.
A continuación se muestra una solución al problema, variante del esquema para obtener
todas las soluciones.
void mochila(int i, int r, int solucion, int *optimo)
{
104
105
Algoritmos Avanzados
Lic. Solange Salazar
Ing. Simón Onofre
int k;
for (k = i; k < n; k++) {
if (peso[k] <= r) {
mochila(k, r - peso[k], solucion + valor[k], optimo);
if (solucion + valor[k] > *optimo) *optimo = solucion+valor[k];
}
}
}
Dicho procedimiento puede ser ejecutado de esta manera, siendo n, W, peso y valor
variables globales para simplificar el programa:
n = 4,
W = 8,
peso[] = {2,3,4,5},
valor[] = {3,5,6,10},
optimo = 0;
...
mochila(0, W, 0, &optimo);
Observar que la solución óptima se obtiene independientemente de la forma en que se
ordenen los objetos.
Algoritmos Avanzados
UNIDAD V
ALGORITMOS PROBABILISTAS
5.1 INTRODUCCIÓN
Existen muchos problemas en los que llegado a cierto punto, se ha de tomar una
decisión óptima. A menudo, la búsqueda de esta decisión toma un tiempo excesivo
A veces es mejor no tomar esta decisión óptima, sino tomar una buena decisión
En algunas ocasiones tomar decisiones aleatorias nos puede llevar a la solución
deseada
En términos de la Teoría de Algoritmos: cuando se tenga que realizar una elección en
un algoritmo, a veces es preferible hacerlo aleatoriamente en vez de perder tiempo en
decidir cuál de las posibles alternativas es la correcta
Esta elección aleatoria debe se, en promedio, más eficiente que la decisión, aunque en
algún caso el proceso aleatorio tome, por mala suerte, más tiempo
En estos casos hablamos de tiempo esperado de ejecución y no de orden de
complejidad
En este tema se verán algoritmos que, basados en la Teoría de la Probabilidad,
encuentran una solución aproximada o una solución exacta (aunque a veces no la
encuentran o dan una solución errónea)
Numéricos: solución aproximada
Monte Carlo: solución exacta; pero a veces se equivocan
Las Vegas: nunca devuelven una solución errónea, pero a veces no la
encuentran
Sherwood: siempre encuentran la solución y siempre es correcta
Cada uno de ellos se debe aplicar bajo ciertas condiciones específicas y todos buscan
soluciones de destina forma e incluso encuentran (a veces no) distinto tipo de soluciones
5.3 GENERADORES DE NÚMEROS ALEATORIOS
No se puede definir un número como aleatorio por si mismo
Lo que si se pueden definir son series de números aleatorios: cuando, de entre
un rango de valores posibles, la distribución de los números de la serie es
uniforme
Encontrar un método que nos permita obtener series de números aleatorios en
un ordenador es una tarea difícil, pero no lo es obtener series de números
pseudoaleatorios: números que a todo el mundo le parezcan aleatorios excepto al
programador que sabe el método
Lic. Solange Salazar
Ing. Simón Onofre
106
Algoritmos Avanzados
Propiedades que deben cumplir las series de números aleatorios:
_
Los números han de estar uniformemente distribuidos
Han de ser estadísticamente independientes
Han de ser reproducibles
Que requieran poco espacio en memoria
Que se obtengan rápidamente
Ejemplo ara obtener series de números pseudoaleatorios. Ejemplo simple:
escoger un número de n cifras, elevarlo al cuadrado, coger las k cifras centrales
del resultado obtenido, siendo éstas el siguiente número de la serie.
Para n=k=4:
Este método no tiene nada de aleatorio, pero lo parece. No tiene ninguna
propiedad aceptable, ya que no se puede comprobar absolutamente nada, por lo
que es impredecible cuándo comenzarán a repetirse los números (comenzar con
el 2500)
Una serie de números es aleatoria cuando no se descubre en ella ninguna
propiedad: de crecimiento, repetición, siguiente, etc.
Definición de Lehmer de serie de números pseudoaleatoria:
noción vaga que encierra la idea de una sucesión de números en la que cada término es
impredecible para la persona ajena al problema y cuyos dígitos se someten a un cierto
número de pruebas estadísticas
Generación de números aleatorios:
los métodos más comunes son los llamados métodos congruenciales. El más famoso es
el de Lehmer, en el cual se utilizan tres datos según la fórmula:
Tipos de generación de números aleatorios:
_ Generador congruencial mixto: aquel en el que c _ 0
_ Generador congruencial multiplicativo: aquel en el que c = 0
Lic. Solange Salazar
Ing. Simón Onofre
107
Algoritmos Avanzados
Diferencias: los multiplicativos son más rápidos, aunque tienen una menor longitud de
ciclo
Propiedades del generador de esta naturaleza: fácilmente reproducible (comenzando por
la misma semilla), se obtiene rápidamente y ocupa poco espacio de memoria. Hay que
esforzarse en conseguir que cumpla también las dos primeras propiedades: uniformidad e
independencia
Teorema: La sucesión congruencial definida por X0, a, c y m es de
período máximo si y sólo si:
C es primo relativo a m (su máximo común divisor es 1)
a-1 es múltiplo de p, p primo que divida a m
a-1 es múltiplo de 4 si m es múltiplo de 4
Ejemplo: a = 21, c = 27 y m = 20
Generador que cumple las 5 reglas: el propuesto por Lehmer:
a = 48.271; c = 0; m = 231 – 1 = 2.147.483.647
Problema: produce overflow con algunos valores
Solución: Reducir la expresión a:
donde Q = m div a, R = m mod a y f(m) = 0 si la suma de los dos primeros sumandos de la
fórmula es mayor o igual que 0 y f(m) = m en caso contrario
_
5.4 ALGORITMOS MONTECARLO
encuentran soluciones exactas, aunque a veces se equivocan
suele ser posible reducir
arbitrariamente la probabilidad de error a costa de un ligero aumento del tiempo de cálculo
eficiencia estará en orden inverso a su precisión
Lic. Solange Salazar
Ing. Simón Onofre
108
Algoritmos Avanzados
l objetivo de estos algoritmos es que en sucesivas llamadas a la rutina principal, se
rebaje la probabilidad de error
siempre para una misma instancia de un problema
DEFINICIÓNES
o Algoritmo MonteCarlo p-correcto, (con ½ < p < 1): algoritmo que devuelve una
solución correcta con una probabilidad no menor que p sea cual sea el caso
considerado
o Algoritmo MonteCarlo consistente: devuelve siempre la misma solución correcta
para la misma entrada
y eficientes
109
Descargar