You are on page 1of 75

ANLISIS Y DISEO DE ALGORITMOS

BASADO EN LIBRO DE
TECNICAS Y DISEO DE ALGORITMOS
DE ROSA GUEREQUETA Y ANTONIO VALLECILLO
RECOPILACIN: MG. DANIEL GAMARRA MORENO
HUANCAYO PERU
2005

2
QU ES UN ALGORITMO?
Un algoritmo, nombre que proviene del matemtico persa del siglo IX alKhowrizmi, es sencillamente un conjunto de
reglas para efectuar algn clculo, bien sea a mano o, ms frecuentemente, en una mquina.
El algoritmo ms famoso de la historia procede de un tiempo anterior al de los antiguos griegos: se trata del algoritmo
de Euclides para calcular el mximo comn divisor de dos enteros.
La ejecucin de un algoritmo no debe de implicar, normalmente, ninguna decisin subjetiva, ni tampoco debe de
hacer preciso el uso de la intuicin ni de la creatividad. Por tanto se puede considerar que una receta de cocina es un
algoritmo si describe precisamente la forma de preparar un cierto plato, proporcionndonos las cantidades exactas
que deben de utilizarse y tambin instrucciones detalladas acerca del tiempo que debe de guisarse. Por otra parte, si
se incluyen nociones vagas tales como salpimentar a su gusto o gusese hasta que est medio hecho entonces
no se podra llamar algoritmo.
Algunas circunstancias los algoritmos aproximados pueden resultar tiles. Si deseamos calcular la raz cuadrada de
2, por ejemplo, ningn algoritmo nos podr dar una respuesta exacta en notacin decimal, por cuanto la
representacin de 2 es infinitamente larga y no se repite. En este caso, nos conformaremos con que el algoritmo
nos pueda dar una respuesta que sea tan precisa como nosotros decidamos: 4 dgitos de precisin, o 10 dgitos o los
que queramos.
Hay problemas para los cuales no se conocen algoritmos prcticos. Para tales problemas, la utilizacin de uno de los
algoritmos disponibles para encontrar la respuesta exacta requerir en la mayora de los casos un tiempo excesivo:
por ejemplo, algunos siglos. Cuando esto sucede, nos vemos obligados, si es que necesitamos disponer de alguna
clase de solucin al problema, a buscar un conjunto de reglas que creamos que nos van a dar una buena
aproximacin de la respuesta correcta, y que podremos ejecutar en un tiempo razonable. Si podemos demostrar que
la respuesta computada mediante este conjunto de reglas no es excesivamente errnea, tanto mejor. A ello se
denomina algoritmo heurstico o simplemente heurstica. Obsrvese una diferencia crucial entre los algoritmos
aproximados y la heurstica: con los primeros podemos especificar el error que estamos dispuestos a aceptar; con la
segunda no podemos controlar el error, pero quiz seamos capaces de estimar su magnitud.
Cuando nos disponemos a resolver un problema, es posible que haya toda una gama de algoritmos disponibles. En
este caso, es importante decidir cul de ellos hay que utilizar. Dependiendo de nuestras prioridades y de los lmites
del equipo que est disponible para nosotros, quiz necesitemos seleccionar el algoritmo que requiera menos tiempo,
o el que utilice menos espacio, o el que sea ms fcil de programar y as sucesivamente. La respuesta puede
depender de muchos factores, tales como los nmeros implicados, la forma en que se presenta el problema, o la
velocidad y capacidad de almacenamiento del equipo de computacin disponible. Quizs suceda que ninguno de los
algoritmos disponibles sea totalmente adecuado, as que tendremos que disear un algoritmo nuevo por nuestros
propios medios.
Por ejemplo, en la multiplicacin:
Si nos han educado en Norteamrica, lo ms probable es que se multiplique sucesivamente el multiplicando por cada una
de las cifras del multiplicador, tomadas de derecha a izquierda, y que se escriban estos resultados intermedios uno tras
otro, desplazando cada lnea un lugar a la izquierda, y que finalmente se sumen todas estas filas para obtener la respuesta.
Por tanto para multiplicar 981 por 1.234 se construir una disposicin de nmeros como la de la figura 1. Si, por otra parte,
le han educado a uno en Inglaterra, es ms probable que trabajemos de izquierda a derecha, dando lugar a la distribucin que
se muestra en la figura 2.

Figura 1 . Multiplicacin estilo norteamericano.

3

Figura 2 . Multiplicacin estilo ingls.
MULPLICACIN A LA RUSA
Se escriben el multiplicando y el multiplicador uno junto a otro. Se hacen dos columnas, una debajo de cada
operando, repitiendo la regla siguiente hasta que el nmero de la columna izquierda sea un 1: se divide el nmero de
la columna de la izquierda por 2, ignorando los restos y se duplica el nmero de la columna de la derecha sumndolo
consigo mismo. A continuacin se tachan todas las filas en las cuales el nmero de la columna izquierda sea par, y
finalmente se su man los nmeros que quedan en la columna de la derecha. La figura 3 ilustra la forma de multiplicar
981 por 1.234. La respuesta obtenida es:
1234 + 4936 + 19744 + 78976 + 157952+ 315904 + 631808 = 1210554.

Figura 3 . Multiplicacin a la rusa.
MULTIPLICACIN DIVIDE Y VENCERAS
Es necesario que el multiplicando y el multiplicador tengan el mismo nmero de cifras y adems se necesita que este
nmero sea una potencia de dos, tal como 1, 2, 4, 8, 16, etc. Esto se arregla fcilmente aadiendo ceros por la
izquierda si es necesario: en nuestro ejemplo, aadimos nada ms un cero a la izquierda del multiplicando,
transformndolo en 0981, de tal manera que ambos operandos tengan cuatro cifras.

Figura 4 . Divide y vencers.
Ahora para multiplicar 0981 por 1234 multiplicamos primero la mitad izquierda del multiplicando por la mitad izquierda del
multiplicador (12), y escribimos el resultado (108) desplazado hacia la izquierda tantas veces como cifras haya en el multiplicador:
cuatro, en nuestro ejemplo. A continuacin multiplicamos la mitad izquierda del multiplicando (09) por la mitad derecha del
multiplicador (34), y escribimos el resultado (306) desplazado hacia la izquierda tantas veces como la mitad de las cifras que haya en
el multiplicador: dos, en este caso. En tercer lugar multiplicamos la mitad derecha del multiplicando (81) por la mitad izquierda
del multiplicador (12), y escribimos el resultado (972) desplazado tambin hacia la izquierda tantas veces como la mitad de las cifras
que haya en el multiplicador, y en cuarto lugar multiplicamos la mitad derecha del multiplicando (81) por la mitad derecha del
multiplicador (34) y escribimos el resultado (2.754), sin desplazarlo en absoluto. Por ltimo sumamos los cuatro resultados
intermedios segn se muestra en la figura 4 para obtener la respuesta 1210554.

4

Figura 5 . Multiplicacin de 09 por 12 mediante divide y vencers.
Si se ha seguido el funcionamiento del algoritmo hasta el momento, se ver que hemos reducido la multiplicacin de
dos nmeros de cuatro cifras a cuatro multiplicaciones de nmeros de dos cifras (09 x 12, 09 x 34, 81 x 12 y 81 x 34),
junto con un cierto nmero de desplazamientos y una suma final. El truco consiste en observar que cada una de estas
multiplicaciones de nmeros de dos cifras se puede efectuar exactamente de la misma manera salvo que cada
multiplicacin de nmeros de dos cifras requiere cuatro multiplicaciones de nmeros de una cifra, algunos
desplazamientos y una suma. Por ejemplo la figura 5 muestra cmo multiplicar 09 x 12. Calculamos O x 1 = 0,
desplazado hacia la izquierda dos veces; O x 2 = 0, desplazado hacia la izquierda una vez; 9 x 1 = 9, desplazado a la
izquierda una vez, y 9 x 2 = 18, sin desplazar. Finalmente sumamos estos resultados intermedios para obtener la
respuesta: 108. Utilizando estas ideas se podra operar de tal manera que las multiplicaciones solamente implicasen
a operandos de una cifra.
NOTACIN PARA LOS PROGRAMAS
Es importante decidir la forma en que vamos a describir nuestros algoritmos. Si intentamos explicarlos en espaol,
descubriremos rpidamente que los lenguajes naturales no estn en absoluto adaptados para este tipo de cosas. Hay
algunos aspectos de nuestra notacin para los programas que merecen especial atencin. Utilizaremos frases en
espaol en nuestros programas siempre que esto produzca sencillez y claridad. De manera similar, utilizaremos el
lenguaje matemtico, tal como el del lgebra y de la teora de conjuntos, siempre que sea necesario. Como
consecuencia, una sola instruccin de nuestros programas puede tener que traducirse a varias instrucciones, quiz
a un bucle mientras, si es que el algoritmo tiene que implementarse en un lenguaje de programacin convencional.
Por tanto no se debe esperar ser capaces de ejecutar directamente los algoritmos que presentemos: siempre ser
preciso hacer el esfuerzo necesario para transcribirlos a un lenguaje de programacin real. Sin embargo, este
enfoque es el que ms se ajusta a nuestro objetivo primordial, que es presentar de la forma ms clara posible los
conceptos bsicos que subyacen a nuestros algoritmos.
Para simplificar todava ms nuestros programas, omitiremos casi siempre las declaraciones de magnitudes
escalares (enteras, reales o booleanas). En aquellos casos en que sea importante, tal como en las funciones y
procedimientos recursivos, todas las variables que se utilizan se toman implcitamente como variables locales, a no
ser que el contexto indique claramente lo contrario. En este mismo espritu de simplificacin se evita la proliferacin
de instituciones begin y end que invaden a los programas escritos en Pascal: el rango de instrucciones tales como si,
mientras o for, as como otras declaraciones como procedimiento, funcin o registro se muestra sangrando las
instrucciones en cuestin. La instruccin devolver marca la finalizacin dinmica de un procedimiento o de una
funcin, y en este ltimo caso proporciona adems el valor de la funcin.
En los procedimientos y funciones no se declara el tipo de los parmetros, ni tampoco el tipo de los resultados
proporcionados por una funcin, a no ser que tales declaraciones hagan que el algoritmo sea ms fcil de entender.
Los parmetros escalares se pasan por valor, lo que significa que son tratados como variables locales dentro del
procedimiento o la funcin, a no ser que se declaren como parmetros var, en cuyo caso se pueden utilizar para
proporcionar un valor al programa llamante. En contraste, los parmetros tipo matriz se pasan por referencia, lo cual
significa que toda modificacin efectuada dentro del procedimiento o funcin se vern reflejados en la matriz que
realmente se pase en la instruccin que haga la llamada.
Programa para multiplicar la russe. Aqu el smbolo denota la divisin entera: cualquier posible resto se descarta.
funcin rusa(m, n)
resultado=0
repetir
si m es impar entonces resultado=resultado + n
m=m 2
n=n + n
hasta que m = 1
devolver resultado+n

5
NOTACIN MATEMTICA
CLCULO PROPOSICIONAL
Existen dos valores de verdad, verdadero y falso. Una variable booleana (o proposicional) solamente puede tomar
uno de estos dos valores. Si p es una variable booleana, escribimos p es verdadero, o bien simplemente p, para
indicar p=verdadero. Esto es generalizable a todas las expresiones arbitrarias cuyo valor sea booleano. Sean p y q
dos variables booleanas. Su conjuncin q p , o p y q es verdadero si y slo si p y q son verdaderos. Su disyuncin
q p , o p o q es verdadero si y slo si al menos uno de entre p o q es verdadero. (En particular, la disyuncin de p
y q es verdadera cuando tanto p como q son verdaderos.) La negacin de p que se denota como p o no p, es
verdadero si y slo si p es falso. Si la verdad de p implica la de q escribiremos q p , que se lee si p entonces q. Si
la verdad de p es equivalente a la de q, lo cual significa que son ambos o bien verdadero o bien falso, entonces
escribimos q p . Podemos construir frmulas booleanas a partir de variables booleanas, constantes (verdadero y
falso), conectivas ( , , , , ) y parntesis de la forma evidente.
TEORA DE CONJUNTOS
Para todos los efectos prcticos, resulta suficiente pensar que un conjunto es una coleccin no ordenada de
elementos distintos. Un conjunto se dice finito si contiene un nmero finito de elementos; en caso contrario el conjunto
es infinito. Si X es un conjunto finito, |X|, la cardinalidad de X denota el nmero de elementos que hay en X. Si X es
un conjunto infinito podemos escribir que la cardinalidad de X es infinita. El conjunto vaco, que se denota como , es
el conjunto nico cuya cardinalidad es 0.
La forma ms sencilla de denotar un conjunto es rodear la enumeracin de esos elementos entre llaves. Por ejemplo,
{2,3,5,7}, denota el conjunto de nmeros primos de una sola cifra. Cuando no puede surgir ninguna ambigedad, se
permite el uso de puntos suspensivos, tal como en N= {0,1,2,3, ...} es el conjunto de nmeros naturales.
Si X es un conjunto, xX significa que x pertenece a X. Escribiremos que xX cuando x no pertenezca a X. La barra
vertical | se lee en la forma tal que y se utiliza para definir un conjunto describiendo la propiedad que cumplen
todos sus miembros. Por ejemplo, {n | n N y n es impar} denota el conjunto de todos los nmeros naturales
impares. Hay otras notaciones alternativas ms sencillas para el mismo conjunto que son {nN | n es impar} o
incluso {2n + 1 I nN}.
Si X e Y son dos conjuntos, XY significa que todos los elemento de X pertenecen tambin a Y; y se lee X es un
subconjunto de Y. La notacin XY significa que X Y y adems que hay por lo menos un elemento de Y que no
pertenece a X; se lee X es un subconjunto propio de Y. Tenga en cuenta que algunos autores utilizan para
denotar lo que nosotros denotamos mediante . Los conjuntos X e Y son iguales, lo cual se escribe X=Y, si y slo si
contienen exactamente los mismos elementos. Esto es equivalente a decir que X Y y que YX.
Si X e Y son dos conjuntos, denotamos su unin mediante X

Y={z|z

X o z

Y}, su interseccin como


X

Y={z|z

X y z

Y), y su diferencia como X\Y={z|z

X pero zY}.
Obsrvese en particular que z

XY cuando z pertenece tanto a X como a Y.


Representamos por (x, y) el par ordenado que consta de los elementos x e y en este orden. El producto cartesiano de
X e Y es el conjunto de pares ordenados cuyo primer componente es elemento de X y cuyo segundo componente es
elemento de Y; esto es X x Y= {(x,y)|x

X e y

Y}. Las n-tuplas ordenadas para n > 2 y el producto cartesiano de ms


de dos conjuntos se definen de forma similar. Denotaremos X x X por X
2
y similarmente para X
i
, i3.
ENTEROS, REALES E INTERVALOS
Denotaremos el conjunto de los nmeros enteros por Z ={..., 2, 1, 0, 1, 2, ...}, y el conjunto de nmeros naturales
como ={0, 1, 2, ...}, y el conjunto de los enteros positivos como
+
={1, 2, 3,...}. A veces ponemos de manifiesto
que el 0 no est incluido en
+
haciendo alusin explcita al conjunto de los nmeros enteros estrictamente
positivos. En algunas ocasiones aludiremos a los nmeros naturales con el nombre de enteros no negativos.
Indicamos el conjunto de nmeros reales como , y el conjunto de los nmeros reales positivos como
{ } | 0 x x
+
>
En algunas ocasiones hacemos hincapi en que 0 no est incluido en
+
aludiendo explcitamente al conjunto de
nmeros reales estrictamente positivos. El conjunto de nmeros reales no negativos se denota mediante

6
{ }
0
| 0 x x

.
Un intervalo es un conjunto de nmeros reales que yacen entre dos lmites. Sean a y b dos nmeros reales tales que
ab. El intervalo abierto (a, b) se representa por:
{ } | x a x b < < .
El intervalo es vaco si a = b. El intervalo cerrado [a,b] se representa por
{ } | x a x b
Tambin existen intervalos semiabiertos:
( ] { } , | a b x a x b <
[ ) { } , | a b x a x b <
Lo que es ms, se admiten a y b +. con el significado evidente siempre y cuando queden en el lado
abierto de un intervalo.
Un intervalo entero es un conjunto de enteros que yacen entre dos lmites. Sean i y j dos enteros tales que i j + 1. El
intervalo [ij] se indica por
{ } | n i n j
Este intervalo est vaco si i=j+1. Obsrvese que |[ij]|=j-i+1.
FUNCIONES Y RELACIONES
Sean X e Y dos conjuntos. Todo subconjunto de su producto cartesiano X x Y es una relacin. Cuando xX e yY,
decimos que x est relacionado con y segn , lo cual se denota x y, si y slo si (x, y) . Por ejemplo, uno puede
pensar en la relacin con respecto a los nmeros enteros como el conjunto de los pares de enteros tales que el
primer componente del par es menor o igual que el segundo.

Considrese cualquier relacin f entre X e Y. La relacin se llama funcin si, para cada x X, existe slo un y
perteneciente a Y tal que el par (x,y) f. Esto se representa con la expresin f: X Y, lo que se lee f es una funcin
de X en Y. Dado x X, el nico y Y tal que (x,y) f se representa como f(x). El conjunto X se llama dominio de
la funcin, Y es su imagen, y el conjunto f[X]={f(x) | x X} es su rango. En general, f[Z] denota {f(x) | x e Z} siempre
que Z X.
Una funcin f:X Y se dice inyectiva (o bien uno a uno) si no existen dos x
1
, x
2
X tales que f(x
1
)=f(x
2
). Es
suprayectiva (o sobreyectiva) si para todos los y Y existe al menos una x X tal que f(x)=y. En otras palabras, es
suprayectiva si su rango coincide con su imagen. Se dice que es biyectiva si es a la vez inyectiva y suprayectiva. Si f
es biyectiva, denotaremos por f
-1
, que se pronuncia la inversa de f a la funcin de Y en X que est definida por f(f
-
1
(y))=y para todos los y Y.
Ejemplo inyectiva:
X={a, e, i}
Y={1, 3, 5, 7}

7
f={(a, 7), (e, 1), (i, 5)}
Ejemplo sobreyectiva:
X={a, e, i, o, u}
Y={1, 3, 5, 7}
f={(a, 1), (e, 7), (i, 3), (o, 5), (u, 7)}
Ejemplo biyectiva
X={a, e, i, o, u}
Y={1, 3, 5, 7, 9}
f={(a, 5), (e, 1), (i, 9), (o, 3), (u, 7)}
Dado cualquier conjunto X, una funcin P:X (verdadero, falso) se llama un predicado sobre X. Existe una
equivalencia natural entre predicados de X y subconjuntos de X: el subconjunto correspondiente a P es {x

X|P(x)}.
Cuando P es un predicado sobre X, diremos en algunas ocasiones que P es una propiedad de X. Por ejemplo, la
imparidad es una propiedad de los enteros, que es verdadera para los enteros impares y falsa para los enteros pares.
Tambin existe una interpretacin natural de las frmulas booleanas en trminos de predicado. Por ejemplo, uno
puede definir un predicado P: {verdadero, falso}
3
{verdadero, falso} mediante
( , , ) ( ) ( ) P p q r p q q r
en cuyo caso P (verdadero, falso, verdadero) = verdadero.
CUANTIFICADORES
Los smbolos

se leen para todo y existe, respectivamente. Para ilustrar esto, considrese un conjunto
arbitrario X y una propiedad P sobre X. Escribimos
( )[ ( )] x X P x
para indicar que todos los x de X tienen la
misma propiedad P. De manera similar,
( )[ ( )] x X P x
significa que existe al menos un elemento de x en X que
tiene la propiedad P. Finalmente, escribiremos
( ! )[ ( )] x X P x
para significar existe exactamente un x en X que
tiene la propiedad P. Si X es el conjunto vaco,
( )[ ( )] x X P x
siempre es vacamente verdadero, intente
encontrar un contraejemplo si no est de acuerdo, mientras que
( )[ ( )] x X P x
siempre es trivialmente falso.
Considrense los tres ejemplos concretos siguientes:
1
( 1)
( )
2
n
i
n n
n i

+ 1

1
]


2
1
( ! )
n
i
n i n
+

1

1
]


[ ] ( , ) 1, 1 12573 m n m n y mn > >
Estos ejemplos afirman que la bien conocida frmula para la suma de los n primeros enteros es siempre vlida, que
esta suma es tambin igual a un n
2
slo para un valor entero positivo de n, y que 12.573 es un entero compuesto,
respectivamente.
Se puede utilizar una alternancia de los cuantificadores en una sola expresin. Por ejemplo:
[ ] ( )( ) n m m n >
dice que para todo nmero natural existe otro nmero natural mayor todava. Cuando se utiliza la alternancia de
cuantificadores, el orden en el cual se presentan los cuantificadores es importante. Por ejemplo, la afirmacin
[ ] ( )( ) m n m n > es evidentemente falsa: significara que existe un entero m que es mayor que todos los
nmeros naturales (incluyendo el propio m).
Siempre y cuando el conjunto X sea infinito, resulta til decir que no solamente existe un x

X tal que la propiedad de


P(x) es cierta, sino que adems existen infinitos de ellos. El cuantificador apropiado en este caso es

Por ejemplo,

8
[ ] ( ) n si nes primo


. Obsrvese que

es ms fuerte que

pero ms dbil que

. Otro cuantificador til,


ms fuerte que

pero todava ms dbil que

, es

, que se usa cuando una propiedad es vlida en todos los


casos salvo posiblemente para un nmero finito de excepciones.
Por ejemplo,
[ ] ( ) , n si nes primo entonces nes impar


significa que los nmeros primos siempre son impares,
salvo posiblemente por un nmero finito de excepciones, en este caso hay solamente una excepcin: 2 es a la vez
primo y par.
Cuando estamos interesados en las propiedades de los nmeros naturales, existe una definicin equivalente para
estos cuantificadores, y suele ser mejor pensar en ellos en consecuencia. Una propiedad P de los nmeros naturales
es cierta con infinita frecuencia, si, independientemente de lo grande que sea m, existe un nm tal que P(n) es vlido.
De manera similar, la propiedad P es vlida para todos los nmeros naturales salvo posiblemente por un nmero
finito de excepciones si existe un natural m tal que P(n) es vlido para todos los nmeros naturales nm. En este
ltimo caso, diremos que la propiedad P es cierta para todos los enteros suficientemente grandes. Formalmente:
[ ] ( ) ( ) ( ) ( )[ ( )] n P n es equivalentea m n m P n


mientras que
[ ] ( ) ( ) ( ) ( )[ ( )] n P n es equivalente a m n m P n


El principio de dualidad para los cuantificadores dice que no es cierto que la propiedad P sea vlida para todo x

X si
y slo si existe al menos un x

X para el cual la propiedad P no es vlida. En otras palabras:


[ ] ( ) ( ) ( ) [ ( )] x X P x es equivalentea x X P x
De manera similar:
[ ] ( ) ( ) ( ) [ ( )] x X P x es equivalentea x X P x
El principio de dualidad tambin es vlido para

.
SUMAS Y PRODUCTOS
Considrese una funcin
: f
y un entero n0. (Esto incluye : f como caso especial.) La suma de los
valores tomados por f sobre los n primeros nmeros positivos se denota mediante
1
( ) (1) (2) ( )
n
i
f i f f f n

+ + +

K
que se lee la suma de los f(i) cuando i va desde 1 hasta n.
En el caso en que n=0, la suma se define como O. Esto se generaliza en la forma evidente para denotar una suma
cuando i va desde m hasta n siempre y cuando mn+1. En algunas ocasiones resulta til considerar sumas
condicionales. Si P es una propiedad de los enteros,
( )
( )
P i
f i


denota la suma de f(i) para todos los enteros i tal que sea vlido P(i). Esta suma puede no estar bien definida si
involucra a un nmero infinito de enteros, podemos incluso utilizar una notacin mixta tal como
1
( )
( )
n
i
P i
f i


que denota la suma de los valores tomados por f para aquellos enteros que se encuentran entre 1 y n para los cuales
es vlida la propiedad P. Si no hay tales enteros, la suma es 0. Por ejemplo,

9
10
1
1 3 5 7 9 25
i
i impar
i

+ + + +


El producto de los valores tomados por f sobre los n primeros enteros positivos se denota mediante
1
( ) (1) (2) (3) ( )
n
i
f i f f f f n

K ,
lo cual se lee el producto de los f(i) cuando i va desde 1 hasta n. En el caso n=0, se define el producto como 1. Esta
notacin se generaliza en la misma forma que en la notacin del sumatorio.
MISCELNEA
Si b1 y x son nmeros reales estrictamente positivos, entonces log
b
x , que se lee el logaritmo en base b de x, se
define como el nmero real y tal que
y
b x . Por ejemplo,
10
log 1000 3 . Obsrvese que aun cuando b y x deben
ser positivos, no existe tal restriccin para y. Por ejemplo,
10
log 0, 001 3 . Cuando la base b no est especificada,
interpretamos que se trata de e=2,7182818..., la base de los llamados logaritmos naturales (algunos autores toman la
base 10 cuando no se especifica y denotan el logaritmo natural como ln.) En Algoritmia, la base que se utiliza ms
a menudo para los logaritmos es 2, y merece una notacin propia: lg x es la abreviatura de
2
lg x . Las identidades
logartmicas ms importantes:
log ( ) log log
b b b
xy x y + ,
log log
y
b b
x y x ,
log
log
log
b
a
b
x
x
a

