8 ESTRUCTURAS BÁSICAS 8.1. Concepto de datos estructurados. En temas anteriores vimos la representación de los datos en un computador y los distintos tipos que hay: carácter (ASCII), real (o de coma flotante), entero (o coma fija) y lógico (verdadero o falso). En esta lección, vamos a ver los distintos modos en que pueden agruparse estas clases de datos para formar lo que denominamos estructuras de datos, que nos permiten utilizar grandes volúmenes de datos de forma más sencilla. Una estructura de datos, o un tipo de datos estructurado, es un tipo de dato construido a partir de otros. Un dato de tipo estructurado está compuesto por una serie de datos de tipos elementales y alguna relación existente entre ellos. Normalmente, la relación suele ser de orden aunque puede ser de cualquier otro tipo. Se dice que una estructura de datos que es homogénea cuando todos los datos elementales que la forman son del mismo tipo. En caso contrario, se dice que la estructura es heterogénea. Por ejemplo, el tipo de datos complejo es una estructura homogénea, tanto la parte real como la imaginaria se representan con datos reales. Siempre que se utilice un dato en un programa debe estar determinado su tipo, para que el traductor sepa como debe tratarlo y almacenarlo. En el caso de datos de tipos elementales, el tipo de dato determina el espacio que se utiliza en memoria. Esto, puede no ocurrir si el dato es de un tipo estructurado. Una estructura de datos que siempre ocupa el mismo espacio en memoria, se dice que es estática. Por el contrario, si la memoria asignada a una determinada estructura de datos va variando durante la ejecución del programa, es decir, se realiza una asignación dinámica de memoria, se dice que es una estructura de datos dinámica. 131 Apuntes de Informática 8.2. Estructuras de datos estáticas. Dentro de los tipos de estructuras de datos que ocupan siempre el mismo espacio en memoria tenemos los punteros, las cadenas y los arrays. 8.2.1. Punteros. Un puntero es un dato que indica la posición de otro dato. Su utilidad se pone de manifiesto en la construcción de estructuras de datos, ya que son ellos los que proporcionan los lazos de unión entre los elementos que constituyen las estructuras. Son importantes los punteros al principio y al final de la estructura. Si, ocasionalmente, se necesita que un puntero no señale a ningún dato, se dice que el puntero tiene un valor nulo (puntero nulo). 8.2.2. Cadenas. Una cadena es una secuencia de caracteres que se interpretan como un dato único. Las cadenas pueden tener longitud fija o variable. La longitud de la cadena se indica tanto por el número de caracteres que contiene ésta, indicado al principio de la misma, como por un carácter especial denominado fin-de-cadena. Sobre datos de tipo cadena se pueden realizar las siguientes operaciones: ♦ Concatenación: Consiste en formar una cadena a partir de dos ya existentes, juntando los caracteres de ambas. ♦ Extracción de subcadena: Permite formar una cadena a partir de otra ya existente. La subcadena se forma tomando un tramo consecutivo de la cadena inicial. ♦ Comparación de cadenas: Es posible comparar dos cadenas. Se considera menor aquella en que el primer carácter en que difieren ambas es menor. ♦ Obtención de la longitud: La longitud de una cadena es un dato de tipo entero, cuyo valor es el número de caracteres que contiene ésta. 8.2.3. Arrays. El array (también llamado formación o matriz), es la estructura de datos más usual. Existe en todos los lenguajes de programación y en algunos es de las pocas estructuras de datos existentes (BASIC y FORTRAN). 132 TEMA 8 – Estructuras básicas Un array es una estructura de datos formada por una cantidad fija de datos del mismo tipo, cada uno de los cuales tiene asociado uno, o más índices, que determinan de forma unívoca la posición del dato en el array. Podemos imaginar un array como una estructura de celdas donde se pueden almacenar valores. En la figura podemos ver una matriz de un sólo índice que toma valores de 1 a 7. En el array de la figura siguiente utilizamos dos índices con valores entre 1 y 3, el primero, y entre 1 y 5, el segundo. Cada elemento de esta matriz está representado por un par ordenado de números, el valor de los dos índices. En general, al número de índices del array se le denomina número de dimensiones del array. La dimensión de la formación está dada por los valores máximos de los índices y, el número total de elementos es el producto de estos valores máximos. En los dos ejemplos anteriores, el número de dimensiones de los arrays son 1 y 2, las dimensiones son (7) y (3; 5) y los números totales de elementos son 7 y 15, respectivamente. La principal operación que se puede realizar con los arrays es la selección, que consiste en especificar un elemento determinado del array. Esta operación se efectúa dando un valor para todos y cada uno de los índices del array. Con el elemento seleccionado se pueden realizar las operaciones propias de su tipo. Así, con cada elemento de un array real, una vez seleccionado, se pueden realizar las operaciones definidas para datos de tipo real (operaciones aritméticas). 8.3. Estructuras dinámicas de datos. Como ya se ha dicho, este tipo de estructuras ocupan un espacio en memoria que va evolucionando según el tamaño que dicha estructura vaya adquiriendo. Las estructuras de dinámicas que vamos a estudiar son: colas, pilas, listas encadenadas y árboles. 8.3.1. Colas (FIFO). Una cola es una estructura de datos en la que el primer dato en entrar es el primer dato en salir. Es decir, es una estructura FIFO (First In First Out). Todo el mundo conocemos como 133 Apuntes de Informática funciona una cola, los nuevos se ponen al final, los servicios se prestan al principio y no está permitido “colarse”. Las mismas reglas se aplican a las colas de datos almacenadas en la memoria de un computador. Hay varias formas de implementar una cola en la memoria de un computador. Una forma simple consiste en almacenar los datos en posiciones de memoria adyacentes y utilizar punteros para el principio y el fin de la cola. Cuando un elemento se añade a la cola, el puntero de la parte posterior se ajusta para que señale al nuevo elemento. De manera similar, cuando un elemento se elimina de la cola, se ajusta el puntero delantero para que señale al nuevo primer elemento. El problema de este método para implementar las colas es que las posiciones de memoria que ocupan, varían a medida que se añaden y eliminan elementos de la misma. La solución habitual consiste en asignar un área fija para almacenar la cola y permitir que se mueva en este área de manera circular. Un área de almacenamiento de esta forma se denomina buffer circular , y puede apreciarse en la figura siguiente. Entre las aplicaciones que tienen las colas se encuentran el almacenamiento de datos en camino, entre un procesador y un periférico, o actuar como punto intermedio en las redes de comunicación de datos. 8.3.2. Pilas (LIFO). Una pila es una colección ordenada de datos a los que sólo se puede acceder por un extremo, denominado tope o cima de la pila. La pila es una estructura en la que el último elemento en entrar será el primero en salir, es decir, es lo que se denomina estructura LIFO (Last In First Out). 134 TEMA 8 – Estructuras básicas Podemos comparar esta estructura con una pila de platos colocada sobre un muelle. Cuando se añade un nuevo plato en lo alto de la pila, los demás bajan, cuando se retira un plato de la pila, los demás suben. Igual que en el caso de las colas, van a existir dos punteros, uno que indica la posición tope de la pila, denominado puntero de pila, y otro que señala su base, denominado base de pila, y que mantiene el mismo valor mientras existe la pila. Cuando la pila esta vacía el puntero de pila tiene el mismo valor que la base de pila. La pila es una de las estructuras más importantes en computación. Se usa en cálculos, para pasar de un lenguaje de computador a otro y, para transferir el control de una parte del programa a otra. Las operaciones que se pueden realizar tanto con las colas como con las pilas, son las siguientes: • Añadir o eliminar un elemento: Si es una cola podremos añadirlo o eliminarlo al final de la misma, y si es una pila al principio. • Acceder al primer elemento: Normalmente es el único al que se va a poder acceder directamente. • Acceder al elemento siguiente del último procesado: Este es el mecanismo normal de acceso tanto a colas como a pilas. • Saber si está vacía: Están vacías si no contienen ningún elemento. 8.3.3. Listas encadenadas. Una lista es un conjunto ordenado de datos. Los elementos de la lista pueden insertarse o eliminarse en cualquier punto de la misma, por lo que es menos restrictiva que una pila o una cola. La forma más sencilla de implementar una lista es hacer uso de un puntero que señale desde un dato al siguiente. También hay un puntero que señala al primer elemento de la lista, mientras que para el último se emplea un puntero nulo. Una estructura de este tipo se denomina lista encadenada. Cada elemento de la lista consiste en una parte de datos y un puntero. Una variación sobre la idea de una lista es el caso en el que el puntero del final de la lista señale al primer elemento. Esto crea lo que se denomina una lista circular. Si los elementos de la lista están en orden alfabético o numérico, dichas listas se conocen como listas ordenadas. 135 Apuntes de Informática 8.3.4. Árboles. Un árbol es una estructura que implica una jerarquía, en la que cada elemento está unido a otros bajo él. Cada dato en un árbol es un nodo de dicho árbol. El nodo más alto se denomina raíz. Cada nodo puede estar conectado a uno o más subárboles, que también responden a la estructura de un árbol. Un nodo, en la parte inferior, del que no cuelgue ningún subárbol se denomina nodo terminal u hoja. Un tipo especial de árboles muy usados en computación son los árboles binarios. En ellos, de cada nodo pueden colgar, a lo más, dos subárboles, denominados subárbol derecho y subárbol izquierdo, que también son árboles binarios. La forma usual de representar los árboles supone el uso de punteros. En un árbol binario cada nodo está constituido por una parte de datos y dos punteros. Uno, o ambos punteros, pueden tener un valor nulo si del nodo no cuelgan subárboles. Son muy utilizados en informática. Las partes de muchos programas se enlazan como si se tratara de árboles. Los árboles se utilizan para representar operaciones aritméticas, y en búsquedas y ordenaciones. 8.4. Estructura de archivos. Un archivo no es más que una estructura de datos y, por tanto, podrían haberse incluido en el apartado anterior. Pero debido a la importancia que tienen les dedicaremos una atención especial. 8.4.1. Concepto de archivo. Los datos que se encuentran en la memoria masiva (discos magnéticos, discos ópticos, etc.) suelen organizarse en archivos. Un archivo es un conjunto de información sobre un mismo tema, tratada como una unidad de almacenamiento y organizada de forma estructurada para la búsqueda de un dato individual. Un archivo está compuesto de registros homogéneos que contienen información sobre el tema. La vida de todo archivo comienza cuando se crea y acaba cuando se destruye o borra. Durante la vida del archivo se suelen realizar sobre él determinadas operaciones, de recuperación o consulta y, de mantenimiento o actualización. Las operaciones de actualización que se pueden realizar sobre el archivo son: • Modificación de un registro. • Eliminación o borrado de un registro. • Inserción de un registro. La mayor parte de las operaciones de recuperación, y actualización, implican la realización de una localización, o búsqueda, de un registro concreto para luego actuar sobre él. 136 TEMA 8 – Estructuras básicas 8.4.2. Registros, campos y claves. La unidad elemental que compone los archivos es el registro. Un registro se corresponde con una ficha, o con una hoja de papel en un archivo manual. Cada registro de un archivo tiene, en general, la misma estructura que los demás. Los registros no tienen siempre la misma longitud: en un archivo de facturas, por ejemplo, el número de líneas de una factura (en un registro) no es siempre el mismo. En los sistemas de almacenamiento masivo, hay que reservar un determinado espacio para cada uno de los registros. Los datos individuales ocupan campos dentro de los registros. Un campo puede tener una longitud fija o variable. El término longitud de campo hace referencia al número máximo de caracteres que puede albergar dicho campo. Los campos pueden ser de diferente tipo: numérico, texto, moneda, fecha o código, donde los caracteres tienen un significado especial. La forma más común de identificar un registro es eligiendo un campo dentro del registro. Este campo se denomina clave del registro. La única restricción sobre las claves es que, cada registro debe tener una clave diferente. Por esta y otras razones, se permite utilizar más de un campo como clave para un registro. En este caso, los campos se denominan clave primaria, clave secundaria, etc. Por ejemplo, podríamos considerar la información contenida en una guía telefónica como un archivo. Un registro serían los datos para una persona. En cada registro habrá cuatro campos: apellidos, nombre, dirección y número de teléfono. La clave primaria estaría en el campo dedicado a los apellidos y la clave secundaria en el campo del nombre. Ambas claves permiten identificar a la persona de forma inequívoca, ya que los apellidos solos no son siempre suficiente. 8.4.3. Organización de archivos. En los primeros tiempos de la informática la estructura de los archivos estaba determinada en gran medida por los medios de almacenamiento disponibles. Hoy en día la situación se ha invertido. La naturaleza de las aplicaciones determina la estructura de los archivos, que es la que define los medios de almacenamiento que hay que utilizar. Las estructuras más comunes para los archivos son: secuencial, directa e indexada. 8.4.3.1. Organización secuencial. En esta organización los registros figuran almacenados contiguamente (uno a continuación de otro), siguiendo la secuencia lógica del archivo. La secuencia en la que aparecen los registros en el archivo, puede estar determinada por el valor de algún campo, o ser simplemente temporal. Los archivos secuenciales pueden almacenarse en cintas o discos magnéticos. 8.4.3.2. Organización directa. Un archivo con organización directa o aleatoria es un archivo escrito sobre un soporte direccionable para el cual existe una transformación conocida que genera la dirección de cada 137 Apuntes de Informática registro en el archivo a partir de una llave. El calificativo aleatorio se debe a que normalmente no existe ninguna vinculación aparente entre el orden lógico de los registros y su orden físico. El problema fundamental de esta organización es la elección de la transformación, o método de direccionamiento, que se ha de usar. En algunos casos, pueden aparecer las siguientes situaciones no deseadas: - Direcciones que no corresponden a ninguna llave (zonas de disco sin utilizar). Direcciones que corresponden a más de una llave. Hay tres métodos fundamentales de direccionamiento para los archivos de organización directa: • Directo. Si se utiliza como dirección la propia llave. • Asociado. A cada llave se le asocia una dirección mediante una tabla. • Calculado o por transformación de llave. La dirección se obtiene realizando operaciones y transformaciones con la llave. Los archivos aleatorios son los más apropiados para las aplicaciones en las que se necesita un acceso rápido a los registros. 8.4.3.3. Organización secuencial indexada. Los archivos secuenciales indexados son, actualmente, el tipo de archivos en uso más común. Se trata de archivos secuenciales que disponen de un índice. Este índice es un conjunto de datos que permite asociar la clave de un registro con su posición física en el disco. Es muy similar al índice de un libro que relaciona palabras y párrafos con números de página. El índice se guarda generalmente al principio del archivo, o, más comúnmente, en un archivo aparte. Si se utiliza más de un nivel de indexado, las distintas partes del índice pueden guardarse a lo largo del archivo. Como estos archivos necesitan que los datos se localicen mediante su dirección, es necesario que se almacenen en disco magnético. La técnica de indexado más simple hace referencia a la estructuras de los paquetes de discos magnéticos. Esta técnica de denomina indexado por cilindro-superficie-ysector. Para cada paquete de discos del archivo existe un índice de cilindro. Una vez que se ha seleccionado el cilindro, se utiliza un índice de superficie, y elegida la superficie, se emplea un índice de sector. Una vez determinado el sector se puede acceder al registro deseado, copiando el sector en la memoria principal. De todo esto puede deducirse que, para localizar un registro es necesario acceder cuatro veces al disco, una vez para determinar cada índice y una vez más, para obtener el sector con los datos. Una ventaja de este tipo de archivos es la facilidad para dejar espacio entre los sectores, superficies o cilindros con el fin de insertar nuevos registros. Esto permite que los archivos crezcan sin necesidad de copiarlos a otro disco y reindexarlos con frecuencia. 138 TEMA 8 – Estructuras básicas 8.5. Utilización de las estructuras en los lenguajes de programación. Las estructuras de datos que hemos visto, se han presentado en términos generales. Vistas de este modo suelen denominarse estructuras de datos abstractas, con sus propiedades esenciales definidas de forma completamente independiente de cada computador particular. Esto es necesario para mantener la simplicidad de estas estructuras y para investigar más a fondo sus propiedades. Se plantean, pues, una serie de problemas cuando se representa cualquiera de estas estructuras en un computador. En la mayoría de los casos, el método de representarla interfiere con las propiedades de las mismas. Parte de la limpieza y simplicidad de la estructura abstracta se pierde. El problema más común que se presenta es que los computadores tienen una capacidad de memoria limitada. Todas las estructuras dinámicas que se han expuesto carecen de límites en lo que se refiere al volumen que pueden alcanzar. En la práctica hay que imponer un límite a su tamaño y deben realizarse comprobaciones cada vez que se añade un elemento. 139