LISTAS DOBLEMENTE ENLAZADAS




1. INTRODUCCIÓN.

En algunas aplicaciones podemos desear recorrer la lista hacia adelante y hacia atrás, o dado un elemento, podemos desear conocer rápidamente los elementos anterior y siguiente. En tales situaciones podríamos desear darle a cada celda sobre una lista un puntero a las celdas siguiente y anterior en la lista tal y como se muestra en la figura.


Otra ventaja de las listas doblemente enlazadas es que podemos usar un puntero a la celda que contiene el i-ésimo elemento de una lista para representar la posición i, mejor que usar el puntero a la celda anterior aunque lógicamente, también es posible la implementación similar a la expuesta en las listas simples haciendo uso de la cabecera. El único precio que pagamos por estas características es la presencia de un puntero adicional en cada celda y consecuentemente procedimientos algo más largos para algunas de las operaciones básicas de listas. Si usamos punteros (mejor que cursores) podemos declarar celdas que consisten en un elemento y dos punteros a través de:

typedef struct celda{
	tipoelemento elemento;
   	struct celda *siguiente,*anterior;
}tipocelda;

typedef tipocelda *posicion;


Un procedimiento para borrar un elemento en la posición p en una lista doblemente enlazada es:

void borrar (posicion p)
{
   if (p->anterior != NULL)
	p->anterior->siguiente = p->siguiente;
   if (p->siguiente != NULL)
	p->siguiente->anterior = p->anterior;
   free(p);
}


El procedimiento anterior se expresa de forma gráfica en como muestra la figura:


Donde los trazos contínuos denotan la situación inicial y los punteados la final. El ejemplo visto se ajusta a la supresión de un elemento o celda de una lista situada en medio de la misma. Para obviar los problemas derivados de los elementos extremos (primero y último) es práctica común hacer que la cabecera de la lista doblemente enlazada sea una celda que efectivamente complete el círculo, es decir, el anterior a la celda de cabecera sea la última celda de la lista y la siguiente la primera. De esta manera no necesitamos chequear para NULL en el anterior procedimiento borrar.

Por consiguiente, podemos realizar una implementación de listas doblemente enlazadas con cabecera tal que tenga una estructura circular en el sentido de que dado un nodo y por medio de los punteros siguiente podemos volver hasta él como se puede observar en la figura (de forma analoga para anterior).



Es importante notar que aunque la estructura física de la lista puede hacer pensar que mediante la operación siguiente podemos alcanzar de nuevo un nodo de la lista, la estructura lógica es la de una lista y por lo tanto habrá una posición primero y una posición fin de forma que al aplicar una operación anterior o siguiente respectivamente sobre estas posiciones el resultado será un error.

Respecto a la forma en que trabajarán las funciones de la implementación que proponemos hay que hacer constar los siguientes puntos:



2. OPERACIONES PRIMITIVAS DE LISTAS DOBLES.

Dentro del tipo abstracto de listas doblemente enlazadas podemos proponer las siguientes primitivas:


ESPECIFICACIÓN SEMANTICA Y SINTACTICA.



3. EFICIENCIA.

Comparación de la eficiencia para las distintas implementaciones de las listas:




4. IMPLEMENTACIÓN DE LISTAS DOB. ENLAZADAS.

Una vez aclaradas las posibles ambigüedades y dudas que se pueden plantear, la implementación de las listas doblemente enlazadas quedaría como sigue:

typedef struct celda {
   tElemento elemento;
   struct celda *siguiente,*anterior;
} tipocelda;

typedef tipocelda *tPosicion;
typedef tipocelda *tLista;

static void error(char *cad)
{
   fprintf(stderr, "ERROR: %s\n", cad);
   exit(1);
}

tLista Crear()
{
   tLista l;
   
    	
    	
   

   l = (tLista)malloc(sizeof(tipocelda));
   if (l == NULL)
	Error("Memoria insuficiente.");
   l->siguiente = l->anterior = l;
   return l;
}

void Destruir (tLista l)
{
   tPosicion p;
   
    	
    	
   

   for (p=l, l->anterior->siguiente=NULL; l!=NULL; p=l) {
	l = l->siguiente;
	free(p);
   }
}   

tPosicion Primero (tLista l)
{
   
    	
    	
   
   return l->siguiente;
}

tPosicion Fin (tLista l)
{
   
    	
    	
   
   return l;
}

void Insertar (tElemento x, tPosicion p, tLista l)
{
   tPosicion nuevo;
   
    	
    	
   

   nuevo = (tPosicion)malloc(sizeof(tipocelda));
   if (nuevo == NULL) 
	Error("Memoria insuficiente.");
   nuevo->elemento = x;
   nuevo->siguiente = p;
   nuevo->anterior = p->anterior;
   p->anterior->siguiente = nuevo;
   p->anterior = nuevo;
}

void Borrar (tPosicion *p, tLista l)
{
   tPosicion q;
   
    	
    	
   
   if (*p == l){ 
	Error("Posicion fin(l)");
   }
   q = (*p)->siguiente;
   (*p)->anterior->siguiente = q;
   q->anterior = (*p)->anterior;
   free(*p);
   (*p) = q; 
}

tElemento elemento(tPosicion p, tLista l)
{
   
    	
    	
   
   if (p == l){ 
	Error("Posicion fin(l)");
   }
   return p->elemento;
}

tPosicion siguiente (tPosicion p, tLista l)
{
   
    	
    	
   
   if (p == l){ 
	Error("Posicion fin(l)");
   }
   return p->siguiente;
}

tPosicion anterior( tPosicion p, tLista l)
{
   
    	
    	
   
   if (p == l->siguiente){
	Error("Posicion primero(l)");
   }
   return p->anterior;
}

tPosicion posicion (tElemento x, tLista l)
{
   tPosicion p;
   int encontrado;
   
    	
    	
   

   p = primero(l);
   encontrado = 0;
   while ((p != fin(l)) && (!encontrado))
	if (p->elemento == x)
		encontrado = 1;
	else
		p = p->siguiente;
   return p;
}






Tutor de Estructuras de Datos Interactivo
Exposito Lopez Daniel, Abraham García Soto, Martin Gomez Antonio Jose
Director de proyecto: Joaquín Fernández Valdivia
5º Licenciatura Informatica
ETSII 99/00 (Universidad de Granada).