Está en la página 1de 43

El Mundo de los Algoritmos

Rafael Ángel Garcı́a Leiva

July 9, 2006
Prólogo

Hablar sobre la dificultad de encontrar referencias bibliográficas en castellano.

1
Chapter 1

El Arte de Programar

La mayorı́a de las ciudades de hoy en dı́a funcionan gracias a los ordenadores.


Sea cual sea el lugar a donde vayamos siempre encontraremos un ordenador
cerca. Son ya tan comunes que ni siquiera nos damos cuenta de que están ahı́,
pero basta con detenemos un momento y fijamos con atención para verlos, puede
incluso que los encontremos en los lugares más insospechados. En la oficina ten-
emos ordenadores que nos ayudan a hacer nuestro trabajo; en casa tenemos un
ordenador como centro de ocio, para juegos, música y video, o miniordenadores
empotrados dentro de los más variados electrodomésticos, como la televisión, el
equipo de alta fidelidad, e incluso en el microondas; en el supermercado de la
esquina hay unos cuantos ordenadores más, las cajas registradoras, los equipos
de inventario; en nuestro coche está el famoso ordenador de abordo, que vig-
ila que todo funciona correctamente; y ası́ un largo etcétera. Y también están
aquellos ordenadores que llevamos todo el dı́a de un lado a otro con nosotros,
como el teléfono móvil, la agenda electrónica, o el reloj. Hemos llegado a una
situación de dependencia tal que nos resulta muy difı́cil concebir el mundo sin
la existencia de los ordenadores1 .
Lo que nunca hacemos, a menos que uno sea un profesional que trabaja en
el mundo de la informática, es pararnos a reflexionar sobre cómo funcionanan
todos estos ordenadores. Nos han dicho muchas veces que los ordenadores son
en realidad máquinas muy tontas, que para funcionar correctamente necesitan
de un conjunto de instrucciones que les digan con todo lujo de detalles qué es lo
que tienen que hacer en cada momento, y cómo lo tienen que hacer. También
sabemos que estos conjuntos de instrucciones se llaman programas, y que los
programas se escriben utilizando los llamados lenguajes de programación, que
son muchos y muy variados. Seguramente nos suenen nombres como Visual
Basic, Java o C++. Pero en realidad sabemos muy poco sobre la forma y
contenido de estos programas, lo cual es perfectamente comprensible, porque a
priori parece un tema irrelevante y súmamente aburrido. ¿A quién se le ocur-
rirı́a estudiar en su tiempo libre el programa que controla un aparato de aire
1 Otra cuestión muy distinta es si realmente en todos los casos el ordenador nos hace la

vida más fácil, pero éste es un tema del que no vamos a hablar en este libro.

2
CHAPTER 1. EL ARTE DE PROGRAMAR 3

acondicionado? Sin embargo, los programas no son tan aburridos como parece.
En el corazón de los programas se encuentran los algoritmos. La mayorı́a de las
veces, estos algoritmos son simples sucesiones de pasos triviales que conducen a
la solución de un problema. Por ejemplo, si la temperatura de la habitación ha
superado los 25o centı́grados, entonces enciende el compresor del aire acondi-
cionado. Pero otras veces, los alogitmos que implementan los programas de
ordenador son verdaderas obras de arte del intelecto humano. Quién sabe, a lo
mejor el funcionamiento de nuestro aparato de aire acondicionado está basado
en una interesantı́sima teorı́a matemática de lógica difusa, donde las cosas no
son sólo ciertas o falsas, sino que pueden ser medio ciertas o tres cuartos falsas.
En este libro vamos a ver algunas de esas joyas de la programación que hay
escondidas dentro de los ordenadores. Veremos cómo problemas aparentemente
my simples requieren de algoritmos muy complejos para su solución, o de al-
goritmos que en la práctica resultan inútiles porque requieren cientos de años
de cálculo. También estudiaremos ejemplos del caso contrario, es decir, cómo
problemas que en un principio parecı́an intratables son resueltos de manera muy
simple, generalmente gracias a la ayuda de alguna idea brillante. Aprendere-
mos cómo comparar algoritmos, en base al tiempo que tardan en resolver un
problema, y a la cantidad de memoria que necesitan para ello, y veremos que
pueden existir enormes diferencias entre dos algoritmos que resuelven un mismo
problema. Por útimo estudiaremos las técnicas más comunes de resolución de
problemas de las que se valen los programadores para hacer su trabajo. Todo
ello mezclado con algunas notas históricas, anécdotas, preguntas sin respuesta
conocida, y sobre todo, con numerosos problemas propuestos para que el lector
pueda divertirse creando sus propios algoritmos.

1.1 Pero, ¿qué es un algoritmo?


Hasta ahora he asumido que el lector tiene al menos una idea intuitiva de lo
que es un algoritmo, y también que tiene unos conocimientos mı́nimos sobre el
funcionamiento de los ordenadores (qué es un programa de ordenador, cuál es la
diferencia entre hardware y software, etcétera). Pero para poder continuar con
nuestro viaje por el maravilloso mundo de los algoritmos, y para poder apreciar
mejor la belleza del paisaje que se nos ofrece, tenemos que definir de manera
más precisa qué entendemos por algoritmo y aclarar algunos conceptos básicos.
La Real Academia de la Lengua define la palabra algoritmo como conjunto
ordenado y finito de operaciones que permite hallar la solución de un problema.
Aunque quizás no seamos conscientes de ello, es muy normal que nos ayudemos
de algoritmos en nuestra vida contidiana, y no sólo cuando utilizamos orde-
nadores o realizamos cálculos algebráicos. Por ejemplo, cuando compramos una
estanterı́a barata de las de móntela usted mismo, junto con las tablas de madera
encontramos un folleto de instrucciones con un algoritmo que nos indica cómo
montarla; cuando nos subimos en nuestro coche seguimos un algoritmo preciso
que nos indica cómo conducir (introducir la llave, girarla a la posición de con-
tacto, volverla a girar hasta la posición de arranque, soltar la llave cuando el
CHAPTER 1. EL ARTE DE PROGRAMAR 4

motor arranque, pisar el embrague, introducir la marcha primera, etc); o cuando


invitamos a unos amigos a cenar a casa y queremos sorprenderlos con un menú
especial, consultamos un libro de recetas de cocina, que en definitiva no es otra
cosa que un libro lleno de algoritmos culinarios.
Veamos uno de estos ejemplos con más detalle. Imaginemos que queremos
hacer una tortilla de patatas pero, como nos ha pasado a todos en algún mo-
mento de nuestra vida, no sabemos cómo se hace. Ası́ que cojemos nuestro
libro de recetas de concina para hijos recién emancipados, y buscamos la receta
correspondiente a la tortilla. Seguramente el libro encontraremos algo parecido
a:

Trocear las patatas en taquitos de medio centı́metro cuadrado,


y freirlas en una sartén con aceite abundante y no muy caliente. Al
poco rato, añadir la cebolla y dejar que se frı́a junto con las patatas.
En un cuenco batimos los huevos, le echamos sal, y añadimos las
patatas y cebolla ya fritas, mezclándolo todo bien. Dejamos un poco
de aceite en la sarten y hechamos la mezcla. Esperamos a que se
cuaje un poco, le damos la vuelta, y la dejamos a fuego lento hasta
que termine de cuajar.

Evidentemente el párrafo anterior, aunque resulta fácil de entender para cualquier


ser humano, es totalmente incomprensible para un ordenador. Podrı́amos ree-
scribir la receta de manera algo más formal y precisa, intentando darle apariencia
de programa de ordenador (véase el cuadro titulado Algorimo 1), pero aun ası́
seguirı́a siendo un galimatı́as ininteligible para las máquinas. De hecho, aun
contando con instrucciones tan precisas de cómo se hace una tortilla de patatas,
es normal que a cada uno de nosotros nos salgan tortillas completamente difer-
entes (yo personalmente, aun no consigo entender qué hace mi mujer para que
a ella le salgan las tortillas mucho más ricas que a mi). El problema es que nue-
stro algoritmo “Tortilla de Patatas” contiene todavı́a muchos elementos que, o
bien tienen interpretaciones subjetivas (¿a qué temperatura se entiende que el
aceite no está muy caliente?), o bien están descritos de manera demasiado vaga
(¿qué se entiende por cuajar?), y por tanto, no son directamente interpretables
por un ordenador. Las recetas, tal y como nos las encontramos en los libros de
concina, no entrarı́an dentro de lo que en informática se conoce como proced-
imientos computacionalmente bien definidos, y por tanto, no son directamente
interpretables por los ordenadores. Los algoritmos utilizados en informática
tienen que ser bastante más precisos que nuestro ejemplo de la tortilla, y basase
en pasitos más pequeños.
Un algoritmo para ordenador está compuesto de un conjunto de pasos muy
simples y muy bien definidos. Ejemplos tı́picos de posibles pasos son: sumar
dos números dados, comparar si un número es mayor que otro, comprobar si
la tercerla letra de la palabra “ejemplo” es una “e”, etcétera. Estos pasos
suelen venir agrupados en bloques que se pueden repetir varias veces, también
existen pasos en los que es posible tomar decisiones, llamadas a grupos de pasos
comunes, etc. Además, para que el algoritmo esté completo, es fundamental
CHAPTER 1. EL ARTE DE PROGRAMAR 5