y por ltimo
log log
b b
y x
x y
Recurdese tambin que el log log n es el logaritmo del logaritmo de n, pero log
2
n es el cuadrado del logaritmo de
n.
Si x es un nmero real, x 1
]
representa el mayor entero que no es mayor que x, y se denomina el suelo de x. Por
ejemplo,
1
3 3
2
1

1
]
. Cuando x es positivo, x 1
]
es el entero que se obtiene descartando la parte fraccionaria de x si
es que existe. Sin embargo, cuando x es negativo y no es un entero en s x es ms pequeo que este valor por una
unidad. Por ejemplo,
1
3 4
2
1

1
]
. De manera similar, definimos el techo de x, que se denota como x 1
1
, como el
menor entero que no es menor que x. Obsrvese que 1 1 x x x x x < < + 1 1
] 1
para todo x.
Si m0 y n>0 son enteros, m/n denota como siempre el resultado de dividir m por n, lo cual no es necesariamente un
entero. Por ejemplo, 7/2 = 3. Denotamos el cociente entero mediante el smbolo , por tanto 72=3. Formalmente,
/ m n m n 1
]
. Tambin utilizamos mod para denotar el operador mdulo que se define como:
mod ( ) m n m n m n
En otras palabras, m mod n es el resto cuando m es dividido por n.
Si m es un entero positivo, denotamos el producto de los m primeros enteros positivos como m!, lo cual se lee
factorial de m. Es natural definir 0!=1. Ahora bien n!=n(n 1)! para todos los enteros positivos n. Una aproximacin
til del factorial es la que da la frmula de Stirling: ! 2
n
n
n n
e


,
, en donde e es la base de los logaritmos
naturales.
Si n y r son enteros tales que 0 r n , se representa mediante
n
r
_

,
el nmero de formas de seleccionar r
elementos de un conjunto de cardinalidad n, sin tener en cuenta el orden en el cual hagamos nuestras selecciones.

10
TCNICA DE DEMOSTRACIN 1: CONTRADICCIN
Ya hemos visto que puede existir toda una seleccin de algoritmos disponibles cuando nos preparamos para resolver
un problema. Para decidir cul es el ms adecuado para nuestra aplicacin concreta, resulta crucial establecer las
propiedades matemticas de los diferentes algoritmos, tal como puede ser el tiempo de ejecucin como funcin del
tamao del ejemplar que haya que resolver. Esto puede implicar demostrar estas propiedades mediante una
demostracin matemtica. Dos tcnicas de demostracin que suelen ser tiles en Algoritmia: la demostracin por
contradiccin y la demostracin por induccin matemtica.
La demostracin por contradiccin, que tambin se conoce como prueba indirecta, consiste en demostrar la
veracidad de una sentencia demostrando que su negacin da lugar a una contradiccin. En otras palabras,
supongamos que se desea demostrar la sentencia S. Por ejemplo, S podra ser existe un nmero infinito de
nmeros primos. Para dar una demostracin indirecta de S, se empieza por suponer que S es falso (o,
equivalentemente, suponiendo que no S es verdadero). Qu se puede deducir si, a partir de esa suposicin, el
razonamiento matemtico establece la veracidad de una afirmacin que es evidentemente falsa? Naturalmente,
podra ser que el razonamiento en cuestin estuviera equivocado. Sin embargo, si el razonamiento es correcto, la
nica explicacin posible es que la suposicin original es falsa. De hecho, solamente es posible demostrar la
veracidad de una falsedad a partir de una hiptesis falsa.
Ilustraremos este principio con dos ejemplos, el segundo de los cuales es una sorprendente ilustracin de que las
pruebas indirectas nos dejan algunas veces un sabor algo amargo. Nuestro primer ejemplo es el que ya se ha
mencionado, y que era conocido por los antiguos griegos (Proposicin Vigsima del Libro IX de los Elementos de
Euclides).
Teor ema 1. (Eu clides ) Existen infinitos nmeros primos
DEMOSTRACIN
Sea P el conjunto de los nmeros primos. Supongamos para buscar una contradiccin que P es un conjunto finito. El
conjunto P no es vaco, porque contiene al menos el entero 2. Dado que P es finito y no est vaco, tiene sentido
multiplicar todos sus elementos. Sea x ese producto, y sea y el valor x + 1. Consideremos el menor entero d que es
mayor que 1 y que es el divisor de y. Este entero existe ciertamente por cuanto y es mayor que 1 y no exigimos
que d sea distinto de y. Obsrvese en primer lugar que d en s es primo, porque en caso contrario todo divisor propio
de d dividira tambin a y, y sera ms pequeo que d, que estara en contradiccin con la definicin de d. Por tanto,
de acuerdo con nuestra suposicin de que P contiene absolutamente todos los nmeros primos, d pertenece a P.
Esto demuestra que d es tambin divisor de x, puesto que x es el producto de una coleccin de enteros que contiene
a d. Hemos llegado a la conclusin de que d divide exactamente tanto a x como a y. Pero se recordar que y = x + 1.
Por tanto, hemos obtenido un entero d ms grande que 1 y que divide a dos enteros consecutivos x e y. Esto es
claramente imposible: si realmente d divide a x, entonces la divisin de y por d dejar necesariamente 1 como resto.
La conclusin ineludible es que la suposicin original era igualmente imposible. Pero la suposicin original era que el
conjunto P de todos los primos es finito, y por tanto su imposibilidad indica que el conjunto P es, de hecho, infinito.
La demostracin del Teorema de Euclides se puede transformar en un algoritmo, aunque no sea muy eficiente, capaz
de hallar un nuevo nmero primo dado cualquier conjunto finito de primos.
funcin Nuevoprimo (P : conjunto de enteros)
{El argumento P debera ser un conjunto de primos no vaco finito}
x= producto de los elementos de P
y= x + 1
d=1
repetir d=d+1 hasta que d divide a y
devolver d
La demostracin de Euclides establece que el valor proporcionado por Nuevoprimo (P) es un nmero primo que no
pertenece a P. Pero quin necesita a Euclides cuando est escribiendo un algoritmo para esta tarea? Por qu no
utilizar el siguiente algoritmo, mucho ms sencillo?
funcin EvitarEuclides (P : conjunto de enteros)
{El argumento P ha de ser un conjunto de primos no vaco finito}
x el mayor elemento de P
repetir x = x + 1 hasta que x es primo
devolver x
Es evidente que este segundo algoritmo proporciona como resultado un nmero primo que no pertenece a P,
verdad? La respuesta es s, siempre y cuando el algoritmo termine. El problema es que EvitarEuclides quedara en
un bucle infinito si P contuviera el mayor de los nmeros primos.

11
Naturalmente, esta situacin no se puede dar porque no existe tal cosa como el mayor nmero primo, pero se
necesita la demostracin de Euclides para establecer esto. En resumen, EvitarEuclides funciona, pero la
demostracin de su terminacin no es inmediata. Por contraposicin, el hecho de que Nuevoprimo siempre termine
es evidente (en el caso peor terminar cuando d alcance el valor de y), pero el hecho de que proporcione un nuevo
primo requiere demostracin.
Acabamos de ver que a veces es posible transformar una demostracin matemtica en un algoritmo.
Desafortunadamente, esto no siempre sucede cuando la demostracin es por contradiccin. Ilustraremos esto con un
ejemplo elegante.
Teorema 2. Existen dos nmeros irracionales x e y tales que x
y
es racional.
DEMOSTRACIN
Para realizar la demostracin por contradiccin supongamos que x
y
es necesariamente irracional siempre que tanto x
como y son irracionales. Es bien conocido que 2 es irracional (esto se saba ya en tiempos de Pitgoras, que vivi
incluso antes de Euclides). Hagamos que z tome el valor de
2
2 . Por nuestra suposicin z es irracional por cuanto es el
resultado de elevar un nmero irracional ( 2 ) a una potencia irracional (una vez ms 2 ). Hagamos ahora que w tenga el
valor de
2
z .

Una vez ms, tenemos que w es irracional por nuestra suposicin, en cuanto que tanto z como 2 son
irracionales. Pero
( ) ( )
2
2 2 2 2
2
2 2 2 2 w z

_


,
.
Hemos llegado a la conclusin de que 2 es irracional, lo cual claramente es falso. Por tanto hay que concluir que
nuestra suposicin era falsa: debe ser posible obtener un nmero racional cuando se eleva un nmero irracional a
una potencia irracional.
Ahora bien, cmo se podra transformar esta demostracin en un algoritmo? Claramente el propsito del algoritmo
sera mostrar dos nmeros irracionales x e y tales que
y
x es racional. A primera vista, puede uno sentirse tentado a
decir que el algoritmo podra limitarse a indicar x z (tal como se define z en la demostracin) e 2 y , puesto
que se ha demostrado que z es irracional y que
2
2 z . Cuidado! la demostracin de que z es irracional,
depende de la suposicin falsa con la que hemos comenzado y por tanto sta demostracin no es vlida (slo la
demostracin no es vlida; a decir verdad, es cierto que z es irracional, pero esto es difcil de establecer). Siempre
hay que tener cuidado de no utilizar posteriormente un resultado intermedio demostrado en medio de una
demostracin por contradiccin.
No hay una forma directa de extraer la pareja requerida (x, y) a partir de la demostracin del teorema. Lo que se
puede hacer es extraer dos parejas y afirmar con confianza que una de ellas hace lo que deseamos (pero no ser
posible saber cul). Esta demostracin se denomina no constructiva y no es muy frecuente entre las pruebas
indirectas. Aun cuando algunos matemticos no aceptan las demostraciones no constructivas, la mayora de ellos las
ven como perfectamente vlidas. En todo caso, nos abstendremos en lo posible de utilizarlas en el contexto de la
Algoritmia.

12
TCNICA DE DEMOSTRACIN 2: INDUCCIN MATEMTICA
De las herramientas matemticas bsicas tiles en la Algoritmia, quiz no haya ninguna ms importante que la
induccin matemtica. No slo nos permite demostrar propiedades interesantes acerca de la correccin y eficiencia
de los algoritmos, sino que adems puede utilizarse para determinar qu propiedades es preciso probar.
Antes de discutir la tcnica, se ha de indicar una digresin acerca de la naturaleza del descubrimiento cientfico. En la
ciencia hay dos enfoques opuestos fundamentales: induccin y deduccin. De acuerdo con el Concise Oxford
Dictionary, la induccin consiste en inferir una ley general a partir de casos particulares, mientras que una
deduccin es una inferencia de lo general a lo particular. Veremos que, aun cuando la induccin puede dar lugar a
conclusiones falsas, no se puede despreciar. La deduccin, por otra parte, siempre es vlida con tal de que sea
aplicada correctamente.
En general no se puede confiar en el resultado del razonamiento inductivo. Mientras que haya casos que no hayan
sido considerados, sigue siendo posible que la regla general inducida sea incorrecta. Por ejemplo, la experiencia de
todos los das nos puede haber convencido inductivamente que siempre es posible meter una persona ms en un
tranva. Pero unos instantes de pensamiento nos muestran que esta regla es absurda. Como ejemplo ms
matemtico considrese el polinomio
2
( ) 41 p n n n + + . Si computamos p(0), p(1), p(2),..., p(10), se va
encontrando 41, 43, 47, 53, 61, 71, 83, 97, 113, 131 y 151. Es fcil verificar que todos estos enteros son nmeros
primos. Por tanto es natural inferir por induccin que p(n) es primo para todos los valores enteros de n. Pero de hecho
p(40)=1681=41
2
es compuesto.
Un ejemplo ms sorprendente de induccin incorrecta es el dado por una conjetura de Euler, que formul en 1769.
Es posible que la suma de tres cuartas potencias sea una cuarta potencia? Formalmente, es posible encontrar
cuatro enteros positivos A, B, C y D tales que
4 4 4 4
A B C D + + ?
Al no poder encontrar ni siquiera un ejemplo de este comportamiento, Euler conjetur que esta ecuacin nunca se
podra satisfacer. (Esta conjetura est relacionada con el ltimo Teorema de Fermat.) Transcurrieron ms de dos
siglos antes de que Elkies en 1987 descubriese el primer contraejemplo, que implicaba nmeros de siete y ocho
cifras. Posteriormente ha sido demostrado por parte de Frye, utilizando cientos de horas de tiempo de computacin
en varias Connection Machines, que el nico contraejemplo en el que D es menor que un milln es
4 4 4 4
95 800 217 519 414 560 422 481 + +
(sin contar con la solucin obtenida multiplicando cada uno de estos nmeros por 2). Obsrvese que 422 481
4
es un
nmero de 23 dgitos.
La ecuacin de Pell proporciona un caso todava ms extremo de razonamiento inductivo tentador pero incorrecto.
Considrese el polinomio
2
( ) 91 1 p n n + . La pregunta es si existe un entero positivo n tal que p(n) es un cuadrado
perfecto. Si uno va probando varios valores para n, resulta cada vez ms tentador suponer inductivamente que la
respuesta es negativa. Pero de hecho se puede obtener un cuadrado perfecto con este polinomio: la solucin ms
pequea se obtiene cuando
n = 12 055 735 790 331 359 447 442 538 767
Por contraste, el razonamiento deductivo no est sometido a errores de este tipo. Siempre y cuando regla invocada
sea correcta, y sea aplicable a la situacin que se estudie, la conclusin que se alcanza es necesariamente correcta.
Matemticamente, si es cierto que alguna afirmacin P(x) es vlida para todos los x de algn conjunto X, y si de
hecho y pertenece a X, entonces se puede afirmar sin duda que P(y) es vlido. No quiere decir esto que no se pueda
inferir algo falso utilizando un razonamiento deductivo. A partir de una premisa falsa, se puede derivar
deductivamente una conclusin falsa; ste es el principio que subyace a las demostraciones indirectas. Por ejemplo,
si es correcto que P(x) es cierto para todos los x de X, pero si somos descuidados al aplicar esta regla a algn y que
no pertenezca a X, entonces podemos creer equivocadamente que P(y) es cierto. De manera similar, si nuestra
creencia en que P(x) es cierto para todos los x de X est basada en un razonamiento inductivo descuidado, entonces
P(y) puede resultar falso aun cuando de hecho y pertenezca a X. En conclusin, el razonamiento deductivo puede
producir un resultado incorrecto, pero slo si las reglas que se siguen son incorrectas o si no se siguen
correctamente.
Como ejemplo perteneciente a la ciencia de la computacin consideremos la multiplicacin la russe. Si se prueba
este algoritmo con varios pares de nmeros positivos se observar que siempre da la respuesta correcta. Por
induccin, puede uno formular la conjetura consistente en que el algoritmo siempre es correcto. En este caso, la

13
conjetura alcanzada inductivamente resulta ser cierta: mostraremos rigurosamente (mediante razonamiento
deductivo) la correccin de este algoritmo. Una vez que se ha establecido la correccin, si se utiliza el algoritmo para
multiplicar 981 por 1234 y se obtiene 1 210 554, se puede concluir que
981 x 1 234 = 1 210 554
En esta ocasin, la correccin de este caso especfico de multiplicacin de enteros es un caso especial de la
correccin del algoritmo en general. Por tanto, la conclusin de que 981 x 1 234 = 1 210 554 est basada en un
razonamiento deductivo. Sin embargo, la prueba de la correccin del algoritmo no dice nada acerca de su
comportamiento para nmeros negativos y fraccionarios y por tanto no se puede deducir nada acerca del resultado
obtenido por el algoritmo si se ejecuta con -12 y 83,7.
Es muy probable que se pregunte por qu utilizar la induccin, que es proclive a errores, en lugar de una deduccin
que es a prueba de bomba? Hay dos razones bsicas para el uso de la induccin en el proceso del descubrimiento
cientfico. Si uno es un fsico cuyo objetivo es determinar las leyes fundamentales que gobiernan el Universo, es
preciso utilizar un enfoque inductivo: las reglas que se infieren deberan de reflejar datos reales obtenidos de
experimentos. Aun cuando sea uno un fsico terico, tal como Einstein, siguen siendo necesarios experimentos reales
que otras personas hayan efectuado. Por ejemplo, Halley predijo el retorno de su cometa homnimo por
razonamiento inductivo, y tambin por razonamiento inductivo Mendeleev predijo no slo la existencia de elementos
qumicos todava no descubiertos, sino tambin sus propiedades qumicas.
Con seguridad, slo ser legtima en matemticas y en las rigurosas ciencias de la computacin la deduccin?
Despus de todo las propiedades matemticas como el hecho consistente en que existen infinitos nmeros primos y
que la multiplicacin la russe es un algoritmo correcto se pueden demostrar de una forma deductiva rigurosa, sin
datos experimentales. Los razonamientos inductivos deberan eliminarse de las matemticas, verdad? Pues no! En
realidad la matemtica puede ser tambin una ciencia experimental. No es infrecuente que un matemtico descubra
una verdad matemtica considerando varios casos especiales e infiriendo a partir de ellos por induccin una regla
general que parece plausible. Por ejemplo, si se observa que
1
3
= 1 = 1
2

1
3
+2
3
= 9 = 3
2

1
3
+2
3
+3
3
= 36 = 6
2

1
3
+2
3
+3
3
+4
3
= 100 = 10
2

1
3
+2
3
+3
3
+4
3
+5
3
= 225 = 15
2

Se puede empezar a sospechar que la suma de los cubos de los nmeros enteros positivos es siempre un cuadrado
perfecto. Resulta en este caso que el razonamiento inductivo proporciona una ley correcta. Si soy todava ms
perceptivo quiz me d cuenta de que esta suma de los cubos es precisamente el cuadrado de la suma de los
primeros nmeros enteros positivos.
Sin embargo, independientemente de lo tentadora que sea la evidencia cuantos ms valores de n se ensayan, una
regla de este tipo no se puede basar en la evidencia inductiva solamente. La diferencia entre las matemticas y las
ciencias inherentemente experimentales es que, una vez se ha descubierto por induccin una ley matemtica
general, debemos demostrarla rigurosamente aplicando el proceso deductivo. Sin embargo, la induccin tiene su
lugar en el proceso matemtico. En caso contrario, como podramos esperar comprobar rigurosamente un teorema
cuyo enunciado ni siquiera ha sido formulado? Para resumir, la induccin es necesaria para formular conjeturas, y la
deduccin es igualmente necesaria para demostrarlas, o a veces para demostrar su falsedad. Ninguna de estas
tcnicas puede ocupar el lugar de la otra. La deduccin slo es suficiente para las matemticas muertas o
congeladas, tal como en los Elementos de Euclides (que quiz sean el mayor monumento de la historia a las
matemticas deductivas, aun cuando no cabe duda de que gran parte de su material fue descubierto por
razonamiento inductivo). Pero se necesita la induccin para mantener vivas las matemticas. Tal como Plya dijo una
vez las matemticas presentadas con rigor son una ciencia deductiva sistemtica, pero las matemticas que se
estn haciendo son una ciencia inductiva experimental.
Por ltimo, la moraleja de esta digresin: una de las tcnicas deductivas ms tiles que estn disponibles en
matemticas tiene la mala fortuna de llamarse induccin matemtica. Esta terminologa resulta confusa, pero
tenemos que vivir con ella.
EL PRINCIPIO DE INDUCCIN MATEMTICA
Considrese el algoritmo siguiente:
funcin cuadrado (n)
Si n = O entonces devolver O

14
Sino devolver 2n + cuadrado (n 1) 1
Si se prueba con unas cuantas entradas pequeas, se observa que:
cuadrado(0)=0, cuadrado(1)=1, cuadrado(2)=4, cuadrado(3)=9, cuadrado(4)=16
Por induccin, parece evidente que cuadrado(n)=n
2
para todos los n0, pero cmo podra demostrarse esto
rigurosamente? Ser verdad? Diremos que el algoritmo tiene xito sobre el entero n siempre que cuadrado(n)=n
2
y
que fracasa en caso contrario.
Considrese cualquier entero n1 y supngase por el momento que el algoritmo tiene xito en n 1. Por definicin del
algoritmo cuadrado(n)=2n+cuadrado(n 1)1. Por nuestra suposicin cuadrado(n 1)=(n 1)
2
. Por tanto:
cuadrado(n)=2n+(n 1)
2
1=2n+(n
2
2n+1)1=n
2

Qu es lo que hemos conseguido? Hemos demostrado que el algoritmo debe tener xito en n siempre que tenga
xito en n 1, siempre y cuando n1. Adems est claro que tiene xito en n=0.
El principio de induccin matemtica, nos permite inferir a partir de lo anterior que el algoritmo tiene xito en todos los
n0. Hay dos formas de comprender porque se sigue esta conclusin: de forma constructiva y por contradiccin.
Considrese cualquier entero positivo m sobre el cual se desea probar que el algoritmo tiene xito. A efectos de
nuestra discusin, supongamos que m9 (los valores menores se pueden demostrar fcilmente). Ya sabemos que el
algoritmo tiene xito en 4. A partir de la regla general consistente en que debe tener xito en n siempre que tenga
xito en n1 para n1, inferimos que tambin tendr xito en 5. Aplicando una vez ms esta regla se muestra que el
algoritmo tiene xito en 6 tambin. Puesto que tiene xito en 6, tambin tiene que tener xito en 7, y as
sucesivamente. Este razonamiento contina tantas veces como sea necesario para llegar a la conclusin de que el
algoritmo tiene xito en m1. Finalmente, dado que tiene xito en m 1, tiene que tener xito en m tambin. Est
claro que podramos efectuar este razonamiento de forma explcita, sin necesidad de y as sucesivamente, para
cualquier valor positivo fijo de m.
Si preferimos una nica demostracin que funcione para todos los n0 y que no contenga y as sucesivamente,
debemos aceptar el axioma del mnimo entero, que dice que todo conjunto no vaco de enteros positivos contiene un
elemento mnimo. Este axioma nos permite utilizar este nmero mnimo como el fundamento a partir del cual
demostraremos teoremas.
Ahora, para demostrar la correccin (exactitud) del algoritmo por contradiccin, supongamos que existe al menos un
entero positivo en el cual falla el algoritmo. Sea n el menor de estos enteros, que existe por el axioma del mnimo
entero. En primer lugar, n tiene que ser mayor o igual a 5, puesto que ya hemos verificado que cuadrado(i)=i
2
cuando
i=1, 2, 3 4. En segundo lugar el algoritmo tiene que tener xito en n 1, porque en caso contrario n no sera el
menor entero positivo en el cual falla. Pero esto implica por regla general que el algoritmo tambin tiene xito en n, lo
cual contradice a nuestra suposicin acerca de la seleccin de n. Por tanto esa n no puede existir, lo cual significa
que el algoritmo tiene xito para todos los enteros positivos. Puesto que tambin sabemos que el algoritmo tiene xito
en 0, concluiremos que cuadrado(n)=n
2
para todos los enteros de n0.
Una versin sencilla del principio de induccin matemtica, que es suficiente en muchos casos. Considrese
cualquier propiedad P de los enteros. Por ejemplo, P(n) podra ser cuadrado(n)=n
2
o la suma de los cubos de los n
primeros nmeros enteros es igual al cuadrado de la suma de esos enteros, o n
3
<2
n
. Las dos primeras
propiedades son ciertas para todos los n0, mientras que la tercera es vlida siempre y cuando n10. Considrese
tambin un entero a, que se conoce con el nombre de base. Si
1. P(a) es cierto
2. P(n) debe de ser cierto siempre que P(n 1) sea vlido para todos los enteros n>a
entonces la propiedad P(n) es vlida para todos los enteros na. Usando este principio, podramos afirmar que
cuadrado(n)=n
2
para todos los n0, e inmediatamente, demostrar que cuadrado(0)=0=0
2
y que cuadrado (n)=n
2

siempre que cuadrado(n 1)
2
y n1.
Nuestro primer ejemplo de induccin matemtica mostraba cmo se puede utilizar de forma rigurosa para demostrar
la correccin de un algoritmo. Como segundo ejemplo, vamos a ver cmo las demostraciones mediante induccin
matemtica se pueden transformar a veces en algoritmos. Este ejemplo tambin es instructivo por cuanto pone de
manifiesto la forma correcta de escribir una demostracin por induccin matemtica. La discusin que sigue hace
hincapi en los puntos importantes que son comunes a este tipo de pruebas.
Considrese el siguiente problema de embaldosado. Se nos da un tablero dividido en cuadrados iguales. Hay m
cuadrados por fila y m cuadrados por columna, en donde m es una potencia de 2. Un cuadrado arbitrario del tablero
se distingue como especial; vase la Figura 6 (a).

15

