Taller de Programación en C 2008-II Estructuras Estructuras • Vector: colección de datos del mismo tipo • Estructura: colección de datos de tipos diferentes D fi un ti Define tipo d de dato d t compuesto t Daniel Herrera P. danherrera@udec.cl Estructuras • Estructuras pueden ser Argumentos de funciones Retornadas por funciones Asignadas a otras estructuras del mismo tipo • También es posible Declarar punteros a estructuras Obtener dirección de una estructura Declarar un vector de estructuras Estructuras y typedef • Comando typedef permite definir nuevos tipos de datos typedef struct { float r; float im; } complejo; • Define un nuevo tipo de datos complejo complejo x, y, *z; ©Daniel Herrera P. • Estructura de datos tiene: Nombre que la identifica Datos miembros También identificados por nombres Declaración de estructuras struct nombre { miembro1; miembro2; miembro3; } variables; Nombre o variable pueden ser omitidos ` Pero no ambos! struct punto3D { int x; int y; int z; } p1 p1, p2 p2, p3; Define las variables p1, p2, p3 como estructuras de tipo punto3D Miembros de una estructura • Miembros pueden ser struct S1 { char, int, float float x; Vectores int a[20]; Punteros long g *lp; p; Ti Tipos d de d datos t typedef t d f struct punto3D p1; Otras estructuras complejo *cp; complejo vc[10]; } dato; 1 Taller de Programación en C 2008-II Acceso directo a miembros Acceso indirecto a miembros • Miembros de una estructura se accesan con el operador . • Formato: estructura.miembro • Dado un puntero a una estructura, sus miembros se accesan: Con un puntero y el operador . con el operador p -> p1 x = 10; (p1 es estructura punto3D) p1.x x.r = 3.14; (x es estructura complejo) (dato.vc[4]).im = 45; (dato es estructura S1, vc es vector de estructuras complejo, im es miembro de complejo) struct punto3D *p; (*p).x = 10; p->y = 20; *p.x: Error! ` Operador . Tiene precedencia sobre * ` Equivalente a *(p.x) Ejemplo Inicialización de estructuras • Lista de valores separados por coma, entre llaves { y } Valores dados en el orden de la declaración Variables sin inicialización toman valor por omisión complejo c = {1.0, 0.0}; punto3D vertice = {1, , 3}; typedef struct { int a; short b[2]; } S2; x a Ejemplo Ejemplo S1 x = {10, “Hi”, {5, {-1, 25}}, 0}; S1 *px = x; • • • • • x px a b 10 H i \0 c a b 5 -1 25 typedef struct S1 { int a; char b[3]; S2 c; struct S1 *d; } x; d b px contiene la dirección de la estructura x *px apunta al contenido de la estructura x px->a apunta al contenido de a (10) px->b[1] es el segundo elemento de b (‘i’) px->c b[1] es el segundo elemento de la estructura b en px->c.b[1] c (25) x d 0 px a b 10 ©Daniel Herrera P. c a b H i \0 c a b 5 -1 25 d 0 2 Taller de Programación en C 2008-II Ejemplo Funciones y estructuras S1 y; x.d = y; px->d->a = ? px->d->c.b[1] = ? • Estructuras pueden ser: x px a b 10 H i \0 c a b 5 -1 25 d &y • Paso por valor implica se hacen copias de las estructuras y a b 15 N o \0 c a b 45 3 7 Argumentos de funciones Valores retornados por funciones Ineficiente y lento d 0 • Mejor usar paso por referencia Usar punteros a estructuras Ejemplo Ejemplo complejo conjugado(complejo x){ x.im = -x.im; return x;; } Recibe una copia del argumento La modifica Retorna una copia de estructura modificada void conjugado(complejo *x) { x->im = -x->im; } Modifica variable compleja in situ Recibe puntero como argumento Más eficiente Invocado como conjugado(&x); ` Recibe dirección como argumento Porqué usar listas? Listas • En una palabra: orden Fácil ordenar datos Fácil reordenar datos Ciudad Daniel Herrera P. danherrera@udec.cl Población ciudad Población área urbana Bombay 4360 12147100 12147100 Buenos Aires 3680 11655100 12923800 Ciudad de México 4980 8589600 19013000 8680 8103700 18498000 13500 8027500 31036900 Nueva York Tokio ©Daniel Herrera P. Superficie urbana (km2) 3 Taller de Programación en C 2008-II Vector de estructuras 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} }; • Acceso alfabético por nombre Fácil: orden usado en almacenamiento de datos • Acceso por población urbana creciente O d más Orden á complejo: l j 1, 1 3, 3 2 2, 4 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! ` Movimiento de datos es costoso! • Ciudades almacenadas en orden alfabético Listas vs. Vectores Listas vs. Vectores Lista • Listas • Vectores • Idea: dejar los datos donde están Generar listas de punteros a los datos Reordenar las listas Tamaño definido en tiempo de ejecución Acceso aleatorio a datos eficiente ` Memoria dinámica ` Acceso via índice es O(1) Lista 2 Bombay 4360 12147100 12147100 1 1 Buenos Aires 3680 11655100 12923800 3 R Requiere i conocer ell tamaño a priori 3 Ciudad de México 4980 8589600 19013000 2 ` Memoria estática 4 Nueva York 8680 8103700 18498000 4 5 Tokio 13500 8027500 31036900 5 Inserciones y eliminaciones li i i eficientes fi i t ` O(1) Búsquedas son más lentas ` O(n) ` Una vez encontrado el nodo, muchas operaciones son O(1) Búsquedas ` Si índice es conocido, O(1) ` Si no, O(n) Difícil cambiar el tamaño! Los datos no se mueven! Listas Lista encadenada • Colección de estructuras de datos independientes (nodos) que contienen datos y están unidas p por p 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 Datos ©Daniel Herrera P. 9 NUL L 4 Taller de Programación en C 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; } 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 2008-II 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 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 no se cumple, la segunda nunca se evalúa ©Daniel Herrera P. 5 Taller de Programación en C 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; } 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; } ©Daniel Herrera P. 2008-II 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 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 M difi código ódig para considerar id este t hecho h h Puntero p apunta a la raíz o al próximo nodo Puntero anterior ya no es necesario 6 Taller de Programación en C 2008-II 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 NUL L 5 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 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 Nodos NUL L Puntero al primer nodo de la lista Puntero al último nodo de la lista 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 ©Daniel Herrera P. 7 Taller de Programación en C 2008-II 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; } nuevo->dato = valor; nuevo->proximo = siguiente; actual->proximo = nuevo; • 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 Lista circular • Cada nodo contiene un puntero al nodo siguiente • Último nodo apunta al primer nodo P t Puntero raíz í all primer i nodo d Lista puede ser simple o doblemente encadenada Nodos Raíz 5 ©Daniel Herrera P. 6 8 9 8