Algorithm 1 Tortilla de Patatas


1 Cortar patatas en tacos
2 Calentar aceite en sartén
3 A~nadir patatas a sartén
4 A~nadir cebolla a sartén
5 Freir patatas y cebolla
6 Batir huevos en cuenco
7 A~nadir patatas y cebolla al cuenco
8 Quitar aceite sartén
9 Hechar la mezcla de cuenco a sartén
10 Cuajar un poco y dar la vuelta
11 Terminar de cuajar

indicar cuales son los valores de entrada del mismo, y cuales son los resultados
o valores de salida. Por ejemplo, un algoritmo podrı́a tener como entrada un
conjunto de diez números (17, 1, 5, 13, 15, 9, 21, 2, 4, 8), y como salida devolver
el mismo conjunto de números pero ordenados de mayor a menor (21, 17, 15,
13, 9, 8, 5, 4, 2, 1).
Pero no se preocupe el lector si por ahora no entiende del todo cómo son
los algoritmos que utilizan los ordenadores, ya que todas estas cuestiones irán
quedando más claras a lo largo de este libro.

1.2 Un poco de historia


Antes de entrar a fondo en materia es conveniente dedicar algo de tiempo a
revisar los orı́genes de los algoritmos, cómo ha ido evolucionando el concepto de
algoritmo a lo largo de la historia, y sobre todo, quienes han sido los artı́fices
de todas estas ideas tan maravillosas.
Euclides de Alejandrı́a (325 adC, 265 adC) fue el más importante de los
matemáticos de la antigüedad, sin embargo, se sabe muy poco de su vida, ex-
cepto que enseñó en Alejandrı́a, y en Egipto. Euclides debió de haber estudiado
en la Academia de Platón en Atenas, donde aprendió la geometrı́a de Eudoxus
y Theaetetus, con los que estaba muy familiarizado. Su trabajo más conocido
es un tratado en matemáticas titulado Los Elementos. El tratado es un com-
pendio del conocimento matemático de la época, que se convirtió en el centro
de la enseñanza de las matemáticas durante más de 2000 años. La durabilidad
de los Elementos hacen de Euclides el profesor de matemáticas lı́der indiscutible
de la antigüedad, y quizás de todos los tiempos. Seguramente los resultados que
encontramos en Los Elementos no fueron demostrados por primera vez por Eu-
clides, pero la organización del material y la exposición son con toda seguridad
suyas. Desde que fue escrito y hasta la fecha, Los Elementos han constituido
una continua e importante influencia, consituyendo una fuente primaria para el
razonamiento geométrico, teoremas y métodos. A veces se dice que, junto con
CHAPTER 1. EL ARTE DE PROGRAMAR 6

Figura 1.1: Imágen de Al-Khwarizmi en un sello soviético

la Biblia, los Elementos puden ser el libro de texto más traducido, publicado y
estudiado del mundo occidental.
Los Elementos se dividen en 13 libros. Los libros de uno a seis tratan de la
geometrı́a del plano; los libros de siete a nueve tratan de la teorı́a de números;
el libro diez trata de la teorı́a de los números irracionales; y los libros de once
a trece tratan de la geometrı́a en tres dimensiones. En particular nos aquı́ nos
interesa el libro número siete, una introducción auto-contenida de la teorı́a de
números, porque contiene el famoso algoritmo de Euclides, un método muy as-
tuto para calcular el máximo común divisor de dos números (véase la Sección
1.3), y que está considerado como el primer alroritmo de la historia de la hu-
manidad.
La palabra algoritmo proviene del nombre del matemático persa del siglo
IX Abu Abdullah Muhammad bin Musa al-Khwarizm (véase Figura 1.1). Al-
Khwarizmi fue el matemático más influyente de su tiempo, y su legado incluye
algunos conceptos que hoy dı́a resultan básicos en álgebra. En realidad se conoce
muy poco sobre la vida de Al-Khwarizmi, se sabe que trabajó en la Casa de
la Sabidurı́a de Bagdad, y que su trabajo consistı́a fundamentalmente en la
traducción de manuscritros cientı́ficos griegos, aunque también hizo importantes
contribuciones en álgebra, geometrı́a y astronomı́a.
Sin duda, el trabajo más importante y conocido de Al-Khwarizmi es su
tratado sobre álgebra al-jabr w’al-muqabal. Este tratado es el primer libro de
la historia dónde se estudia en profundidad y de manera sistemática el álgebra,
y por tanto, se habla de Al-Khwarizmi como fundador de esta rama de la
matemática (de hecho, el tı́tulo del tratado al-jabr ha dado origen a la propia
palabra álgebra). El tratado sobre álgebra de Al-Khwarizmi también es impor-
tante porque describe el sistema posicional hindú de númeración, basado en los
guarismos decimales 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, y que ahora conocemos comun-
mente como números arábigos. Aunque no seamos consciente de ello, el sistema
posicional es fundamental en álgebra, y si no, trate el lector de multiplicar 728
por 293 utilizando el sistema de numeración romano (es decir DCCXXVIII por
CHAPTER 1. EL ARTE DE PROGRAMAR 7

Figure 1.2: Ada Lovelace

CCXCIII). En el tratado también se introduce el uso del cero como contenedor


de lugar en la notación basada en posiciones, otra idea fundamental para el
desarrollo actual del álgebra, que nos permite distinguir entre, por ejemplo, las
cantidades 202, 220 y 22. De echo, hay autores que opinan que la invencción
del cero fue más importante que la invención de la propia rueda.
Originalmente la palabra algoritmo se referı́a únicamente al conjunto de
reglas necesarias para realizar aritmética utilizando los números arábigos. Fue
en el sigo XVIII cuando el concepto evolucionó para incluir a todo procedimiento
bien definido para resolver un problema dado, o realizar una tarea concreta.
El primer ejemplo de algoritmo escrito especı́ficamente para un ordenador
fue realizado por Augusta Ada King (véase la Figura 1.2), Condesa de Lovelace,
en 1842. El ordenador en cuestión era el Ingenio Analı́tico de Charles Bab-
bage, concebido en 1834. El Ingenio Analı́tico de Babbage era una computa-
dora mecánica programable, de caracterı́sticas muy similares a los ordenadores
electrónicos modernos: disponı́a de un sistema de entrada de datos basado en
tarjetas perforadas, una memoria con capacidad para almacenar 1000 números
de 50 dı́gitos, una unidad capaz de realizar las cuatro operaciones aritméticas
básicas, y una impresora.
Ada Lovelace tradujo para Babbage el memorándum que el matemático Luigi
Menabrea habı́a escrito sobre el Ingenio Analı́tico. Junto a la traducción, Ada
añadió un conjunto de notas en las que describia en detalle un procedimiento
para calcular números de Bernoulli2 utilizando la máquina, y que ha sido recono-
cido por los historiadores como el primer programa de ordenador. En realidad
el Ingenio Analı́tico no era una máquina tangible, sino que se trataba de un con-
junto de diseños que Babbage fue perfeccionando a lo largo de su vida. Dado
que Babbage nunca llegó a construir su ingenio analı́tico, el algoritmo de Ada
Lovelace no se llegó a implementar. A pesar de ello, para muchos (incluido el
autor de este libro) Ada Lovelace es considerada como la primera programadora
2 Secuencia de números racionales con importantes implicaciones en teoria de números y

análisis matemático.
CHAPTER 1. EL ARTE DE PROGRAMAR 8

Figure 1.3: Alan Turing

de ordenadores de la historia. Ada Lovelace también es conocida, aparte de


