Professional Documents
Culture Documents
1) INTRODUCCION
La lista enlazada bsica es la lista enlazada simple la cual tiene un enlace por nodo.
Este enlace apunta al siguiente nodo en la lista, o al valor NULL o a la lista vaca, si
es el ltimo nodo.
Una lista enlazada simple contiene dos valores: el valor actual del nodo y un enlace
al siguiente nodo
Una lista doblemente enlazada contiene tres valores: el valor, el link al nodo
siguiente, y el link al anterior
En algn lenguaje de muy bajo nivel, XOR-Linking ofrece una va para implementar
listas doblemente enlazadas, usando una sola palabra para ambos enlaces, aunque el
uso de esta tcnica no se suele utilizar.
En una lista enlazada circular, el primer y el ltimo nodo estn unidos juntos. Esto se
puede hacer tanto para listas enlazadas simples como para las doblemente enlazadas.
Para recorrer una lista enlazada circular podemos empezar por cualquier nodo y
seguir la lista en cualquier direccin hasta que se regrese hasta el nodo original.
Desde otro punto de vista, las listas enlazadas circulares pueden ser vistas como listas
sin comienzo ni fin. Este tipo de listas es el ms usado para dirigir buffers para
ingerir datos, y para visitar todos los nodos de una lista a partir de uno dado.
Cada nodo tiene un enlace, similar al de las listas enlazadas simples, excepto que el
siguiente nodo del ltimo apunta al primero. Como en una lista enlazada simple, los
nuevos nodos pueden ser solo eficientemente insertados despus de uno que ya
tengamos referenciado. Por esta razn, es usual quedarse con una referencia
solamente al ltimo elemento en una lista enlazada circular simple, esto nos permite
rpidas inserciones al principio, y tambin permite accesos al primer nodo desde el
puntero del ltimo nodo. 1
En una lista enlazada doblemente circular, cada nodo tiene dos enlaces, similares a
los de la lista doblemente enlazada, excepto que el enlace anterior del primer nodo
apunta al ltimo y el enlace siguiente del ltimo nodo, apunta al primero. Como en
una lista doblemente enlazada, las inserciones y eliminaciones pueden ser hechas
desde cualquier punto con acceso a algn nodo cercano. Aunque estructuralmente una
lista circular doblemente enlazada no tiene ni principio ni fin, un puntero de acceso
externo puede establecer el nodo apuntado que est en la cabeza o al nodo cola, y as
mantener el orden tan bien como en una lista doblemente enlazada.
Nodos Centinelas
A veces las listas enlazadas tienen un nodo centinela (tambin llamado falso nodo o
nodo ficticio) al principio o al final de la lista, el cual no es usado para guardar datos.
Su propsito es simplificar o agilizar algunas operaciones, asegurando que cualquier
nodo tiene otro anterior o posterior, y que toda la lista (incluso alguna que no
contenga datos) siempre tenga un primer y ltimo nodo.
Las listas enlazadas son usadas como mdulos para otras muchas estructuras de
datos, tales como pilas, colas y sus variaciones.
El campo de datos de un nodo puede ser otra lista enlazada. Mediante este
mecanismo, podemos construir muchas estructuras de datos enlazadas con listas; esta
prctica tiene su origen en el lenguaje de programacin Lisp, donde las listas
enlazadas son una estructura de datos primaria (aunque no la nica), y ahora es una
caracterstica comn en el estilo de programacin funcional.
A veces, las listas enlazadas son usadas para implementar arrays asociativos, y estas
en el contexto de las llamadas listas asociativas. Hay pocas ventajas en este uso de las
listas enlazadas; hay mejores formas de implementar stas estructuras, por ejemplo
con rboles binarios de bsqueda equilibrados. Sin embargo, a veces una lista
enlazada es dinmicamente creada fuera de un subconjunto propio de nodos
semejante a un rbol, y son usadas ms eficientemente para recorrer sta serie de
datos.
4) Ventajas
Por otra parte, los arrays permiten acceso aleatorio mientras que las listas
enlazadas slo permiten acceso secuencial a los elementos. Las listas
enlazadas simples, de hecho, solo pueden ser recorridas en una direccin. Esto
hace que las listas sean inadecuadas para aquellos casos en los que es til
buscar un elemento por su ndice rpidamente, como el heapsort. El acceso
secuencial en los arrays tambin es ms rpido que en las listas enlazadas.
Tambin puede resultar lento y abusivo el asignar memoria para cada nuevo
elemento. Existe una variedad de listas enlazadas que contemplan los
problemas anteriores para resolver los mismos.
Un buen ejemplo que muestra los pros y contras del uso de arrays sobre listas
enlazadas es la implementacin de un programa que resuelva el problema de
Josephus. Este problema consiste en un grupo de personas dispuestas en forma
de crculo. Se empieza a partir de una persona predeterminadas y se cuenta n
veces, la persona n-sima se saca del crculo y se vuelve a cerrar el grupo. Este
proceso se repite hasta que queda una sola persona, que es la que gana. Este
ejemplo muestra las fuerzas y debilidades de las listas enlazadas frente a los
arrays, ya que viendo a la gente como nodos conectados entre s en una lista
circular se observa cmo es ms fcil suprimir estos nodos. Sin embargo, se ve
como la lista perder utilidad cuando haya que encontrar a la siguiente persona
a borrar. Por otro lado, en un array el suprimir los nodos ser costoso ya que no
se puede quitar un elemento sin reorganizar el resto. Pero en la bsqueda de la
n-sima persona tan slo basta con indicar el ndice n para acceder a l
resultando mucho ms eficiente.
Las listas doblemente enlazadas requieren ms espacio por nodo y sus operaciones
bsicas resultan ms costosas pero ofrecen una mayor facilidad para manipular ya
que permiten el acceso secuencial a lista en ambas direcciones. En particular, uno
puede insertar o borrar un nodo en un nmero fijo de operaciones dando nicamente
la direccin de dicho nodo (Las listas simples requieren la direccin del nodo anterior
para insertar o suprimir correctamente). Algunos algoritmos requieren el acceso en
ambas direcciones.
Las listas circulares son ms tiles para describir estructuras circulares y tienen la
ventaja de poder recorrer la lista desde cualquier punto. Tambin permiten el acceso
rpido al primer y ltimo elemento por medio de un puntero simple.
Los lenguajes que no aceptan cualquier tipo de referencia pueden crear uniones reemplazando los
punteros por ndices de un array. La ventaja es de mantener un array de entradas, donde cada
entrada tiene campos enteros indicando el ndice del siguiente elemento del array. Pueden haber
nodos sin usarse. Si no hay suficiente espacio, pueden usarse arrays paralelos.
Entonces una lista enlazada puede ser construida, creado un array con esta estructura, y una variable
entera para almacenar el ndice del primer elemento. (ver en la seccin de implementaciones).
Especialmente para una lista pequea, los arrays indexados pueden ocupar mucho menos
espacio que un conjunto de punteros.
La localidad de referencia puede ser mejorada guardando los nodos juntos en memoria y
siendo reordenados peridicamente.
Usar un fondo general de memoria deja ms memoria para otros datos si la lista es ms
pequea de lo esperado si muchos nodos son liberados.
El crecimiento de un array cuando est lleno no puede darse lugar (o habra que
redimensionarlo) mientras que encontrar espacio para un nuevo nodo en una lista resulta
posible y ms fcil.
Por estas razones, la propuesta se usa principalmente para lenguajes que no soportan asignacin de
memoria dinmica. Estas desventajas se atenan tambin si el tamao mximo de la lista se conoce
en el momento en el que el array se crea.
Lenguajes soportados
Muchos lenguajes de programacin tales como Lisp y Scheme tienen listas enlazadas simples ya
construidas. En muchos lenguajes de programacin, estas listas estn construidas por nodos, cada
uno llamado cons o celda cons. Las celdas cons tienen dos campos: el car, una referencia del dato al
nodo, y el cdr, una referencia al siguiente nodo. Aunque las celdas cons pueden ser usadas para
construir otras estructuras de datos, este es su principal objetivo.
En lenguajes que soportan tipos abstractos de datos o plantillas, las listas enlazadas ADTs o
plantillas estn disponibles para construir listas enlazadas. En otros lenguajes, las listas enlazadas
son tpicamente construidas usando referencias junto con el tipo de dato record.
El almacenamiento externo, por otro lado, tiene la ventaja de ser ms genrico, en la misma
estructura de datos y cdigo mquina puede ser usado para una lista enlazada, no importa cul sea
su tamao o los datos. Esto hace que sea ms fcil colocar el mismo dato en mltiples listas
enlazadas. Aunque con el almacenamiento interno los mismos datos pueden ser colocados en
mltiples listas incluyendo mltiples referencias siguientes en la estructura de datos del nodo, esto
podra ser entonces necesario para crear rutinas separadas para aadir o borrar celdas basadas en
cada campo. Esto es posible creando listas enlazadas de elementos adicionales que usen
almacenamiento interno usando almacenamiento externo, y teniendo las celdas de las listas
enlazadas adicionales almacenadas las referencias a los nodos de las listas enlazadas que contienen
los datos.
En general, si una serie de estructuras de datos necesita ser incluida en mltiples listas enlazadas, el
almacenamiento externo es el mejor enfoque. Si una serie de estructuras de datos necesitan ser
incluidas en una sola lista enlazada, entonces el almacenamiento interno es ligeramente mejor, a no
ser que un paquete genrico de listas genricas que use almacenamiento externo est disponible.
Asimismo, si diferentes series de datos que pueden ser almacenados en la misma estructura de datos
son incluidos en una lista enlazada simple, entonces el almacenamiento interno puede ser mejor.
Otro enfoque que puede ser usado con algunos lenguajes implica tener diferentes estructuras de
datos, pero todas tienen los campos iniciales, incluyendo la siguiente (y anterior si es una lista
doblemente enlazada) referencia en la misma localizacin. Despus de definir estructuras distintas
para cada tipo de dato, una estructura genrica puede ser definida para que contenga la mnima
cantidad de datos compartidos por todas las estructuras y contenidos al principio de las estructuras.
Entonces las rutinas genricas pueden ser creadas usando las mnimas estructuras para llevar a cabo
las operaciones de los tipos de las listas enlazadas, pero separando las rutinas que pueden manejar
los datos especficos. Este enfoque es usado a menudo en rutinas de anlisis de mensajes, donde
varios tipos de mensajes son recibidos, pero todos empiezan con la misma serie de campos,
generalmente incluyendo un campo para el tipo de mensaje. Las rutinas genricas son usadas para
aadir nuevos mensajes a una cola cuando son recibidos, y eliminarlos de la cola en orden para
procesarlos. El campo de tipo de mensaje es usado para llamar a la rutina correcta para procesar el
tipo especfico de mensaje.
En la seccin implementaciones (en este mismo artculo) se expone cdigo referente a este tema.
Hay que notar que cuando usamos almacenamiento externo, se necesita dar un paso extra para
extraer la informacin del nodo y hacer un casting dentro del propio tipo del dato. Esto es porque
ambas listas, de familias y miembros, son almacenadas en dos listas enlazadas usando la misma
estructura de datos (nodo), y este lenguaje no tiene tipos paramtricos.
En una lista desordenada, una forma simple para decrementar el tiempo de bsqueda medio es el
mover al frente de forma heurstica, que simplemente mueve un elemento al principio de la lista una
vez que es encontrado. Esta idea, til para crear cachs simples, asegura que el tem usado ms
recientemente es tambin el ms rpido en ser encontrado otra vez.
Otro enfoque comn es indizar una lista enlazada usando una estructura de datos externa ms
eficiente. Por ejemplo, podemos construir un rbol rojo-negro o una tabla hash cuyos elementos
estn referenciados por los nodos de las listas enlazadas. Pueden ser construidos mltiples ndices
en una lista simple. La desventaja es que estos ndices puede necesitar ser actualizados cada vez que
uno nodo es aadido o eliminado (o al menos, antes que el ndice sea utilizado otra vez).
La skip list, o lista por saltos, es una lista enlazada aumentada con capas de punteros para saltos
rpidos sobre grandes nmeros de elementos, y descendiendo haca la siguiente capa. Este proceso
contina hasta llegar a la capa inferior, la cual es la lista actual.
Un rbol binario puede ser visto como un tipo de lista enlazada donde los elementos estn enlazados
entre ellos mismos de la misma forma. El resultado es que cada nodo puede incluir una referencia al
primer nodo de una o dos listas enlazadas, cada cual con su contenido, formando as los subrboles
bajo el nodo.
Una lista enlazada desenrollada es una lista enlazada cuyos nodos contiene un array de datos. Esto
mejora la ejecucin de la cach, siempre que las listas de elementos estn contiguas en memoria, y
reducen la sobrecarga de la memoria, porque necesitas menos metadatos para guardar cada
elemento de la lista.
Una tabla hash puede usar listas enlazadas para guardar cadenas de tems en la misma posicin de la
tabla hash.
Implementaciones
Operaciones sobre listas enlazadas
Listas Enlazadas Lineales
Nuestra estructura de datos tendr dos campos. Vamos a mantener la variable PrimerNodo que
siempre apunta al primer nodo de tal lista, nulo para la lista vaca.
record Node {
data // El dato almacenado en el nodo
next // Una referencia al nodo siguiente, nulo para el ltimo nodo
}
record List {
Node PrimerNodo //Apunta al primer nodo de la lista; nulo para la lista vaca
}
El recorrido en una lista enlazada es simple, empezamos por el primer nodo y pasamos al siguiente
hasta que la lista llegue al final.
node := list.PrimerNodo
while node not null {
node := node.next
}
El siguiente cdigo inserta un elemento a continuacin de otro en una lista simple. El diagrama
muestra cmo funciona.
Insertar al principio de una lista requiere una funcin por separado. Se necesita actualizar
PrimerNodo.
De forma similar, tambin tenemos funciones para borrar un nodo dado para borrar un nodo del
principio de la lista. Ver diagrama.
function removeAfter(Node node) {
obsoleteNode := node.next
node.next := node.next.next
destroy obsoleteNode
}
function removeBeginning(List list) {
obsoleteNode := list.firstNode
list.firstNode := list.firstNode.next
destroy obsoleteNode
}
Advertimos que BorrarPrincipio pone PrimerNodo a nulo cuando se borra el ltimo elemento de la
lista. Adjuntar una lista enlazada a otra puede resultar ineficiente a menos que se guarde una
referencia a la cola de la lista, porque si no tendramos que recorrer la lista en orden hasta llegar a la
cola y luego aadir la segunda lista.
Con estas listas es necesario actualizar muchos ms punteros pero tambin se necesita menos
informacin porque podemos usar un puntero para recorrer hacia atrs y consultar elementos. Se
crean nuevas operaciones y elimina algunos casos especiales. Aadimos el campo anterior a
nuestros nodos, apuntando al elemento anterior, y UltimoNodo a nuestra estructura, el cual siempre
apunta al ltimo elemento de la lista. PrimerNodo y UltimoNodo siempre estn a nulo en la lista
vaca.
record Node {
data // El dato almacenado en el nodo
next // Una referencia al nodo siguiente, nulo para el ltimo nodo
prev // Una referencia al nodo anterior, nulo para el primer nodo
}
record List {
Node firstNode //apunta al primer nodo de la lista; nulo para la lista vaca
Node lastNode //apunta al ltimo nodo de la lista; nulo para la lista vaca
}
Hacia Delante
node := list.firstNode
while node null
<do something with node.data>
node := node.next
Hacia Atrs
node := list.lastNode
while node null
<do something with node.data>
node := node.prev
Estas funciones simtricas aaden un nodo despus o antes de uno dado, como el diagrama muestra:
Tambin necesitamos una funcin para insertar un nodo al comienzo de una lista posiblemente
vaca.
Borrar un nodo es fcil, solo requiere usar con cuidado firstNode y lastNode.
Una consecuencia especial de este procedimiento es que borrando el ltimo elemento de una lista se
ponen PrimerNodo y UltimoNodo a nulo, habiendo entonces un problema en una lista que tenga un
nico elemento.
Estas pueden ser simples o doblemente enlazadas. En una lista circular todos los nodos estn
enlazados como un crculo, sin usar nulo. Para listas con frente y final (como una cola), se guarda
una referencia al ltimo nodo de la lista. El siguiente nodo despus del ltimo sera el primero de la
lista. Los elementos se pueden aadir por el final y borrarse por el principio en todo momento.
Ambos tipos de listas circulares tienen la ventaja de poderse recorrer completamente empezando
desde cualquier nodo. Esto nos permite normalmente evitar el uso de PrimerNodo y UltimoNodo,
aunque si la lista estuviera vaca necesitaramos un caso especial, como una variable UltimoNodo
que apunte a algn nodo en la lista o nulo si est vaca. Las operaciones para estas listas simplifican
el insertar y borrar nodos en una lista vaca pero introducen casos especiales en la lista vaca.
Asumiendo que someNodo es algn nodo en una lista no vaca, esta lista presenta el comienzo de
una lista con someNode.
Hacia Delante
node := someNode
do
do something with node.value
node := node.next
while node != someNode
Hacia Atrs
node := someNode
do
do something with node.value
node := node.prev
while node := someNode
Esta funcin inserta un nodo en una lista enlazada doblemente circular despus de un elemento
dado:
function insertAfter(Node node, Node newNode)
newNode.next := node.next
newNode.prev := node
node.next.prev := newNode
node.next := newNode
Como una lista doblemente enlazada, "removeAfter" y "removeBefore" puede ser implementada
con "remove (list, node.prev)" y "remove (list, node.next)".
record Entry {
integer next; // ndice de la nueva entrada en el array
integer prev; // entrada previa
string name;
real balance;
}
Entry Records[1000];
typedef struct ns {
int data;
struct ns *next;
} node;
int main(void) {
node *n = NULL;
return 0;
}