TEMA 3: TIPOS ABSTRACTOS DE DATOS. TIPOS LINEALES
Í
NDICE.
3.1 Tipos Abstractos de Datos (TAD´s)
Especificación e Implementación
Programación con TADs
Ejemplo: Números complejos
3.2 Definición y especificación de tipos lineales
Pilas
Colas
Listas con punto de interés
3.3 Implementaciones de los tipos lineales
Asignación dinámica de memoria. Punteros
Implementaciones del tipo Pila
Implementaciones del tipo Cola
Implementaciones del tipo Lista con punto de interés
O
BJETIVOS.
Reforzar a través de la definición de abstracción de datos las diferencias entre especificación (TAD) e implementación de una abstracción.
Estudiar los tipos (abstractos) de datos lineales: listas, pilas y colas. Analizar sus distintas implementaciones (representación de los datos y diseño de los algoritmos de manejo).
Estudiar los mecanismos de asignación dinámica de memoria y el tipo puntero.
Estudiar algunas aplicaciones concretas de estas estructuras de datos.
B
IBLIOGRAFÍA.
Aho, Hopcroft, Ullman. “Estructuras de Datos y Algoritmos.”
Addison Wesley Iberoamericana, 1988 (edición en inglés de 1983).
Capítulo1, capítulo 2 (apartados 1, 2, 3 y 4).
Franch X. “Estructura de Dades. Especificació, disseny i implementació”. Edicions UPC, 1993. Capítulos 3 y 7.
3.1 T
IPOSA
BSTRACTOS DED
ATOS(TAD
S).
Un grupo de operaciones que actúan sobre un mismo tipo de datos (conjunto de valores)
Con la restricción de que el comportamiento de estas operaciones define al tipo.
“Lo interesante de un tipo de datos NO es tanto los valores que lo constituyen y MUCHO MENOS su representación concreta, como la clase de manipulaciones a las que se le puede someter o las propiedades que
satisface” (F. Orejas).
Ejemplos de abstracción de datos:
1.- Tipos elementales de un lenguaje de programación. La definición de la abstracción de datos entero sería:
entero
+ -
*
IMPLEMENTACION
2.- Los tipos definidos por el usuario no tienen operadores predefinidos. Se deben definir como (sub)algoritmos. Definición de la abstracción de datos conjunto:
conjunto
U
-
IMPLEMENTACION
U
E
SPECIFICACIÓN E IMPLEMENTACIÓN DETAD
S Como ocurre con toda abstracción, tanto de operaciones como de datos, debemos distinguir dos niveles en su DISEÑO:1.- Especificación o definición ¿qué es? o ¿qué hace? la abstracción. Esta definición puede ser formal o informal.
Ejemplo: Especificación de una abstracción de operaciones mediante su pre/postcondición expresadas mediante el lenguaje lógico de primer orden.
2.- Implementación o ¿Cómo es? o ¿Cómo lo hace? en un determinado entorno informático.
Ejemplo: Implementación de una abstracción de operaciones mediante las “function” y “procedure” de los lenguajes de programación.
La ESPECIFICACIÓN DE UN TIPO DE DATOS debe incluir:
Nombre del tipo [parámetro1, ... , parámetron]
Lista de operaciones sobre el tipo: sintaxis o perfil de las operaciones. Se dará como cabecera de procedimiento o función
Semántica de las operaciones sobre el tipo: precondición y postcondición
P
ROGRAMACIÓN CONTAD
SVENTAJAS:
Independencia de la implementación del tipo.
Programas más portables, legibles, reutilizables.
Mayor seguridad de la información, menos efectos
colaterales.
Mayor facilidad para comprobar la corrección.
TADs y Lenguajes de Programación:
Los lenguajes de programación posteriores al 1975 soportan el concepto de TAD (Ej. ADA, Modula-2, C++)
Si el lenguaje no permite el diseño basado en TADs será necesario suplir esta insuficiencia con una disciplina en su uso: usar sólo las posibilidades del mismo que no violen los principios de ocultamiento establecidos en el diseño (Ej.
PASCAL, C, FORTRAN, COBOL)
E
JEMPLO:
NÚMEROS COMPLEJOSPara poder resolver problemas en los que se utilizan números complejos se define el tad número complejo mediante:
- una representación posible (forma cartesiana) - operaciones de transformación de tipos:
- creaC, crea un complejo a partir de dos reales - prealC, obtiene la parte real de un complejo - pimgC, obtiene la parte imaginaria de un complejo - operaciones aritméticas entre complejos:
- sumaC, restaC, multC, divsnC y módC
Especificación del Tad Número complejo
TIPO complejo SINTAXIS
función creaC(pr,pi:real):complejo;
función prealC(c:complejo):real;
función pimagC(c:complejo):real;
función sumaC(c1,c2:complejo):complejo;
función restaC(c1,c2:complejo):complejo;
función multC(c1,c2:complejo):complejo;
función divsnC(c1,c2:complejo):complejo;
función módC(c:complejo):real;
SEMÁNTICA
{ pr=PR pi=PI } c:=creaC(pr,pi) { c=(PR + PIi) } { c = (A + Bi) }
r :=prealC(c) { r = A }
{ c = (A + Bi) } r :=pimagC(c) { r = B }
{ c1 = (A + Bi) c2 = (C + Di) } c3:=sumaC(c1,c2)
{ c3 =(A+C) + (B+D)i }
{ c1 = (A + Bi) c2 = (C + Di) } c3:=restaC(c1,c2)
{ c3 =(A-C) + (B-D)i }
{ c1 = (A + Bi) c2 = (C + Di) } c3:=multC(c1,c2)
{ c3 =(AC-BD) + (AD+BC)i }
{ c1 = (A + Bi) c2 = (C + Di) c2 (0 + 0i) } c3:=divsnC(c1,c2)
{ c3 =[(AC+BD)/(C²+D²)] + [(BC-AD) /(C²+D²)] i } { c = (A + Bi) }
r :=módC(c)
{ r = raíz(A²+B²) 0r }
Implementación del Tad Número complejo
Como se sigue la representación cartesiana de los números complejos, se representarán mediante una tupla con dos componentes:
Tipo: complejo = tupla
preal: real;
pimag: real;
ftupla Operaciones:
{ pr=PR pi=PI }
función creaC(pr,pi:real): complejo;
var c:complejo fvar c.preal:=pr; c.pimag:=pi;
devuelve(c) ffunción
{ creaC(pr,pi)=(PR + PIi) } { c = (A + Bi) }
función prealC(c:complejo):real;
devuelve(c.preal) ffunción
{ prealC(c)= A } { c = (A + Bi) }
función pimagC(c:complejo):real;
devuelve(c.pimag) ffunción
{ pimagC(c) = B }
{ c1 = (A + Bi) c2 = (C + Di) }
función sumaC(c1,c2:complejo):complejo;
var c3:complejo fvar
c3.preal:=c1.preal + c2.preal;
c3.pimag:=c1.pimag + c2.pimag;
devuelve(c) ffunción
{ sumaC(c1,c2) =(A+C) + (B+D)i } { c1 = (A + Bi) c2 = (C + Di) }
función restaC(c1,c2:complejo):complejo;
var c3:complejo fvar
c3.preal:=c1.preal - c2.preal;
c3.pimag:=c1.pimag - c2.pimag;
devuelve(c) ffunción
{ restaC(c1,c2) =(A+C) - (B+D)i } { c1 = (A + Bi) c2 = (C + Di) }
función multC(c1,c2:complejo):complejo;
var c3:complejo fvar
c3.preal:=c1.preal*c2.preal - c1.pimag*c2.pimag;
c3.pimag:=c1.preal*c2.pimag + c2.preal*c1.pimag;
devuelve(c) ffunción
{ multC(c1,c2) =(AC-BD) + (AD+BC)i }
{ c1 = (A + Bi) c2 = (C + Di) c2 (0 + 0i) } función divsnC(c1,c2:complejo):complejo;
var c3:complejo; aux:real fvar
aux:=c2.preal*c2.preal + c2.pimag*c2.pimag;
c3.preal:=(c1.preal*c2.preal + c1.pimag*c2.pimag)/aux;
c3.pimag:=(c2.preal*c1.pimag - c1.preal*c2.pimag)/aux;
devuelve(c3) ffunción
{divsnC(c1,c2)=[(AC+BD)/(C²+D²)]+[(BC-AD) /(C²+D²)]i}
{ c = (A + Bi) }
función módC(c:complejo):real;
devuelve(raíz(c.preal*c.preal+c.pimag*c.pimag)) { módC(c) = raíz(A²+B²) 0r }
Una vez definido el tad número complejo, se tiene:
- Es posible utilizar el tipo complejo para efectuar nuevas definiciones, de forma similar a como se haría con los restantes tipos numéricos.
- Las únicas operaciones permitidas sobre datos de tipo complejo son las definidas en el tad del mismo.
- Los algoritmos que utilicen el tad complejo no dependen de la representación interna de dicho tipo, ni de la implementación que se haya efectuado de las operaciones.
Ejemplo: utilización del tad complejo
En teoría de circuitos es habitual utilizar matrices de números complejos para representar circuitos eléctricos en los que aparecen elementos inductivos o capacitivos.
En el estudio de dichos circuitos se utilizan operaciones algebraicas como el producto matricial, entre otras, así:
dadas A,BCnxn ;se desea calcular MCnxn, tal que M=A*B Tipo
MatrizC : Vector[1..N,1..N] de complejo;
{a=A b=B }
procedimiento prodMC( a,b: MatrizC; var m: MatrizC);
var i,j,k: entero fvar Para i:=1 hasta N hacer
Para j:=1 hasta N hacer
m[i,j]:=creaC(0,0); /* se asigna el neutro*/
Para k:=1 hasta N hacer
m[i,j]:=sumaC(m[i,j], multC(a[i,k],b[k,j]));
fpara fpara fpara
fprocedimiento { m= A*B }
3.2 D
EFINICIÓN YE
SPECIFICACIÓN DET
IPOSL
INEALESPILAS
Una abstracción de datos Pila es una estructura LIFO (del inglés Last-In First-Out, o "el último que entra es el primero que sale"):
Los elementos se insertan de uno en uno y se recuperan también de uno en uno, en orden inverso a como se han introducido.
Se caracteriza por las siguientes operaciones:
creap(p): crea una pila vacía
apilar(p,v): añade el elemento v en la pila p desapilar(p): saca un elemento de la pila topep(p): consultar el tope de la pila vaciap?(p): decidir si la pila está vacia
Ejemplos: utilización de pilas en el diseño de algoritmos
Gestión de las direcciones de retorno que hace el sistema en las llamadas a procedimientos en un programa.
Evaluación de expresiones.
Transformación de algunas clases de funciones recursivas en iterativas.
Especificación de PILA o TAD PILA
TIPO pila [elemento]
SINTAXIS
procedimiento creap(sal p:pila);
procedimiento apilar(ent/sal p:pila; v:elemento);
procedimiento desapilar(ent/sal p:pila);
función topep(p:pila) devuelve elemento;
función vaciap?(p:pila) devuelve lógico;
SEMÁNTICA {}
creap(p) {p= }
{p=P1 P2.. Pn v=V } apilar(p,v)
{p=P1 P2.. Pn V}
{p=P1 P2.. Pn p<> } desapilar(p)
{p=P1 P2.. Pn-1}
{p=P1 P2.. Pn p<> } e:=topep(p)
{ p=P1 P2.. Pn e= Pn} { p=P1 P2.. Pn }
b:=vaciap?(p)
{ p=P1 P2.. Pn b=(p= )}
COLAS
Una Cola es una estructura FIFO (First-In First-Out o “el primero que entra es el primero que sale”).
Las inserciones se realizan por un extremo y las extracciones por el otro; el elemento que se puede consultar en todo momento es el que primero se insertó.
Se caracteriza por las siguientes operaciones:
creaq: crea una cola vacía
encolar(q,e): inserta un elemento en la cola
desencolar(q): elimina el primer elemento de la cola primero(q): obtiene el primer elemento de la cola
vaciaq?(q): devuelve CIERTO si la cola está vacía y FALSO en caso contrario
Ejemplo de aplicación:
La asignación de CPU a procesos de usuarios que realiza el sistema operativo con una política round-robin sin prioridades:los procesos que solicitan el procesador se encolan de forma que cuando acaba el que se está ejecutándose actualmente pasa a ejecutarse el que lleva mas tiempo esperando.
Especificación de COLA o TAD COLA
TIPO cola [elemento]
SINTAXIS
procedimiento creaq (e/s q:cola);
procedimiento encolar (e/s q:cola; e:elmento);
procedimiento desencolar (e/s q:cola);
función primero (q:cola) devuelve elemento;
función vaciaq? (q:cola) devuelve lógico;
SEMÁNTICA {}
creaq(q) {q= }
{q=Q1 Q2 .. Qn e=E}
encolar(q,e) {q=Q1 Q2 .. Qn E}
{q=Q1 Q2 .. Qn q<> } desencolar(q)
{q=Q2 .. Qn }
{q=Q1 Q2 .. Qn q<> } e:=primero (q)
{ q=Q1 Q2 .. Qn e = Q1 } { q=Q1 Q2 .. Qn }
b:=vaciaq?(q)
{ q=Q1 Q2 .. Qn b=(q= }
3.2.3 L
ISTAS CON PUNTO DE INTERÉSUna lista es una secuencia de elementos de un tipo dado, ordenados linealmente según la posición que ocupan en la misma.
Una lista con punto de interés es una lista que tiene una posición especial, o distinguida, sobre la que se aplican las distintas operaciones.
La posición distinguida, o punto de interés, se puede cambiar, con lo que es posible operar sobre los distintos elementos de la lista.
La lista con punto de interés se caracteriza por tres tipos de operaciones:
Operaciones para manipular el contenido:
creal(l): crea una nueva lista vacía.
inserta(l,e): inserta el elemento e en la lista l en la posición inmediatamente anterior a la distinguida.
suprime(l): elimina el elemento que está en la posición distinguida de la lista l.
recupera(l): devuelve el elemento que está en la posición distinguida de la lista l.
Operaciones para cambiar el punto de interés:
principio(l): sitúa el punto de interés de la lista l al principio fin(l): sitúa el punto de interés de la lista l al final
siguiente(l): cambia el punto de interés de la lista l de la posición que ocupa a la siguiente.
Operaciones para consultar el estado de la lista:
esvacial(l): devuelve CIERTO si la lista l está vacía y FALSO en caso contrario.
esfin(l): devuelve CIERTO si el punto de interés de la lista l es posterior al último elemento y FALSO en caso contrario.
Especificación del Tad Lista con punto de interés
TIPO lista[elemento]
SINTAXIS
procedimiento creal (sal l:lista);
procedimiento inserta (ent/sal l:lista; e:elemento);
procedimiento suprime (ent/sal l:lista);
función recupera (l:lista) devuelve elemento;
procedimiento principio (ent/sal l:lista);
procedimiento fin (ent/sal l:lista);
procedimiento siguiente (l:lista);
función esvacial (l:lista) devuelve lógico;
función esfinl (l:lista) devuelve lógico;
SEMÁNTICA Notación utilizada:
- l = L1L2..Ln 0n, donde n=0 equivale a l= , - pos, 1posn+1,es la posición del punto de interés.
{}
creal(l)
{l = pos=1}
{l =L1L2..Ln pos=i 1posn+1 e=E } inserta(l,e)
{l=L’1L’2...L’n+1(L’1=L1...L’i-1=Li-1)L’i=E(L’i+1=Li...L’n+1=Ln)pos=i+1}
{l =L1L2...Ln pos=i 1posn } suprime(l)
{l=L’1 L’2...L’n-1 (L’1=L1...L’i-1= Li-1)(L’i=Li+1...L’n-1= Ln) pos=i } {l = L1L2...Ln 1posn }
e:=recupera(l)
{l = L1L2...Ln e=Lpos}
{l=L1 L2...Ln 0n}
principio(l)
{l=L1L2...Ln pos=1}
{l=L1L2...Ln 0n}
fin(l)
{l=L1L2...Ln pos=n+1}
{l=L1L2...Ln 1posn pos=i}
siguiente(l)
{ l=L1L2...Ln pos=i+1}
{l=L1L2...Ln 0n}
b:=esvacial(l) {b=(n=0)}
{l=L1L2...Ln 0n}
b:=esfin(l) {b=(pos=n+1)}
Ejemplos de utilización del TAD lista con
punto de interés
1.- Tratamiento de todos los elementos de una lista l:
{ l=L1L2...Ln 0n }
procedimiento recorrer(ent l:lista) var e:elemento fvar;
primero(l);
mientras no esfin(l) hacer e:=recupera(l); tratar(e);
siguiente(l);
fmientras;
fprocedimiento
{ l=L1L2...Ln i:1in:tratar(Li) }
Ejercicios propuestos:
-Dada la lista l, crear la lista l’ con el mismo contenido que l -Dada la lista l, borrar todos sus elementos
-Dadas las listas l y l’, obtener l’’ concatenación de l y l’
2.-¿Existe en l un elemento que cumple la propiedad P?
{ l=L1L2...Ln 0n }
función búsqueda(l:lista): lógico var enc: lógico; e:elemento fvar enc:=falso; primero(l);
mientras (no esfinl(l)) y (no enc) hacer e:=recupera(l);
si P(e) entonces enc:=cierto sino siguiente(l) fsi fmientras;
devuelve(enc) ffunción
{ búsqueda(l) l=L1L2...Ln i:1in:P(Li) }
Ejercicios propuestos:
-Obtener la posición del primer elemento de l que cumple P -Obtener la posición del último elemento de l que cumple P -¿Todos los elementos de l, cumplen la propiedad P?
3.3 I
MPLEMENTACIÓN DET
IPOSL
INEALES.
1.- Representación vectorial: Los elementos de la estructura se almacenan en un vector de forma que elementos consecutivos en la pila ocupan posiciones consecutivas en el vector (array).
2.- Representación enlazada: Se rompe la propiedad de representación secuencial y se introduce el concepto de enlace: todo elemento de la estructura identifica explícitamente la posición que ocupa su sucesor en la misma.
La estructura se puede almacenar en un vector o gestionarla dinámicamente utilizando punteros.
A
SIGNACIÓND
INÁMICA DEM
EMORIA. P
UNTEROS EL TIPO PUNTERO EN PASCALSe caracteriza por su bajo nivel de abstracción debido a que esta muy próximo a la organización de la información en la memoria central de un ordenador.
Es un tipo de datos elemental cuyo dominio está compuesto por direcciones de memoria con tipo. El tipo de estas variables se llama Tipo Puntero a un tipo de datos dado T.
No existe un tipo puntero único como ocurre en los otros tipos de datos básicos, sino tipos punteros a tipos de datos.
Definición de una variable de tipo puntero-a:
VAR nombre_de_variable : T; var
...
I : T;
PtoI : T;
p p+1 p+2 p+n-1
La variable I es de tipo T y la variable PtoI es de tipo puntero a un valor de tipo T que ocupa n posiciones de memoria a partir de la posición p.
Ejemplo: Var I : integer;
PtoI, ptoJ : ˆ integer;
VALOR NULO DE LOS TIPOS PUNTERO-A (polimorfismo) Para todos los conjuntos de valores de los tipos puntero-a, existe un valor que representa la dirección de memoria nula: NIL.
Ejemplo: Var p1: ˆ T1;
p2: ˆ T2; /*Donde T1 T2*/
p1 := Nil;
p2 := Nil;
Los tipos de p1 y p2 son puntero a T1 y puntero a T2. Son dos tipos diferentes a los que se le asigna un valor nulo diferente para cada tipo pero con la misma representación (NIL).
VARIABLES DINÁMICAS
En el lenguaje Pascal existen dos clases de variables según el momento en el que se asigna memoria para poder guardar un valor:
- Una variable es estática si la asignación de memoria se realiza durante el análisis estático (compilación)
- Una variable es dinámica si la asignación de memoria se realiza durante la ejecución del programa.
Para la asignación de memoria a una variable dinámica el programa en ejecución dispone de dos zonas de memoria:
La Pila: se utiliza para asignar memoria a los parámetros y las variables locales de funciones y procedimientos.
El Heap: se utiliza para asignar memoria al resto de variables dinámicas.
ASIGNACIÓN Y LIBERACIÓN DE MEMORIA
Las variables dinámicas sólo utilizan memoria durante la ejecución de un programa, por lo que se hace necesario un mecanismo que permita el proceso de asignación y liberación de memoria.
Para la pila de ejecución este proceso es automático en el lenguaje Pascal.
Para la gestión del Heap, Pascal proporciona una primitivas que permiten que el proceso de asignación y liberación de memoria lo determine el propio programador.
Primitivas para la obtención y liberación de memoria:
A la obtención de memoria para una variable dinámica se denomina CREACIÓN DE UNA VARIABLE DINÁMICA:
Primitiva: NEW(pto)
Var pto : ˆ T;
new(pto);
new(pto) reserva la memoria suficiente en el heap para guardar la representación binaria de un valor de tipo T. En la variable pto se almacena la dirección a la celda de memoria a partir de la cual se puede almacenar un valor de tipo T.
pto
Heap
...
p p+1 p+2 p+n-1 - La variable pto contiene la dirección p
- Son necesarias n celdas para guardar la representación binaria de un valor de tipo T.
A la liberación de la memoria utilizada por una variable dinámica se denomina BORRADO DE UNA VARIABLE DINÁMICA.
Primitiva: DISPOSE(pto)
pto
Heap
...
p p+1 p+2 p+n-1
El procedimiento dispose libera el uso de la memoria del heap utilizada para guardar un valor de tipo T.
dispose(pto) pto
Heap ¿?
...
p
La memoria liberada por dispose queda disponible para nuevas demandas de memoria.
La variable puntero que apunta a una variable dinámica que ha sido borrada pasa a contener un valor indefinido.
ACCESO A LOS VALORES DE UN VARIABLE DINÁMICA A una variable dinámica se accede a través de la dirección a partir de la cual esta guardado su valor, es decir, utilizando un puntero que la apunta. El operador de acceso a una variable dinámica es:
Var pto : T;
begin
new(pto);
pto:= <valor>;
Donde <valor> es un dato de tipo T.
Ejemplo:
Program ejemplo (input,output);
Var p:integer;
I:integer;
begin I:=5;
New(p);
p:=I;
writeln(p);
p:= p+1;
writeln(p);
end.
Este programa produce los valores 5 y 6 en su salida estándar.
EJEMPLOS DE USO Var
pto1, pto2, pto3: integer;
begin
new(pto1);
new(pto2);
{ pto1 pto2 } pto1:= 1;
pto2:= 2;
{pto1 = 1 pto2 = 2 pto1 pto2 } pto3 := pto1;
{pto1=1pto2=2pto3=1pto1pto2pto1pto3 pto2 pto3}
pto1:= pto2;
{pto1=2pto2=2pto3=2pto1pto2pto1pto3 pto2 pto3}
dispose(pto3);
{pto2=2pto1=indefinido pto3=indefinido}
end.
I
MPLEMENTACIÓN DE UNA PILA. Representación vectorial
P1 P2 ... Pn
tope
Apilar un elemento:
P1 P2 ... Pn V
tope
Desapilar un elemento:
P1 P2 ... Pn-1 Pn
tope
tipo pila = tupla
datos: vector [1..MaxP] de elemento;
tope: Natural;
ftupla
Si p es de tipo pila, la información está en p.datos[1..p.tope]
{}
procedimiento creap (sal p:pila) p.tope := 0
fprocedimiento {p= }
{p = P1 P2.. Pn v = V }
procedimiento apilar (ent/sal p: Pila; v: elemento) si p.tope<MaxP entonces
p.tope := p.tope + 1; p.datos[p.tope]:=v sino error(‘La pila está llena’)
fprocedimiento {p = P1 P2.. Pn V}
{p = P1 P2.. Pnp <> }
procedimiento desapilar (ent/sal p: Pila) p.tope := p.tope - 1
fprocedimiento {p = P1 P2.. Pn-1}
{p = P1 P2.. Pn p <> }
función topep (p: Pila) devuelve elemento devuelve p.datos[p.tope]
ffunción
{ p = P1 P2.. Pn topep(p) = Pn} { p = P1 P2.. Pn }
función vaciap? (p:Pila) devuelve Lógico devuelve (p.tope = 0)
ffunción
{ p = P1 P2.. Pn vaciap?(p) = (p = )}
El coste temporal de las operaciones es óptimo, (1)
El coste espacial es (MaxP) independientemente del número de elementos que la formen.
Representación enlazada usando variable dinámica
tipo pila = Nodo Nodo = tupla
dato: elemento;
sig: pila;
ftupla;
{}
procedimiento creap (sal p: pila) p := nil
fprocedimiento {p= }
{p = P1 P2.. Pn v = V }
procedimiento apilar (ent/sal p: pila;v: elemento) var nuevo: pila fvar;
new(nuevo); nuevo^.dato := v; nuevo^.sig := p; p := nuevo;
fprocedimiento {p = P1 P2.. Pn V}
{p = P1 P2.. Pnp <> }
procedimiento desapilar (ent/sal p:pila) var borra:pila;
borra := p; p := p^.sig; dispose(borra);
fprocedimiento {p = P1 P2.. Pn-1}
{p = P1 P2.. Pn p <> }
función topep (p:pila) devuelve elemento devuelve p^.dato;
ffunción
{ p = P1 P2.. Pn topep(p) = Pn} { p = P1 P2.. Pn }
función vaciap? (p:pila) devuelve Lógico devuelve (p=nil)
ffunción
{ p = P1 P2.. Pn vaciap?(p) = (p = )}
Estudio comparativo de las implementaciones
• La complejidad temporal de todas las operaciones en ambas representaciones es independiente de la talla del problema.
• En cuanto a complejidad espacial, la implementación vectorial presenta el inconveniente de la estimación del tamaño máximo y la reserva de espacio que en muchos casos no se utilizará. La representación enlazada requiere un espacio de memoria adicional para los enlaces.
I
MPLEMENTACIÓN DEC
OLASRepresentación vectorial
Los elementos de la cola se distribuyen secuencialmente en un vector; además necesitamos para su representación:
1.- un marcador (“libre”) de la posición donde insertar el siguiente elemento (marcador al primer elemento libre de la cola).
a b c
libre MaxC
¿qué pasa si desencolamos?
Si el 1er elemento de una cola fuera siempre el que ocupa la 1ª posición del vector, cada operación de borrado, “desencolar”, supondría el desplazamiento del resto de elementos de la cola una posición hacia la izquierda;
a b c
libre MaxC
desencolar(q)
b c
libre MaxC
desplazar(q);
b c
libre MaxC
¡extremadamente ineficiente!
2.- Necesitamos también un marcador de primer elemento de la cola, que llamaremos “prim”
a b c
prim libre MaxC
Como consecuencia de tener sólo los marcadores “libre” y “prim”
para manejar el vector-cola, es posible que cualquiera de estos dos marcadores (o ambos) llegen al final del vector (MaxC)
¡SIN QUE LA COLA ESTÉ TOTALMENTE LLENA!
Para poder reutilizar todas las posiciones del vector sin tener que desplazar elementos, y así llegar a su final con todas sus posiciones ocupadas:
Consideraremos el vector como una estructura circular, sin principio ni fin, donde después del último elemento va el primero
...
....
MaxC
1
2
prim libre
COLA c
COLA_VACIA: prim= libre COLA_LLENA: prim = libre
3. Aún con la estructura circular propuesta NO hay suficiente información para decidir si una cola está vacía o no, ya que la condición de cola vacía es la misma que la de cola llena. Por tanto, además de la estructura circular con dos marcadores, el vector-cola necesita de un tercer marcador: el indicador de cola vacía o llena.
Hemos implementado este indicador como un contador de elementos de la cola.
Representación vectorial circular
tipocola = tupla
datos = vector [1..MaxC] of elemento;
prim, libre: Nat;
long: Nat;
ftupla
Si q es de tipo cola, la información está en q.datos[q.prim..q.libre-1]
...
....
MaxC
1
2
c.prim c.libre
COLA c
COLA_VACIA: (c.prim = c.libre) (c.long = 0) COLA_LLENA: (c.prim = c.libre) (c.long MaxC)
{}
procedimiento creaq (e/s q:cola) q.pri:= 1; q.libre:= 1; q.long:=0;
fprocedimiento {q = }
{ q=Q1 Q2 .. Qn e=E }
procedimiento encolar (e/s q:cola; e:elemento) si q.long<MaxC entonces
q.elementos[q.libre]:=e;
q.libre:=siguiente(q.libre);
q.long:=q.long+1 sino error (‘Cola llena’) fprocedimiento
{q=Q1 Q2 .. Qn E}}
{q=Q1 Q2 .. Qn q }
procedimiento desencolar (e/s q:cola) q.pri:=siguiente(q.pri);
q.long:=q.long-1;
fprocedimiento {q=Q2 .. Qn }
{q=Q1 Q2 .. Qn q }
función primero (q:cola) devuelve elemento devuelve q.datos[q.pri]
fprocedimiento
{ q=Q1 Q2 .. Qn primero(q) = Q1}
{ q=Q1 Q2 .. Qn }
función vaciaq? (q:cola) devuelve lógico;
devuelve (q.long=0) ffunción
{ q=Q1 Q2 .. Qn b=(q= }
Función privada de la implementación de la cola
{1 i MaxC}
función siguiente (i:Nat) devuelve Nat devuelve (i mod MaxC) + 1
ffunción
{(1 i MaxC siguiente(i) = i+1)
i = MaxC siguiente(i) = 1)}
El coste temporal de las operaciones es (1) .
Representación enlazada usando variable dinámica.
Ejercicio.
Implementar el TAD COLA utilizando una representación enlazada con variables dinámicas.
tipo
enlace= nodo;
cola= tupla
prim,ult:enlace;
long:nat;
ftupla nodo= tupla
dato:elemento;
sig:enlace;
ftupla
Nota: En este caso el marcador señala al último elemento de la cola y NO al primer elemento libre (como en la implementación con vector circular).
I
MPLEMENTACIÓN DE LISTAS CON PUNTO DE INTERÉSRepresentación secuencial:
Vector en el que los elementos de la lista se almacenan en posiciones contiguasconst MaxL = --;
tipo
posición= Natural;
lista= tupla
datos: vector [1..MaxL] de elemento;
ult, pos: posición;
ftupla
datos L1 L2 L3 ... Ln ...
1 2 3 MaxL
ult
No se utiliza apenas, por ser una representación ineficiente, puesto que:
- la operación de inserción supone el desplazamiento (una posición a la derecha) de todos los elementos posteriores
- la operación de borrado supone el desplazamiento (una posición a la izquierda) de todos los elementos posteriores
Representación enlazada
Para evitar los desplazamientos de los valores se utiliza una representación enlazada, en la que los elementos no están almacenados en el mismo orden que ocupan en la lista.
Esta representación enlazada requiere guardar junto con cada elemento la dirección del siguiente elemento de la lista.
La disposición de los elementos en el vector es irrelevante y depende sólo de la historia de la estructura y de la implementación de los algoritmos
Dos posibles implementaciones: Vectores o Variables dinámicas
(a) Utilizando vectores
Para definir listas enlazadas se utiliza un vector de nodos con la información del nodo más la posición que ocupa dentro del vector su elemento sucesor.
const Máximo= ---;
tipo
posición=Natural ; Nodo= tupla
e:elemento;
pos: posición;
ftupla
MEMORIA= vector [1.. Máximo] de NODO;
Lista=tupla
pri, ult, pos:posición ftupla;
Convención: el último elemento tendrá el 0 como indicador de su sucesor
Obsérvese cómo en esta estructura se puede representar más de una lista, e incluso otras estructuras.
Ejemplo: lista de enteros = 20,23,25,30,35
1 30 4
2 20 5 pri
3 25 1
4 35 0 ult
5 23 3
...
... ...
Máximo
Ejercicio: Implementar las operaciones del tipo Lista con punto de interés utilizando una representación vectorial enlazada. Para ello, supóngase que se dispone de dos funciones predefinidas,
obten_posnodo(var m:memoria) devuelve posición libera_nodo(p:posición; var m: memoria)
(b) Utilizando variables dinámicas
Una lista queda representada por una secuencia de nodos enlazados y tres punteros: pri, ult y ant.
Para mejorar las operaciones de borrado e inserción y facilitar el tratamiento de la lista vacía, el primer nodo de la lista es
‘ficticio’, y tiene posición 0.
pri, puntero al primer nodo existente,
ult, puntero al último nodo
ant, puntero al nodo anterior a la posición distinguida tipo
ptr = Nodo;
Nodo= tupla
dato:elemento;
sig:ptr;
ftupla Lista= tupla
pri, ult, ant: ptr;
ftupla
pri ult
L1 L2 L3
{}
procedimiento creal (e/s l:lista) var aux:ptr fvar
new(aux); aux.sig:=NIL;
l.pri:= aux; l.ult:= aux; l.ant:= aux fprocedimiento
{l = pos=1}
{l =L1L2..Ln pos=i 1posn+1 e=E }
procedimiento inserta (e/s l:lista; e:elemento) var q:ptr fvar
new(q); q.dato:=e; q.sig:=l.ant.sig;
l.ant.sig:=q;
si l.ant=l.ult entonces l.ult:=q fsi l.ant:=q
fprocedimiento
{l=L’1L’2...L’n+1(L’1=L1...L’i-1=Li-1)L’i=E(L’i+1=Li...L’n+1=Ln)pos=i+1}
{l =L1L2...Ln pos=i 1posn } procedimiento suprime (e/s l:lista) var q:ptr fvar
si l.ant.sig = l.ult entonces l.ult:= l.ant fsi;
q:=l.ant.sig;
l.ant.sig:=l.ant.sig.sig;
dispose(q) fprocedimiento
{l=L’1 L’2...L’n-1(L’1=L1...L’i-1= Li-1)(L’i=Li+1...L’n-1= Ln) pos=i } {l = L1L2...Ln 1posn }
función recupera (l:lista) devuelve elemento devuelve (l.ant.sig.dato)
ffunción
{ recupera(l)=Lpos l = L1L2...Ln }
{l=L1 L2...Ln 0n}
procedimiento principio (e/s l:lista);
l.ant:= l.pri;
f procedimiento {l=L1L2...Ln pos=1}
{l=L1L2...Ln 0n}
procedimiento fin (e/s l:lista);
l.ant:= l.ult;
ffprocedimiento {l=L1L2...Ln pos=n+1}
{l=L1L2...Ln 1posn pos=i}
procedimiento siguiente (e/s l:lista);
l.ant:=l.ant.sig;
f procedimiento { l=L1L2...Ln pos=i+1}
{l=L1L2...Ln 0n}
función esvacial (l:Lista) devuelve Lógico devuelve (l.pri=l.ult)
ffunción
{ esvacial(l)=(n=0) l=L1L2...Ln } {l=L1L2...Ln 0n}
función esfinl (l:Lista) devuelve Lógico devuelve (l.ant=l.ult)
ffunción
{ esfin(l)=(pos=n+1) l=L1L2...Ln }
Estudio comparativo de las implementaciones de Listas
COMPLEJIDAD TEMPORAL Secuencial Enlazada
creal(l) (1) (1)
inserta(l, x) (Nºelem) (1)
suprime(l) (Nºelem) (1)
e:=recupera(l) (1) (1)
b:=esvacial(l) (1) (1)
fin(l) (1) (1)
primero(l) (1) (1)
Siguiente(l) (1) (1)
b:=esfin(l) (1) (1)
COMPLEJIDAD ESPACIAL
(MaxL) (Nºelem)
• La asignación enlazada requiere un espacio de memoria adicional para los enlaces.
• Es fácil insertar o borrar un elemento en cualquier parte de una lista cuando se usan enlaces, pues solo hay que cambiar dos de ellos. En el caso secuencial esta operación representa un gran consumo de tiempo.
• La representación enlazada facilita la unión de dos listas o su separación.
• El esquema de enlaces se presta para obtener estructuras más complejas que las simples listas lineales