por ser la única hija legı́tima del poeta Lord Byron, por haber dado nombre al
lenguaje de programación Ada3 .
La falta de rigor matemático en la definición algoritmo como procedimento
bien definido creó muchas dificultades a los matemáticos y lógicos del siglo XIX
y principios del XX. Este problema fue en gran medida resuelto gracias a la
descripción formal de algoritmo proporcionada por lo que hoy se conoce con el
nombre de Máquina de Turing, un modelo abstracto de computadora formulado
por el matemático británico Alan Mathision Turing en 1936.
Turing hizo importantes contribuciones en matemáticas, lógica y criptoanálisis.
Durante la segunda guerra mundial trabajó para los aliados descifrando los
mensajes del ejército alemán, y en concreto, descifrado el código secreto de la
famosa máquina Enigma. Una vez finalizada la guerra trabajó en el desarrollo
de software para uno de los primeros ordenadores de la histororia, el Mark I
de Manchester, además de que diseñó su propia computadora, llamada ACE,
que no llegó a construirse. Turing también es conocido por sus ideas en el área
de la inteligencia artificial, donde proporcionó un experimento conocido con el
nombre de Test de Turing que nos permite determinar si una máquina es o no
inteligente. Pero sin lugar a dudas, el trabajo más influyente de Turing es su
contribución a los fundamentos de la informática: actualmente las máquinas
de Turing constituyen una parte fundamental en el estudio de la teorı́a de la
computación.
Turing fue un homosexual durante una época donde la homosualidad era ile-
gal. En 1952 fue condenado por actitudes indecentes al admitir que habı́a man-
tenido relaciones sexuales con un hombre en Manchester. La condena también
3 Ada es un lenguaje de propósito general que fue desarrollado por el departamento de de-

fensa norteamericano en un intento de contener la torre de babel de lenguajes de programación


en la que se habı́an convertido los proyectos informáticos (en 1983 se estimaba que habı́a del
orden de 450 lenguajes de programación diferentes en uso en el departamento).
CHAPTER 1. EL ARTE DE PROGRAMAR 9

le impuso que siguiera una terapia hormonal para corregir su homosexualidad.


Dos años después, en Junio de 1954, Alan Turing fue encontrado muerto en su
casa al haber ingerido una manzana impregnada con cianuro. Si fue un sui-
cidio, o un accidente (Turing disponia de un laboratorio de fotografı́a en el que
utilizaba habitualmente cianuro), es algo que todavı́a no está resuelto.
Dede el año 1966 la asociación internacional de informática ACM (Associ-
ation for Computing Machinery) concede anualmente el premio Turing, que es
otorgado a aquellos investigadores que hayan destacado por sus contribuciones
técnicas a la comunidad informática. Estos premios estan considerados como
los equivalentes en informatica a los premios Nobel.

1.3 El algoritmo de Euclides


Por fin ha llegado el momento de ver nuestros primeros algoritmos. En concreto
vamos a estudiar dos ejemplos de algoritmos, que aunque son totalmente difer-
entes, resuelven exactamente el mismo problema: calculan el máximo común
divisor de dos números dados. El primero de ellos está basado en un método
más bien ingenuo para el cálculo del máximo común divisor; el segundo se basa
en una idea muy original que nos simplificará enormemente la tarea. Estos dos
algoritmos nos van a permitir introducir algunos conceptos importantes sobre
algorı́tmica y la programación de ordenadores, y también nos van a ayudar a
definir la notación que vamos a utilizar a lo largo de este libro para la descripción
de los algoritmos.
Antes de empezar a estudiar en detalle estos algoritmos, me gustarı́a asegu-
rarme de que todos mis lectores tienen los conocimientos necesarios para enten-
der lo que vamos a hacer. Para ello, aquellos lectores que no tengan experiencia
previa en la programación de ordenadores, y que no conozcan ningún lenguaje de
programación, deberı́an leer primero el Apendice 12 donde se proporciona una
introducción a la programación lo suficientemente detallada para entender los
contenidos de este libro. Aquellos lectores que tengan cierta experiencia previa
en el manejo de ordenadores, y que conozcan algún lenguaje de programación,
pueden continuar leyendo directamente, aunque también se les recomienda que
hechen un vistazo rápido al Apéndice 12, con la idea de familizarizarse con la
notación en pseudocódigo que vamos a utilizar en el libro.
Vamos a la tarea. Recordemos que el máximo común divisor de dos números
enteros positivos a y b, escrito como MCD(a, b), es el mayor de los divisores
comunes de a y de b, es decir, el mayor de aquellos números que dividen a la
vez a ambos. Por ejemplo MCD(5, 3) = 1, MCD(15, 9) = 3, y MCD(60, 15) =
5. El cálculo del máximo común divisor resulta muy útil a la hora de simplificar
facciones, por ejemplo la fracción 60 12x5
15 podrı́amos simplificarla a 3x5 = 3 .
12

En el Algoritmo 2 podemos encontrar el primer método para el cálculo del


máximo común divisor. El algoritmo recibe como parámetros de entrada dos
números, a y b, y como resultado nos muestra en pantalla el MCD(a, b). Para
ello, el algoritmo se basa en un procedimiento muy simple: primero calcula cual
es el menor de los números a y b, a continuación recorre todos los números que
CHAPTER 1. EL ARTE DE PROGRAMAR 10

Algorithm 2 Cálculo del MCD


función MCD(a, b)
min := mı́nimo(a, b);
para x := 1 hasta min hacer
si (módulo(a, x) = 0 ) & (módulo(b, x) = 0) entonces
mcd := x
finsi
finpara
escribir(mcd)

van desde 1 hasta el menor de a y b, y para cada uno de ellos comprueba si


dividen a la vez a ambos. Para comprobar si un número x divide a otro y, lo que
hacemos es utilizar la función módulo, que nos devuelve el resto de la división
enterea entre x e y (por ejemplo módulo(7, 2) = 1, o módulo(6, 2) = 0, en
cuyo caso 2 es un divisor de 6 pero no de 7). Al final del bucle para, tendremos
en la variable mcd el mayor de los números que han dividido a la vez a a y b, es
decir, su máximo común divisor.
Existen muchas formas de mejorar el algoritmo anterior. Por ejemplo, no
harı́a falta comprobar cuales son los divisores de a y b desde 1 hasta mı́nimo
de a y b, bastarı́a con comprobar desde 1 hasta la raiz cuadrada del mı́nimo
(piense el lector por qué).
Otra manera más refinada de calcular el MCD serı́a mediante la factorización
de los números a y b. Recordemos que cualquier número entero puede ser
descompuesto de manera única mediante el producto de números primos. Por
ejemplo: 6 = 2 ∗ 3, 15 = 3 ∗ 5, y 60 = 22 ∗ 3 ∗ 5. En general cualquer número n
puede ser factorizado como:

n = pf11 ∗ pf11 ∗ . . . ∗ pf11

donde pi son números primos, y f i el número de veces que aparecen di-


chos números. Una vez factorizados ambos números tan sólo tendrı́amos que
quedamos con los factores primos comunes, elevados a la mı́nima potencia. Ası́
MCD(6, 60) = 2 * 3 = 6. Sin embargo, descomponer un número en sus fac-
tores primos no es una tarea fácil, como veremos más adelante cuando tratemos
el tema de la criptografı́a.
En lugar de factorizar ambos números, vamos a utilizar otro método, cono-
cido como algoritmo de Euclides, que nos permite calcular el máximo común
divisor de una manera fácil, elegante, y rápida. El algoritmo de Euclides se basa
en la siguiente propiedad:

si a y b son dos números enteros cualesquiera, entonces MCD(a, b)


= MCD(a-b, b).

Podrı́amos hacer unos cuantos ejemplos a mano para convencernos a nosotros


mismos de que esta propiedad es cierta, y aquellos lectores que sean más hábiles
CHAPTER 1. EL ARTE DE PROGRAMAR 11

Figure 1.4: Regla divisora

con las matemáticas, podrı́an incluso intentar dar una demostración formal.
Pero en realidad no hace falta complicarse tanto la vida. Sólo tenemos que
pensar un poco qué es lo que realmente significa esta propiedad para darnos
cuenta de que la idea es trivial (y como toda idea trivial que se precie, tuvieron
que pasar muchos años hasta que alguien se percató de ella).
Imaginemos que tenemos dos barras de acero de longitudes a y b respecti-
vamente, y que queremos medirlas utilizando para ello una pequeña regla de
madera. Si la regla de madera nos da un número entero de medidas en una de
las barras es porque la regla es un divisor de dicha barra. Si después de medir
nos sobra un poquito de barra, es porque la regla no es un divisor. De entre
todas las reglas que miden a ambas barras a y b nos quedamos con la que tenga
una longitud mayor, que será precisamente el máximo común divisor (véase la
Figura 1.4).
Ahora bien, si encontramos una regla que divide a la barra b, para ver si
también divide a la barra a bastarı́a con comprobar que divide a aquella parte
de la barra a que sobresale de la barra b (es decir b-a), ya que la otra parte ya
ha sido medida. Si ahora cortamos lo que sobresale de la barra a, podrı́amos
repetir de nuevo el proceso pero utizando la barra cortada b-a y la barra b. Se
tratarı́a de resolver el mismo problema pero invertidos los papeles de las barras.
Basándonos en esta idea, podemos escribir el siguiente Algoritmo de Euclides
(véase el Algoritmo 3). La idea consiste en ir cortando alternativamente de la
barra más larga la parte que sobresale de la barra más pequeña, hasta que
ambas barras tengan exactamente la misma logitud, que será el máximo común
divisor.
El algoritmo puede ser mejorado todavı́a un poco más, utilizando para ello
la función módulo que hemos visto más arriba, pero esta mejora se la dejo
propuesta al lector como ejercicio.