Figura 6 . Problema del embaldosado.
Tambin se nos da un montn de baldosas, cada una de las cuales tiene el aspecto de un tablero 2 x 2 del cual se ha
eliminado un cuadrado, segn se ilustra en la figura 6 (b). El acertijo consiste en recubrir el tablero con estas
baldosas, para que cada cuadrado quede cubierto exactamente una vez con excepcin del cuadrado especial, que
quedar en blanco. Este recubrimiento se denomina embaldosado, figura 6 (d) da una solucin del caso dado en la
figura 6 (a).
TEOREMA: EL PROBLEMA DE TESELADO SIEMPRE SE PUEDE RESOLVER
Demostracin. La demostracin se hace por induccin matemtica sobre el entero n tal que m = 2
n
.
Base: el caso n=0 se satisface de forma trivial. Aqu m=1, y el tablero 1x1 es un nico cuadrado, que es
necesariamente especial. Este tablero se recubre sin hacer nada! (Si no le gusta este argumento,
compruebe el caso siguiente por orden de sencillez: si n = 1, entonces m = 2 y todo tablero 2 x 2 del cual se
elimine un cuadrado tiene exactamente el aspecto de una baldosa, por definicin.)
Paso de induccin: considrese cualquier n1. Sea m=2
n
. Asmase la hiptesis de induccin, consistente en
que el teorema es vlido para tableros 2
n-1
x2
n-1
. Considrese un tablero mxm, que contiene un cuadrado
especial colocado arbitrariamente. Divdase el tablero por cuatro subtableros iguales partindolo en dos
horizontal y verticalmente. El cuadrado especial original pertenece ahora exactamente a uno de los
subtableros. Colquese una baldosa en medio del tablero original de tal manera que cubra exactamente un
cuadrado de cada uno de los dems subtableros; vase la figura 6 (c). Consideremos cada uno de los tres
cuadrados as cubiertos especial para el subtablero correspondiente. Nos quedan cuatro subtableros del
tipo 2
n-1
x2
n-1
, cada uno de los cuales contiene un cuadrado especial. Por nuestra hiptesis de induccin, cada
uno de estos cuatro subtableros se puede recubrir. La solucin final se obtiene combinando los
recubrimientos de los subtableros junto con la baldosa colocada en la posicin media del tablero original.
Puesto que el teorema es verdadero cuando m=2
0
, y dado que su validez para m=2
n
se sigue de la suposicin de su
validez para m=2
n-1
para todos los n1, se sigue del principio de induccin matemtica que el teorema es verdadero
para todo m, siempre y cuando m sea una potencia de 2.
No es difcil para transformar esta prueba de un teorema matemtico a un algoritmo para efectuar el
embaldosamiento real (quiz no sea un algoritmo de computadora, pero es al menos un algoritmo adecuado para el
procesamiento a mano). Este algoritmo de teselado sigue el esquema general conocido con el nombre de divide y
vencers. Esta situacin no es infrecuente cuando se demuestra constructivamente un teorema por induccin
matemtica.
Examinemos ahora con detalle todos los aspectos de una demostracin formalmente correcta por induccin
matemtica, tal como la anterior. Considrese una vez ms una propiedad abstracta P de los enteros, un entero a, y
supngase que se desea demostrar que P(n) es vlido para todos los na. Es preciso comenzar la demostracin

16
mediante el caso base, que consiste en demostrar que P(a) es vlido. Este caso base suele ser sencillo, y algunas
veces incluso trivial, pero resulta crucial efectuarlo correctamente; en caso contrario, toda la demostracin carece
literalmente de fundamento.
El caso base va seguido por el paso de induccin, que suele ser ms complicado. ste debera empezar por
considrese cualquier n>a (o de manera equivalente consideremos cualquier n>a+1). Debera continuar con una
indicacin explcita de la hiptesis de induccin, que establece esencialmente que P(n1) es vlido. En este momento
queda por demostrar que es posible inferir que P(n) es vlido suponiendo vlida la hiptesis de induccin.
Con respecto a la hiptesis de induccin, es importante comprender que suponemos que P(n1) es vlido de forma
provisional; no sabemos realmente que sea vlido mientras no se haya demostrado el teorema. En otras palabras, el
objetivo del paso de induccin es demostrar que la veracidad de P(n) se deduce lgicamente de la veracidad de P(n-
1), independientemente de si P(n1) es vlido. De hecho si P(n-1) no es vlido, el paso de induccin no nos permite
llegar a ninguna conclusin acerca de la veracidad de P(n).
Por ejemplo, considrese la afirmacin n
3
<2
n
, que representaremos mediante P(n). Para un positivo entero n,
resulta sencillo demostrar que n
3
<2(n1)
3
precisamente si n5. Considrese cualquier n5 y asumamos
provisionalmente que P(n 1) es verdadero. Ahora
n
3
<2(n-1)
3
porque n5
< 2x2
n-1
por la suposicin consistente en que P(n 1) es vlido
=2
n
.
De esta manera se ve que P(n) se sigue lgicamente de P(n1) siempre que n5. Sin embargo, P(4) no es vlido (se
dira que 4
3
<2
4
, lo cual sera decir que 64<16) y por tanto no se puede inferir nada con respecto a la veracidad de
P(5). Por prueba y error averiguaremos sin embargo que P(10) s es vlido (10
3
=1000<2
10
=1024). Por consiguiente es
legtimo inferir que P(11) tambin es vlido, y de la veracidad de P(11) se sigue que P(12) tambin es vlido, y as
sucesivamente. Por el principio de induccin matemtica, dado que P(10) es vlido y dado que P(n) se sigue de P(n
1) siempre que n5, concluimos que n
3
<2
n
es verdadero para todo n10. Resulta instructivo observar que P(n)
tambin es vlido para n=0 y n = 1, pero no podemos utilizar estos puntos como base de la induccin matemtica
porque el paso de induccin no es aplicable para valores tan pequeos de n.
Quiz suceda que la propiedad que deseamos demostrar no afecta a todos los enteros mayores o iguales a uno
dado. Nuestro acertijo del embaldosado concierne solamente al conjunto de enteros que son potencias de 2. En
algunas ocasiones, la propiedad no concierne en absoluto a los nmeros enteros. Por ejemplo, no es infrecuente en
Algoritmia tener la necesidad de demostrar una propiedad de los grafos. (Se podra decir, incluso, que nuestro
problema del embaldosado no concierne realmente a los nmeros enteros, sino ms bien a los tableros y a las
baldosas, pero esto sera partir un pelo en el aire.) En tales casos, si se ha de utilizar la induccin matemtica
sencilla, la propiedad que hay que demostrar deber transformarse primeramente en una propiedad del conjunto de
todos los nmeros enteros que no sean menores que un cierto caso base. En nuestro ejemplo de embaldosado,
demostrbamos que P(n) es vlido para todas las potencias de 2, demostrando que Q(n) es vlido para n0, en
donde Q(n) es equivalente a P(2
n
). Cuando esta transformacin es necesaria, se acostumbra comenzar la
demostracin (tal como hicimos) con las palabras la demostracin es por induccin matemtica sobre tal y cual
parmetro. De esta manera se realizan demostraciones acerca del nmero de nodos de un grafo, acerca de la
longitud de una cadena de caracteres, acerca de la profundidad de un rbol, y as sucesivamente.
Existe un aspecto de las demostraciones por induccin matemtica que la mayora de los principiantes encuentra
sorprendente, si es que no les parece paradjico: a veces es ms sencillo demostrar una afirmacin ms fuerte que
una ms dbil! Ilustraremos esto con un ejemplo que ya hemos encontrado. Vimos que resulta sencillo conjeturar por
induccin (no por induccin matemtica) que la suma de los cubos de los n primeros nmeros enteros siempre es un
cuadrado perfecto. Demostrar esto por induccin matemtica no es sencillo. La dificultad estriba en que una hiptesis
de induccin tal como la suma de los cubos de los n1 primeros nmeros enteros es un cuadrado perfecto no sirve
de gran ayuda para demostrar que esto tambin sucede para los n primeros nmeros enteros porque no dice cul de
los cuadrados es perfecto: en general, no hay ninguna razn para creer que se obtenga un cuadrado cuando n
3
se
suma con otro cuadrado. Por contraste, resulta ms sencillo demostrar el teorema ms fuerte consistente en que
nuestra suma de cubos es precisamente el cuadrado de la suma de los n primeros enteros: la hiptesis de induccin
es ahora mucho ms significativa.

17
TEOREMA. LA MULTIPLICACIN LA RUSSE MULTIPLICA CORRECTAMENTE CUALQUIER
PAREJA DE NMEROS ENTEROS POSITIVOS

DEMOSTRACIN.
Supongamos que se desea multiplicar m por n. La demostracin se hace por induccin matemtica sobre el valor de
m.
v Base: el caso m = 1 es fcil; slo se tiene una fila que consta de un 1 en la columna de la izquierda y de n en la
columna de la derecha. Esta fila no se tacha porque 1 no es par. Cuando se suma el nico nmero que
queda en la columna de la derecha, obtenemos evidentemente n, que es el resultado correcto de multiplicar 1
por n.
v Paso de induccin: considrese cualquier m2 y cualquier entero positivo n. Supongamos como hiptesis de
induccin que la multiplicacin la russe multiplica correctamente s por t para cualquier entero positivo s menor
que m y para cualquier entero positivo t. (Obsrvese que no se exige que t sea menor que n.) Hay que considerar
dos casos:
Si m es par, la segunda fila de la tabla obtenida cuando se multiplica m por n contiene m/2 en la columna de
la izquierda, y 2n en la columna de la derecha. Esto es idntico a la primera fila que se obtiene cuando se
multiplica m / 2 por 2n. Dado que toda fila no inicial de estas tablas depende solamente de la fila anterior, la
tabla obtenida cuando se multiplica m por n es por tanto idntica a la que se obtiene al multiplicar m / 2 por
2n, salvo por la primera fila adicional, que contiene m en la columna de la izquierda y n en la columna de la
derecha. Como m es par, esta fila adicional se tachar antes de la suma final. Por tanto, el resultado final
obtenido cuando se multiplica m por n la russe es el mismo que cuando se multiplica m / 2 por 2n. Pero m/2
es positivo y menor que m. Por tanto, la hiptesis de induccin es aplicable: el resultado obtenido cuando se
multiplica m/2 por 2n la russe es (m/2) x (2n), tal como debe de ser. Consiguientemente, el resultado que se
obtiene cuando se multiplica m por n la russe es igual a (m/2) x (2n) = mn, que es lo que tiene que ser.
El caso en que m es impar resulta similar, salvo que hay que sustituir m/2 por (m-1)/2 en todas partes, y que
no se borra la primera fila cuando se multiplica m por n. Por tanto, el resultado final al multiplicar m por n la
russe es igual a n ms el resultado de multiplicar (m 1)/2 por 2n la russe. Por hiptesis de induccin, esto
ltimo se calcula correctamente en la forma ((m 1)/2) x 2n, y por tanto lo anterior se calcula en la forma n +
((m 1)/2) x 2n, que es mn tal como deba de ser.
Esto completa la demostracin del paso por induccin y por tanto del teorema.
EFICIENCIA Y COMPLEJIDAD
Una vez dispongamos de un algoritmo que funciona
correctamente, es necesario definir criterios para
medir su rendimiento o comportamiento. Estos
criterios se centran principalmente en su simplicidad
y en el uso eficiente de los recursos.
A menudo se piensa que un algoritmo sencillo no
es muy eficiente. Sin embargo, la sencillez es una
caracterstica muy interesante a la hora de disear un
algoritmo, pues facilita su verificacin, el estudio de su
eficiencia y su mantenimiento. De ah que muchas
veces prime la simplicidad y legibilidad del cdigo
frente a alternativas ms crpticas y eficientes del
algoritmo.
Respecto al uso eficiente de los recursos, ste suele
medirse en funcin de dos parmetros: el espacio, es
decir, memoria que utiliza, y el tiempo, lo que tarda en
ejecutarse. Ambos representan los costes que supone
encontrar la solucin al problema planteado mediante
un algoritmo. Dichos parmetros van a servir adems
para comparar algoritmos entre s, permitiendo

18
determinar el ms adecuado de entre varios que
solucionan un mismo problema.
El tiempo de ejecucin de un algoritmo va a
depender de diversos factores como son: los datos
de entrada que le suministremos, la calidad del
cdigo generado por el compilador para crear el
programa objeto, la naturaleza y rapidez de las
instrucciones mquina del procesador concreto que
ejecute el programa, y la complejidad intrnseca del
algoritmo. Hay dos estudios posibles sobre el tiempo:
1. Uno que proporciona una medida terica (a
priori), que consiste en obtener una funcin
que acote (por arriba o por abajo) el tiempo de
ejecucin del algoritmo para unos valores de
entrada dados.
2. Y otro que ofrece una medida real (a
posteriori), consistente en medir el tiempo de
ejecucin del algoritmo para unos valores de
entrada dados y en un ordenador concreto.
Ambas medidas son importantes puesto que, si bien la
primera nos ofrece estimaciones del comportamiento
de los algoritmos de forma independiente del
ordenador en donde sern implementados y sin
necesidad de ejecutarlos, la segunda representa las
medidas reales del comportamiento del algoritmo.
Estas medidas son funciones temporales de los
datos de entrada.
Entendemos por tamao de la entrada el nmero de
componentes sobre los que se va a ejecutar el
algoritmo. Por ejemplo, la dimensin del vector a
ordenar o el tamao de las matrices a multiplicar.
La unidad de tiempo a la que deben hacer referencia
estas medidas de eficiencia no puede ser expresada
en segundos o en otra unidad de tiempo concreta,
pues no existe un ordenador estndar al que puedan
hacer referencia todas las medidas.
Denotaremos por T(n) el tiempo de ejecucin de un
algoritmo para una entrada de tamao n.
Tericamente T(n) debe indicar el nmero de
instrucciones ejecutadas por un ordenador idealizado.
Debemos buscar por tanto medidas simples y
abstractas, independientes del ordenador a utilizar.
Para ello es necesario acotar de alguna forma la
diferencia que se puede producir entre distintas
implementaciones de un mismo algoritmo, ya sea del
mismo cdigo ejecutado por dos mquinas de distinta
velocidad, como de dos cdigos que implementen el
mismo mtodo. Esta diferencia es la que acota el
siguiente principio:
PRINCIPIO DE INVARIANZA
Dado un algoritmo y dos implementaciones suyas I1 e
I2, que tardan T1(n) y T2(n) segundos
respectivamente, el Principio de Invarianza afirma que
existe una constante real c > 0 y un nmero natural n0
tales que para todo n n0 se verifica que T1(n)
cT2(n).
Es decir, el tiempo de ejecucin de dos
implementaciones distintas de un algoritmo dado
no va a diferir ms que en una constante
multiplicativa.
Con esto podemos definir sin problemas que un
algoritmo tarda un tiempo del orden de T(n) si existen
una constante real c > 0 y una implementacin I del
algoritmo que tarda menos que cT(n), para todo n
tamao de la entrada.
Dos factores a tener muy en cuenta son la constante
multiplicativa y el n0 para los que se verifican las
condiciones, pues si bien a priori un algoritmo de
orden cuadrtico es mejor que uno de orden cbico,
en el caso de tener dos algoritmos cuyos tiempos de
ejecucin son
2 6
10 n y
3
5n el primero slo ser mejor
que el segundo para tamaos de la entrada superiores
a 200.000.
Tambin es importante hacer notar que el
comportamiento de un algoritmo puede cambiar
notablemente para diferentes entradas (por ejemplo, lo
ordenados que se encuentren ya los datos a ordenar).
De hecho, para muchos programas el tiempo de
ejecucin es en realidad una funcin de la entrada
especfica, y no slo del tamao de sta. As suelen
estudiarse tres casos para un mismo algoritmo: caso
peor, caso mejor y caso medio.
El caso mejor corresponde a la traza (secuencia de
sentencias) del algoritmo que realiza menos
instrucciones. Anlogamente, el caso peor
corresponde a la traza del algoritmo que realiza ms
instrucciones. Respecto al caso medio, corresponde
a la traza del algoritmo que realiza un nmero de
instrucciones igual a la esperanza matemtica de la
variable aleatoria definida por todas las posibles trazas
del algoritmo para un tamao de la entrada dado, con
las probabilidades de que stas ocurran para esa
entrada.
Es muy importante destacar que esos casos
corresponden a un tamao de la entrada dado,
puesto que es un error comn confundir el caso
mejor con el que menos instrucciones realiza en
cualquier caso, y por lo tanto contabilizar las
instrucciones que hace para n = 1.
A la hora de medir el tiempo, siempre lo haremos en
funcin del nmero de operaciones elementales que
realiza dicho algoritmo, entendiendo por operaciones
elementales (en adelante OE) aquellas que el
ordenador realiza en tiempo acotado por una
constante. As, consideraremos OE las operaciones
aritmticas bsicas, asignaciones a variables de tipo
predefinido por el compilador, los saltos (llamadas a
funciones y procedimientos, retorno desde ellos, etc.),
las comparaciones lgicas y el acceso a estructuras
indexadas bsicas, como son los vectores y matrices.
Cada una de ellas contabilizar como 1 OE.
Resumiendo, el tiempo de ejecucin de un algoritmo
va a ser una funcin que mide el nmero de

19
operaciones elementales que realiza el algoritmo para
un tamao de entrada dado.
En general, es posible realizar el estudio de la
complejidad de un algoritmo slo en base a un
conjunto reducido de sentencias, aquellas que
caracterizan que el algoritmo sea lento o rpido en el
sentido que nos interesa. Tambin es posible distinguir
entre los tiempos de ejecucin de las diferentes
operaciones elementales, lo cual es necesario a veces
por las caractersticas especficas del ordenador (por
ejemplo, se podra considerar que las operaciones + y
presentan complejidades diferentes debido a su
implementacin). Sin embargo, a menos que se
indique lo contrario, todas las operaciones elementales
del lenguaje, y supondremos que sus tiempos de
ejecucin son todos iguales.
Para hacer un estudio del tiempo de ejecucin de un
algoritmo para los tres casos citados comenzaremos
con un ejemplo concreto. Supongamos entonces que
disponemos de la definicin de los siguientes tipos y
constantes:
// nmero mximo de elementos de un vector
#define N .
// Tamao del vector, no se usa la posicin cero
#define NV N+1
y de un algoritmo cuya implementacin en C++ es:
int buscar(int vector[NV],int c){
int j;

/* 1 */ j=1;
/* 2 */ while (vector[j]<c) && (j<N){
/* 3 */ j=j+1;
/* 4 */ }
/* 5 */ if (vector[j]==c)
/* 6 */ return j;
/* 7 */ else return 0;
}
Para determinar el tiempo de ejecucin, calcularemos
primero el nmero de operaciones elementales (OE)
que se realizan:
En la lnea (1) se ejecuta 1 OE (una
asignacin).
En la lnea (2) se efecta la condicin del
bucle, con un total de 4 OE (dos
comparaciones, un acceso al vector, y un
AND).
La lnea (3) est compuesta por un incremento
y una asignacin (2 OE).
La lnea (5) est formada por una condicin y
un acceso al vector (2 OE).
La lnea (6) contiene un RETURN (1 OE) si la
condicin se cumple.
La lnea (7) contiene un RETURN (1 OE),
cuando la condicin del IF anterior es falsa.
Obsrvese cmo no se contabiliza la copia del vector
a la pila de ejecucin del programa, pues se pasa por
referencia y no por valor. En caso de pasarlo por valor,
necesitaramos tener en cuenta el coste que esto
supone (un incremento de n OE). Con esto:
En el caso mejor para el algoritmo, se
efectuar la lnea (1) y de la lnea (2) slo la
primera mitad de la condicin, que supone 2
OE (suponemos que las expresiones se
evalan de izquierda a derecha, y con
cortocircuito, es decir, una expresin lgica
deja de ser evaluada en el momento que se
conoce su valor, aunque no hayan sido
evaluados todos sus trminos). Tras ellas la
funcin acaba ejecutando las lneas (5) a (7).
En consecuencia, T(n)=1+2+3=6.
En el caso peor, se efecta la lnea (1), el
bucle se repite n1 veces hasta que se
cumple la segunda condicin, despus se
efecta la condicin de la lnea (5) y la funcin
acaba al ejecutarse la lnea (7). Cada
iteracin del bucle est compuesta por las
lneas (2) y (3), junto con una ejecucin
adicional de la lnea (2) que es la que
ocasiona la salida del bucle. Por tanto

En el caso medio, el bucle se ejecutar un
nmero de veces entre 0 y n1, y vamos a
suponer que cada una de ellas tiene la misma
probabilidad de suceder. Como existen n
posibilidades (puede que el nmero buscado
no est) suponemos a priori que son
equiprobables y por tanto cada una tendr una
probabilidad asociada de 1/n. Con esto, el
nmero medio de veces que se efectuar el
bucle es de

Tenemos pues que

Es importante observar que no es necesario conocer
el propsito del algoritmo para analizar su tiempo de
ejecucin y determinar sus casos mejor, peor y medio,
sino que basta con estudiar su cdigo. Suele ser un
error muy frecuente el determinar tales casos
basndose slo en la funcionalidad para la que el
algoritmo fue concebido, olvidando que es el cdigo
implementado el que los determina.
En este caso, un examen ms detallado de la funcin
(y no de su nombre!) nos muestra que tras su

20
ejecucin, la funcin devuelve la posicin de un entero
dado c dentro de un vector ordenado de enteros,
devolviendo 0 si el elemento no est en el vector. Lo
que acabamos de probar es que su caso mejor se da
cuando el elemento est en la primera posicin del
vector. El caso peor se produce cuando el elemento
no est en el vector, y el caso medio ocurre cuando
consideramos equiprobables cada una de las
posiciones en las que puede encontrarse el elemento
dentro del vector (incluyendo la posicin especial 0,
que indica que el elemento a buscar no se encuentra
en el vector).
REGLAS GENERALES PARA EL CLCULO
DEL NMERO DE OE
La siguiente lista presenta un conjunto de reglas
generales para el clculo del nmero de OE, siempre
considerando el peor caso. Estas reglas definen el
nmero de OE de cada estructura bsica del lenguaje,
por lo que el nmero de OE de un algoritmo puede
hacerse por induccin sobre ellas.
Vamos a considerar que el tiempo de una OE
es, por definicin, de orden 1. La constante c
que menciona el Principio de Invarianza
depender de la implementacin particular,
pero nosotros supondremos que vale 1.
El tiempo de ejecucin de una secuencia
consecutiva de instrucciones se calcula
sumando los tiempos de ejecucin de cada
una de las instrucciones.
El tiempo de ejecucin de la sentencia
switch(C){ case v1:S1| case v2:S2 | ... | case
vn:Sn}; es T = T(C) +
max{T(S1),T(S2),...,T(Sn)}. Obsrvese que
T(C) incluye el tiempo de comparacin con v1,
v2 ,..., vn.
El tiempo de ejecucin de la sentencia if(C)
S1 else S2; es T = T(C) + max{T(S1),T(S2)}.
El tiempo de ejecucin de un bucle de
sentencias WHILE(C) S; es T = T(C) + (n
iteraciones)*(T(S) + T(C)). Obsrvese que
tanto T(C) como T(S) pueden variar en cada
iteracin, y por tanto habr que tenerlo en
cuenta para su clculo.
Para calcular el tiempo de ejecucin del resto
de sentencias iterativas (for, do while) basta
expresarlas como un bucle WHILE. A modo de
ejemplo, el tiempo de ejecucin del bucle:
For(i=1;i<=n;i++)
S;
puede ser calculado a partir del bucle equivalente:
i:=1;
WHILE(i<=n){
S;
I=i+1;
}
El tiempo de ejecucin de una llamada a un
procedimiento o funcin F(P1, P2,..., Pn) es 1
(por la llamada), ms el tiempo de evaluacin
de los parmetros P1, P2,..., Pn, ms el
tiempo que tarde en ejecutarse F, esto es, T =
1 + T(P1) + T(P2) + ... + T(Pn) + T(F). No
contabilizamos la copia de los argumentos a la
pila de ejecucin, salvo que se trate de
estructuras complejas (registros o vectores)
que se pasan por valor. En este caso
contabilizaremos tantas OE como valores
simples contenga la estructura. El paso de
parmetros por referencia, por tratarse
simplemente de punteros, no contabiliza
tampoco.
El tiempo de ejecucin de las llamadas a
procedimientos recursivos va a dar lugar a
ecuaciones en recurrencia.
Tambin es necesario tener en cuenta, cuando el
compilador las incorpore, las optimizaciones del
cdigo y la forma de evaluacin de las expresiones,
que pueden ocasionar cortocircuitos o realizarse de
forma perezosa (lazy). En el presente trabajo
supondremos que no se realizan optimizaciones, que
existe el cortocircuito y que no existe evaluacin
perezosa.
ESPERANZA Y SUMATORIAS
ESPERANZA O VALOR ESPERDADO
Para una variable aleatoria discreta con valores posibles
n
x x x , , ,
2 1
K y sus proabilidades representadas por
) (
i
x p la esperanza se calcula con:
[ ]

n
k
k k
x p x X E
1
) (
PROPIEDADES DE LAS SUMATORIAS
Propiedad #1: te cons una es c cn c
n
k
tan
1



21
Propiedad #2: ( )


t t
n
k
k
n
k
k
n
k
k k
y x y x
1 1 1

Propiedad #3:

n
k
k
n
k
k
te cons una es c x c cx
1 1
tan
Propiedad #4:

+
+

+
n
k
k k n
n
k
k
a a a
0
1
0

Propiedad #5:

+
+

+
n
k
k k n
n
k
k
a a a
0
1
0

Propiedad #6:

+
+
n
j k
k
j
k
k
n
k
k
a a a
1 1 1

Propiedad #7: j m j m
m
j k

, 1
Propiedad #8:
( )
2
1
1
+

n n
k
n
k

Propiedad #9:
( )( )
2
1 + +

p q p q
k
q
p k

Propiedad #10: ( ) 1 2
1
+

n n k
n
k

Propiedad #11: ( )
2
1
1 2 n k
n
k


Propiedad #12: ( ) ( ) 1 2 1 4
1
+

n n k
n
k

Propiedad #13: ( ) 1 2 4
1
+

n n k
n
k

Propiedad #14:
( ) ( )
6
1 2 1
1
2
+ +

n n n
k
n
k

Propiedad #15: ( ) ( )( ) 2 1
3
1
1
1
+ + +

n n n k k
n
k

Propiedad #16:
( ) 1 1
1
1
+

n
n
k k
n
k

Propiedad #17:
( )
2
1
3
2
1
1
]
1

