Listas

Anuncio
Algoritmos y Lenguaje de Programación,
Sección 1
Porqué usar listas?
• En una palabra: orden
Listas
)Fácil ordenar datos
)Fácil reordenar datos
Ciudad
Mario Medina C.
mariomedina@udec.cl
Superficie
urbana (km2)
4360
12147100
12147100
Buenos Aires
3680
11655100
12923800
Ciudad de
México
4980
8589600
19013000
Nueva York
8680
8103700
18498000
13500
8027500
31036900
Acceso a los datos
Struct ciudad {
char *nombre;
int superficie;
int pobCiudad;
int pobUrbana;
} ciudades[5] = {
{“Bombay”, 4360, 12147100, 12147100},
{“Buenos Aires”, 3680, 11655100, 12923800},
{“Ciudad de México”, 4980, 8589600, 19013000},
{“Nueva York”, 8680, 8103700, 18498000},
{“Tokio”, 13500, 8027500, 31036900} };
Listas vs. Vectores
)Generar listas de punteros a los datos
)Reordenar las listas
)Orden más complejo: 1, 3, 2, 4, 5
• Acceso por superficie creciente
)Orden más complejo: 2, 1, 3, 4, 5
• Qué pasa si elimino o agrego una ciudad?
)Reordenar la tabla!
)Acceso aleatorio a datos
eficiente
Lista
4360
12147100
12147100 Í
1
1 Î Buenos Aires
3680
11655100
12923800 Í
3
3 Î Ciudad de México
4980
8589600
19013000 Í
2
8680
8103700
18498000 Í
4
13500
8027500
31036900 Í
5
)Los datos no se mueven!
©Mario Medina C.
• Acceso por población urbana creciente
• Vectores
2 Î Bombay
5 Î Tokio
)Fácil: orden usado en almacenamiento de datos
Listas vs. Vectores
• Idea: dejar los datos donde están
4 Î Nueva York
• Acceso alfabético por nombre
` Movimiento de datos es costoso!
• Ciudades almacenadas en orden alfabético
Lista
Población
área urbana
Bombay
Tokio
Vector de estructuras
Población
ciudad
` Acceso via índice es O(1)
)Requiere conocer el
tamaño a priori
` Memoria estática
)Búsquedas
` Si índice es conocido, O(1)
` Si no, O(n)
• Listas
)Tamaño definido en
tiempo de ejecución
` Memoria dinámica
)Inserciones y
eliminaciones eficientes
` O(1)
)Búsquedas son más lentas
` O(n)
` Una vez encontrado el nodo,
muchas operaciones son
O(1)
)Difícil cambiar el tamaño!
1
Algoritmos y Lenguaje de Programación,
Sección 1
Listas
Lista encadenada
• Colección de estructuras de datos
independientes (nodos) que contienen datos
y están unidas por punteros
• Cada nodo contiene un puntero al nodo
siguiente
• Último nodo apunta a NULL
• Raíz apunta a primer nodo
Nodos
Raíz
)Raíz no contiene datos
NULL
Nodos
Raíz
5
6
8
9
NULL
Datos
Estructura para lista encadenada
typedef struct miNodo {
struct miNodo *proximo;
int dato;
} Nodo;
• Fácil recorrer lista de raíz a final
• No permite recorrer en orden inverso
• En este ejemplo, nodos están ordenados por valor
en forma ascendente
Buscar en lista encadenada (1)
Nodo* buscar(Nodo* actual, int valor) {
Nodo *n = actual;
while (n->dato != valor) {
n = n->proximo;
}
return n;
}
©Mario Medina C.
Función buscar()
• Buscar valor 8 en la lista
)Comenzar en la raíz
` Comparar dato con 8
` Si no es igual, continuar
)Apuntar a próximo nodo
` Comparar con 8
` Si no es igual, repetir
)Detenerse al encontrar un nodo NULL
Función buscar()
• Qué pasa al llegar al final de la lista?
)Último nodo tiene proximo = NULL
)Ciclo intenta obtener miembro dato de un
puntero a NULL
` Error!
)Verificar que n no sea NULL antes de acceder a
n->dato
2
Algoritmos y Lenguaje de Programación,
Sección 1
Buscar en lista encadenada (2)
Nodo* buscar(Nodo* actual, int valor) {
Nodo *n = actual;
while (n != NULL && n->dato != valor) {
n = n->proximo;
}
return n;
}
• Condición aprovecha la propiedad de cortocircuito
Función insertar()
• Insertar valor 7 en mitad de la lista
)Crea un nuevo nodo
` Solicita memoria via malloc()
)Asigna a dato de éste el valor 7
)Modifica puntero proximo de nodo anterior
` Apunta al nodo nuevo
)Puntero próximo de nodo nuevo apunta a nodo
siguiente
)Si la primera condición se cumple, la segunda nunca se
evalúa
Insertar en lista encadenada
int insertar(Nodo* actual, int valor) {
Nodo *nuevo, *anterior;
while (actual->dato < valor) {
anterior = actual;
actual = actual->proximo;
}
nuevo = (Nodo *) malloc(sizeof(Nodo));
if (nuevo == NULL)
return FALSE;
nuevo->dato = valor;
nuevo->proximo = actual;
anterior->proximo = nuevo;
return TRUE;
}
Insertar en lista encadenada (2)
int insertar(Nodo* actual, int valor) {
Nodo *nuevo, *anterior;
while (actual != NULL && actual->dato < valor) {
anterior = actual;
actual = actual->proximo;
}
nuevo = (Nodo *) malloc(sizeof(Nodo));
if (nuevo == NULL)
return FALSE;
nuevo->dato = valor;
nuevo->proximo = actual;
anterior->proximo = nuevo;
return TRUE;
}
©Mario Medina C.
Función insertar()
• Cómo insertar al final de la lista?
)Código trata de leer dato de nodo NULL
` Error!
)Verificar que actual no sea NULL antes de
acceder a actual->dato
Función insertar()
• Cómo insertar al comienzo de la lista?
)Modificar raiz
)Pasar raiz como argumento a la función
` Raíz es un puntero a un Nodo
` Argumento es puntero a puntero a Nodo
)Modifica puntero de nodo anterior
)Apunta a nodo siguiente
3
Algoritmos y Lenguaje de Programación,
Sección 1
Insertar en lista encadenada (3)
int insertar(Nodo **raiz, int valor) {
Nodo *nuevo, *actual, *anterior;
actual = *raiz;
anterior = NULL;
while (actual != NULL && actual->dato < valor) {
anterior = actual;
actual = actual->proximo;
}
nuevo = (Nodo *) malloc(sizeof(Nodo));
if (nuevo == NULL)
return FALSE;
nuevo->dato = valor;
nuevo->proximo = actual;
if (anterior == NULL)
*raiz = nuevo;
else
anterior->proximo = nuevo;
return TRUE;
}
Insertar en lista encadenada (4)
int insertar(Nodo **p, int valor) {
Nodo *nuevo, *actual;
while ((actual = *p) != NULL && actual->dato <
valor) {
n = &actual->proximo;
}
nuevo = (Nodo *) malloc(sizeof(Nodo));
if (nuevo == NULL)
return FALSE;
nuevo->dato = valor;
nuevo->proximo = actual;
*p = nuevo;
return TRUE;
}
Lista doblemente encadenada
• Cada nodo contiene un puntero al nodo
siguiente y al nodo anterior
)Puede recorrerse en ambas direcciones
Raíz
NULL
5
Función insertar()
• Cómo insertar al comienzo de la lista?
)raiz es un puntero a un nodo
)proximo también es un puntero a un nodo
• Modificar código para considerar este hecho
)Puntero p apunta a la raíz o al próximo nodo
)Puntero anterior ya no es necesario
Otras funciones
• Contar los nodos en una lista
• Insertar un dato al comienzo de una lista
• Insertar un dato al final de una lista
• Retornar el n-ésimo dato de una lista
• Eliminar un dato de una lista
• Eliminar todas las ocurrencias de un dato en
una lista
• Eliminar la lista
Estructura para lista doble
typedef struct miNodo {
struct miNodo *proximo;
struct miNodo *anterior;
int dato;
} Nodo;
• Raíz necesita ahora dos punteros
6
8
9
NULL
)Puntero al primer nodo de la lista
)Puntero al último nodo de la lista
Nodos
©Mario Medina C.
4
Algoritmos y Lenguaje de Programación,
Sección 1
Técnica del nodo fantasma
Función insertar()
• Declarar raíz como un nodo de la lista
• Cuatro casos:
)proximo es puntero al primer nodo de la lista
)anterior es puntero al último nodo de la lista
)dato puede ser usado para almacenar el número
de nodos, por ejemplo
)Insertar valor en mitad de la lista
)Insertar valor al comienzo de la lista
)Insertar valor al final de la lista
)Insertar valor en lista vacía
• En cada caso, necesario modificar 4 punteros:
)2 en nodo a insertar
)1 en nodo anterior
)1 en nodo siguiente
Insertar en lista doble
Insertar en lista doble
int insertarDoble(Nodo* raiz, int valor) {
Nodo *nuevo, *actual, *siguiente;
for (actual = raiz; (siguiente = actual->proximo) !=
NULL; actual = siguiente) {
if (siguiente->dato == valor)
return 0;
if (siguiente->dato > valor)
break;
}
nuevo = (Nodo *) malloc(sizeof(Nodo));
if (nuevo == NULL)
return -1;
if (actual != raiz)
nuevo->anterior = actual;
else
nuevo->anterior = NULL;
if (siguiente != NULL)
siguiente->anterior = nuevo;
else
raiz->anterior = nuevo;
return 1;
}
• Función retorna 0 si el valor ya está en la lista, 1 si la
inserción fue exitosa y –1 si hubo un error
nuevo->dato = valor;
nuevo->proximo = siguiente;
actual->proximo = nuevo;
Lista circular
• Cada nodo contiene un puntero al nodo
siguiente
• Último nodo apunta al primer nodo
)Puntero raíz al primer nodo
)Lista puede ser simple o doblemente
encadenada
Nodos
Raíz
5
©Mario Medina C.
6
8
9
5
Descargar