

































Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity
Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium
Prepara tus exámenes
Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity
Prepara tus exámenes con los documentos que comparten otros estudiantes como tú en Docsity
Encuentra los documentos específicos para los exámenes de tu universidad
Estudia con lecciones y exámenes resueltos basados en los programas académicos de las mejores universidades
Responde a preguntas de exámenes reales y pon a prueba tu preparación
Consigue puntos base para descargar
Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium
Comunidad
Pide ayuda a la comunidad y resuelve tus dudas de estudio
Ebooks gratuitos
Descarga nuestras guías gratuitas sobre técnicas de estudio, métodos para controlar la ansiedad y consejos para la tesis preparadas por los tutores de Docsity
introduccion a la estructura de datos en informatica
Tipo: Resúmenes
1 / 41
Esta página no es visible en la vista previa
¡No te pierdas las partes importantes!


































En la práctica, la mayor parte de información útil no aparece aislada en forma de datos simples, sino que lo hace de forma organizada y estructurada. Los diccionarios, guías, enciclopedias, etc., son colecciones de datos que serían inútiles si no estuvieran organizadas de acuerdo con unas determinadas reglas. Además, tener estructurada la información supone ventajas adicionales, al facilitar el acceso y el manejo de los datos. Por ello parece razonable desarrollar la idea de la agrupación de datos, que tengan un cierto tipo de estructura y organización interna.
Como tendremos ocasión de ver, la selección de una estructura de datos frente a otra, a la hora de programar es una decisión importante, ya que ello influye decisivamente en el algoritmo que vaya a usarse para resolver un determinado problema. El objetivo de este capítulo no es sólo la descripción de las distintas estructuras, sino también la comparación de las mismas en términos de utilidad para la programación. De hecho, se trata de dar una idea, acerca de los pros y contras de cada una de ellas con el propósito final de justificar la ya citada ecuación de:
PROGRAMACION = ESTRUCTURAS DE DATOS + ALGORITMOS
Empecemos recordando que un dato de tipo simple, no esta compuesto de otras estructuras, que no sean los bits, y que por tanto su representación sobre el ordenador es directa, sin embargo existen unas operaciones propias de cada tipo, que en cierta manera los caracterizan. Una estructura de datos es, a grandes rasgos, una colección de datos (normalmente de tipo simple) que se caracterizan por su organización y las operaciones que se definen en ellos. Por tanto, una estructura de datos vendrá caracterizada tanto por unas ciertas relaciones entre los datos que la constituyen (p.e., el orden de las componentes de un vector de números reales), como por las operaciones posibles en ella. Esto supone que podamos expresar formalmente, mediante un conjunto de reglas, las relaciones y operaciones posibles (tales como insertar nuevos elementos o como eliminar los ya
existentes). Por el momento y a falta de otros, pensemos en un vector de números, como el mejor ejemplo de una estructura de datos.
Llamaremos dato de tipo estructurado a una entidad, con un solo identificador, constituida por datos de otro tipo, de acuerdo con las reglas que definen cada una de las estructuras de datos. Por ejemplo: una cadena esta formada por una sucesión de caracteres, una matriz por datos simples organizados en forma de filas y columnas y un archivo, está constituido por registros, éstos por campos, que se componen, a su vez, de datos de tipo simple. Por un abuso de lenguaje, se tiende a hacer sinónimos, el dato estructurado con su estructura correspondiente. Aunque ello evidentemente no es así, a un primer nivel, en este libro, asumiremos esta identificación.
Para muchos propósitos es conveniente tratar una estructura de datos como si fuera un objeto individual y afortunadamente, muchos lenguajes de programación permiten manipular estructuras completas como si se trataran de datos individuales, de forma que los datos estructurados y simples se consideran a menudo por el programador de la misma manera. Así a partir de ahora un dato puede ser tanto un entero como una matriz, por nombrar dos ejemplos.
Las estructuras de datos son necesarias tanto en la memoria principal como en la secundaria, de forma que en este capítulo nos centraremos en las correspondientes a la memoria principal, dejando para el capítulo siguiente las estructuras más adecuadas para el almacenamiento masivo de datos.
Los datos de tipo simple tienen una representación conocida en términos de espacio de memoria. Sin embargo, cuando nos referimos a datos estructurados esta correspondencia puede no ser tan directa; por ello vamos a hacer una primera clasificación de los datos estructurados en: contiguos y enlazados. Las estructuras contiguas o físicas son aquellas que al representarse en el hardware del ordenador, lo hacen situando sus datos en áreas adyacentes de memoria; un dato en una estructura contigua se localiza directamente calculando su posición relativa al principio del área de memoria que contiene la estructura. Los datos se relacionan por su vecindad o por su posición relativa dentro de la estructura. Las estructuras enlazadas son estructuras cuyos datos no tienen por qué situarse de forma contigua en la memoria; en las estructuras enlazadas los datos se relacionan unos con otros mediante punteros (un tipo de dato que sirve para ‘apuntar’ hacia otro dato y por tanto para determinar cuál es el siguiente datos de la estructura). La localización de un dato no es inmediata sino que se produce a través de los punteros que relacionan unos datos con otros.
10 C a p i t a l 9 4 C a p i t a l 9 4 #
Long. Cadena (long. = 10) Cadena (long. = 10) cadenaFin de
en el segundo caso el carácter elegido como fin-de-cadena ha sido el #. La cadena que no contiene ningún carácter se denomina cadena vacía y su longitud es 0, que no tiene que ser confundida por una cadena formada sólo por blancos (o espacios), cuya longitud es igual al número de blancos que contiene. De esta manera, una variable de tipo cadena de tamaño 10 puede guardar cadenas de 10 caracteres, pero también de menos si indicamos dónde terminan los caracteres de la cadena. Por ejemplo la cadena “Jaca 99”:
Long. Cadena (long. = 7)
7 J a c a 9 9
Cadena (long. = 7)
J a c a 9 9 #
Libre Fin de Libre cadena
Sobre datos de tipo cadena se pueden realizar las siguientes operaciones:
Asignación: Guardar una cadena en una variable tipo cadena. Como en toda asignación a una variable, la cadena que se guarda puede ser una constante, una variable tipo cadena o una expresión que produzca un dato tipo cadena. Por ejemplo: nombre ← “Pepe” nombre ← mi-nombre-de-pila
Concatenación : Formar una cadena a partir de dos ya existentes, yuxtaponiendo los caracteres de ambas. Si se denota por // al operador “concatenación”, el resultado de:
“ab” // “cd” es “abcd”
Nótese que las constantes de tipo cadena se escriben entre comillas, para no confundirlos con nombres de variables u otros identificadores del programa.
Extracción de subcadena: Permite formar una cadena (subcadena) a partir de otra ya existente. La subcadena se forma tomando un tramo consecutivo de la cadena inicial. Si NOMBRE es una variable de tipo cadena que contiene “JUAN PEDRO ORTEGA” y denotamos por (n:m) la extracción de m caracteres tomados a partir del lugar n , entonces NOMBRE(6:5) es una subcadena que contiene “PEDRO”.
Un caso particular de extracción que se utiliza a menudo es el de extraer un único caracter. Por ello se suele proporcionar un método directo: el nombre seguido por el lugar que ocupa dentro de la cadena. Así, en el ejemplo anterior, NOMBRE(6) = “P” = NOMBRE(6:1)
Obtención de longitud: La longitud de una cadena es un dato de tipo entero, cuyo valor es el número de caracteres que contiene ésta. En el primero de los dos métodos anteriores de representación de cadenas, la longitud se obtiene consultando el número de la primera casilla; en el segundo método la longitud es el número de orden que ocupa el caracter de fin-de-cadena, menos uno.
Comparación de cadenas: Consiste en comparar las cadenas carácter a carácter comenzando por el primero de la izquierda, igual que se consulta un diccionario. El orden de comparación viene dado por el código de E/S del ordenador (ASCII habitualmente). Así la expresión booleana: “Jose” < “Julio”, se evaluara como verdadera. Nótese que en los códigos de E/S, las mayúsculas y las minúsculas son diferentes, dando lugar a resultados paradójicos en la comparación, así pues, si el código de E/S es ASCII, donde las mayúsculas tienen códigos inferiores a las minúsculas, se cumpliría que “Z” < “a”.
70504" Cttc{u 3
Es un conjunto de datos del mismo tipo almacenados en la memoria del ordenador en posiciones adyacentes. Sus componentes individuales se llaman elementos y se distinguen entre ellos por el nombre del array seguido de uno o varios índices o subíndices. Estos elementos se pueden procesar, bien individualmente, determinando su posición dentro del array, bien como array completo. El número de elementos del array se específica cuando se crea éste, en la fase declarativa del programa, definiendo el número de dimensiones o número de índices del mismo y los límites máximo y mínimo que cada uno de ellos puede tomar, que llamaremos rango. Según sea este número, distinguiremos los siguientes tipos de arrays:
Por ello hablaremos de arrays de dimensión 1, 2 ó n , cuyo producto por el rango (o rangos) especifica el número de elementos que lo constituyen. Este dato lo utiliza el compilador para reservar el espacio necesario para almacenar en memoria todos
(^1) Utilizaremos el término array, ya que su traducción castellana, “arreglo, colección, etc” es poco significativa y la imensa mayoría de veces se usa en el argot informático el anglicismo array. Sin embargo, sí usaremos vector y matriz para referirnos a determinados tipos de array.
x: array[1..10] of real float x[10]
Es importante señalar que podemos implementar arrays cuyos elementos sean a su vez cadenas o elementos de otro tipo, así podemos pensar en situaciones como la siguiente, durante la fase declarativa
tipo: palabra = cadena[16] COCHES = vector [1..9] de palabra
lo que nos permitirá manipular en un solo vector hasta 9 cadenas conteniendo como máximo 16 caracteres cada una. Con lo cual COCHES puede contener una información tal como:
1 Alfa Romeo 2 Fiat 3 Ford 4 Lancia 5 Renault 6 Seat 7 8 9
Ya hemos dicho que las operaciones sobre arrays se pueden realizar con elementos individuales o sobre la estructura completa mediante las correspondientes instrucciones y estructuras de control del lenguaje. Las operaciones sobre elementos del vector son:
Asignación: Tiene el mismo significado que la asignación de un valor a una variable no dimensionada, ya que un vector con su índice representa la misma entidad que una variable no dimensionada.
A[20] ← 5 asigna el valor 5 al elemento 20 del vector A A[17] ← B asigna el valor de la variable B al elemento 17 del vector A
Acceso secuencial o recorrido del vector: Consiste en acceder a los elementos de un vector para someterlos a un determinado proceso, tal como introducir datos (escribir) en él, visualizar su contenido (leer), etc.. A la operación de efectuar una acción general sobre todos los elementos de un vector se la denomina recorrido y para ello utilizaremos estructuras repetitivas, cuyas variables de control se utilizan
como subíndices del vector, de forma que el incremento del contador del bucle producirá el tratamiento sucesivo de los elementos del vector.
Ejemplo 1:
Escribir un algoritmo para recorrer secuencialmente un vector H de 10 elementos (haciendo la lectura y escritura de cada elemento) primero con un bucle desde y luego con un bucle mientras.
El pseudocódigo correspondiente será el siguiente:
desde i ← 1 hasta 10 hacer leer (H[i]) escribir (H[i]) fin_desde
i ← 1 mientras i <= 10 hacer leer (H[i]) escribir (H[i]) i ← i+ fin-mientras
Ejemplo 2:
Supongamos que queremos procesar los primeros elementos de un vector PUNTOS, previamente declarado, realizando las siguientes operaciones desde 1 hasta LIMITE (donde este valor debe necesariamente ser menor que el límite superior del rango): a) lectura del array; b) cálculo de la suma de los valores del array; c) cálculo de la media de los valores. Escribir el algoritmo correspondiente.
inicio escribir ‘número de datos’ leer numero
escribir ‘datos del array’ desde i=1 hasta numero hacer leer PUNTOS[i]
fin_desde
escribir ‘la media es’, media fin
BÚSQUEDA EN UN VECTOR: Esta operación, relacionada con la recuperación de información, consiste en encontrar un determinado valor dentro del vector, obteniendo su posición en el mismo en caso que éste exista o declarar la búsqueda
consiguiente este algoritmo será preferible al método anterior. Si el índice alcanzase el valor n+1, supondrá que el argumento no pertenece al vector original y en consecuencia la búsqueda no tiene éxito.
algoritmo búsqueda_centinela A: vector donde se busca (contiene n elementos); t: valor buscado inicio
mientras A(i) <> t hacer
fin_mientras si i = n+ entonces escribir ‘No se encuentra el elemento’ si_no entonces escribir ‘El elemento se encuentra en la posición’, i fin_si fin
Notemos que, a pesar de esta mejora, el número de comparaciones que hemos de efectuar es del orden de la magnitud del vector y si ésta es muy grande el tiempo necesario para la búsqueda puede ser alto. Por ello, puede valer la pena tener ordenado el vector pues, como veremos ahora, las búsquedas sobre vectores ordenados son sensiblemente más rápidas.
Búsqueda binaria: Se aplica a vectores cuyos datos han sido previamente ordenados y es un ejemplo del uso del ‘divide y vencerás’ para localizar el valor deseado. Más adelante, trataremos los algoritmos (y su coste) para ordenar un vector; sin embargo ahora supondremos que esta ordenación ya ha tenido lugar. El algoritmo de búsqueda binaria se basa en los siguientes pasos:
Examinar el elemento central del vector; si éste es el elemento buscado, entonces la búsqueda ha terminado
En caso contrario, se determina si el elemento buscado está en la primera o en la segunda mitad del vector (de aquí el nombre de binario) y a continuación se repite este proceso, utilizando el elemento central del subvector correspondiente.
Ejemplo 3:
Considerar el siguiente vector ordenado de nueve elementos enteros, donde queremos buscar el valor 2983
1 2 3 4 5 6 7 8 9 2473 2545 2834 2892 2898 2983 3005 3446 3685 central
Para buscar el elemento 2983, se examina el número central 2898, situado en la quinta posición, que resulta ser distinto. Al ser 2983 mayor que 2898, se desprecia la primera mitad del vector, quedándonos con la segunda:
6 7 8 9 2983 3005 3446 3685 central
Se examina ahora el número central 3005, situado en la posición 7, que resulta ser distinto. Al ser 2983 menor que 3005, nos quedamos con la primera mitad del vector:
6 2983 central
Finalmente encontramos que el valor buscado coincide con el central. Nótese que si el valor buscado hubiera sido, por ejemplo, el 2900, la búsqueda habría finalizado con fracaso al no quedar mitades donde buscar.
Vamos a dar el algoritmo de búsqueda binaria para encontrar un elemento K, en un vector de elementos X(1), X(2),..., X(n), previamente clasificados en orden ascendente (ordenado en orden creciente si los datos son numéricos, o alfabéticamente si son caracteres). El proceso de búsqueda debe terminar normalmente conociendo si la búsqueda ha tenido éxito (se ha encontrado el elemento) o bien no ha tenido éxito (no se ha encontrado el elemento), y normalmente se deberá devolver la posición del elemento buscado dentro del vector. Las variables enteras BAJO, CENTRAL, ALTO, indican los límites inferior, central y superior del intervalo de búsqueda, en cada subvector que sucesivamente se esta considerando, durante la búsqueda binaria.
algoritmo búsqueda_binaria X: vector de N elementos K: valor buscado inicio
Fig. 5.2. Ejemplo de búsqueda binaria: (a), con éxito; (b), sin éxito.
Insertar datos en un vector
La operación insertar consiste en colocar un nuevo elemento, en una determinada posición del vector; ello supone no perder la información, que pudiera hallarse anteriormente, en la posición que va a ocupar, el valor a insertar. Es condición necesaria para que esta operación pueda tener lugar, la comprobación
de espacio de memoria suficiente en el vector, para el nuevo valor; dicho de otro modo, que el vector no tenga todos sus elemento ocupados por datos. Cada vez que insertamos un dato en una determinada posición hemos de correr todos los elementos posteriores del vector, hacia abajo, poniendo especial cuidado en que no se pierda ninguno de estos datos.
Ejemplo 4:
Consideremos el ya visto vector COCHES de 9 elementos que contiene 7 marcas de automóviles, en orden alfabético, en el que se desea insertar dos nuevas marcas OPEL y CITRÖEN manteniendo el orden alfabético del vector. Como Opel está comprendido entre Lancia y Renault , se deberán desplazar hacia abajo los elementos 5 y 6, que pasarán a ocupar la posición relativa 6 y 7. Posteriormente debe realizarse la misma operación con Citröen que ocupará la posición 2. El algoritmo que realiza esta operación para un vector de n elementos es el siguiente, suponiendo que hay espacio suficiente en el vector, y que conocemos la posición, que designaremos por P, que debe ocupar el elemento a insertar. Por ejemplo, la primera inserción, supone que Opel debe ocupar la posición P = 5.
{decrementar contador}
fin_mientras
Los contenidos sucesivos del vector COCHES son los siguientes:
1 Alfa Romeo 1 Alfa Romeo 1 Alfa Romeo
2 Fiat 2 Fiat 2 Citröen
3 Ford 3 Ford 3 Fiat
4 Lancia 4 Lancia 4 Ford
5 Renault 5 Opel 5 Lancia
= 4 para una torre blanca = 5 para una reina blanca = 6 para un rey blanco
con los correspondientes números negativos para las piezas negras.
Puesto que la memoria del ordenador no está organizada en forma rectangular sino en forma de una enorme fila para almacenar una matriz hemos de recurrir a guardar las filas una a continuación de otra. Así, si en una matriz con un número de columnas C, que es el número de elementos que tiene cada fila, para localizar el elemento de fila i y columna j, tendríamos que movernos, desde la posición de inicio de la matriz, hasta la posición: C*(i-1) + j. Afortunadamente el propio software del sistema se encarga de convertir los términos en forma de filas y columnas, en localizaciones concretas dentro de la memoria, de forma que podemos pensar conceptualmente en forma de tabla, aunque se almacenen en forma de fila, dentro de la máquina^2.
5.3.2.3" Arrays multidimensionales
Dependiendo del tipo de lenguaje, pueden existir arrays de tres o más dimensiones (por ejemplo FORTRAN 77 admite hasta siete dimensiones). Para el caso de tres dimensiones, la estructura puede visualizarse como un cubo, y para mayor número de dimensiones ésta visualización no es posible.
El tratamiento de estos arrays es similar al de las matrices, cada conjunto de índices individualiza un elemento de la estructura, que se almacena en memoria de forma secuencial.
70505" Tgikuvtqu
Hasta ahora nos hemos referido a estructuras formadas por datos simples del mismo tipo; sin embargo, es interesante poder manejar una especie de arrays heterogéneos en los que sus elementos puedan ser de tipos diferentes. Llamaremos registro a una estructura de datos, formada por yuxtaposición de elementos que contienen información relativa a un mismo ente. A los elementos que componen el registro los llamamos campos, cada uno de los cuales es de un determinado tipo, simple o estructurado. Los campos dentro del registro aparecen en un orden determinado y se identifican por un nombre. Para definir el registro es necesario
(^2) La forma de almacenar ls matrices expuesta se conoce como “orden principal de la fila”, pero no es la única. Así el FORTRAN utiliza el “orden principal de la columna” consistente en almacenar los elementos de una columna consecutivamente y pasar a la columna siguiente.
especificar el nombre y tipo de cada campo. Por ejemplo consideremos un registro, referido a Empleado, que está constituido por tres campos: Nombre (cadena), Edad (entero) y Porcentaje de impuestos (real).
Nombre Edad Porcentaje de impuestos
Las operaciones básicas que se ejecutan con los registros son: asignación del registro completo a una variable de tipo registro, (definida con sus mismos campos) y selección de un campo, que se realiza especificando el nombre del campo. Puesto que esta estructura, esta especialmente ligada a las transferencias con los periféricos de almacenamiento, volveremos sobre ella cuando nos refiramos a los archivos.
Hasta este momento hemos venido trabajando con variables, dimensionadas o no, que son direcciones simbólicas de posiciones de memoria, de forma que existe una relación bien determinada entre nombres de variables y posiciones de memoria durante toda la ejecución del programa. Aunque el contenido de una posición de memoria asociada con una variable puede cambiar durante la ejecución, es decir el valor asignado a la variable puede variar, las variables por si mismas no pueden crecer ni disminuir, durante la ejecución. Sin embargo, en muchas ocasiones es conveniente poder disponer de un método por el cual, podamos adquirir posiciones de memoria adicionales, a medida que las vayamos necesitando durante la ejecución y al contrario liberarlas cuando no se necesiten.
Las variables y estructuras de datos que reúnen estas condiciones se llaman dinámicas^3 y se representan con la ayuda de un nuevo tipo de dato, llamado puntero , que se define como un dato que indica la posición de memoria ocupada por otro dato. Puede concebirse como una flecha, que “apunta”, al dato en cuestión (Ver Figura 5.3).
(^3) El concepto de estructura dinámica se refiere a la utilización de punteros que permiten que la estructura tenga las propiedades expuestas. Sin embargo, como veremos más adelante, éste es un concepto ligado a la implementación que se haga de la estructura y no de la estructura en sí. Así hay estructuras que pueden implementarse estática o dinámicamente (p.e. colas o pilas).
índice del mismo hace de puntero. Es un caso relativamente banal de estructura dinámica, al cual no prestaremos mayor atención.
Estas listas están formadas por un conjunto de nodos, en los que cada elemento contiene un puntero con la posición o dirección del siguiente elemento de la lista, es decir su enlace. Se dice entonces, que los elementos de una lista están enlazados por medio de los campos enlaces. Cada nodo está constituido por dos partes: información o campos de datos (uno o varios) y un puntero (con la dirección del nodo siguiente). Al campo o campos de datos del nodo lo designaremos como INFORMACIÓN del nodo y al puntero por SIGUIENTE del nodo. Con esta organización de los datos, es evidente que no es necesario que los elementos de la lista estén almacenados en posiciones físicas adyacentes para estar relacionados entre sí, ya que el puntero indica unívocamente la posición del dato siguiente en la lista.
Una forma alternativa de ver las listas enlazadas, es considerarlas como una estructura que contienen un dato dado como la cabeza , y el resto como la cola. La
el elemento A en la cabeza, mientras que la cola está forma por una lista con el elemento B en la cabeza y una lista que contiene los elementos C y D en la cola, etc.
Nótese que para tener definida una lista enlazada, además de la estructura de cada uno de sus nodos, necesitamos una variable externa a la propia lista, con un puntero que marque la posición de la cabeza (inicio, primero) de la lista. Esta variable es quien normalmente da nombre a la lista, pues nos dice donde localizarla, sea cual sea su tamaño. Para detectar el último elemento de la lista se emplea un puntero nulo, que por convenio, se suele representar de diversas formas: por un enlace con la palabra reservada nil (NULO) , por una barra inclinada (/) (Ver Figura 5.5) o por un signo especial, tomado de la toma de tierra en electricidad. Una lista enlazada sin ningún elemento se llama lista vacía y las distinguiremos asignando a su puntero de cabecera el valor nil.
KPKEKQ RTKOGTQ (^) informacion del nodo 1
puntero o enlace al nodo 2
A B C D
nil KPKEKQ RTKOGTQ
A B C D C
Fig. 5.5. Ejemplos de listas enlazadas
5.5.1.1" Creación de una lista
La implementación de una lista enlazada, depende del lenguaje de programación, ya que no todos soportan el puntero como tipo de datos; afortunadamente los más modernos como C, Pascal, etc., si lo hacen, por lo que en lo que sigue vamos a asumir el uso de punteros para su manejo. La alternativa, al uso de punteros, es recurrir a vectores paralelos en los que se almacenan los datos correspondientes a INFORMACION y SIGUIENTE, con una variable que apunte al índice que contiene la cabeza de la lista, de forma que nos podemos imaginar el siguiente esquema de equivalencia, entre ambas estructuras físicas:
1 2 3 4 5 6 7 8 9
10 11 12
RTKOGTQ
KPH UKI (^8) KPH UKI KPH UKI KPH UKI
Una vez definida la estructura de sus nodos, cosa que dejamos aparte, pues dependerá de cada lenguaje de programación, crear una lista consiste en llenar un primer nodo con la información correspondiente a un elemento y cuyo campo de enlace sea NULO. Además hay que definir la variable con el puntero externo que debe contener la dirección de este nodo inicial. Fijarse que ello supone que el lenguaje de programación que utilicemos debe tener una función que nos de la posición de memoria en la que se ha almacenado este nodo. A partir de este nodo