Вы находитесь на странице: 1из 24

ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS

Universidad de Cuenca
Facultad de Ingeniería
Ma. Fernanda Granda
fernanda.granda@ucuenca.edu.ec

LISTAS LIGADAS
NOVIEMBRE2016

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 1


Contenido
Introducción a la memoria dinámica
Puntero de memoria, declaración, operadores
Asignar memoria dinámica con malloc
Verificar la asignación de memoria
Liberar la memoria dinámica
Reservar memoria e inicializar con calloc
Lista Ligada o Enlazada
Definición de un dato tipo Struct
Declaración e inicialización de una lista enlazada
Operador ->
Operaciones con listas, agregar al inicio, agregar al final, anular la lista
Ejercicios de Aplicación

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 2


Introducción
Hasta ahora hemos visto que cada vez que queremos usar una variable debemos reservarle un lugar de la memoria al
comenzar el programa.
Char → 1 byte
Int → 4 bytes
Float → 4 bytes
Double → 8 bytes
Int arr[20] → ? Bytes
Debemos indicar cuánta memoria vamos a usar. Pero hay ocasiones en que esto no es bueno, hay veces en las que no
sabemos cuánta memoria vamos a necesitar. Por ejemplo si hacemos un editor de texto no podemos saber de antemano
cuál va a ser la longitud del texto.
Por eso a veces es necesario poder reservar memoria según se va necesitando. Además de esta forma nuestros
programas aprovecharán mejor la memoria del ordenador en el que se ejecuten, usando sólo lo necesario.
Necesitamos, en tiempo de ejecución, indicarle al Sistema Operativo que vamos a necesitar más memoria y cuánta ->
necesitamos usar memoria dinámica.

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 3


Puntero de memoria
Un puntero es una variable que contiene la dirección de memoria de otra variable que contiene
información. Ésto quiere decir, que el puntero apunta al espacio físico donde está el dato o la
variable. Un puntero puede apuntar a un objeto de cualquier tipo.
Los punteros se pueden utilizar para referencia y manipular estructuras de datos, para referenciar
bloques de memoria asignados dinámicamente y para proveer el paso de argumentos por referencias
en las llamadas a funciones.
Muchas de las funciones estándares de C, trabajan con punteros, como es el caso del scanf o strcpy.
Estas funciones reciben o devuelve un valor que es un puntero. Por ejemplo: A scanf se le pasa la
dirección de memoria del dato a leer:
char a;
scanf(“%c”, &a);
En este caso se le pasa la dirección de memoria de la variable a, la cual tiene reservado un espacio en
la memoria por el compilador. Podríamos hacer lo mismo con este código:
char *a= (char) malloc (sizeof(char));
scanf(“%c”, a);

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 4


Declarando punteros
Igual que una variable, pero anteponiendo *
tipo *nombrePuntero;
Ejemplo:
#include <stdio.h>
int main()
{ int a=0; // declaración de una variable entera
int *puntero; // declaración de variable puntero de tipo entero
puntero=&a; // asignación de la dirección de memoria de a
printf(“el valor de a es: %d. \nEl valor del *puntero es: %d. \n “, a, *puntero);
printf(“la dirección de memoria de *puntero es: %p “, puntero);
printf(“la dirección de memoria de a es: %p “, &a);
return 0;
}
PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 5
Operadores
Operador de Dirección (&): Este nos permite acceder a la dirección de memoria de una variable.
Operador de Indirección (*): Además de que nos permite declarar un tipo de dato puntero,
también nos permite ver el VALOR que está en la dirección asignada.
int a; Es Igual int *puntero=&a;
printf("%d",a); Es Igual printf("%d",*puntero);
En cada ejecución la dirección de memoria cambia, esto es porque es el sistema operativo quien
está encargado de administrar la memoria y es éste quien dice qué espacios podrá tomar el
programa.
Se puede inicializar con Null
int *puntero=NULL;

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 6


