Estructuras

Anuncio
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.
[email protected]
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.
[email protected]
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
Descargar