n n
k
n
k

Propiedad #18:
( ) ( ) ( )
30
1 3 3 1 2 1
2
1
4
+ + +

n n n n n
k
n
k

Propiedad #19:



t
k t k
k k
1
1

Propiedad #20: 0


t
t k
k
Propiedad #21:


h k k
t
k
h
t k
h
1
2
1
2


22
Propiedad #22:


h k k
t
k
h
t
t k
h
1
2 2
2
Propiedad #23:


+
h k
t
t k
h
0
1 2

Propiedad #24: ( )
0
1
1
a a a a
n
n
k
k k


Propiedad #25: ( ) n m a a a a
n m
m
n k
k k

1 1

Propiedad #26: ( ) n m a a a a
j n j m
m
n k
j k j k

1 1

COTAS DE COMPLEJIDAD. MEDIDAS ASINTTICAS
T(n) para clasificar, vamos a definir clases de equivalencia, correspondientes a las funciones que crecen de la misma
forma.
COTA SUPERIOR. NOTACIN O (OMICRON)
Dada una funcin f, las funciones g que a lo sumo crecen tan deprisa como f. Al conjunto de tales funciones se le
llama cota superior de f y lo denominamos O(f). Conociendo la cota superior de un algoritmo podemos asegurar que,
en ningn caso, el tiempo empleado ser de un orden superior al de la cota.
Definicin
Sea f: N[0,):
[ ) { }
0 0
( ) : 0, | , 0, ( ) ( ) O f g c c n g n cf n n n >

Diremos que una funcin
[ )
: 0, t
es de orden O de f si
( ) t O f

Propiedades de O


23
COTA INFERIOR. NOTACIN
Dada una funcin f, queremos estudiar aquellas funciones g que a lo sumo crecen tan lentamente como f. Al conjunto
de tales funciones se le llama cota inferior de f y lo denominamos (f). Conociendo la cota inferior de un algoritmo
podemos asegurar que, en ningn caso, el tiempo empleado ser de un orden inferior al de la cota.
Definicin
Sea f: N[0,):
[ ) { }
0 0
( ) : 0, | , 0, ( ) ( ) f g c c n g n cf n n n >

Diremos que una funcin
[ )
: 0, t
es de orden de f si
( ) t f

Propiedades de


ORDEN EXACTO. NOTACIN
Definicin
Sea f: N[0,):
( ) ( ) ( ) f O F f

[ ) { }
0 0
( ) : 0, | , , , 0, ( ) ( ) ( ) f g c d c d n cf n g n df n n n >

Diremos que una funcin
[ )
: 0, t
es de orden de f si
( ) t f


24
Propiedades de



EJERCICIOS
Se tiene las siguientes definiciones:
#define N 10
typedef int VECTOR[N+1];
VECTOR vector={0,10,2,6,4,5,6,7,8,9,1};
Determine T(n) para el mejor caso, peor caso y el caso medio para las siguientes funciones
int busquedaLineal(VECTOR vector,int c){
int i;

i=1;
while(vector[i]!=c && i<N) i++;
if (vector[i]==c)
return i;
else
return 0;
}

void burbujaDerIzq(VECTOR vector,int n){
int i,j;

for (i=2;i<=n;i++)
for (j=n;j>=i;j--)
if (vector[j-1]>vector[j])
cambio(vector[j-1],vector[j]);
}

void cambio(int &n1,int &n2){
int t;


25
t=n1;
n1=n2;
n2=t;
}
ALGORITMOS RECURSIVOS
La recursin es un concepto fundamental en Matemticas y en la Ciencia de la Computacin, en este ltimo caso
particularmente cuando se manipulan ciertas estructuras de datos de naturaleza recursiva o como paso previo para
obtener una solucin no recursiva.
Un objeto es llamado recursivo si parcialmente consiste o est definido en trminos de s mismo.
Las caractersticas esenciales en la recursin son:
La condicin de terminacin. Sin ella, se tendra un nmero infinito de llamadas recursivas.
Un parmetro que vara en cada llamada recursiva y que despus de un nmero finito de valores debe
conducir a la condicin de terminacin.
PROGRAMA EJEMPLO UNO
Factorial de un nmero natural
El factorial de un nmero natural n es otro nmero natural denotado por n! y definido como sigue:
(a) n! = n (n-1)! , si n > 0
(b) 0! = 1
De acuerdo con est definicin, el clculo de factorial de 4! es como sigue:
4! = 4 * (3!) por (b)
4! = 4 * 3* (2!) por (b)
4! = 4 * 3* 2 * (1!) por (b)
4! = 4 * 3* 2 * 1* (0!) por (b)
4! = 4 * 3* 2 * 1* 1 por (b)
4! = 120
La implementacin de una funcin factorial recursiva en el Lenguaje C++ es:
public class factorial{
public static long factorial(long n){
if (n==0) return(1);
else return(n*factorial(n-1));
}

public static void main(String []args){
long n;

System.out.print("Ingrese un nmero: ");
n=Leer.datoLong();
System.out.println("El factorial es "+factorial(n));
}
}
PROGRAMA EJEMPLO DOS
Multiplicacin de nmeros naturales.
El producto a*b en donde a y b son enteros positivos, se define como a sumado a s mismo b veces (definicin
iterativa).
Una definicin recursiva equivalente es:
a*b = a, si b=1
a*b = a * (b-1) + a si b>1

26
De acuerdo con esta definicin, si se desea evaluar 6 * 2 se tiene:
6 *3 = 6 * 2 + 6
= 6 * 1+ 6 + 6
= 6 + 6 + 6
public class producto{
public static int producto(int a, int b){
if (b==1) return(a);
else return(producto(a,b-1)+a);
}

public static void main(String []args){
int a,b,p;

System.out.print("Ingrese 1er nmero: ");
a=Leer.datoInt();
System.out.print("Ingrese 2do nmero: ");
b=Leer.datoInt();
p=producto(a,b);
System.out.println("El producto es "+p);
}
}
PROGRAMA EJEMPLO TRES
Suma de los n nmeros consecutivos: 1+2+3++n
public class sumaConsecutivos{
public static int suma(int n){
if (n==0) return(0);
else return(suma(n-1)+n);
}

public static void main(String []args){
int n,s;

System.out.print("Ingrese un nmero: ");
n=Leer.datoInt();
s=suma(n);
System.out.println("La suma es "+s);
}
}
PROGRAMA EJEMPLO CUATRO
Programa que cambia del sistema de numeracin de base 10 a otra base utilizando una funcin recursiva.
#include <iostream>
#include <cstdlib>
using namespace std;
void base(int n,int b);
void invertir(int n);

int main(int argc,char *argv[]){
int n,b;
long nb;

cout<<"Numero en base 10 : ";
cin>>n;
cout<<"Base (0..9): ";
cin>>b;
invertir(n);

27
cout<<endl;
cout<<"En la nueba base es: ";

base(n,b);
cout<<endl;
system("PAUSE");
}

void base(int n,int b){
int resto;

if (n==0) return;
else{
resto=n%b;
n=n/b;
base(n,b);
}
cout<<resto;
}

void invertir(int n){
int resto;

if (n==0) return;
else{
resto=n%10;
n=n/10;
cout<<resto;
invertir(n);
}
}
PROGRAMA EJEMPLO CINCO
Determina el mayor valor alamacenado en un arreglo unidimensional.
#include <cstdlib>
#include <iostream>

using namespace std;

int suma(int a[],int n);
int mayor(int a[],int n,int i);
void imprimirNor(int a[],int n);
void imprimirInv(int a[],int n);
void imprimir(int a[],int n,int i);
void imprimirI(int a[],int n,int i);

int main(int argc, char *argv[]){
int n=4;
int v[]={3,4,5,2};

imprimirNor(v,n);
cout<<endl;
imprimir(v,n,0);
cout<<endl;
imprimirInv(v,n);
cout<<endl;
imprimirI(v,n,0);
cout<<endl;
cout<<"suma = "<<suma(v,n)<<endl;
cout<<"mayor= "<<mayor(v,n,0)<<endl;

28
system("PAUSE");
return EXIT_SUCCESS;
}
int suma(int a[],int n){
if(n==1)
return a[0];
else return a[n-1]+suma(a,n-1);
}

int mayor(int a[],int n, int i){
if (i==n-1) return a[n-1];
else
if (a[i]>mayor(a,n,i+1)) return a[i];
else return mayor(a,n,i+1);
}

void imprimirInv(int a[],int n){
if(n==0) return;
else{
cout<<a[n-1]<<"\t";
imprimirInv(a,n-1);
}
}

void imprimirNor(int a[],int n){
if(n==0) return;
else{
imprimirNor(a,n-1);
cout<<a[n-1]<<"\t";
}
}

void imprimir(int a[],int n,int i){
if(i==n) return;
else{
cout<<a[i]<<"\t";
imprimir(a,n,i+1);
}
}void imprimirI(int a[],int n,int i){
if(i==n) return;
else{
imprimirI(a,n,i+1);
cout<<a[i]<<"\t";
}
}
PROGRAMA EJEMPLO SEIS
Realiza la bsqueda binaria en un arreglo unidimensional. La bsqueda binaria funciona en arreglos donde sus
elmentos estan ordenados.
public class busquedaBinaria extends ArregloUniOrd{

busquedaBinaria(){
super();
}

public int BinariaBusqueda(int busca,int ini,int fin){
int mid;

if(ini>fin) return -1;
else{

29
mid=(ini+fin)/2;
if(a[mid]==busca)
return mid;
else
if(busca>a[mid])
return BinariaBusqueda(busca,mid+1,fin);
else
return BinariaBusqueda(busca,ini,mid-1);
}
}

public static void main(String []args){
busquedaBinaria b;
int busca,pos;
char resp;

b=new busquedaBinaria();
b.ingreso();
b.reporte();
do{
System.out.print("Elemento a buscar: ");
busca=Leer.datoInt();
pos=b.BinariaBusqueda(busca,1,b.cantidad());

if(pos>-1) System.out.println("Se encuentra en el ndice "+pos);
else System.out.println("No se encunetra ese valor");
System.out.print("Continuar (S/N)?");
resp=Leer.datoChar();
}while(resp !='n' && resp !='N');
}
}
PROGRAMA EJEMPLO SIETE
Calcula el mximo comn divisor
Solucin
#include <cstdlib>
#include <iostream>

using namespace std;
int mcd(int a,int b);

int main(int argc, char *argv[])
{
int n1,n2;
cout<<"Ingrese los numeros: ";
cin>>n1>>n2;
cout<<"MCD="<<mcd(n1,n2)<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}

int mcd(int n1,int n2){
if (n2==0) return n1;
else return mcd(n2,n1%n2);
}

30
PROGRAMA EJEMPLO OCHO
Problema de las Torres de Hanoi
El problema de las "Torres de Hanoi", cuya posicin inicial se muestra en la Ilustracin 1. Existen tres estacas, A, B y
C. Se colocan cinco discos de diferentes dimetros en la estaca 1, de manera que un disco ms grande siempre est
abajo de un disco ms pequeo. El objetivo es pasar los cinco discos a la estaca C usando la B como auxiliar. Slo
puede moverse el disco superior de cualquier estaca a otra y un disco ms grande nunca puede colocarse sobre uno
ms pequeo. Para solucionar este problema, en lugar de concentrar nuestra atencin en una solucin para cinco
discos, consideremos el caso general de n discos. Suponga que tenamos una solucin para n-1 discos y podamos
plantear una solucin para n discos en trminos de la solucin para n-1 discos. En este caso se resolvera el
problema. Esto es cierto debido a que en el caso trivial de un disco (restando una y otra vez 1 de n terminar por
producir 1), la solucin es simple: nicamente mover un solo disco de la estaca A a la C. As, habremos desarrollado
una solucin recursiva si planteamos una solucin para n discos en trminos de n-1. Vea si puede encontrar esta
relacin. En particular, para el caso de cinco discos, suponga que sabernos cmo mover los cuatro discos superiores
de la estaca A a otra de acuerdo con las reglas. Cmo podramos entonces terminar el trabajo de mover las cinco?
Recuerde que hay tres estacas disponibles.

Ilustracin 1. Posicin inicial de las torres de Hanoi.
Suponga que podemos mover cuatro discos de la estaca A a la C. Entonces podramos pasarlos con facilidad a B
usando C como auxiliar. Esto producira la situacin mostrada en la Ilustracin 2. Despus podramos mover el disco
ms grande de A a C (Ilustracin 3) y por ltimo aplicar una vez ms la solucin para cuatro discos con el fin de pasar
cuatro discos de B a C, usando la ahora vaca estaca A como auxiliar (Ilustracin 4).

Ilustracin 2. Posicin luego de mover cuatro discos de la estaca A a B, usando C como auxiliar

Ilustracin 3. Posicin despus de mover el disco ms grande de A a C.
A

B

C

A

B

C

A

B

C


31

Ilustracin 4. Posicin luego de mover cuatro discos de la estaca B a C, usando A como auxiliar
Por tanto, podemos plantear una solucin recursiva para el problema de las Torres de Hanoi del modo siguiente: Para
mover n discos de A a C usando B como auxiliar:
1. Si n == 1, mover el disco nico de A a C y detenerse.
2. Mover los n-1 discos superiores de A a B usando C como auxiliar.
3. Mover el disco restante de A a C.
4. Mover los n-1 discos de B a C usando A como auxiliar.
public class hanoi{

public static void hanoi(int num,char ini,char fin,char auxiliar){
if(num==1){
System.out.println("Mueva disco "+num
+" desde la clavija "+ini
+" hasta la clavija "+fin);
return;
}
else{
hanoi(num-1,ini,auxiliar,fin);
System.out.println("Mueva disco "+num
+" desde la clavija "+ini
+" hasta la clavija "+fin);
hanoi(num-1,auxiliar,fin,ini);
}
}

public static void main(String []args){
int n;
char resp;

do{
System.out.println("Los clavijas son A B C");
System.out.print("Numero de discos: ");
n=Leer.datoInt();
System.out.println();
hanoi(n,'A','C','B');
System.out.println();
System.out.print("Continuar (s/n)? ");
resp=Leer.datoChar();
}while(resp!='n' && resp!='N');
}
}
PROBLEMAS PROPUESTOS
PROBLEMA UNO
Desarrollar un procedimiento o funcin recursiva para hallar las combinaciones de n nmeros combinados en grupos
de r elementos. Recomendacin: Utilice las siguientes propiedades:
1
0

n
C
A

B

C


32
n
r
n
r
C
r
r n
C
1
*
1

+

PROBLEMA DOS
Escribir un programa que utilizando una funcin recursiva imprima los n numeros consecutivos.
PROBLEMA TRES
Disear un mtodo recursivo para que reciba como parmetro un dato a buscar. Como respuesta debe devolver la
direccin del nodo que contiene el dato o una direccin nula si el dato no es encontrado.
PROBLEMA CUATRO
Disear un mtodo recursivo para que recorra e imprima los elementos de una lista simplemente enlazada.
PROBLEMA CINCO
Considere un arreglo (matriz) unidimensional de enteros. Escriba algoritmos recursivos para calcular:
a) El mayor elemento del arreglo.
b) El menor elemento del arreglo.
c) La suma de los elementos del arreglo.
d) El producto de los elementos del arreglo.
e) El promedio de los elementos del arreglo.
PROBLEMA SEIS
La funcin de Ackerman se define en forma recursiva para enteros no negativos de la siguiente manera:
a(m,n)=n+1 si m=0
a(m,n)=a(m-1,n) si m0, n=0
a(m,n)=a(m-1,a(m,n-1) si m0,n 0
Escriba un algoritmo recursivo para la funcin de Ackerman.
PROBLEMA SIETE
Disear una funcin recursiva para que reciba como parmetros un dato a buscar. Como respuesta debe devolver la
direccin del nodo que contiene el dato o una direccin nula si el dato no es encontrado.
PROBLEMA EIGHT
Disear una funcin recursiva para calcular los elementos de la serie de fibbonacci. Se comienza incicalizando los
dos primeros terminos, y a partir del tercero, el elemento siguiente se halla sumando los dos elementos anteriores;
por ejemplo, si inicializamos en 0 y 1, la serie es: 0, 1, 2, 3, 5, 8, 13, 21,
#include <cstdlib>
#include <iostream>

using namespace std;

int fib(int n);
int main(int argc, char *argv[]){
int n;

cout<<"Termino: ";
cin>>n;
cout<<"Termino "<<n<<": "<<fib(n)<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}

33
int fib(int n){
if(n==0)
return 0;
else if(n==1)
return 1;
else return fib(n-1)+fib(n-2);
}
ECUACIONES DE RECURRENCIA
RECURRENCIAS HOMOGENEAS
0 ) ( ) 2 ( ) 1 ( ) (
2 1 0
+ + + + k n T a n T a n T a n T a
k
K

Donde los coeficientes ai son nmeros reales, y k es un nmero natural entre 1 y n.
Para resolverlas vamos a buscar soluciones que sean combinaciones de funciones exponenciales de la forma:

+ + +
k
i
n
i i i
n
k k k
n n
r n p c r n p c r n p c r n p c n T
1
2 2 2 1 1 1
) ( ) ( ) ( ) ( ) ( K

Donde los valores c1, c2,...,cn y r1, r2, ...,rn son nmeros reales, y p1(n),...,pk(n) son polinomios en n con coeficientes
reales.
Para resolverlas haremos el cambio xn = T(n), con lo cual obtenemos la ecuacin caracterstica asociada:
0
2
2
1
1
+ + + +

k
k k k
o
a x a x a x a K

Llamemos r1, r2,...,rk a sus races, ya sean reales o complejas.
CASO 1: RACES DISTINTAS
Llamemos r1, r2,...,rk a sus races, ya sean reales o complejas. Dependiendo del orden de multiplicidad de tales races,
pueden darse los dos siguientes casos.

+ + +
k
i
n
i i
n
k k
n n
r c r c r c r c n T
1
2 2 1 1
) ( K

Donde los coeficientes ci se determinan a partir de las condiciones iniciales.
CASO 2: RACES CON MULTIPLICIDAD MAYOR QUE 1
Supongamos que alguna de las races (por ejemplo r1) tiene multiplicidad m>1. Entonces la ecuacin caracterstica
puede ser escrita en la forma:
( ) ( ) ( )
1 2 1 +

m k
m
r x r x r x K

en cuyo caso la solucin de la ecuacin en recurrencia viene dada por la expresin:

+
+

+
k
m i
n
m i i
m
i
n i
i
r c r n c n T
1
1
1
1
1
) (

donde los coeficientes c i se determinan a partir de las condiciones iniciales.

34
Este caso puede ser generalizado de la siguiente forma. Si r1,r2,...,rk son las races de la ecuacin caracterstica de
una ecuacin en recurrencia homognea, cada una de multiplicidad mi, esto es, si la ecuacin caracterstica puede
expresarse como:
( ) ( ) ( )
k
m
k
m m
r x r x r x K
2 1
2 1

entonces la solucin a la ecuacin en recurrencia viene dada por la expresin:

+ + +
k
m
i
n
k
i
ki
m
i
n i
i
m
i
n i
i
r n c r n c r n c n T
1
1
1
2
1
2
1
1
1
1
2 1
) ( K

RESUMEN
ECUACIONES RECURRENTES HOMOGENEAS
FORMA DE LA ECUACION DE RECURRENCIA
0 ) ( ) 1 ( ) (
1 0
+ + + k n T a n T a n T a
k
K

ECUACIN CARACTERISTICA
( ) ( ) ( ) 0
2 1
2 1

k
m
k
m m
r x r x r x K

ECUACIN DE RECURRENCIA

+ + +
k
m
i
n
k
i
ki
m
i
n i
i
m
i
n i
i
r n c r n c r n c n T
1
1
1
2
1
2
1
1
1
1
2 1
) ( K

ECUACIONES RECURRENTES NO HOMOGENEAS
FORMA DE LA ECUACIN DE RECURRENCIA
) ( ) ( ) ( ) ( ) 1 ( ) (
2 2 1 1 1 0
n p b n p b n p b k n T a n T a n T a
s
n
s
n n
k
+ + + + + + K K

ECUACIN CARCTERISTICA
( )( ) ( ) ( ) 0
1 1
2
1
1
2
2
1
1
2 1
+ + + +
+ + +

s
d
s
d d
k
k k k
o
b x b x b x a x a x a x a K K

CAMBIO DE VARIABLE
Esta tcnica se aplica cuando n es potencia de un nmero real a, esto es, n = a
k
.
RECURRENCIAS NO LINEALES
En este caso, la ecuacin que relaciona T(n) con el resto de los trminos no es lineal. Para resolverla intentaremos
convertirla en una ecuacin lineal como las que hemos estudiado hasta el momento.
EJEMPLO PARA LA BSQUEDA BINARIA
int busquedaBinR(int prim, int ult,int x){
int mitad;

/* 1 */ if(prim>=ult)
/* 2 */ return a[ult]==x;
/* 3 */ else

35
/* 4 */ mitad=(prim+ult)/2;
/* 5 */ if(x==a[mitad])
/* 6 */ return 1;
/* 7 */ else if(x<a[mitad])
/* 8 */ return busquedaBinR(prim,mitad-1,x);
/* 9 */ else
/* 10 */ return busquedaBinR(mitad+1,ult,x);
}

/* 1 */ 1
/* 2 */ 3
/* 3 */ 0
/* 4 */ 3
/* 5 */ 2
/* 6 */ 1
/* 7 */ 2
/* 8 */ 3+T(n/2)
/* 9 */ 0
/* 10 */ 3+T(n/2
La ecuacin de recurrencia para el peor caso es:
T(n)=11+T(n/2) y T(1)=4
Haciendo n=2
k
:
T(2
k
)=11+T(2
k
/2)
T(2
k
)=11+T(2
k-1
)
Haciendo t
k
=T(2
k
):
t
k
=11+t
k-1

t
k
+ t
k-1
=11
t
k
+ t
k-1
=1
n
11
Ecuacin caracterstica:
(x-1)(x-1)=0
(x-1)
2
=0
La ecuacin de recurrencia:
t
k
=c
1
k+c
2
Haciendo T(2
k
)=t
k
:
T(2
k
)=c
1
K+c
2

Haciendo 2
k
=n y k= log(n):
T(n)=c
1
log(n)+c
2

Cuando n=1, T(1)=4:
c
1
log(1)+c
2
=4
c
2
=4
Cuando n=2, T(2)=4:
T(2)=11+T(2/2)=11+T(1)=11+4=15
T(2)=c
1
log(2)+c
2
=15
c
1
+4=15
c
1
=11
Por lo tanto:
T(n)=11 log(n)+4
int busquedaBin(int prim, int ult,int x){
int mitad;

/* 1 */ while(prim<ult){
/* 2 */ mitad=(prim+ult)/2;
/* 3 */ if(x==a[mitad])
/* 4 */ return 1;
/* 5 */ else if(x<a[mitad])
/* 6 */ ult=mitad-1;
/* 7 */ else
/* 8 */ prim=mitad+1;
/* 9 */ }
/* 10 */ return x==a[ult];
}

/* 1 */ 1
/* 2 */ 3
/* 3 */ 2
/* 4 */ 1
/* 5 */ 2
/* 6 */ 2
/* 7 */ 0
/* 8 */ 2
/* 9 */ 0
/* 10 */ 3
Para el peor caso el bucle se tomo como una ecuacin
recursiva igual a:
T(n)=c+T(n/2)
Donde T(1)=0 y T(2)=1
Haciendo n=2
k
:
T(2
k
)=c+T(2
k
/2)
T(2
k
)=c+T(2
k-1
)
Haciendo t
k
=T(2
k
):
t
k
=c+t
k-1

t
k
+ t
k-1
=c
t
k
+ t
k-1
=1
n
c
Ecuacin caracterstica:
(x-1)(x-1)=0
(x-1)
2
=0
La ecuacin de recurrencia:
t
k
=c
1
k+c
2
Haciendo T(2
k
)=t
k
:
T(2
k
)=c
1
K+c
2

Haciendo 2
k
=n y k= log(n):
T(n)=c
1
log(n)+c
2

Cuando n=1, T(1)=0:

36
c
1
log(1)+c
2
=0
c
2
=0
Cuando n=2, T(2)=1:
T(2)=c
1
log(2)+c
2
=1
c
1
+0=1
c
1
=1
Por lo tanto:
T(n)=log(n)
Ecuacin de recurrencia para el algoritmo:

DIVIDE Y VENCERS
El trmino Divide y Vencers en su acepcin ms amplia es algo ms que una tcnica de diseo de algoritmos. De
hecho, suele ser considerada una filosofa general para resolver problemas y de aqu que su nombre no slo forme
parte del vocabulario informtico, sino que tambin se utiliza en muchos otros mbitos.
En nuestro contexto, Divide y Vencers es una tcnica de diseo de algoritmos que consiste en resolver un problema
a partir de la solucin de subproblemas del mismo tipo, pero de menor tamao. Si los subproblemas son todava
relativamente grandes se aplicar de nuevo esta tcnica hasta alcanzar subproblemas lo suficientemente pequeos
para ser solucionados directamente. Ello naturalmente sugiere el uso de la recursin en las implementaciones de
estos algoritmos.
La resolucin de un problema mediante esta tcnica consta fundamentalmente de los siguientes pasos:
1. En primer lugar ha de plantearse el problema de forma que pueda ser descompuesto en k subproblemas del
mismo tipo, pero de menor tamao. Es decir, si el tamao de la entrada es n, hemos de conseguir dividir el
problema en k subproblemas (donde 1 k n), cada uno con una entrada de tamao n
k
y donde 0 n
k
< n. A
esta tarea se le conoce como divisin.
2. En segundo lugar han de resolverse independientemente todos los subproblemas, bien directamente si son
elementales o bien de forma recursiva. El hecho de que el tamao de los subproblemas sea estrictamente
menor que el tamao original del problema nos garantiza la convergencia hacia los casos elementales,
tambin denominados casos base.
3. Por ltimo, combinar las soluciones obtenidas en el paso anterior para construir la solucin del problema
original. El funcionamiento de los algoritmos que siguen la tcnica de Divide y Vencers descrita
anteriormente se refleja en el esquema general que presentamos a continuacin:
tiposolucion DyV(x:TipoProblema){
int i,k;
TipoSolucion s;
TipoProblema subproblemas[xx];
TipoSolucion subsoluciones[yy];

if EsCasobase(x)
s=ResuelveCasoBase(x)
else
k=Divide(x,subproblemas);
for(i=1;i<=k;i++)
subsoluciones[i]=DyV(subproblemas[i])
s=Combina(subsoluciones)
return s;
}
Hemos de hacer unas apreciaciones en este esquema sobre el procedimiento Divide, sobre el nmero k que
representa el nmero de subproblemas, y sobre el tamao de los subproblemas, ya que de todo ello va a depender la
eficiencia del algoritmo resultante.
En primer lugar, el nmero k debe ser pequeo e independiente de una entrada determinada. En el caso particular de
los algoritmos Divide y Vencers que contienen slo una llamada recursiva, es decir k = 1, hablaremos de algoritmos
de simplificacin. Tal es el caso del algoritmo recursivo que resuelve el clculo del factorial de un nmero, que
sencillamente reduce el problema a otro subproblema del mismo tipo de tamao ms pequeo. Tambin son
algoritmos de simplificacin el de bsqueda binaria en un vector o el que resuelve el problema del k-simo elemento.
La ventaja de los algoritmos de simplificacin es que consiguen reducir el tamao del problema en cada paso, por lo
que sus tiempos de ejecucin suelen ser muy buenos (normalmente de orden logartmico o lineal). Adems pueden
admitir una mejora adicional, puesto que en ellos suele poder eliminarse fcilmente la recursin mediante el uso de

37
un bucle iterativo, lo que conlleva menores tiempos de ejecucin y menor complejidad espacial al no utilizar la pila de
recursin, aunque por contra, tambin en detrimento de la legibilidad del cdigo resultante.
Por el hecho de usar un diseo recursivo, los algoritmos diseados mediante la tcnica de Divide y Vencers van a
heredar las ventajas e inconvenientes que la recursin plantea:
a) Por un lado el diseo que se obtiene suele ser simple, claro, robusto y elegante, lo que da lugar a una mayor
legibilidad y facilidad de depuracin y mantenimiento del cdigo obtenido.
b) Sin embargo, los diseos recursivos conllevan normalmente un mayor tiempo de ejecucin que los iterativos,
adems de la complejidad espacial que puede representar el uso de la pila de recursin.
Desde un punto de vista de la eficiencia de los algoritmos Divide y Vencers, es muy importante conseguir que los
subproblemas sean independientes, es decir, que no exista solapamiento entre ellos. De lo contrario el tiempo de
ejecucin de estos algoritmos ser exponencial. Como ejemplo pensemos en el clculo de la sucesin de Fibonacci,
el cual, a pesar de ajustarse al esquema general y de tener slo dos llamadas recursivas, tan slo se puede
considerar un algoritmo recursivo pero no clasificarlo como diseo Divide y Vencers. Esta tcnica est concebida
para resolver problemas de manera eficiente y evidentemente este algoritmo, con tiempo de ejecucin exponencial,
no lo es.
En cuanto a la eficiencia hay que tener en tambin en consideracin un factor importante durante el diseo del
algoritmo: el nmero de subproblemas y su tamao, pues esto influye de forma notable en la complejidad del
algoritmo.
BSQUEDA BINARIA
El algoritmo de bsqueda binaria es un ejemplo claro de la tcnica Divide y Vencers. El problema de partida es
decidir si existe un elemento dado x en un vector de enteros ordenado. El hecho de que est ordenado va a permitir
utilizar esta tcnica, pues podemos plantear un algoritmo con la siguiente estrategia: comprese el elemento dado x
con el que ocupa la posicin central del vector. En caso de que coincida con l, hemos solucionado el
problema. Pero si son distintos, pueden darse dos situaciones: que x sea mayor que el elemento en posicin
central, o que sea menor. En cualquiera de los dos casos podemos descartar una de las dos mitades del
vector, puesto que si x es mayor que el elemento en posicin central, tambin ser mayor que todos los
elementos en posiciones anteriores, y al revs. Ahora se procede de forma recursiva sobre la mitad que no hemos
descartado.
En este ejemplo la divisin del problema es fcil, puesto que en cada paso se divide el vector en dos mitades
tomando como referencia su posicin central. El problema queda reducido a uno de menor tamao y por ello
hablamos de simplificacin. Por supuesto, aqu no es necesario un proceso de combinacin de resultados.
Su caso base se produce cuando el vector tiene slo un elemento. En esta situacin la solucin del problema se basa
en comparar dicho elemento con x. Como el tamao de la entrada (en este caso el nmero de elementos del vector a
tratar) se va dividiendo en cada paso por dos, tenemos asegurada la convergencia al caso base.
El esquema de la clase ArregloUni es:
#define MAX 20