1.4 Ciencia, arte o ingenierı́a


Prácticamente desde que aparecieron las primeras computadoras electrónicas, y
con ellas los primeros programadores, ha habido un debate sobre si la progra-
mación de ordenadores es una ciencia, un arte o un proceso de ingenierı́a. El
problema radica en que no existe ningún método formal para diseñar algorit-
mos, es decir, no existe ningún algoritmo que nos permita escribir algoritmos,
CHAPTER 1. EL ARTE DE PROGRAMAR 12

Figure 1.5: Interpretación geométrica del algoritmo de Euclides

Algorithm 3 Algoritmo de Euclides


función MCD Euclides(a, b)
mientras a != b hacer
si a < b entonces
a := a - b
sino
b := b - a
finsi
finmientras
escribir(a)
CHAPTER 1. EL ARTE DE PROGRAMAR 13

y por tanto, los programadores han de ingeniárselas con cada nuevo problema
que tienen que resolver. Tan sólo disponemos de un conjunto de técnicas gen-
erales y de buenas prácticas, que iremos viendo a lo largo de este libro, y que
nos pueden servir como base para diseñar nuestros propios algoritmos. Pero
al final, el diseñador de algoritmos ha de basarse más en su experiencia y su
intuición que en ningún método formal.
En la sección 1.2 mencionamos los premios Turing, que la asociación ACM
concede cada año, en honor al matemático Alan Turing, a aquellos investigadores
que hayan destacado por sus contribuciones técnicas a la comunidad informática.
En el año 1974 el premio Turing fue concedio al profesor Donald E. Knuth, de
la Universidad de Stanford (EEUU), por sus contribuciones en el análisis de
algoritmos y en el diseño de lenguajes de programación, y en particular por sus
famosa serie de libros “El Arte de Programar Ordenadores”.
En 1968 el profesor Knuth publicó el primero de una serie de libros con
el ánimo de recopilar todo el conocimiento disponible sobre el “arte” de pro-
gramar ordenadores. El primer volumen estaba dedicado a los algoritmos más
fundamentales; el segundo volumen, publicado en 1969, trataba de los algorit-
mos seminuméricos; y el tercer volumen, publicado en 1973, sobre algoritmos
de ordenación y búsqueda. El cuarto volumen, aun no publicado, tratará sobre
algoritmos combinatorios, y finalmente, el quinto volumen, que se espera para el
año 2010, hablará de los algoritmos sintácticos4 . Para hacerse una idea de la im-
portancia de la serie de libros publicada por el profesor Knuth, basta mencionar
que en el año 1999, la revista American Scientist, los incluyó en una lista con
las doce mejores monografı́as de la ciencia del siglo XX, junto con tı́tulos como
la relatividad de Einstein, la mecánica cuántica de Dirac, o los fundamentos de
la matemática de Russell y Whitehead.
Durante la entrega de la medalla Turing, el profesor Knuth hizo una ardiente
defensa de la idea de que la programación es, ante todo, un arte. El profesor
nos enseñaba sobre la necesidad de buscar la belleza en la programación:

Preparar un programa, es como escribir poesı́a o componer música;


como Adrei Ershov dijo una vez, la programación puede darnos satis-
facción emocional e intelectual a la vez, porque es un verdadero logro
dominar la complejidad y establecer un sistema de reglas consis-
tentes. Además, cuando leemos los programas que otros han escrito,
podemos reconocer algunos de ellos un genuino trabajo artı́stico [...]
Algunos programas son elegantes, otros son esquisitos, y otros son
brillantes. Creo que es posible escribir programas excelentes, pro-
gramas nobles, y programas realmente magnı́ficos.

Finalmente, el profesor Knuth concluye su disertación diciendo:

“Hemos visto que la programación de ordenadores es un arte,


porque aplica el conocimiento acumulado al mundo, porque requiere
4 Aunque quizás el profesor nos sorprenda con dos volúmentes más: volumen 6, sobre la

teorı́a de los lenguajes libres de contexto, y el volumen 7, sobre técnicas para compiladores.
CHAPTER 1. EL ARTE DE PROGRAMAR 14

habilidad e ingenuidad, y sobre todo porque produce objetos bellos.


Un programador que inconscientemente se ve a sı́ mismo como un
artista se divertirá con lo que hace, y lo hará mucho mejor.”
Mi opinión personal es que la programación de ordenadores es una mezcla de
todo, de ciencia, de arte y de ingenierı́a, y que estas diferentes visiones se com-
plementan entre sı́. El programador de hoy dı́a ha de conocer y manejar cor-
rectamente estas tres facetas. Ha de conocer los métodos de ingenierı́a de la
programación si quiere ser capaz de gestionar la complejidad de los grandes
proyectos. Además, el programador ha de estar familiarizado con los funda-
mentos teóricos y cientı́ficos de la programación de ordenadores. Y sobre todo,
el programador ha de buscar la belleza de los algoritmos que escribe.

1.5 Algoritmos y lenguajes de programación


No me gustarı́a finalizar este capı́tulo si escribir unas palabras sobre los lengua-
jes de programación. Sabemos que para que un ordenador pueda ejecutar un
algoritmo primero tenemos que traducirlo a alguno de los muchos lenguajes de
programación de ordenadores que existen. Los ordenadores de hoy en dı́a no son
capaces de procesar directamente el lenguaje pseudocódigo que vamos a utilizar
en este libro para escribir los algoritmos. El problema es que el pseudocódigo
aquı́ utilizado, aunque es lo suficientemente claro y conciso para los humanos,
contiene demasiadas ambigüedades que son insuperables por los ordenadores.
De ahı́ que necesitamos primero traducir nuestro pseudocódigo a algún lenguaje
de programación formal antes de poder ver nuestros algoritmos funcionando en
un ordenador.
Existen numeros lenguajes de programación, que se basan a su vez en paradig-
mas. Por ejemplo, tenemos la programación estructurada con los lenguajes Pas-
cal y C, la programación orientada a objetos con Java o C++, la programación
lógica con Prolog, la programación funcional con LISP, etc. Pero no vamos a en-
trar a estudiar los detalles de cada uno de estos paradigmas y lenguajes, porque
esto nos llevarı́a demasiado tiempo. Tampoco voy a proporcionar la traducción
del pseudocódigo de los ejemplos a alguno de estos lenguajes de programación;
en primer lugar porque para ello primero tendrı́a que elegir uno de lenguajes
existentes (C, Pascal, Java, etc.), y elija el que elija, seguro que el 80% de mis
lectores se enfadarı́a por la elección (existen unas terribles guerras de religión
en cuanto cual es el mejor lenguaje de programación que existe); en segundo
lugar porque pienso que el pseudocódigo que he utilizado en los ejemplos es lo
suficientemente claro para que los lectores no tengan dificultad para traducirlo
ellos mismos a su lenguaje favorito.
Tan sólo hacer una pequeña advertencia a los lectores, y es que tengan en
cuenta que según que problema queramos resolver, es mejor utilizar un lenguaje
que otro. Es decir, que a pesar de que disponemos de lenguajes que por su
versatilidad son conocidos como lenguajes de propósito general, algunas cosas
són más fáciles de hacer en algunos lenguajes que en otros. Luego no es buena
idea conocer un único lenguaje.
CHAPTER 1. EL ARTE DE PROGRAMAR 15

1.6 Para divertirse más ...


Existen muchos, y muy buenos, libros sobre algorı́tmica y sobre la programación
de ordenadores. En esta sección sólo voy a comentar algunos de ellos, para que el
lector los tenga como referencia por si quiere profundizar más sobre la materia.
Un excelente recopilación del conocimiento actual sobre los algoritmos es el
libro Introduction to Algorithms, de Thomas H. Cormen et al. (editorial MIT
Press), pero no tiene traducción al castellano.
También en inglés, y mucho más accesible que el anterior, tenemos el li-
bro Algorithmics, the Spirit of Computing, de David Harel y Yishai Feldman
(editorial Addison Wesley).
De dibulgación y en castellano podemos encontrar el libro De Euclides a
Java, de Ricardo Peña Marı́ (editorial Nivola).

Importancia del cero


