1. Presentación
Estructuras de Datos Lineales
Mauricio Paletta
INGENIERÍA EN INFORMÁTICA
Programación II
Coordinación General de Pregrado
UNIVERSIDAD NACIONAL EXPERIMENTAL DE GUAYANA
Programación II
2. Introducción
La memoria se puede ver como una gran
matriz de celdas, cada una de las cuales
representa un byte de información y tiene
asociado un único número o dirección a la
cual se le puede llegar directamente.
Una interesante analogía de este concepto lo
es un conjunto de casilleros postales
agrupados matricialmente. El contenido del
casillero es el byte de información. El número
(llave) es la dirección.
Programación II
4. Introducción
Cada vez que se declara algo (variable,
constante, función, estructura, etc.) esta
declaración debe tener asociado un conjunto
de celdas de la matriz de memoria de tantos
bytes como requiera la declaración.
Para cualquier declaración es importante
saber cómo se accede o se obtiene:
1) La data (contenido del buzón).
2) La dirección (llave o número del buzón).
Programación II
5. Introducción
Según el momento en el cual se solicita la
memoria, esta solicitud puede ser:
1) Estática (tiempo de compilación).
2) Dinámica (tiempo de ejecución).
La asignación de memoria se puede hacer de
dos maneras:
1) Continua o lineal.
2) No continua o no lineal.
Programación II
6. Solicitudes estáticas
Ocurre en tiempo de compilación del
programa. El compilador sabe exactamente
la cantidad de bytes que se requieren para
satisfacer la declaración y se procede a hacer
la reserva del espacio.
Ejemplo:
int I;
double D;
Programación II
7. Solicitudes estáticas
En cualquier declaración de tipo básico (int,
double, char, bool, etc.) el identificador de la
declaración representa el contenido o dato
(buzón). Para obtener la dirección (llave /
número del buzón) se hace uso del operador
unario &.
Ejemplo:
double D; D es la data
&D es la dirección
Programación II
8. Solicitudes estáticas
Cantidad de bytes necesarios
para representar/almacenar un
dato de tipo double
sizeof(double)
… …
&D D
dirección data
Programación II
9. Solicitudes dinámicas
Ocurre en tiempo de ejecución del programa.
Se hace uso del operador de reserva de
memoria new. Este operador retorna una
dirección de memoria o apuntador, por lo que
se requiere declarar un apuntador para recibir
lo que el operador new retorna.
Para declarar apuntadores de memoria se
hace uso del símbolo *.
Programación II
10. Solicitudes dinámicas
Ejemplo:
int *pI;
double *pD;
En cualquier declaración de tipo básico, si se
tiene una dirección de memoria (apuntador),
el operador * permite obtener la data
almacenada en el espacio de memoria a la
cual el apuntador apunta.
Programación II
11. Solicitudes dinámicas
Ejemplo:
double *pD = new double();
Uso del operador new para
reserva dinámica de memoria
… …
pD *pD
dirección data
Programación II
12. Solicitudes dinámicas
Ejemplo:
double D, *pD = &D;
Los apuntadores no sólo son utilizados para
hacer reserva dinámica de memoria
… …
&D = pD D = *pD
dirección data
Programación II
13. Solicitudes dinámicas
En C++ el programador es responsable de
cualquier espacio de memoria que se reserva
dinámicamente con el operador new. Una
vez que el espacio no es más requerido, se
debe hacer una liberación del espacio para
que éste pueda ser reutilizado por otros
usuarios. Para ello se hace uso del operador
delete.
Programación II
14. Solicitudes dinámicas
Ejemplo:
int *pI = new int();
…
delete pI;
NOTA: Si por alguna razón la reserva de
espacio de memoria no es posible (por
ejemplo cuando no hay más memoria
disponible), el operador new retorna una
dirección nula no válida equivalente a 0L.
Programación II
15. Solicitudes dinámicas
NOTA: Los apuntadores son un tipo particular
de declaración que también requieren de un
espacio de memoria para su representación
y almacenamiento. Este espacio es
equivalente al tipo de dato long. Esa es la
razón porque un apuntador nulo es igual al
literal 0L.
Hay que tener cuidado de no hacer un
delete con una dirección de memoria no
válida.
Programación II
16. Solicitudes dinámicas
Ejemplo:
char *pC = new char(„A‟);
if (pC == 0L) {
cout << “Posible problema de memoria”;
return;
}
…
delete pC;
Programación II
17. Estructuras continuas / lineales
Son aquellas que ocupan un espacio de
memoria (bloque) continuo (un byte al lado
del otro).
Todas las declaraciones de tipos básicos son
lineales, tanto si se reservan de manera
estática como de manera dinámica.
Es necesario tener una dirección de memoria
que permita llegar al bloque donde está la
estructura.
Programación II
18. Estructuras continuas / lineales
Ventajas:
1) Sólo se requiere una dirección de memoria
para llegar al bloque acceso fácil a la
información.
2) Una vez que se llega al bloque toda la data
está accesible acceso rápido a la
información.
Programación II
19. Estructuras continuas / lineales
Desventajas:
1) Es necesario conocer, ya sea en tiempo
estático o dinámico, el número de bytes que
se requiere para reservar el bloque su
tamaño es estático, no puede crecer.
2) Para bloques de gran tamaño, es posible que
se de el caso de no encontrar un espacio
continuo lo suficientemente grande para
satisfacer la necesidad (aún habiendo
memoria disponible) posibles problemas
de reserva de memoria.
Programación II
20. Estructuras continuas / lineales
NOTA: Las estructuras continuas son
susceptibles al problema de fragmentación
de memoria aún habiendo varios bytes
de memoria disponibles, la alternación de
bloques ocupados y libres hace que los
espacios continuos sean más pequeños de
los que se necesitan.
Programación II
22. Estructuras continuas / lineales
Dos clases de estructuras:
1) Arreglos: conjunto finito de elementos del
mismo tipo.
2) Registros: conjunto finito de elementos de
tipo diferente.
Programación II
23. Arreglos
Para su definición / declaración se requieren
tres cosas:
1) Conocer el tipo de dato único de todos sus
elementos.
2) Conocer la cantidad de elementos del
conjunto.
3) Dar un identificador válido para acceder a los
elementos del conjunto.
Programación II
24. Arreglos
Ejemplo:
char Cadena[10];
Cadena es un arreglo de 10 elementos de
tipo char. Cadena es también la dirección de
memoria del espacio continuo donde se
almacenan los 10 elementos.
Para acceder a los elementos del arreglo se
hace uso de un índice que indica la posición
del elemento en el conjunto.
Programación II
25. Arreglos
En C++ el primer elemento se encuentra en
la posición 0; para el ejemplo anterior, el
último elemento está en la posición 9.
Los elementos de un arreglo se almacenan
en memoria uno al lado del otro de acuerdo a
su posición y conforme a la cantidad de bytes
que ocupa cada elemento.
Los arreglos también pueden ser inicializados
en tiempo de declaración.
Programación II
26. Arreglos
Ejemplo:
int V[3] = { 10, 20, 30 };
Inicialización en tiempo de declaración
sizeof(int)
… … …
V V[0] = 10 V[1] = 20 V[2] = 30
dirección
Programación II
27. Arreglos
Nótese que *V y V[0] son lo mismo. En
general, *(V+i) y V[i] son lo mismo.
Para estimar el número de bytes que ocupa
la estructura en memoria basta multiplicar el
número de elementos por el tamaño en bytes
del tipo de dato de cada elemento. Para el
ejemplo anterior:
3 x sizeof(int) sizeof(V)
Programación II
28. Arreglos
¿Qué hacer cuando no se conoce a priori
(estáticamente) el número de elementos? Por
ejemplo cuando se tiene que leer durante la
ejecución del programa.
Hay que hacer reserva de espacio dinámica
de memoria usar un apuntador o dirección
de memoria y los operadores new y delete.
Programación II
30. Arreglos
Nótese que no es posible hacer:
double V[N];
A menos que N sea una constante
previamente definida:
const int N = 10;
O una macro previamente definida:
#define N 10
Programación II
32. Arreglos
¿Cómo representar arreglos de más de una
dimensión: matrices por ejemplo?
1 dimensión vector
2 dimensiones matriz bidimensional
3 dimensiones matriz tridimensional
N dimensiones matriz híper-espacial
Se agrega otro tamaño y se usa otro índice
para cada nueva dimensión.
Programación II
33. Arreglos
Ejemplo:
char M[2][2] = { { „A‟, „B‟ }, { „C‟, „D‟ } };
Inicialización en tiempo de declaración
M[1][0]
M[0][0]
M[0][1]
M[1][1]
„A‟ „B‟ „C‟ „D‟
M
Fila 0 Fila 1
dirección
Programación II
34. Arreglos
Nótese que *M y M[0][0] son lo mismo. En
general, *(M+2*i+j) y M[i][j] son lo mismo.
Para estimar el número de bytes que ocupa
la estructura en memoria basta multiplicar el
número de elementos de cada dimensión por
el tamaño en bytes del tipo de dato de cada
elemento. Para el ejemplo anterior:
2 x 2 x sizeof(char) sizeof(M)
Programación II
36. Arreglos
Para hacer la reserva dinámica de espacio en
arreglos de más de una dimensión se hace
uso igual de apuntadores y los operadores
new y delete.
En algunas versiones del lenguaje NO es
posible hacer:
double *V = new double[N][M];
Programación II
39. Arreglos
Otra solución es hacer vectores enlazados:
[1][C-1] [0][C-1]
Dirección …
[0][1]
[0][0]
del arreglo
[1][0]
[1][1]
…
…
[F-1][0]
[F-1][1]
…
[C-1]
[F-1]
Vector de F F Vectores de
apuntadores C elementos
Programación II
41. Registros
Para su definición / declaración se requieren
tres cosas:
1) Conocer el tipo de dato posiblemente
diferente de cada uno de sus elementos.
2) Conocer el identificador único y válido de
cada uno de sus elementos.
3) Dar un identificador válido para acceder a los
elementos del conjunto.
Programación II
42. Registros
En C++ se reconocen tres tipos de registros:
1) Estructuras: derivadas del lenguaje C. Se
hace uso de la palabra reservada struct.
2) Registros invariantes (union): derivadas del
lenguaje C. Se hace uso de la palabra
reservada union.
3) Clases: agregadas en C++ para la
programación orientada a objetos. Se hace
uso de la palabra reservada class.
Programación II
43. Registros
Los registros pueden incluir tanto datos como
métodos y se almacenan en memoria un
elemento al lado del otro en el mismo orden
secuencial en el cual están definidos.
Para el caso de los métodos la memoria
correspondiente tiene el código del método ó
la dirección donde está el código
dependiendo si la definición es inline ó outline
respectivamente.
Programación II
44. Registros
Las estructuras y registros invariantes, al
igual que las clases, poseen declarativas de
interfaz (public, protected, private). Si no se
dice nada por defecto los elementos son
públicos.
Para acceder a los elementos se hace uso
del operador „.‟ si se trata de una variable /
objeto ó el operador „->‟ si se trata de una
dirección de memoria.
Programación II
45. Registros
Al igual que se hace con los datos básicos,
para acceder a la dirección de memoria
teniendo una variable / objeto declarado se
hace uso del operador „&‟.
Las estructuras y registros invariantes se
pueden anidar (definir en cascada uno dentro
del otro); las clases no.
Programación II
46. Registros
Ejemplo:
struct Tstr {
int I;
double D;
char C;
Tstr() {
I = 0; D = 0.0; C = „0‟;
}
};
typedef Tstr *pTstr;
Programación II
48. Registros
Para obtener el número de bytes del bloque
de memoria que ocupa un registro basta
aplicar el operador sizeof al tipo de dato que
se define implícitamente o la variable / objeto
correspondiente. Para el caso anterior:
sizeof(Tstr) sizeof(st) sizeof(*stp)
Nótese que no es lo mismo sizeof(stp) que
sizeof(*stp); el primero es el número de bytes
que requiere un apuntador.
Programación II
49. Registros
Ejemplo:
struct Tiempo {
struct Fecha {
int D, M, A;
} Fec;
struct Hora {
int H, M, S;
} Hor;
} t = { { 12, 8, 2010 }, { 11, 48, 0 } };
Programación II
50. Registros
La única característica que diferencia los
registros invariantes (union) de las
estructuras (struct) es que en los primeros
todos los elementos ocupan el mismo
espacio de memoria.
Son útiles para administrar una misma data
(memoria) haciendo uso de tipos de dato
diferentes.
Programación II
51. Registros
sizeof(short) =
2 x sizeof(char)
u.c[0] 0xAA
u.c[1] 0xBB
&u
dirección
Programación II
52. Registros
El número de bytes de un registro invariante
coincide con el número de bytes del elemento
que más bytes ocupa en la estructura.
Todos los elementos se organizan en
memoria de forma tal que empiezan
exactamente con el primer byte de la
estructura.
Programación II
53. Estructuras complejas
Está claro que puede haber combinación de
arreglos de arreglos, registros de registros,
arreglos de registros y registros de arreglos.
Es importante tener claro:
1) La manera como se organizan en el bloque de
memoria.
2) La manera de obtener la dirección de memoria
al bloque.
3) La manera en la que se accede a todos los
elementos.
Programación II
54. Estructuras complejas
Cada vez que se consigue en el camino un
arreglo, se requieren tantos índices como
dimensiones tenga el arreglo para acceder al
elemento y se hace uso del operador de
direccionamiento „[ ]‟.
Cada vez que se consigue en el camino un
registro, se requiere el operador „.‟ para
acceder al elemento que se desee.
Programación II
57. TAD particulares
1) Conjuntos finitos hasta un máximo de
elementos y sus respectivas operaciones:
agregar, suprimir, pertenencia,
cardinalidad, unión, intersección,
diferencia, lleno, vacío.
2) Pilas: conjuntos particulares cuyas
operaciones de agregación/supresión
siguen el algoritmo LIFO (Last In First Out
– último en entrar, primero en salir).
Programación II
58. TAD particulares
3) Colas: conjuntos particulares cuyas
operaciones de agregación/supresión
siguen el algoritmo FIFO (First In First Out
– primero en entrar, primero en salir).
4) Dipolo: colas en los dos sentidos dos
operaciones de agregación y dos
operaciones de supresión.
Programación II
59. TAD particulares
3) Buffer circular: Se maneja como una cola
pero no hay límite sobre los elementos a
agregar. Primeros elementos agregados
pueden ser reemplazados por nuevos
elementos si la velocidad de agregación es
mayor que la de supresión.
Programación II