Ejemplos de operaciones aritméticas
int x[100],b,*pa,*pb; //con estas variables definidas
x[50]=10; //Le asignamos el valor de 10, al array #50
pa=&x[50]; //Le asignamos al puntero pa, la direccion de memoria que tiene x[50]
//Ahora mostramos algunas posibles operaciones:
b = *pa+1; //Esto es como decir el valor que tiene el array de x[50] sumarle 1.
//Esto es igual a: b=x[50]+1; => Su valor seria igual a 11.
b = *(pa+1); //Esto primero pasa a la siguiente dirección de memoria y luego lo referencia
//El resultado es: b = x[51];
pb = &x[10]; //al puntero pb se le asigna la direccion de x[10]
*pb = 0; //Al valor que tiene el puntero se le asigna 0
//Esto es igual que decir: x[10] = 0
*pb += 2; //El valor del puntero se incrementa en dos unidades, es decir x[10] = 2
(*pb)--; //El valor del puntero se decrementa en una unidad.
x[0] = *pb--; //A x[0] se le pasa el valor de x[10] y el puntero pb, pasa a apuntar a x[9]
//recuerda, que -- es post-incremento, primero asignara y luego restara.

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 7


Asignar memoria con malloc
Si queremos crear un arreglo luego de pedir al usuario su tamaño debemos reservar los espacios con
malloc.
void* malloc(cantBytes)
Se le piden cantBytes al SO. Devuelve un puntero a donde comienza dicha memoria o NULL sino.
Nota: Debo castear void al tipo de datos al que va a apuntar el puntero. Ejemplo:
int cant;
scanf("%d",&cant);
int* arr;
arr = (int*) malloc(cant*sizeof(int));

for(int i=0; i<cant; i++{


scanf("%d",&arr[i]);
}

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 8


Verificar la asignación de memoria
Es conveniente chequear si se asignó dicha memoria o informar de lo contrario.
int cant;
scanf("%d",&cant);
int* arr;
arr = (int*) malloc(cant*sizeof(int));
if(arr==NULL){ printf(“No hay memoria disponible!”); exit(EXIT_FAILURE); }
for(int i=0; i<cant; i++{
scanf("%d",&arr[i]);
}

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 9


Liberar la memoria
Cuando no se usa más la memoria obtenida dinámicamente, hay que liberarla la memoria
asignada al puntero arr:
free(arr);

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 10


Reservar memoria e inicializar
malloc no inicializa la memoria. Para inicializarla usamos calloc.
calloc reserva para cantElements de tamaño byteElements, es decir
cantElements*bytesElements bytes de memoria. Sino, devuelve NULL.
void* calloc(cantElements, bytesElements);
calloc pone 0 en cada elemento del arreglo (vector).
calloc es mas costosa temporalmente ya que debe recorrer todo el arreglo y poner 0’s.

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 11


Listas Enlazadas
¿En todo programa conocemos TODA la información que vamos a necesitar de antemano?
¿Hay ejemplos en donde esto no ocurra?
Por ejemplo, un programa al estilo de una agenda. No sabemos a priori cuántos nombres vamos
a meter en la agenda, así que si usamos un array para este programa podemos quedarnos cortos
o pasarnos. Si por ejemplo creamos una agenda con un array de mil elementos (que pueda
contener mil números) y usamos sólo 100 estamos desperdiciando una cantidad de memoria
importante. Si por el contrario decidimos crear una agenda con sólo 100 elementos para ahorrar
memoria y necesitamos 200 nos vamos a quedar cortos. La mejor solución para este tipo de
programas son las listas enlazadas.

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 12


Listas Enlazadas
 Pero que pasa si queremos un vector que podamos ir añadiendo un elemento a la vez, en
medida que lo vayamos necesitando. Las listas no tienen problemas para crecer a izquierda,
centro o derecha, dependiendo de lo que se pretenda conseguir. La única pega es la memoria
disponible. También, las listas engloban a las pilas y colas. En consecuencia, saber manejar bien
las listas, implica conocer más cosas
 Tenemos que ir agregando elementos que contengan un puntero al próximo elemento:

 O sea que, cada elemento está formado por:


 Un elemento del tipo deseado (e.g. int)
 Un puntero al próximo, o null si es el último.

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 13


Definición de un dato tipo Struct
Un registro es una agrupación de datos de distinto tipo, accesibles a través de un identificador. Ejem:
struc Persona{
char cedula[11];
char nombre[50];
int edad;
};
Para definer una variable e inicializar los valores del registro se puede usar:
1) struct Persona persona1={“0123456789”, “Nombre Ficticio”, 26};
2) struct Persona persona2={.cedula=“0123456789”, .nombre=“Nombre Ficticio”, .edad=26};
3) struct Persona persona3={.nombre=“Nombre Ficticio”, .edad=26,.cedula=“0123456789”};
4) struct Persona alguien;
alguien.cedula=“0123456789”;
alguien.nombre=“Nombre Ficticio”;
alguien.edad=26;

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 14