class ArregloUni{
private:
int n;
int a[MAX];
public:
//Se definen los mtdos
};
Algoritmo recursivo para la bsqueda binaria:
//El mtodo pertenece a la clase ArregloUni
int ArregloUni::busquedaBinR(int prim, int ult,int x){
int mitad;

if(prim>=ult)
return a[ult]==x;
else
mitad=(prim+ult)/2;
if(x==a[mitad])
return 1;
else if(x<a[mitad])

38
return busquedaBinR(prim,mitad-1,x);
else
return busquedaBinR(mitad+1,ult,x);
}
El tiempo de ejecucin del algoritmo resultante en el peor caso es:
( ) 11log 4 T n n + (log ) n
BSQUEDA BINARIA NO CENTRADA
Una de las cuestiones a considerar cuando se disea un algoritmo mediante la tcnica de Divide y Vencers es la
particin y el reparto equilibrado de los subproblemas. Ms concretamente, en el problema de la bsqueda binaria
nos podemos plantear la siguiente cuestin: supongamos que en vez de dividir el vector de elementos en dos mitades
del mismo tamao, las dividimos en dos partes de tamaos 1/3 y 2/3. Conseguiremos de esta forma un algoritmo
mejor que el original?
Algoritmo recursivo para la bsqueda binaria no balanceada:
int ArregloUni::busquedaBinRNB(int prim, int ult,int x){
int tercio;

if(prim>=ult)
return a[ult]==x;
else
tercio=prim+((ult-prim+1)/3);
if(x==a[tercio])
return 1;
else if(x<a[tercio])
return busquedaBinRNB(prim,tercio,x);
else return busquedaBinRNB(tercio+1,ult,x);
}
Dividiendo el vector en dos partes de tamaos k y nk, el tiempo de ejecucin del algoritmo resultante en el peor caso
es:
/ max{ , }
( ) 11log 4
n k n k
T n n

+ (log ) n

Ahora bien, para 1 k < n sabemos que la funcin n/max{k,nk} se mantiene por debajo de 2, y slo alcanza este
valor para k = n/2.
BSQUEDA TERNARIA
Podemos plantearnos tambin disear un algoritmo de bsqueda ternaria, que primero compara con el elemento en
posicin n/3 del vector, si ste es menor que el elemento x a buscar entonces compara con el elemento en posicin
2n/3, y si no coincide con x busca recursivamente en el correspondiente subvector de tamao 1/3 del original.
Conseguimos as un algoritmo mejor que el de bsqueda binaria?
int ArregloUni::busquedaBin3(int prim, int ult,int x){
int nterc;

if(prim>=ult)
return a[ult]==x;
nterc=(ult-prim-1)/3;
if(x==a[prim+nterc])
return 1;
else if(x<a[prim+nterc])
return busquedaBin3(prim,prim+nterc-1,x);
else if(x==a[ult-nterc])
return 1;
else if(x<a[ult-nterc])
return busquedaBin3(prim+nterc+1,ult-nterc-1,x);
else
return busquedaBin3(ult-nterc+1,ult,x);
}

39
El tiempo de ejecucin del algoritmo resultante en el peor caso es:
3
( ) 23log 4 T n n + (log ) n
MULTIPLICACIN DE ENTEROS
Sean u y v dos nmeros naturales de n bits donde, por simplicidad, n es una potencia de 2. El algoritmo tradicional
para multiplicarlos es de complejidad O(n
2
).
El algoritmo basado en la tcnica de Divide y Vencers divide los nmeros en dos partes:
/ 2
2
n
u a b +

/ 2
2
n
v c d +

siendo a, b, c y d nmeros naturales de n/2 bits, y calcula su producto como sigue:
( )( )
( )
/ 2 / 2 / 2
2 2
n n n n
uv an b cn d ac ad bc bd + + + + +

Sustituyendo
( ) ( )
ad bc a b d c ac bd + + +

( )( ) ( )
/ 2
2 2
n n
uv ac a b d c ac bd bd + + + +

Las multiplicaciones ac y bd se realizan usando este algoritmo recursivamente.
El algoritmo tradicional esta en el orden n
2
, pero con este mtodo se obtiene un tiempo de ejecucin para el algoritmo
recursivo igual a:
log log log3
1 2 1 2
( ) 3 2
n n
T n c c c n c n + +
1.59
( ) n
Este mtodo es menor que el tradicional cuando el nmero es mayor a 500 bits.
ALGORITMOS VIDOS
El mtodo que produce algoritmos vidos es un mtodo muy sencillo y que puede ser aplicado a numerosos
problemas, especialmente los de optimizacin.
Dado un problema con n entradas el mtodo consiste en obtener un subconjunto de stas que satisfaga una
determinada restriccin definida para el problema. Cada uno de los subconjuntos que cumplan las restricciones
diremos que son soluciones prometedoras. Una solucin prometedora que maximice o minimice una funcin objetivo
la denominaremos solucin ptima.
Como ayuda para identificar si un problema es susceptible de ser resuelto por un algoritmo vido vamos a definir una
serie de elementos que han de estar presentes en el problema:
Un conjunto de candidatos, que corresponden a las n entradas del problema.
Una funcin de seleccin que en cada momento determine el candidato idneo para formar la solucin de
entre los que an no han sido seleccionados ni rechazados.
Una funcin que compruebe si un cierto subconjunto de candidatos es prometedor. Entendemos por
prometedor que sea posible seguir aadiendo candidatos y encontrar una solucin.
Una funcin objetivo que determine el valor de la solucin hallada. Es la funcin que queremos maximizar o
minimizar.
Una funcin que compruebe si un subconjunto de estas entradas es solucin al problema, sea ptima
o no.
Con estos elementos, podemos resumir el funcionamiento de los algoritmos vidos en los siguientes puntos:
1. Para resolver el problema, un algoritmo vido tratar de encontrar un subconjunto de candidatos tales
que, cumpliendo las restricciones del problema, constituya la solucin ptima.
2. Para ello trabajar por etapas, tomando en cada una de ellas la decisin que le parece la mejor, sin
considerar las consecuencias futuras, y por tanto escoger de entre todos los candidatos el que

40
produce un ptimo local para esa etapa, suponiendo que ser a su vez ptimo global para el
problema.
3. Antes de aadir un candidato a la solucin que est construyendo comprobar si es prometedora al
aadirlo. En caso afirmativo lo incluir en ella y en caso contrario descartar este candidato para
siempre y no volver a considerarlo.
4. Cada vez que se incluye un candidato comprobar si el conjunto obtenido es solucin.
Resumiendo, los algoritmos vidos construyen la solucin en etapas sucesivas, tratando siempre de tomar la decisin
ptima para cada etapa. A la vista de todo esto no resulta difcil plantear un esquema general para este tipo de
algoritmos:
Conjunto AlgoritmoAvido(entrada:Conjunto){
Elemento x
Conjunto solucion
Bolean Encontrada

encontrada=FALSE
crear(solucion)
WHILE NOT EsVacio(entrada) AND (NOT encontrada) HACER
x=SeleccionarCandidato(entrada)
IF EsPrometedor(x,solucion)
Incluir(x,solucion);
ENDIF
IF EsSolucion(solucion)
encontrada=TRUE
ENDIF
ENDWHILE
RETURN solucion
}
De este esquema se desprende que los algoritmos vidos son muy fciles de implementar y producen soluciones
muy eficientes. Entonces cabe preguntarse por qu no utilizarlos siempre? En primer lugar, porque no todos los
problemas admiten esta estrategia de solucin. De hecho, la bsqueda de ptimos locales no tiene por qu conducir
siempre a un ptimo global, como mostraremos en varios ejemplos. La estrategia de los algoritmos vidos consiste en
tratar de ganar todas las batallas sin pensar que, como bien saben los estrategas militares y los jugadores de ajedrez,
para ganar la guerra muchas veces es necesario perder alguna batalla.
Desgraciadamente, y como en la vida misma, pocos hechos hay para los que podamos afirmar sin miedo a
equivocarnos que lo que parece bueno para hoy siempre es bueno para el futuro. Y aqu radica la dificultad de estos
algoritmos. Encontrar la funcin de seleccin que nos garantice que el candidato escogido o rechazado en un
momento determinado es el que ha de formar parte o no de la solucin ptima sin posibilidad de reconsiderar dicha
decisin. Por ello, una parte muy importante de este tipo de algoritmos es la demostracin formal de que la funcin de
seleccin escogida consigue encontrar ptimos globales para cualquier entrada del algoritmo. No basta con disear
un procedimiento vido, que seguro de entre todos los candidatos el que produce un ptimo local para esa etapa,
suponiendo que ser a su vez ptimo global para el problema.
Debido a su eficiencia, este tipo de algoritmos es muchas veces utilizado aun en los casos donde se sabe que no
necesariamente encuentran la solucin ptima. En algunas ocasiones la situacin nos obliga a encontrar pronto una
solucin razonablemente buena, aunque no sea la ptima, puesto que si la solucin ptima se consigue demasiado
tarde, ya no vale para nada (pinsese en el localizador de un avin de combate, o en los procesos de toma de
decisiones de una central nuclear).
Tambin hay otras circunstancias, como en los algoritmos que siguen la tcnica de Ramificacin y Poda, en donde lo
que interesa es conseguir cuanto antes una solucin del problema y, a partir de la informacin suministrada por ella,
conseguir la ptima ms rpidamente. Es decir, la eficiencia de este tipo de algoritmos hace que se utilicen aunque
no consigan resolver el problema de optimizacin planteado, sino que slo den una solucin aproximada.
El nombre de algoritmos vidos, tambin conocidos como voraces (su nombre original proviene del trmino ingls
greedy) se debe a su comportamiento: en cada etapa toman lo que pueden sin analizar consecuencias, es decir,
son glotones por naturaleza.
En este tipo de algoritmos el proceso no acaba cuando disponemos de la implementacin del procedimiento que lo
lleva a cabo. Lo importante es la demostracin de que el algoritmo encuentra la solucin ptima en todos los casos, o
bien la presentacin de un contraejemplo que muestra los casos en donde falla.

41
EL PROBLEMA DEL CAMBIO
Suponiendo que el sistema monetario de un pas est formado por monedas de valores v
1
, v
2
, ..., v
n
, el problema del
cambio de dinero consiste en descomponer cualquier cantidad dada M en monedas de ese pas utilizando el menor
nmero posible de monedas.
En primer lugar, es fcil implementar un algoritmo vido para resolver este problema, que es el que sigue el proceso
que usualmente utilizamos en nuestra vida diaria. Sin embargo, tal algoritmo va a depender del sistema monetario
utilizado y por ello vamos a plantearnos dos situaciones para las cuales deseamos conocer si el algoritmo vido
encuentra siempre la solucin ptima:
a) Suponiendo que cada moneda del sistema monetario del pas vale al menos el doble que la moneda de valor
inferior, que existe una moneda de valor unitario, y que disponemos de un nmero ilimitado de monedas de
cada valor.
b) Suponiendo que el sistema monetario est compuesto por monedas de valores 1, p, p
2
, p
3
,..., p
n
, donde p>1 y
n>0, y que tambin disponemos de un nmero ilimitado de monedas de cada valor.
SOLUCIN
Comenzaremos con la implementacin de un algoritmo vido que resuelve el problema del cambio de dinero:
#include <cstdlib>
#include <iostream>
using namespace std;
#define N 3
int valor[]={11,5,1};

void cambio(int n,int solucion[]);
int main(int argc,char *argv[]){
int solucion[N],i;

cambio(15,solucion);
for(i=0;i<N;i++)
cout<<solucion[i]<<" monedas de "<<valor[i]<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}

void cambio(int n,int solucion[]){
int i=0,moneda;
for(moneda=0;moneda<N;moneda++)
solucion[moneda]=0;

for(moneda=0;moneda<N;moneda++){
while(valor[moneda]<=n){
solucion[moneda]=solucion[moneda]+1;
n=n-valor[moneda];
}
}
}
Este algoritmo es de complejidad lineal respecto al nmero de monedas del pas, y por tanto muy eficiente. Respecto
a las dos cuestiones planteadas, comenzaremos por la primera. Supongamos que nuestro sistema monetario esta
compuesto por las siguientes monedas:
Valor[]={11,5,1};
Tal sistema verifica las condiciones del enunciado pues disponemos de moneda de valor unitario, y cada una de ellas
vale ms del doble de la moneda inmediatamente inferior.
Consideremos la cantidad n = 15. El algoritmo vido del cambio de monedas descompone tal cantidad en: 15 = 11 +
1 + 1 + 1 + 1, es decir, mediante el uso de cinco monedas. Sin embargo, existe una descomposicin que utiliza
menos monedas (exactamente tres): 15 = 5 + 5 + 5.
EL VIAJANTE DE COMERCIO
Se conocen las distancias entre un cierto nmero de ciudades. Un viajante debe, a partir de una de ellas, visitar cada
ciudad exactamente una vez y regresar al punto de partida habiendo recorrido en total la menor distancia posible.

42
Este problema tambin puede ser enunciado ms formalmente como sigue: dado un grafo g conexo y ponderado y
dado uno de sus vrtices
0
v , encontrar el ciclo Hamiltoniano de coste mnimo que comienza y termina en
0
v .
Cara a intentar solucionarlo mediante un algoritmo vido, nos planteamos las siguientes estrategias:
a) Sea
( ) , C v
el camino construido hasta el momento que comienza en
0
v
y termina en v . Inicialmente C es
vaco y
0
v v . Si C contiene todos los vrtices de g , el algoritmo incluye el arco
0
( , ) v v y termina. Sino,
incluye el arco ( , ) v w de longitud mnima entre todos los arcos desde v a los vrtices w que no estn en el
camino C .
b) Otro posible algoritmo vido escogera en cada iteracin el arco ms corto an no considerado que cumpliera
las dos condiciones siguientes: (i) no formar un ciclo con los arcos ya seleccionados, excepto en la ltima
iteracin, que es donde completa el viaje; y (ii) no es el tercer arco que incide en un mismo vrtice de entre
los ya escogidos.
SOLUCIN PRIMER ALGORITMO
#include <cstdlib>
#include <iostream>
using namespace std;
#define N 4
#define TRUE 1
#define FALSE 0

int busca(int g[N][N],int vertice,int yaesta[N]);
void viajante(int g[N][N],int sol[N][N]);

int main(int argc,char *argv[]){
int grafo[N][N]={{0,1,5,2},{0,0,4,6},{0,0,0,3},{0,0,0,0}};
int solucion[N][N],i,j;


viajante(grafo,solucion);
cout<<"Grafo:"<<endl;
for(i=0;i<N;i++){
for(j=0;j<N;j++)
cout<<grafo[i][j]<<"\t";
cout<<endl;
}

cout<<"Solucion:"<<endl;
for(i=0;i<N;i++){
for(j=0;j<N;j++)
cout<<solucion[i][j]<<"\t";
cout<<endl;
}
system("PAUSE");
return EXIT_SUCCESS;
}

void viajante(int g[N][N],int sol[N][N]){
int yaEsta[N];
int i,j,verticeEnCurso,verticeAnterior;

for(i=0;i<N;i++)
yaEsta[i]=FALSE;

for(i=0;i<N;i++)
for(j=0;j<N;j++)
sol[i][j]=FALSE;


43
verticeEnCurso=0;
for(i=0;i<N;i++){
verticeAnterior=verticeEnCurso;
yaEsta[verticeAnterior]=TRUE;
verticeEnCurso=busca(g,verticeEnCurso,yaEsta);
sol[verticeAnterior][verticeEnCurso]=TRUE;
}
}

int busca(int g[N][N],int vertice,int yaEsta[N]){
int mejorvertice,i,min;

mejorvertice=0;
min=999999;
for(i=0;i<N;i++)
if((i!=vertice) && (!yaEsta[i]) && (g[vertice][i]<min)){
min=g[vertice][i];
mejorvertice=i;
}
return mejorvertice;
}
La clave de este algoritmo est en la funcin Busca, que es la que realiza el proceso de seleccin, decidiendo en
cada paso el siguiente vrtice de entre los posibles candidatos.
Respecto a los ejemplos de grafos en donde el algoritmo encuentra o no la solucin ptima, comenzaremos por un
grafo en donde la encuentra. Sea entonces
2 3 4
1 1 5 2
2 4 6
3 3
una tabla que representa la matriz de adyacencia de un grafo ponderado
1
g con cuatro vrtices. Partiendo del vrtice
1, el algoritmo encuentra la solucin ptima, que est formada por los arcos
(1,2),(2,3),(3,4),(4,1)
lo que da lugar al ciclo (1,2,3,4,1), cuyo coste es 1 + 4 + 3 + 2 = 10, ptimo pues el resto de soluciones poseen costes
superiores o iguales a l: 15, 17, 14, 17 y 10.
Para ver un ejemplo en donde el algoritmo falla, consideraremos un grafo ponderado
2
g con seis vrtices definido
por la siguiente matriz de adyacencia:
2 3 4 5 6
1 3 10 11 7 25
2 6 12 8 26
3 9 4 20
4 5 15
5 18
Partiendo del vrtice 1 el algoritmo va a ir escogiendo la secuencia de arcos (1,2),(2,3),(3,5),(5,4),(4,6),(6,1) lo que da
lugar al ciclo (1,2,3,5,4,6,1), cuyo coste es 3 + 6 + 4 + 5 + 15 + 25 = 58. Sin embargo, ste no es el ciclo con menor
coste, pues el camino definido por los arcos: (1,2),(2,3),(3,6),(6,4),(4,5),(5,1) tiene un coste de 3 + 6 + 20 + 15 + 5 + 7
= 56.
SOLUCIN SEGUNDO ALGORITMO
#include <cstdlib>
#include <iostream>
using namespace std;


44
#define CIUDADES 6
#define N CIUDADES+1
#define NO CIUDADES*(CIUDADES-1)/2+1
#define TRUE 1
#define FALSE 0

struct item{
int origen;
int destino;
int peso;
};

void inicParticion(int p[N]);
void fusionar(int p[N],int a,int b);
int finParticion(int p[N]);
int obtenerComponente(int p[N],int i);
void viajante(int g[N][N],int sol[N][N]);
int ordenar(int g[N][N],item g2[NO]);

int main(int argc,char *argv[]){
int grafo[N][N]={{ 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 3,10,11, 7,25},
{ 0, 0, 0, 6,12, 8,26},
{ 0, 0, 0, 0, 9, 4,20},
{ 0, 0, 0, 0, 0, 5,15},
{ 0, 0, 0, 0, 0, 0,18},
{ 0, 0, 0, 0, 0, 0, 0}};
int solucion[N][N],i,j;


cout<<"Grafo:"<<endl;
for(i=1;i<N;i++){
for(j=1;j<N;j++)
cout<<grafo[i][j]<<"\t";
cout<<endl;
}
viajante(grafo,solucion);
cout<<"Solucion:"<<endl;
for(i=1;i<N;i++){
for(j=1;j<N;j++)
cout<<solucion[i][j]<<"\t";
cout<<endl;
}
system("PAUSE");
return EXIT_SUCCESS;
}