No bromeaba cuando afirmaba que el concepto de cero es una de las ideas más
importantes de la historia de la humanidad. Para saber más sobre el génesis
de la idea de cero, y sobre sus implicaciones, recomiendo la lectura del libro La
Biografı́a de una Idea Peligrosa, de Charles Seife (de Ediciones Ellago).

Charles Babage
Charles Babbage sı́ que intentó construir otra máquina más simple que su In-
genio Analı́tico, denominada Ingenio de Diferencias, pero desgraciadamente el
proyecto acabó en el más absoluto fracaso. Son muchos los historiadores que
opinan que Chales Babagge fue un adelantado a su tiempo, ya que con la tec-
nologı́a disponible en la época Victoriana no era posible construir semejante
artilugio. Sin embargo, la reciente construcción con éxito en el Museo de la
Ciencia de Londres de uno de los ingenios calculadores diseñados por Babbage
ha demostrado que la historia ha juzgado erróneamente al precursor de la com-
putación automática. Los ingenieros del museo demostraron que las máquinas
de Babbage no contenı́an errores lógicos o de diseño graves, y técnicamente eran
viables para lo que los artesanos del siglo XIX podı́an fabricar. El fracaso del
proyecto fue debido más a un problema de gestión del mismo que a problemas
técnicos o de concepto.
Para saber más sobre la computadora mecánica de Charles Babbage re-
comiendo la lectura del artı́culo de Doron D. Swade en la revista Investigación
y Ciencia (Abril 1993).
En otra lı́nea está el libro de Willian Gibson, The Difference Engine, una
novela de ficción que narra como hubiera sido la evolución de la humanidad si
Charles Babbage hubiese conseguido finalizar su computadora, adelantando el
inicio de la era de la información un siglo.
CHAPTER 1. EL ARTE DE PROGRAMAR 16

Alan Turing
Sobre Alan Turing hay muchos libros escritos. Le recomiendo al lector que
además de leer alguna biografı́a de Turing, también intente buscar información
sobre los siguientes apasionantes temas: la máquina universal de Turing, el
código enigma y el test de Turing.

Donald Knuth
La trilogı́a de libros El Arte de Programar Ordenadores de Donald Knuth, ha
sido publicada en castellano por la editoriral Reverté.
La disertación del profesor Knuth durante la recepción de premio Turing
titulada Computer Programming as an Art fue publicada por la revista Com-
munications of the ACM (volumen 17, número 12, Diciembre de 1974).
Chapter 2

Algunos Ejemplos

Antes de pasar directamente a revisar cuales son las principales técnicas de


resolución de problemas utilizadas en algorı́tmica, vamos a ver unos ejemplos
de algoritmos, con la idea de allanar el camino y familizarizarnos con el tema.

2.1 Complejidad Algorı́tmica


Como hemos visto, los algoritmos juegan un papel muy importante en el mundo
de la informática. Resulta de vital importancia contar con buenos algoritmos,
que soluciones los problemas de manera rápida, y que a la vez consuman pocos
recursos, como memoria. Piénsese por ejemplo en el caso de los buscadores de in-
formación en internet, aquel buscador que posea el mejor algoritmo de búsqueda,
que sea capaz de proporcionar información relevante para el usuario, será el que
consiga mayor cuota de mercado (un mercado muy lucrativo, por cierto). O
por ejemplo en el area de la seguridad informática y la criptografı́a, donde es
fundamental contar con algoritmos de encriptación que sean indescifrables, al
menos desde un punto de vista práctico. O en el caso de las bases de datos, tan
comunes en bancos, departamentos de contabilidad, administraciones y muchos
otros lugares, donde es necesario contar con algoritmos de búsqueda que nos
proporcionen la información que necesitamos en un tiempo razonable. La al-
gorı́tmica es la rama de la informática encargada de buscar y analizar algoritmos
eficientes que solucionen problemas comunes (qué entendemos por algoritmo efi-
ciente es algo que veremos en detalle más adelante).
Ejemplos con algoritmos de ordenación
2.- Complejidad Algorı́tmica
Antes de empezar a estudiar las diferentes técnicas de solución de proble-
mas, deberı́amos hacer una pequeña revisión de las herramientas matemáticas
que nos permiten estudiar los diferentes algoritmos. Primero, revisaremos las
definiciones y términos matemáticos que vamos a usar en el resto del libro. Al
disponer de este vocabulario matemático podremos ser más precisos y formular
los problemas de manera más fácil. Después, revisaremos las técnicas existentes

17
CHAPTER 2. ALGUNOS EJEMPLOS 18

para analizar el tiempo de ejecución de un algoritmo. En el resto del libro, de-


spués de cada algoritmo proporcionaremos un análisis de su tiempo de ejecución
y una prueba de su correcteness.
Notación Asintótica
Denotamos porel conjunto de los números naturales (tales como 1, 2, 3,
etcétera). En complejidad algorı́tmica estamos interesados en funciones de N
en N, tales como n2, 2n y n3-2n+5.
Decimos que un algoritmo es O(g(n)), pronunciado orden de g(n), si f crece
como g o más lento.
Analyzing an algorithm has come to mean predicting the resources that
the algoritm requires. Occasionally, resource such as memory, communications
bandwidth, or computer hardware are of primary concern, but most often it is
computational time that we want to measure. Generally, by analyzing several
candidate algorithms for a problem, a most efficient one can be easily identified.
Such analysis may indicate more than one viable candidate, but several infe-
rior algorithms are usually discarded in the process. Asymptotic Notation In
addition to correctness another important characteristic of a useful algorithm
is its time and memory consumption. Time and memory are both valuable
resources and there are important differences (even when both are abundant)
in how we can use them. How can you measure resource consumption? One
way is to create a function that describes the usage in terms of some charac-
teristic of the input. One commonly used characteristic of an input dataset is
the its size. For example, suppose an algorithm takes as input an array of n
integers. We can describe the time this algorithm takes as a function f written
in terms of n. For example, we might write: f(n) = n2 + 3n + 14 where the
value of f(n) is some unit of time (in this discussion the main focus will be
on time, but we could do the same for memory consumption). Rarely are the
units of time actually in seconds, because that would depend on the machine
itself, the system its running, and its load. Instead, the units of time typically
used are in terms of the number of some fundamental operation performed. For
example, some fundamental operations we might care about are: the number
of additions or multiplications needed; the number of element comparisons; the
number of memory-location swaps performed; or the raw number of machine
instructions executed. In general we might just refer to these fundamental op-
erations performed as steps taken. Is this a good approach to determine an
algorithm’s resource consumption? Yes and no. When two different algorithms
are similar in time consumption a precise function might help to determine
which algorithm is faster under given conditions. But in many cases it is either
difficult or impossible to calculate an analytical description of the exact number
of operations needed, especially when the algorithm performs operations con-
ditionally on the values of its input. Instead, what really is important is not
the precise time required to complete the function, but rather the degree that
resource consumption changes depending on its inputs. Concretely, consider
these two functions, representing the computation time required for each size of
input dataset: f(n) = n3 - 12n2 + 20n + 110 g(n) = n3 + n2 + 5n + 5 They
look quite different, but how do they behave? Let’s look at a few plots of the
CHAPTER 2. ALGUNOS EJEMPLOS 19

function (f(n) is in red, g(n) in blue):


