Listas Enlazadas 2

  • June 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Listas Enlazadas 2 as PDF for free.

More details

  • Words: 2,375
  • Pages: 10
INSTITUTO POLITECNICO NACIONAL ESCUELA SUPERIOR DE INGENIERIA MECANICAY ELECTRICA UNIDAD ZACATENCO

ASIGNATURA: ESTRUCTURAS Y BASES DE DATOS

PROFESORA: CORTES HERNANDEZ LILIA

ALUMNOS: JUAREZ PEREZ MIGUEL RAMIREZ TOFOLLA JOSUE

GRUPO: 3CV4

“LISTAS ENLAZADAS”

Listas enlazadas 1. Introducción 2. Memoria dinámica 2.1 Malloc y free 3. ¿En qué consisten las listas enlazadas? 3.1. Ejercicios 4. Paso de punteros a funciones 4.1 Paso por valor 4.2. Paso por referencia 5. Liberar la memoria de una lista enlazada 6. Solución a los ejercicios 6.1. Ejercicio de números primos 7. Eliminar un elemento de una lista enlazada 7.1. Suprimir por cabeza 7.2. Suprimir por el final 7.3. Eliminación de un elemento cualquiera de una lista enlazada 8. Ventajas y desventajas de las listas enlazadas respecto a los arrays 9. Acabando Introducción En esta segunda entrega vamos a ver lo que son las listas enlazadas y para ello trataremos la memoria dinámica. Es un tema muy utilizado en la práctica. Antes de empezar hago una pequeña aclaración que seguramente es innecesaria: Arrays = tabla = vector (si es de una dimensión) Tupla = estructura = record Lo dejo claro desde el principio por si os encontráis con alguna de estos términos, aunque intentaré no mezclarlos para no liar al personal. Memoria dinámica Cuando queremos utilizar una variable la declaramos al principio del código y como ya sabemos tenemos varios tipos de variables. También sabemos que podemos agrupar estas variables en arrays. El inconveniente es que necesitamos saber el tamaño del array cuando lo declaramos. Esto es una limitación muy grande, por ejemplo, si programamos una agenda deberemos saber a cuanta gente tendremos como máximo, o si nos diera por hacer un programa que calculase números primos tendríamos que poner un máximo (lamentablemente hay infinitos números primos J ); si reservábamos mucha memoria para curarnos en salud y nunca agotar una tabla, estamos desperdiciando mucha memoria; si por el contrario reservamos poca memoria podemos agotar la memoria que tenemos reservada.

Para optimizar la memoria, lo ideal sería reservar memoria en el momento que la necesitásemos y no como hasta la fecha, que lo hacíamos al principio del programa. Malloc y free Estas dos funciones nos servirán para asignar dinámicamente memoria. Estas 2 funciones se encuentran en la librería Reserva memoria y devuelve la posición de memoria del comienzo de ésta, por lo que deberemos guardar el resultado en un puntero (más información de punteros en el número 13 malloc(); de la revista de hackxcrack) . Si no se ha podido reservar memoria, malloc devolverá el valor NULL, así que el puntero apuntará a NULL. Es importante asegurarnos de que se ha podido reservar la memoria, haremos algo así: PUNTERO

= (T IPO _VARIABLE *)

IF ( PUNTERO

= ( INT *)

MALLOC

MALLOC

(BYTES _RESERVAR );

(SIZEOF (CHAR )))

{PUTS (“C ORRECTO ”);}

free() Cuando ya no necesitemos el espacio que habíamos reservado, liberaremos la memoria, haremos: FREE ( PUNTERO );

Donde el puntero que pasamos como parámetro apunta al principio de la memoria que habíamos reservado. Veremos un ejemplo: #include <stdio.h> #include void main() { unsigned long int bytes; char *texto; printf("Cuantos bytes quieres reservar: "); scanf("%li",&bytes); texto = (char *) malloc(bytes); /* Comprobamos si ha tenido éxito la operación */ if (texto) { printf("Memoria reservada: %li bytes = %li kbytes = %li Mbytes\n", bytes, bytes/1024,bytes/(1048576)); printf("El bloque comienza en la dirección: %p\n", texto); /* Ahora liberamos la memoria */ free( texto ); } else printf("No se ha podido reservar memoria\n"); }