void viajante(int g[N][N],int sol[N][N]){
int p[N];
int c1,c2; // Componentes de la particin
item gOrdenado[NO];
int i,j,narcos; //Numero de arcos del grafo
int u,v; //vertices tratados en cada paso
int ndest[N]; //num. veces que cada vertice es destino en
// la solucion

inicParticion(p);
for(i=1;i<N;i++)
for(j=1;j<N;j++)
sol[i][j]=FALSE;

for(i=1;i<N;i++)

45
ndest[i]=0;
narcos=ordenar(g,gOrdenado); // devuelve el num. de arcos
i=0;
while(not finParticion(p) && i<narcos){
i=i+1;
u=gOrdenado[i].origen;
v=gOrdenado[i].destino;
c1=obtenerComponente(p,u);
c2=obtenerComponente(p,v);
if(c1!=c2 && ndest[u]<2 && ndest[v]<2){
fusionar(p,c1,c2);
sol[u][v]=TRUE;
ndest[u]=ndest[u]+1;
ndest[v]=ndest[v]+1;
}
}
// ahora solo nos queda el ultimo vertice,
// que cierra el ciclo
while(i<narcos){
i=i+1;
u=gOrdenado[i].origen;
v=gOrdenado[i].destino;
if(ndest[u]<2 && ndest[v]<2){ // lo encontramos!
sol[u][v]=TRUE;
ndest[u]=ndest[u]+1;
ndest[v]=ndest[v]+1;
i=narcos; // para salirnos del bucle
}
}
}

void inicParticion(int p[N]){
int i;

for(i=1;i<N;i++)
p[i]=i;
}

void fusionar(int p[N],int a,int b){
int i,temp;

if(a>b){
temp=a;
a=b;
b=temp;
}
for(i=1;i<N;i++)
if(p[i]==b)
p[i]=a;
}

int finParticion(int p[N]){
int i;

for(i=1;i<N;i++)
if(p[i]!=1)
return FALSE;
return TRUE;
}


46
int obtenerComponente(int p[N],int i){
return p[i];
}

int ordenar(int g[N][N],item g2[NO]){
int i,j,k;
item t;

k=0;
for(i=1;i<N;i++)
for(j=1;j<N;j++)
if(g[i][j]!=0){
k=k+1;
g2[k].origen=i;
g2[k].destino=j;
g2[k].peso=g[i][j];
}
for(i=1;i<=k-1;i++)
for(j=k;j>=i+1;j--)
if(g2[j-1].peso>g2[j].peso){
t=g2[j-1];
g2[j-1]=g2[j];
g2[j]=t;
}
return k;
}
El grafo
1
g es un ejemplo para el cual el algoritmo encuentra la solucin ptima, al igual que ocurra con el anterior.
Sin embargo, este algoritmo no encuentra la solucin ptima en todos los casos, como ocurre por ejemplo con el
grafo
2
g del apartado anterior. Para l, y partiendo del vrtice 1, el algoritmo va a ir escogiendo la secuencia de
arcos (1,2),(3,5),(4,5),(2,3),(4,6),(1,6) que da lugar al mismo ciclo que obtenamos antes, (1,2,3,5,4,6,1), de coste 58 y
por tanto no ptimo.
LA MOCHILA
Dados n elementos e
1
, e
2
, ..., e
n
con pesos p
1
, p
2
, ..., p
n
y beneficios b
1
, b
2
, ..., b
n
, y dada una mochila capaz de
albergar hasta un mximo de peso M (capacidad de la mochila), queremos encontrar las proporciones de los n
elementos x
1
, x
2
, ..., x
n
(0 x
i
1) que tenemos que introducir en la mochila de forma que la suma de los beneficios
de los elementos escogidos sea mxima.
Esto es, hay que encontrar valores (x
1
, x
2
, ..., x
n
) de forma que se maximice la cantidad
1
n
i i
i
b x

, sujeta a la restriccin
1
n
i i
i
p x M

.
SOLUCIN
Un algoritmo vido que resuelve este problema ordena los elementos de forma decreciente respecto a su ratio /
i i
b p
y va aadiendo objetos mientras stos vayan cabiendo.
Con ellos, el algoritmo vido para resolver el problema pedido con n elementos y para una capacidad de la mochila M
es:
#include <cstdlib>
#include <iostream>
using namespace std;

#define N 10
#define TAMANO N+1
#define M 30

struct elemento{

47
float peso;
float beneficio;
float ratio;
};

void ordenar(elemento a[TAMANO]);
void imprimir(elemento a[TAMANO]);
void mochila(elemento e[TAMANO],int n,float m,float sol[TAMANO]);

int main(int argc,char *argv[]){
float solucion[TAMANO];
int i;
elemento elementos[]={{0,0,0},
{5,10,0},
{10,2,0},
{6,20,0},
{3,4,0},
{12,10,0},
{8,120,0},
{5,6,0},
{4,8,0},
{7,4,0},
{9,12,0}};
ordenar(elementos);
imprimir(elementos);
mochila(elementos,N,M,solucion);
cout<<"\nSolucion"<<endl;
cout<<"Peso\tBeneficio\tProporcion"<<endl;
for(i=1;i<=N;i++)
cout<<elementos[i].peso<<"\t"<<elementos[i].beneficio
<<"\t\t"<<solucion[i]<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}

void mochila(elemento e[TAMANO],int n,float m,
float sol[TAMANO]){
// supone que los elementos de "e" estan en
// orden decreciente de su ratio bi/pi
float pesoEnCurso;
int i;

for(i=1;i<=n;i++)
sol[i]=0.0;
pesoEnCurso=0.0;
i=1;
while(pesoEnCurso<M && i<=n){
if(e[i].peso+pesoEnCurso<=M)
sol[i]=1.0;
else
sol[i]=(M-pesoEnCurso)/e[i].peso;
pesoEnCurso=pesoEnCurso+(sol[i]*e[i].peso);
i=i+1;
}
}

void ordenar(elemento a[TAMANO]){
int i,j;
elemento t;

for(i=1;i<=N;i++)

48
a[i].ratio=a[i].beneficio/a[i].peso;
for(i=1;i<=N;i++)
for(j=N;j>=i+1;j--)
if(a[j-1].ratio<a[j].ratio){
t=a[j-1];
a[j-1]=a[j];
a[j]=t;
}
}

void imprimir(elemento a[TAMANO]){
int i,j;
elemento t;

cout<<"Elementos:"<<endl;
cout<<"Peso\tBeneficio\tRatio"<<endl;
for(i=1;i<=N;i++)
cout<<a[i].peso<<"\t"<<a[i].beneficio<<"\t\t"
<<a[i].ratio<<endl;
}

Figura 7 . Salida del programa.
Respecto al tiempo de ejecucin del algoritmo, ste consta de la ordenacin previa, de complejidad O( nlogn), y de un
bucle que como mximo recorre todos los elementos, de complejidad O(n), por lo que el tiempo total resulta ser de
orden O(nlogn).
Para demostrar que siguiendo la ordenacin dada el algoritmo encuentra la solucin ptima, vamos a suponer sin
prdida de generalidad que los elementos ya estn ordenados de esta forma, es decir, que / /
i i j j
b p b p > si i j > .
Por simplicidad en la notacin utilizaremos los smbolos de sumatorios sin los ndices.
Sea X = (x
1
, x
2
, ...,x
n
) la solucin encontrada por el algoritmo. Si x
i
= 1 para todo i, la solucin es ptima. Si no, sea j el
menor ndice tal que x
j
< 1. Por la forma en que trabaja el algoritmo, 1
i
x para todo i < j, 0
i
x para todo i > j, y
adems
i i
x p M

. Sea ( )
i i
B x x b

el beneficio que se obtiene para esa solucin.


Consideremos entonces Y = (y
1
, y
2
, ...,y
n
) otra solucin, y sea ( )
i i
B y y b

su beneficio. Por ser solucin cumple


que
i i
y p M

. Entonces, restando ambas capacidades, podemos afirmar que ( ) 0


i i i i
x p y p

.
Calculemos entonces la diferencia de beneficios:

49
( ) ( ) ( ) ( ) ( )
i
i i i i i
i
i
b
B X B Y x y b x y p
p



La segunda igualdad se obtiene multiplicando y dividiendo por
i
p . Con esto, para el ndice j escogido anteriormente
sabemos que ocurre:
Si i < j entonces 1
i
x , y por tanto ( ) 0
i i
x y . Adems, / /
i i j j
b p b p por la ordenacin escogida
(decreciente).
Si i > j entonces 0
i
x , y por tanto ( ) 0
i i
x y . Adems, / /
i i j j
b p b p por la ordenacin escogida
(decreciente).
Por ltimo, si i = j entonces / /
i i j j
b p b p .
En consecuencia, podemos afirmar que ( )( / ) ( )( / )
i i i i i i j j
x y b p x y b p para todo i, y por tanto:
( ) ( ) ( ) ( / ) ( / ) ( ) 0
i i i i i j j i i i
B X B Y x y p b p b p x y p

, esto es, ( ) ( ) B X B Y , como queramos
demostrar.
TAREA ACADEMICA
Realice la implementacin de la mochila (0,1), para lo cual se da la siguiente descripcin.
LA MOCHILA (0,1)
Consideremos una modificacin al problema de la Mochila en donde aadimos el requerimiento de que no se pueden
escoger fracciones de los elementos, es decir, 0
i
x 1
i
x , 1 i n . Como en el problema original, deseamos
maximizar la cantidad
1
n
i i
i
b x

, sujeta a la restriccin
1
n
i i
i
p x M

. Seguir funcionando el algoritmo anterior en


este caso?
En la solucin considere
Que lamentablemente no funciona, como pone de manifiesto el siguiente ejemplo.
Supongamos una mochila de capacidad M = 6, y que disponemos de los siguientes elementos (ya ordenados
respecto a su ratio beneficio/peso):

El algoritmo slo introducira el primer elemento, con un beneficio de 11, aunque sin embargo es posible obtener una
mejor eleccin: podemos introducir los dos ltimos elementos en la mochila puesto que no superan su capacidad, con
un beneficio total de 12.
LA ASIGNACIN DE TAREAS
Supongamos que disponemos de n trabajadores y n tareas. Sea 0
ij
b > el coste de asignarle el trabajo j al
trabajador i. Una asignacin de tareas puede ser expresada como una asignacin de los valores 0 1 a las variables
ij
x , donde 0
ij
x significa que al trabajador i no le han asignado la tarea j, y 1
ij
x indica que s. Una asignacin
vlida es aquella en la que a cada trabajador slo le corresponde una tarea y cada tarea est asignada a un
trabajador. Dada una asignacin vlida, definimos el coste de dicha asignacin como:
1 1
n n
ij ij
i j
x b

.
Diremos que una asignacin es ptima si es de mnimo coste. Cara a disear un algoritmo vido para resolver este
problema podemos pensar en dos estrategias distintas: asignar cada trabajador la mejor tarea posible, o bien asignar

50
cada tarea al mejor trabajador disponible. Sin embargo, ninguna de las dos estrategias tiene por qu encontrar
siempre soluciones ptimas. Es alguna mejor que la otra?
SOLUCIN
Este es un problema que aparece con mucha frecuencia, en donde los costes son o bien tarifas (que los trabajadores
cobran por cada tarea) o bien tiempos (que tardan en realizarlas). Para implementar ambos algoritmos vamos a
definir la matriz de costes (
ij
b ):
int costes[n][n]
que forma parte de los datos de entrada del problema, y la matriz de asignaciones (
ij
x ), que es la que buscamos:
int asignacion[n][n]
El programa principal es:
#include <cstdlib>
#include <iostream>
using namespace std;

#define N 4
#define TAMANO N+1
#define TRUE 1
#define FALSE 0

void asignacionOptima(int b[TAMANO][TAMANO],
int x[TAMANO][TAMANO]);
int mejorTarea(int b[TAMANO][TAMANO],int x[TAMANO][TAMANO],
int i);
int yaEscogida(int x[TAMANO][TAMANO],int trabajador,
int tarea);
void imprimir(int x[TAMANO][TAMANO]);
int costoTotal(int b[TAMANO][TAMANO],int x[TAMANO][TAMANO]);