Plot of f and g, in range 0 to 5
Plot of f and g, in range 0 to 15
Plot of f and g, in range 0 to 100
Plot of f and g, in range 0 to 1000
In the first, very-limited plot the curves appear somewhat different. In the
second plot they start going in sort of the same way, in the third there is only
a very small difference, and at last they are virtually identical. In fact, they
approach n3, the dominant term. As n gets larger, the other terms become much
less significant in comparison to n3. As you can see, modifying a polynomial-
time algorithm’s low-order coefficients doesn’t help much. What really matters
is the highest-order coefficient. This is why we’ve adopted a notation for this
kind of analysis. We say that: f(n) = n3 - 12n2 + 20n + 110 = O(n3) We
ignore the low-order terms. We can say that: This gives us a way to more
easily compare algorithms with each other. Running an insertion sort on n
elements takes steps on the order of O(n2). Merge sort sorts in O(nlogn) steps.
Therefore, once the input dataset is large enough, merge sort is faster than
insertion sort. In general, we write f(n) = O(g(n)) That is, f(n) = O(g(n)) holds
if and only if there exists some constants c and n0 such that for all n > n0 f(n)
is positive and less than or equal to cg(n). Note that the equal sign used in this
notation describes a relationship between f(n) and g(n) instead of reflecting a
true equality. In light of this, some define Big-O in terms of a set, stating that:
when Big-O notation is only an upper bound; both these two are both true: n3
= O(n4) n4 = O(n4) If we use the equal sign as an equality we can get very
strange results, such as: n3 = n4 which is obviously nonsense. This is why the
set-definition is handy. You can avoid these things by thinking of the equal sign
as a one-way equality, i.e: n3 = O(n4) does not imply O(n4) = n3 Always keep
the O on the right hand side. Big Omega Sometimes, we want more than an
upper bound on the behavior of a certain function. Big Omega provides a lower
bound. In general, we say that f(n) = ?(g(n)) when I.e. f(n) = O(g(n)) if and
only if there exist constants c and n0 such that for all n>n0 f(n) is positive and
greater than or equal to cg(n). So, for example, we can say that n2 - 2n = ?(n2)
- (c=1/2, n0=4) or n2 - 2n = ?(n) - (c=1, n0=3), but it is false to claim that
n2 - 2n = ?(n3).
Big Theta When a given function is both O(g(n)) and ?(g(n)), we say it is
?(g(n)), and we have a tight bound on the function.
From Papa It is importan, however, to identify the source of our satisfac-
tion: It is the rate of growth O(n2). In the course of this book we shall regard
such polynomial rates of growth as acceptable time requirements, as a sign that
the problem has been solved statisfactorily. In contrast, exponential rates such
as 2ˆn, even worse, n! Will be a cause of concern. If they persists, and algo-
rithm after algorithm we devise fails to solve the problem in polynomial time,
we generally consider this as evidence that the problem in hand is perhaps
intractable, not amenable to a proactically efficient solution. This dichotomy
between polynomial and nonpolynomial time bounds, and the identification
of polynomial algorithms with the intuitive notion of practically feasible com-
CHAPTER 2. ALGUNOS EJEMPLOS 20

putation, is not uncontroversial. There are efficient computation that are ot


polynomial, and polynomial computations that are not efficent in practice. [In
fact, there is an important problem that provides examples for both kind of
exceptions: Linear programming. A widely used classical algorithm for this
basic problem, the simplex method, is know to be exponential in the worst
case, but has consistently superb performance in practice; in fact, its expected
performace is probably polynomial. In contrast, the first polynomial algorithm
discovered for this problem, the ellipsoid algorithm, appears to be impractically
slow. But the storiy of linear programming may in fact be a subtle argument
for, not against, the methodology of complexity theory: It seems to indicate
that problems that have practical algorithms are indeed polynomial-time solv-
able althogh the polynomial-time algorithm and the empirically good one may
not necessarily coincide.] For example, an nˆ80 algorithm would probably be of
limited practical value, and an algorithm with an exponential growth rate such
as 2ˆ(n/100) (or, more intriguing, a subexponential one such as nˆ(log n) ) may
be far more useful. There are, however, strong arguments in favor of the poly-
nomial paradigm. First, it is a fact that any polynomial rate will be overcome
eventually by any exponetial one, and so the latter is to be preferred for all but
a finite set of instances of the problem but of course this finite set may contain
all instances that are likely to come up in practice, or that can exist within the
confines of our universe ... More to the point, experience with algorithms has
shown that exrem rate of growth, such as nˆ80 and 2ˆ(n/100), rarely come up
in practice. Polynomial algorithms typically have small exponents and reason-
able multiplicative constans, and exponential algorithms are usually impractical
indeed. Another potential criticism of our point of view is that it only examines
how the algorithm performs in the least favorable situations. The exponential
worst-case performance of an algorithm may be due to a statistically insignifi-
cant furaction of the inputs, although the algorithm may perform satisfactorily
on the average. Surely an analysis of the expected, as opposed to the worst-case,
behavior of an algorithm would be more informative. Unfortunately, in practice
we rarely know the input distribution of a problem that is, the probability with
which each possible instance is likely to occur as an input to our algorithm and
thus a truly informative average-case analysis is impossible. Besides, if we wish
to solve just one particular instanc, and our algorithm prforms abysmally on
it, knowing that w have stumbld upon a statistically insignificant exception is
of litle help or consollation. It should not com as a surpris that our choic of
polynomial algorithms as the mathmatical concpt that is supposd to capture
the informal notion of practically fficient computation is open to criticism for
all sides. Any attempt, in any field of mathematics, to capture an intuiive, real-
life notion by a mathematical concept is bound to include certain undesirable
spcecimens, while excluding others that arguable should be embraced. Ulti-
mately, our argument of choice must be this: Adopting polynomial worst-case
performance as our criterion of effciency results in an elegant and useful theory
that says something meaningfull about practical computation, and whould be
impossible without this simplification.
Hablar también de los requerimientos de espacio.
CHAPTER 2. ALGUNOS EJEMPLOS 21

Past experience in the field seems to suggest that, as a rule, once a polyno-
mial algorithm for a problem has been developed, the time requirements undergo
a series of improvements that bring the probme in the real of realistic compu-
tation. The important step is to break the barrier of exponentiality to come
up with the first polynomial time algorithm. A central concep in algorithms
is that of a reduction. A reduction is an algorithm that solves problem A by
transforming any instance of A to an equivalent instance of a previously solved
problem B.

2.2 ¿Está usted seguro?


Una de los principales problemas de la algorı́tmica es cómo estar seguros de que
un algoritmo es correcto. Es decir, cómo sabemos que un algoritmo va a dar la
respuesta correcta a los parámetros de entrada proporcionados.
Chapter 3

Recursividad

Métodos Iterativos y Métodos Recursivos. Cálculo de n! Series de Fibonacci.

22
Chapter 4

Divide y Vencerás

Si el presidente de mi paı́s me llamase por teléfono para pedirme consejo so-


bre como conquistar algún paı́s vecino, seguramente lo que le recomendarı́a es
que tratase de dividir el ejército de nuestro rival, es decir, que aplicase la vieja
estrategia de Divide y Vencerás. La máxima divide y vencerás (del latin Di-
vide et Impera) es atribuida a Maquiavelo, aunque es una táctica que se viene
utilizando desde muy antiguo. Por ejemplo, es de sobra conocido que el César
seguramente la tenı́a en la mente cuando, uno por uno, ya sea en el campo de
batalla o por medios diplomáticos, consiguió que los jefes locales aceptaran la
autoridad de Roma. La estrategia divide y vencerás también ha sido utilizada
con gran acierto por los administradores de los grandes imperios, incluyendo el
imperio británico, el cual jugaba a enfrentar una tribu contra otra para man-
tener el control de sus colonias con un número mı́nimo de fuerzas. El concepto
de “divide y vencerás” ganó especial relevancia cuando la India entró a formar
parte del imperio británico. Los británicos utilizaron esta estrategia de man-
era efectiva para ganar control de un territorio muy extenso como es la India
manteniendo su gente dividida con la religion, el idioma, las castas, etc. Los
británicos tomaron el control de pequeños estados principescos hindues en lugar
de unir Inida en una única nación.
En polı́tica y sociologı́a, el método divide y vencerás es una estrategia de
sobra conocida para ganar y mantener el poder mediante la división de grandes
concentraciones de poder en grupos que individualmente tienen menos poder
que el que aplica la estrategia. Aunque en realidad, a menudo se refiere a una
estrategia donde los pequeños grupos de poder se previene que se enlacen y
se conviertan en más poderosos, dado que es más dificil romper estructuras de
poder ya existentes.
Pero volvamos a los algoritmos, que es lo que en realidad nos interesa, y
veamos cómo podemos aplicar la estrategia divide y vencerás para resolver mu-
chos problemas. Muchos algoritmos útiles son recursivos en estructura: para
resolver un problema, se llaman a sı́ mismos recursivamente una o más ve-
ces para tratar con subproblemas relacionados. Estos algoritmos tı́picamente
siguen un método divide-y-vencerás: parten el problema en subproblemas que

23
CHAPTER 4. DIVIDE Y VENCERÁS 24

son similares al problema original pero más pequeños en tamao, y resuelven el


problema de manera recursiva, y después combinan estas soluciones para crear
una solución al problema original.
El paradigma de resolución de problemas divide y vencerás involucra tes
pasos a cada nivel de la recursión:

• Divide el problema en un número de subproblemas.


• Conquista cada subproblema resolviendolo de manera recursiva. Si el
tamaño de los subproblemas es lo suficientemente pequeño, sin embargo,
tan sólo soluciona los problema de una manera directa.
• Combina la solución de los subproblemas en la solución del problema orig-
inal.

4.1 Merge Sort


4.2 Multiplicación?
Multiplicación divide y vencerás

4.3 La Transformada de Fourier


Chapter 5

Fuerza Bruta

