Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Todos los libros publicados por Editorial Complutense a partir de enero de 2007
han superado el proceso de evaluacin experta.
2011 by Mario Rodrguez Artalejo, Pedro Antonio Gonzlez Calero y Marco Antonio Gmez Martn
2011 by Editorial Complutense, S. A.
Donoso Corts, 63 4. planta. 28015 Madrid
Tels.: 91 394 64 60/61. Fax: 91 394 64 58
ecsa@rect.ucm.es
www.edirorialcomplutense.com
Primera edicin: Septiembre de 2011
ISBN: 978-84-9938-096-4
Esta editorial es miembro de la UNE, lo que garantiza la difusin y comercializacin
de sus publicaciones a nivel nacional e internacional.
Prlogo
PRLOGO
Si has aprendido a programar de manera informal, las tcnicas formales son un descubrimiento asombroso. Se convierten en una herramienta muy potente para razonar sobre los programas y
explorar las alternativas de diseo de algoritmos que cualquier problema no trivial plantea. Por
otra parte, si tu primera aproximacin a la Programacin es a travs de las tcnicas formales, stas
te servirn para enfocar la atencin sobre aspectos concretos del problema, resolverlos por separado y razonar sobre su correccin, adquiriendo una metodologa sistemtica y bien fundamentada para el diseo de algoritmos que, una vez interiorizada, podrs probablemente olvidar.
Este libro es el resultado de nuestra experiencia impartiendo la asignatura Estructuras de datos
y de la informacin en la Facultad de Informtica de la Universidad Complutense de Madrid,
ininterrumpidamente desde el curso 1995-96 hasta la actualidad. Pasando cronolgicamente por
las manos de los tres autores, empezando por las notas de clase de Mario Rodrguez, que Pedro
Gonzlez evolucion y Marco Gmez sigui utilizando posteriormente. Fruto de este trabajo,
adems de este libro, se han generado trasparencias de apoyo a las clases e implementaciones en
C++ de todos los algoritmos aqu descritos. Es este material adicional, disponible en la pgina
http://gaia.fdi.ucm.es/people/pedro/edem/, junto con el texto del libro el que da sentido al
moderno que aparece en el ttulo del libro. Existen libros de estructuras de datos con un enfoque formal, alejados de los lenguajes de programacin concretos, as como otros menos formales
y ms preocupados por proporcionar los detalles de implementacin en un lenguaje concreto.
Este libro pretende ser moderno aproximando ambos enfoques: formal en la presentacin de los
conceptos, que se acompaan de implementaciones ejecutables en el material adicional.
El libro est organizado de la siguiente forma. En el captulo 1 se introducen las tcnicas de
especificacin pre/post de algoritmos. Aunque se puede suponer que los estudiantes han seguido
previamente un curso de lgica para informticos, hemos intentado que la exposicin sea autocontenida, introduciendo todos los elementos de sintaxis y semntica de la lgica con signatura
heterognea que se utiliza en la especificacin. Se introducen las reglas de verificacin de las
construcciones habituales de los lenguajes imperativos, as como las que permiten verificar procedimientos y funciones. Se presentan as mismo las medidas asintticas de la complejidad y los
mtodos de anlisis de la complejidad de algoritmos iterativos. A continuacin, se presentan los
mtodos de derivacin de algoritmos iterativos a partir de la especificacin, con especial atencin
a la derivacin de bucles a partir del invariante. Por ltimo, se utilizan los mtodos de derivacin
recin presentados para obtener los algoritmos de recorrido, bsqueda secuencial y binaria y
mtodos de complejidad cuadrtica de ordenacin de vectores.
El segundo captulo se dedica al estudio de los algoritmos recursirvos. Siguiendo un esquema
similar al del captulo 1, se presentan consecutivamente las tcnicas de especificacin, derivacin
y anlisis de algoritmos recursivos. En el apartado dedicado a la derivacin se derivan los algoritmos de complejidad cuasi-lineal de ordenacin rpida y ordenacin por mezcla. Despus de presentar el esquema general de transformacin de recursin final en iteracin, el captulo concluye
presentando algunas tcnicas adicionales de diseo de algoritmos recursivos como son la generalizacin y el plegado-desplegado.
El captulo 3 da comienzo a la segunda parte del libro, que se dedica al estudio de los tipos
abstractos de datos. Despus de introducir de manera informal el concepto de tipo abstracto de
datos (TADs) y el papel que juegan las estructuras de datos como mecanismos de implementacin, se presentan las tcnicas de especificacin algebraica de tipos abstractos de datos que se
emplearn en el resto del libro para especificar los TADs estudiados. A continuacin se hacen
Prlogo
ii
algunas consideraciones generales acerca de las alternativas de implementacin de un TAD a partir de su especificacin, incluyendo indicaciones acerca de cmo verificar la correccin de dicha
implementacin. Por ltimo, se presentan los punteros como mecanismo para construir estructuras de datos en memoria dinmica, completando as, con tipos simples, registros y vectores que se
suponan conocidos, el repertorio de estructuras de datos bsicas que se usar en el resto del libro
para construir estructuras de datos ms elaboradas.
Los captulos 4, 5, 6 y 7 se dedican al estudio de las estructuras de datos ms habituales: estructuras de datos lineales, rboles, tablas y grafos. Entre las estructuras de datos lineales nos
ocupamos de especificar y presentar implementaciones alternativas para pilas, colas, colas dobles,
listas y secuencias. Las secuencias, que aaden al TAD Lista la posibilidad de recorrer secuencialmente sus elementos, sern muy utilizadas como estructuras auxiliares en los captulos posteriores. En el captulo 5, dedicado a los rboles, se especifican e implementan los rboles binarios,
los rboles de bsqueda y los rboles equilibrados AVL. Los montculos se desarrollan como
ejercicios en este tema. El captulo 6 se dedica a las tablas, especificadas como funciones de claves en valores, y que se pueden implementar eficientemente con la tcnica de las tablas dispersas.
Se estudian las tablas dispersas abiertas y cerradas, adems de los principales mtodos de localizacin y relocalizacin. Por ltimo el captulo 7 se dedica a la especificacin e implementacin de
los grafos. Adems de las operaciones bsicas de construccin de grafos dirigidos etiquetados, se
estudian las operaciones de recorrido en profundidad y anchura, as como los algoritmos de obtencin de caminos mnimos.
Queremos expresar nuestro agradecimiento a las sucesivas generaciones de estudiantes de la
Facultad de Informtica de la Universidad Complutense de Madrid que han seguido la asignatura
de Estructuras de datos y de la informacin y nos han ayudado a mejorar el material a partir del
cual hemos preparado el libro que ahora tienes delante.
Mayo de 2011
Mario Rodrguez Artalejo
Pedro Antonio Gonzlez Calero
Marco Antonio Gmez Martn
iii
ndice de contenidos
NDICE DE CONTENDOS
Captulo 1 Diseo de algoritmos iterativos ...................................................................... 1
1.1 Especificacin pre/post ................................................................................................................. 1
1.1.1 Representacin de asertos en lgica de predicados ...................................................... 3
1.1.2 Especificacin pre/post ................................................................................................. 20
1.1.3 Especificaciones informales ........................................................................................... 33
1.2 Verificacin de algoritmos........................................................................................................... 33
1.2.1 Estructuras algortmicas bsicas .................................................................................... 34
1.2.2 Estructuras algortmicas compuestas............................................................................ 41
1.3 Funciones y procedimientos ....................................................................................................... 62
1.3.1 Procedimientos ................................................................................................................63
1.3.2 Funciones.......................................................................................................................... 70
1.4 Anlisis de algoritmos iterativos ................................................................................................. 76
1.4.1 Complejidad de algoritmos ............................................................................................ 78
1.4.2 Medidas asintticas de la complejidad .......................................................................... 80
1.4.3 Ordenes de complejidad ................................................................................................. 82
1.4.4 Mtodos de anlisis de algoritmos iterativos ............................................................... 85
1.4.5 Expresiones matemticas utilizadas .............................................................................. 90
1.5 Derivacin de algoritmos iterativos ...........................................................................................91
1.5.1 El mtodo de derivacin ................................................................................................ 91
1.5.2 Dos ejemplos de derivacin ........................................................................................... 94
1.5.3 Introduccin de invariantes auxiliares por razones de eficiencia ...........................103
1.6 Algoritmos de tratamiento de vectores ...................................................................................108
1.6.1 Recorrido ........................................................................................................................109
1.6.2 Bsqueda .........................................................................................................................110
1.6.3 Ordenacin .....................................................................................................................120
1.7 Ejercicios ......................................................................................................................................134
Captulo 2 Diseo de algoritmos recursivos ................................................................ 153
2.1 Introduccin a la recursin .......................................................................................................153
1.1.1 Recursin simple ...........................................................................................................155
1.1.2 Recursin mltiple.........................................................................................................157
2.2 Verificacin de algoritmos recursivos......................................................................................159
1.2.1 Verificacin de la recursin simple .............................................................................160
1.2.2 Verificacin de la recursin mltiple ..........................................................................169
2.3 Derivacin de algoritmos recursivos .......................................................................................174
1.3.1 Algoritmos avanzados de ordenacin ........................................................................191
2.4 Anlisis de algoritmos recursivos .............................................................................................197
1.4.1 Despliegue de recurrencias...........................................................................................199
ndice de contenidos
iv
ndice de contenidos
ndice de contenidos
vi
ndice de contenidos
vii
CAPTULO 1
Las especificaciones resultan tiles en las distintas fases del desarrollo de los programas:
Durante la construccin, porque existen tcnicas que nos ayudan a construir programas a
partir de las especificaciones.
tipoVect=Vector[1..1000]deent
queremos implementar una funcin que dado un vector a de tipo vect y un entero n nos devuelva un valor booleano que nos indique si el valor de alguno de los elementos a[1], ..., a[n] es
igual a la suma de todos los que le preceden en el vector. Esta especificacin deja algunos puntos
sin aclarar, para el usuario: se puede llamar a esSuma con un valor negativo de n? y con n=0 o
con n>1000? y en caso afirmativo qu valor devolver la funcin?; y para el implementador: si
n1 y a[1]=0 la funcin debe devolver verdadero o falso?
var
a:Vect;
n:Ent;
b:Bool;
{P{0n1000}
esSuma
{Q{ bli:1in:(a(i)=j:1ji1:a(j))}
1.1.1
Vamos a utilizar la lgica de predicados adaptada a los elementos que aparecen en los programas. En un lenguaje imperativo existen una serie de tipos predefinidos. Una de las primeras propiedades del estado de un programa es el tipo de sus variables. Nuestra lgica dispone de
smbolos para representar a los siguientes tipos predefinidos algunos de los cuales no suelen
aparecer en los lenguajes imperativos:
Esto equivale a utilizar una signatura heterognea para la lgica, donde los gneros son los tipos
predefinidos.
A cada tipo predefinido le asignamos como significado su dominio de valores pretendido: N
para nat, Z para ent, Q para rac, R para real, {cierto, falso} para bool. Los vectores requieren un
tipo ms complejo. Para un vector de tipo
Esto es as porque consideramos que el valor de los cuantificadores, aritmticos y booleanos, aplicados sobre un
dominio nulo dan como resultado el elemento neutro de las correspondientes operaciones binarias [Bal93:pag 8].
1
Vector[v1..vn]deW
siendo los valores v1, ..., vn de tipo W, su dominio ser el conjunto de aplicaciones entre DW y
DW que notaremos D(Wo W)
De esta forma, el dominio para cada uno de los tipos predefinidos queda
Tipo (sintctico)
Nat
Ent
Rac
Real
Bool
Car
Dominio (semntico)2
Dnat= N
Dent= Z
Drac= Q
Dreal= R
Dbool= {cierto, falso}
Dcar= {0, 1, ..., 9, a, ..., z, A, ..., Z}
Vector[W] de W
Dvector[W] de W= D(WoW)
Decimos entonces que un valor de tipo W es cualquier elemento del correspondiente dominio
DW. Los programas disponen de recipientes para dichos valores: las variables y las constantes.
La signatura que utilizaremos contiene tambin las operaciones habituales sobre los tipos predefinidos.
Para definir operaciones, utilizaremos perfiles en los que se indica el nombre de la operacin, el
nmero de argumentos que tiene junto con el tipo de cada uno de ellos, y el tipo del resultado:
f : W1 ... Wn o W
Las operaciones que podemos utilizar sobre los tipos predefinidos:
Operaciones numricas, definidas para los tipos numricos: Nat, Ent, Rac y Real
+, *, : W W o W
Operaciones constantes de tipo bool
2 Ntese la diferencia entre las funciones constantes cierto y falso y los valores del dominio semntico cierto y
falso, que es la diferencia entre un lenguaje formal, como es la lgica que estamos definiendo, y su semntica que se
construye utilizando conceptos matemticos que tienen existencia independiente de dicho lenguaje.
Operaciones de comparacin con resultado booleano definidas para los tipos ordenados:
Nat, Ent, Rac, Real y Car.
>, <, , : W W o Bool
Operaciones de mximo y mnimo para los tipos ordenados
max, min : W W o W
Operaciones de igualdad y desigualdad con resultado booleano, definidas para todos los
tipos con igualdad, en nuestro caso todos los tipos predefinidos excepto los vectores.
=, z : W W o Bool
Una operacin polimrfica para cada uno de los tipos predefinidos que devuelve verdadero o falso dependiendo de si el argumento es de tipo G.
_:G : W o Bool3
Adems, tambin podremos emplear en los asertos cualquier operacin que haya sido totalmente especificada, en el sentido que veremos ms adelante, entendindose en ese caso que su comportamiento est definido por la especificacin.
Construccin de asertos
La forma ms simple de aserto es una expresin de tipo Bool. Definamos primero qu entendemos por expresin de tipo W.
Decimos que E es una expresin de tipo W si y slo si es de alguna de las formas siguientes:
P es
E = E
Vemos aqu cmo en los perfiles tambin se puede indicar el modo de aplicacin: prefijo, infijo o postfijo.
siendo E y E expresiones
Compuesta
R RQ RQ RoQ R l Q
(negacin, conjuncin, disyuncin, condicional y bicondicional)
siendo R y Q aserciones
P es de alguna de las formas
&
&
&
&
&
&
x : D( x ) : R( x )
x : D( x ) : R( x )
&
&
&
donde D( x ) es una asercin de dominio para las variables x , y R( x ) es una
&
asercin donde intervienen las variables x .
x > 10 es una expresin de tipo booleano y, por lo tanto una asercin atmica
(x>0) (x<0)
(x>3) o (x>0)
x : ent : (x*0=0) es una asercin compuesta, donde la asercin de dominio es una notacin abreviada de x : ent, una expresin booleana donde se aplica la operacin :ent
El empleo de cuantificadores en las aserciones supone la utilizacin de variables para representar un subconjunto de los posibles valores de un cierto dominio y no como contenedores de valores asociados a una posicin de memoria. Decimos que las variables cuantificadas son mudas en
el sentido de que no representan un valor de la memoria. Esto hace que una variable sujeta a un
cuantificador se pueda renombrar por otra sin que cambie el sentido de la asercin.
Una aparicin de una variable se dice que est ligada si se encuentra en el mbito de un cuantificador con esa variable. Diremos que es libre en otro caso.
Hablamos de apariciones libres y ligadas porque en una misma asercin una variable puede
aparecer libre y ligada como en
&
ligada en estas expresiones. Los tipos de las expresiones E( i ) son numricos para los sumatorios
y productos extendidos y ordenados para mximos y mnimos.
Veamos algunos ejemplos:
i : 1 i 100 : (i*i) que representa la suma de las expresiones i*i, cuando i toma valores en el dominio indicado. (se puede indicar la notacin con super y subndices que les
puede resultar ms familiar).
cte
N = ?;
% entero 1
var
v: Vector [1..N] de Ent;
max k : 1 k N : v(k) que representa el mximo de los valores del vector v
El ltimo elemento sintctico que nos resta por introducir son las sustituciones. Resulta muy importante pues es la base de la regla de verificacin de una de las instrucciones ms importantes en
los lenguajes imperativos: la asignacin. El significado intuitivo del proceso de sustitucin es el
siguiente: el aserto A expresa un hecho que se requiere de un estado determinado, referido al
valor de x; A[x/E] expresa un hecho anlogo referido al valor que toma la expresin E.
Definimos una sustitucin como el proceso de reemplazar simultneamente todas las apariciones libres de una variable por una expresin. Emplearemos la notacin
[x/E]
para indicar la sustitucin de la variable x por la expresin E. Tambin
[x1/E1, ... , xn/En]
para indicar la sustitucin simultnea de las variables x1, ... , xn por las expresiones E1, ... , En,
respectivamente.
Las sustituciones slo se pueden realizar para variables libres, pues son stas las que representan valores del estado. En esta definicin se supone que en ningn caso se ha utilizado una variable libre y una ligada con el mismo nombre. De no ser as, es necesario realizar un
renombramiento de las variables ligadas cuyos nombres coincidan con las variables libres que
aparecen en las expresiones o en los asertos.
Algunos ejemplos de sustituciones (correctos e incorrectos)
x:13100:(3=a)
es incorrecto cualquier forma de sustitucin que no sustituya todas las apariciones simultneamente
((x=17)(x>100))[x/(x*x)]o((x*x=17)(x>100))[x/(x*x)]o
((x*x)*(x*x)=17)(x*x>100)
((2*y)*y)[y/z] o
((2*z)*z)
V: IdVar o D
donde V es un estado, IdVar es el conjunto de identificadores de las variables y D es la unin
de los dominios de las variables.
Por ejemplo:
var
x,y:Ent;
b:Bool;
Dada una expresin E de tipo W, definimos el significado de E bajo el estado V, que notaremos
val>E, V@
10
Si P es E = E
val>P,V@ =
cierto
si val>E,V@ = val>E,V@
falso
en otro caso
Aserciones compuestas
Si P es R
val>P,V@ =
cierto
falso
en otro caso
cierto
falso
en otro caso
falso
cierto
en otro caso
falso
cierto
en otro caso
Si P es R l Q
val>P,V@ =
si val>R,V@ = cierto
Si P es R o Q
val>P,V@ =
falso
Si P es R Q
val>P,V@ =
si val>R,V@ = falso
Si P es R Q
val>P,V@ =
cierto
&
&
&
Si P es x : D( x ) : R( x ) entonces
val>P, V@ = cierto
&
si y slo si para todo estado V que extiende a V y asigna valores a las variables x de
tal forma que
val>D, V@ = cierto
se cumple que
val>R, V@ = cierto
11
&
&
&
Si P es x : D( x ) : R( x ) entonces
val>P, V@ = cierto
&
si y slo si para algn estado V que extiende a V y asigna valores a las variables x de
tal forma que
val>D, V@ = cierto
se cumple que
val>R, V@ = cierto
Este significado estar definido solamente en el caso de que V asigne valores a todas las variables libres de P.
de la siguiente forma
V={(x, 1), (y, 2), (b, falso)}
E1 es x=y
val>E1, V@ = falso
E2 es x+(2*y)
val>E2, V@ = 5
P1 es (x=1)(NOTb)
val>P1, V@ = cierto
P2 es i:10<i<15:(i*2<30)
val>P2, V@ = cierto
donde los V seran: {(x, 1), (y, 2), (b, falso), (i, 11)}, ..., {(x, 1), (y, 2), (b, falso), (i, 14)}
De manera recproca a cmo definimos el significado de un aserto bajo un estado definimos el
conjunto de estados que hacen cierto un aserto, que nos servir para definir ms cmodamente el
significado de las expresiones que an nos restan.
Definimos el conjunto de estados que satisfacen un aserto P, que notaremos
est>P@
como
est>P@ =def { V | val>P, V@ = cierto }
Naturalmente, para que un estado satisfaga un aserto, el aserto debe estar definido para ese estado, es decir, el estado debe asignar valores a todas las variables libres del aserto.
12
Veamos algunos ejemplos de los conjuntos de estados que satisfacen ciertos asertos
P1 es falso
est>P1@ =
(pues el valor de operacin constante booleana falso siempre es falso)
est>P2@ = TODOS
P2 es cierto
(pues el valor de la operacin constante booleana cierto es siempre cierto)
P3 es i:Nat:x=2*i
est>P3@ = {V | V asigna a la variable x un valor nulo o par}
Definamos por fin el significado de las expresiones que nos restan:
val>E, V G@
G C
&
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i , y siendo
V G el estado que se obtiene al combinar las asignaciones de V y G.
val>E, V G@
&
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i , y siendo
V G el estado que se obtiene al combinar las asignaciones de V y G.
&
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i , y siendo
V G el estado que se obtiene al combinar las asignaciones de V y G.
13
&
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i , y siendo
V G el estado que se obtiene al combinar las asignaciones de V y G.
&
siendo V el estado que se obtiene al considerar las asignaciones de variables de i .
Ntese, en primer lugar, que los smbolos utilizados en el lenguaje de asertos y en el correspondiente significado difieren, con ello se quiere representar que se trata de entidades diferentes;
por ejemplo, en un caso el sumatorio no es ms que un elemento del lenguaje que tiene unas ciertas propiedades mientras que en el otro el sumatorio se refiere al concepto matemtico conocido
por todos.
Por otra parte, ntese tambin que cuando no hay ningn estado que satisfaga la restriccin de
dominio hemos elegido que el valor de la expresin sea el elemento neutro de la correspondiente
operacin matemtica. Para el mximo y el mnimo los elementos neutros seran, respectivamente, el menor elemento posible y el mayor elemento del dominio; no hemos definido el significado
porque no siempre est garantizado que existan dichos elementos.
Veamos un ejemplo:
Si E es #i:5iy:(i*x>34)
entonces
val>E, V@ = |C|
siendo C
C = {V | val>(5iy)(i*x>34), VV@=cierto}
= {V | val>(5iy), VV@=cierto}{V | val>(i*x>34), VV@=cierto}
= {V | val>(5i4), V@=cierto}{V | val>(i*10>34), V@=cierto}
= {V | val>(i*10>34), V@=cierto} =
14
por tanto,
val>E,V@ = |C| = || = 0
En la verificacin de los algoritmos necesitaremos razonar sobre los asertos, las condiciones
que describen el estado de los programas, y tendremos que poder determinar cundo un aserto es
consecuencia lgica de otro. Para ello introducimos el concepto de fuerza de los asertos.
Fuerza de los asertos
Intuitivamente una condicin es ms restrictiva que otra si es ms difcil de satisfacer, o, dicho
de otro modo, si se satisface en menos ocasiones. Refirindonos a los asertos sobre el estado de
los programas, un aserto ser tanto ms fuerte cuantos menos estados lo satisfagan. Aunque no
es exactamente esta idea la que utilizamos, pues no definimos la fuerza como una magnitud absoluta sino relativa, y de tal forma que puede ocurrir que dos asertos no sean comparables.
Dados dos asertos P y Q, decimos que P es ms fuerte que Q si se cumple
est>P@ est>Q@
y lo notaremos PQ
Decimos en este caso que Q es una consecuencia lgica de P, es decir, siempre que se cumple
P se cumple tambin Q. P es ms restrictiva que Q en el sentido de que puede haber estados que
satisfagan Q pero no as P.
Por ejemplo
varx:Ent;
Conviene no confundir la relacin de fuerza entre aserciones PQ con la conectiva implicacin de construccin de aserciones P o Q.4
Se puede demostrar que falso es ms fuerte que cualquier aserto ya que el conjunto vaco
est contenido en cualquier conjunto y que cualquier aserto es ms fuerte que el aserto cierto
ya que cualquier conjunto est contenido en el conjunto TODOS.
Lo que si es correcto es que si el aserto Po Q se satisface en cualquier estado entonces se tiene que P Q, ya
que, o bien est>P@=, o bien est>P@ est>Q@ debido a la definicin de la semntica del conectivo o.
4
15
Cuando los estados que satisfacen dos asertos son los mismos decimos que esos asertos son
equivalentes
Dados dos asertos P y Q, decimos que P y Q son equivalentes si se cumple
est>P@ = est>Q@
y lo notaremos P Q
por ejemplo
varx:Ent;
tenemos que
x=0 (x<1) (x>-1)
ya que
est>x=0@ = {V | V asigna a x el valor cero }
est>(x<1)(x>-1)@ = {V | V asigna a x un valor que 1 y mayor que 1}
por el conocimiento que tenemos de los nmeros enteros podemos concluir que
est>x=0@ = est>(x<1)(x>-1)@
En el razonamiento con los programas nos interesar conseguir asertos que sean fortalecimientos o debilitamientos de otros dados, es decir, asertos que sean ms fuertes (satisfechos por
menos estados) o ms dbiles (satisfechos por ms estados) que uno dado. Una forma de hacerlo
es aadir una condicin adicional fortalecimiento o aadir una condicin alternativa
debilitamiento.
16
Leyes de equivalencia
Tenemos tres grupos de leyes de equivalencia, segn se refieran a las operaciones, a las conectivas lgicas o a los cuantificadores.
Leyes de las operaciones y los dominios predefinidos
Son leyes que se derivan de las propiedades que verifican las operaciones habituales en los tipos predefinidos. De forma resumida dichas propiedades son:
Elemento neutro. De la suma es el cero, del producto es el uno, del AND es la constante
cierto y del OR la constante falso.
Coerciones de los valores numricos. Todos los naturales son enteros, todos los enteros
son racionales, todos los racionales son reales.
Esta propiedad hace que si nos encontramos con una operacin como x*y con x Real e y Ent,
consideremos que se trata de una multiplicacin entre reales.
Leyes de las conectivas lgicas (lgebra de Boole)
La correccin de estas leyes se basa en la semntica que hemos definido para las conectivas.
Son leyes de equivalencia entre asertos como indica el uso de .
Conmutatividad
PQ QP
PQ QP
PlQ QlP
Asociatividad
P (Q R) (P Q) R
P (Q R) (P Q) R
Idempotencia
PP P
PP P
Distributividad
P (Q R) (P Q) (P R)
P (Q R) (P Q) (P R)
Absorcin
P (P Q) P
P (P Q) P
17
Neutros
P falso falso
P cierto falso
P falso P
P cierto cierto
Contradiccin
P P falso
Tercio excluido
P P cierto
Doble negacin
(P) P
Leyes de De Morgan
(P Q) P Q
(P Q) P Q
Implicacin y doble implicacin
P o Q P Q
P l Q (P o Q) (Q o P)
18
Desplazamiento de cuantificadores
(x : D(x) : P(x)) (x : D(x) : Q(x)) x : D(x) : (PQ)(x)
(x : D(x) : P(x)) (x : D(x) : Q(x)) x : D(x) : (PQ)(x)
Decimos que una operacin f con dominio W1, ..., Wn es parcial si para algn i {1, ..., n}, existe un valor en DWi, para el que no est definida. La notaremos empleando una flecha cortada entre
el dominio y el codominio declarado
19
f: W1 ... Wn o W
Decimos que es total en caso contrario.
Una operacin definida con una operacin total puede no estar definida debido a que uno de
sus argumentos contenga una expresin que no est definida. Por ejemplo
dada la operacin parcial
div : Ent Ent o Ent
la operacin * : Nat Nat o Nat es total, sin embargo la expresin
(x div 0) * 2
no est definida, independientemente del estado que se tome para asignar valores a las variables libres.
El empleo de ciertas expresiones en una especificacin se ve limitado por el hecho de que la
expresin pueda no estar definida. Para poder controlar esta situacin y obtener condiciones que
reflejen el comportamiento de los programas, emplearemos asertos de definicin.
Dada una expresin E, el aserto de definicin de E es
def(E)
Dada una expresin E y un estado V, definimos el significado de def(E) bajo V como:
cierto
si val>E,V@ est definido
val>def(E),V@ =def
falso
en otro caso
Utilizaremos los asertos de definicin para completar los asertos donde aparezcan expresiones
con operaciones parciales. Por ejemplo
Sea E una expresin donde interviene una operacin parcial. El aserto
E*0 = 0
no se puede garantizar que sea siempre cierto. Pues el significado del producto depende de
que sus dos argumentos estn definidos. Lo que s ser cierto es el aserto
def(E) o (E*0=0)
ya que si el aserto de definicin es cierto, el aserto de la conclusin est definido y tambin lo
es; y si el aserto de definicin es falso, la semntica de la conectiva o garantiza que el aserto es
cierto. (No se consigue lo mismo con ?)
Ntese que segn la definicin de los asertos de definicin, se incluye tambin la condicin de
que las variables que aparezcan en una expresin E hayan sido declaradas. Si no fuera as,
val>E,V@ no estara definido, ya que el estado V no les dara valor.
1.1.2
20
Especificacin pre/post
Pasaremos ahora a definir las especificaciones con precondiciones y postcondiciones, y a establecer su significado de modo formal. De esta manera podremos emplearlas para la especificacin
de algoritmos y programas. Esto lo mostraremos por medio de ejemplos que ilustrarn las posibilidades del empleo de asertos para definir condiciones de los programas. Por ltimo, estableceremos una serie de propiedades bsicas de las especificaciones que estarn relacionadas con la
verificacin de algoritmos.
Definimos una especificacin con precondiciones y postcondiciones, o especificacin
pre/post, para un programa o algoritmo A como un esquema:
D
{P}
A
{Q}
donde:
De es un conjunto de declaraciones de constantes y variables disponibles para el programa A
21
cin razonable que haga cierta la especificacin; normalmente el objetivo de esta manipulacin
ser obtener la postcondicin del fragmento de programa que preceda a A. Podemos usar tambin la especificacin como ayuda para el diseo del programa; en tal caso, A ser desconocido,
dispondremos de P y Q, y habremos de disear P de manera que se cumpla { P } A { Q }: derivacin de programas a partir de la especificacin.
Es importante hacer notar que siempre consideramos que estn inicializadas todas aquellas variables a las que haga referencia la precondicin.
Variables auxiliares
Antes de pasar a ejemplos concretos de especificaciones pre/post, necesitamos introducir un
mecanismo ms: las variables auxiliares de la especificacin.
Supongamos que queremos especificar la el programa que realiza la divisin entera de dos
nmeros naturales:
vara,b,c,r:Nat;
{b>0}
dividir
{(a=b*c+r)(r<b)}
vara,b,c,r:Nat;
{b>0a=Ab=B}
dividir
{(A=B*c+r)(r<B)}
Usamos las variables auxiliares A y B, cuyo valor establecemos en la precondicin. Por convenio, escribimos en minscula las variables de programa y en mayscula las auxiliares de la especificacin. El programa no puede modificar el valor de las variables de especificacin ya que no
22
forman parte de su comportamiento visible. El siguiente es un criterio para el uso de este tipo de
variables:
Si una variable de programa tiene un valor de entrada relevante, entonces la precondicin debe
reflejarlo con una ecuacin con esa variable y una variable auxiliar.
De esta forma podremos utilizar el valor de entrada en la postcondicin.
Un concepto relacionado con el uso de variables auxiliares en la precondicin es el de existencia de valor. Aquellas variables para las que no se identifica un valor inicial en la precondicin
podemos suponer que no estn inicializadas; es lo que ocurre con las variables de salida, destinadas a recoger el resultado del algoritmo. En [Pe05] se propone el uso de un valor especial indefinido (A), que se aade a cada dominio, y que se supone el valor de las variables no inicializadas.
Las variables auxiliares de la especificacin se utilizan tambin para indicar que ciertas variables de programa no cambian de valor. As, por ejemplo, podemos reforzar an ms la postcondicin de la divisin entera como
{(A=B*c+r)(r<B)(a=A)(b=B)}
Ligadas. Variables introducidas por cuantificadores y expresiones extendidas. No representan a la memoria del programa y los estados no han de asignarles valores. Se pueden
renombrar sin que se modifique el valor o el significado de la expresin o el aserto en el
que aparecen.
23
varx,y:Ent;
{x=Xy=Y}
intercambio
{x=Yy=X}
varx,y:Ent;
{x=X}
copia
{y=Xx=X}
varx,y:Ent;
y tenemos que especificar un algoritmo que calcule en y la raz cuadrada del valor de x, sabiendo que x contiene un valor que en el cuadrado de un entero.
Una primera versin
varx,y:Ent;
{x=X}
Raz
{y*y=X}
Es incompleta porque no exige a los valores de entrada las propiedades necesarias para poder
ejecutar el programa.
24
varx,y:Ent;
{x=XX=Y*Y}
Raz
{y*y=Xx=X}
varx,y:Ent;
{x=XX=Y*YY:Ent}
Raz
{y*y=Xx=X}
Al escribir esta especificacin uno se puede sentir tentado de escribir la postcondicin como
{y=Yx=X}
que es vlida segn la semntica definida para las especificaciones pre/post, pues si se parte de
un estado que cumple la precondicin se alcanzar un estado en el que y es la raz cuadrada exacta de x. El problema es que para poder relacionar la postcondicin con el significado que se pretende dar al programa es necesario tener en cuenta la precondicin y eso hace que dicha
especificacin resulte poco til en determinadas circunstancias, por ejemplo si quisiramos derivar el algoritmo raz a partir de ella.
Deteccin de potencias de 2
Dada la declaracin de variables:
var
x:Ent;
r:Bool;
se trata de especificar un algoritmo que detecte si el valor de x es una potencia de 2, devolviendo en la variable r el valor cierto o falso, segn sea el caso.
En la precondicin slo hemos de exigir que x tenga valor
{x=X}
25
r=P
Sin embargo, en nuestro lenguaje para la construccin de asertos el igual (=) es una operacin
entre expresiones, y P no es una expresin. La solucin consiste en recordar que toda expresin
booleana es un aserto y que, en particular, r lo es. La equivalencia entre asertos la expresamos
con la conectiva de doble implicacin (l):
{rlPx=X}
i:Nat:x=(j:1ji:2)
var
x:Ent;
r:Bool;
{x=X}
esPotencia2?
{rli:Nat:x=(j:1ji:2)x=X}
cte
N=...;
var
26
v:Vector[1..N]deEnt;
m:Ent;
se trata de especificar un algoritmo que obtenga el mximo de los valores que se encuentran
en el vector. Ntese que en la declaracin, las constantes se nombran en mayscula, ya que al
igual que ocurre con las variables auxiliares de la especificacin, el programa no las puede modificar.
En la precondicin indicamos que el vector ha de tener componentes (para que exista un
mximo) y estar todas ellas inicializadas. En la postcondicin utilizamos la expresin extendida
mximo para indicar la condicin
cte
N=...;
var
v:Vector[1..N]deEnt;
m:Ent;
{N1v=V}(*v=Vexpresaquetodaslascomponentesdevtienen
valor*)
mximo
{m=maxi:1iN:v(i)v=V}
cte
N=...;
var
v:Vector[1..N]deEnt;
x,y:Ent;
hemos de especificar un programa que sustituya todas las apariciones de xenv por y.
En la especificacin slo hemos de indicar que las variables de entrada tengan valor
N1v=Vx=Xy=Y
27
i:1iN:(V(i)=Xov(i)=Y)
Pero con esto no es suficiente, porque debemos indicar adems que las componentes distintas
de x conservan su valor. Para ello debemos reforzar la postcondicin con una frmula similar a la
anterior
i:1iN:(V(i)=Xov(i)=Y)
i:1iN:(V(i)zXov(i)=V(i))
cte
N=...;
var
v:Vector[1..N]deEnt;
x,y:Ent;
{N1v=Vx=Xy=Y}
Reemplazar
{i:1iN:[(V(i)=Xov(i)=Y)(V(i)zXov(i)=V(i))]
x=Xy=Y}
varx,y,c:Ent;
El problema es que ahora el resto puede no ser menor que el divisor, debido a los signos.
Podramos escribir una especificacin donde se reflejasen las distintas posibilidades de combinacin de signos de los operandos, utilizando la conectiva de implicacin. Sin embargo, sera ms
natural si en la especificacin pudisemos utilizar la operacin valor absoluto, que, desgraciadamente no se encuentra entre las operaciones disponibles para el tipo primitivo Ent. Cmo podramos incorporar la operacin valor absoluto a nuestro lenguaje de asertos? Como ya dijimos al
enumerar las operaciones disponibles, para incluir una nueva slo hace falta especificarla totalmente.
Hagmoslo:
28
varx,y:Ent;
{x=X}
absoluto
{(X0oy=X)(X<0oY=(1)*X)x=X}
A partir de este momento, ya podemos utilizar la operacin abs en los asertos que escribamos, con lo que la especificacin de la divisin entera queda
varx,y,c:Ent;
{x=Xy=YYz0}
divisin
{X=Y*c+R0abs(R)abs(Y)x=Xy=Y}
Ntese cmo aqu introducimos una variable auxiliar de la especificacin R para expresar
una condicin que debe cumplir una variable de programa c. En este caso la variable auxiliar
sirve para representar a un valor que no viene dado por el valor de una variable de programa en
ningn estado. La inclusin de variables auxiliares nuevas en la postcondicin debe ser una notacin abreviada para la inclusin de existenciales:
r:Ent:X=Y*c+r
varx,y,r:Ent;
{x=Xy=YYz0}
mdulo
{X=Y*C+r0abs(r)abs(Y)x=Xy=Y}
A partir de este punto podemos utilizar las operaciones div y mod en nuestros asertos, con el
significado dado por estas especificaciones.
Es habitual que en la construccin de una especificacin tengamos que especificar operaciones
auxiliares, esto no tiene que implicar necesariamente que en la implementacin tambin debamos
realizarlas.
Propiedades de una especificacin pre/post
Podemos utilizar las especificaciones pre/post para obtener el programa que las cumple
derivacin o para demostrar que un cierto programa las cumple verificacin. En cualquier
caso necesitaremos razonar con las especificaciones.
El mtodo de razonamiento consistir en aplicar ciertas reglas que nos permiten asegurar que
son correctas ciertas especificaciones, a partir de otras especificaciones correctas y relaciones de
fuerza entre los asertos que las forman. Las reglas estarn compuestas por premisas y conclusiones
separadas por una lnea de quebrado de la siguiente forma:
29
premisas
conclusiones
El sentido de una regla de este estilo es que si se cumplen las premisas, tambin se cumple la
conclusin. En las premisas y conclusiones aparecern smbolos que representarn asertos y acciones genricos.
Hay ciertas propiedades que cumplen los asertos en virtud nicamente de su definicin y caractersticas. Podemos presentar estas propiedades como reglas de verificacin bsicas, pues nos servirn en el proceso de verificacin de un algoritmo, o en la obtencin de otras reglas.
Dados los asertos P, P, P1, P2, Q, Q, Q1 y Q2 y la accin algortmica A se tienen las siguientes
reglas de verificacin bsicas
Reforzamiento de la precondicin
{ P } A {Q } P P
{P}A{Q}
Debilitamiento de la postcondicin
{ P } A {Q }
Q Q
{ P } A { Q }
Conjuncin en la postcondicin
{ P } A {Q1 } { P } A {Q2 }
{ P } A { Q1 Q2 }
Disyuncin en la precondicin
{ P1 } A {Q } { P2 } A {Q }
{ P1 P2 } A { Q }
Precondicin falsa
{ falso } A { Q }
Postcondicin falsa
P falso
{ P } A { falso }
Para demostrar estas reglas basta con aplicar las definiciones de especificacin y de fuerza de
los asertos.
30
Estas reglas se suelen aplicar de atrs adelante, para probar que se cumple la conclusin probamos que se cumplen las premisas. Por ejemplo, aplicando la regla de la disyuncin en la postcondicin, para demostrar la correccin de un algoritmo para la especificacin:
varx,y:Ent;
{x=Xy=Y}
sumaYDoble
{x=2*Xy=X+Y}
{x=Xy=Y}sumaYDoble{x=2*X}
y
{x=Xy=Y}sumaYDoble{y=X+Y}
Las leyes de equivalencia junto con estas reglas proporcionan un clculo para razonar con predicados y especificaciones, alternativo al razonamiento en trminos de estados. Las leyes se toman como axiomas del clculo y las reglas como un procedimiento de deduccin para obtener nuevas
leyes. Se demuestra que dicho clculo es correcto, lo que quiere decir que toda equivalencia deducida por l es vlida en la lgica de predicados. Sin embargo, cuando se admiten dominios de valores cualesquiera, el clculo no es completo, lo que significa que no toda equivalencia vlida en la
lgica de predicados es deducible mediante el clculo.
Estas reglas no forman un mtodo completo de verificacin de algoritmos. Necesitamos poder entrar en la forma de las acciones A para poder llevar a cabo la verificacin. Como veremos
en el apartado dedicado a las estructuras algortmicas bsicas.
Otro mtodo de razonamiento sobre las especificaciones radica en el uso del transformador de
predicados precondicin ms dbil que nos permitir razonar sobre la correccin de los programas
utilizando solamente asertos con los que razonaremos utilizando el clculo de predicados.
Precondicin ms dbil
Las reglas de verificacin bsicas son un mecanismo incompleto para demostrar la correccin
de los programas; vamos a introducir otro concepto que nos permitir razonar dentro del mbito
de la lgica de predicados de primer orden.
Supongamos que nos encontramos con una accin algortmica A y queremos comprobar si
cumple la especificacin formada por P y Q como precondicin y postcondicin, respectivamente, con unas ciertas declaraciones que omitimos. Esto es, hemos de comprobar:
{P} A {Q}
La idea de la verificacin es proceder de atrs hacia delante. La especificacin se cumplir
cuando partiendo de un estado que cumpla P se llegue a un estado que cumpla Q. Vamos a pre-
31
guntarnos cmo son los estados tales que partiendo desde ellos y ejecutando A se llega a Q. Estos estados se caracterizarn por unas condiciones, por un aserto R que cumplir:
{R} A {Q}
En este punto, si resulta que P es ms fuerte que R, puede aplicarse la regla de reforzamiento
de la precondicin y obtener as que el algoritmo es correcto.
Analizaremos para cada posible accin A de un lenguaje concreto cul es ese aserto R. Antes
formalizaremos este concepto y estudiaremos algunas de sus propiedades.
Dadas una accin A y un aserto Q, definimos la precondicin ms dbil de A inducida por Q,
que notaremos
pmd( A, Q)
como el aserto que cumple
est>pmd( A, Q)@ = { V | A iniciado en V termina en un estado V est>Q@
Ntese que la precondicin as definida es la ms dbil posible pues incluye a todos los estados que cumplen la propiedad de ser iniciales de A para obtener Q.
Aplicando la definicin de especificacin pre/post y la de precondicin ms dbil tenemos
que
Dados una accin A y un aserto Q se cumple
{ pmd( A, Q) } A { Q }
Si se cumple lo anterior y aplicando la regla de reforzamiento de la precondicin obtenemos la
propiedad bsica de las precondiciones ms dbiles
Dados los asertos P y Q, y una accin A se tiene la siguiente regla de verificacin:
P pmd(A, Q)
{P}A{Q}
Esta es la propiedad que nos permite razonar sobre la correccin de los programas en trminos de frmulas lgicas exclusivamente una vez obtenida la precondicin ms dbil.
Las precondiciones ms dbiles cumplen una serie de propiedades que se pueden presentar
como su definicin axiomtica [Bal93] o como proposiciones que se demuestran utilizan las definiciones de fuerza de los asertos y de especificacin pre/post. Adems, en el problema 12 se pide
que se explique el significado operacional de estas propiedades incluida la propiedad bsica.
Estas propiedades son:
32
Monotona:
Q1 Q2
pmd( A, Q1 ) pmd( A, Q2 )
Exclusin de milagros:
En la direccin
Sea V un estado cualquiera que satisface
pmd( A, Q1 Q2 )
por tanto, A iniciado en V termina en un estado V de
est>Q1 Q2@ = est>Q1@ est>Q2@
como V est>Q1@ entonces V satisface
pmd( A, Q1)
y como V est>Q2@ entonces V satisface
pmd( A, Q2)
por tanto V satisface
pmd( A, Q1 ) pmd( A, Q2 )
y al ser V un estado cualquiera que satisface pmd( A, Q1 Q2 ) se tiene
pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )
En la otra direccin () el razonamiento es reversible, fijndonos en que si V comienza en un
estado que cumple pmd( A, Q1 ) pmd( A, Q2 ) termina en un estado V que satisface Q1 Q2,
con lo que se tiene
pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )
33
Aunque estas propiedades nos resulten de utilidad, necesitamos ser capaces de obtener la precondicin ms dbil para cualquier accin algortmica escrita en un determinado lenguaje, para lo
cual debemos fijar el lenguaje que vamos a utilizar para escribir los programas y determinar para
cada una de sus instrucciones cmo se obtiene la precondicin ms dbil inducida por una postcondicin dada. De esto se ocupa el siguiente apartado de este tema.
1.1.3
Especificaciones informales
Aunque las especificaciones pre/post constituyen un mecanismo preciso y conciso de describir el comportamiento de los programas, no debemos pensar que sustituyen por completo a la
documentacin de los mismos. Un poco de lenguaje natural ayuda a completar la informacin
que proporciona una especificacin y facilita la comprensin de los programas.
Un posible esquema de documentacin de una accin:
Nombre de la accin.
Efecto. Una descripcin del efecto resultante de ejecutar la accin, en trminos de las entradas y las salidas.
Las metodologas de diseo que se estudian en Ingeniera del Software suelen incluir normas
sobre la documentacin.
34
do de cada instruccin al indicar cmo se comporta respecto de los asertos que se cumplen en el
programa. Constituyen lo que se denomina una semntica axiomtica. La obtencin de estos axiomas se justificar mediante la obtencin de la precondicin ms dbil inducida por una postcondicin, es decir, preguntndonos cul es la condicin ms dbil que debe cumplir qu es lo
mnimo que debemos exigir el estado inicial para que al ejecutar la instruccin se acabe en un
estado que cumpla la postcondicin.
1.2.1
La instruccin seguir
Su sintaxis es
seguir
pmd( seguir, Q) Q
El axioma indica cmo ha de ser la precondicin para, al ejecutar la instruccin, obtener una
postcondicin.
{Q}
seguir
{Q}
Y, por ltimo, la regla de verificacin nos da una visin prctica del axioma, orientada a su uso
en un mtodo que emplee razonamiento hacia atrs:
PQ
{ P } seguir { Q }
35
varx,y:Ent;
{P:x1}
seguir
{Q:x0}
La asignacin simple
Su sintaxis es
x:=E
donde E es una expresin del mismo tipo que la variable x. Intuitivamente, se evala la expresin E y se asigna su valor a la variable x, con lo que se modifica el estado siempre que el resultado de la expresin tenga un valor distinto al valor original de x.
Qu debe cumplir el estado inicial para llegar a un estado que cumpla Q? La parte interesante
de Q es la que hace referencia a la x, pues la parte que no haga referencia simplemente se debe
mantener. En cuanto a la parte que se refiere a la x, debemos darnos cuenta de que sea lo que sea
que el aserto Q dice sobre x, el aserto inicial debe decirlo sobre el valor que se le ha asignado a la
x, es decir, E. Eso se expresa como
Q [x/E]
Por otra parte, hemos de garantizar que la expresin E puede evaluarse, y por ello un estado
inicial ha de cumplir tambin:
def(E)
con lo que la precondicin ms dbil queda
36
{ def(E) Q [x/E] }
x := E
{Q}
y la regla de verificacin
P def(E) Q [x/E]
{ P } x := E { Q }
Veamos un ejemplo de verificacin de la asignacin (es el ejercicio 16)
varx,y:Ent;
{P:x22*y>5x=Xy=Y}
x:=2*x+y1
{Q:x3>2}
2*x + y 1 3 > 2
lgebra
2*x + y > 6
lgebra
x2y3
lgebra
x 2 2*y > 5
fuerza
x 2 2*y > 5 x = X y = Y
P
37
Un efecto colateral sera, por ejemplo, que una asignacin a una variable x modificase no slo
el valor de x (el significado de la instruccin) sino que tambin modificase el de otra variable y.
Nuestro lenguaje algortmico carece de efectos colaterales, pero no as la mayora de los lenguajes
de programacin donde es una cuestin de disciplina evitar que se produzcan. Sin embargo, en
ocasiones los efectos colaterales son necesarios si no se quiere degradar la eficiencia de manera
intolerable: comparticin de estructura con punteros vs. copia de las estructuras de datos para
evitar la comparticin. De hecho, en la segunda parte del curso realizaremos implementaciones
de los tipos abstractos de datos que tienen efectos colaterales.
La asignacin mltiple
Su sintaxis es
<x1,...,xm>:=<E1,...,Em>
siendo las xi variables distintas entre s, y las Ei expresiones de los mismos tipos que los de las
variables xi, para i {1,...,m}. Intuitivamente, se evalan las expresiones Ei y se modifica el estado de forma simultnea, asignando a cada variable xi el valor de la correspondiente expresin Ei.
La simultaneidad es relevante cuando en las Ei interviene alguna de las xi.
La obtencin de la pmd es una extensin de lo realizado en la asignacin simple: hay que garantizar la definicin de las expresiones que se asignan, y que los estados iniciales cumplen la
condicin expresada por la postcondicin en la que cada variable xi se sustituye por la correspondiente expresin Ei.
pmd(<x1, ... , xm> := <E1, ... , Em>, Q) def(E1) ... def(Em) Q[x1/E1, ..., xm/Em]
el axioma que indica la forma de la precondicin para obtener la postcondicin
{ def(E1) ... def(Em) Q[x1/E1, ..., xm/Em] }
<x1, ... , xm> := <E1, ... , Em>
{Q}
y la regla de verificacin que se obtiene aplicando la propiedad bsica de las pmd.
P def(E1) ... def(Em) Q[x1/E1, ..., xm/Em]
{ P } <x1, ... , xm> := <E1, ... , Em> { Q }
Veamos un ejemplo con el intercambio de los valores de dos variables
varx,y:Ent;
{P:x=Xy=Y}
<x,y>:=<y,x>
38
{Q:x=Yy=X}
las expresiones estn definidas al no contener operaciones parciales y estar declaradas las variables
def(x) def(y) x:Ent y:Ent P
y la postcondicin con la sustitucin
x = Y y = X [x/y, y/x]
sustitucin
y=Yx=X
boole
x=Xy=Y
P
cteN=...;%Ent
varv:Vector[1..N]deEnt;
i,j:Nat;
{1iNi=j}
v(i):=0
{v(j)=0}
39
Y, es ms, la regla deja de ser correcta pues permite verificar programas errneos (es el ejercicio 21.b, casi).
cteN=...;%Ent
varv:Vector[1..N]deEnt;
{1v(2)N}
v(v(2)):=1
{v(v(2))=1}
que parece correcto y, de hecho se puede probar que lo es con la regla de verificacin de la
asignacin simple
v(v(2)) = 1 [v(v(2))/1]
sustitucin
1=1
lgebra
cierto
fuerza
1 v(2) N
40
cteN=...;%Ent
varv:Vector[1..N]deEnt;
i,j:Nat;
{1iNi=j}
v(i):=0
{v(j)=0}
1 i N def(i)
P
la expresin que se asigna est definida
def(0)
cierto
P
la postcondicin con la sustitucin se obtiene de la precondicin
41
ecuacin
fuerza
1.2.2
0=0i=j
P
Las acciones que vamos a ver a continuacin permiten combinar otras instrucciones para conseguir acciones ms complejas. Veremos tres tipos de composicin que son las que caracterizan la
programacin imperativa estructurada:
Composicin secuencial
Composicin alternativa
Composicin iterativa
Los lenguajes de programacin suelen incluir ms de una instruccin para cada uno de estos
esquemas, pero, como veremos, las instrucciones de nuestro lenguaje algortmico permiten construir acciones que representen a cualquier construccin disponible en un lenguaje imperativo estructurado. Al reducir el nmero de instrucciones facilitamos la verificacin puesto que tenemos
que tratar con menos reglas.
Composicin secuencial
Su sintaxis es
A1 ; A2
siendo A1 y A2 acciones expresadas en el lenguaje algortmico.
Intuitivamente, el comportamiento es que se ejecuta la accin A1 y a su terminacin (si se produce) se ejecuta la accin A2.
Para obtener la pmd empleamos el concepto de razonamiento hacia atrs. Para llegar a una
postcondicin Q hemos de partir de un estado que cumpla la pmd de A2 inducida por Q y, a su
vez, para alcanzar ese estado intermedio hemos de partir de un estado que cumpla la pmd de A1
inducida por la pmd(A2, Q):
pmd( A1;A2, Q ) pmd( A1, pmd( A2, Q))
42
Un estado que cumpla R, tomado como entrada de A2, produce un estado que cumple la
postcondicin
La ejecucin de A1 con entrada en un estado que cumple la precondicin produce un estado que cumple R
La pmd de la segunda accin es un aserto intermedio vlido, pero la regla de verificacin permite considerar otros
{ P } A1 { R } { R } A2 { Q }
{ P } A1 ; A2 { Q }
la obtencin de esta regla se puede justificar por las propiedades de las pmd
Por la definicin de pmd a partir de { P } A1 { R }
P pmd( A1, R)
Por la definicin de pmd a partir de { R } A2 { Q }
R pmd( A2, Q)
Por la propiedad de monotona de las pmd, aplicada a la anterior relacin, con respecto a
la accin A1 ( P Q // pmd(A,P) pmd(A,Q) )
Veamos un ejemplo de verificacin de la composicin secuencial. Vamos a verificar un programa que, dada un cantidad positiva de dinero obtiene a cuntas monedas de 5 y 1 pesetas se
corresponde.
varc,d,p:Ent;
43
%
%c=cantidad,d=monedasde5,p=monedasde1
%
{P:c=CC>0}
d:=cdiv5;
p:=cmod5
{Q:C=d*5+p0d<C0p<5c=C}
En este programa usamos las operaciones div y mod con el significado dado por la especificacin que vimos en el apartado dedicado a los ejemplos de especificacin.
Para aplicar la regla de la composicin secuencial hemos de obtener el aserto intermedio, que
en este caso ser la pmd de la segunda asignacin:
pmd( p := c mod 5, Q)
definicin
sustitucin
lgebra
donde, en el primer paso aplicamos que c mod 5 est definido (cierto S S); y en el ltimo
paso, hemos aplicado 0 c mod 5 < 5 c 0 (hace falta para luego conseguir C > 0)
llamamos R al aserto obtenido
P def( c div 5 ), que se cumple pues div est definido si el divisor es distinto de 0 y la
variable est declarada
P R [d/c div 5]
R [d/c div 5]
sustitucin
lgebra
fuerza P
donde, en el ltimo paso hemos aplicado
C=c
C=c
C=cC>0
0 c div 5 < C
C=cC>0
c0
44
pmd( A1 ; A2 ; A3, Q ) pmd( A1, pmd( A2 ; A3, Q )) pmd( A1, pmd( A2 , pmd( A3, Q )))
y, en general, la composicin secuencial de n acciones:
pmd( A1 ; ... ; An, Q) pmd( A1, ... pmd( An, Q) ... )
y la regla de verificacin extendida
{ P } A1 { R1 } ... { Rn1 } An { Q }
{ P } A1 ; ... ; An { Q }
45
Declaraciones locales
En ocasiones es necesario definir variables de apoyo para almacenar valores temporales de los
algoritmos. Estas variables tienen una naturaleza local en tanto en cuanto su utilidad se limita a
un mbito de instrucciones reducido. Permitimos el uso de variables locales con la siguiente sintaxis:
con el estado ampliado, se ejecuta la accin A, que puede inicializar, acceder y modificar
las variables locales
Para obtener la pmd pensemos que la declaracin local est despus de la precondicin y antes
de la postcondicin, por lo que, si las variables slo existen dentro del mbito indicado y son
variables con nombres distintos a las que existan previamente, no pueden aparecer en la precondicin ni en la postcondicin. Por lo tanto la precondicin se obtiene exclusivamente a partir de
la accin A:
46
No todos los lenguajes de programacin permiten definir variables locales en lugares arbitrarios del cdigo. Lo que s es ms habitual es que se puedan definir en los subprogramas: procedimientos y funciones.
Composicin alternativa
Es la instruccin condicional del lenguaje que permite definir una bifurcacin en el flujo de
control del programa. Su sintaxis es
si B1 o A1
B2 o A2
...
Bm o Am
fsi
siendo las Bi expresiones booleanas y las Ai acciones del lenguaje algortmico. Cada una de las
estructuras Bi o Ai se denomina accin protegida donde la accin Ai est protegida por la barrera Bi.
Una barrera se dice que est abierta si evala a cierto y se dice que est cerrada en caso contrario.
Intuitivamente la composicin alternativa funciona de la siguiente forma
Se evalan todas las barreras (por lo que stas deben estar definidas)
De entre las barreras abiertas, se elige una de modo indeterminista. Esta eleccin debe poder realizarse siempre (por lo que al menos una barrera debe estar abierta).
Para obtener la pmd hemos de considerar que todas las barreras deben estar definidas, al menos una de ellas abierta, y que si una barrera est abierta la ejecucin de su accin asociada debe
terminar en un estado que cumpla la postcondicin. La pmd ha de garantizar estas condiciones:
47
si B1 o A1
B2 o A2
...
Bm o Am
fsi
{Q}
y la regla de verificacin
P def(B1) ... def(Bm)
P B1 ... Bm
{ P B1 } A1 { Q } ... { P Bm } Am { Q }
{ P } si B1 o A1 ... Bm o Am fsi { Q }
Veamos un ejemplo que ilustra el uso de la regla de verificacin de la composicin alternativa:
el mximo de dos nmeros (es el ejercicio 30, aunque all se usa la operacin max)
varx,y,z:Ent;
{P:x=Xy=Y}
sixyz:=x
yxz:=y
fsi
{Q:x=Xy=Y((z=XzY)(z=YzX))}
x y y x cierto
por propiedades de los nmeros enteros
La correccin de cada accin en el supuesto de que la barrera est abierta
4. { P x y } z := x { Q }
que verificamos usando la regla de la asignacin
P xy def(x) al estar declarada la variable
Q[z/x]
sustitucin
Boole
Fuerza
x = X y = Y ((x = X x Y) (x = Y x X))
x=Xy=Y(xYx=Y)
x=Xy=YxY
P xy
48
5. { P y x } z := y { Q }
que verificamos usando la regla de la asignacin
P yx def(y) al estar declarada la variable
Q[z/y]
sustitucin
Boole
Fuerza
x = X y = Y ((y = X y Y) (y = Y y X))
x=Xy=Y(y=XyX)
x=Xy=YyX
P yx
Al igual que en la composicin secuencial tambin podemos anotar la composicin condicional con los asertos intermedios oportunos:
{P}
si B1 o { P B1 } A1 { Q }
B2 o { P B2 } A2 { Q }
...
Bm o { P Bm } Am { Q }
fsi
{Q}
El indeterminismo de nuestra composicin secuencial no est presente en los lenguajes de
programacin que optan por la primera barrera abierta (Pascal) o que siguen todas las barreras
abiertas (C). Si queremos que nuestros programas sean deterministas deberemos modificar las
condiciones de las barreras para evitar que ciertos estados abran ms de una barrera a la vez. As
por ejemplo en el anterior programa bastara con cambiar el de la segunda barrera por un mayor estricto. Este indeterminismo [Bal93] es interesante ya que en el diseo algortmico conviene
no descartar posibilidades y tomar el mnimo de decisiones arbitrarias; as, si existen dos maneras
de lograr una postcondicin, el indeterminismo puede permitir que en el algoritmo consten ambas, aunque tal vez en un paso posterior de programacin se descarte una de ellas.
La instruccin condicional que hemos presentado se corresponde con una versin extendida
de la sentencia CASE que suelen incluir los lenguajes imperativos estructurados. La ms habitual
instruccin IF-THEN-ELSE se puede expresar en nuestro lenguaje como:
siBoA1
NOTBoA2
fsi
49
sinoA2
fsi
it B o
A
fit
Operacionalmente se comporta de la siguiente forma:
Se evala la condicin de repeticin B (que debe estar definida)
Al pensar qu forma tiene la pmd surge el concepto de invariante. Supongamos la especificacin de un bucle
{pmd}
itBo
A
fit
{Q}
La idea es que dependiendo del estado de partida, el bucle se ejecutar un nmero diferente de
veces, y todos esos estados deben satisfacer la pmd. Supongamos que partimos de un estado V
que satisface la pmd y que hace que el bucle se ejecute n veces y acabe en un estado que cumple
Q; al ejecutarse la primera vez, termina en un estado V1 que servir como entrada para la siguiente
pasada por el bucle que despus de ejecutarse n1 veces acabar en un estado que cumpla Q, por
lo que V1 tambin debe cumplir la pmd. Es decir, todos los estados intermedios que se alcanzan
al final de cada pasada por el bucle deben cumplir la pmd pues de hecho se pueden ver como
estados iniciales que despus de un cierto nmero de pasadas i alcanzan la postcondicin.
50
Al ejecutarse la ltima pasada por el bucle llegamos a un estado Vn que al tomarse como entrada del bucle har que no se cumpla la condicin de repeticin con lo que el bucle terminar. Pero
an as Vn tambin debe cumplir la pmd pues se corresponde con la situacin en que el bucle no
se ejecuta ninguna vez y se alcanza directamente la postcondicin. La conclusin es que la pmd es
una propiedad que se debe cumplir antes y despus de ejecutar cada pasada por el bucle; es una
condicin que permanece constante durante todas las iteraciones y que por esa razn se denomina invariante del bucle.
Por ejemplo,
<i,q,p>:=<0,0,1>;
iti<no
i:=i+1;
q:=q+p;
p:=p+2;
fit
V
V1
0
1
0
1
1
3
V2
3 9 7
V3
.
.
.
.
.
.
.
.
.
.
.
.
podemos observar que en todas las iteraciones las variables guardan una cierta relacin entre
s, en este caso
q = i2 p = 2*i + 1
Por lo tanto para encontrar la pmd debemos encontrar un aserto I que cumpla
(i.2) I garantiza que la condicin de repeticin se puede evaluar
I def(B)
I se preserva al ejecutar cada vuelta del bucle
{IB}A{I}
(i.3) I garantiza que al terminar se cumple la postcondicin
I B Q
Pero con esto no est todo, pues para que un programa sea correcto debe terminar y dado que
el nmero de iteraciones depende del estado inicial, cabe la duda de si se alcanzar un estado que
51
haga falsa la condicin de repeticin. La pmd debe garantizar que el bucle termina, pero cmo
podemos asegurar la terminacin de un bucle? La idea es que si un bucle termina es porque el
nmero de veces que se ejecuta es finito; cada vez que se ejecuta una iteracin, disminuye el
nmero de iteraciones que resta por ejecutar.
Hemos de obtener una medida del nmero de vueltas que puede dar un bucle y garantizar que
esa medida cumple dos requisitos:
Para conseguirlo utilizaremos una expresin que decrezca en cada vuelta y de la que se sepa
que no puede decrecer indefinidamente: una expresin que tome valores en un dominio donde
est definida una relacin de orden bien fundamentada.
Una relacin de orden (parcial o total) se dice bien fundamentada si no existen cadenas decrecientes infinitas. (Una cadena decreciente es una sucesin de valores donde cada valor es menor
que el anterior.)
Un dominio se dice bien fundamentado si en l existe un orden bien fundamentado.
La idea es que en un dominio bien fundamentado no podemos encontrar cadenas decrecientes
infinitas.
Algunos ejemplos de dominios bien fundamentados:
Todo dominio finito con un orden estricto (el orden estricto impide la repeticin de los
elementos de la cadena y el nmero finito de elementos hace que no puedan existir cadenas infinitas)
Los naturales () con el orden decreciente estricto habitual. (como mucho pueden llegar
hasta 0)
Los reales () con respecto a un orden total. (podemos construir cadenas infinitas crecientes y decrecientes contenidas en cualquier intervalo de los reales)
Los enteros (Z) con respecto a un orden total. (podemos construir cadenas infinitas crecientes y decrecientes.)
Volviendo a la pmd de la composicin iterativa, para definir la pmd hemos de asegurar que
Existe una expresin de acotacin C, que cumple los siguientes requisitos:
52
(c.1) Dentro del bucle, C est definida, toma valores en un dominio bien fundamentado y
puede decrecer
I B def(C) dec(C)
siendo dec(C) (que leemos decrementable) un aserto que indica que C toma valores en
un dominio bien fundamentado y que no es minimal (puede decrecer).
(c.2) Al ejecutar el cuerpo del bucle el valor de C decrece
{IBC=T}A{C % T}
donde T es un valor del dominio de C y % es el orden bien fundamentado.
Ntese que en ocasiones ser necesario incluir en el invariante condiciones que hagan referencia a la expresin de acotacin, para poder as garantizar la terminacin del bucle. con todo esto
ya podemos escribir la pmd de la composicin iterativa como
pmd( it B o A fit ) I
siempre que existan I y C que verifiquen los requisitos (i.2), (i.3), (c.1) y (c.2).
(ntese que son c.1 y c.2 las que garantizan que el bucle termina)
el axioma se obtiene directamente de la pmd
Si existen un aserto I y una expresin C que cumplen los requisitos: (i.2), (i.3), (c.1) y (c.2), entonces
{I}
it B o
A
fit
{Q}
y, por fin, la regla de verificacin, que tambin recoge que la precondicin ha de ser ms fuerte
que la pmd:
(i.1)
PI
(i.2)
I def(B)
{IB}A{I}
(i.3)
I B Q
(c.1)
I B def(C) dec(C)
(c.2)
{IBC=T}A{C % T}
{ p } it B o A fit { Q }
siendo I un aserto y C una expresin
53
Veamos un ejemplo verificando un programa que calcula el producto de dos enteros mediante
sumas. Es el ejercicio 36(a)
varx,y:Ent;
{P:x=Xy=Yy0}
p:=0;
ityz0o
p:=p+x;
y:=y1
fit
{Q:p=X*Y}
Tenemos la composicin de una asignacin con un bucle. Para verificar esta composicin necesitamos un aserto intermedio, y para encontrarlo tenemos dos alternativas:
Deducir un aserto intermedio a partir de la precondicin y el efecto de la asignacin.
R: x = X y = Y y 0 p = 0
A continuacin vamos a verificar el bucle, procediendo de atrs hacia delante, tomando R como precondicin de bucle.
Para aplicar la regla de verificacin debemos obtener el invariante y la expresin de acotacin.
Este algoritmo suma x a p y veces. La idea para obtener el invariante es observar que en cada
pasada p se va acercando al valor X*Y incrementndose en x, hasta que al final, como se indica
en la postcondicin, alcanza p=X*Y. As, en una iteracin cualquiera
p + algo = X*Y
cunto vale ese algo? El nmero de x que quedan por sumar a p, que son precisamente y.
p + x*y = X*Y
otra forma de obtenerlo sera considerar que, en una iteracin cualquiera, se cumple que
p = x*(Yy) p = x*Y x*y p + x*y = x*Y
y como x = X en todos los estados p + x*y = X*Y
54
otro posible invariante sera p + x*y = x*Y x = X, aunque supongo que para demostrar la
correccin con l sera necesario completar la postcondicin con x = X o quizs no?
Por lo que se refiere a la terminacin del bucle, parece que una buena expresin de acotacin
sera
C: y
ya que el bucle se ejecutar y veces, hasta que y tome el valor cero. Sin embargo, y es una variable entera, por lo que no toma valores en un dominio bien fundamentado; de hecho, si y tuviese un valor negativo el bucle no terminara. Afortunadamente, el aserto R indica que y 0 con lo
que la expresin de acotacin es correcta. An as, es necesario que el invariante incluya esta condicin para que as podamos demostrar las condiciones relacionadas con la terminacin.
Con todo esto el invariante queda
I: p + x*y = X*Y y 0
y la expresin de acotacin
C: y
pasamos entonces a demostrar las condiciones de la regla de verificacin.
{R:x=Xy=Yy0p=0}
ityz0o
p:=p+x;
y:=y1
fit
{Q:p=X*Y}
(i.1) R I
Ry0
Rx=Xy=Yp=0
x*y = X*Y p = 0
0 + x*y = X*Y p = 0
p + x*y = X*Y
donde se han aplicado propiedades del lgebra de los enteros, con lo que
R p + x*y = X*Y y 0
I
(i.2) que pide en primer lugar I def(B)
55
def( y z 0) cierto
I
Adems, hay que probar
{IB}
p:=p+x;
y:=y1
{I}
para lo cual aplicamos la regla de la composicin secuencial, obteniendo la pmd de la segunda asignacin inducida por la postcondicin
def(y1) I[y/y1]
sustitucin
p + x*y = X*Y
y0yz0
y>0
y1
y1 0
p + x*y = X*Y y = 0
p + 0 = X*Y
p = X*Y
Q
cierto
IB
dec(y)
y>0
y0yz0
56
IB
(c.2) Hay que probar la correccin de
{ I B y = T}
p:=p+x;
y:=y1
{y<T}
para lo cual procedemos aplicando la regla de la composicin secuencial, obteniendo la
pmd de la segunda asignacin inducida por la primera
def(y1) y<T[y/y1]
sustitucin
cierto y 1 < T
Boole y 1 < T
que llamaremos R3. Ahora tendremos que obtener la pmd de la primera asignacin inducida por R3, que ser def(p+x) R3[p/p+x], pero como p+x est definida y en R3 no
aparece p como variable libre, nos queda el mismo aserto R3. Para probar la correccin de
la composicin nos resta demostrar
I B y = T R3
lo que se obtiene
y=T
y1<T
R3
Con esto queda demostrada la correccin del bucle para la precondicin R, ahora nos queda
probar
{P}
p := 0
{R}
aplicando la regla de la asignacin
def(0) R[p/0]
sustitucin
Boole y lgebra
cierto x = X y = Y y 0 0 = 0
x=Xy=Yy0
P
con lo que es correcta esta asignacin y, por la regla de la composicin secuencial, es correcto
tambin todo el algoritmo.
Si en lugar de postular el aserto intermedio R hubisemos utilizado el invariante como aserto
intermedio, la verificacin hubiese sido similar. La condicin i.1 se habra simplificado al reducirse a I I, mientras que se habra complicado la demostracin de la correccin de la asignacin
p := 0, al tener que demostrar P I [ p/0 ]; para lo cual hay que utilizar un razonamiento similar
al utilizado en el ejemplo para el requisito i.1
57
Al igual que ocurre con las dems instrucciones compuestas, en la composicin iterativa tambin se define un formato de programa anotado
{P}
{I;C}
it B o
{IB}
A
{I}
fit
{ I B }
{Q}
Para la verificacin de los bucles es imprescindible encontrar un invariante y una expresin de
acotacin, lo cual en muchos casos no resulta trivial. Es una destreza que se desarrolla con la
prctica.
Ntese que pueden existir muchos asertos invariantes de un bucle. El que se necesita ha de ser
suficientemente fuerte para que, junto con B, conduzca a la postcondicin, y lo bastante dbil
para que se cumpla antes de la primera iteracin (lo implique la precondicin) y el cuerpo del
bucle lo mantenga invariante.
Obtencin de invariantes
Podemos plantear dos estrategias particulares y ciertas recomendaciones generales:
p = X*Y y = 0
58
equivalente a
p + y = X*Y y = 0
y tambin a
p + y*x = X*Y y = 0
de donde se puede obtener el invariante p + y*x = X*Y. La condicin que se refiere a la expresin de acotacin se podra aadir al intentar demostrar (c.1) y no obtener que la expresin de
acotacin es decrementable, por lo que se hara necesario aadir y 0. A veces se puede fortalecer el invariante segn avanza la verificacin, y an as no ser necesario modificar la demostracin
ya realizada, pues si una versin debilitada del invariante es suficiente para demostrar un cierto
requisito tambin lo ser el invariante completo.
2. Reemplazar constantes por variables.
i:1iN:v(i)=0
aqu lo que se estara construyendo sera un vector con todas sus componentes a 0, de forma que en un punto intermedio de la ejecucin del bucle slo habra una parte del vector a 0.
Dependiendo de si la iteracin asciende o desciende por el vector, la constante candidata a ser
sustituida por una variable ser N o 1.
En general podemos ofrecer las siguientes
Recomendaciones generales
Analizar la postcondicin
59
B : y = 0
nos indica que y puede ser la expresin de acotacin.
En otras ocasiones puede ser necesario algo ms de elaboracin. Supongamos un bucle que
recorre un vector en sentido ascendente, con una condicin de repeticin
B : i z N+1
y condicin de terminacin
B : i = N+1
que es el mayor de los valores que toma i; para que la expresin de acotacin vaya disminuyendo hasta llegar al valor minimal podemos tomar
C:N+1i
de forma que
i=N+1N+1i=0
Resulta ms complicado cuando la condicin de terminacin no involucra expresiones que
tomen valores en dominios bien fundamentados, en cuyo caso puede ser necesario aplicarles una
funcin de transformacin para conseguirlo.
En general podemos ofrecer las siguientes
Recomendaciones generales
Observar la condicin de terminacin.
Estudiar las expresiones que involucra y las variables que contiene y que se ven modificadas en el cuerpo del bucle.
60
Siempre hay que pensar que quizs es posible demostrar que el bucle no termina
(probndolo con un contraejemplo).
Sintaxis
repetirAhastaBfrepetir
Sintaxis
paraidesdeMhastaNhacerAfpara
61
Sintaxis
paraibajandodesdeMhastaNhacerAfpara
Sintaxis
paraidesdeMhastaNpasoPhacerAfpara
62
Parmetros formales: aquellas variables que se designan como parmetros al definir la accin.
Parmetros reales: las expresiones que se utilizan en el lugar de los parmetros formales
en cada llamada concreta.
El reemplazamiento de los parmetros formales por los parmetros reales se llama paso de
parmetros. Este proceso implica un flujo de informacin cuyo sentido viene dado por el modo de
uso de cada parmetro
Definimos el modo de uso de los parmetros, que se ha de especificar en la definicin de la
accin parametrizada, como una de las siguientes posibilidades
Entrada, que notaremos como e.
Cada uno de los modos de uso tiene unas restricciones con respecto a las expresiones que
pueden actuales como parmetros reales y formales
Parmetros de entrada
Real. Puede ser cualquier expresin (el paso de parmetros la evala y le asigna el valor al correspondiente parmetro formal)
Formal. Se trata como una constante local que se inicializa con el valor del parmetro
real
Parmetros de salida
Real. Slo puede ser una variable, que al finalizar puede haber sido modificada. El
paso de parmetros la hace corresponder con el correspondiente parmetro formal
Formal. Se trata de una variable local no inicializada (no se puede suponer nada sobre
su valor)
Parmetros de entrada/salida
63
Real. Slo puede ser una variable, que al finalizar puede haber sido modificada. El
paso de parmetros la hace corresponder con el correspondiente parmetro formal
Formal. Se trata de una variable local que se supone inicializada con el valor de la variable del parmetro real.
NO
SI
SI
Parmetro real
es consultado
SI
NO
SI
puede modificarse
NO
SI
SI
Ntese que no permitimos realizar asignaciones a los parmetros de entrada: esto puede significar la definicin de variables auxiliares adicionales.
En el lenguaje algortmico tenemos dos tipos de acciones parametrizadas: procedimientos y
funciones.
1.3.1
Procedimientos
los Wi, Gj y Uk son tipos del lenguaje algortmico y representan los tipos de los parmetros
para i = 1, , n, j=1,,m y k=1,,p
64
la variable pos tendr un valor fuera del rango si no existe ninguna componente igual a x.
Declaracin de procedimientos
En la declaracin se completa la especificacin con las declaraciones locales y el cuerpo del
procedimiento
Definimos una declaracin de un procedimiento como un esquema DP:
proc nombreProc (e u1:W1; ; un:Wn; s v1:G1; ; vm:Gm; es w1:U1; ; wp:Up);
{ P0 }
cte ;
var ;
inicio
A
{ Q0 }
fproc
donde
la cabecera de la declaracin, la precondicin P0 y la postcondicin Q0 coinciden con la
especificacin del procedimiento
Las declaraciones locales pueden ser vacas, en cuyo caso no aparecern las palabras cte ni
var
65
La accin A es una accin del lenguaje algortmico que slo emplea los parmetros que
aparecen en la cabecera, las variables y constantes de las declaraciones locales y que respeta el modo de uso de los parmetros.
El ltimo requisito garantiza que no existen efectos colaterales, es decir no hay cambios en el estado que no recojan las especificaciones. En la prctica los lenguajes permiten que no se respete el
modo de uso de los parmetros, por ejemplo, se puede modificar el valor de un parmetro de
entrada sin que ello afecte al parmetro real.
Por ejemplo, para el algoritmo de divisin entera tenemos la siguiente declaracin:
procdivMod(ex,y:Nat;sc,r:Nat);
{P0:x=Xy=YY>0}
inicio
<c,r>:=<0,x>;
{I:x=c*y+ry>0x=Xy=Y;
C:r}
itryo
c:=c+1;
r:=ry
fit
{Q0:x=c*y+rr<yx=Xy=Y}
Llamadas a procedimientos
El ltimo mecanismo que necesitamos introducir es el de las llamadas a los procedimientos.
Definimos una llamada a un procedimiento con una especificacin EP como la instruccin LP
nombreProc(E1, , En, y1, , ym, z1, , zp)
donde
Las yj y zk son variables distintas que admiten los tipos Gj y Uk, respectivamente, para
j=1,,m y k=1,,p
Las variables yj y zk no aparecen en las expresiones Ei, para i=1,,n, j=1,,m y k=1,
,p
La ltima condicin es necesaria para que las reglas de verificacin funcionen correctamente,
como veremos despus. Si no se cumpliese este requisito tendramos que introducir variables
auxiliares que nos permitiesen realizar la verificacin.
Los tipos de los parmetros reales no tienen que coincidir exactamente con los de los parmetros formales cuando existen relaciones de compatibilidad entre tipos. As, para un parmetro for-
66
mal de entrada de tipo Real podemos utilizar un parmetro real de cualquier tipo compatible con
Real: Nat, Ent, Rac, Real. De igual forma, para un parmetro formal de salida de tipo Ent podemos usar un parmetro real de cualquier tipo con el que Ent sea compatible: Ent, Rac, Real. En
los parmetros de entrada/salida se combinan las dos restricciones de forma que el tipo debe ser
exactamente el mismo. La idea es que en el flujo de informacin que implica el paso de parmetros, se puede asignar a una variable de un cierto tipo un valor de un tipo con menos informacin.
Verificacin de procedimientos
Las llamadas a procedimientos se consideran como instrucciones del lenguaje algortmico, y
como tales, deben llevar asociada su regla de verificacin.
{P}
nombreProc(E1, , En, y1, , ym, z1, , zp)
{Q}
Podramos sustituir la llamada al procedimiento por su cuerpo y verificar as, pero no queremos tener que verificar el cuerpo cada vez, nos queremos aprovechar de la abstraccin funcional.
De esta forma, lo que haremos ser verificar por separado (y una sola vez) la declaracin del procedimiento con respecto a su especificacin, y la verificacin de las llamadas se reducir a verificar su correccin con respecto a la especificacin del procedimiento.
Para demostrar que una declaracin de procedimiento DP es correcta con respecto a su especificacin EP, es necesario aplicar las reglas de verificacin al cuerpo del procedimiento. Si el
cuerpo del procedimiento contiene llamadas a otros procedimientos, habr que aplicar a stas las
reglas de verificacin de llamadas.
Con respecto a la correccin de las llamadas, la idea bsica consiste en demostrar que la precondicin de la llamada es ms fuerte que la precondicin del procedimiento y que la postcondicin del procedimiento es ms fuerte que la postcondicin de la llamada. Pero cuidado: puede
haber una parte del estado que no se vea afectada por la ejecucin del procedimiento; las expresiones de la precondicin de la llamada que no hagan referencia a variables de salida del procedimiento, debern aparecer en la postcondicin, pero no ser posible obtenerlas a partir de la
postcondicin del procedimiento, pues ste no las afecta.
Por ejemplo
{x=Xy=Yy>0h=2*x}
divMod(x,y,c,r)
{x=y*c+rr<yx=Xy=Yh=2*x}
67
Dada una especificacin de procedimiento como en EP para la que tenemos una declaracin
asociada DP que es correcta
pmd(nombreProc(E1, , En, y1, , ym, z1, , zp), Q)
F P0[u1/E1, , un/En, w1/z1, , wp/zp]
siendo F el aserto ms dbil que cumple
F Q0[u1/E1, , un/En, v1/y1, , vm/ym, w1/z1, , wp/zp] Q
De aqu podemos obtener directamente el axioma de la llamada y la regla de verificacin. Sin
embargo, la regla de verificacin as obtenida no es fcil de aplicar porque es necesario encontrar
en cada caso el aserto F. Es por ello que, a partir de esta misma idea lo que se ve afectado por el
procedimiento y lo que no definimos un mtodo de verificacin de llamadas en tres pasos.
Mtodo de verificacin de llamadas:
6. Comprobar si se cumple:
P P0[u1/E1, , un/En, w1/z1, , wp/zp]
(Ntese que para que se cumpla esto es necesario que se cumpla la parte de P0 que hace
referencia a que los parmetros de entrada y entrada/salida deben tener valor y adems
estos valores deben ser de los tipos adecuados. Esto ha de comprobarse de forma ms
cuidadosa cuando se trata de tipos sobre los que hay definida alguna relacin de compatibilidad.)
7. Extraer de
P P0[u1/E1, , un/En, w1/z1, , wp/zp]
todas las condiciones que dependen de variables que han de dar algn resultado de salida
y1, , ym, z1, , zp
y definir R como el aserto restante.
(En general no basta con eliminar los asertos donde hay variables de entrada o entrada/salida, y puede ser necesario elaborar los asertos para no perder condiciones derivadas.)
8. Comprobar el requisito
R Q0[u1/E1, , un/En, v1/y1, , vm/ym, w1/z1, , wp/zp] Q
Se puede demostrar que si el mtodo de verificacin termina, la llamada es correcta, partiendo
de la pmd y razonando sobre la fuerza de los asertos, teniendo en cuenta que F es el aserto ms
dbil que puede hacer el papel de R.
Veamos un ejemplo de verificacin de una llamada:
68
dada la declaracin
procacumula(ess:Ent;ex:Ent);
{P0:x=Xs=S}
inicio
s:=s+x
{Q0:s=S+xx=X}
fproc
9. P P0[s/suma, x/2*a*b]
PP0[s/suma,x/2*a*b]
suma>a*b2*a*b=Xsuma=S
P0[s/suma,x/2*a*b]
2*a*b=Xsuma=S
cierto
P
(**)
las condiciones que dependen de variables de salida, en este caso suma. Para ello es necesario extender primero el aserto
suma>a*b2*a*b=Xsuma=S
suma>a*b2*a*b=Xsuma=SS>a*b
Q
2*a*b=XS>a*bsuma=S+2*a*b2*a*b=
suma>a*b+2*a*b
suma>3*a*b
Q
69
Hay que tener cuidado con las llamadas mal construidas. Veamos un ejemplo en que la regla
de verificacin permite verificar la correccin de un ejemplo errneo, porque la llamada no cumple la restriccin de que las variables reales de salida o entrada/salida no pueden aparecer en las
expresiones correspondientes a los parmetros reales de entrada.
{ P : suma = 5 }
Acumula(suma, suma)
{ Q: suma = 3 }
Intuitivamente el resultado debera ser 10, pero como la llamada no cumple la restriccin indicada veamos qu ocurre cuando intentamos verificarla:
12. P P0[s/suma, x/suma]
P0[s/suma,x/suma]
suma=Xsuma=S
cierto
P
PP0[s/suma,x/suma]
suma=5suma=Xsuma=S
las condiciones que dependen de variables de salida, en este caso suma. Para ello es necesario extender primero el aserto
suma=5suma=Xsuma=S
suma=5suma=Xsuma=SX=5S=5
R: X = 5 S = 5
14. Comprobar
RQ0[s/suma,x/suma]
Q
RQ0[s/suma,x/suma]
X=5S=5suma=S+sumasuma=S
X=5S=5S=0suma=S
falso
Q
70
El problema es que no estamos distinguiendo las apariciones de suma que representan un dato
de entrada de las que representan un dato de salida. La solucin sera utilizar una variable auxiliar
que, antes de la llamada, inicializamos con el valor de suma.
Uso de los procedimientos como asertos. Entendido como un aserto, la llamada a un procedimiento es, en esencia, equivalente a establecer que se cumple la postcondicin aplicada sobre
los argumentos de la llamada.
Es decir dada una especificacin de procedimiento EP
Funciones
71
Especificacin de funciones
los Wi y Gj son tipos del lenguaje algortmico y representan los tipos de los parmetros para
i = 1, , n, y j=1,,m
Vemos que no es necesario indicar explcitamente el modo de uso de los parmetros, al estar
separados. Naturalmente, exigimos que los identificadores de los parmetros formales sean distintos entre s, pues representan valores distintos. La precondicin no puede tener condiciones que
se refieran a los parmetros de salida y, finalmente, la postcondicin ha de exigir que los parmetros de entrada no sean modificados. En las funciones nos referimos a los parmetros de entrada
como argumentos y a los de salida como resultados.
Por ejemplo, la divisin y el mdulo se puede especificar como una funcin
func divMod( x, y : Nat) dev c, r : Nat;
{ P0: x = X y = Y y > 0 }
{Q0: x = c*y+r r < y x = X y = Y }
Declaracin de funciones
Es similar a la declaracin de procedimientos, el cuerpo se compone de las declaraciones, una
accin algortmica y una accin de devolucin, al final, que establece los valores de los parmetros de salida.
Definimos una declaracin de funcin como un esquema DF:
func nombreFunc (u1:W1; ; un:Wn) dev v1:G1; ; vm:Gm;
{ P0 }
cte ;
var ;
72
inicio
A;
{ Q0 }
dev <v1, , vm>
ffunc
donde
la cabecera de la funcin, la precondicin P0 y la postcondicin Q0 coinciden con la especificacin de la funcin EF
las declaraciones locales pueden ser vacas en cuyo caso no aparecern las palabras cte ni
var.
la accin A es una accin del lenguaje algortmico que slo emplea los parmetros que
aparecen en la cabecera y las variables y constantes de las declaraciones locales, y que respeta el modo de uso de los parmetros (no modifica el valor de los parmetros de entrada).
El ltimo requisito garantiza la ausencia de efectos colaterales. Ntese que prohibimos realizar
asignaciones a las variables de entrada; los lenguajes de programacin realmente no imponen esta
restriccin pues las variables de entrada almacenan copias de los parmetros reales.
Los valores que se devuelven deben verificar la postcondicin, y es por eso que la accin de
devolucin aparece detrs de aquella.
Veamos algunos ejemplos de declaracin de funciones:
funcdivMod(x,y:Nat)devc,r:Nat;
{P0:x=Xy=Yy>0}
inicio
<c,r>:=<0,x>
{I:x=c*y+ry>0x=Xy=Y;
C:r}
itryo
c:=c+1;
r:=ry
fit;
{Q0:x=c*y+rr<yx=Xy=Y}
dev<c,r>
ffunc
Una cosa curiosa de este ejemplo es que vemos cmo el invariante de un bucle depende de la
postcondicin (el invariante debe cumplir que I B Q); pues de no ser porque tenemos que
obtenerlo en la postcondicin, no se nos ocurrira incluir las condiciones x=X y=Y en el invariante.
Otro ejemplo:
73
funcmcd(x,y:Nat)devm:Nat;
{P0:x>0y>0x=Xy=Y}
var
a,b:Nat;
inicio
<a,b>:=<x,y>;
{I:mcd(a,b)=mcd(x,y)x=Xy=Y
C:a+b}
itazbo
sia>boa:=ab
b>aob:=ba
fsi
fit;
m:=a;
{Q0:m=mcd(x,y)x=Xy=Y}
devm
ffunc
las yj son variables distintas que admiten los tipos Gj, para j=1, , m. Estas variables actan como los parmetros reales de salida.
Los comentarios sobre la compatibilidad entre tipos que hicimos al describir las llamadas a
procedimientos son tambin aplicables aqu, teniendo en cuenta que los parmetros son variables
de entrada (el valor real puede ser de un tipo con menos informacin) y los resultados son variables de salida (el parmetro real puede ser de un tipo con ms informacin).
Operacionalmente en la llamada a una funcin:
74
se ejecuta el cuerpo de la funcin en ese estado auxiliar (incluida la declaracin de variables y constantes locales que puede ampliar el estado)
al terminar la ejecucin de la accin del cuerpo (si termina) se asignan a los parmetros
reales de salida los valores correspondientes que tengan los parmetros formales en el estado auxiliar final
75
{ Q : suma = 5 * num! }
la llamada es correcta porque la variable que recoge el resultado no aparece en la expresin
que se usa como argumento, y adems los tipos son correctos. Sin embargo, necesitamos introducir una variable auxiliar de la correccin f
{ P : num > 0 }
var f: Nat;
inicio
f := fact(num);
suma := 5 * f
fvar
{ Q : suma = 5 * num! }
Una declaracin local es correcta si lo es su cuerpo.
El cuerpo lo verificamos como una composicin secuencial. El aserto intermedio que usamos
en la composicin secuencial ser la pmd de la asegunda asignacin
R1: 5*f = 5 * num!
con lo que pasamos a verificar
{ P : num > 0 }
f := fact(num);
{ R1 : 5*f = 5 * num! }
usando el mtodo de verificacin de las llamadas a procedimientos:
15. Comprobamos
P P0[n/num]
P0[n/num]
num>0num=N
P
PP0[n/num]
num>0num=N
todas la condiciones que dependen de las variables de salida. Como ninguna condicin se
refiere a f, tomamos el aserto completo
R : num > 0 num = N
17. Comprobar el requisito
R Q0[x/f, n/num] R1
RQ0[x/f,n/num]
76
num>0num=Nf=num!num=N
lgebrayfuerza 5*f=5*num!
R1
Igual que ocurre con los procedimientos, se producen errores en la verificacin si intentamos
verificar funciones que no se ajusten al esquema LF (las variables de salida aparezcan en las expresiones de entrada).
Uso de las funciones como asertos. Entendido como un aserto, la llamada a una funcin
es, en esencia, equivalente a establecer que se cumple la postcondicin aplicada sobre los argumentos de la llamada.
Es decir dada una especificacin de funcin EF
Para darnos cuenta del impacto del consumo de recursos veamos unas tablas ideales con distintos rdenes de consumo
Programa Consumo n=1.000
Bytes
Tiempo
77
A1
log2 n
10
10 segs
| 10
1.000
A2
| 1 KB
16 mins
A3
n2
1.000.000
| 1 Meg
11 das
Los programadores de los algoritmos A2 y A3 podran esperar que las mejoras tecnolgicas
hiciesen tiles sus algoritmos. Sin embargo tomemos un volumen de datos de un milln (no descabellado: bases de datos de poblacin, resolucin de impresin, pixeles en pantalla, )
Programa Consumo n=1.000.000
A1
log2 n
| 20
A2
n
1.000.000
A3
n2
1012
Bytes
10
Tiempo
20 segs
| 1 KB
11 das
| 1.000 Gig
30.000 aos
Vemos cmo el programa A1 se ve muy poco afectado por el aumento en el volumen de datos; mientras que los programas A1 y A2 tienen aumentos prohibitivos, que los descalifican para la
tarea.
El programa A1 s puede aprovechar las mejoras tecnolgicas: con un procesador dos veces
ms rpido puede procesar 1000 veces ms informacin; mientras que el programa A3 con un
procesador 1.000 veces ms rpido slo tardara 30 aos en realizar la tarea.
Un algoritmo eficiente es un bien mayor que cualquier mejora tecnolgica; y stas se deben
entender, fundamentalmente, como un medio para procesar un mayor volumen de datos y no
como una forma de remediar la pobre eficiencia de nuestros algoritmos. En resumen
Es conveniente, en todo caso, partir de un diseo claro, aunque sea ineficiente; y optimizar posteriormente.
1.4.1
78
Complejidad de algoritmos
La eficiencia de los algoritmos se cuantifica por medio de medidas de complejidad. Consideramos dos medidas de complejidad diferentes:
Nosotros nos centraremos en la complejidad en tiempo que suele ser ms crucial (la complejidad en espacio se suele resolver aadiendo ms memoria).
El tiempo (y tambin el espacio) que tarda en ejecutarse un programa en una mquina concreta depende de:
El valor de los datos de entrada (la mayor o menor dificultad que entrae el procesamiento de los datos)
La mquina y el compilador.
En nuestro estudio vamos a ignorar este tercer factor pues afecta a todos los algoritmos por
igual, como un factor constante (con una mquina el doble de rpida el algoritmo tardar la mitad); y, por lo tanto, no afecta a las comparaciones entre algoritmos.
El tamao de los datos se pueden referir a distintas cosas segn el tipo de datos y de procesamiento que se lleve a cabo: para un vector su longitud, para un nmero su valor o su nmero
de dgitos,
Con respecto al segundo factor, el valor de los datos, y una vez fijado el tamao de los datos,
podemos adoptar dos enfoques, estudiar el peor caso posible, estableciendo as una cota superior; o analizar el caso promedio, para lo cual debemos estudiar las posibles entradas y su probabilidad.
&
Definimos el coste temporal del algoritmo A con entrada x , que notaremos
&
tA( x )
&
como el tiempo consumido durante la ejecucin del algoritmo A con entrada x .
A partir del coste para unos ciertos datos podemos definir el coste en el caso peor y en el caso
promedio
Definimos la complejidad de A en el caso peor, que notaremos
TA(n)
como
79
&
&
TA(n) =def max{ tA( x ) | x de tamao n }
Definimos la complejidad de A en el caso promedio, que notaremos
TMA(n)
como
&
&
TMA(n)=def p( x ) tA( x )
x de tamao n
&
&
&
siendo p( x ) la funcin de probabilidad (p( x ) [0,1]) de que la entrada sea el dato x .
Aunque la complejidad en promedio parece ms realista, su estimacin exige determinar
la distribucin de probabilidades de los datos iniciales y hacer clculos matemticos complejos.
Lo ms habitual es trabajar con la complejidad en el caso peor, que da una cota superior del
tiempo consumido por cualquier cmputo particular con datos de tamao n.
Vamos a realizar los clculos de la complejidad de manera terica a partir de las instrucciones
que aparecen en los algoritmos, viendo cmo afecta cada instruccin al coste. En cierto modo,
contaremos cuntas instrucciones se ejecutan. No nos planteamos la medida emprica del tiempo de ejecucin porque queremos conocer los resultados a priori.
Debido a que las diferencias en el coste se hacen significativas cuando el tamao de los datos
aumenta, no nos va a interesar encontrar la funcin TA(n) exacta, sino que nos bastar con encontrar otra funcin f(n) que sea del mismo orden de magnitud; para que as podamos decir
que para n grande TA(n) se comporta como f(n): medidas asintticas de la complejidad.
Veamos un primer ejemplo donde se calcula el orden de magnitud del coste temporal de un
algoritmo que determina si una matriz cuadrada de enteros es simtrica:
varv:Vector[1..N,1..N]deEnt;
b:BOOL;
{Pre.:v=VN1}
var
I,J:Ent;
inicio
b=cierto;
paraIdesde1hastaN1hacer
paraJdesdeI+1hastaNhacer
b:=bAND(v(I,J)=v(J,I))
fpara
fpara
fvar
{Post:v=Vbli,j:1i<jN:v(i,j)=v(j,i)}
80
(donde Ni es el nmero de veces que se ejecuta el bucle interno por cada pasada por el bucle
externo)
= 1 + (N-1) + (N-2) + + 1
1 ( N 1)
* (N1)
=1+
2
N * ( N 1)
=1+
2
2
N para todo N 1
y podemos decir por tanto que el coste del algoritmo A es el del orden de magnitud de N2.
1.4.2
Una medida asinttica es una agrupacin de funciones que muestra un comportamiento similar cuando los argumentos toman valores muy grandes. Estos conjuntos de funciones se definen
en trminos de un funcin de referencia f. As definimos tres medidas asintticas segn que la
funcin de referencia sirva como cota superior, cota inferior o cota exacta de las funciones del
conjunto.
En estas definiciones vamos a considerar funciones T y f con el perfil
T, f: N o R+ {0}
donde N es el dominio del tamao de los datos y R+ el valor del coste del algoritmo (ya sea
temporal o espacial, pues las definiciones que siguen son vlidas para ambos casos, as como para
el caso peor y el caso promedio).
Imaginamos que T representa la complejidad de un cierto algoritmo.
Definimos la medida de cota superior f, que notaremos
O(f)
(omicrn mayscula, que abreviaremos por O grande) como el conjunto
{ T | existen c R+, n0 N tales que, para todo n n0, T(n) c f(n) }
Si T O(f) decimos que T(n) es del orden de f(n) y que f es asintticamente una cota superior del crecimiento de T (las funciones de O(f) crecen como mucho a la misma velocidad que
f).
Definimos la medida de cota inferior f, que notaremos
:(f)
(omega mayscula, que abreviaremos llamndola omega grande) como el conjunto
81
f es O(f)
f es de :(f)
f es 4(f)
82
Ordenes de complejidad
Cuando las medidas asintticas se aplican sobre funciones de referencia concretas tenemos
conjuntos concretos de funciones. Habitualmente nos referiremos a las medidas asintticas aplicadas a funciones concretas como rdenes. Algunos rdenes tienen especial inters porque la funcin de referencia es simple y su comportamiento se puede estudiar fcilmente. Estos rdenes
tienen nombres particulares:
83
La jerarqua de los rdenes de complejidad nos sirve como marco de referencia para comparar
la complejidad de los algoritmos. Un algoritmo ser tanto ms eficiente cuanto menor sea su
complejidad dentro de la jerarqua de rdenes de complejidad.
El estudio de los algoritmos para determinar el orden de magnitud de su complejidad se llama
anlisis de algoritmos (que da ttulo a este apartado). El anlisis de algoritmos sofisticados puede ser una tarea muy compleja.
Podemos establecer una comparacin entre algoritmos con rdenes de complejidad diferentes,
para ver cmo se ven afectados con el tamao de los datos.
Supongamos dos algoritmos A y B que resuelven el mismo problema pero con complejidades
diferentes:
TA O(log n)
TB O(2n)
35
30
25
20
Serie1
Serie2
15
10
5
0
0
10
12
TB(n) c 2n para n n1
84
350000
300000
250000
200000
Serie1
Serie2
150000
100000
50000
0
0
100
200
300
400
500
600
Para A aumentar mucho el tamao de los datos aumenta poco el tiempo necesario; y
aumentar poco el tiempo disponible (la velocidad de la mquina) aumenta mucho el
mximo tamao tratable.
log(2n) = 1 + log n
Nos remitimos a la tabla que presentamos al principio del tema, donde el consumo se refiere en realidad a la complejidad.
Como conclusin llegamos a la idea que ya presentamos al principio del tema:
Slo un algoritmo eficiente, con un orden de complejidad bajo puede tratar volmenes de datos grande con un consumo de recursos razonable, y aprovechar las mejoras tcnicas de forma
que produzcan un incremento en el tamao de los datos que puede tratar.
En este sentido, se suele considerar:
Un algoritmo A es
85
Y decimos que
Un algoritmo es tratable si tiene un algoritmo que lo resuelve con complejidad de orden menor que 2n. Decimos que es intratable en caso contrario.
Para algunos problemas se ha demostrado que efectivamente no existe ningn algoritmo que
los haga tratables. En cambio, para otros, simplemente no se ha encontrado ningn algoritmo,
pero no se ha podido demostrar que no exista.
1.4.4
Las definiciones tericas que hemos visto hasta ahora son aplicables tanto a la complejidad
temporal como a la espacial. A partir de aqu vamos a estudiar cmo se puede analizar en la
prctica la complejidad temporal. En la segunda parte de la asignatura haremos algunas consideraciones sobre la complejidad espacial de las estructuras de datos utilizadas en la implementacin
de tipos abstractos de datos.
Vamos a ir analizando la complejidad para cada una de las estructuras de nuestro lenguaje algortmico.
Antes, presentamos un par de reglas que sirven de utilidad para analizar los algoritmos.
Demostracin
Por la definicin de O se tiene:
existen c1, c2 R+; n1, n2 N tales que
n n1 T1(n) c1 f1(n)
n n2 T2(n) c2 f2(n)
suma
T1(n) + T2(n) c1 f1(n) + c2 f2(n)
86
c1 f1(n) c2 f2(n)
A1 ; A2 y TAi(n) O(fi(n))
TA(n)= TA1(n)+TA2(n)
y por la regla de la suma TA O( max( f1(n), f2(n) ) )
si B1 o A1 Br o Ar fsi
TAi(n) O(fi(n)) (1 i n)
entonces TA O( max( f1(n), , fr(n) ) )
siempre que podamos suponer que la evaluacin de las barreras consume tiempo
constante. Si esta suposicin no es razonable y sabemos que
TBi(n) O(gi(n)) (1 i n)
entonces TA O( max( g1(n), , gr(n), f1(n), , fr(n) ) )
it B o A1 fit
TA1(n) O(f(n))
TB(n) O(g(n))
Sea h(n) =def max( g(n), f(n))
Entonces el tiempo de ejecucin de una vuelta del bucle ser O(h(n))
87
Si podemos estimar que el nmero de vueltas en el caso peor es O(t(n)), tendremos (usando la regla del producto):
TA(n) O(h(n) t(n))
Generalmente es posible una estimacin ms fina de TA(n), estimando por separado el tiempo de cada vuelta y calculando un sumatorio.
Si el tiempo de ejecucin de la vuelta nmero i es O(h(n,i)) entonces:
TA(n) O( i : 1 i t(n) : h(n,i) )
Otras clases de bucles se analizan con ideas anlogas.
Aparte de estas reglas generales, una idea til en muchos casos prcticos es la de accin caracterstica. Se trata de localizar en el texto del algoritmo A las acciones que se ejecutan con ms
frecuencia: las acciones caractersticas. El orden de magnitud de TA(n) se podr estimar calculando el nmero de ejecuciones de acciones caractersticas (en realidad, se calcula una cota superior
de ese nmero). La idea es que, puesto que en la composicin secuencial se suman las complejidades, y la regla de la suma nos dice que la complejidad de una suma es igual al mximo de las
complejidades de los sumandos, podemos hacer los clculos quedndonos directamente con los
mximos. Por ejemplo, en un programa que contenga un bucle, normalmente la accin caracterstica ser el cuerpo del bucle.
Veamos por ltimo un par de ejemplo de clculo de la complejidad.
El primero es una implementacin ms eficiente del algoritmo que comprueba si una matriz es
simtrica
funcesSim(a:Vector[1..N,1..N]deEnt)devb:Bool;
{Pre.:a=A}
varI,J:Ent;
inicio
b:=Cierto;
I:=1;
itI<NANDb
oJ:=I+1
itJNANDb
ob:=bAND(a(I,J)=a(J,I))
J:=J+1
fit;
I:=I+1
fit
{Post.:a=A(bli,j:1i<jN:a(i,j)=a(j,i))}
devb
ffunc
88
Ntese que este algoritmo parece mejor que el que presentamos al principio del tema porque
se detiene en cuanto detecta que la matriz no es simtrica. Sin embargo, vamos a ver cmo, en el
caso peor, la complejidad sigue siendo O(N2). Para obtener la complejidad en el caso promedio
deberamos tener una estimacin de la probabilidad de los distintos valores de la matriz, y el
clculo sera muy complejo.
Como tamao de los datos podemos tomar N o N2; optamos por N porque eso nos facilitar
el clculo de la complejidad.
Suponemos que todas las asignaciones, evaluacin de condiciones y acceso a vectores tienen
complejidad constante O(1). Podemos obtener una expresin fina, contando el nmero de instrucciones:
cuerpo del bucle interno:
2
(podramos considerar 3 si cada acceso al vector fuese 1)
coste del bucle interno
((NI) (2+1)) + 1
= 3 (N-I) + 1
coste del cuerpo del bucle interno
1 + (3(N-I) + 1) + 1 = 3(N-I) + 3
coste del bucle externo
N 1
[ (3(N-I) + 3 + 1)] + 1
I 1
condicin de salida
N 1
= 3 (N-I) + 4(N-1) + 1 = 3
I 1
N ( N 1)
+ 4(N-1) + 1
2
= 3/2 N2 3/2 N + 4N 4 +1
= 3/2 N2 + 5/2 N 3
y el coste del algoritmo, considerando las dos asignaciones iniciales
TA(N) = 3/2 N2 + 5/2 N 3 + 2 = 3/2 N2 + 5/2 N 1
con lo que TA(N) O(N2)
Sin embargo, podemos llegar directamente al mismo resultado razonando sobre la instruccin
caracterstica:
Consideramos como acciones caractersticas las asignaciones que componen el cuerpo del bucle ms interno. El bucle interno se ejecuta desde I+1 hasta N, es decir, se ejecuta N-I veces para
cada valor de I, por lo tanto:
N 1
T(N) O(
I 1
(NI)) = O(N2)
89
varx,y,p:Ent;
{Pre.P:x=Xy=Yy0}
p:=0;
ityz0o
sipar(y)
entonces<x,y>:=<x+x,ydiv2>
sino<p,y>:=<p+x,y1>
fsi
fit
{Post.Q:p=X*Y}
n=0:
n=1:
n=2:
n=3:
n=4:
n=5:
y=0
y=1
y=2
y=3
y=4
y=5
y=0
y=1
y=2
y=2
y=4
90
y=0
y=1 y=0
y=1 y=0
y=2 y=1 y=0
formulamos la conjetura
t(n) 2 log n para n 2
esta conjetura se demuestra por induccin sobre n
base
n=2
n=3
t(n) = 2 = 2 log(n)
t(n) = 3 2 log(3)
32log(3)2322log(3)23(2log3)2233289cierto
paso inductivo
n > 3, par
n = 2n, n 2
= 2 log(2 n)
= 2 log( n )
n > 3, impar
n = 2n+1, n 2
= 2 log(2 n)
< 2 log( 2n + 1 )
= 2 log n
1.4.5
max
i min
min max
( max min 1)
2
91
El mtodo de derivacin
Asignacin.
Usamos esta instruccin si podemos encontrar una asignacin A tal que
P pmd(A, Q)
El caso ms habitual es cuando la nica diferencia entre la postcondicin y la precondicin es una igualdad de la forma
x=E
donde E es una expresin que se puede obtener mediante operaciones predefinidas o especificadas.
Composicin secuencial
Usamos esta opcin cuando decidimos que para obtener la postcondicin Q es necesario
obtener un aserto intermedio R, que nos acerca a Q. En ese caso, la derivacin de A se
transforma en la derivacin de dos acciones A1 y A2 que cumplan
{ P } A1 { R }
Composicin alternativa
{ R } A2 { Q }
92
Usamos esta opcin cuando decidimos que los datos de entrada no admiten un tratamiento uniforme para obtener la postcondicin.
Deberemos encontrar condiciones B1, , Bn que discriminen los casos homogneos,
de tal forma que se cumpla
P B1 Bn
Y entonces, para cada condicin Bi deberemos derivar las acciones Ai que verifiquen
{ P Bi } Ai {Q }
para i= 1, , n
Composicin iterativa.
Usamos esta opcin cuando decidimos que la postcondicin debe obtenerse repitiendo
una determinada accin.
Para obtener el algoritmo utilizamos el mtodo de derivacin de bucles
La opcin ms interesante, habitual y difcil es esta ltima, que tiene su propio mtodo y que
es de lo que nos ocupamos en el resto de este tema.
Derivacin de bucles
B.1. Invariante
I B Q
y de ah, la condicin de repeticin. (Hemos de demostrar que efectivamente se cumple
i.3)
En ocasiones los pasos B.1 y B.2 se realizan simultneamente.
B.3. Definicin de la condicin de repeticin
Comprobamos si
I def(B)
En ocasiones no ser as porque en B (y en I) hayan aparecido variables nuevas (sustituir
constantes por variables). En ese caso se debern introducir con una declaracin local.
B.4. Expresin de acotacin
93
I B def(C) dec(C)
(Recurdese que para obtener expresiones de acotacin, la recomendacin era observar la
condicin de terminacin, as como las variables que se modificaban en el bucle).
B.5. Accin de inicializacin.
Si la precondicin no garantiza que se cumple el invariante antes de entrar en el bucle, entonces es necesario derivar una accin de inicializacin que establezca el invariante.
Esta accin se deriva a partir de la especificacin
{P } A0 { I }
B.6. Accin de avance.
Se deriva una accin A2 que haga avanzar al bucle hacia su terminacin. Se puede derivar
a partir de la especificacin
{ I B C = T } A2 { c % T }
B.7. Es suficiente con la accin de avance?
Nos preguntamos si la accin de avance constituye el cuerpo del bucle. Para ello, debemos demostrar
{ B I } A2 { I }
Si lo cumple, entonces hemos acabado de obtener un algoritmo correcto.
B.8. Restablecimiento del invariante
Si la accin de avance no es suficiente (lo ms habitual), entonces necesitamos derivar una
nueva accin A1 que se encargue de restablecer el invariante dentro del bucle, de forma
que
{ I B } A1 ; A2 { I }
la accin A1 se puede derivar de la especificacin
{ I B } A1 { R }
94
{P}
varx1:W1;;xn:Wn;
inicio
A0
{I;C}
itBo
{IB}
A1;
{R}
A2;
{I}
fit;
{IB}
fvar
{Q}
Insistir otra vez en que lo que nos aporta este mtodo es que nos obliga a concentrarnos en las
distintas partes del problema, comprobando paso a paso que vamos bien; y, si nos equivocamos, tenemos puntos bien definidos a los que volver.
1.5.2
Estos dos ejemplos se diferencian por la forma en que se obtiene el invariante a partir de la
postcondicin. Las tcnicas que usaremos son las mismas que comentamos al final del apartado
de verificacin de bucles: tomar una parte de la conjuncin que forma la postcondicin; y sustituir constantes por variables en las postcondicin.
Hay otra recomendacin general en cuanto a la construccin de invariantes a partir de una especificacin:
Si la precondicin y la postcondicin del bucle contienen una igualdad x=X, el invariante tambin debe incluirla.
No parece razonable aunque no sea imposible que una variable empiece tomando valor, que
se ve modificado por el bucle, para que al final vuelva a tomar el valor inicial.
Existe una diferencia esencial en la obtencin del invariante para una verificacin a posteriori y
una derivacin: en la derivacin tenemos menos informacin, y por lo tanto menos restricciones,
sobre el bucle, de forma que disponemos de ms libertad a la hora de seleccionar el invariante.
95
{P: x = X y = Y x 0 y > 0 }
divMod
{Q: x = X y = Y x = c*y + r 0 r < y }
Empezamos por determinar de manera informal la idea del algoritmo. En este caso, nos
proponemos obtener el cociente como el nmero de veces que hay que restarle y a x para obtener
un valor menor que y; siendo ese valor el resto. Esta idea nos sugiere una solucin iterativa, repetimos la resta de y.
c veces
xyyy=r
Una vez determinado que nuestra algoritmo va a ser un bucle, aplicamos los pasos de la derivacin de bucles.
B.1. Invariante
las ecuaciones relativas a la conservacin de valores pasan a formar parte del invariante
x= X y = Y
lo siguiente es darnos cuenta de que la parte de la postcondicin relativa a la condicin de
terminacin es r < y: restaremos hasta que el resto sea menor que y. Por lo tanto el invariante es el resto de la postcondicin
I: x = X y = Y x = c*y + r r 0 y > 0
donde hemos obtenido r 0 y > 0, a partir de 0 r < y. Podramos obviar y > 0? En general, mejor que sobre, siempre y cuando las condiciones que incluimos en el invariante sean
96
IB x=Xy=Yx=c*y+rr0y>0r<y
x=Xy=Yx=c*y+r0r<y
Q
Comprobamos si
I def(B)
def(ry)
cierto
I
def(r)
cierto
IB
97
dec(C)
r>0
ryy>0
IB
para esta ltima prueba nos hace falta y > 0, que est implcita en la postcondicin y
explcita en la postcondicin; podra ser aqu donde nos disemos cuenta de que debe
formar parte del invariante.
Observamos que no se puede deducir el invariante de la precondicin, y que se hace necesario incluir una accin de inicializacin. Lo ms fcil es asignar valores a las variables
de tal modo que se cumplan los asertos del invariante. La frmula problemtica es
x = y*c + r
tomamos como accin de inicializacin
<c,r> := <0,x>
con lo que nos quedara por demostrar
{P } <c,r> := <0,x> { I }
que es, efectivamente, correcto
Tenemos que si x es menor que y entonces el bucle no se ejecuta ninguna vez (r=x<y). Si
x es mayor que y, entonces entramos en el bucle, que debe decrementar el valor de r.
Segn nuestra idea inicial del bucle, le restamos a r el valor de y, haciendo que sea esa
nuestra accin de avance
r := r y
para la que debemos probar que se cumple
{ I B r = T } r := r y { r < T }
que es efectivamente correcto
Nos preguntamos si la accin de avance constituye el cuerpo del bucle. Para ello, debemos demostrar
{ B I } r := ry { I }
def(ry)I[r/ry]
ciertox=Xy=Yx=c*y+(ry)y>0ry0
x=Xy=Yx=(c1)*y+ry>0ry
tenemos entonces
Ix=Xy=Yy>0
Bry
pero no podemos demostrar
I B x = (c1)*y + r
98
por lo que debemos pasar a B.8 e intentar restablecer el invariante, tomando como aserto
intermedio R, el que acabamos de obtener como precondicin ms dbil de la accin de
avance inducido por el invariante
{ I B : x = X y = Y x = c*y + r r 0 y > 0 r y }
A1
{ R : x = X y = Y x = (c1)*y + r y > 0 r y }
Vemos que la nica diferencia se puede solventar con una asignacin
c := c + 1
de forma que al sustituir c/c+1 en la postcondicin, lleguemos a la precondicin.
Efectivamente se puede demostrar
I B R[c/c+1]
B.9. Terminacin.
Por ltimo, nos queda por comprobar que esta nueva accin en el cuerpo del bucle no
afecta a la terminacin, y se verifica
{IBr=T}
c := c +1;
r := r y;
{r<T}
informalmente se puede afirmar que se sigue verificando pues la accin de restablecimiento del invariante no afecta a la expresin de acotacin r. Pero, para completar el proceso,
sera necesario demostrarlo, obteniendo la pmd y verificando que I B r = T es ms
fuerte que ella.
Finalmente el diseo del algoritmo anotado queda:
var x, y, c, r : Ent;
{P:x=Xy=Yy>0x0}
<c,r> := <0,x>;
{ I : x = X y = Y x = c*y + r r 0 y > 0
C:r
}
it r y o
{Iry}
c := c + 1;
{ x = X y = Y x = (c1)*y + r r y y > 0 }
r := r y;
99
{I}
fit
{Ir<y}
{Q:x=Xy=Yx=y*c+r0r<y}
En cuanto a la complejidad del algoritmo; si tomamos como accin caracterstica el cuerpo del
bucle, que es O(1), tenemos que el bucle se ejecuta X div Y veces, con lo que su complejidad es
O(X div Y).
Obtencin del invariante reemplazando constantes por variables
{P:v=Vu=U}
prodEscalar
{ Q : r = i : 1 i N : u(i)*v(i) u = U v = V }
La idea del bucle es un bucle, donde, en cada pasada, sumamos al resultado parcial el resultado
de multiplicar la componente j.
B.1. Invariante
Sustituimos la constante N de la postcondicin por una nueva variable j. Adems conservamos los asertos de conservacin de valores que aparecen tanto en la precondicin como en la postcondicin, e introducimos un aserto sobre el rango de la nueva variable. Si
no lo introdujramos aqu, veramos luego que nos hace falta para demostrar que la expresin de acotacin puede decrecer.
100
I: r = i : 1 i j : u(i) * v(i) u = U v = V 1 j N
aqu hemos introducido el rango de la nueva variable, suponiendo que toma valores en el
rango de ndices del vector; luego veremos que no es as y que debemos ampliar el rango
con 0, para capturar as el primer estado antes de ejecutar el bucle por primera vez. En la
vida real podramos darnos cuenta aqu directamente de que el rango es 0 j N
def(Nj)
cierto
IB
101
dec(Nj)
Nj>0
N>j
1jNjzNIB
comprobamos si P I
pero no es as porque en I aparece
r = i : 1 i j : u(i) * v(i)
y en la precondicin ni siquiera aparece j, por lo tanto es necesaria una accin de inicializacin. Esta ser una asignacin, y como siempre intentamos que sea de tal forma que se
obtenga la condicin del invariante por anulacin de la expresin conflictiva. Probamos
<r,j> := <0,0>
tendramos que demostrar entonces
{ P } <r,j> := <0,0> { I }
def(0)def(0)I[r/0,j/0]
u=Uv=V0=i:1i0:u(i)*v(i)10N
otra solucin habra sido tomar <r,j> := <u(1)*v(1), 1> como accin de inicializacin, en
cuyo caso no tendramos que haber modificado el invariante. Pero es ms elegante una
solucin donde todas las componentes del vector se evalan dentro del bucle. Moraleja: en
los bucles sobre vectores hay que ser cuidadoso con los ndices. En este ejemplo tambin
cabra la duda de si j < N j N; optamos por la segunda opcin para que I se siga
cumpliendo despus de la ltima pasada por el bucle.
Como tenemos que hacer que j se acerque a N parece evidente que la accin de avance
debe ser
A2: j := j + 1;
con lo cual debemos probar el requisito c.2
{ I B Nj = T } j := j + 1 { Nj < T }
def(j+1)(Nj<T[j/j+1])
ciertoN(j+1)<T
Nj<T+1
Nj=TIBNj=T
102
que no se puede obtener del invariante, debido al trmino u(j+1)*v(j+1); por lo tanto se
hace necesaria una accin que restablezca el invariante. Tomando el anterior aserto como
el aserto intermedio R
{ I B Nj = T } A1 ; A2 { Nj < T }
intuitivamente se ve que se mantiene porque A1 no afecta a j. Tambin se podra demostrar.
Con todo esto el algoritmo anotado queda:
const N = ; % Ent
var u, v : Vector[1..N] de Real;
r : Real;
{P:u=Uv=VN1}
var j : Ent;
inicio
<r,j> := <0,0>;
{ I : u = U v = V r = i : 1 i j : u(i)*v(i) 0 j N
C:Nj
it j z N o
{IjzN}
r := r + v(j+1) * u(j+1);
{R}
j := j + 1
{I}
103
fit
{Ij=N}
fvar
{ Q : u = U v = V r = i : 1 i N : u(i) * v(i) }
En cuanto a la complejidad. Si tomamos N como tamao de los datos (n), considerando el
cuerpo del bucle como accin caracterstica es fcil ver que el algoritmo es O(n).
Tambin podemos afirmar que TA(n) es :(n) pues el algoritmo accede, en cualquier caso, a
todas las componentes del vector. Y de las dos afirmaciones anteriores, tenemos que TA(n)
4(n).
Podemos razonar tambin que cualquier algoritmo que resuelva este problema ha de ser :(n),
pues necesariamente ha de visitar al menos una vez cada componente. Tenemos pues que la
complejidad del algoritmo que hemos diseado coincide con la cota inferior para el problema; y,
por lo tanto, hemos obtenido una complejidad ptima.
En muchos casos se puede establecer la cota inferior razonando sobre que cualquier algoritmo
que resuelva el problema debe inspeccionar el dato de entrada completo.
Se puede hacer una generalizacin de la tcnica de sustituir una constante de la postcondicin
por una variable, que consiste en sustituir una constante por una expresin. Esa es la solucin
que se utiliza en el clculo de la raz cuadrada por defecto en tiempo logartmico.
1.5.3
En los ejemplos y consideraciones que hemos hecho hasta ahora en la derivacin de bucles
slo hemos tenido en cuenta la correccin y no as la eficiencia.
El mtodo expuesto empieza eligiendo cul es la estructura externa de la accin a disear. En
los tres primeros casos no tenemos mucho que decir; en la asignacin la complejidad depender
del coste de las operaciones involucradas, en la composicin secuencial del coste de las acciones
que se componen y en la composicin alternativa del coste de evaluar las barreras, que normalmente no admiten muchas posibilidades distintas, y del coste de cada una de las ramas, que
vendr dado por la correspondiente accin que se derive.
El caso ms interesante es, otras vez, el de los bucles. Los peligros que pueden perjudicar a
la eficiencia son:
derivar bucles anidados por haber diseado el restablecimiento del invariante como un
bucle.
Estas son las dos situaciones, sobre todo la segunda, que debemos evitar.
Para resolver el primer problema debemos elegir acciones de inicializacin que contengan expresiones simples.
104
Para resolver el segundo problema en muchas ocasiones es til la tcnica de introducir un invariante auxiliar.
En un punto de la derivacin, normalmente al intentar restablecer el invariante, nos encontramos con que tenemos que obtener el valor de una expresin compleja, tpicamente, una expresin que incluye asertos u operaciones extendidos. La solucin consiste en conseguir que el bucle
adems de lo que ya calculaba, indicado por el invariante, vaya calculando tambin esta expresin.
Introducimos una nueva variable y, de forma que donde se necesite el valor de la expresin E
podamos simplemente tomar el valor de y. Reforzamos el invariante con un invariante auxiliar de
la forma:
I1 : y = E
con lo que el invariante quedara
I : I0 I 1
Con este nuevo invariante ser necesario rehacer una parte de la derivacin, aunque otras partes pueden seguir siendo vlidas, teniendo en cuenta que el nuevo invariante ser un fortalecimiento del antiguo.
Veamos un ejemplo donde se necesita el uso de un invariante auxiliar: el clculo de los nmeros de Fibonacci.
fib(0) = 0
fib(1) = 1
fib(n+2) = fib(n+1) + fib(n)
si n0
partimos de la especificacin
var n, x : Ent;
{P:n=Nn0}
fibonacci
{ Q : n = N x = fib(n) }
Los nmeros de Fibonacci no se pueden especificar utilizando el lenguaje de asertos. Esto
ocurre con muchas funciones definidas de forma recursiva. Entendemos en ese caso que la especificacin se complementa con la definicin recursiva de la funcin, que hemos escrito ms arriba, y que especifica el comportamiento de dicha funcin.
La idea del algoritmo es ir obteniendo los nmeros de Fibonacci para valores sucesivos hasta
llegar al valor de n, uno en cada pasada por un bucle.
105
como n se comporta como una constante, cambiamos n por una variable; aadiendo
adems la condicin de que se conserve el valor de n
I0 : x = fib(i) n = N 0 i n
(lo llamamos I0 porque ya sabemos que introduciremos un invariante auxiliar)
donde adems hemos incluido un rango razonable para la nueva variable.
B.2. Condicin de terminacin
El invariante ser igual a la postcondicin cuando i = n, tomamos pues esa condicin de
terminacin
B : i = n
que efectivamente verifica
I0 B Q
de ah que la condicin de repeticin quede
B:izn
La condicin de repeticin no est definida porque la variable i no est declarada. Incluimos por tanto una declaracin local que rodea a la accin a derivar
var i : Ent;
inicio
A
fvar
con lo que ahora s est definida i z n
Fijndonos en la condicin de terminacin, i = n, y considerando que i debe ir incrementndose tomamos como expresin de acotacin
C:ni
que junto con la condicin sobre el rango de i que aparece en el invariante nos asegura
que esta expresin toma valores en un dominio bien fundamentado.
Probamos entonces
I0 B def(C) dec(C)
def(ni)
cierto
I0B
dec(ni)
ni>0
n>i
0iniznI0B
106
Como queremos ir obteniendo los sucesivos nmeros de Fibonacci hasta alcanzar fib(n),
vamos a hacer que i vaya incrementndose de 1 en 1:
A2 : i := i +1
Demostramos que efectivamente esta accin de avance hace avanzar el bucle
{ I0 B Ni = T } i := i +1 { Ni < T }
que efectivamente podemos demostrar
Se debera cumplir
{ I0 B } i := i +1 { I0 }
o lo que es lo mismo
I0 B I0 [i/i+1] x = fib(i+1) 0 i + 1 n n = N
pero no se puede demostrar
I0 B x = fib(i+1)
Tenemos
{ n = N x = fib(i) 0 i n i z n }
A1
{ n = N x = fib(i+1) 0 i+1 n }
la solucin sera una accin de la forma
x := fib(i+1)
[o lo que es igual
x := fib(i) + fib(i1)
fib(i) lo tenemos, pero para obtener fib(i1) tendramos que escribir otro bucle que lo escribiera]
lo que hacemos es aadir un invariante auxiliar, exigiendo que el bucle vaya calculando
tambin el valor de fib(i+1)
I1 : y = fib(i+1)
107
I B Q
ya que I es un fortalecimiento de I0.
I I0 def(B)
B.4. La expresin de acotacin sigue siendo la misma, y se siguen cumpliendo sus propiedades.
I B def(C) dec(C)
B.6. La accin de avance sigue siendo la misma, y se sigue cumpliendo el avance del bucle
pues hemos construido un reforzamiento de la precondicin.
{ n = N x = fib(i) 0 i n y = fib(i+1) i z n }
A1
{ n = N x = fib(i+1) 0 i+1 n y = fib(i+2) }
y ahora en la precondicin s tenemos los valores necesarios para obtener las igualdades
de la postcondicin:
<x,y> := <y,y+x>
que podemos verificar que es efectivamente correcta respecto de la especificacin
108
B.9. Demostramos que con la accin de restablecimiento del invariante se sigue cumpliendo la terminacin del bucle.
Informalmente podemos argumentar que la expresin de acotacin (ni) no se ve afectada por la accin A1.
Finalmente, el algoritmo anotado nos queda
var n, x : Ent;
{n=Nn0}
var
i, y : Ent;
inicio
<i,x,y> := <0,0,1>;
{ I : n = N x = fib(i) 0 i n y = fib(i+1)
C:ni
}
it i z n o
{Iizn}
<x,y> := <y,x+y>;
{ I[i/i+1] }
i := i +1
{I}
fit
{Ii=n}
fvar
{ n = N x = fib(n) }
Este ejemplo no nos debe hacer pensar que siempre es posible eliminar los bucles anidados introduciendo invariantes auxiliares. Es necesario que el bucle externo sea realmente capaz de obtener el valor para el cual se requiere un bucle interno, y para ello necesitamos que ambos bucles
tengan los mismos lmites y se ejecuten el mismo nmero de veces.
109
posible implementar vectores que pueden crecer, mediante la creacin de un vector de mayor
longitud y la copia de las componentes del vector antiguo). Y la otra caracterstica es que pemiten
el acceso directo a cada una de las componentes, a travs de un ndice. Para que el acceso directo sea eficiente, es necesario que todo el vector est almacenado en memoria principal, lo cual
limita el tamao mximo. En la segunda parte del curso veremos otras formas de manejar colecciones de datos que soslayan los inconvenientes de los vectores, a costa de sacrificar el acceso
directo, susituyndolo por acceso secuencial.
tamao fijo
Casi todos los algoritmos de tratamiento de vectores se ajustan a uno de los dos siguientes esquemas
Bsqueda. Se trata de encontrar la primera componente que verifica una cierta propiedad.
El tercer grupo de algoritmos que trataremos ser el de los algoritmos de ordenacin que no
consideramos como un esquema general sino como una operacin concreta.
1.6.1
Recorrido
var
v:Vector[1..N]deelem;
%otrasdeclaraciones
{P:v=VN1}
n:=0;
{I:0nNi:1in:tratado(v(i))
110
C:Nn}
itnzN
otratar(v(n+1));
n:=n+1
fit;
finalizacin
{Q:i:1iN:tratado(v(i))}
La complejidad en O(n).
Ntese que no imponemos en la postcondicin que se conserve el valor de v porque puede
ocurrir que la accin tratar modifique el valor de las componentes del vector.
En los ejercicios ya hemos derivado ejemplos similares. Para hacer la derivacin formal de un
algoritmo genrico de recorrido, el invariante se obtiene sustituyendo una constante de la postcondicin por una variable.
Normalmente los bucles de recorrido de vectores se escriben utilizando la construccin para,
lo que permite una escritura ms compacta.
No hemos escrito el esquema como un procedimiento o una funcin porque al no conocer
cul es la funcin de tratamiento no sabemos si el vector se modificar, o si se debern obtener
resultados adicionales. Sin embargo, una vez fijada la operacin de tratamiento de las componentes, lo habitual es implementar el recorrido como una funcin o un procedimento segn corresponda.
Algunas variaciones sobre este esquema bsico:
Recorrido de un subvector. Lo habitual es implementar el recorrido como un procedimiento o una funcin que reciba como parmetro los lmites del subvector a recorrer.
1.6.2
El resultado es un valor que se obtiene a partir del vector de entrada, que no se modifica (p.e. obtener el mximo de las componentes)
El resultado es otro vector obtenido a partir del vector de entrada (p.e. copia de un
vector).
Bsqueda
Bajo esta categora encontramos a un gran nmero de algoritmos sobre vectores. El esquema
general tiene la siguiente forma:
111
Bsqueda secuencial
El esquema de bsqueda secuencial queda en funcin de una cierta operacin P que nos dice
si una componente del vector cumple o no la propiedad buscada:
funcbuscarVector(v:Vector[1..N]deelem)devencontrado:Bool;pos:
Ent;
{P0:v=VN1}
inicio
<pos,encontrado>:=<1,P(v(1))>;
{I:v=V1posNi:1i<pos:NOTP(v(i))encontradol
P(v(pos))
C:Npos
}
itposzNANDNOTencontrado
oencontrado:=P(v(pos+1));
pos:=pos+1
fit;
{Q0:v=V1posNi:1i<pos:NOTP(v(i))encontradol
P(v(pos))
(pos=Nencontrado)
}
dev<encontrado,pos>
ffunc
Ntese que si P es una funcin, est permitido utilizarla en los asertos, como ya vimos en el
apartado sobre procedimientos y funciones. Ntese tambin que debemos entender esa funcin
como el resultado de sustituir los parmetros formales por los reales en la postcondicin de la
funcin; y, por lo tanto, si queremos relacionar ese aserto con la variable encontrado debemos
hacerlo con el operador l.
Es necesario sacar la comprobacin de la primera componente fuera del bucle para que el invariante est definido a la entrada pues si inicializsemos pos=0 y encontrado=falso tendramos
encontradolP(v(0))
112
hace necesario evaluar una vez ms P(v(pos)) para comprobar si hemos acabado por llegar al final o por que se cumple la propiedad. Si la comprobacin de la propiedad es costosa esto implica una penalizacin de la eficiencia. Aunque realmente en el caso peor no
es significativo pues slo implica una comprobacin ms sobre las N posibles.
algunos lenguajes de programacin (como Pascal) evalan las condiciones enteras aunque
puedan llegar a un resultado slo con evaluar una parte. Hay que tener cuidado de que no
pueda ocurrir que se intenta evaluar P(v(pos)) para un valor de pos que no pertenezca al
rango del vector. No es el caso aqu.
Otra posible forma de escribir el algoritmo sin necesidad de comprobar la primera componente fuera del bucle:
funcbuscarVector(v:Vector[1..N]deelem)devencontrado:Bool;pos:
Ent;
{P0:v=VN1}
inicio
pos:=1;
{I:v=V1posNi:1i<pos:NOTP(v(i))
C:Npos}
itposzNANDNOTP(v(pos))
opos:=pos+1
fit;
encontrado:=P(v(pos));
{Q0:v=V1posNi:1i<pos:NOTP(v(i))encontradol
P(v(pos))
(pos=Nencontrado)
}
dev<encontrado,pos>
ffunc
funcbuscarVector(v:Vector[1..N]deelem)devencontrado:Bool;pos:
Ent;
113
{P0:v=V1MNP(v(M))}
inicio
pos:=1;
{I:v=V1posMi:1i<pos:NOTP(v(i))
C:Mpos}
itNOTP(v(pos))
opos:=pos+1
fit;
encontrado:=poszM;
{Q0:v=V1posMi:1i<pos:NOTP(v(i))P(v(pos))
encontradolposzM}
dev<encontrado,pos>
ffunc
v(pos)x
Si el valor del centinela es el mayor posible dentro del correspondiente dominio, entonces sabemos que siempre se cumplir la condicin.
Tambin se puede utilizar el centinela como una marca que indica donde est el final de la parte de un vector que contiene informacin. De forma que utilicemos slo las M primeras posiciones de un vector. Esta es un tcnica muy habitual. Como no podemos esperar que el centinela
cumpla cualquier propiedad sobre la que podamos estar interesados, tendramos que usar una
condicin de repeticin de la forma:
NOTP(v(pos))ANDNOTP(v(pos))
siendo P la propiedad buscada y P una propiedad conocida para el centinela, que slo l puede verificar dentro del dominio correspondiente.
Aplicaciones de la bsqueda secuencial
Muchos algoritmos sobre vectores se pueden expresar utilizando el esquema de bsqueda secuencial con o sin centinela. Algunos ejemplos donde se trata de determinar si uno o ms vectores cumplen una determinada propiedad y lo que se hace es buscar una componente que no
cumpla la propiedad.
114
P : u(pos) z v(pos)
Determinar si una matriz es simtrica
P : v(f,c) z v(c,f)
Determinar si un vector de caracteres es un palndromo (es igual leda de izquierda a derecha y de derecha a izquierda).
P : v(pos) z v(Npos+1)
El bucle slo recorrera el vector hasta N div 2
Determinar si un vector est ordenado crecientemente
P : v(pos) > v(pos+1)
El recorrido slo llegara, como mximo, hasta N1.
Hasta ahora no nos hemos aprovechado del acceso directo que ofrecen los vectores. Este es el
primer caso donde la utilizamos.
El algoritmo de bsqueda binaria sirve para
Encontrar un valor dentro de un vector ordenado
La idea es que en cada pasada por el bucle se reduce a la mitad el tamao del subvector donde
puede estar el elemento buscado.
P
M = (P+Q) div 2
115
Q
M
Como el tamao de los datos se reduce a la mitad en cada pasada tenemos claramente una
complejidad logartmica en el caso peor, frente a la complejidad lineal de los algoritmos de
bsqueda secuencial. El caso peor es cuando el elemento no est el vector, o tenemos la mala
suerte de no encontrarlo hasta que p=q.
El tipo de los elementos del vector debe ser un tipo sobre el que est definida la igualdad y
una relacin de orden
tipo
elem=;%tipoordenadoconigualdad
Debemos tener cuidado con los casos extremos, es decir si x es menor que todos los elementos del vector o si x es mayor que todos los elementos del vector. Queremos que si no se encuentra en el vector pos se quede apuntando a la posicin anterior a la que debera ocupar el elemento;
eso quiere decir que tanto 0 como N son valores admisibles para pos. Esto plantea un problema a
la hora de escribir la postcondicin; la forma ms simple de escribir la postcondicin es:
v=Vord(v)0posNv(pos)x<v(pos+1)
Donde no hace falta indicar que todos los elementos a la izquierda de pos son menores que x y
todos los elementos a la derecha son mayores, basta con que lo sean el apuntado por pos y el inmediatamente siguiente, ya que los dems lo sern por estar ordenado el vector. El problema es
que pos puede valer 0, en cuyo caso no est definido v(pos), o puede valer N en cuyo caso no est
definido v(pos+1). Se puede utilizar un artificio en la especificacin que consiste en considerar un
vector ficticio con valores centinela, y +, en las posiciones 0 y N+1, y que coincide con v en
las dems posiciones. O bien, nos podemos aprovechar de que el cuantificador universal se considera cierto cuando el aserto de dominio define un conjunto vaco. Aplicando esta segunda idea
obtenemos la siguiente especificacin
funcbuscarVectorBin(v:Vector[1..N]deelem;x:elem)devpos:Ent;
{P0:v=Vx=XN1ord(v)}
{Q0:v=Vx=Xi:1ipos:v(i)xi:pos+1iN:
x<v(i)
0posN}
ffunc
116
B.1. Invariante
Fijndonos en las diferencias entre el invariante y la postcondicin tenemos que la condicin de terminacin ha de ser:
B:q=pos+1
No est definida porque q no est declarada. Aadimos esta nueva variable a las declaraciones locales de la funcin
varq:Ent;
117
La idea es que en cada pasada se ha de reducir el tamao del subvector a considerar, hasta
llegar a un subvector de longitud 1. Este subvector viene dado por la diferencia entre pos y
q. Por lo tanto una posible expresin de acotacin:
C:qpos
Demostramos
IBdef(C)dec(C)
En la accin de avance es donde est la parte ingeniosa del algoritmo. En cada iteracin
queremos reducir el mximo posible el tamao del subvector a explorar; como consideramos que es igualmente probable que el valor buscado se encuentre en cualquiera de las
posiciones del subvector, tomamos el punto medio entre pos y q, de forma que reduzcamos a la mitad el tamao del subvector a explorar.
A2: m:=(pos+q)div2;
118
{R1{IBqpos=Tm=(pos+q)div2}
si
v(m)xopos:=m;
v(m)>xoq:=m
fsi
{qpos<T}
De esta forma es trivial la correccin de la primera asignacin y lo que nos queda es demostrar la correccin de la composicin alternativa.
Definicin de las barreras. Aadimos m a la declaracin de variables locales
def(v(m)x)def(v(m)>x)
enRango(v,m)
1mN
0pos<m<qN+1
0pos<qN+1m=(pos+q)div2qzpos+1
R1
v(m)xv(m)>xciertoR1
119
{IB}
m:=(pos+q)div2;
{R2{IBm=(pos+q)div2}
si
v(m)xopos:=m;
v(m)>xoq:=m
fsi
{I}
{R2v(m)x}pos:=m{I}
I[pos/m]
v=Vx=X0m<qN+1i:1im:v(i)x
i:qiN:v(i)>xord(v)
I v=Vx=X
ord(v)
I i:qiN:v(i)>x
Im=(pos+q)div2qzpos+1 0m<qN+1
ord(v)v(m)xi:1im:v(i)x
porlotantoR2v(m)xI[pos/m]
{R2v(m)>x}q:=m{I}
I[q/m]
v=Vx=X0pos<mN+1i:1ipos:v(i)x
i:miN:v(i)>xord(v)
I v=Vx=X
ord(v)
I i:1ipos:v(i)x
Im=(pos+q)div2qzpos+1 0pos<mN+1
ord(v)v(m)>xi:miN:v(i)>x
porlotantoR2v(m)>xI[q/m]
120
Con todo esto quedara demostrada la correccin del programa, que finalmente ser
funcbuscarVectorBin(v:Vector[1..N]deelem;x:elem)devpos:Ent;
{P0:v=Vx=XN1ord(v)}
var
q,m:Ent;
inicio
<pos,q>:=<0,N+1>;
{I:x=Xv=Vord(v)0pos<qN+1i:1ipos:v(i)
x
i:qiN:v(i)>x
C:qpos
}
itqzpos+1
om:=(pos+q)div2;
si
v(m)xopos:=m;
v(m)>xoq:=m
fsi
fit;
{Q0:v=Vx=Xi:1ipos:v(i)xi:pos+1iN:
x<v(i)
0posN}
devpos
ffunc
Ntese que efectivamente pos se queda apuntando a la posicin anterior al primer elemento
que es ms grande que x, de forma que desplazando una posicin hacia la derecha todas las componentes i>pos tendramos el hueco donde insertar x.
Ntese, por ltimo, que habremos encontrado el elemento si pos z 0 y v(pos) = x:
(i:1iN:v(i)=x)l(posz0v(pos)=x)
En cuanto a la complejidad, tomando N como tamao de los datos, tenemos que en cada iteracin se divide a la mitad la longitud del subvector a considerar, hasta que se llega a un subvector de longitud 1.
1.6.3
Ordenacin
Los algoritmos de ordenacin sobre vectores son muy importantes dado lo habitual de su uso.
La especificacin de un procedimiento de ordenacin nos dice que, dado un vector cuyos elementos son de un tipo ordenado, hemos de obtener una permutacin ordenada (en orden no
decreciente, por ejemplo) del vector original. Formalmente:
121
procordenaVector(esv:Vector[1..N]deelem);
{P0:v=VN1}
{Q0:ord(v)perm(v,V)}
fproc
Varios algoritmos de ordenacin y, en particular, los que vamos a estudiar en este captulo, basan su funcionamiento en realizar intercambios entre las componentes del vector. Por ello, vamos
a introducir una nueva sentencia en el lenguaje algortmico que nos permita escribir de una vez
dicho intercambio.
Int(v,Ei,Ej)
es equivalente a
<v(Ei),v(Ej)>:=<v(Ej),v(Ei)>
Para no recargar la verificacin de los algoritmos de ordenacin nos interesa observar que si la
nica modificacin que hacemos en un vector es a travs de la operacin de intercambio, el vector resultante va a ser una permutacin del vector original. Es decir, se puede demostrar
{v=V}
Int(v,Ei,Ej)
{perm(v,V)}
Para demostrarlo se utiliza la segunda de las equivalencias que hemos visto ms arriba. Se deja
al lector encontrar esta demostracin.
122
si hemos realizado t operaciones de intercambio entonces la complejidad del algoritmo debe ser
TA(n)tcnlogn
123
inv(v,i,j)defi<jv(i)>v(j)
el caso peor se tendr cuando el vector est ordenado en orden inverso, en cuyo caso se har
mximo el nmero de inversiones:
(#i,j:1i<jn:cierto=i:1i<n:nicn2
Algoritmos de ordenacin
Segn los resultados obtenidos en el apartado anterior y considerando su complejidad, tenemos dos grandes grupos de algoritmos de ordenacin
Algoritmos de ordenacin de complejidad O(n2)
Mtodo de insercin. Emplea intercambios entre vecinos y se basa en tener una parte del
vector ordenando e ir insertando un elemento cada vez en la parte ordenada.
Mtodo de seleccin. Emplea intercambios entre elementos lejanos y se basa en ir eligiendo cada vez el menor de los elementos que quedan en la parte no ordenada.
Mtodo de la burbuja. Consiste en considerar los (N1)*(NI) intercambios posibles entre vecinos para lograr ordenar el vector.
Ordenacin por mezcla (mergesort). Se divide el vector en dos partes que, una vez ordenadas, se mezclan ordenadamente para obtener el resultado final. Su complejidad es cuasilineal, O(n log n), en el caso peor. Requiere un espacio auxiliar de coste O(n).
124
La ordenacin rpida y la ordenacin por mezcla se explican en el tema de algoritmos recursivos. La ordenacin por montculos se explica en el tema de rboles.
Ordenacin por insercin
La idea del algoritmo es que dividimos el vector en una parte ordenada y otra desordena, y en
cada pasada insertamos el primer elemento de la parte desordenada en el lugar que le corresponde
dentro de la parte ordenada, realizando intercambios entre vecinos.
La forma en que escribamos la especificacin del algoritmo nos conducir al algoritmo a implementar pues dirigir la obtencin del invariante.
En la especificacin slo nos preocupamos por la condicin ord(v) pues sabemos que
perm(v,V) est garantizada al tratarse de un algoritmo que slo realiza intercambios.
La postcondicin:
Q{ord(v){i,j:1i<jN:v(i)v(j)
La derivacin formal:
Ya ordenado
Se prueba
125
IBQ
La condicin de repeticin
B:nzN
Se prueba
IBdef(C)dec(C)
Una forma de hacer que el invariante sea cierto de modo trivial es inicializar n como 1
A0:n:=1
Para ello tendra que cumplirse II[n/n+1] lo cual no es cierto porque estar ordenado hasta n no implica estar ordenado hasta n+1
126
{IB}
A1
{I[n/n+1]}
La idea es que para restablecer el invariante tenemos que implementar un bucle que haga
lo siguiente:
n n+1
>
n-1 n n+1
>
n-2 n-1 n n+1
>
Tendramos que aplicar de nuevo el proceso de derivacin de bucles, pero vamos a abreviarlo.
La derivacin de esta accin es el ejercicio 75. All se toma como invariante una versin
reelaborada del invariante que presentamos aqu; una versin con la que es ms sencillo
realizar la verificacin.
La postcondicin es
i,j:1i<jn+1:v(i)v(j)1n+1N
La idea para obtener el invariante es considerar que en el subvector [1..n+1] todos los
elementos estn ordenados menos 1; y que ese uno es menor que todos los que estn a su
derecha. De forma que el subvector entero estar ordenado cuando el elemento problemtico sea mayor o igual que el de su izquierda o el elemento problemtico sea el primero
i,j:1i<jn+1jzm+1:v(i)v(j)
(v(m)v(m+1)m=0)0mn<N
La condicin de terminacin.
Bint:m=0v(m)v(m+1)
127
Es decir, cuando el elemento a insertar es menor que todos los de la parte ordenada, tenemos que llegar hasta m=0 en la ltima iteracin, donde ya no entramos en el cuerpo del
bucle. Pero aunque en esa situacin ya no sera necesario evaluar v(m)>v(m+1), lo estamos haciendo y v(0)>v(1) no est definido porque no existe v(0). Una solucin sera definir una variable booleana auxiliar de forma que no apareciese el acceso al vector dentro de
la condicin de repeticin. La otra solucin es tratar la primera componente del vector
como un caso aparte fuera del bucle, a la terminacin de ste.
El invariante hay que modificarlo, as como las condiciones de repeticin y terminacin
Iint:i,j:1i<jn+1jzm+1:v(i)v(j)1mn<N
Bint:m=1v(m)v(m+1)
Bint:mz1v(m)>v(m+1)
As, a la terminacin del bucle no sabemos si v(1) es o no menor que v(2), por lo que escribimos un condicional para averiguarlo y actuar en consecuencia.
La expresin de acotacin
Cint:m
La accin de inicializacin
m:=n
Con lo que tenemos que el subvector v[1..n] est ordenado porque lo garantiza la precondicin I, y que el elemento problemtico es v(m+1).
La accin de avance ser
m:=m1
128
Habr que restablecer el invariante, intercambiando los valores de v(m) y v(m+1) (Esta
verificacin es laboriosa porque est involucrado un intercambio entre posiciones de un
vector y un invariante con un cuantificador existencial con una excepcin).
Int(v,m,m+1)
Por ltimo la instruccin condicional que va a continuacin del bucle se encarga de intercambiar v(1) y v(2) en caso de que sea necesario.
siv(1)>v(2)
entoncesInt(v,1,2)
sinoseguir
fsi
Una forma de soslayar el inconveniente de escribir este condicional adicional es aprovecharse de la caracterstica que tienen algunos lenguajes al realizar evaluacin perezosa de las
expresiones, evaluando slo la parte de la expresin necesaria para llegar a una solucin.
Aprovechndonos de ello, podramos utilizar el invariante y la condicin de terminacin
que elegimos inicialmente. Si n embargo, a la hora de disear el algoritmo nos debemos
abstraer de estas peculiaridades, dejando la posible modificacin como una optimizacin
posterior.
129
fproc
En cuanto a la complejidad, el caso peor es cuando el vector est ordenado en orden decreciente, en cuyo caso tenemos O( i : 1 i N1 : i ) = O(N2). En el caso mejor, cuando el vector est ordenado crecientemente, la complejidad es O(N).
En [Kal90], pp. 172-174 se puede encontrar una derivacin ms formal de este algoritmo.
Ordenacin por seleccin
i:1i<N:(j:i<jN:v(i)v(j))
La idea consiste en sustituir la primera aparicin de N por una variable, de forma que habr
una parte del vector, que est ordenada, donde se encuentran los elementos menores que el resto.
Puede comprobarse que si se sustituyesen las dos apariciones de N por una variable, obtendramos de nuevo el mtodo de insercin. Para hacer avanzar el bucle, seleccionaremos el menor de
los elementos que quedan y lo colocaremos al final de la parte ordenada.
B.1 Invariante
Lo obtenemos sustituyendo la primera aparicin de N en la postcondicin, e introduciendo la condicin sobre el posible rango de valores:
i:1i<n:(j:i<jN:v(i)v(j))1nN
Ya ordenado
n-1 n
y la condicin de repeticin
B:nzN
IBQ
130
con lo que el invariante se hace trivialmente cierto porque el cuantificador opera sobre un
dominio vaco.
{P}A0{I}
vamos incrementando n
A2:n:=n+1
La accin de restablecimiento del invariante debe recorrer el vector entre n y N para encontrar el menor de los elementos, intercambindolo entonces con el que ocupa la posicin n. Esto lo conseguimos como una composicin secuencial, de un bucle que localiza
el ndice del menor elemento del subvector, junto con un intercambio de ese elemento
con el que ocupa la posicin n, que consigue que se cumpla menor = n.
El invariante del bucle se obtiene sustituyendo la N que aparece en la expresin del
mnimo m, junto con sus condiciones de rango.
Iint:i:1i<n:(j:i<jN:v(i)v(j))
v(menor)=mink:nkm:v(k)nmNnmenorN
La primera parte del invariante hay que incluirla para luego poder obtener la postcondicin I[n/n+1].
Bint:mzN
Cint:Nm
131
En el caso peor (y en el mejor, pues los bucles siempre se ejecutan hasta el final) este algoritmo es de orden O(n2). Aunque si contabilizsemos slo los intercambios tenemos una complejidad de O(n).
Mtodo de la burbuja
i:1i<N:(j:i<jN:v(i)v(j))
De esta forma, todo el proceso de derivacin es idntico hasta que se llega a la accin de restablecimiento del invariante. El objetivo es el mismo, conseguir que el menor de los que quedan
pase a ocupar la posicin n; pero en lugar de hacer una bsqueda y un intercambio, se van
haciendo sucesivos intercambios empezando por el final.
I[n/n+1]i:1i<n:(j:i<jN:v(i)v(j))
132
j:n<jN:v(n)v(j)
Para obtener el invariante sustituimos n por una variable m, con lo que nos queda:
Iint:i:1i<n:(j:i<jN:v(i)v(j))
j:m<jN:v(m)v(j)nmN
Bint:m=n
Cint:m
La complejidad es, en cualquier caso, de orden O(n2) y, a diferencia del mtodo de seleccin, el
nmero de intercambios es tambin O(n2). Sin embargo, se puede hacer una optimizacin si observamos que cuando en un recorrido entero del bucle interno no se efecta ningn intercambio,
eso quiere decir que el subvector v[n..N] ya est ordenado, con lo cual podemos terminar. Para
hacer esta optimizacin es necesario introducir un invariante auxiliar tanto al bucle externo como
al bucle interno
I1:boi,j:ni<jN:v(i)v(j)
I1int:bli,j:mi<jN:v(i)v(j)
133
consideramos un subvector vaco, para el cual es cierto que est ordenado, y luego vamos actualizando b adecuadamente.
Cambiamos adems la condicin de repeticin del bucle externo, incluyendo la comprobacin
sobre el valor de b.
Con todo esto el procedimiento queda:
procordenaVectorBurbuja(esv:Vector[1..N]deelem);
{P0:v=VN1}
var
n,m:Ent;
b:Bool;
inicio
<n,b>:=<1,falso>;
{II1;C}
itnzNANDNOTb
o<m,b>:=<N,cierto>;
{IintI1int;Cint}
itmzn
osiv(m1)<v(m)
entoncesInt(v,m1,m);b:=falso
sinoseguir
fsi;
m:=m1
fit;
n:=n+1
fit
{Q0:ord(v)perm(v,V)}
fproc
De esta forma, la complejidad en el caso peor sigue siendo O(n2), pero ahora si el vector est
ordenado la complejidad pasa a ser O(n).
134
1.7 Ejercicios
Asertos en lgica de predicados
1. Supongamos las declaraciones
cte
N=; %enterot1
var
x,y,p:Ent;
v:Vector[1..N]deEnt;
Escribe asertos que formalicen los tres enunciados siguientes. Seala las variables libres y las
variables ligadas.
(a)
(b)
(c)
(d)
(e)
(f)
(g)
(h)
(i)
(j)
2. Realiza correctamente cada uno de las sustituciones que siguen, e indica posibles formas
errneas de realizarlas. Para v se supone el tipo del ejercicio 1, y las restantes variables se
suponen enteras.
(a)(i:1didn:v(i)=x)[n/n+1] *(e)(i:1didN:x+i)[x/xi]
(b)(i:ndidN:v(i)>0)[n/n1] (f)(i:1didN:x*i)[x/i+2]
(c)(i:1didN:v(i)=x)[x/2*i] *(g)(i:ndidN:x+i)[n/n1]
(d)(i:1didN:v(i)>x)[i/i*i] (h)(i:1didn:x*i)[n/n+1]
3. Comprueba que las dos sustituciones siguientes no producen el mismo resultado. Las va-
(a) (x17)*y[x/2*y,y/z]
(b) (x17)*y[x/2*y][y/z]
4. Las variables que intervienen en lo que sigue se suponen de tipo entero. Realiza las sustitu-
(a) (x2+2*x+1)[x/x+a]
(b)(x2y)[x/y+1,y/x1]
(c)(xy+1yz)[x/x+3*z,y/xy+1]
(d)(k:1kn:x2+y2=k2)[x/x+1,y/y1,n/x+y]
5. Las variables que intervienen en este ejercicio son todas de tipo entero. En cada apartado,
estudia si alguno de los dos asertos es ms fuerte que el otro. Razona tus respuestas.
(a)x0
(b)x0y0
(c)x<0
(d)x1ox0
(e)i:Ent:x=2*i
(f)i:Ent:x=4*i
135
x0y0
x+y=0
x2+y2=9
x1
i:Ent:x=6*i
i:Ent:x=6*i
requerido.
(a)XPQ
(c)XPQ
(b)XQPQ
(d)XQPQ
7. Simplifica, si es posible, los asertos siguientes. Los asertos simplificados deben ser equiva-
(a)(x<1)(x>1)(b)i:ia:xi
(c)i:i0:(j:0j<i:x=2*j)
8. Simplifica los asertos de definicin que siguen, suponiendo que el vector v tiene el tipo
(a)def(xdiv(ab))
(c)def(a+b)
(e)def(v(i)modb)
(b)def(amodb)
(d)def(xdivy+ydivx)
(f)def(i:ai<b:v(i))
9. Aplica las leyes de descomposicin de cuantificadores a los casos que siguen:
(a)i:1in+1:x<v(i)
(c)x=i:n1i<N:i*v(i)
(e)x=#i:1in+1:v(i)=0
(b)i:1in+1:v(i)=i
(d)x=i:n1iN:i+v(i)
(f)x=Maxi:1in+1:v(i)
Especificaciones Pre/Post
10. Formaliza especificaciones Pre/Post para algoritmos que realicen las tareas siguientes. Dis-
tingue en cada caso entre las variables de programa, las variables auxiliares y las variables ligadas.
(a) Copiar el valor de una variable en otra.
(b) Intercambiar los valores de dos variables.
(c) Calcular el valor absoluto de un entero.
(d) Dada una cantidad entera no negativa de segundos, convertirla a horas, minutos y segundos.
(e) Calcular la raz cuadrada por defecto de un entero.
(f) Calcular la raz cuadrada exacta de un entero.
(g) Calcular el cociente y el resto de una divisin entera.
(h) Calcular el mximo comn divisor de dos enteros.
(i) Intercambiar dos componentes de un vector.
(j) Calcular el nmero de ceros que aparecen en un vector de enteros.
(k) Calcular el producto escalar de dos vectores de reales.
(l) Dados dos vectores de enteros, reconocer si uno de ellos es una permutacin del
otro.
136
(m) Dados dos vectores de enteros, reconocer si uno de ellos es la imagen especular del
otro.
diendo que los segmentos de un vector son los diferentes subintervalos del intervalo de
ndices del vector).
{ P1 } A {Q1 }
{ P1 P2 } A { Q1 Q2 }
{ P2 } A { Q2 }
{ P1 P2 } A { Q1 Q2 }
12. Para razonar con el operador pmd son vlidas las reglas que siguen. Explica su significado
operacional.
(a) pmd construye la precondicin ms dbil:
P pmd( A, Q )
(syss)
{P}A{Q}
(b) Exclusin de milagros:
Q1 Q2
pmd( A, Q1 ) pmd( A, Q2 )
(d) Distributividad con respecto a :
guiente:
varx:Ent;{cierto}azar{x=0x=1}
137
14. La accin azar del ejercicio anterior es indeterminista en el sentido de que un mismo estado
inicial puede ser transformado en varios estados finales (el cmputo escoger al azar uno de
ellos, sin que el usuario sepa cul). Por el contrario, una accin se llama determinista si el estado inicial determina unvocamente el estado final. Volviendo a pensar en el apartado (e)
del ejercicio 12, demuestra que si la accin A es determinista entonces puede aceptarse el
axioma siguiente, que falla para A { azar.
pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )
(a)
(b)
(c)
(d)
varx,y:Ent;{x>0y>0}seguir{x+y>0}
varx,y:Ent;{x+y>0}seguir{x>0}
varx,y:Ent;{x>0y>0}seguir{x*y>0}
varx,y:Ent;{x*y<0}seguir{(x>0)l(y<0)}
Asignacin
16. Verifica usando la regla de la asignacin:
varx,y:Ent
{x22*y>5}x:=2*x+y1{x3>2}
138
20. Calcula en cada apartado la precondicin ms dbil P que satisface la especificacin dada, supo-
(a) {P}x:=x+2{x0}
(b) {P}x:=2*x{x99}
(c){P}x:=x*x2{x2x+1>0}
(d){P}x:=x2{x=x2}
(e){P}x:=xmod2{x=xmod2}
(f){P}x:=3*x{i:1in:xz6*i}
(g){P}b:=bAND(x>0){b}
(h){P}(x,y):=(y,x){x=Xy=Y}
(i){P}(x,y):=(y,x){x<2*y}
(j){P}(x,y):=(y2,x+3){x+y>0}
Asignacin a vectores
21. La regla de la asignacin no se puede aplicar a asignaciones de la forma
v(i):=e
tratando a la expresin v(i) como si fuese una variable. Comprueba que si admitisemos aplicar la
regla de la asignacin de este modo, tendramos:
(a) Enunciados verdaderos, pero imposibles de verificar, como:
varv:Vector[1..100]deEnt;i,j:Ent;
{i=j}v(i):=0{v(j)=0}
Composicin secuencial
23. Usa las reglas de la composicin secuencial y de la asignacin para encontrar asertos interme-
139
{R2:}
x:=yx
{Q:x=Yy=X}
24. Supuesto que x, y sean variables de tipo entero, calcula en cada caso la precondicin ms dbil
(a)
(b)
(c)
(d)
{P}x:=x*x;x:=x+1{x>0}
{P}x:=x+y;y:=2*yx{y>0}
{P}y:=4*x;x:=x*xy;x:=x+4{x>0}
{P}x:=y;y:=x{x=Ay=B}
cin de la forma
{ P } A ; (B ; C) { Q }
y
{ P } (A ; B) ; C { Q }
son siempre equivalentes. Por ello, se escribe simplemente:
{P}A;B;C{Q}
Una propiedad similar vale para la composicin secuencial de cualquier nmero de acciones.
Sugerencia: Demuestra que pmd( A ; (B ; C) , Q) pmd( (A ; B) ; C , Q)
27. Demuestra que los dos enunciados de correccin que siguen son equivalentes; esto es, para
(a)varx,y:Ent;
{P}x:=y;y:=x{Q}
(b)varx,y:Ent;
{P}x:=y{Q}
28. El siguiente algoritmo anotado resuelve el problema de convertir una cantidad entera no
Variables locales
29. Verifica usando la regla de las variables locales:
varx,y:Ent;
{x=Xy=Y}
varz:Ent;
140
inicio
z:=x;x:=y;y:=z
fvar
{x=Yy=X}
para.
(a)varx,y,z:Ent;
(b)varx,y,z:Ent;
{x=Xy=Y}
{x=Xy=Y}
si
sixy
xyoz:=y
entoncesz:=y
yxoz:=x
sinoz:=x
fsi
fsi
{x=Xy=Yz=max(x,y)}
{x = X y = Y z =
max(x,y)}
31. Especifica, disea y verifica un algoritmo que calcule el mximo de tres nmeros enteros
dados.
32. En cada uno de los casos que siguen, haz la verificacin formal.
(a)varx:Ent;
{cierto}
si
ciertoox:=0
ciertoox:=1
fsi
{x=0x=1}
(c)varx,y:Ent;
{cierto}
(x,y):=(y*y,x*x);
si
xyox:=xy
yxoy:=yx
fsi
{x0y0}
(b)varx:Ent;
{cierto}
si
x0ox:=x+1
x0ox:=x1
fsi
{xz0}
(d)varx,y,z,p,q,r:Ent;
{x=Xy=Yz=Z}
<p,q,r>:=<x,y,z>;
si
p>qo<p,q>:=<q,p>
pqoseguir
fsi;
si
p>ro<p,r>:=<r,p>
proseguir
fsi;
si
q>ro<q,r>:=<r,q>
qroseguir
fsi
{Perm((p,q,r),(X,Y,Z))pqr}
141
33. Calcula la precondicin ms dbil P que satisface:
varx:Ent;
{P}
x:=x+1;
si
x>0ox:=x2
x=0oseguir
x<0ox:=x+3
{x1}
34. Dado:
varx:Ent;
{x=X}
si
x=1ox:=1
x=1ox:=1
fsi
{x=X}
demuestra con un contraejemplo que la especificacin no se cumple, y a continuacin fortalece la precondicin de manera que la especificacin se cumpla. Haz la verificacin.
35. Usa clculo de pmds para demostrar que las dos construcciones siguientes son equivalentes
(a)siB1oA1B2oA2fsi;A
(b)siB1oA1;AB2oA2;Afsi
(a)varx,y,p:Ent;
(b)varx,y,p:Ent;
{Pre.P:x=Xy=Yy0} {Pre.P:x=Xy=Yy0}
p:=0
p:=0;
{Inv.I:X*Y=p+x*yy0 {Inv.I:X*Y=p+x*yy0
CotaC:y} CotaC:y}
ityz0
ityz0
o{X*Y=p+x*yy>0} o{X*Y=p+x*yy>0}
<p,y>:=<p+x,y1>{I} sipar(y)
fit
entonces
{Post.Q:p=X*Y}
<x,y>:=<x+x,y
div2>
sino<p,y>:=<p+x,y1>
fsi{I}
fit
{Post.Q:p=X*Y}
142
{Post.Q:x=Xy=Yx=c*y+r0r<y}
39. Calcula la precondicin ms dbil posible P que haga vlida en cada caso la especificacin:
(a)varx:Ent;
{P}
it
xz0ox:=x1
fit
{x=0}
(b)varx:Ent;
{P}
it
xz0ox:=x2
fit
{x=0}
(c)varx:Ent;
{P}
itxz0
osix>0
entoncesx:=x2
sinox:=x+3
fsi
fit
{x=0}
40. Completa la verificacin del algoritmo de clculo del m.c.d. que se presenta a continuacin.
varx,y:Ent;
{Pre.P:x=Xy=Yx>Y0}
{Inv.I:x>y0mcd(x,y)=mcd(X,Y)
CotaC:y}
ityz0
o{x>y>0mcd(x,y)=mcd(X,Y)}
<x,y>:=<y,xmody>
fit
{Post.Q:x=mcd(X,Y)}
143
varv:Vector[1..N]deEnt;s:Ent;
{Pre:v=V}
varI:Ent;
%variablelocal
(s,I):=(0,1);
{Inv.I:v=V1IN+1s=i:1i<I:v(i)
CotaC:NI+1}
itIzN+1
os:=s+v(I);
I:=I+1{I}
fit
fvar
{Post:v=Vs=i:1iN:v(i)}
(b)Suma de una matriz de enteros (suponemos N, M 1, ctes.).
varv:Vector[1..N,1..M]deEnt;s:Ent;
{Pre:v=V}
varI,J:Ent;
%variableslocales
(s,I):=(0,1);
{InvExt.I:v=V1IN+1
s=i,j:1i<I1jM:v(i,j)
CotaExt.C:NI+1
}
itIzN+1
oJ:=1;
{InvInt.J:v=V1IN1JM+1
s=i,j:1i<I1jM:v(i,j)+
j:1j<J:v(I,j)
CotaInt.D:MJ+1
}
itJzM+1
os:=s+v(I,J);
J:=J+1{J}
fit;
I:=I+1{I}
fit
fvar
{Post:v=Vs=i,j:1iN1jM:v(i,j)}
42.Construye y verifica algoritmos para los problemas que siguen, anotando pre- y postcondi-
Procedimientos
43. Escribe especificaciones de procedimientos adecuados para las siguientes tareas:
(a) Calcular el m.c.d. de dos enteros. (cfr. Ej. 40)
(b) Calcular el cociente y el resto de una divisin entera. (cfr. Ej. 38)
144
Indica en cada caso la precondicin, la postcondicin, los tipos y los modos de uso de los
parmetros.
44. Disea un procedimiento que satisfaga la especificacin del apartado (d) del ejercicio ante-
Funciones
46. Algunos de los procedimientos del ejercicio 43 pueden ser planteados como funciones.
47. Usando el algoritmo del ejercicio 40, construye una funcin correcta para el clculo del
(a) Refuta mediante un contraejemplo:
{Cierto}x:=doble(x){x=2*x}
145
{x=y}
varz:Ent;
inicio
z:=doble(x);x:=y+z
fvar
{x=3*y}
Considerando la dimensin N de la matriz como medida del tamao de los datos, estima el
tiempo de ejecucin en el caso peor, en los dos supuestos siguientes:
(a) Tomando como nica accin caracterstica la asignacin que aparece en el bucle ms
interno.
funcbusca(v:Vector[1..N]deEnt;x:Ent)devencontrado:Bool;
{Pre.:v=Vx=X}
varI:Ent;
inicio
I:=1;
encontrado:=falso;
itINANDNOTencontrado
oencontrado:=(v(I)=x);
I:=I+1
fit
{Post.:v=Vx=X(encontradoli:1iN:v(i)=x)}
devencontrado
ffunc
146
Analiza la complejidad en promedio del algoritmo, bajo las hiptesis siguientes: (a) la probabilidad
de que x aparezca en v es un valor constante p, 0 < p < 1; y (b) la probabilidad de que x aparezca
en la posicin i de v (y no en posiciones anteriores) es la misma para cada ndice i, 1 i N.
51. Aplicando las definiciones de las notaciones O y :, razona.
(a)(n+1)2 es O(n2)
(c)3n no es O(2n)
(b)3n3+2n2 es O(n3)
(d)n3+2n2 es :(n3)
ciones siguientes:
f(n)
= 0 O(f) O(g).
g(n)
(b)limn
f(n)
z 0 f 4(g).
g(n)
n * (n - 1)
es 4(n2).
2
(b)p(n) es 4(nk), supuesto que p(n) sea un polinomio de grado k.
(c)loga n es 4(logb n), supuesto que a, b > 1.
(a)
56. Supongamos dos algoritmos A, B que resuelven el mismo problema, pero tales que TA(n)
es O(log n), mientras que TB(n) es O(2n). Responde razonadamente a las siguientes preguntas:
(a)Se puede asegurar que el tiempo de ejecucin de B ser superior al de A en todos
los casos?
(b)Si se duplica el tamao de los datos, cmo aumenta el tiempo de ejecucin para cada
uno de los dos algoritmos?
147
(c)Si se cambia la mquina por otra el doble de rpida, cmo aumenta para cada uno
de los dos algoritmos el tamao de los datos que pueden procesarse en un tiempo fijado?
57. El algoritmo siguiente resuelve el mismo problema que el algoritmo del ejercicio 49. Anali-
58. Analiza la complejidad del algoritmo de multiplicacin del ejercicio 36(b), tomando como
tamao de los datos n = y. Demuestra que el tiempo de ejecucin en el caso peor es O(log
n).
(Sugerencia: Razonando por induccin sobre n, demuestra que para y = n 2, el cuerpo del bucle del algoritmo se ejecuta t(n) veces, siendo t(n) 2 (log n).)
Derivacin de algoritmos iterativos
59. Deriva un algoritmo de divisin entera que cumpla la especificacin siguiente y que utilice
60. Deriva y analiza un algoritmo iterativo que cumpla la especificacin siguiente, usando so-
lamente las operaciones aritmticas de suma y producto, y descubriendo un invariante adecuado a partir de una descomposicin conjuntiva de la postcondicin.
148
funcraz(x:Ent)devr:Ent;
{P:x=Xx0}
{Q:x=X0rr2x<(r+1)2}
ffunc
61. Dados un vector v:Vector[1..N]deEnty un nmero x:Ent, se quiere calcular la
posicin de v con el menor ndice posible que contenga el valor x. Formaliza una especificacin pre/post y deriva a partir de ella un algoritmo correcto. OJO: no se supone que el
vector est ordenado.
calcular una posicin de a donde aparezca el valor x, con ndice de fila lo menor posible, e
ndice de columna lo menor posible dentro de esa fila. Escribe una especificacin formal y
deriva a partir de ella un algoritmo correcto. OJO: no se supone que la matriz est ordenada.
63. Deriva un algoritmo iterativo que calcule el producto escalar de dos vectores dados, a partir
de la especificacin siguiente:
funcprodEsc(u,v:Vector[1..N]deReal)devr:Real;
{P:N1v=Vu=U}
{Q:r=i:1iN:u(i)*v(i)u=Uv=V}
ffunc
Observa que (a) conduce a un bucle ascendente, mientras que (b) conduce a un bucle descendente.
64. Deriva un algoritmo iterativo que satisfaga la siguiente especificacin pre/post, partiendo
funcesOrd(v:Vector[1..N]deEnt)devb:Bool;
{P:N1v=V}
{Q:v=V(blord(v))}
ffunc
149
Usando la nueva forma de la postcondicin, descubre un invariante y una expresin de acotacin adecuados, y deriva un algoritmo correcto de complejidad O(N). Finalmente, trata de optimizar fortaleciendo la condicin de iteracin, de modo que se evite el recorrido completo de v
cuando v no est ordenado.
66. Considera otra vez la especificacin pre/post del ejercicio 60. Deriva un nuevo algoritmo
Generalizar la postcondicin reemplazando la expresin (r+1)2 por otra que introduzca una nueva variable s2.
Avanzar asignando el valor medio (r+s)div2 a una de las dos variables r o s, segn
convenga para el mantenimiento del invariante.
Analiza la eficiencia del algoritmo obtenido y compara con la solucin del ejercicio 60.
67. Considera la siguiente especificacin para un algoritmo que debe calcular en k el logaritmo
funclogBin(n:Ent)devk:Ent;
{P:n=Nn1}
{Q:n=Nk02kn<2k+1}
ffunc
recurrencia:
fib(0)=0
fib(1)=1
fib(n)=fib(n2)+fib(n1),paratodon2
150
ffunc
I0:v=V0nNr=#i,j:1i<jn:v(i)0
v(j)0
Obsrvese que se cumple: I0n=NQ. Se exige que el algoritmo obtenido tenga complejidad O(n).
70. Especifica y deriva un algoritmo O(n) que calcule el nmero de concordancias de un vector v:
Vector[1..N]deEnt dado como parmetro. Se entiende que hay que contar una concordancia por cada pareja de ndices i,j tales que 1i<jN y v(i), v(j) tengan el mis-
mo signo. Por convenio, el nmero 0 no tiene signo, a efectos de resolver este ejercicio.
71. Especifica y deriva un algoritmo iterativo que, dado un entero no negativo n, calcule r=
i:0in:i!. Se exige que el algoritmo obtenido sea O(n) y utilice nicamente
72. Especifica y deriva un algoritmo iterativo de complejidad O(n) que calcule el nmero de
ndices puculiares de un vector de tipo Vector[1..N]deEnt, entendiendo que un ndice i
funcbusca(v:Vector[1..N]deEnt;x:Ent)devp:Ent;
{P0:N1v=Vx=X}
{Q0:v=Vx=X1pN(i:1i<p:v(i)zx)
(v(p)=xp=N)}
ffunc
Observa que el algoritmo obtenido en el apartado (b) tiene una condicin de iteracin ms
simple.
74. Deriva una funcin que realice un algoritmo O(log N) de bsqueda binaria en un vector or-
funcbusca(v:Vector[1..N]deEnt;x:Ent)devp:Ent;
{P0:N1v=Vx=Xord(v)}
{Q0:v=Vx=X0pN
i:1ip:v(i)xi:p+1iN:x<v(i)}
ffunc
151
Interpreta la postcondicin. Qu dice Q0 en los casos extremos, es decir, para p=0 y p=N?
Cmo podemos deducir de Q0 si x est o no en el vector?
Pista: Generaliza la postcondicin observando que
Q0v=Vx=X0p<qN+1i:1ip:v(i)x
i:qiN:x<v(i)q=p+1
satisfaga.
procinserta(esv:Vector[1..N]deEnt;ep:Ent);
{P0:1p<Nv=Vp=Pi,j:1i<jp:v(i)v(j)}
{Q0:p=Ppermut(v,V)i,j:1i<jp+1:v(i)v(j)}
fproc
i,j:m+1i<jp+1:v(i)v(j)
i,j:1imm+2jp+1:v(i)v(j)(m=0v(m)
v(m+1))
procordena(esv:Vector[1..N]deEnt);
{P0:v=vN1}
{Q0:permut(v,V)ord(v)}
fproc
siendo
ord(v)defi,j:1i<jN:v(i)v(j)
Descompn para encontrar un invariante y aprovecha el procedimiento del ejercicio anterior para disear la accin encargada de su restablecimiento.
77. Deriva una funcin O(Np+1) que satisfaga la especificacin siguiente:
funcbuscaMin(v:Vector[1..N]deEnt;p:Ent)devm:Ent;
{P0:1p<Nv=Vp=P}
{Q0:p=Pv=VpmNv(m)=mink:pkN:v(k)}
ffunc
seleccin, partiendo de la misma especificacin del ejercicio 76, pero tomando ahora
ord(v)defi:1i<N:(j:i<jN:v(i)v(j))
152
n=N
Descompn para encontrar un invariante y aprovecha el procedimiento del ejercicio anterior para disear la accin encargada de su restablecimiento.
Algoritmos recursivos
153
CAPTULO 2
funfact(n:Nat)devr:Nat;
{P0:N=n}
inicio
si
n=0or:=1
n>0or:=n*fact(n1)
fsi
{Q0:n=Nr=3i:1in:i}1
devr
ffunc
Ntese que para n=0 el aserto de dominio del producto extendido define el conjunto vaco, y por lo tanto el
producto extendido da 1 como resultado, el elemento neutro del producto.
1
Algoritmos recursivos
154
La ejecucin de fact(2)?
Programa
principal
.
.
.
x := fact(2)
Programa
principal
.
.
.
x := fact(2)
Programa
principal
.
.
.
x := fact(2)
Programa
principal
.
.
.
x := fact(2)
Programa
principal
.
.
.
x := fact(2)
Programa
principal
.
.
.
x := fact(2)
Programa
principal
.
.
.
x := fact(2)
fact(2)
.
.
.
r := n * fact(n-1)
fact(2)
fact(1)
.
.
.
.
.
.
r := n * fact(n-1)
r := n * fact(n-1)
fact(2)
fact(1)
fact(0)
.
.
.
.
.
.
.
.
.
r := n * fact(n-1)
r := n * fact(n-1)
fact(2)
fact(1)
.
.
.
.
.
.
r := n * fact(n-1)
fact(2)
.
.
.
r := n * fact(n-1)
r := n * fact(n-1)
r := 1
.
.
.
Algoritmos recursivos
155
En realidad no se realizan copias del cdigo sino simplemente de las variables locales y de la direccin de retorno, pero valga el smil.
La importancia de la recursin radica en que:
Al hilo de esto, sealar que hay algunos autores que consideran que la recursin es un mtodo
ms natural de resolver problemas que la repeticin (iteracin). Es por ello que, por ejemplo, en
el libro de Pea se trata primero el diseo recursivo y despus el iterativo. Relacionado con esta
postura nos encontramos con que se proponen cursos de introduccin a la programacin utilizando lenguajes funcionales, donde el mecanismo natural de resolucin de problemas es la recursin. Mi opinin personal es que determinados problemas se resuelven ms fcilmente con un
diseo iterativo mientras que otros piden una solucin recursiva; de modo que a veces resulta
forzado intentar encontrar diseos recursivos para ciertos problemas cuya solucin iterativa es
directa, y viceversa.
En este tema nos vamos a limitar al estudio de funciones recursivas. Los procedimientos no
aportan nada ms y el tratamiento es menos engorroso con las funciones. Al trasladar los resultado sobre funciones a procedimientos slo hay que tomar algunas precauciones con los parmetros de entrada/salida. Vamos a analizar los diferentes esquemas a los que se ajustan las funciones
recursivas, dependiendo del nmero de llamadas recursivas y de la funcin de combinacin.
1.1.1
Recursin simple
Decimos que una accin recursiva tiene recursin simple si cada caso recursivo realiza a lo
sumo una llamada recursiva.
El esquema de declaracin de funciones recursivas simples:
funcnombreFunc(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
{P0:Px1=X1xn=Xn}
cte;
var;
inicio
si
&
&
&
&
&
&
&
Algoritmos recursivos
156
dev<y1,,ym>
ffunc
donde
&
d( x ) es la condicin que determina el caso directo
&
d( x ) es la condicin que determina el caso recursivo
d(n) n = 0
d(n) n > 0
g(n) = 1
s(n) = n1
c(n, fact (n1)) = n * fact(n1)
A la recursin simple tambin se la conoce como recursin lineal porque el nmero de llamadas recursivas depende linealmente del tamao de los datos.
Una caso particular de recursin simple se obtiene cuando la funcin de combinacin se limita
a transmitir el resultado de la llamada recursiva. Este tipo de recursin simple recibe el nombre de
recursin final (tail recursion). Ntese que en una funcin recursiva final, el resultado devuelto
ser siempre el obtenido en uno de los casos base, ya que la funcin de combinacin se limita a
transmitir el resultado que recibe, sin modificarlo. Se llama final porque lo ltimo que se hace en
cada pasada es la llamada recursiva.
Las funciones recursivas finales tienen la interesante propiedad de que pueden ser traducidas
de manera directa a soluciones iterativas, ms eficientes.
Como ejemplo de funcin recursiva final veamos el algoritmo de clculo del mcd por el mtodo de Euclides.
funcmcd(a,b:Ent)devm:Ent;
{P0:a=Ab=Ba>0b>0}
inicio
si
Algoritmos recursivos
157
a>bom:=mcd(ab,b)
a<bom:=mcd(a,ba)
a=bom:=a
fsi
{Q0:a=Ab=Bm=MCD(a,b)}
devm
ffunc
Tenemos aqu un ejemplo donde se distinguen ms de dos casos, en particular dos casos recursivos y un caso base. La funcin es recursiva simple porque en cada caso recursivo se hace una
sola llamada recursiva. Veamos cmo se ajusta al esquema de recursin simple:
d(a,b) a = b
d2(a,b) a < b
d1(a,b) a > b
g(a,b) = a
s2(a,b) = (a, ba)
s1(a,b) = (ab, a)
c(a,b,mcd(x,y)) = mcd(x,y)
Recursin mltiple
Este tipo de recursin se caracteriza por que en cada pasada se realizan varias llamadas recursivas. El esquema correspondiente:
funcnombreFunc(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
{P0:Px1=X1xn=Xn}
cte;
var;
inicio
si
&
&
&
&
&
&
&
&
donde
k > 1, indica el nmero de llamadas recursivas
Algoritmos recursivos
158
&
d( x ) es la condicin que determina el caso directo
&
d( x ) es la condicin que determina el caso recursivo
si (funciones sucesor) calculan la descomposicin de los datos de entrada para realizar la isima llamada recursiva, para i = 1, , k
Se puede implementar de manera directa una funcin que calcula los nmeros de Fibonacci siguiendo el esquema de recursin mltiple:
funcfibo(n:Nat)devr:Nat;
{P0:n=N}
inicio
si
n=0or:=0
n=1or:=1
n>1or:=fibo(n1)+fibo(n2)
fsi
{Q0:n=Nr=fib(n)}
devr
ffunc
d1(n) n = 0
d2(n) n = 1
d(n) n > 1
g(0) = 0
g(1) = 1
(g(n) = n?)
s2(n) = n2
s1(n) = n1
c(n, fibo(n1), fibo(n2)) = fibo(n1) + fibo(n2)
En la recursin mltiple el nmero de llamadas no depende linealmente del tamao de los datos; en general ser de mayor orden.
Podemos ver un ejemplo con las llamadas que se desencadenan al computar fib(4). Comprobamos que algunos resultados se computan ms de una vez:
Algoritmos recursivos
159
1+2=3
0+1=1
0
fib(2)
fib(0)
1+1=2
fib(4)
1
fib(1)
0+1=1
fib(3)
0
1
fib(1)
fib(2)
fib(0)
fib(1)
Para terminar con la introduccin recapitulamos los distintos tipos de funciones recursivas que
hemos presentado:
r:=n*fact(n1)
Algoritmos recursivos
1.2.1
160
Hemos de verificar el cuerpo de una funcin recursiva simple, que se corresponde con el esquema:
si
&
&
&
&
&
&
&
Para ello, como hemos, dicho utilizaremos el principio de induccin, en este caso el principio de
induccin ordinaria que, expresada sobre N queda
P(0)
n:NatP(n)P(n+1)
n:Nat:P(n)
O, si consideramos que R(x) es la propiedad de correccin del cuerpo con respecto a la especificacin:
&
&
d( x )R( x )
&
&
R(s( x ))R( x )
&
&
x R( x )
&
&
Considerando que s( x ) es el valor anterior de x .
Para obtener las reglas de verificacin vamos a empezar aplicando las reglas de verificacin de
la composicin alternativa, pues esa es la forma que toma el cuerpo de la funcin recursiva. En
primer lugar, se tiene que garantizar que las barreras estn definidas y que al menos una de ellas
se abra:
&
&
&
&
P0d( x )d( x )
Por otra parte, debe ser correcta la rama del caso directo:
&
&
&
Algoritmos recursivos
161
&
&
&
&
como es posible que la funcin no sea recursiva final, y que por lo tanto la llamada recursiva
aparezca dentro de una expresin, introducimos una variable auxiliar que nos facilite la verificacin:
&
{P0d( x )}
&
&
y :=nombreFunc(s( x ));
&
& &
y :=c( x , y )
{Q0}
para verificar esta composicin secuencial tomamos un aserto intermedio, obtenido como la
pmd de la segunda asignacin inducida por Q0:
&
{P0d( x )}
&
&
y :=nombreFunc(s( x ));
& &
&
&
&
la segunda asignacin ser correcta pues hemos tomado como aserto intermedio su pmd; por
lo tanto slo nos resta verificar la llamada recursiva. Aqu es donde aparece la hiptesis de induccin, verificaremos la llamada suponiendo que existe una realizacin correcta para dicha lla&
mada con s( x ).
Aplicando el mtodo de verificacin de llamadas tenemos:
1. Se puede realizar la llamada
&
&
&
P0d( x )P0[ x /s( x )]
sin embargo, es necesario tener en cuenta un detalle en la mayora de las ocasiones irre&
&
levante y es que en P0[ x /s( x )] pueden aparecer variables auxiliares de la especificacin, con los mismos identificadores que las que aparecen en P0; y no deben confundirse.
Por ello, en realidad demostraremos:
& &
&
&
&
P0d( x )P0[ x /s( x )][ X / X ]
&
siendo X las variables que recogen los valores iniciales de la llamada recursiva
2. Extraer de
&
&
&
&
&
Algoritmos recursivos
162
& &
&
&
se ha verificado el paso 1, todo lo que se puede obtener de P0 [ x / s( x ) ] [ X / X ] est
&
ya en P0 d( x ). Por lo tanto el aserto R que consideramos es:
&
P0d( x )
Este ltimo razonamiento tambin es aplicable en el caso general de llamadas a procedimientos y funciones. La razn por la que en el caso general no aplicamos esta simplificacin es que la precondicin del procedimiento puede incluir condiciones que nos ayuden
a reescribir la precondicin de la llamada y sacar a la luz las condiciones implcitas.
3. Demostramos que:
&
&
&
&
& &
&
&
&
&
&
R(s( x ))R( x )
Lo ltimo que nos queda para demostrar la correccin de una funcin recursiva es comprobar
su terminacin. Para ello vamos a definir, al igual que en los bucles, una expresin de acotacin
que dependa de los parmetros de la funcin y que tome valores en un dominio bien fundamentado
&
t( x ) : DW& o D
Tendremos que comprobar entonces que al entrar en la rama recursiva la expresin de acotacin est definida y sea decrementable
&
&
&
&
&
&
Con todo esto la verificacin de una funcin recursiva pasa por demostrar los siguientes requisitos:
(r.1) condiciones definidas
&
&
P0def(d( x ))def(d( x ))
(r.2) condiciones exhaustivas
&
&
P0d( x ))d( x )
Algoritmos recursivos
&
163
&
&
&
&
&
Veamos un ejemplo de aplicacin de las reglas de verificacin de funciones recursivas, aplicada a la funcin factorial.
(r.1) condiciones definidas
N=ndef(n=0)def(n>0)cierto
n=0n>0
n:Nat
declaracin ciertoN=n
def(1)Q0[r/1]
cierton=N1=3i:1in:1
n=Nn=0
P0n=0
P0[n/n1][N/N]
n1=N
n1:Nat
Algoritmos recursivos
164
n>0n:Nat
n=Nn>0
def(n*r)Q0[r/n*r]
cierton=Nn*r=3i:1in:i
n=Nn>0Q0[n/n1,r/r][N/N]
n=Nn>0n1=Nr=3i:1in1
n=Nn*r=n*(3i:1in1:i)
n=Nn*r=3i:1in:i
def(n*r)Q0[r/n*r]
demostramos
n=Nn>0def(n)dec(n)n>0
(r.7) descenso
n=Nn>0n1<ncierto
Algoritmos recursivos
&
165
&
&
&
&
donde las dj( x ) son las condiciones de los casos directos y las gj( x ) las funciones que obtiene la solucin directa en cada caso, para j= 1, , p
El resto de los requisitos no se ven modificados pues hacen referencia a los casos recursivos.
Ms de un caso recursivo
Los requisitos que se ven modificados
(r.1) todas las barreras han de estar definidas
&
&
&
P0def(d( x ))def(d1( x ))def(dp( x ))
(r.2) condiciones exhaustivas
&
&
&
P0d( x ))d1( x )dp( x )
(r.4) Cada llamada recursiva ha de estar definida. Tenemos p requisitos de la forma (r.4)j
& &
&
&
&
P0dj( x )P0[ x /sj( x )][ X / X ]
(r.5) Se ha de comprobar el paso inductivo para cada caso recursivo. Tenemos p requisitos de
la forma (r.5)j
& &
&
&
&
& &
& &
P0dj( x )Q0[ x /sj( x ), y / y ][ X / X ]def(cj( x , y ))
&
& &
Q0[ y /cj( x , y )]
(r.6) Aunque la expresin de acotacin es nica, debemos verificar que todos los casos recursivos garantizan (r.6)j
&
&
&
P0dj( x )def(t( x ))dec(t( x ))
(r.7) Se ha de comprobar el descenso de la expresin de acotacin para cualquier descomposicin recursiva. Tenemos requisitos (r.7)j de la forma
&
&
&
P0dj( x )t(sj( x )) % t( x )
&
&
Siendo las dj( x ) las condiciones de los casos recursivos, las sj( x ) las funciones que calculan
& &
las descomposiciones recursivas, y las cj( x , y ) las funciones que combinan los resultados, para
j= 1, , p
Algoritmos recursivos
166
Cuando nos encontremos con una funcin recursiva que incluya ms de un caso directo y ms
de un caso recursivo, deberemos considerar los dos conjuntos de cambios que acabamos de indicar.
Veamos un ejemplo de funcin recursiva con varios casos base y varios casos recursivos: el
producto de dos nmeros naturales por el mtodo del campesino egipcio. La idea del mtodo es ir
dividiendo por 2 un operando y multiplicando por 2 el otro, hasta que un operando valga 1
0
a
si b = 0
si b = 1
(2 a) (b div 2)
si b > 1, b par
(2 a) (b div 2) + a
si b > 1, b impar
ab=
funcprod(a,b:nat)devr:Nat;
{P0:a=Ab=B}
inicio
sib=0 or:=0
b=1 or:=a
(b>1ANDpar(b)) or:=prod(2*a,bdiv2)
(b>1ANDNOTpar(b))or:=prod(2*a,bdiv2)+a
fsi
{Q0:a=Ab=Br=a*b}
devr
ffunc
Algoritmos recursivos
b=0b=1(b>1(par(b)ORNOTpar(b)))
b=0b=1b>1
ciertoa=ab=B
167
Algoritmos recursivos
168
(b>1ANDpar(b))r=(2*a)*(bdiv2)
(b>1ANDpar(b))r=a*2*(bdiv2)
(b>1ANDpar(b))r=a*b
r=a*b
Se demuestra
P0(b>1ANDpar(b))def(b)dec(b)ciertob>0
P0(b>1ANDNOTpar(b))def(b)dec(b)ciertob>0
Algoritmos recursivos
169
P0(b>1ANDNOTpar(b))(bdiv2)<b
secumpleporb>0
1.2.2
&
&
&
&
&
&
&
&
En el que aparecen varias llamadas recursivas. Para demostrar la correccin hemos de aplicar
el principio de induccin completa, que generaliza al principio de induccin normal cuando existe
ms de una descomposicin recursiva. Hemos de demostrar que se cumple el paso inductivo para
cada una de las descomposiciones recursivas:
n:Nat i:Nat:0i<n:P[i]P[n]
n:Nat:P[n]
&
O considerando que R( x ) es la propiedad de correccin del cuerpo con respecto a la especificacin, aplicamos la regla:
&
&
d( x )R( x )
&
&
&
&
&
x R( x )
&
&
&
&
&
&
&
&
& &
&
y :=c( x , y 1,, y k)
y k:=nombreFunc(sk( x ));
Algoritmos recursivos
170
{Q0}
La verificacin de cada una de las llamadas recursivas introduce una serie de condiciones, que
podemos trasladar al final de la composicin y considerar as la verificacin de una sola instruccin:
&
&
&
&
& &
&
&
&
&
& &
&
&
&
&
&
Para ver que este resultado de correccin es equivalente al anterior tendramos que ir construyendo la pmd de cada una de las asignaciones intermedias, con lo cual dichas asignaciones sern
correctas por el axioma de correccin de la asignacin.
Aplicando la regla de verificacin de la asignacin, la anterior verificacin es equivalente a:
&
&
&
&
& &
&
&
&
& &
&
&
[ X / X k]
&
&
&
&
&
&
&
que es precisamente el paso inductivo; supuestas correctas las llamadas recursivas con las correspondientes descomposiciones, se demuestra que es correcto el paso actual.
Con todo esto los requisitos que hay que comprobar para demostrar la correccin de una funcin con recursin mltiple:
(r.1) condiciones definidas
&
&
P0def(d( x ))def(d( x ))
(r.2) condiciones exhaustivas
&
&
P0d( x ))d( x )
(r.3) caso base
&
&
&
Algoritmos recursivos
171
&
&
&
&
&
&
&
&
&
&
&
&
(r.5) Se ha de comprobar el paso inductivo para cada caso recursivo. Tenemos p requisitos de
la forma (r.5)j2
& &
&
&
&
& &
&
&
& &
P0dj( x )Q0[ x /sj1( x ), y / y 1][ X / X 1]Q0[ x /sjk ( x ), y / y k ]
& &
[ X / X k ]
j
Ntese que reutilizamos las variables auxiliares en los distintos casos recursivos, porque si no deberamos definir variables yj1, , yjkj
2
Algoritmos recursivos
&
&
172
&
&
&
&
&
(r.6) Aunque la expresin de acotacin es nica, debemos verificar que todos los casos recursivos garantizan (r.6)j
&
&
&
P0dj( x )def(t( x ))dec(t( x ))
(r.7) Se ha de comprobar el descenso de la expresin de acotacin para cualquier descomposicin recursiva. Tenemos requisitos (r.7)j de la forma
&
&
&
&
&
P0dj( x )t(sj1( x )) % t( x )t(sjk ( x )) % t( x )
j
&
&
Siendo las dj( x ) las condiciones de los casos recursivos, las sji( x ) las funciones que calculan
& &
&
las descomposiciones recursivas, y las cj( x , y 1, , y k) las funciones que combinan los resultados, para j= 1, , p, i = 1, , kj
Si en una funcin aparecen varios casos directos y varios casos recursivos, tendremos que
combinar los dos conjuntos de modificaciones arriba descritos.
Veamos como ejemplo de verificacin de funcin con recursin mltiple la verificacin de la
funcin fibo que presentamos al principio del tema:
funcfibo(n:Nat)devr:Nat;
{P0:n=N}
inicio
si
n=0or:=0
n=1or:=1
n>1or:=fibo(n1)+fibo(n2)
fsi
{Q0:n=Nr=fib(n)}
devr
ffunc
n=Nn=0n=1n>1n:Natcierto
Algoritmos recursivos
173
{n=Nn=0}
r:=0
{n=Nr=fib(n)}
n=Nn=0def(0)n=N0=fib(n)
P0[n/n2][N/N2]
n2=N2
n2:Nat
n:Natn2
n=Nn>1
r1=fib(n1)r2=fib(n2)
r1+r2=fib(n1)+fib(n2)
Algoritmos recursivos
174
r1+r2=fib(n)
n=Nn>1n1<nn2<ncierto
&
&
Algoritmos recursivos
175
(R.3) Caso directo. Hemos de encontrar la accin que resuelve el caso directo; la podemos
derivar a partir de la especificacin
&
{P0d( x )}A1{Q0}
De acuerdo con los esquemas de las funciones recursivas intentaremos que A1 sea de
la forma:
&
&
y :=g( x )
&
s( x )
Si hay ms de un caso recursivo obtenemos la funcin siguiente para cada uno de ellos.
(R.5) Funcin de acotacin. Antes de pasar a derivar el caso recursivo, nos interesa determinar si la funcin siguiente escogida garantiza la terminacin de las llamadas. Para
&
ello hemos de obtener la funcin de acotacin t( x ), definida en los casos recursivos y
que toma valores no minimales en un dominio bien fundamentado. Demostraremos
entonces el requisito (r.6)
&
&
&
&
&
&
Algoritmos recursivos
176
(R.7) Llamada recursiva. Pasamos a ocuparnos entonces del caso recursivo. Empezamos
verificando que las descomposiciones recursivas permiten realizar las llamadas recursivas. Es decir, comprobamos el requisito (r.4):
&
&
&
&
&
&
&
&
& &
&
&
&
&
& &
&
&
& &
&
&
&
& &
&
&
donde las variables yj representan a los resultados de las llamadas recursivas, para
j=1,, k
(R.9) Escritura del caso recursivo. Lo ltimo que nos queda por decidir es si escribimos la
solucin del caso recursivo como una composicin secuencial
Algoritmos recursivos
177
&
{P0d( x )}
&
&
y :=nombreFunc(s( x ));
& &
&
&
&
& &
{P0d( x )Q0[ x /s( x ), y / y ][ X / X ]}
&
& &
y :=c( x , y )
{Q0}
cuya correccin est demostrada por el paso anterior (la llamada es correcta por la
forma de la postcondicin y el aserto intermedio es ms fuerte que la pmd de la segunda asignacin inducida por la postcondicin).
O si podemos incluir la llamada o llamadas recursivas dentro de la expresin que las
combina:
&
{P0d( x )}
&
&
&
y :=c( x ,nombreFunc(s( x )))
{Q0}
que tambin es correcta, porque para verificarla la convertiramos a la forma anterior, sobre la que ya hemos razonado su correccin.
Este esquema se generaliza de manera inmediata para el caso de mltiples llamadas recursivas.
Veamos un primer ejemplo de aplicacin del mtodo de derivacin. Vamos a obtener una
funcin que dado un natural n calcule la suma de los dobles de los naturales hasta n:
i:0in:2*i
La especificacin:
funcsumaDoble(n:Nat)devs:Nat;
{P0:n=N}
{Q0:n=Ns=i:0in}
demostramos la correccin
n=Ndef(n=0)def(nz0)cierto
n=Nn=0nz0cierto
Algoritmos recursivos
178
Algoritmos recursivos
179
n1=N
n1:Nat
n:Natn1
n:Natnz0
Observamos que la diferencia entre pre y postcondicin se puede salvar con la asignacin:
s:=s+2*n
Verificamos que efectivamente esta sentencia verifica la especificacin, pues la precondicin es ms fuerte que la pmd de la asignacin inducida por Q0:
def(s+2*n)Q0[s/s+2*n]
cierton=Ns+2*n=i:1in:2*i
n=Ns+2*n=i:1in1:2*i+2*n
n=Ns=i:1in1:2*i
P0nz0Q0[n/n1,s/s][N/N]
Nos resta plantearnos si es posible fundir la llamada recursiva en la funcin de combinacin. En este caso s es posible ya que la funcin devuelve un nico resultado que se utiliza en la combinacin:
{P0nz0}
s:=sumaDob(n1)+2*n
{Q0}
Algoritmos recursivos
180
funcsumaDoble(n:nat)devs:Nat;
{P0:n=N}
inicio
si
n=0o
{P0n=0}
s:=0
{Q0}
nz0o
{P0nz0}
s:=sumaDoble(n1)+2*n
{Q0}
fsi
{Q0:n=Ns=i:1in:2*i}
devs
ffunc
funcsumaVec(v:Vector[1..N]deEnt)devs:Ent;
{P0:v=VN1}
{Q0:v=Vs=i:1iN:v(i)}
Algoritmos recursivos
181
Pero, ya que estamos, y pensando en darle ms utilidad a nuestra funcin podemos generalizar por los dos lados y derivar una funcin que pueda obtener la suma de un subvector
cualquiera:
funcsumaVec(v:Vector[1..N]deEnt;a,b:Ent)devs:Ent;
{P0:v=VN1b=Ba=A1abN}
{Q0:v=Vb=Bs=i:aib:v(i)}
Para facilitarle la vida a los usuarios de nuestra funcin podemos plantearnos la posibilidad de implementar una funcin con la especificacin original y otra funcin auxiliar con
la especificacin generalizada. De esta forma el cuerpo de la funcin principal consistir
nicamente en la invocacin inicial a la funcin auxiliar.
Con todo esto el planteamiento recursivo queda:
sumaVec(v,a,b)=v(a)+sumaVec(v,a+1,b)
{P0a=b}
A1
{a=Ab=Bv=Vs=i:aib:v(i)}
A1:s:=v(a)
3 Estamos incluyendo en la precondicin las restricciones que nos interesan y no nos preocupamos por garantizarlas. En esa misma lnea, en este ejemplo, no nos preocupamos por el caso b < a, para el que podramos devolver
0. Si considersemos ese caso tendramos una implementacin ms robusta.
Algoritmos recursivos
182
Se demuestra
P0azbdef(ba)dec(ba)
dec(ba)
ba>0
b>a
baazbP0azb
(R.6) Avance
Demostramos
P0azbb(a+1)<baba1<bacierto
o lo que es igual
Algoritmos recursivos
183
{P0azbv=Va=Ab=Bs=i:a+1ib:v(i)
}
A2
{v=Va=Ab=Bs=i:aib:v(i)}
Algoritmos recursivos
184
la terminacin del bucle ocurre cuando esas posiciones son consecutivas, y tambin es por ello
por lo que no desplazamos pos a la derecha de m o q a la izquierda de m, sino que las desplazamos
a m. De acuerdo con estas ideas, la inicializacin del bucle asigna <pos,q>:=<0,N+1> que es la
nica forma de no hacer ninguna suposicin sobre dnde est x.
Para implementar la idea recursiva vamos a introducir dos variables adicionales que indiquen
dnde empieza y dnde acaba el vector a considerar, que es una idea ligeramente distinta de la
utilizada en el algoritmo iterativo (no es que aqu no podamos utilizar esta misma idea, pero resulta ms natural la que proponemos). De estar en algn sitio la componente buscada estar en el
intervalo v[a..b], modificaremos los lmites de forme que a la izquierda de a estn valores menores
o iguales que el buscado y la derecha de b valores mayores que el buscado, pero sin presuponer
nada sobre los valores de v[a..b], a diferencia del otro planteamiento donde v(a)x y v(b)>x, cosa
que no podemos garantizar ahora si restringimos a y b a los lmites vlidos del vector, dentro de
los cuales no sabemos si hay algn valor mayor que el buscado o algn valor menor o igual.
Si x no se encuentra en el vector queremos que pos se quede apuntando a la posicin anterior a
la que debera ocupar x.
Con todo esto la especificacin queda:
funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P0:1abNord(v,a,b)}
{Q0:a1pbv[a..p]x<v[(p+1)..b]}
donde:
hemos obviado los asertos sobre la conservacin de los parmetros de entrada (demasiado laborioso)
El aserto ord(v,a,b), que ya apareci en un ejercicio indica que el vector entre las posiciones a y b est ordenado:
ord(v,a,b)i,j:ai<jb:v(i)v(j)
Podramos exigir simplemente ord(v), pues de hecho se cumple, pero esta otra condicin
es ms coherente con la cabecera de la funcin.
Hemos introducido una nueva notacin para escribir condiciones que se cumplen en un
subconjunto contiguo de un vector. As
v[a..p]x
abrevia
i:aip:v(i)x
y
x<v[(p+1)..b]
abrevia
i:p+1ib:x<v(i)
Algoritmos recursivos
185
p=bv[a..b]x%v(b)=xxesmayorquetodaslascomponentes
demostramos
P0def(a=b)def(azb)
P0a=bazb
puesto que
P0enRango(v,a)
P0a=bv(a)=xv(a)<xv(a)>xcierto
Algoritmos recursivos
186
A1
{v[a..p]x<v[(p+1)..b]}
A1:p:=a
{P0a=bv(a)<x}
A2
{v[a..p]x<v[(p+1)..b]}
A2:p:=a
{P0a=bv(a)>x}
A3
{v[a..p]x<v[(p+1)..b]}
A3:p:=a1
Algoritmos recursivos
187
tenemos que
abazba<bam<b
vamos a aadir esta condicin a todas las demostraciones pues partimos de un estado en
el que se cumple esto, despus de haber hecho la asignacin a m
&
{P0d( x )}
m:=(a+b)div2
&
{P0d( x )am<b}
A4
{Q0}
Por otra parte, si x v(m) quiere decir que m est entre a y p y que podemos acotar la
bsqueda con la descomposicin:
s2(v,x,a,b)=<v,x,m+1,b>
Podramos pensar en distinguir el caso x = v(m), haciendo a = m. Pero esto puede llevar a
una cadena infinita de llamadas si a=b1 v(a)=x
(R.5) Funcin de acotacin
Lo que va a ir disminuyendo segn avanza la recursin es la longitud del subvector a considerar, por lo tanto tomamos como funcin de acotacin:
t(v,x,a,b)=ba
Algoritmos recursivos
188
ba>0
b>a
abazb
P0azb
(R.6) Terminacin
Tenemos que demostrar
&
&
&
&
&
&
&
&
1abN[a/a,b/m1]
1am1N
1abNazbam<b?
No siempre se llega al caso base a = b, sino que tambin se puede llegar al caso base a =
b+1, eso implica que b puede tomar el valor 0 y que a puede tomar el valor N+1. Por lo
tanto hay que hacer varios cambios a lo ya obtenido:
Hay que cambiar la especificacin para que la precondicin incluya:
1ab+1N+11aN+10bNab+1
Algoritmos recursivos
189
&
d3( x ):a<b
En los razonamientos sobre la terminacin (R.5 y R.6) no hay que cambiar nada porque estbamos razonando a partir de a b a z b que es equivalente a razonar con
a < b.
&
&
&
&
1ab+1N+1[a/a,b/m1]
1am1+1N+1
1amN+1
1ab+1N+1azbam<b
1ab+1N+1[a/m+1,b/b]
1m+1b+1N+1
0mbN
1ab+1N+1azbam<b
a1pbv[a..p]v(x)<v[(p+1)..b]a=
a1pa1v[a..(a1)]v(x)<v[a..(a1)]a
a1=pa=b+1
Intuitivamente podemos ver que este caso puede darse cuando v(a)>x y por lo tanto p debe
apuntar a la posicin anterior (para as apuntar a la posicin anterior del lugar de insercin); o, si no admitisemos como caso base a=b, porque v(b)x con lo que p debe apuntar a b, que es precisamente a1.
(R.8) Funcin de combinacin
En los dos casos de descomposicin recursiva nos limitamos a propagar el resultado de la
llamada recursiva:
&
p:=nombreFunc(s( x ));p:=p
P0am<ba<bv(m)xm+11pbv[(m+1)..p]x<
v[(p+1)..b]
Algoritmos recursivos
190
a1pbv[a..p]x<v[(p+1)..b]
Y no se vayan todava que an hay ms: analizando este programa con cuidado nos damos
cuenta de que el caso base a = b es redundante y lo podemos eliminar.
Algoritmos recursivos
1.3.1
191
Vamos a estudiar dos algoritmos de ordenacin de complejidad cuasi-lineal, O(n log n): la ordenacin rpida y la ordenacin por mezcla. Debido a la complejidad de estos algoritmos y con el
nimo de terminar algn da la asignatura, relajaremos los requisitos formales para la obtencin
de los algoritmos.
Vamos a disear ambos algoritmos como procedimientos para evitar as el coste que supone
realizar una copia del vector a ordenar (aunque en la ordenacin por mezcla se utiliza un vector
auxiliar) y considerando as mismo que la mayora de los lenguajes de programacin imperativa
no permiten que las funciones devuelvan tipos estructurados.
Ordenacin rpida
Ideado por C. A. R. Hoare en 1962.
La especificacin de la que partimos:
procquickSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P0:v=Va=Ab=B1ab+1N+1}
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc
para indicar que v es una permutacin de V entre las posiciones a y b; el aserto equivalente es
el mismo que para perm(v, V), pero haciendo que las variables ligadas vayan entre a y b, en lugar
de entre 1 y N.
El planteamiento recursivo consiste en colocar en su sitio el primer elemento del subvector
v[a..b], dejando a su izquierdo los elementos menores o iguales y a su derecha los mayores o iguales, y ordenar recursivamente los dos fragmentos.
Anlisis de casos
a>b
Tenemos el caso directo cuando se trata de un segmento vaco.
Algoritmos recursivos
192
P0a>ba=b+1
Q0a=b+1v=V
ab
Se trata de un segmento no vaco y tenemos entonces el caso recursivo. Las ideas en las
que se basa el caso recursivo son:
considerar x = v(a) como elemento pivote
ordenar recursivamente v[a..(p1)] y v[(p+1)..b]. Este algoritmo ejemplifica la estrategia general de resolucin de problemas divide y vencers, puesto que resuelve el problema dividindolo en dos partes ms pequeas.
Por ejemplo:
a
Particin
a
>= 3,5
Funcin de acotacin
t(v,a,b)=ba+1
Hay que sumarle 1 porque tambin consideramos caso recursivo cuando a = b, y as evitamos que la funcin de acotacin se haga 0.
Suponiendo que tenemos una implementacin correcta de particin, sobre la que luego iremos,
el algoritmo anotado nos queda:
procquickSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P0:v=Va=Ab=B1ab+1N+1}
var
p:Ent;
inicio
si
a>boseguir{Q0}
abo{P0ab}
particion(v,a,b,p);
{Q}
Algoritmos recursivos
193
{QP0[b/p1]P0[a/p+1]
quickSort(v,a,p1);
quickSort(v,p+1,b);
{QQ0[b/p1]Q0[a/p+1]}
{Q0}
fsi
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc
Diseo de particin
La idea es obtener un bucle que mantenga invariante la siguiente situacin, de forma que i y j
se vayan acercando hasta cruzarse, y finalmente intercambiemos v(a) y v(j)
a
x
<= x
>= x
Invariante
I:a+1ij+1b+1v[(a+1)..(i1)]v(a)v[(j+1)..b]
perm(v,V,a,b)
(i:1i<a:v(i)=V(i))(i:b<iN:v(i)=V(i))
aunque lo obviamos porque slo vamos a modificar el vector con intercambios dentro del
intervalo [a..b].
Condicin de repeticin
B:i=j+1
B:ij
%cuandoi=j+1hemosparticionadotodoelvector
Algoritmos recursivos
194
Expresin de acotacin
C:ji+1
Accin de inicializacin
<i,j>:=<a+1,b>
Esta accin hace trivialmente cierto el invariante al hacer que los dos segmentos sean vacos.
Accin de avance
Habr que hacer un anlisis de casos comparando las componentes v(i) y v(j) con v(a)
v(i)v(a)
incrementamosi
v(a)v(j)
decrementamosj
v(i)>v(a)ANDv(j)<v(a) intercambiamosiconjyavanzamosambas
Las dos primeras condiciones son no excluyentes y las tres condiciones juntas garantizan
que al menos una de ellas se cumple.
Con todo esto el algoritmo queda:
procparticion(esv:Vector[1..N]deElem;ea,b:Ent;sp:Ent)
{P}
var
i,j:Ent;
inicio
<i,j>:=<a+1,b>;
{I;C}
itij
osi
v(i)v(a)oi:=i+1
v(j)v(a)oj:=j1
v(i)>v(a)ANDv(j)<v(a)ointercambiar(v,i,j);
<i,j>:=<i+1,j1>
fsi
fit
{Ii=j+1}
p:=j;
intercambiar(v,a,p)
Algoritmos recursivos
195
{Q}
fproc
procmergeSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P0:v=Va=Ab=B1ab+1N+1}
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc
Anlisis de casos
ab
Segmento de longitud 1.
Ya est ordenado
P0aba=ba=b+1
Q0(a=ba=b+1)v=V
a<b
Segmento de longitud 2.
La idea del caso recursivo:
Dividir v[a..b] en dos mitades. Al ser la longitud 2 es posible hacer la divisin (por
eso hemos considerado como caso base el subvector de longitud 1). Cada una de las
mitades tendr una longitud estrictamente menor que el segmento original.
Usamos un procedimientos auxiliar para mezclar las dos mitades, quedando ordenado todo v[a..b]
Ejemplo:
Algoritmos recursivos
196
m m+1
m m+1
Mezcla
a
Funcin de acotacin
t(v,a,b)=ba+1
Suponiendo que tenemos una implementacin correcta para mezcla, el procedimiento de ordenacin queda:
procmergeSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P0:v=Va=Ab=B1ab+1N+1}
var
m:Ent;
inicio
si
aboseguir{Q0}
a<bo{P0a<b}
m:=(a+b)div2;
{P0am<b}
{P0[b/m]P0[a/m+1]}
mergeSort(v,a,m);
mergeSort(v,m+1,b);
{am<bQ0[b/m]Q0[a/m+1]}
mezcla(v,a,m,b)
{Q0}
fsi
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc
Nos resta obtener el procedimiento que se encarga de mezclar los dos subvectores ordenados
adyacentes.
Algoritmos recursivos
197
Diseo de mezcla
El problema es que para conseguir una solucin eficiente, O(n), necesitamos utilizar un vector
auxiliar donde iremos realizando la mezcla, para luego copiar el resultado al vector original.
La idea del algoritmo es colocarse al principio de cada segmento e ir tomando de uno u otro el
menor elemento, y as ir avanzando. Uno de los subvectores se acabar primero y habr entonces
que copiar el resto del otro subvector. Finalmente copiaremos el resultado al vector original.
procmezcla(esv:Vector[1..N]deElem;ea,m,c:Ent);
{P0:v=Va=Am=Mc=Cam<bord(v,a,m)ord(v,
m+1,b)}
var
u:Vector[1..N]deElem;
i,j,k:Ent;
inicio
u:=v;
<i,j,k>:=<a,m+1,a>;
itimANDjb
osi
u(i)u(j)o
v(k):=v(i);
i:=i+1;
u(i)>u(j)o
v(k):=u(j);
j:=j+1;
fsi;
k:=k+1;
itim
ov(k):=u(i);
<i,k>:=<i+1,k+1>
fit;
itjb
ov(k):=u(j);
<j,k>:=<j+1,k+1>
fit
{Q0:a=Ab=Bm=Mperm(V,v,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(i:b<iN:v(i)=V(i)}
Algoritmos recursivos
198
1 de la asignacin de n*fact(n1)
1 de evaluar la descomposicin n1
T(n1) de la llamada recursiva (el coste de la funcin con datos de tamao n1).
si n = 1
4 + T(n1)
si n > 1
T(n) =
Mtodo del campesino egipcio
1 de la asignacin
T(n/2) de la llamada recursiva (el coste de la funcin con datos de tamao b div 2).
si n = 0, 1
7 + T(n/2)
si n > 1
T(n) =
Algoritmos recursivos
199
si n = 0
1 del movimiento
si n = 0
3 + 2*T(n1)
si n > 0
T(n) =
Las recurrencias no nos dan informacin sobre el orden de complejidad, necesitamos una
ecuacin explcita.
Hay dos mtodos para resolver las recurrencias:
despliegue de recurrencias
1.4.1
aplicar soluciones generales que se conocen para algunos tipos comunes de recurrencias
Despliegue de recurrencias
Despliegue. Sustituimos las apariciones de T en la recurrencia tantas veces como sea necesario hasta encontrar una frmula en la que el nmero de despliegues dependa de un
parmetro k.
Postulado. A partir de la frmula paramtrica resultado del paso anterior obtenemos una
frmula explcita. Para ello, obtenemos el valor de k que nos permite alcanzar un caso directo y sustituimos la referencia recursiva por la complejidad del caso directo. (Aqu estamos haciendo la suposicin de que la recurrencia para el caso recursivo es tambin vlida
para el caso directo.)
Demostracin. Se demuestra por induccin que la frmula obtenida cumple las ecuaciones de recurrencia. (Este paso lo vamos a obviar)
Apliquemos el mtodo a los dos ejemplos que hemos presentado anteriormente: el factorial y
la multiplicacin por el mtodo del campesino egipcio:
Algoritmos recursivos
200
Factorial
3
si n = 1
T(n) =
4 + T(n1)
si n > 1
Despliegue:
T(n)=4+T(n1)
=4+4+T(n2)
=4+4+4+T(n3)
=4k+T(nk)
Postulado
por lo tanto
T(n)O(n)
Campesino egipcio
5
si n = 0, 1
T(n) =
7 + T(n/2)
si n > 1
Despliegue:
T(n)=7+T(n/2)
=7+7+T(n/2/2)
=7+7+7+T(n/2/2/2)
=7k+T(n/2k)
Postulado
El caso directo se tiene para n = 0,1; para alcanzar n=1 tenemos que hacer
k=logn
%comosiemprelogenbase2
T(n)=7logn+T(n/2logn)
=7logn+T(1)
Algoritmos recursivos
201
=7logn+5
por lo tanto
T(n)O(logn)
(Ntese que la igualdad k=logn slo tiene sentido cuando n es una potencia de 2 y
por lo tanto tiene un logaritmo exacto, ya que el nmero de pasos k tiene que ser un
nmero entero. Por lo tanto el anterior resultado slo sera vlido para ciertos valores de
n, sin embargo esto no es una limitacin grave, porque la complejidad T(n) del algoritmo
es una funcin montona creciente de n, y por lo tanto nos basta con estudiar su comportamiento slo en algunos puntos).
Ntese que la funcin de complejidad que hemos obtenido no es vlida para n = 0, ya
que no est definido el logaritmo de 0; sin embargo, esto no es importante ya que nos interesa el comportamiento asinttico de la funcin.
Torres de Hanoi
2
si n = 0
T(n) =
3 + 2*T(n1) si n > 0
Despliegue:
T(n)=3+2*T(n1)
=3+2*(3+2*T(n2))
=3+2*(3+2*(3+2*T(n3)))
=3+2*3+22*3+23*T(n3)
k 1
=3
2i+2kT(nk)
i 0
Postulado
El caso directo se tiene para n = 0; para alcanzarlo
k=n
k 1
T(n)=3
2i+2kT(nk)
i 0
n 1
=3
2i+2nT(nn)
i 0
n 1
(*)=3
2i+2nT(0)
i 0
=3(2n1)+22n
=52n3
Algoritmos recursivos
202
ri=
i 0
rn 1
r 1
rz1
Por lo tanto:
T(n)O(2n)
Es un algoritmo de coste exponencial; es habitual que las funciones recursivas mltiples tengan costes prohibitivos.
1.4.2
Se pueden obtener unos resultados tericos aplicables a un gran nmero de recurrencias, aquellas en las que la descomposicin recursiva se obtiene por sustraccin y aquellas otras en las que
se obtiene por divisin.
Disminucin del tamao del problema por sustraccin
Si la descomposicin recursiva se obtiene restando una cierta cantidad constante, tenemos entonces que esto da lugar a recurrencias de la forma:
(D) T(n) = G(n)
si 0 n < b
si n b
donde:
T(n)=amT(nmb)+
i 0
aiC(nib)
Algoritmos recursivos
203
Como nos interesa el comportamiento asinttico podemos tomas las siguientes hiptesis simplificadoras:
n=mbm=n/b
esdecir,consideramosslolosnque
sonmltiplosdeb
delosposiblescasosdirectos(0n<b)nos
T(0)=G(0)=c0
quedamosconuno(n=0)
T(n)=amc0+
aiC(mbib)
i 0
Donde damos nombre a cada uno de los sumandos para poder tratarlos por separado:
T1(n)=amc0
m 1
T2(n)=
aiC(b(mi))
i 0
Estudiamos ahora un caso particular frecuente en la prctica: cuando el coste de preparar las
llamadas y combinar los resultados es de la forma:
C(n)=cnk
paraciertascR+,kN
Y ahora estudiamos dos posibilidades dependiendo del valor de a el nmero de llamadas recursivas:
a=1
T1(n)=c0
m 1
T2(n)=
c(b(mi))k
i 0
m 1
=cbk
(mi)k=cbk
i 0
m k 1k
m
2
cbkmk+1
=cbk(n/b)k+1
=c/bnk+1
por lo tanto
T2(n)O(nk+1)
Algoritmos recursivos
204
T(n)=c0+T2(n)O(nk+1)
Cuando se aplica una nica llamada recursiva (a = 1) obtenemos un orden de complejidad polinmico en n, cuyo exponente es uno ms que el del coste de C(n)
a>1
T1(n)=amc0
m 1
T2(n)=
aic(b(mi))k
i 0
m 1
=
ai
i 0
am
c(b(mi))k
am
%multiplicandoarribay
abajoporam
m 1
=amcbk
i 0
m 1
=amcbk
i 0
a i ( m i )k
am
( m i )k
a m i
y ahora hacemos un cambio de ndices para as darnos cuenta de que esta serie converge.
j=mi
i=0j=m
i=m1j=1
entonces
T2(n)=amcbk
j 1
jk
aj
y se cumple
m
T2(n)=amcbk
j 1
jk
<amcbks
aj
s=
j 1
jk
aj
Algoritmos recursivos
205
Es decir, cuando tenemos varias llamadas recursivas (a>1) y el tamao del problema disminuye por sustraccin, la complejidad resultante es exponencial. Por lo tanto, la recursin mltiple
con divisin del problema por sustraccin conduce a algoritmos muy ineficientes; como ocurre
por ejemplo con el algoritmo para las torres de Hanoi donde
b=1
a=2
T(n)O(2n)
si 0 n < b
si n b
donde:
aiC(n/bi)
i 0
Como nos interesa el comportamiento asinttico podemos tomas las siguientes hiptesis simplificadoras:
n=bmm=logbn
esdecir,consideramosslolosnqueson
potenciasdeb
T(1)=G(1)=c1
delosposiblescasosdirectos(0n<b)nos
quedamosconuno(n=1)
Algoritmos recursivos
206
m 1
T(n)=amc1+
m 1
aiC(n/bi)=amc1+
i 0
aiC(bm/bi)
i 0
m 1
T(n)=amc1+
aiC(bmi)
i 0
Donde damos nombre a cada uno de los sumandos para poder tratarlos por separado:
T1(n)=amc1
m 1
T2(n)=
aiC(bmi)
i 0
log b n
c1=c1 n
log b a
log b n
= b
(log b n )(log b a )
= n
log b a
paraciertascR+,kN
T2(n)=
aiC(bmi)
i 0
m 1
=
aicb(mi)k
i 0
m 1
=c
ai
i 0
b mk
b(mi)k %multiplicandoarribayabajoporbm
b mk
m 1
=cbmk
aibik
m 1
a
k
b
i 0
=cb
mk
i 0
log b a
m 1
+cb
mk
i 0
a
k
b
Algoritmos recursivos
207
Estudiamos el valor del sumatorio segn la relacin que exista entre a y bk.
a = bk
Tenemos entonces
T1(n)=c1nk
m 1
T2(n)=cbmk
1i
i 0
=cbmkm
=cnklogbn
%yaquem=logbn
De esta forma
T(n)=c1nk+cnklogbnO(nklogbn)=O(nklogn)
Vemos que T2(n) domina a T1(n). Observamos asimismo que la complejidad depende de nk
que es un trmino que proviene de C(n), por lo tanto la complejidad de un algoritmo de este tipo
se puede mejorar disminuyendo la complejidad de C(n) = c nk.
a < bk
Tenemos entonces que
logba<k
y, por lo tanto
T1(n)=c1 n
log b a
<c1nk
m 1
mk
T2(n)=cb
i 0
a
k
b
si a < bk entonces am < bmk por lo tanto el trmino dominante en la anterior expresin es
bmk y podemos afirmar (ntese que la anterior expresin tiene signo positivo):
T2(n)O(bmk)=O( b
(log b n ) k
)=O(nk)
Algoritmos recursivos
208
a > bk
Tenemos entonces que
logba>k
y, por lo tanto
T1(n)=c1 n
log b a
m 1
mk
T2(n)=cb
i 0
a
k
b
si a > bk entonces am > bmk por lo tanto el trmino dominante en la anterior expresin es
am y podemos afirmar (ntese que la anterior expresin tiene signo positivo):
T2(n)O(am)=O( a
log b n
)=O( n
log b a
)
log b a
)
Observamos aqu que T1(n) y T2(n) tienen el mismo orden de complejidad, que slo depende
de a y b. Por lo tanto, en este caso no se consigue mejorar la eficiencia global mejorando la eficiencia de C(n). Las mejoras se obtendrn disminuyendo a el nmero de llamadas recursivas o
aumentando b obtenindose as subproblemas de menor tamao.
Divisin del problema por sustraccin
c0
si 0 n < b
Algoritmos recursivos
209
T(n) =
a T(nb) + c nk
si n b
O(nk+1)
si a = 1
O(an div b)
si a > 1
T(n)
Cuando se aplica una nica llamada recursiva (a = 1) obtenemos un orden de complejidad polinmico en n, cuyo exponente es uno ms que el del coste de C(n).
Cuando tenemos varias llamadas recursivas (a>1) y el tamao del problema disminuye por
sustraccin, la complejidad resultante es exponencial. Por lo tanto, la recursin mltiple con divisin del problema por sustraccin conduce a algoritmos muy ineficientes; como ocurre por
ejemplo con el algoritmo para las torres de Hanoi donde
a=2
b=1
T(n)O(2n)
si 0 n < b
a T(n/b) + c nk
si n b
O(nk)
si a < bk
O(nk log n)
si a = bk
T(n) =
T(n)
O( n logb a )
si a > bk
Si a < bk o a = bk la complejidad depende de nk que es un trmino que proviene de C(n), por
lo tanto la complejidad de un algoritmo de este tipo se puede mejorar disminuyendo la complejidad de la preparacin de las llamadas y la combinacin de los resultados: C(n) = c nk.
Si a > bk no se consigue mejorar la eficiencia global mejorando la eficiencia de C(n). Las mejoras se obtendrn disminuyendo a el nmero de llamadas recursivas o aumentando b
obtenindose as subproblemas de menor tamao.
1.4.3
si 1 n 0
Algoritmos recursivos
210
2 T(n/2) + c n
si n 1
donde hemos considerado que el tamao de los datos n es la longitud del subvector a ordenar;
en el caso directo tenemos 2 pasos para evaluar las dos barreras. En cada llamada recursiva el
problema se divide aproximadamente por 2. Ntese que la ecuacin de recurrencias no se ajusta
exactamente al esquema terico (b=2 pero la expresin recurrente se aplica para n 1 y no para
n2); como las diferencias afectan a valores pequeos de n no afectan a su estudio asinttico.
En realidad no tenemos que preocuparnos por el coste en el caso directo, siempre y cuando
sea un coste constante. Tampoco nos preocupamos por las constantes aditivas que aparezcan en
el caso recursivo.
El coste de la combinacin (c nk) sale lineal porque ese es el coste del procedimientos mezcla.
Con todo esto tenemos
a = 2, b = 2, k = 1
estamos por tanto en el caso
a = bk
y entonces la complejidad es:
O(n log n)
Nmeros de Fibonacci
T(0) = c0
T(1) = c1
T(n) = T(n1) + T(n2) + c
si n 2
Esta recurrencia no se ajusta a los esquemas que hemos estudiado, y por ello hacemos una
simplificacin:
T(n) 2 T(n1) + c
Que se corresponde al esquema de divisin del problema por sustraccin:
a = 2, b = 1, k = 0
Como se tiene a > 1 la complejidad es
Algoritmos recursivos
211
n>0
O(nk+1) = O(n)
Esto implica una enorme mejora con respecto a la versin anterior del algoritmo.
Suma recursiva de un vector de enteros
funcsumaVec(v:Vector[1..N]deEnt;a,b:Ent)devs:Ent;
Recurrencia:
T(1) = c1
T(n) = T(n1) + c
si n > 1
O(nk+1) = O(n)
Bsqueda dicotmica
Tomamos como tamao de los datos:
Algoritmos recursivos
212
n=ba+1
Recurrencias
T(0) = c0
T(1) = c1
T(n) = T(n/2) + c
si n > 1
Suponiendo n = 2m
si n 1
El procedimiento particin, que prepara las llamadas recursivas, tiene coste lineal, de ah el
trmino cn. El trmino T(0) que aparece en la recurrencia es una constante que ignoramos en el
anlisis.
Estamos entonces en el caso de divisin por sustraccin
a=b=k=1
O(nk+1) = O(n2)
Algoritmos recursivos
213
T(0) = c0
T(n) = 2T(n/2) + c n
si n 1
con
a=b=2
k=1
funcnombreFunc(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
&
{P( x )}
var
&
&
x : W ;
inicio
si
&
&
&
&
& &
{Q( x , y )}
&
&
&
d( x )o{P( x )d( x )}
&
&
x :=s( x );
&
{P( x )}
&
y :=g( x )
Algoritmos recursivos
214
&
&
&
&
& &
{Q( x , y )}
{Q( x , y )}
fsi
& &
&
dev y
{Q( x , y )}
ffunc
&
&
x o nombreFunc( x )
p
&
nombreFunc(s( x ))
p
&
nombreFunc(s2( x ))
p
p
&
&
&
nombreFunc(sn( x )) o g(sn( x )) o y
&
De hecho, habra otro bucle ascendente que ira devolviendo el valor de y ; sin embargo,
&
como en ese proceso no se modifica y recursin final podemos ignorarlo.
Formalmente, el bucle descendente da lugar a una definicin iterativa equivalente de nombreFunc de la siguiente forma:
funcnombreFuncItr(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
&
{P( x )}
var
&
&
x : W ;
inicio
&
&
x := x ;
&
&
&
&
C:t( x )}
&
itd( x )
Algoritmos recursivos
215
&
&
o x :=s( x )
fit;
&
{Id( x )}
&
&
&
&
y :=g( x )
&
&
{ y =nombreFunc( x )}
&
&
{Q( x , y )}
&
dev y
ffunc
Este paso se puede realizar de forma mecnica. Adems la verificacin del algoritmo iterativo
se deduce de las condiciones conocidas para el algoritmo recursivo:
La precondicin y la postcondicin coinciden
&
&
&
El invariante I es P( x ) nombreFunc( x ) = nombreFunc( x ). Justo antes de cada iteracin es como si estuvisemos delante de una llamada recursiva, donde se cumple la pre&
&
condicin sustituyendo x por s( x ). Adems por ser recursiva final, se tiene la segunda
parte del invariante: la funcin aplicada sobre cualquiera de los valores intermedios coincide con el resultado para el valor original.
La expresin de acotacin viene dada por la funcin de acotacin del algoritmo recursivo.
La correccin del algoritmo recursivo garantiza la del algoritmo iterativo obtenido de esta manera.
1.5.1
Ejemplos
funcacuFact(a,n:Nat)devm:Nat;
{P0:a=An=N}
var
a,n:Nat;
inicio
si
n=0om:=a
n>0o<a,n>:=<a*n,n1>;
m:=acuFact(a,n)
fsi
{Q0:a=An=Nm=a*n!}
devm
ffunc
Algoritmos recursivos
216
En esta versin hemos separado el clculo de la descomposicin recursiva de los datos para
que as se ajuste exactamente al esquema terico presentado.
Y la versin iterativa:
funcacuFact(a,n:Nat)devm:Nat;
{P0:a=An=N}
var
a,n:Nat;
inicio
<a,n>:=<a,n>;
itn>0
o<a,n>:=<a*n,n1>;
fit;
m:=a;
{Q0:a=An=Nm=a*n!}
devm
ffunc
En realidad no haran falta las variables locales, pero si no las utilizamos no se cumplir la parte de la especificacin que se refiere a la conservacin de los valores de los parmetros de entrada.
Considerando que
fact(n) = acuFact(1, n)
podemos convertir la versin iterativa en una funcin que calcule el factorial, eliminando el
parmetro a e inicializando a con el valor 1.
Bsqueda binaria
Partimos de la solucin que obtuvimos en el tema de derivacin de algoritmos recursivos:
funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P0:1abNord(v,a,b)}
var
m:Ent;
inicio
si
a=b+1op:=a1
a=bosi
v(a)xop:=a
v(a)>xop:=a1
fsi
Algoritmos recursivos
217
a<bom:=(a+b)div2;
si
v(m)xop:=buscaBin(v,x,m+1,b)
v(m)>xop:=buscaBin(v,x,a,m1)
fsi
fsi
{Q0:a1pbv[a..p]x<v[(p+1)..b]}
devp
ffunc
Esta funcin no se ajusta exactamente al esquema terico porque no aparecen las variables
auxiliares que recogen la descomposicin recursiva; las introduciremos en la versin iterativa.
Adems tenemos ms de un caso directo e implcitamente ms de un caso recursivo, lo que se
traduce a composiciones alternativas en la versin iterativa.
La solucin iterativa
funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P0:1abNord(v,a,b)}
var
m,a,b:Ent;
inicio
<a,b>:=<a,b>;
ita<b
om:=(a+b)div2;
si
v(m)xo<a,b>:=<m+1,b>;
v(m)>xo<a,b>:=<a,m1>;
fsi
fit;
si
a=b+1op:=a1
a=bosi
v(a)xop:=a
v(a)>xop:=a1
fsi
fsi
{Q0:a1pbv[a..p]x<v[(p+1)..b]}
devp
ffunc
Algoritmos recursivos
218
Plantear el diseo de un algoritmo recursivo a partir de su especificacin, es decir, tcnicas que ayudan a obtener planteamientos recursivos de los problemas.
En ambos casos nos preocuparemos especialmente por obtener algoritmos recursivos finales
debido a la correspondencia directa que existe entre este tipo de algoritmos recursivos y algoritmos iterativos equivalentes, ms eficientes.
Nos limitaremos al estudio de funciones aunque estas tcnicas se pueden adaptar fcilmente al
diseo de procedimientos.
1.6.1
Generalizaciones
Todas las tcnicas que vamos a estudiar se basan en la idea de generalizacin (en los libros
recomendados a las generalizaciones se las denomina inmersiones).
Decimos que una funcin F es una generalizacin de otra funcin f cuando:
F tiene ms parmetros de entrada y/o devuelve ms resultados que f.
Particularizando los parmetros de entrada adicionales de F a valores adecuados y/o ignorando los resultados adicionales de F se obtiene el comportamiento de f.
En esta primera parte del tema vamos a considerar generalizaciones que slo introducen
parmetros adicionales y no resultados adicionales.
Al estudiar funciones recursivas ya nos hemos encontrado con varios ejemplos de generalizaciones, por ejemplo, acuFact es una generalizacin de fact de forma que
Algoritmos recursivos
219
acuFact(1, n) = fact(n)
En este ejemplo la generalizacin es interesante porque conduce fcilmente a una funcin recursiva final, lo cual no ocurre si partimos de la especificacin de fact.
Otro ejemplo tpico son las funciones recursivas sobre vectores, donde hemos obtenido generalizaciones aadiendo parmetros que indiquen el subvector a considerar en cada llamada recursiva. Por ejemplo, la funcin de bsqueda binaria implementada recursivamente es una
generalizacin de la misma funcin implementada de forma iterativa:
buscaBinRec(v, x, 1, N) = buscaBin(v, x)
Aunque nosotros a la hora de implementarlas le hemos dado el mismo nombre a las dos funciones, en realidad se trata de funciones distintas pues tienen parmetros distintos.
Estos dos ejemplos ponen de manifiesto las dos razones para obtener generalizaciones que,
como ya indicamos antes, son obtener soluciones recursivas ms eficientes o posibilitar los planteamientos recursivos.
Vamos a caracterizar formalmente en trminos de su especificacin qu relacin debe existir
entre una funcin y su generalizacin.
Dadas dos especificaciones
& &
& &
(Ef) func f( x ; W ) dev y : V ;
&
{ P( x ) }
& &
{ Q( x , y ) }
ffunc
& &
(EF) func F( a : W ;
& &
{ P( a , x ) }
& & &
{ Q( a , x , b ,
ffunc
Decimos que EF es una generalizacin de Ef si se cumplen
(G1)
(G2)
&
&
&
& &
P( x ) a =ini( x ) P( a , x )
Si estamos en un estado donde es posible invocar a f entonces tambin es posible
invocar a F, asignando a los parmetros adicionales los valores que indica una
cierta funcin ini.
& & & &
&
&
& &
Q( a , x , b , y ) a = ini( x ) Q( x , y )
Algoritmos recursivos
220
&
&
Donde ini es una funcin que asocia a cada valor de los parmetros x el valor ini( x ) de los
& &
&
parmetros adicionales, adecuado para que F(ini( x ), x ) emule el comportamiento de f( x )
Hemos introducido aqu una nueva notacin para escribir la cabecera de las funciones, deno&
&
tando con x a una lista de variables y con W a una lista de tipos.
Veamos como ejemplo que acuFact es una generalizacin de fact segn la definicin que acabamos de dar:
funcfact(n:Ent)devr:Ent;
{P(n):n0}
{Q(n,r):r=n!}
ffunc
funcacuFact(a,n:Ent)devr:Ent;
{P(a,n):n0}
{Q(a,n,r):r=a*n!}
ffunc
En este tema vamos a obviarlas condiciones relativas a la conservacin de los valores de los
parmetros, para simplificar un poco el tratamiento.
Al aplicar la tcnica de las generalizaciones, partiremos de la especificacin Ef e intentaremos
obtener la especificacin EF de una generalizacin adecuada. Este proceso es esencialmente creativo; vamos a estudiar a continuacin la aplicacin de esta tcnica a algunos casos particulares y
presentaremos as heursticas que pueden ayudar a descubrir las generalizaciones interesantes.
1.6.2
Vamos a estudiar dos tcnicas, ejemplificndolas, una ms simple que permite obtener planteamientos recursivos no finales y otra que persigue la obtencin de planteamientos recursivos
que admiten recursin final.
Algoritmos recursivos
221
& &
&
& &
P( a , x ) P( x ) D( a , x )
Veamos cmo se aplica esta tcnica en un ejemplo que calcula el producto escalar de dos vectores:
funcprodEsc(u,v:Vector[1..N]deEnt)devp:Ent;
{P(u,v):cierto}
{Q(u,v,p):p=i:1iN:u(i)*v(i)}
ffunc
Algoritmos recursivos
222
funcprodEscGen(a:Ent;u,v:Vector[1..N]deEnt)devp:Ent;
{P(a,u,v):0aN}
{Q(a,u,v,p):p=i:1ia:u(i)*v(i)}
ffunc
de forma que
prodEscGen(N,u,v)=prodEsc(u,v)
Algoritmos recursivos
223
(FG1) { (G1)
&
& &
&
& & &
P( x ) < a , e > = ini( x ) P( a , e , x )
Ya sabemos que las funciones recursivas finales se puede traducir de manera inmediata a funciones iterativas; es por ello que las condiciones que aparecen en la caracterizacin de una generalizacin recursiva final se corresponden de manera directa con las de los bucles, como ya
pudimos darnos cuenta cuando presentamos el esquema de traduccin de una funcin recursiva
final a otra iterativa:
invariante
condicin de repeticin
postcondicin
donde tenemos tambin que el requisito FG2 es equivalente a una de las condiciones que han
de verificar los bucles:
I B Q
P d Q
Para obtener este tipo de generalizaciones utilizamos las mismas heursticas que hemos estudiado para la obtencin de invariantes: tomar una parte de la postcondicin o generalizar la postcondicin sustituyendo constantes por variables. La condicin que obtengamos as deberemos
incluirla en la precondicin de la generalizacin.
Como ejemplo vamos a obtener otra generalizacin de la funcin que calcula el producto escalar. Intuitivamente, vemos que tenemos que utilizar la idea de la generalizacin anterior para
poder llegar a un planteamiento recursivo y adems tenemos que introducir otro parmetro que
Algoritmos recursivos
224
lleve el resultado hasta ese momento. Con esta idea planteamos la siguiente precondicin para la
generalizacin (este paso es el equivalente a obtener el invariante a partir de la postcondicin, con
la diferencia de que al obtener el invariante no necesitamos una variable nueva para acumular el
resultado y aqu s):
P(a,e,u,v):a=i:1ie:u(i)*v(i)0eN
El siguiente paso es encontrar la condicin del caso directo de forma que se garantice la condicin (FG2), es decir, para conseguir que en a est el resultado final deseado. Es equivalente a
obtener la condicin de terminacin:
d(a,e,u,v)e=N
(Ntese que en Q aparece a como resultado; es decir, en el caso base simplemente se asigna a p
el valor de a)
Por ltimo, nos resta encontrar la funcin ini(u, v) que asigne valores a (a, e) de forma que se
cumpla (FG1):
P(u,v)<a,e>=ini(u,v)P(a,e,u,v)
Ntese que al final de todas las llamadas recursivas el valor de p es el resultado final, por eso la
postcondicin es exactamente la misma que la de la funcin factorial sin ningn tipo de generalizacin.
Tenemos entonces que
Algoritmos recursivos
225
prodEsc(u, v) = prodEscGenFin( 0, 0, u, v )
De la misma se puede seguir un proceso similar para obtener una generalizacin sustituyendo
por una nueva variable la constante 1 en lugar de N.
El uso de acumuladores es una tcnica que tiene especial relevancia en programacin funcional donde, en principio, slo se dispone de recursin. Esta es la forma de conseguir funciones
recursivas eficientes, ocurriendo incluso que el compilador sea capaz de hacer la traduccin automtica de recursiva final a iterativa. En programacin imperativa tiene menos sentido pues en
muchos casos resulta ms natural obtener directamente la solucin iterativa.
1.6.3
En ocasiones se puede mejorar la eficiencia de un algoritmo recursivo evitando el clculo repetido de una cierta expresin en todas las llamadas recursivas, o simplificando algn clculo sacando provecho del resultado obtenido para ese clculo en otra llamada recursiva. En estos casos
el uso de generalizaciones puede mejorar la eficiencia, introduciendo parmetros adicionales que
transmitan los resultados a llamadas recursivas posteriores, o con resultados adicionales que se
utilicen en llamadas recursivas anteriores. En esencia, es la misma idea que se utiliza en la introduccin de invariantes auxiliares cuando se disean algoritmos iterativos.
Vamos a distinguir dos casos, segn que la generalizacin consista en aadir parmetros o en
aadir resultados. Tambin hay ocasiones en que interesa utilizar simultneamente los dos tipos
de generalizaciones. En ambos casos vamos a suponer que partimos de funciones ya diseadas y
describiremos las soluciones generalizadas en trminos de los cambios que sera necesario introducir en las funciones originales.
Generalizacin con parmetros acumuladores
Cuando se aplica esta tcnica, la funcin ms general F posee parmetros de entrada adiciona&
&
les a , cuya funcin es transmitir el valor de una cierta expresin e( x ) que depende de los parmetros de entrada de la funcin original f, a fin de evitar su clculo. Por lo tanto la precondicin
de F deber plantearse como un fortalecimiento de la precondicin de f, y la postcondicin podr
mantenerse tal cual:
& &
&
&
&
P( a , x ) P( x ) a =e( x )
& & &
& &
Q( a , x , y ) Q( x , y )
Suponiendo disponible un algoritmo recursivo para f (que queremos optimizar), podremos disear un algoritmo ms eficiente para F del siguiente modo:
&
&
Se reutiliza el texto de f, reemplazando e( x ) por a
& &
&
Diseamos una nueva funcin sucesor s( a , x ), a partir de la orginal s( x ), de modo que
se cumpla:
&
&
&
{ a =e( x )d( x )}
&
&
& &
< a , x >:=s( a , x )
&
&
&
&
{ x =s( x ) a =e( x )}
Algoritmos recursivos
226
&
La tcnica resultar rentable cuando en el clculo de a nos podamos aprovechar de los valo& &
res de a y x para realizar un clculo ms eficiente.
Por ejemplo una funcin que calcula cuntas componentes de un vector son iguales a la suma
de las componentes que las preceden:
funcnumCortesGen(e:Ent;v:Vector[1..N]deEnt)devr:Ent;
{P(e,v):0eN}
var
s,h:Ent;
inicio
si
e=Nor:=0
e<Nos:=0;
parahdesde1hastaehacer
s:=s+v(h);
fpara;
sis=v(e+1)
entoncesr:=1+numCortesGen(e+1,v)
sinor:=numCortesGen(e+1,v)
fsi
fsi
{Q(e,v,r):r=#i:e+1iN:v(i)=j:1je:v(j)}
devr
ffunc
funcnumCortesGenEfi(e,s:Ent;v:Vector[1..N]deEnt)devr:Ent;
{P(s,e,v):0eNs=i:1ie:v(i)}
inicio
si
e=Nor:=0
Algoritmos recursivos
227
e<Nosis=v(e+1)
entoncesr:=1+numCortesGenEfi(e+1,s+v(e+1),v)
sinor:=numCortesGenEfi(e+1,s+v(e+1),v)
fsi
fsi
{Q(e,v,r):r=#i:e+1iN:v(i)=j:1je:v(j)}
devr
ffunc
(G1)
(G2)
&
&
&
& &
P( x ) a =ini( x ) P( a , x )
Si estamos en un estado donde es posible invocar a f entonces tambin es posible
invocar a F, asignando a los parmetros adicionales los valores que indica una
cierta funcin ini.
& & & &
&
&
& &
Q( a , x , b , y ) a = ini( x ) Q( x , y )
De la postcondicin de F, restringiendo los parmetros adicionales a los valores
&
dados por ini( x ), es posible obtener la postcondicin de f.
&
&
Donde ini es una funcin que asocia a cada valor de los parmetros x el valor ini( x ) de los
& &
&
parmetros adicionales, adecuado para que F(ini( x ), x ) emule el comportamiento de f( x )
&
En una generalizacin F con resultados acumuladores b , estos parmetros de salida adiciona&
&
les tendrn como misin transmitir ciertos valores e( y ) dependientes de los resultados y de f.
Algoritmos recursivos
228
&
&
P( x )P( x )
&
&
&
&
&
&
& &
&
& &
Reutilizar el texto de f, reemplazando e( y , x ) por b (el valor de los resultados
acumuladores en la llamada recursiva)
&
&
& &
Aadir el clculo de b , de manera que la parte b =e( y , x ) de la postcondicin
& & &
Q( x , b , y ) quede garantizada, tanto en los casos directos como en los recursivos
funcsumaCuad(n:Nat)devs:Nat;
{P(n):cierto}
inicio
si
n=0os:=0
n>0os:=n*n+sumaCuad(n1)
fsi
{Q(n,s):s=i:0in:i*i}
devs
ffunc
Algoritmos recursivos
229
donde pr3 es la funcin que proyecta una terna ordenada en su tercera componente.
Vamos ahora con el ejemplo de los nmeros de Fibonacci, la optimizacin consiste en devolver dos nmeros en lugar de uno:
funcfibo(n:Nat)devr:Nat;
{P(n):cierto}
inicio
si
n=0or:=0
n=1or:=1
n>1or:=fibo(n1)+fibo(n2)
fsi
{Q(n,r):r=fib(n)}
devr
ffunc
Algoritmos recursivos
230
y la generalizacin
funcfiboGen(n:Nat)devr,s:Nat;
{P(n):cierto}
var
r,s:Nat;
inicio
si
n=0o<r,s>:=<0,1>
n>0o<r,s>:=fiboGen(n1)
<r,s>:=<s,r+s>
fsi
{Q(n,r,s):r=fib(n)s=fib(n+1)}
dev<r,s>
ffunc
fibo(n)=pr1(fiboGen(n))
1.6.4
Tcnicas de plegado-desplegado
En un apartado anterior hemos visto cmo generalizar una especificacin Ef de manera que la
especificacin ms general EF haga posible un diseo recursivo final. Una idea alternativa es la de
transformacin de programas. Si suponemos ya diseado un algoritmo recursivo simple que
satisfaga la especificacin Ef, puede interesar transformarlo de manera que se obtenga un algoritmo recursivo final para una funcin ms general con especificacin EF. La versin recursiva final
ser, como ya sabemos, ms eficiente por poderse traducir a un bucle iterativo.
Las tcnicas de plegadodesplegado sirven para obtener el algoritmo recursivo final para F
mediante manipulaciones algebraicas del algoritmo recursivo simple conocido para f.
Lo interesante de esta tcnica es que es posible automatizar la transformacin pues los pasos
estn bien definidos a partir de la forma de la funcin recursiva simple original. El inconveniente
es que no es aplicable a cualquier funcin recursiva simple, sino que estas se deben ajustar a unos
determinados esquemas donde las funciones que intervienen cumplan unas ciertas propiedades
asociatividad, elemento neutro, .
Vamos a estudiar tres esquemas distintos junto con el procedimiento para construir la funcin
recursiva final a partir de una funcin recursiva simple que se ajuste al correspondiente esquema y
cumpla las propiedades indicadas.
Plegado y desplegado I
Supongamos dada una funcin recursiva simple f cuya implementacin se ajuste al siguiente
esquema:
&
&
&
&
funcf( x : W )dev y : V ;
&
{P( x )}
Algoritmos recursivos
inicio
si
231
&
&
&
&
&
&
&
&
&
{Q( x , y )}
&
dev y
ffunc
&
&
x : W :P( x )
(N) =
(EF) =
&
f(
& x ) &
0 f(
& & x )
F( 0 , x )
Para demostrar que F admite un algoritmo recursivo final daremos el procedimiento que lo
construye, a partir del algoritmo para f y la especificacin de F. El algoritmo sintetizado ser correcto por construccin.
Para la sntesis del algoritmo utilizamos el anlisis de casos del algoritmo de f. En lo que sigue
&
&
presuponemos que los valores de x cumplen P( x ):
&
Caso d( x )
EF,desplegadodeF
Df,desplegadodef
& &
F( a , x )
&
&
=
a f( x )
&
&
=
a g( x )
Algoritmos recursivos
232
&
Caso d( x )
EF,desplegadodeF
Rf,desplegadodef
(A) =
EF,plegadodeF =
& &
F( a , x )
&
&
=
a f( x )
&
&
&
=
a (h( x )f(s( x )))
&
&
&
( a h( x ))f(s( x ))
&
&
&
F( a h( x ),s( x ))
&
&
&
&
&
funcF( a : V ; x : W )dev y : V ;
&
{P( x )}
inicio
si
&
&
&
&
&
&
&
&
&
d( x )o y :=F( a h( x ),s( x ))
d( x )o y := a g( x )
fsi
&
&
&
&
dev y
ffunc
Ejemplos de funciones recursivas que se ajustan al esquema PDP I y que admiten esta transformacin:
La funcin acuFact del ejercicio 82, y que apareci al principio de este tema como ejemplo de
generalizacin, se obtiene aplicando PDP I a la funcin fact
h(n)=n =*
&
0 =1
La funcin prodEscGen que vimos al principio de este tema pg. 5 como ejemplo de generalizacin que hace posible la recursin con planteamiento recursivo no final se ajusta al esquema
PDP I con los siguiente valores para los parmetros del mtodo:
h(u,v,n)=u(n)*v(n) =+
&
0 =0
Algoritmos recursivos
233
inicio
si
a=0op:=b
a>0op:=prodEscF(b+u(a)*v(a),a1,u,v)
fsi
{Q(b,a,u,v,p):p=b+i:1ia:u(i)*v(i)}
devp
ffunc
En el ejemplo que sigue la aplicacin de la tcnica se complica un poco porque tenemos dos
&
casos recursivos y la funcin h( x ) se define por distincin de casos. El algoritmo es el mtodo de
multiplicacin del campesino egipcio que presentamos en el tema de verificacin de algoritmos
recursivos, aunque aqu se ha eliminado uno de los casos bases por ser redundante.
funcprod(x,y:Nat)devr:Nat;
{P0:cierto}
inicio
six=0 or:=0
(x>0ANDpar(x)) or:=prod(xdiv2,y+y)
(x>0ANDNOTpar(x))or:=y+prod(xdiv2,y+y)
fsi
{Q0:r=x*y}
devr
ffunc
&
0 =0
0 sipar(x)
h(x,y)=
y sipar(x)
Algoritmos recursivos
234
Plegado y desplegado II
Supongamos dada una funcin recursiva simple f cuya implementacin se ajuste al siguiente
esquema:
&
&
&
&
funcf( x : W )dev y : V ;
&
{P( x )}
inicio
si
&
&
&
&
&
&
&
&
&
&
{Q( x , y )}
&
dev y
ffunc
siendo , funciones
&
&
&
,: V x V o V
&
&
&
&
&
&
&
&
& &
&
&
& & & &
&
&
&
&
&
&
(A) u , v , w : V : u ( v w )=( u v ) w
&
& & &
&
&
&
&
&
&
(A) u , v , w : V : u ( v w )=( u v ) w
& & & &
&
&
&
&
&
&
&
(D) u , v , w : V : u ( v w )=( u v )( u w )
(N) u : V : 1 u = u ( 1 constante)
entonces la funcin F, especificada como sigue, es una generalizacin de f que admite un algoritmo recursivo final:
& & & & & &
& &
funcF( a : V ; b : V ; x : W )dev y : V ;
&
{P( x )}
&
&
&
&
{ y = a ( b f( x ))}
ffunc
&
&
x : W :P( x )
&
f( x )
Algoritmos recursivos
235
&
&
&
(N,N) = 0 (
x ))
& & 1 f(
&
(EF) = F( 0 , 1 , x )
Para demostrar que F admite un algoritmo recursivo final daremos el procedimiento que lo
construye, a partir del algoritmo para f y la especificacin de F. El algoritmo sintetizado ser correcto por construccin.
Para la sntesis del algoritmo utilizamos el anlisis de casos del algoritmo de f. En lo que sigue
&
&
presuponemos que los valores de x cumplen P( x ):
&
Caso d( x )
&
&
&
EF,desplegadodeF
Df,desplegadodef
F( a , b , x )&
&
&
=
a ( b& f( x ))
&
&
=
a ( b g( x ))
EF,desplegadodeF
Rf,desplegadodef
(D) =
(A,A) =
EF,plegadodeF =
F( a , b , x ) &
&
&
=
a ( b& f( x ))
&
&
&
&
=
a &[ b [h( x )[k(
x )f(s( x ))]]]
&
&
&
&
&
a [[ b& h( x )][ b [k(
x )f(s( x ))]]]
&
&
&
&
&
[ a [ b h(
x )]][[
x )]f(s( x ))]
&
& b k(
&
&
&
&
F( a ( b h( x )), b k( x ),s( x ))
&
Caso d( x )
& &
&
&
&
&
&
&
&
&
funcF( a : V ; b : W ; x : W )dev y : V ;
&
{P( x )}
inicio
si
&
&
&
&
&
&
&
&
&
&
&
&
&
&
&
dev y
&
&
&
ffunc
Como ejemplo vamos a ver una funcin que dada una base b y un natural n obtiene la representacin de n en base b en el ejercicio 92 se obtena esta funcin para el caso particular b=2.
funccambioBase(b,n:Nat)devr:Nat;
{P0:2b9}
Algoritmos recursivos
236
inicio
si
n<bor:=n
nbor:=nmodb+10*cambioBase(b,ndivb)
fsi
{Q0:r=i:Nat:((ndivbi)modb)*10i}
devr
ffunc
&
0 =0
&
=* 1 =1
=+
h(n,b)=nmodb
k(n,b)=10
La funcin que se obtiene con la transformacin es mucho ms oscura que la funcin original.
Esto es tpico de las optimizaciones, se pierde claridad a cambio de ganar eficiencia. Para documentar la solucin eficiente se puede utilizar la solucin intuitiva. Existen entornos en el mbito
de la investigacin que realizan estas transformaciones automticamente, el usuario escribe el
diseo intuitivo y el sistema se encarga de optimizarlo.
Plegado y desplegado III
Supongamos dada una funcin recursiva simple que se ajuste al siguiente esquema:
&
&
&
&
&
&
funcf( x : W ; a : W )dev y : V ;
&
{P( x )}
inicio
Algoritmos recursivos
237
si
&
&
&
&
&
& &
d( x )o y :=h(f(s( x ), a ))
&
&
&
{Q( x , a , y )}
&
dev y
ffunc
&
Lo que tiene de particular esta funcin es que su solucin se obtiene aplicando n( x ) veces la
&
&
funcin h al resultado del caso base g( a ), siendo n( x ) el nmero de veces que hay que aplicar la
descomposicin recursiva s para llegar al caso base. Es decir, el resultado slo depende de los
parmetros que controlan el avance de la recursin para determinar cuntas veces se aplica la
funcin h:
&
&
&
& &
n
x :P( x ):f( x , a )=h ( x )(g(a))
Afirmamos entonces que la funcin F especificada como sigue es una generalizacin de f que
admite un algoritmo recursivo final:
&
&
&
&
&
funcF( x : W ; b :V)dev y : V ;
&
{P( x )}
&
{ y =h
&
n( x
&
( b )}
ffunc
siendo
&
&
n( x )=defminn:Nat:d(s ( x ))
es decir, n representa el nmero de llamadas recursivas necesarias para alcanzar un caso directo
&
a partir de x .
Vemos que efectivamente el valor de f se puede obtener a partir de F pues se cumple
&
&
&
&
&
Algoritmos recursivos
238
Para demostrar la anterior ecuacin se puede utilizar induccin sobre la funcin de acotacin
&
&
de f, t( x ), demostrando la ecuacin cuando x cumple la condicin del caso directo, y tomando
como hiptesis de induccin
&
&
&
&
&
es decir, suponiendo que es cierto para un valor menor sobre el dominio de induccin demos&
&
tramos que es cierto para un valor mayor, t(s( x )) < t( x )
&
Caso d( x )
(Df) =
&
&
(d( x )n( x )=0)=
(EF) =
&
&
&
&
f( x , a )
&
g( a&)
&
n
h ( x )(g( a ))
&
&
F( x ,g( a ))
&
Caso d( x )
(Rf)
H.I.
(EF)
&
&
(1+n(s( x ))=n( x ))
(EF)
=
=
=
=
=
f( x , a )
&
&
h(f(s( x ), a ))
&
&
h(F(s( x&),g( a )))
&
n(s( x ))
h(h &
(g( a )))
&
n( x )
h
(g( a ))
&
&
F( x ,g( a ))
Una vez demostrado que f se puede obtener a partir de F, veamos cul es el procedimiento para construir el algoritmo de F a partir del algoritmo de f.
&
Caso d( x )
EF,desplegadodeF
&
&
(d( x )n( x )=0)=
&
&
&
&
F( x , b )&
n( x ) &
=
h
( b )
&
b
&
Caso d( x )
E ,desplegadodeF =
& F
&
&
d( x )n( x )=1+n(s( x ))
EF,plegadodeF =
F( x&, b&)
n( x )
h
( b ) &
&
n(s( x ))
=
h
(h( b ))
&
&
F(s( x ),h( b ))
Algoritmos recursivos
239
&
&
&
&
funcF( x : W ; b :V)dev y : V ;
&
{P( x )}
inicio
si
&
&
&
&
&
&
&
&
{ y =h
&
&
n( x
&
( b )}
dev y
ffunc
Algoritmos recursivos
240
funcfiboGenF(n,r,s:Nat)devr,s:Nat;
{cierto}
inicio
si
n=0o<r,s>:=<r,s>
n>0o<r,s>:=fiboGenF(n1,s,r+s)
fsi
n
{<r,s>=h (r,s)}
dev<r,s>
ffunc
Como postcondicin no podemos poner nada sobre los nmeros de Fibonacci porque todas
las llamadas devuelven el mismo valor por algo es recursiva final : los nmeros de Fibonacci n
y n+1 siendo n el valor con el que se hizo la primera invocacin. Los que s son nmeros de Fibonacci son <r, s>.
Para obtener el n-simo nmero de Fibonacci hacemos la siguiente invocacin:
fiboGen(n)=fiboGenF(n,0,1)=<fib(n),fib(n+1)>
Aplicando a este algoritmo la transformacin de recursivo a iterativo obtendramos aproximadamente el algoritmo que derivamos en el tema de algoritmos iterativos como ejemplo de introduccin de un invariante auxiliar:
varn,x:Ent;
{n=Nn0}
var
i,y:Ent;
inicio
<i,x,y>:=<0,0,1>;
{I:n=Nx=fib(i)0iny=fib(i+1)
C:ni}
itizno
{Iizn}
<x,y>:=<y,x+y>;
{I[i/i+1]}
i:=i+1
{I}
fit
{Ii=n}
fvar
{n=Nx=fib(n)}
Algoritmos recursivos
241
La transformacin PDP III tambin se puede aplicar a la funcin sumaCuadGen que obtuvimos
en este mismo tema en el apartado dedicado a la generalizacin con parmetros acumuladores:
funcsumaCuadGen(n:Nat)devc,p,s:Nat
{P(n):cierto}
inicio
si
n=0o<c,p,s>:=<0,1,0>
n>0o<c,p,s>:=sumaCuadGen(n1);
<c,p,s>:=<c+p,p+2,c+p+s>
fsi
{Q(n,c,p,c):s=i:0in:i*ic=n*np=2*n+1}
dev<c,p,s>
ffunc
&
x =n
&
a =<>
&
b =<c,p,s>
g()=<0,1,0>
h<c,p,s>=<c+p,p+2,c+p+s>
{<c,p,s>=h (c,p,s)}
dev<c,p,s>
ffunc
Algoritmos recursivos
242
2.7 Ejercicios
Introduccin a la recursin
79. Construye y compara dos funciones que calculen el factorial de n, dado como parmetro:
(a) Una funcin iterativa (bucle con invariante 0inr=i!).
(b) Una funcin recursiva.
80. Analiza cmo la funcin recursiva del ejercicio 79(b) se ajusta al esquema general de defini-
funcnombreFunc(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
{P0:Px1=X1xn=Xn}
cte;
var;
inicio
&
&
&
&
&
&
&
d( x )o y :=c( x ,nombreFunc(s( x )))
sid( x )o y :=g( x )
fsi;
{Q0:Qx1=X1xn=Xn}
dev<y1,,ym>
ffunc
81. Construye una funcin recursiva simple cuadrado que calcule el cuadrado de un nmero
82. Una funcin se llama recursiva final si su definicin se ajusta al siguiente esquema:
funcnombreFunc(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
{P0:Px1=X1xn=Xn}
cte;
var;
inicio
&
&
&
&
&
&
d( x )o y :=nombreFunc(s( x ))
sid( x )o y :=g( x )
fsi;
{Q0:Qx1=X1xn=Xn}
dev<y1,,ym>
ffunc
Es decir, una funcin recursiva final es una funcin recursiva lineal especialmente sencilla, tal
que la llamada recursiva devuelve directamente el resultado deseado.
Construye una funcin recursiva final que satisfaga la siguiente especificacin pre/post:
funcacuFact(a,n:Nat)devm:Nat;
Algoritmos recursivos
243
{P0:a=An=N}
{Q0:a=An=Nm=a*n!}
ffunc
&
&
&
&
&
&
&
&
Construye una funcin recursiva mltiple fib con parmetro n, que devuelva el n-simo nmero de Fibonacci. Sigue el esquema de la definicin recursiva de la sucesin de Fibonacci, segn se
conoce en matemticas, distinguiendo los casos 0 n 1 (caso directo) y n 2 (caso recursivo).
84. Dibuja un diagrama en forma de rbol, representando todas las llamadas a la funcin fib
que se originan a partir de la llamada incial fib(4). Cuenta el nmero total de llamadas y observa las llamadas repetidas.
85. Una funcin recursiva puede tener tambin varios casos directos y/o varios casos recursi-
vos, en lugar de uno solo. Aunque haya varios casos recursivos, la recursin se considera lineal si stos estn separados, de tal manera que en cada llamada a la funcin se pase o bien
por uno solo de los casos directos o bien por uno solo de los casos recursivos. Ejemplo:
(a) Funcin recursiva lineal que calcula potencias, con un caso recursivo:
funcpot(x:Ent;y:Nat)devp:Ent;
{P0:x=Xy=Y}
var
y:Nat;p:Ent;
inicio
si
y=0op:=1
y>0oy:=ydiv2;
p:=pot(x,y);
si
par(y)op:=p*p
NOTpar(y)op:=x*p*p
fsi
fsi
{Q0:x=Xy=Yp=xY}
Algoritmos recursivos
244
devp
ffunc
(b) Una funcin recursiva lineal que calcula potencias, con dos casos recursivos:
funcpot(x:Ent;y:Nat)devp:Ent;
{P0:x=Xy=Y}
var
x,y:Nat;p:Ent;
inicio
si
y=0op:=1
y>0ANDpar(y)o<x,y>:=<x*x,ydiv2);
p:=pot(x,y)
y>0ANDNOTpar(y)oy:=y1;
p:=pot(x,y)
p:=x*p
fsi
{Q0:x=Xy=Yp=xY}
devp
ffunc
obtener un algoritmo que slo utilice las operaciones + y div, y que est basado en el siguiente anlisis de casos:
Caso directo: x=0
Casos recursivos: xz0ANDpar(x)
xz0ANDNOTpar(x)
91. Deriva una funcin recursiva log que calcule la parte entera de lobbn, siendo los datos b,
n:Nat tales que b2n1. El algoritmo obtenido deber usar solamente las ope-
raciones + y div.
92. Deriva una funcin recursiva bin tal que, dado un nmero natural n, bin(n) sea otro nmero
natural cuya representacin decimal tenga los mismos dgitos que la representacin binaria
de n. Es decir, debe tenerse: bin(0) = 0; bin(1) = 1; bin(2) = 10; bin(3) = 11; bin(4) = 100; etc.
Algoritmos recursivos
245
94. Deriva una funcin recursiva doble que satisfaga la siguiente especificacin:
funcsumaVec(v:Vector[1..N]deEnt;a,b:Ent)devs:Ent;
{P0:N1v=Va=Ab=B1ab+1N+1}
{Q0:v=Va=Ab=Bs=i:aib:v(i)}
ffunc
funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P0:1ab+1N+1ord(v,a,b)}
{Q0:a1pbv[a..p]x<v[(p+1)..b]}
ffunc
procquickSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P0:v=Va=Ab=B1ab+1N+1}
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc
Algoritmos recursivos
246
un vector por el mtodo de mezcla, segn la especificacin y anlisis de casos que siguen:
procmergeSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P0:v=Va=Ab=B1ab+1N+1}
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc
99. Deriva una funcin recursiva simple dosFib que satisfaga la siguiente especificacin
pre/post:
funcdosFib(n:Nat)devr,s:Nat;
{P0:n=N}
{Q0:n=Nr=fib(n)s=fib(n+1)}
ffunc
En la postcondicin, fib(n) y fib(n+1) representan los nmeros que ocupan los lugares n y
n+1 en la sucesin de Fibonacci, para la cual suponemos la definicin recursiva habitual en matemticas.
n
100. Deriva una funcin recursiva que calcule el nmero combinatorio a partir de los
m
datos m,n:Nat. Usa la recurrencia siguiente:
n
m
n 1 n 1
m 1 m
101. Una palabra se llama palndroma si la sucesin de sus letras no vara al invertir el orden.
Especifica y deriva una funcin recursiva final que decida si una palabra dada, representada
como vector de caracteres, es o no palndroma.
Algoritmos recursivos
247
102. El problema de las torres de Hanoi consiste en trasladar una torre de n discos desde la
varilla ini a la varilla fin, con ayuda de la varilla aux. Inicialmente, los n discos son de diferentes tamaos y estn apilados de mayor a menor, con el ms grande en la base. En
ningn momento se permite que un disco repose sobre otro menor que l. Los movimientos permitidos consisten en desplazar el disco situado en la cima de una de las varillas a la
cima de otra, respetando la condicin anterior. Construye un procedimiento recursivo hanoi
tal que la llamada hanoi(n, ini, fin, aux) produzca el efecto de escribir una serie de movimientos que represente una solucin del problema de Hanoi. Supn disponible un procedimiento movimiento(i,j), cuyo efecto es escribir Movimiento de la varilla i a la varilla j.
(a) Explica por qu motivos la definicin de ack no se ajusta a los esquemas de defini-
104. En cada uno de los casos que siguen, plantea una ley de recurrencia para la funcin T(n)
que mide el tiempo de ejecucin del algoritmo en el caso peor, y usa el mtodo de desplegado para resolver la recurrencia.
(a) Funcin fact (ejercicio 79).
(b) Funcin acuFact (ejercicio 82).
(c) Funciones pot y pot (ejercicio 85).
(d) Funcin log (ejercicio 91).
(e) Funcin sumaVec (ejercicio 94).
(f) Funcin buscaBin (ejercicio 95).
*(g) Procedimiento de ordenacin mergeSort (ejercicio 97).
*(h) Procedimiento hanoi (ejercicio 102).
105. Aplica las reglas de anlisis para dos tipos comunes de recurrencia a los algoritmos recur-
sivos del ejercicio anterior. En cada caso, debers determinar si el tamao de los datos del
problema decrece por sustraccin o por divisin, as como los parmetros relevantes para
el anlisis.
*106. En cada caso, calcula a partir de las recurrencias el orden de magnitud de T(n). Hazlo
Algoritmos recursivos
248
sea idntico a meregeSort con la nica diferencia de que el procedimiento auxiliar mezcla usado por badMergeSort opera en tiempo O(n2). Demuestra que bajo este supuesto el tiempo de
ejecucin de badMergeSort pasa a ser O(n2), frente a O(n log n) de mergeSort.
110. Considera de nuevo la sucesin de Fibonacci, definida en el ejercicio 83. Sean M y M las
1
M n M n
5
112. Aplicando las reglas de anlisis para dos tipos comunes de recurrencia, demuestra que la
funcin recursiva simple dosFib del ejercicio 99 consume tiempo O(n). Compara con el resultado del ejercicio anterior.
funciones:
(a) Funcin acuFact (ejercicio 82).
(b) Funcin mcd (ejercicio 98).
Algoritmos recursivos
249
Tcnicas de generalizacin
114. La siguiente especificacin corresponde a una funcin prodEsc que calcula el producto
escalar de dos vectores. Especifica una generalizacin prodEsc con un parmetro de entrada
adicional, de manera que prodEsc admita un algoritmo recursivo.
funcprodEsc(u,v:Vector[1..N]deEnt)devp:Ent;
{P0:cierto}
{Q0:p=i:1iN:u(i)*v(i)}
ffunc
115. Comprueba que acuFact (ejercicio 82) es una generalizacin de fact (ejercicio 79). Observa
116. Comprueba que dosFib (ejercicio 99) es una generalizacin de fib (ejercicio 83). En este
caso, dosFib tiene un parmetro de salida adicional, el cual permite un algoritmo recursivo
simple ms eficiente que la recursin doble de fib.
117. Usando un parmetro de salida adicional, especifica una generalizacin dosCuca de la fun-
cin cuca del ejercicio 93, de manera que dosCuca admita un algoritmo recursivo simple.
118. Comprueba que la funcin combi especificada como sigue es una generalizacin de la fun-
cin combi del ejercicio 100, y que combi admite un algoritmo recursivo simple, ms eficiente
que la recursin dobre de combi.
funccombi(n,m:nat)devv:Vector[0..N]deEnt;
{P0:0mnmN}
n
i
{Q0:i:0im:v(i)= }
funcsumaCuad(n:Nat)devs:Nat;
{P0:cierto}
{Q0:s=i:0in:i*i}
ffunc
funcsumaCuad(n:Nat)devc,p,s:Nat
{P0:cierto}
{Q0:s=i:0in:i*ic=n*np=2*n+1}
ffunc
Algoritmos recursivos
250
Observa que n2+2n+1 = (n+1)2. Gracias a esto, los dos parmetros de salida aadidos a sumaCuad (c y p) permiten programar sumaCuad sin necesidad de calcular n2 en cada llamada recursiva,
mejorndose as la eficiencia.
120. Comprueba que procesaVec es una generalizacin de procesaVec, y construye un algoritmo
funcprocesaVec(v:Vector[1..N]deEnt)devr:Ent;
{P0:cierto}
i
{Q0:r=i:1iN:2 *v(i)}
ffunc
funcprocesaVec(n,s,p:Ent;v:Vector[1..N]deEnt)devr:Ent;
n
{P0:1nN+1p=2 }
i
{Q0:r=s+i:niN:2 *v(i)}
ffunc
Observa el papel que juega cada uno de los tres parmetros adicionales de procesaVec: n posibilita la recursin; s acumula un resultado parcial; y p mantiene una potencia de 2 para evitar su
clculo explcito en cada llamada recursiva.
Tcnicas de plegado-desplegado
121. Aplica la tcnica de plegado-desplegado para transformar la funcin recursiva lineal fact
122. Comprueba que prod es una generalizacin de prod, y construye un algoritmo recursivo
funcprod(x,y:Nat)devr:Nat;
{P0:cierto}
inicio
six=0 or:=0
(x>0ANDpar(x)) or:=prod(xdiv2,y+y)
(x>0ANDNOTpar(x))or:=y+prod(xdiv2,y+y)
fsi
{Q0:r=x*y}
devr
ffunc
funcprod(a,x,y:Nat)devr:Nat;
{P0:cierto}
{Q0:r=a+x*y}
ffunc
Algoritmos recursivos
251
123. Aplica plegado-desplegado para transformar en funciones recursivas finales las siguientes
ma:
&
&
&
&
funcf( x : W )dev y : V ;
&
{P( x )}
inicio
si
&
&
&
&
&
&
&
&
&
{Q( x , y )}
&
dev y
ffunc
&
&
&
&
&
&
funcF( a : V ; x : W )dev y : V ;
&
{P( x )}
&
&
&
&
Siendo una operacin asociativa y con elemento neutro 0 . Comprueba que en este esquema
general F es una generalizacin de f, y deriva un algoritmo recursivo final para F usando plegadodesplegado.
125. Comprueba que bin es una generalizacin de bin, y deriva un algoritmo recursivo final
para bin usando plegado-desplegado. Observa que bin es la funcin recursiva simple del
ejercicio 92.
funcbin(n:Nat)devr:Nat;
{P0:cierto}
inicio
si
n<2or:=n
n2or:=10*bin(ndiv2)+(nmod2)
fsi
{Q0:r=i:Nat:((ndiv2i)mod2)*10i}
devr
ffunc
Algoritmos recursivos
252
funcbin(s,p,n:Nat)devr:Nat;
{P0:cierto}
{Q0:r=s+p*bin(n)}
ffunc
funccambioBase(b,n:Nat)devr:Nat;
{P0:2b9}
{Q0:r=i:Nat:((ndivbi)modb)*10i}
ffunc
donde la postcondicin quiere expresar que los dgitos de la representacin decimal de r deben
coincidir con los dgitos de n en base b. Por ejemplo, debe cumplirse cambioBase(2,9) = 1001.
(a) Construye un algoritmo recursivo lineal para cambioBase.
(b) Usando una tcnica de plegado-desplegado similar a la del ejercicio 125, construye
127. Las transformaciones de los ejercicios 125 y 126 se ajustan al siguiente esquema:
&
&
&
&
funcf( x : W )dev y : V ;
&
{P( x )}
inicio
si
&
&
&
&
&
&
&
&
&
&
{Q( x , y )}
&
dev y
ffunc
&
&
&
&
&
&
&
&
funcF( a : V ; b : V ; x : W )dev y : V ;
&
{P( x )}
&
&
&
&
& &
Siendo , operaciones asociativas con elementos neutros 0 y 1 , respectivamente, y tales
que sea distributiva con respecto a . Comprueba que en este esquema general F es una generalizacin de f, y deriva un algoritmo recursivo final para F usando plegado-desplegado.
Algoritmos recursivos
253
128. Comprueba que fiboGen es una generalizacin de fiboGen, y deriva un algoritmo recursivo
funcfiboGen(n:Nat)devr,s:Nat;
{P0:cierto}
funccombina(u,v:nat)devu,v:Nat;
inicio
<u,v>:=<v,u+v>
dev<u,v>
ffunc
var
r,s:Nat;
inicio
si
n=0o<r,s>:=<0,1>
n>0o<r,s>:=fiboGen(n1)
<r,s>:=combina(r,s);
fsi
{Q0:r=fib(n)s=fib(n+1)}
dev<r,s>
ffunc
funcfiboGen(n,r,s:Nat)devr,s:Nat;
{P0:cierto}
n
129. Aplica una tcnica de plegado-desplegado similar a la del ejercicio anterior para transfor-
130. Las transformaciones de los ejercicios 128 y 129 se ajustan al esquema que sigue. Com-
prueba que F es una generalizacin de f, y deriva un algoritmo recursivo final para F usando plegado-desplegado.
& & & &
& &
funcf( x : W ; a : W )dev y : V ;
&
{P( x )}
inicio
si
&
&
&
&
&
&
&
&
&
&
{Q( x , a , y )}
&
dev y
ffunc
Algoritmos recursivos
254
&
&
&
&
&
funcF( x : W ; b :V)dev y : V ;
&
{P( x )}
&
{ y =h
ffunc
&
n( x
&
255
CAPTULO 3
Los problemas reales tienden a ser complejos porque involucran demasiados detalles, y resulta
imposible considerar todas las posibles interacciones a la vez. Hay estudios en Psicologa que
afirman que en la memoria a corto plazo slo es posible almacenar 7 elementos distintos.
Una abstraccin es un modelo simplificado de un problema, donde se contemplan los aspectos de un determinado nivel, ignorndose los restantes.
Ejemplo. Para explicar a un granjero que no ha ido nunca a la ciudad cmo ir al banco en su
tractor, conviene describir por separado:
Ejemplo. Para indicarle a un robot cmo cambiar la rueda de un coche podemos utilizar una
descripcin con distintos niveles de abstraccin:
Tirar de la manivela
Empujar la puerta
Ir al maletero
Abrir el maletero
Demostramos que hay un invariante se cumple antes y despus de todas las iteraciones
Obtenemos el invariante
256
Pueden obtenerse soluciones de utilidad ms general (por ej. las instrucciones para llegar
al banco, sirven tambin para peatones o ciclistas)
3.1.2
Especificacin
Abstraccin
Implementacin
La abstraccin funcional es la base del mtodo de diseo descendente, del cul es un ejemplo
las instrucciones que han de darse a un robot para que cambie una rueda.
257
La abstraccin de datos
Esta idea es posterior a la abstraccin funcional formulada por Guttag y otros hacia 1974.
Se trata de separar el comportamiento deseado de un tipo de datos de los detalles relativos a la
representacin y el manejo de los valores de ese tipo. Al igual que la abstraccin funcional se
puede ver como una forma de aadir operaciones a un lenguaje, la abstraccin de datos se puede
ver como una forma de aadir tipos al lenguaje.
Datos histricos:
El concepto de tipo de datos (data types) surgi con la aparicin de los lenguajes de programacin
estructurada (como Pascal, derivado de Algol), en la dcada de los 60.
O. J. Dahl, E. W. Dijkstra, C. A. R. Hoare. Structured Programming. Academic Press 1972.
N. Wirth. The Programming Language PASCAL. Acta Informatica 1, 1971, pp. 35-63.
El concepto de tipo abstracto de datos (abstract data type) fue introducido a mediados de la dcada
de los 70. Pioneros: S. N. Zilles, J. V. Guttag y grupo ADJ (J. A. Goguen, J. W. Thatcher, E. G.
Wagner, J. B. Wright).
[ADJ78] J. A. Goguen, J. W. Thatcher, E. G. Wagner. An Initial Algebra Approach to
the Specification, Correctnes and Implementation of Abstract Data Types. En Current
Trends in Programming Methodology, vol. IV, Prentice Hall, 1978.
3.1.3
Los primeros ejemplos que podemos considerar son los tipos predefinidos de los lenguajes de
programacin de alto nivel:
Enteros. Tenemos acceso a las representaciones abstractas de los nmeros en base 10,
y a las operaciones aritmticas. Sabemos qu leyes algebraicas obedecen las operaciones.
La representacin interna de los nmeros binaria en complemento a 2 y la implementacin de las operaciones al nivel de la mquina algoritmos de aritmtica en complemento
a 2 implementados en el procesador son irrelevantes al nivel del usuario.
En cambio, los tipos de datos definidos por los programadores en la mayora de los lenguajes
de programacin no son abstractos, porque:
258
como consecuencia, el usuario puede realizar con valores del tipo operaciones absurdas
con respecto a la semntica pretendida.
Por ejemplo, si queremos definir un tipo de datos para representar fechas, podemos utilizar
una representacin como esta:
tipo
Da=[1..31];
Mes=enero|febrero||diciembre;
Ao=Ent;
Fecha=reg
da:Da;
mes:Mes;
ao:Ao
freg;
Aparecen aqu por primera vez algunas definiciones de tipos. Los tipos que consideramos son
los de Pascal traducidos al castellano.
El hecho de que el usuario tenga libre acceso a la representacin del tipo de datos, permite que
se realicen operaciones semnticamente absurdas, como:
confechahacer
mes:=febrero;
da:=30;
fcon
Aparece aqu por primera vez la sentencia confcon, que es el equivalente a la sentencia
with de Pascal.
3.1.4
las operaciones del tipo que pueden hacer referencia a otros tipos
Ntese que en la abstraccin de datos se incluye tambin la abstraccin funcional de las operaciones del TAD.
Todo ello sin hacer referencia a ninguna representacin concreta de los datos. El usuario del
TAD slo necesita conocer la especificacin.
259
260
funcprimer(d:DaSemana;m:Mes;a:Ao)devf:Fecha;
{P0:}
{Q0:feslaprimerafechadelmesmdelaoacuyodadelasemanaesd
}
ffunc;
Por ejemplo, con la funcin primer podramos calcular cuando cae el primer martes de noviembre de 1871:
primer(martes,noviembre,1871)
Una especificacin del TAD fecha debera incluir adems axiomas para las operaciones, que
omitimos aqu.
3.1.5
Definir una representacin de los valores del TAD con ayuda de otros tipos ya disponibles.
Definir las operaciones del TAD como funciones o procedimientos, usando la representacin elegida, y de modo que se satisfaga la especificacin.
Para hacer posible esta separacin entre especificacin e implementacin, es necesario que
nuestro lenguaje incluya mecanismos de
Proteccin: el tipo slo puede usarse a travs de sus operaciones; otros accesos incontrolados se hacen imposibles.
261
Cuando hay varias representaciones posibles, normalmente una representacin concreta facilita unas determinadas operaciones y dificulta otras, con respecto a una representacin alternativa.
Por ejemplo el TAD fecha que vimos antes:
La eleccin de una implementacin concreta para un TAD puede posponerse o variarse segn
convenga, sin afectar a los programas ya realizados o en proceso de diseo ayudando as a la
divisin de tareas que usen el TAD.
3.1.6
El diseo descendente
La reutilizacin
El diseo se hace ms abstracto. Ya no dependemos exclusivamente de los tipos concretos que nos ofrezca un lenguaje de programacin determinado. Podemos especificar
TADs adecuados y posponer los detalles de su implementacin.
El diseo puede incluir refinamiento de tipos. Al especificar un TAD necesario para el diseo, podemos dejar incompletos detalles que se completen en etapas posteriores. Por
ejemplo, la decisin de cules deben ser las operaciones del tipo puede irse completando a
lo largo del diseo. De esta forma, se va refinando la definicin del TAD.
262
La estructura de datos elegida a priori puede no ser la ms eficiente, ya que para saber cul
es la eleccin ms eficiente debemos saber cules son las operaciones necesarias sobre
ella, y eso no lo sabremos hasta que hayamos diseado el algoritmo que la utiliza.
Si suponemos disponible un TAD para los multiconjuntos de nmeros enteros, con operaciones adecuadas, podemos disear la siguiente funcin que resuelve el problema:
funcmayores(k:Nat;v:Vector[1..N]deEnt)devm:MCjtoEnt;
{P0:1kNN1}
var
n:Ent;
inicio
Vaco(m);
n:=0;
{Inv.I:mcontieneloselementosdev[1..n]}
itnzko
Pon(v(n+1),m);
n:=n+1
fit
{InvJ:mcontieneloskelementosmayoresdev[1..n]}
itnzNo
siv(n+1)>min(m)
entonces
quitaMin(m);
Pon(v(n+1),m)
sino
seguir
fsi
fit
{Q0:mcontieneloskelementosmayoresdev[1..N]}
devm
ffunc
263
funcVacodevm:MCjtoEnt
{P0:}
{Q0:mrepresenta}
ffunc
procPon(ex:Ent;esm:MCjtoEnt);
{P0:m=Me=E}
{Q0:e=Em=M{x}}
fproc
procquitaMin(esm:MCjtoEnt);
{P0:m=Mmnoesvaco}
{Q0:mesMmenosunacopiadelelementomnimodeM}
fproc
funcmin(m:MCjtoEnt)devr:Ent;
{P0:m=Mmnoesvaco}
{Q0:m=Mreselelementomnimodem}
ffunc
Pon y quitaMin se han implementado como procedimientos para evitar la copia del multiconjunto recibido como parmetro. Un multiconjunto se podra implementar como un vector de
registros de dos componentes, una que indica el nmero almacenado y otra el nmero de apariciones de ese valor; adems se podra optar entre mantenerlo o no ordenado con lo que se hara
ms eficiente la obtencin del mnimo o las operaciones poner y quitar, respectivamente.
Se pueden encontrar infinidad de ejemplos como este donde es beneficioso disear en trminos de un TAD.
Los TADs como apoyo a la programacin modular
El desarrollo de programas grandes y complejos normalmente se realiza descomponiendo el
programa en unidades menores llamadas mdulos, cada uno de los cuales encapsula una coleccin de declaraciones en un mbito de visibilidad cerrado y se comunica con otros mdulos a
travs de una interfaz bien definida.
En un diseo modular es importante que los mdulos se elijan adecuadamente en cuanto a su
tamao y conexiones mutuas. Por lo general siempre resulta adecuado disear como mdulos las
implementaciones de TADs, debido a su utilidad general, su tamao mediano, y la simplicidad de
la conexin con los mdulos usuarios.
En Delphi las clases constituyen un mecanismo de modularizacin.
La implementacin de los TADs como mdulos est relacionado con las ideas bsicas de la
orientacin a objetos, donde las clases encapsulan las definiciones de tipos de datos junto con las
operaciones que los manejan. Utilizando criterios de ingeniera del software, la organizacin del
diseo en torno a los datos resulta ventajoso frente a la organizacin en torno a las funciones,
pues los datos objetos suelen ser ms resistentes al cambio.
264
Datos histricos.
La metodologa de diseo modular (modular design) surgi como extrapolacin de la tcnica de
diseo con TADs a aplicaciones de gran dimensin.
Una obra clsica que trata en profundidad esta metodologa es
B. H. Liskov, J. V. Guttag. Abstraction and Specification in Program Development. The MIT Press, 1986.
3.1.7
En este curso, entenderemos que una estructura de datos (data structure) es la representacin de un
TAD mediante una combinacin adecuada de los tipos de datos y constructoras de tipos disponibles en los lenguajes de programacin habituales (booleanos, enteros, reales, caracteres, vectores, registros, punteros, etc.) Es decir, concebimos las estructuras de datos como estructuras de
implementacin.
Hay muchas estructuras de datos clsicas que corresponden a combinaciones interesantes de
los tipos bsicos: las estructuras lineales, los rboles, las tablas, los grafos, etc. Estas estructuras
clsicas pueden entenderse como las implementaciones de ciertos TADs bsicos asociados a ellas.
Adems, las combinaciones de estas estructuras sirven para implementar muchos otros TADs.
Existe la tendencia entre los alumnos a identificar el estudio de los TADs con el estudio de estas estructuras de datos clsicas. Y a considerar que los TADs asociados con ellas son entidades
monolticas cuyas especificaciones no pueden ser modificadas porque entonces ya no sera el
TAD x. Estos TADs representan colecciones de datos a los que se accede de un cierto modo;
como tales son muy interesantes porque son muchos los programas que manejan colecciones de
datos, y la literatura se ha encargado de identificar agrupaciones interesantes de operaciones de
acceso, e investigar posibles implementaciones representaciones concretas que hagan eficientes
dichas operaciones. Sin embargo, el concepto de TAD es ms simple y ms general a la vez, simplemente un TAD define un dominio de valores junto con las operaciones que permiten manejarlos, y esto es algo que puede variar de unas aplicaciones a otras, an en lo que se refiere a las
estructuras de datos clsicas.
Operaciones
265
Axiomas
Los ejemplos del tema anterior pueden sugerir la posibilidad de especificar un TAD dando un
nombre a su dominio de valores, y dando nombres y especificaciones pre/post para funciones o
procedimientos correspondientes a sus operaciones.
En este enfoque las especificaciones pre/post deberan reflejar los axiomas que deseamos que
cumplan las operaciones del TAD.
Un enfoque diferente consiste en imaginar las operaciones de un TAD como anlogas a las
operaciones del lgebra y formular los axiomas que queramos exigirles por medio de ecuaciones
igualdades entre aplicaciones de las operaciones. Este enfoque se llama especificacin algebraica. (Un
lgebra es un dominio de valores equipado con operaciones que cumplen axiomas algebraicos
dados).
La principal diferencia con respecto al mtodo de las pre y postcondiciones es que se usan
ecuaciones en lugar de otros asertos ms complicados. Esto tiene varias ventajas:
Las ecuaciones no slo especifican las propiedades de las operaciones, sino que especifican tambin cmo construir valores de ste
Las especificaciones basadas en ecuaciones suelen dar muchas pistas para la implementacin
Tipos: son nombres de dominios de valores. Entre ellos est siempre el tipo principal del
TAD; pero puede haber tambin otros que se relacionen con ste, quiz pertenecientes a
otros TADs especificados anteriormente. (En algunos libros a los tipos se les denomina
gneros para hacer ms hincapi en la diferencia entre dominio de valores y TAD que
adems est equipado con un conjunto de operaciones)
Operaciones: deben ser funciones con un perfil asociado, que indique el tipo de cada uno
de los argumentos y el tipo del resultado. En una especificacin algebraica no se permiten
funciones que devuelvan varios valores, ni tampoco procedimientos no funcionales. (En
una implementacin, como veremos, s podrn usarse procedimientos.)
Ecuaciones entre trminos formados con las operaciones y variables de tipo adecuadas
Definimos la signatura de un TAD como los tipos que utiliza, junto con los nombres y los perfiles de las operaciones sin incluir las ecuaciones.
266
Algo de notacin:
: signatura de un TAD
W, Wi : tipos
c:oW
f : W1, , Wn o s
operacin con perfil, donde los Wi son los tipos de los argumentos y W es el
tipo del resultado
Cierto,Falso:oBool/*gen*/
(not):BooloBool /*mod*/
(and),(or):(Bool,Bool)oBool/*mod*/
ecuaciones
ftad
x:Bool
notCierto =Falso
notFalso =Cierto
Ciertoandx=x
Falsoandx =Falso
Ciertoorx =Cierto
Falsoorx =x
Ntese que slo hacemos distincin de casos en uno de los argumentos. La idea intuitiva es
que tenemos que escribir las ecuaciones de forma que reflejen el comportamiento de las
operaciones, segn el modelo mental que estamos intentando especificar.
Un ejemplo ms complicado, donde aparece la clusula usa para indicar que importamos otro
TAD, de forma que todas las operaciones, tipos y ecuaciones de BOOL son visibles en NAT. Es
como si cogisemos la especificacin de BOOL y la pegsemos en la especificacin de NAT. En
ocasiones puede ser necesario renombrar alguna de las operaciones del TAD usado para que no
se colapsen con las operaciones que voy a definir en el TAD que lo usa.
tadNAT
usa
BOOL
tipo
Nat
operaciones
Cero:oNat /*gen*/
Suc_:NatoNat /*gen*/
(+),(*):(Nat,Nat)oNat /*mod*/
(==),(/=):(Nat,Nat)oBool /*obs*/
(),(),(<),(>):(Nat,Nat)oBool /*obs*/
ecuaciones
ftad
267
x,y:nat
x+Cero =x
x+Suc(y) =Suc(x+y)
x*Cero =Cero
x*Suc(y) =(x*y)+x
Cero==Cero =Cierto
Cero==Suc(y) =Falso
Suc(x)==Cero =Falso
Suc(x)==Suc(y) =x==y
x/=y =not(x==y)
Ceroy =Cierto
Suc(x)Cero =Falso
Suc(x)Suc(y)=xy
xy =yx
x<y =(xy)and(x/=y)
x>y =y<x
Ntese que las ecuaciones tienen forma de definiciones recursivas, definimos el comportamiento de la operacin para el o los casos base y luego definimos una o ms reglas recursivas que
nos van acercando al caso base a los trminos generados. Por ejemplo, para la suma tenemos
como caso base
x+Cero=x
Trminos de tipo W: TW
Fijando un conjunto de variables X, y aplicando las operaciones del TAD podemos generar
valores de distintos tipos a los que llamamos trminos. Es decir, una signatura permite definir un
268
conjunto, tpicamente infinito, de trminos que se pueden construir utilizando las operaciones de
la signatura. Nos va a interesar razonar sobre esos trminos y expresar algunas propiedades que
debe cumplir la especificacin con respecto a ellos.
T, W (X)
Donde el conjunto de variables X hace referencia a las variables que hemos utilizado al escribir las ecuaciones.
que se define recursivamente como:
x XW x T,W (X)
donde XW X
si x pertenece a las variables de tipo W entonces pertenece al conjunto de trminos de tipo
W con variables en X
c : o W c T,W (X)
Convenios
T,W es T,W (). A los trminos sin variables se les denomina trminos cerrados
puede omitirse si se da por sabida, con lo que el conjunto de trminos cerrados de tipo
W para una signatura que se da por sabida es TW
Ejemplos
En NAT hay dos tipos Ent y Bool, y se tiene
Suc(x)+Suc(Suc(y))TEnt(x,y)
Suc(Cero)+Suc(Suc(Cero))TEnt
Suc(x)==CeroTBool(x)
Suc(Cero)/=CeroTBool
3.2.3
Para saber qu quiere decir una especificacin algebraica tenemos que determinar cules son
las igualdades entre trminos que son vlidas de acuerdo con las ecuaciones de la especificacin.
Las ecuaciones de la especificacin de un TAD pueden usarse para deducir de ellas otras ecuaciones, segn las leyes de la lgica.
Sea T un TAD con tipo principal W, dos trminos t, t: W se llaman T-equivalentes (en
smbolos t=Tt), si la ecuacin t=t se puede deducir a partir de los axiomas ecuacionales
de T. (De un TAD que use a otro tambin se pueden deducir T-equivalencias entre trminos de
los tipos importados, sin embargo, como luego veremos, no se puede introducir ninguna equivalencia nueva, por lo que en un TAD basta con hacer referencia a las equivalencias entre trminos del
tipo principal.)
269
Las reglas del clculo ecuacional que nos permiten determinar la igualdad algebraica entre
trminos son:
Reflexividad
t=t
Simetra
t=t
t=t
Transitividad
t=tt=t
t=t
Congruencia
t1=t1tn=tn
f(t1,,tn)=f(t1,,tn)
Axiomas ecuacionales
t[x1/t1,,xn/tn]=t[x1/t1,,xn/tn]
si
Por ejemplo, en NAT se puede deducir la siguiente igualdad algebraica entre trminos
Suc(Cero)*Suc(Cero)=Suc(Cero)
*.2
*.1
+.2
+.1
==
==
==
==
Suc(Cero)*Suc(Cero)
(Suc(Cero)*Cero)+Suc(Cero)
Cero+Suc(Cero)
Suc(Cero+Cero)
Suc(Cero)
donde, en todos los pasos, hemos utilizado la regla axiomas ecuacionales del clculo.
Advertencia: Las notaciones
t=t y t=Tt
270
no deben confundirse
t = t indica una ecuacin que puede cumplirse o no
por ejemplo
Suc(x)=Suc(y)
Suc(x)=TSuc(y)
Las operaciones y ecuaciones del tipo deben elegirse de forma que estas dos condiciones se
correspondan con la intencin del especificador.
Hay que escribir las ecuaciones de forma que sea posible deducir todas las equivalencias que
son vlidas en el modelo en el que estamos pensando. Pero sin escribir demasiadas, para que sea
posible deducir equivalencias indeseables.
3.2.4
Para lograr el requisito de que el TAD se corresponda con la intencin del especificador conviene
seguir la siguiente metodologa: una vez elegido el tipo principal W, clasificamos las operaciones
segn el papel que queremos que jueguen en relacin con el tipo principal, como:
Generadoras (o constructoras)
Sern algunas operaciones con perfil
&
c: W oW
Modificadoras
Sern las restantes operaciones con perfil
&
f: W oW
271
que no sean generadoras. Estas operaciones estn pensadas para hacer clculos que produzcan resultados de tipo W.
Observadoras
Sern algunas operaciones con perfil
&
Cierto,Falso:oBool /*gen*/
(not):BooloBool /*mod*/
(and),(or):(Bool,Bool)oBool /*mod*/
y para NAT
Cero:oNat /*gen*/
Suc:NatoNat /*gen*/
(+),(*):(Nat,Nat)oNat /*mod*/
(==),(/=):(Nat,Nat)oBool /*obs*/
(),(),(<),(>):(Nat,Nat)oBool /*obs*/
Dada esta clasificacin de las operaciones, hay un tipo especialmente interesante de trminos,
los que slo utilizan operaciones generadoras.
3.2.5
Dado un TAD T con tipo principal W se llaman trminos generados a aquellos trminos de tipo W
que estn construidos usando solamente operaciones generadoras de T (y trminos generados de
otros TADs usados por T, si es necesario). (No nos preocupamos por los trminos de otros tipos
que no sean W pues esos trminos deben ser equivalentes a trminos generados en el TAD donde
se define el correspondiente tipo.)
TG,W(X)=def{tT,W(X)|todaslasoperacionesusadasentson
generadoras}
272
Suc(Suc(Cero))Suc(Suc(Suc(Cero))) Suc(Suc(Cero))
Ej. 141: Indica cules son los trminos generados de los TADs BOOL, NAT y COMPLEJO.
La utilidad de distinguir el conjunto de trminos generados dentro del conjunto de trminos es
poder establecer un subconjunto de trminos que representen a todos los valores posibles de
TAD, de forma que los trminos no generados sean equivalentes a algn trmino generado. De
esta forma nos bastar con razonar sobre los trminos generados, pues el resto de trminos se
podrn reducir a ellos. La propiedad de que cualquier trmino se pueda reducir a uno generado es
una propiedad que le debemos exigir a nuestras especificaciones.
3.2.6
Dado el TAD T con tipo principal W, se dice que el conjunto de operaciones generadoras de T
es suficientemente completo si es cierta la siguiente condicin: para todo trmino cerrado t de tipo W
debe existir un trmino cerrado y generado t de tipo W que sea T-equivalente a t
t:tT,W:t:tTG,W:t=Tt
No nos preocupamos por los trminos de otros tipos distintos de W porque esos trminos deben ser equivalentes a trminos generados en el TAD donde se define su tipo. Esto ha de ser as
para que el TAD usado quede protegido, como luego veremos.
La especificacin de cualquier TAD siempre debe construirse de manera que el conjunto de
operaciones generadoras elegido sea suficientemente completo.
Ejemplo: las generadoras de BOOL son suficientemente completas
Este tipo de demostraciones se hace por induccin sobre la estructura sintctica de los trminos induccin estructural.
Base:t/Cierto
Cierto=BOOLCierto
Base:t/Falso
Falso=BOOLFalso
Pasoinductivo:t/nott1
273
H.I.
t1/Cierto
not.1
t1/Falso
not.2
nott1
=BOOLnott1
notCierto
=BOOLFalso
notFalso
=BOOLCierto
Pasoinductivo:t/t1andt2
H.I.
t1/Cierto
and.1
t1/Falso
and.2
t1andt2
=BOOLt1andt2
Ciertoandt2
=BOOLt2
Falsoandt2
=BOOLFalso
Suc(t1)
H.I. =NATSuc(t1)
Pasoinductivo:t/t1+t2
t1+t2
H.I. =NATt1+t2
Lema1 =NATt
Ellema1dicequedadosdostrminosgeneradost1yt2existeotro
trminogeneradottalquet1+t2=NATt.Lodemostraremosms
abajo.
Pasoinductivo:t/t1*t2
t1*t2
274
H.I. =NATt1*t2
Lema2 =NATt
Ellema2dicequedadosdostrminosgeneradost1yt2existeotro
trminogeneradottalquet1*t2=NATt.Lodemostraremosms
abajo.
Lema1
Base:t2/Cero
t1+Cero
+.1 =NATt1
Pasoinductivo:t2/Suc(t3)
t1+Suc(t3)
+.2 =NATSuc(t1+t3)
H.I. =NATSuc(t4)
Lema2
Base:t2/Cero
t1*Cero
*.1 =NATCero
Pasoinductivo:t2/Suc(t3)
t1*Suc(t3)
*.2 =NATt1*t3+t1
H.I. =NATt4+t1
Lema1 =NATt5
Para los trminos de tipo Bool no lo demostramos porque es un tipo que se define en otro
TAD, lo que tendremos que demostrar es que el TAD usado queda protegido.
3.2.7
Razonamiento inductivo
El mismo tipo de demostraciones que hemos utilizado para probar la completitud suficiente
de las generadoras se puede utilizar para demostrar otras ecuaciones. Hay ecuaciones cuya validez
no se puede obtener simplemente con el clculo ecuacional, sino que hay que hacer suposiciones
sobre las posibles formas sintcticas de los trminos. Aqu nos aprovechamos de la completitud suficiente de las generadoras, para as razonar solamente sobre la estructura sintctica de los
trminos generados.
Ejemplo (ej. 143): Cero + y = y
Base:y/Cero
Cero+Cero
+.1 =NATCero
Pasoinductivo:y/Suc(y1)
Cero+Suc(y1)
+.2 =NATSuc(Cero+y1)
H.I. =NATSuc(y1)
275
3.2.8
Una especificacin algebraica define las condiciones de una familia de lgebras, aquellas que le
pueden servir como modelo. Aunque nosotros cuando elegimos el nombre de las constructoras y
de las operaciones ya estamos pensando en un cierto modelo y es por ello que les asignamos
nombres significativos dentro de ese modelo, eso no implica que sea el nico modelo posible. Un
modelo de una especificacin algebraica debe:
Incluir un conjunto de valores para cada tipo utilizado en la especificacin ntese que en
la especificacin no se dice nada sobre la cardinalidad de ese conjunto. De hecho un lgebra donde el dominio de valores tenga un solo elemento es modelo de cualquier especificacin algebraica que no tenga operaciones parciales
Por cada operacin que aparezca en la especificacin, debe incluir una funcin con el
mismo perfil el nombre puede ser distinto. No puede incluir ms funciones.
Las funciones asociadas con las operaciones deben cumplir las ecuaciones de la especificacin.
notAv
v1v2
andA v
v1v2 orAv
c
f
cc
cc
c
f
c
cf
cf
c
c A
A
c A
c
fc
fc
c
ff
ff
f
f A
f A
A A
A c A
A f f
AA A
A
c
A c
A f A
AA A
notCierto=Falso porlaprimerafiladenotA
notFalso=Cierto porlasegundafiladenotA
Ciertoandx=x porlastresprimerasfilasdeandA
276
Falsoandx=Falso porlasfilas4a6deandA
Ciertoorx=Cierto porlastresprimerasfilasdeorA
Falsoorx=x porlasfilas4a6deorA
Ntese que en la ltima fila de notA, y las tres ltimas de andA y notA podramos haber elegido
cualquier otra combinacin de valores pues las ecuaciones no imponen ninguna condicin sobre
la aplicacin de las operaciones a esos valores.
Ntese asimismo que en las ecuaciones donde aparecen generadoras es necesario realizar dos
sustituciones:
notCierto=Falso
seinterpretacomo
notACiertoA=FalsoAnotAc=ff=f
3.2.9
Modelo de trminos
Es decir consideramos como valores los trminos generados, y como resultados de las operaciones los trminos generados a los que son equivalentes.
Por ejemplo, en NAT
277
SucA(Suc2(Cero))=TSuc3(Cero)
Suc2(Cero)+ASuc3(Cero)=TSuc5(Cero)
Suc3(Cero)*ASuc2(Cero)=TSuc6(Cero)
Este modelo da la semntica inicial a la especificacin algebraica. Se llama inicial pues se puede
establecer un isomorfismo entre ese y cualquier otro modelo de la especificacin. Es el modelo
con el nmero mnimo de ecuaciones, o dicho de otra forma, cualquier modelo de la especificacin algebraica debe cumplir todas las ecuaciones que cumpla el modelo de trminos.
La especificacin de un TAD debe ser diseada de manera que su modelo abstracto describa
el tipo de datos que el diseador tiene en mente. Como veremos ms adelante, las implementaciones de un TAD estn obligadas a realizar una representacin del modelo abstracto.
3.2.10
Cuando un TAD utiliza un tipo s definido en otro TAD, las ecuaciones observadoras se deben
escribir de tal forma que no introduzcan nuevos valores de tipo s basura ni tampoco nuevas
equivalencias entre valores antiguos de tipo s confusin. Si se cumplen estas dos condiciones,
decimos que el TAD usado est protegido.
Sea T un TAD con tipo principal W cuya especificacin algebraica usa otro TAD S con tipo
principal s. Se dice que T protege a S si se cumplen las dos condiciones siguientes:
(i) T no introduce basura en S, es decir: para todo trmino cerrado t : s que se pueda
construir en T, existe un trmino generado t:s que ya se poda construir en S y
tal que t=Tt.
(ii) T no introduce confusin en S, es decir: Si t,t:s son trminos cerrados generados
que ya se podan construir en S, y se tiene t=Tt, entonces ya se tena t=St.
(a) Demuestra que el siguiente TAD que usa a BOOL no protege a BOOL, porque
tadPERSONA
usa
BOOL
tipo
Persona
operaciones
Carmen,Roberto,Luca,Pablo:oPersona
gen*/
/*
feliz:PersonaoBool
obs*/
ecuaciones
feliz(Carmen) =feliz(Roberto)
feliz(Pedro) =feliz(Luca)
feliz(Pedro) =notfeliz(Pablo)
feliz(Luca) =Cierto
/*
278
feliz(Pablo) =Cierto
Introduce basura porque hay trminos de tipo Bool para los que no se puede deducir la equivalencia con ninguno de los trminos generados de BOOL es decir, que no se sabe si son ciertos o
falsos:
feliz(Carmen) feliz(Roberto)
feliz.4
feliz.2
feliz.3
feliz.5
not.1
=PERSONA
=PERSONA
=PERSONA
=PERSONA
=PERSONA
Cierto
feliz(Luca)
feliz(Pedro)
notfeliz(Pablo)
notCierto
Falso
No se introduce basura, porque en NAT cualquier trmino cerrado t : Bool cumple t =NAT
Cierto o t =NAT Falso. Como se puede demostrar por induccin sobre la estructura sintctica de t.
3.2.11
Generadoras no libres
Sea T un TAD con tipo principal W. Se dice que las operaciones generadoras de T son no libres
si existen trminos generados no idnticos t y t de tipo W, que sean T-equivalentes. Por el contrario, se dice que las operaciones generadoras de T son libres si no es posible encontrar trminos
generados no idnticos t, t de tipo W que sean T-equivalentes.
Decimos que dos trminos son idnticos cuando son iguales sintcticamente.
Los TAD que venimos estudiando hasta ahora, BOOL y NAT, presentan generadoras libres.
Veamos una especificacin para los enteros donde aparecen generadoras no libres:
tadENT
usa
BOOL,NAT
tipo
Ent
operaciones
Cero:oEnt /*gen*/
Suc,Pred:EntoEnt /*gen*/
():EntoEnt /*mod*/
(+),(),(*):(Ent,Ent)oEnt /*mod*/
(^):(Ent,Nat)oEnt/*mod*/
ecuaciones
x,y:Ent:n:Nat
279
ftad
Suc(Pred(x))=x
Pred(Suc(x))=x
x+Cero =x
x+Suc(y) =Suc(x+y)
x+Pred(y) =Pred(x+y)
xCero =x
xSuc(y) =Pred(xy)
xPred(y) =Suc(xy)
x =Cerox
x*Cero =Cero
x*Suc(y) =(x*y)+x
x*Pred(y) =(x*y)x
x^Cero =Suc(Cero)
x^Suc(n) =(x^n)*x
Podemos ver que las generadoras del TAD ENT no son libres pues existen trminos generados que son ENT-equivalentes entre s:
Suc(Pred(Suc(Cero)))
Suc =ENTSuc(Cero)
Por el contrario las generadoras de BOOL y TAD s son libres pues la especificacin no incluye ninguna ecuacin que slo involucre a generadoras, por lo tanto no es posible aplicar ninguna
regla del clculo ecuacional que no sea la reflexiva. (es correcto este razonamiento?).
Esto nos da una idea a utilizar en el diseo de especificaciones: si las generadoras son libres
entonces no escribimos ninguna ecuacin que las relacione; por el contrario si las generadoras no
son libres es necesario escribir ecuaciones que permitan determinar las equivalencias que nos
interesen entre trminos generados. En este segundo caso puede ser til pensar en una eleccin
de trminos cannicos.
Representantes cannicos de los trminos: TCW TGW
3.2.12
Sea T un TAD con tipo principal W. Se llama sistema de representantes cannicos a cualquier subconjunto TCW del conjunto TGW de todos los trminos generados y cerrados de tipo W, tal que para
cada t TGW exista un nico t TCW tal que t =T t. Es decir, si todos los elementos de TC son
algebraicamente diferentes, y para cada elemento de TG se puede encontrar uno T-equivalente en
TC; lo que hacemos es elegir un representante para cada una de las clases de equivalencia que es
posible definir entre los elementos de TG.
Si un TAD tiene generadoras libres entonces el conjunto de representantes cannicos coincide
con el de trminos generados, como ocurre en BOOL y NAT. No es as cuando tratamos con
TAD con generadoras no libres. De hecho el concepto de trminos cannicos se introduce para
conseguir un conjunto mnimo de valores que representen a todos los valores posibles, cuando
tenemos TAD con generadoras no libres.
Por ejemplo, en ENT podemos elegir como trminos cannicos:
TCEnt=def{Cero}{Sucn(Cero)|n>0}{Predn(Cero)|n>0}
280
Insistir en la idea expuesta anteriormente de que cuando tenemos generadoras no libres debemos escribir ecuaciones sobre las generadoras de forma que sea posible traducir cualquier
trmino generado a un trmino cannico. El conjunto de representantes cannicos en el que se
estaba pensando cuando se escribi la especificacin de ENT era el primero que hemos escrito
ms arriba. Podemos ver intuitivamente que efectivamente con las dos ecuaciones dadas es posible traducir cualquier trmino generado a uno de la forma Cero Sucn(Cero) Predn(Cero).
Resumiendo lo dicho hasta ahora sobre las ecuaciones, debemos escribirlas teniendo en mente
las siguientes condiciones:
Las ecuaciones deben permitir las igualdades entre trminos que son posibles entre los valores del modelo en el que estamos pensando.
Las ecuaciones de las modificadoras deben permitir convertir cualquier trmino en uno
generado acercarnos al caso base.
Las ecuaciones sobre las observadoras deben proteger al tipo usado sin introducir basura
ni confusin.
3.2.13
Vamos a introducir dos nuevos elementos que es posible utilizar en las especificaciones algebraicas:
Las operaciones privadas. Son operaciones que no exporta el TAD a sus clientes pero que
ayudan a especificar el comportamiento de las operaciones exportadas, y, en algunos casos, tambin a su implementacin.
Veamos un ejemplo con la especificacin de los enteros con orden. Aparece aqu otro concepto nuevo: enriquecimiento de un TAD. Un enriquecimiento es un TAD que usa a otro al que
extiende aadindole operaciones nuevas, pero sin definir ningn tipo adicional.
La idea para escribir las ecuaciones de comparacin es darse cuenta de que basta con dar el
modo de calcular , y el resto de las operaciones se pueden expresar en trminos de sta se
podra hacer una eleccin diferente. Y la idea para indicar el modo de calcular es hacerlo en
trminos de la resta, viendo qu signo tiene sta. Para ello introducimos la operacin auxiliar noNeg. Y para escribir las ecuaciones de noNeg tenemos que utilizar ecuaciones condicionales.
tadENTORD
usa
ENT
operaciones
(==),(/=):(Ent,Ent)oBool /*obs*/
(),(),(<),(>):(Ent,Ent)oBool /*obs*/
operacionesprivadas
noNeg:EntoBool /*obs*/
281
ecuaciones
ftad
x,y:Ent
noNeg(Cero) =Cierto
noNeg(Suc(x)) =CiertosinoNeg(x)=Cierto
noNeg(Pred(Cero)) =Falso
noNeg(Pred(x)) =FalsosinoNeg(x)=Falso
xy =noNeg(yx)
xy =yx
x<y =(xy)and(x/=y)
x>y =y<x
x==y =xyandyx
x/=y =not(x==y)
La inclusin de ecuaciones condicionales modifica una de las reglas del clculo ecuacional
Axiomas ecuacionales
C[x1/t1,,xn/tn]
t[x1/t1,,xn/tn]=t[x1/t1,,xn/tn]
si
Como ejercicio se propone la especificacin del TAD POLINOMIO, nada trivial, que necesita de las operaciones privadas multMono para expresar la multiplicacin de polinomios, y quita
Exp para expresar la operacin esNulo.
3.2.14
Clases de tipos
Introducimos un nuevo elemento en las especificaciones: las clases de tipos. Estas especificaciones permiten especificar restricciones sobre TADs. Son tiles como luego veremos para escribir TADs parametrizados, TADs que dependen de otros, pues permiten expresar las condiciones
que se les exigen a los parmetros de los tipos parametrizados.
Veamos un primer ejemplo:
claseANY
tipo
Elem
fclase
282
La clase de tipos ANY representa a cualquier tipo. Hay una sutilidad y es que no todos los
TAD definen como tipo principal Elem de hecho ninguno. Entendemos que Elem es sinnimo
del tipo principal que se define en un TAD. Para hacerlo ms formal podramos cambiar la definicin del TAD y cambiar el nombre del tipo principal; o en el TAD paramtrico que depende de
ANY, podramos hacer un renombramiento del correspondiente tipo principal a Elem, pero como escribimos el tipo paramtrico sin saber sobre qu tipos se instanciar, no tenemos esa informacin.
En las clases de tipos se pueden poner axiomas, es decir condiciones que no son ecuaciones. La
razn de que en las especificaciones de TAD slo se pongan ecuaciones es que as est definida
de forma unvoca un lgebra con un conjunto mnimo de suposiciones: el modelo inicial. Si
aceptsemos axiomas con conectivas lgicas entonces podramos encontrar ms de un lgebra
con el mismo coste:
a=bora=c
hace posible tanto un lgebra donde a sea igual a b o igual a c ambos con el mismo coste o
igual a ambas.
En las clases de tipos s podemos poner axiomas porque no tenemos la necesidad de construir
una implementacin particular de la clase de tipos, sino identificar una familia de lgebras posibles.
Un par de ejemplos ms: los tipos con igualdad. aparece aqu un nuevo elemento que es la
herencia entre clases de tipos, indicando que la clase EQ contiene todas las operaciones y tipos
especificados en la clase ANY.
claseEQ
hereda
ANY
usa
BOOL
operaciones
(==),(/=):(Elem,Elem)oBool
axiomas
%(==)esunaoperacindeigualdad.
ecuaciones
x,y:Elem:
x/=y=not(x==y)
fclase
(),(),(<),(>):(Elem,Elem)oBool
axiomas
%()esunaoperacindeorden
ecuaciones
283
x,y:Elem:
xy=yx
x<y=(xy)and(x/=y)
x>y=y<x
Escribimos las condiciones sobre == y d como axiomas porque no sabemos cules son las
constructoras del tipo concreto y por lo tanto no podemos escribir las ecuaciones concretas.
Ntese que diferentes TADs pertenecientes a la misma clase de tipos pueden tener signaturas
diferentes, ya que se exige que aparezcan una ciertas operaciones, pero no exclusivamente esas.
3.2.15
TADs genricos
Los TADs genricos son TADs que dependen de otros uno o ms de forma que es posible
construir distintos ejemplares del TAD genrico segn el tipo de los parmetros.
Los TADs genricos representan tpicamente colecciones de elementos. Estos TADs tienen
un cierto comportamiento independiente del tipo de los elementos, aunque tambin puede haber
operaciones que dependan de los elementos. Las exigencias sobre los elementos se expresan indicando la clase de tipos a la que tienen que pertenecer los parmetros admisibles.
Como ejemplo vamos a escribir la especificacin de los conjuntos CJTO[E :: EQ]
tadCJTO[E::EQ]
usa
BOOL
tipo
Cjto[Elem]
operaciones
:oCjto[Elem] /*gen*/
Pon:(Elem,Cjto[Elem])oCjto[Elem] /*gen*/
quita:(Elem,Cjto[Elem])oCjto[Elem]
mod*/
/*
esVaco:Cjto[Elem]oBool /*obs*/
():(Elem,Cjto[Elem])oBool /*obs*/
ecuaciones
x,y:Elem:xs:Cjto[Elem]:
Pon(y,Pon(x,xs))
=Pon(x,xs)six==y
Pon(y,Pon(x,xs))
=Pon(x,Pon(y,xs))
quita(x,) =
** quita(x,Pon(y,xs))=quita(x,xs)six==y
quita(x,Pon(y,xs))=Pon(y,quita(x,xs))six/=y
esVaco() =Cierto
esVaco(Pon(x,xs)) =Falso
284
x =Falso
xPon(y,xs) =x==yorxxs
ftad
el problema es que aqu no estamos teniendo en cuenta que x ya poda estar en el conjunto xs,
en cuyo caso la ecuacin no sera vlida. Si admitisemos la ecuacin en esta segunda forma, sera
posible obtener equivalencias como esta:
quita.2
Pon.1
quita.2
=
=
=
Vaco
quita(0,Pon(0,Vaco))
quita(0,Pon(0,Pon(0,Vaco)))
Pon(0,Vaco)
Aparece aqu un nuevo elemento sintctico: la cualificacin de los elementos de un TAD con
el nombre del TAD, como en E.Elem y NAT.Nat
Podemos hacer esta instanciacin porque NAT pertenece a la clase EQ puesto que contiene
las operaciones == y /=, siendo == una operacin de igualdad.
La especificacin de este nuevo TAD se obtiene a partir de la especificacin del TAD genrico:
sustituyendo Elem por Nat, con lo que el tipo principal es Cjto[Nat]
tipo
Pareja[A.Elem,B.Elem]
operaciones
Par:(A.Elem,B.Elem)oPareja[A.Elem,B.Elem]/*gen*/
pr:Pareja[A.Elem,B.Elem]oA.Elem/*obs*/
sg:Pareja[A.Elem,B.Elem]oB.Elem /*obs*/
ecuaciones
285
x:A.Elem:y:B.Elem:
pr(Par(x,y))=x
sg(Par(x,y))=y
ftad
O parejas de conjuntos
PAREJA[CJTO[NAT],CJTO[BOOL]]
contipoprincipalPareja[Cjto[Nat],Cjto[Bool]]
3.2.16
En muchas ocasiones nos tendremos que especificar TADs que incluyan operaciones parciales. En ese caso existirn trminos que no estn definidos y que deben recibir un tratamiento
cuidadoso.
Como ejemplo de TAD con operaciones parciales vamos a utilizar el TAD PILA: un apila representa una coleccin de valores donde es posible acceder al ltimo elemento aadido, implementa la idea intuitiva de pila de objetos.
tadPILA[E::ANY]
usa
BOOL
tipo
Pila[Elem]
operaciones
PilaVaca:oPila[Elem] /*gen*/
Apilar:(Elem,Pila[Elem])oPila[Elem] /*gen*/
desapilar:Pila[Elem]oPila[Elem] /*mod*/
cima:Pila[Elem]oElem /*obs*/
esVaca:Pila[Elem]oBool /*obs*/
286
ecuaciones
x:Elem:xs:Pila[Elem]:
defdesapilar(Apilar(x,xs))
desapilar(Apilar(x,xs))=xs
defcima(Apilar(x,xs))
cima(Apilar(x,xs)) =x
esVaca(PilaVaca)=Cierto
esVaca(Apilar(x,xs))=Falso
errores
desapilar(Pilavaca)
cima(PilaVaca)
ftad
Dos de las operaciones se han especificado como parciales, segn indica el uso de o en su
perfil. Cuando una operacin se especifica como parcial, su valor queda indefinido en ciertos
casos. En este ejemplo, despilar y cima quedan indefinidas en los casos indicados en la seccin
errores de la especificacin. La implementacin deber responder adecuadamente hay distintas posibilidades ante la aparicin de estos errores.
Axiomas de definicin
Aparece un nuevo tipo de axiomas en las especificaciones: los axiomas de definicin. En este
ejemplo:
defdesapilar(Apilar(x,xs))
defcima(Apilar(x,xs))
Ntese que slo se han escrito axiomas de definicin para las operaciones parciales. Por convenio, para cada operacin declarada como total en la signatura, con un perfil de la forma:
f:W1,,WnoW
Decimos entonces que un trmino t se considera definido siempre que def t sea deducible a partir de la especificacin del TAD, e indefinido en caso contrario.
En los axiomas de definicin cabe preguntarse si no es necesario exigir que las variables estn
tambin definidas. Sin embargo esto no es necesario; lo que se hace es modificar la regla del
287
clculo referida a los axiomas ecuacionales, imponiendo que slo se pueden hacer sustituciones
de variables por trminos definidos.
Modificaciones a las reglas del clculo ecuacional
La existencia de axiomas de definicin da lugar a la modificacin de algunas reglas del clculo,
y a la inclusin de reglas nuevas relacionadas con la definicin de los trminos.
Reflexividad
deft
t=t
Congruencia
t1=t1tn=tndeff(t1,,tn)
f(t1,,tn)=f(t1,,tn)
Axiomas ecuacionales
C[x1/t1,,xn/tn]deft1deftn
t[x1/t1,,xn/tn]=t[x1/t1,,xn/tn]
si
Igualdad existencial
t=t
deftdeft
por la forma como hemos modificado las reglas del clculo, slo vamos a poder establecer igualdades entre trminos definidos, por lo tanto una igualdad entre trminos implica
la definicin de dichos trminos.
Esto implica que cuando escribamos las ecuaciones de la especificacin slo podemos utilizar trminos definidos.
Operaciones estrictas.
Convenimos en que todas las operaciones de nuestras especificaciones son estrictas, por
lo que
deff(t1,,tn)
deft1deftn
Esta regla, como las anteriores, tambin se puede aplicar hacia atrs de forma que si no
est definido algn ti entonces no est definida f(t1, , tn).
Variables definidas
288
defx
Las variables estn definidas por hiptesis; y slo hay que tener cuidado de que se sustituyen por trminos definidos.
Axiomas de definicin
C[x1/t1,,xn/tn]deft1deftn
si
deft[x1/t1,,xn/tn]
Para que el conjunto de generadoras sea suficientemente completo, debe ocurrir que cada
trmino cerrado y definido t sea T-equivalente a algn trmino cerrado generado y definido t
t:tTDW:t:tTGDW:t=Tt
289
En cuanto a la proteccin de los TAD usados, las condiciones se reescriben para hacer referencia slo a trminos definidos
(i) T no introduce basura en S, es decir: para todo trmino cerrado y definido t:s que se
pueda construir en T, existe un trmino generado y definido t:s que ya se poda
construir en S y tal que t=Tt.
(ii) T no introduce confusin en S, es decir: Si t,t:s son trminos cerrados generados y
definidos que ya se podan construir en S, y se tiene t=Tt, entonces ya se tena
t=St.
Por ltimo, se modifica la definicin del modelo de trminos para considerar slo los
trminos definidos
AW=defTGDW
t=Ttt=tsededucedelaespecificacin
parat,tAW
por ejemplo:
cimaA(PilaVaca) estindefinido
cimaA(desapilarA(Apilar(t2,Apilar(t1,PilaVaca))))=t1
290
3.2.17
Igualdad dbil =d
Igualdad fuerte =f
t =f t def
es decir, no son iguales cuando slo uno de los dos est indefinido
Con la igualdad existencial se pueden expresar los otros dos tipos de igualdades, aprovechndonos de que
t=etodeft
t=dtequivalea
t=etmt=ett=et
es decir, si ambos estn definidos y son iguales (=e) o si alguno no est definido, con
lo que el antecedente de la implicacin es falso, y la implicacin cierta
t=ftequivalea
(t=etmt=et)
(t=etmt=et)
(t=etmt=ett=et)
291
Las operaciones concretas pueden implementarse como procedimientos aunque en la especificacin slo se admitan funciones.
De modo que satisfagan dos requisitos:
Privacidad y proteccin: la estructura interna de los datos debe estar oculta; el nico acceso posible al tipo debe ser a travs de las operaciones pblicas de ste.
Vamos a presentar las condiciones que debemos exigir a una implementacin para que sea correcta con respecto a una especificacin dada. Introduciremos las ideas mediante un ejemplo: la
implementacin del TAD PILA[NAT]. Consideramos que el TAD NAT est implementado como uno de los tipos predefinidos del lenguaje algortmico.
Implementacin del tipo
En primer lugar, tenemos que decidir cmo implementamos el tipo Pila[Nat], es decir, tenemos que elegir un tipo representante. En este caso elegimos representar las pilas como un registro
con dos campos, uno que es un vector donde se almacenan los elementos y otro que es un ndice
que apunta a la cima de la pila dentro del vector
Por ejemplo la representacin de
Apilar(2,Apilar(7,Apilar(5,PilaVaca)))
292
IndCima
Esta implementacin tiene el inconveniente de que impone a priori un lmite al tamao mximo de la pila.
El tipo representante escogido es:
const
limite=100;
tipo
Pila[Nat]=reg
espacio:Vector[1..limite]deNat;
indCima:Nat
freg
Para luego poder plantear una implementacin correcta de las operaciones, conviene comenzar definiendo dos cosas:
cul es el valor abstracto representado por cada valor concreto que sea un representante
vlido.
RPila[Nat](xs)
def
xs:Pila[Nat]
0dxs.indCimadlimite
i:1didxs.indCima:RNat(xs.espacio(i))
La primera condicin es una notacin abreviada, en realidad lo que queremos decir es que xs
es un valor del tipo que representa a Pila[Nat], es decir, el registro.
Las condiciones que se les exigen a los representantes vlidos se denominan invariante de la representacin.
Notacin:
TRW:tiporepresentantedeltipoW
RVW:conjuntoderepresentantesvlidosdeltipoW{DW,eldominioasignado
altipoW
RW:invariantedelarepresentacindeltipoW
Los representantes vlidos son aquellos valores del tipo representante que cumplen el
invariante de la representacin:
293
RVW={x:TRW|RW(x)}
En segundo lugar indicamos una forma de obtener cul es el valor abstracto representado por
cada valor concreto que sea un representante vlido. Para ello definimos de forma recursiva una
funcin de abstraccin de la siguiente forma:
paraxstalqueRPila[Nat](xs)
APila[Nat](xs)=defhazPila(xs.espacio,xs.indCima)
hazPila(v,0)=defPilaVaca
hazPila(v,n)=defApilar(ANat(v(n)),hazPila(v,n1))si1dnd
lmite
ntese que la funcin de abstraccin es una funcin que va de los representantes vlidos en
los trminos generados definidos, es decir, el conjunto de valores para W dado en el modelo de
trminos
AW:RVWoTGDW
Convenios:
Ntese que dos representantes vlidos diferentes pueden tener asociado el mismo valor abstracto. Como se puede comprobar en el siguiente ejemplo:
xs:
ys:
indCima:
espacio:
indCima:
espacio:
294
Apilar(Suc7(Cero),Apilar(Suc2(Cero),Apilar(Suc5(Cero),
PilaVaca)))
Normalmente abreviaremos la notacin no utilizando los valores abstractos para los tipos
primitivos, con lo que escribiramos:
Apilar(7,Apilar(2,Apilar(5,PilaVaca)))
El hecho de que dos representantes vlidos diferentes puedan representar al mismo valor abstracto implica que, en general, la igualdad entre representantes no representa la igualdad entre
valores abstractos. Es por ello, que, en general, no podemos utilizar la igualdad incorporada en
los lenguajes sino que cada TAD en cuya especificacin se incluya una operacin de igualdad,
deber implementarla explcitamente.
Implementacin de las operaciones
Lo que exigimos a las operaciones es que implementen el modelo de trminos. Este hecho lo
reflejaremos en la especificacin pre/post de los subprogramas que implementan a las operaciones del TAD. Para cada operacin supondremos como parte de su precondicin que los argumentos son representantes vlidos de valores abstractos. La postcondicin, a su vez, deber
garantizar que el resultado sea un representante vlido del resultado de la correspondiente operacin abstracta aplicada sobre los valores abstractos que representan los argumentos. Formalmente,
la especificacin pre/post para una funcin fC que implementa una operacin
f:(W1,,Wn)oW
del TAD T
funcfC(x1:TRW1,,xn:TRWn)devy:TRW;
{P0:RW1(x1)RWn(xn)DOMf(x1,,xn)LIMf(x1,,xn)}
{Q0:RW(y)AW(y)=TfA(AW1(x1),,AWn(xn))}
donde
TRWi, TRW son los tipos representantes
RWi, RW son los invariantes de la representacin
AWi, AW son las funciones de abstraccin
DOMf es la condicin de dominio.
Para xi : TRWi tales que RWi(xi)
DOMf(x1, , xn) def f(AW1(x1), , AWn(xn))
las condiciones de DOM pueden de venir expresadas como operaciones sobre trminos abstractos o como condiciones sobre el tipo representante
LIMf expresa restricciones adicionales impuestas por la implementacin
295
Este esquema explica la terminologa invariante de la representacin, pues es una condicin que
deben cumplir tanto los parmetros como los resultados de las operaciones.
Cuando LIMf no es la condicin trivial cierto, decimos que la implementacin es parcialmente correcta.
Hay que tener cuidado con la aparicin de fA en la postcondicin. Cuando el TAD T tiene un
conjunto de generadoras libres, entonces no hay problema. Sin embargo, si el conjunto de generadoras no es libre, entonces en el modelo de trminos no est determinado cul de los trminos
generados pertenecientes a la misma clase de equivalencia es el resultado de la funcin abstracta.
Podramos interpretar que el resultado de fA es uno de los trminos generados que pertenecen
a la misma clase de equivalencia. Otra posibilidad sera haber definido el modelo de trminos
utilizando el conjunto de trminos cannicos en lugar del conjunto de trminos generados.
Ntese que el esquema que hemos presentado generaliza a todas las posibles operaciones de
un TAD, incluyendo generadoras, modificadoras y observadoras.
Veamos algunos ejemplos de cmo se implementaran las operaciones de las pilas de naturales,
con la representacin escogida para el tipo.
funcPilaVacadevxs:Pila[Nat];
{P0:}
inicio
xs.indCima:=0
{Q0:RPila[Nat](xs)APila[Nat](xs)=PILA[NAT]PilaVaca}
devxs
ffunc
Segn el esquema terico, en la postcondicin debera aparecer PilaVacaA, pero el valor de esta funcin es el trmino generado PilaVaca, que es el que hemos utilizado.
Vamos con la otra generadora:
funcApilar(x:Nat;xs:Pila[Nat])devys:Pila[Nat];
{P0:RNat(x)RPila[Nat](xs)xs.indCima<lmite}
var
llena:Bool;
inicio
ys:=xs;
llena:=ys.indCima=lmite;
sillena
entonces
error(Nosepuedeapilarporquelapilaestllena)%**
sino
ys.indCima:=ys.indCima+1;
ys.espacio(ys.indCima):=x
fsi
{Q0:RPila[Nat](ys)APila[Nat](ys)=PILA[NAT]Apilar(ANat(x),APila[Nat](xs))}
devys
296
ffunc
implementar un mecanismo que permite a los clientes del TAD saber si se ha producido
un error y actuar en consecuencia
La funcin cima
funccima(xs:Pila[Nat])devx:Nat;
{P0:RPila[Nat](xs)notesVaca(A(xs))}
inicio
siesVaca(xs) %xs.indCima==0
entonces
error(Lapilavacanotienecima)%*
sino
x:=xs.espacio(xs.indCima)
fsi
{Q0:RNat(x)ANat(x)=PILA[NAT]cimaA(APila[Nat](xs))}**
devx
ffunc
En esta operacin aparece una condicin en la precondicin de tipo DOMf, es decir, una restriccin impuesta por la especificacin.
297
298
En muchos casos consideramos a los valores de un TAD como objetos mutables, de modo que el resultado de la modificadora se lo asignamos a la misma variable que contena el
valor modificado.
La solucin radica en implementar las operaciones como procedimientos, que en las operaciones sobre las pilas daran lugar a las siguientes especificaciones:
procPilaVaca(sxs:Pila[Nat]);
{P0:}
{Q0:RPila[Nat](xs)APila[Nat](xs)=PILA[NAT]PilaVaca}
fproc
procApilar(ex:Nat;esxs:Pila[Nat]);
{P0:xs=XSRNat(x)RPila[Nat](xs)xs.indCima<lmite}
{Q0:RPila[Nat](xs)APila[Nat](xs)=PILA[NAT]Apilar(ANat(x),APila[Nat](XS))}
fproc
procdesapilar(esxs:Pila[Nat]);
{P0:xs=XSRPila[Nat](xs)xs.indCima>0}
{Q0:RPila[Nat](xs)APila[Nat](xs)=PILA[NAT]despilarA(APila[Nat](XS))}
fproc
La implementacin de cima se puede mantener como una funcin, siempre que el tipo de los
elementos lo permita. Y, de la misma forma, podemos mantener como funcin la operacin esVaca.
Es trivial modificar las implementaciones como funciones para llegar a la implementacin como procedimientos.
La implementacin como modelo dbil de la especificacin (no explicar)
El inters de construir una implementacin correcta con respecto a la especificacin radica en
que cualquier equivalencia que sea posible deducir de la especificacin tambin ser vlida para la
implementacin, salvo indefiniciones impuestas por las limitaciones de la implementacin de ah
la debilidad del modelo. Formalmente:
299
Esta idea habra que formalizarla con ms cuidado pues no hemos definido cul es el trmino
concreto, tC, asociado con un trmino cualquiera, t. Lo que se pretende expresar es que la ejecucin
de dos trminos equivalentes da resultados equivalentes.
Verificacin de la correccin de las operaciones
Como ha ocurrido en el ejemplo anterior, no vamos a hacer las verificaciones de que efectivamente las implementaciones de las operaciones son correctas con respecto a las especificaciones pre/post que hemos escrito.
La demostracin de la correccin de una implementacin pasara por la verificacin de todas y
cada una de las implementaciones de las operaciones. el problema radica en que la forma de las
funciones de abstraccin suele ser compleja y, por lo tanto, las verificaciones donde estn inmersas estas funciones tambin lo sern.
3.3.2
Vectores de naturales
300
Vectores de naturales
El invariante de la representacin:
RCjto[Nat](xs)
def
xs:Cjto[Nat]
0dxs.tamaodlimite
i:1didxs.tamao:RNat(xs.espacio(i))
La funcin de abstraccin:
paraxstalqueRCjto[Nat](xs)
ACjto[Nat](xs)=defhazCjto(xs.espacio,xs.tamao)
hazCjto(v,0)=def
hazCjto(v,n)=defPon(ANat(v(n)),hazCjto(v,n1))si1dndlmite
Ntese que como las constructoras no son libres, la funcin de abstraccin puede dar como
resultado trminos generados distintos pero equivalentes.
Vectores de naturales sin repeticin
El invariante de la representacin:
RCjto[Nat](xs)
def
xs:Cjto[Nat]
0dxs.tamaodlimite
i:1didxs.tamao:RNat(xs.espacio(i))
i,j:1di<jdxs.tamao:xs.espacio(i)/=xs.espacio(j)
La funcin de abstraccin es la misma de antes. Tambin aqu puede ocurrir que dos conjuntos equivalentes estn representados por trminos generados diferentes, aunque equivalentes.
Vectores de naturales sin repeticin y ordenados
El invariante de la representacin:
RCjto[Nat](xs)
def
xs:Cjto[Nat]
0dxs.tamaodlimite
i:1didxs.tamao:RNat(xs.espacio(i))
i,j:1di<jdxs.tamao:xs.espacio(i)<xs.espacio(j)
301
La funcin de abstraccin es la misma, pero ahora cada valor concreto vendr representado
por un trmino generado diferente. En este caso la demostracin de la correccin de las operaciones ser ms sencilla pues no habr que preocuparse por equivalencias entre los trminos generados.
Implementacin de la operacin Pon
Vectores de naturales:
procPon(ex:Nat;esxs:Cjto[Nat]);
{P0:xs=XSR(xs)tamao<limite}%nosolvidamosdeR(x)ydelos
subndices
inicio
sitamao==limite
entonces
error(Nosepuedeinsertarelelemento)
sino
xs.tamao:=xs.tamao+1;
xs.espacio(xs.tamao):=x
fsi
{Q0:R(xs)A(xs)=PILA[NAT]Pon(x,A(XS))}
fproc
La operacin Pon
procPon(ex:Nat;esxs:Cjto[Nat]);
{P0:xs=XSR(xs)tamao<limite}
inicio
sitamao==limite
entonces
error(Nosepuedeinsertarelelemento)
sino
sibusca(x,xs.espacio,1,xs.tamao)
entonces
302
seguir
sino
xs.tamao:=xs.tamao+1;
xs.espacio(xs.tamao):=x
fsi
fsi
{Q0:R(xs)A(xs)=PILA[NAT]Pon(x,A(XS))}
fproc
Vectores de naturales sin repeticin y ordenados. Vamos a suponer implementada una funcin
de bsqueda binaria con la siguiente especificacin:
funcbuscaBin(x:Nat;v:Vector[1..limite]deNat;a,b:Nat)devp:
Nat;
r:Bool;
{P0:1dadb+1dN+1ord(v[a..b])}
{Q0:adp+1db+1v[a..p]dx<v[p+1..b]rli:adidb:v(i)
=x}
procPon(ex:Nat;esxs:Cjto[Nat]);
{P0:xs=XSR(xs)tamao<limite}
var
p:Nat;
encontrado:Bool;
inicio
sitamao==limite
entonces
error(Nosepuedeinsertarelelemento)
sino
<p,encontrado>:=buscaBin(x,xs.espacio,1,xs.tamao);
siencontrado
entonces
seguir
sino
desplazaDrch(v,p+1,xs.tamao);
xs.tamao:=xs.tamao+1;
xs.espacio(p+1):=x
fsi
fsi
{Q0:R(xs)A(xs)=PILA[NAT]Pon(x,A(XS))}
fproc
Y la operacin desplazaDrch:
procdesplazaDrch(esv:Vector[1..limite]deNat;ea,b:Nat);
{P0:v=V1dadb+1<N+1}
303
var
k:Nat;
inicio
parakdesdeb+1bajandohastaa+1hacer
v(k):=v(k1)
fpara
{P0:v[a+1..b+1]=V[a..b]v[1..a]=V[1..a]v[b+2..limite]=
V[b+2..limite]}
fproc
Se pueden analizar las diferencias entre estas tres representaciones en trminos de las operaciones Pon y quita.
3.3.3
Al aplicar las reglas de verificacin a programas que usen TADs implementados funcionalmente, las operaciones de estos se pueden tratar del mismo modo que si fuesen operaciones predefinidas.
Esto quiere decir que, en lugar de usar las reglas de verificacin para llamadas a funciones, se
puede usar la regla de la asignacin. Adems, se pueden utilizar todas aquellas propiedades de las
operaciones del TAD en cuestin que se deduzcan de la especificacin de ste, ya que se supone
correcta la implementacin disponible para el mismo.
Si la implementacin del TAD es procedimental en lugar de funcional, se pueden aplicar las
mismas tcnicas reemplazando previamente (a efectos de la verificacin) las llamadas a procedimientos por llamadas a funciones.
Por ejemplo, supuesta una implementacin procedimental del TAD PILA[NAT] queremos
verificar:
var
x:Nat;
xs,ys:Pila[Nat]
{P:xs=Apilar(1,ys)}
x:=cima(xs);
desapilar(xs);
x:=2*x+1
{Q:x=3xs=ys}
Como paso intermedio reescribiremos el programa de manera que todas las operaciones de las
pilas se usen como funciones:
var
x:Nat;
xs,ys:Pila[Nat]
{P:xs=Apilar(1,ys)}
x:=cima(xs);
xs:=desapilar(xs);
304
x:=2*x+1
{Q:x=3xs=ys}
Finalmente, hacemos la verificacin con ayuda de asertos intermedios adecuados, usando las
propiedades especificadas para el TAD PILA[NAT]:
var
x:Nat;
xs,ys:Pila[Nat]
{P:xs=Apilar(1,ys)}
x:=cima(xs);
{R:x=1xs=Apilar(1,ys)}/*1*/
xs:=desapilar(xs);
{S:x=1xs=ys}/*2*/
x:=2*x+1
{Q:x=3xs=ys}/*3*/
Verificacin de /* 1 */
PILA
PILA
pmd(x:=cima(xs),R)
def(cima(xs))R[x/cima(xs)]
def(cima(xs))cima(xs)=1xs=Apilar(1,ys)
P
pmd(xs:=desapilar(xs),S)
def(desapilar(xs))S[xs/desapilar(xs)]
def(desapilar(xs))x=1desapilar(xs)=ys
R
pmd(x:=2*x+1,Q)
def(2*x+1)Q[x/2*x+1]
2*x+1=3xs=ys
S
Verificacin de /* 2 */
Verificacin de /* 3 */
aritmtica(i.e.,NAT)
305
Una estructura de datos se llama esttica cuando el espacio que va a ocupar est determinado
en tiempo de compilacin.
Puede que ese espacio se ubique al principio de la ejecucin variables globales o en la invocacin a procedimientos o funciones variables locales.
La mayora de los tipos predefinidos de los lenguajes de programacin se realizan por medio
de estructuras estticas. Son ejemplos:
los caracteres
los booleanos
los vectores
los registros
Muchos TADs sirven para representar colecciones de datos. La estructura de datos esttica
apta para representar colecciones de datos son los vectores. Sin embargo, para muchos TADs
especificados por el usuario, las implementaciones basadas en vectores tienen inconvenientes
graves que ya hemos mencionado en el tema 3.3 en relacin con las pilas y los conjuntos, y que se
resumen as:
En suma, las estructuras estticas los vectores resultan demasiado rgidas para la implementacin de TADs cuyas operaciones sean capaces de generar valores cuya representacin necesite
una cantidad de espacio potencialmente ilimitado.
306
Se llama estructura de datos dinmica a una estructura que ocupa en memoria un espacio variable durante la ejecucin del programa, y que no se determina en tiempo de compilacin.
Esta clase de estructuras ofrecen la posibilidad de implementar muchos TADs de forma mucho ms flexible: no se malgasta espacio a priori, y el nico lmite en tiempo de ejecucin es la
cantidad total de memoria disponible.
La cuestin es de qu modo podemos crear y manejar esta clase de estructuras?
Punteros
Los punteros son el medio ofrecido por diversos lenguajes de programacin para construir y
manejar estructuras de datos dinmicas.
Las constantes y variables que conocemos hasta ahora son objetos que tienen asociado:
un nombre identificador
un espacio de memoria
un contenido, que es un valor de un cierto tipo fijo en el caso de las constantes y cambiante en el caso de las variables
Podemos utilizar aqu el conocido smil de las variables como recipiente o cajas de valores.
Una variable de tipo puntero tiene igualmente asignado un identificador, un espacio de memoria que es siempre del mismo tamao, independientemente de a dnde est apuntado el puntero y su valor es la direccin de otra variable:
El valor de un puntero1 es la direccin de otra variable, tal que:
El nombre de la variable apuntada por un puntero se puede obtener a partir del identificador del puntero. Al igual que en algunos lenguajes, nosotros obtendremos ese identificador colocando el carcter ^ detrs del identificador del puntero.
Es posible anular una variable apuntada por un puntero, liberando as el espacio que
ocupa. Despus de ser anulada, la variable deja de existir.
Normalmente se abusa del lenguaje y se dice puntero cuando se quiere decir variable de tipo puntero.
307
p^
puntero a W
Diremos que los valores de este tipo son punteros a variables de tipo W.
Las variables de tipo puntero se declaran como cualquier otra variable:
var
p:punteroaW;
308
un puntero p se realiza como un nmero natural que es interpretado como una direccin
de memoria
procubicar(sp:punteroaW)
siendo q un puntero del mismo tipo, tal que q^ ya tuviese espacio ubicado. En este caso, p y q
apuntarn a la misma variable.
Una vez ubicada, p^ se puede utilizar como una variable cualquiera de tipo W.
309
La otra caracterstica fundamental de los punteros es que es posible liberar el espacio ocupado
por las variables a las que apuntan. Esto tambin se hace a travs de un procedimiento predefinido
procliberar(ep:punteroaW)
p:=q esvlidosipyqsondelmismotipo
Ntese que comparamos direcciones y no los valores de las variables a las que apuntan los
punteros, p^ y q^.
310
Como conclusin de este apartado, vamos a seguir grficamente con detalle un ejemplo del
uso de las operaciones sobre punteros.
var
p,q:punteroaEnt;
ubicar(p);
p^:=3;p^:=2*p^;
ubicar(q);
q^:=5;q^:=p^+q^;
p:=q; merror
liberar(p);
liberar(q);merror
Vemos cmo justo antes de empezar la ejecucin ya estn ubicadas las variables p y q, y que
ambas contienen inicialmente basura (#).
3.5.2
La utilidad de los punteros para construir colecciones de datos se obtiene cuando un puntero p
apunta a una variable de tipo registro que contiene en uno de sus campos un puntero del mismo
tipo que p. Pueden usarse entonces los punteros para encadenar entre s registros, formando estructuras dinmicas, ya que ubica y libera podrn usarse en tiempo de ejecucin para aadir o
eliminar registros de la estructura.
Por ejemplo:
tipo
Enlace=punteroaNodo;
Nodo=reg
num:Ent;
sig:Enlace
freg;
Ntese la referencia adelantada al tipo al que apunta el puntero, esto es necesario porque estos
dos tipos son mutuamente dependientes. Normalmente este tipo de dependencia slo se permite
cuando uno de los dos tipos es un puntero.
Con este tipo de punteros podemos crear estructuras como
311
Como resulta evidente en el anterior ejemplo, para poder construir estructuras dinmicas como esta es necesario poder indicar de alguna forma el final de las estructuras. Esto se hace con un
valor especial de tipo puntero: el puntero vaco.
definimos nil como una constante que admite el tipo
punteroaW
para cualquier tipo W, y que representa a un puntero ficticio que no apunta a ninguna variable.
Por lo tanto:
Armados con la constante nil ya podemos construir la estructura dinmica que presentamos
antes grficamente:
var
p,q:Enlace;
ubicar(q);
q^.num:=5;
q^.sig:=nil;
p:=q;
%representacingrficadelestadoactual
ubicar(q);
q^.num:=2;
q^.sig:=p;
p:=q;
%representacingrficadelestadoactual
ubicar(q);
q^.num:=3;
q^.sig:=p;
p:=q;
%representacingrficadelestadofinal
3.5.3
312
Los punteros son una tcnica de programacin de bajo nivel, cuyo uso no slo es bastante engorroso, sino que adems puede conducir a estructuras muy intrincadas nada impide pensar en
nodos con varios punteros cada uno, enlazndose entre s de forma arbitrariamente compleja.
Por consiguiente:
Advertencia: el uso indiscriminado de punteros es peligroso.
Disciplina: nosotros nos limitaremos a usar punteros dentro de los mdulos de implementacin de TADs. Esta restriccin permite sacar partido de la potencia de los punteros sin sacrificar las ventajas de una metodologa de la programacin basada en la abstraccin. Adems, est
de acuerdo con la metodologa de utilizar tipos ocultos. Dado que los punteros son un mecanismo para representar tipos, y puesto que slo permitimos acceder a la representacin interna
de los tipos en los mdulos que los implementan, es razonable prohibir el uso de punteros
fuera de este mbito.
Como ejemplo de la implementacin de TADs mediante estructuras dinmicas vamos a desarrollar una implementacin para las pilas.
Ejemplo: implementacin dinmica de las pilas
La representacin grfica de lo que pretendemos implementar:
p
en
en-1
tipo
Enlace=punteroaNodo;
Nodo=reg
elem:Elem;
sig:Enlace;
freg;
e2
e1
Ntese que con esta representacin el nico lmite impuesto a las dimensiones de
la pila es el tamao de la memoria de la computadora.
Invariante de la representacin
Sea p : Enlace, definimos
RPila[Elem](p)
def
p=nil
(pznilubicado(p)
RElem(p^.elem)RPila[Elem](p^.sig)
pcadena(p^.sig))
313
donde la funcin auxiliar cadena permite especificar que no hay dos punteros apuntando al
mismo nodo:
cadena(nil)=def
cadena(p)=def{p}cadena(p^.sig)sipznil
La idea de este invariante de la representacin es que de p arranca una cadena de punteros finita, sin repeticiones y acabada en nil, cada uno de los cuales apunta a la representacin correcta de
un elemento. Lo que no veo es cmo se indica en este invariante que la cadena ha de terminar.
Funcin de abstraccin
Sea p tal que RPila[Elem](p)
APila[Elem](p)=PilaVaca sip=nil
APila[Elem](p)=Apilar(AElem(p^.elem),APila[Elem](p^.sig))sipznil
mduloimplPILA[ELEM]
%aqunohacefaltaimportarELEMporqueyaestimportadoenelde
especificacin
privado
tipo
Enlace=punteroaNodo;
Nodo=reg
elem:Elem;
sig:Enlace;
freg;
Pila[Elem]=Enlace;
funcPilaVacadevxs:Pila[Elem];
{P0:Cierto}
inicio
xs:=nil
{Q0:R(xs)A(xs)=PILA[ELEM]PilaVaca}
devxs
ffunc
314
funcApilar(x:Elem;xs:Pila[Elem])devys:Pila[Elem];
{P0:R(x)R(xs)}
inicio
ubicar(ys);
ys^.elem:=x;
ys^.sig:=xs;
{Q0:R(ys)A(ys)=PILA[ELEM]Apilar(A(x),A(xs))}
devys
ffunc
Ntese que esta implementacin de apilar introduce comparticin de estructura entre xs e ys.
Otra opcin sera realizar una copia de xs. A continuacin haremos algunas consideraciones sobre estas cuestiones.
funcdesapilar(xs:Pila[Elem])devys:Pila[Elem];
{P0:R(xs)notesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(nosepuededesapilardelapilavaca)
sino
ys:=xs^.sig
fsi
{Q0:R(ys)A(ys)=PILA[ELEM]despilar(A(xs))}
devys
ffunc
Otra vez comparticin de estructura. Ntese tambin que no estamos anulando la cima de xs.
funccima(xs:Pila[Elem])devx:Elem;
{P0:R(xs)notesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(Lapilavacanotienecima)
sino
x:=xs^.elem
fsi
{Q0:R(x)A(x)=PILA[ELEM]cima(A(xs))}
devx
ffunc
funcesVaca(xs:Pila[Elem])devr:Bool;
{P0:R(xs)}
inicio
r:=xs==nil
315
{Q0:rlesVaca(A(xs))}{RBool(r)ABool(r)=PILA[ELEM]esVaca(
APila[Elem](xs))
devr
ffunc
procerror(eCadena:Vector[1..lmite]deCar);
{P0:Cierto}
inicio
escribir(Cadena);
abortar
{Q0:Falso}
fproc
fmdulo
var
p,p1,p2,p3,p4:Pila[Car];
p:=PilaVaca();
p1:=Apilar(b,Apilar(a,p));
p2:=Apilar(c,p1);
p3:=Apilar(d,p1);
p4:=Apilar(e,p3);
p3:=Apilar(f,p3);
p1:=desapilar(p4);
p2
b
a
Qu ocurre si ahora hacemos lo siguiente?
p:=PilaVaca();
f
p1
p3
316
p1:=PilaVaca();p2:=PilaVaca();
p3:=PilaVaca();p4:=PilaVaca();
Toda la memoria dinmica que ocupaban las pilas ha pasado a estar inaccesible: se ha generado basura en la memoria dinmica.
La solucin a este problema pasa, en primer lugar, por aadir al TAD una operacin que se
encargue de liberar todo el espacio ocupado por un valor de ese tipo. Habr que invocar a esta
operacin cada vez que deseemos reinicializar una variable con un nuevo valor. A esta operacin
la denominamos anular y en el caso de las pilas se implementara de la siguiente manera:
procanular(esxs:Pila[Elem]);
{P0:R(xs)}
var
p:Enlace;
inicio
itnotesVaca(xs)o
p:=xs;
xs:=xs^.sig;
liberar(p)
fit
{Q0:R(xs)A(xs)=PILA[ELEM]PilaVaca}
fproc
Sin embargo esta solucin no es completa pues los elementos tambin pueden ser estructuras
dinmicas, en cuyo caso sera necesario anularlas:
procanular(esxs:Pila[Elem]);
{P0:R(xs)}
var
p:Enlace;
inicio
itnotesVaca(xs)o
p:=xs;
xs:=xs^.sig;
ELEM.anular(p^.elem);
liberar(p)
fit
{Q0:R(xs)A(xs)=PILA[ELEM]PilaVaca}
fproc
De esta forma estamos exigiendo que los posibles parmetros de PILA[ELEM] implementen
una operacin anular. Esta restriccin deberamos expresarla en la definicin de la clase de tipos.
Puede plantearse que esa operacin no tiene sentido si el tipo se ha implementado con una es-
317
tructura esttica. Sin embargo, siguiendo la filosofa de que el usuario no debe conocer los detalles de la representacin interna, todos los TAD deberan exportar una operacin de anular que
en el caso de las implementaciones estticas no hara nada, pues al liberar el nodo ya se devuelve
el espacio ocupado por el valor. Otro problema son los tipos predefinidos, para los cuales suponemos que est definida la operacin anular y que se comporta como en el caso de las estructuras
estticas.
En cuanto al uso de anular
Se debe invocar anular antes de reinicializar una variable con otro valor. En el ejemplo anterior, se debera sustituir:
p1:=PilaVaca();
por
anular(p1);
p1:=PilaVaca();
tambin es necesario invocar anular sobre las variables locales de los procedimientos justo antes de la salida del procedimiento, siempre y cuando la variable local no comparta estructura con
un parmetro de salida de dicho procedimiento.
Evidentemente slo se debe invocar a anular si la variable contena algn valor.
Pero, qu ocurre con la estructura compartida? En el anterior ejemplo, si anulsemos p1 antes
de asignarle otro valor, corromperamos el estado de todas las dems pilas, que estn compartiendo estructura con p1.
Por lo tanto, si permitimos la comparticin de estructura no podemos anular las variables, pero si no podemos anular las variables puede ocurrir que dejemos basura en la memoria dinmica.
cul es la solucin? Algunos lenguajes incluyen un mecanismo que se denomina recogida automtica de basura. Este mecanismo controla cuntas referencias hay a cada espacio de la memoria, de forma que cuando detecta que una zona de la memoria ya no es accesible desde ningn
puntero, la libera automticamente. En los lenguajes que tienen recogida de basura no es necesario preocuparse por las anulaciones, pues estas tendrn lugar automticamente. El inconveniente
de la recogida automtica de basura es que este mecanismo ralentiza la ejecucin de los programas; como es habitual, los mecanismos que facilitan la vida de los programadores tienen un cierto
coste computacional.
Si queremos mantener la implementacin funcional, la solucin por la que debemos optar entonces es la de realizar copias de los parmetros de las operaciones para evitar as la comparticin
de estructura aunque esta implementacin de las pilas permite la comparticin de estructura,
salvo por el problema de la anulacin, en algunas implementaciones de TADs es obligatorio realizar las copias de los parmetros porque si no los argumentos dejaran de ser representantes vlidos del tipo en cuestin. Sin embargo, esta solucin aumenta innecesariamente el coste de las
operaciones, en muchos casos de manera ridcula:
Funcionara bien en un caso como este:
318
p2:=Apilar(a,p1);
hemos generado basura pues no hemos anulado el valor original de p1, por lo tanto habra que
hacer algo tan artificioso como esto:
p2:=p1;
p1:=Apilar(a,p1);
anular(p2);
Este ltimo ejemplo nos sugiere otra solucin que es la que realmente emplearemos: implementar las generadoras y modificadoras como procedimientos, de forma que consideremos a los
valores como objetos mutables accesibles a travs de una nica referencia.
Realizacin de las operaciones como procedimientos
Planteamos la implementacin de las generadoras y modificadoras como procedimientos:
procPilaVaca(sxs:Pila[Elem]);
{P0:Cierto}
inicio
xs:=nil
{Q0:R(xs)A(xs)=PILA[ELEM]PilaVaca}
fproc
procApilar(ex:Elem;esxs:Pila[Elem]);
{P0:xs=XSR(x)R(xs)}
var
p:Enlace;
inicio
ubicar(p);
p^.elem:=x;
p^.sig:=xs;
xs:=p
{Q0:R(xs)A(xs)=PILA[ELEM]Apilar(A(x),A(XS))}
Fproc
procdesapilar(esxs:Pila[Elem]);
{P0:xs=XSR(xs)notesVaca(A(xs))}
319
var
p:Enlace;
inicio
siesVaca(xs)
entonces
error(nosepuededesapilardelapilavaca)
sino
p:=xs;
xs:=xs^.sig;
%ELEM.anular(p^.elem);
liberar(p)
fsi
{Q0:R(xs)A(xs)=PILA[ELEM]desapilar(A(XS))}
fproc
En esta implementacin podramos dejar las operaciones cima y esVaca como funciones. Sin
embargo, esto podra dar problemas para la operacin cima si los elementos fuesen de un tipo que
estuviese implementado con memoria dinmica, porque dara lugar a comparticin de estructura,
la solucin sera devolver una copia, o que el cliente del TAD hiciera una copia si lo considerase
necesario. En el siguiente apartado comentaremos la necesidad de incorporar una operacin de
copia en todos los TADs.
En definitiva, no prohibimos definitivamente la comparticin de estructura ya que en algunos
casos para evitarla habra que incurrir en graves ineficiencias. Por ejemplo, un rbol se construye
a partir de la informacin de la raz y dos subrboles. Para evitar la comparticin, se deberan
realizar copias de los subrboles, sin embargo, en la mayora de los casos estos se han creado con
el nico propsito de formar parte de un rbol mayor. Como implementadores del TAD debemos informar del hecho y dejar en manos del cliente la decisin de si desea realizar copias o no.
3.5.4
Agotamiento inesperado de la memoria. Evidentemente la memoria dinmica no es ilimitada. Puede ocurrir que en un punto de la ejecucin ya no sea posible ubicar ms variables
dinmicas. Una forma de paliar este problema es comprobar antes de ubicar una nueva
variable si existe espacio suficiente, para en caso negativo no intentar ubicarla. Para saber
si la ubicacin tendr xito los lenguajes de programacin suelen incorporar operaciones
predefinidas para obtener la memoria disponible en Pascal MemAvail y el tamao de un
cierto tipo de datos en Pascal SizeOf.
Los errores en el uso de los punteros pueden provocar fallos irrecuperables cuelgues del
ordenador y difciles de detectar
320
cin y destruccin de estructuras dinmicas lleva ms tiempo que la creacin y destruccin de estructuras estticas, de la que se encarga automticamente el compilador.
Existen adems problemas especficos de la implementacin de TADs. Lo ideal es poder resolver estos problemas de forma transparente para los clientes del TAD de forma que ni siquiera
tengan que saber si la implementacin del TAD es esttica o dinmica. Sin embargo esto no es
realista, pues resolverlo de esta forma nos obligara a realizar algunas operaciones de manera innecesariamente ineficiente necesidad de realizar copias y anulaciones. La solucin por la que
optamos es dejar una parte de las decisiones en manos del cliente del TAD, que, por lo tanto,
debe ser consciente de si se trata de una representacin esttica o dinmica.
Almacenamiento de los datos en disco
Los datos representados por estructuras dinmicas no pueden escribirse y leerse en archivos
directamente, porque los valores de los punteros direcciones en cada ejecucin particular sern
diferentes.
La escritura de estructuras dinmicas se limitar a escribir la informacin los elementos sin
guardar los valores de los punteros. La lectura deber reconstruir las estructuras dinmicas, a partir de los datos ledos del archivo, solicitando nueva memoria dinmica donde almacenar los datos ledos.
Los TADs debern exportar operaciones de escritura y lectura de archivo siempre que estas
puedan resultar necesarias para los usuarios:
guardar(x,a) recuperar(x,a)
Asignacin
Si x, y son variables de un mismo tipo abstracto, el efecto de la asignacin es diferente dependiendo de si el tipo representante es esttico o dinmico.
causa que se copie en la zona de memoria designada por x el valor almacenado en la zona
de memoria designada por y. Se mantiene la independencia entre los dos valores, las modificaciones de uno de ellos no afectarn al otro.
En una implementacin dinmica la asignacin implica comparticin de estructura
x:=y
causa que x, y apunten a la misma estructura dinmica. Los cambios realizados en la estructura a travs de una variable afectarn a la estructura puntada por la otra.
Por ejemplo:
var
p,q:Pila[Nat];
321
PilaVaca(p);
Apilar(1,p);
q:=p;
despilar(q);
p dejara de ser un representante vlido pues estara apuntando a un nodo que ha sido liberado al desapilar de q.
Una solucin a este problema sera imponer la disciplina de que no se pueden realizar asignaciones entre valores de TADs, y que en caso de ser necesarias se exporte en el TAD una operacin encargada de ello. Esta operacin tendra semntica de copia. Sin embargo, esta solucin es
demasiado restrictiva pues en algunos casos al cliente puede no preocuparle la comparticin de
estructura. Lo que debemos hacer entonces es exportar en todos los TADs una operacin que
permita realizar copias de los valores del TAD, para que as el cliente pueda elegir entre las dos
opciones.
proccopiar(ex:W;sy:W)
proccopiar(exs:Pila[Elem];sys:Pila[Elem]);
{P0:R(xs)}
var
p,q1,q2:Enalce;
inicio
siesVaca(xs)
entonces
PilaVaca(ys)
sino
ubicar(ys);
ys^.elem:=xs^.elem;%ELEM.copiar(xs^.elem,ys^.elem)
p:=xs^.sig;
q1:=ys;
itp/=nilo
ubicar(q2);
q2^.elem:=p^.elem;%ELEM.copiar(p^.elem,q2^.elem)
q1^.sig:=q2;
p:=p^.sig;
322
q1:=q2
fit;
q1^.sig:=nil
fsi
{Q0:R(ys)A(xs)=A(ys)}
fproc
p^.elem:=x
si Elem est implementado con una estructura dinmica, esto dara lugar a comparticin de estructura:
var
p:Pila[Nat];
pp:Pila[Pila[Nat]];
PilaVaca(pp);
PilaVaca(p);
Apilar(1,p);
apilar(p,pp); mcomparticindeestructura
desapilar(p); msehacorrompidopp
Una solucin sera realizar copias de los parmetros de entrada antes de incorporarlos a la estructura. Sin embargo, de nuevo, esto produce una ineficiencia intolerable. La solucin es advertir
de esta circunstancia a los clientes del TAD, para que sean ellos quienes decidan si conviene o no
realizar una copia de los parmetros antes de realizar la invocacin.
323
Igualdad
El tipo representante de un tipo abstracto puede no admitir el uso de la comparacin de igualdad. Y aunque la admitiese, la identidad entre valores del tipo representante en general no corresponder a la igualdad entre los valores abstractos representados. Este problema se presenta por
igual en las implementacin estticas como ya vimos en el ejemplo de la implementacin esttica
de las pilas como en las dinmicas; aunque en las estticas puede o no ocurrir mientras que en
las dinmicas sucede siempre.
La solucin radica en que el TAD exporte una operacin de comparacin:
funciguales(x,y:W)devr:Bool;
Puede ocurrir que la propia signatura de la especificacin algebraica del TAD incluyese una
operacin de igualdad. si esto no es as, y si el mdulo de datos tampoco la exporta, los usuarios
del TAD debern entender que no se les permite realizar comparaciones de igualdad.
Como ejemplo, vemos cmo se implementara la funcin iguales para el caso de las pilas:
funciguales(xs,ys:Pila[Elem])devr:Bool;
{P0:R(xs)R(ys)}
var
p,q:Enlace;
inicio
<p,q>:=<xs,ys>;
r:=cierto;
it(p/=nil)and(q/=nil)andro
r:=p^.elem==q^.elem;%r:=ELEM.iguales(p^.elem,q^.elem)
<p,q>:=<p^.sig,q^.sig>
fit;
r:=rand(p==nil)and(q==nil);
{Q0:rlA(xs)=A(ys)}
devr
ffunc
Generacin de basura
Como ya hemos indicado al comentar la implementacin dinmica de las pilas, puede ocurrir
que zonas de la memoria dinmica estn marcadas como ocupadas pero no sean accesibles desde
ningn puntero.
Esta situacin puede ocurrir con:
324
Variables que se reinicializan al ser utilizadas como parmetro de salida de un procedimiento. La estructura a la que apuntaba la variable anteriormente se convierte en basura.
En estos tres casos no tiene que generarse basura necesariamente, slo ser as cuando la variable afectada sea la nica referencia a la correspondiente estructura dinmica, es decir, si dicha
estructura no est compartida por ms de una referencia.
La solucin como ya vimos ms arriba radica en que el mdulo que implementa el TAD exporte una operacin que libere la estructura dinmica que representa al valor:
procanular(esx:W);
Para los TAD que implementen el tipo con una estructura esttica operacin anular no har
nada.
Los clientes del TAD debern invocar a la operacin anular:
Referencias perdidas
Este problema se produce cuando un puntero p queda con un valor diferente de nil, pero
habiendo sido liberado el espacio al que apunta. Por ejemplo, la siguiente serie de acciones hara
que la referencia de p quedase perdida:
ubicar(q);p:=q;liberar(q)
La solucin radica en no liberar ninguna estructura que est siendo compartida por ms de
una referencia.
Estructura compartida
Este problema ha aparecido en prcticamente todos los que hemos descrito hasta ahora. Decimos que hay estructura compartida cuando dos o ms punteros comparten todo o una parte de
la estructura dinmica a la que apuntan. El problema es que los cambios realizados en la estructu-
325
ra a travs de un puntero afectan a la estructura apuntada por los dems, pudiendo incluso ocurrir
que se deje de verificar el invariante de la representacin.
En los anteriores puntos ya hemos ido viendo las situaciones que pueden dar lugar a comparticin de estructura:
operaciones que construyen una estructura dinmica aadindole nodos a otra ya existente.
Puede provocar efectos colaterales: las modificaciones de una variable afectan a otras
Prohibir la comparticin de estructura. La ventaja es que no se producen efectos colaterales inesperados y el inconveniente es que se aumenta el nmero de copias y anulaciones
necesarias con lo que se degrada, en muchos casos de forma intolerable, la eficiencia de
los algoritmos.
En los lenguajes con recogida automtica de basura no existe el problema de la anulacin. El usuario es responsable de realizar las copias que sea necesario.
En los lenguajes sin recogida automtica de basura el cliente del TAD es el responsable de su buen uso, realizando las copias y anulaciones que sea necesario.
326
3.6 Ejercicios
Introduccin a la programacin con TADs
131. En cada uno de los apartados siguientes, escribe cabeceras de funciones que especifiquen
el comportamiento deseado para las operaciones del TAD que se sugiere en cada caso, sin
hacer ninguna suposicin acerca de la representacin concreta de los datos del TAD.
(a) TAD de los nmeros complejos, con el tipo Complejo y operaciones adecuadas para
133. Para cada uno de los TADs del ejercicio 131, sugiere una o varias posibilidades para re-
presentar los datos del TAD usando tipos de datos conocidos (tales como registros, vectores, etc.). Influye la eleccin de la representacin en la eficiencia de las operaciones del
TAD? De qu manera?
leanos, junto con las operaciones constantes Cierto, Falso y las operaciones booleanas not,
(and) y (or)
327
135. Usando BOOL, especifica algebraicamente un TAD NAT que ofrezca el tipo Nat de los
nmeros naturales, con operaciones Cero, Suc, (+), (*), (==), (/=), (), (), (<) y (>).
136. Escribe la signatura de un TAD REAL que ofrezca el tipo Real de los nmeros reales, jun-
137. Usando el TAD REAL del ejercicio anterior, especifica algebraicamente un TAD
COMPLEJO que ofrezca el tipo Complejo junto con las operaciones consideradas en el ejercicio 131(a).
138. Usa razonamiento ecuacional en NAT para demostrar la validez de las ecuaciones siguientes:
(a) Suc(Cero)+Suc(Cero)=Suc(Suc(Cero))
(b) Suc(Cero)*Suc(Cero)=Suc(Cero)
(c) (Suc(Cero)*Suc(Cero)==Suc(Cero))=Cierto
(d) (Suc(Suc(Cero))+Suc(Suc(Cero))Suc(Suc(Suc(Cero))))=Falso
139. Sea T un TAD con tipo principal W, Dos trminos t,t:W se llaman T-equivalentes (en
smbolos t=Tt), si la ecuacin t=t se puede deducir a partir de los axiomas ecua-
cionales de T.
(a) Comprueba que la T-equivalencia es una relacin de equivalencia.
(b) Encuentra varios trminos que sean NAT-equivalentes a Suc(Cero)).
140. Observa las especificaciones de los TADs BOOL, NAT y COMPLEJO. En cada caso,
141. Dado un TAD T con tipo principal W, se llaman trminos generados a aquellos trminos de
tipo W que se pueden construir usando nicamente operaciones generadoras de T (y trminos generados de otros TADs usados por T, si es necesario). Indica cules son los trminos
generados de los TADs BOOL, NAT y COMPLEJO.
142. Sea T un TAD con tipo principal W. Se dice que el conjunto de operaciones generadoras
Pista: Para (a), (b) usa induccin sobre y. Para (c) usa induccin sobre x, aplicando (a), (b).
144. Usa razonamiento inductivo en BOOL para demostrar que las operaciones (and) y (or) son
conmutativas; es decir, demuestra que para x, y : Bool cualesquiera se cumplen las
ecuaciones:
328
(a) xandy=yandx
(b) xory=yorx
145. Construye un modelo de BOOL tal que el soporte del tipo Bool sea un conjunto de tres
elementos: Cierto, Falso y Nodef (que quiere representar un valor booleano indefinido). Interpreta las operaciones not, (and), y (or) en este modelo de manera que las ecuaciones de
BOOL se cumplan. Compara con el modelo de trminos de BOOL.
146. Sea T un TAD con tipo principal W cuya especificacin algebraica usa otro TAD S con
tipo principal s. Se dice que T protege a S si se cumplen las dos condiciones siguientes:
(i) T no introduce basura en S, es decir: para todo trmino cerrado t : s que se pueda
construir en T, existe un trmino generado t:s que ya se poda construir en S y
tal que t=Tt.
(ii) T no introduce confusin en S, es decir: Si t,t:s son trminos cerrados generados
que ya se podan construir en S, y se tiene t=Tt, entonces ya se tena t=St.
(a) Demuestra que el siguiente TAD que usa a BOOL no protege a BOOL, porque in-
tadPERSONA
usa
BOOL
tipo
Persona
operaciones
Carmen,Roberto,Luca,Pablo:oPersona
gen*/
/*
feliz:PersonaoBool
obs*/
ecuaciones
feliz(Carmen) =feliz(Roberto)
feliz(Pedro) =feliz(Luca)
feliz(Pedro) =notfeliz(Pablo)
feliz(Luca) =Cierto
feliz(Pablo) =Cierto
ftad
/*
(b) Construye un TAD que use a BOOL introduciendo basura, pero no confusin.
(c) Construye un TAD que use a BOOL introduciendo confusin, pero no basura.
(d) Comprueba que NAT, que usa a BOOL, deja a BOOL protegido.
Importante: Siempre que un TAD T se especifique usando otro TAD S ya especificado anteriormente, debe hacerse de manera que S quede protegido.
147. Usando NAT, especifica un TAD ENT que ofrezca el tipo Ent de los nmeros enteros,
con operaciones Cero, Suc, Pred, (+), (), (*) y (^) debe especificarse de modo que represente
la operacin de exponenciacin con base entera y exponente natural.
329
148. Sea T un TAD con tipo principal W. Se dice que las operaciones generadoras de T son no
149. Sea T un TAD con tipo principal W. Se llama sistema de representantes cannicos a cualquier
subconjunto TCW del conjunto TGW de todos los trminos generados y cerrados de tipo W,
tal que para cada t TGW exista un nico t TCW tal que t =T t. Construye sistemas de
representantes cannicos para los TADs BOOL, NAT y ENT. Observa que en el caso de
ENT hay varias formas posibles de elegir los representantes cannicos, debido a que las
generadoras no son libres.
150. Especifica algebraicamente un TAD ENT-ORD que enriquezca el TAD ENT aadiendo
una indeterminada con coeficientes enteros, junto con las operaciones consideradas en el
ejercicio 131(d). Observa que las operaciones generadoras de este TAD no son libres, y que
resulta conveniente utilizar operaciones privadas en la especificacin.
TADs genricos
152. Llamaremos clase de tipos a una familia de TADs caracterizados por disponer de determi-
nados tipos y operaciones, indicados en la especificacin de la clase. Especifica las siguientes clases de tipos:
(a) ANY: Clase formada por todos los TADs que tengan un tipo principal Elem.
(b) EQ: Clase formada por todos los TADs de la clase ANY que posean adems opera-
clase. Indica ejemplares de las clases ANY, EQ y ORD. Observa que diferentes TADs
miembros de una misma clase de tipos pueden tener diferentes signaturas.
154. Los TADs parametrizados (tambin llamados genricos) tienen un parmetro formal que re-
presenta a otro TAD, obligado a ser miembro de cierta clase de tipos. Construye una especificacin de un TAD genrico CJTO[E :: EQ] que ofrezca el tipo Cjto[Elem] formado por
los conjuntos (finitos) de elementos de tipo Elem (dado por el TAD parmetro E), junto
con operaciones para crear un conjunto vaco, aadir y quitar un elemento a un conjunto,
reconocer el conjunto vaco, y reconocer si un elemento dado pertenece a un conjunto dado.
330
155. Una vez especificado un TAD genrico, es posible declarar ejemplares suyos. Cada ejemplar
corresponde a un cierto reemplazamiento del parmetro formal del TAD genrico por un
parmetro actual. Declara dos ejemplares diferentes del TAD genrico del ejercicio anterior, tomando como parmetro actual NAT y ENT-ORD, respectivamente.
156. Enriquece la especificacin del TAD genrico CJTO[E :: EQ], aadiendo nuevas opera-
ciones para calcular uniones, intersecciones, diferencias y cardinales de conjuntos, y operaciones de comparacin (==), (/=) entre conjuntos. Naturalmente, debes aadir tambin las
ecuaciones necesarias para especificar el comportamiento de las nuevas operaciones. Las
ecuaciones de (==) deben construirse de manera que, dados dos trminos generados cualesquiera t,t:Cjto[Elem], se verifiquen las dos condiciones siguientes:
(i) t=t es deducible si y slo si es deducible (t==t)=Cierto.
(ii) t=t no es deducible si y slo si es deducible (t==t)=Falso.
157. Un TAD genrico puede tener ms de un parmetro formal. Formula una especificacin
158. Enriquece la especificacin del TAD del ejercicio anterior, obteniendo un nuevo TAD
PAREJA-ORD[A,B :: ORD] que ofrezca operaciones de igualdad y orden entre las parejas.
Observa que ahora es necesario exigir que los parmetros formales A, B representen TADs
miembros de la clase de tipos ORD. Cualquier ejemplar de PAREJA-ORD tambin ser
miembro de la clase ORD.
la[Elem] formado por las pilas de elementos de tipo Elem (dado por el TAD parmetro),
junto con operaciones que permitan crear una pila vaca, apilar un elemento en una pila,
consultar y desapilar el elemento de la cima de una pila no vaca, y reconocer si una pila es
vaca o no. Observa que las operaciones desapilar y cima son parciales, por lo cual la especificacin del TAD incluye clusulas que determinan su dominio de definicin.
160. Siempre que un TAD tenga operaciones parciales, puede ocurrir que ciertos trminos
estn indefinidos. Un trmino t se considera definido siempre que def t sea deducible a partir
de la especificacin del TAD, e indefinido en caso contrario. Declara el TAD PILA[NAT] de
las pilas de nmeros naturales como ejemplar del TAD genrico del ejercicio anterior, y escribe varios ejemplos de trminos definidos e indefinidos de tipo Pila[Nat].
161. Para TADs con operaciones parciales, es necesario revisar algunos de los conceptos que
Para que el conjunto de generadoras sea suficientemente completo (ej. 142), debe ocurrir que cada trmino cerrado y definido t sea T-equivalente a algn trmino cerrado
generado y definido t.
331
En cualquier especificacin que use ecuaciones condicionales, las ecuaciones de las condiciones deben usar igualdad existencial.
163. Enriquece el TAD de las pilas aadiendo dos nuevas operaciones, especificadas infor-
332
164. Especifica un TAD genrico MCJTO-MIN[E :: ORD] que ofrezca el tipo MCjto[Elem]
formado por los multiconjuntos de elementos de tipo Elem (dado por el TAD parmetro),
junto con operaciones similares a las consideradas en el ejercicio 131(b). Observa que el
TAD del ejercicio 131(b) se puede especificar como ejemplar del TAD genrico de este
ejercicio.
166. Plantea una implementacin de las operaciones del TAD PILA[NAT] usando la represen-
167. Disea tres posibles representaciones para el TAD CJTO[NAT], formulando en cada
168. Plantea implementaciones de la operacin Pon usando las tres representaciones del ejerci-
169. Disea implementaciones de la operacin quita usando las tres representaciones del ejerci-
170. Plantea dos implementaciones para el TAD POLI del ejercicio 151,
(a) representando los polinomios mediante vectores de parejas, cada pareja formada por
un coeficiente y un exponente.
(b) representando los polinomios mediante vectores de parejas como en (a), pero exigiendo que el vector est ordenado por exponentes.
En cada caso, formula el invariante de la representacin, la funcin de abstraccin, y las especificaciones Pre/Post de los procedimientos y funciones que realicen las operaciones de POLI.
Intenta comparar la eficiencia de las dos implementaciones.
Implementacin modular de TADs
171. Plantea una implementacin modular del TAD COMPLEJO del ejercicio 137, en dos
fases:
333
172. Plantea una implementacin modular de un ejemplar PILA[ELEM] del TAD genrico
173. Plantea implementaciones modulares para otros TADs ya estudiados, tales como POLI,
Punteros
174. Representa grficamente el efecto de ejecutar lo siguiente:
var
p,q:punteroaEnt;
ubicar(p);
p^:=3;p^:=2*p^;
ubicar(q);
q^:=5;q^:=p^+q^;
p:=q;
liberar(p);
liberar(q);
Dara lo mismo omitir la orden liberar(p)? Queda liberado al final todo el espacio que se
ha ido solicitando?
175. Escribe un fragmento de programa cuya ejecucin construya la estructura correspondien-
176. Escribe ahora un fragmento de programa que libere el espacio ocupado por la estructura
creada en el ejercicio anterior. Ten en cuenta que cada llamada liberar el espacio de un solo registro.
334
CAPTULO 4
El anlisis de la complejidad de las implementaciones de TADs se basa en los conceptos generales que hemos estudiado en la primera parte de la asignatura: complejidad en el caso peor y en
promedio; rdenes de magnitud asintticos; Estas mismas ideas se pueden aplicar para analizar por separado la complejidad de los algoritmos que implementan cada una de las operaciones
de un TAD.
En este apartado haremos algunas consideraciones sobre la complejidad espacial y nos concentraremos en el problema especfico de analizar la complejidad de algoritmos que utilizan
TADs, donde interesa obtener la complejidad de secuencias de llamadas a las operaciones del
TAD.
Complejidad en espacio
El anlisis del espacio tiene especial relevancia en las implementaciones de TADs, ya que stas
emplean muchas veces estructuras de datos muy voluminosas.
Ya hemos visto ejemplos donde distintas elecciones del tipo representante dan lugar a requisitos de espacio diferentes. Sin embargo lo ms habitual es que las distintas representaciones correspondan a un coste en espacio del mismo orden de magnitud asinttico. En tales casos, puede
interesar analizar con ms detalle el coste real teniendo en cuenta los valores concretos de las
constantes multiplicativas, ya que las optimizaciones solamente se podrn plantear a este nivel de
anlisis.
Para ms detalles puede consultarse [Fra94] pp: 110-111.
Complejidad temporal: anlisis de sucesiones de llamadas
Al analizar la complejidad de algoritmos que utilizan TADs interesa ocuparse de sucesiones de
llamadas, ya que, en muchos casos, las operaciones modifican el tamao del valor, de forma que
el coste temporal de una llamada se ve afectado por las llamadas que sobre ese mismo valor se
han realizado con anterioridad.
Dada una implementacin de un TAD con operaciones:
f1,f2,,fr
335
Sean
T1,T2,,Tr
las funciones que miden la complejidad en tiempo, en el caso peor, de los algoritmos que implementan las r operaciones.
Queremos estimar el coste de realizar m llamadas a operaciones del TAD, siendo:
ti tiempoconsumidoporlaisimallamada
ji ndicedelaoperacindelaisimallamada(1djidr)
ni1,ni tamaodelosdatosantesydespusdelaisimallamada
n
max:1didm:ni
mj nmerodellamadasafj(1djdr)
ti
i 1
d
m
Tji(ni1)
i 1
d(silasTjsonmontonas)
m
Tji(n)
i 1
=
m1T1(n)++mrTr(n)
Ntese que este anlisis estndar se puede realizar sin conocer los detalles de la implementacin, basta con que sean conocidas las funciones Tj(n).
Anlisis amortizado de sucesiones de llamadas
Los resultados del anlisis estndar que acabamos de realizar siempre dan una cota superior
correcta, pero muchas veces sta es demasiado grande. El anlisis estndar es demasiado pesimista, bsicamente debido a la simplificacin que supone sustituir los diferentes tamaos ni1 por el
mximo n de todos ellos. Esto podemos observarlo con un ejemplo.
Consideremos una variante del TAD PILA[E :: ELEM] donde la operacin desapilar se sustituya por
desapilarK:(Nat,Pila[Elem])oPila[Elem]
336
En una implementacin tpica de este TAD, la complejidad de las operaciones en el caso peor
sera como sigue:
fj
PilaVaca
Apilar
desapilarK
esVaca
cima
Tj(n)
O(1)
O(1)
O(n)
O(1)
O(1)
suponiendo que n mida el tamao de la pila. Ntese que desapilarK es O(n) porque el caso peor
se da cuando se desapilan todos los elementos.
Para una sucesin compuesta por m1 llamadas a Apilar y m2 llamadas a desapilarK, el anlisis
estndar nos dara como estimacin de la complejidad:
O(m1+m2n)
Esta estimacin se puede mejorar bastante si suponemos que la pila est inicialmente vaca
(n0=0) y tenemos en cuenta que, bajo este supuesto, el nmero total de elementos desapilados no
puede exceder al nmero total de elementos apilados. Este razonamiento demuestra que
O(m1)
tiempoamortizado=deftiempo+'potencial
ai=ti+pipi1
siendo
ai
ti
pi
pi1
tiempoamortizadodelaisimallamada
tiempoconsumidoporlaisimallamada
potencialdelosdatosdespusdelaisimallamada
potencialdelosdatosantesdelaisimallamada
337
El potencial de una estructura de datos X se calcula como una funcin p(X) t 0 que debe definirse intentando medir la susceptibilidad de la estructura a la ejecucin de operaciones costosas;
si el potencial es mayor entonces es posible realizar operaciones ms costosas. En muchas ocasiones el potencial viene dado por el tamao de la estructura, en cuyo caso
pi=ni
Con esta definicin podemos realizar la siguiente estimacin para una sucesin de m llamadas:
m
ti
i 1
d
m
(ai+pi1pi)
i 1
=
m
ai)+(p0pm)
i 1
d(suponiendop0dpm)
m
ai
i 1
Para estimar una cota superior de los tiempos amortizados, definimos el tiempo amortizado en el
caso peor para datos de tamao n Aj(n)
Aj(n)=defMax{aj(X)|tamaodeX=n} (1djdr)
siendo
aj(X)=deftj(X)+p(fj(X))p(X) (1djdr)
siendo
tj(X)=deftiempodeejecucindefj(X) (1djdr)
338
Una vez definido el tiempo amortizado en el caso peor para datos de tamao n podemos seguir con el anlisis del coste de las m llamadas
m
ai
i 1
d
m
Aji(ni1)
i 1
d(silasAjsonmontonas)
m
Aji(n)
i 1
=
m1A1(n)++mrAr(n)
El anlisis amortizado dar una estimacin del tiempo ms ajustada que el anlisis estndar si
la funcin potencial se elige adecuadamente. Volviendo al ejemplo de las pilas con operacin
desapilarK, podemos definir el potencial de una pila como su tamao n:
'p = 'n
fj
Tj(n)
PilaVaca
Apilar
desapilarK
esVaca
cima
O(1)
O(1)
O(n)
O(1)
O(1)
O(2)
O(2)
O(0)
O(1)
O(1)
Volviendo al ejemplo de las m1 llamadas a Apilar y m2 llamadas a desapilarK, tenemos que el resultado anterior se podr aplicar siempre que el tamao final sea mayor o igual que el tamao
inicial en particular, esto ocurrir siempre que la pila est vaca inicialmente, en cuyo caso:
O(m12+m20)=O(m1)
4.1.2
339
El esquema general de definicin de una funcin recursiva lineal segn aparece en el ejercicio
186:
funcf(x:T)devy:S;
{P0:P(x);Cota:t(x)}
var
x:T;y:S;
%Otrasposiblesdeclaracioneslocales
inicio
sid(x)
entonces
{P(x)d(x)}
y;=r(x)
{Q(x,y)}
sino
{P(x)d(x)}
x:=s(x);
{x=s(x)P(x)}
y:=f(x);
{x=s(x)Q(x,y)}
y:=c(x,y);
{Q(x,y)}
fsi
{Q0:Q(x,y)}
devy
ffunc
&
&
Intuitivamente, la ejecucin de una llamada recursiva y := f( x ) consiste en un bucle descendente seguido de un bucle ascendente:
&
&
&
&
x of( x ) c( x ,.)o y
p
p
p
p
&
&
&
&
&
&
&
&
340
El bucle descendente reitera el clculo de parmetros para nuevas llamadas recursivas, mientras que el bucle ascendente reitera el procesamiento de los resultados devueltos por las llamadas.
La transformacin a iterativo se basa en la idea intuitiva que hemos expuesto antes: se utilizan
dos bucles compuestos secuencialmente, de forma que en el primero se van obteniendo las descomposiciones recursivas hasta llegar al caso base, y en el segundo se aplica sucesivamente la
&
funcin de combinacin. Vemos que los datos sni( x ) esto es, los parmetros actuales de las
diferentes llamadas deben estar disponibles durante la ejecucin del bucle ascendente. Segn la
&
forma cmo se obtengan los valores de sni( x ) en el bucle ascendente distinguimos tres casos,
que vienen dados por la forma de la funcin de descomposicin recursiva:
Caso especial. La funcin s de descomposicin recursiva, posee una funcin inversa cal&
culable s1. En este caso, los datos sni( x ) pueden calcularse sobre la marcha, usando s1.
Caso especial
El esquema de la transformacin es el que se muestra en el ejercicio 186:
funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
{Inv.I:R(x,x);Cota:t(x)}
itd(x)o
{Id(x)}
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(x,x)y=f(x);Cota:m(x,x)}
itx/=xo
{Jxzx}
x:=s1(x);
341
y:=c(x,y)
{J}
fit
{Jx=x}
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc
R(x,x)
def
n:Nat:(x=sn(x)P(x)i:0di<n:(P(si(x))
d(si(x))))
que expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, es decir, despus de un cierto nmero de aplicaciones de la funcin s, de forma que todos los valores
intermedios cumplen la precondicin de la funcin y la barrera del caso recursivo.
La cota del algoritmo descendente es la misma que se haya utilizado para la funcin recursiva.
que expresa el nmero de llamadas recursivas necesarias para alcanzar x desde x, cantidad que
va disminuyendo pues en cada iteracin nos vamos acercando al valor de x al ir aplicando s1.
Como ejemplo de la aplicacin de este mtodo veamos cmo podemos transformar la versin
recursiva no final de la funcin factorial:
s(n)=n1 s1(n)=n+1
funcfact(n:Nat)devr:Nat;
{P0:cierto}
var
n:Nat;
inicio
n:=n;
{I:R(n,n);C:n}
itn/=0o
n:=n1
fit;
342
r:=0;
{J:R(n,n)r=n!;D:nn}
itn/=no
n:=n+1;
r:=r*n
fit
{Q0:r=n!}
devr
ffunc
Caso general
Este es el caso donde nos ayudamos de una pila para almacenar los sucesivos valores del
&
parmetro x . Como se muestra en el ejercicio 187:
funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;
xs:Pila[T];
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
PilaVaca(xs);
{Inv.I:R(xs,x,x);Cota:t(x)}
itd(x)o
{Id(x)}
Apilar(x,xs);
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(xs,x,x)y=f(x);Cota:tamao(xs)}
itNOTesVaca(xs)o
{JesVaca(xs)}
x:=cima(xs);
y:=c(x,y);
desapilar(xs)
{J}
fit
{JesVaca(xs)}
{y=f(x)}
{Q0:Q(x,y)}
343
devy
ffunc
R(xs,x,x)
def
n:Nat:(x=sn(x)P(x)
i:0di<n:(P(si(x))d(si(x))si(x)=elem(i,
xs)))
donde elem(i, xs) indica el elemento que ocupa el lugar i en la pila xs, contando el elemento del fondo como elem(0, xs)
Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, cuyos parmetros reales, hasta x exclusive, estn apilados en xs
La cota del algoritmo ascendente viene dada por el tamao de la pila, que se va decrementando en 1 con cada iteracin
tamao(xs)
=def
nmerodeelementosapiladosenxs
funcbin(n:Nat)devr:Nat;
{P0:n=N}
inicio
si
n<2or:=n
n2or:=10*bin(ndiv2)+(nmod2)
fsi
{Q0:n=Nr=i:Nat:((ndiv2i)mod2)*10i}
devr
ffunc
La transformacin a iterativo:
funcbin(n:Nat)devr:Nat;
{P0:n=N}
var
n:Nat;
ns:Pila[Nat];
inicio
n:=n;
344
PilaVaca(ns);
{I:R(ns,n,n);C:n}
itnt2o
Apilar(n,ns);
n:=ndiv2 %n:=s(n)
fit;
r:=n;
{J:R(ns,n,n)r=i:Nat:((ndiv2i)mod2)*10i;D:
tamao(ns)}
itNOTesVaca(ns)o
n:=cima(ns);
r:=10*r+nmod2;
%r:=c(n,r)
desapilar(ns)
fit
{Q0:n=Nr=i:Nat:((ndiv2i)mod2)*10i}
devr
ffunc
d(x)u=g(x)estdefinidoycumplequex=h(u,s(x))
Modificando el esquema del ejercicio 187 segn esta idea, se obtiene la siguiente versin iterativa de f segn se muestra en el ejercicio 189:
funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;u:Z;
us:Pila[Z];
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
PilaVaca(us);
{Inv.I:R(us,x,x);Cota:t(x)}
itd(x)o
{Id(x)}
u:=g(x);
345
Apilar(u,us);
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(us,x,x)y=f(x);Cota:tamao(us)}
itNOTesVaca(us)o
{JesVaca(us)}
u:=cima(us);
x:=h(u,x);
y:=c(x,y);
desapilar(us)
{J}
fit
{JesVaca(us)}
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc
R(us,x,x)
def
n:Nat:(x=sn(x)P(x)
i:0di<n:(P(si(x))d(si(x))g(si(x))=
elem(i,us)))
donde elem(i, us) indica el elemento que ocupa el lugar i en la pila us, contando el elemento del fondo como elem(0, us)
Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, y que en us est apilada informacin suficiente para recuperar los parmetros
reales de dichas llamadas
Y la cota del bucle ascendente como en el caso anterior viene dada por el nmero de elementos de la pila
tamao(us)
=def
nmerodeelementosapiladosenus
346
s(n)=ndiv2;
Aqu no hemos podido aplicar la tcnica del caso especial porque no existe la inversa de esta
funcin: para obtener n a partir de n div 2 tenemos que saber si n es par o impar, por eso hemos
tenido que aplicar la transformacin del caso general, e ir apilando los sucesivos valores de n. Sin
embargo, no hace falta apilar n, basta con apilar su paridad, pues a partir de n div 2 y la paridad de
n podemos obtener n. Por lo tanto:
g(n)=par(n)
2*s(n) siu
h(u,s(n))=
2*s(n)+1 siu
347
4.2 Colas
Este TAD representa una coleccin de datos del mismo tipo donde las operaciones de acceso
hacen que su comportamiento se asemeje al de una cola de personas esperando a ser atendidas:
los elementos se aaden por el final y se eliminan por el principio, o dicho de otra forma, el primero en llegar es el primero en salir en ser atendido: FIFO first in first out.
4.2.1
Especificacin
ColaVaca:oCola[Elem] /*gen*/
Aadir:(Elem,Cola[Elem])oCola[Elem] /*gen*/
avanzar:Cola[Elem]oCola[Elem] /*mod*/
primero:Cola[Elem]oElem /*obs*/
esVaca:Cola[Elem]oBool /*obs*/
ecuaciones
x:Elem:xs:Cola[Elem]:
defavanzar(Aadir(x,xs))
avanzar(Aadir(x,xs))=ColaVaca siesVaca(xs)
avanzar(Aadir(x,xs))=Aadir(x,avanzar(xs)) siesVaca(xs)
defprimero(Aadir(x,xs))
primero(Aadir(x,xs))=x siesVaca(xs)
primero(Aadir(x,xs))=primero(xs) siesVaca(xs)
esVaca(ColaVaca)=Cierto
esVaca(Aadir(x,xs))=Falso
errores
avanzar(Colavaca)
primero(ColaVaca)
ftad
4.2.2
348
Implementacin
ini
fin
Inicialmente ini y fin estn al principio del vector, pero tras un cierto nmero de invocaciones a
Aadir y avanzar llegaremos a un estado como el indicado en la figura.
El tipo representante:
const
lmite=100;
tipo
Cola[Elem]=reg
ini,fin:Nat;
espacio:Vector[1..lmite]deElem;
freg;
Invariante de la representacin
Dada xs : Cola[Elem]
R(xs)
def
1dxs.inidxs.fin+1dlmite+1
i:xs.inididxs.fin:R(xs.espacio(i))
349
Funcin de abstraccin
procColaVaca(sxs:Cola[Elem]); % O(1)
{P0:Cierto}
inicio
xs.ini:=1;
xs.fin:=0;
{Q0:R(xs)A(xs)=COLA[ELEM]ColaVaca}
fproc
procAadir(ex:Elem;esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(x)R(xs)xs.fin<lmite}
inicio
sixs.fin==lmite
entonces
error(Colallena)
sino
xs.fin:=xs.fin+1;
xs.espacio(xs.fin):=x; %comparticindeestructura
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]Aadir(A(x),A(XS))}
fproc
procavanzar(esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(xs)NOTesVaca(A(xs))%xs.inidxs.fin}
inicio
siesVaca(xs)
entonces
error(Colavaca)
350
sino
%ELEM.anular(xs.espacio(xs.ini))
xs.ini:=xs.ini+1
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]avanzar(A(XS))}
fproc
funcprimero(xs:Cola[Elem])devx:Elem; % O(1)
{P0:R(xs)NOTesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(Colavaca)
sino
x:=xs.espacio(xs.ini)
fsi
{Q0:A(x)=COLA[ELEM]primero(A(xs))}
devx
ffunc
funcesVaca(xs:Cola[Elem])devr:Bool; % O(1)
{P0:R(xs)}
inicio
r:=xs.ini==xs.fin+1
{Q0:A(r)=COLA[ELEM]esVaca(A(xs))}
devr
ffunc
ini
fin
Una solucin sera que cuando llegsemos a esta situacin se desplazasen todos los elementos
de la pila hacia la izquierda haciendo ini=1. Sin embargo esto penalizara en esos casos la eficiencia de la operacin Aadir O(n). Es mejor la solucin descrita a continuacin.
351
1
L
fin
Para que se vea mejor, se podra mostrar un ejemplo de cmo evolucionara la cola ante un
cierto nmero de invocaciones de Aadir y avanzar.
El tipo representante:
const
lmite=100;
tipo
Cola[Elem]=reg
ini,fin,tam:Nat;
espacio:Vector[1..lmite]deElem;
freg;
Usamos el campo tam para guardar el nmero de elementos almacenados en cada instante. Este campo es necesario para distinguir en la situacin ini=fin+1 si tenemos una cola vaca o llena.
en [Fran93] se describen otras formas de resolver este problema.
Para escribir el invariante de la representacin, la funcin de abstraccin y las operaciones vamos a utilizar dos funciones auxiliares pred y suc que restan y suman 1 mdulo lmite. En la implementacin de las operaciones podramos utilizar directamente la operacin mod, aunque para
ello sera mejor idea definir el vector entre 0 y lmite1. Sin embargo, en la funcin de abstraccin
nos hace falta una operacin de restar 1 que cumpla lmite1 = 1, para lo cual no disponemos de
ninguna funcin.
Por lo tanto definimos las siguientes funciones auxiliares:
suc,pred:[1..lmite]o[1..lmite]
i+1 sii<limite
suc(i)=def
1 sii=lmite
i1 sii>1
pred(i)=def
lmite sii=1
Invariante de la representacin
Dado xs : Cola[Elem]
R(xs)
def
0dxs.tamdlmite
1dxs.inidlmitexs.fin=sucxs.tam1(xs.ini)
i:0didxs.tam1:R(xs.espacio(suci(xs.ini)))
Funcin de abstraccin
352
353
hazCola(e,t,f)=defAadir(A(e(f)),hazCola(e,t1,pred(f)))sit>0
procColaVaca(sxs:Cola[Elem]); % O(1)
{P0:Cierto}
inicio
xs.ini:=1;
xs.fin:=lmite;
xs.tam:=0;
{Q0:R(xs)A(xs)=COLA[ELEM]ColaVaca}
fproc
Ntese que con esta inicializacin se cumple la parte del invariante de la representacin:
xs.fin=sucxs.tam1(xs.ini)
procAadir(ex:Elem;esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(x)R(xs)xs.tam<lmite}
inicio
sixs.tam==lmite
entonces
error(Colallena)
sino
xs.tam:=xs.tam+1;
xs.fin:=suc(xs.fin);
xs.espacio(xs.fin):=x; %comparticindeestructura
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]Aadir(A(x),A(XS))}
fproc
procavanzar(esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(xs)NOTesVaca(A(xs))%xs.tam>0}
inicio
siesVaca(xs)
354
entonces
error(Colavaca)
sino
%ELEM.anular(xs.espacio(xs.ini))
xs.tam:=xs.tam1;
xs.ini:=suc(xs.ini)
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]avanzar(A(XS))}
fproc
funcprimero(xs:Cola[Elem])devx:Elem; % O(1)
{P0:R(xs)NOTesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(Colavaca)
sino
x:=xs.espacio(xs.ini)
fsi
{Q0:A(x)=COLA[ELEM]primero(A(xs))}
devx
ffunc
funcesVaca(xs:Cola[Elem])devr:Bool; % O(1)
{P0:R(xs)}
inicio
r:=xs.tam==0
{Q0:A(r)=COLA[ELEM]esVaca(A(xs))}
devr
ffunc
Implementacin dinmica
La idea es tener acceso directo al principio y al final de la cola para as poder realizar las operaciones de manera eficiente.
Tipo representante
Idea grfica:
355
xs
prim
x1
x2
ult
xn
Existe tambin la posibilidad de implementar el nodo cabecera con memoria dinmica, con esto conseguiramos que en aquellos lenguajes donde las funciones no pueden devolver tipos estructurados, devolvieran valores de tipo cola y en Modula-2 estaramos obligados si quisiramos
que el tipo fuese oculto.
El tipo representante:
tipo
Enlace=punteroaNodo;
Nodo=reg
elem:Elem;
sig:Enlace
freg;
Cola[Elem]=reg
prim,ult:Enlace
freg;
Invariante de la representacin
Dada xs : Cola[Elem]
R(xs)defRCV(xs)RCNV(xs)
escribimos por separado las condiciones que debe cumplir y una cola vaca y otra que no lo est.
RCV(xs)
def
xs.prim=nilxs.ult=nil
RCNV(xs)
def
xs.primznilxs.ultznil
buenaCola(xs.prim,xs.ult)
356
entre otras cosas, en el predicado buenaCola tenemos que expresar la condicin de que desde p
es posible llegar a q
buenaCola(p,q)
def
(p=qubicado(p)R(p^.elem)p^.sig=nil)
(pzqubicado(p)R(p^.elem)
pcadena(p^.sig)buenaCola(p^.sig,q))
cadena(p)=def sip=nil
cadena(p)=def{p}cadena(p^.sig) sipznil
Funcin de abstraccin
para construir la cola tenemos que aadir elementos hasta que lleguemos a una cola con un solo elemento, donde se cumple p=q
hazCola(p,q)=defAadir(A(p^.elem),ColaVaca) sip=q
hazCola(p,q)=defponerPrimero(A(p^.elem),hazCola(p^.sig,q)) sipzq
ponerPrimero(y,ColaVaca)=defAadir(y,colaVaca)
ponerPrimero(y,Aadir(x,xs))=defAadir(x,PonerPrimero(y,xs))
es necesario el predicado auxiliar ponerPrimero porque el orden de recorrido del valor representante de la cola es el inverso al orden de insercin en la misma.
procColaVaca(sxs:Cola[Elem]); % O(1)
{P0:Cierto}
inicio
xs.prim:=nil;
xs.ult:=nil;
{Q0:R(xs)A(xs)=COLA[ELEM]ColaVaca}
fproc
procAadir(ex:Elem;esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(x)R(xs)xs.tam<lmite}
var
p:Enlace;
inicio
ubicar(p);
p^.elem:=x; %comparticindeestructura
p^.sig:=nil;
siesVaca(xs)
entonces
xs.prim:=p;
xs.ult:=p
sino
xs.ult^.sig:=p;
xs.ult:=p
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]Aadir(A(x),A(XS))}
fproc
procavanzar(esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(xs)NOTesVaca(A(xs))%xs.tam>0}
var
p:Enlace;
inicio
siesVaca(xs)
entonces
error(Colavaca)
sino
p:=xs.prim;
xs.prim:=xs.prim^.sig;
sixs.prim==nil
entonces
xs.ult:=nil %slotenaunelemento
sino
seguir
fsi;
%ELEM.anular(p^.elem)
liberar(p)
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]avanzar(A(XS))}
fproc
funcprimero(xs:Cola[Elem])devx:Elem; % O(1)
{P0:R(xs)NOTesVaca(A(xs))}
inicio
siesVaca(xs)
357
358
entonces
error(Colavaca)
sino
x:=xs.prim^.elem %comparticindeestructura
fsi
{Q0:A(x)=COLA[ELEM]primero(A(xs))}
devx
ffunc
funcesVaca(xs:Cola[Elem])devr:Bool; % O(1)
{P0:R(xs)}
inicio
r:=xs.prim==nil
{Q0:A(r)=COLA[ELEM]esVaca(A(xs))}
devr
ffunc
Como cualquier implementacin de un TAD, debemos incluir una operacin que realice copias de los valores representados:
funccopiar(xs:Cola[Elem])devys:Cola[Elem];
{P0:R(xs)}
var
p,q,r:Enlace;%precorrelaoriginalyq,rlacopia
inicio
siesVaca(xs)
entonces
ys.prim:=nil;
ys.ult:=nil;
sino
p:=xs.prim;
ubicar(q);
ys.prim:=q;
q^.elem:=p^.elem; %q^.elem:=ELEM.copiar(p^.elem)
{I:pcadena(xs.prim)
lospunterosdecadena(ys.prim)apuntanacopiasdelosnodosde
cadena(xs.prim)hastap^inclusive
q=ltimo(cadena(ys.prim));
C:|cadena(p)|
}
itp^.sig/=nilo
p:=p^.sig;
ubicar(r);
q^.sig:=r;
q:=r;
359
q^.elem:=p^.elem;
%q^.elem:=ELEM.copiar(p^.elem)
fit;
q^.sig:=nil;
ys.ult:=q
fsi
{Q0:R(ys)A(xs)=COLA[ELEM]A(ys)
xs,ysapuntanaestructurasquenocompartennodos}
devys
ffunc
Especificacin
DColaVaca:oDCola[Elem] /*gen*/
PonDetrs:(Elem,DCola[Elem])oDCola[Elem]
gen*/
/*
ponDelante:(Elem,DCola[Elem])oDCola[Elem] /*mod*/
quitaUlt:DCola[Elem]oDCola[Elem] /*mod*/
ltimo:DCola[Elem]oElem /*obs*/
quitaPrim:DCola[Elem]oDCola[Elem]
mod*/
/*
primero:DCola[Elem]oElem /*obs*/
esVaca:DCola[Elem]oBool/*obs*/
ecuaciones
x,y:Elem:xs:DCola[Elem]:
ponDelante(y,DColaVaca)=PonDetrs(y,DColaVaca)
ponDelante(y,PonDetrs(x,xs))=PonDetrs(x,ponDelante(y,xs))
defquitaUlt(xs)siesVaca(xs)
quitaUlt(PonDetrs(x,xs))=xs
defltimo(xs)siesVaca(xs)
360
ltimo(PonDetrs(x,xs))=x
defprimero(xs)siesVaca(xs)
primero(PonDetrs(x,xs))=x siesVaca(xs)
defquitaPrim(xs)siesVaca(xs)
quitaPrim(PonDetrs(x,xs))=DColaVaca siesVaca(xs)
quitaPrim(PonDetrs(x,xs))=PonDetrs(x,quitaPrim(xs))
siesVaca(xs)
primero(PonDetrs(x,xs))=primero(xs)siesVaca(xs)
esVaca(DColaVaca)=Cierto
esVaca(PonDetrs(x,xs))=Falso
errores
quitaUlt(DColavaca)
ltimo(DColaVaca)
quitaPrim(DColavaca)
primero(DColaVaca)
ftad
Se podra elegir como generador PonDetrs o ponDelante. Hemos elegido PonDetrs para que la
especificacin sea ms parecida a la de las colas normales, ya que PonDetrs { Aadir.
Se puede establecer de manera evidente la siguiente correspondencia entre las operaciones de
las colas y las de las colas dobles:
COLA[ELEM]
ColaVaca
Aadir
avanzar
primero
esVaca
4.3.2
DCOLA[ELEM]
DColaVaca
PonDetrs
quitaPrim
primero
esVaca
ponDelante
quitaUlt
ltimo
Implementacin
Bsicamente podemos considerar las mismas implementaciones utilizadas para las colas, a excepcin de la primera que, como ya vimos, no es muy conveniente.
Implementacin basada en un vector circular
Tipo representante
Igual que en el caso de las colas ordinarias, dndole le nombre DCola[Elem].
361
Invariante de la representacin
Igual que el caso de las colas ordinarias.
Funcin de abstraccin
Es igual que el caso de las colas ordinarias, sustituyendo ColaVaca por DColaVaca y Aadir
por PonDetrs:
Dado xs: Cola[elem] tal que R(xs):
A(xs)=defhazDCola(xs.esp,xs.tam,xs.fin)
hazDCola(e,t,f)=defDColaVaca sit=0
hazDCola(e,t,f)=defPonDetrs(A(e(f)),hazDCola(e,t1,pred(f)))sit>
0
procponDelante(ex:Elem;esxs:DCola[Elem]); % O(1)
{P0:xs=XSR(x)R(xs)xs.tam<lmite}
inicio
sixs.tam==lmite
entonces
error(Colallena)
sino
xs.tam:=xs.tam+1;
xs.ini:=pred(xs.ini);
xs.espacio(xs.ini):=x; %comparticindeestructura
fsi
{Q0:R(xs)A(xs)=DCOLA[ELEM]ponDelante(A(x),A(XS))}
fproc
procquitaUlt(esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(xs)NOTesVaca(A(xs))%xs.tam>0}
inicio
siesVaca(xs)
entonces
error(Colavaca)
sino
362
%ELEM.anular(xs.espacio(xs.fin))
xs.tam:=xs.tam1;
xs.fin:=pred(xs.fin)
fsi
{Q0:R(xs)A(xs)=DCOLA[ELEM]quitaUlt(A(XS))}
fproc
Ms interesante resulta la implementacin de quitaUlt que necesita recorrer la cola con lo que
resulta en un complejidad de O(n). La razn es que el puntero al penltimo nodo slo se puede
obtener recorriendo la cola:
procquitaUlt(esxs:Cola[Elem]); % O(n)
{P0:xs=XSR(xs)NOTesVaca(A(xs))}
var
p:Enlace;
inicio
siesVaca(xs)
entonces
error(Colavaca)
363
sino
p:=xs.prim;
sip==xs.ult
entonces
xs.prim:=nil;
xs.ult:=nil;
ELEM.anular(p^.elem);
liberar(p)
sino
itp^.sig/=xs.ulto
p:=p^.sig
fit;
p^.sig:=nil;
ELEM.anular(xs.ult^.elem);
liberar(xs.ult);
xs.ult:=p
fsi
fsi
{Q0:R(xs)A(xs)=DCOLA[ELEM]quitaUlt(A(XS))}
fproc
Para conseguir que tambin esta operacin tenga complejidad O(1) se puede utilizar la siguiente representacin.
Implementacin dinmica con encadenamiento doble
Tipo representante
xs
prim
x1
x2
ult
xn-1
xn
Con esta representacin tenemos acceso en tiempo constante al penltimo elemento. El tipo
representante queda:
tipo
Enlace=punteroaNodo;
Nodo=reg
elem:Elem;
sig,ant:Enlace
freg;
364
DCola[Elem]=reg
prim,ult:Enlace
freg;
Esta representacin permite una implementacin procedimental de todas las operaciones con
complejidad de O(1).
4.4 Listas
Es un TAD que representa a una coleccin de elementos de un mismo tipo que generaliza a
pilas y colas porque incluye una operacin que permite acceder al elemento i-simo. Incluye
adems operaciones de concatenacin, conteo de los elementos,
4.4.1
Especificacin
Nula:oLista[Elem]
/*gen*/ %[]
Cons:(Elem,Lista[Elem])oLista[Elem]
[x/xs]
/*gen*/ %
/*mod*/ %[x]
[_]:ElemoLista[Elem]
ponDr:(Lista[Elem],Elem)oLista[Elem]
[xs\x]
/*mod*/ %
/*obs*/
primero:Lista[Elem]oElem
resto:Lista[Elem]oLista[Elem]
/*mod*/
ltimo:Lista[Elem]oElem
/*obs*/
inicio:Lista[Elem]oLista[Elem]
(++):(Lista[Elem],Lista[Elem])oLista[Elem] /*mod*/
nula?:Lista[Elem]oBool
/*obs*/
(#):Lista[Elem]oNat
/*obs*/
(!!):(Lista[Elem],Nat)oElem
/*obs*/
/*mod*/
365
ecuaciones
x,y:E.Elem:xs,ys:Lista[Elem]:n:Nat:
[x]
=Cons(x,Nula)
Nula++ys
=ys
Cons(x,xs)++ys
=Cons(x,(xs++ys))
ponDr(xs,x)
=xs++[x]
nula?(Nula)
=Cierto
nula?(Cons(x,xs))
=Falso
defprimero(xs)siNOTnula?(xs)
primero(Cons(x,xs))
=x
defresto(xs)siNOTnula?(xs)
resto(Cons(x,xs))
=xs
defltimo(xs)siNOTnula?(xs)
ltimo(Cons(x,xs))
=x
si
nula?(xs)
ltimo(Cons(x,xs))
=ltimo(xs)
siNOT
nula?(xs)
definicio(xs)siNOTnula?(xs)
inicio(Cons(x,xs))
=Nula
si
nula?(xs)
inicio(Cons(x,xs))
=Cons(x,inicio(xs))siNOT
nula?(xs)
#Nula
=Cero
#Cons(x,xs)
=Suc(#xs)
Por primera aparece la igualdad fuerte en las especificaciones. Recordemos que la igualda
fuerte representa el hecho de que ambos trminos estn definidos y son equivalentes, o que
ambos trminos estn indefinidos. El problema con esta operacin es que no podemos escribir
una ecuacin que especifique su comportamiento y que est garantizado que slo involucra
trminos definidos; es por ello que utilizamos la igualdad fuerte.
defxs!!siSuc(Cero)dnd(#xs)
Cons(x,xs)!!Suc(Cero)
=x
Cons(x,Cons(y,ys))!!Suc(Suc(n))= Cons(y,ys)!!Suc(n)
errores
primero(Nula)
resto(Nula)
ltimo(Nula)
inicio(Nula)
xs!!nsi(n==Cero)OR(n>(#xs))
ftad
Notacioneshabitualespararepresentarlistas(siendoxi:Elem)
[x1,,xn]
[x1,,xn/xs]
[xs\x1,,xn]
366
Muchas de las operaciones de las listas tienen una anloga en DCOLA[ELEM], y es por ello
que las representaciones son similares. Se verifican las siguientes equivalencias entre las operaciones:
Operaciones de LISTA Operaciones de DCOLA
Nula
DColaVaca
Cons
ponDelante
[_]
ponDr
PonDetrs
primero
primero
resto
quitaPrim
ltimo
ltimo
inicio
quitaUlt
( ++ )
nula?
esVaca
(# )
( !! )
4.4.2
Implementacin
367
x1
xs
x2
xn
El problema es que esta representacin slo es adecuada para implementar eficientemente las
operaciones que acceden a una lista por su extremo inicial. en particular, se pueden implementar
en tiempo O(1) las operaciones: Nula, Cons, primero, resto, nula?. Sin embargo, no permite una implementacin eficiente de las operaciones que accede a una lista por el final.
Representacin dinmica como la utilizada para las colas
La idea grfica de esta representacin
xs
prim
x1
x2
ult
xn
Tipo representante
tipo
Enlace=punteroaNodo;
Nodo=reg
elem:Elem;
sig:Enlace
freg;
Lista[Elem]=reg
prim,ult:Enlace
freg;
Invariante de la representacin
Es exactamente igual que el invariante de la representacin que vimos para las colas. Si la lista
est vaca entonces prim y ult valen nil. Si no est vaca, entonces ambos son distintos de nil, y se
debe cumplir que desde el nodo apuntado por prim se llegue al nodo apuntado por ult, que ser el
ltimo de la cadena, donde no se introduce circularidad y donde todos los punteros estn ubicados y los elementos son representantes vlidos.
368
Funcin de abstraccin
Es muy similar a la de las colas, aunque ms simple porque ahora la generadora que aade
elementos, lo hace por el principio, con lo que no resulta necesaria introducir la funcin auxiliar
ponerPrimero.
Dada xs : Lista[Elem] tal que R(xs):
A(xs)=defNula siRLV(xs)
A(xs)=defhazLista(xs.prim,xs.ult) siRLNV(xs)
para construir la lista tenemos que aadir elementos hasta que lleguemos a una lista con un solo elemento, donde se cumple p=q
hazLista(p,q)=defCons(A(p^.elem),Nula)
sip=q
hazLista(p,q)=defCons(A(p^.elem),hazLista(p^.sig,q)) sipzq
ys
xs
prim
x1
x2
prim
ult
xn
y1
y2
ult
yn
369
xs
prim
ult
ys
prim
x1
x2
xn
y1
y2
ult
yn
procconc(esxs:Lista[Elem];eys:Lista[Elem])
{P0:R(xs)R(ys)xs=XSys=YS}
inicio
sinula?(xs)
entonces
xs:=ys
sino
sinula?(ys)
entonces
seguir
sino
xs.ult^.sig:=ys.prim;
xs.ult:=ys.ult
fsi
fsi
{Q0:R(xs)R(ys)A(xs)=LISTA[ELEM]A(XS)++A(ys)}
fproc
En cuanto a las operaciones copiar y anular tambin las implementamos como procedimientos,
pudiendo optar, como en los dems TADs, por realizar de forma simple o profunda. La operacin
de copia sera exactamente igual a la que ya implementamos para las colas. En cuanto a la anulacin, es muy similar a la que implementamos para las pilas:
procanula(esLista:Lista[Elem]);
{P0:R(xs)xs=XS}
370
var
p,q:Enlace;
inicio
sinula?(xs)
entonces
seguir
sino
p:=xs.prim;
itp/=nilo
q:=p^.sig;
ELEM.anular(p^.elem); %slosisehaceanulacinprofunda
liberar(p);
p:=q
fit;
xs.prim:=nil;
xs.ult:=nil
fsi
{Q0:R(xs)A(xs)=LISTA[ELEM]NulalosnodosaccesiblesdesdeXS
atravsdepunterosdetipoEnlace,sehanliberado}
fproc
funcNula()devxs:Lista[Elem]; /*O(1)*/
{P0:cierto}
inicio
xs.prim:=nil;
xs.ult:=nil
{Q0:R(xs)A(xs)=LISTA[ELEM]Nula}
devxs
ffunc
funcCons(x:Elem;xs:Lista[Elem])devys:Lista[Elem];/*O(1)
{P0:R(x)R(xs)}
var
nuevo:Enlace;
inicio
ubicar(nuevo);
ys.prim:=nuevo;
nuevo^.elem:=x;%comparticindeestructura
371
sinula?(xs)
entonces
ys.ult:=nuevo;
nuevo^.sig:=nil
sino
ys.ult:=xs.ult;
nuevo^.sig:=xs.prim
fsi
{Q0:R(ys)A(ys)=LISTA[ELEM]Cons(A(x),A(xs))}
devys
ffunc
ys
prim
ult
xs
prim
x1
x2
ult
xn
funcponDr(xs:Lista[elem];x:Elem)devys:Lista[Elem]/*O(n)por
copia*/
{P0:R(x)R(xs)}
var
nuevo:Enlace;
inicio
ys:=copia(xs);
ubicar(nuevo);
nuevo^.elem:=x;%comparticindeestructura
nuevo^.sig:=nil;
sinula?(ys)
entonces
ys.prim:=nuevo
sino
ys.ult^.sig:=nuevo
fsi;
ys.ult:=nuevo;
{Q0:R(ys)A(ys)=LISTA[ELEM]ponDr(A(xs),A(x))}
devys
ffunc
Ntese que en este caso es necesaria la copia para que se mantenga el invariante de la representacin de xs el valor nil del campo sig del ltimo nodo.
372
ys
xs
prim
x1
prim
ult
x2
x1
xn
x2
ult
xn
funcresto(xs:Lista[Elem])devys:Lista[Elem]/*O(1)*/
{P0:R(xs)NOTnula?(A(xs))}
inicio
sinula?(xs)
entonces
error(Listavaca)
sino
sixs.prim==xs.ult
entonces
ys.prim:=nil;
ys.ult:=nil
sino
ys.prim:=xs.prim^.sig;
ys.ult:=xs.ult
fsi
fsi
{Q0:R(ys)A(ys)=LISTA[ELEM]resto(A(xs))}
devys
ffunc
xs
prim
ult
ys
prim
x1
x2
ult
xn
El siguiente es el primer ejemplo de operacin que slo implementamos parcialmente; los detalles de la funcin privada auxiliar copiaInicio se quedan como ejercicio.
373
funcinicio(xs:Lista[Elem])devys:Lista[Elem]/*O(n)porcopia*/
{P0:R(xs)NOTnula?(A(xs))}
inicio
sinula?(xs)
entonces
error(Listavaca)
sino
ys:=copiaInicio(xs)%funcinprivadaO(n)
fsi
{Q0:R/ys)A(ys)=LISTA[ELEM]inicio(A(xs))}
devys
ffunc
x1
x2
ult
prim
xn-1
xn
x1
x2
funcconc(xs,ys:Lista[Elem])devzs:Lista[Elem];
%tiempoO(n)debidoalacopiadexs,siendon=#A(xs)
{P0:R(xs)R(ys)}
inicio
sinula?(xs)
entonces
zs:=ys
sino
zs:=copia(xs);
xs.ult^.sig:=ys.prim;
zs.ult:=ys.ult
fsi
{Q0:R(zs)A(zs)=LISTA[ELEM]A(xs)++A(ys)}
devzs
ffunc
ult
xn-1
374
xs
prim
x1
x2
ult
xn
ys
xs
prim
x1
x2
prim
ult
xn
y1
y2
ult
yn
funclongitud(xs:Lista[Elem])devn:Nat;/*O(n)*/
{P0:R(xs)}
var
aux:Enlace;
inicio
n:=0;
aux:=xs.prim;
itaux/=nilo
n:=n+1;
aux:=aux^.sig
fit
{Q0:n=LISTA[ELEM]#(A(xs))}
devn
ffunc
375
aux:=aux^.sig
fit;
sij==1andaux/=nil
entonces
x:=aux^.elem %comparticindeestructura
sino
error(Posicininexistente)
fsi
{Q0:A(x)=LISTA[ELEM]A(xs)!!i}
devx
ffunc
Como conclusin del apartado de implementacin mostramos una tabla con la complejidad de
las distintas operaciones:
Operacin
Nula
Cons
[_]
ponDr
primero
resto
ltimo
inicio
( ++ )
nula?
(# )
( !! )
4.4.3
Procedimiento
O(1)
O(1)
O(1)
O(1)
O(1)
O(n)
O(1)
Funcin
O(1)
O(1)
O(1)
O(n)
O(1)
O(1)
O(1)
O(n)
O(n)
O(1)
O(n)
O(i)
Suponemos dada una implementacin funcional del TAD LISTA. Usamos las operaciones pblicas, sin programar con punteros.
376
entonces
n:=0
sino
n:=1+longitud(resto(xs))
fsi
{Q0:n=LISTA[ELEM]#(A(xs))}
devn
ffunc
377
Cuando escribimos funciones que usan un TAD ya no aparecen los representantes vlidos ni
las funciones de abstraccin; usamos las operaciones del TAD como predicados que pueden aparecer en nuestros asertos.
functira(n:Nat;xs:Lista[Elem])devvs:Lista[Elem];
{P0:cierto}
si
n==0ovs:=xs
n>0ANDnula?(xs)ovs:=Nula
n>0ANDNOTnula?(xs)ovs:=tira(n1,resto(xs))
fsi
{Q0:#vs=max(0,#xsn)i:1did#vs:vs!!i=xs!!(n+i)}
devus
ffunc
En ambos casos tenemos disminucin del tamao del problema por sustraccin de 1, con una
nica llamada recursiva. La combinacin de los resultados consume tiempo constante. Por lo
tanto la complejidad es O(n). Podramos pensar que esa n es el min del parmetro n y #xs, sin
embargo para un valor de n y xs dados el caso peor se da cuando #xs t n.
Inversa de una lista
La idea naive para implementar la operacin de inversin es una funcin recursiva de la forma:
(CD)inv([])=[]
(CR)inv([x/xs])=inv(xs)++[x]
378
funcinv(xs:Lista[Elem])devys:Lista[Elem];
{P0:cierto}
inicio
sinula?(xs)
entoncesys:=Nula
sinoys:=inv(resto(xs))++[primero(xs)]
fsi
{Q0::1did(#xs):xs!!i=ys!!(#xs)i+1}
ffunc
Donde se han utilizado las operaciones de las listas como operadores, porque en el enunciado
de los ejercicios todava no habamos escrito sus implementaciones y no les habamos dado nombre. En realidad, habra que reescribirla utilizando la notacin habitual algunos lenguajes, como
C++, permiten definir funciones como operadores.
Esta implementacin tiene complejidad O(n2), siendo n = #xs, porque la concatenacin implementada funcionalmente tiene complejidad O(n) debido a la copia del primer argumento:
c0
si 0 n < b
T(n) =
a T(nb) + c nk
donde b = 1, a = 1, k = 1
si n b
O(nk+1)
si a = 1
O(an div b)
si a > 1
T(n)
379
Donde tenemos ahora que la preparacin de la llamada es O(1), y, por lo tanto, la complejidad
total es O(n)
Sera
(CD) mezcla([],ys)=ys
mezcla([x/xs],[])=[x/xs]
(CR) mezcla([x/xs],[y/ys])=[x/mezcla(xs,[y/ys])]sixdy
mezcla([x/xs],[y/ys])=[y/mezcla([x/xs],ys)]six>y
380
mezcla(as,xs,ys)=as++mezcla(xs,ys)
=
=
mezcla(as,[],ys)
as++mezcla([],ys)
as++ys
=
=
mezcla(as,[x/xs],[])
as++mezcla([x/xs],[])
as++[x/xs]
xdy
=
=
=
=
x>y =
mezcla(as,[x/xs],[y/ys])
as++mezcla([x/xs],[y/ys])
as++[x/mezcla(xs,[y/ys])]
as++[x]++mezcla(xs,[y/ys])
mezcla(ponDr(as,x),xs,[y/ys])
mezcla(as,[x/xs],[y/ys])
mezcla(ponDr(as,y),[x/xs],ys)
Esta implementacin tendr complejidad O(n), con n = #xs + #ys, siempre y cuando la concatenacin de los casos base y ponDr de la descomposicin recursiva tengan complejidad O(1),
lo cual slo se verifica si utilizamos una implementacin procedimental de las operaciones.
El predicado permutacin que aparece en la postcondicin se puede especificar de la siguiente
manera:
permutacin(us,vs)
def
x:Elem:(#i:1did#us:us!!i=x)=
(#i:1did#vs:vs!!i=x)
4.5 Secuencias
Son colecciones de elementos con una posicin distinguida dentro de esa coleccin. Es el
TAD que utilizamos para poder hacer recorridos sobre una coleccin de elementos. Insistir aqu
en la idea de que los TAD no son objetos monolticos y que si, por ejemplo, me interesase una
coleccin de elementos con acceso por posicin y que adems se pudiese recorrer, podra escribir
la especificacin de un TAD que mezclase incluyese operaciones de LISTA y SECUENCIA.
Ntese tambin que si quisiese recorrer una Lista[Elem], tendra que hacerlo con la operacin ( !!
), lo que llevara a un recorrido de complejidad cuadrtica; mientras que con las operaciones de
las secuencias podemos conseguir complejidad lineal.
4.5.1
381
Especificacin
La especificacin se basa en la idea de que podemos representar las secuencias como dos listas, siendo el punto de inters el punto divisorio entre las dos partes. Ntese que esto es ligeramente distinto de la primera idea que hemos presentado, donde el punto de inters es un
elemento de la secuencia, que, en este planteamiento es el primer elemento de la parte derecha de
la secuencia.
Aparecen en esta especificacin dos elementos nuevos relacionados: el uso privado de un
TAD y la definicin de una generadora privada. El uso privado indica que los clientes de este
TAD no tiene que conocer el uso que ste hace de LISTA[ELEM]. La generadora privada se
introduce para llevar a cabo la idea de representar las secuencias como dos listas; entonces se
expresa el comportamiento de las generadoras pblicas, que son libres, en trminos de la generadora privada. Para expresar comportamiento de las operaciones no generadoras no se utilizan las
generadoras pblicas sino la privada.
tadSEC[E::ANY]
usa
BOOL
usaprivadamente
LISTA[E]
tipo
Sec[Elem]
operaciones
Crea:oSec[Elem]
/*gen*/
Generadora que inserta un elemento a la izquierda del punto de inters como el ltimo de la
parte izquierda
Inserta:(Sec[Elem],Elem)oSec[Elem]
/*gen*/
Modificadora que elimina el elemento a la derecha del punto de inters el primero de la parte
derecha. Es parcial porque la parte derecha puede estar vaca; esta es tambin la razn que
explica la parcialidad de las dos siguientes operaciones
borra:Sec[Elem]oSec[Elem]
/*mod*/
actual:Sec[Elem]oElem
/*obs*/
Desplaza un lugar a la derecha el punto de inters. Es una generadora porque dos secuencias
con el mismo contenido pero distinto punto de inters son secuencias diferentes
Avanza:Sec[Elem]oSec[Elem]
/*gen*/
Reinicia:Sec[Elem]oSec[Elem]
/*gen*/
vaca?:Sec[Elem]oBool
/*obs*/
382
fin?:Sec[Elem]oBool
operacionesprivadas
S:(Lista[Elem],Lista[Elem])oSec[Elem] /*gen*/
piz,pdr,cont:Sec[Elem]oLista[Elem] /*obs*/
/*obs*/
ecuaciones
s:Sec[Elem]:iz,dr:Lista[Elem]:x:Elem:
Crea
=S(Nula,Nula)
Inserta(S(iz,dr),x)
=S(iz++[x],dr)
fin?(S(iz,dr))
=nula?(dr)
defborra(s)siNOTfin?(s)
borra(S(iz,Cons(x,dr))) =S(iz,dr)
defactual(s)siNOTfin?(s)
actual(S(iz,Cons(x,dr))) =x
defAvanza(s)siNOTfin?(s)
Avanza(S(iz,Cons(x,dr))) =S(iz++[x],dr)
Reinicia(S(iz,dr))
=S(Nula,iz++dr)
vaca?(S(iz,dr))
=nula?(iz)ANDnula?(dr)
piz(S(iz,dr))
=iz
pdr(S(iz,dr))
=dr
cont(S(iz,dr))
=iz++dr
errores
borra(s)sifin?(s)
actual(s)sifin?(s)
avanza(s)sifin?(s)
ftad
4.5.2
Implementacin
Implementacin esttica
Basada en un vector, siguiendo una idea similar a la estudiada para la representacin de las pilas. La idea grfica:
act
ult
Ntese que act apunta al primero de la parte derecha. Cuando el punto de inters est a la derecha del todo, su valor ser ult+1
Tipo representante
const
383
l=100;
tipo
Sec[Elem]=reg
act,ult:Nat;
esp:Vector[1..l]deElem;
freg;
Invariante de la representacin
Dada xs : Sec[Elem]:
R(xs)
def
0dx.ultdl1dxs.actdxs.ult+1
i:1didxs.ult:R(xs.esp(i))
Ntese que:
ult = 0 act = 1
En este caso cont(A(xs)) = [ ]
Funcin de abstraccin
[A(e(c))/hazLista(e,c+1,f)] sicdf
Ntese que R(xs) garantiza que las dos llamadas a lista cumplen c d f+1, es decir, que se hacen
con segmentos de xs.esp de longitud t 0
Implementacin procedimental de las operaciones
Con esta representacin, la eficiencia que se puede conseguir para las operaciones con una
implementacin procedimental:
Operacin
Crea
Inserta
Complejidad
O(1)
O(n)
borra
actual
Avanza
Reinicia
vaca?
fin?
384
O(n)
O(1)
O(1)
O(1)
O(1)
O(1)
La razn de la complejidad lineal de Inserta y borra es que suponen desplazamientos. Consideramos que esta implementacin es ineficiente, ya que nuestro objetivo es conseguir que todas las
operaciones tengan complejidad O(1).
Implementacin basada en una pareja de listas
Seguimos directamente la idea de la especificacin del TAD
Tipo representante
tipo
Sec[Elem]=reg
piz:Lista[Elem];
pdr:Lista[Elem]
freg;
Complejidad
proc O(1)
proc O(1)
Idea
ponDr en piz
385
borra
actual
Avanza
proc O(1)
func O(1)
proc O(1)
Reinicia
proc O(n2)
vaca?
fin?
func O(1)
func O(1)
resto en pdr
primero en pdr
primero y resto en pdr; ponDr
en piz
reiterar; ltimo e inicio O(n)
en piz; Cons en pdr
nula? en piz y pdr
nula? en pdr
xs
prim
x1
primero
(fantasma)
ant
xi-1
xi
xn
anterior
al actual
Tipo representante
tipo
Nodo=reg
386
elem:Elem;
sig:Enlace
freg;
Enlace=punteroaNodo;
Sec[Elem]=reg
pri,ant:Enlace
freg;
Invariante de la representacin
Dada xs : Sec[Elem]:
R(xs)
def
xs.priznilubicado(xs.pri)
xs.antznilubicado(xs.ant)
xs.antcadena(xs.pri)
xs.pricadena(xs.pri^.sig)
cadenaCorrecta(xs.pri^.sig)
Donde, como siempre, cadena(p) da el conjunto de enlace que parten de p hasta llegar a nil, exclusive.
sip=nil
cadena(p)=def
{p}cadena(p^.sig) sipznil
Y donde, dado p : Enlace, cadenaCorrecta(p) se cumple SYSS todos los punteros de cadena(p) apuntan a nodos diferentes correctamente construidos:
cadenaCorrecta(p)
def
p=nil
(pznilubicado(p)R(p^.elem)
pcadena(p^.sig)cadenaCorrecta(p^.sig))
Funcin de abstraccin
387
A(xs)=defS(hazPiz(xs.pri^.sig,xs.ant^.sig),hazPdr(xs.ant^.sig))
Parece ms intuitivo invocar a hazPiz con xs.ant en lugar de xs.ant^.sig; sin embargo, esto complicara la definicin de hazPiz, porque habra que incluir un caso base ms: p=q=nil; y lo que es
peor, habra que distinguir si p = q = xs.prim. Es decir, facilita la definicin de la funcin en los
casos especiales de: piz vaca y secuencia vaca.
hazPiz(p, q) construye una lista abstracta con los elementos localizados en los nodos sealados
por los punteros de cadena(p), hasta q exclusive.
Nula sip=q (estoincluyep=q=
nil)
hazPiz=def
Cons(A(p^.elem),hazPiz(p^.sig,q)) sipzq
Ntese que R(xs) garantiza que p^.elem est bien definido en el caso recursivo de hazPiz. Y,
efectivamente, la funcin se comporta correctamente cuando la parte izquierda est vaca:
xs.pri=xs.ant
xs.pri^.sig=xs.ant^.sig (quepuedesernilsilasecuenciaest
vaca)
hazPiz(xs.pri^.sig,xs.ant^.sig)=Nula
hazPdr(p) construye una lista abstracta con los elementos localizados en los nodos sealados
por los punteros de cadena(p):
Nula sip=nil
hazPdr(p)=def
Cons(A(p^.elem),hazPdr(p^.sig)) sipznil
Ntese que R(xs) garantiza que p^.elem est bien definido en el caso recursivo de hazPdr. Y,
efectivamente, la funcin se comporta correctamente cuando la parte derecha est vaca: si
xs.ant^.sig = nil resulta hazPdr( xs.ant^.sig) = Nula.
Implementacin procedimental de las operaciones
procCrea(sxs:Sec[Elem]);
{P0:cierto}
var
fantasma:Enlace;
inicio
ubicar(fantasma);
fantasma^.sig:=nil;
xs.pri:=fantasma;
xs.ant:=fantasma
{Q0:R(xs)A(xs)=SEC[ELEM]Crea}
fproc
388
procinserta(esxs:Sec[Elem];ex:Elem);
{P0:xs=XSR(xs)R(x)}
var
nuevo:Enlace;
inicio
ubicar(nuevo);
nuevo^.elem:=x;
nuevo^.sig:=xs.ant^.sig;/*1*/
xs.ant^.sig:=nuevo;/*2*/
xs.ant:=nuevo/*3*/
{Q0:R(xs)A(xs)=SEC[ELEM]inserta(A(XS),A(x))}
fproc
xs
prim
ant
3
x1
xi-1
xi
2
x
procborra(esxs:Sec[Elem]);
{P0:xs=XSR(xs)NOTfin?(A(xs))}
var
act:Enlace;
inicio
act:=x.ant^.sig;
siact==nil
entonces
error(Partederechavaca)
sino
xs.ant^.sig:=act^.sig;/*1*/
ELEM.anular(act^.elem);
liberar(act)
fsi
{Q0:R(xs)A(xs)=SEC[ELEM]borra(A(XS))}
fproc
xn
389
xs
prim
ant
1
x1
xi-1
xi
xi+1
act
funcactual(xs:Sec[Elem])devx:Elem;
{P0:R(xs)NOFfin?(A(xs))}
var
act:Enlace;
inicio
act:=xs.ant^.sig;
siact==nil
entonces
error(Partederechavaca)
sino
x:=act^.elem; %x:=ELEM.copia(act^.elem)
fsi
{Q0:A(x)=SEC[ELEM]actual(A(xs))}
devx
ffunc
procavanza(esxs:Sec[Elem]);
{P0:xs=XSR(xs)NOTfin?(A(xs))}
var
act:Enlace;
inicio
act:=xs.ant^.sig;
siact==nil
entonces
error(Partederechavaca)
sino
xs.ant:=act
fsi
{Q0:R(xs)A(xs)=SEC[ELEM]avanza(A(XS))}
Fproc
procreinicia(esxs:Sec[Elem]);
xn
390
{P0:R(xs)}
inicio
xs.ant:=xs.pri
{Q0:R(xs)A(xs)=SEC[ELEM]reinicia(A(XS))}
fproc
funcvaca?(xs:Sec[Elem])devr:Bool;
{P0:R(xs)}
inicio
r:=xs.pri^.sig==nil
{Q0:r=SEC[ELEM]vaca?(A(xs))}
devr
ffunc
funcfin?(xs:Sec[Elem])devr:Bool;
{P0:R(xs)}
inicio
r:=xs.ant^.sig==nil
{Q0:r=SEC[ELEM]fin?(A(xs))}
devr
ffunc
4.5.3
Como hemos indicado anteriormente, las operaciones de las secuencias estn pensadas para
representar colecciones de datos que se pueden recorrer.
Esquema de recorrido de una secuencia
Un procedimiento genrico de recorrido:
procrecorre(esxs:Sec[elem]);
{P0:xs=XSpiz(xs)=[]}
var
x:Elem;
inicio
%reinicia(xs);sinosupusisemosquepiz(xs)=[]
{I:i:1did#piz(xs):tratado(piz(xs)!!i);
C:#pdr(xs)}
itNOTfin?(xs)o
x:=actual(xs);
tratar(x);
avanza(xs)
fit
{Q0:cont(xs)=cont(XS)fin?(xs)
391
i:1did#piz(xs):tratado(piz(xs)!!i)
fproc
Como variante de este esquema, podramos no pedir piz(xs)=[ ] en P0, y no comenzar con reinicia(xs). De esta forma se recorre la secuencia desde el punto de inters hasta el final.
Ntese que en este ejemplo no hemos cualificado las operaciones con el nombre del TAD. La
razn es que estamos usando un nico TAD y, por lo tanto, no hay posibilidad de colisiones.
Ntese tambin que aunque piz es una operacin privada del TAD, s la utilizamos en las especificaciones de los procedimientos que usan el TAD.
Esquema de bsqueda de una secuencia
Un procedimiento genrico de recorrido:
procbusca(esxs:Sec[elem];sencontrado:Bool);
{P0:xs=XSpiz(xs)=[]}
var
x:Elem;
inicio
%reinicia(xs);sinosupusisemosquepiz(xs)=[]
encontrado:=falso;
{I:i:1did#piz(xs):prop(piz(xs)!!i)
encontradooNOTfin?(xs)prop(actual(xs)));
C:#pdr(xs)}
392
de ser estricta. Si la lgica es estricta, entonces cuando una parte de una frmula est indefinida,
lo est la frmula entera. Operacionalmente podemos interpretar la implicacin del ejemplo como que cuando la parte izquierda de la implicacin es falso, entonces afirmo que la implicacin es
cierta, y no me preocupo por el lado derecho.
Me corrijo, en realidad en este caso es inevitable utilizar un aserto que puede estar indefinido,
porque la alternativa podra ser:
(encontrado)(encontradoprop(act(xs)))
Como variante de este esquema, podramos no pedir piz(xs)=[ ] en P0, y no comenzar con reinicia(xs). De esta forma se busca en la secuencia desde el punto de inters hacia la derecha.
Si prop(x) x == u, con u dado como parmetro, entonces se trata de la bsqueda de un valor dentro de una secuencia.
Ejemplos de aplicacin de los esquemas de recorrido y bsqueda
Conteo en una secuencia de enteros
Dada xs : Sec[Ent], queremos contar cuntas posiciones hay en ella que contengan un entero
igual a la suma de los enteros en todas las posiciones anteriores. Este es un ejemplo de aplicacin
del esquema de recorrido:
procconteo(esxs:Sec[Ent];sr:Ent);
{P0:xs=XSpiz(xs)=[]}
var
x:Elem;
s:Ent; %llevalasumahastaesemomento
inicio
s:=0;
{I:cont(xs)=cont(XS)
r=#i:1did#piz(xs):
cont(xs)!!i=6j:1dj<i:cont(xs)!!j
s=6i:1did#piz(xs):cont(xs)!!i;
C:#pdr(xs)}
itNOTfin?(xs)o
x:=actual(xs);
six==s
entonces
<r,s>:=<r+1,s+x>
sino tratar(x)
s:=s+x
fsi;
avanza(xs)
fit;
393
{Q0:cont(xs)=cont(XS)fin?(xs)
r=#i:1did#cont(xs):
cont(xs)!!i=6j:1dj<i:cont(xs)!!j}
fproc
Hay que implementarlo como un procedimiento porque se modifica el punto de inters de xs.
Bsqueda en una secuencia de enteros
Dada xs : Sec[Ent], queremos buscar la primera posicin donde aparezca un entero que no sea
mayor o igual que todos los anteriores, es decir, el primero que sea menor que el situado a su
izquierda. Grficamente:
x1dx2ddxp>xp+1
No consideramos al primero como candidato. Por ello, eso lo implementamos como una modificacin del esquema de bsqueda, donde tratamos por separado al primer elemento, lo cual
nos obliga a verificar que efectivamente hay un primer elemento i.e., la secuencia no est vaca.
procbusca(esxs:Sec[Ent];sencontrado:Bool);
{P0:piz(xs)=[]}
var
x:Elem;
m:Ent; %paraguardarelanterior
inicio
encontrado:=Falso;
sivaca?(xs)
entonces
seguir
sino
m:=actual(xs);
avanza(xs);
{I:noDecreciente(piz(xs))m=ultimo(piz(xs))
encontradooNOTfin?(xs)actual(xs)<m;
C:#pdr(xs)}
itNOTfin?(xs)ANDNOTencontradoo
x:=actual(xs);
six<m
entonces
encontrado:=cierto
sino
m:=x;
avanza(xs)
fsi
fit
fsi
{Q0:cont(xs)=cont(XS)noDecreciene(piz(xs))
394
encontradoli:2did#cont(xs):cont(xs)!!i<cont(xs)!!i
1
encontradooactual(xs)<ltimo(piz(xs))
}
Donde el predicado auxiliar noDecreciente indica que la lista est ordenada en orden no decreciente:
noDecreciente(ls)defi,j:1di<jd#ls:(ls!!i)d(ls!!j)
395
{Q0:cont(xs)=cont(XS)cont(ys)=cont(YS)ordenada(cont(zs))
permutacin(cont(zs),cont(xs)++cont(ys))}
fproc
396
4.6 Ejercicios
Pilas
177. Las pilas formadas por nmeros naturales del intervalo [0..N1] (para cierto N t 2 fijado de
antemano) se pueden representar por medio de nmeros, entendiendo que un nmero natural P cuya representacin en base N tenga 1 como dgito de mayor peso representa la
pila formada por los restantes dgitos de la representacin de P en base N (excepto el de
mayor peso), siendo la cima el dgito de menor peso. Por ejemplo, si N = 10, el nmero
1073 representa la pila que contiene los nmeros 0, 7, 3, con 0 en la base y 3 en la cima.
Formaliza el invariante de la representacin y la funcin de abstraccin siguiendo esta idea.
Comprueba que esta representacin permite implementar todas las operaciones de las pilas
como funciones de coste O(1).
179. Desarrolla una implementacin de las operaciones del TAD PILA mediante funciones,
180. Supongamos disponible un mdulo que exporte pilas de caracteres, implementadas con la
tcnica de los ejercicios 178 y 179. Representa grficamente la estructura resultante de ejecutar lo siguiente:
var
p,p1,p2,p3,p4:Pila[Car];
p:=PilaVaca();
p1:=Apilar(b,Apilar(a,p));
p2:=Apilar(c,p1);
p3:=Apilar(d,p1);
p4:=Apilar(e,p3);
p3:=Apilar(f,p3);
p1:=desapilar(p4);
Observa que en esta implementacin funcional de las pilas, la ejecucin de una operacin
nunca destruye estructuras creadas previamente por la ejecucin de otras operaciones. Qu suceder con el espacio ocupado por la estructura, si continuamos ejecutando lo que sigue?
p:=PilaVaca();
p1:=PilaVaca();p2:=PilaVaca();
p3:=PilaVaca();p4:=PilaVaca();
181. Usando la representacin del ejercicio 178, especifica y programa un procedimiento con
cabecera
procanular(esxs:Pila[Elem])
cuyo efecto sea liberar todo el espacio ocupado por xs antes de la llamada, dejando como nuevo valor de xs la representacin de la pila vaca.
397
182. Usando la representacin del ejercicio 178, completa el desarrollo de una implementacin
del TAD PILA que realice las operaciones generadoras y modificadoras como procedimientos. Comenta las ventajas e inconvenientes frente a la implementacin esttica de las
pilas, estudiada en los ejercicios 165, 166 y 172.
183. Especifica un TAD parametrizado DOS_PILAS[E :: ANY], con un tipo Pilas[Elem] y ope-
raciones adecuadas. La idea es que un dato de tipo Pilas representa una pareja de pilas, que
pueden manejarse independientemente del modo usual. Desarrolla una implementacin
esttica de este TAD, representando la pareja de pilas con ayuda de un nico vector de almacenamiento. Organiza la implementacin de modo que las dos pilas crezcan en sentidos
opuestos, cada una desde uno de los dos extremos del vector.
184. Especifica un enriquecimiento SUPER_PILA del TAD PILA, aadiendo una nueva opera-
desapilarK:(Nat,Pila[Elem])oPila[Elem]
a apilar y m2 llamadas a desapilarK es O(m1), siempre que todas las llamadas se refieran a una
misma pila, inicialmente vaca.
es de la forma:
funcf(x:T)devy:S;
{P0:P(x);Cota:t(x)}
var
x:T;y:S;
%Otrasposiblesdeclaracioneslocales
inicio
sid(x)
entonces
{P(x)d(x)}
y;=r(x)
{Q(x,y)}
sino
{P(x)d(x)}
x:=s(x);
{x=s(x)P(x)}
y:=f(x);
{x=s(x)Q(x,y)}
y:=c(x,y);
{Q(x,y)}
fsi
{Q0:Q(x,y)}
398
devy
ffunc
Supongamos que la funcin s encargada de calcular los parmetros de la siguiente llamada recursiva posea una inversa s-1. En este caso, es posible transformar f en una definicin iterativa
equivalente, de la forma siguiente:
funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
{Inv.I:R(x,x);Cota:t(x)}
itd(x)o
{Id(x)}
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(x,x)y=f(x);Cota:m(x,x)}
itx/=xo
{Jxzx}
x:=s1(x);
y:=c(x,y)
{J}
fit
{Jx=x}
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc
siendo
R(x,x)
def
n:Nat:(x=sn(x)P(x)
i:0di<n:(P(si(x))d(si(x))))
399
cio 186. si la funcin inversa s-1 no existe o es muy costosa de calcular, se puede aplicar otra
transformacin a forma iterativa, usando una pila para almacenar los parmetros de las sucesivas llamadas recursivas. La versin iterativa de f queda ahora:
funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;
xs:Pila[T];
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
PilaVaca(xs);
{Inv.I:R(xs,x,x);Cota:t(x)}
itd(x)o
{Id(x)}
Apilar(x,xs);
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(xs,x,x)y=f(x);Cota:tamao(xs)}
itNOTesVaca(xs)o
{JesVaca(xs)}
x:=cima(xs);
y:=c(x,y);
desapilar(xs)
{J}
fit
{JesVaca(xs)}
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc
siendo
R(xs,x,x)
def
n:Nat:(x=sn(x)P(x)
i:0di<n:(P(si(x))d(si(x))si(x)=elem(i,xs)))
400
donde elem(i, xs) indica el elemento que ocupa el lugar i en la pila xs, contando el elemento del fondo como elem(0, xs)
(Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, cuyos parmetros reales, hasta x exclusive, estn apilados en xs)
tamao(xs)
=def
nmerodeelementosapiladosenxs
Aplica la transformacin que acabamos de definir a las siguientes funciones recursivas lineales:
(a)
(b)
(c)
*(d)
188. Enriquece la especificacin del TAD PILA[E :: ANY], aadiendo las ecuaciones que defi-
nan el comportamiento de las operaciones elem y tamao que hemos definido informalmente
en el ejercicio anterior.
189. En la prctica, cuando se aplica la transformacin de recursin lineal a iteracin que hemos
Modificando el esquema del ejercicio 187 segn esta idea, se obtiene la siguiente versin iterativa de f:
funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;u:Z;
us:Pila[Z];
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
PilaVaca(us);
{Inv.I:R(us,x,x);Cota:t(x)}
itd(x)o
{Id(x)}
u:=g(x);
Apilar(u,us);
x:=s(x)
{I}
401
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(us,x,x)y=f(x);Cota:tamao(us)}
itNOTesVaca(us)o
{JesVaca(us)}
u:=cima(us);
x:=h(u,x);
y:=c(x,y);
desapilar(us)
{J}
fit
{JesVaca(us)}
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc
siendo
R(us,x,x)
def
n:Nat:(x=sn(x)P(x)
i:0di<n:(P(si(x))d(si(x))g(si(x))=elem(i,us)))
donde elem(i, us) indica el elemento que ocupa el lugar i en la pila us, contando el elemento del fondo como elem(0, us)
(Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, y que en us est apilada informacin suficiente para recuperar los parmetros
reales de dichas llamadas)
tamao(us)
=def
nmerodeelementosapiladosenus
Comprueba que esta transformacin optimizada se puede aplicar a las funciones recursivas lineales del ejercicio 187, utilizando en todos los casos una pila de valores booleanos. Precisa quines son en cada caso las funciones denominadas g y h en el esquema.
Colas
190. Especifica algebraicamente un TAD genrico COLA[e :: ANY], partiendo de la siguiente
ColaVaca:oCola[Elem]
402
Aadir:(Elem,Cola[Elem])oCola[Elem]
avanzar:Cola[Elem]oCola[Elem]
primero:Cola[Elem]oElem
esVaca:Cola[Elem]oBool
191. Plantea una implementacin esttica del TAD COLA basada en un vector, similar a la que
ya conocemos para el caso de las pilas (ejercicios 165 y 166). Observa cmo evoluciona la
zona ocupada del vector al ir ejecutando llamadas a las operaciones Aadir y avanzar.
Cmo afecta este problema a la eficiencia de la implementacin?
192. Desarrolla una implementacin esttica del TAD COLA basada en un vector circular, reali-
193. Desarrolla una implementacin dinmica del TAD COLA, realizando las operaciones gene-
194. Usando la representacin del ejercicio 193, especifica y programa una funcin con cabecera
funccopia(xs:Cola[elem])devys:Cola[Elem]
que devuelva en ys un puntero a una estructura que represente a la misma cola que xs, pero
ocupando celdas de memoria diferentes de las que ocupa la estructura apuntada por xs. Observa
que la asignacin xs:=ys no tendra este efecto.
195. Una frase se llama palndroma si la sucesin de caracteres obtenida al recorrerla de izquier-
similar al de las colas, pero con operaciones que permiten el acceso a los dos extremos de la
sucesin de los elementos de la cola. La especificacin informal de las operaciones de las
colas dobles es como sigue:
DColaVaca:oDCola[Elem]
403
PonDetrs:(Elem,DCola[Elem])oDCola[Elem]
ponDelante:(Elem,DCola[Elem])oDCola[Elem]
quitaUlt:DCola[Elem]oDCola[Elem]
ltimo:DCola[Elem]oElem
quitaPrim:DCola[Elem]oDCola[Elem]
primero:DCola[Elem]oElem
esVaca:DCola[Elem]oBool
es O(n), debido a que el penltimo nodo slo puede localizarse recorriendo toda la estructura desde el primer nodo. Modifica la implementacin, de manera que la estructura representante de una cola est doblemente enlazada; cada nodo deber incorporar un puntero al
siguiente y un puntero al anterior. Implementa todas las operaciones en base a la nueva representacin, obteniendo procedimientos ejecutables en tiempo O(1).
En segundo lugar, X se transforma en la sucesin de caracteres X obtenida al ir tomando sucesivamente: el primer carcter de X; luego el ltimo; luego el segundo; luego el penltimo; etc.
Ejemplo: para X = Bond, James Bond, resultan:
X = BnodJo s, dBneam
404
Nula:oLista[Elem]
Cons:(Elem,Lista[Elem])oLista[Elem]
Generadora. Cons(x,xs) genera una nueva lista aadiendo x como primer elemento, por delante de los elementos de xs.
nula?:Lista[Elem]oBool
primero:Lista[Elem]oElem
resto:Lista[Elem]oLista[Elem]
(++):(Lista[Elem],Lista[Elem])oLista[Elem]
[_]:ElemoLista[Elem]
(#):Lista[Elem]oNat
(!!):(Lista[Elem],Nat)oElem
miembro:(Elem,Lista[Elem])oBool
ponDr:(Lista[Elem],Elem)oLista[Elem]
ltimo:Lista[Elem]oElem
inicio:Lista[Elem]oLista[Elem]
cin similar a las que ya conocemos para implementaciones estticas de pilas y colas. Qu
operaciones resultan problemticas, y por qu?
204. Adapta la representacin dinmica de las colas utilizada en el ejercicio 198 al TAD LISTA.
Usando esta representacin, programa una funcin copia que copie la estructura representante de una lista, y un procedimiento anula que libere todo el espacio ocupado por la estructura representante de una lista.
405
205. Localiza aquellas operaciones del TAD LISTA que tienen una anloga en el TAD DCOLA,
y comprueba que los algoritmos estudiados en los ejercicios 198 y 199 se pueden adaptar
para implementarlas, usando la representacin del ejercicio 204.
206. Usando la misma representacin del ejercicio anterior, desarrolla una implementacin pro-
de los dos ejercicios anteriores. Analiza los tiempos de ejecucin de las operaciones Estudia
si se necesitan estructuras compartidas (para mejorar la eficiencia), o copias de estructuras
(para proteger parmetros de entrada). Plantea posibles modificaciones del tipo representante, que sirvan para mejorar la eficiencia de alguna operacin.
(#), ( !! ) y ( ++ ), usando otras operaciones ms bsicas del TAD LISTA. Analiza el tiempo de ejecucin de los algoritmos obtenidos.
209. Usando la especificacin del TAD LISTA y razonando por induccin sobre la estructura de
los trminos generados, demuestra que son vlidas las siguientes ecuaciones. Atencin: cada
igualdad debe entenderse en el sentido de que sus dos miembros representan la misma lista
abstracta.
xs,ys,zs:Lista[Elem]:x,y:Elem:
(a)ltimo([xs\x])=x
(b)inicio([xs\x])=xs
*(c)xs++[]=xs
*(d)xs++(ys++zs)=(xs++ys)++zs
(e)xs++[ys\y]=[(xs++ys)\y]
210. Formaliza la especificacin Pre/Post de las dos funciones que siguen, y construye algorit-
(a) funccoge(n:Nat;xs:Lista[Elem])devus:Lista[Elem]
{P0:cierto}
{Q0:useslalistaformadaporlosnprimeroselementosdexs}
406
ffunc
(b) functira(n:Nat;xs:Lista[Elem])devvs:Lista[Elem]
{P0:cierto}
{Q0:vseslalistaformadaquitandolosnprimeroselementosdexs}
ffunc
211. Demuestra usando induccin que la igualdad
coge(n,xs)++tira(n,xs)=xs
vale para xs, ys : Lista[Elem] y n : Nat cualesquiera. Atencin: la igualdad debe entenderse en el
sentido de que sus dos miembros representan la misma lista abstracta.
212. La siguiente funcin recursiva calcula la inversa de una lista dada:
funcinv(xs:Lista[Elem])devys:Lista[Elem];
{P0:cierto}
inicio
sinula?(xs)
entoncesys:=Nula
sinoys:=inv(resto(xs))++[primero(xs)]
fsi
{Q0::1did(#xs):xs!!i=ys!!(#xs)i+1}
ffunc
(==):(Lista[Elem],Lista[Elem])oBool
(d):(Lista[Elem],Lista[Elem])oBool
ordenada:Lista[Elem]oBool
insertaOrd:(Elem,Lista[Elem])oLista[Elem]
407
214. Plantea una implementacin del TAD LISTA-ORD especificado en el ejercicio anterior,
extendiendo la implementacin de LISTA con funciones recursivas que realicen las operaciones extra de LISTA-ORD. Observa que no es necesario definir un tipo representante
para Lista[Elem], ya que Lista[Elem] se puede importar del mdulo que implementa a
LISTA. Tampoco es necesario usar punteros.
215. Construye una funcin recursiva simple que satisfaga la especificacin que sigue, y aplica el
mtodo de plegado-desplegado para transformarla en una funcin recursiva final ms general. Suponemos que se trata de listas ordenas de tipo Lista[Elem].
funcmezcla(xs,ys:Lista[Elem])devzs:Lista[Elem]
{P0:ordenada(xs)ordenada(ys)}
{Q0:ordenada(zs)permutacin(zs,xs++ys)}
iterativo.
217. Construye especificaciones Pre/Post y funciones recursivas que resuelvan los dos proble-
218. Usa las tcnicas de plegado-desplegado y eliminacin de la recursin final para transformar
los algoritmos recursivos obtenidos en el ejercicio anterior en algoritmos iterativos ejecutables en tiempo O(n), siendo n la longitud de xs.
bin llamadas listas con punto de inters. Una secuencia se comporta como una lista dividida en
una parte derecha y una parte izquierda. El punto de inters se imagina como el punto divisorio
entre estas dos partes. La especificacin informal de las operaciones de las secuencias es
como sigue:
Crea:oSec[Elem]
Inserta:(Sec[Elem],Elem)oSec[Elem]
borra:Sec[Elem]oSec[Elem]
actual:Sec[Elem]oElem
Avanza:Sec[Elem]oSec[Elem]
408
Reinicia:Sec[Elem]oSec[Elem]
vaca?:Sec[Elem]oBool
fin?:Sec[Elem]oBool
221. Desarrolla una implementacin dinmica del TAD secuencia realizando las operaciones
comn en muchas aplicaciones. Construye en cada caso un algoritmo que satisfaga la especificacin, suponiendo disponible un mdulo que implemente el TAD de las secuencias.
(a) Esquema de recorrido secuencial:
procrecorre(esxs:Sec[Elem]);
{P0:xs=XSpiz(xs)=[]}
{Q0:cont(xs)=cont(XS)fin?(xs)=cierto
tratarsehaaplicadoatodosloselementosdexs}
fproc
(b) Esquema de bsqueda secuencial:
procbusca(esxs:Sec[Elem];sencontrado:Bool);
{P0:xs=XSpiz(xs)=[]}
{Q0:cont(xs)=cont(XS)
(encontradolexisteenxsalgnelementoquecumplaprop)
(encontradooactual(xs)cumpleprop
loselementosdepiz(xs)nocumplenprop)}
fproc
NOTA: Si el esquema de bsqueda se usa para buscar un elemento dado z (es decir, si se tiene
prop(x) x = z), conviene modificar el planteamiento del esquema introduciendo z como
parmetro.
223. Resuelve los problemas que siguen aplicando el esquema de recorrido de secuencias. En
409
(c) Dada una secuencia de enteros, contar cuantas posiciones hay en ella tales que el en-
tero que aparece en esa posicin es igual a la suma de todos los precedentes.
224. Resuelve los problemas que siguen aplicando el esquema de bsqueda secuencial.
(a) Buscar la primera aparicin de b en una secuencia de caracteres dada.
(b) Buscar la primera aparicin de una consonante en una secuencia de caracteres dada.
(c) Dada una secuencia de enteros , buscar la primera posicin ocupada por un nmero
225. Algunos problemas de procesamiento de secuencias requieren modificar y/o combinar los
226. Construye un procedimiento iterativo que satisfaga la especificacin que sigue, suponiendo
procmezcla(esxs,ys:Sec[Elem];szs:Sec[Elem])
{P0:xs=XSys=YSordenada(cont(xs))piz(xs)=[]
ordenada(cont(ys))piz(ys)=[]}
{Q0:cont(xs)=cont(XS)cont(ys)=cont(YS)ordenada(cont(zs))
permutacin(cont(zs),cont(xs)++cont(ys))}
fproc
227. Una expresin aritmtica construida con los operadores binarios +, , *, / y operandos
(representados cada uno por un solo carcter) se dice que est en forma postfija si es o bien
un solo operando o dos expresiones en forma postfija una tras otra, seguidas inmediatamente de un operador. Lo que sigue es un ejemplo de una expresin escrita en la notacin
infija habitual, junto con su forma postfija:
Forma postfija: ABC/DE+*
Forma infija: (A/(BC))*(D+E))
Disea un algoritmo iterativo que calcule el valor de una expresin dada en forma postfija por
el siguiente mtodo: se inicializa una pila vaca de nmeros y se van recorriendo de izquierda a
derecha los caracteres de la expresin. Cada vez que se pasa por un operando, se apila su valor.
Cada vez que se pasa por un operador, se desapilan los dos nmeros ms altos de la pila, se componen con el operador, y se apila el resultado. Al acabar el proceso, la pila contiene un solo
nmero, que es el valor de la expresin. Representa la expresin dada como secuencia de caracteres, y supn disponible una funcin valor que asocie a cada operando su valor numrico.
228. Dado un nmero natural N t 2, se llaman nmeros afortunados a los que resultan de ejecutar
el siguiente proceso: se comienza generando una cola que contiene los nmeros desde 1
hasta N, en este orden; se elimina de la cola un nmero de cada 2 (es decir, los nmeros 1,
3, 5, etc.); de la nueva cola, se elimina ahora un nmero de cada 3; etc. El proceso termina
cuando se va a eliminar un nmero de cada m y el tamao de la cola es menor que m. Los
nmeros que queden en la cola en este momento son los afortunados. Disea un procedi-
410
miento que reciba N como parmetro y produzca una secuencia formada por los nmeros
afortunados resultantes.
(Indicacin: para eliminar de una cola de n nmeros un nmero de cada m, hay que reiterar n veces el siguiente proceso: extraer el primer nmero de la cola, y aadirlo al final de la misma, salvo
si le tocaba ser eliminado.)
Ms aplicaciones de los tipos de datos con estructura lineal
229. Estudia posibles implementaciones del TAD CJTO (cfr. ejercicios 154 y 156) usando listas
como representantes de los conjuntos. Debes suponer que las listas se importan de un
mdulo separado, sin tener acceso a su representacin interna. Compara con las implementaciones de conjuntos planteadas en el ejercicio 167.
230. Estudia una implementacin del TAD de los polinomios (cfr. ejercicio 151) usando listas
ordenadas (importadas de un mdulo separado) como representantes de polinomios. Discute las ventajas e inconvenientes con respecto a las implementaciones basadas en vectores
que se plantearon en el ejercicio 170.
231. Severino del Pino, profesor de arameo de la Universidad Imponente, ha detectado proble-
mas de aburrimiento entre su numeroso alumnado. Su colega Tadeo de la Tecla, del departamento de informtica, ha ofrecido ayudarle diseando un sistema informtico de control
de bostezos. Tadeo propone una especificacin de un TAD parametrizado BOSTEZOS[E
:: ORD], donde el parmetro E nos da un tipo Elem equipado con operaciones de igualdad
y orden, que representa a los alumnos. Tadeo propone que BOSTEZOS disponga de las
siguientes operaciones:
Crea:oBostezos[Elem]
Otro:(Elem,Bostezos[Elem])oBostezos[Elem]
borra:(Elem,Bostezos[Elem])oBostezos[Elem]
cuntos?:(Elem,Bostezos[Elem])oNat
listaNegra:Bostezos[Elem]oSec[Elem]
Observadora. Devuelve la secuencia ordenada de todos los elementos que tengan tres o ms bostezos registrados.
411
clase de tipos ORD. Se desea que CONSULTORIO ofrezca a sus usuarios un tipo principal Consultorio junto con las operaciones que se describen informalmente a continuacin:
Crea:oConsultorio
NuevoMdico:(Consultorio,Mdico)oConsultorio
Altera un consultorio, dando de alta a un nuevo mdico que antes no figuraba en el consultorio.
PideConsulta:(Consultorio,Mdico,Paciente)oConsultorio
siguientePaciente:(Consultorio,Mdico)oPaciente
Consulta el paciente a quien le toca el turno para ser atendido por un mdico;
ste debe estar dado de alta, y debe tener algn paciente que le haya pedido consulta.
atiendeConsulta:(Consultorio,Mdico)oConsultorio
Modifica un consultorio, eliminando el paciente al que le toque el turno para ser atendido
por un mdico; ste debe estar dado de alta, y debe tener algn paciente que le haya pedido
consulta.
tienePacientes:(Consultorio,Mdico)oBool
Reconoce si hay o no pacientes a la espera de ser atendidos por un mdico, el cual debe estar de alta.
(a) Construye una especificacin algebraica completa del TAD CONSULTORIO, inclu-
CONSULTORIO
del
TAD
233. En este ejercicio se trata de desarrollar un TAD MERCADO que modelice el comporta-
miento de un mercado de trabajo simplificado, donde las personas pueden ser contratadas y
despedidas por empresas. La especificacin de MERCADO usar (entre otros) los TADs
PERSONA (con tipo principal Persona) y EMPRESA (con tipo principal Empresa), que se
suponen ya conocidos. Suponemos adems que PERSONA y EMPRESA pertenecen a la
412
clase de tipos ORD. Se desea que MERCADO ofrezca a sus usuarios un tipo principal Mercado junto con las operaciones que se describen informalmente a continuacin:
Crea
Contrata
despide
Altera un mercado, efectuando el despido de cierta persona que era antes empleado de cierta empresa.
empleados
Consulta los empleados de una empresa, devolviendo el resultado como secuencia ordenada de personas.
empleado?
Averigua si es cierto o no que una persona dada es empleado de una empresa dada.
pluriempleado?
TADs que se necesite usar, los perfiles de las operaciones, la clasificacin de las operaciones en generadoras, modificadoras y observadoras, y los dominios de definicin
de las operaciones parciales (si las hay).
(b) Plantea un mdulo de implementacin para el TAD MERCADO, del modo indicado
en el enunciado del ejercicio anterior. Elige el tipo representante de Mercado de manera que puedas realizarlo utilizando TADs conocidos con estructura lineal, importndolos de mdulos que los implementen, y sin acceder a la representacin interna.
(c) Completa el mdulo de implementacin del apartado (b) desarrollando los procedimientos y funciones que implementan las operaciones de MERCADO, y analizando
sus tiempos de ejecucin en funcin de las siguientes medidas del tamao de un mercado: NE, el nmero de empresas; y NP, el nmero mximo de personas contratadas
por una misma empresa.
234. Especifica un TAD BANCO adecuado para modelizar el comportamiento de un banco
simplificado, incluyendo operaciones que sirvan para crear un banco vaco (i.e., sin ninguna
cuenta), abrir y cerrar cuentas, efectuar ingresos y extracciones en una cuenta, preguntar si
un cliente tiene cuenta, consultar el saldo de una cuenta, etc. Desarrolla una implementacin de este TAD, usando listas o secuencias, importadas de un mdulo separado, para
construir la representacin de los bancos.
235. Supongamos disponible un mdulo que implemente el TAD BANCO del ejercicio ante-
rior. Disea un procedimiento que procese una cola de solicitudes de servicios de clientes de un
banco, dando como resultados un nuevo estado del banco y una secuencia de incidencias. Cada
solicitud debe representar una peticin de ingreso o extraccin de una cantidad por parte de
un cliente, y cada incidencia debe representar un cliente que ha solicitado realizar una operacin indefinida. Adems del mdulo de datos BANCO, debes suponer disponibles otros
dos mdulos SOLICITUDES e INCIDENCIAS, que exporten los tipos Solicitud e Incidencia, respectivamente, junto con operaciones adecuadas. Especifica los TADs correspondientes al comportamiento abstracto de estos dos mdulos, e indica claramente qu otros
mdulos de datos necesita utilizar el procedimiento que disees.
413
236. En este ejercicio se trata de automatizar algunos aspectos de la gestin de una biblioteca
anterior. Disea un procedimiento que procese una cola de solicitudes de prstamo de usuarios
de una biblioteca, dando como resultados un nuevo estado de la biblioteca y una secuencia de
incidencias. Cada solicitud debe identificar a un usuario y a un libro solicitado en prstamo
por ste, y cada incidencia debe identificar una operacin de prstamo que no ha podido
ejecutarse, indicando el usuario solicitante y el motivo de la incidencia (libro no existente,
ejemplares no disponibles, etc.). Adems del mdulo de datos BIBLIOTECA, debes suponer disponibles otros dos mdulos SOLICITUDES e INCIDENCIAS, lo mismo que en el
ejercicio 235.
414
rboles
CAPTULO 5
RBOLES
5.1 Modelo matemtico y especificacin
Los rboles son estructuras jerrquicas formadas por nodos, de acuerdo con la siguiente construccin inductiva:
Un solo nodo forma un rbol a; se dice que el nodo es raz del rbol.
a
a1
an
a1
an
Arboles genealgicos
Arboles taxonmicos
+
2
415
rboles
condicin
entonces
sino
<
:=
:=
*
-
+
2
*
3
*
x
Clases de rboles
Generales o n-arios. Un rbol se llama general si no hay una limitacin fijada al nmero
de hijos de cada nodo. Si el nmero de hijos est limitado a un valor fijo n, se dice que el
rbol es de grado n.
Con o sin punto de inters. En un rbol con punto de inters hay un nodo distinguido
(aparte de la raz, que es distinguida en cualquier rbol). Nosotros vamos a estudiar fundamentalmente rboles sin punto de inters.
5.1.1
Arboles generales
Modelo matemtico
En Matemtica discreta se suele definir el concepto de rbol como grafo conexo acclico con
un nodo distinguido como raz. Sin embargo, esta definicin no refleja el orden entre los hijos, ni
tampoco las informaciones asociadas a los nodos.
Nosotros vamos a adoptar un modelo de rbol basado en la idea de representar las posiciones
de los nodos como cadenas de nmeros naturales positivos:
416
rboles
Si un cierto nodo de un rbol tiene posicin D +*, el hijo nmero i de ese nodo tendr
posicin D.i (que indica D seguida de i).
Ejemplo:
A
B 1
A 1.1
D 3
A 2
C 1.2
E
3.1
B
3.2
D
3.3.1
3.3
3.4
E
3.3.2
donde N +* es el conjunto de posiciones de los nodos, y V es el conjunto de valores posibles para las informaciones que se asocian a los nodos.
Volviendo al ejemplo:
N={H,1,2,3,1.1,1.2,3.1,3.2,3.3,3.4,3.3.1,3.3.2}
a(H)=A
a(1)=Ba(2)=Ca(3)=D etc.
N es finito.
H N posicin de la raz.
D.i N D N
Nodo es cada posicin, junto con la informacin asociada: (D, a(D)), siendo D N.
417
rboles
Hojas son los nodos de posicin D tal que no existe i tal que D.i N.
Nodos internos son los nodos que no son hojas.
Camino es una sucesin de nodos tal que cada uno es padre del siguiente:
D, D.i1, , D.i1. .in
El nivel o profundidad de un nodo de posicin D es |D|+1. Es decir: n+1, siendo n la longitud del camino (nico) que va de la raz al nodo. En particular, el nivel de la raz es 1. El
rbol del ejemplo tiene nodos a 4 niveles. El nivel de un nodo es igual al nmero de nodos del camino que va desde la raz al nodo.
La talla, altura o profundidad de un rbol es el mximo de todos los niveles de nodos del
rbol. Equivalentemente: 1+n, siendo n el mximo de las longitudes de las ramas. el rbol
del ejemplo es de talla 4.
Si hay un camino del nodo de posicin D al nodo de posicin E (i.e., si D es prefijo de E),
se dice que D es antepasado de E y que E es descendiente de D.
Cada nodo de un rbol a determina un subrbol a0 con raz en ese nodo. Formalmente, si el
nodo tiene posicin D, entonces:
a0:N0oV
siendo
N0=def{E+*|DEN}
a0(E)=defa(DE) paracadaEN0
Dado un rbol a, los subrboles de a (si existen), se llaman rboles hijos de a. El rbol del
ejemplo tiene 3 rboles hijos.
Construir rboles.
Para construir un rbol a partir de su raz y sus hijos, se plantea el problema de que el nmero
de hijos es variable. Es por ello que el TAD, adems del tipo principal Arbol[Elem], incluye el tipo
Bosque[Elem], que representan sucesiones finitas de rboles. Las operaciones generadoras de bosques siguen la misma idea que las generadoras de listas.
418
rboles
tadARBOL[E::ANY]
usa
BOOL,NAT
tipos
Arbol[Elem],Bosque[Elem]
operaciones
[]:oBosque[Elem]
/*gen*/
[_/_]:(Arbol[Elem],Bosque[Elem])oBosque[Elem]
/*gen*/
rbol:(Nat,Bosque[Elem])oArbol[Elem]
/*obs*/
nrArboles:Bosque[Elem]oNat
/*obs*/
esVaco:Bosque[Elem]oBool
/*obs*/
Cons:(Elem,Bosque[Elem])oArbol[Elem]
/*gen*/
hijo:(Nat,Arbol[Elem])oArbol[Elem]
/*mod*/
nrHijos:Arbol[Elem]oNat
/*obs*/
hijos:Arbol[Elem]oBosque[Elem]
/*obs*/
raz:Arbol[Elem]oElem
/*obs*/
esHoja:Arbol[Elem]oBool
ecuaciones
/*obs*/
i:Nat:as:Bosque[Elem]:a:Arbol[Elem]:x:Elem:
defhijo(i,a)siSuc(Cero)didnrHijos(a)
hijo(i,a) =frbol(i,hijos(a))
nrHijos(Cons(x,as))=nrArboles(as)
hijos(Cons(x,as))
=as
raz(Cons(x,as)) =x
esHoja(a) =nrHijos(a)==0
errores
i:Nat:as:Bosque[Elem]:a:Arbol[Elem]:
rbol(i,as)siNOT(Suc(Cero)didnrArboles(as))
defrbol(i,as)siSuc(Cero)didnrArboles(as)
rbol(i,[a/as]) =a sii=Suc(Cero)
rbol(i,[a/as]) =frbol(i1,as)sii>Suc(Cero)
nrArboles([]) =Cero
nrArboles([a/as]) =Suc(nrArboles(as))
esVaco(as) =nrArboles(as)==Cero
hijo(i,a)siNOT(Suc(Cero)didnrHijos(a))
ftad
419
rboles
5.1.2
Arboles binarios
Modelo matemtico
Los rboles binarios se definen de tal modo que cada nodo interno tiene como mximo dos
hijos. Las posiciones de los nodos de estos rboles pueden representarse como cadenas
D {1,2}*. En caso de que un nodo interno (con posicin D) tenga un solo hijo, se distingue si
ste es hijo izquierdo (con posicin D.1) o hijo derecho (con posicin D.2).
Estas ideas conducen a modelar un rbol binario etiquetado con informaciones de V como
una aplicacin:
a:NoV
donde el conjunto N de posiciones de los nodos de a debe cumplir las siguientes condiciones:
N {1,2}*, finito.
D.i N D N
D.1 N y D.2 N
.1
D.1 N y D.2 N
dos hijos
.2
.1
D.1 N y D.2 N
D.1 N y D.2 N
420
rboles
Vemos que en los rboles binarios no se exige que no haya huecos en la serie de hijos de un
nodo. Por esto, algunos rboles binarios pueden no ser vlidos como rboles generales.
Cuando falta alguno de los hijos de un nodo interno, se dice tambin que el hijo inexistente es
vaco. En particular, podemos decir que las hojas tienen dos hijos vacos.
As, aceptamos la idea de rbol vaco, cuyo conjunto de posiciones es (el subconjunto vaco
de {1, 2}*.
Toda la terminologa y conceptos bsicos que hemos estudiado para los rboles generales,
pueden adaptarse sin dificultad a los rboles binarios.
Especificacin algebraica de los rboles binarios
Gracias al concepto de rbol vaco, podemos suponer que cualquier rbol binario no vaco se
construye a partir de un elemento raz y dos rboles hijos (que pueden a su vez ser o no vacos).
Esto conduce a la signatura del TAD, con operaciones para:
Vaco:oArbin[Elem] /*gen*/
Cons:(Arbin[Elem],Elem,Arbin[Elem])oArbin[Elem] /*gen*/
hijoIz,hijoDr:Arbin[Elem]oArbin[Elem]
/*mod*/
raz:Arbin[Elem]oElem /*obs*/
esVaco:Arbin[Elem]oBool/*obs*/
ecuaciones
iz,dr:Arbin[Elem]:x:Elem:
defhijoIz(Cons(iz,x,dr))
hijoIz(Cons(iz,x,dr)) =iz
defhijoDr(Cons(iz,x,dr))
hijoDr(Cons(iz,x,dr)) =dr
defraz(Cons(iz,x,dr))
raz(Cons(iz,x,dr)) =x
esVaco(Vaco) =cierto
esVaco(Cons(iz,x,dr)) =falso
errores
hijoIz(Vaco)
hijoDr(Vaco)
raz(Vaco)
ftad
421
rboles
5.1.3
Arboles n-arios
Se definen como generalizacin de los rboles binarios, de tal manera que cada nodo interno
tiene exactamente n hijos ordenados, cualquiera de los cuales puede ser vaco (i.e., se admiten
huecos en la sucesin de hijos de un nodo). En particular, las hojas tienen n hijos vacos.
Observemos que rbol n-ario no es lo mismo que rbol de grado n, de acuerdo con la definicin de grado o aridad de un rbol que dimos anteriormente. En un rbol de grado n, cada nodo tiene como mucho n hijos, pero distintos nodos pueden tener distinto nmero de hijos, y los
hijos existentes ocupan posiciones consecutivas.
Ejemplo. a1 es un rbol general de grado 2, pero no es un rbol binario. Los rboles a2 y a3 s
son binarios:
1.1
1.2
1.1
a1
5.1.4
1.2
a2
2.1
2.2
a3
Como representacin matemtica de un rbol con punto de inters podemos adoptar una pareja de la forma:
(a,D0)
Insertar un nuevo nodo, o todo un rbol, como hijo del punto de inters.
422
rboles
5.2.1
Tipo representante
tipo
Nodo=reg
ra:Elem;
iz,dr:Enlace;
freg;
Enlace=punteroaNodo;
Arbin[Elem]=Enlace;
Invariante de la representacin
Sea p : Enlace. Consideramos dos posibilidades:
siendo
sip=nil
enlaces(p)=def
{p}enlaces(p^.iz)enlaces(p^.dr) sipznil
423
rboles
x2
Funcin de abstraccin
Sea a : Arbin[Elem] tal que R(a)
Vaco sia=nil
A(a)=def
Cons(A(a^.iz),A(a^.elem),A(a^.dr))siaznil
424
rboles
funchijoIz(a:Arbin[Elem])deviz:Arbin[Elem]; /*O(1)*/
{P0:R(a)NOTesVaco(A(a))}
inicio
siesVaco(a)
entonces
error(Elrbolvaconotienehijoizquierdo)
sino
iz:=a^.iz
fsi
{Q0:R(iz)A(iz)=ARBIN[ELEM]hijoIz(A(a))}
deviz
ffunc
Comparticin de estructura
Como ya hemos comentado, esta implementacin de los rboles da lugar a comparticin de
estructura. El problema, ya conocido, de la comparticin de estructura est en que al liberar el
espacio ocupado por una variable puede destruirse el valor de otra. La solucin ideal radica en
que el programador no tenga que preocuparse por la anulacin de las estructuras, sino que sea el
sistema de gestin de memoria quien se encargue de ello: recoleccin automtica de basura.
Con esta implementacin, el cliente del TAD debe indicar expresamente cundo quiere evitar
la comparticin de estructura, realizando copias de los parmetros:
a:=Cons(copia(a1),x,copia(a2))
425
rboles
426
rboles
Tambin podra venir bien que el mdulo de los rboles exportase una operacin de igualdad.
5.2.2
Esta implementacin se basa en simular la memoria dinmica con un vector, de forma que los
punteros sean ndices dentro de dicho vector. Esta implementacin se describe en la subseccin
5.2.1, pp. 229-232, del libro de Franch.
5.2.3
En este apartado estudiamos una tcnica vlida para representar un rbol binario en un vector
sin ayuda de encadenamientos. La idea consiste en calcular, en funcin de la posicin de cada
nodo del rbol, el ndice del vector donde vamos a almacenar la informacin asociada a ese nodo.
Para hacer esto necesitamos establecer una biyeccin entre posiciones y nmeros positivos. Podemos conseguir una numeracin de las posiciones por niveles, y de izquierda a derecha dentro
de cada nivel. Si comenzamos asociando el nmero 1 a la posicin H, resulta:
1
2
1
2
1.2
1.1
4
8
1.1.1
3
2.1
7
5
9
10
1.1.2 1.2.1
2.2
11 12
1.2.2 2.1.1
13 14
15
2.1.2 2.2.1 2.2.2
427
rboles
Con ayuda de ndice, podemos representar un rbol binario en un vector, almacenando la informacin del nodo D en la posicin ndice(D). Por ejemplo:
A
1 B
C 2
2.1 D
E 2.1.2
donde tenemos
ndice(H)=1
ndice(1)=2ndice(H)=2
ndice(2)=2ndice(H)+1=3
ndice(2.1)=2ndice(2)=6
ndice(2.1.2)=2ndice(2.1)+1=13
D
4
E
7
10
11 12 13 14
Como vemos, pueden quedar muchos espacios desocupados si el rbol tiene pocos nodos en
relacin con su nmero de niveles. Por este motivo, esta representacin slo se suele aplicar a
una clase especial de rboles binarios, que definimos a continuacin.
Un rbol binario de talla n se llama completo si y slo si todos sus nodos internos tienen
dos hijos no vacos, y todas sus hojas estn en el nivel n.
428
rboles
Y este es semicompleto:
Un rbol binario completo de talla n tiene el mximo nmero de nodos que puede tener un
rbol de esa talla. En concreto se verifica:
1. El nmero de nodos de cualquier nivel i en un rbol binario completo es mi = 2 i1
2. El nmero total de nodos de un rbol binario completo de talla n es Mn = 2 n 1. Y, por
lo tanto, la talla de un rbol binario completo con M nodos es log(M+1).
Como se puede demostrar fcilmente:
1. Induccin sobre i :
i=1 m1=1=211
i>1 mi=2mi1=H.I.22i2=2i1
Mn=
i 1
mi=
2i1=2n1
i 1
Usando estos dos resultados podemos demostrar que la definicin recursiva de ndice presentada anteriormente es correcta. La definicin no recursiva es como sigue: si D es un posicin de
nivel n+1 (n t 0)
ndice(D) =nmerodeposicionesdeniveles1..n+
nmerodeposicionesdeniveln+1hastaDinclusive
=(2n1)+m
m
.1
.2
n+1
n+2
429
rboles
ndice(H)=1
ndice(D.1)= nmerodeposicionesdeniveles1..(n+1)+
nmerodeposicionesdeniveln+2hastaD.1inclusive
= (2n+11)+2(m1)+1=2n+1+2m2
= 2ndice(D)
ndice(D.2)= nmerodeposicionesdeniveles1..(n+1)+
nmerodeposicionesdeniveln+2hastaD.2inclusive
= (2n+11)+2(m1)+2=2n+1+2m1
= 2ndice(D)+1
Dado un nodo D almacenado en la posicin ndice(D) = i, tal que 1 d i d max, valen las siguientes frmulas para el clculo de otro nodos relacionados con i:
Esta representacin es til para rboles completos y semicompletos, cuando estos se generan y procesan mediante operaciones que hagan crecer al rbol por niveles habra que
cambiar la especificacin del TAD.
No es una representacin til para implementar rboles binarios cualesquiera, con las
operaciones del TAD ARBIN[ELEM].
5.2.4
Se consigue con modificaciones sencillas de las tcnicas que hemos estudiado para los rboles
binarios.
Implementacin dinmica
La idea consiste en representar los nodos como registros con tres campos:
430
rboles
Informacin asociada.
En realidad, esta idea se basa en la posibilidad de representa run rbol general cualquiera como
rbol binario, a travs de la siguiente conversin:
ARBOL GENERAL
Primer hijo
Hermano derecho
ARBOL BINARIO
Hijo izquierdo
Hijo derecho
Por ejemplo, el rbol general de la izquierda se representa como el rbol binario de la derecha:
E
C
F
G
de manera que resulten dos biyecciones inversas una de la otra. La especificacin ecuacional
de estas operaciones:
hazArbin([])=ARBIN.Vaco
431
rboles
hazArbin([ARBOL.Cons(x,hs)/as])=ARBIN.Cons(hazArbin(hs),x,
hazArbin(as))
hazBosque(ARBIN.Vaco)=[]
hazBosque(ARBIN.Cons(iz,x,dr))=[ARBOL.Cons(x,hazBosque(iz))/
hazBosque(dr)]
Se puede demostrar por induccin estructural que efectivamente una operacin es la inversa
de la otra:
as:Bosque[Elem]:hazBosque(hazArbin(as))=as
b:Arbin[Elem]:hazArbin(hazBosque(b))=b
5.2.5
Arboles n-arios
Se pueden adaptar todos los mtodos conocidos para los rboles binarios.
En las representaciones dinmicas, se puede optar por usar n punteros a los n hijos, o un
puntero al primero hijo y otro al hermano derecho. Cul es mejor en trminos de espacio?
Las representaciones dinmicas debern equiparse con punteros adicionales para permitir
una realizacin eficiente de las operaciones de desplazamiento del punto de inters. en
particular, resultar til un puntero al padre.
432
rboles
5.2.6
En las representaciones dinmicas hay que contar con el nmero de campos de enlace.
Si hay n nodos con k campos de enlace cada uno, la estructura ocupar espacio (k+x) n,
suponiendo que x sea el espacio ocupado por un elemento.
5.3 Recorridos
Recorrer un rbol consiste en visitar todos sus nodos en un cierto nodo, ya sea simplemente
para escribirlos, o bien para aplicar a cada uno de ellos un cierto tratamiento.
En este tema vamos a estudiar los recorridos de los rboles binarios, representando un recorrido como una funcin que transforma un rbol en la lista de elementos de los nodos visitados
en el orden de la visita.
Clases de recorridos
Los principales recorridos de rboles binarios se clasifican como sigue:
Preorden (RID)
Inorden (IRD)
Postorden (IDR)
En profundidad
Recorridos
Por niveles
Los recorridos en profundidad se basan en la relacin padre-hijos y se clasifican segn el orden en que se consideren la raz (R), el hijo izquierdo (I) y el hijo derecho (D).
Ejemplo:
1
2
4
5
8
Preorden:
Inorden:
Postorden:
Niveles:
1, 2, 4, 5, 8, 3, 6, 9, 7
4, 2, 8, 5, 1, 9, 6, 3, 7
4, 8, 5, 2, 9, 6, 7, 3, 1
1, 2, 3, 4, 5, 6, 7, 8, 9
3
6
9
433
rboles
Niveles: para rboles que representen espacios de bsqueda, este recorrido encuentra caminos de longitud mnima.
5.3.1
Recorridos en profundidad
Especificacin algebraica
Segn aparecen en las hojas con las especificaciones de los TAD:
tadRECPROFARBIN[E::ANY]
usa
ARBIN[E],LISTA[E]
operaciones
preOrd,inOrd,postOrd:Arbin[Elem]oLista[Elem]
ecuaciones
ftad
/*obs*/
iz,dr:Arbin[Elem]:x:Elem:
preOrd(Vaco)
=[]
preOrd(Cons(iz,x,dr))
=[x]++preOrd(iz)++preOrd(dr)
inOrd(Vaco)
=[]
inOrd(Cons(iz,x,dr))
=inOrd(iz)++[x]++inOrd(dr)
postOrd(Vaco)
=[]
postOrd(Cons(iz,x,dr))
=postOrd(iz)++postOrd(dr)++[x]
434
rboles
LISTA.Nula(xs)
sino
iz:=preOrd(hijoIz(a));
dr:=preOrd(hijoDr(a));
LISTA.Cons(ARBIN.raz(a),iz);
LISTA.conc(iz,dr);
xs:=iz %noseanulaniz,drporquecompartenestructura
conxs
fsi
{Q0:xs=preOrd(a)}
devxs
ffunc
En cuanto a la complejidad de esta operacin, suponemos una implementacin de las listas donde Nula, conc, ponDr y Cons tengan coste O(1) lo que nos obliga a utilizar implementaciones procedimentales de conc y ponDr. En ese caso obtenemos una complejidad para los tres
recorridos de O(n), como se puede ver aplicando los resultados tericos. Se trata de funciones
donde el tamao del problema disminuye por divisin:
c1
si 0 n < b
a T(n/b) + c nk
si n b
O(nk)
si a < bk
O(nk log n)
si a = bk
O( n logb a )
si a > bk
T(n) =
T(n)
435
rboles
Es posible obtener algoritmos de recorrido iterativos con ayuda de pilas de rboles. La idea
bsica es: en vez de hacer llamadas recursivas para recorrer los rboles hijos, apilamos estos en un
orden tal que cuando luego los desapilemos, sean procesados en el orden correcto.
Veamos grficamente cmo funciona esta idea en un ejemplo. Vamos a recorrer en preorden
el rbol que representa a la expresin x*(y)+(x)*y
+
*
*
_
Mostramos la pila de rboles indicando las posiciones de las races de los rboles apilados en
ella, cuando estos se consideran como subrboles de a:
Pila (cima a la derecha)
Recorrido
H
+
2, 1
+*
2, 1.2, 1.1
+*x
2, 1.2
+*x
2, 1.2.1
+*xy
2
etc.
podemos generalizarla obteniendo otra operacin que acumula el efecto de R sobre una pila as
de rboles, concatenando las listas resultantes. Formalmente:
acumulaR:Pila[Arbin[Elem]]oLista[Elem]
acumulaR(PilaVaca)=[]
acumulaR(Apilar(a,as))=R(a)++acumulaR(as)
436
rboles
Usaremos las operaciones acumulaR para formular los invariantes de los algoritmos que siguen.
Tambin usaremos el concepto de tamao de un rbol binario, definido como la suma de nodos y
el nmero de arcos. Formalmente:
tamao:Arbin[Elem]oNat
tamao(Vaco) =0
tamao(Cons(iz,x,dr)) =1
esVaco(dr)
=2+tamao(iz)
esVaco(dr)
=2+tamao(dr)
esVaco(dr)
=3+tamao(iz)
+tamao(dr)
esVaco(dr)
siesVaco(ix)AND
siNOTesVaco(ix)AND
siesVaco(ix)ANDNOT
siNOTesVaco(ix)ANDNOT
Recorrido en preorden:
funcpreOrd(a:Arbin[Elem])devxs:Lista[Elem];
{P0:cierto}
var
as:Pila[Arbin[Elem]];
aux,iz,dr:Arbin[Elem];
x:Elem;
inicio
LISTA.Nula(xs);
siARBIN.esVaco(a)
entonces
seguir
sino
PILA.PilaVaca(as);
PILA.Apilar(a,as);
{I:preOrd(a)=xs++acumulapreOrd(as);
C:sumadelostamaosdelosrbolesdeas}
itNOTPILA.esVaca(as)o
aux:=PILA.cima(as); /*auxnoesvaco*/
PILA.desapilar(as);
x:=ARBIN.raz(aux);
iz:=ARBIN.hijoIz(aux);
dr:=ARBIN.hijoDr(aux);
LISTA.ponDr(xs,x);
/*visitarx*/
siARBIN.esVaco(dr) /*apilardr,iz*/
entonces
seguir
437
rboles
sino
PILA.apilar(dr,as)
fsi;
siARBIN.esVaco(iz)
entonces
seguir
sino
PILA.apilar(iz,as)
fsi
fit
fsi
{Q0:xs=preOrd(a)}
devxs
ffunc
Recorrido en postorden:
funcpostOrd(a:Arbin[Elem])devxs:Lista[Elem];
{P0:cierto}
var
as:Pila[Arbin[Elem]];
aux,iz,dr:Arbin[Elem];
x:Elem;
inicio
LISTA.Nula(xs);
siARBIN.esVaco(a)
entonces
seguir
sino
PILA.PilaVaca(as);
PILA.Apilar(a,as);
{I:postOrd(a)=xs++acumulapostOrd(as);
C:sumadelostamaosdelosrbolesdeas}
itNOTPILA.esVaca(as)o
aux:=PILA.cima(as); /*auxnoesvaco*/
PILA.desapilar(as);
x:=ARBIN.raz(aux);
iz:=ARBIN.hijoIz(aux);
dr:=ARBIN.hijoDr(aux);
siARBIN.esVaco(iz)ANDARBIN.esVaco(dr)
entonces
LISTA.ponDr(xs,x); /*visitarx*/
sino /*apilarx,dr,iz*/
PILA.Apilar(ARBIN.Cons(ARBIN.Vaco,x,ARBIN.Vaco),as);
siARBIN.esVaco(dr)
entonces
seguir
sino
438
rboles
PILA.apilar(dr,as)
fsi;
siARBIN.esVaco(iz)
entonces
seguir
sino
PILA.apilar(iz,as)
fsi
fsi
fit
fsi
{Q0:xs=postOrd(a)}
devxs
ffunc
Recorrido en inorden:
funcinOrd(a:Arbin[Elem])devxs:Lista[Elem];
{P0:cierto}
var
as:Pila[Arbin[Elem]];
aux,iz,dr:Arbin[Elem];
x:Elem;
inicio
LISTA.Nula(xs);
siARBIN.esVaco(a)
entonces
seguir
sino
PILA.PilaVaca(as);
PILA.Apilar(a,as);
{I:inOrd(a)=xs++acumulainOrd(as);
C:sumadelostamaosdelosrbolesdeas}
itNOTPILA.esVaca(as)o
aux:=PILA.cima(as); /*auxnoesvaco*/
PILA.desapilar(as);
x:=ARBIN.raz(aux);
iz:=ARBIN.hijoIz(aux);
dr:=ARBIN.hijoDr(aux);
siARBIN.esVaco(iz)ANDARBIN.esVaco(dr)
entonces
LISTA.ponDr(xs,x); /*visitarx*/
sino /*apilardr,x,iz*/
siARBIN.esVaco(dr)
entonces
seguir
sino
PILA.apilar(dr,as)
439
rboles
fsi;
PILA.Apilar(ARBIN.Cons(ARBIN.Vaco,x,ARBIN.Vaco),as);
siARBIN.esVaco(iz)
entonces
seguir
sino
PILA.apilar(iz,as)
fsi
fsi
fit
fsi
{Q0:xs=inOrd(a)}
devxs
ffunc
Si todas las operaciones involucradas de pilas, rboles y listas tienen complejidad O(1) entonces los tres recorridos tienen complejidad O(n), siendo n el nmero de nodos. En el recorrido en
preorden cada nodo pasa una sola vez por la cima de la pila, por lo tanto se realizan n iteraciones.
En los recorridos en inorden y postorden los nodos internos pasan 2 veces y las hojas una sola vez,
por lo tanto se realizan menos de 2n pasadas.
En cuanto a la complejidad en espacio, debemos preguntarnos cul es el mximo nmero de
elementos que debe almacenar la pila; considerando que en una implementacin dinmica cada
nodo de la pila ocupa espacio 2: 1 por el puntero al rbol y 1 por el puntero al siguiente nodo. El
caso peor se da para rboles de la forma:
A
H
I
G
E
C
440
rboles
El caso mejor para preOrd se da cuando cada nodo interno tiene exactamente un hijo. En ese
caso se mantiene una pila con un solo nodo.
Con un rbol totalmente degenerado hacia la izquierda, inOrd y postOrd tambin estn en el caso peor de construir pilas con n elementos.
Con un rbol totalmente degenerado hacia la derecha tenemos el caso mejor para inOrd porque la pila alcanza un tamao mximo de 2. En cambio para postOrd, sigue siendo el caso peor
con una pila de tamao n.
Cuando tenemos un rbol completo con n nodos, tenemos el caso intermedio para preOrd con
un tamao mximo para la pila de log n. Este es el caso mejor para postOrd con tamao 2log n. Y
tambin caso intermedio para inOrd con tamao 2log n.
5.3.2
En este caso, el orden recorrido no est tan relacionado con la estructura recursiva del rbol y
por lo tanto no es natural utilizar un algoritmo recursivo. Se utiliza una cola de rboles, de forma
que para recorrer un rbol con hijos, se visita la raz y se ponen en la cola los dos hijos. Veamos
grficamente cmo funciona esta idea el recorrido por niveles del ejemplo anterior, x*(y)+(x)*y
+
*
*
_
Mostramos la cola de rboles indicando las posiciones de las races de los rboles almacenados
en ella, cuando estos se consideran como subrboles de a:
Cola (ltimo a la derecha)
Recorrido
H
+
1, 2
+*
2, 1.1, 1.2
+**
1.1, 1.2, 2.1, 2.2
+**x
1.2, 2.1, 2.2
+**x
2.1, 2.2, 1.2.1
etc.
441
rboles
Especificacin algebraica
Necesitamos utilizar una cola de rboles auxiliar. La cola se inicializa con el rbol a recorrer. El
recorrido de la cola de rboles tiene como caso base la cola vaca. Si de la cola se extrae un rbol
vaco, se avanza sin ms. Y si de la cola se extrae un rbol no vaco, se visita la raz, y se insertan
en la cola el hijo izquierdo y el derecho, por este orden
tadRECNIVELESARBIN[E::ANY]
usa
ARBIN[E],LISTA[E]
usaprivadamente
COLA[ARBIN[E]]
operaciones
niveles:Arbin[Elem]oLista[Elem] /*obs*/
operacionesprivadas
nivelesCola:Cola[Arbin[Elem]]oLista[Elem] /*obs*/
ecuaciones
ftad
a:Arbin[Elem]:as:Cola[Arbin[Elem]]:
niveles(a) =nivelesCola(Aadir(a,ColaVaca))
nivelesCola(as)=[] siesVaca(as)
nivelesCola(as)=nivelesCola(Avanzar(as))
siNOTesVaca(as)ANDesVaco(primero(as))
nivelesCola(as)=[raz(primero(as))]++
nivelesCola(Aadir(hijoDr(primero(as)),
Aadir(hijoIz(primero(as)),
avanzar(as))))
siNOTesVaca(as)ANDNOTesVaco(primero(as))
donde a es el rbol dado para recorrer, xs es la lista con la parte ya construida del recorrido y
as es la cola de rboles pendientes de ser recorridos.
funcniveles(a:Arbin[Elem])devxs:Lista[Elem];
{P0:cierto}
var
as:Cola[Arbin[Elem]];
aux,iz,dr:Arbin[Elem];
x:Elem:
inicio
LISTA.Nula(xs);
siARBIN.esVaco(a)
442
rboles
entonces
seguir
sino
COLA.ColaVaca(as);
COLA.Aadir(a,as);
{I:niveles(a)=xs++nivelesCola(as)
todoslosrbolesdelacolaassonnovacos;
C:sumadelostamaosdelosrbolesdeas}
itNOTCOLA.esVaca(as)o
aux:=COLA.primero(as);
COLA.avanzar(as);
x:=ARBIN.raz(aux);
iz:=ARBIN.hijoIz(aux);
dr:=ARBIN.hijoDr(aux);
LISTA.ponDr(xs,x);
/*visitarx*/
siARBIN.esVaco(iz)
entonces
seguir
sino /*izalacola*/
COLA.Aadir(iz,as)
fsi;
siARBIN.esVaco(dr)
entonces
seguir
sino /*dralacola*/
COLA.Aadir(dr,as)
fsi
fit
fsi
{Q0:xs=niveles(a)}
devxs
ffun
443
rboles
5.3.3
Los rboles hilvanados son una representacin de los rboles binarios que permite algoritmos
O(n) de recorrido en profundidad, sin ayuda de una pila auxiliar. La idea fundamental es aprovechar los muchos punteros nulos que quedan desaprovechados en la representacin dinmica del
rbol binario. En particular, se puede demostrar por induccin que un rbol binario con n nodos
tiene 2n+1 subrboles, de los cuales n+1 son vacos; por lo tanto, su representacin dinmica
tendr n+1 punteros nulos.
Los punteros nulos se sustituyen por punteros que facilitan un tipo particular de recorridos. Se
necesita algo ms de espacio para cada nodo pues es necesario indicar si los punteros son normales o hilvanes.
5.3.4
El esquema de la transformacin est en los apuntes. La idea es que la ejecucin de un algoritmo doblemente recursivo se corresponde con un recorrido en postorden del rbol de llamadas
determinado por la llamada inicial. La pila representa en cada momento el camino desde la raz
hasta el nodo que se est visitando en un momento dado.
444
rboles
5.4.1
Arboles ordenados
Un rbol ordenado es un rbol binario que almacena elementos de un tipo de la clase ORD.
Se dice que el rbol a est ordenado si se da alguno de los dos casos siguientes:
a es vaco.
a no es vaco, sus dos hijos estn ordenados, todos los elementos del hijo izquierdo son
estrictamente menores que el elemento de la raz, y el elemento de la raz es estrictamente
menor que todos los elementos del hijo derecho.
20
12
31
17
43
26
35
ordenado?(Vaco) =cierto
ordenado?(Cons(iz,x,dr)) =ordenado?(iz)ANDmenor(iz,x)AND
ordenado?(dr)ANDmayor(dr,x)
menor(Vaco,y)=cierto
menor(Cons(iz,x,dr),y)=x<yANDmenor(iz,y)ANDmenor(dr,y)
mayor(Vaco,y)=cierto
mayor(Cons(iz,x,dr),y)=x>yANDmayor(iz,y)ANDmayor(dr,y)
Una cualidad muy interesante de los rboles ordenados es que su recorrido en inorden produce una lista ordenada de elementos. De hecho, esta es una forma de caracterizar a los rboles
ordenados:
Un rbol binario a es un rbol ordenado si y slo si xs = inOrd(a) est ordenada, es decir.
i,j:1di<jd#xs:xs!!i<xs!!j
Es posible construir distintos rboles ordenados con la misma informacin, o, dicho de otro
modo, dos rboles de bsqueda distintos pueden dar el mismo resultado al recorrerlos en inorden. Por ejemplo, el siguiente rbol produce el mismo recorrido que el presentado anteriormente:
26
20
12
7
35
31
17
43
445
rboles
Las operaciones que nos interesan sobre rboles ordenados son: la insercin, la bsqueda y el
borrado. En esencia, las tres operaciones tienen que realizar una bsqueda en el rbol ordenado,
donde se saca partido de dicha caracterstica. Resulta adems interesante, equipar a los rboles
ordenados con una operacin de recorrido y otra que nos indique si un determinado elemento
est o no en el rbol.
Insercin en un rbol ordenado
En principio, suponemos que si intentamos insertar un elemento que ya est en el rbol, el resultado es el mismo rbol. Al presentar los rboles de bsqueda refinaremos esta idea.
La insercin se especifica de forma que si el rbol est ordenado, siga estndolo despus de la
insercin. La insercin de un dato y en un rbol ordenado a:
Si a no es vaco:
inserta(y,Vaco) =Cons(Vaco,y,Vaco)
inserta(y,Cons(iz,x,dr)) =Cons(iz,x,dr)siy==x
inserta(y,Cons(iz,x,dr)) =Cons(inserta(y,iz),x,dr)siy<x
inserta(y,Cons(iz,x,dr)) =Cons(iz,x,inserta(y,dr))siy>x
Se puede observar que el primer ejemplo que presentamos de rbol ordenado es el resultado
de insertar sucesivamente: 20, 12, 17, 31, 26, 43, 7 y 35, en un rbol inicialmente vaco.
Bsqueda en un rbol ordenado
Especificamos esta operacin de forma que devuelva como resultado el subrbol obtenido
tomando como raz el nodo que contenga el rbol buscado; as, a continuacin, podramos obtener la informacin de ese nodo mediante la operacin que obtiene la raz del rbol. Si ningn
nodo del rbol, contiene el elemento buscado, se devuelve el rbol vaco.
La idea de la bsqueda es similar a la insercin: si el elemento buscado est en la raz, ya
hemos terminado; si es menor que la raz buscamos en el hijo izquierdo; y si es mayor que la raz,
buscamos en el hijo derecho. Algebraicamente, podemos especificarla como:
busca(y,Vaco)=Vaco
busca(y,Cons(iz,x,dr))=Cons(iz,x,dr)siy==x
busca(y,Cons(iz,x,dr))=busca(y,iz)siy<x
busca(y,Cons(iz,x,dr))=busca(y,dr)siy>x
446
rboles
Si D tiene un solo hijo, se elimina el nodo D y se coloca en su lugar el rbol hijo, cuya raz
quedar en la posicin D
Se borra el nodo D. Ntese que, por ser el mnimo del hijo derecho de D, D no puede tener hijo izquierdo, por lo tanto, estaremos en la situacin de eliminar una hoja o
un nodo con un solo hijo el derecho.
Especificado algebraicamente:
borra(y,Vaco) =Vaco
borra(y,Cons(iz,x,dr))=drsiy==xANDesVaco(iz)
borra(y,Cons(iz,x,dr))=izsiy==xANDesVaco(dr)
borra(y,Cons(iz,x,dr))=Cons(iz,z,borra(z,dr))
siy==xANDNOTesVaco(iz)AND
NOTesVaco(dr)ANDz=min(dr)
borra(y,Cons(iz,x,dr))=Cons(borra(y,iz),x,dr)siy<x
borra(y,Cons(iz,x,dr))=Cons(iz,x,borra(y,dr))siy>x
Por ejemplo, aplicamos algunas operaciones de borrado al primer ejemplo que presentamos de
rbol ordenado:
borra(35, a)
20
12
17
31
43
26
borra(43, a)
20
12
7
borra(31, a)
31
17
26
43
447
rboles
20
12
7
35
17
26
43
inserta:(Elem,Arbus[Elem])oArbus[Elem]/*gen*/
busca:(Elem,Arbus[Elem])oArbus[Elem]/*mod*/
borra:(Elem,Arbus[Elem])oArbus[Elem]/*mod*/
est?:(Elem,Arbus[Elem])oBool /*obs*/
operacionesprivadas
ordenado?:Arbus[Elem]oBool/*obs*/
min:Arbus[Elem]oElem /*obs*/
mayor,menor:(Arbus[Elem],Elem)oBool/*obs*/
ecuaciones
x,y,z:Elem:a,iz,dr:Arbus[Elem]:
defmin(a)siNOTesVaco(a)
min(Cons(iz,x,dr))=x siesVaco(iz)
min(Cons(iz,x,dr))=min(iz) siNOTesVaco(iz)
menor(Vaco,y)=cierto
menor(Cons(iz,x,dr),y)=x<yANDmenor(iz,y)ANDmenor(dr,y)
448
rboles
mayor(Vaco,y)=cierto
mayor(Cons(iz,x,dr),y)=x>yANDmayor(iz,y)ANDmayor(dr,y)
ordenado?(Vaco) =cierto
ordenado?(Cons(iz,x,dr)) =ordenado?(iz)ANDmenor(iz,x)AND
ordenado?(dr)ANDmayor(dr,x)
inserta(y,Vaco)=Cons(Vaco,y,Vaco)
inserta(y,Cons(iz,x,dr)) =Cons(iz,x,dr)siy==x
inserta(y,Cons(iz,x,dr)) =Cons(inserta(y,iz),x,dr)siy<x
inserta(y,Cons(iz,x,dr)) =Cons(iz,x,inserta(y,dr))siy>x
busca(y,Vaco)=Vaco
busca(y,Cons(iz,x,dr))=Cons(iz,x,dr)siy==x
busca(y,Cons(iz,x,dr))=busca(y,iz)siy<x
busca(y,Cons(iz,x,dr))=busca(y,dr)siy>x
borra(y,Vaco)=Vaco
borra(y,Cons(iz,x,dr))=drsiy==xANDesVaco(iz)
borra(y,Cons(iz,x,dr))=izsiy==xANDesVaco(dr)
borra(y,Cons(iz,x,dr))=Cons(iz,z,borra(z,dr))
siy==xANDNOTesVaco(iz)AND
NOTesVaco(dr)ANDz=min(dr)
borra(y,Cons(iz,x,dr))=Cons(borra(y,iz),x,dr)siy<x
borra(y,Cons(iz,x,dr))=Cons(iz,x,borra(y,dr))siy>x
est?(y,a) =NOTesVaco(busca(y,a))
errores
min(Vaco)
ftad
El caso peor se da en un rbol degenerado reducido a una sola rama, en cuyo caso la talla de
un rbol de bsqueda con n nodos en n. Tales rboles pueden producirse a partir del rbol
vaco por la insercin consecutiva de n elementos ordenados en orden creciente o decreciente.
La intuicin que hay detrs de este resultado es que en promedio un rbol de bsqueda se
comporta como un rbol completo, para el que ya vimos que su talla vena dada por la expresin
log(n+1) siendo n el nmero de nodos.
449
rboles
Este anlisis es cualitativamente similar al anlisis de la complejidad del quicksort, donde el caso
peor se tiene cuando el vector ya est ordenado, porque en ese caso la particin del subvector
slo reduce en 1 el tamao del problema. Este caso se corresponde con el rbol degenerado.
Si se quiere garantizar complejidad de O(log n) en el caso peor, es necesario restringirse a trabajar con alguna subclase de los rboles ordenados en la cual la talla se mantenga logartmica con
respecto al nmero de nodos. De esto nos ocuparemos en el siguiente apartado del tema, dedicado a los rboles AVL.
No nos ocupamos de la implementacin de los rboles ordenados, ya que slo los hemos introducido para presentar los conceptos bsicos de los rboles de bsqueda, que describimos a
continuacin, y de cuya implementacin s nos ocuparemos.
5.4.2
Arboles de bsqueda
La diferencia entre los rboles de bsqueda y los rboles ordenados radica en las condiciones
que se exigen a los elementos. En los rboles ordenados simplemente se exige que el tipo de los
elementos pertenezca a la clase ORD, es decir est equipado con operaciones de igualdad y comparacin. Sin embargo, en muchas aplicaciones las comparaciones entre elementos se puede establecer en base a una parte de la informacin total que almacenan dichos elementos: un campo
clave. Esta idea proviene de las bases de datos.
Supongamos por ejemplo que tuvisemos que manejar una coleccin de datos de personas
donde uno de los campo almacenados fuese su DNI. Errores administrativos al margen, podemos suponer que el DNI es nico y que, por lo tanto podemos identificar a una persona simplemente a partir de su DNI. Si almacensemos la coleccin de datos en un rbol ordenado,
podramos utilizar simplemente el DNI como clave de acceso.
En esta situacin, lo que deberamos exigirle a los elementos de un rbol de bsqueda es que
dispusieran de una operacin observadora cuyo resultado fuese de un tipo ordenado:
clave:ElemoClave
Siendo Clave un tipo que pertenece a la clase ORD. Definiramos as la clase de los tipos que
tienen un operacin de la forma clave, y especificaramos e implementaramos los rboles de
bsqueda en trminos de esta clase. Ntese que con este planteamiento los rboles ordenados
son un caso particular de los rboles de bsqueda donde la operacin clave es la identidad.
Sin embargo, podemos generalizar an ms el planteamiento si consideramos que la clave y el
valor son datos separados, de forma que en cada nodo del rbol se almacenen dos datos. Ntese
que este planteamiento engloba al anterior aunque tiene el inconveniente de que las claves se almacenaran dos veces. As, el TAD ARBUS tendr dos parmetros: el TAD de las claves y el
TAD de los valores.
Al separar las claves y los valores, se nos plantea la cuestin de qu hacer cuando intentamos
insertar un elemento asociado con una clave que ya est en el rbol. Dependiendo del tipo de los
elementos tendr sentido realizar distintas operaciones.
Con el objetivo de que el TAD sea lo ms general posible, la solucin que adoptamos es exigir
que el tipo de los valores tenga una operacin modificadora que combine elementos, con la signatura:
():(Elem,Elem)oElem
450
rboles
De forma que cuando insertemos un elemento asociado a una clave que ya existe, utilicemos
esta operacin para combinar los valores y obtener el nuevo dato que se debe almacenar en el
rbol. As, en cada ejemplar del TAD tendremos un comportamiento especfico del tipo de los
valores, segn la operacin que haya sido designada para realizar la combinacin. Por ejemplo, si
queremos que el nuevo elemento reemplace al antiguo entonces definiremos esta funcin como:
xy=y
Definimos entonces la siguiente clase de tipos, a la que deben pertenecer los valores de un
rbol de bsqueda
claseCOMB
hereda
ANY
operaciones
():(Elem,Elem)oElem
%Operacindecombinacindeelementos.
fclase
Es decir, estamos exigiendo que los valores de un rbol de bsqueda tengan una operacin
con esa signatura que se llame combina. Sera ms flexible si en el lenguaje de implementacin pudisemos, al declarar una variable concreta de tipo Arbus, indicar explcitamente cul es la operacin que se corresponde con combina.
La idea de separar las claves de los datos, e indicar cul es la forma de combinar datos, se puede aplicar a todos los TAD que manejan colecciones de datos. Aunque tiene ms sentido cuando
las claves estn ordenadas y nos ayudan a construir una implementacin ms eficiente.
Con todo esto, la especificacin de los rboles de bsqueda queda igual que la de los rboles
ordenados, pero sustituyendo los elementos por parejas de (clave, valor), y utilizando la operacin
de combinacin cuando se inserta un elemento asociado con una clave que ya existe.
Especificacin algebraica
tadARBUSCA[C::ORD,V::COMB]
renombraC.ElemaCla
V.ElemaVal
usa
RECPROFARBIN[PAREJA[C,V]]renombrandoinOrdarecorre
Arbin[Pareja[Cla,Val]]aArbus[Cla,Val]
ocultandopreOrd,postOrd,
Cons,hijoIz,hijoDr
tipo
Arbus[Cla,Val]
operaciones
inserta:(Cla,Val,Arbus[Cla,Val])oArbus[Cla,Val] /*gen*/
busca:(Cla,Arbus[Cla,Val])oArbus[Cla,Val] /*mod*/
borra:(Cla,Arbus[Cla,Val])oArbus[Cla,Val] /*mod*/
est?:(Cla,Arbus[Cla,Val])oBool /*obs*/
operacionesprivadas
ordenado?:Arbus[Cla,Val]oBool /*obs*/
min:Arbus[Cla,Val]oPareja[Cla,Val] /*obs*/
mayor,menor:(Arbus[Cla,Val],Cla)oBool
/*obs*/
ecuaciones
c,c,d:Cla:x,x,y:Val:a,iz,dr:Arbus[Cla,Val]:
451
rboles
defmin(a)siNOTesVaco(a)
min(Cons(iz,Par(c,x),dr)) =Par(c,x) siesVaco(iz)
min(Cons(iz,Par(c,x),dr)) =min(iz) siNOTesVaco(iz)
menor(Vaco,c) =cierto
menor(Cons(iz,Par(c,x),dr),c) =c<cANDmenor(iz,c)AND
menor(dr,c)
mayor(Vaco,c) =cierto
mayor(Cons(iz,Par(c,x),dr),c) =c>cANDmayor(iz,c)AND
mayor(dr,c)
ordenado?(Vaco) =cierto
ordenado?(Cons(iz,Par(c,x),dr)) =ordenado?(iz)ANDmenor(iz,c)AND
ordenado?(dr)ANDmayor(dr,c)
inserta(c,x,Vaco) =Cons(Vaco,Par(c,x),Vaco)
inserta(c,x,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(c,xx),dr)
sic==c
inserta(c,x,Cons(iz,Par(c,x),dr)) =Cons(inserta(c,x,iz),
Par(c,x),dr)
sic<c
inserta(c,x,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(c,x),inserta(c,
x,dr))
sic>c
busca(c,Vaco) =Vaco
busca(c,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(c,x),dr)sic==c
busca(c,Cons(iz,Par(c,x),dr)) =busca(c,iz)sic<c
busca(c,Cons(iz,Par(c,x),dr)) =busca(c,dr)sic>c
borra(c,Vaco) =Vaco
borra(c,Cons(iz,Par(c,x),dr)) =drsic==cANDesVaco(iz)
borra(c,Cons(iz,Par(c,x),dr)) =izsic==cANDesVaco(dr)
borra(c,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(d,y),borra(d,dr))
sic==cANDNOTesVaco(iz)AND
NOTesVaco(dr)ANDPar(d,y)=min(dr)
borra(c,Cons(iz,Par(c,x),dr)) =Cons(borra(c,iz),Par(c,x),dr)si
c<c
borra(c,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(c,x),borra(c,dr))si
c>c
est?(c,a) =NOTesVaco(busca(c,a))
errores
min(Vaco)
ftad
452
rboles
si
ORD.igual(x,raz(a))ob:=a
ORD.menor(x,raz(a))ob:=Cons(inserta(x,hijoIz(a)),raz(a),
hijoDr(a))
ORD.mayor(x,raz(a))ob:=Cons(hijoIz(a),raz(a),
inserta(x,hijoDr(a)))
fsi
fsi
{Q0:R(b)A(b)=ARBORD[ELEM]inserta(x,A(a))}
devb
ffunc
En el rbol resultado b tendremos que los nodos recorridos en la bsqueda se habrn copiado,
como efecto de la operacin Cons, mientras que el resto de los nodos se compartirn. Un problema similar se plantea con la operacin de borrado
Vemoslo con un ejemplo. Dado el rbol a
17
27
21
33
25
17
9
27
27
21
33
21
25
19
A diferencia de la comparticin de estructura que producen las funciones Cons, hijoIz o hijoDr,
esta otra es arbitraria y en la prctica impedira cualquier intento de anulacin de los rboles invo-
453
rboles
lucrados, a no ser que decidisemos dejar de usar al mismo tiempo todos los rboles que sabemos
que pueden compartir algn nodo. Una solucin a este problema sera hacer que el resultado no
compartiese ningn nodo con el argumento, para lo cual deberamos realizar copias al hacer las
llamadas recursivas:
b:=Cons(inserta(x,hijoIz(a)),raz(a),copia(hijoDr(a)))
b:=Cons(copia(hijoIz(a)),raz(a),inserta(x,hijoDr(a)))
Pero de esta forma tenemos que el coste de la operacin pasa a O(n), con lo cual perdemos la
ventaja que estamos pretendiendo obtener con los rboles ordenados.
La solucin es por tanto implementar inserta y borra como procedimientos que acceden a la estructura interna de la representacin; es decir, no podemos implementarlo como un mdulo
cliente de los rboles binarios, sino como un mdulo donde se vuelva a definir el tipo.
Tipo representante
La definicin del tipo es similar a la de los rboles binarios, aunque cada nodo contiene dos
campos de informacin en lugar de uno. Lo escribimos tal y como aparecera en el mdulo de
implementacin, para indicar que el tipo de los valores y las claves se importa de los mdulos
abstractos COMB y ORD.
mduloimplARBUSCA[CLA,VAL]
importa
COMB,ORD
privado
tipo
Val=COMB.Elem;
Cla=ORD.Elem;
Nodo=reg
cla:Cla;
val:Val;
hi,hd:Enlace
freg;
Enlace=punteroaNodo;
Arbus[Cla,Val]=Enlace;
%Implementacindelasoperaciones
fmdulo
Invariante de la representacin
Exigimos que cumplan el invariante de la representacin de los rboles binarios sin comparticin de estructura, y que el resultado del recorrido en inorden est ordenado.
Dado p : Enlace
RArbus[Cla,Val]
def
454
rboles
RNCArbin[Elem](p)
i,j:1di<jd#inOrd(p):inOrd(p)!!i<inOrd(p)!!j
Ntese que podemos exigir el invariante sin comparticin de estructura porque en ARB-BUS
no est disponible la operacin Cons.
Funcin de abstraccin
La misma que se utiliza con rboles binarios:
Implementacin de las operaciones
funcbusca(d:Cla;aArbus[Cla,Val])devb:Arbus[Cla,Val];
{P0:R(d)R(a)}
inicio
sia==nil
entonces
b:=nil
sino
si
ORD.igual(d,a^.cla)ob:=a
ORD.menor(d,a^.cla)ob:=busca(d,a^.hi)
ORD.mayor(d,a^.cla)ob:=busca(d,a^.hd)
fsi
fsi
{Q0:R(b)A(b)=ARBUSCA[CLA,VAL]busca(A(d),A(a))}
devb
ffunc
procinserta(ed:Cla;ey:Val;esa:Arbus[Cla,Val]);
{P0:R(d)R(y)R(a)a=A}
inicio
sia==nil
entonces
ubicar(a);
a^.cla:=d;
a^.val:=y;
a^.hi:=nil;
a^.hd:=nil;
sino
si
ORD.igual(d,a^.cla)oa^.val:=COMB.combina(a^.val,y)
ORD.menor(d,a^.cla)oinserta(d,y,a^.hi) /*ref*/
ORD.mayor(d,a^.cla)oinserta(d,y,a^.hd) /*ref*/
fsi
455
rboles
fsi
{Q0:R(a)A(a)=ARBUSCA[CLA,VAL]inserta(A(d),A(y),A(A))}
fproc
En las dos instrucciones marcadas como ref es donde se muestra la necesidad de que esta operacin tenga acceso a la representacin interna de los rboles. En las siguientes operaciones tambin ocurre esto cada vez que pasamos como parmetro actual uno de los hijos del nodo en
cuestin.
Para la operacin de borrado vamos a utilizar dos procedimientos auxiliares privados.
procborra(ed:Cla;esa:Arbus[Cla,Val]);
{P0:R(d)R(a)a=A}
inicio
sia==nil
entonces
seguir
sino
si
ORD.igual(d,a^.cla)oborraRaz(a)
ORD.menor(d,a^.cla)oborra(d,a^.hi)
ORD.mayor(d,a^.cla)oborra(d,a^.hd)
fsi
fsi
{Q0:R(a)A(a)=ARBUSCA[CLA,VAL]borra(A(d),A(A))}
fproc
456
rboles
NOTvacoIzANDNOTvacoDroborraConMin(a,a^.hd)
fsi
{Q0:R(a)A(a)=ARBUSCA[CLA,VAL]borra(A(A^.cla),A(A))}
fproc
Dejando fijo el puntero a, descendemos por sus hijos izquierdos con el parmetro b hasta llegar al menor, y en ese punto realizamos el borrado
procborraConMin(esa,b:Arbus[cla,Val]);
{P0:a=AR(a)a/=nila^.hi/=nila^.hd/=nil
besundescendientedea^.hdvaunacadenadehijosizquierdos}
var
aux:Arbus[Cla,Val];
inicio
sib^.hi/=nil
entonces
borraConMin(a,b^.hi)
sino
a^.cla:=b^.cla;
%COMB.anula(a^.val);
a^.val:=b^.val;
aux:=b;
b:=b^.hd;
liberar(aux)
fsi
{Q0:R(a)A(a)=ARBUSCA[CLA,VAL]borra(A(A^.cla),A(A))}
Fproc
Arboles equilibrados
En el tema anterior vimos cmo en los rboles de bsqueda se consigue una complejidad logartmica para las operaciones de bsqueda, insercin y borrado en el caso promedio, aunque en
le caso peor llega a ser O(n). Si se quiere garantizar complejidad logartmica en el caso peor, es
necesario restringirse a trabajar con alguna subclase de la clase de los rboles ordenados en la cual
la talla se mantenga logartmica con respecto al nmero de nodos.
Una familia F de rboles binarios se llama equilibrada si existen constantes n0 , c + tales
que para todo n t n0 y todo rbol a F con n nodos se tenga talla(a) d c log n.
Efectivamente, para rboles pertenecientes a una familia equilibrada la complejidad de las operaciones antes citadas en el caso peor es O(t) siendo t la talla, y por lo tanto resulta O(log n).
Algunos ejemplos de familias equilibradas son:
La familia de los rboles semicompletos
457
rboles
La familia de los rboles equilibrados en nmero de nodos. Un rbol pertenece a esta familia si cualquier subrbol suyo cumple que el nmero de nodos del hijo izquierdo y el
nmero de nodos del hijo derecho difieren a lo sumo en 1:
La familia de los rboles equilibrados en altura. Un rbol pertenece a esta familia si cualquier subrbol suyo cumple que la talla del hijo izquierdo y la talla del hijo derecho difieren a lo sumo en 1:
Ntese que, como se puede observar en los anteriores ejemplos, todos los rboles semicompletos son equilibrados en altura, pero que existen rboles equilibrados en altura que no son semicompletos. La idea es que para conseguir el coste logartmico no es necesario exigir una
condicin tan fuerte como la semicompletitud sino que basta con una condicin ms dbil: el
equilibrio en altura.
En 1962, G. M. Adelson-Velskii y E. M. Landis demostraron que la familia de los rboles
equilibrados en altura son equilibrados en el sentido de la definicin dada ms arriba. En honor
suyo, la familia suele llamarse familia de los rboles AVL. En efecto, los inventores de esta familia
demostraron que un rbol AVL con n nodos tiene una talla t acotada como sigue:
log(n+1)dt<14404log(n+2)0328
El caso peor, es decir, los rboles AVL con el mayor desequilibrio posible, se alcanza para los
llamados rboles de Fibonacci. Para cada talla t t 0, el rbol de Fibonacci at y su nmero de nodos nt
se construyen recursivamente como sigue:
t=0
a0 = Vaco
n0 = 0
t=1
a1 = Cons(a0, 1, a0)
n1 = 1
tt2
nt = nt2 + 1 + nt1
donde
i es el mnimo valor t 1 que no aparece en at2
at1[+ i] es el rbol que se obtiene sumando i a cada nodo de at1
458
rboles
Por ejemplo:
a1
a2
1
2
a3
2
1
3
4
a4
3
1
5
2
6
7
Los nmeros nt se llaman nmeros de Leonardo. Para los rboles de Fibonacci at se alcanza el
caso peor de la estimacin anterior
t|14404log(n+2)0328|15logn
459
rboles
menor(Cons(iz,Par(v,x),dr),u) =v<uANDmenor(iz,u)ANDmenor(dr,
u)
mayor(Vaco,u) =cierto
mayor(Cons(iz,Par(v,x),dr),u) =v>uANDmayor(iz,u)ANDmayor(dr,
u)
equilibrado?(Vaco) =cierto
equilibrado?(Cons(iz,ux,dr)) =equilibrado?(iz)ANDequilibrado?(dr)
AND
|talla(iz)talla(dr)|d1
talla(Vaco) =0
talla(Cons(iz,us,dr)) =1+max(talla(iz),talla(dr))
5.5.2
Segn los resultados del apartado anterior, los rboles AVL constituyen una familia equilibrada,
y, por lo tanto, las operaciones de bsqueda, insercin y borrado van a tener coste O(log n) en el
caso peor, siempre que Inserta y borra se modifiquen de manera que al aplicarse a un rbol AVL
devuelvan otro rbol AVL.
Las operaciones de insercin y borrado en rboles AVL funcionan bsicamente en dos pasos:
rotDr
y
x
c
b
x
y
a
rotIz
Una propiedad interesante de este proceso es que si cualquiera de las operaciones de rotacin
se aplica a un rbol ordenado, el resultado es un nuevo rbol ordenado con el mismo recorrido
en inorden que el rbol original.
460
rboles
defrotIz(a)siNOTesVaco(a)ANDNOTesVaco(hijoDr(a))
rotIz(Cons(a,ux,Cons(b,vy,c))) =Cons(Cons(a,ux,b),vy,c)
defrotDr(a)siNOTesVaco(a)ANDNOTesVaco(hijoIz(a))
rotDr(Cons(Cons(a,ux,b),vy,c)) =Cons(a,ux,Cons(b,vy,c))
Factor de equilibrio
Los algoritmos de insercin y borrado tienen que averiguar cundo un subrbol se ha desequilibrado para proceder a reequilibrarlo con ayuda de rotaciones. Para caracterizar las distintas situaciones que pueden darse introducimos el concepto de factor de equilibrio, que toma uno entre
tres valores posibles: equilibrado, desequilibrado a la izquierda y desequilibrado a la derecha.
Formalmente, introducimos en la especificacin el tipo privado FactEq, con las siguientes generadoras:
tipoprivado
FactEq
operacionesprivadas
DI:oFactEq
/*gen*/
EQ:oFactEq
/*gen*/
DD:oFactEq
/*gen*/
factEq(Vaco) =EQ
factEq(Cons(iz,ux,dr)) =DIsitalla(iz)>talla(dr)
factEq(Cons(iz,ux,dr)) =EQsitalla(iz)=talla(dr)
factEq(Cons(iz,ux,dr)) =DDsitalla(iz)<talla(dr)
Como luego veremos al tratar la implementacin, el factor de equilibrio se almacenar explcitamente en cada nodo. La razn es que aunque sera posible averiguar dicho factor calculando las
tallas de los subrboles involucrados, este mtodo encarecera demasiado los algoritmos.
Insercin
La idea es que este proceso, como ya indicamos antes, consiste en una insercin ordinaria seguida de reequilibrado, si ste es necesario.
Algebraicamente, se corresponde con la siguiente especificacin:
inserta(v,y,Vaco) =Cons(Vaco,Par(v,y),Vaco)
inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,xy),dr)
siv==u
inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,x),dr)
rboles
461
siv<uANDiz=inserta(v,y,iz)ANDtalla(iz)==talla(iz)
inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,x),dr)
siv<uANDiz=inserta(v,y,iz)ANDtalla(iz)==talla(iz)+1
AND
factEq(Cons(iz,Par(u,x),dr)/=DI
inserta(v,y,Cons(iz,Par(u,x),dr))=reeqIz(Cons(iz,Par(u,x),dr))
siv<uANDiz=inserta(v,y,iz)ANDtalla(iz)==talla(iz)+1
AND
factEq(Cons(iz,Par(u,x),dr)==DI /*Iz*/
inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,x),dr)
siv>uANDdr=inserta(v,y,dr)ANDtalla(dr)==talla(dr)
inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,x),dr)
siv>uANDdr=inserta(v,y,dr)ANDtalla(dr)==talla(dr)+1
AND
factEq(Cons(iz,Par(u,x),dr)/=DD
inserta(v,y,Cons(iz,Par(u,x),dr))=reeqDr(Cons(iz,Par(u,x),dr))
siv>uANDdr=inserta(v,y,dr)ANDtalla(dr)==talla(dr)+1
AND
factEq(Cons(iz,Par(u,x),dr)==DD /*Dr*/
El reequilibrado se hace necesario cuando la insercin modifica la talla del subrbol donde se
realiza, y el rbol original estaba desequilibrado hacia ese subrbol. Ntese que en este caso basta
con reequilibrar el subrbol ms prximo a la nueva hoja que se haya desequilibrado y corregir el
desequilibrio con ayuda de rotaciones; ya que, por efecto de las rotaciones, el subrbol reequilibrado conserva la misma talla que tena antes de desequilibrarse, por lo que el rbol total queda
equilibrado.
Se produce desequilibrio cuando algn rbol a que era equilibrado antes de la insercin se
convierte despus de la insercin en un rbol no equilibrado a. Suponemos que a es el subrbol
ms profundo en el cual la insercin causa desequilibrio. Esto slo es posible en dos casos:
Caso [Iz]: a tena factor de equilibrio DI. La insercin se ha producido en el hijo izquierdo
de a, y a ha quedado:
462
rboles
t+1
Caso [Dr]: a tena factor de equilibrio DD. La insercin se ha producido en el hijo derecho de a, y a ha quedado:
x
t+1
Ntese que el subrbol izquierdo, en el caso [Iz], y el subrbol derecho, en el caso [Dr], no
pueden ser vacos, pues de lo contrario la insercin no podra haber causado desequilibrio.
Vamos a tratar slo el caso [Dr], dejando [Iz], que es simtrico, como ejercicio.
Hemos de distinguir dos situaciones, segn en qu hijo del subrbol derecho se ha producido
la insercin:
463
rboles
rotIz
en
y
t
a0
c0
t
c0
b0
a0
b0
Ntese que b0 debe tener talla t, porque el rbol considerado es el subrbol ms profundo
que se ha desequilibrado al insertar.
Observamos que el resultado final queda AVL con factor de equilibrio EQ y la misma talla t+2 que el rbol original a. Por lo tanto, si a era un subrbol de un rbol mayor, tambin ste ha quedado equilibrado.
Insertar
4
11
10
10
rotIz
8
4
2
11
en
10
11
464
rboles
rotDr
Insertar
x
en 2
y
y
rotIz
en
y
Aunque hemos hecho la transformacin utilizando las dos rotaciones de la solucin general, para mostrar que efectivamente dicha solucin funciona, en la prctica esto se puede
implementar como un caso especial que se realiza en un solo paso.
En el caso no trivial se tiene:
rotDr
x
en 2
y
a0
z
t
z
t-1 b0
c0
d0
t-1
a0
c0
rotIz
en
z
y
a0
t-1 b0
nuevo < z
t-1 b0
c0 t-1
nuevo > z
d0
t-1
d0
465
rboles
Insertar
4
2
8
6
10
10
7
rotIz
6
4
2
en
8
rotDr
en 2
10
8
7
10
Aunque la primera ecuacin cubre los casos factEq(dr) == DD y factEq(dr) == EQ, ste ltimo
no se producir nunca en el reequilibrado provocado por insercin, como podemos observar en
la discusin anterior. Sin embargo, lo incluimos aqu porque en el caso de reequilibrado por supresin s que puede presentarse.
El algoritmo de insercin AVL se puede programar recursivamente siguiendo este mtodo.
Conviene utilizar un procedimiento auxiliar con un parmetro s crece? : Bool, que informa de si una
insercin ha aumentado la talla. Esta informacin permite reequilibrar y actualizar los factores de
equilibrio. La correccin del desequilibrio debe aplicarse al primer subrbol desequilibrado que se
encuentre yendo desde la nueva hoja hacia la raz.
466
rboles
En cuanto a la complejidad del algoritmo de insercin, el anlisis matemtico an no est resuelto del todo, pero las pruebas empricas indican que:
La talla media que puede esperarse para un rbol AVL generado por la insercin aleatoria
de n claves diferentes es 025 + log n.
Un promedio de 1 de cada 2 inserciones requiere reequilibrio. Todos los tipos de desequilibrio son equiprobables, y, como acabamos de ver, un desequilibrio siempre se puede corregir con 1 o 2 rotaciones.
5.5.3
Implementacin
Al igual que ocurre con los rboles de bsqueda es necesario implementar los rboles AVL
con acceso al tipo representante de los rboles binarios.
Tipo representante
El tipo representante es similar al de los rboles de bsqueda, pero aadiendo el campo donde
se almacena el factor de equilibrio.
mduloimplAVL[CLA,VAL]
importa
COMB,ORD
privado
tipo
FactEq=DI|EQ|DD;
Val=COMB.Elem;
Cla=ORD.Elem;
Nodo=reg
feq:FactEq;
cla:Cla;
val:Val;
hi,hd:Enlace
freg;
Enlace=punteroaNodo;
Arbus[Cla,Val]=Enlace;
%Implementacindelasoperaciones
fmdulo;
Invariante de la representacin
Exigimos que cumplan el invariante de la representacin de los rboles binarios sin comparticin de estructura, y que el resultado del recorrido en inorden est ordenado.
Dado p : Enlace
RAVLArbus[Cla,Val]
def
RNCArbin[Elem](p)
i,j:1di<jd#inOrd(p):inOrd(p)!!i<inOrd(p)!!j
467
rboles
p:Enlaces(p):p^.feqrepresentacorrectamente
elfactordeequilibriodep
Implcitamente en la ltima condicin estamos exigiendo que sea equilibrado en altura, por la
forma cmo estn definidos los factores de equilibrio.
Ntese que podemos exigir el invariante sin comparticin de estructura porque en AVL no
est disponible la operacin Cons.
Funcin de abstraccin
La misma que se utiliza con rboles binarios:
Implementacin de las operaciones
A excepcin de la insercin y el borrado, todas las operaciones se implementan de la misma
forma que en los rboles de bsqueda. Para no extendernos slo presentamos la implementacin
de algunas de las operaciones. En el texto de Franch se pueden encontrar el resto.
procinserta(ed:Cla;ey:Val;esa:Arbus[Cla,Val]);
{P0:R(d)R(y)R(a)a=A}
var
crece?:Boolean;
inicio
insertaAVL(d,y,a,crece?)
{Q0R(a)A(a)=AVL[CLA,VAL]inserta(A(d),A(y),A(A))}
fproc
468
rboles
siNOTcrece?
entonces
seguir
sino
casoa^.feqde
DDoa^.feq:=EQ;crece?:=falso
EQoa^.feq:=DI
DIoreeqIz(d,a);crece?:=falso
fcaso
fsi
ORD.mayor(d,a^.cla)oinsertaAVL(d,y,a^.hd,crece?)
siNOTcrece?
entonces
seguir
sino
casoa^.feqde
DIoa^.feq:=EQ;crece?:=falso
EQoa^.feq:=DD
DDoreeqDr(d,a);crece?:=falso
fcaso
fsi
fsi
fsi
{Q0:R(a)A(a)=AVL[CLA,VAL]inserta(A(d),A(y),A(A))
crece?=AVL[CLA,VAL](talla(A(a))==1+talla(A(A)))}
fproc
Vemos que en este procedimiento, cuando se realiza un reequilibrado se pone la variable crece?
a falso, con lo que ya no se producirn ms reequilibrados a la vuelta de las llamadas recursivas.
en la implementacin de borra el reequilibrado debe tener otro parmetro adicional que nos indique si dicha operacin ha hecho decrecer la talla del rbol, con lo que se debera propagar dicha
informacin hacia las llamadas anteriores.
Procedimiento auxiliar privado de reequilibrado por la derecha.
procreeqDr(ed:Cla;esa:Arbus[Cla,Val]);
{P0:a=ARARBUSCA[CLA,VAL](a)
talla(hijoDr(A(a)))=ARBUSCA[CLA,VAL]talla(hijoIz(A(a)))+2
deslaclavedelnodoqueproduceeldesequilibrio}
inicio
sia^.hi==nilAND(a^.hd)^.hd==nil
entonces /*desequilibriotrivial*/
reorgDr(a);
a^.feq:=EQ;
a^.hi^.feq:=EQ;
a^.hD^.feq:=EQ
469
rboles
sino
sia^.hd^.feq==DD
entonces
rotIz(a);
a^.feq:=EQ;
a^.hi^.feq:=EQ
sino
rotDr(a^.hd);
rotIz(a);
a^.feq:=EQ;
si
ORD.menor(d,a^.cla)oa^.hi^.feq:=EQ;
a^.hd^.feq:=DD
ORD.mayor(d,a^.cla)oa^.hi^.feq:=DI;
a^.hd^.feq:=EQ
fsi
fsi
fsi
{Q0:RAVL[CLA,VAL](a)recorre(A(a))=ARBUSCA[CLA,VAL]recorre(A(A))}
fproc
470
rboles
arb:=arb^.hi;
arb^.hi:=arb^.hd;
arb^.hd:=aux
{Q0:arbrepresentaelrbolCons(a,ux,Cons(b,vy,c)),
siendoa,ux,b,vy,closmismodeP0}
fproc
Borrado
La idea es que este proceso, como ya indicamos antes, consiste en un borrado ordinario seguida de reequilibrado, si ste es necesario.
Mediante ecuaciones, podemos especificar esta operacin de la siguiente forma:
borra(v,Vaco) =Vaco
borra(v,Cons(iz,Par(u,x),dr)) =quitaRaz(Cons(iz,Par(u,x),dr))
siv==u
borra(v,Cons(iz,Par(u,x),dr)) =Cons(iz,Par(u,x),dr)
siv<uANDiz=borra(v,iz)ANDtalla(iz)==talla(iz)
borra(v,Cons(iz,Par(u,x),dr)) =Cons(iz,Par(u,x),dr)
siv<uANDiz=borra(v,iz)ANDtalla(iz)==talla(iz)1AND
factEq(Cons(iz,Par(u,x),dr)/=DD
borra(v,Cons(iz,Par(u,x),dr)) =reeqDr(Cons(iz,Par(u,x),dr))
siv<uANDiz=borra(v,iz)ANDtalla(iz)==talla(iz)1AND
factEq(Cons(iz,Par(u,x),dr)==DD /*Dr*/
borra(v,Cons(iz,Par(u,x),dr)) =Cons(iz,Par(u,x),dr)
siv>uANDdr=borra(v,dr)ANDtalla(dr)==talla(dr)
borra(v,Cons(iz,Par(u,x),dr)) =Cons(iz,Par(u,x),dr)
siv>uANDdr=borra(v,dr)ANDtalla(dr)==talla(dr)1AND
factEq(Cons(iz,Par(u,x),dr)/=DI
borra(v,Cons(iz,Par(u,x),dr)) =reeqIz(Cons(iz,Par(u,x),dr))
siv>uANDdr=borra(v,dr)ANDtalla(dr)==talla(dr)1AND
factEq(Cons(iz,Par(u,x),dr)==DI /*Iz*/
471
rboles
siesVaco(dr)
quitaRaz(Cons(iz,ux,dr)) =Cons(iz,Par(v,y),dr)
siNOTesVaco(iz)ANDNOTesVaco(dr)ANDPar(v,y)=min(dr)AND
dr=borra(v,dr)AND
talla(dr)==talla(dr)
quitaRaz(Cons(iz,ux,dr)) =Cons(iz,Par(v,y),dr)
siNOTesVaco(iz)ANDNOTesVaco(dr)ANDPar(v,y)=min(dr)AND
dr=borra(v,dr)AND
talla(dr)==talla(dr)1ANDfactEq(Cons(iz,ux,dr))/=DI
quitaRaz(Cons(iz,ux,dr)) =reeqIz(Cons(iz,Par(v,y),dr))
siNOTesVaco(iz)ANDNOTesVaco(dr)ANDPar(v,y)=min(dr)AND
dr=borra(v,dr)AND
talla(dr)==talla(dr)1ANDfactEq(Cons(iz,ux,dr))==DI
La idea del reequilibrado consiste ahora en localizar el subrbol ms profundo a lo largo del
camino de bsqueda que se haya desequilibrado, y corregir el desequilibrio con ayuda de rotaciones. Puede suceder ahora que el reequilibrio produzca un subrbol cuya talla sea una unidad menor que la que haba antes del borrado, causndose un nuevo desequilibrio. En este caso el
proceso de reequilibrio tiene que reiterarse.
Se produce desequilibrio cuando algn rbol a que era equilibrado antes del borrado se convierte despus de la insercin en un rbol no equilibrado a. Suponemos que a es el subrbol ms
profundo en el cual el borrado causa desequilibrio. Esto slo es posible en dos casos:
Caso [Iz]: a tena factor de equilibrio DI. El borrado se ha producido en el hijo derecho
de a, y a ha quedado:
t+1
t-1
Caso [Dr]: a tena factor de equilibrio DD. El borrado se ha producido en el hijo izquierdo de a, y a ha quedado:
472
rboles
t-1
t+1
a0
t+1
c0
b0
talla(b0) = t, talla(c0) = t
Es decir, tenemos que el factor de equilibrio del hijo derecho de a es EQ.
El rbol a puede reequilibrarse como en el caso [DrDr] de la insercin AVL:
rotIz
en
y
t-1
a0
c0
t
b0
c0
t-1
a0
b0
El rbol resultante es AVL con factor de equilibrio DI y la misma talla t+2 del rbol a
original. Si a era un subrbol de un rbol mayor, ste tambin ha quedado reequilibrado.
473
rboles
en
y
a0
t-1
c0
b0
t-1
c0
t-1
a0
b0
t-1
En este caso, el rbol AVL resultante tiene factor de equilibrio EQ y talla t+1, una unidad
menos que a. Por lo tanto, si a era subrbol de un rbol mayor, ste puede necesitar an
ser reequilibrado.
talla(b0) = t, talla(c0) = t1
Es decir, tenemos que el factor de equilibrio del hijo derecho de a es DI. El reequilibrio
puede lograrse con dos rotaciones, como en el caso [DrIz] de la insercin AVL
rotDr
x
t-1
en 2
y
a0
t-1
c0
t
d0
a0
t-1
t-1?
d0
e0
t
t-1?
rotIz
en
z
y
x
t
t
a0
d0
e0
c0
e0
c0
474
rboles
Donde hay combinaciones posibles para las tallas de d0 y e0. Si f es el factor de equilibrio
de
z
d0
e0
se tiene
f
EQ
DI
DD
talla(d0)
t1
t1
t2
talla(e0)
t1
t2
t1
Ntese que al menos uno de los dos rboles d0 , e0 debe tener talla t1, con lo cual es seguro que el rbol resultante de las dos rotaciones es AVL con factor de equilibrio EQ y talla
t+1, una unidad menor que la del rbol original a. Si a era un subrbol de otro rbol mayor, ste puede seguir necesitando reequilibrado.
Observamos, por lo tanto, que la operacin de reequilibrado es prcticamente igual que en el
caso del reequilibrado provocado por una insercin. La nica diferencia radica en que ahora hay
un caso ms: fatEq(dr) = EQ. A nivel de implementacin tambin podemos establecer diferencias
porque en el reequilibrado por insercin no hace falta considerar el caso adicional, y porque en el
reequilibrado por borrado nos hace falta un parmetro adicional que indique si la talla del rbol
ha disminuido. Aunque, tambin existe la posibilidad de hacer una nica implementacin.
El algoritmo de borrado AVL se puede programar recursivamente siguiendo este mtodo.
conviene utilizar un procedimiento auxiliar con un parmetro s encoge? : Bool, que informa de si un
borrado ha disminuido la talla. Esta informacin permite reequilibrar y actualizar los factores de
equilibrio.
Para terminar con este apartado dedicado a las operaciones de insercin y borrado en rboles
AVL, indicar que existe una diferencia fundamental entre ellas:
En cada insercin AVL, hay que reequilibrar a los sumo 1 subrbol (1 o 2 rotaciones).
En un borrado AVL, puede llegar a ser necesario reequilibrar todos los subrboles encontrados a lo largo de la trayectoria de bsqueda, que tiene una longitud O(log n), si n s el
nmero de nodos. Cada reequilibrado requiere 1 o 2 rotaciones. Los rboles de Fibonacci
dan lugar a este caso psimo en el comportamiento del borrado.
475
rboles
5.6 Ejercicios
Arboles: modelo matemtico y especificacin
238. Dibuja los rboles siguientes:
*(a) Tu rbol genealgico.
(b) El rbol taxonmico de los seres vivos.
(c) Un rbol que represente la organizacin del texto de Xavier Franch Estructuras de da-
(d)
(e)
*(f)
*(g)
tos: especificacin, diseo e implementacin, con sus divisiones en captulos, secciones y subsecciones.
Un rbol que represente la organizacin jerrquica del sistema de directorios y archivos en el selvtico PC del profesor Tadeo de la Tecla.
El rbol de llamadas inducido por la llamada inicial fib(4) (cfr. ejercicio 83).
El rbol de anlisis sintctico de la expresin aritmtica (2+x)*(y3)
El rbol de anlisis sintctico de la siguiente expresin condicional:
six<y
entonces
x:=(2+x)*(y3)
sino
y:=2*x3*y
fsi
239. Los rboles generales se caracterizan porque cada nodo tiene un nmero arbitrario de hijos,
ordenados en secuencia. Construye un TAD parametrizado ARBOL[E :: ANY] que represente el comportamiento de los rboles generales, con operaciones adecuadas para construirlos y acceder a los datos almacenados en sus nodos.
240. Muestra mediante un ejemplo que los dos conceptos siguientes no son equivalentes:
(a) Arbol general de grado dos: Arbol general con la propiedad de que cada nodo tiene como
mximo dos hijos.
(b) Arbol binario: Arbol con la propiedad de que cada nodo tiene exactamente dos hijos,
izquierdo y derecho. Se admite el rbol vaco, sin ningn nodo.
241. Construye un TAD parametrizado ARBIN[E :: ANY] que especifique formalmente el
242. Partiendo de la especificacin del ejercicio anterior, aade ecuaciones que formalicen el
talla:Arbin[Elem]oNat
numNodos:Arbin[Elem]oNat
numHojas:Arbin[Elem]oNat
476
rboles
Se pide:
tivas del nivel n, de manera que al rellenar dichas posiciones con nuevas hojas se obtiene un rbol completo.
2t1 nodos.
244. Suponiendo conocidas las operaciones del TAD ARBIN y la operacin talla del ejercicio
242, construye ecuaciones que especifiquen formalmente el comportamiento de las operaciones siguientes:
esCompleto:Arbin[Elem]oBool
esSemiCompleto:Arbin[Elem]oBool
246. La idea del ejercicio anterior puede extenderse a la representacin de un bosque as de rboles
generales, por medio de un rbol binario b. Basta construir uno por uno los rboles binarios que representan los diferentes rboles del bosque as, y enganchar cada uno de ellos
como hijo derecho del precedente. Dibuja un ejemplo de esta construccin.
247. Formaliza las ideas de los dos ejercicios anteriores, especificando por medio de ecuaciones
hazArbin:Bosque[Elem]oArbin[Elem]
hazBosque:Arbin[Elem]oBosque[Elem]
248. Las operaciones hazArbin y hazBosque son inversas una de otra. Verifcalo demostrando por
(a) as:Bosque[Elem]:hazBosque(hazArbin(as))=as
477
rboles
(b) b:Arbin[Elem]:hazArbin(hazBosque(b))=b
representacin considerando dos variantes: RNC, que prohibe que diferentes subrboles de
un mismo rbol compartan estructura; y R, que no lo prohibe. Formaliza tambin la funcin de abstraccin.
250. Desarrolla una implementacin de ARBIN basada en la representacin del ejercicio ante-
rior. Comprueba que el coste temporal de todas las operaciones es O(1) y que el espacio
ocupado por la representacin es O(n), siendo n el nmero de nodos del rbol.
compartidas. comprubalo dibujando un grfico que represente las estructuras representantes de a1, a2, a3 al finalizar la ejecucin del siguiente fragmento de programa:
var
a1,a2,a3:Arbin[Ent];
inicio
a1:=Cons(Vaco(),1,Vaco());
a2:=Cons(Vaco(),2,Vaco());
a3:=Cons(a1,3,a2);
a1:=Cons(a1,4,a3)
funccopia(a:Arbin[Elem])devb:Arbin[Elem];
{P0:R(a)}
{Q0:RNC(b)A(a)=A(b)
laestructurarepresentantedebestubicadoenespacionuevo}
ffunc
Nota: Para programa anula y copia es necesario tener acceso a la representacin dinmica de los
rboles. En la prctica, convendra que el mdulo de implementacin de ARBIN exportase estos
procedimientos.
253. Enriquece la especificacin del TAD ARBIN con una operacin de igualdad
(==):(Arbin[Elem],Arbin[Elem])oBool
Extiende la implementacin del ejercicio 250 aadiendo una funcin que implemente ( == ) y
analiza su coste temporal.
478
rboles
254. La implementacin dinmica de ARBIN abordada en el ejercicio 250 se puede realizar re-
emplazando la memoria dinmica por un vector de tipo adecuado que la simule. Estudia los
detalles en el texto de Xavier Franch (subseccin 5.2.1, pp. 229-232) y desarrolla la implementacin resultante.
255. Demuestra que la definicin recursiva dada a continuacin establece una aplicacin biyecti-
va ndice ; {1,2}* o + entre el conjunto de todas las posiciones posibles de los rboles binarios y el conjunto de los nmeros naturales positivos.
ndice(H) =1
ndice(D.1) =2*ndice(D)
ndice(D.2) =2*ndice(D)+1
256. Para representar un rbol binario de tipo Arbin[Elem] puede usarse un vector de tipo
Vector[1..max]deElem, siguiendo el criterio de que el elemento que ocupa la posicin D
del rbol se almacene en el lugar ndice(D) del vector. Esta representacin resulta til para
algunos algoritmos que operan con rboles semicompletos. Segn el resultado del ejercicio
243(b), un valor max = 2t 1 basta para representar cualquier rbol semicompleto de talla
menor o igual que t. Plantea las frmulas numricas que habra que utilizar para calcular el
padre y los hijos de un nodo dado, usando esta representacin.
257. Para desarrollar una implementacin dinmica de los rboles generales, suele utilizarse la
258. Para desarrollar una implementacin dinmica de los rboles n-arios hay dos opciones po-
sibles:
cho, respectivamente.
La opcin (b) se basara en utilizar una representacin de los rboles n-arios como rboles binarios, al estilo estudiado en el ejercicio 245. compara las dos opciones desde el punto de vista del
espacio ocupado por la estructura representante del rbol.
259. Define los conceptos de rbol n-ario completo y rbol n-ario semicompleto. Enuncia y demuestra
para esta clase de rboles los resultados anlogos a los obtenidos en el ejercicio 243(b).
260. Extiende los resultados de los ejercicios 255 y 256 al caso de los rboles n-arios.
479
rboles
263. Especifica y programa una funcin recursiva que calcule la frontera de un rbol dado como
parmetro. Por frontera se entiende la lista formada por los elementos almacenados en las
hojas del rbol, tomados de izquierda a derecha. Se supone disponible un mdulo que implementa el TAD LISTA.
264. Programa un funcin recursiva que calcule el valor numrico de una expresin aritmtica a
Recorridos de rboles
265. Construye las listas resultantes de aplicar los diferentes recorridos posibles al rbol binario
266. Especifica algebraicamente los tres tipos de recorridos en profundidad de rboles binarios,
planteados como operaciones que convierten un rbol en una lista, con perfil Arbin[Elem]
o Lista[Elem]. Presenta la especificacin resultante como enriquecimiento del TAD
ARBIN.
267. Suponiendo disponibles dos mdulos ARBIN y LISTA que implementen los TADs del
mismo nombre, y sin acceder a la representacin, programa funciones recursivas que realicen las operaciones de recorrido especificadas en el ejercicio anterior.
268. El recorrido de un rbol binario puede realizarse tambin con ayuda de un procedimiento
procrecorre(ea:Arbin[Elem];esxs:Sec[Elem]);
{P0:xs=XSfin?(xs)=cierto}
{Q0:fin?(xs)=Ciertocont(xs)=cont(XS)++recorrido(a)}
fproc
Especifcalos algebraicamente por medio de una extensin del TAD ARBOL que contenga
ecuaciones para dos nuevas operaciones:
preAG,postAG:Arbol[Elem]oLista[Elem]
Nota: Debido a la dependencia mutua entre rboles generales y bosques, debers especificar
tambin dos operaciones auxiliares que describen el recorrido de bosques:
preBos,postBos:Bosque[Elem]oLista[Elem]
270. Supongamos que R : Arbin[Elem] o Lista[Elem] sea una cualquiera de las tres operaciones
480
rboles
tal que acumulaR(as) construya la concatenacin de todos los recorridos R(a) correspondientes a
los rboles a apilados en as, empezando por la cima.
271. Los recorridos en profundidad de rboles binarios pueden realizarse con algoritmos iterati-
R(a)=xs++acumulaR(as)todoslosrbolesdeassonnovacos
profundidad de rboles binarios. Considera la operacin ms general R : (Pila[Arbin[Elem]], Lista[Elem]) o Lista[Elem] especificada por la ecuacin
R(as,xs)=xs++acumulaR(as)
niveles(a)=xs++nivelesCola(as)todoslosrbolesdeassonnovacos
donde a es el rbol dado para recorrer, xs es la lista que debe contener al final el recorrido, y as
es una cola de rboles auxiliar. Busca una inicializacin adecuada para xs y as!
275. Modifica los algoritmos iterativos de recorrido de los ejercicios 271 y 274, convirtindolos
en procedimientos iterativos con una especificacin del estilo indicado en el ejercicio 268,
de manera que el resultado del recorrido quede en una secuencia.
481
rboles
277. Estudia el tema relativo a los rboles binarios hilvanados en la seccin 5.3.2 del texto de Xavier
Franch. Se trata de una representacin dinmica ms sofisticada de los rboles binarios que
aprovecha los punteros que quedan vacos en la representacin dinmica ordinaria (cfr.
ejercicio 249), de modo que los algoritmos iterativos de recorrido pueden programarse eficientemente sin ayuda de una pila auxiliar.
los en un rbol de smbolos que represente la estructura sintctica de la expresin. Supn que
los smbolos componentes de la expresin dada son operadores, operandos y parntesis, considerando para los operadores las prioridades habituales y asociatividad por la izquierda (excepto el operador de exponenciacin, que asociar por la derecha).
Sugerencia: consulta la seccin 7.1.1 del texto de Xavier Franch, donde se resuelve un problema
similar a ste.
280. Demuestra por medio de un ejemplo que dos rboles binarios diferentes pueden tener el
donde xi es el elemento del nodo visitado en i-simo lugar durante el recorrido en preorden, y
ti es el nmero de nodos del hijo izquierdo de dicho nodo. Construye algoritmos que realicen la
reconstruccin del rbol a partiendo de ps, en los dos supuestos siguientes:
482
rboles
282. El problema de bsqueda por niveles de un rbol binario es anlogo al problema de bsqueda
en profundidad, con la diferencia de que en este caso se desea localizar la primera posicin
en la que aparece x al efectuar el recorrido por niveles de a. Por ejemplo, si a es el rbol de
caracteres mostrado en el ejercicio anterior y x es el carcter P, el resultado de la bsqueda
por niveles debe ser la lista [1,1], que representa la posicin 1.1. Especifica y programa una
funcin iterativa buscaNiv que resuelva el problema de la bsqueda por niveles.
283. Un rbol de codificacin es un rbol binario a que almacena en cada una de sus hojas un carc-
Carcter
Cdigo
A
T
G
R
E
1.1
1.2
2.1.1
2.1.2
2.2
(b) Construye el resultado de codificar la cadena de caracteres RETA utilizando el
284. Suponemos disponibles mdulos que exportan rboles binarios de caracteres, listas de en-
teros y listas de caracteres, con las operaciones bsicas adecuadas. Convenimos en llamar
texto a una lista de caracteres, y cdigo a una lista de enteros en la que slo aparezcan los enteros
1 y 2. Construye funciones que resuelvan los problemas siguientes:
483
rboles
slo almacenen datos en sus hojas, y desarrolla una implementacin dinmica eficiente, representando los rboles por medio de estructuras con dos clases de nodos: nodos hoja, con
un campo de tipo Elem; y nodos internos, con dos campos de tipo Enlace.
Sugerencia: Para la especificacin, utiliza dos operaciones generadoras:
Hoja:ElemoHArbin[Elem]
Nodo:(HArbin[Elem],HArbin[Elem])oHArbin[Elem]
484
rboles
{Q0:Q(x,y)}
devy
ffunc
485
rboles
Apilar(nuevoNodo,pila)
m=2ov:=nodo.resul;
y:=c(u,v,y);
desapila(pila)
fsi
fit;
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc
Si un nodo cumple marca = 1 y param = u, entonces el nodo apilado justo sobre l (si
existe) cumple param = s1(u).
Siempre que el nodo cima cumple marca = 2 y param = u, se verifica tambin que y =
f(s2(u)).
El invariante, que no detallamos, debera formalizar estas condiciones. Cuando la pila queda
vaca y el bucle termina, en y queda el resultado f(x) correspondiente a la llamada inicial. En cuanto a la expresin de acotacin, es posible definirla como el nmero de visitas a nodos del rbol de
llamadas que estn an pendientes de realizar en cada momento.
(a) Aplica la transformacin descrita a la funcin recursiva doble fib del ejercicio 83.
Estudia paso a paso la evolucin de la pila de nodos para una llamada inicial
concreta, tal como fibit(3) o fibit(4).
(b) Aplica la transformacin a las funciones recursivas dobles consideradas en los ejercicios 261-264.
rboles
486
resultante quede ordenado si lo estaba el rbol de partida. Dibuja el rbol ordenado de enteros que se obtiene a partir del rbol vaco, insertando sucesivamente los nmeros siguientes:
20, 12, 17, 31, 26, 43, 7, 35
devuelva como resultado el subrbol obtenido tomando como raz el nodo que contenga el
elemento buscado. Si ningn nodo del rbol contiene el elemento buscado, se devuelve el
rbol vaco. Indica los resultados de buscar los enteros 35, 43 y 31, respectivamente, en el
rbol obtenido en el ejercicio 288.
el resultado sea un nuevo rbol ordenado del cual se ha eliminado el nodo que contena el
elemento en cuestin, si exista. Si ningn nodo del rbol contena dicho elemento, la operacin de borrado deja el rbol inalterado. Dibuja los rboles resultantes de borrar los enteros que se indican a continuacin en el rbol obtenido en el ejercicio 288:
(a) Borrar 35: Este elemento aparece en una hoja. Esta hoja se elimina.
(b) Borrar 43: Este elemento aparece en un nodo interno con un solo hijo. Este nodo se
elimina y se sustituye por su hijo.
(c) Borrar 31: Este elemento aparece en un nodo interno con dos hijos. Se reemplaza el
elemento de este nodo por el menor elemento de su hijo derecho. Mediante otro borrado, se quita del hijo derecho el nodo que contena este elemento.
291. Escribe una especificacin algebraica de una TAD ARB-ORD[E :: ORD] que represente el
comportamiento de los rboles ordenados, con operaciones para crear un rbol vaco, insertar, buscar, borrar y preguntar si un elemento aparece en un rbol.
Sugerencia: Usa REC-PROF-ARBIN[E], ocultando aquellas operaciones que no convenga poner a disposicin de los clientes de ARB-ORD[E]. En particular, las operaciones pblicas de
ARB-ORD[E] deben ser tales que los clientes de este TAD slo puedan construir rboles ordenados.
292. Sean a :Arbin[elem], x : Elem. Si a no es un rbol ordenado, algunas operaciones de ARB-
487
rboles
Busca ejemplos concretos en los que esto ocurra. Observa que los clientes del TAD ARBORD no tropiezan con este problema, debido a que las operaciones exportadas slo permiten
construir rboles ordenados.
293. Los rboles de bsqueda, de tipo Arbus[Cla, Val], son semejantes a los rboles ordenados;
pero ahora cada nodo almacena dos informaciones: una clave de tipo Cla, y un valor de tipo
Val. El tipo Cla debe poseer igualdad y orden, y el tipo Val debe estar dotado de una operacin (): (Val, Val) o Val que combine dos valores. Especifica un TAD
ARBUSCA[C::ORD, V::COMB] que represente el comportamiento de los rboles de
bsqueda, donde la clase de tipos COMB requiere que el tipo-parmetro V posea una operacin de combinacin ( ). ARBUS[C, V] debe exportar operaciones para crear un rbol
vaco, insertar un valor asociado a una clave dada, buscar una clave dada, y preguntar si una
clave dada aparece en un rbol. La operacin de insercin debe usar ( ) para combinar el
nuevo valor insertado con el antiguo, en el caso de que la clave de insercin ya aparezca en
el rbol.
27
21
33
25
295. Desarrolla una implementacin de ARB-ORD basada en una representacin dinmica simi-
lar a la del ejercicio 249, realizando las operaciones busca y est? como una funcin, y las
operaciones inserta y borra como procedimientos con un parmetro es a : Arbusca[Elem]. Advertencia: Esta implementacin no puede plantearse modularmente, importando el tipo representante de un mdulo ARBIN[ELEM]. Para programar correctamente inserta y borra es
necesario tener acceso a la estructura representante del rbol.
296. Extiende el ejercicio anterior para obtener una implementacin de ARBUSCA basada en
una representacin dinmica semejante a la del ejercicio 249. Ahora, cada nodo deber contener 4 campos: una clave, un valor, y dos enlaces apuntando a los hijos.
488
rboles
297. Modifica las implementaciones desarrolladas en los dos ejercicios anteriores, de manera que
la funcin busca y los procedimientos inserta y borra sean iterativos en lugar de recursivos.
hazArbusca:Lista[Elem]oArbus[Elem]
tal que hazArbusca(xs) sea el rbol ordenado resultante de insertar sucesivamente los datos de
la lista xs, comenzando con un rbol vaco.
Sugerencia: Especifica hazArbusca(xs) = reiteraInserta(xs, Vaco), y aade ecuaciones que especifiquen la operacin ms general reiteraInserta.
300. Sea Elem como en el ejercicio anterior. Sea a : Arbus[elem] un rbol ordenado.
(a) Demuestra que en general no es cierto que a = hazArbus(inOrd(a)). Qu forma tiene
el rbol hazArbusca(inOrd(a))?
(b) Demuestra que siempre se cumple que a = hazArbusca(preOrd(a)), razonando por in-
301. Basndote en el apartado (b) del ejercicio anterior, construye un algoritmo que sea capaz de
reconstruir un rbol ordenado a partir de su recorrido en preorden, sin ninguna informacin adicional. Compara con el ejercicio 280. Desarrolla dos variantes del algoritmo, adecuadas a dos posibles presentaciones del recorrido en preorden: como lista o como
secuencia.
302. Programa un procedimiento recursivo que realice el recorrido en inorden de un rbol bina-
Nota: Este ejercicio y el anterior se pueden generalizar fcilmente para extenderlos al caso de
rboles de bsqueda de tipo Arbus[Cla, Val].
303. El algoritmo de ordenacin mediante rbol de bsqueda (treeSort) ordena un vector v : Vector[1..N] de
rboles
489
(b) se recorre el rbol obtenido en inorden, y durante el recorrido se van colocando los
rbol almacenen un mismo elemento, pero con la condicin siguiente: el elemento de cualquier nodo debe ser estrictamente mayor que los elementos de los nodos del hijo izquierdo, y
menor o igual que los elementos del hijo derecho. Equivalentemente, el recorrido en inorden
del rbol debe dar una lista de elementos ordenada en orden no decreciente. Especifica un
TAD parametrizado ARB-ORD-REP[E :: ORD] que represente el comportamiento de este
nuevo tipo de rboles ordenados.
cin del ejercicio 303 de modo que sea posible ordenar vectores que contengan repeticiones de elementos.
306. Construye algoritmos de ordenacin basados en rboles de bsqueda que puedan aplicarse
307. El problema de las concordancias consiste en lo siguiente: Dado un texto, se trata de contar el
nmero de veces que aparece en l cada palabra, y producir un listado ordenado alfabticamente por palabras, donde cada palabra aparece acompaada del nmero de veces que ha
aparecido en el texto. Suponemos que el texto a analizar viene dado como secuencia de tipo Sec[Palabra], siendo Palabra un tipo disponible de la clase ORD. Se pide construir un algoritmo que resuelva el problema con ayuda de un rbol de bsqueda de tipo
Arbus[Palabra, Nat], y analizar su complejidad. El listado pedido se dar como secuencia de
parejas, de tipo Sec[Pareja[Palabra, Nat]].
308. Dado un texto organizado por lneas, el problema de las referencias cruzadas pide producir un
listado ordenado alfabticamente por palabras, donde cada palabra del texto vaya acompaada de una lista de referencias, que contendr los nmeros de todas las lneas del texto en las
que aparece la palabra en cuestin (con posibles repeticiones si la palabra aparece varias veces en una misma lnea). Suponiendo que el texto a analizar venga dado como secuencia de
tipo Sec[Sec[Palabra]], construye un algoritmo que resuelve el problema con ayuda de un
rbol de bsqueda de tipo Arbus[Palabra, Lista[Nat]], y analiza su complejidad. El listado
pedido se dar como secuencia de parejas, de tipo Sec[Pareja[Palabra, Lista[Nat]]].
309. Estudia una implementacin del TAD POLI de los polinomios con coeficientes enteros en
bsqueda importados de un mdulo conveniente para la construccin del tipo representante. En cada caso, analiza el tiempo de ejecucin de las operaciones y el espacio ocupado
por la representacin.
(a) El TAD BOSTEZOS del ejercicio 231.
(b) El TAD CONSULTORIO del ejercicio 232.
490
rboles
Arboles AVL
311. Especifica mediante ecuaciones operaciones booleanas que reconozcan los rboles binarios
312. Observa que todos los rboles completos son equilibrados. Dibuja un rbol AVL que no
313. Construye todos los rboles ordenados posibles formados por cuatro nodos con elementos
314. Busca una ley de recurrencia para la sucesin bn , siendo bn el nmero de rboles de bsque-
da que se pueden formar con n nodos con elementos diferentes (e.g. 1, 2, , n).
315. Define recursivamente dos sucesiones nt : Nat, at : Arbus[Nat], de manera que para todo t
316. Especifica ecuacionalmente una operacin que calcule el factor de equilibrio de un rbol
binario dado.
317. Especifica ecuacionalmente las operaciones de rotacin a la derecha y rotacin a la izquierda que
318. En cada uno de los casos siguientes, dibuja el rbol resultante de la insercin ordinaria, y a
continuacin efecta las rotaciones necesarias para conseguir el efecto de una insercin
AVL. Suponemos que los datos son enteros, y que la funcin clave es la identidad.
(a) insertaAVL(11, a), siendo a:
4
2
8
6
10
491
rboles
50
30
20
60
40
15
55
45
65
(ver ejercicios 249, 250 y 296) de manera que sea posible determinar eficientemente los factores de equilibrio sin calcular tallas. Formula un invariante de la representacin y una funcin de abstraccin adecuados para representar rboles AVL con ayuda del nuevo tipo.
321. Desarrolla un algoritmo recursivo que implemente la insercin AVL. Puedes consultar la
322. Dibuja el rbol resultante de efectuar el borrado ordinario borra(1, a5), siendo a5 el rbol de
Fibonacci de lugar 5 (ver ejercicio 315). A continuacin, efecta las rotaciones necesarias
para reequilibrar dos subrboles a lo largo de la trayectoria de bsqueda para que el rbol
total quede reequilibrado.
to similar al del ejercicio 321, y usando el mismo tipo representante. Consulta la Seccin
5.6.2 del texto de Franch.
325. Adapta todo lo desarrollado en los ejercicios 316-324 para obtener una especificacin e
Colas de prioridad
326. Una cola de prioridad tiene un comportamiento similar al de una cola, con la diferencia de que
492
rboles
En los ejercicios siguientes suponemos que el tipo Elem dispone de operaciones de igualdad y
orden, que se interpretan en el sentido de una comparacin de prioridades.
327. Plantea una implementacin secuencial de las colas de prioridad, utilizando listas o secuen-
cias ordenadas como tipo representante. Estima los rdenes de magnitud de los tiempos de
ejecucin de las operaciones, en el caso peor.
Montculos
328. Sea a : Arbin[Elem]. Se dice que a es un montculo (de mnimos) syss a es semicompleto, la
raz de a (si existe) precede en prioridad a los elementos almacenados en los hijos, y los
hijos (si existen) son a su vez montculos.
*(a) Dibuja un montculo de nmeros naturales que contenga los nmeros del intervalo
[1..7].
(b) Especifica ecuacionalmente una operacin booleana
montculo?:Arbin[Elem]oBool
de modo que la siguiente ecuacin especifique correctamente la operacin de insercin para montculos:
insertar:(Elem,Arbin[Elem])oArbin[Elem]
insertar(x,a)=flotar(ponerHoja(x,a))
(se trata de que insertar(x, a) sea un montculo, siempre que a lo sea y x sea diferente
en prioridad de todos los elementos de a).
330. Para eliminar el elemento de la raz de un montculo a, se utiliza el siguiente algoritmo:
(E.1) Si a es vaco, la operacin no tiene sentido. Si a tiene un solo nodo, el resultado de la
493
rboles
(a) Realiza sucesivas eliminaciones de la raz en el montculo obtenido en el ejercicio
329(a), hasta llegar al montculo vaco. Observa el orden en el que van siendo extrados los nmeros.
(b) Especifica ecuacionalmente dos operaciones
preparar:Arbin[Elem]oArbin[Elem]
hundir:Arbin[Elem]oArbin[Elem]
332. Define un tipo representante adecuado para una representacin de los montculos basada
Recuerda que un rbol completo de talla t tiene 2t1 nodos. Para los ejercicios que siguen presuponemos las declaraciones:
const
max=2t1;
tipo
Vec=Vector[1..max]deElem;
333. Programa los procedimientos especificados informalmente a continuacin.
procflota(esv:Vec;ei,n:Nat);
{P0:v=V1didndmax
dejandoflotarv(i)puedelograrsequev[1..n]lleguea
representarunmontculo}
{Q0:v[1..n]representaunmontculo
vsehaobtenidoapartirdeVdejandoflotarV(i)}
fproc
prochunde(esv:Vec;ei,n:Nat);
{P0:v=V1didndmax
dejandohundirsev(i)puedelograrsequev[1..n]lleguea
representarunmontculo}
494
rboles
{Q0:v[1..n]representaunmontculo
vsehaobtenidoapartirdeVdejandohundirseV(i)}
fproc
335. Usando los procedimientos del ejercicio 333, programa un procedimiento ejecutable en
tiempo O(log n) que satisfaga la especificacin siguiente. Observa que este procedimiento
puede servir para implementar una operacin que modifique el montculo cambiando uno
de sus datos por otro de diferente prioridad.
procaltera(esv:Vec;ei,n:Nat;ex:);
{P0:v=V1didndmax
v[1..n]representaunmontculo
laprioridaddexesdiferentedelasprioridadesde
losdatosdev[1..n]}
{Q0:v[1..n]representaunmontculo
vsehaobtenidoapartirdeVasignandoxaV(i)y
dejndoloflotarohundirse,segnconvenga}
fproc
336. Desarrolla una implementacin de las colas de prioridad basada en vectores, con un inva-
cola de prioridad a partir de la cola vaca por insercin reiterada de los elementos, y reiterando seguidamente la operacin de extraer el elemento mnimo (con prioridad mxima)
hasta que la cola se vace. Suponiendo disponible un mdulo que implemente el TAD
COLA-PRIO, construye un procedimiento de ordenacin para vectores de tipo Vec siguiendo este mtodo, y razona que el tiempo de ejecucin es O(n log n).
338. Una generalizacin del problema del ejercicio anterior consiste en obtener los k elementos
495
rboles
que se est ordenando para representar el montculo. Los ejercicios que siguen desarrollan esta
idea.
339. El siguiente procedimiento reorganiza un segmento de vector de manera que pase a repre-
sentar un montculo:
prochazMont(esv:Vec;en:Nat);
{P0:v=V0dndmax
i,j:1di<jdn:v(i)zv(j)}
var
i:Nat;
inicio
sind1
entonces
seguir
sino
paraibajandodesdendiv2hasta1hacer
{I:k:i+1dkdn:v[k..n]representaunmontculo}
hunde(v,i,n) /*verej.334*/
fpara
fsi
{Q0:v[1..n]representaunmontculov[n+1..max]=V[n+1..max]
v[1..n]esunapermutacindeV[1..n]}
fproc
Se puede demostrar que este procedimiento consume tiempo O(n); ver el texto de Xavier
Franch, Seccin 5.5.2. Dibuja el rbol semicompleto cuyo recorrido por niveles da la sucesin de
nmeros 10, 8, 2, 4, 7, 5, 9, 3, 6, 1 (en este orden) y convirtelo en un montculo siguiendo el algoritmo del procedimiento hazMont.
340. El siguiente procedimiento ordena un vector con ayuda de un montculo representado de-
procordenaMont(v:Vec;n:Nat);
{P0:v=V0dndmax
i,j:1di<jdn:v(i)zv(j)}
var
i:Nat;
inicio
hazMont(v,n);
sind1
entonces
seguir
sino
paraibajandodesdenhasta2hacer
496
rboles
{I:v[1..n]esunapermutacindeV[1..n]
v[n+1..max]=V[n+1..max]
v[i+1..n]contienelosnidatosdeV[1..n]de
menorprioridad,ordenadoenordendecreciente
v[1..i]representaunmontculo}
fpara
fsi
{Q0:v[1..n]esunapermutacindeV[1..n]
v[n+1..max]=V[n+1..max]
v[1..n]estordenadoenordendecreciente}
fproc
Usando el ejercicio 339, razona que este algoritmo de ordenacin requiere tiempo O(n log n).
Ensaya el funcionamiento del algoritmo para ordenar un vector que contenga la sucesin de
nmeros 10, 8, 2, 4, 7, 5, 9, 3, 6, 1, en este orden.
341. Desarrolla modificaciones del algoritmo del ejercicio 340, de manera que:
(a) Se permitan prioridades repetidas y se obtenga al final un vector ordenado en orden
diante montculos, ya que puede ocurrir que ciertos elementos con prioridades iguales no
salgan del montculo en el mismo orden en que entraron. Construye un ejemplo que ilustre
este fenmeno.
497
Tablas
CAPTULO 6
TABLAS
6.1 Modelo matemtico y especificacin
Desde el punto de vista matemtico, las tablas son aplicaciones que hacen corresponder a claves, c C C conjunto de claves, valores, v V V conjunto de valores. Podemos verlas como una generalizacin de los rboles de bsqueda, como colecciones de pares (clave, valor),
donde el acceso se realiza por las claves y donde, a diferencia de los rboles de bsqueda, no se
supone una estructura de rbol aunque pueden ser implementadas como tales.
Las aplicaciones de las tablas en Informtica incluyen, entre otras: las tablas de smbolos de los
compiladores, las tablas usadas en los sistemas de archivos, las tablas usadas en los sistemas de
gestin de bases de datos.
Para dar un modelo matemtico de las tablas hay tres posibilidades naturales (cfr. [Franch 93],
pp. 160-162):
Funciones parciales T : C o V.
Conjuntos T V, suponiendo en este caso que V es un conjunto de elementos con claves asociadas mediante una operacin clave: V o C, y conviniendo en que C no puede
contener dos elementos distintos con la misma clave.
Las operaciones que nos interesan en las tablas son las que permiten: crear una tabla vaca,
insertar una pareja (clave, valor), comprobar si la tabla est vaca, comprobar si una clave est en
la tabla, consultar el valor asociado a una clave, y borrar un par (clave, valor).
6.1.1
TablaVaca:oTabla[Clave,Valor]
Inserta:(Tabla[Clave,Valor],Clave,Valor)oTabla[Clave,Valor]/*gen*/
esVaca:Tabla[Clave,Valor]oBool
/*obs*/
est:(Tabla[Clave,Valor],Clave)oBool
/*obs*/
consulta:(Tabla[Clave,Valor],Clave)oValor
/*obs*/
/*gen*/
498
Tablas
borra:(Tabla[Clave,Valor],Clave)oTabla[Clave,Valor] /*mod*/
ecuaciones
t:Tabla[Clave,Valor]:i,j:Clave:x,y:Valor:
Inserta(Inserta(t,i,x),j,y)
=
Inserta(t,j,y)
sii==j
Inserta(Inserta(t,i,x),j,y)
=
Inserta(Inserta(t,j,y),i,x)
sii/=j
=cierto
esVaca(TablaVaca)
esVaca(Inserta(t,i,x))
=falso
est(TablaVaca,j)
=falso
est(Inserta(t,i,x),j)
=i==jORest(t,j)
defconsulta(t,i)siest(t,i)
consulta(Inserta(t,i,x),j)=x
sii==j
f
consulta(Inserta(t,i,x),j)= consulta(t,j)
sii/=j
borra(TablaVaca,j)
=TablaVaca
borra(Inserta(t,i,x),j) =borra(t,j)
sii==j
borra(Inserta(t,i,x),j) =Inserta(borra(t,j),i,x) sii/=j
errores
consulta(t,i)siNOTest(t,i)
ftad
6.1.2
Para poder especificar las tablas como funciones totales la representacin en la que nos centraremos en el resto de este tema debemos suponer a las claves equipadas con un valor especial
NoDef, restriccin que expresamos mediante una clase de tipos:
claseEQND
hereda
EQ
operaciones
NoDef:oElem
fclase
TablaVaca:oTabla[Clave,Valor]
/*gen*/
499
Tablas
Inserta:(Tabla[Clave,Valor],Clave,Valor)oTabla[Clave,Valor]/*gen*/
esVaca:Tabla[Clave,Valor]oBool
/*obs*/
est:(Tabla[Clave,Valor],Clave)oBool
/*obs*/
consulta:(Tabla[Clave,Valor],Clave)oValor
/*obs*/
borra:(Tabla[Clave,Valor],Clave)oTabla[Clave,Valor] /*mod*/
ecuaciones
t:Tabla[Clave,Valor]:i,j:Clave:x,y:Valor:
Inserta(Inserta(t,i,x),j,y)
=
Inserta(t,j,y)
sii==j
Inserta(Inserta(t,i,x),j,y)
=
Inserta(Inserta(t,j,y),i,x)
sii/=j
esVaca(TablaVaca)
=cierto
esVaca(Inserta(t,i,x))
=falso
est(TablaVaca,j)
=falso
est(Inserta(t,i,x),j)
=i==jORest(t,j)
consulta(TablaVaca,j)
=NoDef
consulta(Inserta(t,i,x),j)=x
sii==j
consulta(Inserta(t,i,x),j)=consulta(t,j)
sii/=j
borra(TablaVaca,j)
=TablaVaca
borra(Inserta(t,i,x),j) =borra(t,j)
sii==j
borra(Inserta(t,i,x),j) =Inserta(borra(t,j),i,x) sii/=j
ftad
6.1.3
Tablas ordenadas
Para ciertas aplicaciones de las tablas resulta conveniente que exista un orden entre las claves,
y que se disponga de una operacin que extraiga la informacin almacenada en la tabla en forma
de lista (o secuencia) de parejas de tipo (Clave, Valor), ordenada por claves. En ocasiones, tambin resulta til que una insercin con clave ya presente en la tabla combine el nuevo valor con el
antiguo, en lugar de limitarse a reemplazar el valor antiguo por el nuevo. Llamaremos tablas ordenadas a las tablas que incorporen estas dos ideas. Este TAD es prcticamente igual al de los rboles de bsqueda, con la diferencia de que la consulta en un rbol de bsqueda da un rbol como
resultado, mientras que la consulta en un tabla devuelve un valor. Es directo implementar las
tablas ordenadas como rboles de bsqueda.
La especificacin hace uso de una clase que caracteriza a los tipos combinables equipados con
el valor NoDef:
claseEQNDCOMB
hereda
EQND,COMB
axiomas
x:Elem:
NoDefx=x
fclase
500
Tablas
tadTABLAORD[C::ORD,V::EQNDCOMB]
renombra
C.ElemaClave
V.ElemaValor
usa
BOOL
LISTA[PAREJA[C,V]]
tipo
Tabla[Clave,Valor]
operaciones
TablaVaca:oTabla[Clave,Valor]
/*gen*/
Inserta:(Tabla[Clave,Valor],Clave,Valor)oTabla[Clave,Valor]/*gen*/
esVaca:Tabla[Clave,Valor]oBool
/*obs*/
est:(Tabla[Clave,Valor],Clave)oBool
/*obs*/
consulta:(Tabla[Clave,Valor],Clave)oValor
/*obs*/
borra:(Tabla[Clave,Valor],Clave)oTabla[Clave,Valor] /*mod*/
lista:Tabla[Clave,Valor]oLista[Pareja[Clave,Valor]]
operacionesprivadas
minClave:Tabla[clave,Valor]oClave
ecuaciones
/*obs*/
t,t:Tabla[Clave,Valor]:i,j:Clave:x,y:Valor:
Inserta(Inserta(t,i,x),j,y)
=
Inserta(t,i,xy)
sii==j
Inserta(Inserta(t,i,x),j,y)
=
Inserta(Inserta(t,j,y),i,x)
sii/=j
=cierto
esVaca(TablaVaca)
esVaca(Inserta(t,i,x))
=falso
est(TablaVaca,j)
=falso
est(Inserta(t,i,x),j)
=i==jORest(t,j)
consulta(TablaVaca,j)
=NoDef
consulta(Inserta(t,i,x),j)=consulta(t,j)x
sii==j
consulta(Inserta(t,i,x),j)=consulta(t,j)
sii/=j
borra(TablaVaca,j)
=TablaVaca
borra(Inserta(t,i,x),j) =borra(t,j)
sii==j
borra(Inserta(t,i,x),j) =Inserta(borra(t,j),i,x) sii/=j
defminClave(t)siNOTesVaca(t)
minClave(Inserta(TablaVaca,j,y))=j
minclave(Inserta(Inserta(t,i,x),j,y))=minClave(Inserta(t,i,x))sii
dj
minclave(Inserta(Inserta(t,i,x),j,y))=minClave(Inserta(t,j,y))sii
>j
lista(t)
=[]siesVaca(t)
lista(t)
=[Par(i,x)/lista(t)]
siNOTesVaca(t)ANDi=minClave(t)AND
x=consulta(t,i)ANDt=borra(t,i)
501
Tablas
ftad
6.1.4
Conjuntos
El comportamiento del TAD CJTO[E :: EQ] se puede considerar equivalente al del TAD
TABLA[E :: EQ, V :: EQ-ND], suponiendo que V sea un tipo especial que slo contenga los dos
valores Si y Nodef. Se puede establecer la siguiente correspondencia entre las operaciones de ambos TADs:
CJTO[E :: EQ]
Vaco( )
Pon(x, xs)
quita(x, xs)
esVaco(xs)
pertenece(x, xs)
Vectores
Los vectores tambin pueden considerarse como una clase especial de tablas, donde las claves
({ ndices) deben ser de un tipo discreto. Un tipo de datos es discreto si el tipo contiene una cantidad
finita de valores y es isomorfo a un intervalo [1..n] de los enteros, disponiendo de igualdad, orden
y operaciones para determinar los valores primero y ltimo y los valores siguiente y anterior de un
valor dado. La clase DIS de los tipos de datos discretos se puede especificar como subclase de la
clase de tipos ORD.
claseDIS
hereda
ORD
operaciones
card:oNat
ord:ElemoNat
elem:NatoElem
prim,ult:oElem
suc,pred:ElemoElem
axiomas
x,y:Elem:i:Nat:
cardt1
1dord(x)dcard
defelem(i)si1didcard
ord(elem(i))=di
elem(ord(x))=x
prim=elem(1)
ult=elem(card)
502
Tablas
defsuc(x)six/=ult
suc(x)
=delem(ord(x)1)
x==y
=ord(x)==ord(y)
xdy
fclase
=ord(x)dord(y)
Equipados con la clase DIS, podemos construir una especificacin algebraica del TAD
VECTOR[I :: DIS, D :: ANY] pensado para representar el comportamiento de los vectores, con
operaciones que permitan crear un vector vaco, modificar un vector asignando un nuevo dato en
la posicin de un ndice dado, y consultar en un vector el dato asociado a un ndice dado:
tadVECTOR[I::DIS,D::ANY]
renombra
I.ElemaIndice
D.ElemaDato
usa
BOOL
tipo
Vector[Indice,Dato]
operaciones
Crea:oVector[Indice,Dato]
Asigna:(Vector[Indice,Dato],Indice,Dato)oVector[Indice,Dato]/*gen*/
valor:(Vector[Indice,Dato],Indice)oDato
operacionesprivadas
def?:(Vector[Indice,Dato],Indice)oBool /*obs*/
ecuaciones
/*gen*/
/*obs*/
v:Vector[Indice,Dato]:i,j:Indice:x,y:Dato:
Asigna(Asigna(v,i,x),j,y)=Asigna(v,j,y)
sii==j
Asigna(Asigna(v,i,x),j,y)=Asigna(Asigna(v,j,y),i,x) sii
/=j
def?(Crea,j)
=falso
def?(Asigna(v,i,x),j)
=i==jORdef?(v,j)
defvalor(v,i)sidef?(v,i)
valor(Asigna(v,i,x),j)
=x
sii==j
f
valor(Asigna(v,i,x),j)
= valor(v,j)
sii/=j
errores
valor(v,i)siNOTdef?(v,i)
ftad
Podemos establecer la siguiente correspondencia entre las operaciones de los vectores y las tablas, que nos sugiere cmo los vectores se pueden implementar de manera directa en forma de
tablas
VECTOR[I :: DIS, D :: ANY]
Crea( )
503
Tablas
Asigna(xs, i, x)
valor(xs, i)
Inserta(xs, i, x)
consulta(xs, i)
Vectores de parejas (clave, valor), desordenados u ordenados por clave. Las complejidades que se pueden obtener para las operaciones:
Operacin
TablaVaca
Inserta
esVaca
est
consulta
borra
Vector desordenado
O(1)
O(n) / O(1) **
O(1)
O(n)
O(n)
O(n)
Vector ordenado
O(1)
O(n)
O(1)
O(log n)
O(log n)
O(n)
** La complejidad de la insercin en un vector desordenado es O(n) si no permitimos claves repetidas y O(1) si las permitimos, con el consiguiente desaprovechamiento de espacio
y haciendo necesario que las bsquedas comiencen por el final de la zona ocupada del
vector.
Secuencia desordenada
O(1)
O(n) / O(1) **
O(1)
O(n)
O(n)
O(n)
Secuencia ordenada
O(1)
O(n)
O(1)
O(n)
O(n)
O(n)
504
Tablas
Arbol de bsqueda
O(1)
O(log n)
O(1)
O(log n)
O(log n)
O(log n)
siendo N suficientemente grande, aunque mucho menor que el nmero total de claves posibles.
Funcin de localizacin
Para operar con la tabla se necesitar interponer una funcin que asocie a cada clave un ndice
del vector:
505
Tablas
h:Claveo[0..N1]
Dada una clave c, el ndice h(c) es la posicin del vector en la que en un principio intentaremos
localizarla clave. La funcin h que usemos para esto se llamar funcin de localizacin (en ingls, hashing function).
Al ser el nmero total de claves posibles mayor que N, h no puede ser inyectiva. Se deber
procurar que se cumplen las dos condiciones siguientes:
Eficiencia. El coste de calcular h(c) para una clave c dada debe ser bajo.
0
1
N-1
Clave
[0..N-1]
Por ejemplo, supongamos que N = 16 y que las claves son cadenas de caracteres. Una posible
funcin de localizacin es la definida como:
h(c)=deford(ult(c))mod16
donde ult(c) es el ltimo carcter de c, y ord es la funcin que devuelve el cdigo ASCII de un
carcter. Usando esta funcin de localizacin, obtenemos:
h(Fred)=ord(d)mod16=100mod16=4
h(Joe)=ord(e)mod16=101mod16=5
h(John)=ord(n)mod16=110mod16=14
aunque esta funcin de localizacin no es muy buena, la seguiremos usando para algunos
ejemplos debido a su sencillez. Ms adelante presentaremos algunos mtodos para definir buenas
funciones de localizacin.
506
Tablas
Colisiones
Como hemos visto, el dominio de una funcin de localizacin siempre suele tener un cardinal
mucho mayor que su rango. Por lo tanto, una funcin de localizacin h no puede ser inyectiva.
Cuando se encuentran claves c, c tales que:
c z c h(c) = h(c)
se dice que se ha producido una colisin. Se dice tambin que c y c son claves sinnimas con respecto a h.
Por ejemplo, para la anterior funcin de colisin:
h(Fred) = h(David) = h(Violet) = h(Roland) = 4
las cuatro cadenas colisionan en el ndice 4 y son sinnimas con respecto a h.
La probabilidad de que no se produzcan colisiones es muy baja. Usando clculo de probabilidades puede demostrarse la llamada paradoja de los cumpleaos, que dice: en un grupo de 23 o
ms personas, la probabilidad de que al menos dos de ellas tengan su cumpleaos en el mismo
da del ao es mayor que 1/2.
Tablas dispersas
Se llaman as a las tablas implementadas de manera que:
tablas abiertas, y
tablas cerradas
6.3.1
Para cada ndice i, t(i) almacena una lista de parejas (Clave, Valor) con claves sinnimas c, tal
que h(c) = i. Estas listas se llaman agrupaciones o, simplemente, listas de colisiones. Las colisiones que
se producen durante una operacin de insercin se resuelven fcilmente alargando las agrupaciones. Durante las operaciones de borrado, las agrupaciones se acortan.
Por ejemplo, una tabla dispersa abierta implementada sobre un vector con ndices del intervalo
[0..15] y donde se utilizada la funcin de localizacin presentada anteriormente, despus de las
siguientes operaciones:
TablaVaca(t);
inserta(t,Fred,25);inserta(t,Alex,18);inserta(t,Philip,10);
507
Tablas
inserta(t,Joe,38);inserta(t,John,36);inserta(t,Hanna,19);
inserta(t,David,40);inserta(t,Martin,28);inserta(t,Violet,20);
inserta(t,George,48);inserta(t,Helen,90);inserta(t,Manyu,24);
inserta(t,Roland,14);
resulta:
0
Philip
10
Fred
25
David
40
Violet
20
Joe
38
George 48
Manyu
24
Alex
18
Hanna
19
John
36
Martin
28
Helen
90
1
2
3
Roland 14
6
7
8
9
10
11
12
13
14
15
Aunque hemos dibujado las listas con punteros por mayor claridad, no presuponemos nada
sobre la implementacin de las agrupaciones. Ntese que la insercin en las listas se realiza por el
final porque es necesario comprobar en cada insercin si ya existe la clave.
Una caracterstica interesante de un valor concreto de una tabla dispersa es su tasa de ocupacin,
que en el caso de las tablas abiertas de define como el cociente entre el nmero de parejas (Clave,
Valor) almacenadas en la tabla, y el nmero total de posiciones del vector.
La situacin final:
La realizacin de las operaciones de las tablas en una tabla abierta es sencilla, empleando
tcnicas bsicas de procesamiento de listas. Las colisiones se manejan fcilmente. Otra ventaja
508
Tablas
es que la tabla puede llegar a almacenar ms de N parejas, aunque en este caso la rapidez de los
accesos puede degenerar, hacindose O(n) como en los mtodos de bsqueda secuencial.
Implementacin de las tablas dispersas abiertas
Tipo representante
mduloimplTABLA[CLAVE,VALOR]
importa
EQ,EQND,SEC[ELEM]
privado
const
N=??;
tipo
Clave=EQ.Elem;
Valor=EQND.Elem;
Indice=[0..N1];
Pareja=reg
clave:Clave;
valor:Valor
freg;
Agrupacin=SEC.Sec[Pareja];
Tabla[Clave,Valor]=VectorIndicedeAgrupacin
%implementacindelasoperaciones
fmdulo
Invariante de la representacin
Para t : Tabla[Clave, Valor]:
R(t)
def
i:0di<N:
((t(i):Agrupacin)
(j:1djd#cont(t(i)):h((cont(t(i))!!j).clave)=i)
(j,k:1dj<kd#cont(t(i)):(cont(t(i))!!j).clavez
(cont(t(i))!!k).clave)
(j:1djd#cont(t(i)):(cont(t(i))!!j).valorzNoDef))
Donde se expresa que t(i) es tipo Agrupacin, cada pareja de t(i) tiene una clave c tal que h(c) =
i, parejas diferentes de t(i) tienen diferente clave y cada pareja de t(i) tiene un valor z NoDef.
509
Tablas
Funcin de abstraccin
Para t : Tabla[Clave, Valor] tal que R(t):
A(t)=defhazTabla(t,0)
TablaVaca
sii=N
hazTabla(t,i)=def
aadeAgrupacin(cont(t(i)),hazTabla(t,i+1))sii<
N
aadeAgrupacin([],t)=deft
aadeAgrupacin([x/xs],t)=defaadeAgrupacin(xs,Inserta(t,x.clave,
x.valor))
510
Tablas
ffunc
El resto de las operaciones se realiza con ayuda de un procedimiento privado auxiliar que realiza una bsqueda en una agrupacin, una versin del algoritmo de bsqueda en una secuencia:
procbusca(eclave:Clave;esxs:Agrupacin;sencontrado:Bool);
{P0:xs=XS}
var
x:Pareja;
inicio
SEC.reinicia(xs);
encontrado:=falso;
itNOTSEC.fin?(xs)ANDNOTencontradoo
x:=SEC.actual(xs);
siEQ.igual(x.clave,clave)
entoncesencontrado:=cierto
sinoSEC.avanza(xs)
fsi
fit
{Q0:cont(xs)=cont(XS)
encontradollaclavedeunelementodexscoincideconclave
encontradooelactualdexseselprimerocuyaclavecoincidecon
clave}
fproc
Disponiendo de busca la realizacin de Inserta, est, consulta y borra resulta inmediata. Veamos
cmo es la implementacin de Inserta
procInserta(est:Tabla[Clave,Valor];ec:Clave;ev:Valor);
{P0:t=TR(t)}
var
i:Indice;
encontrado:Bool;
par:Pareja;
inicio
i:=h(clave);
busca(clave,t(i),encontrado);
siencontrado
entonces
SEC.borra(t(i))
sino
seguir
fsi;
par.clave:=clave;
par.valor:=valor;
SEC.Inserta(t(i),par)
{Q0:R(t)A(t)=TABLA[CLAVE,VALOR]Inserta(A(T),A(c),A(v))}
fproc
511
Tablas
6.3.2
La idea bsica de esta representacin es que la informacin almacenada en la tabla se representa por medio de un vector de parejas (Clave, Valor). Cualquier acceso a una tabla va una clave
dada c comienza calculando el llamado ndice primario:
i0=h(c)
Si el ndice primario produce colisin con otra clave sinnima, se ensaya un ndice alternativo.
Como la colisin puede reiterarse, es necesario ensayar una sucesin de ndices:
i1=prueba(1,c),i2=prueba(2,c),,im=prueba(m,c),
llamada sucesin de pruebas. A esta tcnica se la conoce como relocalizacin. Una tcnica de relocalizacin concreta debe cumplir que para toda clave c se puede calcular la sucesin de pruebas:
im=prueba(m,c) 0dm<N
que es una permutacin de [0..N1]. Por lo tanto, las sucesiones de pruebas de dos claves distintas slo difieren en el orden, pero no en los elementos.
Por ejemplo, un mtodo muy sencillo de relocalizacin consiste en definir la sucesin de
pruebas como:
i0=h(c)
im=(im1+1)modN
1dm<N
o lo que es lo mismo
prueba(m,c)=(h(c)+m)modN
Para marcar los distintos tipos de posiciones del vector, podemos utilizar un campo adicional
en cada uno de los registros almacenados en l, o suponer que el tipo de las claves est equipado
con dos valores especiales que denominados claves ficticias
ClaveVaca,ClaveBorrada:Clave
diferentes entre s. Llamaremos claves ordinarias a las que sean distintas de estas dos. La funcin
prueba no est definida sobre las claves ficticias.
Si suponemos disponible una funcin
512
Tablas
clave:(Tabla[Clave,Valor],[0..N1])oClave
que calcula la clave de la posicin i-sima de la tabla, podemos definir predicados que caractericen a los distintos tipos de posiciones de una tabla dispersa cerrada:
vaca(t,i)defclave(t,i)==ClaveVaca
borrada(t,i)defclave(t,i)==ClaveBorrada
ocupada(t,i)defNOTvaca(t,i)ANDNOTborrada(t,i)
usada(t,i)defNOTvaca(t,i)ocupada(t,i)ORborrada(t,i)
disponible(t,i)defNOTocupada(t,i)vaca(t,i)ORborrada(t,i)
La tasa de ocupacin en una tabla dispersa cerrada viene dada entonces por:
D=n/N
nmero de posiciones ocupadas entre nmero de posiciones del vector. Obviamente, en una
tabla cerrada 0 d n d N, y con ello 0 d D d 1.
Idea de los algoritmos
Todos los algoritmos sobre tablas se basan en una caracterstica que debe cumplir el invariante
de la representacin: cada clave ordinaria c que aparezca en la tabla aparece necesariamente en
una posicin de la ruta de c, definida de la siguiente forma:
Dadas t :Tabla[clave, Valor] y c : Clave, definimos la ruta de c como el segmento inicial de la sucesin de pruebas de c anterior a la primera posicin vaca (si la hay):
ruta(c,t)=def[prueba(0,c),,prueba(m1,c)]
siendo m el menor i comprendido entre 0 y N1 tal que vaca(t, prueba(i, c)), si existe; si no existe, tomamos m = N.
c
h
i0
i1
im-1
Ruta de c:
Posiciones ocupadas o borradas
im
Posicin vaca
o bien m = N
Observamos que ruta(c, t) = [ ] cuando se cumple vaca(t, prueba(0, c)); i.e., cuando el ndice primario de c corresponde a una posicin vaca.
513
Tablas
Bsqueda
Partiendo del ndice primario, se sigue la sucesin de pruebas hasta encontrar la clave buscada
o una posicin vaca. Las posiciones borradas no interrumpen la bsqueda:
c
i0
i1
im-1
Insercin
Partiendo del ndice primario, se sigue la sucesin de pruebas hasta agotar la tabla o encontrar
una posicin apta para la insercin. Si se agota la tabla, se produce error. Si se encuentra una posicin apta, debe darse uno de los tres casos siguientes:
La posicin est ocupada por la clave de insercin. Entonces se cambia el valor asociado.
i0
i1
im-1
im
in-1
Posible presencia
de la clave c
514
Tablas
Consulta
Se realiza una bsqueda con la clave de consulta. Si se encuentra la clave, se devuelve el valor
asociado. En otro caso, se devuelve NoDef.
Borrado
Se realiza una bsqueda con la clave de borrado. Si la bsqueda tiene xito, la posicin correspondiente se borra, i.e., se pone ClaveBorrada.
Ejemplo
Veamos cmo evoluciona una tabla dispersa cerrada, con ndices en el intervalo [0..9], usando
relocalizacin lineal y con una funcin h de localizacin definida como
h(c)=cmod10
Serie de pruebas
resultado
3
3,4
6
3,4,5
NoDef
6,7
3,4,5
3,4
60
3,4,5,6,7,8
3,4
3,4,5,6,7,8
9
9,0
3,4
110
9,0,1
NoDef
515
Tablas
99
3
23
2000
4
33
43
50
60
90
106
206
70
Borr
8
53
80
79
100
1000
Borr
60
100
53
110
Con lo que la tasa de ocupacin resultante queda:
D=nmerodeposicionesusadas/tamaodelvector=8/10=08
Donde hemos optado por suponer que el tipo Clave dispone de dos claves ficticias
ClaveVaca,ClaveBorrada:Clave
516
Tablas
Invariante de la representacin
Dada t : Tabla[Clave, Valor]
R(t) def I0 I1 I2 I3
siendo:
I0 def t.tamao = |t| (nmero de posiciones usadas)
I1 def el valor ficticio NoDef no aparece en t.parejas
I2 def cada clave ordinaria c aparece en t.parejas a lo sumo en una posicin
I3 def cada clave ordinaria c que aparezca en t.parejas lo hace en una posicin perteneciente a ruta(c, t)
Funcin de abstraccin
Dada t : Tabla[Clave, Valor] tal que R(t):
A(t)=defhazTabla(t,0)
TablaVaca
sii=N
hazTabla(t,i)=def hazTabla(t,i+1)
sii<NANDNOT
ocupada(t,i)
Inserta(hazTabla(t,i+1),t.parejas(i).clave,
t.parejas(i).valor)sii<NANDocupada(t,
i)
Disponemos tambin de una funcin auxiliar privada que permite comparar la clave de una
posicin dada de una tabla con una clave dada:
funcigualClave(t:Tabla[Clave,Valor];i:Indice;c:Clave)devr:
Bool;
inicio
r:=EQ.igual(t.parejas(i).clave,c)
devr
ffunc
517
Tablas
Y usando la anterior, funciones que nos permiten determinar el tipo de una posicin: vaca, borrada, ocupada, usada, disponible.
Las operaciones est, consulta y borra se programan con ayuda de una funcin auxiliar de
bsqueda, que llamaremos busca. La idea es seguir la sucesin de pruebas hasta encontrar la clave
buscada o una posicin vaca. Las posiciones borradas no interrumpen la bsqueda.
funcbusca(t:Tabla[Clave,Valor];c:Clave)devi:IndiceN;
{P0:R(t)cnoesunaclaveficticia}
var
m,lm:Indice;
inicio
m:=0;
i:=hash(c);
%i=prueba(0,c)
518
Tablas
lm:=t.tamao1;
c
%lmacotalalongituddelarutade
itm/=lmANDNOTvaca(t,i)ANDNOTigualClave(t,i,c)o
m:=m+1;
i:=prueba(m,c)
fit;
siigualClave(t,i,c)
entonces
%claveencontrada
seguir
sino
i:=N
%clavenoencontrada
fsi
{Q0:(NOTest(A(t),c)i=N)
(est(A(t),c)t.parejas(i).clave==c)}
devi
ffunc
519
Tablas
sim==lm
entonces
seguir
sino
m:=m+1;i:=prueba(m,c);
itm/=lmANDNOTvaca(t,i)ANDNOTigualClave(t,i,c)o
m:=m+1;i:=prueba(m,c)
fit;
siigualclave(t,i,c)
entonces
t.parejas(i).clave:=ClaveBorrada
sino
seguir
fsi
fsi
fsi
{Q0:R(t)A(t)=TABLA[CLAVE,VALOR]Inserta(A(T),A(c),A(v))}
fproc
En la prctica, la relocalizacin no se implementa mediante llamadas a una funcin prueba, como hemos supuesto en las anteriores implementaciones, sino que el clculo de los ndices de la
sucesin de pruebas se incorpora dentro del cdigo de la funcin busca y el procedimiento Inserta,
del modo adecuado a la tcnica de relocalizacin que se est utilizando. Presentamos a continuacin las versiones de busca e Inserta que incorporan relocalizacin lineal sin llamadas a una funcin
prueba.
funcbusca(t:Tabla[Clave,Valor];c:Clave)devi:IndiceN;
{P0:R(t)cnoesunaclaveficticia}
var
li:Indice;
inicio
i:=hash(c);
li:=(i+t.tamao1)modN;
rutadec
%acotaelltimondiceenla
iti/=liANDNOTvaca(t,i)ANDNOTigualClave(t,i,c)o
i:=(i+1)modN
fit;
siigualClave(t,i,c)
entonces
%claveencontrada
seguir
sino
i:=N
%clavenoencontrada
fsi
{Q0:(NOTest(A(t),c)i=N)
(est(A(t),c)t.parejas(i).clave==c)}
devi
ffunc
520
Tablas
Y el algoritmo de insercin
procInserta(est:Tabla[Clave,Valor];ec:Clave;ev:Valor);
{P0:t=TR(t)cnoesunaclaveficticia
(est(A(t),c)i:0didN1:disponible(t,i))}
var
i0,i,li:Indice;
n:IndiceN;
inicio
n:=t.tamao;
i0:=hash(c);
i:=i0;
li:=(i0+N1)modN;
%acotaelltimondicedela
sucesindepruebas
iti/=liANDocupada(t,i)ANDNOTigualClave(t,i,c)o
i:=(i+1)modN
fit;
si
ocupada(t,i)ANDNOTigualClave(t,i,c)
oerror(Tablallena)
igualClave(t,i,c)
ot.parejas(i).valor:=v
vaca(t,i)
ot.parejas(i).clave:=c;
t.parejas(i).valor:=v;
t.tamao:=n+1
borrada(t,i)
ot.parejas(i).clave:=c;
t.parejas(i).valor:=v;
li:=(i0+n1)modN;%acotaelltimondiceenlarutadec
sii==li
entonces
seguir
sino
i:=(i+1)modN
iti/=liANDNOTvaca(t,i)ANDNOTigualClave(t,i,c)o
i:=(i+1)modN
fit;
siigualclave(t,i,c)
entonces
t.parejas(i).clave:=ClaveBorrada
sino
seguir
fsi
fsi
521
Tablas
fsi
{Q0:R(t)A(t)=TABLA[CLAVE,VALOR]Inserta(A(T),A(c),A(v))}
fproc
6.3.3
Mtodos de localizacin
Como se ha dicho ms atrs, una buena funcin de localizacin debe ser uniforme y fcil de
calcular eficientemente. En general, lo ms recomendable es calcular el resto mdulo el tamao N
de la tabla. Es decir:
Conviene que N sea un nmero primo mayor que 20. No conviene que N sea una potencia de
la base de numeracin B, pues esto tiende a hacer colisionar las claves cuyos cdigos numricos
coinciden en los dgitos de menor peso.
El cdigo de h(c) se puede hacer ms eficiente expresndolo como
h(c)=(num(c)mod2w)modN=((6i:0di<k:Biord(cki))mod2w)mod
N
siendo w el nmero de bits de una palabra mquina. As, el resto mod 2w puede ser ejecutado
muy rpidamente por la circuitera.
Se recomienda el valor B = 131 porque para este valor Bi tiene un ciclo mximo mod 2k para 8
d k d 64.
Mtodos de relocalizacin
Cada uno se caracteriza por una tcnica diferente para el clculo de la sucesin de pruebas.
Desde el punto de vista de la eficiencia, lo deseable es que un mtodo de relocalizacin desacople
lo ms posible las sucesiones de pruebas.
Relocalizacin lineal
En ingls Linear probing hashing.
La serie de pruebas es:
522
Tablas
prueba(m,c)=def(h(c)+m)modN
0dm<N
Se cumple:
i0=h(c)
im=(im1+1)modN
El primer ndice determina la serie de pruebas. Se dice por esto que hay agrupamientos primarios.
Hay una nica sucesin de pruebas, mdulo permutaciones cclicas.
En general, se dice que un mtodo de relocalizacin produce agrupamientos k-arios si el nmero de series de pruebas, mdulo permutaciones circulares es 4(Nk1); o equivalentemente, si cada
serie de pruebas est determinada por sus k primeros ndices.
En la relocalizacin lineal, el hecho de que los agrupamientos correspondientes a diferentes
claves pueden llegar a solaparse, junto con la presencia de posiciones borradas, degrada la eficiencia de los accesos.
Relocalizacin cuadrtica
En ingls Quadratic probing hashing.
La serie de pruebas es
prueba(m,c)=def(h(c)+m2)modN
0dm<N
Para un clculo ms eficiente, se aprovecha que los cuadrados se pueden obtener como suma
de impares consecutivos:
i0=h(c)
im=(im1+jm1)modN
j0=1
jm=jm1+2
prueba(2j,c)=(h(c)j )modN
1djd(N1)/2
1djd(N1)/2
N
3
7
11
19
23
31
k
10
14
31
62
125
254
N
43
59
127
251
503
1019
523
Tablas
En este mtodo se dice que hay agrupamientos secundarios, porque los dos primeros ndices de
una sucesin de pruebas la determinan. Hay 4(N) sucesiones de pruebas, mdulo permutaciones
circulares.
Relocalizacin doble
En ingls Double hashing.
Este mtodo usa una segunda funcin de localizacin k para calcular un incremento que, junto con el ndice primario, determina la sucesin de pruebas:
prueba(m,c)=(h(c)+mk(c))modN0dm<N
Se cumple:
i0=h(c)
im=(im1+d)modN
d=k(c)[1..N1]
Con una buena eleccin de h, k el comportamiento de este mtodo es muy similar en la prctica al que resultara del modelo terico conocido como relocalizacin uniforme (en ingls, uniform probing hashing). Este modelo presupone que la sucesin de pruebas es una permutacin aleatoria de
[0..N1], dependiente de la clave de acceso, con lo cual no se producen agrupamientos.
Una buena eleccin de N, h, k es:
Por ser N primo, N y d son primos entre s, y, por lo tanto, la serie de pruebas recorre toda la
tabla i.e., es una permutacin de [0..N1].
6.3.4
Eficiencia
Los resultados sobre eficiencia vienen expresados en trminos del tamao n (nmero de parejas en las tablas abiertas y nmero de posiciones usadas en las tablas cerradas) y la tasa de ocupacin D = n/N.
En el caso peor, una bsqueda o insercin puede requerir tiempo O(n), tanto en el caso de las
tablas abiertas como en el caso de las tablas cerradas.
Las evaluaciones experimentales, sin embargo, muestran para las tablas dispersas un rendimiento superior al de los rboles de bsqueda equilibrados.
Los anlisis probabilsticos permiten estimar la complejidad en promedio esperada para los
distintos modelos de tabla dispersa. Para realizar esta clase de anlisis se hacen los siguientes supuestos:
La funcin de localizacin se supone uniforme; es decir, para cada i [0..N1], la probabilidad de que una clave aleatoria c cumpla h(c) = i debe ser 1/N. (En ciertas aplicaciones,
por ejemplo en compiladores, no est claro si esta suposicin es realista).
524
Tablas
Se cumple entonces:
(a) Para tablas abiertas
Cn | 1 + D/2
Cn | D
1
1
1
2 1D
Cn |
1
1
1
2 1 D 2
Cn |
1
D
1D
Cn | 08
(b) Cn | 3
Cn | 13
(c) Cn | 20118
Cn | 5
Observamos que, para una tasa de ocupacin fijada, las tablas abiertas son ms eficientes que
las cerradas. Como contrapartida, las tablas abiertas consumen ms memoria.
En ambos tipos de tablas la eficiencia puede degradarse si la tasa de ocupacin aumenta en exceso. Algunas posibilidades para prevenir esta circunstancia son:
En las tablas cerradas, se puede incluir una operacin que limpie la tabla, vaciando las
posiciones borradas y reestructurando las ocupadas, de forma que todas las informaciones
con claves sinnimas queden en posiciones consecutivas de su serie de pruebas, a partir
de la posicin primaria que corresponda.
En ambos tipos de tablas, se puede incluir una operacin que duplique el tamao del vector. Para ello, es necesario cambiar la funcin de localizacin h a otra con rango doble del
anterior, y reorganizar el vector representante de la tabla.
Ambas operaciones son costosas, pero debido a la disminucin que producen en la tasa de
ocupacin, son operaciones beneficiosas desde el punto de vista de un anlisis amortizado del
tiempo requerido por una serie de operaciones de acceso a la misma tabla.
525
Tablas
6.4 Ejercicios
Tablas: Modelos matemticos y especificacin algebraica
344. Construye
una especificacin algebraica de un TAD TABLA[C :: EQ, V :: ANY] que represente el comportamiento de las tablas entendidas como funciones parciales que asignan valores a claves. La operacin:
consulta:(Tabla[Clave,Valor],Clave)oValor
ser parcial y estar definida solamente cuando la clave de consulta est en la tabla.
345. Construye
ahora ser total, y devolver un valor ficticio NoDef cuando la clave de consulta no est en la
tabla. Especifica tambin la clase de tipos EQ-ND como subclase de EQ, de manera que a los
tipos de esta clase se les exija disponer de un elemento distinguido NoDef.
Casos especiales: Conjuntos y vectores
346. El
comportamiento del TAD CJTO[E :: EQ] se puede considerar equivalente al del TAD
TABLA[E :: EQ, V :: EQ-ND], suponiendo que V sea un tipo especial que slo contenga
los dos valores Si y Nodef. Razona por qu, estudiando cmo se corresponden las operaciones disponibles de ambos TADs.
347. Diremos
que un tipo de datos es discreto si el tipo contiene una cantidad finita de valores y
es isomorfo a un intervalo [1..n] de los enteros, disponiendo de igualdad, orden y operaciones para determinar los valores primero y ltimo y los valores siguiente y anterior de un valor dado. Especifica la clase DIS de los tipos de datos discretos como subclase de la clase
de tipos ORD.
348. Construye
349. Para
526
Tablas
una representacin de las tablas basada en vectores que almacenen parejas de tipo
(Clave, Valor), realizando las operaciones generadoras y modificadores como procedimientos con un parmetro es de tipo Tabla[Clave, Valor]. Estudia la eficiencia que podra obtenerse para una implementacin del TAD TABLA basada en esta representacin,
considerando por separado los dos casos siguientes:
(a) El invariante de la representacin exige que el vector representante se mantenga ordenado en orden creciente de claves (suponiendo que el tipo de las claves sea de la
clase ORD).
(b) El invariante de la representacin permite que el vector representante se mantenga
desordenado.
351. Haz
352. Plantea
ahora una representacin de las tablas como rboles de bsqueda de tipo Arbus[Clave, Valor]. Cmo conviene definir la operacin ( ) de combinacin de valores,
que se usa en caso de insercin con una clave repetida? Qu eficiencia puede esperarse para las operaciones de una implementacin del TAD TABLA basada en esta representacin,
suponiendo que los rboles de bsqueda sean equilibrados?
353. Para
ciertas aplicaciones de las tablas resulta conveniente que exista un orden entre las claves, y que se disponga de una operacin que extraiga la informacin almacenada en la tabla
en forma de lista (o secuencia) de parejas de tipo (Clave, Valor), ordenada por claves. En
ocasiones, tambin resulta til que una insercin con clave ya presente en la tabla combine
el nuevo valor con el antiguo, en lugar de limitarse a reemplazar el valor antiguo por el
nuevo. Llamaremos tablas ordenadas a las tablas que incorporen estas dos ideas. Construye
una especificacin algebraica de las tablas ordenadas como TAD parametrizado, y desarrolla una implementacin basada en rboles de bsqueda. Qu diferencia de comportamiento hay entre este TAD y el TAD de los rboles de bsqueda?
implementar tablas dispersas que usen cadena de caracteres como claves, podra considerarse la siguiente funcin de localizacin:
h(c)=deford(ult(c))mod16
donde ord es la funcin que hace corresponder a cada carcter el nmero de orden que le corresponde, segn el cdigo ASCII. Calcula el ndice que h hace corresponder a cada una de las
cadenas de caracteres que siguen, y observa qu colisiones se producen:
Fred, Alex, Philip, Joe, John, Hanna, David,
Martin, Violet, George, Helen, Manyu, Roland
355. Una tabla dispersa abierta
527
Tablas
eficiencia que puede lograrse para las operaciones (realizando las generadoras y modificadoras como procedimientos con un parmetro es de tipo Tabla[Clave, Valor]).
356. Sea t : Tabla[Cadena, Nat]
Observa cmo se resuelven las colisiones y cul es en cada momento la tasa de ocupacin de
la tabla.
358. Define
un tipo representante adecuado para una implementacin del TAD TABLA que use
tablas dispersas cerradas. Formula el invariante de la representacin y la funcin de abstraccin.
359. Usando
528
Tablas
360. Usando
la representacin del ejercicio 358, programa una funcin auxiliar busca que satisfaga la siguiente especificacin pre/post:
funcbusca(t:Tabla[Clave,Valor];c:Clave)devi:Nat;
{P0:R(t)cnoesunaclaveficticia}
{Q0:(NOTest(A(t),c)i=N)
(est(A(t),c)t.parejas(i).clave==c)}
ffunc
Idea: Para buscar la clave c, se comienza con el ndice primario i0 = h(c) y se sigue la sucesin de
pruebas hasta encontrar la clave buscada o una posicin vaca. Las posiciones borradas no interrumpen la bsqueda. El invariante de la representacin garantiza que este algoritmo es correcto.
361. Usando
la funcin auxiliar del ejercicio anterior y la representacin del ejercicio 358, programa el procedimiento que implementa la operacin borra y la funcin que implementa la
operacin consulta.
362. Usando
364. Como
en el ejercicio 357, consideramos t : Tabla[Nat, Nat] implementada como tabla cerrada, pero ahora suponemos que N = 100 y que la funcin de localizacin est definida
como h(c) =def c mod 100. Estudia el efecto de la sucesin de llamadas.
TablaVaca(t);
Inserta(t,10,1010);Inserta(t,210,2110);
Inserta(t,110,1110);Inserta(t,310,3110);
x:=consulta(t,10);y:=consulta(t,310);z:=consulta(t,56);
Inserta(t,109,1109);
u:=consulta(t,9);
Inserta(t,9,9999);Inserta(t,410,4111);Inserta(t,12,1212);
Inserta(t,99,1099);Inserta(t,1999,1999);Inserta(t,0,0);
(a)
(b)
365. Disea
un procedimiento que limpie una tabla cerrada, de tal manera que las posiciones
borradas se eliminen y las restantes informaciones se reubiquen en la tabla. Todas las in-
529
Tablas
llaman vectores dispersos a los vectores implementados por medio de tablas dispersas. Esta
tcnica es recomendable cuando el conjunto total de ndices posibles es muy grande, y la
gran mayora de los ndices tiene asociado un valor por defecto (por ejemplo, cero). Disea
funciones que ejecuten el producto escalar y la suma de vectores dispersos de nmeros reales
con ndices enteros, suponiendo como valor por defecto el cero. Para ello debers suponer
disponible un mdulo que implemente los vectores dispersos por medio de tablas (con
NoDef = 0). Las operaciones exportadas por este mdulo sern las del TAD VECTOR, y la
representacin interna de los vectores no ser visible para los usuarios del mdulo.
367. Desarrolla
una implementacin del TAD POLI de los polinomios con coeficientes enteros
en una indeterminada (cfr. ejercicio 151), representando los polinomios como vectores dispersos de coeficientes, indexados por exponentes y con el valor por defecto cero. El planteamiento de la implementacin debe ser modular, importando los vectores dispersos de
un mdulo que los implemente.
368. Resuelve
369. Una
variante del problema de las concordancias consiste en pedir como resultado un listado de las k palabras que han aparecido ms frecuentemente en el texto, ordenadas en orden
decreciente de frecuencias de aparicin. Disea un algoritmo que resuelva este problema,
combinando las ideas de los ejercicios 368 y 338
370. Desarrolla
530
Grafos
CAPTULO 7
GRAFOS
Simples o multigrafos. Segn que se permita una o ms aristas entre dos vrtices.
Los grafos son objeto de estudio en la asignatura Matemtica discreta. Desde el punto de vista
matemtico, un grafo se puede modelar como una pareja formada por un conjunto de vrtices y
un conjunto de aristas:
G=(V,A)
AVuV
(Segn si el grafo est dirigido o no, A es una relacin o un conjunto de conjuntos de dos
elementos).
Otro posible modelo para los grafos consiste en representarlos como un aplicacin:
g:VuVoBool
g:VuVoW
paralosgrafosnovalorados
paralosgrafosvalorados
Grafo completo:
Arbol: NA = NV 1
Un camino es una sucesin de vrtices v0 , v1 , , vn tal que para 1 d i d n (vi1 , vi) es un arco (o una arista si el grafo no es dirigido). La longitud del camino es n (el nmero de arcos
o aristas).
Camino abierto: vn z v0
531
Grafos
Grafo conexo: grafo no dirigido tal que existe un camino entre cada 2 vrtices
Grafo fuertemente conexo: grafo dirigido tal que existe un camino entre cada 2 vrtices.
Especificacin algebraica
Los grafos se pueden especificar partiendo la idea de generarlos a partir del grafo vaco, usando operaciones que aadan vrtices y aristas, e incluyendo otras operaciones para decidir si dos
vrtices dados son adyacentes, calcular los vrtices vecinos de un vrtice dado, etc. Los detalles de
la especificacin cambian segn la clase de grafos que se quiera especificar.
En el caso ms general de los grafos valorados, necesitamos especificar los grafos con dos
parmetros: el tipo de los vrtices y el tipo de los valores de los arcos.
A los vrtices les exigimos que pertenezcan a la clase de los tipos discretos, una clase que ya
presentamos al hablar del TAD VECTOR, y que sirve como generalizacin de los tipos que se
pueden utilizar como ndices de los vectores.
claseDIS
hereda
ORD
operaciones
card:oNat
ord:ElemoNat
elem:NatoElem
prim,ult:oElem
suc,pred:ElemoElem
axiomas
x,y:Elem:i:Nat:
cardt1
1dord(x)dcard
defelem(i)si1didcard
ord(elem(i))=di
elem(ord(x))=x
prim=elem(1)
ult=elem(card)
defsuc(x)six/=ult
suc(x)
=delem(ord(x)1)
x==y
=ord(x)==ord(y)
xdy
fclase
=ord(x)dord(y)
532
Grafos
La razn de exigir esta condicin a los vrtices de los grafos est en que algunos algoritmos
sobre grafos devuelven vectores bidimensionales indexados por vrtices. Para que tuviese ms
sentido esta restriccin, se deberan incluir dichas operaciones en la especificacin del TAD.
En cuanto a las etiquetas de los arcos, les exigimos que pertenezcan a un tipo ordenado, que
exporte una operacin de suma, y que incluya el valor NoDef, que renombramos a f, y el valor 0.
La razn de estas restricciones est otra vez en los algoritmos que queremos implementar sobre
los grafos, donde necesitaremos sumar valores, comparar valores, y tener disponible un valor
mnimo y un valor mximo. En la especificacin de los grafos que presentamos a continuacin
slo aparece el valor mximo, f, para conseguir que la operacin que consulta sobre el coste de
una arista sea una operacin total, que de f como coste de una arista que no est en el grafo.
claseVALORD
hereda
EQND,ORD
renombra
ElemaValor
Nodefaf
operaciones
0:oValor
(+):(Valor,Valor)oValor
axiomas
x,y,z:Valor:
0dx
xdf
x+y
=y+x
(x+y)+z=x+(y+z)
x+0
=0
x+f
fclase
=f
Incluimos operaciones para: construir un grado vaco, Vaco; aadir una arista, indicando los
vrtices que une, y su coste, PonArista; quitar la arista que une a dos vrtices dados, quitaArista;
obtener el coste de la arista que une a dos vrtices dados, costeArista; consultar si existe una arista
que une a dos vrtices dados, hayArista?; obtener los vrtices sucesores de uno dado, sucesores; y
obtener los vrtices predecesores de uno dado, predecesores. No prohibimos que haya bucles, con
origen y destino en el mismo vrtice. Ntese que no incluimos ninguna operacin para insertar
un vrtice, por lo que no puede existir ningn grafo componente conexa con un solo vrtice, a
no ser que tenga un bucle. Especificamos PonArista de forma que no pueda haber dos aristas
entre los mismos vrtices; en la especificacin de los multigrafos habra que suprimir esta
restriccin.
Finalmente, la especificacin de las grafos dirigidos valorados:
tadWDGRAFO[V::DIS,W::VALORD]
renombra
V.ElemaVrtice
533
Grafos
usa
BOOL,SEC[PAREJA[V,W]]
usaprivadamente
CJTO[PAREJA[V,W]]
tipo
DGrafo[Vrtice,Valor]
operaciones
Vaco:oDGrafo[Vrtice,Valor]
PonArista:(DGrafo[Vrtice,Valor],Vrtice,Vrtice,Valor)
/*gen*/
oDGrafo[Vrtice,Valor]
quitaArista:(DGrafo[Vrtice,Valor],Vrtice,Vrtice)
/*gen*/
oDGrafo[Vrtice,Valor]
/*mod*/
costeArista:(DGrafo[Vrtice,Valor],Vrtice,Vrtice)oValor/*obs*/
hayArista?:(DGrafo[Vrtice,Valor],Vrtice,Vrtice)oBool /*obs*/
sucesores:(DGrafo[Vrtice,Valor],Vrtice)
oSec[Pareja[Vrtice,Valor]]
predecesores:(DGrafo[Vrtice,Valor],Vrtice)
/*obs*/
oSec[Pareja[Vrtice,Valor]]
operacionesprivadas
cjtoSuc:(DGrafo[Vrtice,Valor],Vrtice)
/*obs*/
oCjto[Pareja[Vrtice,Valor]]
cjtoPred:(DGrafo[Vrtice,Valor],Vrtice)
/*obs*/
oCjto[Pareja[Vrtice,Valor]]
enumera:Cjto[Pareja[Vrtice,Valor]]
/*obs*/
oSec[Pareja[Vrtice,Valor]]
/*obs*/
insertaOrd:(Pareja[Vrtice,Valor],Sec[Pareja[Vrtice,Valor]]
oSec[Pareja[Vrtice,Valor]]
ecuaciones
/*obs*/
g:DGrafo[Vrtice,Valor]:u,u1,u2,v,v1,v2:Vrtice:
c,c1,c2:Valor:ps:Sec[Pareja[Vrtice,Valor]]:
xs:Cjto[Pareja[Vrtice,Valor]]:
PonArista(PonArista(g,u1,v1,c1),u2,v2,c2)=PonArista(g,u2,v2,c2
)
siu1==u2ANDv1==v2
PonArista(PonArista(g,u1,v1,c1),u2,v2,c2)
=PonArista(PonArista(g,u2,v2,c2),u1,v1,c1)
siNOT(u1==u2ANDv1==v2)
quitaArista(Vaco,u,v)
quitaArista(PonArista(g,u1,v1,c),u2,v2)
siu1==u2ANDv1==v2
=Vaco
=quitaArista(g,u2,v2)
quitaArista(PonArista(g,u1,v1,c),u2,v2)
=PonArista(quitaArista(g,u2,v2),u1,v1,c)
siNOT(u1==u2ANDv1==v2)
costeArista(Vaco,u,v)
=f
534
Grafos
costeArista(PonArista(g,u1,v1,c),u2,v2)
siu1==u2ANDv1==v2
=c
costeArista(PonArista(g,u1,v1,c),u2,v2)
siNOT(u1==u2ANDv1==v2)
=costeArista(g,u2,v2)
hayArista?(g,u,v)
f
=costeArista(g,u,v)/=
=enumera(cjtoSuc(g,u))
=enumera(cjtoPred(g,v))
sucesores(g,u)
predecesores(g,v)
%enumeradevuelveunasecuenciaconparteizquierdavaca
enumera(CJTO.Vaco)
=SEC.Crea
enumera(Pon(Par(v,c)),xs))
=insertaOrd(Par(v,c),enumera(quita(Par(v,c),xs)))
%insertaOrddevuelveunasecuenciaconparteizquierdavaca
insertaOrd(Par(v,c),ps)
=reinicia(inserta(Par(v,c),ps))
sifin?(ps)
insertaOrd(Par(v,c),ps)
=reinicia(inserta(Par(v,c),ps))
siNOTfin?(ps)ANDactual(ps)=Par(v1,c1)ANDv<v1
insertaOrd(Par(v,c),ps)
=insertaOrd(Par(v,c),avanza(ps))
siNOTfin?(ps)ANDactual(ps)=Par(v1,c1)ANDvtv1
cjtoSuc(Vaco,u)
=CJTO.Vaco
cjtoSuc(PonArista(g,u1,v,c),u)
=Pon(Par(v,c),cjtoSuc(quitaArista(g,u,v),u))
siu==u1
cjtoSuc(PonArista(g,u1,v,c),u)
cjtoSuc(g,u)
siu/=u1
cjtoPred(Vaco,v)
=CJTO.Vaco
=
cjtoPred(PonArista(g,u,v1,c),v)
=Pon(Par(u,c),cjtoPred(quitaArista(g,u,v),v))
siv==v1
cjtoPred(PonArista(g,u,v1,c),v)
siv/=v1
ftad
=cjtoPred(g,v)
535
Grafos
1
2
5
2
3
4
2
D
B
C
f
2
B
1
C
5
D
2
f
4
(B, 1)
(D, 3)
(A, 2)
(B, 4)
(C, 5)
(D, 2)
(D, 2)
536
Grafos
(A, B, 1)
(A, C, 5)
(B, D, 3)
B
C
(A, D, 2)
(C, A, 2)
(C, D, 2)
(D, B, 4)
En este caso no se puede realizar una implementacin modular que importe las listas de otro
mdulo; hay que construir directamente un tipo representante usando registros y punteros.
Variantes
Grafos no dirigidos.
En las listas de adyacencia puede ahorrarse espacio suponiendo un orden entre vrtices y poniendo v en la lista de u slo si u < v con lo que nos ahorramos representar
dos veces la misma arista.
Grafos no valorados.
Multigrafos.
Las listas de adyacencia deben ser listas de ternas (identificador, vrtice, valor), donde el
identificador identifica unvocamente al arco.
NV:
nmero de vrtices
NA:
nmero de aristas
GE:
537
Grafos
GS:
Matriz
O(NV2) (1)
O(1)
O(1)
O(1)
O(1)
O(NV) (1)
O(NV) (1)
Lista
O(NV)
O(GS) (2)
O(GS) (2)
O(GS) (2)
O(GS) (2)
O(GS) (2) (C)
O(NV+NA) (3)
Multilista
O(NV)
O(GS+GE) (4)
O(GS+GE) (4)
O(GS) (2)
O(GS) (2)
O(GS) (2) (C)
O(GE) (5) (C)
538
Grafos
%g(u,v):=f
Hay un error porque el sucesor del ltimo no est definido, y eso es lo que intentamos obtener
en la ltima iteracin. Se resolvera utilizando un bucle repeat .. until o incluyendo la obtencin del
sucesor dentro de una condicin que protegiera la situacin conflictiva.
Quitar una arista:
procquitaArista(esg:DGrafo[Vrtice,Valor];eu,v:Vrtice)
inicio
VECTOR.Asigna(g,u,v,VALORD.infi()); %g(u,v):=f
fproc
539
Grafos
u:=DIS.prim();
itDIS.menorIgual(u,DIS.ult())o
w:=VECTOR.valor(g,u,v);
%w:=g(u,v)
siVALORD.igual(w,VALORD.infi())
entonces
seguir
sino
SEC.inserta(ps,PAREJA.Par(u,w))
fsi;
u:=DIS.suc(u)
fit;
SEC.reinicia(ps);
devps
ffunc;
Invariante de la representacin
Dado g : DGrafo[Vrtice, Valor]
R(g)
def
u:Vrtice:lasparejasdeg(u)aparecenordenadasenorden
creciente
conrespectoalacomponenteVrtice
Mantenemos las secuencias de sucesores ordenadas, para mejorar la eficiencia de las operaciones. Para escribir formalmente este aserto es necesario definir un cuantificador sobre el dominio
de los tipos de la clase DIS.
540
Grafos
Algunas operaciones
Insercin de una arista:
procPonArista(esg:DGrafo[Vrtice,Valor];eu,v:Vrtice;ew:Valor
);
var
xs:Sec[Pareja[Vrtice,Valor]];
encontrado:Bool;
inicio
xs:=VECTOR.valor(g,u);
%xs:=g(u);
{piz(xs)=[]}
busca(xs,v,encontrado); %proc.auxiliardebsquedaensecuencia
siencontrado
entonces
SEC.borra(xs)
sino
seguir
fsi;
SEC.inserta(xs,PAREJA.Par(v,w));
SEC.reinicia(xs);
VECTOR.asigna(g,u,xs)
%g(u):=xs
fproc
541
Grafos
fsi;
devw
ffunc
Estamos suponiendo que las secuencias se implementan con un nodo cabecera, un registro
representado con memoria esttica, y que por lo tanto los cambios en xs no afectan a g(u). Si lo
que tuvisemos fuese un puntero al nodo cabecera, entonces deberamos reiniciar xs.
La funcin que obtiene los predecesores de un vrtice
funcpredecesores(g:DGrafo[Vrtice,Valor];v:Vrtice)
devps:Sec[Pareja[Vrtice,Valor]]
var
u:Vrtice;
w:Valor;
xs:Sec[Pareja[Vrtice,Valor]];
inicio
SEC.Crea(ps);
u:=DIS.prim();
itDIS.menorIgual(u,DIS.ult())o
xs:=VECTOR.valor(g,u);
%xs:=g(u)
busca(xs,v,encontrado);
siencontrado
entonces
w:=PAREJA.sg(SEC.actual(xs));
SEC.inserta(ps,PAREJA.Par(u,w))
sino
seguir
fsi;
u:=DIS.suc(u)
fit;
SEC.reinicia(ps);
devps
ffunc;
542
Grafos
Los grafos dirigidos acclicos admiten un tercer tipo de recorrido, denominado recorrido de ordenacin topolgica.
Vamos a hacer implementaciones modulares de las operaciones, sin acceder a la representacin interna de los grafos.
7.3.1
Recorrido en profundidad
Se puede considerar como una generalizacin del recorrido en preorden de un rbol. La idea
es:
En otro caso, retroceder al vrtice visitado anteriormente, e intentar continuar el recorrido desde ste.
Una vez visitados todos los descendientes del vrtice inicial, se quedan vrtices no visitados
hay que iniciar un recorrido de otra componente del grafo.
Por ejemplo, el recorrido del grafo:
B
A
D
E
F
H
A
B
1
C
2
I 8
D
3
5 F
E 4
6 H
J 9
G 7
K 10
L 11
543
Grafos
Devuelve una secuencia en la cual aparece cada vrtice del grafo una sola vez.
Usa un conjunto de vrtices para llevar cuenta de los vrtices visitados. Las operaciones
de CJTO necesarias son O(1), lo que se puede conseguir con una implementacin basada
en tablas dispersas (un vector de booleanos).
Usa un procedimiento auxiliar privado encargado del recorrido de una sola componente.
funcrecorreProf(g:DGrafo[Vrtice])devxs:Sec[Vrtice];
{P0:cierto}
var
v:Vrtice;
vs:Cjto[Vrtice];
inicio
CJTO.Vaco(vs);
SEC.Crea(xs);
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o
siCJTO.pertenece(v,vs)
entonces
seguir
sino
recorreProfComp(g,v,vs,xs)
fsi;
v:=DIS.suc(v)
fit
{Q0:cont(xs)representaunrecorridoenprofundidaddeg}
devxs
ffunc
544
Grafos
ss:=DGRAFO.sucesores(g,v);
itNOTSEC.fin?(ss)o
u:=SEC.actual(ss);
SEC.avanza(ss);
siCJTO.pertenece(u,vs)
entonces
seguir
sino
recorreProfComp(g,u,vs,xs)
fsi
fit
{Q0:SEC.fin?(xs)=cierto
cont(xs)escont(XS)prolongadoconelresultadodeunrecorrido
enprofundidaddelosvrticesdegaccesiblesdesdevqueno
estabanenVSvscontienelosvrticesdexs}
fproc
7.3.2
Recorrido en anchura
El recorrido en anchura, o por niveles, generaliza el recorrido de rboles con igual denominacin. La idea es:
Si el ltimo vrtice visitado tiene sucesores an no visitados, realizar sucesivamente un recorrido desde cada uno de estos. (Esta descripcin informal no describe correctamente el
recorrido en anchura).
En otro caso, continuar con un recorrido iniciado en cualquier vrtice no visitado an.
El recorrido en anchura encuentra caminos de longitud mnima. Por este motivo, es tpico
aplicarlo a problemas de planificacin, donde:
545
Grafos
E
F
H
C
2
D
3
8
I
E
G
6
H
7
J
9
K 10
L 11
La implementacin es similar a la del recorrido en profundidad, con ayuda de un procedimiento auxiliar que recorre una componente del grafo. La diferencia est en que en lugar de utilizar un
procedimiento recursivo, lo hacemos iterativo con ayuda de una cola a la manera del recorrido
por niveles de un rbol. si cambisemos esa cola por una pila tendramos la versin iterativa del
recorrido en profundidad.
funcrecorreNiv(g:DGrafo[Vrtice])devxs:Sec[Vrtice];
{P0:cierto}
var
v:Vrtice;
vs:Cjto[Vrtice];
inicio
CJTO.Vaco(vs);
SEC.Crea(xs);
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o
siCJTO.pertenece(v,vs)
entonces
seguir
546
Grafos
sino
recorreNivComp(g,v,vs,xs)
fsi;
v:=DIS.suc(v)
fit
{Q0:cont(xs)representaunrecorridopornivelesdeg}
devxs
ffunc
547
Grafos
fproc
7.3.3
B
C
F
D
H
I
J
K
xs : [ A, B, C, D, E, F, G, H, I, J, K, L]
xs : [ A, B, D, F, H, J, C, E, G, I, L, K]
La idea del algoritmo es reiterar la eleccin de un vrtice an no visitado y tal que todos sus
predecesores hayan sido ya visitados. El problema es que el algoritmo resultante de seguir directamente esta idea es poco eficiente.
Una forma de lograr un algoritmo ms eficiente es:
Mantener un vector P indexado por vrtices, tal que P(v) es el nmero de predecesores de
v an no visitados.
actualiza P y M
Para la implementacin de esta idea necesitamos suponer al mdulo que implementa a los
conjuntos equipado con una operacin que permite extraer un elemento cualquiera del conjunto:
548
Grafos
funcelige(m:Cjto[Vrtice])devu:Vrtice;
{P0:NOTesVaco(m)}
{Q0:pertenece(u,m)}
ffunc
procordenaTop(eg:DGrafo[Vrtice];sxs:Sec[Vrtice]);
{P0:gesacclico}
var
u,v:Vrtice;
p:Vector[Vrtice,Nat];
m:Cjto[Vrtice];
ss:Sec[Vrtice];
inicio
%inicializacindepym,primerafaseO(NV)
CJTO.Vaco(m);
VECTOR.Crea(p);
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o
VECTOR.Asigna(p,v,0);
%p(v):=0
CJTO.Pon(v,m);
v:=DIS.suc(v)
fit;
%inicializacindepym,segundafase,O(NV+NA)oO(NV2)
u:=DIS.prim();
itDIS.menorIgual(u,DIS.ult())o
ss:=DGRAFO.sucesores(g,u);
itNOTSEC.fin?(ss)o
v:=SEC.actual(ss);
SEC.avanza(ss);
VECTOR.Asigna(p,v,VECTOR.valor(p,v)+1);%p(v):=p(v)+1
CJTO.quita(v,m)
fit
fit;
SEC.Crea(xs);
%bucleprincipal,O(NV+NA)oO(NV2)
itNOTCJTO.esVaco(m)o
u:=CJTO.elige(m);
CJTO.quita(u,m);
SEC.inserta(u,xs);
ss:=DGRAFO.sucesores(u);
itNOTSEC.fin?(ss)o
v:=SEC.actual(ss);
SEC.avanza(ss);
VECTOR.Asigna(p,v,VECTOR.valor(p,v)1);%p(v):=p(v)1
siVECTOR.valor(p,v)==0
entonces
CJTO.Pon(v,m)
sino
seguir
fsi
549
Grafos
fit
fit
{Q0:cont(xs)representaunrecorridodeordenacintopolgicadeg}
fproc
Este algoritmo se puede modificar para detectar si el grafo es acclico o no, en lugar de exigir
aciclicidad en la precondicin. Si el conjunto M es queda vaco antes de haber visitado NV vrtices, el grafo no es acclico.
7.4.1
Dado un vrtice u de un grafo dirigido valorado g, nos interesa calcular todos los caminos
mnimos con origen u y sus correspondientes costes.
Para resolver este problema vamos a utilizar el algoritmo de Dijkstra (1959). El algoritmo mantiene un conjunto M de vrtices v para los cuales ya se ha encontrado el camino mnimo desde u.
La idea del algoritmo es:
Se entra en un bucle. En cada vuelta se elige un vrtice w que no est todava en M y cuyo
coste estimado sea mnimo. Se aade w a M, y se actualizan los costes estimados de los
restantes vrtices v que no estn en M, haciendo C(v) := min( C(v), C(w) + costeArista(G, w,
v) ).
Al terminar, se han encontrado caminos de coste mnimo C(v) desde u hasta los restantes
vrtices v.
Un par de comentarios sobre la implementacin del algoritmo:
Si en algn momento llega a cumplirse que para todos los vrtices v que no estn en M
C(v) = f, el algoritmo puede terminar.
Adems de calcular C, calculamos otro vector p, indexado por los ordinales de los vrtices, que representa los caminos mnimos desde u a los restantes vrtices, segn el siguiente criterio:
550
Grafos
Como ejemplo del funcionamiento del algoritmo, vamos a ejecutarlo para el grafo dirigido de
la figura siguiente, tomando como vrtice inicial u = 1.
1
30
50
2
40
100
40
70
30
10
3
10
5
4
20
It.
0
M
{1}
1
2
3
4
5
{1, 2}
{1, 2, 5}
{1, 2, 5, 4}
{1, 2, 5, 4, 3}
{1, 2, 5, 4, 3, 6}
2
5
4
3
6
40/1
40/1
40/1
40/1
40/1
100/1
100/1
100/1
90/3
90/3
De aqu se deduce, por ejemplo, que un camino mnimo de 1 a 6, con coste c(v) = 90, es [1, 4,
3, 6].
Este es un ejemplo tpico de algoritmo voraz, porque sigue la estrategia de construir la solucin
haciendo en cada momento lo que parece localmente ptimo (eleccin de w en cada vuelta del bucle
principal) sin reconsiderar nunca las decisiones tomadas.
En los apuntes de Mario se demuestra la correccin de este algoritmo.
procDijkstra(eg:DGrafo[Vrtice,Valor];eu:Vrtice;
sc:Vector[Vrtice,Valor];
sp:Vector[1..DIS.card()]de[0..DIS.card()]);
{P0:cierto}
var
m:Cjto[Vrtice];
%Vrticesparalosqueyatenemosel
caminomnimo
n:Nat;
%cardinaldem
v,w:Vrtice;
fin:Bool;
minCoste,nuevoCoste:Valor;
inicio
%inicializacindem,n,c,pyfin
CJTO.Vaco(m);
CJTO.Pon(u,m);
n:=1;
Grafos
551
v:=DIS.prim();
itmenorIgual(v,DIS.ult())o
%O(NV)conMA;O(NV+NA)
conVLA
VECTOR.Asigna(c,v,WDGRAFO.costeArista(g,u,v));
siVALORD.igual(VECTOR.valor(c,v),VALORD.infi())
entonces
p(DIS.ord(v)):=0
%vnotienepredecesor
sino
p(DIS.ord(v)):=DIS.ord(u)
fsi;
v:=DIS.suc(v);
fit;
VECTOR.asigna(c,u,0);
p(DIS.ord(u)):=DIS.ord(u);
fin:=falso;
%bucleprincipal,O(NV2)
itn<DIS.card()ANDNOTfino
%O(NV)iteraciones
%elegimoswmtalquec(w)seamnimo
minCoste:=VALORD.infi();
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o%O(NV)
siNOTCJTO.pertenece(v,m)ANDVALORD.menor(VECTOR.valor(c,v),
minCoste)
entonces
minCoste:=VECTOR.valor(c,v);
w:=v
sino
seguir
fsi;
v:=DIS.suc(v);
fit;
siVALORD.igual(minCoste,VALORD.infi())
entonces
%Noquedanmsvrticesaccesiblesdesde
u.Terminamos
fin:=cierto
sino
%aadimoswam;actualizamoscyp
CJTO.Pon(w,m);n:=n+1;
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o %O(NV)conMA
siNOTCJTO.pertenece(v,m)
entonces
nuevoCoste:=VALORD.suma(VECTOR.valor(c,w),
WDGRAFO.costeArista(g,w,v));
si
entonces
VECTOR.Asigna(c,v,nuevoCoste);
p(DIS.ord(v)):=DIS.ord(w)
sino
seguir
fsi
sino
seguir
fsi;
v:=DIS.suc(v)
fit
fsi
fit
{Q0:v:Vrtice:c(v)eselcostedeuncaminomnimodeuav
552
Grafos
p(ord(v))indicaelnmerodeordendelvrticepredecesor
inmediatodevendichocamino}
fproc
Al definir el tipo del vector donde almacenamos los caminos Vector [1..DIS.card( )] de
[0..DIS.card( )] estamos usando la informacin que aparece en la especificacin de la clase DIS,
donde se dice que los ordinales de los elementos estn en el intervalo [1..card].
Complejidad del algoritmo de Dijkstra
La realizacin anterior est pensada para una representacin de los grafos con matrices de adyacencia. El coste de las diferentes etapas:
Incializacin de c y p: O(NV). NV iteraciones donde cada iteracin slo involucra operaciones con coste constante. Esto es cierto si los grafos se implementan con matrices de adyacencia, donde efectivamente costeArista es O(1).
La seleccin de w se realiza con un bucle de coste O(NV). Siempre y cuando utilicemos una implementacin de los conjuntos donde pertenece sea O(1).
Si se utilizan grafos representados con listas de adyacencia, es necesario realizar algunos cambios en el algoritmo para obtener esta misma complejidad.. En lugar de recorrer todo el dominio
de los vrtices, consultado el coste de cada posible arista, se recorren exclusivamente los sucesores del vrtice considerado en cada caso. La razn es que el coste de la operacin costeArista es
O(1) en matrices de adyacencia y O(NV) en listas de adyacencia. Por lo tanto los cambios son:
Para cada pareja (c, v) perteneciente a sucesores(g, u) se hace: c(v) := c; p(v) := u. O(GS)
O(NV).
En algunos puntos del anlisis con listas de adyacencia hemos utilizado O(GS), indicando luego
que este valor est acotado por O(NV). Sin embargo, tambin se cumple que O(GS) O(NA), y
esto nos hace pensar que aunque en el caso general NA es O(NV2), si tenemos un grafo disperso
donde se cumple que NA << NV2, podemos obtener mejores resultados utilizando listas de adyacencia. Siguiendo esta idea se pueden realizar una optimizacin del algoritmo de Djikstra que
consiste en sustituir el conjunto m por una cola de prioridad formada por parejas (c, v), donde v es
553
Grafos
un vrtice y c = c(v). Para conseguir la eficiencia deseada, hay que implementar la cola de prioridad
por medio de un montculo, y dotarla de operaciones algo diferentes a las operaciones habituales
de las colas de prioridad. En [Fra94] pp. 323-325 se dan ms detalles. Se consigue complejidad
O((NV+NA) log NV), que es mejor que O(NV2) para grafos dispersos.
En cuanto al espacio, todas las versiones del algoritmo de Dijkstra que hemos estudiado requieren espacio adicional O(NV) para el conjunto de vrtices m, adems del espacio ocupado por
el propio grafo.
7.4.2
El problema es, dado un grafo dirigido valorado g, se quieren calcular todos los caminos
mnimos entre todas las parejas de vrtices de g, junto con sus costes.
Aplicando reiteradamente el algoritmo de Dijkstra, se obtiene una solucin cuya complejidad
depende de la variante del algoritmo de Dijkstra que se haya utilizado: O(NV3) o bien O(NV NA
log NV).
La otra posibilidad es utilizar el algoritmo de Floyd (1962). Este algoritmo tiene la ventaja de ser
ms elegante y compacto. Adems slo necesita espacio adicional O(1) (aparte del ocupado por el
grafo y la solucin calculada), mientras que cualquier versin del algoritmo de Dijkstra necesita
espacio auxiliar O(NV).
Mientras que el algoritmo de Dijkstra es voraz, el de Floyd es dinmico. Esto quiere decir que las
iteraciones realizadas por el algoritmo se van aproximando a la solucin, pero ninguna parte de
sta es definitiva hasta que no se termina.
La idea bsica del algoritmo consiste en mejorar la estimacin c(u, v) del coste de un camino
mnimo de u a v mediante la asignacin:
c(v,w):=min(c(v,w),c(v,u)+c(u,w))
Diremos que el vrtice u acta como pivote en esta actualizacin de c(v, w). El algoritmo comienza con una inicializacin natural de c, y a continuacin reitera la actualizacin de c(v, w) para
todas las parejas de vrtices (v, w) y con todos los pivotes u.
Al igual que en el algoritmo de Dijkstra, construimos un resultado adicional que representa los
caminos mnimos entre cada par de vrtices. Este resultado viene representado por un vector
bidimensional s indexado por parejas de ordinales de los vrtices, segn el siguiente criterio:
s(ord(v), ord(w)) = ord(u) si w es accesible desde v y u es el sucesor inmediato de v en el camino mnimo de v a w calculado por el algoritmo.
554
Grafos
C
0:
Estado inicial
Matriz de costes c
Matriz de sucesores s
B
3
C
9
A
0
f
0
f
7
1
2
3
4
5
1
1
0
0
0
0
2
2
2
0
0
0
3
3
3
3
0
0
4
0
0
0
4
0
5
0
0
0
5
5
1:
2:
Matriz de sucesores s
B
3
C
7
A
0
f
0
f
7
1
2
3
4
5
1
1
0
0
0
0
2
2
2
0
0
0
3
2
3
3
0
0
4
0
0
0
4
0
5
0
0
0
5
5
Grafos
555
procFloyd(eg:DGrafo[Vrtice,Valor];
sc:Vector[Vrtice,Vrtice,Valor];
ss:Vector[1..DIS.card(),1..DIS.card()]de[0..DIS.card()]
);
{P0:cierto}
var
u,v,w:Vrtice;
c:Valor;
inicio
%inicializacindecys.O(NV2)oO(NV+NA)
v:=DIS.prim();
itmenorIgual(v,DIS.ult())o
w:=DIS.prim();
itmenorIgual(w,DIS.ult())o
VECTOR.Asigna(c,v,w,WDGRAFO.costeArista(g,v,w));
siVALORD.igual(VECTOR.valor(c,v,w),VALORD.infi())
entonces
s(DIS.ord(v),DIS.ord(w)):=0
sino
s(DIS.ord(v),DIS.ord(w)):=DIS.ord(w)
fsi;
VECTOR.Asigna(c,v,v,VALORD.Cero());
s(DIS.ord(v),DIS.ord(v)):=DIS.ord(v)
w:=DIS.suc(w)
fit;
v:=DIS.suc(v);
fit;
%bucleprincipal,O(NV3)
u:=DIS.prim();
itmenorIgual(u,DIS.ult())o
%actualizamoscydusandoucomopivote
v:=DIS.prim();
itmenorIgual(v,DIS.ult())o
w:=DIS.prim();
itmenorIgual(w,DIS.ult())o
c:=VALORD.suma(VECTOR.valor(c,v,u),VECTOR.valor(c,u,w));
siVALORD.menor(c,VECTOR.valor(c,v,w))
entonces
%podemosmejorarelcaminopasandoporu
VECTOR.Asigna(c,v,w,c);
s(DIS.ord(v),DIS.ord(w)):=s(DIS.ord(v),DIS.ord(u))
sino
seguir
fsi;
w:=DIS.suc(w)
fit;
v:=DIS.suc(v);
fit;
u:=DIS.suc(u)
fit
{Q0:v,w:Vrtice:
c(v,w)eselcostedeuncaminomnimodevaw
s(ord(v),ord(w))indicaelnmerodeordendelvrticesuecsor
inmediatodevendichocamino}
fproc
La complejidad del algoritmo de Floyd es claramente O(NV3) si el grafo est representado como matriz de adyacencia. En el caso de que g est representado como vector de listas de adyacen-
Grafos
556
cia, conviene modificar el bucle doble de inicializacin de c y s, cambiando el bucle interno que
recorre todos los vrtices w por un recorrido de los sucesores del vrtice v. As, la inicializacin
requiere tiempo O(NV+NA), y el bucle principal sigue consumiendo tiempo O(NV3).
557
Ejercicios
7.5 Ejercicios
Grafos: Modelos matemticos y especificacin algebraica
371. Especifica un TAD WDGRAFO[V :: DIS, W :: VAL-ORD] que represente el comporta-
miento de los grafos dirigidos valorados con vrtices tomados del tipo discreto V y arcos
etiquetados por valores tomados del tipo con valores ordenados W. Especifica convenientemente la clase de tipos VAL-ORD, e incluye en WDGRAFO operaciones para crear un
grafo vaco (sin aristas), poner y quitar aristas, consultar el coste asociado a una arista, recorrer los sucesores inmediatos de un vrtice, y recorrer los predecesores inmediatos de un
vrtice.
372. Modifica la especificacin del ejercicio anterior para obtener TADs que describan el com-
373. Especifica un TAD REL[X, Y :: DIS] que describa el comportamiento de las relaciones
binarias entre datos de tipo X.Elem y datos de tipo Y.Elem (siendo X, Y tipos discretos).
REL deber estar equipado con un tipo Rel[X.Elem, Y.Elem] y operaciones con los perfiles
siguientes:
Vaca:oRel[X.Elem,Y.Elem]
Inserta:(Rel[X.Elem,Y.Elem],X.Elem,Y.Elem)oRel[X.Elem,Y.Elem]
borra:(Rel[X.Elem,Y.Elem],X.Elem,Y.Elem)oRel[X.Elem,Y.Elem]
est?:(Rel[X.Elem,Y.Elem],X.Elem,Y.Elem)oBool
fila:(Rel[X.Elem,Y.Elem],X.Elem)oSec[Y.Elem]
columna:(Rel[X.Elem,Y.Elem],Y.Elem)oSec[X.Elem]
558
Ejercicios
Compara el TAD WDGRAFO[V :: DIS, W :: VAL-ORD] del ejercicio 371 con los ejemplares WREL[V :: DIS, V :: DIS, W :: VAL-ORD] de WREL.
Grafos: Tcnicas de implementacin
375. Dibuja la matriz de adyacencia y el vector de listas/secuencias de adyacencia que represen-
1
2
5
2
3
4
2
D
376. Plantea dos implementaciones del TAD de los grafos dirigidos valorados, usando dos re-
presentaciones alternativas:
(a) Matriz de adyacencia indexada por pares de vrtices.
(b) Vector de listas/secuencias de adyacencia, indexado por vrtices.
Compara la eficiencia de las dos implementaciones, considerando los tiempos de ejecucin de
las operaciones y el espacio ocupado por la representacin.
377. Dibuja la representacin del grafo del ejercicio 375 como vector de multilistas de adyacen-
cia. Plantea una implementacin del TAD de los grafos dirigidos valorados basada en esta
representacin, y analiza su eficiencia.
Sugerencia: Consulta el texto de Xavier Franch, seccin 6.2.2, apartado c).
378. Estudia cmo modificar las tcnicas de implementacin del ejercicio 376 para adaptarlas a
las otras variedades de grafos del ejercicio 372, as como a las relaciones de los ejercicios
382 y 383.
379. Especifica un TAD que describa las matrices simtricas. Disea una implementacin basada
en la representacin de una matriz simtrica NuN como vector, evitando representar dos
veces los elementos que ocupen posiciones simtricas en la matriz. Plantea una implementacin optimizada de los grafos no dirigidos (valorados o no) usando matrices de adyacencia simtricas como tipo representante.
380. En un grafo completo (i.e., grafo que posea todos los arcos posibles entre sus vrtices) el
nmero NA de arcos es del orden NV2, siendo NV el nmero de vrtices. Un grafo se llama
disperso si NA es significativamente menor que NV2. Estudia implementaciones de los grafos
basadas en una representacin de la matriz de adyacencia como matriz dispersa (cfr. ejercicio
366). Obviamente, esta tcnica de implementacin es recomendable para los grafos dispersos con un conjunto de vrtices de cardinal grande.
559
Ejercicios
Recorridos en grafos
381. Para un grafo dirigido, el recorrido en profundidad y el recorrido por niveles se efectan de manera
anloga al caso de los rboles, pero cuidando de que cada vrtice se visite una sola vez aunque haya ciclos en el grafo. Para ello, se usa un conjunto de vrtices auxiliar que contiene
los vrtices ya visitados. Dibuja en forma de bosques los resultados de recorrer en profundidad y por niveles el grafo dirigido de la figura siguiente, indicando el orden de visita de
los vrtices.
B
A
D
E
F
H
382. Disea un algoritmo de recorrido en profundidad para grafos dirigidos no valorados, cons-
truido como funcin recursiva que devuelva el recorrido en forma de secuencia de vrtices.
Usa un conjunto de vrtices auxiliar para llevar cuenta de los vrtices ya visitados. analiza la
complejidad del algoritmo en funcin de la representacin adoptada para el grafo.
383. Disea un algoritmo de recorrido por niveles para grafos dirigidos no valorados, construido
como funcin iterativa que devuelva el recorrido en forma de secuencia de vrtices. Usa
una cola de vrtices para controlar el recorrido, y un conjunto de vrtices auxiliar para llevar la cuenta de los vrtices ya visitados. Analiza la complejidad del algoritmo en funcin
de la representacin adoptada para el grafo.
384. Modifica el algoritmo del ejercicio 383 cambiando la cola por una pila, de manera que re-
385. Desarrolla una implementacin del TAD CJTO[E :: DIS] usando como tipo representante
el tipo Vector E de Bool, de manera que las operaciones Pon, quita y pertenece sean ejecutables en tiempo constante, y las operaciones Vaco y esVaco sean ejecutables en tiempo lineal. Esta implementacin de CJTO es recomendable para los conjuntos auxiliares utilizados
en los algoritmos de recorrido de los ejercicios anteriores.
386. Dado un grafo dirigido acclico G, la relacin entre vrtices definida como
560
Ejercicios
B
C
F
D
H
I
J
K
387. Dado un grafo dirigido y acclico G, se desea construir una secuencia de vrtices xs que
represente un recorrido de ordenacin topolgica de G. Disea un procedimiento que resuelva este problema, y analiza su complejidad en funcin de la representacin adoptada
para el grado.
Idea: El algoritmo debe utilizar un bucle que elija en cada vuelta un vrtice cuyos antepasados
ya hayan sido visitados y que no haya sido visitado todava, para aadirlo al recorrido. Conviene
usar un conjunto de vrtices auxiliar M que contiene en cada momento los vrtices que tienen la
propiedad de que todos sus antepasados han sido ya visitados.
388. Modifica el algoritmo del ejercicio anterior, obteniendo una variante que no exija en su
precondicin que G sea acclico, y que detecte durante la ejecucin si G es acclico o no.
Idea: Si el conjunto M mencionado en el ejercicio anterior se queda vaco antes de haber visitado todos los vrtices, entonces el grafo no es acclico; en caso contrario, el grafo es acclico, y el
algoritmo termina completando un recorrido de ordenacin topolgica.
Bsqueda de caminos mnimos en grafos
389. Dados un grafo dirigido valorado G y un vrtice u de G, el algoritmo de Dijkstra (1959) busca
caminos de coste mnimo con origen en u y destinos en los dems vrtices v de G. El algoritmo mantiene un conjunto M de vrtices v para los cuales ya se ha encontrado el camino
mnimo desde u. La idea del algoritmo es:
Al terminar, se han encontrado caminos de coste mnimo C(v) desde u hasta los restantes
vrtices v. Ejecuta el algoritmo de Dijkstra para el grafo dirigido de la figura siguiente, tomando
como vrtice inicial u = 1.
561
Ejercicios
30
50
2
40
100
40
70
30
10
3
10
5
20
390. Disea un procedimiento iterativo que implemente el algoritmo de Dijkstra, devolviendo
un vector C indexado por vrtices, tal que C(v) indique el coste del camino mnimo de u a v;
y otro vector P indexado por ordinales de vrtices, tal que P(ord(v)) indique el ordinal del
vrtice predecesor de v en el camino mnimo desde u hasta v (por convenio, ser 0 si no hay
camino de u a v). Analiza la complejidad del algoritmo en funcin de la representacin
adoptada para el grafo, y estudia las optimizaciones que se describen en el texto de Xavier
Franch, seccin 6.4.1.
391. Aplica el algoritmo de Dijkstra al grafo dirigido y valorado de la figura siguiente, tomando
A como vrtice inicial. Observa que el algoritmo se detiene despus de haber encontrado
caminos mnimos para todos los vrtices accesibles desde A.
392. Si se desea obtener caminos de coste mnimo entre todas las posibles parejas de vrtices de
Se entra en un bucle anidado que recorre todas las ternas de vrtices u, v, w, actualizando
C(v, w) := min( C(v, w), C(v, u) + C(u, w) ).
Al terminar, est garantizado que todos los valores C(v, w) corresponden a costes de caminos
mnimos. ejecuta el algoritmo de Floyd para el grafo del ejercicio 391.
adems de la matriz C mencionada en el ejercicio 392, otra matriz S indexada por parejas
de ordinales de vrtices, tal que S(ord(v), ord(w)) indique el ordinal del vrtice sucesor de v en
un camino mnimo de v a w (por convenio, ser 0 si no hay camino de v a w). Analiza la
complejidad del algoritmo en funcin de la representacin adoptada para el grafo.
562
Bibliografa
BIBLIOGRAFA
[Bal93]
[Brass08]
[BS04]