Declaración e inicialización de una lista
En nuestra lista cada elemento, que llamaremos “nodo” estará definido por un struct o registro así:
struct Nodo { /*lista simple enlazada*/
int info; // el valor numérico del nodo
struct Nodo *sig; //puntero al siguiente nodo
};
Hay dos elementos en el registro:
 Un elemento llamado info de tipo entero
 Un puntero al próximo nodo
¡Observemos que hay un toque de recursividad en la definición!
 Definimos la lista inicialmente vacía así:
int main (void)
{ struct Nodo * lista=NULL;
}

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 15


Operador ->
El operador -> reemplaza el uso del operador * junto con el operador “. “
lista no es una variable de tipo struct, sino una variable de tipo puntero a struct.
Para acceder al campo info del struct nodo, debemos hacer:
- *(lista).info=8;
O bien:
- lista->info=8;
El operador ->se llama selector indirecto, mientras que el operador “.” se llama selector directo

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 16


Operaciones con Listas
Calcular la longitud de una lista: el número de elementos que contiene. El algoritmo no puede ser más simple,
inicializamos un contador a cero, y vamos recorriendo la lista, incrementando dicho contador, hasta encontrar el
NULL final. En concreto:
/* Devuelve la longitud de una lista */
int longitud (struct Nodo *l) {
struct Nodo *p;
int n= 0;
p = l;
while (p != NULL) {
++n;
p = p->sig;
}
return n;
}

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 17


Añadir un nodo al inicio de la lista

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 18


Agregar un nodo a la lista vacía
struct Nodo * creanodo(void) {
return (struct Nodo *) malloc(sizeof(struct Nodo));
}
/* inserta un nodo al comienzo de la lista (e.g. para pilas) */
struct Nodo * insertacomienzo(struct Nodo *lista, int value){
struct Nodo *aux;
aux=creanodo();
aux->info=value;
aux->sig=lista;
lista=aux;
return lista;
}

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 19


Agregar un nodo al final de la lista
/* Inserta dato al final de la lista (para colas) */
struct Nodo * insertafinal(struct Nodo *lista, int value) {
struct Nodo *p,*aux;
aux = creanodo(); /* crea un nuevo nodo */
aux->info = value; /* copiar los datos */
aux->sig = NULL;
if (lista== NULL)
return aux;
/* la lista argumento no es vacía. Situarse en el último */
p = lista;
while (p->sig != NULL)
p = p->sig;
p->sig = aux;
return lista;
}

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 20


Anular una lista
Si queremos anular el contenido de una lista necesitamos liberar la memoria asignada. OJO no basta con poner
lista=NULL.

/* anula una lista liberando la memoria */


struct nodo * anulalista(struct nodo *l) {
struct nodo *p,*q;
p = l;
while (p != NULL) {
q = p->sig; /* para no perder el nodo */
free(p);
p = q;
}
return NULL;
}
Para invocar esta función se tiene:
lista=anulalista(lista);
free(lista);

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 21


Ejercicios en clase
Implementar el código de la lista enlazada y probar (ver código main.c en la plataforma virtual)
Definir una estructura de lista enlazada donde cada nodo sea un registro de agenda como sigue:
struct _agenda {
char nombre[20];
char telefono[12];
struct _agenda *siguiente;
};
Definir un menú que permita 1) ingresar personas en la agenda (se inserta al final), 2) mostrar
todos los contactos y 3) Salir.

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 22


Implementación de la lista enlazada
void imprimelista(struct Nodo * l){
struct Nodo * p=NULL;
int cont=0;
p=l;
while (p != NULL) {
cont++;
printf("\n Valor %d ", cont); printf("%d",p->info);
p = p->sig; // para no perder el nodo
}
}
int main(int argc, char *argv[]) {
struct Nodo * lista=NULL;
int num, numero,i ;
printf("\n Cuantos numeros: "); scanf("%d",&num);
for (i=1;i<=num;i++)
{ printf("\n Numero %d",i); scanf("%d",&numero);
lista=insertacomienzo(lista, numero);
//lista=insertafinal(lista, numero); // se puede usar una de las dos funciones para insertar
}
imprimelista(lista);
printf("\n Tamano Lista %d",longitud(lista));
lista=anulalista(lista);
free(lista);
printf("\n Lista Eliminada");
return 0;
}
PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 23
¿Qué hemos aprendido?

PROGRAMACION II: ESTRUCTURA DE DATOS Y ANÁLISIS DE ALGORITMOS - CAPÍTULO IV 24

Вам также может понравиться