int main(int argc,char *argv[]){
int costos[TAMANO][TAMANO]={{0,0,0,0,0},
{0,3,4,5,7},
{0,5,4,6,9},
{0,2,3,8,7},
{0,4,5,3,4}};
int asignacion[TAMANO][TAMANO];

asignacionOptima(costos,asignacion);
cout<<"Matriz de costos"<<endl;
imprimir(costos);
cout<<"\n\nMatriz de asignacion"<<endl;
imprimir(asignacion);
cout<<"\n\nCosto total: "<<costoTotal(costos,asignacion)
<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Con esto, el primer algoritmo puede ser implementado como sigue:
void asignacionOptima(int b[TAMANO][TAMANO],
int x[TAMANO][TAMANO]){
int trabajador,tarea;

for(trabajador=1;trabajador<=N;trabajador++)
for(tarea=1;tarea<=N;tarea++)
x[trabajador][tarea]=FALSE;


51
for(trabajador=1;trabajador<=N;trabajador++)
x[trabajador][mejorTarea(b,x,trabajador)]=TRUE;
}
La funcin MejorTarea es la que busca la mejor tarea an no asignada para ese trabajador:
int mejorTarea(int b[TAMANO][TAMANO],int x[TAMANO][TAMANO],
int i){
int tarea,min,mejorTarea;

min=65535;
for(tarea=1;tarea<=N;tarea++){
if(!yaEscogida(x,i,tarea) && b[i][tarea]<min){
min=b[i][tarea];
mejorTarea=tarea;
}
}
return mejorTarea;
}
Por ltimo, la funcin YaEscogida decide si una tarea ya ha sido asignada previamente:
int yaEscogida(int x[TAMANO][TAMANO],int trabajador,
int tarea){
int i;

for(i=1;i<=trabajador-1;i++)
if(x[i][tarea])
return TRUE;
return FALSE;
}

void imprimir(int x[TAMANO][TAMANO]){
int i,j;

for(i=1;i<=N;i++){
for(j=1;j<=N;j++)
cout<<x[i][j]<<"\t";
cout<<endl;
}
}

int costoTotal(int b[TAMANO][TAMANO],int x[TAMANO][TAMANO]){
int costo,trabajador,tarea;

costo=0;
for(trabajador=1;trabajador<=N;trabajador++)
for(tarea=1;tarea<=N;tarea++)
costo=costo+b[trabajador][tarea]*x[trabajador][tarea];
return costo;
}
Salida del programa:

52

Lamentablemente, este algoritmo vido no funciona para todos los casos como pone de manifiesto la siguiente matriz
de valores:
Tarea

1 2 3
1 16 20 18
2 11 15 17 Trabajador
3 17 1 20
Para ella, el algoritmo produce una matriz de asignaciones en donde los unos estn en las posiciones (1,1), (2,2) y
(3,3), esto es, asigna la tarea i al trabajador i (i = 1, 2 ,3), con un valor de la asignacin de 51 (= 16 + 15 + 20). Sin
embargo la asignacin ptima se consigue con los unos en posiciones (1,3), (2,1) y (3,2), esto es, asigna la tarea 3
al trabajador 1, la 1 al trabajador 2 y la tarea 2 al trabajador 3, con un valor de la asignacin de 30 (= 18 + 11 + 1).
Si utilizamos la segunda estrategia nos encontramos en una situacin anloga. En primer lugar, su implementacin
es:
#include <cstdlib>
#include <iostream>
using namespace std;

#define N 4
#define TAMANO N+1
#define TRUE 1
#define FALSE 0

void asignacionOptima(int b[TAMANO][TAMANO],
int x[TAMANO][TAMANO]);
int mejorTrabajador(int b[TAMANO][TAMANO],
int x[TAMANO][TAMANO],int i);
int yaEscogida(int x[TAMANO][TAMANO],int trabajador,
int tarea);
void imprimir(int x[TAMANO][TAMANO]);
int costoTotal(int b[TAMANO][TAMANO],int x[TAMANO][TAMANO]);

int main(int argc,char *argv[]){
int costos[TAMANO][TAMANO]={{0,0,0,0,0},
{0,3,4,5,7},
{0,5,4,6,9},
{0,2,3,8,7},
{0,4,5,3,4}};
int asignacion[TAMANO][TAMANO];

asignacionOptima(costos,asignacion);
cout<<"Matriz de costos"<<endl;
imprimir(costos);
cout<<"\n\nMatriz de asignacion"<<endl;
imprimir(asignacion);
cout<<"\n\nCosto total: "<<costoTotal(costos,asignacion)
<<endl;
system("PAUSE");

53
return EXIT_SUCCESS;
}

void asignacionOptima(int b[TAMANO][TAMANO],
int x[TAMANO][TAMANO]){
int trabajador,tarea;

for(trabajador=1;trabajador<=N;trabajador++)
for(tarea=1;tarea<=N;tarea++)
x[trabajador][tarea]=FALSE;

for(tarea=1;tarea<=N;tarea++)
x[mejorTrabajador(b,x,tarea)][tarea]=TRUE;
}
La funcin MejorTrabajador es la que busca el mejor trabajador an no asignado para esa tarea:
int mejorTrabajador(int b[TAMANO][TAMANO],int x[TAMANO][TAMANO],int i){
int trabajador,min,mejorTrabajador;

min=65535;
for(trabajador=1;trabajador<=N;trabajador++){
if(!yaEscogida(x,trabajador,i) && b[trabajador][i]<min){
min=b[trabajador][i];
mejorTrabajador=trabajador;
}
}
return mejorTrabajador;
}
Por ltimo, la funcin YaEscogido decide si un trabajador ya ha sido asignado previamente:
int yaEscogida(int x[TAMANO][TAMANO],int trabajador,
int tarea){
int i;

for(i=1;i<=tarea-1;i++)
if(x[trabajador][i])
return TRUE;
return FALSE;
}

void imprimir(int x[TAMANO][TAMANO]){
int i,j;

for(i=1;i<=N;i++){
for(j=1;j<=N;j++)
cout<<x[i][j]<<"\t";
cout<<endl;
}
}

int costoTotal(int b[TAMANO][TAMANO],int x[TAMANO][TAMANO]){
int costo,trabajador,tarea;

costo=0;
for(trabajador=1;trabajador<=N;trabajador++)
for(tarea=1;tarea<=N;tarea++)
costo=costo+b[trabajador][tarea]*x[trabajador][tarea];
return costo;
}

54
Salida del programa:

Lamentablemente, este algoritmo vido tampoco funciona para todos los casos como pone de manifiesto la siguiente
matriz de valores:
Tarea

1 2 3
1 16 11 17
2 20 15 1 Trabajador
3 18 17 20
Para ella, el algoritmo produce una matriz de asignaciones en donde los unos vuelven a estar en las posiciones
(1,1), (2,2) y (3,3), con un valor de la asignacin de 51 (=16+15+20). Sin embargo la asignacin ptima se consigue
con los unos en posiciones (3,1), (1,2) y (2,3), con un valor de la asignacin de 30 (=18+11+1).
Respecto a la pregunta de si una estrategia es mejor que la otra, la respuesta es que no. La razn es que las
soluciones son simtricas. An ms, una es la imagen especular de la otra. Por tanto, si suponemos equiprobables
los valores de las matrices, ambos algoritmos van a tener el mismo nmero de casos favorables y desfavorables.
PROGRAMACIN DINMICA
Existe una serie de problemas cuyas soluciones pueden ser expresadas recursivamente en trminos matemticos, y
posiblemente la manera ms natural de resolverlos es mediante un algoritmo recursivo. Sin embargo, el tiempo de
ejecucin de la solucin recursiva, normalmente de orden exponencial y por tanto impracticable, puede mejorarse
substancialmente mediante la Programacin Dinmica.
En el diseo Divide y Vencers veamos cmo para resolver un problema lo dividamos en subproblemas
independientes, los cuales se resolvan de manera recursiva para combinar finalmente las soluciones y as resolver el
problema original. El inconveniente se presenta cuando los subproblemas obtenidos no son independientes sino que
existe solapamiento entre ellos; entonces es cuando una solucin recursiva no resulta eficiente por la repeticin de
clculos que conlleva. En estos casos es cuando la Programacin Dinmica nos puede ofrecer una solucin
aceptable. La eficiencia de esta tcnica consiste en resolver los subproblemas una sola vez, guardando sus
soluciones en una tabla para su futura utilizacin.
La Programacin Dinmica no slo tiene sentido aplicarla por razones de eficiencia, sino porque adems presenta un
mtodo capaz de resolver de manera eficiente problemas cuya solucin ha sido abordada por otras tcnicas y ha
fracasado.
Donde tiene mayor aplicacin la Programacin Dinmica es en la resolucin de problemas de optimizacin. En este
tipo de problemas se pueden presentar distintas soluciones, cada una con un valor, y lo que se desea es encontrar la
solucin de valor ptimo (mximo o mnimo).
La solucin de problemas mediante esta tcnica se basa en el llamado principio de ptimo enunciado por Bellman en
1957 y que dice: En una secuencia de decisiones ptima toda subsecuencia ha de ser tambin ptima.
Hemos de observar que aunque este principio parece evidente no siempre es aplicable y por tanto es necesario
verificar que se cumple para el problema en cuestin. Un ejemplo claro para el que no se verifica este principio
aparece al tratar de encontrar el camino de coste mximo entre dos vrtices de un grafo ponderado.
Para que un problema pueda ser abordado por esta tcnica ha de cumplir dos condiciones:
La solucin al problema ha de ser alcanzada a travs de una secuencia de decisiones, una en cada etapa.
Dicha secuencia de decisiones ha de cumplir el principio de ptimo.
En grandes lneas, el diseo de un algoritmo de Programacin Dinmica consta de los siguientes pasos:

55
1. Planteamiento de la solucin como una sucesin de decisiones y verificacin de que sta cumple el principio
de ptimo.
2. Definicin recursiva de la solucin.
3. Clculo del valor de la solucin ptima mediante una tabla en donde se almacenan soluciones a problemas
parciales para reutilizar los clculos.
4. Construccin de la solucin ptima haciendo uso de la informacin contenida en la tabla anterior.
CLCULO DE LOS NMEROS DE FIBONACCI
Antes de abordar problemas ms complejos veamos un primer ejemplo en el cual va a quedar reflejada toda esta
problemtica. Se trata del clculo de los trminos de la sucesin de nmeros de Fibonacci. Dicha sucesin podemos
expresarla recursivamente en trminos matemticos de la siguiente manera:

Por tanto, la forma ms natural de calcular los trminos de esa sucesin es mediante un programa recursivo:
int fib(int n){
if(n<=1)
return 1;
else
return fib(n-1)+fib(n-2);
}
El inconveniente es que el algoritmo resultante es poco eficiente ya que su tiempo de ejecucin es de orden
exponencial. Como podemos observar, la falta de eficiencia del algoritmo se debe a que se producen llamadas
recursivas repetidas para calcular valores de la sucesin, que habindose calculado previamente, no se conserva el
resultado y por tanto es necesario volver a calcular cada vez.
Para este problema es posible disear un algoritmo que en tiempo lineal lo resuelva mediante la construccin de una
tabla que permita ir almacenando los clculos realizados hasta el momento para poder reutilizarlos:
Fib(0) Fib(1) Fib(2) ... Fib(n)
El algoritmo iterativo que calcula la sucesin de Fibonacci utilizando tal tabla es:
#include <cstdlib>
#include <iostream>

using namespace std;

int fib(int n);
int main(int argc, char *argv[]){
int n;

cout<<"Termino: ";
cin>>n;
cout<<"Termino "<<n<<": "<<fib(n)<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}

int fib(int n){
int t[n+1],i;

if(n<=1)
return 1;
else{
t[0]=1;
t[1]=1;
for(i=2;i<=n;i++)
t[i]=t[i-1]+t[i-2];

56
}
return t[n];
}
Existe an otra mejora a este algoritmo, que aparece al fijarnos que nicamente son necesarios los dos ltimos
valores calculados para determinar cada trmino, lo que permite eliminar la tabla entera y quedarnos solamente con
dos variables para almacenar los dos ltimos trminos:
int fib(int n){
int i,suma,x,y;
// x e y son los 2 ltimos trminos

if(n<=1)
return 1;
else{
x=1; y=1;
for(i=2;i<=n;i++){
suma=x+y; y=x; x=suma;
}
}
return suma;
}
Aunque esta funcin sea de la misma complejidad temporal que la anterior (lineal), consigue una complejidad
espacial menor, pues de ser de orden O(n) pasa a ser O(1) ya que hemos eliminado la tabla.
El uso de estructuras (vectores o tablas) para eliminar la repeticin de los clculos, pieza clave de los algoritmos de
Programacin Dinmica, hace que no slo en la complejidad temporal de los algoritmos estudiados, sino tambin en
su complejidad espacial.
En general, los algoritmos obtenidos mediante la aplicacin de esta tcnica consiguen tener complejidades (espacio y
tiempo) bastante razonables, pero debemos evitar que el tratar de obtener una complejidad temporal de orden
polinmico conduzca a una complejidad espacial demasiado elevada.
CLCULO DE LOS COEFICIENTES BINOMIALES
En la resolucin de un problema, una vez encontrada la expresin recursiva que define su solucin, muchas veces la
dificultad estriba en la creacin del vector o la tabla que ha de conservar los resultados parciales. As en este
segundo ejemplo, aunque tambin sencillo, observamos que vamos a necesitar una tabla bidimensional algo ms
compleja. Se trata del clculo de los coeficientes binomiales, definidos como:

El algoritmo recursivo que los calcula resulta ser de complejidad exponencial por la repeticin de los clculos que
realiza. No obstante, es posible disear un algoritmo con un tiempo de ejecucin de orden O(nk) basado en la idea del
Tringulo de Pascal. Para ello es necesaria la creacin de una tabla bidimensional en la que ir almacenando los
valores intermedios que se utilizan posteriormente:

57

Iremos construyendo esta tabla por filas de arriba hacia abajo y de izquierda a derecha mediante el siguiente
algoritmo de complejidad polinmica:
int coefBinomial(int n,int k);
int main(int argc, char *argv[]){
int n,k;

cout<<"Valor de n: ";
cin>>n;
cout<<"Valor de k: ";
cin>>k;
cout<<"Coeficiente binomial : "<<coefBinomial(n,k)<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}

int coefBinomial(int n,int k){
int i,j;
int c[n+1][n+1];

for(i=0;i<=n;i++) c[i][0]=1;
for(i=1;i<=n;i++) c[i][1]=i;
for(i=2;i<=k;i++) c[i][i]=1;
for(i=3;i<=n;i++)
for(j=2;j<=i-1;j++)
if(j<=k)
c[i][j]=c[i-1][j-1]+c[i-1][j];
return c[n][k];
}
Usando la recursividad:
int coefBinomial(int n,int k){
if(k==0) return 1;
else if(k==n) return 1;
else return coefBinomial(n-1,k-1)+coefBinomial(n-1,k);
}
EL CAMPEONATO MUNDIAL
Considere una competicin en la cual hay dos equipos A y B que juegan un mximo de 2n 1 partidas, y en donde el
ganador es el primer equipo que consiga n victorias. Suponemos que no hay empates, que los resultados de todos
los partidos son independientes, y que para cualquier partido dado hay una probabilidad constante p de que el equipo
A sea el ganador, y por tanto una probabilidad constante q=1-p de que gane el equipo B.
Sea P(i,j) la probabilidad de que el equipo A gane el campeonato, cuando todava necesita i victorias ms para
conseguirlo, mientras que el equipo B necesita j victorias ms para ganar. Por ejemplo, antes del primer partido del
campeonato la probabilidad de que gane el equipo A es P(n,n): ambos equipos necesitan todava n victorias para

58
ganar el campeonato. Si el equipo A necesita cero victorias ms, entonces lo cierto es que ya ha ganado el
campeonato, y por tanto P(0,i)=1, con 1<=i<=n. De manera similar, si el equipo B necesita 0 victorias mis, entonces
ya ha ganado el campeonato, y por tanto P(i,0)=0, con 1<=i<=n. Como no puede producirse una situacin en la que
ambos equipos hayan ganado todos los partidos que necesitaban, P(0, 0) carece de significado. Por ltimo dado que
el equipo A gana cualquier partido con una probabilidad p y pierde con una probabilidad q:
P(i, j) = pP(i-1,j)+qP(i,j-1), i >=1, j>=1
Entonces podemos calcular P(i,j) en la forma siguiente:
funcin P(i,j)
si i=0 entonces
devolver 1
sino si j=0 entonces
devolver 0
sino
devolver pP(i-1,j)+qP(i,j-1)

Figura 8 . Ll a m a d a s recu rs i va s ef ect u a d a s p or u n a l l a ma d a a P(i , j).
Implementacin en C++:
#include <cstdlib>
#include <iostream>

using namespace std;

float prob(int i,int j,float p);
int main(int argc, char *argv[]){
int i,j;
float p;

cout<<"probabilidad (p) de que gane: ";
cin>>p;
cout<<"Partidos que le faltan ganar a A : ";
cin>>i;
cout<<"Partidos que le faltan ganar a B: ";
cin>>j;
cout<<"P("<<i<<","<<j<<")="<<prob(i,j,p)<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}

float prob(int i,int j,float p){
if(i==0)
return 1;
else if(j==0)
return 0;
else
return p*prob(i-1,j,p)+(1-p)*prob(i,j-1,p);
}

59
Para acelerar el algoritmo, procederemos ms o menos igual que con el tringulo de Pascal: declaramos un vector
del tamao adecuado y despus vamos rellenando las entradas. Esta vez, sin embargo, en lugar de ir llenando el
vector lnea por lnea, vamos a trabajar diagonal por diagonal. ste es el algoritmo para calcular P(n,n). El algoritmo
implementado en C++ es:
#include <cstdlib>
#include <iostream>

using namespace std;

float serie(int n,float p);
int main(int argc, char *argv[]){
float p;
int n;

cout<<"Valor de n: ";
cin>>n;
cout<<"Probabilidad (p) de que gane: ";
cin>>p;
cout<<"P(n,n)="<<serie(n,p)<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}

float serie(int n,float p){
float P[n+1][n+1];
float q;
int s,k;

q=1-p;
// Llenamos desde la esquina izquierda hasta la diagonal principal
for(s=1;s<=n;s++){
P[0][s]=1;
P[s][0]=0;
for(k=1;k<=s-1;k++)
P[k][s-k]=p*P[k-1][s-k]+q*P[k][s-k-1];
}
// Llenamos desde debajo de la diagonal principal hasta la esuina derecha
for(s=1;s<=n;s++){
for(k=0;k<=n-s;k++)
P[s+k][n-k]=p*P[s+k-1][n-k]+q*P[s+k][n-k-1];
}
return P[n][n];
}
Salidas del programa:



60
Dado que el algoritmo tiene que llenar una matriz n x n, y dado que se necesita una constante temporal para calcular
cada entrada, su tiempo de ejecucin se encuentra en O(n
2
). Al igual que en el tringulo de Pascal, resulta sencillo
implementar este algoritmo de tal manera que baste con un espacio de almacenamiento en O(n).
DEVOLVER CAMBIO
Recuerde que el problema consiste en desarrollar un algoritmo para pagar una cierta cantidad a un cliente,
empleando el menor nmero posible de monedas. Describamos un algoritmo voraz para este problema.
Desafortunadamente, aunque el algoritmo voraz es muy eficiente, funciona solamente en un nmero limitado de
casos. Con ciertos sistemas monetarios, o cuando faltan monedas de una cierta denominacin (o su nmero es
limitado), el algoritmo puede encontrar una respuesta que no sea ptima, o incluso puede no hallar respuesta
ninguna.
Por ejemplo, supongamos que vivimos en un lugar en el cual hay monedas de 1, 4 y 6 unidades. Si tenemos que
cambiar 8 unidades, el algoritmo voraz propondr hacerlo con una moneda de 6 unidades y dos de una unidad, con
un total de tres monedas. Sin embargo, est claro que podemos hacerlo mejor: basta con dar al cliente su cambio
empleando tan slo dos monedas de cuatro unidades. Aunque el algoritmo voraz no halla esta solucin, resulta
sencillo obtenerla empleando programacin dinmica.
Como en la seccin anterior, el quid del mtodo consiste en preparar una tabla que contenga resultados intermedios
tiles, que sern combinados en la solucin del caso que estamos considerando. Supongamos que el sistema
monetario que estamos considerando tiene monedas de n denominaciones diferentes, y que una moneda de
denominacin i, con 1<=i<=n tiene un valor de d
i
unidades. Supondremos, como es habitual, que todos los d
i
> 0. Por
el momento, supondremos tambin que se dispone de un suministro ilimitado de monedas de cada denominacin.
Por ltimo, supongamos que tenemos que dar al cliente monedas por valor de N unidades, empleando el menor
nmero posible de monedas.
Para resolver este problema mediante programacin dinmica, preparamos una tabla c[1..n, 0..N] con una fila para
cada denominacin posible y una columna para las cantidades que van desde 0 unidades hasta N unidades. En esta
tabla, c[i,j] ser el nmero mnimo de monedas necesarias para pagar una cantidad de j unidades, con 0<=j<=N,
empleando solamente monedas de las denominaciones desde 1 hasta i, con 1<=i<=n. La solucin del ejemplar, por
tanto, est dada por c[n,N] si lo nico que necesitamos saber es el nmero de monedas que se necesitan. Para
rellenar la tabla, obsrvese primero que c[i,0] es cero para todos los valores de i. Despus de esta inicializacin, la
tabla se puede rellenar o bien fila por fila de izquierda a derecha, o bien columna por columna avanzando hacia abajo.
Para abonar una cantidad j utilizando monedas de las denominaciones entre 1 e i, tenemos dos opciones en general.
En primer lugar, podemos decidir que no utilizaremos monedas de la denominacin i, aun cuando esto est permitido
ahora, en cuyo caso c[i,j] = c[i1,j]. Como alternativa, podemos decidir que emplearemos al menos una moneda de la
denominacin i. En este caso, una vez que hayamos entregado la primera moneda de esta denominacin, quedan
por pagar j d
i
unidades. Para pagar esto se necesitan c[i,jd
i
] unidades, as que c[i,j]=1+c[i,j-d
i
]. Dado que deseamos
minimizar el nmero de monedas utilizadas, seleccionaremos aquella alternativa que sea mejor. En general, por
tanto:
c[i,j] = mn(c[i1,j],1+c[i,j-d
i
])
Cuando i = 1, uno de los elementos que hay que comparar cae fuera de la tabla. Lo mismo sucede cuando j<d
i
.
Resulta cmodo pensar que tales elementos poseen el valor +. Si i = 1 y j<d
1
, entonces los dos elementos que hay
que comparar caen fuera de la tabla. En este caso, hacemos c[i,j] igual a + para indicar que es imposible pagar una
cantidad j empleando solamente monedas del tipo 1.
La figura 9 ilustra el caso en el que tenamos que pagar 8 unidades con monedas que valan 1, 4 y 6 unidades. Por
ejemplo, c[3,8] se obtiene en este caso como el menor de c[2,8]=2 y 1+c[3,8-d
3
] =1+c[3,2]=3. Las entradas en el resto
de la tabla se obtienen de forma similar. La respuesta para este caso concreto es que podemos pagar ocho unidades
empleando nicamente dos monedas. De hecho, la tabla nos da la solucin de nuestro problema para todos los
casos que supongan un pago de 8 unidades o menos.
Cantidad: 0 1 2 3 4 5 6 7 8
d1=1 0 1 2 3 4 5 6 7 8
d2=4 0 1 2 3 1 2 3 4 2
d3=6 0 1 2 3 1 2 1 2 2
Figura 9 . Devolver cambio empleando programacin dinmica.
Vase a continuacin la implementacin del algoritmo usando la programacin dinmica:
#include <cstdlib>
#include <iostream>
using namespace std;

61
#define n 4

int monedas(int N);
int min(int a,int b);
int main(int argc,char *argv[]){
int monto;

cout<<"Monto a cambiar: ";
cin>>monto;
cout<<"Numero de monedas a entregar: "<<monedas(monto)<<endl;

system("PAUSE");
return EXIT_SUCCESS;
}

int monedas(int N){
// Devuelve el mnimo nmero de monedas necesarias
// para cambiar N unidades.
// El vector d[1..n] especifica las denominaciones;
// en el ejemplo hay monedas de 1, 4 y 6 unidades
int d[n]={0,1,4,6};
int c[n][N+1];
int i,j;

for(i=1;i<=n;i++)
c[i][0]=0;
for(i=1;i<=n;i++)
for(j=1;j<=N;j++)
if(i==1 && j<d[i])
c[i][j]=-1;
else if(i==1)
c[i][j]=1+c[1][j-d[1]];
else if(j<d[i])
c[i][j]=c[i-1][j];
else
c[i][j]=min(c[i-1][j],1+c[i][j-d[i]]);
return c[n][N];
}

int min(int a,int b){
if(a<b)
return a;
else
return b;
}
Implementacin recursiva del problema cambio a monedas:
#include <cstdlib>
#include <iostream>
using namespace std;

int monedas(int i, int j);
int min(int a,int b);

int main(int argc,char *argv[]){
int monto;

cout<<"Monto a cambiar: ";
cin>>monto;

62
cout<<"Numero de monedas a entregar: "<<monedas(3,monto)<<endl;

system("PAUSE");
return EXIT_SUCCESS;
}

int monedas(int i, int j){
int d[]={0,1,4,6};

if(j==0)
return 0;
else if(i==1 && j<d[i])
return -1;
else if(i==1)
return 1+monedas(1,j-d[1]);
else if(j<d[i])
return monedas(i-1,j);
else
return min(monedas(i-1,j),1+monedas(i,j-d[i]));
}

int min(int a,int b){
if(a<b)
return a;
else
return b;
}

Si est disponible un suministro inagotable de monedas con un valor de una unidad, entonces siempre podemos
hallar una solucin para nuestro problema. De no ser as, puede haber valores de N para los cuales no exista una
solucin. Esto sucede, por ejemplo, si todas las monedas representan un nmero par de unidades, y se nos pide que
paguemos un nmero impar de unidades. En tales casos, el algoritmo devuelve el resultado artificial +. El problema
invita a modificar el algoritmo para abordar una situacin en que est limitado el suministro de monedas de una cierta
denominacin.
Aun cuando el algoritmo slo parece decir cuntas monedas se necesitan para dar el cambio correspondiente a una
determinada cantidad, es fcil descubrir las monedas que se necesitan una vez que se ha construido la tabla c.
Supongamos que es preciso abonar una cantidad j empleando monedas de las denominaciones 1, 2, ..., i. Entonces
el valor de c[i,j] dice cuntas monedas se van a necesitar. Si c[i,j] = c[i-1, j] entonces no se necesitan monedas de la
denominacin i, y pasamos a c[i -1, j], para ver lo que hay que hacer a continuacin; si c[i,j]=1+c[i,j-d
i
] entonces
entregamos una moneda de denominacin i, que vale d
i
y avanzamos hacia la izquierda hasta c[i,j-di] para ver lo que
hay que hacer a continuacin. Si c[i-1,j] y 1+c[i,j-d
i
] son ambos iguales a c[i,j] entonces podemos seleccionar
cualquiera de las dos posibilidades. Siguiendo de esta manera, volveremos eventualmente a c[0,0] y ya no quedar
nada por abonar. Esta fase del algoritmo es esencialmente un algoritmo voraz que basa sus decisiones en la
informacin de la tabla, y no tiene que retroceder nunca.
El anlisis del algoritmo es directo. Para ver cuntas monedas se necesitan para dar un cambio de N unidades
cuando estn disponibles monedas de n denominaciones diferentes, el algoritmo tiene que rellenar una matriz
nx(N+1), as que el tiempo de ejecucin est en O(nN). Para ver las monedas que habra que utilizar, la bsqueda
que retrocede desde c[n,N] hasta c[0, 0] da n-1 pasos hasta la fila de encima (que corresponde a no utilizar una
moneda de la denominacin actual) y c[n,N] pasos hacia la izquierda (que corresponde a entregar una moneda).
Dado que cada uno de estos pasos se puede dar en un tiempo constante, el tiempo total que se requiere est en
O(n+c[n,N]).
INTERESES BANCARIO
Dadas n funciones f
1
, f
2
, ..., f
n
y un entero positivo M, deseamos maximizar la funcin f
1
(x
1
) + f
2
(x
2
) + ... + f
n
(x
n
) sujeta a
la restriccin x
1
+x
2
+ ... + x
n
= M, donde f
i
(0)=0 (i=1,..,n), x
i
son nmeros naturales, y todas las funciones son
montonas crecientes, es decir, x y implica que f
i
(x) > f
i
(y). Supngase que los valores de cada funcin se
almacenan en un vector.

63
Este problema tiene una aplicacin real muy interesante, en donde f
i
representa la funcin de inters que proporciona
el banco i, y lo que deseamos es maximizar el inters total al invertir una cantidad determinada de dinero M. Los
valores x
i
van a representar la cantidad a invertir en cada uno de los n bancos.
SOLUCIN
Sea f
i
un vector que almacena el inters del banco i (1 i n) para una inversin de 1, 2, 3, ..., M pesetas. Esto es,
f
i
(j) indicar el inters que ofrece el banco i para j pesetas, con 0 < i n , 0 < j M.
Para poder plantear el problema como una sucesin de decisiones, llamaremos I
n
(M) al inters mximo al invertir M
pesetas en n bancos,
I
n
(M) = f
1
(x
1
) + f
2
(x
2
) + ... + f
n
(x
n
)
que es la funcin a maximizar, sujeta a la restriccin x
1
+x
2
+ ... + x
n
= M.
Veamos cmo aplicar el principio de ptimo. Si I
n
(M) es el resultado de una secuencia de decisiones y resulta ser
ptima para el problema de invertir una cantidad M en n bancos, cualquiera de sus subsecuencias de decisiones ha
de ser tambin ptima y as la cantidad
I
n1
(M x
n
) = f
1
(x
1
) + f
2
(x
2
) + ... + f
n1
(x
n1
)
ser tambin ptima para el subproblema de invertir (M x
n
) pesetas en n 1 bancos. Y por tanto el principio de
ptimo nos lleva a plantear la siguiente relacin en recurrencia:

Para resolverla y calcular I
n
(M), vamos a utilizar una matriz I de dimensin nxM en donde iremos almacenando los
resultados parciales y as eliminar la repeticin de los clculos. El valor de I[i,j] va a representar el inters de j pesetas
cuando se dispone de i bancos, por tanto la solucin buscada se encontrar en I[n,M]. Para guardar los datos iniciales
del problema vamos a utilizar otra matriz F, de la misma dimensin, y donde F[i,j] representa el inters del banco i
para j pesetas.
En consecuencia, para calcular el valor pedido de I[n,M] rellenaremos la tabla por filas, empezando por los valores
iniciales de la ecuacin en recurrencia, y segn el siguiente algoritmo:
int max(int F[FILAS][COLUMNAS],int I[FILAS][COLUMNAS],int i,int j);
int max2(int a,int b);
void imprimir(int a[FILAS][COLUMNAS]);

int main(int argc,char *argv[]){
int f[FILAS][COLUMNAS]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 3, 4, 5, 8,10,11,12,12,12},
{0, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7},
{0, 2, 3, 4, 5, 6, 7, 8, 9,10,11}};
int i[FILAS][COLUMNAS];

cout<<"Intereses: "<<intereses(f,i)<<endl;
cout<<"\nTabla de intereses"<<endl;
imprimir(f);
cout<<"\nTabla de resultados"<<endl;
imprimir(i);

system("PAUSE");
return EXIT_SUCCESS;
}

int intereses(int F[FILAS][COLUMNAS],int I[FILAS][COLUMNAS]){
int i,j;

for(i=1;i<=n;i++)

64
for(j=0;j<=M;j++)
I[i][j]=0;

for(i=1;i<=n;i++)
I[i][0]=0;
for(j=1;j<=M;j++)
I[1][j]=F[1][j];
for(i=2;i<=n;i++)
for(j=1;j<=M;j++)
I[i][j]=max(F,I,i,j);
return I[n][M];
}

int max(int F[FILAS][COLUMNAS],int I[FILAS][COLUMNAS],int i,int j){
int maximo,t;

maximo=I[i-1][j]+F[i][0];
for(t=1;t<=j;t++)
maximo=max2(maximo,I[i-1][j-t]+F[i][t]);
return maximo;
}

int max2(int a,int b){
if(a>=b)
return a;
else
return b;
}

void imprimir(int a[FILAS][COLUMNAS]){
int i,j;

cout.setf(ios::fixed);
for(i=1;i<=n;i++){
for(j=0;j<=M;j++){
cout.width(5);
cout.precision(3);
cout<<a[i][j];
}
cout<<endl;
}
}
La funcin Max es la que calcula el mximo que aparece en la expresin recursiva. La funcin Max2 es la que calcula
el mximo de dos nmeros naturales.
La complejidad del algoritmo completo es de orden O(nM
2
). En este ejemplo queda de manifiesto la efectividad del
uso de estructuras en los algoritmos de Programacin Dinmica para conseguir obtener tiempos de ejecucin de
orden polinmico, frente a los tiempos exponenciales de los algoritmos recursivos iniciales.
EL VIAJE MS BARATO POR RO
Sobre el ro Guadalhorce hay n embarcaderos. En cada uno de ellos se puede alquilar un bote que permite ir a
cualquier otro embarcadero ro abajo (es imposible ir ro arriba). Existe una tabla de tarifas que indica el coste del
viaje del embarcadero i al j para cualquier embarcadero de partida i y cualquier embarcadero de llegada j ms abajo
en el ro (i < j). Puede suceder que un viaje de i a j sea ms caro que una sucesin de viajes ms cortos, en cuyo
caso se tomara un primer bote hasta un embarcadero k y un segundo bote para continuar a partir de k. No hay coste
adicional por cambiar de bote.

65
Nuestro problema consiste en disear un algoritmo eficiente que determine el coste mnimo para cada par de puntos
i,j (i < j) y determinar, en funcin de n, el tiempo empleado por el algoritmo.
SOLUCIN
Llamaremos T[i,j] a la tarifa para ir del embarcadero i al j (directo). Estos valores se almacenarn en una matriz
triangular superior de orden n, siendo n el nmero de embarcaderos.
El problema puede resolverse mediante Programacin Dinmica ya que para calcular el coste ptimo para ir del
embarcadero i al j podemos hacerlo de forma recurrente, suponiendo que la primera parada la realizamos en un
embarcadero intermedio k (i < k j):
C(i,j) = T(i,k) + C(k,j).
En esta ecuacin se contempla el viaje directo, que corresponde al caso en el que k coincide con j. Esta ecuacin
verifica tambin que la solucin buscada C(i,j) satisface el principio del ptimo, pues el coste C(k,j), que forma parte
de la solucin, ha de ser, a su vez, ptimo. Podemos plantear entonces la siguiente expresin de la solucin:

La idea de esta segunda expresin surge al observar que en cualquiera de los trayectos siempre existe un primer
salto inicial ptimo.
Para resolverla segn la tcnica de Programacin Dinmica, hace falta utilizar una estructura para almacenar
resultados intermedios y evitar la repeticin de los clculos. La estructura que usaremos es una matriz triangular de
costes C[i,j], que iremos rellenando por diagonales mediante el procedimiento que hemos denominado Costes. La
solucin al problema es la propia tabla, y sus valores C[i,j] indican el coste ptimo para ir del embarcadero i al j.
#include <cstdlib>
#include <iostream>
using namespace std;
#define MAXEMBARCADEROS 10
#define FILAS MAXEMBARCADEROS+1
#define COLUMNAS MAXEMBARCADEROS+1

void costes(int C[FILAS][COLUMNAS],int n);
int min(int C[FILAS][COLUMNAS],int i,int j);
int min2(int a,int b);

void imprimir(int a[FILAS][COLUMNAS]);

int T[FILAS][COLUMNAS]={
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 3,14,15, 6, 7, 8, 9,10},
{0, 0, 4, 4, 5, 5, 5, 5, 6, 6, 7},
{0, 0, 0, 4, 5, 5, 5, 5, 6, 6, 7},
{0, 0, 0, 0, 5,15, 5, 5, 6, 6, 7},
{0, 0, 0, 0, 0, 5, 5, 5, 6, 6,17},
{0, 0, 0, 0, 0, 0, 5,12, 6, 6, 7},
{0, 0, 0, 0, 0, 0, 0, 5, 6, 6,17},
{0, 0, 0, 0, 0, 0, 0, 0, 6, 6,17},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0,11}};
int main(int argc,char *argv[]){

int C[FILAS][COLUMNAS];

costes(C,MAXEMBARCADEROS);
cout<<"\nTabla de tarifa"<<endl;
imprimir(T);
cout<<"\nTabla de costos optimos"<<endl;

66
imprimir(C);

system("PAUSE");
return EXIT_SUCCESS;
}

void costes(int C[FILAS][COLUMNAS],int n){
int i,j,diagonal;

// condiciones iniciales
for(i=1;i<=MAXEMBARCADEROS;i++)
for(j=1;j<=MAXEMBARCADEROS;j++)
C[i][j]=0;
for(i=1;i<=n;i++)
C[i][i]=0;
for(diagonal=1;diagonal<=n-1;diagonal++)
for(i=1;i<=n-diagonal;i++)
C[i][i+diagonal]=min(C,i,i+diagonal);
}

int min(int C[FILAS][COLUMNAS],int i,int j){
int k,minimo;

minimo=65535;
for(k=i+1;k<=j;k++)
minimo=min2(minimo,T[i][k]+C[k][j]);
return minimo;
}

int min2(int a,int b){
if(a<b)
return a;
else
return b;
}

