Professional Documents
Culture Documents
Algoritmia
4 Estructuras Enlazadas
Introducción:
En el presente trabajo se incorpora el concepto de asignación dinámica en memoria.
Esto permite romper con la limitación de tamaño fijo que proporcionan las tablas
cuando es necesario trabajar con colección de datos el mismo tipo en memoria.
Para esto se incorporan los conceptos de estructuras enlazadas, punteros y asignación
dinámica en memoria.
LÉXICO
TipoRegistro=TIPO <Campo1 : Entero ; Campo2 : Entero>
TipoPunteroRegistro = TIPO Apuntador a TipoRegistro;
Registro : TipoRegistro
PunteroRegistro : TipoPunteroRegistro
ALGORITMO
Registro.Campo1 ← 5; //asigna valores al registro
Registro.Campo2 10;
PunteroRegistro ← dirección_de(Registro);//
PunteroRegistro↑.Campo1 ← 35;
Escribir(Registro.Campo1); //imprime el valor 35
FIN
Obviamente, cuando una variable de tipo apuntador haga referencia a una variable
declarada en el léxico, como en el caso del ejemplo anterior, no será posible destruir su
contenido mediante la acción Destruir, ya que solo pueden destruirse las instancias que
se crean en tiempo de ejecución con la acción Nuevo.
El tipo pila
Una pila es una colección de elementos de un mismo tipo, posiblemente vacía, sobre la
que podemos hacer operaciones de inserción de un nuevo elemento, eliminación de un
elemento. Una pila es una estructura de tipo LIFO (del inglés, Last-Input, First-
Output), lo cual significa que los elementos siempre serán eliminados de ella en orden
inverso al que fueron colocados, de modo que el último elemento en entrar será el
primero en salir. A este tipo de colección de datos se la conoce por el nombre de “pila”
precisamente por esta característica. A la hora de retirar elementos, únicamente
podremos tomar el que está en la cima de la pila, que será el último que fue apilado.
Para definir el tipo de pila debemos, por tanto, diseñar las siguientes operaciones:
PilaVacia: Pila
EsPilaVacia: Pila Booleano
Apilar : Pila X TipoBase Pila
Cima : Pila TipoBase
Desapilar : Pila Pila
La lista anterior de operaciones disponibles para un tipo se conoce con el nombre de
signatura, y en ella se establecen los nombres de las operaciones y el número y tipo de
parámetros de cada una de ellas. En la signatura de un cierto tipo T se distinguen tres
tipos de operaciones:
1. Operaciones constructoras: son aquellas en las que el tipo T aparece como
resultado devuelto, pero no como parámetro de la operación.
2. Operaciones modificadoras: son aquellas en las que el tipo T aparece tanto en la
lista de parámetros como en el resultado devuelto.
3. Operaciones de consulta: son aquellas en las que el tipo T aparece únicamente
en la lista de parámetros.
En el caso del tipo pila anterior, la única operación constructora sería PilaVacia; las
operaciones Apilar y Desapilar serían modificadoras, y las operaciones EsPilaVacia y
Cima serían de consulta. Las operaciones Cima y Desapilar tendrán como precondición
que la pila que reciben como parámetro no sea vacía, pues para una pila vacía no estaría
definida su cima ni se podría desapilar.
Se puede definir una pila de caracteres mediante las siguientes declaraciones:
TPNodoPilaCars = TIPO Apuntador a NodoPilaCars;
NodoPilaCars=Tipo < dato: Carácter; sig : TPNodoPilaCars >;
PilaCars = TPNodoPilaCars;
PilaVacia PilaCars : una función
ALGORITMO
PilaVacia ← NULO
FIN
Es importante que se sepa que cuando se definen tipos de datos basados en estructuras
de datos complejas, las operaciones usuales de igualdad o desigualdad, que se realizan
por defecto mediante los operadores booleanos = y ≠, producirían resultados que no se
corresponden con la semántica del tipo implementado. Por ejemplo, suponga que
declaramos dos variables, p1 y p2 de tipo PilaCars, y efectuamos una comparación
como la siguiente:
SI p1 = p2 ENTONCES
...
FIN_SI;
Lo que estamos comparando en realidad no son las pilas, es decir, si constan de los
mismos elementos y están en idéntico orden, sino los apuntadores p1 y p2, puesto que
ambas variables son de tipo PilaCars el cual, a su vez, es un tipo apuntador. La
comparación p1 = p2 devolverá Verdadero si y sólo si los apuntadores p1 y p2
apuntan a la misma dirección de memoria.
El tipo cola
El tipo cola difiere del tipo pila únicamente en la forma de inserción y extracción de
elementos. Una cola es una estructurade tipo FIFO (del inglés, First-Input, First-
Output), es decir, primero en entrar, primero en salir. Se conoce con el nombre de cola
por ser la que habitualmente se utiliza en las colas de la vida cotidiana: el primero que
llega (que entra en la cola) es el primero en ser atendido (en ser eliminado de la cola).
TPNodoColaCars = TIPO Apuntador a NodoColaCars;
NodoColaCars = TIPO < dato: carácter; sig TPNodoColaCars >;
ColaCars = TPNodoColaCars;
Las operaciones propias del tipo cola son las siguientes:
ColaVacia : Cola
EsColaVacia : Cola Booleano
Encolar : Cola X TipoBase Cola
Primero : Cola TipoBase
EliminarPrimero : Cola Cola
El tipo lista
El tipo lista es el caso más general de estructura de datos lineal. No existe una forma
prefijada de inserción y extracción de elementos, de modo que éstos pueden ser
insertados en, y también eliminados de, cualquier posición arbitraria.
Con las listas existe un repertorio más amplio de operaciones básicas que se pueden
realizar:
1. Añadir o insertar elementos.
2. Buscar o localizar elementos.
3. Borrar elementos.
4. Moverse a través de una lista, anterior, siguiente, primero.
Algoritmo de inserción:
2. El primer paso es crear un nodo para el dato que vamos a insertar.
3. Si Lista es NULO, o el valor del primer elemento de la lista es mayor que el del
nuevo, insertaremos el nuevo nodo en la primera posición de la lista.
4. En caso contrario, se debe buscar el lugar adecuado para la inserción, tenemos
un puntero "anterior". Lo inicializamos con el valor de Lista, y avanzaremos
mientras anterior^.siguiente no sea NULO y el dato que contiene
anterior^.siguiente sea menor que el dato a insertar.
5. Ahora anterior señalando al nodo previo al insertar, por lo que se enlazan lo
puntero de modo que el siguiente del nuevo sea el que era siguiente del anterior
y al siguiente del anterior se lo hace apuntar al nuevo nodo. Se debe recordar que
anterior contiene el valor inmediato anterior al valor insertado y el siguiente es
mayor. El nuevo nodo debe intercalarse entre ambos.
En el capitulo de algoritmos puntuales se ofrecen varios ejemplos para insertar nodos en
situaciones distintas y con distintos fines
Listas circulares
Una lista circular es una lista lineal en la que el último nodo a punta al primero.
Las listas circulares evitan excepciones en la operaciones que se realicen sobre ellas. No
existen casos especiales, cada nodo siempre tiene uno anterior y uno siguiente.
En algunas listas circulares se añade un nodo especial de cabecera, de ese modo se evita
la única excepción posible, la de que la lista esté vacía.
El nodo típico es el mismo que para construir listas abiertas es una estructura
autoreferenciada que tiene un campo con la información y otro campo con un puntero a
una estructura del mismo tipo:
Lista es el puntero para contener el inicio de las listas, tanto abiertas como circulares.
En el caso de las circulares, apuntará a un nodo cualquiera de la lista.
A pesar de que las listas circulares simplifiquen las operaciones sobre ellas, también
introducen algunas complicaciones. Por ejemplo, en un proceso de búsqueda, no es tan
sencillo dar por terminada la búsqueda cuando el elemento buscado no existe.
Por ese motivo se suele resaltar un nodo en particular, que no tiene por qué ser siempre
el mismo. Cualquier nodo puede cumplir ese propósito, y puede variar durante la
ejecución del programa.
Otra alternativa que se usa a menudo, y que simplifica en cierto modo el uso de listas
circulares es crear un nodo especial de hará la función de nodo cabecera. De este modo,
la lista nunca estará vacía, y se eliminan casi todos los casos especiales.
Operaciones básicas con listas circulares
A todos los efectos, las listas circulares son como las listas abiertas en cuanto a las
operaciones que se pueden realizar sobre ellas:
1. Añadir o insertar elementos.
2. Buscar o localizar elementos.
3. Borrar elementos.
4. Moverse a través de la lista.
Cada una de estas operaciones podrá tener varios casos especiales, por ejemplo,
tendremos que tener en cuenta cuando se inserte un nodo en una lista vacía, o cuando se
elimina el único nodo de una lista.
Añadir un elemento:
El único caso especial a la hora de insertar nodos en listas circulares es cuando la lista
esté vacía.
Este método también funciona con listas circulares de un sólo elemento, salvo que el
nodo a borrar es el único nodo que existe, y hay que hacer que lista apunte a NULO.
En una lista circular desde cualquier nodo se puede alcanzar cualquier otro. En una lista
simplemente enlazada apuntando a un nodo no es posible volver a nodos anteriores, es
necesario volver a recorrer desde el principio. En una lista circular apuntando a un nodo
se puede volver al predecesor recorriendo hacia delante.
El riesgo con esta estructura es que de no ser riguroso se puede caer en ciclos infinitos
la lista esta sin orden el puntero a lista puede apuntar, como señalamos a cualquier
nodo.
Estas lista tienen código sencillo pero ineficiente. Para insertar un nodo en esta lista sin
orden:
1. Si se aceptan repeticiones simplemente habrá que insertar el nodo en el lugar
donde se encuentra el puntero.
2. Si no se aceptan repeticiones habrá que recorrer la lista, sin caer en ciclos
infinitos para ver si el elemento a insertar ya existe. Para evitar ciclos
infinitos se debe:
a. Guardar en un puntero auxiliar el nodo donde se encuentra el comienzo
del recorrido.
b. Detener el ciclo al encontrar el dato, si es que esta o al igualar el puntero
de recorrido con el auxiliar.
Definiciones
Nodo = (header, común) // enumera dos valores posibles si es header o común.
TipoPunteroCircularCon Header = TIPO Apuntador a TipoNodo;
TipoNodo = <
Sig : TipoPuntero;
Según tag: Nodo
Header : (cant: Entero; promedio Real; mínimo Real);
// parte del nodo cabecera//
común : (nombre Cadena; Sueldo : Real)
// Parte común del resto de los nodos//
FIN_SEGUN
>;
TipoDato = <nombre : Cadena; sueldo : Rel>
Añadir un elemento:
La estructura s diferente a las vistas anteriormente por lo que se analizaran todos los
casos posibles de inserción.
El proceso es el siguiente:
Nodo^.siguiente debe apuntar a Lista.
Nodo^.anterior apuntará a Lista^.anterior.
Lista^.anterior debe apuntar a nodo.
Lista no tiene por qué apuntar a ningún miembro concreto de una lista doblemente
enlazada, cualquier miembro es igualmente válido como referencia.
El paso 2 separa el nodo a borrar del resto de la lista, independientemente del nodo al
que apunte Lista.