Cap1y2.Textoguiapdf

896 views
850 views

Published on

Guia sobre algoritmo

Published in: Education
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
896
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
13
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Cap1y2.Textoguiapdf

  1. 1. Cap´ ıtulo 1 Problemas, programas, estructuras y algoritmos El proceso de resoluci´n de problemas, en programaci´n, est´ compuesto por una o o a serie de pasos que se aplican de forma met´dica. Empieza con el an´lisis de las carac- o a ter´ ısticas del problema, contin´a con el dise˜o de una soluci´n, la implementaci´n u n o o del dise˜o y termina con la verificaci´n y evaluaci´n del programa resultante. Los dos n o o ejes b´sicos en los que se articula el desarrollo de un programa son las estructuras de a datos y los algoritmos. Las estructuras de datos determinan la forma de almacenar la informaci´n necesaria para resolver el problema. Los algoritmos manipulan esa o informaci´n, para producir unos datos de salida a partir de unos datos de entrada. o El objetivo ultimo de la algor´ ´ ıtmica es encontrar la mejor forma de resolver los problemas, en t´rminos de eficiencia y de calidad del software. e Objetivos del cap´ ıtulo: Entender el desarrollo de programas como un proceso met´dico e ingenieril, formado por o una serie de etapas con distintos niveles de abstracci´n, frente a la idea de la programaci´n o o como arte. Tomar conciencia de la importancia de realizar siempre un an´lisis y dise˜ o previos del a n problema, como pasos anteriores a la implementaci´n en un lenguaje de programaci´n. o o Motivar el estudio de los algoritmos y las estructuras de datos, como una disciplina fun- damental de la inform´tica. a Repasar algunos de los principales conceptos de programaci´n, que el alumno debe conocer o de cursos previos como: tipos de datos, estructuras de datos, tipos abstractos, algoritmos, complejidad algor´ıtmica y eficiencia. Entender la importancia del an´lisis de algoritmos, el objetivo del an´lisis, los factores a a que influyen y el tipo de notaciones utilizadas para su estudio. Distinguir entre algoritmos y esquemas algor´ıtmicos, presentando brevemente los princi- pales esquemas algor´ıtmicos que ser´n estudiados. a 1
  2. 2. 2 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos Contenido del cap´ ıtulo: 1.1. Resoluci´n de problemas . . . . . . . . . . . . . . . . . . . . . . o . . . . . . . . . . 3 1.1.1. An´lisis de requisitos del problema . . . . . . . . . . . . a . . . . . . . . . . 3 1.1.2. Modelado del problema y algoritmos abstractos . . . . . . . . . . . . . . . 4 1.1.3. Dise˜o de la soluci´n . . . . . . . . . . . . . . . . . . . . n o . . . . . . . . . . 5 1.1.4. Implementaci´n del dise˜o . . . . . . . . . . . . . . . . . o n . . . . . . . . . . 6 1.1.5. Verificaci´n y evaluaci´n de la soluci´n . . . . . . . . . . o o o . . . . . . . . . . 7 1.2. Tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.2.1. Definici´n de tipo de datos, tipo abstracto y estructura o . . . . . . . . . . 8 1.2.2. Tipos de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.2.3. Repaso de tipos y pseudolenguaje de definici´n . . . . . o . . . . . . . . . . 12 1.3. Algoritmos y algor´ıtmica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.3.1. Definici´n y propiedades de algoritmo . . . . . . . . . . o . . . . . . . . . . 15 1.3.2. An´lisis de algoritmos . . . . . . . . . . . . . . . . . . . a . . . . . . . . . . 17 1.3.3. Dise˜o de algoritmos . . . . . . . . . . . . . . . . . . . . n . . . . . . . . . . 21 1.3.4. Descripci´n del pseudoc´digo utilizado . . . . . . . . . . o o . . . . . . . . . . 25 1.4. Consejos para una buena programaci´n . . . . . . . . . . . . . o . . . . . . . . . . 26 1.4.1. Importancia del an´lisis y dise˜ o previos . . . . . . . . . a n . . . . . . . . . . 26 1.4.2. Modularidad: encapsulaci´n y ocultamiento . . . . . . . o . . . . . . . . . . 28 1.4.3. Otros consejos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Cuestiones de autoevaluaci´n . . . . . . . . . . . . . . . . . . . . . . o . . . . . . . . . . 31 Referencias bibliogr´ficas . . . . . . . . . . . . . . . . . . . . . . . . a . . . . . . . . . . 32
  3. 3. 1.1. Resoluci´n de problemas o 3 1.1. Resoluci´n de problemas o La habilidad de programar ordenadores constituye una de las bases necesarias de to- do inform´tico. Pero, por encima del nivel de programaci´n, la caracter´ a o ıstica fundamental de un inform´tico es su capacidad para resolver problemas. La resoluci´n de problemas a o inform´ticos en la vida real implica otras muchas m´s habilidades que unicamente saber a a ´ programar –habilidades que distinguen a un ingeniero de un simple programador. Implica comprender y analizar las necesidades de los problemas, saber modelarlos de forma abs- tracta diferenciando lo importante de lo irrelevante, disponer de una variada bater´ de ıa herramientas conceptuales que se puedan aplicar en su resoluci´n, trasladar un dise˜o abs- o n tracto a un lenguaje y entorno concretos y, finalmente, ser capaz de evaluar la correcci´n, o prestaciones y posibles inconvenientes de la soluci´n planteada. o Las herramientas que surgen en el desarrollo de programas son esencialmente de dos clases: estructuras de datos y algoritmos. Las estructuras de datos representan la parte est´tica de la soluci´n al problema, el componente almacenado, donde se encuentran a o los datos de entrada, de salida y los necesarios para posibles c´lculos intermedios. Los a algoritmos representan la parte din´mica del sistema, el componente que manipula los a datos para obtener la soluci´n. Algoritmos y estructuras de datos se relacionan de forma o muy estrecha: las estructuras de datos son manipuladas mediante algoritmos que a˜aden o n modifican valores en las mismas; y cualquier algoritmo necesita manejar datos que estar´n a almacenados en cierta estructura. La idea se puede resumir en la f´rmula magistral de o Niklaus Wirth: Algoritmos + Estructuras de datos = Programas. En ciertos ´mbitos de aplicaci´n predominar´ la componente algor´ a o a ıtmica –como, por ejemplo, en problemas de optimizaci´n y c´lculo num´rico– y en otros la componente o a e de estructuras –como en entornos de bases de datos y sistemas de informaci´n–, pero o cualquier aplicaci´n requerir´ siempre de ambos. Esta dualidad aparece en el nivel abs- o a tracto: un tipo abstracto est´ formado por un dominio de valores (estructura abstracta) y a un conjunto de operaciones (algoritmos abstractos). Y la dualidad se refleja tambi´n en el e nivel de implementaci´n: un m´dulo (en lenguajes estructurados) o una clase (en lengua- o o jes orientados a objetos) est´n compuestos por una estructura de atributos o variables, y a una serie de procedimientos de manipulaci´n de los anteriores. o Antes de entrar de lleno en el estudio de los algoritmos y las estructuras de datos, vamos a analizar los pasos que constituyen el proceso de resoluci´n de problemas. o 1.1.1. An´lisis de requisitos del problema a El proceso de resoluci´n de problemas parte siempre de un problema, de un enun- o ciado m´s o menos claro que alguien plantea porque le vendr´ bien que estuviera resuelto1 . a ıa El primer paso es la comprensi´n del problema, entender las caracter´ o ısticas y peculiari- dades de lo que se necesita. Este an´lisis de los requisitos del problema puede ser, a de por s´ una de las grandes dificultades; sobre todo en grandes aplicaciones, donde los ı, documentos de requisitos son ambiguos, incompletos y contradictorios. 1 Esta afirmaci´n puede parecer irrelevante por lo obvia. Pero no debemos perder de vista el objetivo o ultimo de la utilidad pr´ctica, en el estudio de los algoritmos y las estructuras de datos. ´ a
  4. 4. 4 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos El an´lisis debe producir como resultado un modelo abstracto del problema. El a modelo abstracto es un modelo conceptual, una abstracci´n del problema, que reside o exclusivamente en la mente del individuo y donde se desechan todas las cuestiones irre- levantes para la resoluci´n del problema. Supongamos, por ejemplo, los dos siguientes o problemas, con los cuales trabajaremos en el resto de los puntos. Ejemplo 1.1 El problema de las cifras. Dado un conjunto de seis n´meros enteros, u que pueden ser del 1 al 10, o 25, 50, 75 o 100, encontrar la forma de conseguir otro entero ´ ´ dado entre 100 y 999, combinando los n´meros de partida con las operaciones de suma, u resta, producto y divisi´n entera. Cada uno de los seis n´meros iniciales s´lo se puede o u o usar una vez. Ejemplo 1.2 El problema de detecci´n de caras humanas. Dada una imagen en o color en un formato cualquiera (por ejemplo, bmp, jpeg o gif) encontrar el n´mero de u caras humanas presentes en la misma y la posici´n de cada una de ellas. o Figura 1.1: Ejemplos del problema de las cifras (izquierda) y detecci´n de caras (derecha). o Ambos enunciados representan categor´ de problemas muy distintas. El proble- ıas ma 1.1 tiene un enunciado m´s o menos claro, aunque cabr´ algunas dudas. Es de a ıan tipo matem´tico, se puede modelar formalmente y es previsible que exista un algoritmo a adecuado. El problema 1.2 tiene un enunciado m´s corto pero mucho m´s ambiguo, no a a est´ claro exactamente lo que se pide. Es m´s, incluso teni´ndolo bien claro, el problema a a e parece m´s adecuado para ser resuelto de cabeza por un humano, pero dif´ de imple- a ıcil mentar en un ordenador. Aun as´ ambos problemas tienen algo en com´n: son de inter´s, ı, u e as´ que necesitamos resolverlos de la mejor forma que podamos. ı 1.1.2. Modelado del problema y algoritmos abstractos El primer paso ser´ crear un modelo abstracto, en el que nos quedamos con lo ıa esencial del problema. Normalmente, este modelo se crea a trav´s de una analog´ con e ıa algo conocido previamente. Por ejemplo, para ense˜ar a un alumno inexperto lo que es un n algoritmo (concepto para ´l desconocido) se utiliza la analog´ con una receta de cocina e ıa
  5. 5. 1.1. Resoluci´n de problemas o 5 (concepto conocido, esperemos), y las estructuras de datos se asimilan con la disposici´n de o armarios, cajones y recipientes donde se guardan los ingredientes y el bizcocho resultante. Ejemplo 1.1 (cont.) Modelo abstracto. Para el problema de las cifras, el modelo abstracto puede prescindir del hecho de que los n´meros sean seis, de que est´n en ciertos u e rangos y que se trabaje s´lo con enteros. Una soluci´n ser´ una expresi´n matem´tica o o ıa o a –usando los n´meros de partida– que produce el n´mero buscado. Entonces, el problema u u se podr´ resolver generando todas las posibles expresiones que contengan los n n´meros ıa u iniciales, o menos, y qued´ndonos la que obtenga un resultado m´s pr´ximo al buscado. a a o Pero el modelo de un problema no tiene por qu´ ser unico. Otra persona podr´ e ´ ıa interpretarlo como un conjunto de cajas, que se combinan entre s´ Ahora tendr´ ı. ıamos un modelo “temporal”: tenemos que buscar dos cajas que combin´ndolas nos den el objetivo a o un valor pr´ximo. Si no llegamos al resultado, buscar otra caja y as´ sucesivamente. La o ı idea resultante ser´ similar a lo que aparece en la figura 1.1, abajo a la izquierda. ıa Ejemplo 1.2 (cont.) Modelo abstracto. En el problema de detecci´n de caras, el o modelo abstracto elimina el hecho de que las im´genes tengan distinto formato o incluso a que los objetos buscados sean caras humanas. Un posible modelo ser´ un problema de ıa patrones –patrones como los de un sastre– que admiten cierta variabilidad: se pueden mover, estirar, cambiar el tama˜o, rotar, etc. El problema consistir´ en buscar esos pa- n ıa trones en las im´genes, en todos los sitios que se pueda. Grosso modo, un algoritmo podr´ a ıa consistir en generar todas las posibles colocaciones, rotaciones y tama˜os de los patrones n y quedarnos con las que “encajen” con lo que hay en la imagen de partida. 1.1.3. Dise˜ o de la soluci´n n o Una vez que tengamos un modelo completo y adecuado, significar´ que hemos en- a tendido bien el problema. Como hemos visto en los ejemplos, el modelo conlleva tambi´n e un algoritmo informal, que no es m´s que una vaga idea de c´mo resolver el problema. El a o siguiente paso de refinamiento consistir´ en dise˜ar una soluci´n. Usando otra analog´ ıa n o ıa, el dise˜ o de un programa es equivalente a los planos de un arquitecto: el dise˜o des- n n cribe el aspecto que tendr´ el programa, los materiales que se necesitan y d´nde y c´mo a o o colocarlos. El dise˜o es siempre un paso previo a la implementaci´n, pero que se olvida n o muy a menudo por los programadores primerizos. Un ingeniero inform´tico deber´ ser a ıa tan consciente del error de programar sin partir de un buen dise˜o previo, como lo es un n arquitecto del fallo de hacer un puente sin tener antes los planos. El dise˜o de un programa consta de dos clases de elementos: tipos de datos y algo- n ritmos. Los tipos de datos usados a nivel de dise˜o son tipos abstractos, en los cuales n lo importante son las operaciones que ofrecen y no la representaci´n en memoria. Los o algoritmos son una versi´n refinada de los algoritmos abstractos del paso anterior. No o obstante, son a´n algoritmos en pseudoc´digo, donde se dan cosas por supuestas. u o Definir los tipos de datos para almacenar la soluci´n suele ser mucho m´s dif´ que o a ıcil definirlos para los datos de entrada. Adem´s, modificaciones en los algoritmos pueden a requerir cambios en los tipos de datos y viceversa. Ejemplo 1.1 (cont.) Dise˜o de la soluci´n. En el problema de las cifras, los tipos n o de datos para la entrada podr´ ser una tabla de 6 enteros y otro entero para el ıan
  6. 6. 6 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos n´mero buscado. Pero ¿c´mo representar la soluci´n? En este caso, la soluci´n es una u o o o serie de operaciones sobre los n´meros del array. Cada operaci´n podr´ ser una tupla u o ıa (indice1 , indice2 , operacion), que significa: “operar el n´mero de indice1 en el array con u el de indice2 usando operacion”. La representaci´n de la soluci´n ser´ una lista de es- o o ıa tas tuplas. Con esta representaci´n, el algoritmo deber´ tener en cuenta que indice1 e o ıa indice2 ya est´n usados. Adem´s aparece otro nuevo n´mero, que puede usarse con pos- a a u terioridad. Entonces, puede ser necesario definir otros tipos o modificar los existentes, por ejemplo haciendo que la tupla signifique: “operar el n´mero ..., almacenando el resultado u en indice1 y colocando un 0 en indice2 ”. Ejemplo 1.2 (cont.) Dise˜o de la soluci´n. En el problema de detecci´n de caras, n o o la entrada ser´ una imagen, que se puede representar con matrices de enteros, de cierto ıa ancho y alto. Adem´s, hay otro dato oculto, el modelo de lo que se considera una cara. a ¿C´mo se puede modelar la forma de todas las posibles caras? ¿Qu´ informaci´n se deber´ o e o ıa almacenar? La representaci´n no es nada trivial. Por ejemplo, una soluci´n sencilla ser´ o o ıa tener un conjunto variado de im´genes de caras de ejemplo. La representaci´n de la a o soluci´n debe indicar el n´mero de caras y la posici´n de cada una; por ejemplo centro, o u o (xi , yi ), escala, si , y rotaci´n, αi , de cada cara. Podr´ o ıamos usar una lista de tuplas de tipo (xi , yi , si , αi ). Con todo esto, habr´ que dise˜ar un algoritmo en pseudolenguaje que ıa n pruebe todos los valores de los par´metros anteriores, y diga d´nde se encuentra una cara. a o 1.1.4. Implementaci´n del dise˜ o o n El siguiente paso en la resoluci´n del problema es la implementaci´n. La implementa- o o ci´n parte del dise˜o previo, que indica qu´ cosas se deben programar, c´mo se estructura o n e o la soluci´n del problema, d´nde colocar cada funcionalidad y qu´ se espera en concreto de o o e cada parte en la que se ha descompuesto la soluci´n. Esta descripci´n de lo que debe hacer o o cada parte es lo que se conoce como la especificaci´n. Una implementaci´n ser´ correcta o o a si cumple su especificaci´n. o La dualidad tipos/algoritmos del dise˜o se traslada en la implementaci´n a estruc- n o turas/algoritmos. Para cada uno de los tipos de datos del dise˜o, se debe elegir una n estructura de datos adecuada. Por ejemplo, los dos ejemplos de problemas analizados necesitan listas. Una lista se puede representar mediante una variedad de estructuras: lis- tas enlazadas con punteros o cursores, mediante celdas adyacentes en un array, enlazadas simple o doblemente, con nodos cabecera o no, etc. La elecci´n debe seguir los criterios o de eficiencia –en cuanto a tiempo y a uso de memoria– que se hayan especificado para el problema. En cada caso, la estructura m´s adecuada podr´ ser una u otra. a a Por otro lado, la implementaci´n de los algoritmos tambi´n se puede ver como o e una serie de tomas de decisiones, para trasladar un algoritmo en pseudoc´digo a un o lenguaje concreto. Por el ejemplo, si se necesita repetir un c´lculo varias veces, se puede a usar un bucle for, un while, se puede usar recursividad e incluso se puede poner varias veces el mismo c´digo si el n´mero de ejecuciones es fijo2 . o u Realmente, la implementaci´n de estructuras de datos y algoritmos no va por sepa- o rado. Ambas son simult´neas. De hecho, as´ ocurre con los mecanismos de los lenguajes a ı 2 Algo nada aconsejable, de todos modos.
  7. 7. 1.2. Tipos de datos 7 de programaci´n –m´dulos o clases– en los que se asocia cada estructura con los algo- o o ritmos que la manejan. La organizaci´n temporal de la implementaci´n es, m´s bien, o o a por funcionalidades: cuestiones de interface con el usuario, accesos a disco, funcionalidad matem´tica, etc. a ANÁLISIS DEL MODELO DISEÑO DE TIPOS DE DATOS IMPLEMEN- LA SOLUCIÓN ESTRUCTURAS PROBLEMA PROBLEMA ABSTRACTO ABSTRACTOS TACIÓN DE DATOS Y ALGORITMO ALGORITMO EN ALGORITMOS INFORMAL PSEUDOCÓDIGO VERIFICACIÓN Y EVALUACIÓN Figura 1.2: Esquema del proceso c´ ıclico de desarrollo de software. 1.1.5. Verificaci´n y evaluaci´n de la soluci´n o o o La resoluci´n de un problema no acaba con la implementaci´n de un programa3 . Los o o programas deber´ ser comprobados de forma exhaustiva, verificando que se ajustan a los ıan objetivos deseados, obtienen los resultados correctos sin provocar fallos de ejecuci´n. En la o pr´ctica, hablamos de un proceso c´ a ıclico de desarrollo: implementar, verificar, corregir la implementaci´n, volver a verificar, volver a corregir, etc´tera. En cierto momento, puede o e que tengamos que cambiar el dise˜o, e incluso puede que lo que est´ fallando sea el modelo n e abstracto. Cuanto m´s atr´s tengamos que volver, m´s tiempo habremos perdido haciendo a a a cosas que luego resultan in´tiles. u La evaluaci´n de los programas incluye no s´lo la correcci´n del resultado sino o o o tambi´n la medici´n de las prestaciones, lo que normalmente se denomina el an´lisis de e o a eficiencia. La eficiencia es una proporci´n entre los resultados obtenidos y los recursos o consumidos. Fundamentalmente, un programa consume recursos de tiempo y de memoria. Aunque usualmente hablamos de “an´lisis de algoritmos”, tanto las estructuras de datos a como los algoritmos influyen en el consumo de recursos de un programa. Incluso antes de llegar a la implementaci´n de un programa, es posible y adecuado o hacer el an´lisis del algoritmo en pseudoc´digo. Este an´lisis ser´ una previsi´n de la a o a a o eficiencia que obtendr´ la soluci´n dise˜ada si la implementamos. Se trata, por lo tanto, ıa o n de un estudio te´rico. Cuando el estudio se realiza sobre una implementaci´n concreta, o o hablamos normalmente de estudios experimentales. 1.2. Tipos de datos A modo de breve repaso, vamos a ver en esta secci´n las definiciones de tipo de datos, o tipo abstracto, estructura de datos, las distintas clases de tipos y algunos de los principales 3 O no deber´ Desafortunadamente, estamos tan acostumbrados a ver programas que se cuelgan, ıa. como coches que fallan justo al salir del garaje, ¡exactamente por lo mismo: falta de pruebas!
  8. 8. 8 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos tipos de datos usados en programaci´n. Se supone que el alumno ya est´ familiarizado o a con todos estos conceptos. Es m´s, ser´ un interesante ejercicio pararse en este punto, a ıa cerrar el libro y tratar recordar todo lo que se pueda de los anteriores temas. 1.2.1. Definici´n de tipo de datos, tipo abstracto y estructura o Posiblemente, la primera duda del programador no ingeniero ser´ ¿qu´ necesidad ıa: e hay de tantos t´rminos para referirse a lo mismo? Los conceptos de tipo abstracto de datos, e tipo de datos y estructura de datos no son, en absoluto, redundantes ni incoherentes. Co- mo ya hemos visto, aparecen en los distintos niveles de desarrollo. Un tipo abstracto es un concepto de dise˜o y por lo tanto previo e independiente de cualquier implementaci´n. n o Un tipo de datos es un concepto de programaci´n y se puede ver como la realizaci´n o o o materializaci´n de un tipo abstracto. La estructura de datos es la organizaci´n o disposi- o o ci´n en memoria de la informaci´n almacenada para cierto tipo de datos. Veamos, por o o partes, las definiciones. Definici´n 1.1 Un Tipo Abstracto de Datos (TAD) est´ compuesto por un dominio o a abstracto de valores y un conjunto de operaciones definidas sobre ese dominio, con un comportamiento espec´ ıfico. Por ejemplo, los booleanos constituyen un TAD, formado por el dominio de valores {verdadero, falso} y las operaciones sobre ese dominio: NO, Y, O, XOR, IMPLICA, etc. Decimos que el dominio de valores es abstracto porque no se le supone ninguna repre- sentaci´n ni ning´n significado. Es decir, el valor verdadero no tiene por qu´ ser un 1, ni o u e debe almacenarse necesariamente en un bit. Pero, es m´s, ¿qu´ significa verdadero? Es a e un concepto hueco, vac´ sin ning´n significado expl´ ıo, u ıcito. El unico significado le viene ´ dado impl´ ıcitamente por su relaci´n con las operaciones del tipo. Por ejemplo, verdadero o Y cualquier cosa es siempre igual a cualquier cosa. En el otro extremo de complejidad, otro ejemplo de TAD podr´ ser las ventanas de ıan un entorno Windows o XWindows. En este caso, el dominio de valores estar´ compuesto ıa por el conjunto –pr´cticamente infinito– de todas las ventanas en sus distintas formas, a tama˜os, colores y contenidos. Las operaciones sobre el dominio ser´ del tipo: crear n ıan ventana, mover, cambiar de tama˜o, a˜adir botones, etc. n n Las operaciones del TAD son abstractas, en el sentido de que sabemos lo que hacen pero no c´mo lo hacen. A la hora de programar el TAD, esto da lugar a un principio o conocido como ocultaci´n de la implementaci´n: lo importante es que se calcule el o o resultado esperado y no c´mo se calcule. Volveremos a insistir en esto m´s adelante. o a En cuanto que el TAD es un concepto abstracto, reside exclusivamente como un ente et´reo en la mente del programador. La realizaci´n del TAD en un lenguaje concreto, en e o cierto entorno de programaci´n y por determinado programador, es lo da lugar a un tipo o de datos, ahora sin el apellido “abstracto”. Definici´n 1.2 El tipo de datos de una variable, un par´metro o una expresi´n, deter- o a o mina el conjunto de valores que puede tomar esa variable, par´metro o expresi´n, y las a o operaciones que se pueden aplicar sobre el mismo.
  9. 9. 1.2. Tipos de datos 9 El tipo de datos es un concepto de programaci´n, de implementaci´n, mientras o o que el TAD es un concepto matem´tico previo a la programaci´n. Un tipo de datos debe a o ajustarse al comportamiento esperado de un TAD. Por ejemplo, el tipo integer de Pascal o el tipo int de C son dos tipos de datos, que corresponden al TAD de los enteros, Z. Adem´s de los tipos de datos elementales existentes en un lenguaje de programaci´n, a o los lenguajes suelen ofrecer mecanismos para que el usuario se defina sus propios tipos. Estos mecanismos pueden ser m´s o menos avanzados. En lenguajes como C o Pascal, la a definici´n del tipo indica los atributos que se almacenan para las variables de ese tipo. o Las operaciones se definen por separado. Por ejemplo, en C una pila de enteros ser´ ıa: struct PilaEnteros { int datos[]; int tope, maximo; }; En lenguajes orientados a objetos, como C++ o Java, los tipos definidos por el usuario se llaman clases, y su definici´n incluye tanto los atributos almacenados como o las operaciones sobre los mismos. Por ejemplo, en C++ la definici´n de las pilas podr´ o ıa ser la siguiente: class PilaEnteros { private: // Atributos int datos[]; int tope, maximo; public: // Operaciones PilaEnteros (int max); Push (int valor); int Pop(); }; Volveremos a tratar los mecanismos de definici´n de tipos en el siguiente cap´ o ıtulo. En la programaci´n de un tipo de datos surgen dos cuestiones: c´mo almacenar en o o memoria los valores del dominio y c´mo programar las operaciones del tipo de datos. o La primera cuesti´n implica dise˜ar una estructura de datos para el tipo. La segunda o n est´ relacionada con la implementaci´n de las operaciones de manipulaci´n y, evidente- a o o mente, estar´ en funci´n de la primera. a o Definici´n 1.3 La estructura de datos de un tipo de datos es la disposici´n en memo- o o ria de los datos necesarios para almacenar los valores de ese tipo. Por ejemplo, para representar las pilas podemos usar una estructura de arrays como la que aparece antes, o una lista enlazada de enteros, o una lista de arrays de enteros, o un array de listas de enteros, y as´ sucesivamente. Por lo tanto, un tipo de datos puede ı ser implementado utilizando diferentes estructuras.
  10. 10. 10 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos De forma an´loga, una misma estructura de datos puede corresponder a diferentes a tipos. Por ejemplo, una estructura de arrays puede usarse para implementar listas, pilas o colas de tama˜o limitado. Ser´ la forma de actuar de las operaciones la que determine n a si el array almacena una lista, una pila o una cola. La utilizaci´n de estructuras de datos adecuadas para cada tipo de datos es una o cuesti´n fundamental en el desarrollo de programas. La elecci´n de una estructura ten- o o dr´ efectos no s´lo en la memoria utilizada por el programa, sino tambi´n en el tiempo a o e de ejecuci´n de los algoritmos. Por ejemplo, la representaci´n de colas con arrays puede o o producir desperdicio de memoria, ya que el tama˜o del array debe ser el tama˜o de la n n m´xima ocupaci´n prevista de la cola. En una representaci´n con punteros se podr´ usar a o o a menos memoria, pero posiblemente el tiempo de ejecuci´n ser´ sensiblemente mayor. o a 1.2.2. Tipos de tipos Dentro de este apartado vamos a hacer un repaso de terminolog´ clases y propie- ıa, dades relacionadas con los tipos de datos. Podemos hacer una primera clasificaci´n de los tipos teniendo en cuenta si el tipo o es un tipo est´ndar del lenguaje o no. Encontramos dos categor´ a ıas: Tipos primitivos o elementales. Son los que est´n definidos en el lenguaje de a programaci´n. Por ejemplo, en C son tipos primitivos los tipos int, long, char y o float, entre otros. Tipos definidos por el usuario. Son todos los tipos no primitivos, definidos por la misma persona que los usa, o bien por otro programador. Idealmente, un buen lenguaje deber´ hacer transparente el uso de tipos de una u ıa otra categor´ Es decir, al usar una variable de cierto tipo T deber´ ser lo mismo que ıa. ıa T sea un tipo primitivo o definido por el usuario. Ambos deber´ ser conceptualmente ıan equivalentes. Pero esto no siempre ocurre. Por ejemplo, en C es posible hacer asignaciones entre tipos primitivos, pero no entre ciertos tipos definidos por el usuario. La asignaci´n o “a= b;” funcionar´ o no, dependiendo de que los tipos de a y b sean primitivos o no. a Por otro lado, podemos hacer otra clasificaci´n –seg´n el n´mero de valores alma- o u u cenados– en tipos simples y compuestos: Tipo simple. Una variable de un tipo simple contiene un unico valor en cierto ´ instante. Por ejemplo, los enteros, reales, caracteres y booleanos son tipos simples. Tipo compuesto. Un tipo compuesto es aquel que se forma por la uni´n de varios o tipos simples o compuestos. Por lo tanto, una variable de tipo compuesto contiene varios valores de tipo simple o compuesto. Los lenguajes de programaci´n suelen ofrecer distintos mecanismos de composici´n o o para crear tipos compuestos. Los m´s habituales son los arrays y los registros. Un a array contiene una serie de posiciones consecutivas de variables del mismo tipo, cada una identificada con un ´ındice dentro del rango del array. Los registros se definen enumerando los campos que forman el tipo compuesto, cada uno de los cuales tiene un nombre y un
  11. 11. 1.2. Tipos de datos 11 tipo (que, a su vez, puede ser simple o compuesto). Las clases de lenguajes orientados a objetos tambi´n se pueden ver como una manera de crear tipos compuestos, en la cual se e pueden a˜adir atributos y operaciones al tipo. n Normalmente los tipos simples son tipos elementales y los tipos compuestos son definidos por el usuario. No obstante, tambi´n pueden existir tipos compuestos definidos e en el lenguaje de programaci´n, y tipos simples definidos por el usuario. Un mecanismo o que permite al usuario definir nuevos tipos simples son los enumerados. Un enumerado se define listando de forma completa el dominio del tipo. Por ejemplo, el tipo Sexo se puede definir como un tipo enumerado con el dominio {masculino, femenino}. Un tipo contenedor, o colecci´n, es un tipo compuesto que puede almacenar un o n´mero indefinido de valores de otro tipo cualquiera. Por ejemplo, una lista de enteros o un u array de Sexo son tipos contenedores. Un registro con dos enteros no ser´ un contenedor. ıa En general, interesa que los tipos contenedores puedan almacenar valores de otro tipo cualquiera; por ejemplo, que las listas puedan ser de enteros, de reales, de registros o de cualquier otra cosa. Un tipo se dice que es gen´rico o parametrizado si su significado e depende de otro tipo que es pasado como par´metro. Por ejemplo, una lista gen´rica a e tendr´ la forma Lista[T], donde T es un par´metro que indica el tipo de los objetos ıa a almacenados. Podr´ ıamos tener una variable a de tipo Lista[entero] y otra variable b de tipo Lista[real]; estos casos concretos se llaman instanciaciones del tipo gen´rico. e Realmente son muy pocos los lenguajes que permiten la definici´n de tipos para- o metrizados. Entre ellos se encuentra C++, que permite parametrizar un tipo mediante el uso de plantillas template. Por ejemplo, un tipo gen´rico Pila[T] ser´ e ıa: template <class T> class Pila { private: // Atributos T datos[]; int tope, maximo; public: // Operaciones Pila (int max); Push (T valor); T Pop(); }; En la parte de pr´cticas se profundizar´ m´s en el uso de plantillas como una manera a a a de crear tipos gen´ricos. e Finalmente, podemos hacer una distinci´n entre tipos mutables e inmutables. Deci- o mos que un tipo de datos es inmutable cuando los valores del tipo no cambian despu´s e de haber sido creados. El tipo ser´ mutable si los valores pueden cambiar. ¿Qu´ impli- a e caciones tiene que un tipo sea mutable o inmutable? Supongamos que queremos definir el tipo Lista[T]. Si el tipo se define con una representaci´n mutable, podemos incluir o operaciones que a˜aden o suprimen elementos de la lista. n operaci´n Inserta (var lista: Lista[T]; elemento: T) o operaci´n SuprimePrimero (var lista: Lista[T]) o
  12. 12. 12 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos Donde lista es un par´metro que se puede modificar dentro de las operaciones. Pero a si el tipo es inmutable no ser´ posible que un par´metro de entrada se modificara. En ese ıa a caso, las operaciones de inserci´n y eliminaci´n deber´ devolver una nueva lista, con el o o ıan elemento correspondiente insertado o eliminado. operaci´n Inserta (lista: Lista[T]; elemento: T): Lista[T] o operaci´n SuprimePrimero (lista: Lista[T]): Lista[T] o Seg´n nos interese, definiremos los tipos como mutables o inmutables. Normalmente, u la mayor´ de los tipos ser´n mutables, aunque en algunos casos nos interesar´ usar tipos ıa a a inmutables: la suposici´n de que los valores del tipo no cambian puede simplificar, o hacer o m´s eficiente, la implementaci´n de la estructura de datos para el tipo. a o 1.2.3. Repaso de tipos y pseudolenguaje de definici´n o La mayor´ de los lenguajes ofrecen al programador un conjunto similar de tipos de ıa datos simples (enteros, reales, booleanos, etc.), con las operaciones necesarias para mane- jarlos. Estos tipos tienen una correspondencia con los tipos manejados por el procesador, por lo que su implementaci´n es muy eficiente. Vamos a ver los que nos podemos encon- o trar m´s habitualmente. Para cada uno de ellos, indicaremos tambi´n la notaci´n que se a e o usar´ en el resto del libro para referirse a ese tipo, cuando usemos el pseudolenguaje4 . a Enteros. Con signo o sin signo, con precisi´n simple, doble o reducida (un byte). o En nuestro pseudoc´digo supondremos un unico tipo entero, con las operaciones o ´ habituales. Reales. Con precisi´n simple o doble. Para los n´meros reales se suelen usar re- o u presentaciones mantisa-exponente. Cuando lo usemos, pondremos el tipo real. Caracteres. Lo denotaremos como caracter. Cadenas. En algunos lenguajes las cadenas de caracteres son tipos elementales. En otros, como en C, las cadenas no son un tipo elemental sino que son simplemente arrays de caracteres. En nuestro caso, suponemos que tenemos el tipo cadena con operaciones para concatenar o imprimir por pantalla. Booleanos. Igual que antes, no existen en C –donde se usan enteros en su lugar–, aunque s´ en C++ y Pascal. Supondremos que tenemos el tipo booleano. ı En cuanto a los tipos contenedores (listas, pilas, colas, arboles, etc.), normalmente ´ no forman parte del lenguaje de programaci´n, sino que se definen en librer´ de utili- o ıas dades que se pueden incluir en un programa o no. No obstante, s´ que se suelen incluir ı mecanismos de composici´n de tipos como arrays, registros y tipos enumerados. En nues- o tro pseudolenguaje tenemos la posibilidad de definir nuevos tipos, mediante una cl´usula a tipo. Adem´s, permitimos que se definan tipos parametrizados. Por ejemplo, podemos a tener los siguientes tipos: 4 Este pseudolenguaje de definici´n de tipos s´lo indica la estructura de datos del tipo. Las operaciones o o son descritas aparte. Al implementar los tipos –sobre todo usando un lenguaje orientado a objetos– no hay que olvidar que las estructuras y las operaciones deben ir juntos, en el mismo m´dulo o clase. o
  13. 13. 1.2. Tipos de datos 13 tipo DiasMeses = array [1..12] de entero Pila[T] = registro datos: array [1..MAXIMO] de T tope, maximo: entero finregistro Sexo = enumerado (masculino, femenino) Para los arrays, se indica entre corchetes, [min..max], el rango m´ ınimo y m´ximo de a ındices del array5 . Para los registros, se listan sus atributos, cada uno con su nombre los ´ y tipo. Y, por otro lado, los enumerados se forman listando los valores del dominio. En algunos sitios usaremos tambi´n registros con variantes. Un registro con va- e riantes es un registro que contiene un campo especial, en funci´n del cual una variable o podr´ tener unos atributos u otros (pero nunca ambos a la vez). Por ejemplo, si en a una aplicaci´n que almacena empleados de una empresa, queremos almacenar diferente o informaci´n para los hombres y las mujeres podemos tener algo como lo siguiente: o tipo Empleado = registro nombre: cadena edad: entero seg´n sexo: Sexo u masculino: (direcci´n: cadena; sueldo: entero) o femenino: (salario: entero; tel´fono: entero) e finseg´nu finregistro En cuanto a los tipos colecciones, adem´s de los arrays daremos por supuesto que a disponemos de los siguientes: Listas. Tendremos listas parametrizadas, Lista[T], y que guardan una posici´n actu- o al. De esta forma, la inserci´n, eliminaci´n, etc., se hacen siempre sobre la posici´n o o o actual. Por conveniencia, en algunos sitios no se dar´n por supuestas las listas. a Pilas. Una pila es una lista LIFO: last in, first out (´ltimo en entrar, primero en u salir). Suponemos el tipo gen´rico Pila[T], con las operaciones push, pop y tope. e Colas. Una cola es una lista FIFO: first in, first out (primero en entrar, primero en salir). Suponemos el tipo gen´rico Cola[T], con las operaciones meter, sacar y cabeza. e Por ultimo, pero no menos importante, tenemos el tipo de datos puntero. Consi- ´ derado como un tipo non-grato por algunos autores6 por tratarse de un concepto cercano al bajo nivel, lo cierto es que los punteros son esenciales en la definici´n de estructuras de o datos. Los punteros son un tipo parametrizado. Una variable de tipo Puntero[T] contiene una referencia, o direcci´n de memoria, donde se almacena una variable de tipo T. o 5 Ojo, hay que tener en cuenta que en lenguajes como C/C++ el ´ ındice m´ınimo es siempre 0, as´ que ı s´lo se indica el tama˜o del array. o n 6 Por ejemplo, seg´n C.A.R. Hoare, “su introducci´n en los lenguajes de alto nivel fue un paso atr´s u o a del que puede que nunca nos recuperemos”. En el lenguaje Java se decidi´ no permitir el uso de punteros, o aunque dispone de referencias, que vienen a ser lo mismo.
  14. 14. 14 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos En nuestro pseudolenguaje suponemos que tenemos el tipo Puntero[T] con las si- guientes operaciones y valores especiales: Operador de indirecci´n. Si a es de tipo Puntero[T], entonces la variable apuntada o por a se obtiene con el operador de indirecci´n flecha, ↑, esto es: a↑ es de tipo T. o Obtenci´n de la direcci´n. Si t es una variable de tipo T, entonces podemos o o obtener la direcci´n de esa variable con la funci´n PunteroA(t: T), que devuelve un o o puntero de tipo Puntero[T]. Puntero nulo. Existe un valor especial del tipo Puntero[T], el valor predefinido NULO, que significa que el puntero no tiene una referencia v´lida o no ha sido a inicializado. Creaci´n de una nueva variable. Para crear una nueva variable apuntada por un o puntero, suponemos la funci´n gen´rica Nuevo(T: Tipo), que devuelve un Puntero[T] o e que referencia a una nueva variable creada. Esta variable se borrar´ con la funci´n a o Borrar(a: Puntero[T]). Comparaci´n. Suponemos que hay definidos operadores de comparaci´n de igual- o o dad y desigualdad entre punteros (=, =). No consideramos otras operaciones sobre punteros, al contrario de lo que ocurre con C/C++, donde los punteros son consideramos como enteros, pudiendo aplicar sumas, restas, etc. Por esta raz´n, entre otras, decimos que lenguajes como C/C++ son lenguajes o d´bilmente tipados: existen pocas restricciones en la asignaci´n, mezcla y manipulaci´n e o o de tipos distintos. Como contraposici´n, los lenguajes fuertemente tipados, como Pas- o cal, tienen reglas m´s estrictas en el sistema de compatibilidad de tipos. En la pr´ctica, a a esta flexibilidad de C/C++ le proporciona una gran potencia pero es fuente de numerosos errores de programaci´n.o 1.3. Algoritmos y algor´ ıtmica Como ya hemos visto, los algoritmos junto con las estructuras de datos constituyen los dos elementos imprescindibles en el proceso de resoluci´n de problemas. Los primeros o definen el componente manipulador y los segundos el componente almacenado, y se com- binan estrechamente para crear soluciones a los problemas. No est´ de m´s recordar que la historia de los algoritmos es mucho anterior a la a a aparici´n de los ordenadores. De hecho, podr´ o ıamos datar la aparici´n de los algoritmos o al primer momento en que los seres humanos se plantearon resolver problemas de forma gen´rica. Hist´ricamente uno de los primeros algoritmos inventados, para la resoluci´n e o o de problemas de tipo matem´tico, es el algoritmo de Euclides de Alejandr´ Egipto, a ıa, propuesto alrededor del a˜o 300 a.C. Euclides propone el siguiente m´todo para calcular n e el m´ximo com´n divisor de dos n´meros enteros. a u u Ejemplo 1.3 Algoritmo de Euclides para calcular el m´ximo com´n divisor de dos a u n´meros enteros, a y b, siendo a > b. u
  15. 15. 1.3. Algoritmos y algor´ ıtmica 15 1. Hacer r := a m´dulo b o 2. Si r = 0 entonces el m´ximo com´n divisor es b a u 3. En otro caso: 3.1. Hacer a:= b 3.2. Hacer b:= r 3.3. Volver al paso 1 Por ejemplo, si aplicamos el algoritmo con a = 20 y b = 15, entonces r = 5. No se cumple la condici´n del paso 2 y aplicamos el paso 3. Obtenemos a = 15, b = 5. Volvemos o al punto 1, llegando a r = 0. Al ejecutar el paso 2 se cumple la condici´n, con lo que el o resultado es 5. Pero el honor de dar nombre al t´rmino algoritmo lo recibe el matem´tico arabe e a ´ del siglo IX Muhammad ibn Musa al-Khwarizmi (que significa algo as´ como Mo- ı hamed hijo de Mois´s de Khorezm, en Oriente Medio). Sus tratados sobre la resoluci´n e o de ecuaciones de primer y segundo grado ponen de relieve la idea de resolver c´lculos a matem´ticos a trav´s de una serie de pasos predefinidos. Por ejemplo, para resolver ecua- a e ciones de segundo grado del tipo: x2 + bx = c, define la siguiente serie de pasos7 . Ejemplo 1.4 Algoritmo de al-Khwarizmi para resolver la ecuaci´n x2 + bx = c. Se o 2 muestra el ejemplo x + 10x = 39, el mismo que al-Khwarizmi utiliza en su libro “Hisab al-jabr w’al-muqabala” (“El c´lculo de reducci´n y restauraci´n”), sobre el a˜o 825 d.C. a o o n 1. Tomar la mitad de b. (En el ejemplo, 10/2 = 5) 2. Multiplicarlo por s´ mismo. (En el ejemplo, 5 ∗ 5 = 25) ı 3. Sumarle c. (En el ejemplo, 25 + 39 = 64) √ 4. Tomar la ra´ cuadrada. (En el ejemplo, 64 = 8) ız 5. Restarle la mitad de b. (En el ejemplo, 8 − 10/2 = 8 − 5 = 3) El resultado es: (b/2)2 − c−b/2, expresado algor´ ıtmicamente. El matem´tico bag- a dad´ es tambi´n considerado como el introductor del sistema de numeraci´n ar´bigo en ı e o a occidente, y el primero en usar el n´mero 0 en el sistema posicional de numeraci´n. u o 1.3.1. Definici´n y propiedades de algoritmo o ¿Qu´ es un algoritmo? En este punto, el lector no deber´ tener ning´n problema para e ıa u dar su propia definici´n de algoritmo. Desde los ejemplos de algoritmos m´s antiguos – o a como los de Euclides y al-Khwarizmi, mostrados antes– hasta los m´s modernos algoritmos a –como los usados por el buscador Google o en compresi´n de v´ o ıdeo con DivX– todos ellos se caracterizan por estar constituidos por una serie de pasos, cuya finalidad es producir unos datos de salida a partir de una entrada. Adem´s, para que ese conjunto de reglas tenga a sentido, las reglas deben ser precisas y terminar en alg´n momento. As´ pues, podemos u ı dar la siguiente definici´n informal de algoritmo. o Definici´n 1.4 Un algoritmo es una serie de reglas que dado un conjunto de datos o de entrada (posiblemente vac´ produce unos datos de salida (por lo menos una) y ıo) cumple las siguientes propiedades: 7 La notaci´n es adaptada, ya que al-Khwarizmi no utiliza s´ o ımbolos, expresiones ni variables, sino una descripci´n completamente textual. Por ejemplo, a b lo llama “las ra´ o ıces” y a c “las unidades”.
  16. 16. 16 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos Definibilidad. El conjunto de reglas debe estar definido sin ambig¨edad, es decir, u no pueden existir dudas sobre su interpretaci´n. o Finitud. El algoritmo debe constar de un n´mero finito de pasos, que se ejecuta en u un tiempo finito y requiere un consumo finito de recursos. En la figura 1.3 se muestra una interpretaci´n de los algoritmos como cajas ne- o gras, donde se destaca la visi´n del algoritmo como algo que dado unos datos de entrada o produce una salida. La propiedad de definibilidad se refiere a los algoritmos “en papel”, que tambi´n son algoritmos si est´n expresados sin ambig¨edades. Un trozo de c´digo e a u o implementado en un lenguaje de programaci´n no tendr´ ambig¨edad, pero tambi´n de- o a u e ber´ tener una duraci´n finita para ser considerado un algoritmo. El problema general a o de demostrar la terminaci´n de un conjunto de reglas es un problema complejo y no o computable, es decir no puede existir ning´n programa que lo resuelva. u ENTRADA SALIDA ENTRADA SALIDA palabras a ALGORITMO clave ALGORITMO DE conjunto mcd(a,b) DE EUCLIDES idioma BUSCADOR WEB de páginas b conjunto resultantes de páginas Figura 1.3: Interpretaci´n de los algoritmos como cajas negras. o No todo lo que aparece en inform´tica son algoritmos. Un ejemplo t´ a ıpico de algo que no es un algoritmo es un sistema operativo. El sistema operativo consta de una serie de instrucciones, pero no se le prev´ una ejecuci´n finita, sino que deber´ poder ejecutarse e o ıa mientras no se requiera lo contrario. Algoritmos deterministas y no deterministas Seg´n un algoritmo produzca o no siempre los mismos valores de salida para las u mismas entradas, clasificamos los algoritmos en dos tipos: Algoritmos deterministas. Para los mismos datos de entrada producen siempre los mismos datos de salida. Algoritmos no deterministas. Para los mismos datos de entrada pueden producir diferentes salidas. El no determinismo puede chocar un poco con la idea de definibilidad. Sin embar- go, ambas propiedades no son contradictorias. Por ejemplo, el resultado de un algoritmo paralelo, que se ejecuta en distintos procesadores, puede depender de cuestiones de tem- porizaci´n que no forman parte de los datos del problema. o La existencia del no determinismo hace que un algoritmo no siempre se pueda ver como una funci´n –en sentido matem´tico– de la forma f : ENTRADA → SALIDA. o a
  17. 17. 1.3. Algoritmos y algor´ ıtmica 17 Definici´n formal de algoritmo o Donald E. Knuth, uno de los grandes pioneros en el estudio de los algoritmos y la programaci´n, propone la siguiente definici´n formal de algoritmo. o o Definici´n 1.5 Un m´todo de c´lculo es una cuaterna (Q, I, W, f ), en la cual: o e a I es el conjunto de estados de entrada; W es el conjunto de estados de salida; Q es el conjunto de estados de c´lculo, que contiene a I y W ; y a f es una funci´n: f : Q → Q, con f (w) = w; ∀ w ∈ W . o Definici´n 1.6 Una secuencia de c´lculo es una serie de estados: x0 , x1 , x2 , ..., xn , ..., o a donde x0 ∈ I y ∀k ≥ 0; f (xk ) = xk+1 . Se dice que la secuencia de c´lculo acaba en n pasos a si n es el menor entero con xn ∈ W . Una definici´n formal como esta nos puede servir para comprobar, de manera ri- o gurosa, si un algoritmo cumple las propiedades de finitud y definibilidad. Sin embargo, intentar resolver un problema definiendo la funci´n f puede resultar una tarea bastante o compleja. Y, lo que es peor, el resultado puede que nos aporte pocas ideas sobre c´mo o implementar el algoritmo en un lenguaje de programaci´n. En lo sucesivo, intentaremos o seguir una visi´n m´s pr´ctica, pero formalizando en aquellos sitios donde sea conveniente. o a a La disciplina de la algor´ ıtmica Si bien, como hemos visto, la historia de los algoritmos se remonta muy atr´s, el estu- a dio de los algoritmos no se concibi´ como una disciplina propia hasta bien entrada la mitad o del pasado siglo XX. Actualmente, entendemos la algor´ ıtmica como la disciplina, dentro del ´mbito de la inform´tica, que estudia t´cnicas para construir algoritmos eficientes y a a e t´cnicas para medir la eficiencia de los algoritmos. En consecuencia, la algor´ e ıtmica consta de dos grandes ´reas de estudio: el an´lisis y el dise˜o de algoritmos. a a n Pero, ¿cu´l es el objetivo ultimo de la algor´ a ´ ıtmica?, ¿qu´ es lo que motiva su estudio? e El objetivo ultimo es dado un problema concreto ser capaz de resolverlo de la mejor forma ´ posible, de forma r´pida, corta, elegante y f´cil de programar. Y recordemos que en esta a a resoluci´n entran tambi´n en juego las estructuras de datos, que son manejadas por los o e algoritmos. En definitiva, todos los ejemplos, t´cnicas, m´todos y esquemas que vamos e e a estudiar tienen como finalidad ultima servir de herramientas utiles en el momento de ´ ´ afrontar la resoluci´n de un problema completamente nuevo y desconocido. o 1.3.2. An´lisis de algoritmos a El an´lisis de algoritmos es la parte de la algor´ a ıtmica que estudia la forma de medir la eficiencia de los algoritmos. Pero ¿cu´ndo decimos que un algoritmo es m´s o menos a a eficiente? Utilizando un punto de vista empresarial, podemos definir la eficiencia como la relaci´n entre recursos consumidos y productos obtenidos. Se trata, por lo tanto, de una o
  18. 18. 18 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos nueva formulaci´n de la bien conocida ley del m´ o ınimo esfuerzo: maximizar los resultados, minimizando el esfuerzo. Los resultados que ofrece un algoritmo pueden ser de distintos tipos: Un algoritmo puede resolver el problema de forma muy precisa, si es de tipo mate- m´tico, o garantizar el optimo, en problemas de optimizaci´n, o encontrar siempre a ´ o la soluci´n, si es de satisfacci´n de restricciones. o o Otro algoritmo puede ser que s´lo encuentre soluciones aproximadas, o m´s o menos o a cercanas al optimo, pero siempre encuentre alguna. ´ Finalmente, otro algoritmo puede que encuentre soluciones s´lo en determinados o casos, buenas o malas, y en otros casos acabe sin devolver ninguna respuesta. Por otro lado, los recursos que consume un algoritmo pueden ser de distintos tipos. Entre los m´s importantes tenemos: a Tiempo de ejecuci´n, desde el inicio del programa hasta que acaba. o Utilizaci´n de memoria principal. o N´mero de accesos a disco, a la red o a otros perif´ricos externos. u e N´mero de procesadores usados, en el caso de los algoritmos paralelos, etc. u En aplicaciones distintas puede que el recurso cr´ ıtico sea diferente. En la mayor´ ıa de los casos, el recurso clave es el tiempo de ejecuci´n, por lo que muchas veces se asocia o eficiencia con tiempo. Pero, recordemos, ni el tiempo es el unico recurso, ni el tiempo por ´ s´ solo mide la eficiencia, si no que se debe contrastar con los resultados obtenidos. ı En conclusi´n, un algoritmo ser´ mejor cuanto m´s eficiente sea. No obstante, el o a a sentido com´n nos dice que habr´n otros muchos criterios para decidir lo bueno que es un u a algoritmo. Por ejemplo, ser´ importante: que sea f´cil de entender y de programar, que a a sea corto en su extensi´n, que sea robusto frente a casos extra˜os, que se pueda reutilizar o n en otros problemas, que se pueda adaptar, etc. Factores en la medici´n de recursos o Supongamos el siguiente algoritmo sencillo, que realiza una b´squeda secuencial con u centinela dentro de un array de enteros. La cuesti´n que se plantea es ¿cu´ntos recursos, o a de tiempo de ejecuci´n y memoria, consume el algoritmo? o operaci´n BusquedaSec (x, n: entero; a: array [1..n+1] de entero): entero o i:= 0 a[n+1]:= x repetir i:= i + 1 hasta a[i] = x devolver i Por muy perdido que ande el alumno, dif´ ıcilmente habr´ contestado algo como “tarda a 3 segundos” o “requiere 453 Kbytes...”. Como m´ ınimo, est´ claro que el tiempo y la a
  19. 19. 1.3. Algoritmos y algor´ ıtmica 19 memoria dependen de n, de los datos de entrada, del procesador, de que estemos grabando un CD al mismo tiempo y de otras mil cosas m´s. Podemos clasificar todos los factores a que intervienen en el consumo de recursos en tres categor´ ıas. Factores externos. No indican nada relevante sobre las caracter´ ısticas del algo- ritmo; son, m´s bien, cuestiones externas al algoritmo, relacionadas con el entorno a en el que se implementa y ejecuta. Entre los principales factores externos tenemos: el ordenador donde se ejecuta el algoritmo, el uso de procesador por parte de otras tareas, el sistema operativo, el lenguaje de programaci´n usado, el compilador (in- o cluyendo las opciones de compilaci´n usadas), la implementaci´n concreta que se o o haga del algoritmo, etc. Tama˜ o del problema. El tama˜o es el volumen de datos de entrada que debe n n manejar el algoritmo. Normalmente, el tama˜o viene dado por uno o varios n´meros n u enteros. La relaci´n del tiempo con respecto al tama˜o s´ que resulta interesante o n ı sobre las caracter´ ısticas del algoritmo. Contenido de los datos de entrada. Para un tama˜o de entrada fijo y bajo las n mismas condiciones de ejecuci´n, un algoritmo puede tardar distinto tiempo para o diferentes entradas. Hablamos de diferentes casos: mejor caso, es el contenido de los datos de entrada que produce la ejecuci´n m´s r´pida del algoritmo (incluso o a a para tama˜os de entrada grandes); peor caso, es el que da lugar a la ejecuci´n n o m´s lenta para un mismo tama˜o; caso promedio, es el caso medio de todos los a n posibles valores de entrada. Por ejemplo, en el anterior algoritmo de b´squeda secuencial con centinela, el tama˜o u n del problema viene dado por n. No es lo mismo aplicar el algoritmo con n = 5 que con n = 5.000.000. Pero la ejecuci´n con n = 5.000.000 puede tardar menos que con n = 5, si el o x buscado se encuentra en la primera posici´n. As´ pues, el mejor caso ser´ que a[1] = x, o ı a independientemente de lo que valga n. El peor caso ser´ que x no se encuentre en el array, a con lo cual la b´squeda acabar´ al llegar a a[n + 1]. El caso promedio ser´ la media de u a ıa todas las posibles entradas. Supongamos que x est´ en a con probabilidad p y que, en a caso de estar, todas las posiciones tienen la misma probabilidad. Entonces se recorrer´n a n/2 posiciones con probabilidad p, y n + 1 posiciones con probabilidad 1 − p. Notaciones en el an´lisis de algoritmos a Nos interesa que el an´lisis de los recursos consumidos por un algoritmo sea inde- a pendiente de los factores externos. En consecuencia, el objetivo es estudiar la variaci´n o del tiempo y la memoria utilizada, en funci´n del tama˜o de la entrada y, posiblemente, o n para los casos mejor, peor y promedio, en caso de ser distintos. Esta dependencia se expresa como una o varias funciones matem´ticas. El tiempo a ser´ normalmente una funci´n t : N → R , y de forma parecida la memoria m : N → R+ . a o + Pero si el algoritmo tarda distinto tiempo para la misma n, entonces no se puede decir que t sea una funci´n de N en R+ . En ese caso, definimos los tiempos en los casos mejor, o peor y promedio; denotaremos estos tiempos como tm , tM y tp , respectivamente. En concreto, ¿qu´ se mide con t(n)? Pues lo que m´s nos interese. e a
  20. 20. 20 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos Podemos medir tiempos de ejecuci´n, en unidades de tiempo, asignando cons- o tantes indefinidas a cada instrucci´n o l´ o ınea de c´digo. En el algoritmo de b´squeda o u secuencial, tendr´ıamos los siguientes tiempos: tm (n) = c1 + c2 + c4 + c5 + c6 ; tM (n) = c1 + c2 + c6 + (c4 + c5 )(n + 1); tp (n) = c1 + c2 + c6 + (c4 + c5 )(1 + (1 − p)n + p n/2) Siendo cada ci el tiempo de ejecuci´n de la instrucci´n de la l´ o o ınea i-´sima. e Tambi´n podemos medir simplemente el n´ mero de instrucciones. Tendr´ e u ıamos: tm (n) = 5; tM (n) = 5 + 2n; tp (n) = 5 + 2((1 − p)n + p n/2) No todas las instrucciones tardan el mismo tiempo, pero sabemos que todas ellas tardan como m´ximo un tiempo que est´ acotado por una constante. a a Puede que lo que nos interese medir sea unicamente alg´ n tipo de instrucci´n, ´ u o que sabemos que es la m´s relevante. Por ejemplo, si contamos el n´mero de com- a u paraciones en el algoritmo de b´squeda secuencial tenemos: u tm (n) = 1; tM (n) = 1 + n; tp (n) = 1 + (1 − p/2)n Las tres versiones de cada una de las funciones tm , tM y tp anteriores, nos vienen a decir lo mismo: en el mejor caso el tiempo es constante, en el peor es proporcional a n, y en promedio depende de n y de p. Pero seguro que la ultima forma, contando s´lo ´ o comparaciones, se puede interpretar m´s f´cilmente, porque es la m´s sencilla. a a a Para simplificar las expresiones de t(n) usamos las notaciones asint´ticas. Las no- o taciones asint´ticas son notaciones que s´lo tienen en cuenta el crecimiento asint´tico o o o de una funci´n –es decir, para valores tendiendo a infinito– y son independientes de las o constantes multiplicativas. Tenemos las siguientes notaciones: O-grande: orden de complejidad. Establece una cota superior de la funci´n, o asint´ticamente e independiente de constantes multiplicativas. o Ω: omega. Establece una cota inferior, asint´tica e independiente de constantes. o Θ: orden exacto. Se usa cuando la cota inferior y superior de una funci´n coinciden. o o-peque˜ a. Es similar a la notaci´n Θ, pero teniendo en cuenta el factor que n o multiplica al t´rmino de mayor grado. e Podemos encontrar diferentes utilidades a las notaciones asint´ticas: o Simplificar la notaci´n de una f´rmula con expresi´n larga y complicada. Por o o √o ejemplo, dado un t(n) = 2n2 + 7,2n log3 n − 21πn + 2 n + 3, podemos quedarnos simplemente con que t(n) ∈ Θ(n2 ), o tambi´n t(n) ∈ o(2n2 ). e Acotar una funci´n que no se puede calcular f´cilmente. Esto puede ocurrir en o a algunos algoritmos cuyo comportamiento es dif´ de predecir. Supongamos, por ıcil ejemplo, la funci´n t(n) = 8n5−cos(n) , similar a la mostrada en la figura 1.4a). Se o puede acotar superiormente con t(n) ∈ O(n6 ) e inferiormente con t(n) ∈ Ω(n4 ).
  21. 21. 1.3. Algoritmos y algor´ ıtmica 21 Delimitar el rango de variaci´n del tiempo de un algoritmo, cuando no siem- o pre tarda lo mismo. Por ejemplo, en el algoritmo de b´squeda secuencial tenemos u distintos casos mejor y peor. Podemos decir que tiene un O(n) y un Ω(1). En la figura 1.4 se muestran dos ejemplos de aplicaci´n de las notaciones asint´ticas, o o para los dos ultimos casos. ´ a) t(n)ÎO(f(n)); t(n)ÎW(g(n)) b) t(n)ÎO(tM(n)); t(n)ÎW(tm(n)) 150 15 125 12.5 f(n) tM(n) 100 10 t(n) t(n) 75 t(n) 7.5 50 5 25 2.5 g(n) tm(n) 0 0 0 2 4 6 8 10 0 20 40 60 80 100 n n Figura 1.4: Distintas aplicaciones de las notaciones asint´ticas. a) Para acotar una funci´n o o oscilante o dif´ de calcular con precisi´n. b) Para acotar un algoritmo que no siempre ıcil o tarda lo mismo para el mismo n. Las notaciones asint´ticas pueden servir tambi´n para comparar f´cilmente los tiem- o e a pos de dos algoritmos. Pero hay que tener en cuenta sus limitaciones, derivadas de la inde- pendencia de las constantes y de los t´rminos de menor grado. Por ejemplo, un algoritmo e 0,01 puede tener un tiempo t1 (n) = 10n + 2 y otro t2 (n) = 2 log2 n + 30. Las notaciones nos dicen que O(log n) < O(n0,01 ). Sin embargo, ¡t1 (n) s´lo ser´ mayor que t2 (n) a partir de o a 216 valores de n mayores que 4,5 ∗ 10 ! 1.3.3. Dise˜ o de algoritmos n El dise˜o de algoritmos es la parte de la algor´ n ıtmica que estudia t´cnicas generales e de construcci´n de algoritmos eficientes. El objetivo no es, por lo tanto, estudiar unos o cuantos algoritmos dados, sino desarrollar t´cnicas generales que se pueden aplicar sobre e diversos tipos de problemas. Por ejemplo, divide y vencer´s no es un algoritmo, sino una a t´cnica que puede ser aplicada sobre una amplia variedad de problemas. e En primer lugar, cada t´cnica de dise˜o de algoritmos propone una interpretaci´n e n o particular del problema, es decir, una forma de ver o modelar el objetivo del problema. Esta interpretaci´n consta de una serie de elementos gen´ricos, que deben ser concre- o e tados en cada caso. Por ejemplo, divide y vencer´s interpreta que el problema trabaja a con una serie de datos, que se pueden dividir para tener problemas m´s peque˜os. Los a n elementos gen´ricos de la t´cnica ser´ e e ıan: la manera de hacer la divisi´n de los datos, la o forma de juntar las soluciones, una forma de resolver casos de tama˜o peque˜o, etc. Los n n componentes pueden ser tanto operaciones como estructuras de datos.
  22. 22. 22 Cap´ ıtulo 1. Problemas, programas, estructuras y algoritmos Habiendo resuelto los componentes gen´ricos del problema, la t´cnica propone un e e esquema algor´ ıtmico, que es una gu´ de c´mo combinar los elementos para cons- ıa o truir algoritmos basados en esa t´cnica. El esquema algor´ e ıtmico puede ser m´s o menos a detallado. Idealmente, el esquema algor´ıtmico ser´ como un “algoritmo con huecos”, co- ıa rrespondientes a los elementos gen´ricos. El programador simplemente deber´ insertar el e ıa c´digo y los tipos adecuados en los huecos que correspondan, y ya tendr´ un algoritmo o ıa que resuelve el problema. La idea se muestra en la figura 1.5. ALGORITMO Voraz (C: ConjuntoCandidatos ; var S: ConjuntoSolución ) S:= Ø mientras (C ≠ Ø) Y NO SOLUCION(S) hacer Insertar tipos AQUÍ x:= SELECCIONAR(C) C:= C - {x} Insertar si FACTIBLE(S, x) entonces código AQUÍ INSERTAR(S, x) finsi finmientras Figura 1.5: Interpretaci´n de los esquemas algor´ o ıtmicos como “algoritmos con huecos”. Esquema de algoritmo voraz. En la pr´ctica, esta idea de construir algoritmos “rellenando huecos de un esquema” a no suele ser tan simple ni extendida, por varios motivos. En primer lugar, la variedad de situaciones que nos podemos encontrar en distintos problemas es, normalmente, mucho mayor que la prevista en los esquemas de algoritmos. Pero, por otro lado, en esquemas excesivamente flexibles el problema suele ser la ineficiencia; un algoritmo escrito desde cero para un problema particular puede incluir c´digo optimizado para ese caso dado. De o todas formas, los esquemas algor´ ıtmicos son una ayuda muy util en la construcci´n de ´ o algoritmos, por lo menos para la obtenci´n de una primera versi´n. o o Otra cuesti´n fundamental en la resoluci´n de problemas es decidir qu´ t´cnica se o o e e puede aplicar en cada caso. Aunque puede que ocurra en un examen, en un problema de la vida real nadie nos dir´ “progr´mame esto utilizando backtracking” o “calcula aquello a a con programaci´n din´mica”; el que lo resuelve debe decidir tambi´n c´mo. Puede que se o a e o puedan aplicar varias t´cnicas, que alguna de ellas se pueda aplicar de diferentes modos, e que distintas t´cnicas den diferentes resultados –por ejemplo, una aproxima la soluci´n y e o otra encuentra el ´ptimo– o que no se pueda aplicar ninguna t´cnica. En el ultimo caso, o e ´ entrar´ en juego la habilidad de la persona para enfrentarse a situaciones nuevas. a Vamos a adelantar algunas de las principales t´cnicas generales de dise˜o de al- e n goritmos, que veremos a lo largo de este libro. Se supone que el alumno est´ un poco a familiarizado con alguna de ellas –como divide y vencer´s, o backtracking–, aunque no a resulta imprescindible.

×