Una técnica de resolución de problemas para la que los ordenadores están espe-
cialmente bien dotados es la conocida como fuerza bruta. Básicamente la idea
cosiste en dado un problema, intentar de manera sistemática todas las soluciones
posibles hasta dar con la solución buscada. Un ejemplo de algoritmo basado en
fuerza bruta lo vimos en la sección 1.3 al calcular el máximo común divisor de
dos números naturales a y b, para ello enumerábamos todos los enteros desde
1 hasta el mı́nimo de a y b, y comprobábamos si dividı́an a ambos números a
la vez. La fuerza bruta es un método de resolución de problemas muy ingénuo,
y que apriori puede parecernos que no deberı́a tener mucho éxito. El princi-
pal inconveniente que tiene es su coste computacional, que es proporcional al
número de soluciones candidatas a examinar, y que en el caso de muchos prob-
lemas prácticos, tiende a crecer de manera muy rápida cuando el tamaño del
problema se incrementa. Sin embargo, si tenemos en cuenta la fantástica habil-
idad de cálculo de los ordenadores de hoy dı́a, capaces de realizar millones de
operaciones por segundo, entenderemos que para el caso de aquellos problemas
en los que no se conoce una solución mejor, la fuerza bruta puede ser una buena
alternativa. Además, la fuerza bruta también se suele aplicar en aquellos proble-
mas cuyo tamaño está limitado, o cuando existen técnicas heurı́sticas especı́fcas
del problema que pueden ser utilizadas para reducir el conjunto de soluciones
candidatas a examinar a un tamaño razonable.

5.1 El problema de la mochila


El problema de la mochila.

5.2 Máquinas que juegan al ajedrez


Seguramente muchos de los lectores saben jugar al ajedrez, y son conscientes de
lo complicado que resulta dominar el juego. Aunque en realidad, si uno lo piensa
un poco, no se trata de un juego tan difı́cil. Las reglas pueden aprenderse en

25
CHAPTER 5. FUERZA BRUTA 26

varios minutos de práctica; y la estrategia a seguir es muy clara: en cada posición


hacer aquella jugada que maximice las posibilidades de que ganemos y minimice
las posibilidades de que gane nuestro rival. Para ello lo único que tenemos que
hacer es revisar todos los movimientos posibles a nuestro alcance, y para cada
uno de ellos todas las posibles respuestas del bando contrario, y para cada una
de las posibles respuestas miramos todas nuestras posibles respuestas, y ası́
sucesivamente hasta completar unas diez o doce jugadas por adelantado. Esta
técnica de juego, basada en la fuerza bruta, deberı́a garantizarnos la victoria.
La pregunta es: ¿cuantas posiciones tendrı́amos que analizar? Al empezar la
partida, las blancas tienen a su disposición un total de 20 movimientos distintos,
podemos adelantar cada uno de los peones una o dos casillas, junto con los cuatro
movimientos posibles de los caballos. Para cada uno de los 20 movimientos de
las blancas, las negras pueden a su vez responder con otros 20 movimientos, lo
que hace un total de 400 diferentes posiciones posibles. En la siguiente jugada,
las blancas pueden optar entre 5362 posiciones diferentes, y las negras, en su
respuesta por 71842. En la tercera jugada ya hablamos de más de 800.000
posiciones para las blancas, y más de 9 millones para las negras. Se estima que
un Gran Maestro de ajedrez puede examinar y evaluar del orden de 3 posiciones
por segundo, y por tanto, calcular y evaluar todas las posibles posiciones para
los tres primeros movimientos del juego, requerirı́a nada menos que 100 dı́as.
Evidentemente, a pesar de ser una estrategia correcta desde un punto de vista
teórico, desde un punto de vista práctico resulta totalmente irrealizable, y de ahı́
que los humanos tengamos que utilizar estrategias alternativas. Básicamente lo
que hacemos cuando jugamos es descartar la mayorı́a de las posibles jugadas,
utilizando para ello nuestro conocimiento del juego. Un Gran Maestro del aje-
drez no suele considerar más de 3 ó 4 posibles alternativas en cada posición. Sin
embargo, los ordenadores son máquinas de calcular por excelencia, y quizás esta
estrategia para jugar al ajedrez puede que esté dentro de sus posibilidades de
cálculo. Por ejemplo, la máquina Deep Blue diseñada por IBM en 1997 para ju-
gar contra el campeón del Mundo Garry Kasparov, podı́a examinar y evaluar del
orden de 200 millones de posiciones por segundo. Esta máquina tardarı́a menos
de un segundo en evaluar las tres primeros movimientos del juego. ¿Es la fuerza
bruta una técnica viable para jugar al ajedrez? A esta pregunta intentaremos
responder en esta sección.

5.2.1 Arboles de Juego


Los principales programas de ajedrez funcionan median la búsqueda de un árbol
de movimientos y contramovimientos. El programa empieza con la posición ac-
tual y genera todos los movimientos, todas las respuestas legales a esos movimien-
tos, y ası́ sucesivamente hasta que una profundidad fijada es alcanzada. A cada
nodo hoja, se aplica una función de evaluación la cual le asigna un valor numérico
a cada posición. Estas puntuaciones son retroasignada mediante un proceso lla-
mado minimax, el cual simplemente asume que cada bando escogerá aquella
lı́na de juego que le es más favorable en cada momento. Si un valor positivo
es bueno para las blancas, simplemente escogemos aquel movimiento con mayor
CHAPTER 5. FUERZA BRUTA 27

valor, y las negras escogeran aquella jugada con menor valor. Estos conceptos
están ilustrados en la Figura ?.
La función de evaluación empleada es simplemente una combinación de equi-
librio de material y varios otros términos que representan factores posicionales.
Los términos posicionales son pequeñas cantidades pero son importantes dado
que el equilibrio de material rararmente cambia en el juego del ajedrez en tor-
neos.
El problema de este método basado en la fuerza bruta es que el tamaño
del árbol explota combinacionalmente. El “factor de ramificación” o número
de movimientos legales posibles en una posición tı́pica des de 35. Para poder
juegar a nivel de maestro de ajedrez es necesario utilizar una profundidad de 8
jugadas, lo que implica un árbol de 35**8 nodos finales.
Fortunately, there is a better way. Alpha-beta pruning is a technique which
always gives the same answer as brute-force searching without looking at so
many nodes of the tree. Intuitively, alpha-beta pruning works by ignoring sub-
trees which it knows cannot be reached by best play (on the part of both sides).
This reduces the effective branching factor from 35 to about 6, which makes
strong play possible.
The idea of alpha-beta pruning is illustrated in Figure 14.4. Assume that all
child nodes are searched in the order of left to right in the figure. On the left side
of the tree (the first subtree searched), we have minimaxed and found a score of
+4 at depth one. Now, start to analyze the next subtree. The children report
back scores of +5, -1, . The pruning happens after the score of -1 is returned:
since we are taking the minimum of the scores +5, -1, , we immediately have
a bound on the scores of this subtree-we know the score will be no larger than
-1. Since we are taking the maximum at the next level up (the root of the tree)
and we already have a line of play better than -1 (namely, the +4 subtree), we
need not explore this second subtree any further. Pruning occurs, as denoted
by the dashed branch of the second subtree. The process continues through the
rest of the subtrees.
The amount of work saved in this small tree was insignificant but alpha-
beta becomes very important for large trees. From the nature of the pruning
method, one sees that the tree is not evolved evenly downward. Instead, the
algorithm pursues one branch all the way to the bottom, gets a “score to beat”
(the alpha-beta bounds), and then sweeps across the tree sideways. How well
the pruning works depends crucially on move ordering. If the best line of play
is searched first, then all other branches will prune rapidly.
Actually, what we have discussed so far is not full alpha-beta pruning, but
merely “pruning without deep cutoffs.” Full alpha-beta pruning shows up only
in trees of depth four or greater. A thorough discussion of alpha-beta with some
interesting historical comments can be found in Knuth and Moore [Knuth:75a].
CHAPTER 5. FUERZA BRUTA 28

5.2.2 Evaluación de Posiciones


5.2.3 El Juego del Go
El juego del Go es de una naturaleza diferente al del Ajedrez.