Si queremos reservar un número muy grande de bytes el programa nos dará error. ¿En qué consisten las listas enlazadas? Aquí entran en juego varios temas, se trata de combinar las estructuras con los punteros para acabar por fin con la limitación de los arrays, ya no hará falta indicar el tamaño del array al principio. Después comentaremos los pros y los contras de las listas enlazas respecto a los arrays. Las listas enlazadas pueden ser simples, dobles o circulares. De momento veremos las simples. Estas listas tendrán una estructura como la de la figura:

Para crear listas necesitaremos estructuras asignación dinámica de memoria. Vamos a ver cómo utilizar una lista en el ejemplo de la agenda: STRUCT

_AGENDA {

CHAR NOMBRE [20]; CHAR TELEFONO [12]; STRUCT

_AGENDA * SIGUIENTE ;

};

De esta forma hemos creado una estructura formada por un nombre y un teléfono como datos principales y visibles para el usuario, lo que sucede esta vez es que no sé a cuanta gente voy a meter en mi agenda, por lo que utilizo un puntero que apunta a otro elemento con esta estructura . Cada vez que queramos meter a un nuevo elemento en la agenda deberemos reservar memoria para este elemento con el malloc. NUEVO

= (_ AGENDA *)

MALLOC

( SIZEOF (_AGENDA ));

Nos quedará algo así: Llenaremos los campos de ese nuevo elemento y lo meteremos en la lista. Para meter un elemento en la lista tendremos que hacer que el puntero del elemento anterior apunte a este nuevo elemento. Si nos paramos a pensar ya vemos que necesitamos un puntero que nos vaya indicando donde está el elemento actual, pero es que también necesitaremos un puntero que nos indique donde comienza la lista para poder recorrerla desde el principio ya que en cada elemento de agenda tenemos un puntero hacia el siguiente elemento, pero no tenemos ninguno hacia el elemento anterior (si lo tuviéramos estaríamos hablando de una lista doblemente enlazada). Ejemplo : Veremos un ejemplo que nos servirá de gran ayuda:

#include #include #include #include

<stdio.h> <stdlib.h> //exit()

typedef struct _agenda { char nombre[20]; char telefono[12]; _agenda *siguiente; }; void mostrar_menu(); void anadir_elemento(); void mostrar_lista(); _agenda *primero, *ultimo; /*De momento lo haremos con variables globales, más adelante veremos como pasar punteros a funciones*/ void main() { char opcion; primero = (_agenda *) NULL; ultimo = (_agenda *) NULL; do { mostrar_menu(); opcion = getch(); switch ( opcion ) { case '1': anadir_elemento(); break; case '2': printf("Ir pensando como hacer esto :D\n"); break; case '3': mostrar_lista(); break; case '4': exit( 1 ); default: printf( "Opción no válida\n" ); break; } } while (opcion!='4'); } void mostrar_menu() { printf("\n\nMenú:\n=====\n\n"); printf("1.- Añadir elementos\n"); printf("2.- Borrar elementos\n"); printf("3.- Mostrar lista\n"); printf("4.- Salir\n\n"); printf("Escoge una opción: ");fflush(stdin); } /* Con esta función añadimos un elemento al final de la lista */ void anadir_elemento() { _agenda *nuevo; /* reservamos memoria para el nuevo elemento */ nuevo = (_agenda *) malloc (sizeof(_agenda)); if (nuevo==NULL) printf( "No hay memoria disponible!\n"); else { printf("\nNuevo elemento:\n"); printf("Nombre: "); fflush(stdin);

gets(nuevo->nombre); printf("Teléfono: "); fflush(stdin); gets(nuevo->telefono); /* el campo siguiente va a ser NULL por ser el último elemento la lista */ nuevo->siguiente = NULL; /* ahora metemos el nuevo elemento en la lista. lo situamos al final de la lista */ /* comprobamos si l0a lista está vacía. si primero==NULL es que no hay ningún elemento en la lista. también vale ultimo==NULL */ if (primero==NULL) { printf( "Primer elemento\n"); primero = nuevo; ultimo = nuevo; } else { /* el que hasta ahora era el último tiene que apuntar al nuevo */ ultimo->siguiente = nuevo; /* hacemos que el nuevo sea ahora el último */ ultimo = nuevo; } } } void mostrar_lista() { _agenda *auxiliar; /* lo usamos para recorrer la lista */ int i; i=0; auxiliar = primero; printf("\nMostrando la lista completa:\n"); while (auxiliar!=NULL) { printf( "Nombre: %s, Teléfono: %s\n", auxiliar->nombre,auxiliar->telefono); auxiliar = auxiliar->siguiente; i++; } if (i==0) printf( "\nLa lista está vacía!!\n" ); }

