Está en la página 1de 39

Estructuras de Datos

Listas (Lists)
 Introducción a las listas
 Implementación con arreglos
 Implementación con listas enlazadas
 Comparación de implementaciones
Objetivo
Aprender a utilizar la estructura de listas.

2
Estructuras de Datos

Listas (Lists)
 Introducción a las listas
 Implementación con arreglos
 Implementación con listas enlazadas
 Comparación de implementaciones
El TDA Lista (List)
• Una secuencia de elementos, no necesariamente distintos, que
tienen el mismo tipo de datos y son ordenados posicionalmente
• Ejemplos: una lista de estudiantes, una lista de eventos, una lista de
contactos, etc. Ampliamente usada en ciencias de la computación
void initialize(List *l)

void append(List *l, E item) E get(List *l, int idx)

void add(List *l, int idx, E item) List int contains(List *l, E item)

int isEmpty(List *l) E remove(List *l, int idx)

E : tipo de datos de c/elemento


int size(List *l) 4
idx : índice (comienza con cero)
item: valor de c/elemento
Aplicaciones de las listas

• Las listas pueden utilizarse en cualquier programa que requiere


cargar datos en memoria, en donde el orden importa, pero no se
conoce por anticipado el número de elementos

• En la práctica, se usan para implementar otras estructuras de


datos. Ejemplo: pilas, colas, tablas hash (unidad V)

5
Operaciones de una lista
• initialize(List *l): inicializar una lista
• size(List *l): tamaño de la lista (número de elementos)
• append(List *l, E item): agregar ítem al final de la lista
• add (List *l, int idx, E item): agregar un ítem en el índice idx
• isEmpty(List *l): comprueba si la lista está vacía
• get(List *l, idx): obtener el elemento en el índice idx
• contains(List *l, E item): comprueba si ítem está en la lista
• remove(List *l, int idx): remueve el elemento en el índice idx

6
Estructuras de Datos

Listas (Lists)
 Introducción a las listas
 Implementación con arreglos
 Implementación con listas enlazadas
 Comparación de implementaciones
Uso de un array para implementar una lista
• Ventajas: implementación sencilla, recuperación rápida de
elementos (los índices del array representan posiciones de la lista)

• Desventajas: capacidad limitada (MAX_LIST), inserción lenta


List

count items[MAX_LIST]

k 12 3 19 75 .... 18 ? ? .... ?

0 1 2 3 k-1 MAX_LIST-1

8
Espacios utilizados Espacios libres
Especificación (archivo de cabecera)

9
Nota: para prevenir conflictos de nombres, se agregó el prefijo “list” a los nombres de las funciones.
Operaciones sencillas
count items[MAX_LIST] MAX_LIST

0 10

0 1 2 3 4 5 6 7 8 9

/* Inicializar la lista */
void listInitialize(List *l) {
l->count = 0;
}

/* Esta la lista vacia? */


int listIsEmpty(List *l) {
return l->count == 0;
}

10
Operaciones sencillas
count items[MAX_LIST] MAX_LIST

3 12 7 19 10

0 1 2 3 4 5 6 7 8 9

/* Tamano de la lista */
int listSize(List *l) {
return l->count;
}

/* Valor del elemento en la posicion idx */


E listGet(List *l, int idx) {
assert(idx >= 0 && idx < l->count); /* Posición Válida */
return l->items[idx];
}
11
Agregar un elemento al final
count items[MAX_LIST] MAX_LIST

3
4 12 7 19 30 10

0 1 2 3 4 5 6 7 8 9

/* Agregar elemento al final */


void listAppend(List *l, E item) {
assert(l->count < MAX_LIST); /* La lista no está llena */
l->items[l->count] = item; /* Agrega el valor */
(l->count)++; /* Actualiza número de elementos */
}

Ejemplo: Agregar el número 30 al final de la lista


12
Agregar un elemento con índice específico
count items[MAX_LIST] MAX_LIST

4
5 12 7
41 19
7 30
19 30 10

0 1 2 3 4 5 6 7 8 9

/* Agregar elemento en posicion idx */


void listAdd(List *l, int idx, E item) {
assert(l->count < MAX_LIST); /* La lista no está llena */
assert(pos >= 0 && idx <= l->count); /* Posición valida */

/* Desplazar elementos hacia la derecha, a partir de idx */


int index;
for(index = l->count - 1; index >= idx; index--)
l->items[index + 1] = l->items[index];

l->items[idx] = item; /* Sobreescribe el valor en el índice idx */


(l->count)++; /* Actualiza el tamaño */
} 13