5.3 El Viajante
Vamos a ver ahora un problema clásico en la teorı́a de algoritmos, que ha sido
con diferencia, el problema más difı́cil de resolver, y por ahora, el mayor de los
fracasos. Además, este problema ha supuesto una de las principales motiva-
ciones para el desarrollo de la teorı́a de la complejidad computacional.
Descripción del problema .... y su versión equivalente como problema de
decisión
Evidentemente, el problema puede ser resuelto mediante la enumeración de
todas las posibles soluciones, calculando el coste de cada una, y seleccionando
la mejor entre ellas. Este procedimiento requerirı́a un tiempo proporcional a n!
(existen 12 (n-1)! Rutas a considerar), que no es un tiempo polinomial. Nótese
que este algoritmo se comporta muy bien en cuanto a requerimientos de espacio:
sólo require un espacio proporcional a n, ya que sólo necesitamos recordar la per-
mutación que estamos analizando en este momento, y la mejor ruta encontrada
hasta el momento.
No se conoce ninguna solución al problema del viajante en tiempo poli-
nomial. Podemos mejorar el n! Tiempo un poco mediante el método de la
programación dinámica (aunque a costa de utilizar unos requerimientos de es-
pacio exponenciales). Existen algoritmos eurı́sticos que se comportan bien, y
encuentran rutas que no estan lejos de la ruta optima cuando se le proporcio-
nan instancias tı́picas. Pero, si insistimos en algoritmos que garanticen el valor
óptimo, el estado del arte sólo ofrece respuestas exponenciales. Y esto no es
debido a una falta de interés, ya que el problema del viajante es probablemente
uno de los algoritmos que más se han estudiado.
Nos sentimos tentados a suponer que hay un impedimento fundamental,
un lı́nea divisoria dramática entre el problema del viajante y otros problemas
similares. Podrı́amos suponer que no hay solución en tiempo polinómico para
el problema del viajante. Esta afirmación es una de las muchas maneras de
formular la famosa afirmación de P != NP, el más importante de los problemas
en informática teórica.
Decir algo sobre los problemas del milenio y la fundación Clay

5.4 Criptografı́a
brute-force attack
CHAPTER 5. FUERZA BRUTA 29

5.5 Para divertirse más


Existen dos tipos de problemas relacionados con el ajedrez
1.- Cuantas piezas de un tipo dado pueden colocarse en un mismo tablero
sin que se ataquen? Problema de las 8 reinas.
2.- ¿Cual es el mı́nimo número de piezas necesarias para ocupar o atacar
cada cuadrado del tablero?
3.- El problema de la vuelta del caballo
Chapter 6

Métodos Aleatorios

Cálculo de Pi.

30
Chapter 7

Otros Métodos

Programación Dinámica: Fibonacci Greedy Algorithms Hill Climbing: Network


flow (mapa de carreteras?)

31
Chapter 8

Algunos Problemas Clásicos

Torres de Hanoi

32
Chapter 9

Programación Concurrente

Los Filósofos Comensales

33
Chapter 10

Imitando a la Naturaleza

Métodos aleatorios? Aloritmos Genéticos. Redes Neuronales. Hormigas. Enfri-


amiento Estadı́stico

34
Chapter 11

Epı́logo

11.1 Algoritmos y patentes


Patentabilidad de los algoritmos

35
Chapter 12

Apéndice 1: Introducción a
la Programación

Introducción a la programación
Operador de asignación :=

Sentencias Condicionales
si condición entonces
sentencias
finsi

Operadores relacionales
<, >, =, <=, >=, !=
Operadorres lógicos
&
|
!

Bucles
mientras
finmientras
para inicialización hasta finalización hacer
sentencias
finpara

Funciones

36
Chapter 13

Apéndice 3: Solución a los


Ejercicios

37
Chapter 14

Notas

Notas varias

Técnicas de resolución de problemas


Listado de técnicas de resolución de problemas existentes, y secciónes en las que
son cubiertas:
recursividad
fuerza bruta
divide y vencerás
backtraking
greedy algorithms
programación dinámica
programación lineal

El Arte de Programar
Un poco de historia
Se podrı́a extender un poco más el tema de la multiplicación de números
romanos, y hablar del algoritmo que tenı́an los romanos para ello.
Hablar de Granada (una de las seis universidades en las que he estudiado) de
aquello de que quien divide por cero en Junio dividirá por cero en Septiembre.
De que debe haber algo fundamentalmente erroneo en la idea de cero. Ver algun
truquito de cosas raras que pasan cuando uno divide por cero. ¿Hablar de los
problemas que tienen los ordenadores para dividir por cero?
Pseudocódigo Utilizado
Pendiente: Estudiar similitud con Logo.
para i de 1 hasta 100 repetir fin para
si condición entonces sino fin si
mientras condición repetir fin mientras
leer variable
escribir variable | texto

38
CHAPTER 14. NOTAS 39

(wikipedia algorithms)
For a solvable problem, there are four different levels an algorithmic solution
to the problem could be done: 1.the obvious way; 2.the methodical way; 3.the
clever way; and 4.the miraculous way. On the first and most basic level, the
”obvious” solution might try to exhaustively search for the answer. Intuitively,
the obvious solution is the one that comes easily if you’re familiar with a pro-
gramming language and the basic problem solving techniques. The second level
is the methodical level and is the heart of this book: after understanding the
material presented here you should be able to methodically turn most obvious
algorithms into better performing algorithms. The third level, the clever level,
requires more understanding of the elements involved in the problem and their
properties or even a reformulation of the algorithm (e.g., numerical algorithms
exploit mathematical properties that are not obvious). A clever algorithm may
be hard to understand by being non-obvious that it is correct, or it may be
hard to understand that it actually runs faster than what it would seem to re-
quire. The fourth and final level of an algorithmic solution is the miraculous
level: this is reserved for the rare cases where a breakthrough results in a highly
non-intuitive solution. Naturally, all of these four levels are relative, and some
clever algorithms are covered in this book as well, in addition to the methodical
techniques.
Máquinas de Turing
The Turing machine is a formal method for expressing arbitrary algorithms.

• Serı́a interesante comentar algo sobre la universalidad de los algoritmos.


Es decir, que no necesitamos necesariamente un ordenador para poder
implementarlos, quizás nos baste con unas cajas de cerillas y unas bolitas,
una mesa de billar, o unos clavos y unas gómas elásticas. Quizás en el
apartado en el que se hable de máquinas de Turing.
• Cita: “homines dum docent discunt” que significa “la gente aprende cuando
enseña” (Seneca, Epistulae Morales, 7.8).

14.1 Otros Posibles Temas


Temas de los que se podrı́a hablar en el libro:

• Criptografı́a
• El cubo de Rubik
• Máquinas de Turing
• Torres Quevedo
• Laberintos: Altoritmos para escapar de un laberinto. Laberintos conexos.
Mitologı́a.
• Los top 10 de los algorithmos
CHAPTER 14. NOTAS 40

• Morphogénesis
• numerical recipes

• Dijkstraa

14.2 Ejemplos
• números perfectos
• cuadrados mágico

14.3 A Investigar
• Ariadne’s thread
Chapter 15

Bibliografı́a

Bibliografı́a
Libros
Algorithmics: The Spirit of Computing David Harel Addison-Wesley, Read-
ing, Mass., 1987
A History of Algorithms: From the Pebble to the Microchip E. Barbin et al.
Springer
Wikibook?
Turing Omnibus
The Art of Computer Programming Donald Knuth
Introduction to Algorithms Thomas H. Cormen et al MIT Press and McGraw
Hill
The Design and Analysis of Computer Algorithms Alfred V. Aho Addison-
Wesley
Algorithms Robert Sedgewick Addison-Wesley
Algorithms + Data Structures = Programs Niklaus Wirth Prentice Hall
Algoritmos de ordenación
http://www.algoritmia.net/articles.php?id=31 http://dir.yahoo.com/Science/Computer Science/Algorithm
Backtracking
http://www.algoritmia.net/articles.php?id=33
Divide y Vencerás
http://www.algoritmia.net/articles.php?id=34
Bibliografı́a Recomendada
Revisar texto
En este apartado ...
Además de las referencias bibliográficas escritas, también se ha incluido un
numeroso conjunto de referencias a documentos en internet. Sin embargo, dada
la volatilidad de los contenidos en internet, donde documentos aparecen y desa-
parecen, y las direcciones cambian, las referencias no son del todo fiables. Para
intentar solucionar este problema, junto a las direcciones en internet o URL,
se ha incluı́do suficiente información para que, con la ayuda de un buscador en
internet, el lector pueda buscar la nueva localización del documento.

41
CHAPTER 15. BIBLIOGRAFÍA 42

Capı́tulo 1.- El Arte de Programar


The MacTutor History of Mathematics archive
Created by John J O’Connor and Edmund F Robertson
http://www-history.mcs.st-andrews.ac.uk/ School of Mathematics and Statis-
tics University of St Andrews Scotland
En castellano la wikipedia.
Edición impresa:
Euclides Elementos (3 volúmenes) Biblioteca Clásica Gredos Editorial Gre-
dos
Edición en Internet, en inglés con comentarios y programas de muestra:
D. E. Joyce Dept. Math. & Comp. Sci. Clark University http://aleph0.clarku.edu/˜djoyce/java/elements/e
Traducida al castellano por Jaume Domenech Larraz en http://www.euclides.org/