En el ejemplo vemos una función por terminar, la de eliminar un elemento. ¿Os atrevéis? En este ejemplo también vemos algo nuevo, “->”, esta flecha la utilizamos en punteros cuando nos referimos a algún campo apuntado por el puntero. Por ejemplo, ultimo->siguiente = nuevo; En esta línea decimos que el puntero que siguiente (apuntado por ultimo) coge el valor de nuevo. Recordemos que último está apuntando a toda una estructura, con la flecha nos podemos referir a cada uno de los campos. Ejercicios:

1-Crear la función eliminar un elemento de la lista enlazada. 2-Crear un programa para calcular números primos y almacenarlos en una lista enlazada y de esta forma poder seguir calculando números hasta que

queramos. Podéis utilizar la función kbhit() (esta función no es ANSI C, C estándar) que nos devuelve cierto cuando pulsamos una tecla. Así podemos calcular números primos hasta que pulsemos una tecla. Si no queréis utilizar kbhit() podéis calcular números primos hasta un cierto número.

Paso de punteros a funciones: En este tema hemos utilizado variables globales, algo que como ya hemos comentado durante el curso debemos evitar. En este apartado veremos como pasar punteros a funciones por valor y por referencia para que no necesitemos recurrir a variables globales. Paso por valor: Observaremos el ejemplo #INCLUDE <STDIO.H> VOID CAMBIA ( INT * P ); VOID MAIN ()

{

= 10; *P; = &I;

INT I INT P

("\N1-MAIN\ NDIRECCIÓN : %P\ NVALOR : %I", P,* P); (P); PRINTF ("\ N \ N 3-MAIN\ N D ESPUÉS DE CAMBIAR \ N D IRECCIÓN : % P \NV ALOR: % I", P, *P); } PRINTF CAMBIA

(INT * P) { = 20; P = &J; PRINTF ("\ N \ N 2-FUNCION\ N D IRECCIÓN : % P \ N V ALOR : % I ", } VOID CAMBIA INT J

P ,* P );

En este ejemplo vemos que el paso por valor lo hacemos igual que hasta ahora, hay que tener en cuenta que en la definición de la función ponemos int *p porque este es el tipo de variable. Paso por referencia: Veremos el ejemplo anterior modificado para que ahora sea paso por referencia: #INCLUDE <STDIO.H> VOID CAMBIA ( INT ** P ); VOID MAIN ()

{

INT I

= 10;

*P; = &I;

INT P

("\N1-MAIN\ NDIRECCIÓN : %P\ NVALOR : %I", (&P); PRINTF ("\ N \ N 3-MAIN\ N D ESPUÉS DE CAMBIAR \ N DIRECCIÓN : %P\ NVALOR : %I",P, * P); PRINTF

P ,* P );

CAMBIA

} VOID CAMBIA

(INT **P)

{

= 20; *P = &J; PRINTF ("\ N \ N 2-FUNCION\ N D IRECCIÓN : % P \ N V ALOR : % I ", * P ,** P ); } INT J

