LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ UNIDAD IV LISTAS ENLAZADAS 4.1. REPRESENTACIÓN DE LISTAS ENLAZADAS 4.1.1. IMPLEMENTACION DE LISTA ENLAZADA USANDO ARREGLOS LISTAS ENLAZADAS.- Se define como un conjunto de elementos(nodos) en donde cada nodo se compone al menos de dos campos en donde un campo contiene el dato o información del nodo y el otro campo la dirección del siguiente nodo, con lo cual los datos o información no tienen que estar en direcciones contiguas de memoria, las listas enlazadas emplean puntero o apuntadores para poder iniciar la lista se requiere de un puntero que su única función Serra la de apuntar al primer elemento de la lista, además en el ultimo nodo de la lista su apuntador deberá ser a un nulo o cero para indicar el final de la lista. NODO Dato (información) enlace (puntero) LISTA ENLAZADA LISTA 5 2 8 10 6 DIFERENTES FORMAS DE INDICAR APUNTADOR NULO 4 4 11 NULL Las características que deberá tener un alista enlazada para definirse, se listan a continuación. El tipo de sus elementos. Campo de información (datos). Campos enlace (puntero). Un puntero de cabecera que permite acceder al primer elemento de la lista. Un medio para detectar el ultimo elemento de la lista puntero nulo (NULL). ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 64 LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ Para procesar una lista enlazada se necesitan las siguientes informaciones. Primer nodo (cabecera). El tipo de elementos. El tamaño de la lista (la definición de sus elementos o nodos). Las operaciones que normalmente se ejecutan con listas enlazadas son. Recuperar información de un nodo especifico (acceso a un elemento). Encontrar el nodo que contiene una información especifica (localizar la posición de un elemento dado). Insertar un nuevo nodo en un lugar especifico de la lista. Insertar un nuevo nodo en relación a una información particular. Borrar (eliminar) un nodo existente que contiene información especifica. En el lenguaje C la forma de constituir una lista enlazada será a través de estructuras de datos que dentro de sí contienen un campo que es un puntero el cual apunta a estructuras del mismo tipo y a esto se le conoce como estructura auto referenciada, a continuación se da un ejemplo. struct node { int data; struct node *nextptr; }; Para el manejo de la asignación dinámica de memoria se requiere de emplear malloc() y free() en donde la función malloc se empleara para tomar como argumento el numero de bytes a asignarse y devuelve un apuntador de tipo void a la memoria asignada, y los apuntadores tipo void pueden asignarse a una variable de cualquier tipo de apuntador, deberá emplearse conjuntamente con el operador sizeof(), la función free() se empleara para liberar la memoria asignada dinámicamente y la regresa al sistema, para que pueda ser asignada de nuevo mediante una llamada de malloc, sizeof() es un operador que devuelve el tamaño del tipo de dato que se ponga entre paréntesis, el valor devuelve es en bytes. Las lista enlazadas en turbo C se construirán a partir de estructuras auto referenciadas en donde cada elemento de la lista (nodo) están conectados por los enlaces del apuntador de aquí que la lista se le llama enlazada, la lista debe de contar con un apuntador al primer elemento, uno o mas nodos y un nodo, el ultimo que contenga el apuntador hacia un valor nulo (NULL) para indicar el final de la lista, a continuación se dará un ejemplo de lista enlazada en partes de modo que se pueda explicar que es lo que hace cada una de esas partes. Una lista enlazada es una colección lineal de estructuras autorreferenciadas llamadas nodos, conectadas por enlace de apuntador –de ahí el termino lista “enlazada”. Se tiene acceso a una lista enlazada via un apuntador al primer nodo de la lista. Se puede tener acceso a los nodos subsecuentes via el apuntador de enlace, en el ultimo nodo de una lista, se define a NULL. En la lista enlazada los datos se almacenan dinámicamente – cada nodo se crea conforme sea necesario. Un nodo puede contener datos de cualquier tipo, incluyendo otras struct. Las pilas y las colas de espera tambien son estructuras lineas de datos, y como veremos, son versiones restringidas de listas enlazadas. Los arboles son estructuras no lineales de datos. ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 65 LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ Las listas de datos pueden ser almacenadas en arreglos, pero las listas enlazadas proporcionan varias ventajas. Una lista enlazada es apropiada cuando no es predecible de inmediato el numero de elementos de datos s representarse en la estructura. Las listas enlazadas son dinamicas, por lo que conforme sea necesario la longitud de una lista puede aumentar o disminuir. Por su parte, el tamaño de un arreglo no puede ser modificado, porque la memoria del arreglo es asignada en tiempo de compilación. Los arreglos pueden llenarse. Las listas enlazadas solo se llenan cuando el sistema no tiene suficiente memoria para satisfacer las solicitudes de asignación dinamica de almacenamiento. Las listas enlazadas pueden mantenerse en orden, insertando cada elemento nuevo en el punto apropiado dentro de la lista. Normalmente, los nodos de las listas no estan almacenados en memoria en forma contigua. Sin embargo, lógicamente, los nodos de una lista enlazada, aparecen como contiguos. En la siguiente figura se ilustra una lista enlazada con varios nodos. LISTA 5 2 8 6 10 El programa que se presenta enseguida, manipula una lista de carácteres. El programa da dos opciones: 1) insertar un carácter en la lista en orden alfabético (función insert) y 2) borra un carácter de la lista (función deleded). Este es un programa grande y complejo. Sigue un análisis detallado respecto al programa. Las dos funciones primarias de las lista enlazadas son insert y deleted. La función isEmpty (esta vacía) se conoce como una función predicada – no altera en forma alguna la lista; mas bien determina si la lista esta vacia (es decir, si el apuntador al primer nodo de la lista es NULL). Si la lista esta vacía, se regresa 1; de lo contrario se regresa 0. La función printList imprime la lista. En la lista los carácteres se insertan en orden alfabético (lo hace el programa). La función insert recibe la dirección de la lista y el carácter a insertarse. Es necesaria la dirección de la lista cuando se va a insertar un valor en el inicio de la lista. Proporcionar la dirección de la lista permite que se pueda modificar la lista( el apuntador al primer nodo de la lista) vía una llamada por referencia. Dado, que la lista de caracteres ó dicha es un apuntador (a su primer elemento), pasar la dirección de la lista crea un apuntador a un apuntador (es decir, una doble indirección). Este es un concepto complejo y requiere de cuidadosa programacion. Los pasos para la inserción de un caracter en la lista son como sigue. 1. Crear un nodo llamado malloc la dirección de la memoria asignada, asignando el carácter a insertarse a newPtr ->data, asignando NULL a newPtr -> nextPtr 2. Inicialice previousPtr a NULL, y currentPtr a *sPtr ( el apuntador al inicio de la ista). Los apuntadores previousPtr y currentPtr se utilizan para almacenar las posiciones del nodo anterior al punto de inserción y del nodo posterior al punto de inserción 3. En tanto currentPtr no sea NULL y el valor a insertar sea mayor que currentPtr->dato, asigne currentPtr a previousPtr y avance currentPtr al siguiente nodo en la lista. Esto posiciona en la lista ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 66 LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ el punto de inserción del valor. 4. Si previousPtr es NULL, se inserta el nodo como primer nodo de la lista. Asigne *sPtr a newPtr>nextPtr (el nuevo enlace de nodo apunta al anterior primer nodo), asigne newPtr a sPtr ( *sPtr apunta al nuevo nodo). Si previousPtr no es NULL, el nuevo nodo se inserta en su lugar. Asigne newPtr a previousPtr->nextPtr (el nodo anterior apunta al nuevo nodo), y asigne currentPtr a newPtr->nextPtr (el enlace de nuevo nodo apunta al nodo actual). En los siguientes diagramas se muestra como es que se inserta un nodo en la lista enlazada. *sPtr previousPtr A currentPtr B D E newPtr C *sPtr previousPtr A B currentPtr D E newPtr C En la siguiente figura se ilustra la insercion de un nodo conteniendo el carácter C en una lista ordenada. La parte a) de la figura muestra la lista y el nuevo nodo antes de la insercion. La parte b) de la figura muestra el resultado de la insercion del nuevo nodo. Loa apuntadores rreasignados estan representados por flechas y lineas punteadas La funcion deleted recibe la direccion del apuntador hacia el principio de la lista y un carácter a borrarse. Los pasos para borrar un carácter de la lista son como siguen: 1. Si el carácter a borrarse coincide con el primer carácter del primer nodo de la lista, asigna *sPtr ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 67 LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ a tempPtr (tempPtr sera utilizado para liberar, usando free, la memoria no necesaria), asigna (*sPtr)->nextPtr a sPtr a (sPtr ahora apunta al segundo node de la lista), free libera la memoria apuntada por tempPtr, y regresa el carácter que fue liberado. 2. De no ser asi, inicializa previousPtr con sPtr e inicializa currentPtr con (*sPtr)->nextPtr . 3. En tanto currentPtr no sea NULL y el valor a borrarse no sea igual currrentPtr->data, asigna currentPtr a previousPtr, y asigna currentPtr->nextPtr a currentPtr. Esto localizara el carácter a borrarse, si esta contenido dentro de la lista. 4. Si currentPtr no es NULL, asigna currentPtr a tempPtr, asigna currentPtr->nextPtr a previousPtr>nextPtr, libera al nodo al cual apunta tempPtr, y regresa el carácter que fue borrado de la lista. Si currentPtr es NULL regresa el carácter NULL (‘\0’), para significar que el carácter a borrarse no fue encontrado dentro de la linea. A continuacion se darán 2 diagramas a bloques que nos muestran como se borra un elemento o nodo de la lista enlazada. *sPtr previousPtr curenttPtr a) A *sPtr B C previousPtr currentPtr D E b) A B C D E tempPtr En la figura anterior se ilustra el borrado de un nodo de una lista enlazada. La parte a) de la figura muestra la lista enlazada, antes de la operación de inserción anterior. La parte b) muestra la reasignación del elemento de enlace de previousPtr y la asignación de currentPtr a tempPTr. El apuntador tempPtr se utiliza para liberar la memoria asignada par almacenar C. La funcion printList recibe un apuntador al inicio de la lista como un argumento y se refiere al apuntador como currentPtr. La funcion primero determina si la lista esta vacia, si es asi, printList imprime”la lista esta vacia” y termina. De lo contrario los datos en la lista. En tanto currentPtr no sea NULL, currentPtr ->data sera impreso por la funcion, y currentPtr ->nextPtr sera signado a currentPtr. ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 68 LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ Note que si en el ultimo nodo el enlace de la lista no es NULL, el algoritmo de impresión es idéntico para listas enlazadas, pilas y colas de espera. /* PROGRAMA DE UNA LISTA*/ #include <stdio.h> #include <conio.h> #include <stdlib.h> */ /* Incluye el fichero stdio.h las funciones scanf y printf */ /* Incluye el fichero conio.h las funciones clrscr*/ /* Incluye el fichero stdlib.h las funciones malloc y free struct listNode { char data; struct listNode *nextPtr; }; /* Definicion estructura autoreferenciada */ /* Miembro de la estructura, dato de tipo caracter */ /* Miembro, tipo puntero al mismo tipo de estructura */ typedef struct listNode LISTNODE; /* Renombrar la struct listNode con LISTNODE */ typedef LISTNODE *LISTNODEPTR; /* Renombrar la LISTONODE con *LISTNODE */ void insert (LISTNODEPTR , char); char deleted (LISTNODEPTR *, char); int isEmpty (LISTNODEPTR); void printList(LISTNODEPTR); void instructions(void); /* Funcion de insertar caracter */ /* Funcion de borrar caracter */ /* Funcion de lista vacia */ /* Funcion de imprimir lista */ /* Funcion de menu de instrucciones */ /* Programa principal es aquí desde donde se mandan a ejecución a las funciones que permitiran insertar y borrar elementos de la lista enlazada, este programa contiene un puntero startPtr el cual se empleara para apuntar al primer dato o nodo de la lista y definir si existen elementos o no existrir elementos el valor de este puntero permanecera en un NULL, tambien contiene la ejecución de la función de instrucciones la cual le indicara al usuario que hacer, después hay una estructura while que hace repetitivo todo el programa y dentro de esta hay una estructura de selección múltiple la cual permitira ejecutar por decisión del usuario que desea hacer es decir insertar un elemento, borrar el elemento o salir del programa*/ main() { LISTNODEPTR startPtr = NULL; int opcion; char item; /* Declaracion de la variable (de una estructura), starPtr igual a NULL */ /* Declaracion de la variable opcion de tipo entero */ /* Declaracion de la variable item de tipo caracter */ clrscr(); instructions(); printf("?"); scanf("%d",&opcion); /* Limpiar pantalla */ /* Llamada a la funcion de instrucciones */ /* Imprimir el sigo de interrogacion ? */ /* Almacenar la opcion elegida */ while (opcion != 3 ) /* Mientras la opcion sea diferente a 3 realiza lo siguiente */ { switch(opcion) { /* switch evalua el numero de opcion */ ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 69 LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ case 1: printf("Introduzca un caracter: "); /* Si es 1 ejecuta lo siguiente*/ scanf("\n%c",&item); /* Almacena en item el caracter a insertar */ insert(&startPtr,item); /* Llamada a la funcion de insertar, enviando los argumento de la direccion de starPtr y item donde esta el caracter */ printList(startPtr); /* Llamada a la funcion de imprimir lista */ break; /* Finalizar cas 1 y switch */ case 2: if( ! isEmpty(startPtr)) /* Si es 2 evalua si lo que regresa la llamada a la funcion lista vacia es cero y con el inveror se vuelve 1 (verdarero) y ejecuta los siguiente*/ { printf("Introduzca el caracter a borrar: "); scanf("\n %c",&item); /* almacena en item el caracter a borrar */ if(deleted(&startPtr,item)) /* Ir a la funcion de borrar carácter, para ver si existe el caracter y borrarlo, regresa un cero si no lo encuentra y se sale de if*/ { printf("\n%c borrado ",item); /* Imprime el caracter encontrado */ printList(startPtr); /* Llamada aa la funcion de imprimir */ } else /* Si no cumplio if ejecuta lo siguiente */ printf("%c no encontrado.\n\n",item); } else /* Si la evaluación de 1er. if fue 0 ejecuta lo sig */ printf("La lista esta vacia \n\n"); break; /* Salir de case 2 y de switch */ default: printf("Opcion no valida \n\n"); /*Si la opcion fue diferente a 1 y 2 ejecuta*/ instructions(); /* Llamad a la funcion de instrucciones */ break; } printf("?"); scanf("%d",&opcion); } printf("Final de la ejecución. \n"); return 0 ; } /* Salir de default y de switch */ /* fin de switch */ /* Almacenar la nueva opcion seleccionada */ /* Fin de while (opcion !=3) */ /* Si es la opcion 3 imprimir lo siguiente */ /* El programa se ejecuto con éxito */ /*Fin de la funcion principal */ /* función instrucciones (subprograma) que despliega en pantalla las opciones de lo que puede hacer el programa es decir un menu de opciones*/ void instructions(void) /* Funcion instrucciones, el !er Void indica que no regresa un valor y el segundo que no recibe argumentos */ { printf("Introduzca su opcion \n" " 1 Para insertar un elemento dentro de la lista \n" ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 70 LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ " 2 Para borrar un elemento de la lista.\n" " 3 Para finalizar. \n"); } /*función insertar (subprograma) la cual permitr insertar nuevos elementos (nodos) ademas de ir ordenados alfabéticamente los nodos*/ /* Funcion de insertar un nodo con carácter y enlace */ void insert(LISTNODEPTR *sPtr, char value) /* El 1er. void porque no regresa ningun valor, recibe los argumentos (dirección de starPtr la asignara a *sPtry y el item (carácter) a value */ { LISTNODEPTR previousPtr, currentPtr, newPtr; /*Declaracion de las variables de estructura */ newPtr = (LISTNODEPTR) malloc(sizeof(LISTNODEPTR)); /* Crear un nuevo nodo llamado malloc asignando a newPtr la direccion de la memoria asignada */ if (newPtr != NULL) /* Si el nuevo nodo es diferente a NULL ejecuta lo siguiente */ { newPtr->data = value; /* Asignale el carácter a insertarse al miembro de la estructura newptr->data */ newPtr->nextPtr = NULL; /* Asignale NULL al apuntador al inicio de la ista (enlace) del miembro de la estructura newPtr->nextPtr */ previousPtr=NULL; /* Inialice y asignale NULL al apuntador (enlace) previousPtr y se utilza para almacenar las posiciones del nodo anterior al punto de insercion */ currentPtr=*sPtr; /* Inialice y asignale la direccionde *sPtr al apuntador currentPtr (enlace), y se utilza para almacenar las posiciones del nodo posterior al punto de insercion */ while(currentPtr !=NULL && value > currentPtr->data) /*Mientras el apuntador current no sea NULL y el carácter a insertarse sea mayor a currentPtr->data, ejecuta y ORDENA */ { previousPtr = currentPtr; /* Asigna currentPtr al apuntador previousPtr */ currentPtr = currentPtr->nextPtr; /* Avance currentPtr al siguiente nodo de la lista, esto lo posiciona en el punto de inserción */ } if(previousPtr == NULL) /* Si previousPtr es NULL, se inserta el nuevo nodo como el 1er. nodo de la lista */ { newPtr->nextPtr = *sPtr; /* Lo que tengas en *sPtr, asígnaselo a newPrt->nextPtr, *sPtr apunta NULL ahora newPtr-> apunta a NULL, el nuevo enlace de nodo apunta al anterior primer nodo*/ *sPtr = newPtr; /* Asignale newPtr a *sPtr, *sPtr apunta al nuevo nodo */ } else /* Si previous no es NULL, el nuevo ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 71 LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ nodo se inserta en su lugar de la lista */ { previousPtr->nextPtr = newPtr; /* Asignale newPtr a previousPtr->nextPtr, al newPtr->nextPtr =currentPtr; nodo anterior apunta al nuevo nodo */ /* Asignale currentPtr a NewPtr *nextPtr el enlace del nuevo nodo apunta al nodo actual */ } } else printf("%c no insertada memoria no disponible.\n\n", value); } /* Fin de if(newPtr !=NULL) */ /* memoria insuficiente */ /* Fin de la funcion de insertar */ /*Función para borrar un elemento (nodo) de la lista */ /* Funcion de borrar un nodo con carácter y enlace */ char deleted (LISTNODEPTR *sPtr, char value) /* El char es porque regresa un caracter, recibe los argumentos (dirección del apuntador al principio de la lista) starPtr la asignara a *sPtry y el item (carácter a borrarse) a value */ { LISTNODEPTR previousPtr, currentPtr, tempPtr; /*Declaracion de las variables de estructura */ if (value == (*sPtr)->data) /* Si el carácter a borrarse coincide con el primer carácter del primer nodo de la lista (*sPtr)->data ejecuta lo siguiente */ { tempPtr = *sPtr; *sPtr = (*sPtr)->nextPtr; free(tempPtr); return value; } else { previousPtr = *sPtr; currentPtr = (*sPtr)->nextPtr; /* Asigne *sPtr a tempPtr (trmpPtr sera utilizado para liberar, usando free, la memoria no necesaria) */ /* Asigna (*sPtr)->nextPtr a *sPtr (*sPtr ahora apunta al segundo nodo de la lista) */ /* free libera la memoria apuntada por tempPtr */ /* Regresa el caracter que fue borrado */ /* sie carcater a borrarse no coindice con el 1er, caracter */ /* Inicializa previousPtr con *sPtr */ /* Inicializa current->nextPtr con (*sPtr)->nextPtr */ while(currentPtr !=NULL && currentPtr->data != value) /* En tanto currentPtr no sea NULL y el valor a borrarse nos sea igual a currentPtr->data */ { previousPtr = currentPtr; /* Asigna currentPtr a previousPtr */ currentPtr = currentPtr->nextPtr; /* Asigna currentPtr->nextPtr a currrentPtr, esto localizara el caracter a borrarse, si esta contenido dentro de la lista */ } if(currentPtr != NULL) { /* Si currentePtr no sea NULL */ ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 72 LISTAS ENLAZADAS UNIDAD IV ____________________________________________________________________________________________________ tempPtr = currentPtr; /* Asigna currentPtr a tempPtr */ previousPtr->nextPtr = currentPtr->nextPtr; /*Asigna currentPtr->nextPtr a previousPtr->nextPtr */ free(tempPtr); /* Libera el nodo al cual apunta tempPtr */ return value; /* Regresa el caracter que fue borrado de la lista, } } return '\0'; /*Si currentPtr es NULL, regresa el caracter NULL (‘\0’), para significar que el carácter a borrarse no fue encontrado dentro de la lista */ } /* función que determina si la lista contiene o no elementos es decir indica con un valor que devuelve un 1 si la lista esta vacia */ int isEmpty(LISTNODEPTR sPtr) /* Funcion isEmpty (lista vacia) regresa un entero y recibe de argumento starPtr y lo asigna a sPtr */ { return sPtr == NULL; /* Regresa un cero si la pila no esta vacia*/ } /* Funcion de imprimir */ void printList(LISTNODEPTR currentPtr) /* La funcion imprimir lista recibe un apuntador al inicio de la lista como un argumento y se refiere al apuntador como currentPtr */ { if (currentPtr == NULL) printf(" La lista esta vacia \n\n"); else { printf(" La lista es: \n") ; /* La funcion primero determina si la lista esta vacia, si es asi es printList imprime “ La lista esta vacia” */ /* De lo contrario imprime los datos de la lista*/ /* Imprime “La lista es: “ */ while(currentPtr != NULL) /* Mientras currentPtr sea diferente a NULL */ { printf("%c -->",currentPtr->data); /*current->data sera impreso por la funcion */ currentPtr = currentPtr->nextPtr; /* currentPtr->nextPtr sera asignado a currentPtr */ } printf("NULL\n\n"); /* Si currentPtr es NULL imprime NULL*/ } } ____________________________________________________________________________________________________ ESTRUCTURA DE DATOS ING. OSORNIO 73