void imprimir(int a[FILAS][COLUMNAS]){
int i,j;

cout.setf(ios::fixed);
for(i=1;i<=MAXEMBARCADEROS;i++){
for(j=1;j<=MAXEMBARCADEROS;j++){
cout.width(5);
cout.precision(3);
cout<<a[i][j];
}
cout<<endl;
}
}
Dicho algoritmo utiliza la siguiente funcin min, que permite calcular la expresin del mnimo que aparece en la
ecuacin en recurrencia. La funcin Min2 es la que calcula el mnimo de dos nmeros naturales. Es importante
observar que esta funcin, por la forma en que se va rellenando la matriz C, slo hace uso de los elementos
calculados hasta el momento.
La complejidad del algoritmo es de orden O(n
3
).

67
VUELTA ATRAS
Dentro de las tcnicas de diseo de algoritmos, el mtodo de Vuelta Atrs (del ingls Backtracking) es uno de los de
ms amplia utilizacin, en el sentido de que puede aplicarse en la resolucin de un gran nmero de problemas, muy
especialmente en aquellos de optimizacin.
Los mtodos estudiados en los captulos anteriores construyen la solucin basndose en ciertas propiedades de la
misma; as en los algoritmos vidos se va construyendo la solucin por etapas, siempre avanzando sobre la solucin
parcial previamente calculada; o bien podremos utilizar la Programacin Dinmica para dar una expresin recursiva
de la solucin si se verifica el principio de ptimo, y luego calcularla eficientemente. Sin embargo ciertos problemas
no son susceptibles de solucionarse con ninguna de estas tcnicas, de manera que la nica forma de resolverlos es a
travs de un estudio exhaustivo de un conjunto conocido a priori de posibles soluciones, en las que tratamos de
encontrar una o todas las soluciones y por tanto tambin la ptima.
Para llevar a cabo este estudio exhaustivo, el diseo Vuelta Atrs proporciona una manera sistemtica de generar
todas las posibles soluciones siempre que dichas soluciones sean susceptibles de resolverse en etapas.
En su forma bsica la Vuelta Atrs se asemeja a un recorrido en profundidad dentro de un rbol cuya existencia slo
es implcita, y que denominaremos rbol de expansin. Este rbol es conceptual y slo haremos uso de su
organizacin como tal, en donde cada nodo de nivel k representa una parte de la solucin y est formado por k
etapas que se suponen ya realizadas.
Sus hijos son las prolongaciones posibles al aadir una nueva etapa. Para examinar el conjunto de posibles
soluciones es suficiente recorrer este rbol construyendo soluciones parciales a medida que se avanza en el
recorrido.
En este recorrido pueden suceder dos cosas. La primera es que tenga xito si, procediendo de esta manera, se llega
a una solucin (una hoja del rbol). Si lo nico que buscbamos era una solucin al problema, el algoritmo finaliza
aqu; ahora bien, si lo que buscbamos eran todas las soluciones o la mejor de entre todas ellas, el algoritmo seguir
explorando el rbol en bsqueda de soluciones alternativas.
Por otra parte, el recorrido no tiene xito si en alguna etapa la solucin parcial construida hasta el
momento no se puede completar; nos encontramos en lo que llamamos nodos fracaso. En tal
caso, el algoritmo vuelve atrs (y de ah su nombre) en su recorrido eliminando los elementos que
se hubieran aadido en cada etapa a partir de ese nodo. En este retroceso, si existe uno o ms
caminos an no explorados que puedan conducir a solucin, el recorrido del rbol contina por
ellos.
La filosofa de estos algoritmos no sigue unas reglas fijas en la bsqueda de las soluciones.
Podramos hablar de un proceso de prueba y error en el cual se va trabajando por etapas construyendo gradualmente
una solucin. Para muchos problemas esta prueba en cada etapa crece de una manera exponencial, lo cual es
necesario evitar.
Gran parte de la eficiencia (siempre relativa) de un algoritmo de Vuelta Atrs proviene de considerar el menor
conjunto de nodos que puedan llegar a ser soluciones, aunque siempre asegurndonos de que el rbol podado siga
conteniendo todas las soluciones. Por otra parte debemos tener cuidado a la hora de decidir el tipo de condiciones
(restricciones) que comprobamos en cada nodo a fin de detectar nodos fracaso. Evidentemente el anlisis de estas
restricciones permite ahorrar tiempo, al delimitar el tamao del rbol a explorar. Sin embargo esta evaluacin requiere
a su vez tiempo extra, de manera que aquellas restricciones que vayan a detectar pocos nodos fracaso no sern
normalmente interesantes. No obstante, y como norma de actuacin general, podramos decir que las restricciones
sencillas son siempre apropiadas, mientras que las ms sofisticadas que requieren ms tiempo en su clculo
deberan reservarse para situaciones en las que el rbol que se genera sea muy grande.
Vamos a ver como se lleva a cabo la bsqueda de soluciones trabajando sobre este rbol y su recorrido. En lneas
generales, un problema puede resolverse con un algoritmo Vuelta Atrs cuando la solucin puede expresarse como
una n-tupla [x1, x2, ..., xn] donde cada una de las componentes xi de este vector es elegida en cada etapa de entre
un conjunto finito de valores. Cada etapa representar un nivel en el rbol de expansin.
En primer lugar debemos fijar la descomposicin en etapas que vamos a realizar y definir, dependiendo del problema,
la n-tupla que representa la solucin del problema y el significado de sus componentes xi. Una vez que veamos las
posibles opciones de cada etapa quedar definida la estructura del rbol a recorrer. Vamos a ver a travs de un
ejemplo cmo es posible definir la estructura del rbol de expansin.

68
LAS N REINAS
Numeramos las reinas del 1 al 8. Cualquier solucin a este problema estar representada por una 8-tupla
[x1,x2,x3,x4,x5,x6,x7,x8] en la que cada xi representa la columna donde la reina de la fila i-sima es colocada. Una
posible solucin al problema es la tupla [4,6,8,2,7,1,3,5].
Para decidir en cada etapa cules son los valores que puede tomar cada uno de los elementos xi hemos de tener en
cuenta lo que hemos denominado restricciones a fin de que el nmero de opciones en cada etapa sea el menor
posible. En los algoritmos Vuelta Atrs podemos diferenciar dos tipos de restricciones:
Restricciones explcitas. Formadas por reglas que restringen los valores que pueden tomar los elementos xi a un
conjunto determinado. En nuestro problema este conjunto es S = {1,2,3,4,5,6,7,8}.
Restricciones implcitas. Indican la relacin existente entre los posibles valores de los xi para que stos puedan
formar parte de una n-tupla solucin. En el problema que nos ocupa podemos definir dos restricciones implcitas. En
primer lugar sabemos que dos reinas no pueden situarse en la misma columna y por tanto no puede haber dos xi
iguales (obsrvese adems que la propia definicin de la tupla impide situar a dos reinas en la misma fila, con lo cual
tenemos cubiertos los dos casos, el de las filas y el de las columnas). Por otro lado sabemos que dos reinas no
pueden estar en la misma diagonal, lo cual reduce el nmero de opciones. Esta condicin se refleja en la segunda
restriccin implicita que, en forma de ecuacin, puede ser expresada como |x x| |y y|, siendo (x,y) y (x,y) las
coordenadas de dos reinas en el tablero.
De esta manera, y aplicando las restricciones, en cada etapa k iremos generando slo las k-tuplas con posibilidad de
solucin. A los prefijos de longitud k de la ntupla solucin que vamos construyendo y que verifiquen las restricciones
expuestas los denominaremos k-prometedores, pues a priori pueden llevarnos a la solucin buscada. Obsrvese que
todo nodo generado es o bien fracaso o bien k-prometedor.
Con estas condiciones queda definida la estructura del rbol de expansin, que representamos a continuacin para
un tablero 4x4:

Como podemos observar se construyen 15 nodos hasta dar con una solucin al problema. El orden de generacin de
los nodos se indica con el subndice que acompaa a cada tupla.
Conforme vamos construyendo el rbol debemos identificar los nodos que corresponden a posibles soluciones y
cules por el contrario son slo prefijos suyos. Ello ser necesario para que, una vez alcanzados los nodos que sean
posibles soluciones, comprobemos si de hecho lo son.
Por otra parte es posible que al alcanzar un cierto nodo del rbol sepamos que ninguna prolongacin del prefijo de
posible solucin que representa va a ser solucin a la postre (debido a las restricciones). En tal caso es absurdo que
prosigamos buscando por ese camino, por lo que retrocederemos en el rbol (vuelta atrs) para seguir buscando por
otra opcin. Tales nodos son los que habamos denominado nodos fracaso.
Tambin es posible que aunque un nodo no se haya detectado a priori como fracaso (es decir, que sea k-prometedor)
ms adelante se vea que todos sus descendientes son nodos fracaso; en tal caso el proceso es el mismo que si lo

69
hubisemos detectado directamente. Tal es el caso para los nodos 2 y 3 de nuestro rbol. Efectivamente el nodo 2 es
nodo fracaso porque al comprobar una de las restricciones (estn en la misma diagonal) no se cumple. El nodo 3 sin
embargo es nodo fracaso debido a que sus descendientes, los nodos 4 y 5, lo son.
Por otra parte hemos de identificar aquellos nodos que pudieran ser solucin porque por ellos no se puede continuar
(hemos completado la n-tupla), y aquellos que corresponden a soluciones parciales. No por conseguir construir un
nodo hoja de nivel n quiere decir que hayamos encontrado una solucin, puesto que para los nodos hojas tambin es
preciso comprobar las restricciones. En nuestro rbol que representa el problema de las 4 reinas vemos cmo el nodo
8 podra ser solucin ya que hemos conseguido colocar las 4 reinas en el tablero, pero sin embargo la tupla [1,4,2,3]
encontrada no cumple el objetivo del problema, pues existen dos reinas x3 = 2 y x4 = 3 situadas en la misma
diagonal. Un nodo con posibilidad de solucin en el que detectamos que de hecho no lo es se comporta como nodo
fracaso.
En resumen, podemos decir que Vuelta Atrs es un mtodo exhaustivo de tanteo (prueba y error) que se caracteriza
por un avance progresivo en la bsqueda de una solucin mediante una serie de etapas. En dichas etapas se
presentan unas opciones cuya validez ha de examinarse con objeto de seleccionar una de ellas para proseguir con el
siguiente paso. Este comportamiento supone la generacin de un rbol y su examen y eventual poda hasta llegar a
una solucin o a determinar su imposibilidad. Este avance se puede detener cuando se alcanza una solucin, o bien
si se llega a una situacin en que ninguna de las soluciones es vlida; en este caso se vuelve al paso anterior, lo que
supone que deben recordarse las elecciones hechas en cada paso para poder probar otra opcin an no examinada.
Este retroceso (vuelta atrs) puede continuar si no quedan opciones que examinar hasta llegar a la primera etapa. El
agotamiento de todas las opciones de la primera etapa supondr que no hay solucin posible pues se habrn
examinado todas las posibilidades.
El hecho de que la solucin sea encontrada a travs de ir aadiendo elementos a la solucin parcial, y que el diseo
Vuelta Atrs consista bsicamente en recorrer un rbol hace que el uso de recursin sea muy apropiado. Los rboles
son estructuras intrnsecamente recursivas, cuyo manejo requiere casi siempre de recursin, en especial en lo que se
refiere a sus recorridos. Por tanto la implementacion ms sencilla se logra sin lugar a dudas con procedimientos
recursivos.
De esta forma llegamos al esquema general que poseen los algoritmos que siguen la tcnica de Vuelta Atrs:
PROCEDURE VueltaAtras(etapa);
BEGIN
IniciarOpciones;
REPEAT
SeleccionarNuevaOpcion;
IF Aceptable THEN
AnotarOpcion;
IF SolucionIncompleta THEN
VueltaAtras(etapa_siguiente);
IF NOT exito THEN
CancelarAnotacion
END
ELSE (* solucion completa *)
exito:=TRUE
END
END
UNTIL (exito) OR (UltimaOpcion)
END VueltaAtras;
En este esquema podemos observar que estn presentes tres elementos principales. En primer lugar hay una
generacin de descendientes, en donde para cada nodo generamos sus descendientes con posibilidad de solucin. A
este paso se le denomina expansin, ramificacin o bifurcacin. A continuacin, y para cada uno de estos
descendientes, hemos de aplicar lo que denominamos prueba de fracaso (segundo elemento). Finalmente, caso de
que sea aceptable este nodo, aplicaremos la prueba de solucin (tercer elemento) que comprueba si el nodo que es
posible solucin efectivamente lo es.
Tal vez lo ms difcil de ver en este esquema es donde se realiza la vuelta atrs, y para ello hemos de pensar en la
propia recursin y su mecanismo de funcionamiento, que es la que permite ir recorriendo el rbol en profundidad.
Para el ejemplo que nos ocupa, el de las n reinas, el algoritmo que lo soluciona quedara como sigue:
#include <cstdlib>
#include <iostream>

70
using namespace std;
#define N 8
int X[N+1]={0,0,0,0,0};
bool exito=false;
void reinas(int k);
bool valido(int k);
int valAbs(int x,int y);

int main(int argc,char *argv[]){
int i;

reinas(1);
if(exito){
cout<<"Solucion: "<<endl;
cout<<"[";
for(i=1;i<=N;i++){
cout<<X[i];
if(i!=N)
cout<<",";
}
cout<<"]"<<endl;
}
system("PAUSE");
return EXIT_SUCCESS;
}

void reinas(int k){
if(k>N)
return;
X[k]=0;
do{
X[k]=X[k]+1; // seleccion de nueva opcion
if(valido(k)){// prueba de fracaso
if(k!=N)
reinas(k+1); // llamada recursiva
else
exito=true;
}
}while(X[k]!=N && !exito);

}

bool valido(int k){
// comprueba si el vector solucion X construido hasta el paso k es
// k-prometedor, es decir, si la reina puede situarse en la columna k)
int i;

for(i=1;i<=k-1;i++)
if(X[i]==X[k] || valAbs(X[i],X[k])==valAbs(i,k))
return false;
return true;
}

int valAbs(int x,int y){
if(x>y)
return x-y;
else
return y-x;
}
La funcin Valido es la que comprueba las restricciones implcitas, realizando la prueba de fracaso y la funcin
ValAbs(x,y), que es la que devuelve |x y|.

71
Cuando se desea encontrar todas las soluciones habr que alterar ligeramente el esquema dado, de forma que una
vez conseguida una solucin se contine buscando hasta agotar todas las posibilidades. Queda por tanto el siguiente
esquema general para este caso:
PROCEDURE VueltaAtrasTodasSoluciones(etapa);
BEGIN
IniciarOpciones;
REPEAT
SeleccionarNuevaOpcion;
IF Aceptable THEN
AnotarOpcion;
IF SolucionIncompleta THEN
VueltaAtrasTodasSoluciones(etapa_siguiente);
ELSE
ComunicarSolucion
END;
CancelarAnotacion
END
UNTIL (UltimaOpcion);
END VueltaAtrasTodasSoluciones;
que en nuestro ejemplo de las reinas queda reflejado la siguiente implementacin:
#include <cstdlib>
#include <iostream>
using namespace std;
#define N 8
int X[N+1]={0,0,0,0,0};
void reinas(int k);
bool valido(int k);
int valAbs(int x,int y);
void comunicarSolucion(int X[N+1]);

int main(int argc,char *argv[]){
int i;

reinas(1);
system("PAUSE");
return EXIT_SUCCESS;
}

void reinas(int k){
// Encuentra todas las maneras de disponer n reinas
if(k>N)
return;
X[k]=0;
do{
X[k]=X[k]+1; // seleccion de nueva opcion
if(valido(k)){// prueba de fracaso
if(k!=N)
reinas(k+1); // llamada recursiva
else
comunicarSolucion(X);
}
}while(X[k]!=N);

}

bool valido(int k){
// comprueba si el vector solucion X construido hasta el paso k es
// k-prometedor, es decir, si la reina puede situarse en la columna k)
int i;


72
for(i=1;i<=k-1;i++)
if(X[i]==X[k] || valAbs(X[i],X[k])==valAbs(i,k))
return false;
return true;
}

int valAbs(int x,int y){
if(x>y)
return x-y;
else
return y-x;
}

void comunicarSolucion(int X[N+1]){
int i;

cout<<"Solucion: "<<endl;
cout<<"[";
for(i=1;i<=N;i++){
cout<<X[i];
if(i!=N)
cout<<",";
}
cout<<"]"<<endl;
}
Aunque la solucin ms utilizada es la recursin, ya que cada paso es una repeticin del anterior en condiciones
distintas (ms simples), la resolucin de este mtodo puede hacerse tambin utilizando la organizacin del rbol que
determina el espacio de soluciones. As, podemos desarrollar tambin un esquema general que represente el
comportamiento del algoritmo de Vuelta Atrs en su versin iterativa:
PROCEDURE VueltaAtrasIterativo;
BEGIN
k:=1;
WHILE k>1 DO
IF solucion THEN
ComunicarSolucion
ELSIF Fracaso(solucion) OR (k<n) THEN
DEC(k);
CalcularSucesor(k)
ELSE
INC(k);
CalcularSucesor(k)
END
END
END VueltaAtrasIterativo;
En este esquema tambin vemos presentes los tres elementos anteriores: prueba de solucin, prueba de fracaso y
generacin de descendientes.
Para cada nodo se realiza la prueba de solucin en cuyo caso se terminar el proceso y la prueba de fracaso que en
caso positivo da lugar a la vuelta atrs.
Observamos tambin que si la bsqueda de descendientes no consigue ningn hijo, el nodo se convierte en nodo
fracaso y se trata como en el caso anterior; en caso contrario la etapa se incrementa en uno y se contina.
Por otra parte la vuelta atrs busca siempre un hermano del nodo que estemos analizando descendiente de su mismo
padre - para pasar a su anlisis; si no existe tal hermano se decrementa la etapa k en curso y si k sigue siendo mayor
que cero (aun no hemos recorrido el rbol) se repite el proceso anterior.
El algoritmo iterativo para el problema de las n reinas puede implementarse por tanto utilizando este esquema, lo que
da lugar a la siguiente implementacin:
#include <cstdlib>
#include <iostream>
using namespace std;

73
#define N 8
int X[N+1];
void reinas();
bool valido(int k);
int valAbs(int x,int y);
void comunicarSolucion(int X[N+1]);

int main(int argc,char *argv[]){
int i;

reinas();
system("PAUSE");
return EXIT_SUCCESS;
}

void reinas(){
int k;

X[1]=0; k=1;
while(k>0){
X[k]=X[k] + 1; // selecciona nueva opcion
while(X[k]<=N && !valido(k)){ // fracaso?
X[k]=X[k]+1;
}
if(X[k]<=N)
if(k==N)
comunicarSolucion(X);
else{
k++;
X[k]=0;
}
else
k--; // vuelta atras
}
}

bool valido(int k){
// comprueba si el vector solucion X construido hasta el paso k es
// k-prometedor, es decir, si la reina puede situarse en la columna k)
int i;

for(i=1;i<=k-1;i++)
if(X[i]==X[k] || valAbs(X[i],X[k])==valAbs(i,k))
return false;
return true;
}

int valAbs(int x,int y){
if(x>y)
return x-y;
else
return y-x;
}

void comunicarSolucion(int X[N+1]){
int i;

cout<<"Solucion: "<<endl;
cout<<"[";
for(i=1;i<=N;i++){
cout<<X[i];
if(i!=N)

74
cout<<",";
}
cout<<"]"<<endl;
}
Hemos visto en este apartado cmo generar el rbol de expansin, pero sin prestar demasiada atencin al orden en
que lo hacemos. Usualmente los algoritmos Vuelta Atrs son de complejidad exponencial por la forma en la que se
busca la solucin mediante el recorrido en profundidad del rbol. De esta forma estos algoritmos van a ser de un
orden de complejidad al menos del nmero de nodos del rbol que se generen y este nmero, si no se utilizan
restricciones, es de orden de zn donde z son las posibles opciones que existen en cada etapa, y n el nmero de
etapas que es necesario recorrer hasta construir la solucin (esto es, la profundidad del rbol o la longitud de la n-
tupla solucin).
El uso de restricciones, tanto implcitas como explcitas, trata de reducir este nmero tanto como sea posible (en el
ejemplo de las reinas se pasa de 88 nodos si no se usa ninguna restriccin a poco ms de 2000), pero sin embargo
en muchos casos no son suficientes para conseguir algoritmos tratables, es decir, que sus tiempos de ejecucin
sean de orden de complejidad razonable.
Para aquellos problemas en donde se busca una solucin y no todas, es donde entra en juego la posibilidad de
considerar distintas formas de ir generando los nodos del rbol. Y como la bsqueda que realiza la Vuelta Atrs es
siempre en profundidad, para lograr esto slo hemos de ir variando el orden en el que se generan los descendientes
de un nodo, de manera que trate de ser lo ms apropiado a nuestra estrategia.
Como ejemplo, pensemos en el problema del laberinto que veremos ms adelante. En cada etapa vamos a ir
generando los posibles movimientos desde la casilla en la que nos encontramos. Pero en vez de hacerlo de cualquier
forma, sera interesante explorar primero aquellos que nos puedan llevar ms cerca de la casilla de salida, es decir,
tratar de ir siempre hacia ella.
Desde un punto de vista intuitivo, lo que intentamos hacer as es llevar lo ms hacia arriba posible del rbol de
expansin el nodo hoja con la solucin (dibujando el rbol con la raiz a la izquierda, igual que lo hemos hecho en el
problema de las reinas), para que la bsqueda en profundidad que realizan este tipo de algoritmos la encuentre
antes. En algunos ejemplos, como puede ser en el del juego del Continental, que tambin veremos ms adelante, el
orden en el que se generan los movimientos hace que el tiempo de ejecucin del algoritmo pase de varias horas a
slo unos segundos, lo cual no es despreciable.
RECORRIDOS DEL REY DE AJEDREZ
Dado un tablero de ajedrez de tamao nxn, un rey es colocado en una casilla arbitraria de coordenadas (x,y). El
problema consiste en determinar los n21 movimientos de la figura de forma que todas las casillas del tablero sean
visitadas una sola vez, si tal secuencia de movimientos existe.
SOLUCIN
La solucin al problema puede expresarse como una matriz de dimensin nxn que representa el tablero de ajedrez.
Cada elemento (x,y) de esta matriz solucin contendr un nmero natural k que indica el nmero de orden en que ha
sido visitada la casilla de coordenadas (x,y).
El algoritmo trabaja por etapas decidiendo en cada etapa k hacia donde se mueve. Como existen ocho posibles
movimientos en cada etapa, ste ser el nmero mximos de hijos que se generarn por cada nodo.
Respecto a las restricciones explcitas, por la forma en la que hemos definido la estructura que representa la solucin
(en este caso una matriz bidimensional de nmeros naturales), sabemos que sus componentes pueden ser nmeros
comprendidos entre cero (que indica que una casilla no ha sido visitada an) y n2, que es el orden del ltimo
movimiento posible. Inicialmente el tablero se encuentra relleno con ceros y slo existe un 1 en la casilla inicial
(x0,y0).
Las restricciones implcitas en este caso van a limitar el nmero de hijos que se generan desde una casilla mediante
la comprobacin de que el movimiento no lleve al rey fuera del tablero o sobre una casilla previamente visitada.
Una vez definida la estructura que representa la solucin y las restricciones que usaremos, para implementar el
algoritmo que resuelve el problema basta utilizar el esquema general, obteniendo:
#include <cstdlib>
#include <iostream>
#include <iomanip.h>
using namespace std;
#define N 4
#define DIMENSION N+1

75
int tablero[DIMENSION][DIMENSION];
int movX[9],movY[9];
void movimientosPosibles();
void rey(int k,int x,int y,bool &exito);

int main(int argc,char *argv[]){
int x0,y0,i,j;
bool exito;

x0=1;
y0=1;
movimientosPosibles();
for(i=1;i<=N;i++)
for(j=1;j<=N;j++)
tablero[i][j]=0;
// x0,y0 es la casilla inicial
tablero[x0][y0]=1;
rey(2,x0,y0,exito);
if(exito)
for(i=1;i<=N;i++){
for(j=1;j<=N;j++)
cout<<setw(3)<<tablero[i][j];
cout<<endl;
}
system("PAUSE");
return EXIT_SUCCESS;
}

void rey(int k,int x,int y,bool &exito){
// busca una solucion, si la hay. k indica la etapa, (x,y) las
// coordenadas de la casilla en donde se encuentra el rey
int orden; // recorre cada uno de los 8 movimientos
int u,v; // u,v indican la casilla destino desde x,y

orden=0;
exito=false;
do{
orden++;
u=x+movX[orden];
v=y+movY[orden];
if(1<=u && u<=N && 1<=v && v<=N && tablero[u][v]==0){
tablero[u][v]=k;
if(k<N*N){
rey(k+1,u,v,exito);
if(!exito)
tablero[u][v]=0;
}
else
exito=true;
}
}while(!exito && orden!=8);
}

void movimientosPosibles(){
movX[1]=0; movY[1]=1; movX[2]=-1; movY[2]=1;
movX[3]=-1; movY[3]=0; movX[4]=-1; movY[4]=-1;
movX[5]=0; movY[5]=-1; movX[6]=1; movY[6]=-1;
movX[7]=1; movY[7]=0; movX[8]=1; movY[8]=1;
}

You might also like