Al igual que hacemos con el resto de variables, en la llamada añadimos &: cambia (&p). En los parámetros añadimos un * en la variable y en el cuerpo de la función añadimos un * cuando queremos cambiar su valor y al imprimirlo. Liberar la memoria de una lista enlazada: Ya hemos utilizado la función free para liberar la memoria que ocupa un puntero cuando ya no lo necesitamos, vamos a ver un ejemplo de cómo liberar la memoria de toda una lista enlazada: VOID LIBERAR ()

{

*AUX , *ANT ; = PRIMERO; (AUX = PRIMERO -> SIGUIENTE ; { FREE ( ANT ); ANT = AUX ; }

PRIMOS ANT FOR

AUX

!= NULL;

AUX

=

AUX -> SIGUIENTE )

}

Vemos en el ejemplo que no basta sólo con una variable auxiliar, hemos utilizado aux y ant ya que si liberamos aux ya no podremos pasar al siguiente elemento. Es muy recomendable liberar la memoria cuando ya no la necesitemos. Eliminar un elemento de una lista enlazada Este es un punto en el que pido concentración para no perdernos, si se imagina la situación es algo que resulta muy sencillo. Veremos las diferentes situaciones que nos podemos encontrar a la hora de eliminar un elemento de una lista enlazada, además si se entiende podréis utilizar las mismas explicaciones para insertar un elemento en la lista enlazada en diferentes zonas.

Nos podemos encontrar con 3 casos: que el elemento sea el primero de la lista, que sea el último o que esté entre medio. Usaremos el puntero “ primero ” que apunta al primer elemento de la lista, el puntero “ aux ” que será auxiliar y el puntero “ ant ” que será el anterior. No necesitaremos los 3 siempre. Analizamos los 3 casos paso a paso pero el código os lo dejo a vosotros. Suprimir por cabeza 1. Necesitaremos 2 punteros. Se trata de eliminar el primer elemento pero

si lo eliminamos directamente la lista quedará perdida en la memoria (no sabremos donde comienza). Así que haremos que guardaremos la posición del primer elemento en aux. 2. Después haremos que primero apunte a primero -> siguiente. O sea, que el nuevo primer elemento será el segundo (claro, si eliminamos al primero…). 3. Por último ya podemos liberar aux.

Suprimir por el final 1. Hay que encontrar el último nodo y eliminarlo pero también tendremos

que buscar el penúltimo para hacer que apunte a NULL. Tendremos que recorrer toda la lista con aux y utilizar ant para que apunte al elemento anterior a aux.

2. Eliminamos el último nodo (aux) y hacemos que ant apunte a NULL.

Eliminación de un elemento cualquiera de una lista enlazada Este caso es parecido al anterior, sólo se diferencia en que en vez de hacer que el elemento anterior al que eliminamos apunte a NULL deberemos hacer que apunte al elemento apuntado por el elemento que eliminamos. Ahora lo vemos con el dibujo (perdonad mi poca gracia dibujando). Supongamos que queremos borrar el segundo elemento. 1. El primer paso es buscar el elemento a borrar que será apuntado por

aux y el elemento anterior será apuntado por ant. 2. ant -> siguiente cogerá el valor de aux -> siguiente. 3. Liberamos aux.

Ventajas y desventajas de las listas enlazadas respecto a los arrays Bueno, después de haber visto las listas enlazadas no debemos pensar que son siempre mejores que los arrays, depende para que los vayamos a utilizar. Ventajas de las listas enlazadas respecto a los arrays 1. Tamaño dinámico. Lo que implica optimización de la memoria. Desventajas de las listas enlazadas respecto a los arrays 1. El acceso es secuencial (para llegar a una posición deberemos pasar por todas las anteriores). Esto significa lentitud. Imaginad por un momento el tinglado que tendríamos que montar para ordenar una lista enlazada. Si buscamos un elemento de una lista ordenada también tardaremos más, no vale la búsqueda dicotómica que vimos en la Ampliación 1 de C (métodos de clasificación en memoria dinámica). 2. El código se complica.

Related Documents