Ejemplo: Agregar el número 41 en índice 1


Comprobar si la lista contiene un valor
count items[MAX_LIST] MAX_LIST

5 12 41 7 19 30 10

0 1 2 3 4 5 6 7 8 9

/* Esta item en la lista? */


int listContains(List *l, E item) {
int index;

/* Compara item con cada elemento de la lista */


for (index = 0; index < l->count; index++)
if (l->items[index] == item) return 1;

return 0;
}

14
Remover un elemento con índice específico
count items[MAX_LIST] MAX_LIST

4
5 12 41 19
7 30
19 30 10

0 1 2 3 4 5 6 7 8 9

/* Remover el elemento en el indice idx */


E listRemove(List *l, int idx) {
assert(!listIsEmpty(l)); /* Lista no vacía */
assert(idx >= 0 && idx < l->count); /* Posición Válida */

/* Lee el valor en el índice idx y desplaza elementos hacia la izquierda */


E val = l->items[idx];
int index;
for (index = idx; index < l->count - 1; index++)
l->items[index] = l->items[index + 1];

(l->count)--; /* Actualiza el tamaño */


return val; /* Devuelve el valor */
} 15

Ejemplo: Remover el número en el índice 2 (7)


Estructuras de Datos

Listas (Lists)
 Introducción a las listas
 Implementación con arreglos
 Implementación con listas enlazadas
 Comparación de implementaciones
Lista enlazada
• Una cadena de nodos, conectados por punteros (enlaces)
• Requiere un puntero al primer nodo de la lista (head)
• Para acceder a otros nodos, se recorren los punteros next
• La lista crece dinámicamente, count es el número de elementos

item next item next item next item next item next

19 7 11 3 23

5 La implementación de una lista enlazada es más compleja ya


que requiere manejar varios escenarios de forma separada
head count 17
Predecesores y sucesores
• Cada elemento tiene como máximo un predecesor (el elemento
anterior) y un sucesor (el elemento siguiente)
• El primer elemento de la lista sólo tiene sucesor
• El último elemento de la lista sólo tiene predecesor

item next item next item next item next item next

19 7 11 3 23

5 El predecesor de 11 es 7 El sucesor de 11 es 3
El primer elemento es 19 El último elemento es 23
head count 18
Especificación (archivo de cabecera)

19
Nota: para prevenir conflictos de nombres, se agregó el prefijo “ list” a los nombres de las funciones.
Funciones auxiliares: crear un nuevo nodo
Se
: define una función que crea un nuevo nodo y devuelve un puntero a él:

static Node* createNode(E item);


La palabra reservada static se usa para que la función sea visible sólo en el archivo donde se declara

Ejemplo: crear un nodo con el valor 3: createNode(3);


item next

Node *newNode = malloc(sizeof(Node));


3 node->item = item; /* Toma el valor de 3 */
node->next = 0; /* Puntero nulo */
return newNode; /* Devuelve el nodo */
20
newNode
Funciones auxiliares: obtener un nodo
En
: ocasiones se necesita acceder al nodo que está en cierto índice (idx):

static Node* getNode(List *l, int idx);

La idea es la siguiente: comenzando con el primer nodo (l->head), dar idx


“saltos” al nodo siguiente (next). Ejemplo: getNode(&lista, 2);

item next item next item next


Node *ptr = l->head;
19 7 11 int i;
for (i = 0; i < idx; i++)
item next item next
ptr = ptr->next;
3 23
head
return ptr; 21
Verificar si una lista contiene un valor
count head item next item next item next

3 19 7 23

/* Esta el item en la lista? */


/* Recorre toda la lista con un bucle while, se detiene si halla el valor */

int listContains(List *l, E item) {


Node *current; /* Nodo actual */

/* Recorrer la lista desde el primer nodo */


for (current = l->head; current != 0; current = current->next)
if (current->item == item) return 1;

return 0;
22
}
Inicializar lista, comprobar si está vacía
count head

/* Inicializar la lista */
void listInitialize(List *l) {
l->head = 0;
l->count = 0;
}

/* Esta la lista vacia? */


int listIsEmpty(List *l) {
return l->count == 0;
}
23
Tamaño de la lista, obtener un elemento
count head item next item next item next

3 19 7 23

/* Tamaño de la lista */
int listSize(List *l) {
return l->count;
}

/* Valor del elemento en la posicion idx */


E listGet(List *l, int idx) {
Node *tmp = getNode(l, idx);
return tmp->item;
}
24
Agregar elementos a la lista
Dos posibles escenarios:

1. Agregar un elemento al principio de la lista

