You are on page 1of 571

Todos los derechos reservados.

Cualquier forma de reproduccin, distribucin, comunicacin pblica


o transformacin de esta obra slo puede ser realizada con la autorizacin expresa de sus titulares,
salvo excepcin prevista por la ley.
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 i
PRLOGO
Si has aprendido a programar de manera informal, las tcnicas formales son un descubrimien-
to 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 sepa-
rado y razonar sobre su correccin, adquiriendo una metodologa sistemtica y bien fundamenta-
da 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 enfo-
que 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 auto-
contenida, 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 pro-
cedimientos 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 algorit-
mos de complejidad cuasi-lineal de ordenacin rpida y ordenacin por mezcla. Despus de pre-
sentar 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 genera-
lizacin 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 implementa-
cin, 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 par-
tir de su especificacin, incluyendo indicaciones acerca de cmo verificar la correccin de dicha
implementacin. Por ltimo, se presentan los punteros como mecanismo para construir estructu-
ras 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: es-
tructuras 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 secuen-
cialmente sus elementos, sern muy utilizadas como estructuras auxiliares en los captulos poste-
riores. 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 cla-
ves 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 localiza-
cin 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 ob-
tencin 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
ndice de contenidos iii
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
1.4.2 Resolucin general de recurrencias ............................................................................. 202
1.4.3 Anlisis de algunos algoritmos recursivos ................................................................. 209
2.5 Transformacin de la recursin final a forma iterativa ......................................................... 213
1.5.1 Ejemplos ......................................................................................................................... 215
2.6 Tcnicas de generalizacin y plegado-desplegado. ................................................................ 218
1.6.1 Generalizaciones ............................................................................................................ 218
1.6.2 Generalizacin para la obtencin de planteamientos recursivos ............................ 220
1.6.3 Generalizacin por razones de eficiencia ................................................................... 225
1.6.4 Tcnicas de plegado-desplegado ................................................................................. 230
2.7 Ejercicios ...................................................................................................................................... 242
Captulo 3 Tipos abstractos de datos ........................................................................... 255
3.1 Introduccin a la programacin con tipos abstractos de datos ........................................... 255
3.1.1 La abstraccin como metodologa de resolucin de problemas ............................ 255
3.1.2 La abstraccin como metodologa de programacin ............................................... 256
3.1.3 Los tipos predefinidos como TADs ........................................................................... 257
3.1.4 Ejemplos de especificacin informal de TADs ........................................................ 258
3.1.5 Implementacin de TADs: privacidad y proteccin ................................................ 260
3.1.6 Ventajas de la programacin con TADs .................................................................... 261
3.1.7 Tipos abstractos de datos versus estructuras de datos ............................................. 264
3.2 Especificacin algebraica de TADs.......................................................................................... 264
3.2.1 Tipos, operaciones y ecuaciones ................................................................................. 265
3.2.2 Trminos de tipo t: T
t
.................................................................................................. 267
3.2.3 Razonamiento ecuacional. T-equivalencia ................................................................. 268
3.2.4 Operaciones generadoras, modificadoras y observadoras ....................................... 270
3.2.5 Trminos generados: TG
t
............................................................................................ 271
3.2.6 Completitud suficiente de las generadoras ................................................................ 272
3.2.7 Razonamiento inductivo ............................................................................................... 274
3.2.8 Diferentes modelos de un TAD .................................................................................. 275
3.2.9 Modelo de trminos ...................................................................................................... 276
3.2.10 Proteccin de un TAD usado por otro ...................................................................... 277
3.2.11 Generadoras no libres ................................................................................................... 278
3.2.12 Representantes cannicos de los trminos: TC
t
_ TG
t
.......................................... 279
3.2.13 Operaciones privadas y ecuaciones condicionales .................................................... 280
3.2.14 Clases de tipos ................................................................................................................ 281
3.2.15 TADs genricos ............................................................................................................. 283
3.2.16 Trminos definidos e indefinidos ................................................................................ 285
3.2.17 Igualdad existencial, dbil y fuerte .............................................................................. 290
3.3 Implementacin de TADs ......................................................................................................... 290
3.3.1 Implementacin correcta de un TAD ........................................................................ 291
3.3.2 Otro ejemplo: CJTO[NAT] ......................................................................................... 299
ndice de contenidos v
3.3.3 Verificacin de programas que usan TADs ............................................................... 303
3.5 Estructuras de datos dinmicas ................................................................................................ 304
3.5.1 Estructuras de datos estticas y estructuras de datos dinmicas ............................ 304
3.5.2 Construccin de estructuras de datos dinmicas ...................................................... 310
3.5.3 Implementacin de TADs mediante estructuras de datos dinmicas .................... 312
3.5.4 Problemas del uso de la memoria dinmica en la implementacin de TADs ...... 319
3.6 Ejercicios ...................................................................................................................................... 326
Captulo 4 Tipos de datos con estructura lineal .......................................................... 334
4.1 Pilas ............................................................................................................................................... 334
4.1.1 Anlisis de la complejidad de implementaciones de TADs .................................... 334
4.1.2 Eliminacin de la recursin lineal no final ................................................................. 339
4.2 Colas ............................................................................................................................................. 347
4.2.1 Especificacin ................................................................................................................ 347
4.2.2 Implementacin ............................................................................................................. 348
4.3 Colas dobles ................................................................................................................................. 359
4.3.1 Especificacin ................................................................................................................ 359
4.3.2 Implementacin ............................................................................................................. 360
4.4 Listas ............................................................................................................................................. 364
4.4.1 Especificacin ................................................................................................................ 364
4.4.2 Implementacin ............................................................................................................. 366
4.4.3 Programacin recursiva con listas ............................................................................... 375
4.5 Secuencias .................................................................................................................................... 380
4.5.1 Especificacin ................................................................................................................ 381
4.5.2 Implementacin ............................................................................................................. 382
4.5.3 Recorrido y bsqueda en una secuencia ..................................................................... 390
4.6 Ejercicios ...................................................................................................................................... 396
Captulo 5 rboles ........................................................................................................ 414
5.1 Modelo matemtico y especificacin ....................................................................................... 414
5.1.1 Arboles generales ........................................................................................................... 415
5.1.2 Arboles binarios ............................................................................................................. 419
5.1.3 Arboles n-arios ............................................................................................................... 421
5.1.4 Arboles con punto de inters ....................................................................................... 421
5.2 Tcnicas de implementacin ..................................................................................................... 422
5.2.1 Implementacin dinmica de los rboles binarios .................................................... 422
5.2.2 Implementacin esttica encadenada de los rboles binarios ................................. 426
5.2.3 Representacin esttica secuencial para rboles binarios semicompletos ............. 426
5.2.4 Implementacin de los rboles generales ................................................................... 430
5.2.5 Implementacin de otras variantes de rboles .......................................................... 431
5.2.6 Anlisis de complejidad espacial para las representaciones de los rboles ............ 432
ndice de contenidos vi
5.3 Recorridos .................................................................................................................................... 432
5.3.1 Recorridos en profundidad .......................................................................................... 433
5.3.2 Recorrido por niveles .................................................................................................... 440
5.3.3 Arboles binarios hilvanados ......................................................................................... 443
5.3.4 Transformacin de la recursin doble a iteracin .................................................... 443
5.4 Arboles de bsqueda .................................................................................................................. 444
5.4.1 Arboles ordenados ........................................................................................................ 444
5.4.2 Arboles de bsqueda ..................................................................................................... 449
5.5 Arboles AVL ............................................................................................................................... 457
5.5.1 Arboles equilibrados ..................................................................................................... 457
5.5.2 Operaciones de insercin y borrado ........................................................................... 460
5.5.3 Implementacin ............................................................................................................. 467
5.6 Ejercicios ...................................................................................................................................... 476
Captulo 6 Tablas ......................................................................................................... 497
6.1 Modelo matemtico y especificacin ....................................................................................... 497
6.1.1 Tablas como funciones parciales ................................................................................. 497
6.1.2 Tablas como funciones totales .................................................................................... 498
6.1.3 Tablas ordenadas ........................................................................................................... 499
6.1.4 Casos particulares: conjuntos y vectores .................................................................... 501
6.2 Implementacin con acceso basado en bsqueda ................................................................. 503
6.3 Implementacin con acceso casi directo: tablas dispersas .................................................... 504
6.3.1 Tablas dispersas abiertas ............................................................................................... 506
6.3.2 Tablas dispersas cerradas .............................................................................................. 511
6.3.3 Funciones de localizacin y relocalizacin ................................................................ 521
6.3.4 Eficiencia ........................................................................................................................ 523
6.4 Ejercicios ...................................................................................................................................... 525
Captulo 7 Grafos .......................................................................................................... 530
7.1 Modelo matemtico y especificacin ....................................................................................... 530
7.2 Tcnicas de implementacin ..................................................................................................... 535
7.3 Recorridos de grafos .................................................................................................................. 541
7.3.1 Recorrido en profundidad ............................................................................................ 542
7.3.2 Recorrido en anchura .................................................................................................... 544
7.3.3 Recorrido de ordenacin topolgica .......................................................................... 547
7.4 Caminos de coste mnimo ......................................................................................................... 549
7.4.1 Caminos mnimos con origen fijo. .............................................................................. 549
7.4.2 Caminos mnimos entre todo par de vrtices ............................................................ 553
7.5 Ejercicios ...................................................................................................................................... 557
Bibliografa ................................................................................................................... 562
ndice de contenidos vii
Diseo de algoritmos iterativos 1
CAPTULO 1
DISEO DE ALGORITMOS ITERATIVOS
1.1 Especificacin pre/post
Los algoritmos se pueden especificar en lenguaje natural. El problema es que para que las es-
pecificaciones as expresadas sean precisas consideren todos los casos posibles se necesitan
descripciones muy extensas. Las especificaciones formales proporcionan a la vez precisin y con-
cisin. La tercera caracterstica deseable de una especificacin es la claridad y aqu es donde,
para un programador no entrenado en las tcnicas formales, pueden resultar ms ventajosas las
especificaciones en lenguaje natural. Otro problema es que los sistemas informticos se ocupan
de un rango tan amplio de cuestiones que puede resultar pretencioso pretender ser capaces de
formalizar cualquier dominio, aparte de que un poco de ambigedad puede venir bien a veces...
Los formalismos que utilizaremos para construir las especificaciones:
Especificaciones con precondiciones y postcondiciones de la lgica de predicados para los algo-
ritmos.
Especificaciones algebraicas mediante ecuaciones para los tipos de datos.
Las especificaciones resultan tiles en las distintas fases del desarrollo de los programas:
Antes de la construccin, como contratos que facilitan la abstraccin y la divisin de tare-
as.
Durante la construccin, porque existen tcnicas que nos ayudan a construir programas a
partir de las especificaciones.
Despus de la construccin, porque constituyen una documentacin excelente.
Una especificacin pre/post de un programa (algoritmo o accin) A es
{P} A {Q}
Que se lee como P son las condiciones que ha de cumplir la entrada para que, tras ejecutar A
se obtenga un resultado que cumpla Q. El problema es determinar qu describen las condiciones
P y Q. Un programa lo podemos ver como un proceso que cambia el estado de una computadora
[CCMRSV93].
[Pe05] La tcnica pre/post se basa en considerar que un algoritmo, contemplado como una
caja negra de la cual slo nos es posible observar sus parmetros de entrada y de salida, acta
como una funcin de estados en estados: comienza su ejecucin en un estado inicial vlido, descrito
por el valor de los parmetros de entrada, y termina en un estado final en el que los parmetros
de salida contienen los resultados esperados.
El problema es que los programas se ejecutan en computadoras y hay aspectos del estado de
una computadora que no son fcilmente formalizables.
Diseo de algoritmos iterativos 2
La solucin que se adopta es definir el
estado de un programa como una descripcin instantnea de los valores asociados a las va-
riables del programa.
[Bal93] Se puede identificar intuitivamente con un estado interno de la mquina en la que se
ejecuta el programa, en un instante determinado. Es posible describir el estado de una computa-
dora exclusivamente mediante variables? (El contenido de la memoria, por supuesto; el estado de
otros dispositivos en ltima instancia viene siempre dado por algn tipo de memoria la de
vdeo, por ejemplo o registro?) Est relacionado el uso de este modelo con el hecho de que las
tcnicas formales obvien la entrada/salida? Qu es un programa?
En {P} A {Q} la A se puede referir a un programa entero, con lo que las variables a que
hacen referencia P y Q seran las variables globales despus de ser inicializadas?; o puede tra-
tarse de un procedimiento o funcin, en general una accin, con lo que las variables seran los
parmetros de entrada y salida; o, en general, un fragmento de programa o algoritmo.
Podemos definir programa como una unidad que recibe una cierta entrada y produce un resul-
tado.
Definimos una entrada de un programa como un estado inicial del mismo, justo antes de ser
ejecutado.
Definimos un resultado de un programa como un estado final del mismo, justo despus de
ser ejecutado.
Definimos los asertos como las frmulas lgicas que se refieren al estado de un programa.
En estos trminos, el significado de la especificacin {P} A {Q} es: si el estado inicial de A
cumple las aserciones de P entonces la ejecucin de A termina en un estado que cumple las aser-
ciones de Q.
Aparece aqu la idea de resultado garantizado si se usa correctamente si se cumple la pre-
condicin. Y resultados impredecibles si no se usa como se indica.
Un ejemplo [Pe05]: suponemos declarado el tipo
tipoVect=Vector[1..1000]deent
queremos implementar una funcin que dado un vector a de tipo vect y un entero n nos de-
vuelva 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?
Diseo de algoritmos iterativos 3
var
a:Vect;
n:Ent;
b:Bool;
{P0n1000}
esSuma
{Q b-i:1in:(a(i)=j:1ji1:a(j))}
esta especificacin deja claro
1. no se puede llamar a la funcin con n negativo o n > 1000
2. las llamadas con n = 0 son correctas y en ese caso la funcin devuelve b = falso
3. las llamadas con n 1 y a[1] = 0 han de devolver b = cierto (suponiendo que el sumatorio
sobre un dominio vaco es igual a cero
1
).
1.1.1 Representacin de asertos en lgica de predicados
Vamos a utilizar la lgica de predicados adaptada a los elementos que aparecen en los progra-
mas. En un lenguaje imperativo existen una serie de tipos predefinidos. Una de las primeras pro-
piedades 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:
Nat , para los naturales
Ent, para los enteros
Rac, para los racionales
Real, para los reales
Bool, para los booleanos
Car, para los caracteres
Vector, para los vectores
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
1
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].
Diseo de algoritmos iterativos 4
Vector[v
1
..v
n
]det
siendo los valores v
1
, ..., v
n
de tipo t, su dominio ser el conjunto de aplicaciones entre D
t

y
D
t
que notaremos D(t t)
De esta forma, el dominio para cada uno de los tipos predefinidos queda
Tipo (sintctico) Dominio (semntico)
2
Nat D
nat
= N
Ent D
ent
= Z
Rac D
rac
= Q
Real D
real
= R
Bool D
bool
= {cierto, falso}
Car D
car
= {0, 1, ..., 9, a, ..., z, A, ..., Z}
Vector[t] de t D
vector[
t
] de
t
= D(tt)
Decimos entonces que un valor de tipo t es cualquier elemento del correspondiente dominio
D
t
. 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 pre-
definidos.
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 : t
1
... t
n
t
Las operaciones que podemos utilizar sobre los tipos predefinidos:
Operaciones numricas, definidas para los tipos numricos: Nat, Ent, Rac y Real
+, *, : t t t
Operaciones constantes de tipo bool
cierto, falso : Bool
Operaciones booleanas
AND, OR : Bool Bool Bool
NOT : Bool 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.
Diseo de algoritmos iterativos 5
Operaciones de comparacin con resultado booleano definidas para los tipos ordenados:
Nat, Ent, Rac, Real y Car.
>, <, , : t t Bool
Operaciones de mximo y mnimo para los tipos ordenados
max, min : t t t
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.
=, = : t t Bool
Una operacin polimrfica para cada uno de los tipos predefinidos que devuelve verdade-
ro o falso dependiendo de si el argumento es de tipo o.
_:o : t Bool
3
Adems, tambin podremos emplear en los asertos cualquier operacin que haya sido totalmen-
te especificada, en el sentido que veremos ms adelante, entendindose en ese caso que su compor-
tamiento est definido por la especificacin.
Construccin de asertos
La forma ms simple de aserto es una expresin de tipo Bool. Definamos primero qu enten-
demos por expresin de tipo t.
Decimos que E es una expresin de tipo t si y slo si es de alguna de las formas siguientes:
Es una variable de tipo t
Es una constante de tipo t
Es f(E
1
, ..., E
n
) siendo el perfil de f
f : t
1
... t
n
t
y las E
i
son expresiones de tipo t
i
para i e {1, ..., n}.
Estas expresiones se corresponden con el concepto de trmino en lgica de predicados. Con
las expresiones construiremos aserciones que nos permiten establecer condiciones sobre el estado
de los programas.
Decimos que P es una asercin (aserto o predicado) si y slo si es:
Atmica.
P es una expresin de tipo Bool
P es
E = E
3
Vemos aqu cmo en los perfiles tambin se puede indicar el modo de aplicacin: prefijo, infijo o postfijo.
Diseo de algoritmos iterativos 6
siendo E y E expresiones
Compuesta
P es de alguna de las formas
R R.Q RvQ RQ R 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
&
.
La existencia de ecuaciones se justifica por el inters en comparar expresiones de tipos en los
que no existe la igualdad. Para los tipos simples coincide con el uso de las operaciones booleanas
de comparacin, pero los vectores como otros tipos de datos que introduciremos ms adelante
slo se pueden comparar mediante esas aserciones ya que no disponen de operacin de igualdad.
Para evitar un uso excesivo de parntesis, definimos el orden de prioridad de las conectivas
lgicas y los cuantificadores existenciales
De mayor a menor prioridad
, ., v, , , o
donde o denota indistintamente o -.
Veamos algunos ejemplos de expresiones y aserciones
x+3 es una expresin de tipo numrico
x > 10 es una expresin de tipo booleano y, por lo tanto una asercin atmica
(z=a) AND (t=cierto) es una expresin de tipo booleano y una asercin atmica
(z=a) . (t=cierto) es una asercin compuesta
(x>0) v (x<0) es una asercin compuesta
(x>3) (x>0) es una asercin compuesta
x : ent : (x*0=0) es una asercin compuesta, donde la asercin de dominio es una no-
tacin abreviada de x : ent, una expresin booleana donde se aplica la operacin :ent
-i : 1iN : (i*2=3) es una asercin compuesta, donde la asercin de dominio es una
notacin abreviada de (1i) . (iN).
Diseo de algoritmos iterativos 7
El empleo de cuantificadores en las aserciones supone la utilizacin de variables para represen-
tar un subconjunto de los posibles valores de un cierto dominio y no como contenedores de va-
lores 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 cuan-
tificador 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
(x>0) . (-x : Ent : x=2)
Por claridad, trataremos de que una misma variable no aparezca libre y ligada. De esta forma,
una asercin equivalente a la anterior es la que resulta de renombrar las apariciones ligadas de x
por y:
(x>0) . (-y : Ent : y = 2)
Para aumentar la expresividad del lenguaje que utilizaremos para describir el estado de los
programas, introducimos otras formas de construir expresiones, que se caracterizan por el uso de
variables ligadas
Las expresiones pueden ser tambin de la siguiente forma:
Sumatorios
i
&
: D( i
&
) : E( i
&
)
Productos extendidos
i
&
: D( i
&
) : E( i
&
)
Mximos
max i
&
: D( i
&
) : E( i
&
)
Mnimos
min i
&
: D( i
&
) : E( i
&
)
Conteos
# i
&
: D( i
&
) : P( i
&
)
Siendo D( i
&
) una asercin de dominio, E( i
&
) una expresin y P( i
&
) una asercin, tales que en
todas ellas pueden aparecer las variables i
&
. Cualquier aparicin de las variables i
&
se entiende
Diseo de algoritmos iterativos 8
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 valo-
res en el dominio indicado. (se puede indicar la notacin con super y subndices que les
puede resultar ms familiar).
j : 1 j n : j esta expresin obtiene el producto factorial del valor contenido en la
variable n.
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
min k : 1 k N : v(k) que representa el mnimo de los valores del vector v
#i : 1 i N : (v(i) = 0) que representa el nmero de componentes cuyo valor es cero.
El ltimo elemento sintctico que nos resta por introducir son las sustituciones. Resulta muy im-
portante 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 apari-
ciones 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
[x
1
/E
1
, ... , x
n
/E
n
]
para indicar la sustitucin simultnea de las variables x
1
, ... , x
n
por las expresiones E
1
, ... , E
n
,
respectivamente.
Las sustituciones slo se pueden realizar para variables libres, pues son stas las que represen-
tan valores del estado. En esta definicin se supone que en ningn caso se ha utilizado una varia-
ble 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)
Diseo de algoritmos iterativos 9
es incorrecto sustituir variables ligadas
(x:1x100:(x=a))[x/3] x:13100:(3=a)
es incorrecto cualquier forma de sustitucin que no sustituya todas las apariciones simult-
neamente
((x=17)v(x>100))[x/(x*x)]((x*x=17)v(x>100))[x/(x*x)]
((x*x)*(x*x)=17)v(x*x>100)
en sustituciones mltiples es incorrecto hacer las sustituciones secuencialmente
((x*17)*y)[x/(2*y),y/z] ((2*y)*y)[y/z] ((2*z)*z)
Significado de los asertos
Intuitivamente un aserto expresa una afirmacin que puede ser verdadera o falsa. El significa-
do de un aserto ser su verdad o su falsedad. Para describir cmo se asigna un valor de verdad a
los asertos empezaremos por describir cmo se asocia ese valor con los asertos atmicos y poste-
riormente daremos significado a las conectivas lgicas, cubriendo as cualquier posible aserto.
Para poder afirmar si un aserto es verdadero o falso necesitamos conocer el valor de las varia-
bles libres que en l aparecen, es decir, necesitamos conocer el estado del programa. Definimos el
estado de un programa como una descripcin instantnea de los valores asociados a las variables
del programa, es decir, una aplicacin de los identificadores de la variables en valores de los do-
minios correspondientes, que representaremos con la notacin
o: IdVar D
donde o 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;
un estado o es una aplicacin
o : {x,y,b} D
ent
D
bool
por ejemplo
o = {(x,1), (y,2), (b,falso)}
El significado de los asertos se construye inductivamente a partir del significado de los predi-
cados las expresiones de tipo booleano.
Dada una expresin E de tipo t, definimos el significado de E bajo el estado o, que notare-
mos
val|E, o|
Diseo de algoritmos iterativos 10
como el valor de D
t
resultante de la aplicacin del significado habitual o especificado de las
operaciones empleadas en E, aplicadas sobre los valores que asigna o a las variables libres. Este
significado estar definido solamente en el caso de que o asigne valores a todas las variables libres
de E.
Dado un aserto P, definimos el significado de P bajo el estado o, que notaremos
val|P, o|
de forma inductiva como:
Aserciones atmicas:
Si P es una expresin de tipo Bool tomamos el significado de P como expresin
Si P es E = E
cierto si val|E,o| = val|E,o|
val|P,o| =
falso en otro caso
Aserciones compuestas
Si P es R
cierto si val|R,o| = falso
val|P,o| =
falso si val|R,o| = cierto
Si P es R . Q
cierto si val|R,o| = cierto y val|Q,o| = cierto
val|P,o| =
falso en otro caso
Si P es R v Q
cierto si val|R,o| = cierto o val|Q,o| = cierto
val|P,o| =
falso en otro caso
Si P es R Q
falso si val|R,o| = cierto y val|Q,o| = falso
val|P,o| =
cierto en otro caso
Si P es R Q
falso si val|R,o| y val|Q,o| tiene significados opuestos
val|P,o| =
cierto en otro caso
Si P es x
&
: D( x
&
) : R( x
&
) entonces
val|P, o| = cierto
si y slo si para todo estado o que extiende a o y asigna valores a las variables x
&
de
tal forma que
val|D, o| = cierto
se cumple que
val|R, o| = cierto
Diseo de algoritmos iterativos 11
Si P es -x
&
: D( x
&
) : R( x
&
) entonces
val|P, o| = cierto
si y slo si para algn estado o que extiende a o y asigna valores a las variables x
&
de
tal forma que
val|D, o| = cierto
se cumple que
val|R, o| = cierto
Este significado estar definido solamente en el caso de que o asigne valores a todas las varia-
bles libres de P.
En el caso de los cuantificadores, consideramos que para o=C el cuantificador universal es
cierto y el existencial falso.
Veamos unos ejemplos del significado de expresiones y aserciones
Sea o un estado que da valores a las variables
var
x,y:Ent;
b:Bool;
de la siguiente forma
o={(x, 1), (y, 2), (b, falso)}
E
1
es x=y val|E
1
, o| = falso
E
2
es x+(2*y) val|E
2
, o| = 5
P
1
es (x=1).(NOTb) val|P
1
, o| = cierto
P
2
es i:10<i<15:(i*2<30) val|P
2
, o| = cierto
donde los o 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
{ o | val|P, o| = cierto }
Naturalmente, para que un estado satisfaga un aserto, el aserto debe estar definido para ese es-
tado, es decir, el estado debe asignar valores a todas las variables libres del aserto.
Diseo de algoritmos iterativos 12
Veamos algunos ejemplos de los conjuntos de estados que satisfacen ciertos asertos
P
1
es falso est|P
1
| = C
(pues el valor de operacin constante booleana falso siempre es falso)
P
2
es cierto est|P
2
| = TODOS
(pues el valor de la operacin constante booleana cierto es siempre cierto)
P
3
es -i:Nat:x=2*i
est|P
3
| = {o | o asigna a la variable x un valor nulo o par}
Definamos por fin el significado de las expresiones que nos restan:
Si E es una expresin de la forma
i
&
: D( i
&
) : E( i
&
)
entonces
val|E,o| =
_
eC o
val|E, o o|
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i
&
, y siendo
o o el estado que se obtiene al combinar las asignaciones de o y o.
Si C es el conjunto vaco, entonces se conviene en que val|E,o| = 0.
Si E es una expresin de la forma
i
&
: D( i
&
) : E( i
&
)
entonces
val|E,o| =
[
eC o
val|E, o o|
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i
&
, y siendo
o o el estado que se obtiene al combinar las asignaciones de o y o.
Si C es el conjunto vaco, entonces se conviene en que val|E,o| = 1.
Si E es una expresin de la forma
max i
&
: D( i
&
) : E( i
&
)
entonces
val|E,o| = Max
oe
C
val|E, o o|
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i
&
, y siendo
o o el estado que se obtiene al combinar las asignaciones de o y o.
Si C es el conjunto vaco, entonces se conviene en que val|E,o| queda indefinido.
Si E es una expresin de la forma
min i
&
: D( i
&
) : E( i
&
)
entonces
Diseo de algoritmos iterativos 13
val|E,o| = Min
oe
C
val|E, o o|
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i
&
, y siendo
o o el estado que se obtiene al combinar las asignaciones de o y o.
Si C es el conjunto vaco, entonces se conviene en que val|E,o| queda indefinido.
Si E es una expresin de la forma
#i
&
: D( i
&
) : P( i
&
)
entonces
val|E,o| = |{o | val|D.P, o o|=cierto }
siendo o 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 corres-
pondiente 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 cier-
tas 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, respectivamen-
te, 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:
Sea o un estado que da valor a las variables
varx,y:Ent;
de la siguiente forma: o={(x,10), (y,4)}
Si E es i:1iy:(i*x)
entonces
val|E, o| = val|i*x, o {(i, 1)}| + ... + val|i*x, o {(i, 4)}|
= 1*10+ ... + 4*10
= 10+20+30+40
= 100
Si E es #i:5iy:(i*x>34)
entonces
val|E, o| = |C|
siendo C
C = {o | val|(5iy).(i*x>34), oo|=cierto}
= {o | val|(5iy), oo|=cierto}{o | val|(i*x>34), oo|=cierto}
= {o | val|(5i4), o|=cierto}{o | val|(i*10>34), o|=cierto}
= C {o | val|(i*10>34), o|=cierto} = C
Diseo de algoritmos iterativos 14
por tanto,
val|E,o| = |C| = |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 abso-
luta 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;
tenemos que x0 x10
ya que
est|x0| = {o | o asigna a x el valor cero }
est|x10| = {o | o asigna a x el valor cero, o el 1, o el 2, ..., o el 10 }
por tanto,
est|x0| _ est|x10|
Conviene no confundir la relacin de fuerza entre aserciones PQ con la conectiva implica-
cin de construccin de aserciones P 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.
4
Lo que si es correcto es que si el aserto P Q se satisface en cualquier estado entonces se tiene que P Q, ya
que, o bien est|P|=C, o bien est|P| _ est|Q| debido a la definicin de la semntica del conectivo .
Diseo de algoritmos iterativos 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| = {o | o asigna a x el valor cero }
est|(x<1).(x>-1)| = {o | o 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 fortaleci-
mientos 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.
Dada una asercin P, decimos que P.Q es un fortalecimiento de P, o que P se fortalece con
Q.
Dada una asercin P, todo fortalecimiento de P es ms fuerte que P
Dada una asercin P, decimos que PvQ es un debilitamiento de P, o que P se debilita con Q.
Dada una asercin P, P es ms fuerte que todo debilitamiento de P.
Ntese que puede ocurrir que un fortalecimiento o un debilitamiento en realidad no vare la
fuerza de un aserto el conjunto de estado que lo satisfacen, pero an as se siguen cumpliendo
las proposiciones sobre la fuerza con respecto a fortalecimientos y debilitamientos porque en la
definicin de fuerza se ha utilizado inclusin no estricta entre conjuntos P es ms fuerte que P.
Como hemos visto en un ejemplo anterior, es posible expresar el mismo predicado de distin-
tas formas, y en cada momento nos puede interesar ms una cierta formulacin. Nos interesa
disponer de un conjunto de leyes de equivalencia que permitan transformar unos asertos en
otros, sabiendo que se preserva el conjunto de estados que los satisfacen.
Diseo de algoritmos iterativos 16
Leyes de equivalencia
Tenemos tres grupos de leyes de equivalencia, segn se refieran a las operaciones, a las conec-
tivas 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 ti-
pos predefinidos. De forma resumida dichas propiedades son:
Conmutatividad. Para las operaciones +, *, AND, OR
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.
Distributividad. Del producto con respecto de la suma y de la diferencia.
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
P . Q Q . P
P v Q Q v P
P Q Q P
Asociatividad
P . (Q . R) (P . Q) . R
P v (Q v R) (P v Q) v R
Idempotencia
P . P P
P v P P
Distributividad
P . (Q v R) (P . Q) v (P . R)
P v (Q . R) (P v Q) . (P v R)
Absorcin
P v (P . Q) P
P . (P v Q) P
Diseo de algoritmos iterativos 17
Neutros
P . falso falso
P . cierto falso
P v falso P
P v cierto cierto
Contradiccin
P . P falso
Tercio excluido
P v P cierto
Doble negacin
(P) P
Leyes de De Morgan
(P v Q) P . Q
(P . Q) P v Q
Implicacin y doble implicacin
P Q P v Q
P Q (P Q) . (Q P)
Donde P, Q y R son asertos.
Leyes de los cuantificadores
La correccin de estas leyes se basa en la semntica que hemos definido para los cuantificado-
res. Son leyes de equivalencia entre asertos como indica el uso de .
Renombramiento de variables ligadas
x : D(x) : P(x) y : D(x)[x/y] : P(x)[x/y]
-x : D(x) : P(x) -y : D(x)[x/y] : P(x)[x/y]
siendo y una variable que no aparece en D ni en P
Cuantificacin en una equivalencia. Si P Q
x : D(x) : P(x) x : D(x) : Q(x)
-x : D(x) : P(x) -x : D(x) : Q(x)
Negacin de un cuantificador
(x : D(x) : P(x)) -x : D(x) : (P(x))
(-x : D(x) : P(x)) x : D(x) : (P(x))
Diseo de algoritmos iterativos 18
Desplazamiento de cuantificadores
(x : D(x) : P(x)) . (x : D(x) : Q(x)) x : D(x) : (P.Q)(x)
(-x : D(x) : P(x)) v (-x : D(x) : Q(x)) -x : D(x) : (PvQ)(x)
Leyes de descomposicin de cuantificadores
Cuando el dominio de un cuantificador no es nulo, podemos separar uno de sus elementos y
reducir en uno el dominio del cuantificador. El elemento separado se combina con el cuantifica-
dor reducido, mediante la correspondiente operacin binaria. Por ejemplo
si N 1
i : 1 i N : a(i) = a(1) + i : 2 i N : a(i)
ntese que la condicin N1 es necesaria para garantizar que a(1) est efectivamente entre los
elementos que han de ser sumados. De la misma manera, podemos separar un elemento arbitra-
rio del dominio de un cuantificador cualquiera, salvo que sea vaco, combinndolo con el resto
mediante la operacin binaria asociada al cuantificador. En el caso de maximizacin y minimiza-
cin, hemos de garantizar que el dominio original tiene al menos dos elementos, con el fin de que
el cuantificador restante no se aplique sobre un dominio nulo; pero para los dems cuantificado-
res definidos basta con que exista en el dominio un elemento que separar.
Ms formalmente, lo que estamos haciendo es aplicar propiedades como la siguiente:
dados dos dominios disjuntos D
1
y D
2
i : i e (D
1
D
2
) : a(i) i : i e D
1
: a(i) + i : i e D
2
: a(i)
Anlogamente, se tienen propiedades similares para los cuantificadores producto extendido
(*), existencial (v), universal (.) y de conteo (+), utilizando la correspondiente operacin binaria
en lugar de la suma. Y para el mximo y el mnimo?
Operaciones parciales
Para terminar por fin con el apartado dedicado a la representacin de los asertos trataremos el
problema de las operaciones parciales.
Con los tipos predefinidos que vamos a utilizar nos encontraremos con operaciones que no
estn definidas para algn valor del dominio asociado a sus argumentos. Por ejemplo, la divisin
no est definida si el divisor es cero, es una operacin parcial.
Decimos que una operacin f con dominio t
1
, ..., t
n
es parcial si para algn i e {1, ..., n}, exis-
te un valor en D
t
i
, para el que no est definida. La notaremos empleando una flecha cortada entre
el dominio y el codominio declarado
Diseo de algoritmos iterativos 19
f: t
1
... t
n
t
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 Ent
la operacin * : Nat Nat 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 varia-
bles 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 o, definimos el significado de def(E) bajo o como:
cierto si val|E,o| est definido
val|def(E),o| =
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) (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 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,o| no estara definido, ya que el estado o no les dara valor.
Diseo de algoritmos iterativos 20
1.1.2 Especificacin pre/post
Pasaremos ahora a definir las especificaciones con precondiciones y postcondiciones, y a esta-
blecer 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 posibi-
lidades del empleo de asertos para definir condiciones de los programas. Por ltimo, establecere-
mos 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 progra-
ma A
P es un aserto (la precondicin)
Q es un aserto (la postcondicin)
Si las declaraciones son conocidas podremos escribir
{ P }
A
{ Q }
o bien, si conviene, { P } A { Q }.
El significado de una especificacin viene dado por el cambio de estado que implica la ejecu-
cin del programa.
Dada una especificacin pre/post
{ P } A { Q }
definimos su significado como:
Si A comienza en un estado que satisface P, entonces A termina en tiempo finito en un
estado que satisface Q.
En esta definicin se recoge el carcter de contrato de la especificacin, el implementador se
compromete a obtener un programa que partiendo de un estado que verifica P llegue a un estado
que verifica Q. Si el estado inicial no verifica P, el implementador no se compromete a nada.
Una especificacin pre/post se puede utilizar con distintos fines dependiendo de cules de sus
constituyentes sean conocidos. Si conocemos los tres elementos entonces el objetivo ser demos-
trar que el programa cumple la especificacin: verificacin del programa. Podemos, en otro caso,
partir de un fragmento de programa y una postcondicin para tratar de determinar una precondi-
Diseo de algoritmos iterativos 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 tam-
bin 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 }: deri-
vacin de programas a partir de la especificacin.
Es importante hacer notar que siempre consideramos que estn inicializadas todas aquellas va-
riables 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)}
Este programa verifica la especificacin dada:
a := 0; c := 0; r := 0;
El problema es que no hemos sido suficientemente restrictivos. Como dice Ricardo, al escribir
una especificacin debemos pensar que el implementador es un ser malvolo y despreciable que
intentar cumplir las condiciones del contrato de la manera que le resulte ms sencilla.
El error se encuentra en la postcondicin que, si bien exige que las variables del programa
cumplan una cierta relacin, permite que stas tomen nuevos valores. Dicho de otra forma, la
postcondicin tienen sentido si a y b se refieren a los valores de entrada.
Este problema requiere el uso de un convenio. Vamos a utilizar variables auxiliares de la especifica-
cin, que nos servirn para recoger los valores de entrada que nos interese para escribir las especi-
ficaciones. Por ejemplo, en el caso de dividir:
vara,b,c,r:Nat;
{b>0.a=A.b=B}
dividir
{(A=B*c+r).(r<B)}
Usamos las variables auxiliares A y B, cuyo valor establecemos en la precondicin. Por conve-
nio, escribimos en minscula las variables de programa y en mayscula las auxiliares de la especi-
ficacin. El programa no puede modificar el valor de las variables de especificacin ya que no
Diseo de algoritmos iterativos 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 existen-
cia 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, destina-
das a recoger el resultado del algoritmo. En [Pe05] se propone el uso de un valor especial indefi-
nido (), 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 varia-
bles de programa no cambian de valor. As, por ejemplo, podemos reforzar an ms la postcon-
dicin de la divisin entera como
{(A=B*c+r).(r<B).(a=A).(b=B)}
Recapitulando, en nuestras especificaciones aparecern los siguientes tipos de variables:
Ligadas. Variables introducidas por cuantificadores y expresiones extendidas. No repre-
sentan 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.
Libres. El resto de las variables.
De programa. Variables que representan el comportamiento visible del programa,
pueden tener valores que el programa puede modificar. Por convenio las escribimos
en minscula.
Auxiliares de la especificacin. No son accesibles para el programa, aunque toman
valores a partir de los estados. Tienen utilidad para la especificacin, porque permiten
recoger los valores de las variables del programa en ciertos estados de inters como,
por ejemplo, el estado inicial. Tambin sirven para representar valores que no nos in-
teresa almacenar en el estado del programa no son ni un dato ni un resultado, pero
que pueden ser necesarios para escribir una condicin determinada (como en la post-
condicin de la divisin entera). Por convenio, las escribimos con la primera letra
mayscula.
Diseo de algoritmos iterativos 23
Algunos ejemplos de especificacin pre/post
Intercambio de valores entre dos variables
La especificacin se apoya en el uso de variables auxiliares de especificacin:
varx,y:Ent;
{x=X.y=Y}
intercambio
{x=Y.y=X}
Copia del valor de una variable
varx,y:Ent;
{x=X}
copia
{y=X.x=X}
podemos obviar la ecuacin x=X en la postcondicin si no queremos imponer esta condicin
adicional.
Raz cuadrada exacta
Tenemos la declaracin de variables
varx,y:Ent;
y tenemos que especificar un algoritmo que calcule en y la raz cuadrada del valor de x, sa-
biendo 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.
Diseo de algoritmos iterativos 24
varx,y:Ent;
{x=X.X=Y*Y}
Raz
{y*y=X.x=X}
An quedara otro detalle. Como las variables auxiliares de la especificacin no aparecen en la
declaracin de variables del programa, no est especificado cul es su tipo. De esta forma, podr-
amos leer la precondicin como que X es el cuadrado de un nmero real. Finalmente
varx,y:Ent;
{x=X.X=Y*Y.Y:Ent}
Raz
{y*y=X.x=X}
Al escribir esta especificacin uno se puede sentir tentado de escribir la postcondicin como
{y=Y.x=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 exac-
ta de x. El problema es que para poder relacionar la postcondicin con el significado que se pre-
tende 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 deri-
var 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, devol-
viendo 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}
Diseo de algoritmos iterativos 25
La postcondicin es ms interesante. Hemos de expresar que r ha de tener el mismo valor que
un aserto, el aserto que se cumple cuando x es una potencia de 2. Supongamos que conocemos
dicho aserto P, estaramos tentados de escribir:
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 ():
{rP.x=X}
En cuanto a la forma de P, ser algo como
-i:Nat:x=2
i

El problema es que no hemos incluido la exponenciacin entre las operaciones disponibles


para los enteros. Pero se expresa fcilmente usando el producto extendido
-i:Nat:x=(j:1ji:2)
con lo que la especificacin quedara
var
x:Ent;
r:Bool;
{x=X}
esPotencia2?
{r-i:Nat:x=(j:1ji:2).x=X}
Mximo de un vector de enteros
Dadas las declaraciones de constantes y variables
cte
N=...;
var
Diseo de algoritmos iterativos 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 modifi-
car.
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;
{N1.v=V}(*v=Vexpresaquetodaslascomponentesdevtienen
valor*)
mximo
{m=maxi:1iN:v(i).v=V}
Modificacin de un vector de enteros
Partiendo de las declaraciones
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
N1.v=V.x=X.y=Y
Podramos cambiar la especificacin para aceptar N=0.
En la postcondicin debemos indicar para cada componente del vector que si a la entrada ten-
a valor x a la salida debe tener valor y
Diseo de algoritmos iterativos 27
i:1iN:(V(i)=Xv(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)=Xv(i)=Y).
i:1iN:(V(i)=Xv(i)=V(i))
utilizando las leyes de equivalencia de los cuantificadores y forzando a que x e y conserven su
valor llegamos a
cte
N=...;
var
v:Vector[1..N]deEnt;
x,y:Ent;
{N1.v=V.x=X.y=Y}
Reemplazar
{i:1iN:[(V(i)=Xv(i)=Y).(V(i)=Xv(i)=V(i))].
x=X.y=Y}

Divisin entera y resto


En el apartado donde se introdujeron las variables auxiliares de la especificacin se present ya
la especificacin de la divisin entera, pero en aquel ejemplo era para operandos de tipo natural.
Ahora, en cambio, partimos de las declaraciones
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 combina-
cin 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, desgraciada-
mente no se encuentra entre las operaciones disponibles para el tipo primitivo Ent. Cmo podr-
amos 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:
Diseo de algoritmos iterativos 28
varx,y:Ent;
{x=X}
absoluto
{(X0y=X).(X<0Y=(1)*X).x=X}
A partir de este momento, ya podemos utilizar la operacin abs en los asertos que escriba-
mos, con lo que la especificacin de la divisin entera queda
varx,y,c:Ent;
{x=X.y=Y.Y=0}
divisin
{X=Y*c+R.0abs(R)abs(Y).x=X.y=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 nota-
cin abreviada para la inclusin de existenciales:
-r:Ent:X=Y*c+r
De forma similar el mdulo quedar
varx,y,r:Ent;
{x=X.y=Y.Y=0}
mdulo
{X=Y*C+r.0abs(r)abs(Y).x=X.y=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:
Diseo de algoritmos iterativos 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 ac-
ciones genricos.
Hay ciertas propiedades que cumplen los asertos en virtud nicamente de su definicin y ca-
ractersticas. Podemos presentar estas propiedades como reglas de verificacin bsicas, pues nos ser-
virn en el proceso de verificacin de un algoritmo, o en la obtencin de otras reglas.
Dados los asertos P, P, P
1
, P
2
, Q, Q, Q
1
y Q
2
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 {Q
1
} { P } A {Q
2
}
{ P } A { Q
1
. Q
2
}
Disyuncin en la precondicin
{ P
1
} A {Q } { P
2
} A {Q }
{ P
1
v P
2
} 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.
Diseo de algoritmos iterativos 30
Estas reglas se suelen aplicar de atrs adelante, para probar que se cumple la conclusin pro-
bamos que se cumplen las premisas. Por ejemplo, aplicando la regla de la disyuncin en la post-
condicin, para demostrar la correccin de un algoritmo para la especificacin:
varx,y:Ent;
{x=X.y=Y}
sumaYDoble
{x=2*X.y=X+Y}
podemos demostrar la correccin de ese algoritmo con respecto a las especificaciones:
{x=X.y=Y}sumaYDoble{x=2*X}
y
{x=X.y=Y}sumaYDoble{y=X+Y}
Las leyes de equivalencia junto con estas reglas proporcionan un clculo para razonar con pre-
dicados y especificaciones, alternativo al razonamiento en trminos de estados. Las leyes se to-
man 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 deduci-
da por l es vlida en la lgica de predicados. Sin embargo, cuando se admiten dominios de valo-
res 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 po-
der 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, respectivamen-
te, 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-
Diseo de algoritmos iterativos 31
guntarnos cmo son los estados tales que partiendo desde ellos y ejecutando A se llega a Q. Es-
tos 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)| = { o | A iniciado en o termina en un estado o e est|Q|
Ntese que la precondicin as definida es la ms dbil posible pues incluye a todos los esta-
dos 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 trmi-
nos 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 defi-
niciones 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:
Diseo de algoritmos iterativos 32
Monotona:
Q
1
Q
2
pmd( A, Q
1
) pmd( A, Q
2
)
Exclusin de milagros:
pmd( A, Falso) Falso
Distributividad con respecto a . :
pmd( A, Q
1
. Q
2
) pmd( A, Q
1
) . pmd( A, Q
2
)
Distributividad con respecto a v :
pmd( A, Q
1
v Q
2
) :pmd( A, Q
1
) v pmd( A, Q
2
)
Como ejemplo veamos la demostracin de la distributividad con respecto a .:
En la direccin
Sea o un estado cualquiera que satisface
pmd( A, Q
1
. Q
2
)
por tanto, A iniciado en o termina en un estado o de
est|Q
1
. Q
2
| = est|Q
1
| est|Q
2
|
como o e est|Q
1
| entonces o satisface
pmd( A, Q
1
)
y como o e est|Q
2
| entonces o satisface
pmd( A, Q
2
)
por tanto o satisface
pmd( A, Q
1
) . pmd( A, Q
2
)
y al ser o un estado cualquiera que satisface pmd( A, Q
1
. Q
2
) se tiene
pmd( A, Q
1
. Q
2
) pmd( A, Q
1
) . pmd( A, Q
2
)
En la otra direccin (:) el razonamiento es reversible, fijndonos en que si o comienza en un
estado que cumple pmd( A, Q
1
) . pmd( A, Q
2
) termina en un estado o que satisface Q
1
. Q
2
,
con lo que se tiene
pmd( A, Q
1
. Q
2
) : pmd( A, Q
1
) . pmd( A, Q
2
)
Diseo de algoritmos iterativos 33
Aunque estas propiedades nos resulten de utilidad, necesitamos ser capaces de obtener la pre-
condicin 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 post-
condicin 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 descri-
bir 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.
Parmetros o variables de entrada
Parmetros o variables de salida
Parmetros o variables de entrada y salida
Precondicin. Una explicacin de los asertos que la componen.
Efecto. Una descripcin del efecto resultante de ejecutar la accin, en trminos de las en-
tradas y las salidas.
Postcondicin. Una explicacin de los asertos que la componen.
Excepciones. Consideraciones adicionales sobre la parcialidad o carcter estricto de la ac-
cin.
Las metodologas de diseo que se estudian en Ingeniera del Software suelen incluir normas
sobre la documentacin.
1.2 Verificacin de algoritmos
Ha llegado el momento de empezar a implementar los programas. Qu lenguaje de progra-
macin vamos a utilizar? Con el objetivo de que nuestro estudio sea lo ms general posible utili-
zaremos un lenguaje, que no se corresponde con ningn lenguaje de programacin concreto,
pero que incluye los elementos fundamentales comunes a los lenguajes de programacin impera-
tiva ms habituales. Este lenguaje imperativo genrico que permite expresar operaciones algort-
micas es lo que denominamos un lenguaje algortmico.
El lenguaje algortmico incluye todos los tipos de valores que pueden aparecer en las especifi-
caciones y sobre los que se pueden aplicar las mismas operaciones.
Como indicamos al final del apartado anterior, para cada una de las instrucciones de nuestro
lenguaje vamos a dar una regla de verificacin que permite verificar la correccin de un programa
compuesto nicamente por la instruccin en cuestin. Esa regla de verificacin se obtendr en
cada caso a partir de un axioma que indique una utilizacin correcta de cada instruccin con res-
pecto al significado de especificacin Pre/Post. Estos axiomas definen formalmente el significa-
Diseo de algoritmos iterativos 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 axio-
mas se justificar mediante la obtencin de la precondicin ms dbil inducida por una postcon-
dicin, 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 Estructuras algortmicas bsicas
La instruccin seguir
Su sintaxis es
seguir
Intuitivamente el significado de seguir es no hacer nada.
La obtencin de la pmd resulta sencilla considerando que seguir no modifica el estado del
cmputo, luego qu han de cumplir los estados iniciales para que despus de ejecutar la instruc-
cin seguir el estado resultante cumpla un aserto Q? pues precisamente Q:
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:
P Q
{ P } seguir { Q }
Por ejemplo, para demostrar la correccin de
Diseo de algoritmos iterativos 35
varx,y:Ent;
{P:x1}
seguir
{Q:x0}
es necesario probar que
x1x0
lo cual es trivialmente cierto.
La asignacin simple
Su sintaxis es
x:=E
donde E es una expresin del mismo tipo que la variable x. Intuitivamente, se evala la expre-
sin E y se asigna su valor a la variable x, con lo que se modifica el estado siempre que el resul-
tado 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
pmd( x := E, Q ) def(E) . Q [x/E]
El axioma que indica cmo ha de ser la precondicin para obtener una postcondicin dada
Diseo de algoritmos iterativos 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:x2.2*y>5.x=X.y=Y}
x:=2*x+y1
{Q:x3>2}
aplicando la regla de verificacin de la asignacin, hemos de probar P def(E) . Q [x/E]
La expresin esta definida porque no contiene operaciones parciales y estn declaradas las va-
riables que contiene
def( 2*x + y 1) 2*x + y 1 : Ent : x:Ent . y:Ent : P
Para la segunda parte hemos de realizar la sustitucin
x 3 > 2 [x/2*x + y 1]
sustitucin 2*x + y 1 3 > 2
lgebra 2*x + y > 6
lgebra : x 2 . y 3
lgebra : x 2 . 2*y > 5
fuerza : x 2 . 2*y > 5 . x = X . y = Y
P
Obsrvese cmo el objetivo de las manipulaciones algebraicas es ir acercndonos a la forma
de la precondicin. En muchas ocasiones, cuando def(E) sea trivialmente cierto omitiremos su
demostracin.
Un aspecto bsico del mtodo de verificacin que estamos presentando es que la pmd, el
axioma y la regla de la asignacin slo son vlidos si no existen efectos colaterales, donde
Definimos los efectos colaterales como aquellas modificaciones en el estado de cmputo de
un programa no provocadas por el significado de las instrucciones.
Diseo de algoritmos iterativos 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
<x
1
,...,x
m
>:=<E
1
,...,E
m
>
siendo las x
i
variables distintas entre s, y las E
i
expresiones de los mismos tipos que los de las
variables x
i
, para i e {1,...,m}. Intuitivamente, se evalan las expresiones E
i
y se modifica el esta-
do de forma simultnea, asignando a cada variable x
i
el valor de la correspondiente expresin E
i
.
La simultaneidad es relevante cuando en las E
i
interviene alguna de las x
i
.
La obtencin de la pmd es una extensin de lo realizado en la asignacin simple: hay que ga-
rantizar 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 x
i
se sustituye por la correspon-
diente expresin E
i
.
pmd(<x
1
, ... , x
m
> := <E
1
, ... , E
m
>, Q) def(E
1
) . ... . def(E
m
) . Q[x
1
/E
1
, ..., x
m
/E
m
]
el axioma que indica la forma de la precondicin para obtener la postcondicin
{ def(E
1
) . ... . def(E
m
) . Q[x
1
/E
1
, ..., x
m
/E
m
] }
<x
1
, ... , x
m
> := <E
1
, ... , E
m
>
{ Q }
y la regla de verificacin que se obtiene aplicando la propiedad bsica de las pmd.
P def(E
1
) . ... . def(E
m
) . Q[x
1
/E
1
, ..., x
m
/E
m
]
{ P } <x
1
, ... , x
m
> := <E
1
, ... , E
m
> { Q }
Veamos un ejemplo con el intercambio de los valores de dos variables
varx,y:Ent;
{P:x=X.y=Y}
<x,y>:=<y,x>
Diseo de algoritmos iterativos 38
{Q:x=Y.y=X}
las expresiones estn definidas al no contener operaciones parciales y estar declaradas las va-
riables
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 = Y . x = X
boole x = X . y = Y
P
Asignaciones a componentes de vectores
Permitimos la asignacin a componentes de vectores empleando la sintaxis de una asignacin
simple o compuesta, donde se utiliza la notacin
v(E
i
)
para indicar la componente del vector v en la posicin del valor de E
i
. Si v(E
i
) aparece a la iz-
quierda del smbolo := se interpreta como un cambio de valor de esa componente, y si aparece en
otro lugar se considera una inspeccin del valor de la componente.
Sin embargo, esto slo es una comodidad sintctica y la asignacin a componentes de vectores
no significa que cada componente del vector se pueda considerar como una variable indepen-
diente. La variable independiente es el vector como un todo y lo que en realidad tiene sentido es
la modificacin del valor del vector y no el de una de sus componentes.
Considerar las componentes de los vectores como variables independientes conduce a efectos
negativos con respecto a la verificacin de los algoritmos. En primer lugar, la regla de verificacin
de la asignacin sera incompleta, no pudiendo demostrar la correccin de programas que s lo
son, como se aprecia en el siguiente ejemplo (es el ejercicio 21.a).
cteN=...;%Ent
varv:Vector[1..N]deEnt;
i,j:Nat;
{1iN.i=j}
v(i):=0
{v(j)=0}
aunque es intuitivamente correcto, no es posible verificarlo con la regla de la asignacin, pues
al realizar la sustitucin en la postcondicin queda
v(j) = 0 [ v(i)/0] v(j) = 0
que no es ms dbil que la precondicin.
Diseo de algoritmos iterativos 39
Y, es ms, la regla deja de ser correcta pues permite verificar programas errneos (es el ejerci-
cio 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
sin embargo, se puede encontrar un contraejemplo, ya que si partimos de un estado o
o(v(1)) = 2 y o(v(2)) = 2
que cumple la precondicin, se llega a un estado o con:
o(v(1)) = 2 y o(v(2)) = 1
que no verifica el aserto v(v(2)) = 1 pues
val|v(v(2)) = 1, o| = falso
no siendo correcto el algoritmo con respecto a la especificacin
Para que las cosas funcionen correctamente hemos de definir operaciones de acceso y
modificacin de las componentes de un vector, operaciones que tomen al vector completo como
argumento.
inspeccin del valor de una componente:
valor(v, E
i
) que normalmente abreviamos como v(E
i
)
modificacin del valor de una componente:
asignar(v, E
i
, E) que abreviamos como v(E
i
) := E
Los vectores verifican las ecuaciones
valor(asignar(v, i, E), i) = E
i = j valor(asignar(v, i, E), j) = valor( v, j )
que nos permite relacionar el comportamiento de ambas ecuaciones
Utilizando estas operaciones podemos obtener la precondicin ms dbil de la asignacin a
componentes de vectores utilizando un razonamiento similar al empleado en las asignaciones
simples, pero considerando que se asigna todo el vector
Diseo de algoritmos iterativos 40
pmd( v(E
i
) := E, Q) def(E) . enRango(v, E
i
) . Q[v/asignar(v, E
i
, E)]
Ntese que el resultado de la operacin asignar es un vector.
Adems de exigir que la expresin que se asigna est definida, tambin pedimos que la
expresin cuyo valor determina el ndice de la componente a modificar cumpla la condicin
enRango. Este aserto incluye la definicin de la expresin E
i
y solicita que su valor est en el rango
de valores que sirven de ndice al vector v. En caso de conocer el rango, el aserto enRango(v, E
i
)
puede sustituirse por
def(E
i
) . (li E
i
ls)
siendo [li..ls] el rango del vector v.
el axioma define las condiciones que han de cumplir los estados de entrada
{ def(E) . enRango(v, E
i
) . Q[v/asignar(v, E
i
, E)] }
v(E
i
) := E
{ Q }
y la regla de verificacin, aplicando la propiedad bsica de las pmd
P def(E) . enRango(v, E
i
) . Q[v/asignar(v, E
i
, E)]
{ P } v(E
i
) := E { Q }
Con esta nueva regla s es posible verificar el ejemplo que antes no
cteN=...;%Ent
varv:Vector[1..N]deEnt;
i,j:Nat;
{1iN.i=j}
v(i):=0
{v(j)=0}
la precondicin garantiza que la expresin que sirve de ndice es vlida
enRango(v, i) 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
Diseo de algoritmos iterativos 41
v(j) = 0 [v/asignar(v, i, 0)]
vectores valor(v, j) = 0 [v/asignar(v, i, 0)]
sustitucin valor(asignar(v, i, 0), j) = 0
: valor(asignar(v, i, 0), i) = 0 . i = j
ecuacin 0 = 0 . i = j
fuerza : P
1.2.2 Estructuras algortmicas compuestas
Las acciones que vamos a ver a continuacin permiten combinar otras instrucciones para con-
seguir 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 cons-
truir acciones que representen a cualquier construccin disponible en un lenguaje imperativo es-
tructurado. Al reducir el nmero de instrucciones facilitamos la verificacin puesto que tenemos
que tratar con menos reglas.
Composicin secuencial
Su sintaxis es
A
1
; A
2
siendo A
1
y A
2
acciones expresadas en el lenguaje algortmico.
Intuitivamente, el comportamiento es que se ejecuta la accin A
1
y a su terminacin (si se pro-
duce) se ejecuta la accin A
2
.
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 A
2
inducida por Q y, a su
vez, para alcanzar ese estado intermedio hemos de partir de un estado que cumpla la pmd de A
1
inducida por la pmd(A
2
, Q):
pmd( A
1
;A
2
, Q ) pmd( A
1
, pmd( A
2
, Q))
Diseo de algoritmos iterativos 42
el axioma se obtiene de la pmd
{ pmd( A
1
, pmd( A
2
, Q)) }
A
1
; A
2
{ Q }
La regla de verificacin supone una relajacin del axioma al permitir utilizar un aserto inter-
medio R cualquiera que garantice que:
Un estado que cumpla R, tomado como entrada de A
2
, produce un estado que cumple la
postcondicin
La ejecucin de A
1
con entrada en un estado que cumple la precondicin produce un es-
tado que cumple R
La pmd de la segunda accin es un aserto intermedio vlido, pero la regla de verificacin per-
mite considerar otros
{ P } A
1
{ R } { R } A
2
{ Q }
{ P } A
1
; A
2
{ Q }
la obtencin de esta regla se puede justificar por las propiedades de las pmd
Por la definicin de pmd a partir de { P } A
1
{ R }
P pmd( A
1
, R)
Por la definicin de pmd a partir de { R } A
2
{ Q }
R pmd( A
2
, Q)
Por la propiedad de monotona de las pmd, aplicada a la anterior relacin, con respecto a
la accin A
1
( P Q // pmd(A,P) pmd(A,Q) )
pmd( A
1
, R) pmd( A
1
, pmd( A
2
, Q))
Por transitividad de la fuerza de los asertos (ya que es una inclusin de conjuntos)
P pmd( A
1
, pmd( A
2
, Q)) ( pmd( A
1
;A
2
, Q ) )
Y por la propiedad bsica de las pmd se obtiene la conclusin de la regla
{ P } A
1
; A
2
{ Q }
Veamos un ejemplo de verificacin de la composicin secuencial. Vamos a verificar un pro-
grama que, dada un cantidad positiva de dinero obtiene a cuntas monedas de 5 y 1 pesetas se
corresponde.
varc,d,p:Ent;
Diseo de algoritmos iterativos 43
%
%c=cantidad,d=monedasde5,p=monedasde1
%
{P:c=C.C>0}
d:=cdiv5;
p:=cmod5
{Q:C=d*5+p.0d<C.0p<5.c=C}
En este programa usamos las operaciones div y mod con el significado dado por la especifica-
cin 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 def(c mod 5) . Q [ p/c mod 5]
sustitucin C = d*5 + c mod 5 . 0 d < C . 0 c mod 5 < 5 . c = C
lgebra C = d*5 + c mod 5 . 0 d < C . c 0 . c = C
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
R: C = d*5 + c mod 5 . 0 d < C . c 0 . c = C
y aplicamos la regla de verificacin para el aserto intermedio. Ntese que ya hemos probado
que
{ R } p := c mod 5 { Q }
es correcta, pues R es la pmd( p:=c mod 5, Q). Nos queda probar
{ P } d := c div 5 { R }
aplicando la regla de verificacin de la asignacin tenemos que demostrar
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 C = c div 5*5 + c mod 5 . 0 c div 5 < C . c 0 . c = C
lgebra C = c . 0 c div 5 < C . c 0
fuerza : P
donde, en el ltimo paso hemos aplicado
C = c C = c
C = c . C > 0 0 c div 5 < C
C = c . C > 0 c 0
Diseo de algoritmos iterativos 44
Con esto hemos demostrado { P } d := c div 5 { R } y { R } p := c mod 5 { Q }, por lo
que, aplicando la regla de verificacin, queda demostrado
{ P } d := c div 5 ; p := c mod 5 { Q }
La introduccin de instrucciones compuestas conduce al empleo de asertos intermedios que se
emplean al realizar las verificaciones. Para facilitar la presentacin de las pruebas escribimos pro-
gramas anotados en los que se incluyen los asertos intermedios, indicando las condiciones que han
de cumplir los estados en cada punto del programa. Con respecto a la composicin secuencial,
los programas anotados incluyen los asertos intermedios
{ P }
A
1
;
{ R }
A
2
;
{ Q }
La composicin secuencial puede hacerse entre acciones del lenguaje cualesquiera, en particu-
lar entre otras composiciones secuenciales. Se cumple la propiedad de que la composicin se-
cuencial es un operador asociativo (la demostracin se hace utilizando las propiedades de la pmd
y es el ejercicio 26), es decir:
el programa
A
1
; { A
2
; A
3
}
es equivalente al programa
{ A
1
; A
2
} ; A
3
y escribimos, simplemente
A
1
; A
2
; A
3
Con respecto a las pmd se cumple
pmd( A
1
; A
2
; A
3
, Q ) pmd( A
1
, pmd( A
2
; A
3
, Q )) pmd( A
1
, pmd( A
2
, pmd( A
3
, Q )))
y, en general, la composicin secuencial de n acciones:
pmd( A
1
; ... ; A
n
, Q) pmd( A
1
, ... pmd( A
n
, Q) ... )
y la regla de verificacin extendida
{ P } A
1
{ R
1
} ... { R
n1
} A
n
{ Q }
{ P } A
1
; ... ; A
n
{ Q }
Diseo de algoritmos iterativos 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 sin-
taxis:
var x
1
: t
1
; ... ; x
m
: t
m
;
inicio
A
fvar
siendo las x
i
variables nuevas, que no se han declarado previamente en el algoritmo, y A una
accin del lenguaje algortmico.
Intuitivamente, las variables locales inducen el siguiente comportamiento:
se amplia el estado para incluir las nuevas variables
con el estado ampliado, se ejecuta la accin A, que puede inicializar, acceder y modificar
las variables locales
al terminar la ejecucin de A, las variables locales dejan de existir
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 precon-
dicin ni en la postcondicin. Por lo tanto la precondicin se obtiene exclusivamente a partir de
la accin A:
pmd( var ... inicio A fvar, Q ) pmd(A, Q)
el axioma se obtiene directamente de la pmd
{ pmd( A, Q) }
var x
1
: t
1
; ... ; x
m
: t
m
;
inicio
A
fvar
{ Q }
y la regla de verificacin
{ P } A { Q }
{ P } var ... inicio A fvar { Q }
Diseo de algoritmos iterativos 46
No todos los lenguajes de programacin permiten definir variables locales en lugares arbitra-
rios del cdigo. Lo que s es ms habitual es que se puedan definir en los subprogramas: proce-
dimientos 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 B
1
A
1
B
2
A
2
...
B
m
A
m
fsi
siendo las B
i
expresiones booleanas y las A
i
acciones del lenguaje algortmico. Cada una de las
estructuras B
i
A
i
se denomina accin protegida donde la accin A
i
est protegida por la barrera B
i
.
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 po-
der realizarse siempre (por lo que al menos una barrera debe estar abierta).
Se ejecuta la accin asociada a la barrera seleccionada.
Para obtener la pmd hemos de considerar que todas las barreras deben estar definidas, al me-
nos 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:
pmd( si B
1
A
1
... B
m
A
m
fsi, Q )
def(B
1
) . ... . def(B
m
) . [(B
1
. pmd(A
1
, Q)) v ... v (B
m
. pmd(A
m
, Q))
Ntese que la disyuncin de los asertos B
i
. pmd(A
i
, Q)) garantiza que al menos una de las
barreras est abierta y que su correspondiente accin conduce a un estado que cumple la post-
condicin.
el axioma
{ pmd( si B
1
A
1
... B
m
A
m
fsi, Q ) }
Diseo de algoritmos iterativos 47
si B
1
A
1
B
2
A
2
...
B
m
A
m
fsi
{ Q }
y la regla de verificacin
P def(B
1
) . ... . def(B
m
)
P B
1
v ... v B
m
{ P . B
1
} A
1
{ Q } ... { P . B
m
} A
m
{ Q }
{ P } si B
1
A
1
... B
m
A
m
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=X.y=Y}
sixyz:=x
yxz:=y
fsi
{Q:x=X.y=Y.((z=X.zY)v(z=Y.zX))}
P def( x y) . def( y x), lo cual se cumple ya que
def( x y) . def( y x) cierto
al no incluir operaciones parciales y estar declaradas las variables de un tipo ordinal
P x y v y x, lo cual se cumple ya que
x y v 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 x = X . y = Y . ((x = X . x Y) v (x = Y . x X))
Boole x = X . y = Y . ( x Y v x = Y )
Fuerza : x = X . y = Y . x Y
P . xy
Diseo de algoritmos iterativos 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 x = X . y = Y . ((y = X . y Y) v (y = Y . y X))
Boole x = X . y = Y . ( y = X v y X )
Fuerza : x = X . y = Y . y X
P . yx
Al igual que en la composicin secuencial tambin podemos anotar la composicin condicio-
nal con los asertos intermedios oportunos:
{ P }
si B
1
{ P . B
1
} A
1
{ Q }
B
2
{ P . B
2
} A
2
{ Q }
...
B
m
{ P . B
m
} A
m
{ 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 ma-
yor 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 am-
bas, 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:
siBA
1

NOTBA
2

fsi
para facilitar su uso introducimos una nueva forma de la instruccin si
siB
entoncesA
1
Diseo de algoritmos iterativos 49
sinoA
2

fsi
Para verificar esta instruccin, se convierte a la forma general de composicin alternativa y se
aplica su regla de verificacin.
Composicin iterativa
La composicin iterativa permite repetir la ejecucin de un misma accin un cierto nmero de
veces; un nmero de veces que depende del estado del programa. En el lenguaje algortmico in-
cluimos las siguiente instruccin iterativa, que se corresponde con el bucle while de los lenguajes
de programacin:
it B
A
fit
Operacionalmente se comporta de la siguiente forma:
Se evala la condicin de repeticin B (que debe estar definida)
Si B toma valor falso, se termina sin modificar el estado
Si B es cierta, se ejecuta el cuerpo A
Se repite el proceso a partir del nuevo estado obtenido.
Al pensar qu forma tiene la pmd surge el concepto de invariante. Supongamos la especifica-
cin de un bucle
{pmd}
itB
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 o
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 o
1
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 o
1
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.
Diseo de algoritmos iterativos 50
Al ejecutarse la ltima pasada por el bucle llegamos a un estado o
n
que al tomarse como entra-
da del bucle har que no se cumpla la condicin de repeticin con lo que el bucle terminar. Pero
an as o
n
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 denomi-
na invariante del bucle.
Por ejemplo,
<i,q,p>:=<0,0,1>;
iti<n
i:=i+1;
q:=q+p;
p:=p+2;
fit
los estados por los que van pasando las variables
estado i q p
o
0 0 1
o
1
1 1 3
o
2
2 4 5
o
3
3 9 7
.
.
.
.
.
.
.
.
.
.
.
.
podemos observar que en todas las iteraciones las variables guardan una cierta relacin entre
s, en este caso
q = i
2
. 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
{ I . B } 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
Diseo de algoritmos iterativos 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:
Decrece en cada vuelta
No puede decrecer indefinidamente
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 decre-
cientes 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 cade-
nas infinitas)
Los naturales () con el orden decreciente estricto habitual. (como mucho pueden llegar
hasta 0)
Ejemplo de dominios que no son bien fundamentados
Los reales (J) con respecto a un orden total. (podemos construir cadenas infinitas cre-
cientes y decrecientes contenidas en cualquier intervalo de los reales)
Los enteros (Z) con respecto a un orden total. (podemos construir cadenas infinitas cre-
cientes 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:
Diseo de algoritmos iterativos 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
{ I . B . C = 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 referen-
cia 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 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), en-
tonces
{ I }
it B
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) P I
(i.2) I def(B)
{ I . B } A { I }
(i.3) I . B Q
(c.1) I . B def(C) . dec(C)
(c.2) { I . B . C = T } A { C % T }
{ p } it B A fit { Q }
siendo I un aserto y C una expresin
Diseo de algoritmos iterativos 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=X.y=Y.y0}
p:=0;
ity=0
p:=p+x;
y:=y1
fit
{Q:p=X*Y}
Tenemos la composicin de una asignacin con un bucle. Para verificar esta composicin ne-
cesitamos un aserto intermedio, y para encontrarlo tenemos dos alternativas:
Deducir un aserto intermedio a partir de la precondicin y el efecto de la asignacin.
Emplear la pmd del bucle como aserto intermedio
La complejidad es similar en ambos casos, y optamos por el primero de los mtodos.
Sabemos que la precondicin se mantiene pues en ella no se hace referencia a p; y sobre esta
variable sabemos que despus de la asignacin su valor ser cero; definimos pues un aserto in-
termedio R
R: x = X . y = Y . y 0 . p = 0
A continuacin vamos a verificar el bucle, procediendo de atrs hacia delante, tomando R co-
mo 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
Diseo de algoritmos iterativos 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 va-
riable entera, por lo que no toma valores en un dominio bien fundamentado; de hecho, si y tuvie-
se 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 con-
dicin 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=X.y=Y.y0.p=0}
ity=0
p:=p+x;
y:=y1
fit
{Q:p=X*Y}
(i.1) R I
R y 0
R x = X . y = Y . p = 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)
Diseo de algoritmos iterativos 55
def( y = 0) cierto
: I
Adems, hay que probar
{ I . B }
p:=p+x;
y:=y1
{ I }
para lo cual aplicamos la regla de la composicin secuencial, obteniendo la pmd de la se-
gunda asignacin inducida por la postcondicin
def(y1) . I[y/y1]
sustitucin cierto . p + x*(y1) = X*Y . y1 0
lgebra p + x*y x = X*Y . y1 0
que llamaremos R
2
. Ahora hallamos las pmd de la primera asignacin inducida por R
2
.
def(p+x) . R
2
[p/p+x]
sustitucin cierto . p + x + x*y x = X*Y . y1 0
lgebra p + x*y = X*Y . y1 0
que llamaremos R
1
. Ahora slo nos queda comprobar que la precondicin implica a R
1
I . B R
1
lo cual se obtiene, aplicando propiedades del lgebra de los enteros, y razonando por par-
tes de R
1
I p + x*y = X*Y
I . B y 0 . y = 0
y > 0
y 1
y1 0
(i.3) I . B Q. Lo cual se obtiene por el siguiente razonamiento
I . B p + x*y = X*Y . y = 0
p + 0 = X*Y
p = X*Y
Q
(c.1) I . B def(C) . dec(C)
def(y) cierto
: I . B
dec(y) y > 0
y 0 . y = 0
Diseo de algoritmos iterativos 56
: I . B
(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 R
3
. Ahora tendremos que obtener la pmd de la primera asignacin indu-
cida por R
3
, que ser def(p+x) . R
3
[p/p+x], pero como p+x est definida y en R
3
no
aparece p como variable libre, nos queda el mismo aserto R
3
. Para probar la correccin de
la composicin nos resta demostrar
I . B . y = T R
3
lo que se obtiene
y = T y 1 < T
R
3
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 cierto . x = X . y = Y . y 0 . 0 = 0
Boole y lgebra x = X . y = Y . y 0
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 reducir-
se 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
Diseo de algoritmos iterativos 57
Al igual que ocurre con las dems instrucciones compuestas, en la composicin iterativa tam-
bin se define un formato de programa anotado
{ P }
{ I ; C }
it B
{ I . B }
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:
1. Relajacin de una conjuncin en la postcondicin. Sabemos que el invariante de un bucle ha de
cumplir el requisito (i.3) que establece
I . B Q
por lo que podemos plantearnos que quizs sea
I . B Q
Si la postcondicin es una conjuncin de asertos, quizs la condicin de terminacin (B)
aparezca expresamente, y que el resto de la postcondicin represente el invariante.
A veces la postcondicin no refleja expresamente como una conjuncin todas las condiciones
que expresa, y puede que sea preciso elaborarla de alguna manera para obtener una formulacin
equivalente y ms explcita.
En el ejemplo anterior la postcondicin p = X*Y puede suponerse fortalecida por la condi-
cin de terminacin quedando
p = X*Y . y = 0
Diseo de algoritmos iterativos 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 ex-
presin 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 fortale-
cer 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.
Partimos de la postcondicin y analizamos si los asertos que incluye dependen de constantes o
de variables que no son modificadas en el cuerpo del bucle. Podemos estudiar si la sustitucin de
esas constantes por variables que se modifican en cada pasada en el bucle nos permite obte-
ner un invariante. Para identificar qu variables sustituir, es preciso analizar el cuerpo del bucle y
ver si las acciones de ste estn construyendo la solucin que expresa la postcondicin, y si hay
alguna variable o variables que representan el avance de la construccin.
Esta situacin es bastante habitual cuando tenemos algoritmos que tratan vectores. Las post-
condiciones incluyen asertos que se refieren a cuantificadores sobre el rango del vector, por
ejemplo:
i:1iN:v(i)=0
aqu lo que se estara construyendo sera un vector con todas sus componentes a 0, de for-
ma 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
Estudiar si el bucle construye algo
Analizar las variables que se modifican y las que no
Diseo de algoritmos iterativos 59
Obtencin de expresiones de acotacin
Para la obtencin de expresiones de acotacin la estrategia es nica
Observar la condicin de terminacin (B)
Esta condicin ha de cumplirse al terminar el bucle, por lo que en ese momento la expresin
de acotacin que estamos buscando puede tomar un valor minimal en un cierto dominio bien
fundamentado. Incluso, puede ocurrir que la propia condicin de terminacin indique que la ex-
presin de acotacin toma un valor minimal, como ocurra en el ejemplo anterior, donde
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 = 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 disminu-
yendo hasta llegar al valor minimal podemos tomar
C : N + 1 i
de forma que
i = N + 1 N + 1 i = 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 modifica-
das en el cuerpo del bucle.
Diseo de algoritmos iterativos 60
Siempre hay que pensar que quizs es posible demostrar que el bucle no termina
(probndolo con un contraejemplo).
Otras construcciones iterativas
Los lenguajes de programacin imperativa suelen incluir varias instrucciones iterativas. Noso-
tros slo hemos incluido una en nuestro lenguaje algortmico, para reducir as el nmero de reglas
de verificacin. Pero esto slo es razonable si las otras construcciones iterativas son traducibles a
nuestra nica instruccin, como de hecho ocurre.
A continuacin, presentamos la sintaxis de otras construcciones iterativas con su correspon-
diente traduccin a la instruccin it. La utilizacin de estas instrucciones en los algoritmos su-
pone que a la hora de verificar la correccin es necesario acudir a su traduccin a la composicin
iterativa simple, y verificar sobre ella.
1. La iteracin repetir
Sintaxis
repetirAhastaBfrepetir
siendo B una expresin de tipo Bool, y A una accin
Semntica
A;
itNOTBAfit

2. La iteracin para ascendente con paso 1.


3.
Sintaxis
paraidesdeMhastaNhacerAfpara
siendo M y N expresiones de tipo Ent, y A una accin que no afecta a la variable i,
que es de tipo Ent.
Semntica
varfinal:Ent:
inicio
i:=M;
final:=N+1;
iti<final
A;
i:=i+1
fit
fvar
Obsrvese que la iteracin es inexistente si el valor inicial de M es mayor que el de N.
Diseo de algoritmos iterativos 61
4. La iteracin para descendente con paso 1.
Sintaxis
paraibajandodesdeMhastaNhacerAfpara
siendo M y N expresiones de tipo Ent, y A una accin que no afecta a la variable i,
que es de tipo Ent.
Semntica
varfinal:Ent:
inicio
i:=M;
final:=N1;
iti>final
A;
i:=i1
fit
fvar
Obsrvese que la iteracin es inexistente si el valor inicial de M es menor que el de N.

5. La iteracin para general.


Sintaxis
paraidesdeMhastaNpasoPhacerAfpara
siendo M, N y P expresiones de tipo Ent, y A una accin que no afecta a la variable i,
que es de tipo Ent.
Semntica
varpaso,final:Ent:
inicio
paso:=P;
i:=M;
final:=N+paso;
sipaso>0
iti<final
A;
i:=i+paso
fit
paso<0
iti>final
A;
i:=i+paso
fit
fvar
Diseo de algoritmos iterativos 62
1.3 Funciones y procedimientos
Las acciones parametrizadas son un mecanismo que nos permite reutilizar el mismo algoritmo
en distintos puntos de un programa, aplicado sobre distintos datos.
Las acciones parametrizadas tienen un identificador asociado, con el que podemos invocarlas,
e identifican algunas de sus variables como parmetros a travs de los cuales es posible transmitir
los datos y recibir los resultados.
Distinguimos entre:
Parmetros formales: aquellas variables que se designan como parmetros al definir la ac-
cin.
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.
Salida, que notaremos como s.
Entrada/Salida, que notaremos como es.
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 va-
lor 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
Diseo de algoritmos iterativos 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 va-
riable del parmetro real.
Podemos resumir en una tabla las caractersticas de los parmetros reales
Modo de uso debe ser variable Parmetro real
es consultado
puede modificarse
e NO SI NO
s SI NO SI
es SI SI SI
Ntese que no permitimos realizar asignaciones a los parmetros de entrada: esto puede signi-
ficar la definicin de variables auxiliares adicionales.
En el lenguaje algortmico tenemos dos tipos de acciones parametrizadas: procedimientos y
funciones.
1.3.1 Procedimientos
Representan a la accin parametrizada ms general, donde se permiten parmetros de entrada,
salida y entrada/salida.
Especificacin de procedimientos
En la especificacin de un procedimiento, adems de la pre y la postcondicin, debemos indi-
car su nombre y sus parmetros formales: la cabecera del procedimiento
Definimos una especificacin de un procedimiento como un esquema EP:
proc nombreProc (e u
1
:t
1
; ; u
n
:t
n
; s v
1
:o
1
; ; v
m
:o
m
; es w
1
:
1
; ; w
p
:
p
);
{ P
0
: P . u
1
= U
1
. . u
n
= U
n
. w
1
= W
1
. . w
p
= W
p
}
{ Q
0
: Q . u
1
= U
1
. . u
n
= U
n
}
fproc
donde
los parmetros u
i,
v
j
y w
k
son identificadores distintos para i= 1, , n, j = 1, , m y k=1,
, p
los t
i
, o
j
y
k
son tipos del lenguaje algortmico y representan los tipos de los parmetros
para i = 1, , n, j=1,,m y k=1,,p
P
0
no contiene expresiones con los parmetros v
j
para j=1, , m.
Diseo de algoritmos iterativos 64
Bsicamente lo que se pide en la especificacin de los procedimientos es que se respete el
comportamiento de los parmetros formales: los parmetros de entrada y entrada/salida tienen
valor, se mantiene el valor de los parmetros de entrada, no hay parmetros repetidos, la precon-
dicin no hace referencia a los parmetros de salida.
Veamos un par de ejemplos:
Procedimiento que obtiene la divisin entera y el resto
procdivMod(ex,y:Nat;sc,r:Nat);
{P
0
:x=X.y=Y.Y>0}
{Q
0
:x=c*y+r.r<y.x=X.y=Y}

Procedimiento que busca un valor en un vector


procbusca(ev:Vector[1..N]deNat;ex:Nat;spos:Nat);
{P
0
:v=V.x=X}
{Q
0
:(1posN.v(pos)=x)(-i:1iN:v(i)=x).x=X.v=
V}

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 u
1
:t
1
; ; u
n
:t
n
; s v
1
:o
1
; ; v
m
:o
m
; es w
1
:
1
; ; w
p
:
p
);
{ P
0
}
cte ;
var ;
inicio
A
{ Q
0
}
fproc
donde
la cabecera de la declaracin, la precondicin P
0
y la postcondicin Q
0
coinciden con la
especificacin del procedimiento
Las declaraciones locales pueden ser vacas, en cuyo caso no aparecern las palabras cte ni
var
Diseo de algoritmos iterativos 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 respe-
ta el modo de uso de los parmetros.
El ltimo requisito garantiza que no existen efectos colaterales, es decir no hay cambios en el esta-
do 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);
{P
0
:x=X.y=Y.Y>0}
inicio
<c,r>:=<0,x>;
{I:x=c*y+r.y>0.x=X.y=Y;
C:r}
itry
c:=c+1;
r:=ry
fit
{Q
0
:x=c*y+r.r<y.x=X.y=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(E
1
, , E
n
, y
1
, , y
m
, z
1
, , z
p
)
donde
Las E
i
son expresiones de los tipos t
i
, para i=1,, n
Las y
j
y z
k
son variables distintas que admiten los tipos o
j
y
k
, respectivamente, para
j=1,,m y k=1,,p
Las variables y
j
y z
k
no aparecen en las expresiones E
i
, 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 parme-
tros formales cuando existen relaciones de compatibilidad entre tipos. As, para un parmetro for-
Diseo de algoritmos iterativos 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 pode-
mos 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 parme-
tros, se puede asignar a una variable de un cierto tipo un valor de un tipo con menos informa-
cin.
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(E
1
, , E
n
, y
1
, , y
m
, z
1
, , z
p
)
{Q}
Podramos sustituir la llamada al procedimiento por su cuerpo y verificar as, pero no quere-
mos 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 pro-
cedimiento con respecto a su especificacin, y la verificacin de las llamadas se reducir a verifi-
car su correccin con respecto a la especificacin del procedimiento.
Para demostrar que una declaracin de procedimiento DP es correcta con respecto a su espe-
cificacin 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 pre-
condicin de la llamada es ms fuerte que la precondicin del procedimiento y que la postcondi-
cin 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 expre-
siones de la precondicin de la llamada que no hagan referencia a variables de salida del procedi-
miento, 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=X.y=Y.y>0.h=2*x}
divMod(x,y,c,r)
{x=y*c+r.r<y.x=X.y=Y.h=2*x}
la condicin h= 2*x no se puede obtener de la postcondicin del procedimiento, pues h no es
una de sus variables de salida.
Con esta idea podemos expresar la pmd de una llamada a un procedimiento
Diseo de algoritmos iterativos 67
Dada una especificacin de procedimiento como en EP para la que tenemos una declaracin
asociada DP que es correcta
pmd(nombreProc(E
1
, , E
n
, y
1
, , y
m
, z
1
, , z
p
), Q)
F . P
0
[u
1
/E
1
, , u
n
/E
n
, w
1
/z
1
, , w
p
/z
p
]
siendo F el aserto ms dbil que cumple
F . Q
0
[u
1
/E
1
, , u
n
/E
n
, v
1
/y
1
, , v
m
/y
m
, w
1
/z
1
, , w
p
/z
p
] 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 P
0
[u
1
/E
1
, , u
n
/E
n
, w
1
/z
1
, , w
p
/z
p
]
(Ntese que para que se cumpla esto es necesario que se cumpla la parte de P
0
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 compati-
bilidad.)
7. Extraer de
P . P
0
[u
1
/E
1
, , u
n
/E
n
, w
1
/z
1
, , w
p
/z
p
]
todas las condiciones que dependen de variables que han de dar algn resultado de salida
y
1
, , y
m
, z
1
, , z
p
y definir R como el aserto restante.
(En general no basta con eliminar los asertos donde hay variables de entrada o entra-
da/salida, y puede ser necesario elaborar los asertos para no perder condiciones deriva-
das.)
8. Comprobar el requisito
R . Q
0
[u
1
/E
1
, , u
n
/E
n
, v
1
/y
1
, , v
m
/y
m
, w
1
/z
1
, , w
p
/z
p
] 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:
Diseo de algoritmos iterativos 68
dada la declaracin
procacumula(ess:Ent;ex:Ent);
{P
0
:x=X.s=S}
inicio
s:=s+x
{Q
0
:s=S+x.x=X}
fproc
supongamos que queremos verificar la llamada
var
suma,b:Ent;
a:Nat;
{P:suma>a*b}
acumula(suma,2*a*b)
{Q:suma>3*a*b}
9. P P
0
[s/suma, x/2*a*b]
P
0
[s/suma,x/2*a*b]
2*a*b=X.suma=S (**)
cierto
: P
ya que se cumple suma:Ent y 2*a*b:Ent por coercin de a como Ent.
(**) Este aserto es cierto si suma y 2*a*b tienen valor, lo cual no est incluido explcita-
mente en P, aunque se puede considerar implcito en suma > a*b, pues este aserto slo
puede suponerse cierto si tanto suma como a*b tienen valor.
10. Extraer de
P.P
0
[s/suma,x/2*a*b]
suma>a*b.2*a*b=X.suma=S
las condiciones que dependen de variables de salida, en este caso suma. Para ello es nece-
sario extender primero el aserto
suma>a*b.2*a*b=X.suma=S
suma>a*b.2*a*b=X.suma=S.S>a*b

con lo que obtenemos


R: 2*a*b = X . S > a*b
11. Comprobar
R.Q
0
[s/suma,x/2*a*b] Q

2*a*b=X.S>a*b.suma=S+2*a*b.2*a*b=
X
suma>a*b+2*a*b
suma>3*a*b
Q

Con lo que la llamada es correcta.


Diseo de algoritmos iterativos 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 cum-
ple 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 indi-
cada veamos qu ocurre cuando intentamos verificarla:
12. P P
0
[s/suma, x/suma]
P
0
[s/suma,x/suma]
suma=X.suma=S
cierto
: P
ya que se cumple suma:Ent por declaracin de esa variable
13. Extraer de
P.P
0
[s/suma,x/suma]
suma=5.suma=X.suma=S
las condiciones que dependen de variables de salida, en este caso suma. Para ello es nece-
sario extender primero el aserto
suma=5.suma=X.suma=S
suma=5.suma=X.suma=S.X=5.S=5

con lo que obtenemos


R: X = 5 . S = 5
14. Comprobar
R.Q
0
[s/suma,x/suma] Q

R.Q
0
[s/suma,x/suma]
X=5.S=5.suma=S+suma.suma=S
X=5.S=5.S=0.suma=S
falso
Q

Con lo que la llamada es correcta.


Diseo de algoritmos iterativos 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 pro-
cedimiento 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
proc nombreProc (e u
1
:t
1
; ; u
n
:t
n
; s v
1
:o
1
; ; v
m
:o
m
; es w
1
:
1
; ; w
p
:
p
);
{ P
0
: P . u
1
= U
1
. . u
n
= U
n
. w
1
= W
1
. . w
p
= W
p
}
{ Q
0
: Q . u
1
= U
1
. . u
n
= U
n
}
fproc
entendemos que el aserto
nombreProc( E
1
, , E
n
, y
1
, , y
m
, z
1
, , z
p
)
es equivalente a
Q[u
1
/E
1
, , u
n
/E
n
, v
1
/y
1
, , v
m
/y
m
, w
1
/z
1
, , w
p
/z
p
]
teniendo cuidado con los parmetros de entrada/salida, para los cules el comportamiento
expresado por Q tiene dos direcciones.
Ntese que de la postcondicin hemos quitado los asertos que se refieren a la conservacin de
valores pues no tienen sentido cuando consideramos la llamada como un aserto.
Por ejemplo:
divMod(3, 4, cociente, resto) es equivalente a
3 = cociente*4 + resto . 0 resto < 4
1.3.2 Funciones
Una funcin es un caso particular de procedimiento que:
no tiene parmetros de entrada/salida
y tiene uno o ms parmetros de salida
Definimos una sintaxis especial para este tipo de procedimientos.
Diseo de algoritmos iterativos 71
Especificacin de funciones
Definimos una especificacin de una funcin como un esquema EF:
func nombreFunc (u
1
:t
1
; ; u
n
:t
n
) dev v
1
:o
1
; ; v
m
:o
m
;
{ P
0
: P . u
1
= U
1
. . u
n
= U
n
}
{ Q
0
: Q . u
1
= U
1
. . u
n
= U
n
}
ffunc
donde
los parmetros u
i
y v
j
son identificadores distintos para i= 1, , n, y j = 1, , m
los t
i
y o
j
son tipos del lenguaje algortmico y representan los tipos de los parmetros para
i = 1, , n, y j=1,,m
P
0
no contiene expresiones con los parmetros v
j
para 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 distin-
tos 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 parme-
tros 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;
{ P
0
: x = X . y = Y . y > 0 }
{Q
0
: 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 parme-
tros de salida.
Definimos una declaracin de funcin como un esquema DF:
func nombreFunc (u
1
:t
1
; ; u
n
:t
n
) dev v
1
:o
1
; ; v
m
:o
m
;
{ P
0
}
cte ;
var ;
Diseo de algoritmos iterativos 72
inicio
A;
{ Q
0
}
dev <v
1
, , v
m
>
ffunc
donde
la cabecera de la funcin, la precondicin P
0
y la postcondicin Q
0
coinciden con la espe-
cificacin 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 res-
peta el modo de uso de los parmetros (no modifica el valor de los parmetros de entra-
da).
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;
{P
0
:x=X.y=Y.y>0}
inicio
<c,r>:=<0,x>
{I:x=c*y+r.y>0.x=X.y=Y;
C:r}
itry
c:=c+1;
r:=ry
fit;
{Q
0
:x=c*y+r.r<y.x=X.y=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 inva-
riante.
Otro ejemplo:
Diseo de algoritmos iterativos 73
funcmcd(x,y:Nat)devm:Nat;
{P
0
:x>0.y>0.x=X.y=Y}
var
a,b:Nat;
inicio
<a,b>:=<x,y>;
{I:mcd(a,b)=mcd(x,y).x=X.y=Y
C:a+b}
ita=b
sia>ba:=ab
b>ab:=ba
fsi
fit;
m:=a;
{Q
0
:m=mcd(x,y).x=X.y=Y}
devm
ffunc
donde el aserto mcd(x,y) es equivalente a
max i : i x . i y . (x mod i = 0) . (y mod i = 0) : i
Llamadas a funciones
El resultado de una llamada a una funcin se recoge como una asignacin mltiple.
Definimos una llamada a una funcin con un especificacin EF y una declaracin DF como la
instruccin
<y
1
, , y
m
> := nombreFunc(E
1
, , E
n
)
donde
las E
i
son expresiones de los tipos t
i
para i = 1, , n
las y
j
son variables distintas que admiten los tipos o
j
, para j=1, , m. Estas variables act-
an como los parmetros reales de salida.
Las variables y
j
no aparecen en las expresiones E
i
para i = 1, , n y j = 1, , m.
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 va-
riables de salida (el parmetro real puede ser de un tipo con ms informacin).
Operacionalmente en la llamada a una funcin:
Diseo de algoritmos iterativos 74
Se evalan las expresiones de los parmetros reales de entrada
Se considera un estado auxiliar para la ejecucin de la llamada, en el que slo intervienen
los parmetros formales de la funcin, y se les asignan los valores correspondientes a los
de entrada.
se ejecuta el cuerpo de la funcin en ese estado auxiliar (incluida la declaracin de varia-
bles 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 es-
tado auxiliar final
Verificacin de las funciones
En cuanto a la verificacin de las funciones, utilizamos los mismos resultados que obtuvimos
para los procedimientos, considerando que no hay ninguna variable de entrada/salida.
Dada una especificacin de funcin como en EF para la que tenemos una declaracin asocia-
da DF que es correcta
pmd(<y
1
, , y
m
>:=nombreFunc(E
1
, , E
n
), Q)
F . P
0
[u
1
/E
1
, , u
n
/E
n
]
siendo F el aserto ms dbil que cumple
F . Q
0
[u
1
/E
1
, , u
n
/E
n
, v
1
/y
1
, , v
m
/y
m
] Q
Para realizar la verificacin utilizaremos el mismo mtodo de verificacin que sugerimos para
los procedimientos.
Cuando la funcin devuelve un nico resultado podemos incluir una llamada a esa funcin
dentro de otra expresin cualquiera, aunque en ese caso ser necesario utilizar una declaracin
local de variable. Veamos un ejemplo de verificacin de una llamada a funcin donde es necesa-
rio utilizar este mecanismo:
tenemos una funcin cuya especificacin es
func fact( n : Nat ) dev x : Nat;
{P
0
: n > 0 . n = N }
{Q
0
: x = n! . n = N }
(suponemos que ya hemos especificado el factorial) y supongamos que tenemos una declara-
cin correcta con respecto a esa especificacin. Queremos verificar la llamada
var num, suma : Nat;
{ P : num > 0 }
suma := 5 * fact(num)
Diseo de algoritmos iterativos 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 intro-
ducir 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
R
1
: 5*f = 5 * num!
con lo que pasamos a verificar
{ P : num > 0 }
f := fact(num);
{ R
1
: 5*f = 5 * num! }
usando el mtodo de verificacin de las llamadas a procedimientos:
15. Comprobamos
P P
0
[n/num]
P
0
[n/num]
num>0.num=N
P
ya num:Nat por la declaracin.
16. Extraer de
P.P
0
[n/num]
num>0.num=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 . Q
0
[x/f, n/num] R
1
R.Q
0
[x/f,n/num]
Diseo de algoritmos iterativos 76
num>0.num=N.f=num!.num=N
lgebrayfuerza 5*f=5*num!
R
1

con lo que la llamada es correcta.


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 ex-
presiones 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 argu-
mentos de la llamada.
Es decir dada una especificacin de funcin EF
func nombreFunc (u
1
:t
1
; ; u
n
:t
n
) dev v
1
:o
1
; ; v
m
:o
m
;
{ P
0
: P . u
1
= U
1
. . u
n
= U
n
}
{ Q
0
: Q . u
1
= U
1
. . u
n
= U
n
}
ffunc
entendemos que el aserto
<y
1
, , y
m
>:= nombreFunc ( E
1
, , E
n
)
es equivalente a
Q[u
1
/E
1
, , u
n
/E
n
, v
1
/y
1
, , v
m
/y
m
]
prescindimos de los asertos relativos a la conservacin del valor de los parmetros de entrada.
1.4 Anlisis de algoritmos iterativos
Hasta ahora nos hemos preocupado fundamentalmente por la correccin de los algoritmos;
ahora vamos a ocuparnos de su eficiencia.
Un algoritmo ser tanto ms eficiente cuantos menos recursos consuma. Los recursos que
consume un algoritmo son tiempo (de CPU) y espacio de memoria necesario para ejecutarlo.
Se ha llegado a afirmar que la eficiencia es irrelevante pues la capacidad de las computadoras
aumenta ao a ao, y que son otros los criterios que se deben primar al desarrollar programas:
claridad, legibilidad, reusabilidad. Sin embargo, lo que ocurre es que las aplicaciones informticas
cada vez son ms exigentes: procesan volmenes mayores de datos, aplicaciones en tiempo real,

Para darnos cuenta del impacto del consumo de recursos veamos unas tablas ideales con dis-
tintos rdenes de consumo
Programa Consumo n=1.000 Bytes Tiempo
Diseo de algoritmos iterativos 77
A
1
log
2
n
~ 10
10 10 segs
A
2
n 1.000
~ 1 KB
16 mins
A
3
n
2
1.000.000
~ 1 Meg
11 das
Los programadores de los algoritmos A
2
y A
3
podran esperar que las mejoras tecnolgicas
hiciesen tiles sus algoritmos. Sin embargo tomemos un volumen de datos de un milln (no des-
cabellado: bases de datos de poblacin, resolucin de impresin, pixeles en pantalla, )
Programa Consumo n=1.000.000 Bytes Tiempo
A
1
log
2
n
~ 20
10 20 segs
A
2
n 1.000.000
~ 1 KB
11 das
A
3
n
2
10
12
~ 1.000 Gig
30.000 aos
Vemos cmo el programa A
1
se ve muy poco afectado por el aumento en el volumen de da-
tos; mientras que los programas A
1
y A
2
tienen aumentos prohibitivos, que los descalifican para la
tarea.
El programa A
1
s puede aprovechar las mejoras tecnolgicas: con un procesador dos veces
ms rpido puede procesar 1000 veces ms informacin; mientras que el programa A
3
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
Un algoritmo eficiente permite explotar las posibilidades de una mquina rpida
Una mquina rpida no es suficiente si no se dispone de un algoritmo eficiente
Sin embargo, tambin hay que tener en cuenta otra mxima:
La eficiencia suele contraponerse a la claridad y exige un desarrollo ms costoso.
Por ello:
En ocasiones puede interesar sacrificar la eficiencia. Por ejemplo, si el programa va a eje-
cutarse pocas veces o con datos de pequeo tamao.
Es conveniente, en todo caso, partir de un diseo claro, aunque sea ineficiente; y optimi-
zar posteriormente.
Diseo de algoritmos iterativos 78
1.4.1 Complejidad de algoritmos
La eficiencia de los algoritmos se cuantifica por medio de medidas de complejidad. Consi-
deramos dos medidas de complejidad diferentes:
Complejidad en tiempo: tiempo de cmputo de un programa
Complejidad en espacio: memoria que utiliza un programa en su ejecucin
Nosotros nos centraremos en la complejidad en tiempo que suele ser ms crucial (la compleji-
dad en espacio se suele resolver aadiendo ms memoria).
El tiempo (y tambin el espacio) que tarda en ejecutarse un programa en una mquina concre-
ta depende de:
El tamao de los datos de entrada
El valor de los datos de entrada (la mayor o menor dificultad que entrae el procesamien-
to 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 mi-
tad); 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 proce-
samiento 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 supe-
rior; o analizar el caso promedio, para lo cual debemos estudiar las posibles entradas y su proba-
bilidad.
Definimos el coste temporal del algoritmo A con entrada x
&
, que notaremos
t
A
( 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
T
A
(n)
como
Diseo de algoritmos iterativos 79
T
A
(n) =
def
max{ t
A
( x
&
) | x
&
de tamao n }
Definimos la complejidad de A en el caso promedio, que notaremos
TM
A
(n)
como
TM
A
(n)=
def _
n tamao de x
p( x
&
) t
A
( x
&
)
siendo p( x
&
) la funcin de probabilidad (p( x
&
) e [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 tiem-
po 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 T
A
(n) exacta, sino que nos bastar con en-
contrar otra funcin f(n) que sea del mismo orden de magnitud; para que as podamos decir
que para n grande T
A
(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=V.N1}
var
I,J:Ent;
inicio
b=cierto;
paraIdesde1hastaN1hacer
paraJdesdeI+1hastaNhacer
b:=bAND(v(I,J)=v(J,I))
fpara
fpara
fvar
{Post:v=V.bi,j:1i<jN:v(i,j)=v(j,i)}
Si tomamos como instrucciones significativas las asignaciones y tomamos a N como tamao
de los datos iniciales tenemos:
T
A
(n) = 1 + i : 1 i N-1 : N-i
Diseo de algoritmos iterativos 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 +
2
) 1 ( 1 + N
* (N1)
= 1 +
2
) 1 ( * N N
N
2
para todo N 1
y podemos decir por tanto que el coste del algoritmo A es el del orden de magnitud de N
2
.
1.4.2 Medidas asintticas de la complejidad
Una medida asinttica es una agrupacin de funciones que muestra un comportamiento simi-
lar 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 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 e R
+
, n
0
e N tales que, para todo n n
0
, T(n) c f(n) }
Si T e O(f) decimos que T(n) es del orden de f(n) y que f es asintticamente una cota su-
perior 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
O(f)
(omega mayscula, que abreviaremos llamndola omega grande) como el conjunto
Diseo de algoritmos iterativos 81
{ T | existe c eR
+
, n
0
e N tales que, para todo n n
0
, T(n) c f(n) }
Si T e O(f) podemos decir que f es asintticamente una cota inferior del crecimiento de T.
Las funciones de O(f) crecen con igual o mayor rapidez que f.
(En los apuntes de Mario se establece una distincin entre O

(f) y O(f).)
Definimos la medida exacta f, que notaremos
O(f)
(theta mayscula, que abreviaremos Theta grande) como el conjunto
{ T | existen c
1
, c
2
e R
+
, n
0
e N, tales que, para todo n n
0
, c
1
f(n) T(n) c
2
f(n)
Si T e O(f) decimos que T(n) es del orden exacto de f(n).
Veamos algunos ejemplos:
f es O(f)
(n+1)
2
es O(n
2
) con c=4, n
0
=1
3n
3
+2n
2
es O(n
3
) con c=5, n
0
=0
p(n) es O(n
k
) para todo polinomio P de grado k
3
n
no es O(2
n
). Si lo fuese existira c e R
+
, n
0
e N tales que
n n
0
: 3
n
c 2
n
n n
0
: (3/2)
n
c
falso
pues lim
n

(3/2)
n
=
f es de O(f)
n es de O(2n) con c = , n
0
=0
(n+1)
2
es de O(n
2
) con c = y n
0
=1 (tambin valdra con c=1)
P(n) e O(n
k
) para todo polinomio P de grado k
f es O(f)
2n es O(n) con c
1
=1, c
2
=2 y n
0
=0
(n+1)
2
es O(n
2
) con c
1
=1, c
2
= 4, n
0
=1
P(n) es O(n
k
) para todo polinomio P de grado k
Diseo de algoritmos iterativos 82
En estos ejemplos se pueden comprobar que
O(f) = O(f) O(f)
o lo que es igual
O(f) O(f) . O(f) (ejercicio 53.g)
1.4.3 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 apli-
cadas a funciones concretas como rdenes. Algunos rdenes tienen especial inters porque la fun-
cin de referencia es simple y su comportamiento se puede estudiar fcilmente. Estos rdenes
tienen nombres particulares:
O(1) orden constante
O(log n) orden logartmico
O(n) orden lineal
O(n log n) orden cuasi-lineal
O(n
2
) orden cuadrtico
O(n
3
) orden cbico
O(n
k
) orden polinmico
O(2
n
) orden exponencial
donde usamos log n para referirnos a log
2
n.
Reciben los mismos nombres las otras medidas asintticas (O, O) aplicadas sobre las mismas
funciones de referencia; aunque nosotros nos ocupamos fundamentalmente de la cota superior,
debido a que nos fijaremos casi siempre en el caso peor, y, dentro de esos supuestos pesimistas,
lo que nos interesa es la cota superior.
Se puede demostrar (en el libro de Joaqun y Mario se sugiere cmo) que se cumplen las si-
guientes relaciones de inclusin entre rdenes de complejidad (jerarqua de rdenes de compleji-
dad):
O(1) c O(log n) c O( n ) c O(n) c O(n log n) c
O(n
2
) c O(n
3
) c c O(n
k
) c
O(2
n
) c O(n!)
Diseo de algoritmos iterativos 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 pue-
de 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:
T
A
e O(log n)
T
B
e O(2
n
)
0
5
10
15
20
25
30
35
0 2 4 6 8 10 12
Serie1
Serie2
tenemos por tanto que
T
A
(n) c log n para n n
0
T
B
(n) c 2
n
para n n
1
podemos hacer algunas observaciones comparativas sobre estos dos algoritmos
Si c >> c entonces B puede ser ms rpido que A para datos pequeos
Diseo de algoritmos iterativos 84
0
50000
100000
150000
200000
250000
300000
350000
0 100 200 300 400 500 600
Serie1
Serie2
comparacin entre n
2
y 10.000 log n
A puede consumir ms memoria que B
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 al doblar el tamao de los datos solo se suma 1 al tiempo
2 log n = log n
2
al doblar la velocidad los datos aumentan al cuadrado
Para B aumentar poco el tamao de los datos aumenta mucho el tiempo necesario; y
aumentar mucho el tiempo disponible (la velocidad de la mquina) aumenta poco el
mximo tamao tratable.
2
2n
= (2
n
)
2
duplicar el tamao de los datos eleva al cuadrado el tiempo necesario
2 2
n
= 2
n+1
duplicar el tiempo slo aumenta en 1 el mximo tratable
Nos remitimos a la tabla que presentamos al principio del tema, donde el consumo se refie-
re 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 da-
tos 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
muy eficiente si su complejidad es de orden log n
eficiente si su complejidad es de orden n
k
(polinmico)
Diseo de algoritmos iterativos 85
ineficiente si su complejidad es de orden 2
n
Y decimos que
Un algoritmo es tratable si tiene un algoritmo que lo resuelve con complejidad de orden me-
nor que 2
n
. 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 Mtodos de anlisis de algoritmos iterativos
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 conside-
raciones 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 al-
gortmico.
Antes, presentamos un par de reglas que sirven de utilidad para analizar los algoritmos.
Si T
1
(n) e O(f
1
(n)) y T
2
(n) e O(f
2
(n)), se cumplen las siguientes propiedades
Regla de la suma
T
1
(n) + T
2
(n) e O( max( f
1
(n),f
2
(n) ) )
Regla del producto
T
1
(n) * T
2
(n) e O( f
1
(n) f
2
(n) )
Demostracin
Por la definicin de O se tiene:
existen c
1
, c
2
e R
+
; n
1
, n
2
e N tales que
n n
1
T
1
(n) c
1
f
1
(n) n n
2
T
2
(n) c
2
f
2
(n)
tomando n
0
= max(n
1
, n
2
)
suma
T
1
(n) + T
2
(n) c
1
f
1
(n) + c
2
f
2
(n)
Diseo de algoritmos iterativos 86
(c
1
+c
2
) max( f
1
(n), f
2
(n) )
por lo que para n n
0
se cumple la propiedad
producto
T
1
(n) T
2
(n) c
1
f
1
(n) c
2
f
2
(n)
(c
1
c
2
) ( f
1
(n) f
2
(n) )
por lo que para n n
0
se cumple la propiedad
Vamos a ir viendo ahora cul es la complejidad para cada una de las instrucciones. Esto es una
simplificacin que puede funcionar en muchos casos, pero no en todos.
Si A es
seguir
T
A
(n) e O(1)
x := e <x
1
, , x
r
> := <e
1
, , e
r
>
T
A
(n) e O(1) (excepto si la evaluacin de e, e
i
es compleja, en cuyo caso sera del
orden del mximo de los rdenes de evaluar las expresiones)
A
1
; A
2
y T
Ai
(n) e O(f
i
(n))
T
A
(n)= T
A1
(n)+T
A2
(n)
y por la regla de la suma T
A
e O( max( f
1
(n), f
2
(n) ) )
si B
1
A
1
B
r
A
r
fsi
T
Ai
(n) e O(f
i
(n)) (1 i n)
entonces T
A
e O( max( f
1
(n), , f
r
(n) ) )
siempre que podamos suponer que la evaluacin de las barreras consume tiempo
constante. Si esta suposicin no es razonable y sabemos que
T
Bi
(n) e O(g
i
(n)) (1 i n)
entonces T
A
e O( max( g
1
(n), , g
r
(n), f
1
(n), , f
r
(n) ) )
it B A
1
fit
T
A1
(n) e O(f(n))
T
B
(n) e 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))
Diseo de algoritmos iterativos 87
Si podemos estimar que el nmero de vueltas en el caso peor es O(t(n)), tendre-
mos (usando la regla del producto):
T
A
(n) e O(h(n) t(n))
Generalmente es posible una estimacin ms fina de T
A
(n), estimando por separa-
do 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:
T
A
(n) e 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 ca-
racterstica. 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 T
A
(n) se podr estimar calculan-
do 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 compleji-
dades, 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 caracters-
tica 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
J:=I+1
itJNANDb
b:=bAND(a(I,J)=a(J,I))
J:=J+1
fit;
I:=I+1
fit
{Post.:a=A.(bi,j:1i<jN:a(i,j)=a(j,i))}
devb
ffunc
Diseo de algoritmos iterativos 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(N
2
). 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 N
2
; 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 ins-
trucciones:
cuerpo del bucle interno:
2 (podramos considerar 3 si cada acceso al vector fuese 1)
coste del bucle interno
((NI) (2+1)) + 1 donde 2 es el cuerpo y 1 la condicin; y el otro 1 la condicin de
salida
= 3 (N-I) + 1
coste del cuerpo del bucle interno
1 + (3(N-I) + 1) + 1 = 3(N-I) + 3
coste del bucle externo
[
_

=
1
1
N
I
(3(N-I) + 3 + 1)] + 1 donde el 1 interno es la condicin; y el 1 externo la
condicin de salida
= 3
_

=
1
1
N
I
(N-I) + 4(N-1) + 1 = 3
2
) 1 ( N N
+ 4(N-1) + 1
= 3/2 N
2
3/2 N + 4N 4 +1
= 3/2 N
2
+ 5/2 N 3
y el coste del algoritmo, considerando las dos asignaciones iniciales
T
A
(N) = 3/2 N
2
+ 5/2 N 3 + 2 = 3/2 N
2
+ 5/2 N 1
con lo que T
A
(N) e O(N
2
)
Sin embargo, podemos llegar directamente al mismo resultado razonando sobre la instruccin
caracterstica:
Consideramos como acciones caractersticas las asignaciones que componen el cuerpo del bu-
cle 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:
T(N) e O(
_

=
1
1
N
I
(NI)) = O(N
2
)
Diseo de algoritmos iterativos 89
Como segundo ejemplo consideramos el algoritmo de multiplicacin eficiente del ejercicio
36(b)
varx,y,p:Ent;
{Pre.P:x=X.y=Y.y0}
p:=0;
ity=0
sipar(y)
entonces<x,y>:=<x+x,ydiv2>
sino<p,y>:=<p+x,y1>
fsi
fit
{Post.Q:p=X*Y}
Como tamao de los datos tomamos Y = n
como complejidad del cuerpo del bucle tomamos la complejidad de la estructura condicional,
que, al ser el mximo de la complejidad de las ramas y las barreras, y tener todas complejidad
constante ser O(1). Por tanto la complejidad del bucle ser el nmero de vueltas
T
A
(n) e O(t(n))
La idea es que el y se divide por 2 en una o dos vueltas con lo que la complejidad es de orden
O(log n). Esta idea se extrae de que si en cada pasada por el bucle se disminuye el tamao de los
datos a la mitad, hasta alcanzar el valor 1 entonces la complejidad es logartmica:
si un algoritmo se ejecuta una vez con datos de tamao n, la siguiente con n/2, , hasta llegar
a datos de tamao 1
n, n/2, n/4, , 1 esta serie es equivalente a
n/2
0
, n/2
1
, n/2
2
, , n/2
k
siendo k el nmero de veces que se ha ejecutado el bucle
1 = n/2
k
2
k
= n
k = log n
por lo tanto
si t(n) e O(log n) entonces T
A
(n) e O(log n)
Slo para este caso, vamos a ver con ms detalle la demostracin de que t(n) e O(log n). Pro-
bando con distintos valores de y vemos que el comportamiento del algoritmo es el siguiente:
Diseo de algoritmos iterativos 90
n = 0 : y = 0
n = 1 : y = 1 y = 0
n = 2 : y = 2 y = 1 y = 0
n = 3 : y = 3 y = 2 y = 1 y = 0
n = 4 : y = 4 y = 2 y = 1 y = 0
n = 5 : y = 5 y = 4 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 t(n) = 2 = 2 log(n) (se ejecuta dos veces, para y=2, y para y=1)
n = 3 t(n) = 3 2 log(3)
32log(3)2
3
2
2log(3)
2
3
(2
log3
)
2
2
3
3
2
89cierto

paso inductivo
n > 3, par n = 2n, n 2
t(n) = 1 + t(n)
HI
1 + 2 log(n) se ejecuta 1 vez ms las veces de n
< 2 ( 1 + log(n) )
= 2 log(2 n)
= 2 log( n )
n > 3, impar n = 2n+1, n 2
t(n) = 2 + t(n)
HI
2 + 2 log(n) se ejecuta 1+1 veces ms las veces de n
= 2 ( 1 + log(n) )
= 2 log(2 n)
< 2 log( 2n + 1 )
= 2 log n
1.4.5 Expresiones matemticas utilizadas
_
=
+
+
=
max
min i
min max
max min
i ) 1 (
2
donde (maxmin+1) es el nmero de trminos.
Diseo de algoritmos iterativos 91
1.5 Derivacin de algoritmos iterativos
Una vez que hemos estudiado las tcnicas para verificar la correccin de programas ya escritos
vamos a estudiar las tcnicas que permiten disear programas a partir de la especificacin.
Utilizamos las reglas de verificacin para disear los programas, de forma que para los pro-
gramas obtenidos ya est demostrada su correccin.
Lo principal que aporta este mtodo es que descompone la tarea de la programacin en distin-
tas subtareas, de forma que nos podemos concentrar en distintos aspectos del proceso de diseo
del programa.
1.5.1 El mtodo de derivacin
Se trata de derivar un programa que cumpla la especificacin
{ P } A { Q }
hay que comenzar tomando una decisin acerca de la estructura de A al nivel ms externo. Es
decir, hay que decidir cul de las estructuras disponibles en el lenguaje algortmico es la que co-
rresponde a la accin A: una accin simple, una composicin secuencial, una distincin de casos,
o una repeticin. Vamos a ir considerando cada caso
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 precondi-
cin es una igualdad de la forma
x = E
donde E es una expresin que se puede obtener mediante operaciones predefinidas o es-
pecificadas.
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 A
1
y A
2
que cumplan
{ P } A
1
{ R } y { R } A
2
{ Q }
Composicin alternativa
Diseo de algoritmos iterativos 92
Usamos esta opcin cuando decidimos que los datos de entrada no admiten un trata-
miento uniforme para obtener la postcondicin.
Deberemos encontrar condiciones B
1
, , B
n
que discriminen los casos homogneos,
de tal forma que se cumpla
P B
1
v v B
n
Y entonces, para cada condicin B
i
deberemos derivar las acciones A
i
que verifiquen
{ P . B
i
} A
i
{Q } para i= 1, , n
Y as, el algoritmo resultante ser de la forma:
A si B
1
A
1
B
n
A
n
fsi
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
La derivacin de un bucle se hace a travs de los siguientes pasos:
B.1. Invariante
Obtenemos el invariante a partir de la postcondicin.
B.2. Condicin de repeticin
Con el invariante y la postcondicin obtenemos las condicin de terminacin
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
Diseo de algoritmos iterativos 93
Considerando I, B se construye una expresin de acotacin C de modo que
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, en-
tonces es necesario derivar una accin de inicializacin que establezca el invariante.
Esta accin se deriva a partir de la especificacin
{P } A
0
{ I }
B.6. Accin de avance.
Se deriva una accin A
2
que haga avanzar al bucle hacia su terminacin. Se puede derivar
a partir de la especificacin
{ I . B . C = T } A
2
{ 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, debe-
mos demostrar
{ B . I } A
2
{ 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 A
1
que se encargue de restablecer el invariante dentro del bucle, de forma
que
{ I . B } A
1
; A
2
{ I }
la accin A
1
se puede derivar de la especificacin
{ I . B } A
1
{ R }
donde R pmd( A
2
, I )
B.9. Terminacin.
Se comprueba que despus de introducir la accin A
1
se sigue cumpliendo
{ I . B . C = T } A
1
; A
2
{ c % T }
Si se comprueba, entonces hemos acabado de obtener un algoritmo correcto
El esquema general resultante de la derivacin de un bucle es de la forma:
Diseo de algoritmos iterativos 94
{P}
varx
1
:t
1
;;x
n
:t
n
;
inicio
A
0

{I;C}
itB
{I.B}
A
1
;
{R}
A
2
;
{I}
fit;
{I.B}
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 equivoca-
mos, tenemos puntos bien definidos a los que volver.
1.5.2 Dos ejemplos de derivacin
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 susti-
tuir constantes por variables en las postcondicin.
Hay otra recomendacin general en cuanto a la construccin de invariantes a partir de una es-
pecificacin:
Si la precondicin y la postcondicin del bucle contienen una igualdad x=X, el invariante tam-
bin 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.
Diseo de algoritmos iterativos 95
Obtencin del invariante por relajacin de una conjuncin en la
postcondicin
El razonamiento, como ya indicamos se basa en considerar la condicin i.3 de la verificacin
de bucles
I . B Q
y preguntarnos si no ser
I . B Q
pudindose as obtener el invariante y la condicin de terminacin directamente o con alguna
reformulacin de la postcondicin.
El ejemplo que vamos a derivar es la divisin entera y el cociente de dos nmeros enteros po-
sitivos.
var x, y, c, r : Ent;
{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, repe-
timos la resta de y.
c veces

x y y y = r
Una vez determinado que nuestra algoritmo va a ser un bucle, aplicamos los pasos de la deri-
vacin 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 inva-
riante 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 ge-
neral, mejor que sobre, siempre y cuando las condiciones que incluimos en el invariante sean
Diseo de algoritmos iterativos 96
fciles de conseguir a partir de la precondicin; o como, en este caso, aparezcan explcitamente en
la precondicin. Pensemos, que el invariante ha de ser lo bastante dbil para que se pueda obte-
ner de la precondicin, y lo bastante fuerte que, junto con la condicin de terminacin, implique
a la postcondicin. Por lo tanto, todo lo que sea reforzar el invariante con condiciones que apare-
cen en la precondicin es, en principio, beneficioso. En este caso, la condicin y>0 es necesaria
para demostrar que la expresin de acotacin (r) es decrementable; podra ser al intentar demos-
trar este punto cuando nos disemos cuenta de la necesidad de incluirla. En cualquier momento
podemos incluir reforzamientos del invariante, sin que ello afecte a una gran parte de la demos-
tracin; tan slo afectar a la demostracin de P I, aunque no sea ste el caso, pues estamos
reforzando el invariante con una condicin que aparece en la precondicin.
Para reconocer que este es un invariante vlido debemos ya tener en la cabeza la forma bsica
del bucle, y darnos cuenta de que vamos a ir aumentando c, 1 a 1 en cada pasada, y vamos a ir
restndole y al valor de r; con lo que en todo momento se cumple la relacin indicada: x = y*c+r.
B.2. Condicin de repeticin
Con el razonamiento anterior la condicin de terminacin
B : r < y
y probamos que efectivamente se cumple
I . B Q
I.B x=X.y=Y.x=c*y+r.r0.y>0.r<y
x=X.y=Y.x=c*y+r.0r<y
Q
y por tanto, la condicin de repeticin
B : r y
B.3. Definicin de la condicin de repeticin
Comprobamos si
I def(B)
def(ry)
cierto
I
ya que las variables estn declaradas
B.4. Expresin de acotacin
Fijndonos en B (r y ) y considerando que r va a ir decreciendo en cada pasada por el
bucle, tomamos r como expresin de acotacin
C : r
para la que debemos probar
I . B def(C) . dec(C)

def(r)
cierto
: I.B
Diseo de algoritmos iterativos 97

dec(C)
r>0
: ry.y>0
: I.B
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.
B.5. Accin de inicializacin.
Observamos que no se puede deducir el invariante de la precondicin, y que se hace ne-
cesario 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
B.6. Accin de avance.
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
B.7. Es suficiente con la accin de avance?
Nos preguntamos si la accin de avance constituye el cuerpo del bucle. Para ello, debe-
mos demostrar
{ B . I } r := ry { I }
def(ry).I[r/ry]
cierto.x=X.y=Y.x=c*y+(ry).y>0.ry0
x=X.y=Y.x=(c1)*y+r.y>0.ry
tenemos entonces
I x = X . y = Y . y > 0
B r y
pero no podemos demostrar
I . B x = (c1)*y + r
Diseo de algoritmos iterativos 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
B.8. Restablecimiento del invariante
{ I . B : x = X . y = Y . x = c*y + r . r 0 . y > 0 . r y }
A
1
{ 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
{ I . B . r = T }
c := c +1;
r := r y;
{ r < T }
informalmente se puede afirmar que se sigue verificando pues la accin de restablecimien-
to 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 = X . y = Y . y > 0 . x 0 }
<c,r> := <0,x>;
{ I : x = X . y = Y . x = c*y + r . r 0 . y > 0
C : r }
it r y
{ I . r y }
c := c + 1;
{ x = X . y = Y . x = (c1)*y + r . r y . y > 0 }
r := r y;
Diseo de algoritmos iterativos 99
{ I }
fit
{ I . r < y }
{ Q : x = X . y = Y . x = y * c + r . 0 r < 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
Esta es la segunda directriz general acerca de la obtencin de invariantes. Es tpica en bucles
que construyen algo, y en bucles sobre vectores; sustituimos una constante por una variable
que va indicando el avance del bucle. Esta tcnica es recomendable cuando existen asertos o ex-
presiones extendidos ( , -, , H, max, min, #) cuyos asertos de dominio incluyen constantes, o
variables que se supone que no se modifican en el bucle.
Como consejo general, el invariante debe contener un aserto que indique el rango de las nue-
vas variables. Este aserto nos ser til para demostrar la terminacin del bucle.
Al obtener un invariante por reemplazamiento de constantes, todas las variables nuevas deben
tener un aserto que indique su rango de posibles valores.
Vamos a derivar un algoritmo para el clculo del producto escalar de dos vectores.
cte N = ; % Nat
var u, v : Vector [1..N] de Real;
r : Real;
{ P : v = V . u = 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 conser-
vamos los asertos de conservacin de valores que aparecen tanto en la precondicin co-
mo 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 ex-
presin de acotacin puede decrecer.
Diseo de algoritmos iterativos 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
B.2. Condicin de terminacin
termina cuando j alcanza el valor N, segn se indica en la postcondicin
B : j = N
probamos I . B Q
I.B
r=i:1ij:u(i)*v(i).u=U.v=V.1jN.j=
N
r=i:1iN:u(i)*v(i).u=U.v=V.1NN.j=
N
r=i:1iN:u(i)*v(i).u=U.v=V
Q

la condicin de repeticin queda entonces


B : j = N
B.3. Definicin de la expresin de repeticin
debemos probar si I def(B)
def( j = N ) falso
pues la variable j no est declarada. Debemos pues incluir una declaracin local de varia-
bles que incluya al algoritmo que estamos derivando
var j : Ent;
inicio
A
fvar
en cuyo caso
def(j = N) cierto : I
B.4. Expresin de acotacin
j va a ir aumentando hasta alcanzar el valor de N (condicin de terminacin), para que la
expresin de acotacin decrezca en cada pasada, tomamos:
C : N j
Probamos que
I . B def(C) . dec(C)
def(Nj)
cierto
: I.B

Diseo de algoritmos iterativos 101


dec(Nj)
Nj>0
N>j
: 1jN.j=N:I.B

B.5. Accin de inicializacin


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 iniciali-
zacin. 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=U.v=V.0=i:1i0:u(i)*v(i).10N

el anterior aserto es falso, pues lo es 1 0 N, cambiamos el invariante y tomamos


I : U = U . v = V . r = i : 1 i j : u(i) * v(i) . 0 j N
esto no invalida las demostraciones hechas hasta ahora, y nos permite seguir con la verifi-
cacin de la accin de inicializacin
pmd(<r,j>:=<0,0>,I)
u=U.v=V.0=i:1i0:u(i)*v(i).00N
u=U.v=V.0=0.0N
:u=U.v=V.N1P
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.
B.6. Accin de avance
Como tenemos que hacer que j se acerque a N parece evidente que la accin de avance
debe ser
A
2
: 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])
cierto.N(j+1)<T
Nj<T+1
: Nj=T:I.B.Nj=T
Diseo de algoritmos iterativos 102
B.7. Comprobamos si es suficiente con la accin de avance
la pmd de la accin de avance inducida por el invariante
I[j/j+1]
u=U.v=V.r=i:1ij+1:u(i)*v(i).0j+1N
u=U.v=V.r=i:1ij:u(i)*v(i)+u(j+1)*v(j+1).
1jN1

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
B.8. Restablecimiento del invariante.
Tenemos que obtener una accin
{ I . B } A
1
{ R }
lo nico que no se puede obtener de R a partir de I . B es el trmino adicional en el su-
matorio; la solucin es una asignacin a r
r := r + u(j+1)*v(j+1)
que efectivamente se puede demostrar que verifica la condicin
B.9. Comprobar que se conserva la terminacin del bucle
{ I . B . Nj = T } A
1
; A
2
{ Nj < T }
intuitivamente se ve que se mantiene porque A
1
no afecta a j. Tambin se podra demos-
trar.
Con todo esto el algoritmo anotado queda:
const N = ; % Ent
var u, v : Vector[1..N] de Real;
r : Real;
{ P : u = U . v = V . N 1 }
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 : N j }
it j = N
{ I . j = N }
r := r + v(j+1) * u(j+1);
{ R }
j := j + 1
{ I }
Diseo de algoritmos iterativos 103
fit
{ I . j = 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 T
A
(n) es O(n) pues el algoritmo accede, en cualquier caso, a
todas las componentes del vector. Y de las dos afirmaciones anteriores, tenemos que T
A
(n) e
O(n).
Podemos razonar tambin que cualquier algoritmo que resuelva este problema ha de ser O(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 Introduccin de invariantes auxiliares por razones de eficiencia
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 normal-
mente 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 ms de un bucle por haber diseado la accin de inicializacin como un bucle
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 ex-
presiones simples.
Diseo de algoritmos iterativos 104
Para resolver el segundo problema en muchas ocasiones es til la tcnica de introducir un in-
variante auxiliar.
En un punto de la derivacin, normalmente al intentar restablecer el invariante, nos encon-
tramos con que tenemos que obtener el valor de una expresin compleja, tpicamente, una expre-
sin 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:
I
1
: y = E
con lo que el invariante quedara
I : I
0
. I
1
Con este nuevo invariante ser necesario rehacer una parte de la derivacin, aunque otras par-
tes pueden seguir siendo vlidas, teniendo en cuenta que el nuevo invariante ser un fortaleci-
miento del antiguo.
Veamos un ejemplo donde se necesita el uso de un invariante auxiliar: el clculo de los nme-
ros 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 = N . n 0 }
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 espe-
cificacin se complementa con la definicin recursiva de la funcin, que hemos escrito ms arri-
ba, 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.
B.1. Obtencin del invariante
Diseo de algoritmos iterativos 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
I
0
: x = fib(i) . n = N . 0 i n
(lo llamamos I
0
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
I
0
. B Q
de ah que la condicin de repeticin quede
B : i = n
B.3. Definicin de la condicin de repeticin
La condicin de repeticin no est definida porque la variable i no est declarada. Inclui-
mos 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 = n
B.4. Expresin de acotacin.
Fijndonos en la condicin de terminacin, i = n, y considerando que i debe ir incre-
mentndose tomamos como expresin de acotacin
C : n i
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
I
0
. B def(C) . dec(C)
def(ni)
cierto
: I
0
.B

dec(ni)
ni>0
n>i
: 0in.i=n:I
0
.B
B.5. Accin de inicializacin
Diseo de algoritmos iterativos 106
Observamos que el invariante no se puede obtener de la precondicin y que es necesaria
una accin de inicializacin
{ n = N . n 0 }
A
0
{ n = N . x = fib(i) . 0 i n }
como siempre intentamos que en la inicializacin se haga el menor trabajo posible. Esto
se consigue con
<x,i> := <0,0>
que cumple la especificacin anterior.
B.6. Accin de avance
Como queremos ir obteniendo los sucesivos nmeros de Fibonacci hasta alcanzar fib(n),
vamos a hacer que i vaya incrementndose de 1 en 1:
A
2
: i := i +1
Demostramos que efectivamente esta accin de avance hace avanzar el bucle
{ I
0
. B . Ni = T } i := i +1 { Ni < T }
que efectivamente podemos demostrar
B.7. Es suficiente con la accin de avance?
Se debera cumplir
{ I
0
. B } i := i +1 { I
0
}
o lo que es lo mismo
I
0
. B I
0
[i/i+1] x = fib(i+1) . 0 i + 1 n . n = N
pero no se puede demostrar
I
0
. B x = fib(i+1)
B.8 Restablecimiento del invariante
Tenemos
{ n = N . x = fib(i) . 0 i n . i = n }
A
1
{ 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 es-
cribiera]
lo que hacemos es aadir un invariante auxiliar, exigiendo que el bucle vaya calculando
tambin el valor de fib(i+1)
I
1
: y = fib(i+1)
Diseo de algoritmos iterativos 107
con lo que el nuevo invariante queda
I I
0
. I
1
n = N . x = fib(i) . 0 i n . y = fib(i+1)
y con el nuevo invariante reiniciamos la derivacin:
B.2. La condicin de repeticin es la misma y se sigue cumpliendo
I . B Q
ya que I es un fortalecimiento de I
0
.
B.3. La condicin de repeticin sigue estando definida
I I
0
def(B)
B.4. La expresin de acotacin sigue siendo la misma, y se siguen cumpliendo sus propie-
dades.
I . B def(C) . dec(C)
B.5. Es necesario modificar la accin de inicializacin.
Por una parte es necesario declarar la variable local y
var i, y : Ent;
inicio
A
fvar
La accin de inicializacin, aceptando que inicializamos i a 0
{ n = N . n 0 }
inicializacin
{ n = N . x = fib(0) . 0 0 n . y = fib(1) }
Lo cual se puede derivar como una asignacin mltiple
A
0
: <i,x,y> := <0,0,1>
y demostrar que efectivamente es correcto { P } A
0
{ I }
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.
B.7. De nuevo comprobamos que no es suficiente con la accin de avance.
B.8. Restablecimiento del invariante
{ n = N . x = fib(i) . 0 i n . y = fib(i+1) . i = n }
A
1
{ 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
Diseo de algoritmos iterativos 108
B.9. Demostramos que con la accin de restablecimiento del invariante se sigue cum-
pliendo la terminacin del bucle.
Informalmente podemos argumentar que la expresin de acotacin (ni) no se ve afecta-
da por la accin A
1
.
Finalmente, el algoritmo anotado nos queda
var n, x : Ent;
{ n = N . n 0 }
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 : n i }
it i = n
{ I . i = n }
<x,y> := <y,x+y>;
{ I[i/i+1] }
i := i +1
{ I }
fit
{ I . i = n }
fvar
{ n = N . x = fib(n) }
Este ejemplo no nos debe hacer pensar que siempre es posible eliminar los bucles anidados in-
troduciendo invariantes auxiliares. Es necesario que el bucle externo sea realmente capaz de ob-
tener 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.
1.6 Algoritmos de tratamiento de vectores
Vamos a tratar en este apartado un conjunto de algoritmos muy habituales sobre vectores. Es-
tos algoritmos son muy utilizados porque representan operaciones habituales sobre colecciones
de datos y los vectores son la estructura de datos ms habitual para almacenar colecciones de
datos de un mismo tipo.
Los vectores se caracterizan porque tienen tamao fijo, es necesario determinar en el mo-
mento de crearlos cul es su tamao (aunque esto no es cierto en lenguajes ms modernos, donde
los vectores se crean en ejecucin, pudindose determinar entonces el tamao; de esta forma es
Diseo de algoritmos iterativos 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 direc-
to 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 colec-
ciones de datos que soslayan los inconvenientes de los vectores, a costa de sacrificar el acceso
directo, susituyndolo por acceso secuencial.
tamao fijo
acceso directo todo el vector en memoria principal
Casi todos los algoritmos de tratamiento de vectores se ajustan a uno de los dos siguientes es-
quemas
Recorrido. Se procesan todos los elementos del vector
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
El esquema de recorrido tiene la siguiente estructura
18. Seleccionar la primera componente a tratar
19. Mientras no se hayan tratado todas las componentes
2.1 Procesar componente
2.2 Seleccionar la siguiente componente a tratar
20. Tratamiento final
Vamos a escribir el algoritmo de recorrido en funcin de una accin genrica tratar
var
v:Vector[1..N]deelem;
%otrasdeclaraciones
{P:v=V.N1}
n:=0;
{I:0nN.i:1in:tratado(v(i))
Diseo de algoritmos iterativos 110
C:Nn}
itn=N
tratar(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 post-
condicin 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 componen-
tes, lo habitual es implementar el recorrido como una funcin o un procedimento segn corres-
ponda.
Algunas variaciones sobre este esquema bsico:
Recorrido de un subvector. Lo habitual es implementar el recorrido como un procedi-
miento o una funcin que reciba como parmetro los lmites del subvector a recorrer.
Recorrido de posiciones no consecutivas. Es aconsejable disponer de una funcin que
nos permita obtener el siguiente ndice a partir del actual (accin de avance).
Forma del resultado y los argumentos.
El resultado es un valor que se obtiene a partir del vector de entrada, que no se mo-
difica (p.e. obtener el mximo de las componentes)
El resultado es el vector de entrada modificado (p.e. todas las componentes a 0)
El resultado es otro vector obtenido a partir del vector de entrada (p.e. copia de un
vector).
Se recorre ms de un vector en paralelo (p.e. producto escalar)
1.6.2 Bsqueda
Bajo esta categora encontramos a un gran nmero de algoritmos sobre vectores. El esquema
general tiene la siguiente forma:
Diseo de algoritmos iterativos 111
21. Seleccionar la primera componente donde buscar
22. Mientras no se termine y no sea encontrada la propiedad
2.1. Comprobamos si la componente verifica la propiedad
2.1.1 Si es as, ha sido encontrada
2.1.2 Si no es as, seleccionamos la siguiente componente donde buscar
23. Tratamiento final
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;
{P
0
:v=V.N1}
inicio
<pos,encontrado>:=<1,P(v(1))>;
{I:v=V.1posN.i:1i<pos:NOTP(v(i)).encontrado
P(v(pos))
C:Npos
}
itpos=NANDNOTencontrado
encontrado:=P(v(pos+1));
pos:=pos+1
fit;
{Q
0
:v=V.1posN.i:1i<pos:NOTP(v(i)).encontrado
P(v(pos)).
(pos=Nvencontrado)
}
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 .
Es necesario sacar la comprobacin de la primera componente fuera del bucle para que el in-
variante est definido a la entrada pues si inicializsemos pos=0 y encontrado=falso tendramos
encontradoP(v(0))
Donde P(v(0)) no est definido porque no lo est v(0).
Podramos escribir el bucle sin utilizar la variable auxiliar encontrado pero eso plantea dos pro-
blemas:
Diseo de algoritmos iterativos 112
hace necesario evaluar una vez ms P(v(pos)) para comprobar si hemos acabado por lle-
gar al final o por que se cumple la propiedad. Si la comprobacin de la propiedad es cos-
tosa 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 componen-
te fuera del bucle:
funcbuscarVector(v:Vector[1..N]deelem)devencontrado:Bool;pos:
Ent;
{P
0
:v=V.N1}
inicio
pos:=1;
{I:v=V.1posN.i:1i<pos:NOTP(v(i))
C:Npos}
itpos=NANDNOTP(v(pos))
pos:=pos+1
fit;
encontrado:=P(v(pos));
{Q
0
:v=V.1posN.i:1i<pos:NOTP(v(i)).encontrado
P(v(pos)).
(pos=Nvencontrado)
}
dev<encontrado,pos>
ffunc
La complejidad en el caso peor es O(n).
Este algoritmo se puede derivar de manera muy sencilla. El invariante se obtiene directamente
de la postcondicin, separando el efecto de la asignacin (encontrado P(v(pos))) y la condi-
cin de terminacin que tambin aparece explcitamente en la postcondicin (pos=N v encon-
trado). En el segundo esquema la propia accin de avance restablece el invariante. En el otro
ejemplo es necesario incluir una asignacin a encontrado.
Bsqueda secuencial con centinela
Es una variacin de la bsqueda secuencial donde nos aseguramos de al menos un elemento
del vector cumple la propiedad; normalmente el ltimo del vector. De esta forma simplificamos
la condicin de repeticin del bucle pues no tenemos que preocuparnos por si nos salimos de los
lmites del vector.
funcbuscarVector(v:Vector[1..N]deelem)devencontrado:Bool;pos:
Ent;
Diseo de algoritmos iterativos 113
{P
0
:v=V.1MN.P(v(M))}
inicio
pos:=1;
{I:v=V.1posM.i:1i<pos:NOTP(v(i))
C:Mpos}
itNOTP(v(pos))
pos:=pos+1
fit;
encontrado:=pos=M;
{Q
0
:v=V.1posM.i:1i<pos:NOTP(v(i)).P(v(pos)).
encontradopos=M}
dev<encontrado,pos>
ffunc
El coste de este algoritmo es O(M).
Para derivarlo tenemos que el invariante aparece explcitamente en la postcondicin (i :
1i<pos : NOT P(v(i))) as como la condicin de terminacin (P(v(pos))), y el efecto de la asig-
nacin (encontrado pos = M).
La bsqueda con centinela es til por ejemplo en una bsqueda dentro de un vector ordenado
donde la condicin de parada ser
v(pos)x
Si el valor del centinela es el mayor posible dentro del correspondiente dominio, entonces sa-
bemos que siempre se cumplir la condicin.
Tambin se puede utilizar el centinela como una marca que indica donde est el final de la par-
te de un vector que contiene informacin. De forma que utilicemos slo las M primeras posicio-
nes 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 pue-
de verificar dentro del dominio correspondiente.
Aplicaciones de la bsqueda secuencial
Muchos algoritmos sobre vectores se pueden expresar utilizando el esquema de bsqueda se-
cuencial con o sin centinela. Algunos ejemplos donde se trata de determinar si uno o ms vec-
tores cumplen una determinada propiedad y lo que se hace es buscar una componente que no
cumpla la propiedad.
Diseo de algoritmos iterativos 114
Determinar si dos vectores son iguales
P : u(pos) = v(pos)
Determinar si una matriz es simtrica
P : v(f,c) = v(c,f)
Determinar si un vector de caracteres es un palndromo (es igual leda de izquierda a de-
recha y de derecha a izquierda).
P : v(pos) = 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.
Bsqueda binaria o dicotmica
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 Q M =(P+Q) div 2
si v(m) x entonces debemos buscar a la derecha de m. En realidad podramos considerar el
caso v(m) = x como un caso especial que nos hace terminar. Sin embargo, para simplificar la de-
rivacin, consideramos que siempre se llega al punto en que q=p+1; planteando el uso de una
variable auxiliar encontrado, como una optimizacin posterior.
P
Q M
Diseo de algoritmos iterativos 115
y si v(m) > x entonces debemos buscar a la izquierda de m
P
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 elemen-
tos del vector o si x es mayor que todos los elementos del vector. Queremos que si no se encuen-
tra 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=V.ord(v).0posN.v(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 in-
mediatamente 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 con-
sidera 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;
{P
0
:v=V.x=X.N1.ord(v)}
{Q
0
:v=V.x=X.i:1ipos:v(i)x.i:pos+1iN:
x<v(i).
0posN}
ffunc
Diseo de algoritmos iterativos 116
Vamos a derivar el algoritmo de bsqueda binaria a partir de esta especificacin
B.1. Invariante
Para obtener el invariante construimos una generalizacin de la postcondicin, teniendo
en cuenta la idea de que vamos a ir acotando el subvector dentro del cual puede estar el
elemento buscado, hasta llegar a un subvector con una sola componente: desde pos a
pos+1. La generalizacin consiste en definir el subvector a explorar como el que va desde
pos hasta el valor de una nueva variable q.
I:v=V.x=X.i:1ipos:v(i)x.i:qiN:v(i)
>x.
0pos<qN+1.ord(v)
La condicin ord(v) es necesario para demostrar la correccin. La condicin pos < q es
necesaria para demostrar la terminacin.
B.2. Condicin de terminacin
Fijndonos en las diferencias entre el invariante y la postcondicin tenemos que la condi-
cin de terminacin ha de ser:
B:q=pos+1

Efectivamente podemos demostrar


I.BQ
0

La condicin de repeticin queda por tanto


B:q=pos+1
B.3. Est definida la condicin de repeticin?
Idef(q=pos+1)

No est definida porque q no est declarada. Aadimos esta nueva variable a las declara-
ciones locales de la funcin
varq:Ent;
B.4. Expresin de acotacin.
Diseo de algoritmos iterativos 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
I.Bdef(C).dec(C)
B.5. Accin de inicializacin
Incialmente el subvector candidato es el vector entero, adems podemos argumentar que
elegimos los valores de pos y q para conseguir que se anulen los correspondientes cuantifi-
cadores universales:
A
0
: <pos,q>:=<0,N+1>;

Con lo cual es trivial demostrar


{P
0
}A
0
{I}
B.6. Accin de avance
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 conside-
ramos 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 reduzca-
mos a la mitad el tamao del subvector a explorar.
A
2
: m:=(pos+q)div2;
y asignamos m a pos o q segn convenga para mantener el invariante:
si
v(m)xpos:=m;
v(m)>xq:=m
fsi

Demostremos ahora el avance del bucle:


{I.B.qpos=T}A
0
{qpos<T}

Para hacerlo tomamos como hiptesis un aserto intermedio de la forma:


{I.B.qpos=T}
m:=(pos+q)div2;
Diseo de algoritmos iterativos 118
{R
1
I.B.qpos=T.m=(pos+q)div2}
si
v(m)xpos:=m;
v(m)>xq:=m
fsi
{qpos<T}
De esta forma es trivial la correccin de la primera asignacin y lo que nos queda es de-
mostrar 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+1.m=(pos+q)div2.q=pos+1
: R
1

y al menos una abierta
v(m)xvv(m)>xcierto:R
1

ahora verificamos cada rama por separado


{R
1
.v(m)x}pos:=m{qpos<T}

qpos<T[pos/m]
qm<T
: qpos=T.pos<m
: qpos=T.pos<q.q=pos+1.m=(pos+q)
div2
: R.v(m)x

{R
1
.v(m)>x}q:=m{qpos<T}

qpos<T[q/m]
mpos<T
: qpos=T.m<q
: qpos=T.pos<q.q=pos+1.m=(pos+q)
div2
: R
1
.v(m)>x

B.7. Es suficiente con la accin de avance?


Diseo de algoritmos iterativos 119
{I.B}
m:=(pos+q)div2;
{R
2
I.B.m=(pos+q)div2}
si
v(m)xpos:=m;
v(m)>xq:=m
fsi
{I}
La verificacin de la primera asignacin es trivial.
La verificacin del condicional
def(v(m)x).def(v(m)>x):R
2

se demuestra de la misma forma que el punto anterior


v(m)xvv(m)>xcierto:R
2

demostramos ahora cada rama

{R
2
.v(m)x}pos:=m{I}

I[pos/m]
v=V.x=X.0m<qN+1.i:1im:v(i)x.
i:qiN:v(i)>x.ord(v)

I v=V.x=X .ord(v)
I i:qiN:v(i)>x
I.m=(pos+q)div2.q=pos+1 0m<qN+1
ord(v).v(m)xi:1im:v(i)x

porlotantoR
2
.v(m)xI[pos/m]

{R
2
.v(m)>x}q:=m{I}

I[q/m]
v=V.x=X.0pos<mN+1.i:1ipos:v(i)x.
i:miN:v(i)>x.ord(v)

I v=V.x=X .ord(v)
I i:1ipos:v(i)x
I.m=(pos+q)div2.q=pos+1 0pos<mN+1
ord(v).v(m)>xi:miN:v(i)>x

porlotantoR
2
.v(m)>xI[q/m]

Diseo de algoritmos iterativos 120


Con todo esto quedara demostrada la correccin del programa, que finalmente ser
funcbuscarVectorBin(v:Vector[1..N]deelem;x:elem)devpos:Ent;
{P
0
:v=V.x=X.N1.ord(v)}
var
q,m:Ent;
inicio
<pos,q>:=<0,N+1>;
{I:x=X.v=V.ord(v).0pos<qN+1.i:1ipos:v(i)
x.
i:qiN:v(i)>x
C:qpos
}
itq=pos+1
m:=(pos+q)div2;
si
v(m)xpos:=m;
v(m)>xq:=m
fsi
fit;
{Q
0
:v=V.x=X.i:1ipos:v(i)x.i: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 com-
ponentes i>pos tendramos el hueco donde insertar x.
Ntese, por ltimo, que habremos encontrado el elemento si pos = 0 y v(pos) = x:
(-i:1iN:v(i)=x)(pos=0.v(pos)=x)
En cuanto a la complejidad, tomando N como tamao de los datos, tenemos que en cada ite-
racin se divide a la mitad la longitud del subvector a considerar, hasta que se llega a un subvec-
tor 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 ele-
mentos son de un tipo ordenado, hemos de obtener una permutacin ordenada (en orden no
decreciente, por ejemplo) del vector original. Formalmente:
Diseo de algoritmos iterativos 121
procordenaVector(esv:Vector[1..N]deelem);
{P
0
:v=V.N1}
{Q
0
:ord(v).perm(v,V)}
fproc

donde los asertos ord(v) y perm(v,V) tienen el siguiente significado:


ord(v)i,j:1i<jN:v(i)v(j)

perm(v,V)i:1iN:(#j:1jN:v(j)=v(i))=
(#j:1jN:V(j)=v(i))
Operacin de intercambio entre posiciones de un vector
Varios algoritmos de ordenacin y, en particular, los que vamos a estudiar en este captulo, ba-
san 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,E
i
,E
j
)
es equivalente a
<v(E
i
),v(E
j
)>:=<v(E
j
),v(E
i
)>
y, a efectos de verificacin es ms cmodo considerar la equivalencia
var
z:elem;
inicio
z:=valor(v,E
i
);
v:=asignar(v,E
i
,valor(v,E
j
));
v:=asignar(v,E
j
,z);
fvar
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 vec-
tor resultante va a ser una permutacin del vector original. Es decir, se puede demostrar
{v=V}
Int(v,E
i
,E
j
)
{perm(v,V)}
Para demostrarlo se utiliza la segunda de las equivalencias que hemos visto ms arriba. Se deja
al lector encontrar esta demostracin.
Diseo de algoritmos iterativos 122
De esta forma, nos despreocupamos de verificar las operaciones de intercambio y obviamos la
condicin perm(v,V) en la postcondicin de los algoritmos de ordenacin.
A los algoritmos de ordenacin que slo modifican el vector por medio de intercambios se les
llama basados en intercambios.
Cota inferior para los algoritmos de ordenacin basados en
intercambios
Tenemos el siguiente resultado sobre la complejidad mnima de los algoritmos de ordenacin
basados en intercambios:
Sea A un algoritmo de ordenacin de vectores basado en intercambios, sea T
A
(n) su compleji-
dad en tiempo en el caso peor, tomando como tamao de los datos n la longitud del vector. Se
verifica:
(a) T
A
(n) e O(n log n)
(b) T
A
(n) e O(n
2
), en el caso de que A slo efecte intercambios de componentes vecinas.
Para demostrar (a) debemos razonar sobre el nmero de intercambios que es necesario realizar
en el caso peor. Realizando un intercambio podemos generar 2 permutaciones distintas (realizar
el intercambio o no realizarlo), con dos intercambios podemos alcanzar 4=2
2
permutaciones, con
tres intercambios 8=2
3
permutaciones, y as sucesivamente, de forma que con t intercambios po-
demos obtener 2
t
permutaciones distintas. En cada iteracin decidimos si hacemos o no el inter-
cambio con lo que escogemos una permutacin y desechamos todas las dems; en el caso peor
deberemos haber escogido una permutacin y haber desechado un nmero de permutaciones
igual o mayor que el nmero de permutaciones total, n!. De esta forma tenemos:
2
t
n!
tlog(n!)
por la frmula de Stirling
tcnlogn
si hemos realizado t operaciones de intercambio entonces la complejidad del algoritmo debe ser
T
A
(n)tcnlogn
y aplicando la definicin de la cota inferior de la complejidad
T
A
(n)eO(nlogn)
El anterior razonamiento puede hacerse en base al nmero de comparaciones o en base al
nmero de intercambios. En el caso peor debemos haber hecho 2
t
n! comparaciones para as
haber considerado todas las permutaciones posibles; pero podemos argumentar, si lo que conta-
mos son las operaciones de intercambio, que el caso peor hemos hecho un nmero de intercam-
bios igual al nmero de comparaciones.
Para demostrar la parte (b) de la anterior proposicin vamos a definir una medida del grado de
desorden de un vector por medio del concepto de inversiones
Diseo de algoritmos iterativos 123
inv(v,i,j)
def
i<j.v(i)>v(j)
tenemos entonces, que un vector estar ordenado si no existen inversiones en l
ord(v)(#i,j:1i<jn:inv(v,i,j))=0
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:nicn
2

(que es el nmero de pares i,j que se pueden definir con i<j)


Si usamos un algoritmo que slo realiza intercambios entre componentes vecinas, a lo sumo
podr deshacer una inversin con cada intercambio (obsrvese que si se hacen intercambios leja-
nos se pueden deshacer ms inversiones de una vez). Eso quiere decir que para deshacer todas las
inversiones en el caso peor tendr que hacer un nmero de intercambios del orden de n
2
, por lo
que
T
A
(n) e O(n
2
)
Algoritmos de ordenacin
Segn los resultados obtenidos en el apartado anterior y considerando su complejidad, tene-
mos dos grandes grupos de algoritmos de ordenacin
Algoritmos de ordenacin de complejidad O(n
2
)
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 eli-
giendo 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 en-
tre vecinos para lograr ordenar el vector.
La implementacin natural del mtodo de insercin consigue complejidad lineal en el caso
mejor, cuando el vector est ordenado. Una implementacin ingeniosa del mtodo de la burbu-
ja consigue complejidad lineal en el caso mejor, cuando el vector est ordenado, y se detiene
cuando el resto del vector ya est ordenado.
Algoritmos de ordenacin de complejidad O(n log n)
Ordenacin rpida (quicksort). Mtodo debido a C. A. R. Hoare. Se aprovecha del acceso
directo de los vectores. La idea consiste en determinar la componente donde corresponde
almacenar un cierto valor del vector y situar en el subvector inferior los valores menores o
iguales y el subvector superior los valores mayores. El problema se reduce entonces a or-
denar los subvectores as obtenidos. Su complejidad es cuadrtica en el caso peor, pero es
O(n log n) en el caso promedio. Requiere un espacio auxiliar de coste O(log n).
Ordenacin por mezcla (mergesort). Se divide el vector en dos partes que, una vez ordena-
das, se mezclan ordenadamente para obtener el resultado final. Su complejidad es cuasi-
lineal, O(n log n), en el caso peor. Requiere un espacio auxiliar de coste O(n).
Diseo de algoritmos iterativos 124
Ordenacin por montculos (heapsort). Debido a J. W. J. Williams. Se trata de organizar los
valores del vector como un montculo, para luego ir extrayendo uno a uno los valores del
montculo e insertndolos al final de la parte ordenada del vector. Su complejidad es O(n
log n) en el caso peor y no requiere espacio auxiliar si se simula el montculo sobre el
propio vector.
La ordenacin rpida y la ordenacin por mezcla se explican en el tema de algoritmos recursi-
vos. 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 im-
plementar 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:
Qord(v)i,j:1i<jN:v(i)v(j)

La derivacin formal:
B.1. Obtencin del invariante
El invariante se obtiene sustituyendo la constante N de la postcondicin por una variable,
y aadiendo una condicin sobre los posibles valores de n
I:i,j:1i<jn:v(i)v(j).1nN

1 n N
Ya ordenado

B.2. Condicin de terminacin
Lo que le falta al invariante para garantizar la postcondicin es
B:n=N

Se prueba
Diseo de algoritmos iterativos 125
I.BQ
B.3 Est definida la condicin de repeticin?
La condicin de repeticin
B:n=N
No est definida porque no est declarada n.
Se aade n a las declaraciones locales.
Idef(B)
B.4. Expresin de acotacin
El valor de n se va a ir acercando paulatinamente a N
C:Nn
Se prueba
I.Bdef(C).dec(C)
B.5. Accin de inicializacin
Una forma de hacer que el invariante sea cierto de modo trivial es inicializar n como 1
A
0
:n:=1
B.6. Accin de avance
A
2
:n:=n+1
Probamos que efectivamente
{I.B.Nn=T}n:=n+1{Nn<T}
B.7. Es suficiente con la accin de avance?
Para ello tendra que cumplirse II[n/n+1] lo cual no es cierto porque estar ordena-
do hasta n no implica estar ordenado hasta n+1
B.8. Restablecimiento del invariante
Diseo de algoritmos iterativos 126
{I.B}
A
1

{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 n+1
>
n-1
n n+1
>
n-1 n-2

Tendramos que aplicar de nuevo el proceso de derivacin de bucles, pero vamos a abre-
viarlo.
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 pro-
blemtico sea mayor o igual que el de su izquierda o el elemento problemtico sea el pri-
mero
i,j:1i<jn+1.j=m+1:v(i)v(j)
.(v(m)v(m+1)vm=0).0mn<N

De esta forma tenemos el invariante y la condicin de terminacin


I
int
:i,j:1i<jn+1.j=m+1:v(i)v(j).0mn<N

La condicin de terminacin.
B
int
:m=0vv(m)v(m+1)

Diseo de algoritmos iterativos 127


Por lo tanto la condicin de repeticin
B
int
:m=0.v(m)>v(m+1)

El problema es que no se cumple


I
int
def(B
int
)

Aparte de que es necesario declarar m, tenemos que no es cierto


0menRango(v,m)

Es decir, cuando el elemento a insertar es menor que todos los de la parte ordenada, te-
nemos 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 esta-
mos haciendo y v(0)>v(1) no est definido porque no existe v(0). Una solucin sera defi-
nir 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
I
int
:i,j:1i<jn+1.j=m+1:v(i)v(j).1mn<N

B
int
:m=1vv(m)v(m+1)

B
int
:m=1.v(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 es-
cribimos un condicional para averiguarlo y actuar en consecuencia.
La expresin de acotacin
C
int
:m

La accin de inicializacin
m:=n

Con lo que tenemos que el subvector v[1..n] est ordenado porque lo garantiza la precon-
dicin I, y que el elemento problemtico es v(m+1).
La accin de avance ser
m:=m1

Diseo de algoritmos iterativos 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 inter-
cambiar 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 aprove-
charse 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.
B.9 Sigue terminando el bucle?
Informalmente podemos ver que s pues la accin de restablecimiento del invariante no
modifica el valor de n.
Con todo esto el algoritmo de ordenacin queda de la siguiente forma:
procordenaVectorInsercin(esv:Vector[1..N]deelem);
{P
0
:v=V.N1}
var
n,m:Ent;
inicio
n:=1;
{I;C}
itn=N
m:=n;
{I
int
;C
int
}
itm=1.v(m)>v(m+1)
Int(v,m,m+1);
m:=m1
fit;
siv(1)>v(2)
entoncesInt(v,1,2)
sinoseguir
fsi;
n:=n+1
fit
{Q
0
:ord(v).perm(v,V)}
Diseo de algoritmos iterativos 129
fproc
En cuanto a la complejidad, el caso peor es cuando el vector est ordenado en orden decre-
ciente, en cuyo caso tenemos O( i : 1 i N1 : i ) = O(N
2
). En el caso mejor, cuando el vec-
tor 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
Formalmente la obtencin de este algoritmo se basa en considerar una formulacin diferente
de la postcondicin:
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, obtendra-
mos 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 introducien-
do la condicin sobre el posible rango de valores:
i:1i<n:(j:i<jN:v(i)v(j)).1nN
1
n-1
N
Ya ordenado
n
Elementos mayores o iguales que los
de la parte ya ordenada
B.2. Condicin de terminacin
B:n=N
y la condicin de repeticin
B:n=N
I.BQ
B.3. Est definida la condicin de repeticin?
Diseo de algoritmos iterativos 130
Hay que aadir n a las declaraciones locales.
Idef(B)
B.4. Expresin de acotacin.
C:Nn
I.Bdef(C).dec(C)

B.5. Accin de inicializacin


A
0
:n:=1
con lo que el invariante se hace trivialmente cierto porque el cuantificador opera sobre un
dominio vaco.
{P}A
0
{I}
B.6. Accin de avance
vamos incrementando n
A
2
:n:=n+1
B.7. Evidentemente no es suficiente con la accin de avance
B.8. Restablecimiento del invariante
la postcondicin a la que hay que llegar (descomponindola y generalizndola para ver de
dnde sale el invariante)
I[n/n+1]i:1i<n:(j:i<jN:v(i)v(j)).
j:n<jN:v(n)v(j)
i:1i<n:(j:i<jN:v(i)v(j)).
v(menor)=mink:nkN:v(k).menor=n
La accin de restablecimiento del invariante debe recorrer el vector entre n y N para en-
contrar el menor de los elementos, intercambindolo entonces con el que ocupa la posi-
cin 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.
I
int
:i:1i<n:(j:i<jN:v(i)v(j)).
v(menor)=mink:nkm:v(k).nmN.nmenorN
La primera parte del invariante hay que incluirla para luego poder obtener la postcondi-
cin I[n/n+1].
B
int
:m=N
C
int
:Nm

La derivacin de esta accin es el ejercicio 77.


Diseo de algoritmos iterativos 131
B.9. Sigue terminando?
Informalmente, el restablecimiento del invariante no afecta a n.
Con todo esto el procedimiento queda:
procordenaVectorSeleccin(esv:Vector[1..N]deelem);
{P
0
:v=V.N1}
var
n,m,menor:Ent;
inicio
n:=1;
{I;C}
itn=N
<m,menor>:=<n,n>;
{I
int
;C
int
}
itm=N
siv(m+1)<v(menor)
entoncesmenor:=m+1
sinoseguir
fsi;
m:=m+1
fit;
Int(v,n,menor);
n:=n+1
fit
{Q
0
:ord(v).perm(v,V)}
fproc
En el caso peor (y en el mejor, pues los bucles siempre se ejecutan hasta el final) este algorit-
mo es de orden O(n
2
). Aunque si contabilizsemos slo los intercambios tenemos una compleji-
dad de O(n).
Mtodo de la burbuja
La postcondicin se escribe de la misma forma que en el mtodo de seleccin.
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 res-
tablecimiento 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)).
Diseo de algoritmos iterativos 132
j:n<jN:v(n)v(j)

Para obtener el invariante sustituimos n por una variable m, con lo que nos queda:
I
int
:i:1i<n:(j:i<jN:v(i)v(j)).
j:m<jN:v(m)v(j).nmN
B
int
:m=n
C
int
:m

Con todo esto el procedimiento queda:


procordenaVectorBurbuja(esv:Vector[1..N]deelem);
{P
0
:v=V.N1}
var
n,m:Ent;
inicio
n:=1;
{I;C}
itn=N
m:=N;
{I
int
;C
int
}
itm=n
siv(m1)<v(m)
entoncesInt(v,m1,m)
sinoseguir
fsi;
m:=m1
fit;
n:=n+1
fit
{Q
0
:ord(v).perm(v,V)}
fproc
La complejidad es, en cualquier caso, de orden O(n
2
) y, a diferencia del mtodo de seleccin, el
nmero de intercambios es tambin O(n
2
). Sin embargo, se puede hacer una optimizacin si ob-
servamos 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
I
1
:bi,j:ni<jN:v(i)v(j)
I
1int
:bi,j:mi<jN:v(i)v(j)
No ponemos doble implicacin en el invariante externo porque al principio de cada pasada
por el bucle externo no sabemos si el resto del vector est ordenado o no, con lo que b es falso
inicialmente. En el invariante interno s podemos poner doble implicacin, porque inicialmente
Diseo de algoritmos iterativos 133
consideramos un subvector vaco, para el cual es cierto que est ordenado, y luego vamos actuali-
zando 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);
{P
0
:v=V.N1}
var
n,m:Ent;
b:Bool;
inicio
<n,b>:=<1,falso>;
{I.I
1
;C}
itn=NANDNOTb
<m,b>:=<N,cierto>;
{I
int
.I
1int
;C
int
}
itm=n
siv(m1)<v(m)
entoncesInt(v,m1,m);b:=falso
sinoseguir
fsi;
m:=m1
fit;
n:=n+1
fit
{Q
0
:ord(v).perm(v,V)}
fproc
De esta forma, la complejidad en el caso peor sigue siendo O(n
2
), pero ahora si el vector est
ordenado la complejidad pasa a ser O(n).
Diseo de algoritmos iterativos 134
1.7 Ejercicios
Asertos en lgica de predicados
1. Supongamos las declaraciones
cte
N=; %entero>1
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) x aparece como componente de v.
(b) x es mayor que todas las componentes de v.
(c) x aparece una sola vez como componente de v.
(d) Todas las componentes positivas de v estn comprendidas entre x e y.
(e) v est ordenado en orden estrictamente creciente.
(f) v est ordenado en orden no decreciente.
(g) v est ordenado en orden estrictamente decreciente.
(h) y es la suma de las componentes positivas de v.
(i) y es el mximo de las componentes negativas de v.
(j) p es el menor ndice de v que contiene el valor x.
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:1sisn:v(i)=x)[n/n+1] *(e)(i:1sisN:x+i)[x/xi]
(b)(i:nsisN:v(i)>0)[n/n1] (f)(i:1sisN:x*i)[x/i+2]
(c)(-i:1sisN:v(i)=x)[x/2*i] *(g)(i:nsisN:x+i)[n/n1]
(d)(i:1sisN:v(i)>x)[i/i*i] (h)(i:1sisn:x*i)[n/n+1]
3. Comprueba que las dos sustituciones siguientes no producen el mismo resultado. Las va-
riables se suponen de tipo entero.
(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-
ciones indicadas y simplifica el resultado.
(a) (x
2
+2*x+1)[x/x+a]
(b)(x
2
y)[x/y+1,y/x1]
(c)(xy+1.yz)[x/x+3*z,y/xy+1]
(d)(-k:1kn:x
2
+y
2
=k
2
)[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.
Diseo de algoritmos iterativos 135
(a)x0 x0.y0
(b)x0vy0 x+y=0
(c)x<0 x
2
+y
2
=9
(d)x1x0 x1
(e)-i:Ent:x=2*i -i:Ent:x=6*i
(f)-i:Ent:x=4*i -i:Ent:x=6*i
6. En este ejercicio, P y Q representan asertos dados, y X representa un aserto incgnita. En
cada caso, encuentra para X la solucin ms fuerte y la solucin ms dbil que cumplan lo
requerido.
(a)XPvQ (b)XvQPvQ
(c)XP.Q (d)X.QP.Q
7. Simplifica, si es posible, los asertos siguientes. Los asertos simplificados deben ser equiva-
lentes a los dados. Las variables se suponen de tipo entero.
(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
indicado en el ejercicio 1, y que las restantes variables son enteras.
(a)def(xdiv(ab)) (b)def(amodb)
(c)def(a+b) (d)def(xdivy+ydivx)
(e)def(v(i)modb) (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) (b)-i:1in+1:v(i)=i
(c)x=i:n1i<N:i*v(i) (d)x=i:n1iN:i+v(i)
(e)x=#i:1in+1:v(i)=0 (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 li-
gadas.
(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 se-
gundos.
(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.
Diseo de algoritmos iterativos 136
(m) Dados dos vectores de enteros, reconocer si uno de ellos es la imagen especular del
otro.
(n) Ordenar un vector de enteros dado.
(o) Calcular el mximo de las sumas de segmentos de un vector de enteros dado (enten-
diendo que los segmentos de un vector son los diferentes subintervalos del intervalo de
ndices del vector).
Reglas de verificacin bsicas
11. Explica operacionalmente el significado de las dos reglas que siguen, y demuestra que se
deducen de las reglas de verificacin bsicas.
{ P
1
} A {Q
1
} { P
2
} A { Q
2
} { P
1
} A {Q
1
} { P
2
} A { Q
2
}
{ P
1
. P
2
} A { Q
1
. Q
2
} { P
1
v P
2
} A { Q
1
v Q
2
}
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:
pmd( A, Falso) Falso
(c) Monotona:
Q
1
Q
2
pmd( A, Q
1
) pmd( A, Q
2
)
(d) Distributividad con respecto a . :
pmd( A, Q
1
. Q
2
) pmd( A, Q
1
) . pmd( A, Q
2
)
(e) Distributividad con respecto a v :
pmd( A, Q
1
v Q
2
) :pmd( A, Q
1
) v pmd( A, Q
2
)
13. Postulemos la existencia de una accin llamada azar que cumpliese la especificacin si-
guiente:
varx:Ent;{cierto}azar{x=0vx=1}
Explica el significado operacional de la especificacin. Puede deducirse de ella que azar
cumpla alguna de las dos especificaciones siguientes?
(a) varx:Ent;{cierto}azar{x=0}
(b) varx:Ent;{cierto}azar{x=1}
Diseo de algoritmos iterativos 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 es-
tado 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, Q
1
v Q
2
) pmd( A, Q
1
) v pmd( A, Q
2
)
En general, este axioma no puede aceptarse como vlido si la accin A es indeterminista.
La accin nula seguir
15. Estudia los enunciados de correccin que siguen. Verifica los que sean vlidos y construye
contraejemplos para los que no lo sean.
(a) varx,y:Ent;{x>0.y>0}seguir{x+y>0}
(b) varx,y:Ent;{x+y>0}seguir{x>0}
(c) varx,y:Ent;{x>0vy>0}seguir{x*y>0}
(d) varx,y:Ent;{x*y<0}seguir{(x>0)(y<0)}
Asignacin
16. Verifica usando la regla de la asignacin:
varx,y:Ent
{x2.2*y>5}x:=2*x+y1{x3>2}
17. Verifica (a), (b) y encuentra contraejemplos para (c), (d).
(a) varx,y,m:Ent;
{x0.y0}m:=x+y{mmax(x,y)}
(b) varx,y,m:Ent;
{x<0.y<0}m:=max(x,y){m>x+y}
(c) varx,y,m:Ent;
{x0.y0}m:=max(x,y){m>x+y}
(d) varx,y,m:Ent;
{x<0.y<0}m:=x+y{mmax(x,y)}
18. Verifica usando la regla de la asignacin:
varx,y:Ent;
{def(e).y=0}x:=e{y=0}
Razona: esto no sera correcto si la evaluacin de e pudiese afectar a la variable y. Concluye: la
regla de la asignacin slo es vlida en general para expresiones cuya evaluacin no cause efectos
colaterales.
19. Verifica usando la regla de la asignacin mltiple:
varx,X,y,Y,p:Ent;
{X*Y=p+x*y.y>0.par(y)}
<y,x>:=<ydiv2,x+x>
{X*Y=p+x*y.y0}
Diseo de algoritmos iterativos 138
20. Calcula en cada apartado la precondicin ms dbil P que satisface la especificacin dada, supo-
niendo la declaracin de variables indicada.
varx,y:Ent;n:Nat;b:Bool;
(a) {P}x:=x+2{x0}
(b) {P}x:=2*x{x99}
(c){P}x:=x*x2{x
2
x+1>0}
(d){P}x:=x2{x=x2}
(e){P}x:=xmod2{x=xmod2}
(f){P}x:=3*x{i:1in:x=6*i}
(g){P}b:=bAND(x>0){b}
(h){P}(x,y):=(y,x){x=X.y=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}
(b) Enunciados falsos, pero que se pueden verificar (incorrectamente), como:
varv:Vector[1..100]deEnt;i,j:Ent;
{v(1)=2.v(2)=2}v(v(2)):=1{v(v(2))=1}
22. Aplicando la regla correcta de asignacin a vectores,
(a) calcula la precondicin ms dbil P que cumple:
varv:Vector[1..100]deEnt;
{P}v(v(2)):=1{v(v(2))=1}
(b) deduce del apartado (a) que:
varv:Vector[1..100]deEnt;
{v(1)=1.v(2)=2}v(v(2)):=1{v(v(2))=1}
Composicin secuencial
23. Usa las reglas de la composicin secuencial y de la asignacin para encontrar asertos interme-
dios adecuados y completar la verificacin siguiente:
varx,y:Ent;
{P:x=X.y=Y}
x:=xy;
{R
1
:}
y:=x+y;
Diseo de algoritmos iterativos 139
{R
2
:}
x:=yx
{Q:x=Y.y=X}
24. Supuesto que x, y sean variables de tipo entero, calcula en cada caso la precondicin ms dbil
que satisface la especificacin.
(a) {P}x:=x*x;x:=x+1{x>0}
(b) {P}x:=x+y;y:=2*yx{y>0}
(c) {P}y:=4*x;x:=x*xy;x:=x+4{x>0}
(d) {P}x:=y;y:=x{x=A.y=B}
25. Lo que sigue demuestra que la composicin secuencial no es conmutativa. Verifica:
(a) varx,y:Ent;
{x=3.y=Y}x:=0;y:=x+y{x=0.y=Y}
(b) varx,y:Ent;
{x=3.y=Y}y:=x+y;x:=0{x=0.y=Y+3}
26. Demuestra que la composicin secuencial es asociativa, razonando que dos enunciados de correc-
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
P y Q cualesquiera, (a) se cumple si y slo si se cumple (b).
(a)varx,y:Ent; (b)varx,y:Ent;
{P}x:=y;y:=x{Q} {P}x:=y{Q}
28. El siguiente algoritmo anotado resuelve el problema de convertir una cantidad entera no
negativa de segundos a horas, minutos y segundos. Completa la verificacin.
vars,m,h:Ent;
{s=S.s0}
<h,s>:=<sdiv3600,smod3600>;
{s+3600*h=S.0s<3600}
<m,s>:=<sdiv60,smod60>
{s+60*m+3600*h=S.0s<60.0m<60}
Variables locales
29. Verifica usando la regla de las variables locales:
varx,y:Ent;
{x=X.y=Y}
varz:Ent;
Diseo de algoritmos iterativos 140
inicio
z:=x;x:=y;y:=z
fvar
{x=Y.y=X}
Composicin alternativa (distincin de casos)
30. Los dos algoritmos siguientes calculan el mximo de dos nmeros enteros. Verifica y com-
para.
(a)varx,y,z:Ent; (b)varx,y,z:Ent;
{x=X.y=Y} {x=X.y=Y}
si sixy
xyz:=y entoncesz:=y
yxz:=x sinoz:=x
fsi fsi
{x=X.y=Y.z=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; (b)varx:Ent;
{cierto} {cierto}
si si
ciertox:=0 x0x:=x+1
ciertox:=1 x0x:=x1
fsi fsi
{x=0vx=1} {x=0}

(c)varx,y:Ent; (d)varx,y,z,p,q,r:Ent;
{cierto} {x=X.y=Y.z=Z}
(x,y):=(y*y,x*x); <p,q,r>:=<x,y,z>;
si si
xyx:=xy p>q<p,q>:=<q,p>
yxy:=yx pqseguir
fsi fsi;
{x0.y0} si
p>r<p,r>:=<r,p>
prseguir
fsi;
si
q>r<q,r>:=<r,q>
qrseguir
fsi
{Perm((p,q,r),(X,Y,Z)).pqr}
Diseo de algoritmos iterativos 141

33. Calcula la precondicin ms dbil P que satisface:


varx:Ent;
{P}
x:=x+1;
si
x>0x:=x2
x=0seguir
x<0x:=x+3
{x1}
34. Dado:
varx:Ent;
{x=X}
si
x=1x:=1
x=1x:=1
fsi
{x=X}
demuestra con un contraejemplo que la especificacin no se cumple, y a continuacin fortale-
ce 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
con respecto a cualquier especificacin Pre/Post:
(a)siB
1
A
1
B
2
A
2
fsi;A
(b)siB
1
A
1
;AB
2
A
2
;Afsi
Composicin iterativa (iteracin)
36. Completa la verificacin de los dos algoritmos de multiplicacin que siguen, ya anotados
con invariante y expresiones de acotacin. Compara.
(a)varx,y,p:Ent; (b)varx,y,p:Ent;
{Pre.P:x=X.y=Y.y0} {Pre.P:x=X.y=Y.y0}
p:=0 p:=0;
{Inv.I:X*Y=p+x*y.y0 {Inv.I:X*Y=p+x*y.y0
CotaC:y} CotaC:y}
ity=0 ity=0
{X*Y=p+x*y.y>0} {X*Y=p+x*y.y>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}
Diseo de algoritmos iterativos 142
37. Considera la especificacin:
varx,y,p:Ent;
{Pre.P:x=X.y=Y.y0}
potencia
{Post.Q:p=X
Y
}
Teniendo en cuenta que la exponenciacin es al producto como el producto es a la suma,
construye y verifica dos algoritmos similares a los del ejercicio anterior, que cumplan esta especi-
ficacin.
38. Construye y verifica un algoritmo de divisin entera que cumpla la especificacin siguiente,
y que utilice solamente operaciones aritmticas de suma y resta.
varx,y,c,r:Ent;
{Pre.P:x=X.y=Y.x0.y>0}
divEnt
{Post.Q:x=X.y=Y.x=c*y+r.0r<y}
39. Calcula la precondicin ms dbil posible P que haga vlida en cada caso la especificacin:
(a)varx:Ent; (b)varx:Ent;
{P} {P}
it it
x=0x:=x1 x=0x:=x2
fit fit
{x=0} {x=0}

(c)varx:Ent;
{P}
itx=0
six>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=X.y=Y.x>Y0}
{Inv.I:x>y0.mcd(x,y)=mcd(X,Y)
CotaC:y}
ity=0
{x>y>0.mcd(x,y)=mcd(X,Y)}
<x,y>:=<y,xmody>
fit
{Post.Q:x=mcd(X,Y)}
41. Verifica usando los invariantes y expresiones de acotacin indicados:
(a)Suma de un vector de enteros (suponemos N 1, cte.).
Diseo de algoritmos iterativos 143
varv:Vector[1..N]deEnt;s:Ent;
{Pre:v=V}
varI:Ent; %variablelocal
(s,I):=(0,1);
{Inv.I:v=V.1IN+1.s=i:1i<I:v(i)
CotaC:NI+1}
itI=N+1
s:=s+v(I);
I:=I+1{I}
fit
fvar
{Post:v=V.s=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=V.1IN+1.
s=i,j:1i<I.1jM:v(i,j)
CotaExt.C:NI+1
}
itI=N+1
J:=1;
{InvInt.J:v=V.1IN.1JM+1.
s=i,j:1i<I.1jM:v(i,j)+
j:1j<J:v(I,j)
CotaInt.D:MJ+1
}
itJ=M+1
s:=s+v(I,J);
J:=J+1{J}
fit;
I:=I+1{I}
fit
fvar
{Post:v=V.s=i,j:1iN.1jM:v(i,j)}
42.Construye y verifica algoritmos para los problemas que siguen, anotando pre- y postcondi-
ciones, invariantes y expresiones de acotacin:
(a) Clculo de la matriz producto de dos matrices dadas.
(b) Clculo del determinante de una matriz dada.
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)
Diseo de algoritmos iterativos 144
(c) Buscar un elemento dentro de un vector.
(d) Intercambiar los contenidos de dos posiciones de un vector. (cfr. Ej. 10)
(e) Ordenar un vector de enteros. (cfr. Ej. 10)
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-
rior, y verifica su correccin con ayuda de la regla de verificacin de asignaciones a vecto-
res.
45. Supongamos un procedimiento acumula que satisfaga la especificacin
procacumula(ess:Ent;ex:Ent);
{Pre.:s=S.x=X}
{Post.:s=S+x.x=X}
fproc
(a) Refuta mediante un contraejemplo:
{suma=S}acumula(suma,2*suma){suma=S+2*suma}

Qu falla al intentar aplicar las reglas de verificacin?


(b) Verifica:
{suma>a*b}acumula(suma,2*a*b){suma>3*a*b}
Funciones
46. Algunos de los procedimientos del ejercicio 43 pueden ser planteados como funciones.
Localzalos y escribe las correspondientes especificaciones.
47. Usando el algoritmo del ejercicio 40, construye una funcin correcta para el clculo del
m.c.d. de dos enteros.
48. Supongamos una funcin doble que satisfaga la especificacin
funcdoble(x:Ent)devy:Ent;
{Pre.:x=X}
{Post.:y=2*x.x=X}
ffunc

(a) Refuta mediante un contraejemplo:


{Cierto}x:=doble(x){x=2*x}

Qu falla al intentar aplicar las reglas de verificacin?


(b) Verifica:
{x=y}x:=y+doble(x){x=3*y}

OJO: Es necesario transformar previamente el planteamiento con ayuda de una va-


riable local, para que sea aplicable la regla de verificacin de llamadas. Queda:
Diseo de algoritmos iterativos 145
{x=y}
varz:Ent;
inicio
z:=doble(x);x:=y+z
fvar
{x=3*y}
Anlisis de algoritmos iterativos
49. El siguiente algoritmo decide si una matriz cuadrada de enteros dada es o no simtrica:
funcesSim(a:Vector[1..N,1..N]deEnt)devb:Bool;
{Pre.:a=A}
varI,J:Ent;
inicio
b:=Cierto;
paraIdesde1hastaN1hacer
paraJdesdeI+1hastaNhacer
b:=bAND(a(I,J)=a(J,I))
fpara
fpara
{Post.:a=A.(bi,j:1i<jN:a(i,j)=a(j,i))}
devb
ffunc
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.
(b) Tomando como acciones caractersticas los accesos a posiciones de la matriz.
50. El siguiente algoritmo de bsqueda decide si un entero dado aparece o no dentro de un
vector de enteros dado:
funcbusca(v:Vector[1..N]deEnt;x:Ent)devencontrado:Bool;
{Pre.:v=V.x=X}
varI:Ent;
inicio
I:=1;
encontrado:=falso;
itINANDNOTencontrado
encontrado:=(v(I)=x);
I:=I+1
fit
{Post.:v=V.x=X.(encontrado-i:1iN:v(i)=x)}
devencontrado
ffunc
Diseo de algoritmos iterativos 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 O, razona.
(a)(n+1)
2
es O(n
2
) (b)3n
3
+2n
2
es O(n
3
)
(c)3
n
no es O(2
n
) (d)n
3
+2n
2
es O(n
3
)
52. Demuestra que si T
1
e O(f
1
) y T
2
e O(f
2
), se tiene:
(a)Regla de la suma: T
1
(n) + T
2
(n) es O(max(f
1
(n), f
2
(n))).
(b)Regla del producto: T
1
(n) * T
2
(n) es O(f
1
(n) * f
2
(n)).
53. Aplicando las definiciones de las notaciones O, O y O, demuestra cada una de las afirma-
ciones siguientes:
(a)f e O(g) . g e O(h) f e O(h).
(b)O(f) _ O(g) f e O(g).
(c)O(f) = O(g) f e O(g) . g e O(f).
(d)O(f) c O(g) f e O(g) . g e O(f).
(e)f e O(g) g e O(f).
(f)f e O(g) f e O(g) . g e O(f).
(g)f e O(g) f e O(g) . f e O(g).
54. Demuestra las afirmaciones que siguen:
(a)lim
n
g(n)
f(n)
= 0 O(f) c O(g).

(b)lim
n
g(n)
f(n)
= 0 f e O(g).
55. Aplicando el ejercicio anterior, demuestra:
(a)
2
1) - (n * n
es O(n
2
).
(b)p(n) es O(n
k
), supuesto que p(n) sea un polinomio de grado k.
(c)log
a
n es O(log
b
n), supuesto que a, b > 1.
56. Supongamos dos algoritmos A, B que resuelven el mismo problema, pero tales que T
A
(n)
es O(log n), mientras que T
B
(n) es O(2
n
). Responde razonadamente a las siguientes pregun-
tas:
(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?
Diseo de algoritmos iterativos 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 fi-
jado?
57. El algoritmo siguiente resuelve el mismo problema que el algoritmo del ejercicio 49. Anali-
za este algoritmo y estima el orden de magnitud de su tiempo de ejecucin en el caso peor.
Se trata de un algoritmo ms eficiente que el del ejercicio 49? Por qu?
funcesSim(a:Vector[1..N,1..N]deEnt)devb:Bool;
{Pre.:a=A}
varI,J:Ent;
inicio
b:=Cierto;
I:=1;
itI<NANDb
J:=I+1
itJNANDb
b:=bAND(a(I,J)=a(J,I))
J:=J+1
fit;
I:=I+1
fit
{Post.:a=A.(bi,j:1i<jN:a(i,j)=a(j,i))}
devb
ffunc

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 bu-
cle 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
solamente operaciones aritmticas de suma y resta. Utiliza la tcnica de descomposicin
conjuntiva de la postcondicin para descubrir un invariante adecuado. analiza el tiempo de
ejecucin del algoritmo obtenido.
funcdivMod(x,y:Ent)devc,r:Ent;
{P:x=X.y=Y.x0.y>0}
{Q:x=X.y=Y.x=c*y+r.0r<y}
ffunc
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 ade-
cuado a partir de una descomposicin conjuntiva de la postcondicin.
Diseo de algoritmos iterativos 148
funcraz(x:Ent)devr:Ent;
{P:x=X.x0}
{Q:x=X.0r.r
2
x<(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 especifi-
cacin pre/post y deriva a partir de ella un algoritmo correcto. OJO: no se supone que el
vector est ordenado.
62. Dados una matriz a:Vector[1..N,1..M]deEnty un nmero x:Ent, se quiere
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:N1.v=V.u=U}
{Q:r=i:1iN:u(i)*v(i).u=U.v=V}
ffunc
Para descubrir un invariante adecuado, intenta generalizar la postcondicin:
(a) Sustituyendo la constante N por una nueva variable.
(b) Sustituyendo la constante 1 por una nueva variable.
Observa que (a) conduce a un bucle ascendente, mientras que (b) conduce a un bucle descenden-
te.
64. Deriva un algoritmo iterativo que satisfaga la siguiente especificacin pre/post, partiendo
de una generalizacin conveniente de la postcondicin para descubrir un invariante:

funcsumaYresta(v:Vector[1..N]deEnt)devs:Ent;
{P:N1.v=V}
{Q:v=V.s=i:1iN:(1)
i
*v(i)}
ffunc
65. La siguiente especificacin corresponde a un algoritmo que debe decidir si un vector de
enteros dado est o no ordenado.
funcesOrd(v:Vector[1..N]deEnt)devb:Bool;
{P:N1.v=V}
{Q:v=V.(bord(v))}
ffunc

donde ord(v) indica que v est ordenado ascendentemente. Observa que


Qv=V.(bord(v,1,N))
siempre que se defina
ord(v,i,j)
def
k,l:1k<lj:v(k)v(l)
Diseo de algoritmos iterativos 149
Usando la nueva forma de la postcondicin, descubre un invariante y una expresin de acota-
cin adecuados, y deriva un algoritmo correcto de complejidad O(N). Finalmente, trata de opti-
mizar 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
iterativo basndote en las dos ideas siguientes:
Generalizar la postcondicin reemplazando la expresin (r+1)
2
por otra que intro-
duzca una nueva variable s
2
.
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
en base 2 por defecto de n:
funclogBin(n:Ent)devk:Ent;
{P:n=N.n1}
{Q:n=N.k0.2
k
n<2
k+1
}
ffunc

Deriva un algoritmo correcto de complejidad O(log n) utilizando


Inv. I: n=N.m1.k0.2
k
*mn<2
k
*(m+1)
Cond. Rep. B.: m=1
Cota C: m
El invariante y la condicin de repeticin se han descubierto a partir de una generalizacin de
la postcondicin. Explica cmo.
68. Los nmeros de Fibonacci se definen matemticamente por medio de la siguiente ley de
recurrencia:
fib(0)=0
fib(1)=1
fib(n)=fib(n2)+fib(n1),paratodon2

Usando la tcnica de introduccin de un invariante auxiliar, deriva un algoritmo iterativo O(n)


que calcule el n-simo nmero de Fibonacci, cumpliendo la especificacin:
funcfib(n:Ent)devx:Ent;
{P:n=N.n0}
{Q:n=N.x=fib(n)}
ffunc
69. Tambin en este caso se pide derivar un algoritmo correcto, usando nuevas variables loca-
les y descubriendo un invariante auxiliar adecuado durante la derivacin. La especificacin
y el invariante inicial que se proponen son como sigue:

funcnumParejas(v:Vector[1..N]deEnt)devr:Ent;
{P:N1.v=V}
{Q:v=V.r=#i,j:1i<jN:v(i)0.v(j)0}
Diseo de algoritmos iterativos 150
ffunc
I
0
:v=V.0nN.r=#i,j:1i<jn:v(i)0.
v(j)0
Obsrvese que se cumple: I
0
.n=NQ. Se exige que el algoritmo obtenido tenga com-
plejidad 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 con-
cordancia 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
operaciones aritmticas de suma y producto.
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
(1iN) es peculiar si v(i) es igual al nmero de ceros almacenados en v en posiciones con
ndice mayor que i.
Algoritmos de bsqueda en vectores
73. Aplica la tcnica de derivacin de algoritmos iterativos para construir funciones de bsque-
da que satisfagan las especificaciones siguientes:
(a) Bsqueda secuencial (cfr. Ej. 61):
funcbusca(v:Vector[1..N]deEnt;x:Ent)devp:Ent;
{P
0
:N1.v=V.x=X}
{Q
0
:v=V.x=X.1pN.(i:1i<p:v(i)=x).
(v(p)=xvp=N)}
ffunc
(b) Bsqueda secuencial con centinela:
funcbusca(v:Vector[1..N]deEnt;x,q:Ent)devp:Ent;
{P
0
:v=V.x=X.q=Q.1qN.v(q)=x}
{Q
0
:v=V.x=X.q=Q.1pq.
(i:1i<p:v(i)=x).v(p)=x}
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-
denado, partiendo de la especificacin siguiente:
funcbusca(v:Vector[1..N]deEnt;x:Ent)devp:Ent;
{P
0
:N1.v=V.x=X.ord(v)}
{Q
0
:v=V.x=X.0pN.
i:1ip:v(i)x.i:p+1iN:x<v(i)}
ffunc

Diseo de algoritmos iterativos 151


Interpreta la postcondicin. Qu dice Q
0
en los casos extremos, es decir, para p=0 y p=N?
Cmo podemos deducir de Q
0
si x est o no en el vector?
Pista: Generaliza la postcondicin observando que
Q
0
:v=V.x=X.0p<qN+1.i:1ip:v(i)x.
i:qiN:x<v(i).q=p+1
Otro algoritmo que usa bsqueda binaria ha aparecido en el ejercicio 66.
Algoritmos de ordenacin de vectores
75. Comenta el significado de la especificacin siguiente, y deriva un procedimiento O(n) que la
satisfaga.
procinserta(esv:Vector[1..N]deEnt;ep:Ent);
{P
0
:1p<N.v=V.p=P.i,j:1i<jp:v(i)v(j)}
{Q
0
:p=P.permut(v,V).i,j:1i<jp+1:v(i)v(j)}
fproc

Pista: Generaliza la postcondicin observando que


Q
0
:p=P.permut(v,V).0mp.
i,j:1i<jm:v(i)v(j).
i,j:m+1i<jp+1:v(i)v(j).
i,j:1im.m+2jp+1:v(i)v(j).(m=0vv(m)
v(m+1))
76. Deriva un procedimiento O(n
2
) para la ordenacin de un vector mediante el algoritmo de
insercin, partiendo de la especificacin siguiente:
procordena(esv:Vector[1..N]deEnt);
{P
0
:v=v.N1}
{Q
0
:permut(v,V).ord(v)}
fproc
siendo ord(v)
def
i,j:1i<jN:v(i)v(j)

Pista: Generaliza la postcondicin observando que


Q
0
:permut(v,V).i,j:1i<jn:v(i)v(j).n=N
Descompn para encontrar un invariante y aprovecha el procedimiento del ejercicio ante-
rior 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;
{P
0
:1p<N.v=V.p=P}
{Q
0
:p=P.v=V.pmN.v(m)=mink:pkN:v(k)}
ffunc
78. Deriva un procedimiento O(n
2
) para la ordenacin de un vector mediante el algoritmo de
seleccin, partiendo de la misma especificacin del ejercicio 76, pero tomando ahora
ord(v)
def
i:1i<N:(j:i<jN:v(i)v(j))

Diseo de algoritmos iterativos 152


Pista: Generaliza la postcondicin observando que
Q
0
:permut(v,V).i:1i<n:(j:i<jN:v(i)v(j))
.
n=N
Descompn para encontrar un invariante y aprovecha el procedimiento del ejercicio ante-
rior para disear la accin encargada de su restablecimiento.
Algoritmos recursivos 153
CAPTULO 2
DISEO DE ALGORITMOS RECURSIVOS
2.1 Introduccin a la recursin
En este apartado introducimos el concepto de programa recursivo, presentamos los distintos
esquemas a los que se ajustan las definiciones recursivas, mostramos ejemplos y fijamos la termi-
nologa bsica.
Optamos por una solucin recursiva cuando sabemos cmo resolver de manera directa un
problema para un cierto conjunto de datos, y para el resto de los datos somos capaces de resol-
verlo utilizando la solucin al mismo problema con unos datos ms simples.
Cualquier solucin recursiva se basa en un anlisis de los datos, x
&
, para distinguir los casos de
solucin directa y los casos de solucin recursiva:
caso directo (caso base), x
&
es tal que el resultado y
&
puede calcularse directamente de
forma sencilla.
caso recursivo, sabemos cmo calcular a partir de x
&
otros datos ms pequeos x
&
, y sa-
bemos adems cmo calcular el resultado y
&
para x
&
suponiendo conocido el resultado
y
&
para x
&
.
Para implementar soluciones recursivas en un lenguaje de programacin tenemos que utilizar
una accin que se invoque a s misma (con datos cada vez ms simples). Las funciones y los
procedimientos son el mecanismo que permite que unas acciones invoquen a otras, y es por tanto
el mecanismo que utilizamos para implementar soluciones recursivas: procedimientos o funcio-
nes que se invocan a s mismos.
Para entender la recursividad a veces resulta til considerar cmo se ejecutan las acciones re-
cursivas en una computadora, como si se crearan mltiples copias del mismo cdigo, operando
sobre datos diferentes. Mercedes cuando les explic la recursividad insisti mucho en la cons-
truccin de trazas de los procedimientos y funciones recursivas. El ejemplo clsico del factorial:
funfact(n:Nat)devr:Nat;
{P
0
:N=n}
inicio
si
n=0r:=1
n>0r:=n*fact(n1)
fsi
{Q
0
:n=N.r=Hi:1in:i}
1

devr
ffunc
1
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.
Algoritmos recursivos 154
La ejecucin de fact(2)?
Programa
principal
.
.
.
x :=fact(2)
Programa
principal
.
.
.
x :=fact(2)
fact(2)
.
.
.
r :=n * fact(n-1)
Programa
principal
.
.
.
x :=fact(2)
fact(2)
.
.
.
r :=n * fact(n-1)
fact(1)
.
.
.
r :=n * fact(n-1)
Programa
principal
.
.
.
x :=fact(2)
fact(2)
.
.
.
r :=n * fact(n-1)
fact(1)
.
.
.
r :=n * fact(n-1)
fact(0)
.
.
.
r :=1
Programa
principal
.
.
.
x :=fact(2)
fact(2)
.
.
.
r :=n * fact(n-1)
fact(1)
.
.
.
r :=n * fact(n-1)
.
.
.
Programa
principal
.
.
.
x :=fact(2)
fact(2)
.
.
.
r :=n * fact(n-1)
Programa
principal
.
.
.
x :=fact(2)
Algoritmos recursivos 155
En realidad no se realizan copias del cdigo sino simplemente de las variables locales y de la di-
reccin de retorno, pero valga el smil.
La importancia de la recursin radica en que:
Es un mtodo muy potente de diseo y razonamiento formal
Tiene una relacin natural con la induccin.
Facilita conceptualmente la resolucin de problemas y el diseo de algoritmos
Es fundamental en los lenguajes de programacin funcionales
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 utili-
zando lenguajes funcionales, donde el mecanismo natural de resolucin de problemas es la recur-
sin. 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 resulta-
do sobre funciones a procedimientos slo hay que tomar algunas precauciones con los parme-
tros 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(x
1
:t
1
;;x
n
:t
n
)devy
1
:o
1
;;y
m
:o
m
;
{P
0
:P.x
1
=X
1
..x
n
=X
n
}
cte;
var;
inicio
si
d( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=c( x
&
,nombreFunc(s( x
&
))) %abusodenotacin
fsi;
{Q
0
:Q.x
1
=X
1
..x
n
=X
n
}
Algoritmos recursivos 156
dev<y
1
,,y
m
>
ffunc

donde
d( x
&
) es la condicin que determina el caso directo
d( x
&
) es la condicin que determina el caso recursivo
g calcula el resultado en el caso directo
s (funcin sucesor) calcula los argumentos para la siguiente llamada recursiva
c (funcin de combinacin) obtiene la combinacin de los resultados de la llamada recur-
siva nombreFunc( s( x
&
) ) junto con los datos de entrada x
&
, proporcionando as el resultado
de nombreFunc( x
&
)
debemos tener en cuenta que
d, g, s y c pueden ser funciones o expresiones.
d( x
&
) y d( x
&
) pueden desdoblarse en una alternativa con varios casos
veamos cmo la funcin factorial se ajusta a este esquema de declaracin:
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 lla-
madas 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 mto-
do de Euclides.
funcmcd(a,b:Ent)devm:Ent;
{P
0
:a=A.b=B.a>0.b>0}
inicio
si
Algoritmos recursivos 157
a>bm:=mcd(ab,b)
a<bm:=mcd(a,ba)
a=bm:=a
fsi
{Q
0
:a=A.b=B.m=MCD(a,b)}
devm
ffunc
Tenemos aqu un ejemplo donde se distinguen ms de dos casos, en particular dos casos re-
cursivos 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
d
1
(a,b) a > b d
2
(a,b) a < b dos caso recursivos
g(a,b) = a
s
1
(a,b) = (ab, a) s
2
(a,b) = (a, ba) dos funciones sucesor
c(a,b,mcd(x,y)) = mcd(x,y)
Observamos cmo la funcin de combinacin se limita a propagar el resultado de la llamada
recursiva y que, por lo tanto, nos encontramos ante un ejemplo de recursin final.
1.1.2 Recursin mltiple
Este tipo de recursin se caracteriza por que en cada pasada se realizan varias llamadas recur-
sivas. El esquema correspondiente:
funcnombreFunc(x
1
:t
1
;;x
n
:t
n
)devy
1
:o
1
;;y
m
:o
m
;
{P
0
:P.x
1
=X
1
..x
n
=X
n
}
cte;
var;
inicio
si
d( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=c( x
&
,nombreFunc(s
1
( x
&
)),,nombreFunc(s
k
( x
&
)))
fsi;
{Q
0
:Q.x
1
=X
1
..x
n
=X
n
}
dev<y
1
,,y
m
>
ffunc

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
g calcula el resultado en el caso directo
s
i
(funciones sucesor) calculan la descomposicin de los datos de entrada para realizar la i-
sima llamada recursiva, para i = 1, , k
c (funcin de combinacin) obtiene la combinacin de los resultados de las llamadas re-
cursivas nombreFunc( s
i
( x
&
) ), con i = 1, , k, junto con los datos de entrada x
&
, propor-
cionando as el resultado de nombreFunc( x
&
)
Se puede implementar de manera directa una funcin que calcula los nmeros de Fibonacci si-
guiendo el esquema de recursin mltiple:
funcfibo(n:Nat)devr:Nat;
{P
0
:n=N}
inicio
si
n=0r:=0
n=1r:=1
n>1r:=fibo(n1)+fibo(n2)
fsi
{Q
0
:n=N.r=fib(n)}
devr
ffunc
En esta funcin tenemos que, segn el esquema
d
1
(n) n = 0 d
2
(n) n = 1 dos caso directos
d(n) n > 1
g(0) = 0 g(1) = 1 (g(n) = n?)
s
1
(n) = n1 s
2
(n) = n2
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 da-
tos; en general ser de mayor orden.
Podemos ver un ejemplo con las llamadas que se desencadenan al computar fib(4). Compro-
bamos que algunos resultados se computan ms de una vez:
Algoritmos recursivos 159
fib(4)
fib(2) fib(3)
fib(1) fib(0) fib(1) fib(2)
fib(0) fib(1)
0 1
0 1
0+1=1
0+1=1
1
1+1=2
1+2=3
Para terminar con la introduccin recapitulamos los distintos tipos de funciones recursivas que
hemos presentado:
Simple. A lo sumo una llamada recursiva en cada caso recursivo
No final. Requiere combinacin de resultados
Final. No requiere combinacin de resultados
Mltiple. Ms de una llamada recursiva en algn caso recursivo.
2.2 Verificacin de algoritmos recursivos
Para verificar funciones recursivas podramos intentar utilizar las reglas de verificacin que
hemos visto hasta ahora, ya que, de hecho, con la recursividad no hemos aadido ningn elemen-
to nuevo a nuestro lenguaje algortmico. El problema radica en que debemos verificar las llama-
das recursivas

r:=n*fact(n1)

Y el mtodo de verificacin de llamadas depende de que la funcin o el procedimiento que se


invocan tengan una implementacin correcta. Pero eso es precisamente lo que estamos intentan-
do demostrar cuando verificamos una funcin recursiva.
La solucin radica en aplicar el principio de induccin, demostrando la correccin para el o los
casos base, y suponiendo que la funcin es correcta para s( x
&
) demostrar entonces que tambin
es correcta para x
&
.
Algoritmos recursivos 160
1.2.1 Verificacin de la recursin simple
Hemos de verificar el cuerpo de una funcin recursiva simple, que se corresponde con el es-
quema:
si
d( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=c( x
&
,nombreFunc(s( x
&
)))
fsi;
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 es-
pecificacin:
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:
P
0
def(d( x
&
)).def(d( x
&
))
P
0
d( x
&
)vd( x
&
)
Por otra parte, debe ser correcta la rama del caso directo:
{P
0
.d( x
&
)} y
&
:=g( x
&
){Q
0
}
Algoritmos recursivos 161
que es el caso base de la demostracin por induccin antes indicada:
d( x
&
)R( x
&
)
A continuacin debemos tratar la correccin de la rama recursiva, para lo cual debemos verifi-
car:
{P
0
.d( x
&
)} y
&
:=c( x
&
,nombreFunc(s( x
&
))){Q
0
}
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 verifica-
cin:
{P
0
.d( x
&
)}
y
&
:=nombreFunc(s( x
&
));
y
&
:=c( x
&
, y
&
)
{Q
0
}

para verificar esta composicin secuencial tomamos un aserto intermedio, obtenido como la
pmd de la segunda asignacin inducida por Q
0
:

{P
0
.d( x
&
)}
y
&
:=nombreFunc(s( x
&
));
{def(c( x
&
, y
&
)).Q
0
[ y
&
/c( x
&
, y
&
)]}

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 in-
duccin, 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
P
0
.d( x
&
)P
0
[ x
&
/s( x
&
)]
sin embargo, es necesario tener en cuenta un detalle en la mayora de las ocasiones irre-
levante y es que en P
0
[ x
&
/s( x
&
)] pueden aparecer variables auxiliares de la especifica-
cin, con los mismos identificadores que las que aparecen en P
0
; y no deben confundirse.
Por ello, en realidad demostraremos:
P
0
.d( x
&
)P
0
[ x
&
/s( x
&
)][ X
&
/ X
&
]
siendo X
&
las variables que recogen los valores iniciales de la llamada recursiva
2. Extraer de
P
0
.d( x
&
).P
0
[ x
&
/s( x
&
)][ X
&
/ X
&
]
las condiciones que no dependen de los parmetros de salida o entrada/salida. Como P
0
es la precondicin de una funcin, no pueden aparecer en ella los parmetros de salida
en realidad, los parmetros de salida de la llamada recursiva. Adems observamos que si
Algoritmos recursivos 162
se ha verificado el paso 1, todo lo que se puede obtener de P
0
[ x
&
/ s( x
&
) ] [ X
&
/ X
&
] est
ya en P
0
. d( x
&
). Por lo tanto el aserto R que consideramos es:
P
0
.d( x
&
)

Este ltimo razonamiento tambin es aplicable en el caso general de llamadas a procedi-


mientos y funciones. La razn por la que en el caso general no aplicamos esta simplifica-
cin 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:

P
0
.d( x
&
).Q
0
[ x
&
/s( x
&
), y
&
/ y
&
][ X
&
/ X
&
]def(c( x
&
, y
&
)).
Q
0
[ y
&
/c( x
&
, y
&
)]

De nuevo al hacer la sustitucin de la postcondicin de la llamada recursiva hemos tenido


cuidado de renombrar tambin las variables auxiliares de la especificacin [ X
&
/ X
&
].
Ntese que en la anterior condicin es donde aparece la hiptesis de induccin: hemos de
demostrar que a partir de la postcondicin de la llamada recursiva se puede obtener la
postcondicin de la llamada actual. Abstrayendo esa condicin como una condicin R
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 fundamen-
tado
t( x
&
) : t
&
D D
Tendremos que comprobar entonces que al entrar en la rama recursiva la expresin de acota-
cin est definida y sea decrementable

P
0
.d( x
&
)def(t( x
&
)).dec(t( x
&
))

y que efectivamente la expresin se vaya decrementando al avanzar la recursin:

P
0
.d( x
&
)t(s( x
&
)) % t( x
&
)

Con todo esto la verificacin de una funcin recursiva pasa por demostrar los siguientes requi-
sitos:
(r.1) condiciones definidas
P
0
def(d( x
&
)).def(d( x
&
))
(r.2) condiciones exhaustivas
P
0
d( x
&
))vd( x
&
)
Algoritmos recursivos 163
(r.3) caso base
{P
0
.d( x
&
)} y
&
=g( x
&
){Q
0
}
(r.4) definicin de la llamada
P
0
.d( x
&
)P
0
[ x
&
/s( x
&
)][ X
&
/ X
&
]
(r.5) paso inductivo
P
0
.d( x
&
).Q
0
[ x
&
/s( x
&
), y
&
/ y
&
][ X
&
/ X
&
]def(c( x
&
, y
&
)).
Q
0
[ y
&
/c( x
&
, y
&
)]
(r.6) expresin de acotacin
P
0
.d( x
&
)def(t( x
&
)).dec(t( x
&
))
(r.7) descenso
P
0
.d( x
&
)t(s( x
&
)) % t( x
&
)
Veamos un ejemplo de aplicacin de las reglas de verificacin de funciones recursivas, aplica-
da a la funcin factorial.
(r.1) condiciones definidas
N=ndef(n=0).def(n>0)cierto
(r.2) condiciones exhaustivas
N=nn=0vn>0
n=0vn>0
:n:Nat
declaracin cierto:N=n
(r.3) caso base
{N=n.n=0}r:=1{Q
0
:n=N.r=Hi:1in:1}
def(1).Q
0
[r/1]
cierto.n=N.1=Hi:1in:1
fortalecimiento :n=N.n=0
P
0
.n=0

(r.4) definicin de la llamada


n=N.n>0P
0
[n/n1][N/N]
P
0
[n/n1][N/N]
n1=N
n1:Nat
Algoritmos recursivos 164
: n>0.n:Nat
n=N.n>0
(r.5) paso inductivo
n=N.n>0.Q
0
[n/n1,r/r][N/N]def(n*r).Q
0
[r/n*r]

elaboramos la parte derecha


def(n*r).Q
0
[r/n*r]
cierto.n=N.n*r=Hi:1in:i
veamos que de la parte izquierda (hiptesis de induccin) se deduce la parte derecha

n=N.n>0.Q
0
[n/n1,r/r][N/N]
n=N.n>0.n1=N.r=Hi:1in1
:i
n=N.n*r=n*(Hi:1in1:i)
n=N.n*r=Hi:1in:i
def(n*r).Q
0
[r/n*r]

(r.6) expresin de acotacin


Como expresin de acotacin tomamos
t(n)=n

demostramos
n=N.n>0def(n).dec(n)n>0
(r.7) descenso
n=N.n>0n1<ncierto
Generalizaciones del esquema de recursin simple
La dos generalizaciones bsicas son que nos encontremos con mltiples casos base, mltiples
casos recursivos, o ambas.
Ms de un caso base
Los requisitos que se ven modificados:
(r.1) todas las barreras han de estar definidas
P
0
def(d
1
( x
&
))..def(d
p
( x
&
)).def(d( x
&
))
(r.2) condiciones exhaustivas
Algoritmos recursivos 165
P
0
d
1
( x
&
))vvd
p
( x
&
))vd( x
&
)
(r.3) se ha de comprobar la correccin de todos los casos base
{P
0
.d
j
( x
&
)} y
&
=g
j
( x
&
){Q
0
}

donde las d
j
( x
&
) son las condiciones de los casos directos y las g
j
( x
&
) las funciones que ob-
tiene 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
P
0
def(d( x
&
)).def(d
1
( x
&
))..def(d
p
( x
&
))
(r.2) condiciones exhaustivas
P
0
d( x
&
))vd
1
( x
&
)vvd
p
( x
&
)
(r.4) Cada llamada recursiva ha de estar definida. Tenemos p requisitos de la forma (r.4)
j
P
0
.d
j
( x
&
)P
0
[ x
&
/s
j
( 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
P
0
.d
j
( x
&
).Q
0
[ x
&
/s
j
( x
&
), y
&
/ y
&
][ X
&
/ X
&
]def(c
j
( x
&
, y
&
)).
Q
0
[ y
&
/c
j
( x
&
, y
&
)]
(r.6) Aunque la expresin de acotacin es nica, debemos verificar que todos los casos recur-
sivos garantizan (r.6)
j
P
0
.d
j
( x
&
)def(t( x
&
)).dec(t( x
&
))
(r.7) Se ha de comprobar el descenso de la expresin de acotacin para cualquier descompo-
sicin recursiva. Tenemos requisitos (r.7)
j
de la forma
P
0
.d
j
( x
&
)t(s
j
( x
&
)) % t( x
&
)
Siendo las d
j
( x
&
) las condiciones de los casos recursivos, las s
j
( x
&
) las funciones que calculan
las descomposiciones recursivas, y las c
j
( 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 indi-
car.
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 si b = 0
a si b = 1
a b =
(2 a) (b div 2) si b > 1, b par
(2 a) (b div 2) + a si b > 1, b impar
Una declaracin para este algoritmo es:
funcprod(a,b:nat)devr:Nat;
{P
0
:a=A.b=B}
inicio
sib=0 r:=0
b=1 r:=a
(b>1ANDpar(b)) r:=prod(2*a,bdiv2)
(b>1ANDNOTpar(b))r:=prod(2*a,bdiv2)+a
fsi
{Q
0
:a=A.b=B.r=a*b}
devr
ffunc
Ntese que el caso base b=1 es redundante y se puede eliminar.
Apliquemos el mtodo de verificacin:
(r.1) Definicin de las barreras
a=A.b=B?def(b=0).def(b=1).def(b>1ANDpar(b)).
def(b>1ANDNOTpar(b))
cierto %suponiendoqueparestdefinido
n:Nat

(r.2) Al menos una barrera se abre


a=A.b=B?b=0vb=1v(b>1ANDpar(b))v(b>1ANDNOTpar(b))
Algoritmos recursivos 167
b=0vb=1v(b>1.(par(b)ORNOTpar(b)))
b=0vb=1vb>1
cierto:a=a.b=B

(r.3)
1
Correccin del primer caso base
{a=A.b=B.b=0}
r:=0
{a=A.b=B.r=a*b}
(r.3)
2
Correccin del segundo caso base
{a=A.b=B.b=1}
r:=a
{a=A.b=B.r=a*b}

(r.4)
1
Es posible hacer la llamada recursiva en el primer caso recursivo
a=A.b=B.(b>1ANDpar(b))?P
0
[a/2*a,b/bdiv2][A/A,B/B]
2*a=A.bdiv2=B
** :a:Nat.b:Nat
:a=A.b=B
** no es doble implicacin porque 1 div 2 e N pero 1 e N.
(r.4)
2
Es posible hacer la llamada recursiva en el segundo caso recursivo
a=A.b=B.(b>1ANDNOTpar(b))?P
0
[a/2*a,b/bdiv2]
[A/A,B/B]
2*a=A.bdiv2=B
:a:Nat.b:Nat
:a=A.b=B
(r.5)
1
Paso inductivo para el primer caso recursivo
P
0
.(b>1ANDpar(b)).Q
0
[a/2*a,b/bdiv2,r/r][A/A,B/B]?Q
0
aqunohahechofaltaintroducirunavariableauxiliarrporquelafuncin
decombinacinselimitaatransmitirelresultadodelallamadarecursiva

a=A.b=B.(b>1ANDpar(b)).2*a=A.bdiv2=B.r=(2*a)*(bdiv
2)?
a=A.b=B.r=a*b

a=A.b=Ba=A.b=B

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

(r.5)
2
Paso inductivo para el segundo caso recursivo
P
0
.(b>1ANDNOTpar(b)).Q
0
[a/2*a,b/bdiv2,r/r][A/A,B/B]?
def(r+a).Q
0
[r/r+a]

def(r+a).Q
0
[r/r+a]
cierto.a=A.b=B.r+a=a*b
a=a.b=B.r=a*(b1)

P
0
a=A.b=B

(b>1ANDNOTpar(b)).Q
0
[a/2*a,b/bdiv2,r/r][A/A,B/B]

(b>1ANDNOTpar(b)).r=(2*a)*(bdiv2)

(b>1ANDNOTpar(b)).r=a*2*(bdiv2)

(b>1ANDNOTpar(b)).r=a*(b1)

r=a*(b1)

(r.6)
1
Definicin de la expresin de acotacin en el primer caso recursivo
Como expresin de acotacin tomamos
t(a,b)=b

Se demuestra
P
0
.(b>1ANDpar(b))def(b).dec(b)cierto.b>0

(r.6)
2
Definicin de la expresin de acotacin en el segundo caso recursivo
P
0
.(b>1ANDNOTpar(b))def(b).dec(b)cierto.b>0

(r.7)
1
Decremento de la expresin de acotacin en el primer caso recursivo
P
0
.(b>1ANDpar(b))(bdiv2)<b

secumpleporb>0

(r.7)
2
Decremento de la expresin de acotacin en el segundo caso recursivo
Algoritmos recursivos 169
P
0
.(b>1ANDNOTpar(b))(bdiv2)<b

secumpleporb>0
1.2.2 Verificacin de la recursin mltiple
Hemos de verificar una composicin alternativa de la forma:
sid( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=c( x
&
,nombreFunc(s
1
( x
&
)),,nombreFunc(s
k
( x
&
)))
fsi
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 especifi-
cacin, aplicamos la regla:
d( x
&
)R( x
&
)
(s
i
( x
&
):R(s
i
( x
&
)))R( x
&
)

x
&
R( x
&
)
El procedimiento de verificacin es anlogo al de la recursin simple, excepto por lo que se
refiere al caso recursivo, donde hemos de verificar:
{P
0
.d( x
&
)} y
&
:=c( x
&
,nombreFunc(s
1
( x
&
)),,nombreFunc(s
k
( x
&
))){Q
0

para cuya verificacin introducimos k variables auxiliares:


{P
0
.d( x
&
)}
y
&
1
:=nombreFunc(s
1
( x
&
));

y
&
k
:=nombreFunc(s
k
( x
&
));
y
&
:=c( x
&
, y
&
1
,, y
&
k
)
Algoritmos recursivos 170
{Q
0
}
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 instruc-
cin:
{P
0
.d( x
&
).Q
0
[ x
&
/s
1
( x
&
), y
&
/ y
&
1
][ X
&
/ X
&
1
]..
Q
0
[ x
&
/s
k
( x
&
), y
&
/ y
&
k
][ X
&
/ X
&
k
]}
y
&
:=c( x
&
, y
&
1
,, y
&
k
)
{Q
0
}
Para ver que este resultado de correccin es equivalente al anterior tendramos que ir constru-
yendo 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:
P
0
.d( x
&
).Q
0
[ x
&
/s
1
( x
&
), y
&
/ y
&
1
][ X
&
/ X
&
1
]..Q
0
[ x
&
/s
k
( x
&
), y
&
/ y
&
k
]
[ X
&
/ X
&
k
]

def(c( x
&
, y
&
1
,, y
&
k
)).Q
0
[ y
&
/c( x
&
, y
&
1
,, y
&
k
)]
que es precisamente el paso inductivo; supuestas correctas las llamadas recursivas con las co-
rrespondientes descomposiciones, se demuestra que es correcto el paso actual.
Con todo esto los requisitos que hay que comprobar para demostrar la correccin de una fun-
cin con recursin mltiple:
(r.1) condiciones definidas
P
0
def(d( x
&
)).def(d( x
&
))
(r.2) condiciones exhaustivas
P
0
d( x
&
))vd( x
&
)
(r.3) caso base
{P
0
.d( x
&
)} y
&
=g( x
&
){Q
0
}
(r.4) definicin de la llamada
P
0
.d( x
&
)P
0
[ x
&
/s
1
( x
&
)][ X
&
/ X
&
1
]..P
0
[ x
&
/s
k
( x
&
)][ X
&
/ X
&
k
]
Algoritmos recursivos 171
(r.5) paso inductivo
P
0
.d( x
&
).Q
0
[ x
&
/s
1
( x
&
), y
&
/ y
&
1
][ X
&
/ X
&
1
]..Q
0
[ x
&
/s
k
( x
&
), y
&
/ y
&
k
]
[ X
&
/ X
&
k
]

def(c( x
&
, y
&
1
,, y
&
k
)).Q
0
[ y
&
/c( x
&
, y
&
1
,, y
&
k
)]
(r.6) expresin de acotacin
P
0
.d( x
&
)def(t( x
&
)).dec(t( x
&
))
(r.7) descenso
P
0
.d( x
&
)t(s
1
( x
&
)) % t( x
&
)..t(s
k
( x
&
)) % t( x
&
)
Generalizaciones del esquema de recursin mltiple
La dos generalizaciones bsicas son que nos encontremos con mltiples casos base, mltiples
casos recursivos, o ambas.
Ms de un caso base
Los requisitos que se ven modificados son los mismos que en el esquema de recursin simple,
pues la verificacin de los dos esquemas tiene en comn los pasos (r.1), (r.2) y (r.3).
Ms de un caso recursivo
Los requisitos que se ven modificados
(r.1) todas las barreras han de estar definidas
P
0
def(d( x
&
)).def(d
1
( x
&
))..def(d
p
( x
&
))
(r.2) condiciones exhaustivas
P
0
d( x
&
))vd
1
( x
&
)vvd
p
( x
&
)
(r.4) Cada llamada recursiva ha de estar definida. Para cada caso recursivo podemos tener k
j
llamadas recursivas. Tenemos p requisitos de la forma (r.4)
j
P
0
.d
j
( x
&
)P
0
[ x
&
/s
j1
( x
&
)][ X
&
/ X
&
1
]..P
0
[ x
&
/s
jkj
( x
&
)][ X
&
/ X
&
kj
]
(r.5) Se ha de comprobar el paso inductivo para cada caso recursivo. Tenemos p requisitos de
la forma (r.5)
j
2
P
0
.d
j
( x
&
).Q
0
[ x
&
/s
j1
( x
&
), y
&
/ y
&
1
][ X
&
/ X
&
1
]..Q
0
[ x
&
/s
jkj
( x
&
), y
&
/ y
&
kj
]
[ X
&
/ X
&
kj
]
2
Ntese que reutilizamos las variables auxiliares en los distintos casos recursivos, porque si no deberamos defi-
nir variables y
j1
, , y
jkj
Algoritmos recursivos 172

def(c
j
( x
&
, y
&
1
,, y
&
kj
)).Q
0
[ y
&
/c
j
( x
&
, y
&
1
,, y
&
kj
)]
(r.6) Aunque la expresin de acotacin es nica, debemos verificar que todos los casos recur-
sivos garantizan (r.6)
j
P
0
.d
j
( x
&
)def(t( x
&
)).dec(t( x
&
))
(r.7) Se ha de comprobar el descenso de la expresin de acotacin para cualquier descompo-
sicin recursiva. Tenemos requisitos (r.7)
j
de la forma
P
0
.d
j
( x
&
)t(s
j1
( x
&
)) % t( x
&
)..t(s
jkj
( x
&
)) % t( x
&
)
Siendo las d
j
( x
&
) las condiciones de los casos recursivos, las s
ji
( x
&
) las funciones que calculan
las descomposiciones recursivas, y las c
j
( x
&
, y
&
1
, , y
&
k
) las funciones que combinan los resulta-
dos, para j= 1, , p, i = 1, , k
j
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;
{P
0
:n=N}
inicio
si
n=0r:=0
n=1r:=1
n>1r:=fibo(n1)+fibo(n2)
fsi
{Q
0
:n=N.r=fib(n)}
devr
ffunc
(r.1) Definicin de las barreras
n=Ndef(n=0).def(n=1).def(n>1)

(r.2) Condiciones exhaustivas


n=Nn=0vn=1vn>1n:Natcierto

(r.3)
1
Correccin del primer caso base
Algoritmos recursivos 173
{n=N.n=0}
r:=0
{n=N.r=fib(n)}

n=N.n=0def(0).n=N.0=fib(n)
(r.3)
2
Correccin del segundo caso base
{n=N.n=1}
r:=1
{n=N.r=fib(n)}

n=N.n=1def(1).n=N.1=fib(n)
(r.4) Llamada recursiva
n=N.n>1P
0
[n/n1][N/N
1
].P
0
[n/n2][N/N
2
]

P
0
[n/n1][N/N
1
]
n1=N
1

n1:Nat
n:Nat.n1
: n=N.n>1
P
0
[n/n2][N/N
2
]
n2=N
2

n2:Nat
n:Nat.n2
: n=N.n>1
(r.5) Paso inductivo
hemosdedemostrar
n=N.n>1.Q
0
[n/n1,r/r
1
][N/N
1
].Q
0
[n/n2,r/r
2
][N/N
2
]

def(r
1
+r
2
).Q
0
(r/r
1
+r
2
)

queesequivalenteademostrar
n=N.n>1.n1=N
1
.r
1
=fib(n1).n2=N
2
.r
2
=fib(n2)

cierto.n=N.r
1
+r
2
=fib(n)

yestosetieneporque

r
1
=fib(n1).r
2
=fib(n2)
r
1
+r
2
=fib(n1)+fib(n2)
Algoritmos recursivos 174
r
1
+r
2
=fib(n)

(r.6) Expresin de acotacin


t(n)=n

n=N.n>1def(n).dec(n)cierto.n>0

(r.7) Avance de la recursin


n=N.n>1n1<n.n2<ncierto

2.3 Derivacin de algoritmos recursivos


El problema es, dada la especificacin
{ P } A { Q }
obtener una accin A que la satisfaga. Podemos incluir las soluciones recursivas como una
quinta opcin sobre la forma de A, junto con: asignacin, secuencia, seleccin e iteracin.
Nos planteamos resolver A como una accin recursiva (siempre ha de ser una accin, proce-
dimiento o funcin, ya que necesitamos la auto-invocacin) cuando podemos obtener
fcilmente una definicin recursiva de la postcondicin.
En esencia el mtodo obtendr el planteamiento recursivo, determinar las condiciones de los ca-
sos directos y recursivos, obtendr la solucin para el caso directo, se buscar una descomposi-
cin recursiva de los datos y se obtendr la solucin para los casos recursivos en trminos de las
soluciones a los datos descompuestos. Vemoslo en detalle:
(R.1) Planteamiento recursivo. Se ha de encontrar una estrategia recursiva para alcanzar la
postcondicin, es decir, la solucin. A veces, la forma de la postcondicin, o de las
operaciones que en ella aparecen, nos sugerir directamente una estrategia recursiva.
En otros casos es necesario encontrarla. Ms adelante veremos algunas heursticas para
encontrar estrategias recursivas.
(R.2) Anlisis de casos. Se trata de obtener las condiciones que permiten discriminar los
casos directos de los recursivos. A veces ser ms sencillo obtener la condicin del ca-
so directo, y obtener la del recursivo como su negacin, y otras ocasiones ser ms
sencillo al revs.
Una vez obtenidas las condiciones, demostramos los requisitos (r.1) y (r.2) de la verifi-
cacin de algoritmos recursivos:
P
0
def(d( x
&
)).def(d( x
&
))
P
0
d( x
&
)vd( x
&
)
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

{P
0
.d( x
&
)}A
1
{Q
0
}

De acuerdo con los esquemas de las funciones recursivas intentaremos que A


1
sea de
la forma:
y
&
:=g( x
&
)

Al verificar la correccin de esta accin estaremos demostrando el requisito (r.3) de la


verificacin de funciones recursivas.
Si hubiese ms de un caso directo repetiramos este paso para cada uno de ellos.
Si A
1
es una composicin alternativa cabe plantearse si no es mejor descomponer el
caso directo en varios casos directos.
(R.4) Descomposicin recursiva. Se trata de obtener la funcin siguiente que nos proporcio-
na los datos que empleamos para realizar la llamada recursiva, a partir de los datos de
entrada:
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 deter-
minar 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)
P
0
.d( x
&
)def(t( x
&
)).dec(t( x
&
))

Si hay ms de un caso recursivo se ha de verificar esta condicin para cada uno de


ellos.
(R.6) Terminacin. Demostramos entonces que la funcin de acotacin escogida se de-
crementa en cada llamada requisito (r.7):
P
0
.d( x
&
)t(s( x
&
)) % t( x
&
)
Si hay ms de un caso recursivo se ha de comprobar este requisito para cada uno de
ellos.
Algoritmos recursivos 176
(R.7) Llamada recursiva. Pasamos a ocuparnos entonces del caso recursivo. Empezamos
verificando que las descomposiciones recursivas permiten realizar las llamadas recursi-
vas. Es decir, comprobamos el requisito (r.4):
P
0
.d( x
&
)P
0
[x/s( x
&
)][ X
&
/ X
&
]

De nuevo, este requisito ha de comprobarse para cada descomposicin recursiva.


(R.8) Funcin de combinacin. Lo nico que nos resta por obtener del caso recursivo es
la funcin de combinacin, ya que hay un elemento indispensable que suponemos
incluido: la llamada o llamadas recursivas, pasando como datos las descomposiciones
recursivas. En el caso de recursin simple, esta accin se puede derivar de la especifi-
cacin:
{P
0
.d( x
&
).Q
0
[ x
&
/s( x
&
), y
&
/ y
&
][ X
&
/ X
&
]}
A
2

{Q
0
}

Para ajustarnos al esquema de las funciones recursivas, intentaremos que A


2
sea de la
forma:
y
&
:=c( x
&
, y
&
)

Con lo que la demostracin de la correccin de A


2
es en realidad la comprobacin de
que se verifica el requisito (r.5)
P
0
.d( x
&
).Q
0
[ x
&
/s( x
&
), y
&
/ y
&
][ X
&
/ X
&
]def(c( x
&
, y
&
)).
Q
0
[ y
&
/c( x
&
, y
&
)]

Si hubiese ms de un caso recursiva habra que derivar una funcin de composicin


para cada uno de ellos.
Si tenemos varias llamadas recursivas recursin mltiple deberemos derivar enton-
ces la accin A
1
a partir de la especificacin:
{P
0
.d( x
&
).Q
0
[ x
&
/s
1
( x
&
), y
&
/ y
&
1
][ X
&
/ X
&
1
]..
Q
0
[ x
&
/s
k
( x
&
), y
&
/ y
&
k
][ X
&
/ X
&
k
]}
A
2

{Q
0
}

donde las variables y


j
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
{P
0
.d( x
&
)}
y
&
:=nombreFunc(s( x
&
));
{P
0
.d( x
&
).Q
0
[ x
&
/s( x
&
), y
&
/ y
&
][ X
&
/ X
&
]}
y
&
:=c( x
&
, y
&
)
{Q
0
}

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 se-
gunda asignacin inducida por la postcondicin).
O si podemos incluir la llamada o llamadas recursivas dentro de la expresin que las
combina:
{P
0
.d( x
&
)}
y
&
:=c( x
&
,nombreFunc(s( x
&
)))
{Q
0
}
que tambin es correcta, porque para verificarla la convertiramos a la forma ante-
rior, sobre la que ya hemos razonado su correccin.
Este esquema se generaliza de manera inmediata para el caso de mltiples llama-
das 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;
{P
0
:n=N}
{Q
0
:n=N.s=i:0in}
(R.1) Planteamiento recursivo.
La idea recursiva es obtener el resultado como:
sumaDoble(n):=sumaDoble(n1)+2*n
(R.2) Anlisis de casos
El caso ms simple es cuando n = 0, en cuyo caso el resultado es 0; por lo tanto las con-
diciones que distinguen los casos:
d(n):n=0
d(n):n=0

demostramos la correccin
n=Ndef(n=0).def(n=0)cierto
n=Nn=0vn=0cierto
Algoritmos recursivos 178
(R.3) Solucin en el caso directo
Hemos de derivar una accin para

{n=N.n=0}
A
1

{n=N.s=i:0in:2*i}

Considerando que n=0 es evidente que alcanzamos la postcondicin con la asignacin:


A
1
:s:=0
(R.4) Descomposicin recursiva
La descomposicin recursiva ya apareca en el planteamiento recursivo, obtenemos la su-
ma de n a partir de la suma hasta n1:
s(n)=n1
(R.5) Funcin de acotacin
El tamao del problema viene dado por el valor de n, que ha de disminuir en las sucesi-
vas llamadas recursivas hasta llegar a 0:
t(n)=n

Probamos los requisitos


n=N.n=0def(n).dec(n)

dec(n)
n>0
: n=0.n:Nat
n=0.n=N
(R.6) Decremento de la funcin de acotacin
Efectivamente la descomposicin recursiva elegida hace que disminuya la funcin de aco-
tacin:
n=N.n=0n1<ncierto
(R.7) Es posible hacer la llamada recursiva
Hemos de demostrar
n=N.n=0P
0
[n/n1][N/N]

P
0
[n/n1][N/N]
Algoritmos recursivos 179
n1=N
n1:Nat
: n:Nat.n1
n:Nat.n=0
(R.8) Funcin de combinacin
Esta funcin apareca ya en el planteamiento recursivo: la suma del resultado recursivo y
2*n. Podemos ver que se deriva de la especificacin:
{P
0
.Q
0
[n/n1,s/s][N/N]:n=N.n=0.n1=N.s=i:1in1
:2*i}
A
2

{Q
0
:n=N.s=i:1in:2*i}

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 precondi-


cin es ms fuerte que la pmd de la asignacin inducida por Q
0
:
def(s+2*n).Q
0
[s/s+2*n]
cierto.n=N.s+2*n=i:1in:2*i
n=N.s+2*n=i:1in1:2*i+2*n
n=N.s=i:1in1:2*i
: P
0
.n=0.Q
0
[n/n1,s/s][N/N]
(R.9) Escritura del caso recursivo
Con lo anterior tenemos demostrado que es correcta la siguiente solucin para el caso re-
cursivo:
{P
0
.n=0}
s:=sumaDob(n1);
{P
0
.n=0.Q
0
[n/n1,s/s][N/N]}
s:=s+2*n
{Q
0
}

Nos resta plantearnos si es posible fundir la llamada recursiva en la funcin de combina-


cin. En este caso s es posible ya que la funcin devuelve un nico resultado que se utili-
za en la combinacin:
{P
0
.n=0}
s:=sumaDob(n1)+2*n
{Q
0
}
Con todo esto la funcin derivada queda:
Algoritmos recursivos 180
funcsumaDoble(n:nat)devs:Nat;
{P
0
:n=N}
inicio
si
n=0
{P
0
.n=0}
s:=0
{Q
0
}
n=0
{P
0
.n=0}
s:=sumaDoble(n1)+2*n
{Q
0
}
fsi
{Q
0
:n=N.s=i:1in:2*i}
devs
ffunc
Ejemplo: suma de las componentes de un vector de enteros
En ocasiones es necesario generalizar, incluyendo parmetros adicionales, la funcin que que-
remos obtener para descubrir as un planteamiento recursivo. Esto es lo que ocurre en este caso.
Nuestra primera idea de especificacin para este problema:
funcsumaVec(v:Vector[1..N]deEnt)devs:Ent;
{P
0
:v=V.N1}
{Q
0
:v=V.s=i:1iN:v(i)}
(R.1) Planteamiento recursivo
el problema es que el planteamiento recursivo evidente es:
para obtener recursivamente la suma de las n componentes de un vector, sumamos la
primera componente a la suma del resto
Pero para poder hacer una descomposicin como esa es necesario que la funcin suma-
Vec nos permita especificar hasta qu componente queremos calcular la suma. Por lo tan-
to para poder llevar a la prctica este planteamiento recursivo debemos modificar la
especificacin de la funcin:

funcsumaVec(v:Vector[1..N]deEnt;a:Ent)devs:Ent;
{P
0
:v=V.N1.b=B.1bN}
{Q
0
:v=V.b=B.s=i:aiN:v(i)}

Algoritmos recursivos 181


Pero, ya que estamos, y pensando en darle ms utilidad a nuestra funcin podemos gene-
ralizar 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;
{P
0
:v=V.N1.b=B.a=A.1abN}
{Q
0
:v=V.b=B.s=i:aib:v(i)}

Cuando necesitamos modificar la especificacin para llegar al planteamiento recursivo


debemos indicar entonces cul es la llamada inicial, es decir, qu valor se debe asignar a
los parmetros adicionales de entrada para resolver el problema original. En este caso
sumaVec(v,1,N)

Para facilitarle la vida a los usuarios de nuestra funcin podemos plantearnos la posibili-
dad 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)

(R.2) Anlisis de casos


El caso ms simple, caso directo, se tiene cuando la longitud del subvector es 1: a = b. En
ese caso tenemos que la suma es directamente el valor de la componente v(a).
3

d(v,a,b):a=b
d(v,a,b):a=b

P
0
def(a=b).def(a=b)
P
0
a=bva=b

(R.3) Solucin en el caso directo


Se deriva de la especificacin
{P
0
.a=b}
A
1

{a=A.b=B.v=V.s=i:aib:v(i)}

A
1
:s:=v(a)
3
Estamos incluyendo en la precondicin las restricciones que nos interesan y no nos preocupamos por garanti-
zarlas. 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
(R.4) Descomposicin recursiva
Utilizando la idea del planteamiento recursivo
s(v,a,b)=<v,a+1,b>
(R.5) Funcin de acotacin
La idea es que la longitud del subvector va a ir disminuyendo
t(v,a,b)=ba
Se demuestra
P
0
.a=bdef(ba).dec(ba)

dec(ba)
ba>0
b>a
: ba.a=b:P
0
.a=b
(R.6) Avance
Demostramos
P
0
.a=bb(a+1)<baba1<bacierto
(R.7) Llamada recursiva
Demostramos
P
0
.a=bP
0
[v/v,a/a+1,b/b][V/V,A/A,B/B]

P
0
[v/v,a/a+1,b/b][V/V,A/A,B/B]
v=V.a+1=A.b=B.1a+1bN

porpartestenemosque
v=V.a=A.b=Bv=V.a+1=A.b=B
ab.a=ba+1b
(R.8) Funcin de combinacin
La funcin de combinacin ya apareca en el planteamiento recursivo, como la suma de la
componente v(a) y el resultado de sumar v[a+1 .. b]. Pero podemos ver que se deduce de
la especificacin:
{P
0
.a=b.Q
0
[v/v,a/a+1,b/b,s/s][V/V,A/A,B/B]}
A
2

{Q
0
}

o lo que es igual
Algoritmos recursivos 183
{P
0
.a=b.v=V.a=A.b=B.s=i:a+1ib:v(i)
}
A
2

{v=V.a=A.b=B.s=i:aib:v(i)}

las diferencias se resuelven con la asignacin:


A
2
:s:=v(a)+s
(R.9) Escritura del caso recursivo
Es posible incluir la llamada recursiva en la expresin de combinacin?
{P
0
.a=b}
s:=v(a)+sumaVec(v,a+1,b)
{Q
0
}
De forma que la funcin resultante:
funcsumaVec(v:Vector[1..N]deEnt;a,b:Ent)devs:Ent;
{P
0
:v=V.N1.b=B.a=A.1abN}
si
a=bs:=v(a)
a=bs:=v(a)+sumaVec(v,a+1,b)
fsi
{Q
0
:v=V.b=B.s=i:aib:v(i)}
devs
ffunc
Implementacin recursiva de la bsqueda dicotmica (o binaria)
Queremos derivar una funcin recursiva que encuentre la aparicin ms a la derecha de un va-
lor x dentro de un vector v. De no encontrarse, queremos que nos indique la posicin donde se
debera insertar. En esta funcin hay que ser cuidadoso con el tratamiento de los ndices y tener
en cuenta la posibilidad de que no est en el vector, o que sea mayor que todos los elementos del
vector o menor que todos ellos.
Vamos a utilizar la misma idea de la implementacin iterativa: comparamos el elemento bus-
cado con el elemento central del subvector, y segn el resultado de la comparacin seguimos
buscando en el subvector de la izquierda o en el de la derecha. De nuevo el planteamiento recur-
sivo nos obliga a generalizar la funcin a obtener, para incluir como parmetros los lmites del
subvector a considerar.
En la implementacin iterativa aparecan dos variables, pos y q, que servan para ir acotando el
subvector a considerar; variables que bamos modificando hasta llegar a la condicin pos=q+1.
Lo que ocurre es que estas variables no indican exactamente dnde empieza y dnde acaba el sub-
vector a considerar, sino que indican a partir de qu posicin pos tenemos componentes me-
nores o iguales que x y partir de qu posicin q tenemos componentes mayores que x. Por eso
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 resul-
ta 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;
{P
0
:1abN.ord(v,a,b)}
{Q
0
:a1pb.v[a..p]x<v[(p+1)..b]}

donde:
hemos obviado los asertos sobre la conservacin de los parmetros de entrada (demasia-
do laborioso)
El aserto ord(v,a,b), que ya apareci en un ejercicio indica que el vector entre las posicio-
nes 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)

Los casos extremos para el valor de pos son


p=a1x<v[a..b] %xnoestyesmenorquetodaslascomponentes
Algoritmos recursivos 185
p=bv[a..b]x%v(b)=xxesmayorquetodaslascomponentes
(R.1) Planteamiento recursivo
Tomamos el punto intermedio m entre a y b, si x es menor que v(m) entonces seguimos
buscando en [a .. m1]; si x es mayor que v(m) entonces seguimos buscando en [m+1 .. b]
y si x es igual a v(m) entonces seguimos buscando en [m+1 .. b]. Se podra pensar que en
este ltimo caso es ms razonable seguir buscando en [m .. b], pero esto nos puede llevar
a un bucle infinito pues puede ocurrir que a=m, con lo que no decrementaramos la longi-
tud del subvector a considerar. Esto lleva a que el valor buscado se pueda quedar a la iz-
quierda del subvector considerado, pero an as el resultado ser correcto. (podemos
discutir este detalle al obtener la descomposicin recursiva).
(R.2) Anlisis de casos
Vamos a considerar como caso directo el caso en el que tenemos un subvector de longi-
tud 1, es decir:
d(v,x,a,b):a=b
d(v,x,a,b):a=b
demostramos
P
0
def(a=b).def(a=b)
P
0
a=bva=b
(R.3) Solucin en el caso directo.
Parece claro que tenemos que hacer una distincin de casos segn el resultado de compa-
rar x con la nica componente del vector.
Las condiciones que discriminan los distintos tratamientos son
v(a)=x v(a)<x v(a)>x

Tenemos que las barreras estn definidas:


P
0
.a=bdef(v(a)=x).def(v(a)<x).def(v(a)>x)

puesto que
P
0
enRango(v,a)

y al menos una se abre:


P
0
.a=bv(a)=xvv(a)<xvv(a)>xcierto

derivamos entonces el cdigo de cada una de las ramas:


{P
0
.a=b.v(a)=x}
Algoritmos recursivos 186
A
1

{v[a..p]x<v[(p+1)..b]}

A
1
:p:=a

{P
0
.a=b.v(a)<x}
A
2

{v[a..p]x<v[(p+1)..b]}

A
2
:p:=a

{P
0
.a=b.v(a)>x}
A
3

{v[a..p]x<v[(p+1)..b]}

A
3
:p:=a1

con lo que la solucin del caso directo queda


si
v(a)xp:=a
v(a)>xp:=a1
fsi

tambin podemos intentar obtenerla razonando sobre la postcondicin, aadindole a=b.


Podemos aadir esta condicin porque sabemos que est en la precondicin de la accin
que queremos derivar, y sabemos que dicha accin no afectar a sus valores, por lo que
debe cumplirse tambin en el estado final: (Esta es la primera vez que utilizamos un razo-
namiento de este tipo)
a1pb.v[a..p]x<v[(p+1)..a].a=b
a1pa.v[a..p]x<v[(p+1)..a].a=b

ppuedeseraa1

(a=p.v[a..a]x<v[(a+1)..a].a=b)v
(a1=p.v[a..(a1)]x<v[a..a].a=b)
(a=p.v(a)x.a=b)v(a1=p.x<v(a).a=b)
y de aqu obtenemos la solucin para el caso base.

(R.4) Descomposicin recursiva


La descomposicin recursiva toma la forma de una seleccin alternativa. Obtenemos el
punto intermedio y segn el resultado de comparar ese punto intermedio con el valor de
x descomponemos la llamada de una u otra forma. Podramos haberlo derivado descom-
poniendo el caso recursivo en ms de un caso, pero para no repetir cdigo el clculo de
m lo hacemos de esta otra forma.
m:=(a+b)div2;
Algoritmos recursivos 187

tenemos que
ab.a=ba<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
{P
0
.d( x
&
)}
m:=(a+b)div2
{P
0
.d( x
&
).am<b}
A
4

{Q
0
}

la parte interesante de la postcondicin es


v[a..p]x<v[(p+1)..b]

Vamos a derivar A
4
como una composicin alternativa, segn el resultado de la compara-
cin
Si x < v(m) quiere decir que m est entre p+1 y b, por lo tanto podemos acotar la
bsqueda con la descomposicin:
s
1
(v,x,a,b)=<v,x,a,m1>

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:
s
2
(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 con-
siderar, por lo tanto tomamos como funcin de acotacin:
t(v,x,a,b)=ba

y demostramos que est definida y es decrementable


P
0
.a=b.am<bdef(ba).dec(ba)

dec(ba)
Algoritmos recursivos 188
ba>0
b>a
: ab.a=b
: P
0
.a=b
(R.6) Terminacin
Tenemos que demostrar
P
0
.a=b.am<bt(s
1
( x
&
))<t( x
&
).t(s
2
( x
&
))<t( x
&
)

(m1)a<ba
: m1<b
: m<b

b(m+1)<ba
bm1<ba
: bmba
ma
(R.7) Llamada recursiva
Tenemos que demostrar:
P
0
.a=b.am<bP
0
[ x
&
/s
1
( x
&
)].P
0
[ x
&
/s
2
( x
&
)]
La precondicin es 1 a b N . ord(v,a,b). El requisito de la ordenacin se mantiene
porque el vector entero est ordenado lo que debemos comprobar es que se conserva la
otra condicin:
1abN[a/a,b/m1]
1am1N
:1abN.a=b.am<b?

No es posible demostrarlo, de a m no se puede obtener a m1. El problema es que


se puede pasar de un subvector de longitud 2 a un subvector de longitud 0:
a=1,b=2,v(1)=3,v(2)=4,x=2
m=1,v(m)>xb=a1

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+1.0bN.ab+1

Hay que cambiar las condiciones de los casos:


d
1
( x
&
):a=b+1
d
2
( x
&
):a=b
Algoritmos recursivos 189
d
3
( x
&
):a<b

En los razonamientos sobre la terminacin (R.5 y R.6) no hay que cambiar nada por-
que estbamos razonando a partir de a b . a = b que es equivalente a razonar con
a < b.
Tampoco hay que cambiar nada en la descomposicin recursiva
Ahora s es posible demostrar:
P
0
.a=b.am<bP
0
[ x
&
/s
1
( x
&
)].P
0
[ x
&
/s
2
( x
&
)]
1ab+1N+1[a/a,b/m1]
1am1+1N+1
1amN+1
: 1ab+1N+1.a=b.am<b

1ab+1N+1[a/m+1,b/b]
1m+1b+1N+1
0mbN
: 1ab+1N+1.a=b.am<b
(R.3)
2
Solucin al segundo caso base
Hemos de derivar una accin a partir de:
{P
0
.a=b+1}
A
5

{Q
0
}
Razonamos a partir de la postcondicin y a = b+1
a1pb.v[a..p]v(x)<v[(p+1)..b].a=
b+1
a1pa1.v[a..(a1)]v(x)<v[a..(a1)].a
=b+1
a1=p.a=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 inser-
cin); o, si no admitisemos como caso base a=b, porque v(b)x con lo que p debe apun-
tar 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
P
0
.am<b.a<b.v(m)x.m+11pb.v[(m+1)..p]x<
v[(p+1)..b]
Algoritmos recursivos 190

a1pb.v[a..p]x<v[(p+1)..b]

Se cumple lo anterior porque en P


0
se incluye ord(v, a, b), por lo que v[a..m] x
De la misma forma, para la otra llamada recursiva
P
0
.am<b.a<b.v(m)>x.a1pm1.v[a..p]x<
v[(p+1)..(m1)]

a1pb.v[a..p]x<v[(p+1)..b]
Tambin se tiene por ord(v,a,b) y por lo tanto x < v[m..b]
(R.9) Escritura de la llamada recursiva
Con todo lo anterior la solucin al caso recursivo queda:
m:=(a+b)div2;
si
v(m)xp:=buscaBin(v,x,m+1,b)
v(m)>xp:=buscaBin(v,x,a,m1)
fsi

Y finalmente la funcin completa:


funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P
0
:1abN.ord(v,a,b)}
var
m:Ent;
inicio
si
a=b+1p:=a1
a=bsi
v(a)xp:=a
v(a)>xp:=a1
fsi
a<bm:=(a+b)div2;
si
v(m)xp:=buscaBin(v,x,m+1,b)
v(m)>xp:=buscaBin(v,x,a,m1)
fsi
fsi
{Q
0
:a1pb.v[a..p]x<v[(p+1)..b]}
devp
ffunc

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 191
1.3.1 Algoritmos avanzados de ordenacin
Vamos a estudiar dos algoritmos de ordenacin de complejidad cuasi-lineal, O(n log n): la or-
denacin 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);
{P
0
:v=V.a=A.b=B.1ab+1N+1}
{Q
0
:a=A.b=B.perm(v,V,a,b).ord(v,a,b).
(i:1i<a:v(i)=V(i)).(j:b<jN:v(j)=V(j))}
fproc
Como en el caso de la bsqueda binaria la necesidad de encontrar un planteamiento recursivo
nos lleva a aadir dos parmetros adicionales al procedimiento, para as poder indicar el subvec-
tor de que nos ocupamos en cada llamada recursiva.
El objetivo de esta funcin es ordenar el subvector comprendido entre las posiciones a y b, de-
jando inalterado el resto del vector.
Tambin de la misma forma que en la bsqueda dicotmica permitimos la llamada con un
subvector de longitud 0
a=b+1
Aparece una notacin que no habamos utilizado hasta ahora:
perm(v,V,a,b)
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 igua-
les, 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
P
0
.a>ba=b+1
Q
0
.a=b+1v=V

a b
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
reordenar parcialmente el subvector v[a..b] para conseguir que x quede en la posicin
p que ocupar cuando v[a..b] est ordenado. Para ello tenemos que colocar a su iz-
quierda los elementos menores o iguales que x y a su derecha los elementos mayores
o iguales.
ordenar recursivamente v[a..(p1)] y v[(p+1)..b]. Este algoritmo ejemplifica la estra-
tegia general de resolucin de problemas divide y vencers, puesto que resuelve el pro-
blema dividindolo en dos partes ms pequeas.
Por ejemplo:
3,5 6,2 2,8 5,0 1,1 4,5
2,8 1,1 3,5 5,0 6,2 4,5
a b
a b
p
<=3,5 >=3,5
Particin
Funcin de acotacin
t(v,a,b)=ba+1

Hay que sumarle 1 porque tambin consideramos caso recursivo cuando a = b, y as evi-
tamos 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);
{P
0
:v=V.a=A.b=B.1ab+1N+1}
var
p:Ent;
inicio
si
a>bseguir{Q
0
}
ab{P
0
.ab}
particion(v,a,b,p);
{Q}
Algoritmos recursivos 193
{Q.P
0
[b/p1].P
0
[a/p+1]
quickSort(v,a,p1);
quickSort(v,p+1,b);
{Q.Q
0
[b/p1].Q
0
[a/p+1]}
{Q
0
}
fsi
{Q
0
:a=A.b=B.perm(v,V,a,b).ord(v,a,b).
(i:1i<a:v(i)=V(i)).(j:b<jN:v(j)=V(j))}
fproc
Donde Q es la postcondicin de particin, y de la precondicin la parte que nos interesa es:
P:1abN:P
0
.ab
Q:1apbN.perm(v,V,a,b).v[a..(p1)]v(p)
v[(p+1)..b].
(i:1i<a:v(i)=V(i)).(i:b<iN:v(i)=V(i))
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)
x
a i
j
b
<=x >=x
Invariante
I:a+1ij+1b+1.v[(a+1)..(i1)]v(a)v[(j+1)..b]

tambin podramos aadir


.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 %cuandoi=j+1hemosparticionadotodoelvector
B:ij
De esta forma al final del bucle se tiene:
I.BI.i=j+1v[(a+1)..j]v(a)v[i..b]

Algoritmos recursivos 194


con lo que las acciones
p:=j;
intercambiar(v,a,p);
ejecutadas a la salida del bucle harn que se alcance la postcondicin
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 vac-
os.

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
si
v(i)v(a)i:=i+1
v(j)v(a)j:=j1
v(i)>v(a)ANDv(j)<v(a)intercambiar(v,i,j);
<i,j>:=<i+1,j1>
fsi
fit
{I.i=j+1}
p:=j;
intercambiar(v,a,p)
Algoritmos recursivos 195
{Q}
fproc
Ordenacin por mezcla
Por las mismas razones que en la ordenacin rpida, decidimos disear este algoritmo como
un procedimientos.
Partimos de una especificacin similar a la del quickSort
procmergeSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P
0
:v=V.a=A.b=B.1ab+1N+1}
{Q
0
:a=A.b=B.perm(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
a b
Segmento de longitud 1.
Ya est ordenado
P
0
.aba=bva=b+1
Q
0
.(a=bva=b+1)v=V
En este caso no hay que hacer nada para garantizar la postcondicin.
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.
Tomando m = (a+b) div 2 ordenamos recursivamente v[a..m] y v[(m+1)..b].
Usamos un procedimientos auxiliar para mezclar las dos mitades, quedando ordena-
do todo v[a..b]
Ejemplo:
Algoritmos recursivos 196
3,5 6,2 2,8 5,0 1,1 4,5
2,8 3,5 6,2 1,1 4,5 5,0
a b
a b
m m+1
m m+1
Ordenacin
recursiva de las
dos mitades
1,1 2,8 3,5 4,5 5,0 6,2
a b
Mezcla
Funcin de acotacin
t(v,a,b)=ba+1
Suponiendo que tenemos una implementacin correcta para mezcla, el procedimiento de orde-
nacin queda:
procmergeSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P
0
:v=V.a=A.b=B.1ab+1N+1}
var
m:Ent;
inicio
si
abseguir{Q
0
}
a<b{P
0
.a<b}
m:=(a+b)div2;
{P
0
.am<b}
{P
0
[b/m].P
0
[a/m+1]}
mergeSort(v,a,m);
mergeSort(v,m+1,b);
{am<b.Q
0
[b/m].Q
0
[a/m+1]}
mezcla(v,a,m,b)
{Q
0
}
fsi
{Q
0
:a=A.b=B.perm(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);
{P
0
:v=V.a=A.m=M.c=C.am<b.ord(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
si
u(i)u(j)
v(k):=v(i);
i:=i+1;
u(i)>u(j)
v(k):=u(j);
j:=j+1;
fsi;
k:=k+1;
itim
v(k):=u(i);
<i,k>:=<i+1,k+1>
fit;
itjb
v(k):=u(j);
<j,k>:=<j+1,k+1>
fit
{Q
0
:a=A.b=B.m=M.perm(V,v,a,b).ord(v,a,b).
(i:1i<a:v(i)=V(i)).(i:b<iN:v(i)=V(i)}
2.4 Anlisis de algoritmos recursivos
Como la recursividad no introduce nuevas instrucciones en el lenguaje algortmico, en princi-
pio no es necesario incluir nuevos mecanismos para el clculo de la complejidad de funciones
recursivas. El problema es que cuando nos ponemos a analizar la complejidad de una funcin
recursiva nos encontramos con que debemos conocer la complejidad de las llamadas recursivas,
Algoritmos recursivos 198
es decir, necesitamos conocer la complejidad de la propia funcin que estamos analizando. El
resultado es que la definicin natural de la funcin de complejidad de una funcin recursiva suele
ser tambin recursiva; a las funciones as definidas se las denomina ecuaciones de recurrencia. Veamos
un par de ejemplos de anlisis de funciones recursivas conocidas donde obtendremos las ecua-
ciones de recurrencia que definen su complejidad.
Clculo del factorial
Tamao de los datos: n
Caso directo: T(n) = 3 si n = 1
El 3 se obtiene porque hay que evaluar las dos barreras (recurdese que en una composi-
cin alternativa se evalan todas las barreras) y ejecutar la asignacin.
Caso recursivo:
2 de evaluar ambas barreras
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).
De esta forma las ecuaciones de recurrencia
3 si n = 1
T(n) =
4 + T(n1) si n > 1
Mtodo del campesino egipcio
Tamao de los datos: n = b
Caso directo: T(n) = 5 si n = 0, 1
4 de evaluar todas las barreras y 1 de la correspondiente asignacin
En ambos casos recursivos:
4 de evaluar ambas barreras
1 de la asignacin
2 de evaluar la descomposicin 2*a y bdiv2.
T(n/2) de la llamada recursiva (el coste de la funcin con datos de tamao b div 2).
De esta forma las ecuaciones de recurrencia
5 si n = 0, 1
T(n) =
7 + T(n/2) si n > 1
Algoritmos recursivos 199
Las torres de Hanoi
Tamao de los datos: n
Caso directo: T(n) = 2 si n = 0
2 de evaluar todas las barreras
En el caso recursivo:
2 de evaluar ambas barreras
1 del movimiento
2*T(n1) de las dos llamadas recursiva
De esta forma las ecuaciones de recurrencia
2 si n = 0
T(n) =
3 + 2*T(n1) si n > 0
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
aplicar soluciones generales que se conocen para algunos tipos comunes de recurrencias
1.4.1 Despliegue de recurrencias
El proceso se compone de tres pasos:
Despliegue. Sustituimos las apariciones de T en la recurrencia tantas veces como sea ne-
cesario 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 di-
recto y sustituimos la referencia recursiva por la complejidad del caso directo. (Aqu esta-
mos 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 ecuacio-
nes 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
El caso directo se tiene para n = 1; para alcanzarlo tenemos que hacer
k=n1

T(n)=4(n1)+T(n(n1))
=4n4+T(1)
=4n4+3
=4n1

por lo tanto
T(n)eO(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/2
k
)

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/2
logn
)
=7logn+T(1)
Algoritmos recursivos 201
=7logn+5

por lo tanto
T(n)eO(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 compor-
tamiento 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 in-
teresa 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+2
2
*3+2
3
*T(n3)

=3
_

=
1
0
k
i
2
i
+2
k
T(nk)
Postulado
El caso directo se tiene para n = 0; para alcanzarlo
k=n

T(n)=3
_

=
1
0
k
i
2
i
+2
k
T(nk)
=3
_

=
1
0
n
i
2
i
+2
n
T(nn)
(*)=3
_

=
1
0
n
i
2
i
+2
n
T(0)
=3(2
n
1)+22
n

=52
n
3
Algoritmos recursivos 202
donde en (*) hemos utilizado la frmula para la suma de progresiones geomtricas:
_

=
1
0
n
i
r
i
=
1
1

r
r
n
r=1

Por lo tanto:
T(n)eO(2
n
)

Es un algoritmo de coste exponencial; es habitual que las funciones recursivas mltiples ten-
gan costes prohibitivos.
1.4.2 Resolucin general de recurrencias
Se pueden obtener unos resultados tericos aplicables a un gran nmero de recurrencias, aque-
llas 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 en-
tonces que esto da lugar a recurrencias de la forma:
(D) T(n) = G(n) si 0 n < b
(R) T(n) = a T(nb) + C(n) si n b
donde:
a 1 es el nmero de llamadas recursivas
b 1 es la disminucin del tamao de los datos
G(n) es el coste en el caso directo
C(n) es el coste de preparacin de las llamadas y de combinacin de los resultados
Aplicando la tcnica de despliegue sobre este esquema:
T(n)=aT(nb)+C(n)
=a(aT(nbb)+C(nb))+C(n)
=a
2
T(n2b)+aC(nb)+C(n)
=a
2
(aT(n2bb)+C(n2b))+aC(nb)+C(n)
=a
3
T(n3b)+a
2
C(n2b)+aC(nb)+C(n)

T(n)=a
m
T(nmb)+
_

=
1
0
m
i
a
i
C(nib)

Algoritmos recursivos 203


Como nos interesa el comportamiento asinttico podemos tomas las siguientes hiptesis sim-
plificadoras:

n=mbm=n/b esdecir,consideramosslolosnque
sonmltiplosdeb
T(0)=G(0)=c
0
delosposiblescasosdirectos(0n<b)nos
quedamosconuno(n=0)
De esta forma podemos eliminar la recurrencia de la expresin anterior, obteniendo:
T(n)=a
m
c
0
+
_

=
1
0
m
i
a
i
C(mbib)
Donde damos nombre a cada uno de los sumandos para poder tratarlos por separado:
T
1
(n)=a
m
c
0

T
2
(n)=
_

=
1
0
m
i
a
i
C(b(mi))

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)=cn
k
paraciertasceR
+
,keN

Y ahora estudiamos dos posibilidades dependiendo del valor de a el nmero de llamadas re-
cursivas:
a = 1

T
1
(n)=c
0

T
2
(n)=
_

=
1
0
m
i
c(b(mi))
k

=cb
k

=
1
0
m
i
(mi)
k
=cb
k
m
m
k k

+
2
1

cb
k
m
k+1

=cb
k
(n/b)
k+1

=c/bn
k+1

por lo tanto
T
2
(n)eO(n
k+1
)

y como T
2
(n) domina a T
1
(n), que es una constante, tenemos
Algoritmos recursivos 204
T(n)=c
0
+T
2
(n)eO(n
k+1
)

Cuando se aplica una nica llamada recursiva (a = 1) obtenemos un orden de complejidad po-
linmico en n, cuyo exponente es uno ms que el del coste de C(n)
a > 1
T
1
(n)=a
m
c
0

T
2
(n)=
_

=
1
0
m
i
a
i
c(b(mi))
k

=
_

=
1
0
m
i
a
i

m
m
a
a
c(b(mi))
k
%multiplicandoarribay
abajopora
m

=a
m
cb
k

=
1
0
m
i
m
k i
a
i m a ) (

=a
m
cb
k

=
1
0
m
i
i m
k
a
i m

) (

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

T
2
(n)=a
m
cb
k

_
=
m
j 1
j
k
a
j

y se cumple

T
2
(n)=a
m
cb
k

_
=
m
j 1
j
k
a
j
<a
m
cb
k
s

siendo s la suma de la serie convergente

s=
_

=1 j
j
k
a
j

tenemos por lo tanto


T
2
(n)eO(a
m
)

T(n)=c
0
a
m
+T
2
(n)eO(a
m
)=O(a
ndivb
)
Algoritmos recursivos 205

Es decir, cuando tenemos varias llamadas recursivas (a>1) y el tamao del problema disminu-
ye 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)eO(2
n
)

Disminucin del tamao del problema por divisin


Si la descomposicin recursiva se obtiene dividiendo por una cierta cantidad constante, tene-
mos entonces que esto da lugar a recurrencias de la forma:
(D) T(n) = G(n) si 0 n < b
(R) T(n) = a T(n/b) + C(n) si n b
donde:
a 1 es el nmero de llamadas recursivas
b 2 es el factor de disminucin del tamao de los datos
G(n) es el coste en el caso directo
C(n) es el coste de preparacin de las llamadas y de combinacin de los resultados
Aplicando la tcnica de despliegue sobre este esquema:
T(n)=aT(n/b)+C(n)
=a(aT(n/b/b)+C(n/b))+C(n)
=a
2
T(n/b
2
)+aC(n/b)+C(n)
=a
2
(aT(n/b
2
/b)+C(n/b
2
))+aC(n/b)+C(n)
=a
3
T(n/b
3
)+a
2
C(n/b
2
)+aC(n/b)+C(n)

T(n)=a
m
T(n/b
m
)+
_

=
1
0
m
i
a
i
C(n/b
i
)

Como nos interesa el comportamiento asinttico podemos tomas las siguientes hiptesis sim-
plificadoras:

n=b
m
m=log
b
n esdecir,consideramosslolosnqueson
potenciasdeb
T(1)=G(1)=c
1
delosposiblescasosdirectos(0n<b)nos
quedamosconuno(n=1)
De esta forma podemos eliminar la recurrencia de la expresin anterior, obteniendo:
Algoritmos recursivos 206
T(n)=a
m
c
1
+
_

=
1
0
m
i
a
i
C(n/b
i
)=a
m
c
1
+
_

=
1
0
m
i
a
i
C(b
m
/b
i
)
T(n)=a
m
c
1
+
_

=
1
0
m
i
a
i
C(b
mi
)
Donde damos nombre a cada uno de los sumandos para poder tratarlos por separado:
T
1
(n)=a
m
c
1

T
2
(n)=
_

=
1
0
m
i
a
i
C(b
mi
)
podemos transformar T
1
(n) de la siguiente forma

T
1
(n)=a
m
c
1
=
n
b
a
log
c
1
=c
1

a
b
n
log

donde la ltima igualdad se tiene por


n
b
a
log
= ( )
n
a b
b
b
log
log
=
) )(log (log a n
b b
b =
a
b
n
log

Continuamos el estudio suponiendo, como ya hicimos en el estudio anterior, que


C(n)=cn
k
paraciertasceR
+
,keN

Y, de esta forma, podemos elaborar T


2
(n):
T
2
(n)=
_

=
1
0
m
i
a
i
C(b
mi
)
=
_

=
1
0
m
i
a
i
cb
(mi)k

=c
_

=
1
0
m
i
a
i

k m
k m
b
b

b
(mi)k
%multiplicandoarribayabajoporb
m
k

=cb
mk

=
1
0
m
i
a
i
b
ik

=cb
mk

=
1
0
m
i
i
k
b
a
|
|
.
|

\
|

Con lo que la complejidad queda

T(n)=c
1

a
b
n
log
+cb
mk

=
1
0
m
i
i
k
b
a
|
|
.
|

\
|

Algoritmos recursivos 207


Estudiamos el valor del sumatorio segn la relacin que exista entre a y b
k
.
a = b
k
Tenemos entonces
T
1
(n)=c
1
n
k

T
2
(n)=cb
mk

=
1
0
m
i
1
i

=cb
mk
m
=cn
k
log
b
n %yaquem=log
b
n

De esta forma
T(n)=c
1
n
k
+cn
k
log
b
neO(n
k
log
b
n)=O(n
k
logn)

Vemos que T
2
(n) domina a T
1
(n). Observamos asimismo que la complejidad depende de n
k
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 n
k
.
a < b
k
Tenemos entonces que
log
b
a<k
y, por lo tanto
T
1
(n)=c
1

a
b
n
log
<c
1
n
k

T
2
(n)=cb
mk

=
1
0
m
i
i
k
b
a
|
|
.
|

\
|

como a < b
k
tenemos una serie geomtrica de razn r = a/b
k
< 1
T
2
(n)=cb
mk

1
1

r
r
m

=c
1 ) / (

k
mk m
b a
b a

si a < b
k
entonces a
m
< b
mk
por lo tanto el trmino dominante en la anterior expresin es
b
mk
y podemos afirmar (ntese que la anterior expresin tiene signo positivo):
T
2
(n)eO(b
mk
)=O(
k n
b
b
) (log
)=O(n
k
)
Algoritmos recursivos 208

Vemos que T
2
(n) domina a T
1
(n) y por lo tanto
T(n)eO(n
k
)

Observamos asimismo que la complejidad depende de n


k
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 n
k
.
a > b
k
Tenemos entonces que
log
b
a>k
y, por lo tanto
T
1
(n)=c
1

a
b
n
log

T
2
(n)=cb
mk

=
1
0
m
i
i
k
b
a
|
|
.
|

\
|

como a > b
k
tenemos una serie geomtrica de razn r = a/b
k
> 1
T
2
(n)=cb
mk

1
1

r
r
m

=c
1 ) / (

k
mk m
b a
b a

si a > b
k
entonces a
m
> b
mk
por lo tanto el trmino dominante en la anterior expresin es
a
m
y podemos afirmar (ntese que la anterior expresin tiene signo positivo):
T
2
(n)eO(a
m
)=O(
n
b
a
log
)=O(
a
b
n
log
)

por lo tanto, por la regla de la suma de complejidades


T(n)=T
1
(n)+T
2
(n)eO(
a
b
n
log
)

Observamos aqu que T


1
(n) y T
2
(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 efi-
ciencia 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
c
0
si 0 n < b
Algoritmos recursivos 209
T(n) =
a T(nb) + c n
k
si n b
O(n
k+1
) si a = 1
T(n) e
O(a
n div b
) si a > 1
Cuando se aplica una nica llamada recursiva (a = 1) obtenemos un orden de complejidad po-
linmico 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 divi-
sin 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)eO(2
n
)
Divisin del problema por divisin
c
1
si 0 n < b
T(n) =
a T(n/b) + c n
k
si n b
O(n
k
) si a < b
k
T(n) e O(n
k
log n) si a = b
k
O(
a
b
n
log
) si a > b
k
Si a < b
k
o a = b
k
la complejidad depende de n
k
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 compleji-
dad de la preparacin de las llamadas y la combinacin de los resultados: C(n) = c n
k
.
Si a > b
k
no se consigue mejorar la eficiencia global mejorando la eficiencia de C(n). Las mejo-
ras se obtendrn disminuyendo a el nmero de llamadas recursivas o aumentando b
obtenindose as subproblemas de menor tamao.
1.4.3 Anlisis de algunos algoritmos recursivos
Ordenacin por mezcla
La recurrencia es de la forma
c
1
si 1 n 0
T(n) =
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 n
k
) 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 = b
k
y entonces la complejidad es:
O(n log n)
Nmeros de Fibonacci
T(0) = c
0
T(1) = c
1
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
O(a
n div b
) = O(2
n
)
complejidad exponencial.
Algoritmos recursivos 211
Fibonacci con recursin final
Es la funcin dosFib del ejercicio 99
T(0) = c
0
Tn) = T(n1) + c n > 0
Divisin del problema por sustraccin
a = 1, b = 1, k = 0
Como se tiene a = 1
O(n
k+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;
Lo vimos en el tema de derivacin de algoritmos recursivos. Suma v(a) al resultado de sumar
recursivamente v[a+1..b].
Tomamos como tamao de los datos
n = b a + 1 la longitud del subvector a sumar
Recurrencia:
T(1) = c
1
T(n) = T(n1) + c si n > 1
Divisin del problema por substraccin
a = 1, b = 1, k = 0
Como se tiene a = 1
O(n
k+1
) = O(n)
Bsqueda dicotmica
Tomamos como tamao de los datos:
Algoritmos recursivos 212
n = b a + 1
Recurrencias
T(0) = c
0
T(1) = c
1
T(n) = T(n/2) + c si n > 1 Suponiendo n = 2
m
Divisin del problema por divisin:
a = 1, b = 2, k = 0
Como a = b
k
1 = 2
0
O(n
k
log n) = O(n
0
log n) = O(log n)
Es una gran mejora con respecto a la bsqueda secuencial, que tiene complejidad O(n). Ntese
que log n no est definido para n = 0; podramos dar como orden de complejidad O(log (n+1)), o
simplemente ignorar el valor n = 0, pues estamos dando una medida asinttica.
Mtodo de ordenacin rpida
Tambin tomamos como tamao de los datos la longitud del vector:
n = b a + 1
Aqu el punto clave es cuntos elementos se quedan a la izquierda y cuntos a la derecha del
elemento pivote. El caso peor es cuando no separa nada, es decir, es el mnimo o el mximo del
intervalo; en ese caso:
T(0) = c
0
T(n) = T(0) + T(n1) + c n 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(n
k+1
) = O(n
2
)
El caso peor se produce cuando el vector est ordenado, creciente o decrecientemente.
El caso mejor se obtiene suponiendo que el pivote divide al subvector en dos mitades iguales,
en cuyo caso tenemos:
Algoritmos recursivos 213
T(0) = c
0
T(n) = 2T(n/2) + c n si n 1
con
a = b = 2 k = 1
disminucin del problema por divisin con a = b
k
O(n
k
log n) = O(n log n)
Con una ligera modificacin del algoritmo, se puede conseguir que el caso peor no ocurra
cuando el vector est ordenado: seleccionando como pivote el elemento central del intervalo.
Se puede demostrar que en promedio la complejidad coincide con la del caso mejor.
2.5 Transformacin de la recursin final a forma iterativa
En general un algoritmo iterativo es ms eficiente que uno recursivo porque la invocacin a
procedimientos o funciones tiene un cierto coste. Es por ello que tiene sentido plantearse la
transformacin de algoritmos recursivos en iterativos.
Los compiladores en algunos casos recursin final eliminan la recursin al traducir los pro-
gramas a cdigo mquina.
El inconveniente de transformar los algoritmos recursivos en iterativos radica en que puede
ocurrir que el algoritmo iterativo sea menos claro, con lo cual se mejora la eficiencia a costa de
perjudicar a la facilidad de mantenimiento. Como en tantas otras ocasiones es necesario llegar a
compromisos.
Eliminacin de la recursin final
El esquema general de una funcin recursiva final es como ya vimos en un tema anterior:
funcnombreFunc(x
1
:t
1
;;x
n
:t
n
)devy
1
:o
1
;;y
m
:o
m
;
{P( x
&
)}
var
x
&
:t
&
;
inicio
si
d( x
&
){P( x
&
).d( x
&
)}
y
&
:=g( x
&
)
{Q( x
&
, y
&
)}
d( x
&
){P( x
&
).d( x
&
)}
x
&
:=s( x
&
);
{P( x
&
)}
Algoritmos recursivos 214
y
&
:=nombreFunc( x
&
)
{Q( x
&
, y
&
)}
{Q( x
&
, y
&
)}
fsi
{Q( x
&
, y
&
)}
dev y
&

ffunc
Intuitivamente, podemos imaginar la ejecucin de una llamada de la forma
y
&
:= nombreFunc( x
&
)
como un bucle descendente
x
&
nombreFunc( x
&
)
!
nombreFunc(s( x
&
))
!
nombreFunc(s
2
( x
&
))
!

!
nombreFunc(s
n
( x
&
)) g(s
n
( x
&
)) 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 nombre-
Func de la siguiente forma:
funcnombreFuncItr(x
1
:t
1
;;x
n
:t
n
)devy
1
:o
1
;;y
m
:o
m
;
{P( x
&
)}
var
x
&
:t
&
;
inicio
x
&
:= x
&
;
{I:P( x
&
).nombreFunc( x
&
)=nombreFunc( x
&
);
C:t( x
&
)}
itd( x
&
)
Algoritmos recursivos 215
x
&
:=s( x
&
)
fit;
{I.d( x
&
)}
{nombreFunc( x
&
)=g( 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 ite-
racin 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 coin-
cide 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 ma-
nera.
1.5.1 Ejemplos
Vamos a aplicar la transformacin a dos algoritmos concretos.
Factorial
Transformamos la versin recursiva final, acuFact, que desarrollamos en el ejercicio 82:
funcacuFact(a,n:Nat)devm:Nat;
{P
0
:a=A.n=N}
var
a,n:Nat;
inicio
si
n=0m:=a
n>0<a,n>:=<a*n,n1>;
m:=acuFact(a,n)
fsi
{Q
0
:a=A.n=N.m=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;
{P
0
:a=A.n=N}
var
a,n:Nat;
inicio
<a,n>:=<a,n>;
itn>0
<a,n>:=<a*n,n1>;
fit;
m:=a;
{Q
0
:a=A.n=N.m=a*n!}
devm
ffunc
En realidad no haran falta las variables locales, pero si no las utilizamos no se cumplir la par-
te de la especificacin que se refiere a la conservacin de los valores de los parmetros de entra-
da.
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;
{P
0
:1abN.ord(v,a,b)}
var
m:Ent;
inicio
si
a=b+1p:=a1
a=bsi
v(a)xp:=a
v(a)>xp:=a1
fsi
Algoritmos recursivos 217
a<bm:=(a+b)div2;
si
v(m)xp:=buscaBin(v,x,m+1,b)
v(m)>xp:=buscaBin(v,x,a,m1)
fsi
fsi
{Q
0
:a1pb.v[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;
{P
0
:1abN.ord(v,a,b)}
var
m,a,b:Ent;
inicio
<a,b>:=<a,b>;
ita<b
m:=(a+b)div2;
si
v(m)x<a,b>:=<m+1,b>;
v(m)>x<a,b>:=<a,m1>;
fsi
fit;
si
a=b+1p:=a1
a=bsi
v(a)xp:=a
v(a)>xp:=a1
fsi
fsi
{Q
0
:a1pb.v[a..p]x<v[(p+1)..b]}
devp
ffunc
Podemos eliminar los parmetros adicionales a y b, inicializando a y b con los valores 1 y N
respectivamente. Obsrvese que el algoritmo iterativo difiere ligeramente del que derivamos en el
Algoritmos recursivos 218
tema de algoritmos iterativos; la razn es que en aqul utilizbamos un planteamiento ligeramente
diferente al de la implementacin recursiva:
el lmite del vector que se modifica se lleva a m y no a m1 o m+1.
se inicializan a y b con los valores 0 y N+1 en lugar de 1 y N.
En la versin derivada directamente como iterativa no aparece la composicin alternativa a
continuacin del bucle.
La transformacin de recursivo a iterativo en funciones recursivas lineales no finales necesita
en general el uso de una pila, por lo tanto posponemos su estudio al tema de ese TAD. La con-
versin de recursin mltiple necesita de un rbol.
2.6 Tcnicas de generalizacin y plegado-desplegado.
En este tema vamos a ver una introduccin elemental a un conjunto de tcnicas que ayudan a:
Plantear el diseo de un algoritmo recursivo a partir de su especificacin, es decir, tcni-
cas que ayudan a obtener planteamientos recursivos de los problemas.
Transformar un algoritmo recursivo ya diseado a otro equivalente y ms eficiente. (La
mejora en la eficiencia radicar fundamentalmente en la conversin a un algoritmo recur-
sivo final que es directamente traducible a un algoritmo iterativo, ms eficiente.)
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 algorit-
mos 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 igno-
rando 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 generaliza-
ciones, 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 re-
cursiva final, lo cual no ocurre si partimos de la especificacin de fact.
Otro ejemplo tpico son las funciones recursivas sobre vectores, donde hemos obtenido gene-
ralizaciones aadiendo parmetros que indiquen el subvector a considerar en cada llamada recur-
siva. 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 fun-
ciones, 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 plan-
teamientos recursivos.
Vamos a caracterizar formalmente en trminos de su especificacin qu relacin debe existir
entre una funcin y su generalizacin.
Dadas dos especificaciones
(E
f
) func f( x
&
; t
&
) dev y
&
: o
&
;
{ P( x
&
) }
{ Q( x
&
, y
&
) }
ffunc
(E
F
) func F( a
&
: t
&
; x
&
; t
&
) dev b
&
: o
&
; y
&
: o
&
;
{ P( a
&
, x
&
) }
{ Q( a
&
, x
&
, b
&
, y
&
) }
ffunc
Decimos que E
F
es una generalizacin de E
f
si se cumplen
(G1) 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.
(G2) Q( a
&
, x
&
, b
&
, y
&
) . a
&
= ini( x
&
) Q( x
&
, y
&
)
Algoritmos recursivos 220
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
&
)
Hemos introducido aqu una nueva notacin para escribir la cabecera de las funciones, deno-
tando con x
&
a una lista de variables y con t
&
a una lista de tipos.
Veamos como ejemplo que acuFact es una generalizacin de fact segn la definicin que aca-
bamos 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

Efectivamente acuFact es una generalizacin de fact con


ini(n)=1
Con lo que se tiene
n0.a=1n0

r=a*n!.a=1r=n!
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 E
f
e intentaremos
obtener la especificacin E
F
de una generalizacin adecuada. Este proceso es esencialmente creati-
vo; 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 Generalizacin para la obtencin de planteamientos recursivos
Vamos a estudiar dos tcnicas, ejemplificndolas, una ms simple que permite obtener plan-
teamientos recursivos no finales y otra que persigue la obtencin de planteamientos recursivos
que admiten recursin final.
Algoritmos recursivos 221
Planteamientos recursivos no finales
El objetivo es llegar a una generalizacin para la cual exista un planteamiento recursivo evidente.
La idea consiste en aadir parmetros de entrada adicionales, como ocurre tpicamente en los
algoritmos sobre vectores, donde es necesario qu fragmento del vector se considera en cada
llamada recursiva. Simplemente lo que vamos a hacer en este apartado es formalizar algo que ya
hemos estado haciendo de manera informal.
Lo que se puede intentar es generalizar la postcondicin introduciendo variables nuevas que
sern los parmetros adicionales de la generalizacin. En el caso de los vectores, lo normal es
generalizar la postcondicin sustituyendo alguna de las constantes, que fijan los lmites del vector,
por variables.
Buscamos una postcondicin Q de forma que se cumpla la condicin G2
Q( a
&
, x
&
, y
&
) . a
&
= ini( x
&
) Q( x
&
, y
&
)
en ese caso la nueva precondicin se obtendr aadiendo a la precondicin original asertos
de dominio, D( a
&
, x
&
), sobre los nuevos parmetros:
P( a
&
, x
&
) P( x
&
) . D( a
&
, x
&
)
Veamos cmo se aplica esta tcnica en un ejemplo que calcula el producto escalar de dos vec-
tores:
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

Buscamos una postcondicin que generalice a la anterior, sustituyendo la constante N por un


nuevo parmetro:
Q(u,v,p):Q(a,u,v,p).a=N

por lo tanto la nueva postcondicin debe ser de la forma:


Q(a,u,v,p)p=i:1ia:u(i)*v(i)

la nueva precondicin se obtendr aadiendo a la antigua un aserto de dominio sobre el nuevo


parmetro a
P(a,u,v)P(u,v).0aN

con lo que la especificacin de la generalizacin del producto escalar queda:


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

vemos que efectivamente esta es una generalizacin con


ini(u,v)=N

de forma que
prodEscGen(N,u,v)=prodEsc(u,v)
A partir de la especificacin de la funcin generalizada es muy sencillo construir una solucin
recursiva:
funcprodEscGen(a:Ent;u,v:Vector[1..N]deEnt)devp:Ent;
{P(a,u,v):0aN}
inicio
si
a=0p:=0
a>0p:=u(a)*v(a)+prodEscGen(a1,u,v)
fsi
{Q(a,u,v,p):p=i:1ia:u(i)*v(i)}
devp
ffunc
Siguiendo el mismo proceso es inmediato construir otra generalizacin de prodEsc sustituyendo
por una constante 1 en lugar de N.
Planteamientos recursivos finales
Nuestro objetivo es, dada una especificacin E
f
, encontrar una especificacin E
F
de una fun-
cin ms general que admita una solucin recursiva final. En una funcin recursiva final el resul-
tado se obtiene en un caso directo, y para conseguirlo lo que podemos hacer es aadir nuevos
parmetros que vayan acumulando el resultado obtenido hasta el momento, de forma que al lle-
gar al caso base de la funcin general F el valor del parmetro acumulador sea precisamente el
resultado de la funcin f. Formalmente, esto quiere decir que tendremos que fortalecer la precon-
dicin para exigir que alguno de los parmetros de entrada ya traiga calculado una parte del resul-
tado.
En este tipo de generalizaciones la postcondicin permanece constante (salvo la conservacin
de los valores de los parmetros adicionales).
Lo que tenemos que exigirle a una generalizacin final es, por lo tanto:
Algoritmos recursivos 223
(FG2) P( a
&
, e
&
, x
&
) . d( a
&
, e
&
, x
&
) Q( x
&
, a
&
)
donde
a
&
son los parmetros acumuladores
e
&
son otros parmetros extra
d( a
&
, e
&
, x
&
) es la condicin del caso directo de F
Q( x
&
, y
&
) es la postcondicin de la funcin f
Es decir, que la precondicin y la condicin del caso directo de la funcin genera-
lizada permiten obtener la postcondicin de la funcin f, sustituyendo los parme-
tros de salida por los parmetros acumuladores, con lo que en el caso directo nos
limitaremos a asignar el valor de los parmetros acumuladores a las variables de
salida.
Esta condicin hace el papel de (G2) para el caso de las generalizaciones recursivas finales. La
otra condicin, (G1), se que da tal cual:
(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 fun-
ciones iterativas; es por ello que las condiciones que aparecen en la caracterizacin de una genera-
lizacin 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:
P( a
&
, e
&
, x
&
) invariante
d( a
&
, e
&
, x
&
) condicin de repeticin
Q( x
&
, y
&
) 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 estu-
diado para la obtencin de invariantes: tomar una parte de la postcondicin o generalizar la post-
condicin 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 es-
calar. 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 con-
dicin (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

De forma que as podemos demostrar la condicin (FG2)


P(a,e,u,v).d(a,e,u,v)Q(u,v,a)

(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)

Esto es equivalente a obtener la accin de inicializacin en un bucle, tenemos que encontrar


asignaciones a las variables del bucle que hagan trivialmente cierta la precondicin de la funcin
generalizada (i.e. el invariante). La forma ms sencilla es consiguiendo que el dominio del sumato-
rio sea el conjunto vaco:
ini(u,v)=<0,0>
A partir de esta especificacin es sencillo disear el siguiente algoritmo recursivo final:
funcprodEscGenFin(a,e:Ent;u,v:Vector[1..N]deEnt)devpEnt;
{P(a,e,u,v)}
inicio
si
e=Np:=a
e<Np:=prodEscGenFin(a+u(e+1)*v(e+1),e+1,u,v)
fsi
{Q(u,v,p)}
devp
ffunc

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 generali-
zacin.
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 funcio-
nal 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 au-
tomtica 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 Generalizacin por razones de eficiencia
En ocasiones se puede mejorar la eficiencia de un algoritmo recursivo evitando el clculo re-
petido de una cierta expresin en todas las llamadas recursivas, o simplificando algn clculo sa-
cando 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 intro-
duccin 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 intro-
ducir 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 par-
metros 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 di-
sear 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=Nr:=0
e<Ns:=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
Esta primer implementacin ya es una generalizacin pues se ha introducido el parmetros e
para hacer posible un planteamiento recursivo: obtener el nmero de cortes a la derecha de e, por
lo que el valor inicial de e ha de ser 0. Aunque desde el punto de vista del planteamiento recursivo
quizs resulte ms natural recorrer el vector en sentido contrario, hasta llegar a 0, no lo hacemos
as para luego hacer posible la siguiente generalizacin, que necesita la suma de las componentes
que hay a la izquierda.
numCortesGen( 0, v ) = numCortes( v )
Introducimos un fortalecimiento de la precondicin para que la suma del vector recorrido has-
ta ese momento se vaya transmitiendo a las siguientes llamadas:
funcnumCortesGenEfi(e,s:Ent;v:Vector[1..N]deEnt)devr:Ent;
{P(s,e,v):0eN.s=i:1ie:v(i)}
inicio
si
e=Nr:=0
Algoritmos recursivos 227
e<Nsis=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
En este caso tenemos
ini(v) = <0, 0>
numCortesGenEfi( 0, 0, v ) = numCortes( v )
La primera versin tiene coste cuadrtico mientras que esta lo tiene lineal.
Es inmediato obtener otra generalizacin, aadiendo un parmetro ms, que convierta esta
funcin en recursiva final.
Generalizacin con resultados acumuladores
La principal diferencia entre parmetros acumuladores y resultados acumuladores radica en el
lugar donde necesitamos la expresin cuyo clculo queremos obviar: antes de la llamada recursiva
parmetros acumuladores o despus de la llamada recursiva resultados acumuladores.
Utilizamos por primera vez la definicin ms general de generalizacin que introdujimos al
principio del tema, porque es la primera vez que la generalizacin va a incluir parmetros adicio-
nales. Recordamos que la generalizacin deba cumplir:
(G1) 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.
(G2) 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.
Tenemos una funcin f con la siguiente llamada recursiva
y
&
:=f( x
&
)
Algoritmos recursivos 228
y a continuacin aparece una expresin de la forma
e( y
&
, x
&
)
la idea es conseguir una generalizacin F donde
<b
&
, y
&
>:=F( x
&
)
tal que
b
&
=e( y
&
, x
&
)
Suponiendo que F no introduzca parmetros acumuladores tenemos la siguiente relacin entre
pre y postcondiciones:
P( x
&
)P( x
&
)
Q( x
&
,b
&
, y
&
)Q( x
&
, y
&
).b
&
=e( y
&
, x
&
)
Suponiendo disponible un algoritmo recursivo para f, que pretendemos optimizar, el diseo de
una algoritmo recursivo ms eficiente para F se obtendr
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
La tcnica resultar rentable siempre que F sea ms eficiente que f.
Es posible utilizar conjuntamente las dos tcnicas de generalizacin para optimizacin, inclu-
yendo parmetros y resultados acumuladores, como se muestra en un ejemplo de [Pea98], pg.
93, para el clculo eficiente de la raz cuadrado por defecto.
Vamos a ver un par de ejemplos, uno para la obtencin de la suma de los cuadrados de los n
primeros nmeros naturales y otro para el clculo de los nmeros de Fibonacci.
funcsumaCuad(n:Nat)devs:Nat;
{P(n):cierto}
inicio
si
n=0s:=0
n>0s:=n*n+sumaCuad(n1)
fsi
{Q(n,s):s=i:0in:i*i}
devs
ffunc
Algoritmos recursivos 229
La idea de la optimizacin consiste en optimizar el clculo de n
2
, conociendo del valor de (n
1)
2
, ya que x
&
= n1:
n
2
=((n1)+1)
2
=(n1)
2
+2*(n1)+1
La idea es aadir resultados adicionales que devuelvan
n
2
2*n+1
La generalizacin queda por tanto:
funcsumaCuadGen(n:Nat)devc,p,s:Nat
{P(n):cierto}
inicio
si
n=0<c,p,s>:=<0,1,0>
n>0<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*i.c=n*n.p=2*n+1}
dev<c,p,s>
ffunc
sumaCuadGen generaliza a sumaCuad si descartamos los dos resultados adicionales:
sumaCuad(n)=pr
3
(sumaCuadGen(n))
donde pr
3
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 devol-
ver dos nmeros en lugar de uno:
funcfibo(n:Nat)devr:Nat;
{P(n):cierto}
inicio
si
n=0r:=0
n=1r:=1
n>1r:=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=0<r,s>:=<0,1>
n>0<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)=pr
1
(fiboGen(n))
1.6.4 Tcnicas de plegado-desplegado
En un apartado anterior hemos visto cmo generalizar una especificacin E
f
de manera que la
especificacin ms general E
F
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 E
f
, puede interesar transformarlo de manera que se obtenga un algorit-
mo recursivo final para una funcin ms general con especificacin E
F
. 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
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
Algoritmos recursivos 231
inicio
si
d( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=h( x
&
)f(s( x
&
))
fsi
{Q( x
&
, y
&
)}
dev y
&

ffunc
siendo una funcin
:o
&
xo
&
o
&

que cumple las propiedades


(N)u
&
:o
&
:0
&
u
&
=u
&
(0
&
constante)
(A)u
&
,v
&
,w
&
:o
&
:u
&
(v
&
w
&
)=(u
&
v
&
)w
&

entonces la funcin F, especificada como sigue, es una generalizacin de f que admite un algo-
ritmo recursivo final:
funcF(a
&
:o
&
; x
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
{ y
&
=a
&
f( x
&
)}
ffunc
Primero vamos a ver que efectivamente F es una generalizacin de f:
x
&
:t
&
:P( x
&
)
f( x
&
)
(N) = 0
&
f( x
&
)
(E
F
) = 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 co-
rrecto 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
&
)
F(a
&
, x
&
)
E
F
,desplegadodeF = a
&
f( x
&
)
D
f
,desplegadodef = a
&
g( x
&
)
Algoritmos recursivos 232

Caso d( x
&
)
F(a
&
, x
&
)
E
F
,desplegadodeF = a
&
f( x
&
)
R
f
,desplegadodef = a
&
(h( x
&
)f(s( x
&
)))
(A) = (a
&
h( x
&
))f(s( x
&
))
E
F
,plegadodeF = F(a
&
h( x
&
),s( x
&
))

Por lo tanto obtenemos el siguiente algoritmo para F


funcF(a
&
:o
&
; x
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
inicio
si
d( x
&
) y
&
:=a
&
g( x
&
)
d( x
&
) y
&
:=F(a
&
h( x
&
),s( x
&
))
fsi
{ y
&
=a
&
f( x
&
)}
dev y
&

ffunc
Ejemplos de funciones recursivas que se ajustan al esquema PDP I y que admiten esta trans-
formacin:
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 genera-
lizacin 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
El resultado de la transformacin es:
funcprodEscF(b,a:Ent;u,v:Vector[1..N]deEnt)devp:Ent;
{P(b,a,u,v):0aN}
Algoritmos recursivos 233
inicio
si
a=0p:=b
a>0p:=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;
{P
0
:cierto}
inicio
six=0 r:=0
(x>0ANDpar(x)) r:=prod(xdiv2,y+y)
(x>0ANDNOTpar(x))r:=y+prod(xdiv2,y+y)
fsi
{Q
0
:r=x*y}
devr
ffunc
La transformacin es aplicable con los siguientes parmetros:
=+
0
&
=0
0 sipar(x)
h(x,y)=
y sipar(x)

Con todo ello la funcin transformada queda:


funcprodF(a,x,y:Nat)devr:Nat;
{P
0
:cierto}
inicio
six=0 r:=a
(x>0ANDpar(x)) r:=prodF(a,xdiv2,y+y)
(x>0ANDNOTpar(x))r:=prodF(a+y,xdiv2,y+y)
fsi
{Q
0
:r=a+x*y}
devr
ffunc
Algoritmos recursivos 234
Plegado y desplegado II
Supongamos dada una funcin recursiva simple f cuya implementacin se ajuste al siguiente
esquema:
funcf( x
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
inicio
si
d( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=h( x
&
)(k( x
&
)f(s( x
&
)))
fsi
{Q( x
&
, y
&
)}
dev y
&

ffunc
siendo , funciones
,:o
&
xo
&
o
&

que cumple las propiedades


(N

) u
&
:o
&
:0
&
u
&
=u
&
(0
&
constante)
(N

) u
&
:o
&
: 1
&
u
&
=u
&
( 1
&
constante)
(A

) u
&
,v
&
,w
&
:o
&
:u
&
(v
&
w
&
)=(u
&
v
&
)w
&

(A

) u
&
,v
&
,w
&
:o
&
:u
&
(v
&
w
&
)=(u
&
v
&
)w
&

(D)u
&
,v
&
,w
&
:o
&
:u
&
(v
&
w
&
)=(u
&
v
&
)(u
&
w
&
)

entonces la funcin F, especificada como sigue, es una generalizacin de f que admite un algo-
ritmo recursivo final:
funcF(a
&
:o
&
;b
&
:o
&
; x
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
{ y
&
=a
&
(b
&
f( x
&
))}
ffunc
Primero vamos a ver que efectivamente F es una generalizacin de f:
x
&
:t
&
:P( x
&
)

f( x
&
)
Algoritmos recursivos 235
(N

,N

) = 0
&
( 1
&
f( x
&
))
(E
F
) = 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 co-
rrecto 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
&
)
F(a
&
,b
&
, x
&
)
E
F
,desplegadodeF = a
&
(b
&
f( x
&
))
D
f
,desplegadodef = a
&
(b
&
g( x
&
))

Caso d( x
&
)
F(a
&
,b
&
, x
&
)
E
F
,desplegadodeF = a
&
(b
&
f( x
&
))
R
f
,desplegadodef = a
&
[b
&
[h( x
&
)[k( x
&
)f(s( x
&
))]]]
(D) = a
&
[[b
&
h( x
&
)][b
&
[k( x
&
)f(s( x
&
))]]]
(A

,A

) = [a
&
[b
&
h( x
&
)]][[b
&
k( x
&
)]f(s( x
&
))]
E
F
,plegadodeF = F(a
&
(b
&
h( x
&
)),b
&
k( x
&
),s( x
&
))
Por lo tanto obtenemos el siguiente algoritmo para F
funcF(a
&
:o
&
;b
&
:t
&
; x
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
inicio
si
d( x
&
) y
&
:=a
&
(b
&
g( x
&
))
d( x
&
) y
&
:=F(a
&
(b
&
h( x
&
)),b
&
k( x
&
),s( x
&
))
fsi
{ y
&
=a
&
(b
&
f( x
&
))}
dev y
&

ffunc
Como ejemplo vamos a ver una funcin que dada una base b y un natural n obtiene la repre-
sentacin 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;
{P
0
:2b9}
Algoritmos recursivos 236
inicio
si
n<br:=n
nbr:=nmodb+10*cambioBase(b,ndivb)
fsi
{Q
0
:r=i:Nat:((ndivb
i
)modb)*10
i
}
devr
ffunc
Esta funcin se ajusta a PDP II con los siguientes parmetros:
=+ 0
&
=0
=* 1
&
=1
h(n,b)=nmodb
k(n,b)=10

Con todo ello el resultado de la transformacin:


funccambioBaseF(s,p,b,n:Nat)devr:Nat;
{P
0
:2b9}
inicio
si
n<br:=s+p*n
nbr:=cambioBaseF(s+p*nmodb,p*10,b,ndivb)
fsi
{Q
0
:r=s+p*i:Nat:((ndivb
i
)modb)*10
i
}
devr
ffunc

cambioBaseF(0,1,b,n)=cambioBase(b,n)
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 docu-
mentar 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
&
:t
&
;a
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
inicio
Algoritmos recursivos 237
si
d( x
&
) y
&
:=g(a
&
)
d( x
&
) y
&
:=h(f(s( x
&
),a
&
))
fsi
{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:
x
&
:P( x
&
):f( x
&
,a
&
)=h
n
( 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
&
:t
&
;b
&
:o)dev y
&
:o
&
;
{P( x
&
)}
{ y
&
=h
n
( x
&
)
(b
&
)}
ffunc
siendo
n( x
&
)=
def
minn:Nat:d(s
n
( 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
P( x
&
)f( x
&
,a
&
)=F( x
&
,g(a
&
))
Podemos obviar la siguiente demostracin y considerar que la ecuacin es suficientemente
intuitiva.
Esto no se ajusta al concepto de generalizacin que hemos manejado hasta ahora pues F no
introduce parmetros ni resultados adicionales. Sustituye unos parmetros por otros: a
&
: t
&
por
b
&
: o
&
. S ser una generalizacin como veremos en un ejemplo posterior si a
&
es la tupla vaca
y b
&
no lo es no puede serlo en ningn caso.
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
f(s( x
&
),a)=F(s( x
&
),g(a
&
))

demostrando entonces que


f( x
&
,a)=F( x
&
,g(a
&
))

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
&
)
f( x
&
,a
&
)
(D
f
) = g(a
&
)
(d( x
&
)n( x
&
)=0)= h
n
( x
&
)
(g(a
&
))
(E
F
) = F( x
&
,g(a
&
))

Caso d( x
&
)
f( x
&
,a
&
)
(R
f
) = h(f(s( x
&
),a
&
))
H.I. = h(F(s( x
&
),g(a
&
)))
(E
F
) = h(h
n(s( x
&
))
(g(a
&
)))
(1+n(s( x
&
))=n( x
&
)) = h
n( x
&
)
(g(a
&
))
(E
F
) = F( x
&
,g(a
&
))
Una vez demostrado que f se puede obtener a partir de F, veamos cul es el procedimiento pa-
ra construir el algoritmo de F a partir del algoritmo de f.
Caso d( x
&
)
F( x
&
,b
&
)
E
F
,desplegadodeF = h
n( x
&
)
(b
&
)
(d( x
&
)n( x
&
)=0)= b
&

Caso d( x
&
)
F( x
&
,b
&
)
E
F
,desplegadodeF = h
n( x
&
)
(b
&
)
d( x
&
)n( x
&
)=1+n(s( x
&
)) = h
n(s( x
&
))
(h(b
&
))
E
F
,plegadodeF = F(s( x
&
),h(b
&
))
Algoritmos recursivos 239
Con lo que obtenemos para F el siguiente algoritmo:
funcF( x
&
:t
&
;b
&
:o)dev y
&
:o
&
;
{P( x
&
)}
inicio
si
d( x
&
) y
&
:=b
&

d( x
&
) y
&
:=F(s( x
&
),h(b
&
))
fsi
{ y
&
=h
n
( x
&
)
(b
&
)}
dev y
&

ffunc
Veamos algunos ejemplos de aplicacin de PDP III.
Es aplicable a la generalizacin de fibo que vimos en el apartado sobre generalizaciones por ra-
zones de eficiencia:
funcfiboGen(n:Nat)devr,s:Nat;
{P(n):cierto}
var
r,s:Nat;
inicio
si
n=0<r,s>:=<0,1>
n>0<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
Corresponde al esquema de PDP III con los siguientes parmetros:
x
&
=n
a
&
=<>
b
&
=<r,s> delmismotipoqueelresultadoNatxNat
g()=<0,1>
h(r,s)=<s,r+s>
Con lo que se obtiene la funcin:
Algoritmos recursivos 240
funcfiboGenF(n,r,s:Nat)devr,s:Nat;
{cierto}
inicio
si
n=0<r,s>:=<r,s>
n>0<r,s>:=fiboGenF(n1,s,r+s)
fsi
{<r,s>=h
n
(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 Fi-
bonacci 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 aproxima-
damente el algoritmo que derivamos en el tema de algoritmos iterativos como ejemplo de intro-
duccin de un invariante auxiliar:
varn,x:Ent;
{n=N.n0}
var
i,y:Ent;
inicio
<i,x,y>:=<0,0,1>;
{I:n=N.x=fib(i).0in.y=fib(i+1)
C:ni}
iti=n
{I.i=n}
<x,y>:=<y,x+y>;
{I[i/i+1]}
i:=i+1
{I}
fit
{I.i=n}
fvar
{n=N.x=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=0<c,p,s>:=<0,1,0>
n>0<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*i.c=n*n.p=2*n+1}
dev<c,p,s>
ffunc
Con los parmetros:
x
&
=n
a
&
=<>
b
&
=<c,p,s>
g()=<0,1,0>
h<c,p,s>=<c+p,p+2,c+p+s>

Con lo que obtenemos el algoritmo


funcsumaCuadGenF(n,c,p,s:Nat)devc,p,s:Nat
{cierto}
inicio
si
n=0<c,p,s>:=<c,p,s>
n>0<c,p,s>:=sumaCuadGenF(n1,c+p,p+2,c+p+s)
fsi
{<c,p,s>=h
n
(c,p,s)}
dev<c,p,s>
ffunc
Con la siguiente correspondencia entre f y F:
sumaCuadGen(n)=sumaCuadGenF(n,0,1,0)=<n
2
,2n+1,sumaCuad(n)>

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 0in.r=i!).
(b) Una funcin recursiva.
80. Analiza cmo la funcin recursiva del ejercicio 79(b) se ajusta al esquema general de defini-
cin de una funcin recursiva lineal (o simple)
funcnombreFunc(x
1
:t
1
;;x
n
:t
n
)devy
1
:o
1
;;y
m
:o
m
;
{P
0
:P.x
1
=X
1
..x
n
=X
n
}
cte;
var;
inicio
sid( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=c( x
&
,nombreFunc(s( x
&
)))
fsi;
{Q
0
:Q.x
1
=X
1
..x
n
=X
n
}
dev<y
1
,,y
m
>
ffunc
81. Construye una funcin recursiva simple cuadrado que calcule el cuadrado de un nmero
natural n, basndote en el siguiente anlisis de casos:
Caso directo: Si n=0, entonces n
2
=0
Caso recursivo: Si n>0, entonces n
2
=(n1)
2
+2*(n1)+1
82. Una funcin se llama recursiva final si su definicin se ajusta al siguiente esquema:
funcnombreFunc(x
1
:t
1
;;x
n
:t
n
)devy
1
:o
1
;;y
m
:o
m
;
{P
0
:P.x
1
=X
1
..x
n
=X
n
}
cte;
var;
inicio
sid( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=nombreFunc(s( x
&
))
fsi;
{Q
0
:Q.x
1
=X
1
..x
n
=X
n
}
dev<y
1
,,y
m
>
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
{P
0
:a=A.n=N}
{Q
0
:a=A.n=N.m=a*n!}
ffunc
Observa que la especificacin de acuFact garantiza que acuFact(1,n) = n!.
83. Una funcin se llama recursiva mltiple si su definicin se ajusta al siguiente esquema:
funcnombreFunc(x
1
:t
1
;;x
n
:t
n
)devy
1
:o
1
;;y
m
:o
m
;
{P
0
:P.x
1
=X
1
..x
n
=X
n
}
cte;
var;
inicio
si
d( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=c( x
&
,nombreFunc(s
1
( x
&
)),,nombreFunc(s
k
( x
&
)))
fsi;
{Q
0
:Q.x
1
=X
1
..x
n
=X
n
}
dev<y
1
,,y
m
>
ffunc
Construye una funcin recursiva mltiple fib con parmetro n, que devuelva el n-simo nme-
ro 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 ob-
serva 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 li-
neal 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;
{P
0
:x=X.y=Y}
var
y:Nat;p:Ent;
inicio
si
y=0p:=1
y>0y:=ydiv2;
p:=pot(x,y);
si
par(y)p:=p*p
NOTpar(y)p:=x*p*p
fsi
fsi
{Q
0
:x=X.y=Y.p=x
Y
}
Algoritmos recursivos 244
devp
ffunc
(b) Una funcin recursiva lineal que calcula potencias, con dos casos recursivos:
funcpot(x:Ent;y:Nat)devp:Ent;
{P
0
:x=X.y=Y}
var
x,y:Nat;p:Ent;
inicio
si
y=0p:=1

y>0ANDpar(y)<x,y>:=<x*x,ydiv2);
p:=pot(x,y)
y>0ANDNOTpar(y)y:=y1;
p:=pot(x,y)
p:=x*p
fsi
{Q
0
:x=X.y=Y.p=x
Y
}
devp
ffunc

Derivacin y verificacin de algoritmos recursivos


86. Verifica la funcin fact obtenida en el ejercicio 79(b).
87. Verifica la funcin cuadrado obtenida en el ejercicio 81.
88. Verifica la funcin acuFact obtenida en el ejercicio 82.
89. Verifica las funciones pot y pot obtenidas en el ejercicio 85.
90. Deriva una funcin recursiva que calcule el producto de dos nmeros x,y:Nat. Debers
obtener un algoritmo que slo utilice las operaciones + y div, y que est basado en el si-
guiente anlisis de casos:
Caso directo: x=0
Casos recursivos: x=0ANDpar(x)
x=0ANDNOTpar(x)
91. Deriva una funcin recursiva log que calcule la parte entera de lob
b
n, siendo los datos b,
n:Nat tales que b2.n1. 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
93. Verifica la siguiente funcin:
funccuca(n:Ent)devr:Ent;
{P
0
:n=N.n0}
inicio
si
n=0r:=0
n=1r:=1
n2r:=5*f(n1)6*f(n2)
fsi
{Q
0
:n=N.r=3
n
2
n
}
ffunc
94. Deriva una funcin recursiva doble que satisfaga la siguiente especificacin:
funcsumaVec(v:Vector[1..N]deEnt;a,b:Ent)devs:Ent;
{P
0
:N1.v=V.a=A.b=B.1ab+1N+1}
{Q
0
:v=V.a=A.b=B.s=i:aib:v(i)}
ffunc
Bsate en el siguiente anlisis de casos:
Caso directo: ab
v[a..b] tiene a lo sumo un elemento. El clculo de s es simple.
Caso recursivo: a < b
v[a..b] tiene al menos dos elementos. Hacemos llamadas recursivas para sumar
v[a..m] y v[m+1..b], siendo m=(a+b)div2.
95. Deriva una funcin recursiva lineal que realice el algoritmo de bsqueda binaria en un vector
ordenado, cumpliendo la especificacin siguiente:
funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P
0
:1ab+1N+1.ord(v,a,b)}
{Q
0
:a1pb.v[a..p]x<v[(p+1)..b]}
ffunc
Compara con el algoritmo iterativo de bsqueda binaria del ejercicio 74.
96. Disea un procedimiento doblemente recursivo que realice el algoritmo de ordenacin
rpida de un vector, segn la especificacin y anlisis de casos que siguen:
procquickSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P
0
:v=V.a=A.b=B.1ab+1N+1}
{Q
0
:a=A.b=B.perm(v,V,a,b).ord(v,a,b).
(i:1i<a:v(i)=V(i)).(j:b<jN:v(j)=V(j))}
fproc
Caso directo: a>b
v[a..b] es vaco y ya est ordenado.
Caso recursivo: a b
Algoritmos recursivos 246
v[a..b] es no vaco. Hacemos una llamada a un procedimiento auxiliar particin(v,
a, b, p) que reorganiza v[a..b] desplazando v(a) a la posicin p, dejando en
v[a..p1]elementos v(p), y en v[p+1..b] elementos v(p). A continua-
cin, usamos llamadas recursivas para ordenar v[a..p1] y v[p+1..b].
97. Disea un procedimiento doblemente recursivo que realice el algoritmo de ordenacin de
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);
{P
0
:v=V.a=A.b=B.1ab+1N+1}
{Q
0
:a=A.b=B.perm(v,V,a,b).ord(v,a,b).
(i:1i<a:v(i)=V(i)).(j:b<jN:v(j)=V(j))}
fproc
Caso directo: ab
v[a..b] tiene a lo sumo un elemento y ya est ordenado.
Caso recursivo: a < b
v[a..b] tiene al menos dos elementos. Calculamos m=(a+b)div2 y usamos
llamadas recursivas para ordenar v[a..m] y v[m+1..b]. A continuacin, efectua-
mos una llamada mezcla(v, a, m, b) a un procedimiento auxiliar cuyo efecto es
mezclar v[a..m] y v[m+1..b], dejando ordenado v[a..b], y sin alterar el resto de
v.
Indicacin: El procedimiento mezcla necesita usar espacio auxiliar, aparte del ocupado por el
propio v. Este espacio puede venir dado por otro vector.
98. Deriva una funcin recursiva final que calcule el mximo comn divisor de dos nmeros
enteros positivos dados.
99. Deriva una funcin recursiva simple dosFib que satisfaga la siguiente especificacin
pre/post:
funcdosFib(n:Nat)devr,s:Nat;
{P
0
:n=N}
{Q
0
:n=N.r=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 ma-
temticas.
100. Deriva una funcin recursiva que calcule el nmero combinatorio
|
|
.
|

\
|
m
n
a partir de los
datos m,n:Nat. Usa la recurrencia siguiente:
|
|
.
|

\
|

+
|
|
.
|

\
|

=
|
|
.
|

\
|
m
n
m
n
m
n 1
1
1
siendo 0 < m < n
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 dife-
rentes 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 movimien-
tos 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 movi-
mientos que represente una solucin del problema de Hanoi. Supn disponible un proce-
dimiento movimiento(i,j), cuyo efecto es escribir Movimiento de la varilla i a la varilla j.
103. La siguiente funcin recursiva se conoce como funcin de Ackermann:
funcack(m,n:Nat)devr:Nat;
inicio
si
m=0r:=n+1
m>0ANDn=0r:=ack(m1,1)
m>0ANDn>0r:=ack(m1,ack(m,n1))
fsi;
devr
ffunc
(a) Explica por qu motivos la definicin de ack no se ajusta a los esquemas de defini-
cin recursiva que hemos estudiado hasta ahora.
(b) Demuestra la terminacin de ack usando un orden bien fundamentado conveniente,
definido sobre .
Anlisis de algoritmos recursivos
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 desple-
gado 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
aplicando las reglas de anlisis para dos tipos comunes de recurrencia.
(a) T(1) = c
1
; T(n) = 4 T(n/2) + n, si n> 1
(b) T(1) = c
1
; T(n) = 4 T(n/2) + n
2
, si n> 1
Algoritmos recursivos 248
(c) T(1) = c
1
; T(n) = 4 T(n/2) + n
3
, si n> 1
107. Usa el mtodo de desplegado para estimar el orden de magnitud de T(n), suponiendo que
T obedezca la siguiente recurrencia:
T(1) = 1; T(n) = 2 T(n/2) + n log n, si n> 1
Pueden aplicarse en este caso las reglas de anlisis para dos tipos comunes de recurrencia? Por
qu?
108. Analiza la complejidad en tiempo del procedimiento de ordenacin quickSort (ejercicio
96), distinguiendo dos casos:
(a) Tiempo de ejecucin en el caso peor.
(b) Tiempo de ejecucin bajo el supuesto de que las sucesivas particiones realizadas por
el procedimiento auxiliar particin produzcan siempre dos partes de igual tamao.
109. Supn un procedimiento recursivo de ordenacin de vectores, llamado badMergeSort, que
sea idntico a meregeSort con la nica diferencia de que el procedimiento auxiliar mezcla usa-
do por badMergeSort opera en tiempo O(n
2
). Demuestra que bajo este supuesto el tiempo de
ejecucin de badMergeSort pasa a ser O(n
2
), frente a O(n log n) de mergeSort.
110. Considera de nuevo la sucesin de Fibonacci, definida en el ejercicio 83. Sean y

las
dos races de la ecuacin x
2
x1=0, es decir:
2
5 1+
=
2
5 1


=
Demuestra por induccin sobre n que ( )
n n
n fib

5
1
) ( =
Pista: Usa que
2
= + 1 y

2
=

+ 1
111. Considera la funcin doblemente recursiva fib del ejercicio 83, que calcula nmeros de
Fibonacci aplicando ingenuamente la definicin matemtica de la sucesin de Fibonacci.
Supn que tomemos el propio valor numrico n como tamao del dato n, y que definimos
T(n) como el nmero de veces que se ejecuta una orden dev de devolucin de resultado en
el cmputo activado por la llamada inicial fib(n).
(a) Plantea una ley de recurrencia para T(n) y demuestra por induccin sobre n que T(n)
= 2 fib(n+1) 1.
(b) Demuestra por induccin sobre n que
n2
fib(n)
n1
se cumple para todo n
2. Concluye que T(n) es O(
n
).
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 re-
sultado del ejercicio anterior.
Eliminacin de la recursin final
113. Aplica la transformacin de funcin recursiva final a funcin iterativa sobre las siguientes
funciones:
(a) Funcin acuFact (ejercicio 82).
(b) Funcin mcd (ejercicio 98).
Algoritmos recursivos 249
(c) Funcin buscaBin (ejercicio 95).
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;
{P
0
:cierto}
{Q
0
:p=i:1iN:u(i)*v(i)}
ffunc
115. Comprueba que acuFact (ejercicio 82) es una generalizacin de fact (ejercicio 79). Observa
que el parmetro de entrada adicional de acuFact acta como acumulador, posibilitando un
algoritmo recursivo final.
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;
{P
0
:0mn.mN}
{Q
0
:i:0im:v(i)=
|
|
.
|

\
|
i
n
}
Observa que el parmetro de salida de combi es ms general que el de combi.
119. Comprueba que sumaCuad es una generalizacin de sumaCuad, y construye un algoritmo
recursivo lineal para sumaCuad:

funcsumaCuad(n:Nat)devs:Nat;
{P
0
:cierto}
{Q
0
:s=i:0in:i*i}
ffunc

funcsumaCuad(n:Nat)devc,p,s:Nat
{P
0
:cierto}
{Q
0
:s=i:0in:i*i.c=n*n.p=2*n+1}
ffunc

Algoritmos recursivos 250


Observa que n
2
+2n+1 = (n+1)
2
. Gracias a esto, los dos parmetros de salida aadidos a suma-
Cuad (c y p) permiten programar sumaCuad sin necesidad de calcular n
2
en cada llamada recursiva,
mejorndose as la eficiencia.
120. Comprueba que procesaVec es una generalizacin de procesaVec, y construye un algoritmo
recursivo final para procesaVec:

funcprocesaVec(v:Vector[1..N]deEnt)devr:Ent;
{P
0
:cierto}
{Q
0
:r=i:1iN:2
i
*v(i)}
ffunc

funcprocesaVec(n,s,p:Ent;v:Vector[1..N]deEnt)devr:Ent;
{P
0
:1nN+1.p=2
n
}
{Q
0
:r=s+i:niN:2
i
*v(i)}
ffunc

Observa el papel que juega cada uno de los tres parmetros adicionales de procesaVec: n posibi-
lita 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
del ejercicio 79 en la funcin recursiva final acuFact del ejercicio 82.
122. Comprueba que prod es una generalizacin de prod, y construye un algoritmo recursivo
final para prod usando plegado-desplegado:

funcprod(x,y:Nat)devr:Nat;
{P
0
:cierto}
inicio
six=0 r:=0
(x>0ANDpar(x)) r:=prod(xdiv2,y+y)
(x>0ANDNOTpar(x))r:=y+prod(xdiv2,y+y)
fsi
{Q
0
:r=x*y}
devr
ffunc

funcprod(a,x,y:Nat)devr:Nat;
{P
0
:cierto}
{Q
0
:r=a+x*y}
ffunc

Algoritmos recursivos 251


123. Aplica plegado-desplegado para transformar en funciones recursivas finales las siguientes
funciones recursivas simples:

(a) La funcin pot del ejercicio 85(b).


(b) La funcin prodEsc del ejercicio 114.
124. Todas las transformaciones de los ejercicios 121, 122 y 123 se ajustan al siguiente esque-
ma:

funcf( x
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
inicio
si
d( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=h( x
&
)f(s( x
&
))
fsi
{Q( x
&
, y
&
)}
dev y
&

ffunc

funcF(a
&
:o
&
; x
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
{ y
&
=a
&
f( x
&
)}
ffunc

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 plegado-
desplegado.
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;
{P
0
:cierto}
inicio
si
n<2r:=n
n2r:=10*bin(ndiv2)+(nmod2)
fsi
{Q
0
:r=i:Nat:((ndiv2
i
)mod2)*10
i
}
devr
ffunc
Algoritmos recursivos 252

funcbin(s,p,n:Nat)devr:Nat;
{P
0
:cierto}
{Q
0
:r=s+p*bin(n)}
ffunc

126. Considera la funcin cambioBase especificada como sigue:

funccambioBase(b,n:Nat)devr:Nat;
{P
0
:2b9}
{Q
0
:r=i:Nat:((ndivb
i
)modb)*10
i
}
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
una generalizacin recursiva final cambioBase de cambioBase.
127. Las transformaciones de los ejercicios 125 y 126 se ajustan al siguiente esquema:

funcf( x
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
inicio
si
d( x
&
) y
&
:=g( x
&
)
d( x
&
) y
&
:=h( x
&
)(k( x
&
)f(s( x
&
)))
fsi
{Q( x
&
, y
&
)}
dev y
&

ffunc

funcF(a
&
:o
&
;b
&
:o
&
; x
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
{ y
&
=a
&
(b
&
f( x
&
))}
ffunc

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 gene-
ralizacin 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
final para fiboGen usando plegado-desplegado.
funcfiboGen(n:Nat)devr,s:Nat;
{P
0
: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=0<r,s>:=<0,1>
n>0<r,s>:=fiboGen(n1)
<r,s>:=combina(r,s);
fsi
{Q
0
:r=fib(n).s=fib(n+1)}
dev<r,s>
ffunc

funcfiboGen(n,r,s:Nat)devr,s:Nat;
{P
0
:cierto}
{<r,s>=combina
n
(r,s)} %indicaaplicarcombinanveces
ffunc
129. Aplica una tcnica de plegado-desplegado similar a la del ejercicio anterior para transfor-
mar en funciones recursivas finales las siguientes funciones recursivas simples:
(a) La funcin dosCuca del ejercicio 117.
(b) La funcin sumaCuad del ejercicio 119.
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 usan-
do plegado-desplegado.
funcf( x
&
:t
&
;a
&
:t
&
)dev y
&
:o
&
;
{P( x
&
)}
inicio
si
d( x
&
) y
&
:=g(a
&
)
d( x
&
) y
&
:=h(f(s( x
&
),a
&
))
fsi
{Q( x
&
,a
&
, y
&
)}
dev y
&

ffunc

Algoritmos recursivos 254


funcF( x
&
:t
&
;b
&
:o)dev y
&
:o
&
;
{P( x
&
)}
{ y
&
=h
n
( x
&
)
(b
&
)} %n(x)indicaelmenorntalqued(s
n
(x))
ffunc
Tipos abstractos de datos 255
CAPTULO 3
TIPOS ABSTRACTOS DE DATOS
3.1 Introduccin a la programacin con tipos abstractos de datos
3.1.1 La abstraccin como metodologa de resolucin de problemas
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 aspec-
tos 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:
instrucciones para encontrar el banco
instrucciones para conducir por carreteras y calles
Ejemplo. Para indicarle a un robot cmo cambiar la rueda de un coche podemos utilizar una
descripcin con distintos niveles de abstraccin:
Bajar del coche
Tirar de la manivela
Empujar la puerta
Levantarse del asiento
Ir al maletero
Abrir el maletero
Sacar la rueda de repuesto

Ejemplo. Para probar la correccin de un bucle
Demostramos que hay un invariante se cumple antes y despus de todas las iteraciones
Obtenemos el invariante
Demostramos que la precondicin implica al invariante
Demostramos que el invariante implica la definicin de la condicin de repeticin
Demostramos que partiendo de un estado que cumple el invariante y la condicin de
repeticin y ejecutando el cuerpo del bucle se llega a un estado que cumple el inva-
riante
Demostramos que el bucle avanza
Demostramos que al final de la ejecucin del bucle se cumple la postcondicin
Tipos abstractos de datos 256
El razonar en trminos de abstracciones conlleva una serie de ventajas:
La resolucin de los problemas se simplifica
Las soluciones son ms claras y resulta ms sencillo razonar sobre su correccin
Es ms fcil adaptar las soluciones a otros problemas
Pueden obtenerse soluciones de utilidad ms general (por ej. las instrucciones para llegar
al banco, sirven tambin para peatones o ciclistas)
3.1.2 La abstraccin como metodologa de programacin
La programacin es un proceso de resolucin de problemas y como tal tambin se beneficia
del uso de abstracciones.
La evolucin de los lenguajes de programacin muestra una tendencia a incluir mecanismos de
abstraccin cada vez de ms alto nivel. El ensamblador es una abstraccin del lenguaje mquina,
los lenguajes de alto nivel son una abstraccin del ensamblador.
Dentro de los lenguajes de programacin de alto nivel existen dos formas fundamentales de
abstraccin
La abstraccin funcional
Son las funciones y los procedimientos. Consiste bsicamente en reunir un conjunto de sen-
tencias que realizan una determinada operacin sobre unos datos y darles un nombre y una
interfaz que las abstrae. De esta forma se puede utilizar ese conjunto de sentencias como si fuese
una operacin definida en el propio lenguaje, abstrayndonos de los detalles de su implementa-
cin. Por ejemplo, para utilizar un procedimiento de ordenacin no necesita saber qu mtodo de
ordenacin implementa.
Para poder poner a disposicin de otros o de mi mismo, pasado el tiempo una abstraccin
funcional, tengo que ser capaz de describirla de la manera ms clara y precisa que sea posible, sin
incluir detalles de la implementacin. Eso lo consigo mediante una especificacin. La abstraccin
es como una barrera que deja a un lado la especificacin, con la que los clientes de la abstraccin
pueden razonar, y a otro la 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.
Especificacin
Abstraccin
Implementacin
Tipos abstractos de datos 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 tipos predefinidos como TADs
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 implementa-
cin 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.
Vectores. Disponemos de operaciones para consultar y modificar los valores almacenados
en un vector. Estas operaciones obedecen a leyes algebraicas que pueden formularse con
precisin si almaceno v en la posicin i de un vector, y luego consulto la posicin i ob-
tendr el valor v, como veremos ms adelante. Los detalles de representacin interna y
almacenamiento de vectores en la memoria de una mquina son irrelevantes para el usua-
rio.
En cambio, los tipos de datos definidos por los programadores en la mayora de los lenguajes
de programacin no son abstractos, porque:
la representacin interna de los valores del tipo es visible,
Tipos abstractos de datos 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 Ejemplos de especificacin informal de TADs
Al igual que al manejar una abstraccin funcional, lo que le proporcionamos al usuario es la
especificacin de la operacin, al abstraer datos tambin debemos proporcionar una especifica-
cin con la que los usuarios puedan trabajar. Informalmente, un tipo abstracto de datos (TAD) se
define especificando:
el dominio de valores del tipo
las operaciones del tipo que pueden hacer referencia a otros tipos
el comportamiento esperado de las operaciones
Ntese que en la abstraccin de datos se incluye tambin la abstraccin funcional de las ope-
raciones del TAD.
Todo ello sin hacer referencia a ninguna representacin concreta de los datos. El usuario del
TAD slo necesita conocer la especificacin.
Tipos abstractos de datos 259
Un ejemplo de especificacin informal del tipo abstracto de datos fecha:
Dominio: las fechas desde el 1/1/1 no hubo ao cero, por lo que el siglo XXI empieza el
1/1/2001. Ntese que omitimos cualquier informacin sobre la estructura interna con la que se
representan las fechas.
Operaciones:

funcNuevaFecha(d:Da;m:Mes;a:Ao)devf:Fecha;
{P
0
:d/m/acumplenlasrestriccionesdeunafecha,segnelcalendario}
{Q
0
:frepresentaalafechad/m/a}
ffunc;

funcdistancia(f1,f2:Fecha)devd:Ent;
{P
0
:}
{Q
0
:desladistancia,medidaennmerodedas,entref1yf2}
ffunc;

funcsuma(f:Fecha;d:Ent)devg:Fecha;
{P
0
:}
{Q
0
:GeslafecharesultantedesumarddasalafechaF}
ffunc;

Cuidado!dpuedeserunnmeronegativodeformaquegnoseaunafecha
posterioral1/1/0

funcda(f:Fecha)devd:Da;
{P
0
:}
{Q
0
:deseldadelafechaf}
ffunc;

funcmes(f:Fecha)devm:Mes;
{P
0
:}
{Q
0
:meselmesdelafechaf}
ffunc;

funcao(f:Fecha)deva:Ao;
{P
0
:}
{Q
0
:aeselaodelafechaf}
ffunc;

funcdaSemana(f:Fecha)devd:DaSemana;
{P
0
:}
{Q
0
:deseldadelasemanacorrespondientealafechaf}
ffunc;

Tipos abstractos de datos 260


funcprimer(d:DaSemana;m:Mes;a:Ao)devf:Fecha;
{P
0
:}
{Q
0
:feslaprimerafechadelmesmdelaoacuyodadelasemanaesd
}
ffunc;

Por ejemplo, con la funcin primer podramos calcular cuando cae el primer martes de no-
viembre de 1871:

primer(martes,noviembre,1871)

Una especificacin del TAD fecha debera incluir adems axiomas para las operaciones, que
omitimos aqu.
3.1.5 Implementacin de TADs: privacidad y proteccin
La implementacin de un TAD queda separada de su especificacin y no es competencia del
usuario, sino del implementador que puede ser el mismo, pero que una vez implementado el
TAD se puede olvidar de sus detalles. Consiste en:
Definir una representacin de los valores del TAD con ayuda de otros tipos ya disponi-
bles.
Definir las operaciones del TAD como funciones o procedimientos, usando la represen-
tacin 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
Privacidad: la representacin interna est oculta y es invisible para los usuarios.
Proteccin: el tipo slo puede usarse a travs de sus operaciones; otros accesos incontro-
lados se hacen imposibles.
La caracterstica importante es la proteccin, pues aunque un usuario pueda conocer el tipo
representante de un TAD, no resulta un problema si slo puede acceder a los valores a traves de
las operaciones pblicas del TAD.
Por desgracia, muchos lenguajes de programacin no incluyen estos mecanismos y la protec-
cin del TAD se convierte entonces en una cuestin de disciplina del programador: slo deben
utilizar los valores del TAD a travs de las operaciones pblicas que ste haya definido, aunque el
lenguaje permita conocer y acceder directamente a la representacin interna de los datos.
En general cualquier TAD admite varias representaciones posibles.
Por ejemplo, una secuencia de nmeros de longitud variable se puede implementar como un
registro con un vector y un campo longitud o como un vector con una marca al final. En cual-
quiera de los dos casos, con tipo as definido en Pascal, el programador podra consultar por un
valor de la secuencia que est ms all del lmite indicado por la longitud o por la marca; algo que
no tiene sentido.
Tipos abstractos de datos 261
Cuando hay varias representaciones posibles, normalmente una representacin concreta facili-
ta unas determinadas operaciones y dificulta otras, con respecto a una representacin alternativa.
Por ejemplo el TAD fecha que vimos antes:
Podemos representar fechas con los registros que vimos antes.
Con esta representa resulta trivial implementar NuevaFecha, da, mes y ao. Mientras que
sera ms difcil implementar el resto de las operaciones
Podramos representar la fechas como el nmero de das transcurridos desde el 1 de Ene-
ro de 1900 de hecho en la mayora de los sistemas es as como se hace, con lo cual lle-
gar un momento en que se alcanzar el mximo del tipo de nmeros seleccionado, y la
representacin dar la vuelta, algo que est relacionado con el problema del ao 2000
As es mucho ms sencillo calcular el nmero de das transcurridos entre dos fechas da-
das, pero resulta ms difcil construir una fecha a partir del da/mes/ao.
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 Ventajas de la programacin con TADs
El uso de TADs ayuda en:
El diseo descendente
El diseo de los programas
La reutilizacin
Mejoras en el diseo descendente
El mtodo de diseo descendente se ve potenciado de dos maneras distintas:
El diseo se hace ms abstracto. Ya no dependemos exclusivamente de los tipos concre-
tos 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 di-
seo, 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.
Veamos un ejemplo ([Kingston 90], pp. 38-39).
Problema: dado un vector v de nmeros enteros con ndices entre 1 y N y un nmero k, 0 k
N, se trata de determinar los k nmeros mayores que aparecen en el vector ntese que no
tienen que ser k nmeros diferentes.
Para resolver este problema necesitamos una estructura auxiliar donde vayamos almacenando
los k mayores encontrados hasta ahora. Podramos elegir una estructura de datos concreta por
ejemplo, un vector e incluir su gestin dentro del propio algoritmo. Los inconvenientes de esta
decisin:
La lgica del algoritmo queda oscurecida por los detalles de la representacin.
Tipos abstractos de datos 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.
La otra opcin es elegir un TAD capaz de almacenar la informacin necesaria e ir viendo qu
operaciones nos hacen falta sobre sus valores. En este problema podemos elegir como TAD los
multiconjuntos. En pseudocdigo, el algoritmo quedara:

m:={v(1),,v(k)}
paraidesdek+1hastaNhacer
min:=minimoelementodeM
si
v(i)>mincambiarminporv(i)enm
v(i)minseguir
fpara

Si suponemos disponible un TAD para los multiconjuntos de nmeros enteros, con operacio-
nes adecuadas, podemos disear la siguiente funcin que resuelve el problema:

funcmayores(k:Nat;v:Vector[1..N]deEnt)devm:MCjtoEnt;
{P
0
:1kN.N1}
var
n:Ent;
inicio
Vaco(m);
n:=0;
{Inv.I:mcontieneloselementosdev[1..n]}
itn=k
Pon(v(n+1),m);
n:=n+1
fit
{InvJ:mcontieneloskelementosmayoresdev[1..n]}
itn=N
siv(n+1)>min(m)
entonces
quitaMin(m);
Pon(v(n+1),m)
sino
seguir
fsi
fit
{Q
0
:mcontieneloskelementosmayoresdev[1..N]}
devm
ffunc
Del algoritmo resultante podemos ver qu operaciones necesitamos en el TAD multiconjunto
Tipos abstractos de datos 263

funcVacodevm:MCjtoEnt
{P
0
:}
{Q
0
:mrepresentaC}
ffunc

procPon(ex:Ent;esm:MCjtoEnt);
{P
0
:m=M.e=E}
{Q
0
:e=E.m=M{x}}
fproc

procquitaMin(esm:MCjtoEnt);
{P
0
:m=M.mnoesvaco}
{Q
0
:mesMmenosunacopiadelelementomnimodeM}
fproc

funcmin(m:MCjtoEnt)devr:Ent;
{P
0
:m=M.mnoesvaco}
{Q
0
:m=M.reselelementomnimodem}
ffunc

Pon y quitaMin se han implementado como procedimientos para evitar la copia del multicon-
junto 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 apari-
ciones 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 trmi-
nos 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 colec-
cin 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.
Tipos abstractos de datos 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 Pre-
ss, 1986.
3.1.7 Tipos abstractos de datos versus estructuras de datos
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 dispo-
nibles en los lenguajes de programacin habituales (booleanos, enteros, reales, caracteres, vecto-
res, 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 es-
tas 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, sim-
plemente un TAD define un dominio de valores junto con las operaciones que permiten manejar-
los, y esto es algo que puede variar de unas aplicaciones a otras, an en lo que se refiere a las
estructuras de datos clsicas.
3.2 Especificacin algebraica de TADs
Un texto de referencia
H. Elvig, B. Mahr. Fundamentals of Algebraic Specification. Vol. 1. EATCS Monographs on
Theoretical Computer Science. Springer Verlag, 1985.
Como ya hemos visto en el tema anterior es conveniente, desde el punto de vista de la abs-
traccin, separar la especificacin de la implementacin.
Los componentes de la especificacin:
Dominio de valores abstracto
Operaciones
Tipos abstractos de datos 265
Axiomas
Los componentes de la implementacin:
Representacin concreta de los valores con estructuras de datos adecuadas
Funciones y procedimientos para las operaciones
Ocultamiento de la representacin y cdigo internos.
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:
El razonamiento con ecuaciones es relativamente sencillo y natural
Las ecuaciones no slo especifican las propiedades de las operaciones, sino que especifi-
can tambin cmo construir valores de ste
Las especificaciones basadas en ecuaciones suelen dar muchas pistas para la implementa-
cin
Una diferencia menor es la disparidad de notaciones.
3.2.1 Tipos, operaciones y ecuaciones
Una especificacin algebraica consta fundamentalmente de tres componentes:
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 per-
files de las operaciones sin incluir las ecuaciones.
Tipos abstractos de datos 266
Algo de notacin:

: signatura de un TAD
t, t
i
: tipos
c : t operacin constante de gnero t
f : t
1
, , t
n
s operacin con perfil, donde los t
i
son los tipos de los argumentos y t es el
tipo del resultado
Ejemplos: BOOL y NAT
Vamos como primer ejemplo la especificacin algebraica de los booleanos
tadBOOL
tipo
Bool

operaciones
Cierto,Falso:Bool/*gen*/
(not):BoolBool /*mod*/
(and),(or):(Bool,Bool)Bool/*mod*/

ecuaciones
x:Bool
notCierto =Falso
notFalso =Cierto
Ciertoandx=x
Falsoandx =Falso
Ciertoorx =Cierto
Falsoorx =x
ftad
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
Tipos abstractos de datos 267
tipo
Nat
operaciones
Cero:Nat /*gen*/
Suc_:NatNat /*gen*/
(+),(*):(Nat,Nat)Nat /*mod*/
(==),(/=):(Nat,Nat)Bool/*obs*/
(),(),(<),(>):(Nat,Nat)Bool /*obs*/
ecuaciones
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
ftad
Ntese que las ecuaciones tienen forma de definiciones recursivas, definimos el comporta-
miento 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
y como caso recursivo
Suc(x)+y=Suc(x+y)
de forma que el sumando de la izquierda se va acercando al caso base Cero.
Ntese la diferencia entre la operacin de igualdad ==, y la igualdad algebraica entre trminos.
La operacin de igualdad es una operacin ms que sirve para construir trminos, mientras que
las ecuaciones de la especificacin indican equivalencias entre trminos, por ejemplo que el
trmino Cero==Cero es igual al trmino Cierto.
3.2.2 Trminos de tipo t: T
t
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
Tipos abstractos de datos 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
,
t
(X) conjunto de los trminos de tipo t con variables en X
Donde el conjunto de variables X hace referencia a las variables que hemos utilizado al escri-
bir las ecuaciones.
que se define recursivamente como:
x e X
t
x e T
,
t
(X)
donde X
t
_ X
si x pertenece a las variables de tipo t entonces pertenece al conjunto de trminos de tipo
t con variables en X
c : t c e T
,
t
(X)
f : t
1
, , t
n
t . t
i
e T
,
t
i
(X) (1 i n) f(t
1
, , t
n
) e T
,
t
(X)
Convenios
T
,
t
es T
,
t
(C). 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
t para una signatura que se da por sabida es T
t
Ejemplos
En NAT hay dos tipos Ent y Bool, y se tiene
Suc(x)+Suc(Suc(y))eT
Ent
(x,y)
Suc(Cero)+Suc(Suc(Cero))eT
Ent

Suc(x)==CeroeT
Bool
(x)
Suc(Cero)/=CeroeT
Bool

3.2.3 Razonamiento ecuacional. T-equivalencia


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 ecua-
ciones, segn las leyes de la lgica.
Sea T un TAD con tipo principal t, dos trminos t, t: t se llaman T-equivalentes (en
smbolos t=
T
t), 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 equivalen-
cia nueva, por lo que en un TAD basta con hacer referencia a las equivalencias entre trminos del
tipo principal.)
Tipos abstractos de datos 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
t
1
=t
1
t
n
=t
n

f(t
1
,,t
n
)=f(t
1
,,t
n
)
Axiomas ecuacionales
t[x
1
/t
1
,,x
n
/t
n
]=t[x
1
/t
1
,,x
n
/t
n
]
si t = t es una ecuacin de la especificacin
Por ejemplo, en NAT se puede deducir la siguiente igualdad algebraica entre trminos
Suc(Cero)*Suc(Cero)=Suc(Cero)
como podemos demostrar utilizando las ecuaciones de la especificacin

Suc(Cero)*Suc(Cero)
*.2 == (Suc(Cero)*Cero)+Suc(Cero)
*.1 == Cero+Suc(Cero)
+.2 == Suc(Cero+Cero)
+.1 == Suc(Cero)

donde, en todos los pasos, hemos utilizado la regla axiomas ecuacionales del clculo.
Advertencia: Las notaciones

t=t y t=
T
t
Tipos abstractos de datos 270

no deben confundirse
t = t indica una ecuacin que puede cumplirse o no
t =
T
t indica que hemos demostrado a partir de la especificacin que t = t se cumple
siempre
por ejemplo
Suc(x)=Suc(y)
puede cumplirse para algunos valores de x, y
Suc(x)=
T
Suc(y)
es falso, pues de la especificacin no se deduce que Suc(x) = Pred(y) se cumpla siempre
Diremos que =
T
es la igualdad algebraica entre trminos inducida por la especificacin de que se
trate.
En cualquier especificacin algebraica de un TAD se tiene que
los trminos sin variables denotan los valores del tipo distintos trminos pueden denotar
al mismo valor
la T-equivalencia =
T
especifica las igualdades vlidas entre valores del tipo
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 Operaciones generadoras, modificadoras y observadoras
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 t, 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:t
&
t
que estn pensadas para construir todos los valores de tipo t

Modificadoras
Sern las restantes operaciones con perfil
f:t
&
t
Tipos abstractos de datos 271
que no sean generadoras. Estas operaciones estn pensadas para hacer clculos que pro-
duzcan resultados de tipo t.
Observadoras
Sern algunas operaciones con perfil
g:t
&
t talquett;yalgnt
i
t
pensadas para obtener valores de otros gneros a partir de valores de tipo t.
Volvemos sobre los ejemplos de BOOL y NAT y clasificamos las operaciones

Cierto,Falso:Bool /*gen*/
(not):BoolBool /*mod*/
(and),(or):(Bool,Bool)Bool /*mod*/
y para NAT

Cero:Nat /*gen*/
Suc:NatNat /*gen*/
(+),(*):(Nat,Nat)Nat /*mod*/
(==),(/=):(Nat,Nat)Bool /*obs*/
(),(),(<),(>):(Nat,Nat)Bool /*obs*/
Dada esta clasificacin de las operaciones, hay un tipo especialmente interesante de trminos,
los que slo utilizan operaciones generadoras.
3.2.5 Trminos generados: TG
t
Dado un TAD T con tipo principal t se llaman trminos generados a aquellos trminos de tipo t
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 t pues esos trminos deben ser equivalentes a trminos generados en el TAD donde
se define el correspondiente tipo.)
TG
,t
(X)=
def
{teT
,t
(X)|todaslasoperacionesusadasentson
generadoras}
Al igual que en la notacin de T, cuando X = C ponemos TG
,
t
, y omitimos si es sabida,
con lo cual notamos TG
t
.
En BOOL los trminos generados son

TG
Bool
={Cierto,Falso}
En NAT son trminos generados:

Tipos abstractos de datos 272


Suc(Suc(Cero))Suc(Suc(Suc(Cero))) Suc(Suc(Cero))

y no son trminos generados


Suc(Suc(Cero))+Suc(Cero) Suc(Cero)*Suc(Cero)

En general, en NAT son trminos generados el conjunto

TG
Nat
={Cero}{Suc
n
(Cero)|n>0}

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 Completitud suficiente de las generadoras
Dado el TAD T con tipo principal t, 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 t
debe existir un trmino cerrado y generado t de tipo t que sea T-equivalente a t

t:teT
,t
:-t:teTG
,t
:t=
T
t
No nos preocupamos por los trminos de otros tipos distintos de t porque esos trminos de-
ben 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 trmi-
nos induccin estructural.

Base:t/Cierto

Cierto=
BOOL
Cierto

Base:t/Falso

Falso=
BOOL
Falso
Pasoinductivo:t/nott1

Tipos abstractos de datos 273


nott1
H.I. =
BOOL
nott1

t1/Cierto

notCierto
not.1 =
BOOL
Falso

t1/Falso

notFalso
not.2 =
BOOL
Cierto

Pasoinductivo:t/t1andt2

t1andt2
H.I. =
BOOL
t1andt2

t1/Cierto

Ciertoandt2
and.1 =
BOOL
t2

t1/Falso

Falsoandt2
and.2 =
BOOL
Falso

Y de igual forma para or


Ejemplo: las generadoras de NAT son suficientemente completas

Base:t/Cero
Cero=
NAT
Cero

Pasoinductivo:t/Suc(t1)
Suc(t1)
H.I. =
NAT
Suc(t1)

Pasoinductivo:t/t1+t2
t1+t2
H.I. =
NAT
t1+t2
Lema1 =
NAT
t
Ellema1dicequedadosdostrminosgeneradost1yt2existeotro
trminogeneradottalquet1+t2=
NAT
t.Lodemostraremosms
abajo.

Pasoinductivo:t/t1*t2
t1*t2
Tipos abstractos de datos 274
H.I. =
NAT
t1*t2
Lema2 =
NAT
t
Ellema2dicequedadosdostrminosgeneradost1yt2existeotro
trminogeneradottalquet1*t2=
NAT
t.Lodemostraremosms
abajo.

Lema1
Base:t2/Cero
t1+Cero
+.1 =
NAT
t1

Pasoinductivo:t2/Suc(t3)
t1+Suc(t3)
+.2 =
NAT
Suc(t1+t3)
H.I. =
NAT
Suc(t4)

Lema2
Base:t2/Cero
t1*Cero
*.1 =
NAT
Cero

Pasoinductivo:t2/Suc(t3)

t1*Suc(t3)
*.2 =
NAT
t1*t3+t1
H.I. =
NAT
t4+t1
Lema1 =
NAT
t5

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 completi-
tud 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 =
NAT
Cero
Pasoinductivo:y/Suc(y1)

Cero+Suc(y1)
+.2 =
NAT
Suc(Cero+y1)
H.I. =
NAT
Suc(y1)
Tipos abstractos de datos 275
3.2.8 Diferentes modelos de un TAD
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 lge-
bra donde el dominio de valores tenga un solo elemento es modelo de cualquier especifi-
cacin 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 especifi-
cacin.
Ejemplo: modelo no estndar de BOOL
(ej. 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). In-
terpreta 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.
A
Bool
={c,f, }

Cierto
A
:A
Bool

Falso
A
:A
Bool

not
A
:A
Bool
A
Bool

(and
A
),(or
A
):(A
Bool
,A
Bool
)A
Bool

Cierto
A
=
def
c
Falso
A
=
def
f

v not
A
v
c f
f c

v1v2 and
A
v
cc c
cf f
c
fc f
ff f
f
f
c
f
f

v1v2 or
A
v
cc c
cf c
c
c
fc c
ff f
f
c
c
f

notCierto=Falso porlaprimerafiladenot
A

notFalso=Cierto porlasegundafiladenot
A

Ciertoandx=x porlastresprimerasfilasdeand
A

Tipos abstractos de datos 276


Falsoandx=Falso porlasfilas4a6deand
A

Ciertoorx=Cierto porlastresprimerasfilasdeor
A

Falsoorx=x porlasfilas4a6deor
A

Ntese que en la ltima fila de not


A
, y las tres ltimas de and
A
y not
A
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

not
A
Cierto
A
=Falso
A
not
A
c=ff=f
3.2.9 Modelo de trminos
En cualquier especificacin algebraica es posible construir un modelo abstracto a partir del
conjunto de trminos generados.
Dado un TAD T, su modelo de trminos viene dado por:
Para cada tipo t, el dominio abstracto de este tipo es

A
t
=
def
TG
t

La T-equivalencia establece las ecuaciones vlidas entre valores abstractos

t=
T
tt=tsededucedelaespecificacin
parat,teA
S

Las operaciones abstractas definidas como sigue

Sif:t
1
,,t
n
t
t
i
eA
ti
(1in)

definimos

f
A
(t
1
,,t
n
)=
def
teA
t
t.q.f(t
1
,,t
n
)=
T
t

Es decir consideramos como valores los trminos generados, y como resultados de las opera-
ciones los trminos generados a los que son equivalentes.
Por ejemplo, en NAT
Tipos abstractos de datos 277

Suc
A
(Suc
2
(Cero))=
T
Suc
3
(Cero)
Suc
2
(Cero)+
A
Suc
3
(Cero)=
T
Suc
5
(Cero)
Suc
3
(Cero)*
A
Suc
2
(Cero)=
T
Suc
6
(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 especifica-
cin 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 implementa-
ciones de un TAD estn obligadas a realizar una representacin del modelo abstracto.
3.2.10 Proteccin de un TAD usado por otro
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 t 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=
T
t.
(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=
T
t, entonces ya se tena t=
S
t.

(a) Demuestra que el siguiente TAD que usa a BOOL no protege a BOOL, porque
in<troduce basura y confusin en BOOL:

tadPERSONA
usa
BOOL
tipo
Persona
operaciones
Carmen,Roberto,Luca,Pablo:Persona /*
gen*/
feliz:PersonaBool /*
obs*/
ecuaciones
feliz(Carmen) =feliz(Roberto)
feliz(Pedro) =feliz(Luca)
feliz(Pedro) =notfeliz(Pablo)
feliz(Luca) =Cierto
Tipos abstractos de datos 278
feliz(Pablo) =Cierto
Introduce basura porque hay trminos de tipo Bool para los que no se puede deducir la equiva-
lencia con ninguno de los trminos generados de BOOL es decir, que no se sabe si son ciertos o
falsos:

feliz(Carmen) feliz(Roberto)
E introduce confusin porque es posible obtener nuevas equivalencias entre trminos:
Cierto
feliz.4 =
PERSONA
feliz(Luca)
feliz.2 =
PERSONA
feliz(Pedro)
feliz.3 =
PERSONA
notfeliz(Pablo)
feliz.5 =
PERSONA
notCierto
not.1 =
PERSONA
Falso

NAT usa a BOOL dejndolo protegido:


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 sintc-
tica de t.
No se introduce confusin, porque en NAT no es posible demostrar la ecuacin Cierto =
Falso.
3.2.11 Generadoras no libres
Sea T un TAD con tipo principal t. Se dice que las operaciones generadoras de T son no libres
si existen trminos generados no idnticos t y t de tipo t, que sean T-equivalentes. Por el contra-
rio, se dice que las operaciones generadoras de T son libres si no es posible encontrar trminos
generados no idnticos t, t de tipo t 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:Ent /*gen*/
Suc,Pred:EntEnt /*gen*/
():EntEnt /*mod*/
(+),(),(*):(Ent,Ent)Ent /*mod*/
(^):(Ent,Nat)Ent/*mod*/
ecuaciones
x,y:Ent:n:Nat
Tipos abstractos de datos 279
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
ftad
Podemos ver que las generadoras del TAD ENT no son libres pues existen trminos genera-
dos que son ENT-equivalentes entre s:

Suc(Pred(Suc(Cero)))
Suc =
ENT
Suc(Cero)
Por el contrario las generadoras de BOOL y TAD s son libres pues la especificacin no inclu-
ye 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.
3.2.12 Representantes cannicos de los trminos: TC
t
_ TG
t
Sea T un TAD con tipo principal t. Se llama sistema de representantes cannicos a cualquier subcon-
junto TC
t
del conjunto TG
t
de todos los trminos generados y cerrados de tipo t, tal que para
cada t e TG
t
exista un nico t e TC
t
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:

TC
Ent
=
def
{Cero}{Suc
n
(Cero)|n>0}{Pred
n
(Cero)|n>0}
Tipos abstractos de datos 280

la anterior es la eleccin ms natural, pero no la nica, por ejemplo

TC
Ent
=
def
{Suc
n
(Cero)|n>0}{Pred
n
(Suc(Cero))|n>0}
Insistir en la idea expuesta anteriormente de que cuando tenemos generadoras no libres de-
bemos 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 posi-
ble traducir cualquier trmino generado a uno de la forma Cero v Suc
n
(Cero) v Pred
n
(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 va-
lores del modelo en el que estamos pensando.
Slo escribiremos ecuaciones entre generadoras cuando estas no sean libres.
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 Operaciones privadas y ecuaciones condicionales
Vamos a introducir dos nuevos elementos que es posible utilizar en las especificaciones alge-
braicas:
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 ca-
sos, tambin a su implementacin.
Ecuaciones condicionales. Son ecuaciones de la forma
ecuacinsiecuacin
ecuacinsitdondeteT
Bool

Veamos un ejemplo con la especificacin de los enteros con orden. Aparece aqu otro concep-
to 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 no-
Neg. Y para escribir las ecuaciones de noNeg tenemos que utilizar ecuaciones condicionales.
tadENTORD
usa
ENT
Tipos abstractos de datos 281
operaciones
(==),(/=):(Ent,Ent)Bool/*obs*/
(),(),(<),(>):(Ent,Ent)Bool /*obs*/
operacionesprivadas
noNeg:EntBool /*obs*/

ecuaciones
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)
ftad
La inclusin de ecuaciones condicionales modifica una de las reglas del clculo ecuacional
Axiomas ecuacionales

C[x
1
/t
1
,,x
n
/t
n
]
t[x
1
/t
1
,,x
n
/t
n
]=t[x
1
/t
1
,,x
n
/t
n
]
si t=tsiC es una ecuacin de la especificacin
Como ejercicio se propone la especificacin del TAD POLINOMIO, nada trivial, que necesi-
ta 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 especifica-
ciones permiten especificar restricciones sobre TADs. Son tiles como luego veremos para escri-
bir 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
Tipos abstractos de datos 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 defi-
nicin 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 co-
mo escribimos el tipo paramtrico sin saber sobre qu tipos se instanciar, no tenemos esa in-
formacin.
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 posi-
bles.
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)Bool
axiomas
%(==)esunaoperacindeigualdad.

ecuaciones
x,y:Elem:
x/=y=not(x==y)
fclase

Y la clase de los tipos con orden

claseORD
hereda
EQ
operaciones
(),(),(<),(>):(Elem,Elem)Bool
Tipos abstractos de datos 283
axiomas
%()esunaoperacindeorden
ecuaciones
x,y:Elem:
xy=yx
x<y=(xy)and(x/=y)
x>y=y<x
Escribimos las condiciones sobre == y s 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 indi-
cando 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
C:Cjto[Elem] /*gen*/
Pon:(Elem,Cjto[Elem])Cjto[Elem] /*gen*/
quita:(Elem,Cjto[Elem])Cjto[Elem] /*
mod*/
esVaco:Cjto[Elem]Bool /*obs*/
(e):(Elem,Cjto[Elem])Bool /*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,C) =C
** quita(x,Pon(y,xs))=quita(x,xs)six==y
quita(x,Pon(y,xs))=Pon(y,quita(x,xs))six/=y
esVaco(C) =Cierto
esVaco(Pon(x,xs)) =Falso
Tipos abstractos de datos 284
xeC =Falso
xePon(y,xs) =x==yorxexs
ftad
Uno podra sentirse tentado de escribir la ecuacin marcada con ** como:

quita(x,Pon(y,xs))=xssix==y

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:
Vaco
quita.2 = quita(0,Pon(0,Vaco))
Pon.1 = quita(0,Pon(0,Pon(0,Vaco)))
quita.2 = Pon(0,Vaco)

En general podramos obtener Vaco = t para cualquier t de tipo Cjto.


Ejemplares de un TAD genrico
Una vez especificado un TAD genrico, es posible declarar ejemplares suyos. Cada ejemplar co-
rresponde a un cierto reemplazamiento del parmetro formal del TAD genrico por un parme-
tro actual.
Por ejemplo, podemos construir un ejemplar del TAD genrico para los naturales, con lo que
el nuevo TAD sera
CJTO[E/NATconE.Elem/NAT.Nat]abreviadocomoCJTO[NAT]

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 genri-
co:
sustituyendo Elem por Nat, con lo que el tipo principal es Cjto[Nat]
considerando que las ecuaciones para la operacin == la nica que no se especifica en la
correspondiente clase de tipos son las ecuaciones que aparecen en la especificacin de
NAT.
TADs genricos con varios parmetros
Tambin es posible parametrizar un TAD con ms de un parmetro.
Como ejemplo podemos especificar el TAD de las parejas de valores cualesquiera PAREJA[A,
B :: ANY]
tadPAREJA[A,B::ANY]
Tipos abstractos de datos 285
tipo
Pareja[A.Elem,B.Elem]
operaciones
Par:(A.Elem,B.Elem)Pareja[A.Elem,B.Elem]/*gen*/
pr:Pareja[A.Elem,B.Elem]A.Elem/*obs*/
sg:Pareja[A.Elem,B.Elem]B.Elem /*obs*/
ecuaciones
x:A.Elem:y:B.Elem:
pr(Par(x,y))=x
sg(Par(x,y))=y
ftad
Posibles instanciaciones de esta TAD genrico:

PAREJA[A/NATconA.Elem/NAT.Nat,B/BOOLconB.elem/BOOL.Bool]

oabreviadamente

PAREJA[NAT,BOOL]
EltipoprincipaldeesteejemplaresPareja[Nat,Bool]

O parejas de conjuntos

PAREJA[CJTO[NAT],CJTO[BOOL]]

contipoprincipalPareja[Cjto[Nat],Cjto[Bool]]
3.2.16 Trminos definidos e indefinidos
En muchas ocasiones nos tendremos que especificar TADs que incluyan operaciones parcia-
les. 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 re-
presenta una coleccin de valores donde es posible acceder al ltimo elemento aadido, imple-
menta la idea intuitiva de pila de objetos.

tadPILA[E::ANY]
usa
BOOL
tipo
Pila[Elem]
operaciones
PilaVaca:Pila[Elem] /*gen*/
Apilar:(Elem,Pila[Elem])Pila[Elem] /*gen*/
desapilar:Pila[Elem]Pila[Elem] /*mod*/
cima:Pila[Elem]Elem /*obs*/
esVaca:Pila[Elem]Bool /*obs*/
Tipos abstractos de datos 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 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 distin-
tas 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 con-
venio, para cada operacin declarada como total en la signatura, con un perfil de la forma:

f:t
1
,,t
n
t

suponemos aadido a la especificacin el axioma:


x
1
:t
1
x
n
:t
n
:deff(x
1
,,x
n
)

Por ejemplo, en las pilas se suponen los axiomas de definicin:

x:Elem:xs:Pila[Elem]:
defPilaVaca
defApilar(x,xs)
defesVaca(xs)

Decimos entonces que un trmino t se considera definido siempre que def t sea deducible a par-
tir 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
Tipos abstractos de datos 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
t
1
=t
1
t
n
=t
n
deff(t
1
,,t
n
)
f(t
1
,,t
n
)=f(t
1
,,t
n
)
Axiomas ecuacionales
C[x
1
/t
1
,,x
n
/t
n
]deft
1
deft
n

t[x
1
/t
1
,,x
n
/t
n
]=t[x
1
/t
1
,,x
n
/t
n
]
si t=tsiC es una ecuacin de la especificacin
Igualdad existencial
t=t
deftdeft
por la forma como hemos modificado las reglas del clculo, slo vamos a poder estable-
cer 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 uti-
lizar trminos definidos.
Operaciones estrictas.
Convenimos en que todas las operaciones de nuestras especificaciones son estrictas, por
lo que
deff(t
1
,,t
n
)
deft
1
deft
n

Esta regla, como las anteriores, tambin se puede aplicar hacia atrs de forma que si no
est definido algn t
i
entonces no est definida f(t
1
, , t
n
).
Variables definidas
Tipos abstractos de datos 288

defx

Las variables estn definidas por hiptesis; y slo hay que tener cuidado de que se sustitu-
yen por trminos definidos.

Axiomas de definicin

C[x
1
/t
1
,,x
n
/t
n
]deft
1
deft
n

deft[x
1
/t
1
,,x
n
/t
n
]
si def tsiC es un axioma de definicin de la especificacin

En las condiciones tambin se utiliza la igualdad existencial


Seccin errores
El contenido de la seccin errores se define por eliminacin: si def t, entonces t no puede in-
cluirse en la seccin errores. Por lo tanto, en la seccin errores se deben incluir aquellos trminos
para los que no se puede demostrar def t
Revisin de conceptos debido a las funciones parciales
La inclusin de funciones parciales hace que debamos revisar algunos de los conceptos que
hemos presentado hasta ahora, bsicamente para indicar que slo consideramos trminos defini-
dos.
La T-equivalencia slo se considera entre trminos definidos.
Por la forma cmo hemos escrito las reglas del clculo slo es posible deducir nuevas
equivalencias entre trminos definidos, por lo que
t=
T
tdeft.deft

TD
t
(X)=
def
{teT
t
(X)|deft}

De entre los trminos generados interesan los definidos

TGD
t
(X)=
def
{teTG
t
(X)|deft}

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 defini-
do t

t:teTD
t
:-t:teTGD
t
:t=
T
t
Tipos abstractos de datos 289
Un sistema de representantes cannicos debe elegirse como un subconjunto del conjunto
de todos los trminos generados cerrados y definidos, de manera que cada trmino cerra-
do y definido sea T-equivalente a un nico representante cannico

TCD
t
(X)=
def
{teTC
t
(X)|deft}

Los dos siguientes puntos se pueden obviar en la explicacin


En cuanto a la proteccin de los TAD usados, las condiciones se reescriben para hacer re-
ferencia 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=
T
t.
(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=
T
t, entonces ya se tena
t=
S
t.
Por ltimo, se modifica la definicin del modelo de trminos para considerar slo los
trminos definidos
Para cada tipo t, el dominio abstracto de este tipo es

A
t
=
def
TGD
t

La T-equivalencia establece las ecuaciones vlidas entre valores abstractos

t=
T
tt=tsededucedelaespecificacin
parat,teA
t

(esta condicin slo se modifica implcitamente, al cambiar la definicinde A


t
)

Las operaciones abstractas definidas como sigue

Sif:t
1
,,t
n
t
t
i
eA
ti
(1in)

definimos

teA
t
t.q.f(t
1
,,t
n
)=
T
tsideff(t
1
,,t
n
)
f
A
(t
1
,,t
n
)=
def

indefinido enotrocaso
por ejemplo:
cima
A
(PilaVaca) estindefinido
cima
A
(desapilar
A
(Apilar(t2,Apilar(t1,PilaVaca))))=t1
Tipos abstractos de datos 290
3.2.17 Igualdad existencial, dbil y fuerte
En presencia de operaciones parciales se pueden definir distintos conceptos de igualdad:
Igualdad existencial =
e
t =
e
t
def
t y t estn ambos definidos y valen lo mismo
nosotros escribiremos simplemente = para la igualdad existencial. Este es el concepto
de igualdad que hemos utilizado al extender las reglas del clculo

Igualdad dbil =
d
t =
d
t
def
t y t valen lo mismo si estn los dos definidos
(la igualdad dbil siempre se cumple si t, t o ambos estn indefinidos)

Igualdad fuerte =
f
t =
f
t
def
o bien t y t estn ambos definidos y valen lo mismo
o bien t y t estn ambos indefinidos
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, aprovechn-
donos de que

t=
e
tdeft

t=
d
tequivalea

t=
e

tt=
e
t.t=
e
t

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=
f
tequivalea

(t=
e
tt=
e
t).
(t=
e
tt=
e
t).
(t=
e
tt=
e
t.t=
e
t)

3.3 Implementacin de TADs


Supongamos dada una especificacin algebraica de un TAD T en el que aparecen diferentes
tipos. Una implementacin de T consiste en:
Un dominio concreto D
t
para cada tipo t incluido en T
Tipos abstractos de datos 291
Los dominios concretos se implementan mediante declaraciones de tipos, usando otros
tipos ya implementados por nosotros o incluidos en el lenguaje que definen el tipo repre-
sentante en realidad el D
t
es el conjunto de representantes vlidos del tipo t.
Una operacin concreta

f
C
:D
t1
,,D
tn
D
t

para cada operacin


f:t
1
,,t
n
t

Las operaciones concretas pueden implementarse como procedimientos aunque en la es-


pecificacin slo se admitan funciones.
De modo que satisfagan dos requisitos:
Correccin: la implementacin debe satisfacer los axiomas de la especificacin
Privacidad y proteccin: la estructura interna de los datos debe estar oculta; el nico acce-
so posible al tipo debe ser a travs de las operaciones pblicas de ste.
En el siguiente tema veremos cmo garantizar la privacidad y proteccin; y ms adelante en
este mismo tema estudiaremos un mtodo para probar la correccin.
Por ahora vamos a empezar con un ejemplo donde motivaremos los conceptos bsicos relati-
vos a la construccin de implementaciones correctas.
3.3.1 Implementacin correcta de un TAD
Vamos a presentar las condiciones que debemos exigir a una implementacin para que sea co-
rrecta 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 co-
mo 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, tene-
mos 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)))

vendra dada por:


Tipos abstractos de datos 292
5 7 2

IndCima
Esta implementacin tiene el inconveniente de que impone a priori un lmite al tamao mxi-
mo 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 comen-
zar definiendo dos cosas:
qu valores concretos queremos aceptar como representantes vlidos de valores abstractos
la idea es que el tipo representante elegido puede tomar valores que no consideremos
vlidos.
cul es el valor abstracto representado por cada valor concreto que sea un representante
vlido.
Empezamos formalizando qu condiciones les exigimos a los representantes vlidos:
R
Pila[Nat]
(xs)

def

xs:Pila[Nat].
0sxs.indCimaslimite.
i:1sisxs.indCima:R
Nat
(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 re-
presentacin.
Notacin:

TR
t
:tiporepresentantedeltipot
RV
t
:conjuntoderepresentantesvlidosdeltipotD
t
,eldominioasignado
altipot
R
t
:invariantedelarepresentacindeltipot

Los representantes vlidos son aquellos valores del tipo representante que cumplen el
invariante de la representacin:

Tipos abstractos de datos 293


RV
t
={x:TR
t
|R
t
(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:
paraxstalqueR
Pila[Nat]
(xs)
A
Pila[Nat]
(xs)=
def
hazPila(xs.espacio,xs.indCima)

hazPila(v,0)=
def
PilaVaca
hazPila(v,n)=
def
Apilar(A
Nat
(v(n)),hazPila(v,n1))si1sns
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 t dado en el modelo de
trminos
A
t
:RV
t
TGD
t

Convenios:
Para simplificar, escribiremos v(n) en lugar de A
Nat
(v(n))
En general, muchas veces simplificaremos omitiendo R
o
y A
o
cuando o no sea el tipo
principal que estemos considerando.
Escribiremos R(d) y A(d) cuando se suponga conocido el tipo correspondiente.
Ntese que dos representantes vlidos diferentes pueden tener asociado el mismo valor abs-
tracto. Como se puede comprobar en el siguiente ejemplo:
indCima:
espacio:
3
5 2 7 9 7

indCima:
espacio:
3
5 2 7 0 6

xs:
ys:
donde se tiene que
xs,yseRV
Pila[Nat]
;xs=ys

A(xs)
=
Pila[Nat]

A(ys)
=
Pila[Nat]

Tipos abstractos de datos 294


Apilar(Suc
7
(Cero),Apilar(Suc
2
(Cero),Apilar(Suc
5
(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 abs-
tracto 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 operacio-
nes del TAD. Para cada operacin supondremos como parte de su precondicin que los argu-
mentos 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 opera-
cin abstracta aplicada sobre los valores abstractos que representan los argumentos. Formalmen-
te,
la especificacin pre/post para una funcin f
C
que implementa una operacin

f:(t
1
,,t
n
)t

del TAD T
funcf
C
(x
1
:TR
t1
,,x
n
:TR
tn
)devy:TR
t
;
{P
0
:R
t1
(x
1
)..R
tn
(x
n
).DOM
f
(x
1
,,x
n
).LIM
f
(x
1
,,x
n
)}

{Q
0
:R
t
(y).A
t
(y)=
T
f
A
(A
t1
(x
1
),,A
tn
(x
n
))}

donde
TR
t
i
, TR
t
son los tipos representantes
R
t
i
, R
t
son los invariantes de la representacin
A
t
i
, A
t
son las funciones de abstraccin
DOM
f
es la condicin de dominio.
Para x
i
: TR
t
i
tales que R
t
i
(x
i
)
DOM
f
(x
1
, , x
n
) def f(A
t
1
(x
1
), , A
t
n
(x
n
))
las condiciones de DOM pueden de venir expresadas como operaciones sobre trmi-
nos abstractos o como condiciones sobre el tipo representante
LIM
f
expresa restricciones adicionales impuestas por la implementacin
Tipos abstractos de datos 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 LIM
f
no es la condicin trivial cierto, decimos que la implementacin es parcialmente co-
rrecta.
Hay que tener cuidado con la aparicin de f
A
en la postcondicin. Cuando el TAD T tiene un
conjunto de generadoras libres, entonces no hay problema. Sin embargo, si el conjunto de gene-
radoras 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 f
A
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];
{P
0
:}
inicio
xs.indCima:=0
{Q
0
:R
Pila[Nat]
(xs).A
Pila[Nat]
(xs)=
PILA[NAT]
PilaVaca}
devxs
ffunc

Segn el esquema terico, en la postcondicin debera aparecer PilaVaca


A
, pero el valor de es-
ta 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];
{P
0
:R
Nat
(x).R
Pila[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
{Q
0
:R
Pila[Nat]
(ys).A
Pila[Nat]
(ys)=
PILA[NAT]
Apilar(A
Nat
(x),A
Pila[Nat]
(xs))}
devys
Tipos abstractos de datos 296
ffunc

** no fijamos cul es el tratamiento de errores. Hay muchas formas de implementarlo:


mostrar un mensaje y detener la ejecucin
mostrar el mensaje y no detener la ejecucin
implementar un mecanismo que permite a los clientes del TAD saber si se ha producido
un error y actuar en consecuencia
En la funcin Apilar aparece un ejemplo de condicin en la precondicin que viene impuesta
por las limitaciones de la implementacin: xs.indCima < lmite. Tenemos por tanto que esta es
una implementacin parcialmente correcta. No obstante, la postcondicin garantiza que Apilar
realiza correctamente la operacin abstracta en aquellos casos en que su ejecucin termina nor-
malmente.
Utilizando esta funcin como ejemplo, podemos representar grficamente cmo se puede es-
tablecer una relacin entre la funcin concreta y la abstracta a travs de las funciones de abstrac-
cin:
Apilar
A

(TGD
Nat
,TGD
Pila[Nat]
) TGD
Pila[Nat]

A
Nat
A
Pila[Nat]
A
Pila[Nat]

(RV
Nat
,RV
Pila[Nat]
) RV
Pila[Nat]
Apilar
C

La funcin cima
funccima(xs:Pila[Nat])devx:Nat;
{P
0
:R
Pila[Nat]
(xs).notesVaca(A(xs))}
inicio
siesVaca(xs) %xs.indCima==0
entonces
error(Lapilavacanotienecima)%*
sino
x:=xs.espacio(xs.indCima)
fsi
{Q
0
:R
Nat
(x).A
Nat
(x)=
PILA[NAT]
cima
A
(A
Pila[Nat]
(xs))}**
devx
ffunc
** podemos abreviar esta condicin, no preocupndonos por los elementos, como:

x=
PILA[NAT]
cima(A
Pila[Nat]
(xs))

En esta operacin aparece una condicin en la precondicin de tipo DOM


f
, es decir, una res-
triccin impuesta por la especificacin.
Tipos abstractos de datos 297
En cuanto a la operacin desapilar:

funcdesapilar(xs:Pila[Nat])devys:Pila[Nat];
{P
0
:R
Pila[Nat]
(xs).xs.indCima>0}
inicio
sixs.indCima==0
entonces
error(nosepuededesapilardelapilavaca)
sino
ys.espacio:=xs.espacio;
ys.indCima:=xs.indCima1
fsi
{Q
0
:R
Pila[Nat]
(ys).A
Pila[Nat]
(ys)=
PILA[NAT]
despilar
A
(A
Pila[Nat]
(xs))}
devys
ffunc

Ntese que aqu hemos escrito la condicin


notesVaca(A(xs)) comoxs.indCima>0

nos permitimos esta libertad.


Ntese tambin que la operacin de igualdad entre naturales la hemos escrito ==, en lugar de
= como venamos haciendo hasta ahora. La razn es que queremos distinguir la operacin de
igualdad de la igualdad entre trminos en las especificaciones.
Nos queda por ltimo la operacin esVaca:
funcesVaca(xs:Pila[Nat])devr:Bool;
{P
0
:R
Pila[Nat]
(xs)}
inicio
r:=xs.indCima==0
{Q
0
:R
Bool
(r).A
Bool
(r)=
PILA[NAT]
esVaca
A
(A
Pila[Nat]
(xs))}
devr
ffunc

Implementacin de las operaciones como funciones o como


procedimientos
En los ejemplos anteriores hemos implementado todas las operaciones como funciones, segn
sugiere la especificacin. Sin embargo, esta implementacin tiene algunos inconvenientes:
En muchos lenguajes de programacin, las funciones no pueden devolver valores de tipos
estructurados.
La implementacin como funciones supone realizar copias de los parmetros, lo cual
consume espacio y tiempo.
Tipos abstractos de datos 298
En muchos casos consideramos a los valores de un TAD como objetos mutables, de mo-
do 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 operacio-
nes sobre las pilas daran lugar a las siguientes especificaciones:

procPilaVaca(sxs:Pila[Nat]);
{P
0
:}
{Q
0
:R
Pila[Nat]
(xs).A
Pila[Nat]
(xs)=
PILA[NAT]
PilaVaca}
fproc

procApilar(ex:Nat;esxs:Pila[Nat]);
{P
0
:xs=XS.R
Nat
(x).R
Pila[Nat]
(xs).xs.indCima<lmite}
{Q
0
:R
Pila[Nat]
(xs).A
Pila[Nat]
(xs)=
PILA[NAT]
Apilar(A
Nat
(x),A
Pila[Nat]
(XS))}
fproc

procdesapilar(esxs:Pila[Nat]);
{P
0
:xs=XS.R
Pila[Nat]
(xs).xs.indCima>0}
{Q
0
:R
Pila[Nat]
(xs).A
Pila[Nat]
(xs)=
PILA[NAT]
despilar
A
(A
Pila[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 es-
Vaca.
Es trivial modificar las implementaciones como funciones para llegar a la implementacin co-
mo 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:

Sea T una especificacin algebraica de una TAD. Supongamos que se cumple:


t =
T
t
siendo
var(t) var(t) = { x
1
: t
1
, , x
n
:t
n
}
t, t e TD
t
Para cualquier implementacin correcta de T puede asegurarse entonces que:
R
t
1
(x
1
) . . R
t
n
(x
n
) . def t
C
. def t
C
A
t
(t
C
) =
T
A
t
(t
C
)
Tipos abstractos de datos 299
o, escrito de otra forma:
{R
t1
(x
1
)..R
tn
(x
n
)}
r:=t
c
;
r:=t
c
;
{A
t
(r)=
T
A
t
(r)}

si este cmputo acaba puede no acabar por limitaciones de la implementacin, entonces se


cumple la postcondicin. Los trminos concretos se obtiene sustituyendo en los trminos abs-
tractos las operaciones por operaciones concretas. El trmino resultante ser ejecutable, siempre
que las operaciones del TAD se implementen todas como funciones:

tf(x,g(y,e)) t
c
f
C
(x,g
C
(y,e
C
))
Esta idea habra que formalizarla con ms cuidado pues no hemos definido cul es el trmino
concreto, t
C
, 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 efecti-
vamente las implementaciones de las operaciones son correctas con respecto a las especificacio-
nes 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 inmer-
sas estas funciones tambin lo sern.
3.3.2 Otro ejemplo: CJTO[NAT]
Vamos a plantear tres posibles representaciones para este tipos de datos:
Vectores de naturales
Vectores de naturales sin repeticin
Vectores de naturales ordenados sin repeticin
En los tres casos elegimos el mismo tipo representante:

const
limite=100;
tipo
Cjto[Nat]=reg
espacio:Vector[1..limite]deNat;
tamao:Nat
freg;
Tipos abstractos de datos 300
Vectores de naturales
El invariante de la representacin:
R
Cjto[Nat]
(xs)

def

xs:Cjto[Nat].
0sxs.tamaoslimite.
i:1sisxs.tamao:R
Nat
(xs.espacio(i))
La funcin de abstraccin:
paraxstalqueR
Cjto[Nat]
(xs)
A
Cjto[Nat]
(xs)=
def
hazCjto(xs.espacio,xs.tamao)

hazCjto(v,0)=
def
C
hazCjto(v,n)=
def
Pon(A
Nat
(v(n)),hazCjto(v,n1))si1snslmite

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:

R
Cjto[Nat]
(xs)

def

xs:Cjto[Nat].
0sxs.tamaoslimite.
i:1sisxs.tamao:R
Nat
(xs.espacio(i)).
i,j:1si<jsxs.tamao:xs.espacio(i)/=xs.espacio(j)

La funcin de abstraccin es la misma de antes. Tambin aqu puede ocurrir que dos conjun-
tos equivalentes estn representados por trminos generados diferentes, aunque equivalentes.
Vectores de naturales sin repeticin y ordenados
El invariante de la representacin:

R
Cjto[Nat]
(xs)

def

xs:Cjto[Nat].
0sxs.tamaoslimite.
i:1sisxs.tamao:R
Nat
(xs.espacio(i)).
i,j:1si<jsxs.tamao:xs.espacio(i)<xs.espacio(j)
Tipos abstractos de datos 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 opera-
ciones ser ms sencilla pues no habr que preocuparse por equivalencias entre los trminos ge-
nerados.
Implementacin de la operacin Pon
Vectores de naturales:
procPon(ex:Nat;esxs:Cjto[Nat]);
{P
0
:xs=XS.R(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
{Q
0
:R(xs).A(xs)=
PILA[NAT]
Pon(x,A(XS))}
fproc
En la postcondicin tambin hemos simplificado la notacin, escribiendo Pon en lugar de
Pon
A
y x en lugar de A
Nat
(x).
Vectores de naturales sin repeticin. Vamos a suponer implementada una funcin de bsque-
da con la siguiente especificacin:

funcbusca(x:Nat;v:Vector[1..limite]deNat;a,b:Nat)devr:
Bool;
{P
0
:1sasb+1sN+1}
{Q
0
:r-i:asisb:v(i)=x}
ffunc

La operacin Pon
procPon(ex:Nat;esxs:Cjto[Nat]);
{P
0
:xs=XS.R(xs).tamao<limite}
inicio
sitamao==limite
entonces
error(Nosepuedeinsertarelelemento)
sino
sibusca(x,xs.espacio,1,xs.tamao)
entonces
Tipos abstractos de datos 302
seguir
sino
xs.tamao:=xs.tamao+1;
xs.espacio(xs.tamao):=x
fsi
fsi
{Q
0
: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;
{P
0
:1sasb+1sN+1.ord(v[a..b])}
{Q
0
:asp+1sb+1.v[a..p]sx<v[p+1..b].r-i:asisb:v(i)
=x}
procPon(ex:Nat;esxs:Cjto[Nat]);
{P
0
:xs=XS.R(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
{Q
0
:R(xs).A(xs)=
PILA[NAT]
Pon(x,A(XS))}
fproc

Y la operacin desplazaDrch:
procdesplazaDrch(esv:Vector[1..limite]deNat;ea,b:Nat);
{P
0
:v=V.1sasb+1<N+1}
Tipos abstractos de datos 303
var
k:Nat;
inicio
parakdesdeb+1bajandohastaa+1hacer
v(k):=v(k1)
fpara
{P
0
: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 opera-
ciones Pon y quita.
3.3.3 Verificacin de programas que usan TADs
Al aplicar las reglas de verificacin a programas que usen TADs implementados funcional-
mente, las operaciones de estos se pueden tratar del mismo modo que si fuesen operaciones pre-
definidas.
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 procedi-
mientos 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=3.xs=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);
Tipos abstractos de datos 304
x:=2*x+1
{Q:x=3.xs=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=1.xs=Apilar(1,ys)}/*1*/
xs:=desapilar(xs);
{S:x=1.xs=ys}/*2*/
x:=2*x+1
{Q:x=3.xs=ys}/*3*/
Los tres pasos se verifican usando la regla de la asignacin:

Verificacin de /* 1 */
pmd(x:=cima(xs),R)
def(cima(xs)).R[x/cima(xs)]
def(cima(xs)).cima(xs)=1.xs=Apilar(1,ys)
PILA : P

Verificacin de /* 2 */
pmd(xs:=desapilar(xs),S)
def(desapilar(xs)).S[xs/desapilar(xs)]
def(desapilar(xs)).x=1.desapilar(xs)=ys
PILA : R

Verificacin de /* 3 */
pmd(x:=2*x+1,Q)
def(2*x+1).Q[x/2*x+1]
2*x+1=3.xs=ys
aritmtica(i.e.,NAT) : S
3.5 Estructuras de datos dinmicas
Recordemos que consideramos como estructuras de datos a los tipos concretos que utilizamos
para realizar los tipos definidos en los tipos abstractos de datos.
3.5.1 Estructuras de datos estticas y estructuras de datos dinmicas
Algunas implementaciones de TADs se basan en estructuras de datos estticas.
Tipos abstractos de datos 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 invo-
cacin 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 tipos numricos
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:
despilfarro de recursos si el espacio mximo reservado resulta ser excesivo
errores de desbordamiento en tiempo de ejecucin si el espacio reservado resulta ser insu-
ficiente
En suma, las estructuras estticas los vectores resultan demasiado rgidas para la implemen-
tacin de TADs cuyas operaciones sean capaces de generar valores cuya representacin necesite
una cantidad de espacio potencialmente ilimitado.
Tipos abstractos de datos 306
Estructuras de datos dinmicas
Se llama estructura de datos dinmica a una estructura que ocupa en memoria un espacio va-
riable 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 mu-
cho 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 cam-
biante 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 memo-
ria que es siempre del mismo tamao, independientemente de a dnde est apuntado el punte-
ro y su valor es la direccin de otra variable:
El valor de un puntero
1
es la direccin de otra variable, tal que:
El nombre de la variable apuntada por un puntero se puede obtener a partir del identifi-
cador del puntero. Al igual que en algunos lenguajes, nosotros obtendremos ese identifi-
cador colocando el carcter ^ detrs del identificador del puntero.
El espacio de memoria de la variable a la que apunta un puntero se ubica dinmicamente
durante la ejecucin. Esta variable no existe mientras que no sea ubicada.
Es posible anular una variable apuntada por un puntero, liberando as el espacio que
ocupa. Despus de ser anulada, la variable deja de existir.
Grficamente lo podemos representar de la siguiente forma:
1
Normalmente se abusa del lenguaje y se dice puntero cuando se quiere decir variable de tipo puntero.
Tipos abstractos de datos 307
p
p^
La propiedad fundamental de los punteros es que permiten obtener y devolver memoria
dinmicamente durante la ejecucin, es decir, crear y destruir variables dinmicamente. Esto re-
suelve los dos problemas que plantebamos anteriormente sobre las estructuras estticas: slo
solicitaremos el espacio imprescindible para los datos que en cada momento necesitemos repre-
sentar.
Veamos con ms detalle cmo se declaran las variables de tipo puntero y cmo es posible ubi-
car y anular las variables a las que apuntan.
Declaracin de punteros
Para cualquier tipo t admitimos que puede formarse un nuevo tipo
puntero a t
Diremos que los valores de este tipo son punteros a variables de tipo t.
Las variables de tipo puntero se declaran como cualquier otra variable:
var
p:punteroat;
El identificador de la variable a la que apunta p es p^.
Una vez creada, p^ se comporta a todos los efectos como una variable de tipo t.
Por ejemplo:

tipo
PunteroEnt=punteroaEnt;
var
p:PunteroEnt;

p^ se comporta como una variable capaz de contener valores enteros.


p
p^
5
A nivel de implementacin, se tiene que:
Tipos abstractos de datos 308
un puntero p se realiza como un nmero natural que es interpretado como una direccin
de memoria
la variable p^ apuntada por p se ubica en un rea de memoria suficientemente amplia
(cunta, depende del tipo t) que comienza en la direccin apuntada por p.
Operaciones bsicas con punteros
Las dos operaciones bsicas con los punteros son ubicar la variable a la que apuntan y anular
dicha variable, liberando el espacio que ocupa, asignar un puntero a otro y comparar el valor de
dos punteros. Insistimos en que la variable a la que apunta un puntero no est disponible hasta
que no se ha ubicado explcitamente a travs de dicho puntero.
La ubicacin del puntero se hace con el siguiente procedimiento predefinido:
procubicar(sp:punteroat)
Este es un procedimiento sobrecargado porque admite como parmetro cualquier tipo de
puntero.
El efecto de ubicar es:
Crear una variable de tipo t
Almacenar en p la direccin del espacio de memoria asignado a dicha variable.
Ntese que el parmetro p es exclusivamente de salida. No se tiene en cuenta si el puntero ya
apunta a una variable o no. Si se invocase varias veces sucesivas al procedimiento ubicar sobre el
mismo puntero p, cada vez se reservara un espacio de memoria diferente.
En el uso de los punteros hay que ser muy cuidadoso pues los errores que se producen por su
uso incorrecto suelen ser difciles de detectar. Usando punteros es relativamente fcil dejar col-
gado al computador: accediendo a zonas de la memoria tericamente prohibidas. La primera
advertencia que hacemos es
La variable p^ no puede usarse jams antes de ejecutar
ubicar(p).
Salvo si se ha ejecutado una asignacin
p:=q
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 t.
Tipos abstractos de datos 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 predefini-
do
procliberar(ep:punteroat)

El efecto de liberar es:


destruir la variable apuntada por p, liberando el espacio de memoria que ocupaba
Dos advertencias sobre el uso de liberar:
No se debe liberar una variable que no est ubicada.
No se puede utilizar la variable p^ despus de ejecutar liberar(p)
Es posible tambin realizar asignaciones entre punteros del mismo tipo:
p:=q esvlidosipyqsondelmismotipo

El efecto de una asignacin como esta es que


p pasa a apuntar al mismo sitio al que est apuntado q.
si antes de la asignacin p apuntaba a una variable, despus de la asignacin p^ ya no es
un identificador vlido para dicha variable.
si q^ no est ubicada entonces p^ tampoco lo est
p^ y q^ son dos identificadores de la misma variable.
Advertencia en el uso de la asignacin:
No debe abandonarse nunca la variable apuntada por un puntero sin liberar previamente
el espacio que ocupa, a menos que la variable sea accesible desde otro puntero.
Tambin permitimos realizar comparaciones entre punteros:
p==q p/=q sonexpresionesvlidassipyqsondelmismotipo

Ntese que comparamos direcciones y no los valores de las variables a las que apuntan los
punteros, p^ y q^.
Tipos abstractos de datos 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; error
liberar(p);
liberar(q);error
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 Construccin de estructuras de datos dinmicas
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 es-
tructuras 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
p
3 2 5
Tipos abstractos de datos 311
Como resulta evidente en el anterior ejemplo, para poder construir estructuras dinmicas co-
mo 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

punteroat

para cualquier tipo t, y que representa a un puntero ficticio que no apunta a ninguna variable.
Por lo tanto:
nil^ est indefinido
ubicar(nil), liberar(nil) no tienen sentido
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

Tipos abstractos de datos 312


3.5.3 Implementacin de TADs mediante estructuras de datos dinmicas
Los punteros son una tcnica de programacin de bajo nivel, cuyo uso no slo es bastante en-
gorroso, 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 implementa-
cin de TADs. Esta restriccin permite sacar partido de la potencia de los punteros sin sacrifi-
car 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 meca-
nismo 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 des-
arrollar una implementacin para las pilas.
Ejemplo: implementacin dinmica de las pilas
La representacin grfica de lo que pretendemos implementar:
Los registros se suelen llamar nodos. La representacin de la pila es el puntero p que
seala al nodo cima. Cada nodo contiene un dato e
i
que representa un elemento, y un
puntero que seala al nodo situado inmediatamente debajo de l en la pila.
Tipo representante

tipo
Enlace=punteroaNodo;
Nodo=reg
elem:Elem;
sig:Enlace;
freg;

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

R
Pila[Elem]
(p)

def

p=nilv
(p=nil.ubicado(p).
R
Elem
(p^.elem).R
Pila[Elem]
(p^.sig).
pecadena(p^.sig))
e
n
e
n-1
e
2
e
1

p
Tipos abstractos de datos 313
donde la funcin auxiliar cadena permite especificar que no hay dos punteros apuntando al
mismo nodo:

cadena(nil)=
def
C
cadena(p)=
def
{p}cadena(p^.sig)sip=nil

La idea de este invariante de la representacin es que de p arranca una cadena de punteros fini-
ta, 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 R
Pila[Elem]
(p)

A
Pila[Elem]
(p)=PilaVaca sip=nil
A
Pila[Elem]
(p)=Apilar(A
Elem
(p^.elem),A
Pila[Elem]
(p^.sig))sip=nil

Ntese que esta definicin recursiva termina porque |cadena(p)| decrece.


Implementacin de las operaciones
Podemos considerar dos posibilidades: implementacin como funciones o como procedimien-
tos. Vamos a concentrarnos primero en la implementacin como funciones.

mduloimplPILA[ELEM]

%aqunohacefaltaimportarELEMporqueyaestimportadoenelde
especificacin

privado
tipo
Enlace=punteroaNodo;
Nodo=reg
elem:Elem;
sig:Enlace;
freg;
Pila[Elem]=Enlace;

funcPilaVacadevxs:Pila[Elem];
{P
0
:Cierto}
inicio
xs:=nil
{Q
0
:R(xs).A(xs)=
PILA[ELEM]
PilaVaca}
devxs
ffunc

Tipos abstractos de datos 314


funcApilar(x:Elem;xs:Pila[Elem])devys:Pila[Elem];
{P
0
:R(x).R(xs)}
inicio
ubicar(ys);
ys^.elem:=x;
ys^.sig:=xs;
{Q
0
: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 so-
bre estas cuestiones.

funcdesapilar(xs:Pila[Elem])devys:Pila[Elem];
{P
0
:R(xs).notesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(nosepuededesapilardelapilavaca)
sino
ys:=xs^.sig
fsi
{Q
0
: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;
{P
0
:R(xs).notesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(Lapilavacanotienecima)
sino
x:=xs^.elem
fsi
{Q
0
:R(x).A(x)=
PILA[ELEM]
cima(A(xs))}
devx
ffunc

funcesVaca(xs:Pila[Elem])devr:Bool;
{P
0
:R(xs)}
inicio
r:=xs==nil
Tipos abstractos de datos 315
{Q
0
:resVaca(A(xs))}R
Bool
(r).A
Bool
(r)=
PILA[ELEM]
esVaca(
A
Pila[Elem]
(xs))
devr
ffunc
procerror(eCadena:Vector[1..lmite]deCar);
{P
0
:Cierto}
inicio
escribir(Cadena);
abortar
{Q
0
:Falso}
fproc
fmdulo
En al implementacin de la operacin apilar hemos introducido comparticin de estructura; la
razn para ello ha sido evitar el coste que supone realizar una copia del parmetro de la funcin.
sin embargo la comparticin de estructura nos ha obligado a no anular el elemento que desapila-
mos porque puede estar formando parte de otra pila.
Veamos un ejemplo:
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);
El estado al que se llega es:
e f
d
c
b
a
p4 p3
p2 p1
Qu ocurre si ahora hacemos lo siguiente?

p:=PilaVaca();
Tipos abstractos de datos 316
p1:=PilaVaca();p2:=PilaVaca();
p3:=PilaVaca();p4:=PilaVaca();

Toda la memoria dinmica que ocupaban las pilas ha pasado a estar inaccesible: se ha genera-
do 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]);
{P
0
:R(xs)}
var
p:Enlace;
inicio
itnotesVaca(xs)
p:=xs;
xs:=xs^.sig;
liberar(p)
fit
{Q
0
: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]);
{P
0
:R(xs)}
var
p:Enlace;
inicio
itnotesVaca(xs)
p:=xs;
xs:=xs^.sig;
ELEM.anular(p^.elem);
liberar(p)
fit
{Q
0
: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-
Tipos abstractos de datos 317
tructura esttica. Sin embargo, siguiendo la filosofa de que el usuario no debe conocer los deta-
lles 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 supo-
nemos 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 ante-
rior, se debera sustituir:

p1:=PilaVaca();

por

anular(p1);
p1:=PilaVaca();

tambin es necesario invocar anular sobre las variables locales de los procedimientos justo an-
tes 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 compar-
tiendo estructura con p1.
Por lo tanto, si permitimos la comparticin de estructura no podemos anular las variables, pe-
ro 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 au-
tomtica de basura. Este mecanismo controla cuntas referencias hay a cada espacio de la me-
moria, 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 necesa-
rio 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 progra-
mas; 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 en-
tonces 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 rea-
lizar las copias de los parmetros porque si no los argumentos dejaran de ser representantes vli-
dos 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:
Tipos abstractos de datos 318
p2:=Apilar(a,p1);

p2 y p1 no compartiran estructura. Pero qu ocurre si lo que queremos hacer es algo como


esto?

p1:=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: imple-
mentar 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]);
{P
0
:Cierto}
inicio
xs:=nil
{Q
0
:R(xs).A(xs)=
PILA[ELEM]
PilaVaca}
fproc

procApilar(ex:Elem;esxs:Pila[Elem]);
{P
0
:xs=XS.R(x).R(xs)}
var
p:Enlace;
inicio
ubicar(p);
p^.elem:=x;
p^.sig:=xs;
xs:=p
{Q
0
:R(xs).A(xs)=
PILA[ELEM]
Apilar(A(x),A(XS))}
Fproc

procdesapilar(esxs:Pila[Elem]);
{P
0
:xs=XS.R(xs).notesVaca(A(xs))}
Tipos abstractos de datos 319
var
p:Enlace;
inicio
siesVaca(xs)
entonces
error(nosepuededesapilardelapilavaca)
sino
p:=xs;
xs:=xs^.sig;
%ELEM.anular(p^.elem);
liberar(p)
fsi
{Q
0
: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 debe-
mos informar del hecho y dejar en manos del cliente la decisin de si desea realizar copias o no.
3.5.4 Problemas del uso de la memoria dinmica en la implementacin de
TADs
Existen algunos inconvenientes generales en el uso de memoria dinmica:
Agotamiento inesperado de la memoria. Evidentemente la memoria dinmica no es ilimi-
tada. 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
La gestin de la memoria dinmica puede aumentar el coste de algunos algoritmos
tiempo empleado en ubicacin y anulacin de las estructuras. Suponiendo que la crea-
Tipos abstractos de datos 320
cin y destruccin de estructuras dinmicas lleva ms tiempo que la creacin y destruc-
cin de estructuras estticas, de la que se encarga automticamente el compilador.
Existen adems problemas especficos de la implementacin de TADs. Lo ideal es poder re-
solver 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 in-
necesariamente 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 par-
tir de los datos ledos del archivo, solicitando nueva memoria dinmica donde almacenar los da-
tos 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 depen-
diendo de si el tipo representante es esttico o dinmico.
En una implementacin esttica la asignacin implica copia
x:=y
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 mo-
dificaciones 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 es-
tructura a travs de una variable afectarn a la estructura puntada por la otra.
Por ejemplo:
var
p,q:Pila[Nat];
Tipos abstractos de datos 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 li-
berado al desapilar de q.
Una solucin a este problema sera imponer la disciplina de que no se pueden realizar asigna-
ciones entre valores de TADs, y que en caso de ser necesarias se exporte en el TAD una opera-
cin 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:t;sy:t)

de forma que se pueda sustituir

x:=y por copiar(y,x)


En el ejemplo de las pilas la operacin de copia se puede implementar de la siguiente forma:
proccopiar(exs:Pila[Elem];sys:Pila[Elem]);
{P
0
: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/=nil
ubicar(q2);
q2^.elem:=p^.elem;%ELEM.copiar(p^.elem,q2^.elem)
q1^.sig:=q2;
p:=p^.sig;
Tipos abstractos de datos 322
q1:=q2
fit;
q1^.sig:=nil
fsi
{Q
0
:R(ys).A(xs)=A(ys)}
fproc
En el caso de estructuras estticas copiar se implementara como una simple asignacin.
Parmetros de entrada
Si alguna de las operaciones de un TAD se realiza como funcin o procedimiento con par-
metro de entrada x : t, y si el tipo representante de t es dinmico, el hecho de que el parmetro
sea de entrada no es suficiente para garantizar que el valor apuntado no se modifica; slo queda
protegido el puntero direccin, pero no la estructura apuntada. Debemos imponernos por tan-
to la disciplina de no modificar en ningn caso el valor de los parmetros de entrada.
Otro problema es que a travs de los parmetros de entrada se puede dar lugar otra vez a
comparticin de estructura. Por ejemplo, as ocurre con la implementacin procedimental que
hemos dado para la operacin Apilar donde aparece la asignacin

p^.elem:=x

si Elem est implementado con una estructura dinmica, esto dara lugar a comparticin de es-
tructura:
var
p:Pila[Nat];
pp:Pila[Pila[Nat]];

PilaVaca(pp);
PilaVaca(p);
Apilar(1,p);
apilar(p,pp); comparticindeestructura
desapilar(p); sehacorrompidopp
Una solucin sera realizar copias de los parmetros de entrada antes de incorporarlos a la es-
tructura. 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.
Tipos abstractos de datos 323
Igualdad
El tipo representante de un tipo abstracto puede no admitir el uso de la comparacin de igual-
dad. Y aunque la admitiese, la identidad entre valores del tipo representante en general no corres-
ponder 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:t)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;
{P
0
:R(xs).R(ys)}
var
p,q:Enlace;
inicio
<p,q>:=<xs,ys>;
r:=cierto;
it(p/=nil)and(q/=nil)andr
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);
{Q
0
:rA(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:
Variables locales a los procedimientos que almacenan referencias a estructuras dinmicas.
Dichas estructuras se convertirn en basura a la salida del procedimiento, ya que, nor-
Tipos abstractos de datos 324
malmente, el compilador recoge automticamente la memoria ocupada por las variables
estticas, pero no as la memoria dinmica.
Variables que se reinicializan al ser utilizadas como parmetro de salida de un procedi-
miento. La estructura a la que apuntaba la variable anteriormente se convierte en basura.
Variables que se reinicializan mediante una asignacin. 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 va-
riable 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 ex-
porte una operacin que libere la estructura dinmica que representa al valor:
procanular(esx:t);
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:
sobre las variables locales antes de terminar cualquier procedimiento
antes de cualquier reinicializacin de una variable
Siempre y cuando la variable anulada sea la nica referencia a la estructura dinmica.
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. De-
cimos 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-
Tipos abstractos de datos 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 compar-
ticin de estructura:
asignaciones entre punteros
operaciones que construyen una estructura dinmica aadindole nodos a otra ya existen-
te.
Los problemas que plantea la comparticin:
Complica la anulacin pues no se deben anular estructuras compartidas
Puede provocar efectos colaterales: las modificaciones de una variable afectan a otras
Se pueden tomas dos posturas ante la comparticin de estructura:
Prohibir la comparticin de estructura. La ventaja es que no se producen efectos colatera-
les 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.
Permitir la comparticin de estructura.
En los lenguajes con recogida automtica de basura no existe el problema de la anu-
lacin. 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 responsa-
ble de su buen uso, realizando las copias y anulaciones que sea necesario.
Tipos abstractos de datos 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
construir nmeros complejos y calcular con ellos.
(b) El TAD para multiconjuntos de nmeros enteros, con el tipo MCjtoEnt y operacio-
nes que permitan: crear un multiconjunto vaco; aadir un nuevo elemento a un mul-
ticonjunto; reconocer si un multiconjunto es vaco; determinar el elemento mnimo
de un multiconjunto no vaco; y finalmente, quitar una copia del elemento mnimo de
un multiconjunto no vaco.
(c) TAD para trabajar con fechas, disponiendo de un tipo Fecha y operaciones que per-
mitan: crear una fecha; determinar el da del mes, el da de la semana, el mes y el ao
de una fecha dada; calcular la distancia en das entre dos fechas dadas; sumar un
entero a una fecha dada; y calcular la primera fecha correspondiente a un da de la
semana, mes y ao dados.
(d) TAD para polinomios en una indeterminada con coeficientes enteros, con un tipo
Poli y operaciones que permitan: crear el polinomio nulo; aadir un nuevo monomio
a un polinomio; sumar y multiplicar polinomios; evaluar un polinomio para un valor
entero dado de la indeterminada; calcular el coeficiente asociado a un exponente da-
do en un polinomio dado; y reconocer si un polinomio dado es nulo.
132. La funcin mayores que se especifica a continuacin resuelve el problema de calcular los k
mayores elementos de un vector de enteros dado (con posibles repeticiones). Implemntala
por medio de un algoritmo iterativo, suponiendo disponible y utilizable el TAD MCjtoEnt
del ejercicio 131(b). Observa que no necesitas saber cul es la representacin concreta de
los multiconjuntos.
funcmayores(k:Nat;v:Vector[1..N]deEnt)devm:MCjtoEnt;
{P
0
:1kN.N1}
{Q
0
:mcontieneloskelementosmayoresdev[1..N]}
ffunc
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, vecto-
res, etc.). Influye la eleccin de la representacin en la eficiencia de las operaciones del
TAD? De qu manera?
Especificacin algebraica de TADs
134. Especifica algebraicamente un TAD BOOL que ofrezca el tipo Bool de los valores boo-
leanos, junto con las operaciones constantes Cierto, Falso y las operaciones booleanas not,
(and) y (or)
Tipos abstractos de datos 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-
to con algunas operaciones bsicas.
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 ejer-
cicio 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 t, Dos trminos t,t:t se llaman T-equivalentes (en
smbolos t=
T
t), 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,
clasifica las operaciones en tres clases: generadoras, modificadoras y observadoras.
141. Dado un TAD T con tipo principal t, se llaman trminos generados a aquellos trminos de
tipo t que se pueden construir usando nicamente operaciones generadoras de T (y trmi-
nos 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 t. 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 t debe existir un trmino cerrado y generado t de tipo t que sea T-equivalente a t.
Demuestra que los conjunto de generadoras de los TADs BOOL y NAT son suficientemente
completos.
Importante: La especificacin de cualquier TAD siempre debe construirse de manera que el
conjunto de operaciones generadoras elegido sea suficientemente completo.
143. Usa razonamiento inductivo en NAT para demostrar la validez de las ecuaciones siguientes:
(a) Cero+y=y

(b) Suc(x)+y=Suc(x+y)

(c) x+y=y+x

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:
Tipos abstractos de datos 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). In-
terpreta 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 t 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=
T
t.
(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=
T
t, entonces ya se tena t=
S
t.

(a) Demuestra que el siguiente TAD que usa a BOOL no protege a BOOL, porque in-
troduce basura y confusin en BOOL:

tadPERSONA
usa
BOOL
tipo
Persona
operaciones
Carmen,Roberto,Luca,Pablo:Persona /*
gen*/
feliz:PersonaBool /*
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 ante-
riormente, 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.
Tipos abstractos de datos 329
148. Sea T un TAD con tipo principal t. Se dice que las operaciones generadoras de T son no
libres si existen trminos generados no idnticos t y t de tipo t, que sean T-equivalentes.
Por el contrario, se dice que las operaciones generadoras de T son libres si no es posible en-
contrar trminos generados no idnticos t, t de tipo t que sean T-equivalentes.
(a) Demuestra que las generadoras del TAD ENT no son libres.
(b) Demuestra que las generadoras de los TADs BOOL y NAT son libres.
149. Sea T un TAD con tipo principal t. Se llama sistema de representantes cannicos a cualquier
subconjunto TC
t
del conjunto TG
t
de todos los trminos generados y cerrados de tipo t,
tal que para cada t e TG
t
exista un nico t e TC
t
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
las operaciones de comparacin (==), (/=), (), (), (<) y (>).
Pista: Es necesario utilizar una operacin privada que reconozca los enteros no negativos. Esta
operacin noNeg: Ent Bool se tiene que especificar usando ecuaciones condicionales.
151. Especifica algebraicamente un TAD POLI que ofrezca el tipo Poli de los polinomios en
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 siguien-
tes 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-
ciones de igualdad y desigualdad (==) Y (/=).
(c) ORD: Clase formada por todos los TADs de la clase EQ que posean adems opera-
ciones de orden (), (), (<) y (>).
153. Los TADs que pertenecen a una cierta clase de tipos se llaman miembros o ejemplares de la
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 espe-
cificacin 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 da-
do.
Tipos abstractos de datos 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 ante-
rior, 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 opera-
ciones 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 cua-
lesquiera 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
algebraica de un TAD genrico PAREJA[A, B :: ANY] que ofrezca el tipo Pareja[A.Elem,
B.Elem], junto con operaciones adecuadas para construir parejas y tener acceso a las dos
componentes de cada pareja.
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.
TADs con operaciones parciales
159. Especifica algebraicamente un TAD genrico PILA[E :: ANY] que ofrezca el tipo Pi-
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 especifi-
cacin 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 es-
cribe 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
hemos estudiado anteriormente. As:
La T-equivalencia (ej. 139) se considera entre trminos definidos.
De entre los trminos generados (ej. 141) interesan los definidos.
Para que el conjunto de generadoras sea suficientemente completo (ej. 142), debe ocu-
rrir que cada trmino cerrado y definido t sea T-equivalente a algn trmino cerrado
generado y definido t.
Tipos abstractos de datos 331
Un sistema de representantes cannicos (ej. 149) debe elegirse como un subconjunto
del conjunto de todos los trminos cerrados generados y definidos, de manera que ca-
da trmino cerrado y definido sea T-equivalente a un nico representante cannico.

Como aplicacin de estas ideas:


(a) Comprueba que las generadoras de PILA[NAT] son suficientemente completas.
(b) Comprueba que las siguientes ecuaciones son equivalencias vlidas en PILA[NAT]:
(b.1)cima(desapilar(Apilar(Cero,Apilar(Suc(Cero),PilaVaca))))
=
Suc(Cero)
(b.2)Apilar(Suc(Cero),desapilar(Apilar(Cero,
Apilar(Suc(Cero),PilaVaca))))
=
Apilar(Suc(Cero),Apilar(Suc(Cero),PilaVaca))
162. Para trabajar con TADs que tengan operaciones parciales, conviene distinguir tres moda-
lidades de igualdad entre trminos:
(a) Igualdad existencial =
e
t =
e
t
def
t y t estn ambos definidos y valen lo mismo
(nosotros escribiremos simplemente = para la igualdad existencial)
(b) Igualdad dbil =
d
t =
d
t
def
t y t valen lo mismo si estn los dos definidos
(la igualdad dbil siempre se cumple si t, t o ambos estn indefinidos)
(c) Igualdad fuerte =
f
t =
f
t
def
o bien t y t estn ambos definidos y valen lo mismo
o bien t y t estn ambos indefinidos
Demuestra que las igualdades dbiles y fuertes siempre se pueden especificar usando ecuacio-
nes condicionales con igualdad existencial.
OJO: En cualquier especificacin que use ecuaciones condicionales, las ecuaciones de las condi-
ciones deben usar igualdad existencial.
163. Enriquece el TAD de las pilas aadiendo dos nuevas operaciones, especificadas infor-
malmente como sigue:
fondo: Pila[Elem] Elem
Observadora. Obtiene el elemento del fondo de una pila no vaca.
inversa: Pila[Elem] Pila[Elem]
Modificadora. Invierte el orden en el que estn apilados los elementos.
Construye ecuaciones adecuadas para especificar algebraicamente las nuevas operaciones.
Como ayuda para la especificacin de inversa, especifica una operacin privada ms general:
apilarInversa: (Pila[Elem], Pila[Elem]) Pila[Elem]
Modificadora. Toma los elementos de una pila y los apila en orden inverso sobre una
segunda pila.
Tipos abstractos de datos 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.
Implementacin correcta de TADs
165. Disea una representacin del TAD PILA[NAT] basada en vectores. Define el tipo repre-
sentante, el invariante de la representacin y la funcin de abstraccin.
166. Plantea una implementacin de las operaciones del TAD PILA[NAT] usando la represen-
tacin establecida en el ejercicio anterior. Formula la especificacin Pre/Post adecuada para
el procedimiento o funcin que realice cada operacin.
167. Disea tres posibles representaciones para el TAD CJTO[NAT], formulando en cada
caso el tipo representante, el invariante de la representacin y la funcin de abstraccin.
(a) Usando vectores de naturales.
(b) Usando vectores de naturales sin repeticiones.
(c) Usando vectores de naturales sin repeticiones y ordenados.
168. Plantea implementaciones de la operacin Pon usando las tres representaciones del ejerci-
cio anterior. Compara.
169. Disea implementaciones de la operacin quita usando las tres representaciones del ejerci-
cio anterior. Compara.
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 exi-
giendo que el vector est ordenado por exponentes.
En cada caso, formula el invariante de la representacin, la funcin de abstraccin, y las espe-
cificaciones 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:
(a) Construccin del mdulo de especificacin (o interfaz).
(b) Construccin del mdulo de implementacin.
Observa que el mdulo de implementacin oculta el tipo representante y la implementacin de
las operaciones, de manera que los clientes del mdulo no tienen acceso a ellas.
Tipos abstractos de datos 333
172. Plantea una implementacin modular de un ejemplar PILA[ELEM] del TAD genrico
PILA[E :: ANY] del ejercicio 159, en dos fases:
(a) Construccin del mdulo de especificacin, incluyendo una clusula de importacin para
importar el tipo elem de otro mdulo ELEM que se supone disponible. La idea es que
ELEM implementa un parmetro actual para PILA.
(b) Construccin del mdulo de implementacin, ocultando el tipo representante, la im-
plementacin de las operaciones y los procedimientos de tratamiento de errores.
173. Plantea implementaciones modulares para otros TADs ya estudiados, tales como POLI,
ejemplares de CJTO[E :: EQ], y ejemplares de MCJTO-MIN[E :: ORD].
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-
te a la representacin grfica que sigue:
3 2 5

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 so-
lo registro.
Tipos de datos con estructura lineal 334
CAPTULO 4
TIPOS DE DATOS CON ESTRUCTURA LINEAL
4.1 Pilas
En el tema dedicado a la implementacin de TADs ya hemos presentado su especificacin as
como un par de implementaciones: esttica y dinmica. Por eso en este captulo nos limitaremos
a presentar la tcnica de anlisis de la complejidad de TADs conocida como anlisis amortizado,
ejemplificada sobre las pilas, y los algoritmos para convertir funciones recursivas lineales no fina-
les en funciones iterativas, con ayuda de una pila.
4.1.1 Anlisis de la complejidad de implementaciones de TADs
El anlisis de la complejidad de las implementaciones de TADs se basa en los conceptos gene-
rales 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 anali-
zar 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 con-
centraremos 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 requisi-
tos de espacio diferentes. Sin embargo lo ms habitual es que las distintas representaciones co-
rrespondan 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:
f
1
,f
2
,,f
r

Tipos de datos con estructura lineal 335


Sean
T
1
,T
2
,,T
r

las funciones que miden la complejidad en tiempo, en el caso peor, de los algoritmos que im-
plementan las r operaciones.
Queremos estimar el coste de realizar m llamadas a operaciones del TAD, siendo:

t
i
tiempoconsumidoporlaisimallamada
j
i
ndicedelaoperacindelaisimallamada(1sj
i
sr)
n
i1
,n
i
tamaodelosdatosantesydespusdelaisimallamada
n max:1sism:n
i

m
j
nmerodellamadasaf
j
(1sjsr)

Entonces son correctas las siguientes estimaciones:

_
=
m
i 1
t
i

_
=
m
i 1
T
ji
(n
i1
)
s(silasT
j
sonmontonas)

_
=
m
i 1
T
ji
(n)
=
m
1
T
1
(n)++m
r
T
r
(n)
Ntese que este anlisis estndar se puede realizar sin conocer los detalles de la implementa-
cin, basta con que sean conocidas las funciones T
j
(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 pesimis-
ta, bsicamente debido a la simplificacin que supone sustituir los diferentes tamaos n
i1
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 sustitu-
ya por

desapilarK:(Nat,Pila[Elem])Pila[Elem]

que es capaz de desapilar k elementos de golpe, mediante una llamada desapilarK( k, p ).


Tipos de datos con estructura lineal 336
En una implementacin tpica de este TAD, la complejidad de las operaciones en el caso peor
sera como sigue:
f
j
T
j
(n)
PilaVaca O(1)
Apilar O(1)
desapilarK O(n)
esVaca O(1)
cima 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 m
1
llamadas a Apilar y m
2
llamadas a desapilarK, el anlisis
estndar nos dara como estimacin de la complejidad:

O(m
1
+m
2
n)
Esta estimacin se puede mejorar bastante si suponemos que la pila est inicialmente vaca
(n
0
=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(m
1
)

es tambin una cota superior vlida en realidad sera O(2 m


1
).
Utilizando la tcnica del anlisis amortizado podemos llegar formalmente a este resultado. Esta
tcnica puede dar cotas superiores ms ajustadas que el anlisis estndar. La idea consiste en sus-
tituir los tiempos por tiempos amortizados definidos de la siguiente forma:
tiempoamortizado=
def
tiempo+Apotencial
a
i
=t
i
+p
i
p
i1

siendo
a
i
tiempoamortizadodelaisimallamada
t
i
tiempoconsumidoporlaisimallamada
p
i
potencialdelosdatosdespusdelaisimallamada
p
i1
potencialdelosdatosantesdelaisimallamada
Tipos de datos con estructura lineal 337
El potencial de una estructura de datos X se calcula como una funcin p(X) > 0 que debe de-
finirse 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 oca-
siones el potencial viene dado por el tamao de la estructura, en cuyo caso
p
i
=n
i

Con esta definicin podemos realizar la siguiente estimacin para una sucesin de m llamadas:

_
=
m
i 1
t
i

_
=
m
i 1
(a
i
+p
i1
p
i
)
=
(
_
=
m
i 1
a
i
)+(p
0
p
m
)
s(suponiendop
0
sp
m
)

_
=
m
i 1
a
i

Para estimar una cota superior de los tiempos amortizados, definimos el tiempo amortizado en el
caso peor para datos de tamao n A
j
(n)

A
j
(n)=
def
Max{a
j
(X)|tamaodeX=n} (1sjsr)

siendo

a
j
(X)=
def
t
j
(X)+p(f
j
(X))p(X) (1sjsr)

siendo

t
j
(X)=
def
tiempodeejecucindef
j
(X) (1sjsr)

y donde estamos suponiendo que si X : t entonces f


j
: t t, en realidad p(f
j
(X)) denota
al potencial de la estructura que representa al valor del tipo principal del TAD, despus de
realizar la operacin f
j
.

En muchos casos se verifica que

A
j
(n)=T
j
(n)+Ap

Tipos de datos con estructura lineal 338


Una vez definido el tiempo amortizado en el caso peor para datos de tamao n podemos se-
guir con el anlisis del coste de las m llamadas

_
=
m
i 1
a
i

_
=
m
i 1
A
ji
(n
i1
)
s(silasA
j
sonmontonas)

_
=
m
i 1
A
ji
(n)
=
m
1
A
1
(n)++m
r
A
r
(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:
Ap = An
f
j
T
j
(n)
A
j
(n) = T
j
(n) + An
PilaVaca O(1) O(2)
Apilar O(1) O(2)
desapilarK O(n) O(0)
esVaca O(1) O(1)
cima O(1) O(1)
Ntese que la complejidad amortizada de desapilarK es O(0) porque el coste de la operacin se
compensa con el incremento negativo del tamao:

A
desapilarK
(n)=Max{k+(nk)n|0sksn}=0

Volviendo al ejemplo de las m


1
llamadas a Apilar y m
2
llamadas a desapilarK, tenemos que el re-
sultado 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(m
1
2+m
2
0)=O(m
1
)

como habamos deducido razonando informalmente.


Tipos de datos con estructura lineal 339
4.1.2 Eliminacin de la recursin lineal no final
El esquema general de definicin de una funcin recursiva lineal segn aparece en el ejercicio
186:

funcf(x:T)devy:S;
{P
0
: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
{Q
0
:Q(x,y)}
devy
ffunc

Intuitivamente, la ejecucin de una llamada recursiva y


&
:= f( x
&
) consiste en un bucle des-
cendente seguido de un bucle ascendente:
x
&
f( x
&
) c( x
&
,.) y
&

!
f(s( x
&
)) c(s( x
&
),.)
!
f(s
2
( x
&
)) c(s
2
( x
&
),.)
!

f(s
n1
( x
&
)) c(s
n1
( x
&
),.)
!
f(s
n
( x
&
)) r(s
n
( x
&
))
Tipos de datos con estructura lineal 340
El bucle descendente reitera el clculo de parmetros para nuevas llamadas recursivas, mien-
tras 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 des-
composiciones recursivas hasta llegar al caso base, y en el segundo se aplica sucesivamente la
funcin de combinacin. Vemos que los datos s
ni
( 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 s
ni
( 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 s
1
. En este caso, los datos s
ni
( x
&
) pueden calcularse sobre la marcha, usando s
1
.
Caso general. La funcin s no posee inversa o, aunque la posea, sta no es calculable, o
su clculo resulta demasiado costoso. En este caso, los datos s
ni
( x
&
) se irn almacenando
en una pila en el curso del bucle descendente y se irn recuperando de sta en el curso del
bucle ascendente.
Combinacin de los casos especial y general. No es necesario apilar toda la informacin
correspondiente a s
ni
( x
&
). Para cada parmetro real s
ni
( x
&
) es suficiente con apilar un
dato ms simple u
&
ni
, elegido de tal forma que sea fcil calcular s
ni
( x
&
) a partir de u
&
ni

dato obtenido de la pila y s
ni+1
( x
&
) obtenido en la iteracin anterior.
Caso especial
El esquema de la transformacin es el que se muestra en el ejercicio 186:

funcf
it
(x:T)devy:S;
{P
0
:P(x)}
var
x:T;
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
{Inv.I:R(x,x);Cota:t(x)}
itd(x)
{I.d(x)}
x:=s(x)
{I}
fit;
{I.d(x)}
y:=r(x);
{Inv.J:R(x,x).y=f(x);Cota:m(x,x)}
itx/=x
{J.x=x}
x:=s
1
(x);
Tipos de datos con estructura lineal 341
y:=c(x,y)
{J}
fit
{J.x=x}
{y=f(x)}
{Q
0
:Q(x,y)}
devy
ffunc
Donde el aserto R(x, x) que aparece en los dos invariantes representa:

R(x,x)

def

-n:Nat:(x=s
n
(x).P(x).i:0si<n:(P(s
i
(x)).
d(s
i
(x))))

que expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, es de-
cir, 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.

y la cota del bucle ascendente, queda

m(x,x)
=
def

minn:Nat:x=s
n
(x)

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 s
1
.
Como ejemplo de la aplicacin de este mtodo veamos cmo podemos transformar la versin
recursiva no final de la funcin factorial:
s(n)=n1 s
1
(n)=n+1

funcfact(n:Nat)devr:Nat;
{P
0
:cierto}
var
n:Nat;
inicio
n:=n;
{I:R(n,n);C:n}
itn/=0
n:=n1
fit;
Tipos de datos con estructura lineal 342
r:=0;
{J:R(n,n).r=n!;D:nn}
itn/=n
n:=n+1;
r:=r*n
fit
{Q
0
: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:

funcf
it
(x:T)devy:S;
{P
0
: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)
{I.d(x)}
Apilar(x,xs);
x:=s(x)
{I}
fit;
{I.d(x)}
y:=r(x);
{Inv.J:R(xs,x,x).y=f(x);Cota:tamao(xs)}
itNOTesVaca(xs)
{J.esVaca(xs)}
x:=cima(xs);
y:=c(x,y);
desapilar(xs)
{J}
fit
{J.esVaca(xs)}
{y=f(x)}
{Q
0
:Q(x,y)}
Tipos de datos con estructura lineal 343
devy
ffunc
donde, la condicin R que aparece en ambos invariantes

R(xs,x,x)

def

-n:Nat:(x=s
n
(x).P(x).
i:0si<n:(P(s
i
(x)).d(s
i
(x)).s
i
(x)=elem(i,
xs)))
donde elem(i, xs) indica el elemento que ocupa el lugar i en la pila xs, contando el ele-
mento del fondo como elem(0, xs)
Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursi-
vas, 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 decrementan-
do en 1 con cada iteracin
tamao(xs)
=
def

nmerodeelementosapiladosenxs
Idea: expresa el nmero de llamadas recursivas necesarias para alcanzar x desde x
Como ejemplo, veamos cmo se aplica este esquema a la funcin bin que obtiene la represen-
tacin binaria de un nmero decimal:
funcbin(n:Nat)devr:Nat;
{P
0
:n=N}
inicio
si
n<2r:=n
n2r:=10*bin(ndiv2)+(nmod2)
fsi
{Q
0
:n=N.r=i:Nat:((ndiv2
i
)mod2)*10
i
}
devr
ffunc

La transformacin a iterativo:

funcbin(n:Nat)devr:Nat;
{P
0
:n=N}
var
n:Nat;
ns:Pila[Nat];
inicio
n:=n;
Tipos de datos con estructura lineal 344
PilaVaca(ns);
{I:R(ns,n,n);C:n}
itn>2
Apilar(n,ns);
n:=ndiv2 %n:=s(n)
fit;
r:=n;
{J:R(ns,n,n).r=i:Nat:((ndiv2
i
)mod2)*10
i
;D:
tamao(ns)}
itNOTesVaca(ns)
n:=cima(ns);
r:=10*r+nmod2; %r:=c(n,r)
desapilar(ns)
fit
{Q
0
:n=N.r=i:Nat:((ndiv2
i
)mod2)*10
i
}
devr
ffunc
Combinacin de los casos especial y general
Como indicamos anteriormente estamos en este caso cuando slo es necesario apilar una parte
de la informacin que contienen los parmetros x
&
de forma que luego sea posible reconstruir
dicho parmetro a partir de la informacin apilada y s( x
&
). Formalmente, hemos de encontrar dos
funciones g y h que verifiquen:
d(x)u=g(x)estdefinidoycumplequex=h(u,s(x))
Modificando el esquema del ejercicio 187 segn esta idea, se obtiene la siguiente versin itera-
tiva de f segn se muestra en el ejercicio 189:

funcf
it
(x:T)devy:S;
{P
0
: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)
{I.d(x)}
u:=g(x);
Tipos de datos con estructura lineal 345
Apilar(u,us);
x:=s(x)
{I}
fit;
{I.d(x)}
y:=r(x);
{Inv.J:R(us,x,x).y=f(x);Cota:tamao(us)}
itNOTesVaca(us)
{J.esVaca(us)}
u:=cima(us);
x:=h(u,x);
y:=c(x,y);
desapilar(us)
{J}
fit
{J.esVaca(us)}
{y=f(x)}
{Q
0
:Q(x,y)}
devy
ffunc

Donde el aserto R que aparece en ambos invariantes:


R(us,x,x)

def

-n:Nat:(x=s
n
(x).P(x).
i:0si<n:(P(s
i
(x)).d(s
i
(x)).g(s
i
(x))=
elem(i,us)))
donde elem(i, us) indica el elemento que ocupa el lugar i en la pila us, contando el ele-
mento del fondo como elem(0, us)
Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursi-
vas, 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 elemen-
tos de la pila
tamao(us)
=
def

nmerodeelementosapiladosenus
Idea: expresa el nmero de llamadas recursivas necesarias para alcanzar x desde x
Vamos a aplicar esta tcnica al mismo ejemplo del caso anterior: la funcin bin.
En la funcin bin la descomposicin recursiva es:

Tipos de datos con estructura lineal 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

Con lo que la versin iterativa aplicando este nuevo esquema quedar:

funcbin(n:Nat)devr:Nat;
{P
0
:n=N}
var
n:Nat;
u:Bool;
us:Pila[Nat];
inicio
n:=n;
PilaVaca(us);
{I:R(us,n,n);C:n}
itn>2
u:=par(n); %u:=g(n)
Apilar(u,us);
n:=ndiv2 %n:=s(n)
fit;
r:=n;
{J:R(us,n,n).r=i:Nat:((ndiv2
i
)mod2)*10
i
;D:
tamao(us)}
itNOTesVaca(us)
u:=cima(us);
si
un:=2*n n:=h(u,n)
NOTun:=2*n+1
fsi
r:=10*r+nmod2; %r:=c(n,r)
desapilar(us)
fit
{Q
0
:n=N.r=i:Nat:((ndiv2
i
)mod2)*10
i
}
devr
ffunc
Tipos de datos con estructura lineal 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 pri-
mero en llegar es el primero en salir en ser atendido: FIFO first in first out.
4.2.1 Especificacin
La especificacin segn aparece en la pgina 9 de las hojas de TADs es:

tadCOLA[E::ANY]
usa
BOOL
tipo
Cola[Elem]
operaciones
ColaVaca:Cola[Elem] /*gen*/
Aadir:(Elem,Cola[Elem])Cola[Elem] /*gen*/
avanzar:Cola[Elem]Cola[Elem] /*mod*/
primero:Cola[Elem]Elem /*obs*/
esVaca:Cola[Elem]Bool /*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

Tipos de datos con estructura lineal 348


4.2.2 Implementacin
Implementacin esttica basada en un vector
Tipo representante
Idea grfica:
1 L
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
1sxs.inisxs.fin+1slmite+1.
i:xs.inisisxs.fin:R(xs.espacio(i))
Ntese que el aserto de dominio implica: 1 s xs.ini s lmite+1 . 0 s xs.fin s lmite
Ntese tambin que estamos permitiendo xs.ini = xs.fin+1, con lo que representamos a una
cola vaca, como veremos a continuacin al formalizar la funcin de abstraccin.
Tipos de datos con estructura lineal 349
Funcin de abstraccin
Dada xs : Cola[Elem] tal que R(xs):

A(xs)=
def
hazCola(xs.espacio,xs.ini,xs.fin)

hazCola(e,i,f)=ColaVaca sii=f+1
hazCola(e,i,f)=Aadir(A(e(f)),hazCola(e,i,f1)) siisf
Implementacin procedimental de las operaciones
Debido a los problemas, que ya comentamos en el caso de las pilas, que conlleva la implemen-
tacin funcional, pasamos directamente a la implementacin con procedimientos.
procColaVaca(sxs:Cola[Elem]); % O(1)
{P
0
:Cierto}
inicio
xs.ini:=1;
xs.fin:=0;
{Q
0
:R(xs).A(xs)=
COLA[ELEM]
ColaVaca}
fproc

procAadir(ex:Elem;esxs:Cola[Elem]); % O(1)
{P
0
:xs=XS.R(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
{Q
0
:R(xs).A(xs)=
COLA[ELEM]
Aadir(A(x),A(XS))}
fproc

procavanzar(esxs:Cola[Elem]); % O(1)
{P
0
:xs=XS.R(xs).NOTesVaca(A(xs))%xs.inisxs.fin}
inicio
siesVaca(xs)
entonces
error(Colavaca)

Tipos de datos con estructura lineal 350


sino
%ELEM.anular(xs.espacio(xs.ini))
xs.ini:=xs.ini+1
fsi
{Q
0
:R(xs).A(xs)=
COLA[ELEM]
avanzar(A(XS))}
fproc

funcprimero(xs:Cola[Elem])devx:Elem; % O(1)
{P
0
:R(xs).NOTesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(Colavaca)
sino
x:=xs.espacio(xs.ini)
fsi
{Q
0
:A(x)=
COLA[ELEM]
primero(A(xs))}
devx
ffunc

funcesVaca(xs:Cola[Elem])devr:Bool; % O(1)
{P
0
:R(xs)}
inicio
r:=xs.ini==xs.fin+1
{Q
0
:A(r)=
COLA[ELEM]
esVaca(A(xs))}
devr
ffunc
Desventaja de esta implementacin
El espacio ocupado por la representacin de la cola se va desplazando hacia la derecha al eje-
cutar Aadir y avanzar.
Cuando fin alcanzar el valor lmite no se pueden aadir nuevos elementos, aunque quede sitio
en espacio[1..ini1].
1 L
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 eficien-
cia de la operacin Aadir O(n). Es mejor la solucin descrita a continuacin.
Tipos de datos con estructura lineal 351
Implementacin basada en un vector circular
Esta implementacin est pensada para resolver el problema de la anterior; cuando fin alcanza
el valor lmite y queda espacio libre entre 1 y ini1 se utiliza ese espacio para seguir aadiendo
elementos. Hay que tener cuidado porque ahora puede suceder que ini > fin, y en particular in-
i=fin+1 puede implicar que la cola est llena o vaca.
Tipo representante
Idea grfica:
1
L
ini
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. Es-
te 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 va-
mos a utilizar dos funciones auxiliares pred y suc que restan y suman 1 mdulo lmite. En la im-
plementacin 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:
Tipos de datos con estructura lineal 352
suc,pred:[1..lmite][1..lmite]

i+1 sii<limite
suc(i)=
def

1 sii=lmite

i1 sii>1
pred(i)=
def

lmite sii=1

Necesitamos definir tambin la aplicacin repetida de suc:

dadosie[1..lmite],j>0definimos

i sij=0
suc
j
(i)=
def

suc
j1
(suc(i)) sij>0

Convenimos por ltimo:

suc
1
(i)=
def
pred(i) %esnecesarioparaescribirelinvarianteR
Invariante de la representacin
Dado xs : Cola[Elem]

R(xs)

def

0sxs.tamslmite.
1sxs.inislmite.xs.fin=suc
xs.tam1
(xs.ini).
i:0sisxs.tam1:R(xs.espacio(suc
i
(xs.ini)))
Funcin de abstraccin
Dado xs: Cola[elem] tal que R(xs):

A(xs)=
def
hazCola(xs.esp,xs.tam,xs.fin)

hazCola(e,t,f)=
def
ColaVaca sit=0
Tipos de datos con estructura lineal 353
hazCola(e,t,f)=
def
Aadir(A(e(f)),hazCola(e,t1,pred(f)))sit>0
Implementacin procedimental de las operaciones
En la implementacin de las operaciones suponemos disponible la funcin suc con el compor-
tamiento antes descrito.
procColaVaca(sxs:Cola[Elem]); % O(1)
{P
0
:Cierto}
inicio
xs.ini:=1;
xs.fin:=lmite;
xs.tam:=0;
{Q
0
:R(xs).A(xs)=
COLA[ELEM]
ColaVaca}
fproc
Ntese que con esta inicializacin se cumple la parte del invariante de la representacin:

xs.fin=suc
xs.tam1
(xs.ini)

para lo cual hay que aplicar el convenio

suc
1
(i)=pred(i)

procAadir(ex:Elem;esxs:Cola[Elem]); % O(1)
{P
0
:xs=XS.R(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
{Q
0
:R(xs).A(xs)=
COLA[ELEM]
Aadir(A(x),A(XS))}
fproc

procavanzar(esxs:Cola[Elem]); % O(1)
{P
0
:xs=XS.R(xs).NOTesVaca(A(xs))%xs.tam>0}
inicio
siesVaca(xs)
Tipos de datos con estructura lineal 354
entonces
error(Colavaca)
sino
%ELEM.anular(xs.espacio(xs.ini))
xs.tam:=xs.tam1;
xs.ini:=suc(xs.ini)
fsi
{Q
0
:R(xs).A(xs)=
COLA[ELEM]
avanzar(A(XS))}
fproc

funcprimero(xs:Cola[Elem])devx:Elem; % O(1)
{P
0
:R(xs).NOTesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(Colavaca)
sino
x:=xs.espacio(xs.ini)
fsi
{Q
0
:A(x)=
COLA[ELEM]
primero(A(xs))}
devx
ffunc

funcesVaca(xs:Cola[Elem])devr:Bool; % O(1)
{P
0
:R(xs)}
inicio
r:=xs.tam==0
{Q
0
: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 opera-
ciones de manera eficiente.
Tipo representante
Idea grfica:
Tipos de datos con estructura lineal 355
x
n
x
2
x
1

prim ult
xs
Existe tambin la posibilidad de implementar el nodo cabecera con memoria dinmica, con es-
to conseguiramos que en aquellos lenguajes donde las funciones no pueden devolver tipos es-
tructurados, 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)
def
R
CV
(xs)vR
CNV
(xs)

escribimos por separado las condiciones que debe cumplir y una cola vaca y otra que no lo est.
R
CV
(xs)

def

xs.prim=nil.xs.ult=nil

R
CNV
(xs)

def

xs.prim=nil.xs.ult=nil.
buenaCola(xs.prim,xs.ult)
Tipos de datos con estructura lineal 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=q.ubicado(p).R(p^.elem).p^.sig=nil)v
(p=q.ubicado(p).R(p^.elem).
pecadena(p^.sig).buenaCola(p^.sig,q))

cadena(p)=
def
C sip=nil
cadena(p)=
def
{p}cadena(p^.sig) sip=nil
Funcin de abstraccin
Dada xs : Cola[Elem] tal que R(xs):

A(xs)=
def
ColaVaca siR
CV
(xs)
A(xs)=
def
hazCola(xs.prim,xs.ult) siR
CNV
(xs)

para construir la cola tenemos que aadir elementos hasta que lleguemos a una cola con un so-
lo elemento, donde se cumple p=q

hazCola(p,q)=
def
Aadir(A(p^.elem),ColaVaca) sip=q
hazCola(p,q)=
def
ponerPrimero(A(p^.elem),hazCola(p^.sig,q))sip=q

ponerPrimero(y,ColaVaca)=
def
Aadir(y,colaVaca)
ponerPrimero(y,Aadir(x,xs))=
def
Aadir(x,PonerPrimero(y,xs))

es necesario el predicado auxiliar ponerPrimero porque el orden de recorrido del valor represen-
tante de la cola es el inverso al orden de insercin en la misma.
Implementacin procedimental de las operaciones
procColaVaca(sxs:Cola[Elem]); % O(1)
{P
0
:Cierto}
inicio
xs.prim:=nil;
xs.ult:=nil;
{Q
0
:R(xs).A(xs)=
COLA[ELEM]
ColaVaca}
fproc

Tipos de datos con estructura lineal 357


procAadir(ex:Elem;esxs:Cola[Elem]); % O(1)
{P
0
:xs=XS.R(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
{Q
0
:R(xs).A(xs)=
COLA[ELEM]
Aadir(A(x),A(XS))}
fproc

procavanzar(esxs:Cola[Elem]); % O(1)
{P
0
:xs=XS.R(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
{Q
0
:R(xs).A(xs)=
COLA[ELEM]
avanzar(A(XS))}
fproc

funcprimero(xs:Cola[Elem])devx:Elem; % O(1)
{P
0
:R(xs).NOTesVaca(A(xs))}
inicio
siesVaca(xs)
Tipos de datos con estructura lineal 358
entonces
error(Colavaca)
sino
x:=xs.prim^.elem %comparticindeestructura
fsi
{Q
0
:A(x)=
COLA[ELEM]
primero(A(xs))}
devx
ffunc

funcesVaca(xs:Cola[Elem])devr:Bool; % O(1)
{P
0
:R(xs)}
inicio
r:=xs.prim==nil
{Q
0
:A(r)=
COLA[ELEM]
esVaca(A(xs))}
devr
ffunc
Como cualquier implementacin de un TAD, debemos incluir una operacin que realice co-
pias de los valores representados:
funccopiar(xs:Cola[Elem])devys:Cola[Elem];
{P
0
: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:pecadena(xs.prim).
lospunterosdecadena(ys.prim)apuntanacopiasdelosnodosde
cadena(xs.prim)hastap^inclusive.
q=ltimo(cadena(ys.prim));
C:|cadena(p)|
}
itp^.sig/=nil
p:=p^.sig;
ubicar(r);
q^.sig:=r;
q:=r;
Tipos de datos con estructura lineal 359
q^.elem:=p^.elem;%q^.elem:=ELEM.copiar(p^.elem)
fit;
q^.sig:=nil;
ys.ult:=q
fsi
{Q
0
:R(ys).A(xs)=
COLA[ELEM]
A(ys).
xs,ysapuntanaestructurasquenocompartennodos}
devys
ffunc
4.3 Colas dobles
Son una generalizacin de las colas y las pilas, donde es posible insertar, consultar y eliminar
tanto por el principio como por el final.
4.3.1 Especificacin
La especificacin segn aparece en la pgina 10 de las hojas con los TADs:

tadDCOLA[E::ANY]
usa
BOOL
tipo
DCola[Elem]
operaciones
DColaVaca:DCola[Elem] /*gen*/
PonDetrs:(Elem,DCola[Elem])DCola[Elem] /*
gen*/
ponDelante:(Elem,DCola[Elem])DCola[Elem] /*mod*/
quitaUlt:DCola[Elem]DCola[Elem] /*mod*/
ltimo:DCola[Elem]Elem /*obs*/
quitaPrim:DCola[Elem]DCola[Elem] /*
mod*/
primero:DCola[Elem]Elem /*obs*/
esVaca:DCola[Elem]Bool/*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)
Tipos de datos con estructura lineal 360
ltimo(PonDetrs(x,xs))=x
defquitaPrim(xs)siesVaca(xs)
quitaPrim(PonDetrs(x,xs))=DColaVaca siesVaca(xs)
quitaPrim(PonDetrs(x,xs))=PonDetrs(x,quitaPrim(xs))
siesVaca(xs)
defprimero(xs)siesVaca(xs)
primero(PonDetrs(x,xs))=x 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] DCOLA[ELEM]
ColaVaca DColaVaca
Aadir PonDetrs
avanzar quitaPrim
primero primero
esVaca esVaca
ponDelante
quitaUlt
ltimo
4.3.2 Implementacin
Bsicamente podemos considerar las mismas implementaciones utilizadas para las colas, a ex-
cepcin 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].
Tipos de datos con estructura lineal 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)=
def
hazDCola(xs.esp,xs.tam,xs.fin)

hazDCola(e,t,f)=
def
DColaVaca sit=0
hazDCola(e,t,f)=
def
PonDetrs(A(e(f)),hazDCola(e,t1,pred(f)))sit>
0
Implementacin procedimental de las operaciones
Todas las operaciones consumen tiempo O(1) en el caso peor. Aquellas operaciones que tie-
nen una anloga en COLA[ELEM] se implementan igual; el resto de manera anloga.
procponDelante(ex:Elem;esxs:DCola[Elem]); % O(1)
{P
0
:xs=XS.R(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
{Q
0
:R(xs).A(xs)=
DCOLA[ELEM]
ponDelante(A(x),A(XS))}
fproc
procquitaUlt(esxs:Cola[Elem]); % O(1)
{P
0
:xs=XS.R(xs).NOTesVaca(A(xs))%xs.tam>0}
inicio
siesVaca(xs)
entonces
error(Colavaca)
sino
Tipos de datos con estructura lineal 362
%ELEM.anular(xs.espacio(xs.fin))
xs.tam:=xs.tam1;
xs.fin:=pred(xs.fin)
fsi
{Q
0
:R(xs).A(xs)=
DCOLA[ELEM]
quitaUlt(A(XS))}
fproc
Se queda como ejercicio la implementacin de ltimo.
Implementacin dinmica
Tipo representante, invariante de la representacin y funcin de abstraccin
Es igual que el caso de las colas ordinarias, sustituyendo ColaVaca por DColaVaca y Aadir
por PonDetrs.
Implementacin procedimental de las operaciones
Aquellas operaciones que tienen una anloga en COLA[ELEM] se implementan igual, con un
tiempo de ejecucin en el caso pero de O(1).
Se puede proponer como ejercicio la implementacin de:

procponDelante(ex:Elem;esxs:DCola[Elem]);%O(1)
{P
0
:x=XS.R(x).R(xs)}
%algoritmoanlogoaAadirenCOLA[ELEM]
{Q
0
:R(x).A(x)=
DCOLA[ELEM]
ponDelante(A(x),A(XS))}
fproc

procltimo(xs:DCola[Elem])devx:Elem; %O(1)
{P
0
:R(xs).NOTesVaca(A(xs))}
%algoritmoanlogoaprimeroenCOLA[ELEM]
{Q
0
:R(x).A(x)=
DCOLA[ELEM]
ltimo(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)
{P
0
:xs=XS.R(xs).NOTesVaca(A(xs))}
var
p:Enlace;
inicio
siesVaca(xs)
entonces
error(Colavaca)
Tipos de datos con estructura lineal 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.ult
p:=p^.sig
fit;
p^.sig:=nil;
ELEM.anular(xs.ult^.elem);
liberar(xs.ult);
xs.ult:=p
fsi
fsi
{Q
0
:R(xs).A(xs)=
DCOLA[ELEM]
quitaUlt(A(XS))}
fproc
Para conseguir que tambin esta operacin tenga complejidad O(1) se puede utilizar la siguien-
te representacin.
Implementacin dinmica con encadenamiento doble
Tipo representante

prim ult
xs
x
2
x
1
x
n-1
x
n
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;
Tipos de datos con estructura lineal 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
Como se recoge en la pgina 11 de las especificaciones de TADs

tadLISTA[E::ANY]
usa
BOOL,NAT
tipo
Lista[Elem]
operaciones

A la derecha aparecen notaciones habituales, que usaremos indistintamente con el nombre de


las operaciones
Nula:Lista[Elem] /*gen*/ %[]
Cons:(Elem,Lista[Elem])Lista[Elem] /*gen*/ %
[x/xs]
[_]:ElemLista[Elem] /*mod*/ %[x]
ponDr:(Lista[Elem],Elem)Lista[Elem] /*mod*/ %
[xs\x]
primero:Lista[Elem]Elem /*obs*/

El resultado de quitarle el primer elemento a la lista


resto:Lista[Elem]Lista[Elem] /*mod*/
ltimo:Lista[Elem]Elem /*obs*/

El resultado de quitarle el ltimo elemento a la lista


inicio:Lista[Elem]Lista[Elem] /*mod*/
(++):(Lista[Elem],Lista[Elem])Lista[Elem] /*mod*/
nula?:Lista[Elem]Bool /*obs*/
(#):Lista[Elem]Nat /*obs*/
(!!):(Lista[Elem],Nat)Elem /*obs*/

Tipos de datos con estructura lineal 365


Las generadoras son libres
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)sns(#xs)
Cons(x,xs)!!Suc(Cero) =x
Cons(x,Cons(y,ys))!!Suc(Suc(n))=
f
Cons(y,ys)!!Suc(n)
errores
primero(Nula)
resto(Nula)
ltimo(Nula)
inicio(Nula)
xs!!nsi(n==Cero)OR(n>(#xs))
ftad
Notacioneshabitualespararepresentarlistas(siendox
i
:Elem)
[x
1
,,x
n
]
[x
1
,,x
n
/xs]
[xs\x
1
,,x
n
]
Tipos de datos con estructura lineal 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 operacio-
nes:
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
Normalmente se utiliza siempre la implementacin dinmica.
Representacin esttica
Podra basarse en un vector o un vector circular como hemos visto para los TAD
COLA[ELEM] y DCOLA[ELEM]. Esta representacin permitira una implementacin procedi-
mental con tiempo O(1) para todas las operaciones de LISTA que tienen una anloga en
DCOLA. Tambin sera fcil implementar:
[ _ ] como proc en tiempo O(1)
(# ) como func en tiempo O(1)
( !! ) como func en tiempo O(1)
Sin embargo se plantean inconvenientes con esta representacin:
El uso de la operacin ( ++ ) tiende a desbordar fcilmente los lmites de espacio de una
representacin esttica.
En muchas aplicaciones de las listas es ms natural usar ( ++ ) y otras operaciones como
funciones en algoritmos recursivos, sobre todo si se viene de la cultura funcional. Una
implementacin funcional con representacin esttica malgasta muchos espacio (por
qu?)
En cualquier caso es muy sencillo llevar a cabo esta implementacin.
Tipos de datos con estructura lineal 367
Representacin dinmica como la utilizada para las pilas
La idea grfica:
x
n
x
2
x
1
xs
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 im-
plementacin 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
x
n
x
2
x
1

prim ult
xs
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 ubica-
dos y los elementos son representantes vlidos.
Tipos de datos con estructura lineal 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)=
def
Nula siR
LV
(xs)
A(xs)=
def
hazLista(xs.prim,xs.ult) siR
LNV
(xs)
para construir la lista tenemos que aadir elementos hasta que lleguemos a una lista con un so-
lo elemento, donde se cumple p=q
hazLista(p,q)=
def
Cons(A(p^.elem),Nula) sip=q
hazLista(p,q)=
def
Cons(A(p^.elem),hazLista(p^.sig,q)) sip=q
Implementacin procedimental de las operaciones
Todas las operaciones de LISTA que poseen una anloga en DCOLA se pueden implementar
con el mismo algoritmo ya estudiado en DCOLA. El tiempo de ejecucin es O(1) en todos los
casos, excepto para inicio (anloga a quitaUlt) que necesita tiempo O(n) este tiempo podra des-
cender a O(1) usando una representacin doblemente enlazada.
Excepto la operacin ( ++ ) el resto de operaciones que no tienen anloga en DCOLA las im-
plementaremos como funciones, y es por eso que nos ocuparemos de ellas en el siguiente aparta-
do.
Para ( ++ ) se puede plantear una implementacin procedimental que modifica su primer ar-
gumento y consume tiempo O(1):
x
n
x
2
x
1

prim ult
xs
y
n
y
2
y
1
prim ult
ys
Tipos de datos con estructura lineal 369
x
n
x
2
x
1

prim ult
xs
y
n
y
2
y
1
prim ult
ys
procconc(esxs:Lista[Elem];eys:Lista[Elem])
{P
0
:R(xs).R(ys).xs=XS.ys=YS}
inicio
sinula?(xs)
entonces
xs:=ys

sino
sinula?(ys)
entonces
seguir
sino
xs.ult^.sig:=ys.prim;
xs.ult:=ys.ult
fsi
fsi
{Q
0
:R(xs).R(ys).A(xs)=
LISTA[ELEM]
A(XS)++A(ys)}
fproc
Esta implementacin da lugar a comparticin de estructura, por lo que:
o las operaciones que eliminan elementos de una lista no los anulan
o la lista usada como parmetro de entrada en la llamada no se vuelve a utilizar despus
de sta.
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 anula-
cin, es muy similar a la que implementamos para las pilas:
procanula(esLista:Lista[Elem]);
{P
0
:R(xs).xs=XS}
Tipos de datos con estructura lineal 370
var
p,q:Enlace;
inicio
sinula?(xs)
entonces
seguir
sino
p:=xs.prim;
itp/=nil
q:=p^.sig;
ELEM.anular(p^.elem); %slosisehaceanulacinprofunda
liberar(p);
p:=q
fit;
xs.prim:=nil;
xs.ult:=nil
fsi
{Q
0
:R(xs).A(xs)=
LISTA[ELEM]
Nula.losnodosaccesiblesdesdeXS
atravsdepunterosdetipoEnlace,sehanliberado}
fproc
Implementacin funcional de las operaciones
Las operaciones observadoras de LISTA que tienen una anloga en DCOLA se pueden im-
plementar con el mismo algoritmo ya estudiado en DCOLA, con tiempo de ejecucin O(1).
A continuacin, estudiamos las operaciones generadoras y modificadoras, as como las obser-
vadoras sin anloga en DCOLA. Vamos a permitir la compartir de estructura.
funcNula()devxs:Lista[Elem]; /*O(1)*/
{P
0
:cierto}
inicio
xs.prim:=nil;
xs.ult:=nil
{Q
0
:R(xs).A(xs)=
LISTA[ELEM]
Nula}
devxs
ffunc

funcCons(x:Elem;xs:Lista[Elem])devys:Lista[Elem];/*O(1)
{P
0
:R(x).R(xs)}
var
nuevo:Enlace;
inicio
ubicar(nuevo);
ys.prim:=nuevo;
nuevo^.elem:=x;%comparticindeestructura
Tipos de datos con estructura lineal 371
sinula?(xs)
entonces
ys.ult:=nuevo;
nuevo^.sig:=nil
sino
ys.ult:=xs.ult;
nuevo^.sig:=xs.prim
fsi
{Q
0
:R(ys).A(ys)=
LISTA[ELEM]
Cons(A(x),A(xs))}
devys
ffunc

x
prim ult
ys
x
n
x
2
x
1
prim ult
xs
funcponDr(xs:Lista[elem];x:Elem)devys:Lista[Elem]/*O(n)por
copia*/
{P
0
: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;
{Q
0
: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 repre-
sentacin de xs el valor nil del campo sig del ltimo nodo.
Tipos de datos con estructura lineal 372
x
n
x
2
x
1

prim ult
xs
x x
2
x
1
prim ult
ys
x
n

funcresto(xs:Lista[Elem])devys:Lista[Elem]/*O(1)*/
{P
0
: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
{Q
0
:R(ys).A(ys)=
LISTA[ELEM]
resto(A(xs))}
devys
ffunc

prim ult
xs
x
n
x
2
prim ult
ys
x
1

El siguiente es el primer ejemplo de operacin que slo implementamos parcialmente; los de-
talles de la funcin privada auxiliar copiaInicio se quedan como ejercicio.

Tipos de datos con estructura lineal 373


funcinicio(xs:Lista[Elem])devys:Lista[Elem]/*O(n)porcopia*/
{P
0
:R(xs).NOTnula?(A(xs))}
inicio
sinula?(xs)
entonces
error(Listavaca)
sino
ys:=copiaInicio(xs)%funcinprivadaO(n)
fsi
{Q
0
:R/ys).A(ys)=
LISTA[ELEM]
inicio(A(xs))}
devys
ffunc

En este caso la copia es necesaria para preservar la representacin correcta de A(xs).


x
n-1
x
2
x
1

prim ult
ys
x
n
x
2
x
1

prim ult
xs
x
n-1
funcconc(xs,ys:Lista[Elem])devzs:Lista[Elem];
%tiempoO(n)debidoalacopiadexs,siendon=#A(xs)
{P
0
:R(xs).R(ys)}
inicio
sinula?(xs)
entonces
zs:=ys
sino
zs:=copia(xs);
xs.ult^.sig:=ys.prim;
zs.ult:=ys.ult
fsi
{Q
0
:R(zs).A(zs)=
LISTA[ELEM]
A(xs)++A(ys)}
devzs
ffunc

La copia es necesaria para preservar la representacin correcta de A(xs).


Tipos de datos con estructura lineal 374
x
n
x
2
x
1

prim ult
xs
y
n
y
2
y
1
prim ult
ys
x
n
x
2
x
1

prim ult
xs
funclongitud(xs:Lista[Elem])devn:Nat;/*O(n)*/
{P
0
:R(xs)}
var
aux:Enlace;
inicio
n:=0;
aux:=xs.prim;
itaux/=nil
n:=n+1;
aux:=aux^.sig
fit
{Q
0
:n=
LISTA[ELEM]
#(A(xs))}
devn
ffunc

El tiempo de ejecucin de longitud se puede reducir a O(1) modificando el tipo representante


Lista[Elem]. La idea es aadir a la cabecera un tercer campo que almacene la longitud.
funcconsultar(xs:Lista[Elem];i:Nat)devx:Elem;/*O(i)*/
{P
0
:R(xs).1sis#(A(xs))}
var
j:Nat;
aux:Enlace;
inicio
j:=i;
aux:=xs.prim;
itj>1andaux/=nil
j:=j1;
Tipos de datos con estructura lineal 375
aux:=aux^.sig
fit;
sij==1andaux/=nil
entonces
x:=aux^.elem %comparticindeestructura
sino
error(Posicininexistente)
fsi
{Q
0
: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 Procedimiento Funcin


Nula O(1) O(1)
Cons O(1) O(1)
[ _ ] O(1) O(1)
ponDr O(1) O(n)
primero O(1)
resto O(1) O(1)
ltimo O(1)
inicio O(n) O(n)
( ++ ) O(1) O(n)
nula? O(1)
(# ) O(n)
( !! ) O(i)
4.4.3 Programacin recursiva con listas
Como se indica en los ejercicios, en los ejemplos que siguen:
Suponemos dada una implementacin funcional del TAD LISTA. Usamos las operacio-
nes pblicas, sin programar con punteros.
Ignoramos los problemas de gestin de memoria: estructura compartida, copia, anula,
Programacin recursiva de #, !! y ++ usando funciones ms bsicas

funclongitud(xs:Lista[Elem])devn:Nat;
{P
0
:R(xs)}
inicio
sinula?(xs)
Tipos de datos con estructura lineal 376
entonces
n:=0
sino
n:=1+longitud(resto(xs))
fsi
{Q
0
:n=
LISTA[ELEM]
#(A(xs))}
devn
ffunc

Complejidad: disminucin por sustraccin de 1, O(n), siendo n = # xs

funcconsultar(xs:Lista[Elem];i:Nat)devx:Elem;/*O(i)*/
{P
0
:R(xs).1sis#(A(xs))}
inicio
si
nula?(xs)ORi==0Error(elementoinexistente)
NOTnula?(xs)ANDi==1x:=primero(xs) %comparticinde
estructura
NOTnula?(xs)ANDi>1x:=consultar(resto(xs),i1)
fsi
{Q
0
:A(x)=
LISTA[ELEM]
A(xs)!!i}
devx
ffunc
Complejidad: disminucin por sustraccin de 1, O(i)
funcconc(xs,ys:Lista[Elem])devzs:Lista[Elem];
{P
0
:R(xs).R(ys)}
inicio
sinula?(xs)
entonces
zs:=ys
sino
zs:=Cons(primero(xs),conc(resto(xs),ys))
fsi
{Q
0
:R(zs).A(zs)=
LISTA[ELEM]
A(xs)++A(ys)}
devzs
ffunc
Complejidad: disminucin por sustraccin de 1, O(n), siendo n = # xs
Las llamadas a Cons van creando nuevos nodos para los elementos de xs. Al final, zs comparte
estructura con ys por la asignacin del caso base, y con los elementos de xs. El efecto es el
mismo que se produca en la implementacin funcional de ( ++ ) con punteros, presentada ms
arriba.
Tipos de datos con estructura lineal 377
Funciones coge y tira
La funcin coge devuelve una lista con los n primeros elementos de xs. Tira devuelve la lista que
resulta de quitar los primeros n elementos de xs. Consideramos el caso de que n sea mayor que el
nmero de elementos de xs.
funccoge(n:Nat;xs:Lista[Elem])devus:Lista[Elem];
{P
0
:cierto}
si
n==0us:=Nula
n>0ANDnula?(xs)us:=Nula
n>0ANDNOTnula?(xs)us:=Cons(primero(xs),coge(n1,resto(xs)
))
fsi
{Q
0
:#us=min(n,#xs).i:1sis#us:us!!i=xs!!i}
devus
ffunc

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 apa-
recer en nuestros asertos.

functira(n:Nat;xs:Lista[Elem])devvs:Lista[Elem];
{P
0
:cierto}
si
n==0vs:=xs
n>0ANDnula?(xs)vs:=Nula
n>0ANDNOTnula?(xs)vs:=tira(n1,resto(xs))
fsi
{Q
0
:#vs=max(0,#xsn).i:1sis#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 > 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]
Que es la idea implementada en el enunciado del ejercicio 212:

Tipos de datos con estructura lineal 378


funcinv(xs:Lista[Elem])devys:Lista[Elem];
{P
0
:cierto}
inicio
sinula?(xs)
entoncesys:=Nula
sinoys:=inv(resto(xs))++[primero(xs)]
fsi
{Q
0
::1sis(#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 nom-
bre. En realidad, habra que reescribirla utilizando la notacin habitual algunos lenguajes, como
C++, permiten definir funciones como operadores.
Esta implementacin tiene complejidad O(n
2
), siendo n = #xs, porque la concatenacin im-
plementada funcionalmente tiene complejidad O(n) debido a la copia del primer argumento:
c
0
si 0 n < b
T(n) =
a T(nb) + c n
k
si n b
donde b = 1, a = 1, k = 1
O(n
k+1
) si a = 1
T(n) e
O(a
n div b
) si a > 1
por lo tanto T(n) e O(n
2
)
Podemos utilizar la tcnica de plegado-desplegado, para obtener un algoritmo recursivo final
para la funcin ms general invSobre, especificada como sigue:

funcinvSobre(as,xs:Lista[Elem])devys:Lista[Elem];
{P
0
:cierto}
{Q
0
:ys=inv(xs)++as}
ffunc
de forma que este algoritmo tenga complejidad O(n)
Efectivamente invSobre es una generalizacin de inv

invSobre(as,xs)=inv(xs)++as
invSobre([],xs)=inv(xs)++[]=inv(xs)

Tipos de datos con estructura lineal 379


Aplicando la tcnica de plegado-desplegado
(CD)invSobre(as,[])=inv([])++as=as
(CR)invSobre(as,[x/xs])=inv([x/xs])++as
=inv(xs)++[x]++as
=inv(xs)++[x/as]
=invSobre([x/as],xs)
Cuya implementacin queda:
funcinvSobre(as,xs:Lista[Elem])devys:Lista[Elem];
{P
0
:cierto}
sinula?(xs)
entonces
ys:=as
sino
ys:=invSobre(Cons(primero(xs),as),resto(xs))
fsi
{Q
0
:ys=inv(xs)++as}
devys
ffunc
Donde tenemos ahora que la preparacin de la llamada es O(1), y, por lo tanto, la complejidad
total es O(n)
Mezcla de listas ordenadas
La solucin directa a este problema segn est especificado en el ejercicio 215:

funcmezcla(xs,ys:Lista[Elem])devzs:Lista[Elem]
{P
0
:ordenada(xs).ordenada(ys)}
{Q
0
:ordenada(zs).permutacin(zs,xs++ys)}

Sera
(CD) mezcla([],ys)=ys
mezcla([x/xs],[])=[x/xs]
(CR) mezcla([x/xs],[y/ys])=[x/mezcla(xs,[y/ys])]sixsy
mezcla([x/xs],[y/ys])=[y/mezcla([x/xs],ys)]six>y
La complejidad es O(n), siendo n = #xs + #ys
Se puede convertir en una funcin recursiva final encontrando una generalizacin con un
parmetro acumulador, y obteniendo el cdigo de esta generalizacin mediante plegado-
desplegado:
Tipos de datos con estructura lineal 380
mezcla(as,xs,ys)=as++mezcla(xs,ys)

Efectivamente es una generalizacin:


mezcla([],xs,ys)=mezcla(xs,ys)

Aplicando plegado-desplegado resulta

(CD)
mezcla(as,[],ys)
= as++mezcla([],ys)
= as++ys

mezcla(as,[x/xs],[])
= as++mezcla([x/xs],[])
= as++[x/xs]

(CR)
mezcla(as,[x/xs],[y/ys])
= as++mezcla([x/xs],[y/ys])
xsy = 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])
x>y = mezcla(ponDr(as,y),[x/xs],ys)

Esta implementacin tendr complejidad O(n), con n = #xs + #ys, siempre y cuando la conca-
tenacin 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:1sis#us:us!!i=x)=
(#i:1sis#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.
Tipos de datos con estructura lineal 381
4.5.1 Especificacin
La especificacin se basa en la idea de que podemos representar las secuencias como dos lis-
tas, siendo el punto de inters el punto divisorio entre las dos partes. Ntese que esto es ligera-
mente 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 genera-
dora 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:Sec[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)Sec[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]Sec[Elem] /*mod*/

Devuelve el elemento situado a la derecha del punto de inters


actual:Sec[Elem]Elem /*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]Sec[Elem] /*gen*/

Generadora que posiciona el punto de inters a la izquierda del todo.


Reinicia:Sec[Elem]Sec[Elem] /*gen*/

vaca?:Sec[Elem]Bool /*obs*/
Tipos de datos con estructura lineal 382
fin?:Sec[Elem]Bool /*obs*/
operacionesprivadas
S:(Lista[Elem],Lista[Elem])Sec[Elem] /*gen*/
piz,pdr,cont:Sec[Elem]Lista[Elem] /*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 pi-
las. La idea grfica:
1 L
act ult
Ntese que act apunta al primero de la parte derecha. Cuando el punto de inters est a la de-
recha del todo, su valor ser ult+1
Tipo representante

const
Tipos de datos con estructura lineal 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

0sx.ultsl.1sxs.actsxs.ult+1.
i:1sisxs.ult:R(xs.esp(i))
Ntese que:
ult = 0 act = 1
En este caso cont(A(xs)) = [ ]
ult = l 1 s act s l+1
act = ult + 1 pdr(A(xs)) = [ ]
Funcin de abstraccin

Dada xs : Sec[Elem] tal que R(xs)

A(xs)=
def
S(hazLista(x.esp,1,xs.act1),hazLista(xs.esp,xs.act,xs.ult)
)

[] sic=f+1
hazLista(e,c,f)=
def

[A(e(c))/hazLista(e,c+1,f)] sicsf

Ntese que R(xs) garantiza que las dos llamadas a lista cumplen c s f+1, es decir, que se hacen
con segmentos de xs.esp de longitud > 0
Implementacin procedimental de las operaciones
Con esta representacin, la eficiencia que se puede conseguir para las operaciones con una
implementacin procedimental:

Operacin Complejidad
Crea O(1)
Inserta O(n)
Tipos de datos con estructura lineal 384
borra O(n)
actual O(1)
Avanza O(1)
Reinicia O(1)
vaca? O(1)
fin? O(1)
La razn de la complejidad lineal de Inserta y borra es que suponen desplazamientos. Conside-
ramos 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;

Donde el tipo Lista[Elem] se importa del mdulo LISTA[ELEM].


Invariante de la representacin
Es trivial. Dada xs : Sec[Elem]

R(xs)
def
R(xs.piz).R(xs.pdr)

que est garantizado automticamente por la correcta implementacin de LISTA[ELEM].


Funcin de abstraccin
Dado xs : Sec[Elem]

A(xs)=
def
S(A(xs.piz),A(xs.pdr))
Implementacin procedimental de las operaciones
Esta implementacin hara uso de los procedimientos exportados por una implementacin
procedimental de LISTA[ELEM]. Se podran obtener los siguientes tiempos de ejecucin:
Operacin Complejidad Idea
Crea proc O(1)
Inserta proc O(1) ponDr en piz
Tipos de datos con estructura lineal 385
borra proc O(1) resto en pdr
actual func O(1) primero en pdr
Avanza proc O(1) primero y resto en pdr; ponDr
en piz
Reinicia proc O(n
2
) reiterar; ltimo e inicio O(n)
en piz; Cons en pdr
vaca? func O(1) nula? en piz y pdr
fin? func O(1) nula? en pdr
Reinicia resulta demasiado ineficiente.
Implementacin dinmica eficiente
La idea grfica de esta representacin:
x
i-1
x
1

prim ant
xs
x
n
x
i

primero
(fantasma)
anterior
al actual
Elegimos apuntar al anterior al actual al ltimo de la parte izquierda porque as podemos
realizar todas las operaciones en tiempo O(1): insertar delante del actual, eliminar el actual, con-
sultar el actual,
Aparece aqu una idea nueva que es el uso de un nodo fantasma. La
razn de utilizar este nodo es para simplificar los algoritmos. Como ant
apunta al anterior al actual, adnde apuntar en una secuencia vaca? Con
el fantasma, nos evitamos el tratamiento de los casos en los que la secuen-
cia est vaca.
Tipo representante
tipo
Nodo=reg
prim ant
xs
Tipos de datos con estructura lineal 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.pri=nil.ubicado(xs.pri).
xs.ant=nil.ubicado(xs.ant).
xs.antecadena(xs.pri).
xs.priecadena(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, ex-
clusive.

C sip=nil
cadena(p)=
def

{p}cadena(p^.sig) sip=nil

Y donde, dado p : Enlace, cadenaCorrecta(p) se cumple SYSS todos los punteros de cadena(p) apun-
tan a nodos diferentes correctamente construidos:

cadenaCorrecta(p)

def

p=nilv
(p=nil.ubicado(p).R(p^.elem).
pecadena(p^.sig).cadenaCorrecta(p^.sig))
Funcin de abstraccin
Dada xs : Sec[Elem] tal que R(xs):

Tipos de datos con estructura lineal 387


A(xs)=
def
S(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 com-
plicara 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)) sip=q

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)) sip=nil

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]);
{P
0
:cierto}
var
fantasma:Enlace;
inicio
ubicar(fantasma);
fantasma^.sig:=nil;
xs.pri:=fantasma;
xs.ant:=fantasma
{Q
0
:R(xs).A(xs)=
SEC[ELEM]
Crea}
fproc
Tipos de datos con estructura lineal 388

procinserta(esxs:Sec[Elem];ex:Elem);
{P
0
:xs=XS.R(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*/
{Q
0
:R(xs).A(xs)=
SEC[ELEM]
inserta(A(XS),A(x))}
fproc
x
i-1
x
1
x

prim ant
xs
x
n
x
i

1
2
3
procborra(esxs:Sec[Elem]);
{P
0
:xs=XS.R(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
{Q
0
:R(xs).A(xs)=
SEC[ELEM]
borra(A(XS))}
fproc
Tipos de datos con estructura lineal 389
x
i-1
x
1

prim ant
xs
x
n
x
i
x
i+1
act
1
funcactual(xs:Sec[Elem])devx:Elem;
{P
0
: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
{Q
0
:A(x)=
SEC[ELEM]
actual(A(xs))}
devx
ffunc
procavanza(esxs:Sec[Elem]);
{P
0
:xs=XS.R(xs).NOTfin?(A(xs))}
var
act:Enlace;
inicio
act:=xs.ant^.sig;
siact==nil
entonces
error(Partederechavaca)
sino
xs.ant:=act
fsi
{Q
0
:R(xs).A(xs)=
SEC[ELEM]
avanza(A(XS))}
Fproc

procreinicia(esxs:Sec[Elem]);
Tipos de datos con estructura lineal 390
{P
0
:R(xs)}
inicio
xs.ant:=xs.pri
{Q
0
:R(xs).A(xs)=
SEC[ELEM]
reinicia(A(XS))}
fproc

funcvaca?(xs:Sec[Elem])devr:Bool;
{P
0
:R(xs)}
inicio
r:=xs.pri^.sig==nil
{Q
0
:r=
SEC[ELEM]
vaca?(A(xs))}
devr
ffunc

funcfin?(xs:Sec[Elem])devr:Bool;
{P
0
:R(xs)}
inicio
r:=xs.ant^.sig==nil
{Q
0
:r=
SEC[ELEM]
fin?(A(xs))}
devr
ffunc
4.5.3 Recorrido y bsqueda en una secuencia
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]);
{P
0
:xs=XS.piz(xs)=[]}
var
x:Elem;
inicio
%reinicia(xs);sinosupusisemosquepiz(xs)=[]
{I:i:1sis#piz(xs):tratado(piz(xs)!!i);
C:#pdr(xs)}
itNOTfin?(xs)
x:=actual(xs);
tratar(x);
avanza(xs)
fit

{Q
0
:cont(xs)=cont(XS).fin?(xs).
Tipos de datos con estructura lineal 391
i:1sis#piz(xs):tratado(piz(xs)!!i)
fproc
Como variante de este esquema, podramos no pedir piz(xs)=[ ] en P
0
, y no comenzar con re-
inicia(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 es-
pecificaciones de los procedimientos que usan el TAD.
Esquema de bsqueda de una secuencia
Un procedimiento genrico de recorrido:
procbusca(esxs:Sec[elem];sencontrado:Bool);
{P
0
:xs=XS.piz(xs)=[]}
var
x:Elem;
inicio
%reinicia(xs);sinosupusisemosquepiz(xs)=[]
encontrado:=falso;
{I:i:1sis#piz(xs):prop(piz(xs)!!i).
encontradoNOTfin?(xs).prop(actual(xs)));
C:#pdr(xs)}
En el invariante no ponemos encontrado porque todava no hemos comprobado si el
elemento actual lo cumple o no.
itNOTfin?(xs)ANDNOTencontrado
x:=actual(xs);
siprop(x)
entoncesencontrado:=cierto
sinoavanza(xs)
fsi
fit
{Q
0
:cont(xs)=cont(XS).
encontrado-i:1sis#cont(xs):prop(cont(xs)!!i).
i:1sis#piz(xs):prop(piz(xs)!!i).
encontradoprop(act(xs))}
fproc

En la postcondicin podramos expresar tambin que si encontrado es falso, entonces estamos al


final de la secuencia.
Existe un problema en esta especificacin, y es que el ltimo aserto puede estar indefinido si
encontrado es falso, ya que entonces estamos al final y act(xs) no est definido. Esto se enmarca
dentro de un problema ms general, y es que hasta ahora hemos evitado siempre escribir asertos
que pudiesen estar indefinidos. Sin embargo, esta restriccin nos obliga en algunos casos como
ste a escribir especificaciones poco naturales. La cuestin es que tenemos que determinar las
caractersticas de la lgica con la que estamos trabajando: ha de incluir el valor indefinido y no ha
Tipos de datos con estructura lineal 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 co-
mo 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)v(encontrado.prop(act(xs)))

que adolece del mismo problema.

Como variante de este esquema, podramos no pedir piz(xs)=[ ] en P


0
, y no comenzar con re-
inicia(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 va-
lor 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);
{P
0
:xs=XS.piz(xs)=[]}
var
x:Elem;
s:Ent; %llevalasumahastaesemomento
inicio
s:=0;
{I:cont(xs)=cont(XS).
r=#i:1sis#piz(xs):
cont(xs)!!i=Ej:1sj<i:cont(xs)!!j.
s=Ei:1sis#piz(xs):cont(xs)!!i;
C:#pdr(xs)}
itNOTfin?(xs)
x:=actual(xs);
six==s
entonces
<r,s>:=<r+1,s+x>
sino tratar(x)
s:=s+x
fsi;
avanza(xs)
fit;
Tipos de datos con estructura lineal 393
{Q
0
:cont(xs)=cont(XS).fin?(xs).
r=#i:1sis#cont(xs):
cont(xs)!!i=Ej:1sj<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:
x
1
sx
2
ssx
p
>x
p+1
No consideramos al primero como candidato. Por ello, eso lo implementamos como una mo-
dificacin 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);
{P
0
: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)).
encontradoNOTfin?(xs).actual(xs)<m;
C:#pdr(xs)}
itNOTfin?(xs)ANDNOTencontrado
x:=actual(xs);
six<m
entonces
encontrado:=cierto
sino
m:=x;
avanza(xs)
fsi
fit
fsi
{Q
0
:cont(xs)=cont(XS).noDecreciene(piz(xs)).
Tipos de datos con estructura lineal 394
encontrado-i:2sis#cont(xs):cont(xs)!!i<cont(xs)!!i
1.
encontradoactual(xs)<ltimo(piz(xs))
}
Donde el predicado auxiliar noDecreciente indica que la lista est ordenada en orden no decre-
ciente:

noDecreciente(ls)
def
i,j:1si<js#ls:(ls!!i)s(ls!!j)
Mezcla ordenada de secuencias
El ltimo ejemplo que vamos a estudiar es el de la mezcla de dos secuencias ordenadas.

procmezcla(esxs,ys:Sec[Elem];szs:Sec[Elem])
{P
0
:xs=XS.ys=YS.ordenada(cont(xs)).piz(xs)=[].
ordenada(cont(ys)).piz(ys)=[]}
var
x,y:Elem;
inicio
Crea(zs);
{I:cont(xs)=cont(XS).cont(ys)=cont(YS).fin?(zs).
ordenada(cont(zs)).
permutacin(cont(zs),piz(xs)++piz(ys);
C:#pdr(xs)+#pdr(ys)
}
itNOTfin?(xs)ANDNOTfin?(ys)
x:=actual(xs);
y:=actual(ys);
sixsy
entonces
inserta(x,zs);
avanza(xs)
sino
inserta(y,zs);
avanza(ys)
fsi
fit;
{I;C}%lamismacotaeinvariantedelbucleanterior
itNOTfin?(xs)
inserta(actual(xs),zs);
avanza(xs)
fit;
{I;C}%lamismacotaeinvariantedelbucleanterior
itNOTfin?(ys)
inserta(actual(ys),zs);
avanza(ys)
fit;

Tipos de datos con estructura lineal 395


{Q
0
:cont(xs)=cont(XS).cont(ys)=cont(YS).ordenada(cont(zs)).
permutacin(cont(zs),cont(xs)++cont(ys))}
fproc
Tipos de datos con estructura lineal 396
4.6 Ejercicios
Pilas
177. Las pilas formadas por nmeros naturales del intervalo [0..N1] (para cierto N > 2 fijado de
antemano) se pueden representar por medio de nmeros, entendiendo que un nmero na-
tural 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).
178. Define el tipo representante, el invariante de la representacin y la funcin de abstraccin
adecuados para una representacin dinmica de las pilas.
179. Desarrolla una implementacin de las operaciones del TAD PILA mediante funciones,
basndote en la representacin del ejercicio anterior.
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 eje-
cutar 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 su-
ceder 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 nue-
vo valor de xs la representacin de la pila vaca.
Tipos de datos con estructura lineal 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 procedi-
mientos. 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 al-
macenamiento. 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-
cin con la siguiente especificacin informal:

desapilarK:(Nat,Pila[Elem])Pila[Elem]
Modificadora. desapilarK(k, xs) desapila los k elementos de xs ms prximos a
la cima, si hay suficientes elementos.
185. Aplicando la tcnica de anlisis amortizado, razona que el tiempo de ejecucin de m
1
llamadas
a apilar y m
2
llamadas a desapilarK es O(m
1
), siempre que todas las llamadas se refieran a una
misma pila, inicialmente vaca.
Eliminacin de la recursin lineal no final con ayuda de una pila
186. Recordemos que el esquema general de definicin de una funcin recursiva lineal no final
es de la forma:
funcf(x:T)devy:S;
{P
0
: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
{Q
0
:Q(x,y)}
Tipos de datos con estructura lineal 398
devy
ffunc

Supongamos que la funcin s encargada de calcular los parmetros de la siguiente llamada re-
cursiva posea una inversa s
-1
. En este caso, es posible transformar f en una definicin iterativa
equivalente, de la forma siguiente:
funcf
it
(x:T)devy:S;
{P
0
:P(x)}
var
x:T;
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
{Inv.I:R(x,x);Cota:t(x)}
itd(x)
{I.d(x)}
x:=s(x)
{I}
fit;
{I.d(x)}
y:=r(x);
{Inv.J:R(x,x).y=f(x);Cota:m(x,x)}
itx/=x
{J.x=x}
x:=s
1
(x);
y:=c(x,y)
{J}
fit
{J.x=x}
{y=f(x)}
{Q
0
:Q(x,y)}
devy
ffunc
siendo
R(x,x)

def

-n:Nat:(x=s
n
(x).P(x).
i:0si<n:(P(s
i
(x)).d(s
i
(x))))
(Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas)

m(x,x)
=
def

minn:Nat:x=s
n
(x)
(Idea: expresa el nmero de llamadas recursivas necesarias para alcanzar x desde x)

Tipos de datos con estructura lineal 399


Aplica esta transformacin a las siguientes funciones recursivas lineales:
*(a) Funcin fact (ejercicio 79).
(b) Funcin dosFib (ejercicio 99, 128)
187. Pensemos de nuevo en una funcin recursiva lineal f definida segn el esquema del ejerci-
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 su-
cesivas llamadas recursivas. La versin iterativa de f queda ahora:

funcf
it
(x:T)devy:S;
{P
0
: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)
{I.d(x)}
Apilar(x,xs);
x:=s(x)
{I}
fit;
{I.d(x)}
y:=r(x);
{Inv.J:R(xs,x,x).y=f(x);Cota:tamao(xs)}
itNOTesVaca(xs)
{J.esVaca(xs)}
x:=cima(xs);
y:=c(x,y);
desapilar(xs)
{J}
fit
{J.esVaca(xs)}
{y=f(x)}
{Q
0
:Q(x,y)}
devy
ffunc

siendo
R(xs,x,x)

def

-n:Nat:(x=s
n
(x).P(x).
i:0si<n:(P(s
i
(x)).d(s
i
(x)).s
i
(x)=elem(i,xs)))
Tipos de datos con estructura lineal 400
donde elem(i, xs) indica el elemento que ocupa el lugar i en la pila xs, contando el ele-
mento del fondo como elem(0, xs)
(Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursi-
vas, cuyos parmetros reales, hasta x exclusive, estn apilados en xs)

tamao(xs)
=
def

nmerodeelementosapiladosenxs
(Idea: expresa el nmero de llamadas recursivas necesarias para alcanzar x desde x)

Aplica la transformacin que acabamos de definir a las siguientes funciones recursivas lineales:
(a) Funciones pot y pot (ejercicio 85).
(b) Funcin mult (ejercicio 90)
(c) Funcin log (ejercicio 91).
*(d) Funcin bin (ejercicio 92)
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
descrito en el ejercicio 187, no es necesario apilar toda la informacin correspondiente a
una tupla
x : T. En ciertos casos, es suficiente apilar un dato u : Z, ms simple que x, elegido de
modo que x sea fcil de calcular a partir de u y s(x). Ms exactamente, deben estar
disponibles dos funciones g y h que verifiquen:
d(x)u=g(x)estdefinidoycumplequex=h(u,s(x))
Modificando el esquema del ejercicio 187 segn esta idea, se obtiene la siguiente versin itera-
tiva de f:

funcf
it
(x:T)devy:S;
{P
0
: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)
{I.d(x)}
u:=g(x);
Apilar(u,us);
x:=s(x)
{I}
Tipos de datos con estructura lineal 401
fit;
{I.d(x)}
y:=r(x);
{Inv.J:R(us,x,x).y=f(x);Cota:tamao(us)}
itNOTesVaca(us)
{J.esVaca(us)}
u:=cima(us);
x:=h(u,x);
y:=c(x,y);
desapilar(us)
{J}
fit
{J.esVaca(us)}
{y=f(x)}
{Q
0
:Q(x,y)}
devy
ffunc

siendo
R(us,x,x)

def

-n:Nat:(x=s
n
(x).P(x).
i:0si<n:(P(s
i
(x)).d(s
i
(x)).g(s
i
(x))=elem(i,us)))
donde elem(i, us) indica el elemento que ocupa el lugar i en la pila us, contando el ele-
mento del fondo como elem(0, us)
(Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursi-
vas, y que en us est apilada informacin suficiente para recuperar los parmetros
reales de dichas llamadas)

tamao(us)
=
def

nmerodeelementosapiladosenus
(Idea: expresa el nmero de llamadas recursivas necesarias para alcanzar x desde x)

Comprueba que esta transformacin optimizada se puede aplicar a las funciones recursivas li-
neales del ejercicio 187, utilizando en todos los casos una pila de valores booleanos. Precisa qui-
nes 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
especificacin informal de las operaciones deseadas:

ColaVaca:Cola[Elem]
Generadora. Crea una cola vaca.
Tipos de datos con estructura lineal 402
Aadir:(Elem,Cola[Elem])Cola[Elem]
Generadora. Aade un nuevo elemento al final de una cola.
avanzar:Cola[Elem]Cola[Elem]
Modificadora. Retira el primer elemento de una cola no vaca.
primero:Cola[Elem]Elem
Observadora. Consulta el primer elemento de una cola no vaca.
esVaca:Cola[Elem]Bool
Observadora. Reconoce si una cola es vaca o no.
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-
zando las operaciones generadoras y modificadoras como procedimientos. Observa que el
tiempo de ejecucin es O(1) para todas las operaciones.
193. Desarrolla una implementacin dinmica del TAD COLA, realizando las operaciones gene-
radoras y modificadoras como procedimientos.
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-
da a derecha (ignorando los blancos) es la misma que si el recorrido se hace de derecha a
izquierda. Esto sucede, por ejemplo, con la socorrida frase dbale arroz a la zorra el abad.
Construye una funcin iterativa ejecutable en tiempo lineal, que decida si una frase dada en
forma de cola de caracteres es o no palndroma. El algoritmo utilizar una pila de caracteres, de-
clarada localmente.
Idea: Primeramente, el algoritmo recorre la cola y va apilando los caracteres no blancos. A con-
tinuacin, se vuelve a recorrer la cola, comparando los caracteres no blancos con los caracteres
almacenados en la pila. El algoritmo debe usar la funcin copia del ejercicio anterior, para garanti-
zar que al final de la ejecucin la estructura representante de la cola (parmetro de entrada) no se
haya alterado.
Colas dobles
196. El TAD parametrizado DCOLA[E :: ANY] especifica el comportamiento de las colas dobles,
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:DCola[Elem]
Tipos de datos con estructura lineal 403
Generadora. Crea una cola vaca.
PonDetrs:(Elem,DCola[Elem])DCola[Elem]
Generadora. Aade un nuevo elemento al final de una cola.
ponDelante:(Elem,DCola[Elem])DCola[Elem]
Modificadora. Aade un nuevo elemento al principio de una cola.
quitaUlt:DCola[Elem]DCola[Elem]
Modificadora. Retira el ltimo elemento de una cola no vaca.
ltimo:DCola[Elem]Elem
Observadora. Consulta el ltimo elemento de una cola no vaca.
quitaPrim:DCola[Elem]DCola[Elem]
Modificadora. Retira el primer elemento de una cola no vaca.
primero:DCola[Elem]Elem
Observadora. Consulta el primer elemento de una cola no vaca.
esVaca:DCola[Elem]Bool
Observadora. Reconoce si una cola es vaca o no.

Completa la especificacin algebraica de este TAD.


197. Modifica la implementacin esttica del ejercicio 192 para adaptarla a las colas dobles.
198. Modifica la implementacin dinmica del ejercicio 193 para adaptarla a las colas dobles.
199. Observa que el tiempo de ejecucin de quitaUlt en la implementacin del ejercicio anterior
es O(n), debido a que el penltimo nodo slo puede localizarse recorriendo toda la estruc-
tura desde el primer nodo. Modifica la implementacin, de manera que la estructura repre-
sentante 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 re-
presentacin, obteniendo procedimientos ejecutables en tiempo O(1).
200. El agente 0069 ha inventado un nuevo mtodo de codificacin de mensajes secretos. El
mensaje original X se codifica en dos etapas:
En primer lugar, X se transforma en X reemplazando cada sucesin de caracteres
consecutivos que no sean vocales por su imagen especular.
En segundo lugar, X se transforma en la sucesin de caracteres X obtenida al ir to-
mando sucesivamente: el primer carcter de X; luego el ltimo; luego el segundo; lue-
go el penltimo; etc.
Ejemplo: para X = Bond, James Bond, resultan:
X = BoJ ,dnameB sodn y X = BnodJo s, dBneam
Construye los algoritmos de codificacin y descodificacin de mensajes y analiza su complejidad,
utilizando pilas y colas. Supn en particular que el mensaje inicial viene dado como una cola de
caracteres.
Especificacin del TAD LISTA
201. Especifica algebraicamente un TAD genrico LISTA[E :: ANY], partiendo de la siguiente
especificacin informal de las operaciones deseadas:

Tipos de datos con estructura lineal 404


Nula:Lista[Elem]
Generadora. Crea una lista vaca.
Cons:(Elem,Lista[Elem])Lista[Elem]
Generadora. Cons(x,xs) genera una nueva lista aadiendo x como primer ele-
mento, por delante de los elementos de xs.
nula?:Lista[Elem]Bool
Observadora. Reconoce si una lista es vaca o no.
primero:Lista[Elem]Elem
Observadora. Consulta el primer elemento de una lista no vaca.
resto:Lista[Elem]Lista[Elem]
Modificadora. Quita el primer elemento de una lista no vaca.
202. Enriquece el TAD parametrizado de las listas con nuevas operaciones, partiendo de las
siguientes especificaciones informales:

(++):(Lista[Elem],Lista[Elem])Lista[Elem]
Modificadora. xs ++ ys construye la concatenacin de xs con ys, dando una
nueva lista formada por los elementos de xs seguidos de los de ys.
[_]:ElemLista[Elem]
Modificadora. [ x ] es la lista formada por el nico elemento x.
(#):Lista[Elem]Nat
Observadora. # xs devuelve la longitud de la lista xs.
(!!):(Lista[Elem],Nat)Elem
Observadora. xs !! i est definido si 1 s i s n, siendo n = # xs, y devuelve el
elemento de lugar i de xs.
miembro:(Elem,Lista[Elem])Bool
Observadora. Reconoce si un elemento es miembro de una lista.
ponDr:(Lista[Elem],Elem)Lista[Elem]
Modificadora. Aade un elemento a la derecha de una lista.
ltimo:Lista[Elem]Elem
Observadora. Devuelve el ltimo elemento de una lista no vaca.
inicio:Lista[Elem]Lista[Elem]
Modificadora. Quita el ltimo elemento de una lista no vaca.
Implementacin del TAD LISTA
203. Discute una posible implementacin esttica del TAD LISTA, basada en una representa-
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 represen-
tante de una lista, y un procedimiento anula que libere todo el espacio ocupado por la es-
tructura representante de una lista.
Tipos de datos con estructura lineal 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-
cedimental de la operacin ( ++ ), que satisfaga la siguiente especificacin Pre/Post:

procconc(esxs:Lista[Elem];eys:Lista[Elem])
{P
0
:R(xs).R(ys).xs=XS.ys=YS}
{Q
0
:R(xs).R(ys).A(xs)=
LISTA[ELEM]
A(XS)++A(ys)}
fproc

Estudia el tiempo de ejecucin de conc.


207. Desarrolla una implementacin funcional del TAD LISTA usando la misma representacin
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 represen-
tante, que sirvan para mejorar la eficiencia de alguna operacin.
Programacin recursiva con listas
En los ejercicios de este apartado, suponemos disponible un mdulo que exporte una implemen-
tacin funcional del TAD LISTA. Debemos programar usando solamente las operaciones expor-
tadas, sin punteros. para simplificar, ignoraremos cuestin de gestin de memoria (copiar, anular).
208. Programa funciones recursivas que realicen el comportamiento correcto de las operaciones
(#), ( !! ) y ( ++ ), usando otras operaciones ms bsicas del TAD LISTA. Analiza el tiem-
po 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-
mos recursivos que las satisfagan
(a) funccoge(n:Nat;xs:Lista[Elem])devus:Lista[Elem]
{P
0
:cierto}
{Q
0
:useslalistaformadaporlosnprimeroselementosdexs}
Tipos de datos con estructura lineal 406
ffunc

(b) functira(n:Nat;xs:Lista[Elem])devvs:Lista[Elem]
{P
0
:cierto}
{Q
0
: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];
{P
0
:cierto}
inicio
sinula?(xs)
entoncesys:=Nula
sinoys:=inv(resto(xs))++[primero(xs)]
fsi
{Q
0
::1sis(#xs):xs!!i=ys!!(#xs)i+1}
ffunc

Usando la tcnica de plegado-desplegado, deriva un algoritmo recursivo final para la funcin


ms general invSobre, especificada como sigue:

funcinvSobre(as,xs:Lista[Elem])devys:Lista[Elem];
{P
0
:cierto}
{Q
0
:ys=inv(xs)++as}
ffunc

Analiza los tiempos de ejecucin de inv e invSobre. Compara.


213. Especifica un TAD parametrizado LISTA-ORD[E :: ORD] que enriquezca el TAD
LISTA[E :: ANY] con nuevas operaciones especificadas informalmente como sigue:

(==):(Lista[Elem],Lista[Elem])Bool
Observadora. Operacin de igualdad entre listas.
(s):(Lista[Elem],Lista[Elem])Bool
Observadora. Orden entre listas.
ordenada:Lista[Elem]Bool
Observadora. Decide si una lista est ordenada.
insertaOrd:(Elem,Lista[Elem])Lista[Elem]
Modificadora. Inserta un elemento de manera que la lista resultante queda ordenada si la
lista original lo estaba.
Tipos de datos con estructura lineal 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 opera-
ciones 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 gene-
ral. Suponemos que se trata de listas ordenas de tipo Lista[Elem].

funcmezcla(xs,ys:Lista[Elem])devzs:Lista[Elem]
{P
0
:ordenada(xs).ordenada(ys)}
{Q
0
:ordenada(zs).permutacin(zs,xs++ys)}
216. Transforma el algoritmo recursivo final obtenido en el ejercicio anterior en un algoritmo
iterativo.
217. Construye especificaciones Pre/Post y funciones recursivas que resuelvan los dos proble-
mas que siguen:
(a) Dada una lista de enteros xs, construir otra lista ys formada por los nmeros pares
que aparezcan en xs, tomados en el mismo orden.

(b) Dada una lista de enteros xs, construir dos listas us,vs formadas respectivamente por
los nmeros pares e impares que aparezcan en xs, tomados en el mismo orden.
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 ejecuta-
bles en tiempo O(n), siendo n la longitud de xs.
Especificacin e implementacin de las secuencias
219. El TAD parametrizado SEC[E :: ANY] especifica el comportamiento de las secuencias, tam-
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:Sec[Elem]
Generadora. Crea una secuencia vaca.
Inserta:(Sec[Elem],Elem)Sec[Elem]
Generadora. Inserta un nuevo elemento a la izquierda del punto de inters.
borra:Sec[Elem]Sec[Elem]
Modificadora. Elimina el elemento situado a la derecha del punto de inters, si
lo hay.
actual:Sec[Elem]Elem
Observadora. Consulta el elemento situado a la derecha del punto de inters, si
lo hay.
Avanza:Sec[Elem]Sec[Elem]
Generadora. Desplaza un lugar a la derecha el punto de inters, si es posible.
Tipos de datos con estructura lineal 408
Reinicia:Sec[Elem]Sec[Elem]
Generadora. Posiciona el punto de inters a la izquierda del todo.
vaca?:Sec[Elem]Bool
Observadora. Reconoce si la secuencia est vaca.
fin?:Sec[Elem]Bool
Observadora. Reconoce si el punto de inters se encuentra a la derecha del to-
do.
Completa la especificacin algebraica de este TAD.
220. Plantea una posible implementacin del TAD secuencia utilizando una representacin est-
tica. Qu tiempo de ejecucin se obtiene para las operaciones inserta y borra?
221. Desarrolla una implementacin dinmica del TAD secuencia realizando las operaciones
generadoras y modificadoras como procedimientos, de manera que todas las operaciones
sean ejecutables en tiempo O(1).
Aplicaciones de las secuencias y otras estructuras lineales
222. Las especificaciones que siguen corresponden a esquemas de procesamiento de secuencias de uso
comn en muchas aplicaciones. Construye en cada caso un algoritmo que satisfaga la espe-
cificacin, suponiendo disponible un mdulo que implemente el TAD de las secuencias.
(a) Esquema de recorrido secuencial:
procrecorre(esxs:Sec[Elem]);
{P
0
:xs=XS.piz(xs)=[]}
{Q
0
:cont(xs)=cont(XS).fin?(xs)=cierto.
tratarsehaaplicadoatodosloselementosdexs}
fproc
(b) Esquema de bsqueda secuencial:
procbusca(esxs:Sec[Elem];sencontrado:Bool);
{P
0
:xs=XS.piz(xs)=[]}
{Q
0
:cont(xs)=cont(XS).
(encontradoexisteenxsalgnelementoquecumplaprop).
(encontradoactual(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
cada caso, el contenido de la secuencia debe quedar inalterado.
(a) Contar el nmero de apariciones de a en una secuencia de caracteres dada.
(b) Contar el nmero de apariciones de vocales en una secuencia de caracteres dada.
Tipos de datos con estructura lineal 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
que no sea mayor o igual que todos los anteriores.
225. Algunos problemas de procesamiento de secuencias requieren modificar y/o combinar los
esquemas de recorrido y bsqueda secuencial. Resuelve los casos siguientes:
(a) Dada una secuencia de caracteres, copiarla en otra eliminando los blancos mltiples.
(b) Contar el nmero de apariciones de a posteriores a la primera aparicin de b en
una secuencia de caracteres dada.
(c) Contar el nmero de caracteres anteriores y posteriores a la primera aparicin de a
en una secuencia de caracteres dada.
(d) Contar el nmero de parejas de vocales consecutivas que aparecen en una secuencia
de caracteres dada.
226. Construye un procedimiento iterativo que satisfaga la especificacin que sigue, suponiendo
que disponemos de un orden s para el tipo Elem.
procmezcla(esxs,ys:Sec[Elem];szs:Sec[Elem])
{P
0
:xs=XS.ys=YS.ordenada(cont(xs)).piz(xs)=[].
ordenada(cont(ys)).piz(ys)=[]}
{Q
0
: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 inmediata-
mente de un operador. Lo que sigue es un ejemplo de una expresin escrita en la notacin
infija habitual, junto con su forma postfija:
Forma infija: (A/(BC))*(D+E)) Forma postfija: ABC/DE+*

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 com-
ponen 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 caracte-
res, y supn disponible una funcin valor que asocie a cada operando su valor numrico.
228. Dado un nmero natural N > 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-
Tipos de datos con estructura lineal 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 ve-
ces 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 implemen-
taciones 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. Dis-
cute 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 depar-
tamento 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:Bostezos[Elem]
Generadora. Crea un sistema de bostezos vaco.
Otro:(Elem,Bostezos[Elem])Bostezos[Elem]
Generadora. Registra un nuevo bostezo en el sistema.
borra:(Elem,Bostezos[Elem])Bostezos[Elem]
Modificadora. Borra del sistema todos los bostezos registrados de un elemento
dado.
cuntos?:(Elem,Bostezos[Elem])Nat
Observadora. Consulta el nmero de bostezos de un elemento dado que estn
registrados en el sistema.
listaNegra:Bostezos[Elem]Sec[Elem]
Observadora. Devuelve la secuencia ordenada de todos los elementos que ten-
gan tres o ms bostezos registrados.

Formaliza la especificacin algebraica del TAD BOSTEZOS y estudia dos implementaciones


alternativas: Una basada en vectores ordenados, y otra basada en secuencias ordenadas, importa-
das de un mdulo separado.
232. En este ejercicio se trata de desarrollar un TAD CONSULTORIO que modelice el com-
portamiento de un consultorio mdico. La especificacin de CONSULTORIO usar (entre
otros) los TADs MEDICO (con tipo principal Mdico) y PACIENTE (con tipo principal
Paciente), que se suponen ya conocidos. Suponemos adems que MEDICO pertenece a la
Tipos de datos con estructura lineal 411
clase de tipos ORD. Se desea que CONSULTORIO ofrezca a sus usuarios un tipo princi-
pal Consultorio junto con las operaciones que se describen informalmente a continuacin:

Crea:Consultorio
Genera un consultorio vaco sin ninguna informacin.
NuevoMdico:(Consultorio,Mdico)Consultorio
Altera un consultorio, dando de alta a un nuevo mdico que antes no figuraba en el consul-
torio.
PideConsulta:(Consultorio,Mdico,Paciente)Consultorio
Altera un consultorio, haciendo que un paciente se ponga a la espera para ser
atendido por un mdico, el cual debe estar de alta en el consultorio.
siguientePaciente:(Consultorio,Mdico)Paciente
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 con-
sulta.
atiendeConsulta:(Consultorio,Mdico)Consultorio
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)Bool
Reconoce si hay o no pacientes a la espera de ser atendidos por un mdico, el cual debe es-
tar de alta.

(a) Construye una especificacin algebraica completa del TAD CONSULTORIO, inclu-
yendo todas las ecuaciones necesarias.
(b) Plantea un mdulo de implementacin CONSULTORIO del TAD
CONSULTORIO, indicando claramente:
Los mdulos que se importan
La definicin del tipo representante de Consultorio
El invariante de la representacin y la funcin de abstraccin para Consultorio
Las cabeceras, pre- y postcondiciones de los procedimientos y funciones que se
exportan.
Elige el tipo representante de Consultorio de manera que puedas realizarlo utilizando
TADs conocidos con estructura lineal, importndolos de mdulos que los implemen-
ten, y sin acceder a la representacin interna.
(c) Completa el mdulo de implementacin del apartado (b) desarrollando procedimien-
tos y funciones que implementen correctamente las operaciones de
CONSULTORIO, y analizando el tiempo de ejecucin de cada una de ellas en el ca-
so peor, con respecto a las siguientes medidas del tamao de un consultorio: M,
nmero de mdicos dados de alta en el consultorio; y P, mximo de los tamaos de
las colas de espera de los diferentes mdicos.
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
Tipos de datos con estructura lineal 412
clase de tipos ORD. Se desea que MERCADO ofrezca a sus usuarios un tipo principal Mer-
cado junto con las operaciones que se describen informalmente a continuacin:
Crea
Genera un mercado vaco, sin ninguna informacin.
Contrata
Altera un mercado, efectuando la contratacin de cierta persona como empleado de cierta
empresa.
despide
Altera un mercado, efectuando el despido de cierta persona que era antes empleado de cier-
ta empresa.
empleados
Consulta los empleados de una empresa, devolviendo el resultado como secuencia ordena-
da de personas.
empleado?
Averigua si es cierto o no que una persona dada es empleado de una empresa dada.
pluriempleado?
Averigua si es cierto o no que una persona es empleado de ms de una empresa.
(a) Construye una especificacin algebraica del TAD MERCADO, indicando los otros
TADs que se necesite usar, los perfiles de las operaciones, la clasificacin de las ope-
raciones 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 mane-
ra que puedas realizarlo utilizando TADs conocidos con estructura lineal, importn-
dolos de mdulos que los implementen, y sin acceder a la representacin interna.
(c) Completa el mdulo de implementacin del apartado (b) desarrollando los procedi-
mientos y funciones que implementan las operaciones de MERCADO, y analizando
sus tiempos de ejecucin en funcin de las siguientes medidas del tamao de un mer-
cado: 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 implementa-
cin 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 opera-
cin indefinida. Adems del mdulo de datos BANCO, debes suponer disponibles otros
dos mdulos SOLICITUDES e INCIDENCIAS, que exporten los tipos Solicitud e Inciden-
cia, respectivamente, junto con operaciones adecuadas. Especifica los TADs correspon-
dientes al comportamiento abstracto de estos dos mdulos, e indica claramente qu otros
mdulos de datos necesita utilizar el procedimiento que disees.
Tipos de datos con estructura lineal 413
236. En este ejercicio se trata de automatizar algunos aspectos de la gestin de una biblioteca
simplificada. Suponemos que la biblioteca dispone de uno o ms ejemplares de cada libro.
Un libro debe tener autor y ttulo, mientras que un ejemplar debe tener adems un nmero de re-
gistro. Los ejemplares se registran en la biblioteca al ser adquiridos, y posteriormente pueden
ser prestados a usuarios. Por consiguiente, la biblioteca debe mantener la informacin de los
ejemplares existentes y del estado de cada uno, que puede consistir en estar disponible o
prestado a un determinado usuario. Especifica un TAD BIBLIOTECA que modelice el
comportamiento de la biblioteca, proporcionando operaciones adecuadas para crear una
biblioteca vaca, registrar un nuevo ejemplar, efectuar prstamos y devoluciones, generar un
listado de autores disponibles, generar un listado de ttulos disponibles para un autor dado,
etc. Plantea una implementacin de BIBLIOTECA, usando listas o secuencias importadas
de mdulos separados para construir la representacin de las bibliotecas.
NOTA: Una representacin razonable de una biblioteca puede ser una lista de listas, donde
cada una de las lista-elemento contenga todos los ejemplares disponibles de un mismo autor (ca-
da uno acompaado por la informacin de si est prestado o no, y en caso afirmativo, a qu usua-
rio). Conviene que el invariante de la representacin exija mantener la lista que representa la
biblioteca ordenada por autores, y la lista de ejemplares de cada autor ordenada por ttulos, con
los diferentes ejemplares de un mismo ttulo ordenados a su vez por nmero de registro.
237. Supongamos disponible un mdulo que implemente el TAD BIBLIOTECA del ejercicio
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 supo-
ner disponibles otros dos mdulos SOLICITUDES e INCIDENCIAS, lo mismo que en el
ejercicio 235.
rboles 414
CAPTULO 5
RBOLES
5.1 Modelo matemtico y especificacin
Los rboles son estructuras jerrquicas formadas por nodos, de acuerdo con la siguiente cons-
truccin inductiva:
Un solo nodo forma un rbol a; se dice que el nodo es raz del rbol.
a
Dados n rboles ya construidos a
1
, , a
n
, se puede construir un nuevo rbol a aadiendo
un nuevo nodo como raz y conectndolo con las races de los a
i
. Se dice que los a
i
son los
hijos de a.
a
a
1
a
n

a
1
a
n

Ejemplos de uso de los rboles


Los rboles tienen muchos usos para representar jerarquas y clasificaciones, dentro y fuera de
la Informtica:
Arboles genealgicos
Arboles taxonmicos
Organizacin de un libro en captulos, secciones, etc.
Estructura de directorios y archivos de una computadora.
Arbol de llamadas de una funcin recursiva.
Arboles de anlisis, por ejemplo para una expresin aritmtica:
2
+
*
-
x y
3
rboles 415
o para una accin condicional
si
sino condicin entonces
x
<
y
2
+
*
-
x y
3
x
:=
2
*
-
*
x
3
y
y
:=
Clases de rboles
Ordenados o no ordenados. Un rbol es ordenado si el orden de los hijos de cada nodo
es relevante. Nosotros vamos a considerar casi siempre rboles ordenados.
Etiquetados o no etiquetados. Un rbol est etiquetado si hay informaciones asociadas a
sus nodos. Nosotros vamos a utilizar rboles etiquetados.
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 fun-
damentalmente 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:
La raz de un rbol tiene como posicin la cadena vaca c.
rboles 416
Si un cierto nodo de un rbol tiene posicin o e
+
*
, el hijo nmero i de ese nodo tendr
posicin o.i (que indica o seguida de i).
Ejemplo:
A
A C G E
A
D
B F
D E
B 1
1.1 1.2
3.1
2 3
3.2
3.3
3.3.1 3.3.2
3.4
El rbol puede modelizarse como una aplicacin:
a:NV

donde N _
+
*
es el conjunto de posiciones de los nodos, y V es el conjunto de valores posi-
bles para las informaciones que se asocian a los nodos.
Volviendo al ejemplo:

N={c,1,2,3,1.1,1.2,3.1,3.2,3.3,3.4,3.3.1,3.3.2}

a(c)=A
a(1)=Ba(2)=Ca(3)=D etc.

En general, un conjunto N _
+
*
de cadenas de nmeros naturales positivos, debe cumplir las
siguientes condiciones para ser vlido como conjunto de posiciones de los nodos de un rbol
general:
N es finito.
c e N posicin de la raz.
o.i e N o e N cerrado bajo prefijos.
o.i e N . 1 s j < i o.j e N hijos consecutivos sin huecos.
Terminologa y conceptos bsicos
Dado un rbol a : N V
Nodo es cada posicin, junto con la informacin asociada: (o, a(o)), siendo o e N.
Raz es el nodo de posicin c.
rboles 417
Hojas son los nodos de posicin o tal que no existe i tal que o.i e N.
Nodos internos son los nodos que no son hojas.
Un nodo o.i tiene como padre a o, y se dice que es hijo de o.
Dos nodos de posiciones o.i, o.j (i = j) se llaman hermanos.
Camino es una sucesin de nodos tal que cada uno es padre del siguiente:
o, o.i
1
, , o.i
1
. .i
n
n es la longitud del camino.
Rama es cualquier camino que comience en la raz y termine en una hoja.
El nivel o profundidad de un nodo de posicin o es |o|+1. Es decir: n+1, siendo n la longi-
tud 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 no-
dos 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.
El grado o aridad de un nodo interno es su nmero de hijos. La aridad de un rbol es el
mximo de las aridades de todos sus nodos internos.
Si hay un camino del nodo de posicin o al nodo de posicin | (i.e., si o es prefijo de |),
se dice que o es antepasado de | y que | es descendiente de o.
Cada nodo de un rbol a determina un subrbol a
0
con raz en ese nodo. Formalmente, si el
nodo tiene posicin o, entonces:
a
0
:N
0
V
siendo
N
0
=
def
{|e
+
*
|o|eN}
a
0
(|)=
def
a(o|) paracada|eN
0

Dado un rbol a, los subrboles de a (si existen), se llaman rboles hijos de a. El rbol del
ejemplo tiene 3 rboles hijos.
Especificacin algebraica de los rboles generales
Optamos por incluir operaciones que permiten:
Construir rboles.
Consultar la informacin de la raz y los rboles hijos.
Contar el nmero de hijos.
Reconocer las hojas.
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 bos-
ques siguen la misma idea que las generadoras de listas.
rboles 418
tadARBOL[E::ANY]
usa
BOOL,NAT
tipos
Arbol[Elem],Bosque[Elem]
operaciones
[]:Bosque[Elem] /*gen*/
[_/_]:(Arbol[Elem],Bosque[Elem])Bosque[Elem] /*gen*/
rbol:(Nat,Bosque[Elem])Arbol[Elem] /*obs*/
nrArboles:Bosque[Elem]Nat /*obs*/
esVaco:Bosque[Elem]Bool /*obs*/
Cons:(Elem,Bosque[Elem])Arbol[Elem] /*gen*/
hijo:(Nat,Arbol[Elem])Arbol[Elem] /*mod*/
nrHijos:Arbol[Elem]Nat /*obs*/
hijos:Arbol[Elem]Bosque[Elem] /*obs*/
raz:Arbol[Elem]Elem /*obs*/
esHoja:Arbol[Elem]Bool /*obs*/
ecuaciones
i:Nat:as:Bosque[Elem]:a:Arbol[Elem]:x:Elem:
defrbol(i,as)siSuc(Cero)sisnrArboles(as)
rbol(i,[a/as]) =a sii=Suc(Cero)
rbol(i,[a/as]) =
f
rbol(i1,as)sii>Suc(Cero)
nrArboles([]) =Cero
nrArboles([a/as]) =Suc(nrArboles(as))
esVaco(as) =nrArboles(as)==Cero
defhijo(i,a)siSuc(Cero)sisnrHijos(a)
hijo(i,a) =
f
rbol(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)sisnrArboles(as))
hijo(i,a)siNOT(Suc(Cero)sisnrHijos(a))
ftad
rboles 419
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
o e {1,2}
*
. En caso de que un nodo interno (con posicin o) tenga un solo hijo, se distingue si
ste es hijo izquierdo (con posicin o.1) o hijo derecho (con posicin o.2).
Estas ideas conducen a modelar un rbol binario etiquetado con informaciones de V como
una aplicacin:

a:NV

donde el conjunto N de posiciones de los nodos de a debe cumplir las siguientes condiciones:
N _ {1,2}
*
, finito.
o.i e N o e N cerrado bajo prefijos.
o e N se dan uno de los 4 casos siguientes:
o.1 e N y o.2 e N dos hijos
.1 .2
o.1 e N y o.2 e N slo hijo izquierdo
.1
o.1 e N y o.2 e N slo hijo derecho
.2
o.1 e N y o.2 e N ningn hijo; hoja
rboles 420
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 C (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:
Generar el rbol vaco.
Construir rboles no vacos.
Consultar la raz y los hijos.
Recorrer el rbol vaco.
tadARBIN[E::ANY]
usa
BOOL
tipos
Arbin[Elem]
operaciones
Vaco:Arbin[Elem] /*gen*/
Cons:(Arbin[Elem],Elem,Arbin[Elem])Arbin[Elem] /*gen*/
hijoIz,hijoDr:Arbin[Elem]Arbin[Elem]/*mod*/
raz:Arbin[Elem]Elem /*obs*/
esVaco:Arbin[Elem]Bool/*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
rboles 421
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 de-
finicin de grado o aridad de un rbol que dimos anteriormente. En un rbol de grado n, cada no-
do tiene como mucho n hijos, pero distintos nodos pueden tener distinto nmero de hijos, y los
hijos existentes ocupan posiciones consecutivas.
Ejemplo. a
1
es un rbol general de grado 2, pero no es un rbol binario. Los rboles a
2
y a
3
s
son binarios:
1
1.1 1.2 1.1 1.2
1 2
2.1 2.2
a
1
a
2
a
3
5.1.4 Arboles con punto de inters
Como representacin matemtica de un rbol con punto de inters podemos adoptar una pa-
reja de la forma:

(a,o
0
)

donde a es un rbol, modelizado matemticamente del modo que ya hemos indicado:

a:NV
N_
+
*

y o
0
e N indica la posicin del punto de inters.
Para especificar TADs basados en rboles con punto de inters hay que considerar algunas
operaciones que saquen partido del punto de inters. hay varias posibilidades:
Modificarla informacin asociada al punto de inters.
Insertar un nuevo nodo, o todo un rbol, como hijo del punto de inters.
Desplazar el punto de inters (e.j., al padre, al hermano izquierdo, al hermano derecho.).
rboles 422
5.2 Tcnicas de implementacin
Vamos a centrarnos en la implementacin de los rboles binarios, y trataremos ms brevemen-
te el caso de los rboles generales.
5.2.1 Implementacin dinmica de los rboles binarios
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:
Representacin que prohibe ciclos, pero no nodos compartidos:

R(p)

def

p=nilv
(p=nil.ubicado(p).R(p^.elem).
R(p^.iz).R(p^.dr).
peenlaces(p^.iz)enlaces(p^.der))

siendo
C sip=nil
enlaces(p)=
def

{p}enlaces(p^.iz)enlaces(p^.dr) sip=nil

Representacin que prohibe ciclos y nodos compartidos

R
NC
(p)

def

p=nilv
(p=nil.ubicado(p).R(p^.elem).
R
NC
(p^.iz).R
NC
(p^.dr).
peenlaces(p^.iz)enlaces(p^.der).
enlaces(p^.iz)enlaces(p^.dr)=C)
rboles 423
Esta representacin prohibe situaciones como:
x
1
x
2
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))sia=nil
Implementacin de las operaciones
Debido a la forma de las generadoras de este TAD, resulta natural realizar todas las operacio-
nes como funciones:

funcVaco()deva:Arbin[Elem]; /*O(1)*/
{P
0
:cierto}
inicio
a:=nil
{Q
0
:R(a).A=
ARBIN[ELEM]
Vaco}
deva
ffunc

funcCons(iz:Arbin[Elem];x:Elem;dr:Arbin[Elem])deva:
Arbin[Elem];
{P
0
:R(iz).R(x).R(dr)} /*O(1)*/
inicio
ubicar(a);
a.ra:=x;
a.iz:=iz;
a.dr:=dr
{Q
0
.R(a).A(a)=
ARBIN[ELEM]
Cons(A(iz),A(x),A(dr))}
deva
ffunc

Ntese que esta implementacin no garantiza R


NC
(a), ya que, por ejemplo:

a:=Cons(a1,x,a1)
rboles 424
funchijoIz(a:Arbin[Elem])deviz:Arbin[Elem]; /*O(1)*/
{P
0
:R(a).NOTesVaco(A(a))}
inicio
siesVaco(a)
entonces
error(Elrbolvaconotienehijoizquierdo)
sino
iz:=a^.iz
fsi
{Q
0
:R(iz).A(iz)=
ARBIN[ELEM]
hijoIz(A(a))}
deviz
ffunc

La funcin hijoDr es dual a sta. Tambin O(1).


funcraz(a:Arbin[Elem])devx:Elem; /*O(1)*/
{P
0
:R(a).NOTesVaco(A(a))}
inicio
siesVaco(a)
entonces
error(Elrbolvaconotieneraz)
sino
x:=a^.ra
fsi
{Q
0
:R(x).A(x)=
ARBIN[ELEM]
raz(A(a))}
devx
ffunc

funcesVaco(a:Arbin[Elem])devr:Bool;
{P
0
:R(a)}
inicio
r:=a==nil
{Q
0
:A(r)=
ARBIN[ELEM]
esVaco(A(a))}
devr
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))
rboles 425
Si no se dispone de recoleccin automtica de basura, entonces si el cliente crea estructuras
compartidas, es responsabilidad suya evitar que se libere el espacio de una estructura mientras
haya alguna variable activa que tenga acceso a dicho espacio.
En resumen, el TAD deber exportar las operaciones copia y anula.

procanula(esa:Arbin[Elem]);
{P
0
:R
NC
(a).a=A}
var
aux:Arbin[Elem];
inicio
sia==nil
entonces
seguir
sino
aux:=a^.iz;
anula(aux);
aux:=a^.dr;
anula(aux);
%ELEM.anula(a^.ra) silaanulacinesprofunda
liberar(a);
a:=nil
fsi
{Q
0
:a=nil.
elespacioqueocupabalaestructurarepresentantedeAsehaliberado
}
fproc
funccopia(a:Arbin[Elem])devb:Arbin[Elem];
{P
0
:R(a)}
var
iz,dr:Arbin[Elem];
inicio
sia==nil
entonces
b:=nil
sino
iz:=copia(a^.iz);
dr:=copia(a^.dr);
ubicar(b);
b^.ra:=a^.ra; %ELEM.copia(a^.ra) silacopiaesprofunda
b^.iz:=iz;
b^.dr:=dr
fsi
{Q
0
:R
NC
(b).A(a)=A(b).
laestructurarepresentantedebestubicadoenespacionuevo}
devb
ffunc
rboles 426
Tambin podra venir bien que el mdulo de los rboles exportase una operacin de igualdad.
5.2.2 Implementacin esttica encadenada de los rboles binarios
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 Representacin esttica secuencial para rboles binarios
semicompletos
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. Po-
demos 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 c, resulta:
8 9
4 6
1
2 3
5 7
10 11 12 13 14 15
1 2
2.2
2.1 1.2
1.1
1.1.1 1.1.2 1.2.1 1.2.2 2.1.1 2.1.2 2.2.1 2.2.2
Esta numeracin de posiciones corresponde a una biyeccin

ndice:{1,2}
*

que admite la siguiente definicin recursiva:

ndice(c) =1
ndice(o.1) =2*ndice(o)
ndice(o.2) =2*ndice(o)+1

rboles 427
Con ayuda de ndice, podemos representar un rbol binario en un vector, almacenando la in-
formacin del nodo o en la posicin ndice(o). Por ejemplo:
C
D
E
B
A
1 2
2.1
2.1.2
donde tenemos

ndice(c)=1
ndice(1)=2ndice(c)=2
ndice(2)=2ndice(c)+1=3
ndice(2.1)=2ndice(2)=6
ndice(2.1.2)=2ndice(2.1)+1=13

que dara lugar al vector:


A B C D E

1 2 3 4 5 6 7 8 9 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.
Un rbol binario de talla n se llama semicompleto si y slo si es completo o tiene vacantes
una serie de posiciones consecutivas del nivel n, de manera que al rellenar dichas posicio-
nes con nuevas hojas se obtiene un rbol completo.
Por ejemplo, el siguiente es un rbol completo
rboles 428
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 m
i
= 2
i1
2. El nmero total de nodos de un rbol binario completo de talla n es M
n
= 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 m
1
=1=2
11

i>1 m
i
=2m
i1
=
H.I.
22
i2
=2
i1

2. Aplicando el resultado de la suma de una progresin geomtrica:

M
n
=
_
=
n
i 1
m
i
=
_
=
n
i 1
2
i1
=2
n
1
Usando estos dos resultados podemos demostrar que la definicin recursiva de ndice presen-
tada anteriormente es correcta. La definicin no recursiva es como sigue: si o es un posicin de
nivel n+1 (n > 0)

ndice(o) =nmerodeposicionesdeniveles1..n+
nmerodeposicionesdeniveln+1hastaoinclusive
=(2
n
1)+m
m n+1
n+2
n
.1 .2
rboles 429
ndice(c)=1

ndice(o.1)= nmerodeposicionesdeniveles1..(n+1)+
nmerodeposicionesdeniveln+2hastao.1inclusive
= (2
n+1
1)+2(m1)+1=2
n+1
+2m2
= 2ndice(o)

ndice(o.2)= nmerodeposicionesdeniveles1..(n+1)+
nmerodeposicionesdeniveln+2hastao.2inclusive
= (2
n+1
1)+2(m1)+2=2
n+1
+2m1
= 2ndice(o)+1
Frmulas para la representacin indexada de rboles binarios
Recapitulando lo expuesto hasta ahora, tenemos que:
Un rbol binario completo de talla n tiene 2
n
1 nodos.
Por tanto, declarando
const
max=2
n
1;
tipo
esp=Vector[1..max]deElem;
tendremos un vector donde podemos almacenar cualquier rbol binario de talla s n
Dado un nodo o almacenado en la posicin ndice(o) = i, tal que 1 s i s max, valen las si-
guientes frmulas para el clculo de otro nodos relacionados con i:
Padre: i div 2, si i > 1
Hijo izquierdo: 2i, si 2i s max
Hijo derecho: 2i + 1, si 2i+1 s max
Esta representacin es til para rboles completos y semicompletos, cuando estos se ge-
neran 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 Implementacin de los rboles generales
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:
rboles 430
Informacin asociada.
Puntero al primer hijo.
Puntero al hermano derecho
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 ARBOL BINARIO
Primer hijo Hijo izquierdo
Hermano derecho Hijo derecho
Por ejemplo, el rbol general de la izquierda se representa como el rbol binario de la derecha:
La idea de esta transformacin se puede formalizar especificando las operaciones:
hazArbin:Bosque[Elem]Arbin[Elem]
hazBosque:Arbin[Elem]Bosque[Elem]

de manera que resulten dos biyecciones inversas una de la otra. La especificacin ecuacional
de estas operaciones:
hazArbin([])=ARBIN.Vaco
A
E F G
C D
I H
J
B
B
A
E
F
C
G D
H
I
J
rboles 431
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

En el libro de Franch se pueden encontrar ms detalles sobre esta implementacin.


5.2.5 Implementacin de otras variantes de rboles
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 espa-
cio?
La representacin esttica indexada en un vector se basa en frmulas de clculo de ndices
que generalizan las del caso binario.
En un rbol completo de talla t
el nivel i (1 s i s t) tiene n
i1
nodos
el rbol completo tiene max = (n
t
1) /( n 1) nodos
La biyeccin ndice : {1 .. n}
*

+
que numera las posiciones, admite la definicin
recursiva:
num(c)=1
num(o.i)=n*num(o)+(i1) (1sisn)
Dado un nodo o con posicin ndice(o)=m, 1 s m s max, se tiene:
Hijo i de o: n*m + i1, si s max
Padre de o: m div n, si m > 1.
Arboles con punto de inters
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.
rboles 432
5.2.6 Anlisis de complejidad espacial para las representaciones
de los rboles
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.
Empleando el esquema primer hijo-hermano derecho, podemos reducirnos a 2 campos
de enlace por nodo, y el espacio ocupado descender a (2+x) n.
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 reco-
rrido 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)
En profundidad Inorden (IRD)
Recorridos Postorden (IDR)
Por niveles
Los recorridos en profundidad se basan en la relacin padre-hijos y se clasifican segn el or-
den en que se consideren la raz (R), el hijo izquierdo (I) y el hijo derecho (D).
Ejemplo:
4
2
1
3
6 5
8 9
7
Preorden: 1, 2, 4, 5, 8, 3, 6, 9, 7
Inorden: 4, 2, 8, 5, 1, 9, 6, 3, 7
Postorden: 4, 8, 5, 2, 9, 6, 7, 3, 1
Niveles: 1, 2, 3, 4, 5, 6, 7, 8, 9
rboles 433
Los recorridos de rboles tienen aplicaciones muy diversas:
Preorden: puede aplicarse al clculo de atributos heredados en una gramtica de atributos
(los hijos heredan, y quiz modifican, atributos del padre).
Inorden: en rboles ordenados que estudiaremos ms adelante este recorrido produce
una lista ordenada.
Postorden: en rboles que representan expresiones formales, este recurrido corresponde a
la evaluacin de la expresin, y genera la forma postfija de sta.
Niveles: para rboles que representen espacios de bsqueda, este recorrido encuentra ca-
minos 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]Lista[Elem] /*obs*/
ecuaciones
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]
ftad
Las operaciones de recorrido no necesitan acceder a la representacin interna de los rboles;
ntese cmo las hemos especificado como un enriquecimiento del TAD ARBIN.
Implementacin recursiva
La implementacin recursiva es directa a partir de la especificacin. Veamos como ejemplo la
implementacin del preOrden:
funcpreOrd(a:Arbin[Elem])devxs:Lista[Elem];
{P
0
:cierto}
var
iz,dr:Lista[Elem];
inicio
siARBIN.esVaco(a)
entonces
rboles 434
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
{Q
0
:xs=preOrd(a)}
devxs
ffunc
En cuanto a la complejidad de esta operacin, suponemos una implementacin de las lis-
tas donde Nula, conc, ponDr y Cons tengan coste O(1) lo que nos obliga a utilizar implementacio-
nes 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:
c
1
si 0 n < b
T(n) =
a T(n/b) + c n
k
si n b
O(n
k
) si a < b
k
T(n) e O(n
k
log n) si a = b
k
O(
a
b
n
log
) si a > b
k
si suponemos que se trata de un rbol completo, entonces tenemos:

a=2 nmerodellamadasrecursivas
b=2 sielrbolescompleto,eltamaosedividepor2encadallamada
k=0

a > b
k
coste O(
a
b
n
log
) = O(n)
Para que este anlisis sea vlido (k = 0) es preciso que las operaciones hijoIz, hihoDr y raz sean
O(1), lo cual se cumple en las implementaciones habituales de los rboles binarios. Concretamen-
te, si la implementacin es dinmica, hijoIz e hijoDr devolvern un puntero sin crear ningn nodo
nuevo.
Implementacin iterativa con ayuda de una pila
De todos es sabido que la ejecucin de algoritmos recursivos es ms costosa que la de algo-
ritmos iterativos de la misma complejidad. Es por ello que nos plateamos la realizacin iterativa
de los recorridos en profundidad.
rboles 435
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
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
c
+
2, 1
+ *
2, 1.2, 1.1
+ * x
2, 1.2
+ * x
2, 1.2.1
+ * x y
2
etc.
Desarrollemos ahora formalmente esta idea.
Dada cualquier operacin de recorrido de rboles:

R:Arbin[Elem]Lista[Elem]

podemos generalizarla obteniendo otra operacin que acumula el efecto de R sobre una pila as
de rboles, concatenando las listas resultantes. Formalmente:
acumula
R
:Pila[Arbin[Elem]]Lista[Elem]
acumula
R
(PilaVaca)=[]
acumula
R
(Apilar(a,as))=R(a)++acumula
R
(as)
rboles 436
Usaremos las operaciones acumula
R
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]Nat
tamao(Vaco) =0
tamao(Cons(iz,x,dr)) =1 siesVaco(ix)AND
esVaco(dr)
=2+tamao(iz) siNOTesVaco(ix)AND
esVaco(dr)
=2+tamao(dr) siesVaco(ix)ANDNOT
esVaco(dr)
=3+tamao(iz)
+tamao(dr) siNOTesVaco(ix)ANDNOT
esVaco(dr)

Para no perjudicar la eficiencia, utilizamos la implementacin procedimental de ponDr:


procponDr(esxs:Lista[Elem];ex:Elem);

Recorrido en preorden:

funcpreOrd(a:Arbin[Elem])devxs:Lista[Elem];
{P
0
: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++acumula
preOrd
(as);
C:sumadelostamaosdelosrbolesdeas}
itNOTPILA.esVaca(as)
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
rboles 437
sino
PILA.apilar(dr,as)
fsi;
siARBIN.esVaco(iz)
entonces
seguir
sino
PILA.apilar(iz,as)
fsi
fit
fsi
{Q
0
:xs=preOrd(a)}
devxs
ffunc
Recorrido en postorden:
funcpostOrd(a:Arbin[Elem])devxs:Lista[Elem];
{P
0
: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++acumula
postOrd
(as);
C:sumadelostamaosdelosrbolesdeas}
itNOTPILA.esVaca(as)
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
rboles 438
PILA.apilar(dr,as)
fsi;
siARBIN.esVaco(iz)
entonces
seguir
sino
PILA.apilar(iz,as)
fsi
fsi
fit
fsi
{Q
0
:xs=postOrd(a)}
devxs
ffunc
Recorrido en inorden:
funcinOrd(a:Arbin[Elem])devxs:Lista[Elem];
{P
0
: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++acumula
inOrd
(as);
C:sumadelostamaosdelosrbolesdeas}
itNOTPILA.esVaca(as)
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)
rboles 439
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
{Q
0
:xs=inOrd(a)}
devxs
ffunc
Si todas las operaciones involucradas de pilas, rboles y listas tienen complejidad O(1) enton-
ces 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
C
E
B
D
G F
I H
Para un rbol de esta forma con n nodos:
preOrd llega a crea una pila con n/2 nodos:
H
I
G
E
C
rboles 440
inOrd y postOrd llegan a crear una pila con n nodos.
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 ca-
so peor de construir pilas con n elementos.
Con un rbol totalmente degenerado hacia la derecha tenemos el caso mejor para inOrd por-
que 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 Recorrido por niveles
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
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
c
+
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.
rboles 441
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]Lista[Elem] /*obs*/
operacionesprivadas
nivelesCola:Cola[Arbin[Elem]]Lista[Elem] /*obs*/
ecuaciones
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))
ftad
Implementacin iterativa con ayuda de una cola
A partir de la especificacin algebraica es sencillo llegar a una implementacin iterativa. el in-
variante del bucle utiliza las dos operaciones de la especificacin:
niveles(a)=xs++nivelesCola(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];
{P
0
:cierto}
var
as:Cola[Arbin[Elem]];
aux,iz,dr:Arbin[Elem];
x:Elem:
inicio
LISTA.Nula(xs);
siARBIN.esVaco(a)
rboles 442
entonces
seguir
sino
COLA.ColaVaca(as);
COLA.Aadir(a,as);
{I:niveles(a)=xs++nivelesCola(as).
todoslosrbolesdelacolaassonnovacos;
C:sumadelostamaosdelosrbolesdeas}
itNOTCOLA.esVaca(as)
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
{Q
0
:xs=niveles(a)}
devxs
ffun

En cuanto a la complejidad, si todas las operaciones involucradas de pilas, colas y rboles


tienen complejidad O(1), entonces el recorrido por niveles resulta de O(n), siendo n el nmero de
nodos. La razn es que cada nodo pasa una nica vez por la primera posicin de la cola.
En cuanto al espacio, si suponemos una implementacin dinmica para ARBIN y COLA,
cada nodo de la cola ocupar espacio 2 (1 puntero al rbol + 1 puntero al siguiente).
Cuando la cola est encabezada por un subrbol a
0
del rbol inicial a, tal que la raz de a
0
est a
nivel n, puede asegurarse que el resto de la cola slo contiene subrboles de nivel n (ms a la
derecha que a
0
) o n+1 (hijos de subrboles de nivel n a la izquierda de a
0
). El caso peor se dar
para un rbol completo, justo despus de visitar el ltimo nodo del penltimo nivel del rbol,
cuando la cola contendr todos los nodos del ltimo nivel, es decir, para un rbol de talla t
tendremos 2
t1
nodos.
rboles 443
5.3.3 Arboles binarios hilvanados
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 aprove-
char 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 norma-
les o hilvanes.
5.3.4 Transformacin de la recursin doble a iteracin
El esquema de la transformacin est en los apuntes. La idea es que la ejecucin de un algo-
ritmo 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.
5.4 Arboles de bsqueda
El almacenamiento ordenado de elementos es til es muchas aplicaciones, como por ejemplo
en la implementacin eficiente de conjuntos y tablas, o de algoritmos eficientes de ordenacin.
Sabemos que la bsqueda en un vector ordenado que contenga n elementos es posible en
tiempo O(log n). Pero otras operaciones en vectores ordenados, tales como insercin y borrado,
exigen tiempo O(n) en el caso peor.
Las representaciones enlazadas de colecciones de datos que hemos estudiado hasta hora
pilas, listas, colas, permiten obtener implementaciones de las inserciones y supresiones en
tiempo O(1), sin embargo las bsquedas son de complejidad O(n) en el caso peor. La razn es
que no tenemos una operacin de acceso directo con coste O(1), lo cual impide realizar una
bsqueda binaria en colecciones implementadas con una representacin enlazada. Lo ms que
podemos conseguir, si tenemos los elementos ordenados, es una complejidad O(n/2) en el caso
promedio.
Los rboles de bsqueda son una solucin a este problema porque permiten realizar las tres
operaciones insercin, borrado y bsqueda en tiempo O(log n). Y adems permiten obtener
una lista ordenada de todos los elementos en tiempo O(n).
Un requisito bsico para este tipo de rboles es que debe ser posible establecer un orden entre
los elementos. Esto puede definirse de distintas formas, ya sea porque exista una funcin aplica-
ble sobre los elementos que de resultados en un dominio ordenado, o porque los datos estn
formados por parejas de valores: la informacin y el valor en el dominio ordenado. Para presentar
los conceptos bsicos nos ocuparemos de una simplificacin de los rboles de bsqueda donde
son los propios elementos los que pertenecen a un dominio ordenado, para luego generalizar esa
restriccin a otros tipos de elementos.
rboles 444
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.
Por ejemplo, el siguiente es un rbol ordenado:
26 7
31 12
43 17
35
20
Ntese que en la anterior definicin no se admiten elementos repetidos.
Podemos especificar algebraicamente una operacin que reconozca si un rbol binario est
ordenado:
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 produ-
ce 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:1si<js#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 inor-
den. Por ejemplo, el siguiente rbol produce el mismo recorrido que el presentado anteriormente:
26
7
31 12 43
17
35 20
rboles 445
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 re-
sultado 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 es vaco, entonces el resultado es un rbol con y en la raz e hijos vacos.
Si a no es vaco:
Si y coincide con la raz de a entonces el resultado es a.
Si y es menor que la raz de a, entonces se inserta y en el hijo izquierdo de a.
Si y es mayor que la raz de a, entonces se inserta y en el hijo derecho de a.
Algebraicamente se puede especificar la insercin de la siguiente forma:
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 obte-
ner 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
rboles 446
Borrado en un rbol ordenado
La operacin de borrado es la ms complicada, porque tenemos que reconstruir el rbol resul-
tado de la supresin.
Se busca el valor y en el rbol. Si la bsqueda fracasa, la operacin termina sin modificar el
rbol. Si la bsqueda tiene xito y localiza un nodo de posicin o, el comportamiento de la ope-
racin depende del nmero de hijos de o:
Si o es una hoja, se elimina el nodo o
Si o tiene un solo hijo, se elimina el nodo o y se coloca en su lugar el rbol hijo, cuya raz
quedar en la posicin o
Si o tiene dos hijos se procede del siguiente modo:
Se busca el nodo con el valor mnimo en el hijo derecho de o. Sea o la posicin de
ste. (Alternativamente, se podra buscar el mayor nodo del hijo izquierdo de o.)
El elemento del nodo o se reemplaza por el elemento del nodo o.
Se borra el nodo o. Ntese que, por ser el mnimo del hijo derecho de o, o no pue-
de 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)
26 7
31 12
43 17
20
borra(43, a)
26 7
31 12
43 17
20
borra(31, a)
rboles 447
26 7
12
43 17
35
20
Especificacin algebraica del TAD ARB-ORD
Con todo lo anterior podemos presentar la especificacin algebraica del TAD ARB-ORD. Lo
especificamos como un enriquecimiento del TAD REC-PROF-ARBIN porque queremos que
incluya una operacin de recorrido que es precisamente el recorrido en inorden especificado en
dicho TAD, el cual a su vez, es un enriquecimiento del TAD ARBIN, de donde importamos el
tipo Arbin[Elem], junto con sus operaciones. Para hacer ms legible la especificacin, renombra-
mos el tipo importado de Arbin[Elem] a Arbus[Elem] y la operacin inOrden a recorrido; este es un
mecanismo que ya habamos utilizado anteriormente. Aparece as mismo un mecanismo nuevo: la
ocultacin de identificadores importados. De esta forma limitamos la interfaz eliminando de ella
operaciones importadas que no tienen sentido sobre el TAD que estamos especificando. El resul-
tado es que este TAD exporta el tipo Arbus[Elem] equipado con las operaciones: Vaco, raz, es-
Vaco, recorre, inserta, busca, borra y est?.
Ntese tambin que la operacin privada ordenado? no se utiliza en la especificacin de las ope-
raciones exportadas. La razn de incluir esta operacin es poder luego utilizarla en los razona-
mientos formales con rboles ordenados: asertos, invariante de la representacin,
tadARBORD[E::ORD]
usa
RECPROFARBIN[E] renombrandoinOrdarecorre
Arbin[Elem]aArbus[Elem]
ocultandopreOrd,postOrd,
Cons,hijoIz,hijoDr
tipo
Arbus[Elem]
operaciones
inserta:(Elem,Arbus[Elem])Arbus[Elem]/*gen*/
busca:(Elem,Arbus[Elem])Arbus[Elem]/*mod*/
borra:(Elem,Arbus[Elem])Arbus[Elem]/*mod*/
est?:(Elem,Arbus[Elem])Bool /*obs*/
operacionesprivadas
ordenado?:Arbus[Elem]Bool/*obs*/
min:Arbus[Elem]Elem /*obs*/
mayor,menor:(Arbus[Elem],Elem)Bool/*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)
rboles 448
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
Complejidad de las operaciones
Claramente, la complejidad de todas las operaciones est determinada por la complejidad de la
bsqueda. El tiempo de una bsqueda en el caso peor es O(t), siendo t la talla del rbol.
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 decre-
ciente.
Se puede demostrar que el promedio de las longitudes de los caminos en un rbol de
bsqueda generado por la insercin de una sucesin aleatoria de n elementos con claves
distintas es asintticamente
t
n
= 2 (ln n + + 1)
siendo = 0,577 la constante de Euler, y suponiendo que las n! permutaciones posibles
de los nodos que se insertan son equiprobables (vase N. Wirth, Algorithms and Data Struc-
tures, Prentice Hall, 1986, pp. 214-217).
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.
rboles 449
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 tra-
bajar 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, dedica-
do a los rboles AVL.
No nos ocupamos de la implementacin de los rboles ordenados, ya que slo los hemos in-
troducido 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 com-
paracin. Sin embargo, en muchas aplicaciones las comparaciones entre elementos se puede esta-
blecer 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, pode-
mos suponer que el DNI es nico y que, por lo tanto podemos identificar a una persona simple-
mente 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:ElemClave

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 al-
macenaran 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 signa-
tura:

():(Elem,Elem)Elem
rboles 450
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)Elem
%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 pu-
disemos, al declarar una variable concreta de tipo Arbus, indicar explcitamente cul es la opera-
cin que se corresponde con combina.
La idea de separar las claves de los datos, e indicar cul es la forma de combinar datos, se pue-
de 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])Arbus[Cla,Val] /*gen*/
busca:(Cla,Arbus[Cla,Val])Arbus[Cla,Val]/*mod*/
borra:(Cla,Arbus[Cla,Val])Arbus[Cla,Val]/*mod*/
est?:(Cla,Arbus[Cla,Val])Bool /*obs*/
operacionesprivadas
ordenado?:Arbus[Cla,Val]Bool /*obs*/
min:Arbus[Cla,Val]Pareja[Cla,Val] /*obs*/
mayor,menor:(Arbus[Cla,Val],Cla)Bool/*obs*/
ecuaciones
c,c,d:Cla:x,x,y:Val:a,iz,dr:Arbus[Cla,Val]:
rboles 451
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
Implementacin de los rboles de bsqueda
Siguiendo las indicaciones de la especificacin, nos podramos plantear la implementacin de
los rboles de bsqueda mediante un mdulo cliente del mdulo de los recorridos y el mdulo de
los rboles binarios que realizase las operaciones como funciones sin acceder a la representacin
interna de los rboles. Veamos cmo sera la operacin de insercin en rboles ordenados, si-
guiendo directamente la idea de la especificacin algebraica:

funcinserta(x:Elem;a:Arbus[Elem])devb:Arbus[Elem];
{P
0
:R(x).R(a)}
inicio
siesVaco(a)
entonces
b:=Cons(Vaco,x,Vaco)
sino
rboles 452
si
ORD.igual(x,raz(a))b:=a
ORD.menor(x,raz(a))b:=Cons(inserta(x,hijoIz(a)),raz(a),
hijoDr(a))
ORD.mayor(x,raz(a))b:=Cons(hijoIz(a),raz(a),
inserta(x,hijoDr(a)))
fsi
fsi
{Q
0
: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 pro-
blema similar se plantea con la operacin de borrado
Vemoslo con un ejemplo. Dado el rbol a
17
21
27
33
9
25

si ejecutamos la operacin b := inserta(19, a), el resultado ser


17
21
27
33
9
25
17
21
27
19
a
b

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-
rboles 453
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 es-
tructura 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 comparti-
cin de estructura, y que el resultado del recorrido en inorden est ordenado.
Dado p : Enlace

R
Arbus[Cla,Val]

def

rboles 454
R
NC
Arbin[Elem]
(p).
i,j:1si<js#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];
{P
0
:R(d).R(a)}
inicio
sia==nil
entonces
b:=nil
sino
si
ORD.igual(d,a^.cla)b:=a
ORD.menor(d,a^.cla)b:=busca(d,a^.hi)
ORD.mayor(d,a^.cla)b:=busca(d,a^.hd)
fsi
fsi
{Q
0
:R(b).A(b)=
ARBUSCA[CLA,VAL]
busca(A(d),A(a))}
devb
ffunc

procinserta(ed:Cla;ey:Val;esa:Arbus[Cla,Val]);
{P
0
: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)a^.val:=COMB.combina(a^.val,y)
ORD.menor(d,a^.cla)inserta(d,y,a^.hi) /*ref*/
ORD.mayor(d,a^.cla)inserta(d,y,a^.hd) /*ref*/
fsi
rboles 455
fsi
{Q
0
: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 ope-
racin tenga acceso a la representacin interna de los rboles. En las siguientes operaciones tam-
bin 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]);
{P
0
:R(d).R(a).a=A}
inicio
sia==nil
entonces
seguir
sino
si
ORD.igual(d,a^.cla)borraRaz(a)
ORD.menor(d,a^.cla)borra(d,a^.hi)
ORD.mayor(d,a^.cla)borra(d,a^.hd)
fsi
fsi
{Q
0
:R(a).A(a)=
ARBUSCA[CLA,VAL]
borra(A(d),A(A))}
fproc

Las operaciones auxiliares de borrado:

procborraRaz(esa:Arbus[cla,Val]);
{P
0
:R(a).a=A.a/=nil}
var
vacoIz,vacoDr:Bool;
aux:Arbus[Cla,Val];
inicio
vacoIz:=a^.hi==nil;
vacoDr:=a^.hd==nil;
si
vacoIzaux:=a;
%COMB.anula(a^.val);
a:=a^.hd;
liberar(aux)
vacoDraux:=a;
%COMB.anula(a^.val);
a:=a^.hi;
liberar(aux)
rboles 456
NOTvacoIzANDNOTvacoDrborraConMin(a,a^.hd)
fsi
{Q
0
: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 lle-
gar al menor, y en ese punto realizamos el borrado

procborraConMin(esa,b:Arbus[cla,Val]);
{P
0
:a=A.R(a).a/=nil.a^.hi/=nil.a^.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
{Q
0
:R(a).A(a)=
ARBUSCA[CLA,VAL]
borra(A(A^.cla),A(A))}
Fproc

5.5 Arboles AVL


5.5.1 Arboles equilibrados
En el tema anterior vimos cmo en los rboles de bsqueda se consigue una complejidad lo-
gartmica 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 n
0
e , c e J
+
tales
que para todo n > n
0
y todo rbol a e F con n nodos se tenga talla(a) s c log n.
Efectivamente, para rboles pertenecientes a una familia equilibrada la complejidad de las ope-
raciones 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
rboles 457
La familia de los rboles equilibrados en nmero de nodos. Un rbol pertenece a esta fa-
milia 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 cual-
quier subrbol suyo cumple que la talla del hijo izquierdo y la talla del hijo derecho difie-
ren a lo sumo en 1:
Ntese que, como se puede observar en los anteriores ejemplos, todos los rboles semicom-
pletos son equilibrados en altura, pero que existen rboles equilibrados en altura que no son se-
micompletos. 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)st<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 > 0, el rbol de Fibonacci a
t
y su nmero de nodos n
t
se construyen recursivamente como sigue:
t = 0 a
0
= Vaco n
0
= 0
t = 1 a
1
= Cons(a
0
, 1, a
0
) n
1
= 1
t > 2 a
t
= Cons( a
t2
, i, a
t1
[+ i]) n
t
= n
t2
+ 1 + n
t1
donde
i es el mnimo valor > 1 que no aparece en a
t2
a
t1
[+ i] es el rbol que se obtiene sumando i a cada nodo de a
t1
rboles 458
Por ejemplo:
a
1
1
a
2
1
2
a
3
4
3 1
2
a
4
2 4 6
5 1
3
7
Los nmeros n
t
se llaman nmeros de Leonardo. Para los rboles de Fibonacci a
t
se alcanza el
caso peor de la estimacin anterior

t~14404log(n+2)0328~15logn
Caracterizacin de los rboles AVL
Podemos caracterizar formalmente las condiciones que deben cumplir los rboles AVL:

avl?(a) =ordenado?(a)ANDequilibrado?(a)

ordenado?(Vaco) =cierto
ordenado?(Cons(iz,Par(u,x),dr)) =ordenado?(iz)ANDmenor(iz,u)AND
ordenado?(dr)ANDmayor(dr,u)

menor(Vaco,u) =cierto
rboles 459
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)|s1

talla(Vaco) =0
talla(Cons(iz,us,dr)) =1+max(talla(iz),talla(dr))

5.5.2 Operaciones de insercin y borrado


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:
Insercin o borrado como en un rbol de bsqueda ordinario.
Reequilibrado del nuevo rbol, si ste h dejado de ser AVL.
El reequilibrado se consigue con ayuda de dos operaciones bsicas, llamadas rotaciones.
Rotaciones
Las rotaciones son operaciones que modifican la estructura de un rbol ordenado, llevando no-
dos de un subrbol a otro para resolver as un desequilibrio en altura.
La rotacin a la derecha lleva nodos del subrbol izquierdo al derecho, mientras que la rotacin a la
izquierda lleva nodos del subrbol derecho al izquierdo. Grficamente:
y
x
a b
c
x
y
b c
a
rotDr
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.
rboles 460
Formalmente podemos expresar las rotaciones como:

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 desequi-
librado para proceder a reequilibrarlo con ayuda de rotaciones. Para caracterizar las distintas si-
tuaciones 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 ge-
neradoras:

tipoprivado
FactEq
operacionesprivadas
DI:FactEq /*gen*/
EQ:FactEq /*gen*/
DD:FactEq /*gen*/

Y una operacin que obtiene el factor de equilibrio de un rbol dado:


factEq:Arbus[Cla,Val]FactEq/*obs*/
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 explci-
tamente 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 se-
guida 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 reequili-
brado 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:
rboles 462
x
t+1
t

Caso [Dr]: a tena factor de equilibrio DD. La insercin se ha producido en el hijo dere-
cho de a, y a ha quedado:
x
t
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:
[DrDr]: se ha insertado en el subrbol derecho del subrbol derecho de a.
Se reequilibra realizando una rotacin a la izquierda del rbol.

rboles 463
y
x
t
t t
a
0
b
0
c
0
x
y
t
t t
c
0
b
0
a
0
rotIz
en

Ntese que b
0
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 ta-
lla t+2 que el rbol original a. Por lo tanto, si a era un subrbol de un rbol mayor, tam-
bin ste ha quedado equilibrado.

Podemos verlo aplicado a un ejemplo:


rotIz
en
2
4
8
6 10
2
4
8
6
11
10
4
2
8
10
6 11
Insertar
11
[DrIz]: se ha insertado en el subrbol izquierdo del subrbol derecho de a.
Se reequilibra haciendo una rotacin a la derecha del subrbol derecho y una rotacin a
izquierda del resultado.
Dentro de este subcaso hay un caso trivial, cuando el hijo izquierdo del hijo derecho de a
es vaco (como a estaba equilibrado, esta condicin equivale a que el hijo derecho de a sea
una hoja):
rboles 464
rotDr
en 2
x
y
Insertar
x
y
x
y
x
y
rotIz
en
Aunque hemos hecho la transformacin utilizando las dos rotaciones de la solucin gene-
ral, 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
en 2
rotIz
en
z
x
t-1
t
t
a
0
c
0
d
0
b
0
t-1
y
y
z
x
t-1
t
t
a
0
c
0
d
0
b
0
t-1
nuevo < z
nuevo < z
nuevo > z
nuevo > z
y
z
t-1
t t
a
0
c
0
d
0
b
0
t-1
x
rboles 465
Tanto si el nuevo nodo ha quedado en b
0
como si ha quedado en c
0
, el rbol resultante
queda AVL con factor de equilibrio EQ, y la misma talla t+2 que el rbol original a. Si a
era subrbol de un rbol mayor, ste habr quedado equilibrado.
Veamos cmo funciona en un ejemplo:
rotIz
rotDr
en 2
en
2
4
8
6 10
2
4
8
6
7
10
Insertar
7
2
4 8
6
7 10
2
4
8
6
7 10
Con todo esto la especificacin de la operacin de reequilibrado a la derecha:

defreeqDr(Cons(iz,ux,dr))sitalla(dr)==talla(iz)+2

%Caso[DrDr]:
reeqDr(Cons(iz,ux,dr)) =
d
rotIz(Cons(iz,ux,dr))sifactEq(dr)/=DI

%Caso[DrIz]:
reeqDr(Cons(iz,ux,dr))=
d
rotIz(Cons(iz,ux,rotDr(dr)))si
factEq(dr)==DI

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 su-
presin 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.
rboles 466
En cuanto a la complejidad del algoritmo de insercin, el anlisis matemtico an no est re-
suelto 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 desequi-
librio son equiprobables, y, como acabamos de ver, un desequilibrio siempre se puede co-
rregir 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 comparti-
cin de estructura, y que el resultado del recorrido en inorden est ordenado.
Dado p : Enlace

R
AVL
Arbus[Cla,Val]

def

R
NC
Arbin[Elem]
(p).
i,j:1si<js#inOrd(p):inOrd(p)!!i<inOrd(p)!!j.
rboles 467
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]);
{P
0
:R(d).R(y).R(a).a=A}
var
crece?:Boolean;
inicio
insertaAVL(d,y,a,crece?)
{Q
0
R(a).A(a)=
AVL[CLA,VAL]
inserta(A(d),A(y),A(A))}
fproc

Procedimiento privado auxiliar de insercin

procinsertaAVL(ed:Cla;ey:Val;esa:Arbus[Cla,Val];screce?:
Bool);
{P
0
: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;
a^.feq:=EQ;
crece?:=cierto
sino
si
ORD.igual(d,a^.cla)a^.val:=COMB.combina(a^.val,y);
crece?:=falso
ORD.menor(d,a^.cla)insertaAVL(d,y,a^.hi,crece?)
rboles 468
siNOTcrece?
entonces
seguir
sino
casoa^.feqde
DDa^.feq:=EQ;crece?:=falso
EQa^.feq:=DI
DIreeqIz(d,a);crece?:=falso
fcaso
fsi
ORD.mayor(d,a^.cla)insertaAVL(d,y,a^.hd,crece?)
siNOTcrece?
entonces
seguir
sino
casoa^.feqde
DIa^.feq:=EQ;crece?:=falso
EQa^.feq:=DD
DDreeqDr(d,a);crece?:=falso
fcaso
fsi
fsi
fsi
{Q
0
: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 indi-
que 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]);
{P
0
:a=A.R
ARBUSCA[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
rboles 469
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)a^.hi^.feq:=EQ;
a^.hd^.feq:=DD
ORD.mayor(d,a^.cla)a^.hi^.feq:=DI;
a^.hd^.feq:=EQ
fsi
fsi
fsi
{Q
0
:R
AVL[CLA,VAL]
(a).recorre(A(a))=
ARBUSCA[CLA,VAL]
recorre(A(A))}
fproc
Procedimiento auxiliar privado de reorganizacin a la derecha:

procreorgDr(esa:Arbus[Cla,Val]);
{P
0
:a=A.R
ARBUS[CLA,VAL]
(a).a^.hi==nil.a^.hd/=nil.
a^.hd^.hd==nil.a^.hd^.hi/=nil.
a^.hd^.hi^.hi==nil.a^.hd^.hi^.hd==nil}
var
aux:Enlace;
inicio
aux:=a^.hd^.hi;
aux^.hi:=a;
aux^.hd:=a^.hd;
aux^.hi^.hd:=nil;
aux^.hd^.hi:=nil;
a:=aux
{Q
0
:R
AVL[CLA,VAL]
(a).recorre(A(a))=
ARBUS[CLA,VAL]
recorre(A(A))}
fproc

Procedimiento auxiliar privado de rotacin a la derecha

procrotDr(esarb:Arbus[Cla,Val]);
{P
0
:arepresentaunrboldelaformaCons(Cons(a,ux,b),vy,c)}
var
aux:Enlace;
inicio
aux:=arb;
rboles 470
arb:=arb^.hi;
arb^.hi:=arb^.hd;
arb^.hd:=aux
{Q
0
:arbrepresentaelrbolCons(a,ux,Cons(b,vy,c)),
siendoa,ux,b,vy,closmismodeP
0
}
fproc
Borrado
La idea es que este proceso, como ya indicamos antes, consiste en un borrado ordinario segui-
da 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*/

La operacin que se encarga de eliminar la raz de un subrbol:

defquitaRaz(Cons(iz,ux,dr))
quitaRaz(Cons(iz,ux,dr)) =dr
siesVaco(iz)

quitaRaz(Cons(iz,ux,dr)) =iz
rboles 471
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 rotacio-
nes. Puede suceder ahora que el reequilibrio produzca un subrbol cuya talla sea una unidad me-
nor 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 con-
vierte 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:
x
t-1
t+1

Caso [Dr]: a tena factor de equilibrio DD. El borrado se ha producido en el hijo izquier-
do de a, y a ha quedado:
rboles 472
x
t-1
t+1

Estudiamos el caso [Dr], dejando [Iz] que es simtrico como ejercicio.


En el caso [Dr], el hijo derecho del rbol inicial a tiene que ser no vaco y con dos o ms no-
dos, ya que en caso contrario un borrado en el hijo izquierdo, no podra haber causado desequili-
brio. Tenemos pues que a es de la forma:
y
x
t-1
t+1
a
0
b
0
c
0
Hacemos la distincin de casos dependiendo de la talla de los subrboles b
0
y c
0
.
talla(b
0
) = t, talla(c
0
) = 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:
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.
y
x
t-1
t t
a
0
a
0
b
0
c
0
x
y
t
t
t-1
c
0
b
0
rotIz
en
rboles 473
talla(b
0
) = t1, talla(c
0
) = t
Es decir, tenemos que el factor de equilibrio del hijo derecho de a es DD.
El reequilibrio se logra con la misma rotacin del caso anterior.
y
x
t-1
t-1
t
a
0
a
0 b
0
c
0
x
y
t
t-1 t-1
c
0
b
0
rotIz
en
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(b
0
) = t, talla(c
0
) = 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
en 2
rotIz
en
z
x
t-1? t-1
t-1
t
a
0
e
0
c
0
d
0
t-1?
y
y
z
x
t-1
t
a
0
e
0
c
0
d
0
y
z
t t
a
0 e
0
c
0
d
0
x
rboles 474
Donde hay combinaciones posibles para las tallas de d
0
y e
0
. Si f es el factor de equilibrio
de
z
e
0
d
0
se tiene
f talla(d
0
) talla(e
0
)
EQ t1 t1
DI t1 t2
DD t2 t1
Ntese que al menos uno de los dos rboles d
0
, e
0
debe tener talla t1, con lo cual es segu-
ro 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 ma-
yor, 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 encon-
trados 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.
Sorprendentemente, las pruebas empricas indican que en promedio se efectan:
1 rotacin cada 2 inserciones
1 rotacin cada 5 borrados.
rboles 475
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-
tos: especificacin, diseo e implementacin, con sus divisiones en captulos, secciones y sub-
secciones.

(d) Un rbol que represente la organizacin jerrquica del sistema de directorios y archi-
vos en el selvtico PC del profesor Tadeo de la Tecla.

(e) El rbol de llamadas inducido por la llamada inicial fib(4) (cfr. ejercicio 83).

*(f) El rbol de anlisis sintctico de la expresin aritmtica (2+x)*(y3)

*(g) 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 repre-
sente el comportamiento de los rboles generales, con operaciones adecuadas para cons-
truirlos 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
comportamiento de los rboles binarios.
242. Partiendo de la especificacin del ejercicio anterior, aade ecuaciones que formalicen el
comportamiento de las operaciones siguientes:

talla:Arbin[Elem]Nat
Calcula la talla (altura, profundidad) de un rbol binario, definida como el
nmero de nodos de la rama ms larga.
numNodos:Arbin[Elem]Nat
Calcula el nmero de nodos de un rbol binario.
numHojas:Arbin[Elem]Nat
Calcula el nmero de hojas de un rbol binario.
rboles 476
243. Sea un rbol binario de talla n. Se define:
(C) a es completo syss todos sus nodos internos tienen dos hijos no vacos, y todas sus
hojas estn en el nivel n.

(S) a es semicompleto syss a es completo o tiene vacantes una serie de posiciones consecu-
tivas del nivel n, de manera que al rellenar dichas posiciones con nuevas hojas se ob-
tiene un rbol completo.
Se pide:
(a) Dibuja ejemplos de rboles binarios completos, semicompletos y no semicompletos.

(b) Demuestra que un rbol binario completo de talla t > 1 tiene 2


t1
hojas y un total de
2
t
1 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 opera-
ciones siguientes:

esCompleto:Arbin[Elem]Bool
Reconoce si un rbol binario dado es completo.
esSemiCompleto:Arbin[Elem]Bool
Reconoce si un rbol binario dado es semicompleto.
245. Un rbol general a siempre se puede representar por medio de un rbol binario b, construi-
do segn la idea siguiente
a y b tienen el mismo nmero de nodos. Cada nodo o de a est representado por un
nodo | de b.
El paso de un nodo o a su primer hijo en a se corresponde con el paso de | a su hijo
izquierdo en b.
El paso de un nodo o a su hermano derecho en a se corresponde con el paso de | a su
hijo derecho en b.
Dibuja un rbol general con 10 nodos, que no sea un rbol binario, y dibuja su representacin
como rbol binario, siguiendo la idea anterior.
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 bina-
rios 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
las dos operaciones siguientes:

hazArbin:Bosque[Elem]Arbin[Elem]
Construye el rbol binario que representa un bosque dado.
hazBosque:Arbin[Elem]Bosque[Elem]
Construye el bosque representado por un rbol binario dado.
248. Las operaciones hazArbin y hazBosque son inversas una de otra. Verifcalo demostrando por
induccin lo que sigue:
(a) as:Bosque[Elem]:hazBosque(hazArbin(as))=as

rboles 477
(b) b:Arbin[Elem]:hazArbin(hazBosque(b))=b
Arboles. Tcnicas de implementacin
249. Disea una representacin dinmica para el TAD ARBIN. Formaliza el invariante de la
representacin considerando dos variantes: R
NC
, que prohibe que diferentes subrboles de
un mismo rbol compartan estructura; y R, que no lo prohibe. Formaliza tambin la fun-
cin 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.
251. La implementacin de ARBIN discutida en el ejercicio anterior puede generar estructuras
compartidas. comprubalo dibujando un grfico que represente las estructuras representan-
tes 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)
252. Programa el procedimiento y la funcin que se especifican a continuacin:
(a) procanula(esa:Arbin[Elem]);
{P
0
:R
NC
(a).a=A}
{Q
0
:a=nil.
elespacioqueocupabalaestructurarepresentante
deAsehaliberado}
fproc

(b) funccopia(a:Arbin[Elem])devb:Arbin[Elem];
{P
0
:R(a)}
{Q
0
:R
NC
(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])Bool

Extiende la implementacin del ejercicio 250 aadiendo una funcin que implemente ( == ) y
analiza su coste temporal.
rboles 478
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 imple-
mentacin resultante.
255. Demuestra que la definicin recursiva dada a continuacin establece una aplicacin biyecti-
va ndice ; {1,2}
*

+
entre el conjunto de todas las posiciones posibles de los rboles bi-
narios y el conjunto de los nmeros naturales positivos.

ndice(c) =1
ndice(o.1) =2*ndice(o)
ndice(o.2) =2*ndice(o)+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 o
del rbol se almacene en el lugar ndice(o) del vector. Esta representacin resulta til para
algunos algoritmos que operan con rboles semicompletos. Segn el resultado del ejercicio
243(b), un valor max = 2
t
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
representacin de los rboles generales como rboles binarios estudiada ms arriba en el
ejercicio 245. Estudia los detalles de esta implementacin en el texto de Xavier Franch,
subseccin 5.2.2., pp. 234-237.
258. Para desarrollar una implementacin dinmica de los rboles n-arios hay dos opciones po-
sibles:
(a) Usar nodos que incluyan n punteros a los n hijos.

(b) Usar nodos que incluyan dos punteros sealando al primer hijo y al hermano dere-
cho, respectivamente.
La opcin (b) se basara en utilizar una representacin de los rboles n-arios como rboles bi-
narios, 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.
Arboles binarios. Ejemplos elementales de algoritmos
Para resolver los ejercicios 261-264 nos situamos como clientes de un mdulo que implemente
el TAD ARBIN, y no tenemos acceso a la representacin interna de los rboles.
261. Programa funciones recursivas que implementen las operaciones del ejercicio 242.
262. Especifica y programa una funcin recursiva espejo que construya la imagen especular de un
rbol binario dado como parmetro.
rboles 479
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 im-
plementa el TAD LISTA.
264. Programa un funcin recursiva que calcule el valor numrico de una expresin aritmtica a
partir de un rbol dado que represente la estructura sintctica de la expresin.
Recorridos de rboles
265. Construye las listas resultantes de aplicar los diferentes recorridos posibles al rbol binario
que representa la estructura de la expresin aritmtica x*(y)+(x)*y.
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]
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 reali-
cen las operaciones de recorrido especificadas en el ejercicio anterior.
268. El recorrido de un rbol binario puede realizarse tambin con ayuda de un procedimiento
recursivo del estilo

procrecorre(ea:Arbin[Elem];esxs:Sec[Elem]);
{P
0
:xs=XS.fin?(xs)=cierto}
{Q
0
:fin?(xs)=Cierto.cont(xs)=cont(XS)++recorrido(a)}
fproc

Desarrolla esta idea para los tres tipos de recorrido en profundidad.


269. Los recorridos en preorden y postorden tambin tienen sentido para rboles generales.
Especifcalos algebraicamente por medio de una extensin del TAD ARBOL que contenga
ecuaciones para dos nuevas operaciones:

preAG,postAG:Arbol[Elem]Lista[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]Lista[Elem]
270. Supongamos que R : Arbin[Elem] Lista[Elem] sea una cualquiera de las tres operaciones
de recorrido en profundidad de rboles binarios. Especifica por medio de ecuaciones una
nueva operacin

acumula
R
:Pila[Arbin[Elem]]Lista[Elem]

rboles 480
tal que acumula
R
(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-
vos, manteniendo para el bucle principal un invariante de la forma:

R(a)=xs++acumula
R
(as).todoslosrbolesdeassonnovacos

donde R es la operacin de recorrido de que se trate, a es el rbol dado para recorrer, xs es la


lista que debe contener al final el recorrido, y as es una pila de rboles auxiliar. Construye funciones
iterativas que realicen esta idea para los tres tipos de recorrido. Busca una inicializacin adecuada
para xs y as!
272. Sea R : Arbin[Elem] Lista[Elem] una cualquiera de las tres operaciones de recorrido en
profundidad de rboles binarios. Considera la operacin ms general R : (Pi-
la[Arbin[Elem]], Lista[Elem]) Lista[Elem] especificada por la ecuacin

R(as,xs)=xs++acumula
R
(as)

siendo acumula
R
la operacin definida en el ejercicio 270. Usa la tcnica de plegado-desplegado
para derivar un algoritmo recursivo final para R, apoyndote en las especificaciones de acumula
R
y
R. Comprueba que la transformacin de los algoritmos recursivos finales as obtenidos a forma
iterativa conduce a los algoritmos iterativos del ejercicio 271.
273. Especifica algebraicamente el recorrido por niveles de un rbol binario, planteado como
operacin niveles : Arbin[Elem] Lista[Elem]. Presenta la especificacin resultante como
enriqueci-miento del TAD ARBIN.
Sugerencia: Usa el TAD COLA[ARBIN[Elem]] y una operacin auxiliar privada ms general
nivelesCola: Cola[Arbin[Elem]] Lista[Elem], especificada de manera que se tenga:

niveles(a)=nivelesCola(as)

en el caso particular de que as sea la cola unitaria formada por a.


274. Programa una funcin iterativa que realice la operacin de recorrido por niveles de un
rbol binario, manteniendo para el bucle un invariante de la forma

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.
rboles 481
Arboles binarios hilvanados
276. Usando induccin sobre n > 0, demuestra que un rbol binario con n nodos tiene 2n+1
subrboles, de los cules n+1 son vacos.
Nota: los subrboles de un rbol se corresponden con los punteros que aparecen en la estruc-
tura que lo representa usando memoria dinmica. Por lo tanto, una representacin dinmica del
rbol sin estructura compartida incluir n+1 punteros vacos que se desaprovechan.
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 efi-
cientemente sin ayuda de una pila auxiliar.
Aplicaciones de los recorridos de los rboles
278. Programa un procedimiento iterativo que convierta una expresin aritmtica representada
como rbol de smbolos, en su forma postfija representada como secuencia de smbolos. Se
supone que todo smbolo es o bien un operador o bien un operando. Puedes suponer
disponible una operacin esOperador: Smbolo Bool que reconoce si un smbolo es un
operador. El algoritmo empleado por el procedimiento que se pide, corresponde a alguno
de los tipos de recorrido de rboles binarios? A cul?
Nota: Combinndolo con el ejercicio 227, este ejercicio proporciona un mtodo til para la eva-
luacin de expresiones aritmticas.
279. Programa una funcin que convierta una expresin aritmtica dada como secuencia de smbo-
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, conside-
rando para los operadores las prioridades habituales y asociatividad por la izquierda (excep-
to 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
mismo recorrido en preorden. Sin embargo, s es posible reconstruir un rbol binario a si se
conoce una lista de parejas

ps=[(x
1
,t
1
),,(x
n
,t
n
)]

donde x
i
es el elemento del nodo visitado en i-simo lugar durante el recorrido en preorden, y
t
i
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:
(a) Que ps venga dada como lista, como se acaba de indicar.

(b) Que ps venga dada como secuencia (i.e. lista con punto de inters).
281. El problema de bsqueda en profundidad de un rbol binario consiste en lo siguiente: Dados
a : Arbin[Elem] y x : Elem, queremos calcular la posicin de a en la que se encuentra por
primera vez el dato x al efectuar un recorrido de a en preorden. Especifica y programa una
funcin iterativa buscaProf que resuelva este problema, usando una lista de valores enteros (1
rboles 482
y 2) para representar la posicin que se devuelve como resultado. Por ejemplo, si a es el
rbol de caracteres mostrado en la figura que sigue, y x es el carcter P, la funcin buscaProf
deber devolver la lista [1,2,1], representando la posicin 1.2.1. Para resolver este problema,
debes suponer disponible una operacin de igualdad entre datos de tipo Elem.
L
M K
P
B
A
J
P
L M
B
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-
ter diferente. La informacin almacenada en los nodos internos se considera irrelevante. Si
un cierto carcter c se encuentra almacenado en la hoja de posicin o, se considera que o es
el cdigo asignado a c por el rbol de codificacin a. Ms en general, el cdigo de cualquier
cadena de caracteres dada se puede construir concatenando los cdigos de los caracteres
que la forman, respetando su orden de aparicin.
(a) Dibuja el rbol de codificacin correspondiente al cdigo siguiente:

Carcter Cdigo
A 1.1
T 1.2
G 2.1.1
R 2.1.2
E 2.2

(b) Construye el resultado de codificar la cadena de caracteres RETA utilizando el


cdigo representado por el rbol de codificacin anterior.
(c) Descifra 1.2.1.1.2.1.2.1.2.1.1 usando el cdigo que estamos utilizando en estos ejem-
plos, construyendo la cadena de caracteres correspondiente.
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 en-
teros 1 y 2. Construye funciones que resuelvan los problemas siguientes:
rboles 483
(a) Decodificacin de un carcter: Dados un rbol de codificacin a y un cdigo us, calcular el
primer carcter x de la cadena de caracteres codificada por us, as como el cdigo vs
que queda pendiente de descifrar.
(b) Decodificacin de un texto: Dados un rbol de codificacin a y un cdigo us, calcular el
texto xs resultante de descifrar us.
Sugerencia: Aplicar reiteradamente la funcin del apartado anterior.
(c) Codificacin de un carcter: Dados un rbol de codificacin a y un carcter x, calcular el
cdigo us de x.
Sugerencia: Usar el algoritmo de bsqueda en profundidad del ejercicio 283.
(d) Codificacin de un texto: Dados un rbol de codificacin a y un texto xs, construir el
cdigo de xs.
Sugerencia: Aplicar reiteradamente la funcin del apartado anterior.
285. Especifica un TAD HARBIN[E :: ANY] adecuado para representar rboles binarios que
slo almacenen datos en sus hojas, y desarrolla una implementacin dinmica eficiente, re-
presentando 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:ElemHArbin[Elem]
Nodo:(HArbin[Elem],HArbin[Elem])HArbin[Elem]
Eliminacin de la recursin doble
286. Una funcin recursiva doble cuya definicin se ajuste al esquema:

funcf(x:T)devy:S;
{P
0
:P(x);Cotat(x)}
var
x
1
,x
2
:T;
y
1
,y
2
:S;
%Otrasposiblesdeclaracioneslocales
inicio
sid(x)
entonces
{P(x).d(x)}
y:=r(x)
{Q(x,y)}
sino
{P(x).d(x)}
x
1
:=s
1
(x);x
2
:=s
2
(x);
{x
1
=s
1
(x).x
2
=s
2
(x).P(x
1
).P(x
2
)}
y
1
:=f(x
1
);y
2
:=f(x
2
);
{x
1
=s
1
(x).x
2
=s
2
(x).Q(x
1
,y
1
).Q(x
2
,y
2
)}
y:=c(x,y
1
,y
2
)
{Q(x,y)}
fsi
rboles 484
{Q
0
:Q(x,y)}
devy
ffunc

Se puede transformar en una funcin iterativa equivalente definida como sigue:

funcf
it
(x:T)devy:S;
{P
0
:P(x)}
tipo
Marca=[0..2];
Nodo=reg
marca:Marca;
param:T;
result:S
freg;
var
pila:Pila[Nodo];
nuevoNodo,nodo:Nodo;
m:Marca;
u:T;
v:S;
inicio
PilaVaca(pila);
nuevoNodo.marca:=0;
nuevoNodo.param:=x;
Apilar(nuevoNodo,pila);
{Inv.I;Cota:C}
itNOTesVaca(pila)
nodo:=cima(pila);
m:=nodo.marca;
u:=nodo.param;
si
m=0ANDd(u)y:=r(u);
desapilar(pila)
m=0ANDNOTd(u)nodo.marca:=1;
desapilar(pila);
Apilar(nodo,pila);
nuevoNodo.marca:=0;
nuevoNodo.param:=s
1
(u)
Apilar(nuevoNodo,pila)
m=1nodo.marca:=2;
nodo.resul:=y;
desapilar(pila);
Apilar(nodo,pila);
nuevoNodo.marca:=0;
nuevoNodo.param:=s
2
(u)
rboles 485
Apilar(nuevoNodo,pila)
m=2v:=nodo.resul;
y:=c(u,v,y);
desapila(pila)
fsi
fit;
{y=f(x)}
{Q
0
:Q(x,y)}
devy
ffunc

La idea de la transformacin es la siguiente: la ejecucin de un algoritmo doblemente recursivo


se corresponde con un recorrido en postorden del rbol de llamadas determinado por la llamada
inicial. En efecto: la raz corresponde a la llamada inicial; las hojas corresponden a caso directos, y
al visitarlas se obtiene un resultado con ayuda de la funcin r; y los nodos internos corresponden
a casos recursivos. Para obtener el resultado, hay que calcular primero el resultado de la primera
llamada, recorriendo el hijo izquierdo; a continuacin, se calcula el resultado de la segunda llama-
da, recorriendo el hijo derecho; finalmente, al visitar el propio nodo, se componen los resultados
de ambas llamadas mediante la funcin c y se obtiene el resultado de la llamada inicial.
Durante el bucle del algoritmo iterativo se van visitando nodos del rbol de llamadas, en el or-
den correspondiente a un recorrido en postorden. La pila representa en cada momento el camino
desde la raz del rbol (correspondiente a la llamada inicial) hasta el nodo que se est visitando en
un momento dado. Las variables y guardan el ltimo valor calculado para un nodo que es raz de
un subrbol ya completamente recorrido. Los nodos de la pila guardan tambin cierta informa-
cin acerca de los resultados obtenidos en la parte de recorrido ya realizada. Ms exactamente:
El nodo cima siempre cumple marca e [0..2]; los dems nodos verifican que marca e
[1..2].
En el nodo del fondo de la pila se tiene param = x (parmetros de la llamada inicial)
Si un nodo cumple marca = 1 y param = u, entonces el nodo apilado justo sobre l (si
existe) cumple param = s
1
(u).
Si un nodo cumple marca = 2 y param = u, entonces el nodo cumple resul = f(s
1
(u)) y el
nodo apilado justo sobre l (si existe) cumple param = s
2
(u).
Siempre que el nodo cima cumple marca = 2 y param = u, se verifica tambin que y =
f(s
2
(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 cuan-
to 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 fib
it
(3) o fib
it
(4).
(b) Aplica la transformacin a las funciones recursivas dobles consideradas en los ejerci-
cios 261-264.
rboles 486
Arboles ordenados y de bsqueda: modelo matemtico y especificacin
287. Sea un rbol binario que almacena en sus nodos elementos de un tipo de la clase ORD. Se
dice que a est ordenado si se da alguno de los dos casos siguientes:
(a) a es vaco.

(b) 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 es-
trictamente menor que todos los elementos del hijo derecho.
Observa que esta definicin equivale a pedir que el recorrido en inorden de a produzca una lis-
ta de elementos ordenada en orden estrictamente creciente. Especifica mediante ecuaciones una
operacin booleana que reconozca los rboles ordenados.
288. La operacin de insercin de un elemento en un rbol se especifica de manera que el rbol
resultante quede ordenado si lo estaba el rbol de partida. Dibuja el rbol ordenado de en-
teros que se obtiene a partir del rbol vaco, insertando sucesivamente los nmeros siguien-
tes:
20, 12, 17, 31, 26, 43, 7, 35
289. La operacin de bsqueda de un elemento en un rbol ordenado se especifica de manera que
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.
290. La operacin de borrado de un elemento en un rbol ordenado se especifica de manera que
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 ope-
racin de borrado deja el rbol inalterado. Dibuja los rboles resultantes de borrar los ente-
ros 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 bo-
rrado, 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, in-
sertar, buscar, borrar y preguntar si un elemento aparece en un rbol.
Sugerencia: Usa REC-PROF-ARBIN[E], ocultando aquellas operaciones que no convenga po-
ner 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 orde-
nados.
292. Sean a :Arbin[elem], x : Elem. Si a no es un rbol ordenado, algunas operaciones de ARB-
ORD pueden comportarse extraamente:
(a) En inserta(x, a) puede haber elementos repetidos, aunque en a no los haya.

(b) Puede ser est?(x, a) = falso, aunque x aparezca en algn nodo de a.


rboles 487

(c) Puede ser borra(x, a) = a, aunque x aparezca en algn nodo de a.


Busca ejemplos concretos en los que esto ocurra. Observa que los clientes del TAD ARB-
ORD 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 ope-
racin (): (Val, Val) 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 ope-
racin 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.
Arboles ordenados y de bsqueda: implementacin
294. Plantea una implementacin de la operacin inserta de los rboles ordenados como funcin
recursiva, basndote en las operaciones ya disponibles para ARBIN y siguiendo la pauta de
la especificacin ecuacional de ARB-ORD. Estudia la estructura compartida resultante de
ejecutar a := inserta(19, a), suponiendo que a sea el rbol de enteros de la figura siguiente y
que la implementacin use la representacin dinmica del ejercicio 249.
17
21
27
33
9
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]. Ad-
vertencia: Esta implementacin no puede plantearse modularmente, importando el tipo re-
presentante 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 con-
tener 4 campos: una clave, un valor, y dos enlaces apuntando a los hijos.
rboles 488
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.
298. Una familia F de rboles binarios se llama equilibrada si existen constantes n
0
e , c e J
+
tales que para todo n > n
0
y todo rbol a e F con n nodos se tenga talla(a) s c log n. Razona
que el tiempo de ejecucin de las operaciones de bsqueda, insercin y borrado en rboles
de una familia equilibrada es O(log n), siendo n el nmero de nodos. Para rboles arbitra-
rios, este tiempo aumenta a O(n) en el caso peor.
Arboles ordenados y de bsqueda: aplicaciones
299. Supongamos que Elem viene dado por un tipo de datos de la clase ORD. Especifica me-
diante ecuaciones una operacin

hazArbusca:Lista[Elem]Arbus[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 especifi-
quen 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-
duccin sobre el nmero de nodos de a.
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 informa-
cin adicional. Compara con el ejercicio 280. Desarrolla dos variantes del algoritmo, ade-
cuadas 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-
rio dejando el resultado en un vector, de acuerdo con la siguiente especificacin pre/post:
procinOrd(ea:Arbin[Elem];esv:Vector[1..N]deElem;esp:Ent);
{P
0
:p=P.v=V.1spsN+1.numNodos(a)sNp+1}
{Q
0
:Psp+1sN+1.contenido(v,P,p)=inOrd(a).
vcoincideconVfueradelintervalo[P..p]}
fproc
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
Elem por medio del siguiente proceso:
(a) se construye un rbol ordenado mediante sucesivas inserciones de los elementos del
vector a partir de un rbol vaco.

rboles 489
(b) se recorre el rbol obtenido en inorden, y durante el recorrido se van colocando los
elementos en v (comenzando por la posicin 1).
Disea una funcin que realice este algoritmo, incluyendo en la precondicin la condicin de
que el vector no tiene elementos repetidos. Razona que el algoritmo consume tiempo O(N*t),
siendo t la talla que llega a alcanzar el rbol de bsqueda durante el proceso.
304. Una variante del concepto de rbol ordenado consiste en permitir que distintos nodos del
rbol almacenen un mismo elemento, pero con la condicin siguiente: el elemento de cual-
quier 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.
305. Usando el TAD ARB-ORD-REP en lugar de ARB-ORD, modifica el algoritmo de ordena-
cin del ejercicio 303 de modo que sea posible ordenar vectores que contengan repeticio-
nes de elementos.
306. Construye algoritmos de ordenacin basados en rboles de bsqueda que puedan aplicarse
para ordenar listas o secuencias en lugar de vectores.
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 alfabti-
camente 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 ti-
po Sec[Palabra], siendo Palabra un tipo disponible de la clase ORD. Se pide construir un al-
goritmo 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 acompa-
ada 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 ve-
ces 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
una indeterminada (cfr. ejercicio 151) utilizando como representacin de un polinomio un
rbol de bsqueda de tipo Arbus[Exp, Coef]. La idea es que cada nodo del rbol representa
un monomio, con el exponente como clave y el coeficiente como valor asociado a sta.
Compara la eficiencia de las operaciones resultantes con las otras implementaciones de los
polinomios estudiadas anteriormente (cfr. ejercicios 170 y 230).
310. Desarrolla implementaciones modulares de los TADs siguientes, usando rboles de
bsqueda importados de un mdulo conveniente para la construccin del tipo representan-
te. 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.
rboles 490
(c) El TAD MERCADO del ejercicio 233.
(d) El TAD BANCO del