LECTURA SOBRE TDA Observación: este apunte fue extraído de lo escrito por los siguientes autores: Josué Gala; Lorena Bojorquez; Behrouz A. Forouzan; es esencial que lo lea para que comprenda que son los TDA´S. A1 menos que se tengan niños pequeños (como es mi caso), los diversos objetos en casa suelen estar acomodados de acuerdo a cierto orden, agrupados con los de su clase: los libros en un librero, la despensa en la alacena, los alimentos perecederos en el refrigerador, la ropa en el ropero o en el “closet”, etc. En nuestros lugares de trabajo las cosas son similares, en general los objetos de los que nos servimos suelen estar organizados de acuerdo con un orden preestablecido. La razón para mantener las cosas en un estado por lo menos cercano a la organización perfecta, es la eficiencia. Todos hemos padecido el disgusto, cuando no la desesperación, de buscar algo que hemos dejado en algún lugar ajeno a su colocación habitual. Se pierden miserablemente varias horas buscando, a veces infructuosamente, un objeto perdido en la complejidad de nuestro entorno. Hay casos extremos en los que la organización de los objetos es fundamental. En el caso de una biblioteca, por ejemplo, el orden de los libros en la estantería debe ser perfecto. Pensemos en buscar un libro especifico en una biblioteca en la que los volúmenes están acomodados aleatoriamente o siguiendo algún criterio extravagante (por tamaño, por grosor o por lo que sugiere el titulo). ¿Cuanto tiempo tomaría encontrar un libro en particular? No podemos saberlo, pero en el peor de los casos sería el último luego de haber recorrido todos los títulos en el acervo, así que nos tardaremos más cuanto mayor sea este. Los libros están organizados estrictamente para poder encontrar cualquiera de ellos rápidamente. Lo mismo ocurre con las historias médicas en un hospital, con los archivos de una escuela, los de la compañia de teléfonos o del registro civil. A pequeña escala también ocurre en nuestros hogares. Idealmente organizamos los CD’s, los libros, las cuentas por pagar, las facturas, la despensa, el contenido del refrigerador y la ropa por lavar. Toda esta organización es para poder hacer rápidamente ciertas operaciones: poner música, lavar ropa, comer o cepillarse los dientes. Poseer cierta estructura en los objetos que manipulamos permite optimizar el tiempo invertido en las operaciones que hacemos con ellos. En el contexto de los programas de computador la situación no es diferente. Los programas operan con datos de entrada, los manipulan a través de una secuencia finita de pasos y luego nos entregan resultados basados en esos datos. Nuestros programas serán más eficientes, aprovecharán mejor los recursos disponibles (tiempo de procesador, memoria y acceso a dispositivos de E/S), dependiendo de la manera en que son organizados los datos de entrada o los resultados parciales del proceso. Igual que en una biblioteca, la manera de organizar los datos establece vínculos entre ellos y se conforma lo que denominaremos una estructura de datos, uno de los temas fundamentales del presente texto. Para poder evaluar la eficiencia de nuestros algoritmos en general, y en particular el impacto que diversas estructuras de datos tienen sobre dicha eficiencia, requeriremos de métodos que nos permitan cuantificar los recursos consumidos. El término adecuado para referirse es: análisis de algoritmos,(otro tema que se tratará en el curso). Ambas cosas tienen singular relevancia tanto en el ámbito de la computación teórica como en el de la práctica de la programación. No puede haber una propuesta seria de un nuevo algoritmo o un protocolo de comunicación que no esté sustentada por un análisis teórico de los mismos. Asimismo, no es posible pensar en que las decisiones de un programador respetable no sean las adecuadas para optimizar el desempeño de sus programas. El análisis de algoritmos y las estructuras de datos, como el resto de los análisis que se hacen en ciencia, están basados en abstracciones. La abstracción es el camino que los seres humanos utilizamos para comprender la complejidad. La teoría de la gravitación, el algebra de Boole, los modelos económicos y los ecológicos son ejemplos de ello. Todos son abstracciones hechas en un intento por comprender fenómenos complejos en los que intervienen una multitud de factores con intrincadas relaciones entre ellos. 1 Extracto de: Estructuras de Datos y Análisis de Algoritmos, una Introducción usando Java. Josué Gala. 2005 Fac. de Ciencias, UNAM. Una abstracción es una descripción simplificada de un sistema y lo más importante en ella es que enfatiza las características esenciales y descarta aquellas que se consideran sin importancia para explicar el comportamiento del sistema. En la teoría de la gravitación, por ejemplo, no son consideradas las propiedades magnéticas o químicas de los cuerpos, solo interesan cosas como su masa y las distancias entre ellos. Formular una buena abstracción es como hacer una buena caricatura. El Quijote de Pablo Picasso es un buen ejemplo de abstracción (ver figura) Con unos cuantos trazos simples el caricaturista representa un personaje que es posible reconocer sin ambigüedad, nada sobra y nada falta, ha sabido encontrar aquellos rasgos que caracterizan a la persona y ha sabido resaltarlos aislándolos de todos los que no son relevantes. En la figura anterior se muestra un buen ejemplo de abstracción. Todos podemos reconocer a Don Quijote y Sancho en sus respectivas monturas y en el episodio de los molinos de viento, no hacen falta los colores ni las texturas ni los detalles de rostros, cuerpos y paisaje. TDA’S, ESTRUCTURAS DE DATOS Por supuesto la programación de computadores es un ejercicio de abstracción. Particularmente cuando se definen las propiedades y los atributos de los datos que deben manipular los programas. En el paradigma actual de la programación se pretende modelar con entes abstractos llamados objetos. Los atributos y el comportamiento de estos objetos están determinados por la abstracción que el programador hace de los actores reales con base en el contexto del problema. Si lo que se pretende es modelar personas de una cierta localidad con el propósito de estudiar sus características genéticas, entonces convendría considerar como abstracción de persona un conjunto de atributos como la estatura, el color de ojos, de cabello, de piel o el tipo sanguíneo; si el problema en cambio, está vinculado con las características socio-económicas, entonces los atributos que convendría incluir en la abstracción son los ingresos mensuales, el gasto promedio y el tipo de trabajo de las personas reales. A fin de cuentas lo que hace el programador es crear un nuevo tipo de dato que modele los objetos involucrados en el problema que pretende resolver. Al igual que los tipos elementales que posee el lenguaje de programación que utilizará, el tipo definido por el programador posee un conjunto de posibles valores, un dominio y un conjunto de operaciones básicas definidas sobre los elementos de ese dominio. Un modelo así se conoce como un Tipo de Dato Abstracto. Un tipo de dato abstracto o TDA es un modelo que describe a todos los elementos de un cierto conjunto dominio, especificando su comportamiento bajo ciertas operaciones que trabajan sobre ellos. El uso del término abstracto significa, como sabemos, que solo atiende a aquellas cualidades que interesan de los elementos del dominio y que son manifiestas a través de las operaciones. Esto significa, en términos matemáticos, que un TDA no dice más que aquello que se puede inferir del comportamiento de sus operaciones, es lo que suele llamarse un sistema formal, cómo lo son la geometría euclidiana, la lógica de primer orden o los números naturales. Como en todo sistema formal, el comportamiento básico de los elementos del dominio está regido por una serie de premisas elementales que se suponen verdaderas a priori, un conjunto de axiomas. La cualidad más relevante en la abstracción de datos es que, al elaborar un nuevo modelo para un tipo de dato lo importante es decidir qué es lo que se puede hacer con un dato de ese tipo y no cómo es que se hace. La especificación de lo que se puede hacer (las operaciones) debe ser lo suficientemente rigurosa como para explicar completamente su efecto sobre los datos que componen al tipo, pero no debe especificar cómo es que se logra dicho efecto ni cómo estos datos están organizados. Es decir, la definición de un TDA es diferente de su implementación concreta en un lenguaje de Programación. Lo que se pretende decir es que un TDA está en un nivel de abstracción mayor que su realización en un lenguaje de programación.2 ************** TIPOS DE DATOS ABSTRACTOS (TDA) 3 Un TDA es un tipo de dato definido por el programador que se puede manipular de un modo similar a los tipos de datos definidos por el lenguaje. En pocas palabras, si los tipos de datos existentes en el lenguaje no son suficientes o no son eficientes para ciertas aplicaciones, la mayoría de los lenguajes de programación permiten al usuario definir sus propios tipos de datos. Esta definición consiste en establecer los elementos de que consta el tipo así como las operaciones que se pueden realizar con instancias de este tipo. DECLARACIÓN DE TDA’s Para construir un tipo abstracto se debe: 1) Establecer la definición del tipo 2) Definir también las operaciones (funciones y procedimientos) que pueden operar con dicho tipo 3) Ocultar la presentación de los elementos del tipo de modo que sólo se puede trabajar con ellos usando los procedimientos definidos en 2) 4) Poder crear instancias múltiples del tipo Un TDA es el elemento básico de la abstracción de datos. Debe verse como una caja negra, pues la representación y la implementación deben permanecer “ocultas”, de forma que para trabajar con los elementos de un TDA el único mecanismo permitido es el de usar las operaciones definidas para dicho TDA. La declaración de TDA’s en C++ requiere de palabras reservadas. Los campos, a su vez, pueden ser variables de tipos definidos por el lenguajes u otros TDA’s. Considere el siguiente ejemplo: struct Tipo_Persona { int edad; double altura; double peso; char nombre[25]; }; 2 Esta parte ha sido obtenida de artículo: Estructuras de Datos y Análisis de Algoritmos, una Introducción usando Java. Josué Gala 2005 Fac. de Ciencias, UNAM. 3 Extraído de: Programación Instituto Tecnológico de Celaya, Ingeniería Química En tal ejemplo, se esta creando una estructura de datos que definirá un nuevo tipo. Este nuevo tipo se denomina Tipo_Persona. Observe que los elementos del nuevo tipo son cuatro; todos ellos representando características particulares del Tipo_Persona. En general, se esperaría desarrollar “operaciones” sobre este nuevo tipo que permitieran modificar cada uno de estos 4 campos o elementos. Sea la siguiente sentencia: typedef struct Tipo_Persona Persona; Lo que esta sentencia lograría es que se está definiendo un nuevo tipo de datos llamado Persona, que contiene exactamente la misma estructura y elementos que la estructura Tipo_Persona definida anteriormente. En otras palabras, con las definiciones anteriores existiría un nuevo tipo en el lenguaje denominado Persona, que podrá utilizarse como cualquier otro tipo. Por ejemplo, si se tiene: Persona Gabriel, Antonio; Se estarán creando dos nuevas variables, Gabriel y Antonio, del tipo Persona. De esta forma, cada una de estas variables (Gabriel y Antonio) contiene todos los elementos definidos en la estructura Tipo_Persona. Obsérvese aquí, que la estructura que contienen estas dos nuevas variables queda “oculta”. Concluimos esta sección mostrando la forma en que cada uno de los elementos de una estructura de datos puede ser accedido. Observe los siguientes ejemplos: Gabriel.edad = 25; Antonio.altura = 1.76; Logrado este acceso, los elementos de estas estructuras se pueden manipular como cualquier otra variable de su mismo tipo OPERACIONES SOBRE UN TDA Las operaciones que se aplican a un TDA generalmente caen dentro de las siguientes tipos básicos: 1) Construcción: Crean una nueva instancia del tipo 2) Transformación: Cambian el valor de uno o más elementos del tipo 3) Observación: Permiten determinar el valor de uno o más elementos de un tipo sin modificarlos 4) Iteradores: Permiten procesar todo los elementos de un TDA en forma secuencial Aunque aquí no se analizarán de la creación de cada uno de estos tipos de operaciones.4 Descripción de las operaciones de un TDA5. Cada operación relacionada con el TDA debe describirse con los siguientes puntos: • Nombre de la operación. • Descripción breve de su utilidad. • Datos de entrada a la operación • Datos que genera como salida la operación • Pre-condición: Condición que deberá cumplirse antes de utilizar la operación para que se realice sin problemas • Post-condición: Condición en que queda el TDA después de ejecutar la operación Ejemplo • Especificación Lógica del Tipo de Dato Abstracto (TDA) Cadena • Elementos: todos los caracteres alfabéticos (letras mayúsculas y minúsculas), caracteres numéricos y caracteres especiales • Dominio: existen entre 0 y 80 caracteres en cada valor del TDA CADENA. El dominio serán todas aquellas secuencias de caracteres que cumplan con las reglas. 4 5 Programación Instituto Tecnológico de Celaya. Ingeniería Química Apuntes de estructura de datos; Adriana Lorena Bojorquez. Inst.Tec de Monterrey, Campus Estado de México Operaciones • • • • • BORRA_INICIO o UTILIDAD: Sirve para eliminar el primer carácter de una cadena. o ENTRADA: Cadena S sobre la que se desea eliminar el primer carácter. o SALIDA: El carácter mas a la izquierda de la cadena S y la cadena S modificada. o PRECONDICIÓN: La cantidad de caracteres es mayor que cero. o POSTCONDICIÓN: La cadena S tiene todos los caracteres, menos el primero. AGREGA_FINAL o UTILIDAD: Sirve para agregar un carácter al final de una cadena. o ENTRADA: Cadena S y el carácter L, que se añadirá a la cadena S. o SALIDA: Cadena S modificada. o PRECONDICIÓN: La cantidad de caracteres en S es menor que 80. o POSTCONDICIÓN: La cadena S tiene el carácter L que queda al extremo derecho de la cadena. VACÍA o UTILIDAD: Sirve para verificar si una cadena esta vacía o no. o ENTRADA: Cadena S que se verificará o SALIDA: VERDADERO si la cadena S no tiene caracteres, FALSO en caso contrario. o PRECONDICIÓN: Ninguna o POSTCONDICIÓN: Ninguna (pues la cadena S no se modifica). LLENA o UTILIDAD: Sirve para verificar si una cadena esta llena o no. o ENTRADA: cadena S que será verificada. o SALIDA: VERDADERO si la cadena S contiene ya 80 caracteres, FALSO en caso contrario. o PRECONDICIÓN: Ninguna o POSTCONDICIÓN: Ninguna (pues la cadena S no se modifica). . INVIERTE o UTILIDAD: Sirve para invertir el orden de los caracteres en una cadena. o ENTRADA: Cadena S a la que se desea invertir el orden de los caracteres. o SALIDA: Cadena S modificada. o PRECONDICIÓN: Ninguna o POSTCONDICIÓN: La secuencia de caracteres en la cadena S se invierte, de forma que el primer carácter toma el lugar del último, el segundo el del penúltimo y así sucesivamente. Niveles de abstracción de datos Al aplicar la abstracción de datos se pueden definir tres niveles de trabajo: 1. El nivel lógico o abstracto, que correspondería a la especificación lógica del TDA que se describió anteriormente. En este nivel se define abstractamente la estructura de datos y las operaciones relacionadas con ella. La descripción que se obtenga en este nivel debe ser independiente del lenguaje de programación en el que se implementará o usará la estructura. 2. El nivel físico o de implementación. En este nivel se decide el lenguaje de programación en que se implementará la estructura, que tipos de datos ya definidos servirán para representarla y, finalmente, bajo estas consideraciones, se implementa como un módulo a cada una de las operaciones del TDA. Este nivel toma el diseño que se ha realizado a nivel lógico y, siguiendo lo que indican las especificaciones de cada operación, construye la estructura que posteriormente se usará en el nivel aplicación. 3. En el nivel aplicación o de uso el programador usará el TDA para resolver determinada aplicación. El uso del TDA se limita a llamar las operaciones sobre la estructura que se requiera cuidando siempre de cumplir con las reglas de cada operación especificadas en el nivel lógico. 4. Independencia de datos y el ocultamiento de información. De lo descrito en la sección anterior se observa que hay una independencia bien marcada entre el nivel físico y el nivel de aplicación del TDA, con el nivel lógico como intermediario. Esto significa que quien implementa el TDA no debe estar influenciado por la aplicación que tendrá la estructura y quien use la estructura no tiene por qué saber cómo se implementaron sus operaciones. Entonces, se dice que la forma en que se almacenan los datos en la estructura es independiente de su aplicación y que para el usuario permanece oculto cómo se implementaron las operaciones del TDA. Esto, sin lugar a dudas, simplifica la labor del usuario del TDA, pues se olvida de detalles de programación al basar su trabajo sólo en el diseño lógico del TDA. Adicionalmente, quien implementa el TDA, podrá hacer cambios o mejoras a su implementación, respetando la especificación lógica y sin afectar en lo más mínimo las aplicaciones desarrolladas. Distinción entre los niveles de abstracción. Una analogía podría hacerse al comparar este proceso con el que se realiza al construir una casa. Primero se trabaja en el nivel lógico al pedirle a un arquitecto que diseñe el plano; el arquitecto hace una abstracción y plasma en el papel los rasgos principales de la casa por construir. Además, especifica claramente medidas y condiciones de construcción y de uso. Una vez aprobado el diseño, se procede en el nivel físico, que correspondería a la construcción. Los albañiles seguirán paso a paso las especificaciones dadas en el plano (diseño en nivel lógico) y no tienen por que preguntar para qué se utilizará cada uno de los espacios construidos. Además, utilizarán el material más apropiado para cada situación. Una vez que la construcción esté terminada, llegará su dueño para habitarla, dándole el uso correspondiente a cada espacio (nivel aplicación). El dueño, al utilizar la casa, no necesita saber cómo se hizo, con qué materiales o quienes la construyeron; sin embargo, le será útil conocer algunas medidas o condiciones de construcción que se representan en el plano (nivel lógico). De la misma forma, cuando se desea aplicar la abstracción de datos en el desarrollo de software que requiere de una estructura de datos, se debe comenzar con el diseño a nivel lógico del TDA, continuar con su implementación y finalmente con su uso en el desarrollo de la aplicación correspondiente. En este proceso podrán intervenir tres personas: el diseñador del TDA, un programador a nivel físico y un programador a nivel aplicación. Cuando el mismo programador trabaje en los niveles físicos y de aplicación, debe tener en cuenta la regla de no invadir los niveles, respetando el nivel lógico que es intermediario. Ventajas al utilizar la técnica de abstracción de datos Seguir la regla de los tres niveles de abstracción redunda en un mejor desarrollo de software. La técnica obliga a diseñar modularmente y, como consecuencia, se tiene una implementación mas clara, documentada y es fácil darle mantenimiento. Adicionalmente, gracias a la independencia de datos y al ocultamiento de información, se pueden crear paquetes como unidades de software reutilizable, con lo que se obtienen estructuras de datos genéricas. Quien utiliza un TDA se limita a llamar las operaciones, cuidando solamente de cumplir con las especificaciones del diseño lógico, lo que facilita y hace más rápido el desarrollo de aplicaciones, pues no es necesario manejar los detalles físicos de la estructura. Ejemplo: En cierta aplicación se requiere calcular el factorial de un número entero positivo. La función factorial aplicada sobre un numero es la multiplicación de todos los números, desde el l hasta el valor del numero correspondiente ( n! = 1 * 2 * 3 * ...* (n – l ) * n ). Solución : int j, factorial = 1 ; for ( j = n; j > 0; j– – ) factorial =factorial *j; Es importante notar que el factorial de un número crece en forma exponencial: 1! = 1, 2! = 2, 3! = 6, 4! = 24, 5! = 120, 6! = 720,7! = 5040, 8! = 40320 ........ La capacidad de los tipos de dato enteros, esta limitada. Lo cual permitirá obtener sólo hasta un factorial bien acotado. Por lo tanto, se requiere de un tipo de dato con una capacidad para almacenar a cualquier numero entero, sin importar que tan grande sea;,normalmente los lenguajes no proveen de un tipo estándar para este caso; se puede diseñar, entonces, un TDA para utilizar números enteros grandes, el cual a su vez será una estructura de datos. Diseño del TDA número (nivel lógico) ESPECIFICACIÓN LÓGICA: • Elementos: un número se compone de dígitos. • Dominio: se pretende que un número pueda contener cualquier cantidad de dígitos bajo cualquier combinación. Sin embargo, se puede limitar a una capacidad máxima de mil dígitos. Operaciones: las mismas que posee el tipo de dato entero. • • • SUMA o UTILIDAD: sirve para sumar dos números. o ENTRADAS: dos números o SALIDAS: un número que guarda la suma de los dos números de entrada. o PRECONDICIÓN: Ninguna o POSTCONDICIÓN: El número de salida contiene la suma aritmética de los dos números de entrada. DESPLIEGA o UTILIDAD: Sirve para desplegar en pantalla un número. o ENTRADAS: Número a desplegar.6 o SALIDAS: Ninguna (Observa en pantalla) o PRECONDICIÓN: Ninguna o POSTCONDICIÓN: Ninguna -------- Con base en este diseño lógico, se puede pasar a la implementación del TDA (nivel físico) para posteriormente utilizarlo (nivel de aplicación) y resolver el problema factorial eficientemente. 6 Apuntes de estructura de datos; Adriana Lorena Bojorquez. Inst.Tec de Monterrey, Campus Estado de México Concepto grafico7 Flujos de datos interno Estructuras de datos Datos Operación A Operación C Operación B Datos Llamadas interna Interfaz externa El área sombreada con un entorno irregular representa el modelo. Dentro del área abstracta hay dos aspectos del modelo : la estructura de datos y las funciones operativas: Ambas están totalmente contenidas en el modelo y no dentro del ámbito del usuario. No, obstante, la estructura de datos está disponible para todas las operaciones con TDA según se requiera y una operación puede llamar a otras funciones para cumplir su tarea. Dicho de otra forma, las estructuras de datos y las funciones están dentro del ámbito de la una y de la otra. Los datos se introducen, se accede a ellos y se eliminan parcialmente mediante interfaces operativas (dibujadas con rectángulos). Para el encabezado (header) de cada operación, se supone que existe un algoritmo que realiza una operación específica. Sólo el nombre de la operación y sus parámetros están visibles para el usuario y proporcionan la única interfaz para el TDA: Operaciones adicionales pueden crearse para cumplir requisitos específicos. 7 Grafico obtenido de libro: Introducción a la ciencia de la computación: Autor Behrouz A. Forouzan