UNIDAD 3: Estructuras de Datos Dinámicas

ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
UNIDAD 3: Estructuras de
Datos Dinámicas
Se sabe que la memoria es un elemento indispensable para llevar a cabo
operaciones sobre objetos del mundo real. En la memoria es posible describir los objetos
mediante una serie de características y trabajar con ellos de manera abstracta.
Por otro lado, el modelo actual de computadora dispone de una memoria con
unidades capaces de mantener información fundamentalmente numérica. Por su
definición y tecnología estas unidades constan de una dirección y contenido
00000000
Dirección
Contenido
UNIDAD O LOCALIDAD DE MEMORIA
La información se haya distribuida en un espacio que hereda características
secuenciales fijas. No es posible disponer objetos tal como en el espacio real lo hacemos.
El almacenamiento dinámico pretende definir estructuras de información para poder
operar con estas características de los objetos como sucede en el mundo real.
3.1.- Apuntadores.
Dentro de las características de un lenguaje de programación existe cierto tipo de
estructuras con capacidad de cambiar de tamaño que las distingue claramente de las
estructuras fundamentales como son arreglos, registros o conjuntos (ya vistos). La técnica
que se utiliza para trabajar este tipo de estructuras implica la asignación dinámica de
almacenamiento, es decir, asignación de espacio en la memoria para componentes
individuales en el momento en que entran en existencia durante la ejecución de un
programa, en vez de en un instante de transición. El compilador asigna después una
cantidad fija de almacenamiento para contener la dirección de la componente asignada
dinámicamente y no de la componente misma. Por ejemplo, el árbol genealógico que se
ilustra a continuación será representado por registros individuales, uno por cada persona.
Estas personas se vinculan por sus direcciones asignadas a los campos digamos de
padre y madre respectivos (véase el uso de flechas o apuntadores en la figura.
ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
TED
FRED
MARY
ADAM
F
F
EVA
F
F
F
F
Actualmente se ha vuelto común en los lenguajes de programación avanzados
hacer posible la manipulación explícita de referencias de datos (mediante apuntadores)
además de los datos mismos. Esto implica que debe existir una distinción notacional entre
los datos y las referencias de estos y que en consecuencia debe introducirse tipos de
datos cuyos valores sean apuntadores (referencias) de otros datos.
Un apuntador es una celda, unidad o localidad de memoria cuyo valor indica,
señala o apunta a otra localidad mediante una dirección. La notación que se emplea para
este objeto es la siguiente:
Tipo T= Apuntador a tipo_elemento
Esta declaración indica que los valores del tipo T son apuntadores de datos del
tipo tipo_elemento.
Así, cuando se representan gráficamente estructuras de datos, el hecho de que
una localidad A sea un apuntador a la localidad B, se indica con una flecha de A a B.
En PASCAL por ejemplo, se puede crear una variable ap que apunte a localidades
de memoria de un tipo determinado de datos mediante la declaración:
var
ap: tipo_elemento
De acuerdo con esto, la expresión ap denota el valor (de tipo_elemento) de la
localidad a la que apunta ap.
ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
Decimos entonces que los valores de los tipos apuntadores se generan siempre
que un elemento de datos es asignado dinámicamente.
ap: t
11110010
11110010
ap: t
ASIGNACION DINAMICA DE VARIABLE
Regresando a la figura del árbol genealógico, se vuelve a mostrar a continuación,
sólo que ahora (siguiendo con la distinción notacional) los apuntadores de personas
desconocidas se simbolizan por la palabra NIL ( o NULL en otros casos).
TED
FRED
ADAM
NIL NIL
NIL
MARY
NIL
EVA
NIL
NIL
Volviendo a hacer referencia a la figura de arriba, supóngase que Fred y Mary son
hermanos, es decir, tienen el mismo padre y madre. Esta situación se expresa fácilmente
reemplazando los dos valores NIL en los campos respectivos de los dos registros.
Considerar a los apuntadores como elementos de datos explícitos en vez de cómo
auxiliares ocultos en la instrumentación permite al programador expresar con claridad
donde se intenta establecer compartición de almacenamiento y donde no. En secciones
posteriores dedicaremos el estudio de la generación y manipulación de estructuras de
datos cuyas componentes se vinculan por medio de apuntadores explícitos.
ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
3.2.- Implantación de listas mediante apuntadores.
Otra forma de implantar una lista como opción alternativa a la presentada en el
capítulo anterior, es utilizar apuntadores para enlazar elementos consecutivos bajo esta
estructura de datos abstracta.
Esta implantación permite eludir el empleo de memoria contigua para almacenar
una lista, y por tanto también elude los desplazamientos de elementos para hacer
inserciones o rellenar vacíos creados por la eliminación de elementos. Sin embargo el
precio que hay que pagar es el de un espacio adicional para los apuntadores.
En este sentido una lista (enlazada) consta de una serie de registros que no
necesariamente son adyacentes en memoria. Cada registro contiene el elemento y un
apuntador a un registro que contiene su sucesor. A éste se le llama el apuntador
siguiente (sig). El apuntador siguiente de la última celda apunta a nil cuyo valor esta
definido en la mayoría de los lenguajes de programación como lo es PASCAL o C y no
puede confundirse con ningún otro apuntador. Un valor característico utilizado para nil es
regularmente el valor 0 (cero). La siguiente figura muestra una lista enlazada de esta
manera:
clientes
















nil
nil
empleados
Listas ligada basada en apuntadores
Recordemos que una variable apuntador sólo es una variable que contiene la
dirección donde otro dato está almacenado. Así, si p se declara como apuntador a un
registro, el valor almacenado en p se interpretará como el lugar, en memoria principal,
donde se puede encontrar un registro.
ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
Para tener acceso a un campo del registro se emplea se emplea
p.nombre_campo donde nombre_campo es el nombre de campo que se desea
examinar. La siguiente figura muestra la representación real de la lista mostrada
anteriormente
clientes





800
712
992
692
0
1000
800
712
992
692
La lista contiene 5 registros, los cuales están en las posiciones de memoria 1000,
800, 712, 992 y 692, respectivamente. El apuntador siguiente del primer registro tiene el
valor 800 y da la indicación de donde se encuentra el segundo registro. Lo mismo pasa
con los demás.
Obviamente, a fin de tener acceso a la lista se necesita conocer donde se puede
encontrar la primer celda de la lista (encabezado o header). Una variable apuntador
puede servir para este propósito. Hay que recordar que un apuntador sólo es un número
aunque para hacer más gráfica las ilustraciones, se utilizarán las flechas.
El tipo de una lista es entonces el mismo que el de una posición; es un apuntador
a una celda particular: el encabezado. La siguiente es una definición formal de las partes
esenciales de una estructura de datos de lista enlazada:
Tipo
ap_nodo = nodo
nodo= Registro
elemento: tipo_elemento
sig: ap_nodo
Fin
LISTA = ap_nodo
posición = ap_nodo
El siguiente pseudocódigo muestra la función Esta_vacia(L) la cual verifica si la
lista esta o no vacía, es decir, si el apuntador del inicio de la lista es o no nil.
ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
Función Esta_vacia(L:LISTA): boolean
Inicio
Si L.sig=nil
Entonces regresa true
Sino regresa false
Fin
Función para comprobar si la lista enlazada está vacía
Las siguientes rutinas implementan las operaciones más comunes que se realizan
en el manejo de listas con apuntadores.
Funcion es_ultimo(p: posición; L:LISTA): booleano
Inicia
Si p.sig=nil
Entonces regresa true
Sino regresa false
fin
Función para comprobar si la posición actual es la última en la lista enlazada
Funcion Buscar(x:tipo_elemento;L:LISTA): posicion
Inicia
Variable
p: posición
encontrado: booleano
encontrado  falso
p  L.sig
mientras (p <> nil) y (no encontrado) hacer
Si p .elemento = x
Entonces encontrado  true
Else p  p .sig
Regresa p
Fin
Función para buscar un elemento en la lista enlazada
ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
Procedimiento Insertar(x:tipo_elemento; L:LISTA; p:posicion)
Inicia
Variable
Celda_Temp: posición
New(celda_Temp)
Celda_temp.elemento  x
Celda_temp.sig  p.sig
p.sig  celda_temp
Fin
Rutina de inserción para listas enlazadas
Procedimiento Eliminar_lista(var L:LISTA)
Inicia
Variable
p,temp: posición
p  L.sig
L.sig  nil
Mientras p <> nil hacer
Temp  p.sig
dispose(p)
p  Temp
Fin
Forma correcta de eliminar una lista
3.2.1.- Listas doblemente enlazadas.
En algunas aplicaciones puede ser deseable recorrer una lista ligada tanto hacia
delante como hacia atrás. O bien, dado un elemento podría desearse determinar con
rapidez el siguiente y el anterior nodo a dicho elemento. La implantación estandar no
ayuda en esto pero la solución es simple: Sólo se añade un campo adicional a la
estructura de datos, que contiene un apuntador a la celda anterior. Véase la siguiente
figura:
ESTRUCTURAS DE DATOS
nil
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ












nil
LISTA DOBLEMENTE LIGADA
El costo de esto es la presencia de un apuntador adicional en cada nodo y los
procedimientos más lentos para algunas de las operaciones básicas con listas. La
declaración de un nodo de una lista ligada podría ser la siguiente:
Tipo
ap_nodo = nodo
nodo= Registro
elemento: tipo_elemento
sig,ant: ap_nodo
Fin
LISTA = ap_nodo
posición = ap_nodo
3.3.- Implantación de pilas mediante apuntadores.
Una pila se puede ver también como una lista, por tanto, cualquier implantación de
listas enlazadas de forma simple funciona para este tipo de dato abstracto. Analicemos
entonces dicha implantación.
De acuerdo a las operaciones naturales de una pila (ya vistas en el capítulo
anterior), se realiza una operación meter insertando un nodo al frente de la lista. Se
efectúa una operación sacar eliminando el elemento o nodo que esta al frente de la lista.
Una operación tope sólo examina el elemento del frente de la lista, devolviendo su valor.
A veces las operaciones sacar y tope se combinan en una.
A continuación se dan las definiciones en pseudocódigo de esta forma de
implantación:
ESTRUCTURAS DE DATOS
Tipo
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
ap_nodo = nodo
nodo= Registro
elemento: tipo_elemento
sig: ap_nodo
Fin
PILA = nodo
La implantación para saber si una pila esta o no vacía se lleva a cabo de la misma
manera que en el caso de la implantación con arreglos pero ahora con apuntadores, es
decir:
Función vacia(p: PILA): booleano
Inicia
Si psig =nil
Entonces regresa verdadero
Sino regresa falso
fin
La creación de una pila vacía también es sencilla. Basta con crear una cabecera
con un apuntador sig a nil.
Procedimiento crear_vacia(var p:PILA)
Inicia
New(p)
psig =nil
Fin
El meter se implanta como inserción al frente de la lista enlazada, donde el frente
de la lista sirve como el tope de la pila.
Procedimiento meter(x:tipo_elemento; p:PILA)
var
celda_temp: nodo
Inicia
New(celda_Temp)
Si celda_temp=nil
Entonces error(“Pila llena”)
Sino celda_temp.elemento  x
celda_temp.sig  p.sig
p.sig celda_temp
Fin
ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
El tope o cima se efectúa examinando el elemento de la primera posición de la
lista:
Función cima(var p:PILA ): tipo_elemento
Inicia
Si vacia(p)
Entonces error(“Pila vacía”)
Sino regresa(p.elemento)
Fin
Finalmente la implantación de la operación sacar que elimina el nodo del frente de
la lista es la siguiente:
Procedimiento sacar(p:PILA)
var
celda_temp: nodo
Inicia
Si vacia(p)
Entonces error(“pila vacia”)
Sino celda_Temp p .sig
p.sig  p.sig.sig
dispose(celda_temp)
Fin
3.3.- Implantación de colas mediante apuntadores.
Igual que en el caso de las pilas, cualquier implantación de listas es lícita para las
colas. Así, para aumentar la eficacia de operaciones tales como “pon en la cola”, es
posible aprovechar el hecho de que las inserciones se efectúan sólo en el extremo
posterior de ésta estructura de datos, es decir; en lugar de recorrer la lista de principio a
fin cada vez que se desea insertar un elemento o nodo, se puede mantener un apuntador
al último elemento.
La implantación de colas que aquí se desarrolla se basa en apuntadores. Por tanto
es posible definir una cola como una estructura que consiste en apuntadores al extremo
anterior de la lista y al extremo posterior. Esta convención permite representar de manera
simple una cola vacía:
ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
Tipo
ap_nodo = nodo
nodo= Registro
elemento: tipo_elemento
sig: ap_nodo
Fin
COLA= Registro
ant,post: nodo
Fin
A continuación se muestran las implantaciones de las cinco operaciones utilizadas
con mayor frecuencia en una estructura de datos tipo cola
Procedimiento anula(var c: COLA)
Inicio
New(c.ant)
c.ant.sig  nil
c.post  c.ant
fin
Procedimiento que crea una cola con encabezado en la primera y ultimas celdas

c.ant
c.post
ANULA
ESTRUCTURAS DE DATOS
OTOÑO 2014
DR. MARIO ROSSAINZ LOPEZ
Funcion vacia(c:COLA):booleano
Inicio
Si c.ant = c.post
Entonces regresa true
Sino regresa falso
Fin
Función que verifica si una cola esta o no vacía
Funcion Frente(c:COLA): tipo_elemento
Inicio
Si vacia(c)
Entonces error(“la cola esta vacia”)
Sino regresa (c.ant.sig.elemento)
Fin
Función que regresa el elemento que se encuentra al frente de la cola
Procedimiento pon_en_cola(x:tipo_elemento; var c:COLA)
Inicio
New(c.post.sig)
c.post  c.post.sig
c.post.elemento  x
c.post.sig  nil
Fin
Procedimiento que coloca en la cola un elemento

c.ant
x

c.post
PON_EN_COLA(x,c)
ESTRUCTURAS DE DATOS
OTOÑO 2014

c.ant
DR. MARIO ROSSAINZ LOPEZ
x

y

c.post
PON_EN_COLA(y,c)
Procedimiento quita_de_cola(var c:COLA)
Inicio
Si vacia(c)
Entonces error(“la cola esta vacía”)
Sino c.ant  c.ant.sig
Fin
Procedimiento que elimina o quita un elemento de la cola

c.ant
x

y

c.post
QUITA_DE_COLA(c)