Sei sulla pagina 1di 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 <alloc.h>

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 *) MALLOC (BYTES _RESERVAR );

IF ( PUNTERO = ( INT *) MALLOC (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 <alloc.h>

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 <stdio.h>
#include <conio.h>
#include <stdlib.h> //exit()
#include <alloc.h>

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 ()
{
INT I = 10;
INT *P;
P = &I;

PRINTF ("\N1-MAIN\ NDIRECCIÓN : %P\ NVALOR : %I", P,* P);


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

VOID CAMBIA (INT * P) {


INT J = 20;
P = &J;
PRINTF ("\ N \ N 2-FUNCION\ N D IRECCIÓN : % P \ N V ALOR : % I ", 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;
INT *P;
P = &I;

PRINTF ("\N1-MAIN\ NDIRECCIÓN : %P\ NVALOR : %I", P ,* P );


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

VOID CAMBIA (INT **P)


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

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 ()
{
PRIMOS *AUX , *ANT ;
ANT = PRIMERO;
FOR (AUX = PRIMERO -> SIGUIENTE ; AUX != NULL; AUX = AUX -> SIGUIENTE )
{
FREE ( ANT );
ANT = AUX ;
}
}

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.

Potrebbero piacerti anche