2. Agregar un elemento en la k-ésima posición

La operación append (agregar al final de la lista) es un caso particular


de agregar un elemento en la k-ésima posición
25
Agregar un ítem al principio de la lista
count head item next item next item next

3
4 19 7 23

item next

31
newNode

void listAdd(List *l, int idx, E item) {


Node *newNode = createNode(item); /* Crea un nuevo nodo */

if (idx == 0) {
newNode->next = l->head; /* El nodo head actual seguirá al nuevo */
l->head = newNode; /* Se actualiza head al nuevo nodo */
}
(l->count)++; /* Incrementa en 1 el número de nodos */ 26
}
Agregar un ítem en la k-ésima posición
count head item next item next item next

3
4 19 7 23

item next

31
prev newNode

void listAdd(List *l, int idx, E item) {


Node *newNode = createNode(item); /* Crear un nuevo nodo */
if (idx != 0) {
Node *prev = getNode(l, idx - 1); /* Obtener el nodo previo */
newNode->next = prev->next; /* Actualiza punteros */
prev->next = newNode;
}
(l->count)++; /* Incrementa el numero de nodos */ 27
}
Agregar un elemento: código completo
Agregar un elemento al final
Como se mencionó antes, agregar un elemento al final es un caso
particular de agregar un elemento en la k-ésima posición. El índice en
el que se agregará elemento es igual a l->count:

29
Remover un nodo de la lista
Al remover un nodo de la lista, de forma análoga a la operación
agregar, los casos pueden resumirse en:

1. Remover el primer nodo de la lista (head)


2. Remover cualquier otro nodo (incluyendo el último nodo)

30
Caso 1: remover el primer nodo
count
E val;
head
Node *curr;
curr = l->head;
l->head = curr->next;
curr
val = curr->item;
free(curr);
count
(l->count)--; head

return val;
31
Caso 2: remover un nodo distinto al primero
E val; count
head
Node *prev, *curr;
prev = getNode(l, idx - 1);
curr = prev->next;
prev->next = curr->next; prev curr

val = curr->item; count

free(curr); head

(l->count)--;
return val; 32
Remover un elemento: código completo
Clasificación de las listas enlazadas
(a) Listas simplemente enlazadas (b) Listas doblemente enlazadas

19 7 23 19 7 23

head head

(c) Listas circulares simplemente enlazadas (d) Listas circulares doblemente enlazadas

19 7 23 19 7 23

head head
34
Piense en cuál sería el código para implementar las operaciones de listas analizadas en clase para (b), (c) y (d)
Estructuras de Datos

Listas (Lists)
 Introducción a las listas
 Implementación con arreglos
 Implementación con listas enlazadas
 Comparación de implementaciones
Eficiencia de implementaciones: tiempo
Operación Arrays Listas enlazadas
Recuperar elementos Rápida: un solo acceso Mejor caso: recuperar el primer
elemento (un acceso)
Peor caso: recuperar el último
elemento (recorre toda la lista)

Agregar elementos Mejor caso: agregar al final (no hay Mejor caso: agregar al principio (un
desplazamiento de elementos) solo acceso)
Peor caso: agregar al principio Peor caso: agregar al final (recorre toda
(desplaza todos los elementos) la lista)

Remover elementos Mejor caso: remover el último Mejor caso: remover el primer
elemento (no hay desplazamiento de elemento (un solo acceso)
elementos) Peor caso: remover el último elemento
Peor caso: remover el primer (recorre toda la lista)
36
elemento (desplaza toda la lista)
Eficiencia de implementaciones: tiempo

• Es posible hacer más eficiente la List


operación de agregar al final de una
lista enlazada de una lista enlazada, si Node *head;
se tiene un puntero tail al último nodo
Node *tail

• Piense en las modificaciones que


int count;
habría que hacer a la implementación
de las operaciones con este cambio

37
Eficiencia en el uso de espacio
• Al usar arrays, el tamaño de la lista está restringido a MAX_LIST,
el problema es que el tamaño máximo no se conoce por
anticipado:
 MAX_LIST muy grande == espacio desperdiciado
 MAX_LIST muy pequeño == espacio se agota fácilmente

• Solución: usar malloc para reservar un bloque de memoria (array)


dinámicamente:
 Cuando el bloque se llene, cambiar su tamaño con realloc
 La operación de cambiar el tamaño del bloque puede tardarse

38
 Las listas enlazadas no tienen esta restricción, pero cada
nodo usa más espacio en memoria para mantener punteros a
los nodos siguientes y son más complejas de implementar

39

También podría gustarte