Estructuras de Datos Dinámicas

Anuncio
Estructuras de Datos Dinámicas
Programación Modular
Curso 2002-2003
David Bueno Vallejo
ÍNDICE
I.1.
INTRODUCCION A LAS ESTRUCTURAS DE DATOS DINÁMICAS............................................1
I.2.
EL TIPO PUNTERO...................................................................................................................................3
I.2.1. OPERACIONES CON PUNTEROS.............................................................................................................. 6
DESREFERENCIACIÓN.............................................................................................................................. 7
ASIGNACIÓN Y COMPARACIÓN.............................................................................................................. 7
PASO DE PUNTEROS COMO PARÁMETROS .......................................................................................... 8
I.3.
APLICACIÓN: LISTAS ENLAZADAS..................................................................................................8
I.3.1. OPERACIONES BASICAS SOBRE LISTAS ENLAZADAS. .......................................................................... 9
INSERTAR UN NODO AL PRINCIPIO .................................................................................................... 10
ELIMINAR EL PRIMER NODO. .............................................................................................................. 11
INSERTAR UN NODO EN UNA LISTA ENLAZADA ORDENADA ........................................................ 12
ELIMINAR UN NODO DE UNA LISTA ENLAZADA.............................................................................. 14
Estructuras de Datos Dinámicas
I.1.
1
INTRODUCCION A LAS ESTRUCTURAS DE DATOS DINÁMICAS
Hasta ahora, todos los tipos de datos que se han visto, ya sean simples o estructurados,
tienen una propiedad común: son estáticos. Esto significa que las variables que se declaran
en un programa de alguno de estos tipos mantendrán la misma estructura durante la
ejecución del mismo. Son variables estáticas, se definen en tiempo de compilación.
Ejemplos:
o Si se declara una variable entera, real, etc, puede cambiar su contenido, pero no el
tamaño asignado por el compilador para ella.
o Si se declara un Array de 5 elementos de tipo Z, éste podrá cambiar su contenido,
pero no su estructura.
Sin embargo, hay muchas situaciones en las que no sólo debe cambiar el contenido o valor
de una variable, sino también su tamaño. Esto es, hay situaciones en las que en tiempo de
compilación no se puede determinar la cantidad de memoria necesaria para almacenar
cierta información, sino que hay que esperar al tiempo de ejecución.
La técnica usada para manejar estas situaciones es la asignación dinámica de memoria.
Con este tipo de asignación se tendrán variables dinámicas o referenciadas (ya se verá el
motivo por el que se denominan de esta segunda forma), que pueden crearse y destruirse
en tiempo de ejecución.
Ejemplo: Si se pide diseñar un programa para gestionar una agenda de teléfonos con los
datos de personas (nombre, apellidos, dirección, cumpleaños, teléfono, email, etc...). ¿Qué
estructura puede utilizarse para realizarla?
Con los conocimientos adquiridos hasta este tema, parece razonable utilizar un array de
personas. Una posible definición sería:
Constantes
MAXAG = 50
Tipos
REGISTRO TPersona
TCadena nombre, apellidos, dirección, email
Fecha cumpleaños
N telefono
TPersona TAgenda[1..MAXAG]
Variables
TipoAgenda ag
Si se usa un array de caracteres de MAXAG posiciones pueden aparecer los siguientes
problemas:
1. ¿Qué pasa si se quieren insertar más personas de MAXAG en la agenda?
? Hay que recompilar el programa para ampliar MAXAG
2. Si el número de personas de la agenda es mucho menor que MAXAG se está
desperdiciando memoria que podría ser utilizada por otros programas
Estructuras de Datos Dinámicas
2
Solución:
...
Sería deseable no tener que reservar
al principio memoria para MAXAG
Ag[1]
Nombre1,...
Nombre2,...
personas, sino que cada vez que se
fuera a insertar una persona en la Ag[2] Nombre2,...
Ag[3]
agenda, se pidiera sólo la memoria
Nombre1,...
Nombre3,...
necesaria para los datos de esa Ag[4] Nombre4,...
persona. Como puede verse en la Ag[5]
Fig1.a., cuando se define un array, se
Nombre4,...
reserva una zona contigua de
Nombre3,...
memoria
del
tamaño Ag[50]
MAXAG*Tamaño(TPersona). En la
Fig.1.b., se supone disponible la
Variable estática: Ag [1..50]
posibilidad de reservar memoria de
Fig 1. a) Agenda estática b) Reserva dinámica de memoria
forma dinámica, es decir, conforme
se va necesitando. Pero parece que falta algo más. Cuando se reserva memoria los datos
no tienen porque estar contiguos, ni siquiera ordenados, ya que el sistema operativo
devuelve una porción de memoria libre del tamaño solicitado y ésta puede estar en
cualquier lugar de la memoria. En la Fig 1.b., puede verse que los datos no están ni
contiguos ni ordenados. Por lo tanto parece necesario otra herramienta que permita
relacionar cada uno de los registros. Esa herramienta está representada en Fig 2.b. por una
flecha que une los distintos registros. Es decir, si se pueden unir los registros mediante esas
flechas (a las que llamaremos punteros y que se desarrollaran en el próximo apartado), no
importará el lugar donde se
encuentre cada registro. También
Ag[1]
Nombre1,...
Nombre2,...
*
puede verse que cada uno de estos
Ag[2]
elementos no tienen nombre
Nombre2,...
Ag[3]
(variables anónimas). En la versión
*
Nombre1,...
Nombre3,...
con array, todos los elementos
Ag[4]
Ag
Nombre4,...
tenían un nombre (ag[1], ag[2],
Ag[5]
etc...). Hasta este punto se dispone
Nombre4,...
*
de un conjunto de elementos
Ag[50]
Nombre3,...
*
unidos, pero como no tienen
nombre se necesita una referencia
para el principio. Para ello es
Variable estática: Ag [1..50]
* Variable sin nombre (Anónimas)
necesario definir la variable ag en
la Fig. 2.b, que apuntará al primer
Fig. 2.a) Agenda con memoria Estática b) Agenda con
memoria dinámica
elemento.
...
En este apartado se ha intentado mostrar la necesidad de utilizar reserva dinámica de
memoria, y las herramientas necesarias, para poder llevarla a cabo. En el siguiente
apartado se verá con más detalle como se puede reservar memoria durante la ejecución,
Estructuras de Datos Dinámicas
3
además de las características de un nuevo tipo llamado puntero que servirá para unir las
distintas porciones de memoria reservada en el ejemplo anterior.
Estructuras de Datos Dinámicas
4
I.2. EL TIPO PUNTERO.
Siempre que se habla de punteros hay que diferenciar claramente entre lo que es:
- Variable referencia o apuntadora (o simplemente puntero)
- Variable referenciada o apuntada.
Una variable de tipo Puntero (variable referencia) contendrá una referencia, esto es, la
dirección en memoria de una variable de un tipo determinado (variable referenciada).
Por tanto, siempre existirá asociado al tipo Puntero, otro tipo que es el de la variable
referenciada. Cuando se defina un tipo Puntero, en el pseudocodigo hay que escribir:
Tipos
Tipo *TipoP
donde Tipo es el tipo de la variable referenciada. Por ejemplo:
Z *TipoP
Para declarar una variable de ese tipo:
Variables
TipoP p
En un momento dado, la variable p será un puntero, o lo que es lo mismo, contendrá la
dirección en memoria de una variable de tipo entero, que a su vez, contendrá un entero. En
el siguiente ejemplo se muestra como quedaría en memoria la representación de una
variable puntero (ptr) que apunta a un entero con valor 100:
Dirección de
memoria
MEMORIA
ptr
Contenido de
la memoria
Variable
23423
23419
234343
23420
324237
23421
28
23422
100
23423
Anónima
Fig. 3. Representación de la memoria de la variable puntero ptr
Dir
23419
p
23423
Dir
23423
100
Estructuras de Datos Dinámicas
5
Así es como realmente aparece en memoria, pero de forma más gráfica se representará
esa situación como:
p
100
de la variable puntero p saldrá una flecha hacia la variable referenciada correspondiente.
Para acceder a la variable apuntada (referenciada) hay que hacerlo a través de la
variable puntero, ya que aquella no tiene nombre (por esto, también se le denomina
variable anónima). La sintaxis para ello es *p. En nuestro ejemplo *p = 100. Si se
quiere acceder a un campo de un registro se utilizará p->
Ejemplo:
Tipos
REGISTRO TipoRegistro
Z num
C car
FINREGISTRO
TipoRegistro *TipoPuntero
Variables
TipoPuntero p
Con esto:
*p
p->num
p->car
Será un registro con dos campos.
Será una variable de tipo entero.
Será una variable de tipo carácter.
p
4
'A'
Si se desea que una variable de tipo Puntero (a algo), en un momento determinado de la
ejecución del programa no apunte a nada, se le asignará la palabra reservada NIL.
Gráficamente se representará como:
p = NIL
p
En este punto ya se sabe como definir tipos Punteros y como declarar variables de tipo
Puntero. Pero ¿por qué los punteros nos ayudan en la creación dinámica de variables
conforme se van necesitando?
Cuando se declara una variable de tipo Puntero, por ejemplo: TipoP p, la variable p se
establece estáticamente, en tiempo de compilación, pero la variable referenciada o
Estructuras de Datos Dinámicas
6
anónima no se crea en este momento, sino que se creará después, en tiempo de
ejecución (variable referenciada <=> variable dinámica), mediante una llamada a una
función predefinida encargada de localizar y reservar una zona de memoria de tamaño
suficiente para contener a la variable referenciada. Esta función tendrá como argumento el
tamaño de la variable referenciada a crear, y devolverá la dirección de memoria de la zona
reservada. Dicha dirección de memoria se deberá almacenar en una variable de tipo
puntero con objeto de acceder posteriormente a la variable dinámica creada. Ejemplo:
p := Reservar(Tamaño(Z))
La ejecución de esta sentencia creará una variable anónima de tamaño suficiente para
almacenar un entero, y hará que p apunte a ella.
p
Tamaño de un
entero
La función predefinida Tamaño(Tipo), previamente utilizada, nos devuelve el nº de bytes
que ocupa una variable del tipo de datos especificado como parámetro. Para simplificar,
en nuestro pseudolenguaje, la sentencia de asignación anterior (para cualquier tipo de datos
de la variable referenciada) se sustituirá por la siguiente sentencia:
Asignar(p)
No se puede olvidar que crear una variable no es más que reservarle una zona de
memoria. Al igual que se pueden crear variables, también se pueden eliminar (liberar la
memoria que ocupan para que pueda ser reutilizada) dinámicamente mediante el
procedimiento predefinido Liberar(p,t), cuya ejecución hace que se libere el
espacio de memoria de tamaño t apuntado por p, haciendo además que p tome el valor
NIL. Gráficamente:
p
Se libera
Al igual que se abrevia la sintaxis para crear variables dinámicas, también se simplificará
para eliminarlas. Para ello, se prescindirá del parámetro t en la operación Liberar.
I.2.1.
OPERACIONES CON PUNTEROS
Estructuras de Datos Dinámicas
7
1.- Desreferenciación
2.- Asignación
3.- Comparación.
4.- Paso de punteros como parámetros.
Sea el siguiente ejemplo de TipoPuntero:
Tipos
REGISTRO Complejo
R PartReal, PartImag
FINREGISTRO
Complejo *PunteroComplejo
Variables
PunteroComplejo punt1, punt2
DESREFERENCIACIÓN
Si se realiza la llamada:
Asignar(punt1)
punt1
La nueva variable no tiene valor alguno definido. El programador podrá, por ejemplo,
asignarle:
punt1->PartReal = 5.0
punt1->PartImag = 1.2
punt1
5.0 1.2
La operación mediante la cual nos referimos a la variable anónima añadiendo ^ al nombre
de la variable puntero se denomina desreferenciación.
ASIGNACIÓN Y COMPARACIÓN
Al igual que en cualquier tipo de datos visto anteriormente, en el tipo puntero también
están predefinidas las operaciones de asignación (=) y comparación de
igualdad/desigualdad (==, !=).
A una variable puntero se le puede asignar el valor contenido en otra variable puntero del
mismo tipo. Esto implica que tras la operación ambas variables apuntarán a la misma zona
de memoria, es decir, a la misma variable referenciada. Ejemplo:
punt2 = punt1
Estructuras de Datos Dinámicas
8
punt1
5.0 1.2
punt2
La palabra reservada NIL se le puede asignar a cualquier variable puntero sea cual sea el
tipo de ésta. Ejemplo:
punt2 = NIL
punt2
La operación de comparación de igualdad con punteros se utiliza para saber si dos
variables puntero (del mismo tipo) apuntan a la misma variable referenciada (punt1 ==
punt2) o para determinar si una variable puntero contiene el valor NIL (punt1 ==
NIL).
PASO DE PUNTEROS COMO PARÁMETROS
El paso de parámetros por valor y por referencia de punteros se comporta de igual
forma que con cualquier otro tipo de datos. Así:
? Si un parámetro real puntero p se pasa por valor, el parámetro formal q
correspondiente obtendrá una copia del valor de dicho puntero. Ambas variables
puntero referenciarán a la misma variable dinámica. Una modificación de esta variable
dinámica dentro del subprograma, perdurará tras la ejecución del mismo. En cambio
una modificación de q, no afectará al parámetro real p.
? Si un parámetro real puntero p se pasa por referencia, el parámetro formal q
correspondiente obtendrá una referencia a dicho puntero. Cualquier modificación de q,
se verá reflejado en p.
I.3. APLICACIÓN: LISTAS ENLAZADAS
Lo que realmente hace de los punteros una herramienta potente es la circunstancia de
que pueden apuntar a variables que a su vez contienen punteros (Ejemplo, registros en los
que uno de sus campos es a su vez un puntero).
Ejemplo: Se desarrollará mediante punteros el tratamiento de la agenda que introdujo en la
problemática de las variables estáticas/dinámicas. A continuación se definen los tipos y las
variables:
Estructuras de Datos Dinámicas
9
Tipos
TNodo *TPuntANodo
REGISTRO TNodo
TPersona info
TPuntANodo sig
FIN
Variables
TPuntANodo
ag
Nombre2,...
Nombre1,...
Ag
En la Fig. 4 se vuelve a representar la agenda en memoria.
Ahora es fácil comprender que la variable ag es un puntero
de tipo TpuntAnodo y que cada elemento tiene los datos de
una persona y un puntero al siguiente elemento de la
agenda. Para saber cual es el último elemento es necesario
que su puntero al siguiente elemento este a NIL.
Nombre4,...
Nombre3,...
Fig 4. Agenda en memoria
Si se desplegase la agenda de forma lineal se tendría lo siguiente:
ag
Nom br e1, ..
Nom br e2, ..
Nombr e3, ..
Nombre4,. .
A este tipo de estructuras se les denomina Lista Enlazada y a cada uno de los registros
que forman la misma se le denomina nodo. En general, un nodo de una lista enlazada
puede contener toda la información que se desee (todos los campos que se quieran), más
un campo de tipo Puntero, que apuntará al siguiente registro.
I.3.1.
OPERACIONES BASICAS SOBRE LISTAS ENLAZADAS.
En este apartado se verán alguna de las operaciones que se pueden realizar sobre las
listas enlazadas. Como elemento de cada nodo se utilizará un entero. Esto dará lugar a una
lista de enteros:
Tipos
TNodo *ListaEnt
REGISTRO TNodo
Z dato
ListaEnt sig
FINREGISTRO
Variables
ListaEnt lista, ptr
Estructuras de Datos Dinámicas
10
INSERTAR UN NODO AL PRINCIPIO
En la lista del ejemplo siguiente se quiere insertar al principio de la misma un nodo con
un valor 5 en su campo entero.
lista
3
9
2
Los pasos que habría que dar una vez creada la lista, para insertar dicho nodo serían:
1.- Asignar(ptr)
ptr
2.- ptr->dato = 5
ptr
5
3.- ptr->sig = lista
lista
3
2
ptr
5
4.- lista = ptr
9
Estructuras de Datos Dinámicas
11
lista
3
9
2
ptr
5
ELIMINAR EL PRIMER NODO.
Partiendo de la lista:
lista
3
2
9
3
2
9
3
2
9
1.- ptr = lista
ptr
lista
2.- lista = lista->sig
ptr
lista
3.- Liberar(ptr)
ptr
2
lista
9
Estructuras de Datos Dinámicas
12
INSERTAR UN NODO EN UNA LISTA ENLAZADA ORDENADA
Se quiere insertar, por ejemplo, el valor 7 en la lista:
lista
2
9
6
Además de lista y ptr será necesario otra variable de tipo Puntero
nuevonodo.
Las acciones a realizar son:
1.- Asignar(nuevonodo)
nuevonodo
2.- nuevonodo->dato = 7
nuevonodo
7
3.SI lista == NIL ENTONCES (* lista vacia *)
nuevonodo->sig := NIL
lista = nuevonodo
SINOSI nuevonodo->dato <= lista->dato ENTONCES
(* insertar al principio *)
nuevonodo->sig = lista
lista = nuevonodo
SINO (* insertar en medio o al final *)
ptr = lista
MIENTRAS (ptr->sig != NIL) AND
((nuevonodo->dato)>(ptr->sig->dato)) HACER
ptr = ptr->sig
FINMIENTRAS
nuevonodo->sig = ptr->sig
ptr->sig = nuevonodo
FINSI
Estructuras de Datos Dinámicas
13
ptr
lista
2
9
6
7
nuevonodo
Estructuras de Datos Dinámicas
ELIMINAR UN NODO DE UNA LISTA ENLAZADA
El nodo a eliminar será el que contenga el número almacenado en valor.
Además de lista se necesitan 2 variables de tipo Puntero ptr y ant.
SI lista != NIL ENTONCES (* lista no vacia *)
SI (lista->dato == valor) ENTONCES
(* eliminar el primer nodo *)
ptr = lista
lista = lista^.sig
Liberar(ptr)
SINO (* buscar dato en resto de la lista *)
ant = lista
ptr = lista^.sig
MIENTRAS ( (ptr != NIL) AND
(ptr^.dato != valor)) HACER
ant = ptr
ptr = ptr->sig
FINMIENTRAS
SI (ptr != NIL) ENTONCES (* encontrado *)
ant->sig = ptr->sig
Liberar(ptr)
FINSI
FINSI
FINSI
14
Descargar