Está en la página 1de 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

PRLOGO
Si has aprendido a programar de manera informal, las tcnicas formales son un descubrimiento asombroso. Se convierten en una herramienta muy potente para razonar sobre los programas y
explorar las alternativas de diseo de algoritmos que cualquier problema no trivial plantea. Por
otra parte, si tu primera aproximacin a la Programacin es a travs de las tcnicas formales, stas
te servirn para enfocar la atencin sobre aspectos concretos del problema, resolverlos por separado y razonar sobre su correccin, adquiriendo una metodologa sistemtica y bien fundamentada para el diseo de algoritmos que, una vez interiorizada, podrs probablemente olvidar.
Este libro es el resultado de nuestra experiencia impartiendo la asignatura Estructuras de datos
y de la informacin en la Facultad de Informtica de la Universidad Complutense de Madrid,
ininterrumpidamente desde el curso 1995-96 hasta la actualidad. Pasando cronolgicamente por
las manos de los tres autores, empezando por las notas de clase de Mario Rodrguez, que Pedro
Gonzlez evolucion y Marco Gmez sigui utilizando posteriormente. Fruto de este trabajo,
adems de este libro, se han generado trasparencias de apoyo a las clases e implementaciones en
C++ de todos los algoritmos aqu descritos. Es este material adicional, disponible en la pgina
http://gaia.fdi.ucm.es/people/pedro/edem/, junto con el texto del libro el que da sentido al
moderno que aparece en el ttulo del libro. Existen libros de estructuras de datos con un enfoque formal, alejados de los lenguajes de programacin concretos, as como otros menos formales
y ms preocupados por proporcionar los detalles de implementacin en un lenguaje concreto.
Este libro pretende ser moderno aproximando ambos enfoques: formal en la presentacin de los
conceptos, que se acompaan de implementaciones ejecutables en el material adicional.
El libro est organizado de la siguiente forma. En el captulo 1 se introducen las tcnicas de
especificacin pre/post de algoritmos. Aunque se puede suponer que los estudiantes han seguido
previamente un curso de lgica para informticos, hemos intentado que la exposicin sea autocontenida, introduciendo todos los elementos de sintaxis y semntica de la lgica con signatura
heterognea que se utiliza en la especificacin. Se introducen las reglas de verificacin de las
construcciones habituales de los lenguajes imperativos, as como las que permiten verificar procedimientos y funciones. Se presentan as mismo las medidas asintticas de la complejidad y los
mtodos de anlisis de la complejidad de algoritmos iterativos. A continuacin, se presentan los
mtodos de derivacin de algoritmos iterativos a partir de la especificacin, con especial atencin
a la derivacin de bucles a partir del invariante. Por ltimo, se utilizan los mtodos de derivacin
recin presentados para obtener los algoritmos de recorrido, bsqueda secuencial y binaria y
mtodos de complejidad cuadrtica de ordenacin de vectores.
El segundo captulo se dedica al estudio de los algoritmos recursirvos. Siguiendo un esquema
similar al del captulo 1, se presentan consecutivamente las tcnicas de especificacin, derivacin
y anlisis de algoritmos recursivos. En el apartado dedicado a la derivacin se derivan los algoritmos de complejidad cuasi-lineal de ordenacin rpida y ordenacin por mezcla. Despus de presentar el esquema general de transformacin de recursin final en iteracin, el captulo concluye
presentando algunas tcnicas adicionales de diseo de algoritmos recursivos como son la generalizacin y el plegado-desplegado.
El captulo 3 da comienzo a la segunda parte del libro, que se dedica al estudio de los tipos
abstractos de datos. Despus de introducir de manera informal el concepto de tipo abstracto de
datos (TADs) y el papel que juegan las estructuras de datos como mecanismos de implementacin, se presentan las tcnicas de especificacin algebraica de tipos abstractos de datos que se
emplearn en el resto del libro para especificar los TADs estudiados. A continuacin se hacen

Prlogo

ii

algunas consideraciones generales acerca de las alternativas de implementacin de un TAD a partir de su especificacin, incluyendo indicaciones acerca de cmo verificar la correccin de dicha
implementacin. Por ltimo, se presentan los punteros como mecanismo para construir estructuras de datos en memoria dinmica, completando as, con tipos simples, registros y vectores que se
suponan conocidos, el repertorio de estructuras de datos bsicas que se usar en el resto del libro
para construir estructuras de datos ms elaboradas.
Los captulos 4, 5, 6 y 7 se dedican al estudio de las estructuras de datos ms habituales: estructuras de datos lineales, rboles, tablas y grafos. Entre las estructuras de datos lineales nos
ocupamos de especificar y presentar implementaciones alternativas para pilas, colas, colas dobles,
listas y secuencias. Las secuencias, que aaden al TAD Lista la posibilidad de recorrer secuencialmente sus elementos, sern muy utilizadas como estructuras auxiliares en los captulos posteriores. En el captulo 5, dedicado a los rboles, se especifican e implementan los rboles binarios,
los rboles de bsqueda y los rboles equilibrados AVL. Los montculos se desarrollan como
ejercicios en este tema. El captulo 6 se dedica a las tablas, especificadas como funciones de claves en valores, y que se pueden implementar eficientemente con la tcnica de las tablas dispersas.
Se estudian las tablas dispersas abiertas y cerradas, adems de los principales mtodos de localizacin y relocalizacin. Por ltimo el captulo 7 se dedica a la especificacin e implementacin de
los grafos. Adems de las operaciones bsicas de construccin de grafos dirigidos etiquetados, se
estudian las operaciones de recorrido en profundidad y anchura, as como los algoritmos de obtencin de caminos mnimos.
Queremos expresar nuestro agradecimiento a las sucesivas generaciones de estudiantes de la
Facultad de Informtica de la Universidad Complutense de Madrid que han seguido la asignatura
de Estructuras de datos y de la informacin y nos han ayudado a mejorar el material a partir del
cual hemos preparado el libro que ahora tienes delante.
Mayo de 2011
Mario Rodrguez Artalejo
Pedro Antonio Gonzlez Calero
Marco Antonio Gmez Martn

iii

ndice de contenidos

NDICE DE CONTENDOS
Captulo 1 Diseo de algoritmos iterativos ...................................................................... 1 
1.1 Especificacin pre/post ................................................................................................................. 1
1.1.1 Representacin de asertos en lgica de predicados ...................................................... 3
1.1.2 Especificacin pre/post ................................................................................................. 20
1.1.3 Especificaciones informales ........................................................................................... 33
1.2 Verificacin de algoritmos........................................................................................................... 33
1.2.1 Estructuras algortmicas bsicas .................................................................................... 34
1.2.2 Estructuras algortmicas compuestas............................................................................ 41
1.3 Funciones y procedimientos ....................................................................................................... 62
1.3.1 Procedimientos ................................................................................................................63
1.3.2 Funciones.......................................................................................................................... 70
1.4 Anlisis de algoritmos iterativos ................................................................................................. 76
1.4.1 Complejidad de algoritmos ............................................................................................ 78
1.4.2 Medidas asintticas de la complejidad .......................................................................... 80
1.4.3 Ordenes de complejidad ................................................................................................. 82
1.4.4 Mtodos de anlisis de algoritmos iterativos ............................................................... 85
1.4.5 Expresiones matemticas utilizadas .............................................................................. 90
1.5 Derivacin de algoritmos iterativos ...........................................................................................91
1.5.1 El mtodo de derivacin ................................................................................................ 91
1.5.2 Dos ejemplos de derivacin ........................................................................................... 94
1.5.3 Introduccin de invariantes auxiliares por razones de eficiencia ...........................103
1.6 Algoritmos de tratamiento de vectores ...................................................................................108
1.6.1 Recorrido ........................................................................................................................109
1.6.2 Bsqueda .........................................................................................................................110
1.6.3 Ordenacin .....................................................................................................................120
1.7 Ejercicios ......................................................................................................................................134
Captulo 2 Diseo de algoritmos recursivos ................................................................ 153
2.1 Introduccin a la recursin .......................................................................................................153
1.1.1 Recursin simple ...........................................................................................................155
1.1.2 Recursin mltiple.........................................................................................................157
2.2 Verificacin de algoritmos recursivos......................................................................................159
1.2.1 Verificacin de la recursin simple .............................................................................160
1.2.2 Verificacin de la recursin mltiple ..........................................................................169
2.3 Derivacin de algoritmos recursivos .......................................................................................174
1.3.1 Algoritmos avanzados de ordenacin ........................................................................191
2.4 Anlisis de algoritmos recursivos .............................................................................................197
1.4.1 Despliegue de recurrencias...........................................................................................199

ndice de contenidos

iv

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 W: TW ..................................................................................................267
3.2.3 Razonamiento ecuacional. T-equivalencia .................................................................268
3.2.4 Operaciones generadoras, modificadoras y observadoras .......................................270
3.2.5
3.2.6
3.2.7
3.2.8
3.2.9
3.2.10
3.2.11

Trminos generados: TGW ............................................................................................271


Completitud suficiente de las generadoras ................................................................272
Razonamiento inductivo...............................................................................................274
Diferentes modelos de un TAD..................................................................................275
Modelo de trminos ......................................................................................................276
Proteccin de un TAD usado por otro ......................................................................277
Generadoras no libres ...................................................................................................278

3.2.12 Representantes cannicos de los trminos: TCW TGW ..........................................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

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

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 especificaciones as expresadas sean precisas consideren todos los casos posibles se necesitan
descripciones muy extensas. Las especificaciones formales proporcionan a la vez precisin y concisin. 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 algoritmos.

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 tareas.

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

La solucin que se adopta es definir el


estado de un programa como una descripcin instantnea de los valores asociados a las variables 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 computadora 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 tratarse 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 resultado.
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 aserciones de Q.
Aparece aqu la idea de resultado garantizado si se usa correctamente si se cumple la precondicin. 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 devuelva un valor booleano que nos indique si el valor de alguno de los elementos a[1], ..., a[n] es
igual a la suma de todos los que le preceden en el vector. Esta especificacin deja algunos puntos
sin aclarar, para el usuario: se puede llamar a esSuma con un valor negativo de n? y con n=0 o
con n>1000? y en caso afirmativo qu valor devolver la funcin?; y para el implementador: si
n1 y a[1]=0 la funcin debe devolver verdadero o falso?

Diseo de algoritmos iterativos

var
a:Vect;
n:Ent;
b:Bool;
{P{0n1000}
esSuma
{Q{ bli: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 cero1).

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 programas. En un lenguaje imperativo existen una serie de tipos predefinidos. Una de las primeras propiedades del estado de un programa es el tipo de sus variables. Nuestra lgica dispone de
smbolos para representar a los siguientes tipos predefinidos algunos de los cuales no suelen
aparecer en los lenguajes imperativos:

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

Esto es as porque consideramos que el valor de los cuantificadores, aritmticos y booleanos, aplicados sobre un
dominio nulo dan como resultado el elemento neutro de las correspondientes operaciones binarias [Bal93:pag 8].
1

Diseo de algoritmos iterativos

Vector[v1..vn]deW

siendo los valores v1, ..., vn de tipo W, su dominio ser el conjunto de aplicaciones entre DW y
DW que notaremos D(Wo W)
De esta forma, el dominio para cada uno de los tipos predefinidos queda
Tipo (sintctico)
Nat
Ent
Rac
Real
Bool
Car

Dominio (semntico)2
Dnat= N
Dent= Z
Drac= Q
Dreal= R
Dbool= {cierto, falso}
Dcar= {0, 1, ..., 9, a, ..., z, A, ..., Z}

Vector[W] de W

Dvector[W] de W= D(WoW)

Decimos entonces que un valor de tipo W es cualquier elemento del correspondiente dominio
DW. Los programas disponen de recipientes para dichos valores: las variables y las constantes.
La signatura que utilizaremos contiene tambin las operaciones habituales sobre los tipos predefinidos.
Para definir operaciones, utilizaremos perfiles en los que se indica el nombre de la operacin, el
nmero de argumentos que tiene junto con el tipo de cada uno de ellos, y el tipo del resultado:

f : W1 ... Wn o W
Las operaciones que podemos utilizar sobre los tipos predefinidos:

Operaciones numricas, definidas para los tipos numricos: Nat, Ent, Rac y Real

+, *, : W W o W
Operaciones constantes de tipo bool

cierto, falso : o Bool


Operaciones booleanas
AND, OR : Bool Bool o Bool
NOT : Bool o 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

Operaciones de comparacin con resultado booleano definidas para los tipos ordenados:
Nat, Ent, Rac, Real y Car.
>, <, , : W W o Bool
Operaciones de mximo y mnimo para los tipos ordenados

max, min : W W o W
Operaciones de igualdad y desigualdad con resultado booleano, definidas para todos los
tipos con igualdad, en nuestro caso todos los tipos predefinidos excepto los vectores.

=, z : W W o Bool
Una operacin polimrfica para cada uno de los tipos predefinidos que devuelve verdadero o falso dependiendo de si el argumento es de tipo G.
_:G : W o Bool3

Adems, tambin podremos emplear en los asertos cualquier operacin que haya sido totalmente especificada, en el sentido que veremos ms adelante, entendindose en ese caso que su comportamiento est definido por la especificacin.
Construccin de asertos
La forma ms simple de aserto es una expresin de tipo Bool. Definamos primero qu entendemos por expresin de tipo W.

Decimos que E es una expresin de tipo W si y slo si es de alguna de las formas siguientes:

Es una variable de tipo W

Es una constante de tipo W

Es f(E1, ..., En) siendo el perfil de f


f : W1 ... Wn o W
y las Ei son expresiones de tipo Wi para i {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

Vemos aqu cmo en los perfiles tambin se puede indicar el modo de aplicacin: prefijo, infijo o postfijo.

Diseo de algoritmos iterativos

siendo E y E expresiones

Compuesta

P es de alguna de las formas

R RQ RQ RoQ R l Q
(negacin, conjuncin, disyuncin, condicional y bicondicional)
siendo R y Q aserciones
P es de alguna de las formas
&
&
&
&
&
&
 x : D( x ) : R( x )
 x : D( x ) : R( x )
&
&
&
donde D( x ) es una asercin de dominio para las variables x , y R( x ) es una
&
asercin donde intervienen las variables x .

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
, , , o, l, G
donde G 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)

(z=a) (t=cierto) es una asercin compuesta

(x>0) (x<0)

(x>3) o (x>0)

x : ent : (x*0=0) es una asercin compuesta, donde la asercin de dominio es una notacin abreviada de x : ent, una expresin booleana donde se aplica la operacin :ent

i : 1iN : (i*2=3) es una asercin compuesta, donde la asercin de dominio es una


notacin abreviada de (1i) (iN).

es una expresin de tipo booleano y una asercin atmica

es una asercin compuesta


es una asercin compuesta

Diseo de algoritmos iterativos

El empleo de cuantificadores en las aserciones supone la utilizacin de variables para representar un subconjunto de los posibles valores de un cierto dominio y no como contenedores de valores asociados a una posicin de memoria. Decimos que las variables cuantificadas son mudas en
el sentido de que no representan un valor de la memoria. Esto hace que una variable sujeta a un
cuantificador se pueda renombrar por otra sin que cambie el sentido de la asercin.
Una aparicin de una variable se dice que est ligada si se encuentra en el mbito de un cuantificador con esa variable. Diremos que es libre en otro caso.
Hablamos de apariciones libres y ligadas porque en una misma asercin una variable puede
aparecer libre y ligada como en

(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

&
ligada en estas expresiones. Los tipos de las expresiones E( i ) son numricos para los sumatorios
y productos extendidos y ordenados para mximos y mnimos.
Veamos algunos ejemplos:

i : 1 i 100 : (i*i) que representa la suma de las expresiones i*i, cuando i toma valores en el dominio indicado. (se puede indicar la notacin con super y subndices que les
puede resultar ms familiar).

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 importante pues es la base de la regla de verificacin de una de las instrucciones ms importantes en
los lenguajes imperativos: la asignacin. El significado intuitivo del proceso de sustitucin es el
siguiente: el aserto A expresa un hecho que se requiere de un estado determinado, referido al
valor de x; A[x/E] expresa un hecho anlogo referido al valor que toma la expresin E.
Definimos una sustitucin como el proceso de reemplazar simultneamente todas las apariciones libres de una variable por una expresin. Emplearemos la notacin
[x/E]
para indicar la sustitucin de la variable x por la expresin E. Tambin
[x1/E1, ... , xn/En]
para indicar la sustitucin simultnea de las variables x1, ... , xn por las expresiones E1, ... , En,
respectivamente.
Las sustituciones slo se pueden realizar para variables libres, pues son stas las que representan valores del estado. En esta definicin se supone que en ningn caso se ha utilizado una variable libre y una ligada con el mismo nombre. De no ser as, es necesario realizar un
renombramiento de las variables ligadas cuyos nombres coincidan con las variables libres que
aparecen en las expresiones o en los asertos.
Algunos ejemplos de sustituciones (correctos e incorrectos)

Diseo de algoritmos iterativos

es incorrecto sustituir variables ligadas


(x:1x100:(x=a))[x/3] o

x:13100:(3=a)

es incorrecto cualquier forma de sustitucin que no sustituya todas las apariciones simultneamente
((x=17)(x>100))[x/(x*x)]o((x*x=17)(x>100))[x/(x*x)]o
((x*x)*(x*x)=17)(x*x>100)

en sustituciones mltiples es incorrecto hacer las sustituciones secuencialmente


((x*17)*y)[x/(2*y),y/z] o

((2*y)*y)[y/z] o

((2*z)*z)

Significado de los asertos


Intuitivamente un aserto expresa una afirmacin que puede ser verdadera o falsa. El significado 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 posteriormente 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 variables 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 dominios correspondientes, que representaremos con la notacin

V: IdVar o D
donde V es un estado, IdVar es el conjunto de identificadores de las variables y D es la unin
de los dominios de las variables.
Por ejemplo:
var
x,y:Ent;
b:Bool;

un estado V es una aplicacin


V : {x,y,b} o Dent Dbool
por ejemplo
V = {(x,1), (y,2), (b,falso)}
El significado de los asertos se construye inductivamente a partir del significado de los predicados las expresiones de tipo booleano.

Dada una expresin E de tipo W, definimos el significado de E bajo el estado V, que notaremos
val>E, V@

Diseo de algoritmos iterativos

10

como el valor de DW resultante de la aplicacin del significado habitual o especificado de las


operaciones empleadas en E, aplicadas sobre los valores que asigna V a las variables libres. Este
significado estar definido solamente en el caso de que V asigne valores a todas las variables libres
de E.
Dado un aserto P, definimos el significado de P bajo el estado V, que notaremos
val>P, V@
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
val>P,V@ =

cierto

si val>E,V@ = val>E,V@

falso

en otro caso

Aserciones compuestas

Si P es R
val>P,V@ =

cierto

si val>R,V@ = cierto y val>Q,V@ = cierto

falso

en otro caso

cierto

si val>R,V@ = cierto o val>Q,V@ = cierto

falso

en otro caso

falso

si val>R,V@ = cierto y val>Q,V@ = falso

cierto

en otro caso

falso

si val>R,V@ y val>Q,V@ tiene significados opuestos

cierto

en otro caso

Si P es R l Q
val>P,V@ =

si val>R,V@ = cierto

Si P es R o Q
val>P,V@ =

falso

Si P es R Q
val>P,V@ =

si val>R,V@ = falso

Si P es R Q
val>P,V@ =

cierto

&
&
&
Si P es  x : D( x ) : R( x ) entonces
val>P, V@ = cierto

&
si y slo si para todo estado V que extiende a V y asigna valores a las variables x de
tal forma que
val>D, V@ = cierto
se cumple que
val>R, V@ = cierto

Diseo de algoritmos iterativos

11

&
&
&
Si P es  x : D( x ) : R( x ) entonces
val>P, V@ = cierto

&
si y slo si para algn estado V que extiende a V y asigna valores a las variables x de
tal forma que
val>D, V@ = cierto
se cumple que
val>R, V@ = cierto
Este significado estar definido solamente en el caso de que V asigne valores a todas las variables libres de P.

En el caso de los cuantificadores, consideramos que para V= el cuantificador universal es


cierto y el existencial falso.
Veamos unos ejemplos del significado de expresiones y aserciones

Sea V un estado que da valores a las variables


var
x,y:Ent;
b:Bool;

de la siguiente forma
V={(x, 1), (y, 2), (b, falso)}
E1 es x=y

val>E1, V@ = falso

E2 es x+(2*y)

val>E2, V@ = 5

P1 es (x=1)(NOTb)

val>P1, V@ = cierto

P2 es i:10<i<15:(i*2<30)

val>P2, V@ = cierto

donde los V seran: {(x, 1), (y, 2), (b, falso), (i, 11)}, ..., {(x, 1), (y, 2), (b, falso), (i, 14)}
De manera recproca a cmo definimos el significado de un aserto bajo un estado definimos el
conjunto de estados que hacen cierto un aserto, que nos servir para definir ms cmodamente el
significado de las expresiones que an nos restan.
Definimos el conjunto de estados que satisfacen un aserto P, que notaremos
est>P@
como
est>P@ =def { V | val>P, V@ = cierto }
Naturalmente, para que un estado satisfaga un aserto, el aserto debe estar definido para ese estado, es decir, el estado debe asignar valores a todas las variables libres del aserto.

Diseo de algoritmos iterativos

12

Veamos algunos ejemplos de los conjuntos de estados que satisfacen ciertos asertos

P1 es falso
est>P1@ =
(pues el valor de operacin constante booleana falso siempre es falso)
est>P2@ = TODOS
P2 es cierto
(pues el valor de la operacin constante booleana cierto es siempre cierto)
P3 es i:Nat:x=2*i
est>P3@ = {V | V asigna a la variable x un valor nulo o par}
Definamos por fin el significado de las expresiones que nos restan:

Si E es una expresin de la forma


&
&
&
i : D( i ) : E( i )
entonces
val>E,V@ =

val>E, V G@

G C

&
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i , y siendo
V G el estado que se obtiene al combinar las asignaciones de V y G.

Si C es el conjunto vaco, entonces se conviene en que val>E,V@ = 0.


Si E es una expresin de la forma
&
&
&
i : D( i ) : E( i )
entonces
val>E,V@ =

val>E, V G@

&
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i , y siendo
V G el estado que se obtiene al combinar las asignaciones de V y G.

Si C es el conjunto vaco, entonces se conviene en que val>E,V@ = 1.


Si E es una expresin de la forma
&
&
&
max i : D( i ) : E( i )
entonces
val>E,V@ = MaxGC val>E, V G@

&
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i , y siendo
V G el estado que se obtiene al combinar las asignaciones de V y G.

Si C es el conjunto vaco, entonces se conviene en que val>E,V@ queda indefinido.


Si E es una expresin de la forma
&
&
&
min i : D( i ) : E( i )
entonces

Diseo de algoritmos iterativos

13

val>E,V@ = MinGC val>E, V G@

&
siendo C el conjunto de estados que satisfacen D, limitados a las variables de i , y siendo
V G el estado que se obtiene al combinar las asignaciones de V y G.

Si C es el conjunto vaco, entonces se conviene en que val>E,V@ queda indefinido.


Si E es una expresin de la forma
&
&
&
# i : D( i ) : P( i )
entonces
val>E,V@ = |{V | val>DP, V V@=cierto }

&
siendo V el estado que se obtiene al considerar las asignaciones de variables de i .
Ntese, en primer lugar, que los smbolos utilizados en el lenguaje de asertos y en el correspondiente significado difieren, con ello se quiere representar que se trata de entidades diferentes;
por ejemplo, en un caso el sumatorio no es ms que un elemento del lenguaje que tiene unas ciertas propiedades mientras que en el otro el sumatorio se refiere al concepto matemtico conocido
por todos.
Por otra parte, ntese tambin que cuando no hay ningn estado que satisfaga la restriccin de
dominio hemos elegido que el valor de la expresin sea el elemento neutro de la correspondiente
operacin matemtica. Para el mximo y el mnimo los elementos neutros seran, respectivamente, el menor elemento posible y el mayor elemento del dominio; no hemos definido el significado
porque no siempre est garantizado que existan dichos elementos.
Veamos un ejemplo:

Sea V un estado que da valor a las variables


varx,y:Ent;

de la siguiente forma: V={(x,10), (y,4)}


Si E es i:1iy:(i*x)
entonces
val>E, V@

= val>i*x, V {(i, 1)}@ + ... + val>i*x, V {(i, 4)}@


= 1*10+ ... + 4*10
= 10+20+30+40
= 100

Si E es #i:5iy:(i*x>34)
entonces
val>E, V@ = |C|
siendo C
C = {V | val>(5iy)(i*x>34), VV@=cierto}
= {V | val>(5iy), VV@=cierto}{V | val>(i*x>34), VV@=cierto}
= {V | val>(5i4), V@=cierto}{V | val>(i*10>34), V@=cierto}
= {V | val>(i*10>34), V@=cierto} =

Diseo de algoritmos iterativos

14

por tanto,
val>E,V@ = |C| = || = 0
En la verificacin de los algoritmos necesitaremos razonar sobre los asertos, las condiciones
que describen el estado de los programas, y tendremos que poder determinar cundo un aserto es
consecuencia lgica de otro. Para ello introducimos el concepto de fuerza de los asertos.
Fuerza de los asertos
Intuitivamente una condicin es ms restrictiva que otra si es ms difcil de satisfacer, o, dicho
de otro modo, si se satisface en menos ocasiones. Refirindonos a los asertos sobre el estado de
los programas, un aserto ser tanto ms fuerte cuantos menos estados lo satisfagan. Aunque no
es exactamente esta idea la que utilizamos, pues no definimos la fuerza como una magnitud absoluta sino relativa, y de tal forma que puede ocurrir que dos asertos no sean comparables.
Dados dos asertos P y Q, decimos que P es ms fuerte que Q si se cumple
est>P@ est>Q@
y lo notaremos PQ
Decimos en este caso que Q es una consecuencia lgica de P, es decir, siempre que se cumple
P se cumple tambin Q. P es ms restrictiva que Q en el sentido de que puede haber estados que
satisfagan Q pero no as P.
Por ejemplo

varx:Ent;

tenemos que x0 x10


ya que
est>x0@ = {V | V asigna a x el valor cero }
est>x10@ = {V | V 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 implicacin de construccin de aserciones P o Q.4
Se puede demostrar que falso es ms fuerte que cualquier aserto ya que el conjunto vaco
est contenido en cualquier conjunto y que cualquier aserto es ms fuerte que el aserto cierto
ya que cualquier conjunto est contenido en el conjunto TODOS.

Lo que si es correcto es que si el aserto Po Q se satisface en cualquier estado entonces se tiene que P Q, ya
que, o bien est>P@=, o bien est>P@ est>Q@ debido a la definicin de la semntica del conectivo o.
4

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@ = {V | V asigna a x el valor cero }
est>(x<1)(x>-1)@ = {V | V asigna a x un valor que 1 y mayor que 1}
por el conocimiento que tenemos de los nmeros enteros podemos concluir que
est>x=0@ = est>(x<1)(x>-1)@
En el razonamiento con los programas nos interesar conseguir asertos que sean fortalecimientos o debilitamientos de otros dados, es decir, asertos que sean ms fuertes (satisfechos por
menos estados) o ms dbiles (satisfechos por ms estados) que uno dado. Una forma de hacerlo
es aadir una condicin adicional fortalecimiento o aadir una condicin alternativa
debilitamiento.

Dada una asercin P, decimos que PQ 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 PQ 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 distintas 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 conectivas lgicas o a los cuantificadores.
Leyes de las operaciones y los dominios predefinidos
Son leyes que se derivan de las propiedades que verifican las operaciones habituales en los tipos predefinidos. De forma resumida dichas propiedades son:

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
PQ QP
PQ QP

PlQ QlP
Asociatividad
P (Q R) (P Q) R

P (Q R) (P Q) R
Idempotencia
PP P

PP P
Distributividad
P (Q R) (P Q) (P R)

P (Q R) (P Q) (P R)
Absorcin
P (P Q) P
P (P Q) P

Diseo de algoritmos iterativos

17

Neutros
P falso falso
P cierto falso
P falso P

P cierto cierto
Contradiccin

P P falso
Tercio excluido

P P cierto
Doble negacin

(P) P
Leyes de De Morgan
(P Q) P Q

(P Q) P Q
Implicacin y doble implicacin
P o Q P Q
P l Q (P o Q) (Q o P)

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 cuantificadores. 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) : (PQ)(x)
(x : D(x) : P(x)) (x : D(x) : Q(x)) x : D(x) : (PQ)(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 cuantificador 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 arbitrario 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 minimizacin, 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 cuantificadores 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 D1 y D2
i : i (D1 D2) : a(i) { i : i D1 : a(i) + i : i D2 : a(i)
Anlogamente, se tienen propiedades similares para los cuantificadores producto extendido
(*), existencial (), 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 W1, ..., Wn es parcial si para algn i {1, ..., n}, existe un valor en DWi, para el que no est definida. La notaremos empleando una flecha cortada entre
el dominio y el codominio declarado

Diseo de algoritmos iterativos

19

f: W1 ... Wn o W
Decimos que es total en caso contrario.
Una operacin definida con una operacin total puede no estar definida debido a que uno de
sus argumentos contenga una expresin que no est definida. Por ejemplo
dada la operacin parcial
div : Ent Ent o Ent
la operacin * : Nat Nat o Nat es total, sin embargo la expresin
(x div 0) * 2
no est definida, independientemente del estado que se tome para asignar valores a las variables libres.
El empleo de ciertas expresiones en una especificacin se ve limitado por el hecho de que la
expresin pueda no estar definida. Para poder controlar esta situacin y obtener condiciones que
reflejen el comportamiento de los programas, emplearemos asertos de definicin.
Dada una expresin E, el aserto de definicin de E es
def(E)
Dada una expresin E y un estado V, definimos el significado de def(E) bajo V como:
cierto
si val>E,V@ est definido
val>def(E),V@ =def
falso
en otro caso
Utilizaremos los asertos de definicin para completar los asertos donde aparezcan expresiones
con operaciones parciales. Por ejemplo
Sea E una expresin donde interviene una operacin parcial. El aserto
E*0 = 0
no se puede garantizar que sea siempre cierto. Pues el significado del producto depende de
que sus dos argumentos estn definidos. Lo que s ser cierto es el aserto
def(E) o (E*0=0)
ya que si el aserto de definicin es cierto, el aserto de la conclusin est definido y tambin lo
es; y si el aserto de definicin es falso, la semntica de la conectiva o garantiza que el aserto es
cierto. (No se consigue lo mismo con ?)
Ntese que segn la definicin de los asertos de definicin, se incluye tambin la condicin de
que las variables que aparezcan en una expresin E hayan sido declaradas. Si no fuera as,
val>E,V@ no estara definido, ya que el estado V no les dara valor.

Diseo de algoritmos iterativos

1.1.2

20

Especificacin pre/post

Pasaremos ahora a definir las especificaciones con precondiciones y postcondiciones, y a establecer su significado de modo formal. De esta manera podremos emplearlas para la especificacin
de algoritmos y programas. Esto lo mostraremos por medio de ejemplos que ilustrarn las posibilidades del empleo de asertos para definir condiciones de los programas. Por ltimo, estableceremos una serie de propiedades bsicas de las especificaciones que estarn relacionadas con la
verificacin de algoritmos.
Definimos una especificacin con precondiciones y postcondiciones, o especificacin
pre/post, para un programa o algoritmo A como un esquema:
D
{P}
A
{Q}
donde:
De es un conjunto de declaraciones de constantes y variables disponibles para el programa A

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 ejecucin 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 demostrar 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 tambin la especificacin como ayuda para el diseo del programa; en tal caso, A ser desconocido,
dispondremos de P y Q, y habremos de disear P de manera que se cumpla { P } A { Q }: derivacin de programas a partir de la especificacin.
Es importante hacer notar que siempre consideramos que estn inicializadas todas aquellas variables a las que haga referencia la precondicin.
Variables auxiliares
Antes de pasar a ejemplos concretos de especificaciones pre/post, necesitamos introducir un
mecanismo ms: las variables auxiliares de la especificacin.
Supongamos que queremos especificar la el programa que realiza la divisin entera de dos
nmeros naturales:

vara,b,c,r:Nat;
{b>0}
dividir
{(a=b*c+r)(r<b)}

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 especificacin, que nos servirn para recoger los valores de entrada que nos interese para escribir las especificaciones. Por ejemplo, en el caso de dividir:

vara,b,c,r:Nat;
{b>0a=Ab=B}
dividir
{(A=B*c+r)(r<B)}

Usamos las variables auxiliares A y B, cuyo valor establecemos en la precondicin. Por convenio, escribimos en minscula las variables de programa y en mayscula las auxiliares de la especificacin. El programa no puede modificar el valor de las variables de especificacin ya que no

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 existencia de valor. Aquellas variables para las que no se identifica un valor inicial en la precondicin
podemos suponer que no estn inicializadas; es lo que ocurre con las variables de salida, destinadas a recoger el resultado del algoritmo. En [Pe05] se propone el uso de un valor especial indefinido (A), que se aade a cada dominio, y que se supone el valor de las variables no inicializadas.
Las variables auxiliares de la especificacin se utilizan tambin para indicar que ciertas variables de programa no cambian de valor. As, por ejemplo, podemos reforzar an ms la postcondicin de la divisin entera como

{(A=B*c+r)(r<B)(a=A)(b=B)}

Recapitulando, en nuestras especificaciones aparecern los siguientes tipos de variables:

Ligadas. Variables introducidas por cuantificadores y expresiones extendidas. No representan a la memoria del programa y los estados no han de asignarles valores. Se pueden
renombrar sin que se modifique el valor o el significado de la expresin o el aserto en el
que aparecen.

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 interesa 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 postcondicin 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=Xy=Y}
intercambio
{x=Yy=X}

Copia del valor de una variable

varx,y:Ent;
{x=X}
copia
{y=Xx=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, sabiendo que x contiene un valor que en el cuadrado de un entero.
Una primera versin

varx,y:Ent;
{x=X}
Raz
{y*y=X}

Es incompleta porque no exige a los valores de entrada las propiedades necesarias para poder
ejecutar el programa.

Diseo de algoritmos iterativos

24

varx,y:Ent;
{x=XX=Y*Y}
Raz
{y*y=Xx=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, podramos leer la precondicin como que X es el cuadrado de un nmero real. Finalmente

varx,y:Ent;
{x=XX=Y*YY:Ent}
Raz
{y*y=Xx=X}

Al escribir esta especificacin uno se puede sentir tentado de escribir la postcondicin como

{y=Yx=X}

que es vlida segn la semntica definida para las especificaciones pre/post, pues si se parte de
un estado que cumple la precondicin se alcanzar un estado en el que y es la raz cuadrada exacta de x. El problema es que para poder relacionar la postcondicin con el significado que se pretende dar al programa es necesario tener en cuenta la precondicin y eso hace que dicha
especificacin resulte poco til en determinadas circunstancias, por ejemplo si quisiramos derivar el algoritmo raz a partir de ella.
Deteccin de potencias de 2
Dada la declaracin de variables:

var
x:Ent;
r:Bool;

se trata de especificar un algoritmo que detecte si el valor de x es una potencia de 2, devolviendo en la variable r el valor cierto o falso, segn sea el caso.
En la precondicin slo hemos de exigir que x tenga valor

{x=X}

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

{rlPx=X}

En cuanto a la forma de P, ser algo como


i:Nat:x=2i

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?
{rli: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 modificar.
En la precondicin indicamos que el vector ha de tener componentes (para que exista un
mximo) y estar todas ellas inicializadas. En la postcondicin utilizamos la expresin extendida
mximo para indicar la condicin

cte
N=...;
var
v:Vector[1..N]deEnt;
m:Ent;
{N1v=V}(*v=Vexpresaquetodaslascomponentesdevtienen
valor*)
mximo
{m=maxi:1iN:v(i)v=V}

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

N1v=Vx=Xy=Y

Podramos cambiar la especificacin para aceptar N=0.


En la postcondicin debemos indicar para cada componente del vector que si a la entrada tena valor x a la salida debe tener valor y

Diseo de algoritmos iterativos

27

i:1iN:(V(i)=Xov(i)=Y)

Pero con esto no es suficiente, porque debemos indicar adems que las componentes distintas
de x conservan su valor. Para ello debemos reforzar la postcondicin con una frmula similar a la
anterior

i:1iN:(V(i)=Xov(i)=Y)
i:1iN:(V(i)zXov(i)=V(i))

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;
{N1v=Vx=Xy=Y}
Reemplazar
{i:1iN:[(V(i)=Xov(i)=Y)(V(i)zXov(i)=V(i))]
x=Xy=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 combinacin de signos de los operandos, utilizando la conectiva de implicacin. Sin embargo, sera ms
natural si en la especificacin pudisemos utilizar la operacin valor absoluto, que, desgraciadamente no se encuentra entre las operaciones disponibles para el tipo primitivo Ent. Cmo podramos incorporar la operacin valor absoluto a nuestro lenguaje de asertos? Como ya dijimos al
enumerar las operaciones disponibles, para incluir una nueva slo hace falta especificarla totalmente.
Hagmoslo:

Diseo de algoritmos iterativos

28

varx,y:Ent;
{x=X}
absoluto
{(X0oy=X)(X<0oY=(1)*X)x=X}

A partir de este momento, ya podemos utilizar la operacin abs en los asertos que escribamos, con lo que la especificacin de la divisin entera queda

varx,y,c:Ent;
{x=Xy=YYz0}
divisin
{X=Y*c+R0abs(R)abs(Y)x=Xy=Y}

Ntese cmo aqu introducimos una variable auxiliar de la especificacin R para expresar
una condicin que debe cumplir una variable de programa c. En este caso la variable auxiliar
sirve para representar a un valor que no viene dado por el valor de una variable de programa en
ningn estado. La inclusin de variables auxiliares nuevas en la postcondicin debe ser una notacin abreviada para la inclusin de existenciales:
r:Ent:X=Y*c+r

De forma similar el mdulo quedar

varx,y,r:Ent;
{x=Xy=YYz0}
mdulo
{X=Y*C+r0abs(r)abs(Y)x=Xy=Y}

A partir de este punto podemos utilizar las operaciones div y mod en nuestros asertos, con el
significado dado por estas especificaciones.
Es habitual que en la construccin de una especificacin tengamos que especificar operaciones
auxiliares, esto no tiene que implicar necesariamente que en la implementacin tambin debamos
realizarlas.
Propiedades de una especificacin pre/post
Podemos utilizar las especificaciones pre/post para obtener el programa que las cumple
derivacin o para demostrar que un cierto programa las cumple verificacin. En cualquier
caso necesitaremos razonar con las especificaciones.
El mtodo de razonamiento consistir en aplicar ciertas reglas que nos permiten asegurar que
son correctas ciertas especificaciones, a partir de otras especificaciones correctas y relaciones de
fuerza entre los asertos que las forman. Las reglas estarn compuestas por premisas y conclusiones
separadas por una lnea de quebrado de la siguiente forma:

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 acciones genricos.
Hay ciertas propiedades que cumplen los asertos en virtud nicamente de su definicin y caractersticas. Podemos presentar estas propiedades como reglas de verificacin bsicas, pues nos servirn en el proceso de verificacin de un algoritmo, o en la obtencin de otras reglas.
Dados los asertos P, P, P1, P2, Q, Q, Q1 y Q2 y la accin algortmica A se tienen las siguientes
reglas de verificacin bsicas
Reforzamiento de la precondicin
{ P } A {Q } P P
{P}A{Q}

Debilitamiento de la postcondicin
{ P } A {Q }

Q Q

{ P } A { Q }

Conjuncin en la postcondicin
{ P } A {Q1 } { P } A {Q2 }
{ P } A { Q1 Q2 }

Disyuncin en la precondicin
{ P1 } A {Q } { P2 } A {Q }
{ P1 P2 } A { Q }

Precondicin falsa
{ falso } A { Q }

Postcondicin falsa
P falso
{ P } A { falso }

Para demostrar estas reglas basta con aplicar las definiciones de especificacin y de fuerza de
los asertos.

Diseo de algoritmos iterativos

30

Estas reglas se suelen aplicar de atrs adelante, para probar que se cumple la conclusin probamos que se cumplen las premisas. Por ejemplo, aplicando la regla de la disyuncin en la postcondicin, para demostrar la correccin de un algoritmo para la especificacin:

varx,y:Ent;
{x=Xy=Y}
sumaYDoble
{x=2*Xy=X+Y}

podemos demostrar la correccin de ese algoritmo con respecto a las especificaciones:

{x=Xy=Y}sumaYDoble{x=2*X}

y
{x=Xy=Y}sumaYDoble{y=X+Y}

Las leyes de equivalencia junto con estas reglas proporcionan un clculo para razonar con predicados y especificaciones, alternativo al razonamiento en trminos de estados. Las leyes se toman como axiomas del clculo y las reglas como un procedimiento de deduccin para obtener nuevas
leyes. Se demuestra que dicho clculo es correcto, lo que quiere decir que toda equivalencia deducida por l es vlida en la lgica de predicados. Sin embargo, cuando se admiten dominios de valores cualesquiera, el clculo no es completo, lo que significa que no toda equivalencia vlida en la
lgica de predicados es deducible mediante el clculo.
Estas reglas no forman un mtodo completo de verificacin de algoritmos. Necesitamos poder entrar en la forma de las acciones A para poder llevar a cabo la verificacin. Como veremos
en el apartado dedicado a las estructuras algortmicas bsicas.
Otro mtodo de razonamiento sobre las especificaciones radica en el uso del transformador de
predicados precondicin ms dbil que nos permitir razonar sobre la correccin de los programas
utilizando solamente asertos con los que razonaremos utilizando el clculo de predicados.
Precondicin ms dbil
Las reglas de verificacin bsicas son un mecanismo incompleto para demostrar la correccin
de los programas; vamos a introducir otro concepto que nos permitir razonar dentro del mbito
de la lgica de predicados de primer orden.
Supongamos que nos encontramos con una accin algortmica A y queremos comprobar si
cumple la especificacin formada por P y Q como precondicin y postcondicin, respectivamente, con unas ciertas declaraciones que omitimos. Esto es, hemos de comprobar:
{P} A {Q}
La idea de la verificacin es proceder de atrs hacia delante. La especificacin se cumplir
cuando partiendo de un estado que cumpla P se llegue a un estado que cumpla Q. Vamos a pre-

Diseo de algoritmos iterativos

31

guntarnos cmo son los estados tales que partiendo desde ellos y ejecutando A se llega a Q. Estos estados se caracterizarn por unas condiciones, por un aserto R que cumplir:
{R} A {Q}
En este punto, si resulta que P es ms fuerte que R, puede aplicarse la regla de reforzamiento
de la precondicin y obtener as que el algoritmo es correcto.
Analizaremos para cada posible accin A de un lenguaje concreto cul es ese aserto R. Antes
formalizaremos este concepto y estudiaremos algunas de sus propiedades.
Dadas una accin A y un aserto Q, definimos la precondicin ms dbil de A inducida por Q,
que notaremos
pmd( A, Q)
como el aserto que cumple
est>pmd( A, Q)@ = { V | A iniciado en V termina en un estado V est>Q@
Ntese que la precondicin as definida es la ms dbil posible pues incluye a todos los estados que cumplen la propiedad de ser iniciales de A para obtener Q.
Aplicando la definicin de especificacin pre/post y la de precondicin ms dbil tenemos
que
Dados una accin A y un aserto Q se cumple
{ pmd( A, Q) } A { Q }
Si se cumple lo anterior y aplicando la regla de reforzamiento de la precondicin obtenemos la
propiedad bsica de las precondiciones ms dbiles
Dados los asertos P y Q, y una accin A se tiene la siguiente regla de verificacin:
P pmd(A, Q)
{P}A{Q}
Esta es la propiedad que nos permite razonar sobre la correccin de los programas en trminos de frmulas lgicas exclusivamente una vez obtenida la precondicin ms dbil.
Las precondiciones ms dbiles cumplen una serie de propiedades que se pueden presentar
como su definicin axiomtica [Bal93] o como proposiciones que se demuestran utilizan las definiciones de fuerza de los asertos y de especificacin pre/post. Adems, en el problema 12 se pide
que se explique el significado operacional de estas propiedades incluida la propiedad bsica.
Estas propiedades son:

Diseo de algoritmos iterativos

32

Monotona:
Q1 Q2

pmd( A, Q1 ) pmd( A, Q2 )

Exclusin de milagros:

pmd( A, Falso) Falso

Distributividad con respecto a :

pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )

Distributividad con respecto a :

pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )


Como ejemplo veamos la demostracin de la distributividad con respecto a :

En la direccin
Sea V un estado cualquiera que satisface
pmd( A, Q1 Q2 )
por tanto, A iniciado en V termina en un estado V de
est>Q1 Q2@ = est>Q1@ est>Q2@
como V est>Q1@ entonces V satisface
pmd( A, Q1)
y como V est>Q2@ entonces V satisface
pmd( A, Q2)
por tanto V satisface
pmd( A, Q1 ) pmd( A, Q2 )
y al ser V un estado cualquiera que satisface pmd( A, Q1 Q2 ) se tiene
pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )
En la otra direccin () el razonamiento es reversible, fijndonos en que si V comienza en un
estado que cumple pmd( A, Q1 ) pmd( A, Q2 ) termina en un estado V que satisface Q1 Q2,
con lo que se tiene
pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )

Diseo de algoritmos iterativos

33

Aunque estas propiedades nos resulten de utilidad, necesitamos ser capaces de obtener la precondicin ms dbil para cualquier accin algortmica escrita en un determinado lenguaje, para lo
cual debemos fijar el lenguaje que vamos a utilizar para escribir los programas y determinar para
cada una de sus instrucciones cmo se obtiene la precondicin ms dbil inducida por una postcondicin dada. De esto se ocupa el siguiente apartado de este tema.
1.1.3

Especificaciones informales

Aunque las especificaciones pre/post constituyen un mecanismo preciso y conciso de describir el comportamiento de los programas, no debemos pensar que sustituyen por completo a la
documentacin de los mismos. Un poco de lenguaje natural ayuda a completar la informacin
que proporciona una especificacin y facilita la comprensin de los programas.
Un posible esquema de documentacin de una accin:

Nombre de la accin.

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 entradas y las salidas.

Postcondicin. Una explicacin de los asertos que la componen.

Excepciones. Consideraciones adicionales sobre la parcialidad o carcter estricto de la accin.

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 programacin vamos a utilizar? Con el objetivo de que nuestro estudio sea lo ms general posible utilizaremos un lenguaje, que no se corresponde con ningn lenguaje de programacin concreto,
pero que incluye los elementos fundamentales comunes a los lenguajes de programacin imperativa ms habituales. Este lenguaje imperativo genrico que permite expresar operaciones algortmicas es lo que denominamos un lenguaje algortmico.
El lenguaje algortmico incluye todos los tipos de valores que pueden aparecer en las especificaciones 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 respecto 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 axiomas se justificar mediante la obtencin de la precondicin ms dbil inducida por una postcondicin, es decir, preguntndonos cul es la condicin ms dbil que debe cumplir qu es lo
mnimo que debemos exigir el estado inicial para que al ejecutar la instruccin se acabe en un
estado que cumpla la postcondicin.
1.2.1

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 instruccin 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:

PQ
{ 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 expresin E y se asigna su valor a la variable x, con lo que se modifica el estado siempre que el resultado de la expresin tenga un valor distinto al valor original de x.
Qu debe cumplir el estado inicial para llegar a un estado que cumpla Q? La parte interesante
de Q es la que hace referencia a la x, pues la parte que no haga referencia simplemente se debe
mantener. En cuanto a la parte que se refiere a la x, debemos darnos cuenta de que sea lo que sea
que el aserto Q dice sobre x, el aserto inicial debe decirlo sobre el valor que se le ha asignado a la
x, es decir, E. Eso se expresa como
Q [x/E]
Por otra parte, hemos de garantizar que la expresin E puede evaluarse, y por ello un estado
inicial ha de cumplir tambin:
def(E)
con lo que la precondicin ms dbil queda

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:x22*y>5x=Xy=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 variables 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

x2y3

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
<x1,...,xm>:=<E1,...,Em>

siendo las xi variables distintas entre s, y las Ei expresiones de los mismos tipos que los de las
variables xi, para i {1,...,m}. Intuitivamente, se evalan las expresiones Ei y se modifica el estado de forma simultnea, asignando a cada variable xi el valor de la correspondiente expresin Ei.
La simultaneidad es relevante cuando en las Ei interviene alguna de las xi.
La obtencin de la pmd es una extensin de lo realizado en la asignacin simple: hay que garantizar la definicin de las expresiones que se asignan, y que los estados iniciales cumplen la
condicin expresada por la postcondicin en la que cada variable xi se sustituye por la correspondiente expresin Ei.
pmd(<x1, ... , xm> := <E1, ... , Em>, Q) def(E1) ... def(Em) Q[x1/E1, ..., xm/Em]
el axioma que indica la forma de la precondicin para obtener la postcondicin
{ def(E1) ... def(Em) Q[x1/E1, ..., xm/Em] }
<x1, ... , xm> := <E1, ... , Em>
{Q}
y la regla de verificacin que se obtiene aplicando la propiedad bsica de las pmd.
P def(E1) ... def(Em) Q[x1/E1, ..., xm/Em]
{ P } <x1, ... , xm> := <E1, ... , Em> { Q }
Veamos un ejemplo con el intercambio de los valores de dos variables

varx,y:Ent;
{P:x=Xy=Y}
<x,y>:=<y,x>

Diseo de algoritmos iterativos

38

{Q:x=Yy=X}

las expresiones estn definidas al no contener operaciones parciales y estar declaradas las variables
def(x) def(y) x:Ent y:Ent P
y la postcondicin con la sustitucin
x = Y y = X [x/y, y/x]
sustitucin

y=Yx=X

boole

x=Xy=Y
P

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(Ei)
para indicar la componente del vector v en la posicin del valor de Ei. Si v(Ei) aparece a la izquierda 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 independiente. 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;
{1iNi=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 ejercicio 21.b, casi).

cteN=...;%Ent
varv:Vector[1..N]deEnt;
{1v(2)N}
v(v(2)):=1
{v(v(2))=1}


que parece correcto y, de hecho se puede probar que lo es con la regla de verificacin de la
asignacin simple
v(v(2)) = 1 [v(v(2))/1]
sustitucin

1=1

lgebra

cierto

fuerza

1 v(2) N

sin embargo, se puede encontrar un contraejemplo, ya que si partimos de un estado V


V(v(1)) = 2 y V(v(2)) = 2
que cumple la precondicin, se llega a un estado V con:
V(v(1)) = 2 y V(v(2)) = 1
que no verifica el aserto v(v(2)) = 1 pues
val>v(v(2)) = 1, V@ = 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, Ei) que normalmente abreviamos como v(Ei)
modificacin del valor de una componente:
asignar(v, Ei, E) que abreviamos como v(Ei) := E
Los vectores verifican las ecuaciones
valor(asignar(v, i, E), i) = E
i z 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(Ei) := E, Q) def(E) enRango(v, Ei) Q[v/asignar(v, Ei, 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 Ei 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, Ei)
puede sustituirse por

def(Ei) (li Ei 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, Ei) Q[v/asignar(v, Ei, E)] }


v(Ei) := E
{Q}
y la regla de verificacin, aplicando la propiedad bsica de las pmd
P def(E) enRango(v, Ei) Q[v/asignar(v, Ei, E)]
{ P } v(Ei) := 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;
{1iNi=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
sustitucin

valor(v, j) = 0 [v/asignar(v, i, 0)]


valor(asignar(v, i, 0), j) = 0
valor(asignar(v, i, 0), i) = 0 i = j

ecuacin
fuerza

1.2.2

0=0i=j
P

Estructuras algortmicas compuestas

Las acciones que vamos a ver a continuacin permiten combinar otras instrucciones para conseguir acciones ms complejas. Veremos tres tipos de composicin que son las que caracterizan la
programacin imperativa estructurada:

Composicin secuencial

Composicin alternativa

Composicin iterativa

Los lenguajes de programacin suelen incluir ms de una instruccin para cada uno de estos
esquemas, pero, como veremos, las instrucciones de nuestro lenguaje algortmico permiten construir acciones que representen a cualquier construccin disponible en un lenguaje imperativo estructurado. Al reducir el nmero de instrucciones facilitamos la verificacin puesto que tenemos
que tratar con menos reglas.

Composicin secuencial

Su sintaxis es
A1 ; A2
siendo A1 y A2 acciones expresadas en el lenguaje algortmico.
Intuitivamente, el comportamiento es que se ejecuta la accin A1 y a su terminacin (si se produce) se ejecuta la accin A2.
Para obtener la pmd empleamos el concepto de razonamiento hacia atrs. Para llegar a una
postcondicin Q hemos de partir de un estado que cumpla la pmd de A2 inducida por Q y, a su
vez, para alcanzar ese estado intermedio hemos de partir de un estado que cumpla la pmd de A1
inducida por la pmd(A2, Q):
pmd( A1;A2, Q ) pmd( A1, pmd( A2, Q))

Diseo de algoritmos iterativos

42

el axioma se obtiene de la pmd


{ pmd( A1, pmd( A2, Q)) }
A1 ; A2
{Q}
La regla de verificacin supone una relajacin del axioma al permitir utilizar un aserto intermedio R cualquiera que garantice que:

Un estado que cumpla R, tomado como entrada de A2, produce un estado que cumple la
postcondicin

La ejecucin de A1 con entrada en un estado que cumple la precondicin produce un estado que cumple R

La pmd de la segunda accin es un aserto intermedio vlido, pero la regla de verificacin permite considerar otros
{ P } A1 { R } { R } A2 { Q }
{ P } A1 ; A2 { Q }
la obtencin de esta regla se puede justificar por las propiedades de las pmd
Por la definicin de pmd a partir de { P } A1 { R }

P pmd( A1, R)
Por la definicin de pmd a partir de { R } A2 { Q }
R pmd( A2, Q)
Por la propiedad de monotona de las pmd, aplicada a la anterior relacin, con respecto a
la accin A1 ( P Q // pmd(A,P) pmd(A,Q) )

pmd( A1, R) pmd( A1, pmd( A2, Q))


Por transitividad de la fuerza de los asertos (ya que es una inclusin de conjuntos)

P pmd( A1, pmd( A2, Q)) ( pmd( A1;A2, Q ) )


Y por la propiedad bsica de las pmd se obtiene la conclusin de la regla
{ P } A1 ; A2 { Q }

Veamos un ejemplo de verificacin de la composicin secuencial. Vamos a verificar un programa que, dada un cantidad positiva de dinero obtiene a cuntas monedas de 5 y 1 pesetas se
corresponde.

varc,d,p:Ent;

Diseo de algoritmos iterativos

43

%
%c=cantidad,d=monedasde5,p=monedasde1
%
{P:c=CC>0}
d:=cdiv5;
p:=cmod5
{Q:C=d*5+p0d<C0p<5c=C}

En este programa usamos las operaciones div y mod con el significado dado por la especificacin que vimos en el apartado dedicado a los ejemplos de especificacin.
Para aplicar la regla de la composicin secuencial hemos de obtener el aserto intermedio, que
en este caso ser la pmd de la segunda asignacin:
pmd( p := c mod 5, Q)
definicin
sustitucin
lgebra

def(c mod 5) Q [ p/c mod 5]


C = d*5 + c mod 5 0 d < C 0 c mod 5 < 5 c = C
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
lgebra

C = c div 5*5 + c mod 5 0 c div 5 < C c 0 c = C


C = c 0 c div 5 < C c 0

fuerza P
donde, en el ltimo paso hemos aplicado
C=c

C=c

C=cC>0

0 c div 5 < C

C=cC>0

c0

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 programas 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}
A1 ;
{R}
A2 ;
{Q}
La composicin secuencial puede hacerse entre acciones del lenguaje cualesquiera, en particular entre otras composiciones secuenciales. Se cumple la propiedad de que la composicin secuencial es un operador asociativo (la demostracin se hace utilizando las propiedades de la pmd
y es el ejercicio 26), es decir:
el programa
A1 ; { A2 ; A3 }
es equivalente al programa
{ A1 ; A2 } ; A3
y escribimos, simplemente
A1 ; A2 ; A3
Con respecto a las pmd se cumple

pmd( A1 ; A2 ; A3, Q ) pmd( A1, pmd( A2 ; A3, Q )) pmd( A1, pmd( A2 , pmd( A3, Q )))
y, en general, la composicin secuencial de n acciones:
pmd( A1 ; ... ; An, Q) pmd( A1, ... pmd( An, Q) ... )
y la regla de verificacin extendida
{ P } A1 { R1 } ... { Rn1 } An { Q }
{ P } A1 ; ... ; An { Q }

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 sintaxis:

var x1 : W1; ... ; xm : Wm;


inicio
A
fvar
siendo las xi 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 precondicin 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 x1 : W1; ... ; xm : Wm;
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 arbitrarios del cdigo. Lo que s es ms habitual es que se puedan definir en los subprogramas: procedimientos y funciones.
Composicin alternativa
Es la instruccin condicional del lenguaje que permite definir una bifurcacin en el flujo de
control del programa. Su sintaxis es

si B1 o A1
 B2 o A2

...
 Bm o Am

fsi
siendo las Bi expresiones booleanas y las Ai acciones del lenguaje algortmico. Cada una de las
estructuras Bi o Ai se denomina accin protegida donde la accin Ai est protegida por la barrera Bi.
Una barrera se dice que est abierta si evala a cierto y se dice que est cerrada en caso contrario.
Intuitivamente la composicin alternativa funciona de la siguiente forma

Se evalan todas las barreras (por lo que stas deben estar definidas)

De entre las barreras abiertas, se elige una de modo indeterminista. Esta eleccin debe poder realizarse siempre (por lo que al menos una barrera debe estar abierta).

Se ejecuta la accin asociada a la barrera seleccionada.

Para obtener la pmd hemos de considerar que todas las barreras deben estar definidas, al menos una de ellas abierta, y que si una barrera est abierta la ejecucin de su accin asociada debe
terminar en un estado que cumpla la postcondicin. La pmd ha de garantizar estas condiciones:

pmd( si B1 o A1 ... Bm o Am fsi, Q )


def(B1) ... def(Bm) [(B1 pmd(A1, Q)) ... (Bm pmd(Am, Q))
Ntese que la disyuncin de los asertos Bi pmd(Ai, Q)) garantiza que al menos una de las
barreras est abierta y que su correspondiente accin conduce a un estado que cumple la postcondicin.
el axioma
{ pmd( si B1 o A1 ... Bm o Am fsi, Q ) }

Diseo de algoritmos iterativos

47

si B1 o A1
B2 o A2

...
Bm o Am

fsi
{Q}
y la regla de verificacin
P def(B1) ... def(Bm)
P B1 ... Bm
{ P B1 } A1 { Q } ... { P Bm } Am { Q }
{ P } si B1 o A1 ... Bm o Am fsi { Q }
Veamos un ejemplo que ilustra el uso de la regla de verificacin de la composicin alternativa:
el mximo de dos nmeros (es el ejercicio 30, aunque all se usa la operacin max)

varx,y,z:Ent;
{P:x=Xy=Y}
sixyz:=x
yxz:=y
fsi
{Q:x=Xy=Y((z=XzY)(z=YzX))}

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 y x, lo cual se cumple ya que

x y y x cierto
por propiedades de los nmeros enteros
La correccin de cada accin en el supuesto de que la barrera est abierta
4. { P x y } z := x { Q }
que verificamos usando la regla de la asignacin
P xy def(x) al estar declarada la variable
Q[z/x]
sustitucin
Boole
Fuerza

x = X y = Y ((x = X x Y) (x = Y x X))
x=Xy=Y(xYx=Y)
x=Xy=YxY
P xy

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
Boole
Fuerza

x = X y = Y ((y = X y Y) (y = Y y X))
x=Xy=Y(y=XyX)
x=Xy=YyX
P yx

Al igual que en la composicin secuencial tambin podemos anotar la composicin condicional con los asertos intermedios oportunos:
{P}
si B1 o { P B1 } A1 { Q }
B2 o { P B2 } A2 { Q }

...
Bm o { P Bm } Am { Q }

fsi
{Q}
El indeterminismo de nuestra composicin secuencial no est presente en los lenguajes de
programacin que optan por la primera barrera abierta (Pascal) o que siguen todas las barreras
abiertas (C). Si queremos que nuestros programas sean deterministas deberemos modificar las
condiciones de las barreras para evitar que ciertos estados abran ms de una barrera a la vez. As
por ejemplo en el anterior programa bastara con cambiar el de la segunda barrera por un mayor estricto. Este indeterminismo [Bal93] es interesante ya que en el diseo algortmico conviene
no descartar posibilidades y tomar el mnimo de decisiones arbitrarias; as, si existen dos maneras
de lograr una postcondicin, el indeterminismo puede permitir que en el algoritmo consten ambas, aunque tal vez en un paso posterior de programacin se descarte una de ellas.
La instruccin condicional que hemos presentado se corresponde con una versin extendida
de la sentencia CASE que suelen incluir los lenguajes imperativos estructurados. La ms habitual
instruccin IF-THEN-ELSE se puede expresar en nuestro lenguaje como:

siBoA1

NOTBoA2
fsi

para facilitar su uso introducimos una nueva forma de la instruccin si


siB
entoncesA1

Diseo de algoritmos iterativos

49

sinoA2
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 incluimos las siguiente instruccin iterativa, que se corresponde con el bucle while de los lenguajes
de programacin:

it B o
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 especificacin de un bucle

{pmd}
itBo
A
fit
{Q}

La idea es que dependiendo del estado de partida, el bucle se ejecutar un nmero diferente de
veces, y todos esos estados deben satisfacer la pmd. Supongamos que partimos de un estado V
que satisface la pmd y que hace que el bucle se ejecute n veces y acabe en un estado que cumple
Q; al ejecutarse la primera vez, termina en un estado V1 que servir como entrada para la siguiente
pasada por el bucle que despus de ejecutarse n1 veces acabar en un estado que cumpla Q, por
lo que V1 tambin debe cumplir la pmd. Es decir, todos los estados intermedios que se alcanzan
al final de cada pasada por el bucle deben cumplir la pmd pues de hecho se pueden ver como
estados iniciales que despus de un cierto nmero de pasadas i alcanzan la postcondicin.

Diseo de algoritmos iterativos

50

Al ejecutarse la ltima pasada por el bucle llegamos a un estado Vn que al tomarse como entrada del bucle har que no se cumpla la condicin de repeticin con lo que el bucle terminar. Pero
an as Vn tambin debe cumplir la pmd pues se corresponde con la situacin en que el bucle no
se ejecuta ninguna vez y se alcanza directamente la postcondicin. La conclusin es que la pmd es
una propiedad que se debe cumplir antes y despus de ejecutar cada pasada por el bucle; es una
condicin que permanece constante durante todas las iteraciones y que por esa razn se denomina invariante del bucle.
Por ejemplo,

<i,q,p>:=<0,0,1>;
iti<no
i:=i+1;

q:=q+p;

p:=p+2;
fit

los estados por los que van pasando las variables


estado

V
V1

0
1

0
1

1
3

V2

3 9 7
V3
.
.
.
.
.
.
.
.
.
.
.
.
podemos observar que en todas las iteraciones las variables guardan una cierta relacin entre
s, en este caso
q = i2 p = 2*i + 1
Por lo tanto para encontrar la pmd debemos encontrar un aserto I que cumpla
(i.2) I garantiza que la condicin de repeticin se puede evaluar
I def(B)
I se preserva al ejecutar cada vuelta del bucle
{IB}A{I}
(i.3) I garantiza que al terminar se cumple la postcondicin
I B Q
Pero con esto no est todo, pues para que un programa sea correcto debe terminar y dado que
el nmero de iteraciones depende del estado inicial, cabe la duda de si se alcanzar un estado que

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 decrecientes infinitas. (Una cadena decreciente es una sucesin de valores donde cada valor es menor
que el anterior.)
Un dominio se dice bien fundamentado si en l existe un orden bien fundamentado.
La idea es que en un dominio bien fundamentado no podemos encontrar cadenas decrecientes
infinitas.
Algunos ejemplos de dominios bien fundamentados:

Todo dominio finito con un orden estricto (el orden estricto impide la repeticin de los
elementos de la cadena y el nmero finito de elementos hace que no puedan existir cadenas infinitas)

Los naturales () con el orden decreciente estricto habitual. (como mucho pueden llegar
hasta 0)

Ejemplo de dominios que no son bien fundamentados

Los reales () con respecto a un orden total. (podemos construir cadenas infinitas crecientes y decrecientes contenidas en cualquier intervalo de los reales)

Los enteros (Z) con respecto a un orden total. (podemos construir cadenas infinitas crecientes y decrecientes.)

Volviendo a la pmd de la composicin iterativa, para definir la pmd hemos de asegurar que
Existe una expresin de acotacin C, que cumple los siguientes requisitos:

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
{IBC=T}A{C % T}
donde T es un valor del dominio de C y % es el orden bien fundamentado.
Ntese que en ocasiones ser necesario incluir en el invariante condiciones que hagan referencia a la expresin de acotacin, para poder as garantizar la terminacin del bucle. con todo esto
ya podemos escribir la pmd de la composicin iterativa como

pmd( it B o A fit ) I
siempre que existan I y C que verifiquen los requisitos (i.2), (i.3), (c.1) y (c.2).
(ntese que son c.1 y c.2 las que garantizan que el bucle termina)
el axioma se obtiene directamente de la pmd
Si existen un aserto I y una expresin C que cumplen los requisitos: (i.2), (i.3), (c.1) y (c.2), entonces
{I}
it B o
A
fit
{Q}
y, por fin, la regla de verificacin, que tambin recoge que la precondicin ha de ser ms fuerte
que la pmd:
(i.1)

PI

(i.2)

I def(B)
{IB}A{I}

(i.3)

I B Q

(c.1)

I B def(C) dec(C)

(c.2)

{IBC=T}A{C % T}

{ p } it B o A fit { Q }
siendo I un aserto y C una expresin

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=Xy=Yy0}
p:=0;
ityz0o
p:=p+x;
y:=y1
fit
{Q:p=X*Y}

Tenemos la composicin de una asignacin con un bucle. Para verificar esta composicin necesitamos un aserto intermedio, y para encontrarlo tenemos dos alternativas:
Deducir un aserto intermedio a partir de la precondicin y el efecto de la asignacin.

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 intermedio R

R: x = X y = Y y 0 p = 0
A continuacin vamos a verificar el bucle, procediendo de atrs hacia delante, tomando R como precondicin de bucle.
Para aplicar la regla de verificacin debemos obtener el invariante y la expresin de acotacin.
Este algoritmo suma x a p y veces. La idea para obtener el invariante es observar que en cada
pasada p se va acercando al valor X*Y incrementndose en x, hasta que al final, como se indica
en la postcondicin, alcanza p=X*Y. As, en una iteracin cualquiera
p + algo = X*Y
cunto vale ese algo? El nmero de x que quedan por sumar a p, que son precisamente y.
p + x*y = X*Y
otra forma de obtenerlo sera considerar que, en una iteracin cualquiera, se cumple que
p = x*(Yy) p = x*Y x*y p + x*y = x*Y
y como x = X en todos los estados p + x*y = X*Y

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 variable entera, por lo que no toma valores en un dominio bien fundamentado; de hecho, si y tuviese un valor negativo el bucle no terminara. Afortunadamente, el aserto R indica que y 0 con lo
que la expresin de acotacin es correcta. An as, es necesario que el invariante incluya esta condicin para que as podamos demostrar las condiciones relacionadas con la terminacin.
Con todo esto el invariante queda

I: p + x*y = X*Y y 0
y la expresin de acotacin
C: y
pasamos entonces a demostrar las condiciones de la regla de verificacin.

{R:x=Xy=Yy0p=0}
ityz0o
p:=p+x;
y:=y1
fit
{Q:p=X*Y}

(i.1) R I
Ry0
Rx=Xy=Yp=0
x*y = X*Y p = 0
0 + x*y = X*Y p = 0
p + x*y = X*Y
donde se han aplicado propiedades del lgebra de los enteros, con lo que
R p + x*y = X*Y y 0
I
(i.2) que pide en primer lugar I def(B)

Diseo de algoritmos iterativos

55

def( y z 0) cierto
I
Adems, hay que probar
{IB}
p:=p+x;
y:=y1

{I}
para lo cual aplicamos la regla de la composicin secuencial, obteniendo la pmd de la segunda asignacin inducida por la postcondicin
def(y1) I[y/y1]
sustitucin

cierto p + x*(y1) = X*Y y1 0

lgebra p + x*y x = X*Y y1 0


que llamaremos R2. Ahora hallamos las pmd de la primera asignacin inducida por R2.
def(p+x) R2[p/p+x]
sustitucin

cierto p + x + x*y x = X*Y y1 0

lgebra p + x*y = X*Y y1 0


que llamaremos R1. Ahora slo nos queda comprobar que la precondicin implica a R1
I B R1
lo cual se obtiene, aplicando propiedades del lgebra de los enteros, y razonando por partes de R1
I
IB

p + x*y = X*Y
y0yz0
y>0
y1
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
IB

dec(y)

y>0
y0yz0

Diseo de algoritmos iterativos

56

IB
(c.2) Hay que probar la correccin de
{ I B y = T}
p:=p+x;
y:=y1

{y<T}
para lo cual procedemos aplicando la regla de la composicin secuencial, obteniendo la
pmd de la segunda asignacin inducida por la primera
def(y1) y<T[y/y1]
sustitucin

cierto y 1 < T

Boole y 1 < T
que llamaremos R3. Ahora tendremos que obtener la pmd de la primera asignacin inducida por R3, que ser def(p+x) R3[p/p+x], pero como p+x est definida y en R3 no
aparece p como variable libre, nos queda el mismo aserto R3. Para probar la correccin de
la composicin nos resta demostrar
I B y = T R3
lo que se obtiene
y=T

y1<T
R3

Con esto queda demostrada la correccin del bucle para la precondicin R, ahora nos queda
probar
{P}
p := 0
{R}
aplicando la regla de la asignacin
def(0) R[p/0]
sustitucin
Boole y lgebra

cierto x = X y = Y y 0 0 = 0
x=Xy=Yy0
P

con lo que es correcta esta asignacin y, por la regla de la composicin secuencial, es correcto
tambin todo el algoritmo.
Si en lugar de postular el aserto intermedio R hubisemos utilizado el invariante como aserto
intermedio, la verificacin hubiese sido similar. La condicin i.1 se habra simplificado al reducirse a I I, mientras que se habra complicado la demostracin de la correccin de la asignacin
p := 0, al tener que demostrar P I [ p/0 ]; para lo cual hay que utilizar un razonamiento similar
al utilizado en el ejemplo para el requisito i.1

Diseo de algoritmos iterativos

57

Al igual que ocurre con las dems instrucciones compuestas, en la composicin iterativa tambin se define un formato de programa anotado
{P}
{I;C}
it B o
{IB}
A
{I}
fit
{ I B }
{Q}
Para la verificacin de los bucles es imprescindible encontrar un invariante y una expresin de
acotacin, lo cual en muchos casos no resulta trivial. Es una destreza que se desarrolla con la
prctica.
Ntese que pueden existir muchos asertos invariantes de un bucle. El que se necesita ha de ser
suficientemente fuerte para que, junto con B, conduzca a la postcondicin, y lo bastante dbil
para que se cumpla antes de la primera iteracin (lo implique la precondicin) y el cuerpo del
bucle lo mantenga invariante.
Obtencin de invariantes
Podemos plantear dos estrategias particulares y ciertas recomendaciones generales:

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
IBQ

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 condicin 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 expresin de acotacin se podra aadir al intentar demostrar (c.1) y no obtener que la expresin de
acotacin es decrementable, por lo que se hara necesario aadir y 0. A veces se puede fortalecer el invariante segn avanza la verificacin, y an as no ser necesario modificar la demostracin
ya realizada, pues si una versin debilitada del invariante es suficiente para demostrar un cierto
requisito tambin lo ser el invariante completo.
2. Reemplazar constantes por variables.

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 obtener 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 postcondiciones 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 forma que en un punto intermedio de la ejecucin del bucle slo habra una parte del vector a 0.
Dependiendo de si la iteracin asciende o desciende por el vector, la constante candidata a ser
sustituida por una variable ser N o 1.
En general podemos ofrecer las siguientes
Recomendaciones generales
Analizar la postcondicin

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 expresin 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 z N+1
y condicin de terminacin
B : i = N+1
que es el mayor de los valores que toma i; para que la expresin de acotacin vaya disminuyendo hasta llegar al valor minimal podemos tomar
C:N+1i
de forma que
i=N+1N+1i=0
Resulta ms complicado cuando la condicin de terminacin no involucra expresiones que
tomen valores en dominios bien fundamentados, en cuyo caso puede ser necesario aplicarles una
funcin de transformacin para conseguirlo.
En general podemos ofrecer las siguientes
Recomendaciones generales
Observar la condicin de terminacin.

Estudiar las expresiones que involucra y las variables que contiene y que se ven modificadas en el cuerpo del bucle.

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. Nosotros 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 correspondiente traduccin a la instruccin it. La utilizacin de estas instrucciones en los algoritmos supone 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;
itNOTBoAfit


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<finalo
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>finalo
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>0o
iti<finalo
A;
i:=i+paso
fit
paso<0o
iti>finalo
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 accin.

Parmetros reales: las expresiones que se utilizan en el lugar de los parmetros formales
en cada llamada concreta.

El reemplazamiento de los parmetros formales por los parmetros reales se llama paso de
parmetros. Este proceso implica un flujo de informacin cuyo sentido viene dado por el modo de
uso de cada parmetro
Definimos el modo de uso de los parmetros, que se ha de especificar en la definicin de la
accin parametrizada, como una de las siguientes posibilidades
Entrada, que notaremos como e.

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 valor al correspondiente parmetro formal)

Formal. Se trata como una constante local que se inicializa con el valor del parmetro
real

Parmetros de salida

Real. Slo puede ser una variable, que al finalizar puede haber sido modificada. El
paso de parmetros la hace corresponder con el correspondiente parmetro formal

Formal. Se trata de una variable local no inicializada (no se puede suponer nada sobre
su valor)

Parmetros de entrada/salida

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 variable del parmetro real.

Podemos resumir en una tabla las caractersticas de los parmetros reales


Modo de uso debe ser variable
e
s
es

NO
SI
SI

Parmetro real
es consultado
SI
NO
SI

puede modificarse
NO
SI
SI

Ntese que no permitimos realizar asignaciones a los parmetros de entrada: esto puede significar la definicin de variables auxiliares adicionales.
En el lenguaje algortmico tenemos dos tipos de acciones parametrizadas: procedimientos y
funciones.
1.3.1

Procedimientos

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 indicar su nombre y sus parmetros formales: la cabecera del procedimiento
Definimos una especificacin de un procedimiento como un esquema EP:
proc nombreProc (e u1:W1; ; un:Wn; s v1:G1; ; vm:Gm; es w1:U1; ; wp:Up);
{ P0: P u1 = U1 un = Un w1 = W1 wp = Wp }
{ Q0: Q u1 = U1 un = Un }
fproc
donde
los parmetros ui, vj y wk son identificadores distintos para i= 1, , n, j = 1, , m y k=1,
, p

los Wi, Gj y Uk son tipos del lenguaje algortmico y representan los tipos de los parmetros
para i = 1, , n, j=1,,m y k=1,,p

P0 no contiene expresiones con los parmetros vj 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 precondicin 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);
{P0:x=Xy=YY>0}
{Q0:x=c*y+rr<yx=Xy=Y}


Procedimiento que busca un valor en un vector


procbusca(ev:Vector[1..N]deNat;ex:Nat;spos:Nat);
{P0:v=Vx=X}
{Q0:(1posNv(pos)=x)l(i:1iN:v(i)=x)x=Xv=
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 u1:W1; ; un:Wn; s v1:G1; ; vm:Gm; es w1:U1; ; wp:Up);
{ P0 }
cte ;
var ;
inicio
A
{ Q0 }
fproc
donde
la cabecera de la declaracin, la precondicin P0 y la postcondicin Q0 coinciden con la
especificacin del procedimiento

Las declaraciones locales pueden ser vacas, en cuyo caso no aparecern las palabras cte ni
var

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 respeta el modo de uso de los parmetros.

El ltimo requisito garantiza que no existen efectos colaterales, es decir no hay cambios en el estado que no recojan las especificaciones. En la prctica los lenguajes permiten que no se respete el
modo de uso de los parmetros, por ejemplo, se puede modificar el valor de un parmetro de
entrada sin que ello afecte al parmetro real.
Por ejemplo, para el algoritmo de divisin entera tenemos la siguiente declaracin:

procdivMod(ex,y:Nat;sc,r:Nat);
{P0:x=Xy=YY>0}
inicio
<c,r>:=<0,x>;
{I:x=c*y+ry>0x=Xy=Y;
C:r}
itryo
c:=c+1;
r:=ry
fit
{Q0:x=c*y+rr<yx=Xy=Y}

Llamadas a procedimientos
El ltimo mecanismo que necesitamos introducir es el de las llamadas a los procedimientos.
Definimos una llamada a un procedimiento con una especificacin EP como la instruccin LP
nombreProc(E1, , En, y1, , ym, z1, , zp)
donde

Las Ei son expresiones de los tipos Wi, para i=1,, n

Las yj y zk son variables distintas que admiten los tipos Gj y Uk, respectivamente, para
j=1,,m y k=1,,p

Las variables yj y zk no aparecen en las expresiones Ei, para i=1,,n, j=1,,m y k=1,
,p

La ltima condicin es necesaria para que las reglas de verificacin funcionen correctamente,
como veremos despus. Si no se cumpliese este requisito tendramos que introducir variables
auxiliares que nos permitiesen realizar la verificacin.
Los tipos de los parmetros reales no tienen que coincidir exactamente con los de los parmetros formales cuando existen relaciones de compatibilidad entre tipos. As, para un parmetro for-

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 podemos usar un parmetro real de cualquier tipo con el que Ent sea compatible: Ent, Rac, Real. En
los parmetros de entrada/salida se combinan las dos restricciones de forma que el tipo debe ser
exactamente el mismo. La idea es que en el flujo de informacin que implica el paso de parmetros, se puede asignar a una variable de un cierto tipo un valor de un tipo con menos informacin.
Verificacin de procedimientos
Las llamadas a procedimientos se consideran como instrucciones del lenguaje algortmico, y
como tales, deben llevar asociada su regla de verificacin.
{P}
nombreProc(E1, , En, y1, , ym, z1, , zp)
{Q}
Podramos sustituir la llamada al procedimiento por su cuerpo y verificar as, pero no queremos tener que verificar el cuerpo cada vez, nos queremos aprovechar de la abstraccin funcional.
De esta forma, lo que haremos ser verificar por separado (y una sola vez) la declaracin del procedimiento con respecto a su especificacin, y la verificacin de las llamadas se reducir a verificar su correccin con respecto a la especificacin del procedimiento.
Para demostrar que una declaracin de procedimiento DP es correcta con respecto a su especificacin EP, es necesario aplicar las reglas de verificacin al cuerpo del procedimiento. Si el
cuerpo del procedimiento contiene llamadas a otros procedimientos, habr que aplicar a stas las
reglas de verificacin de llamadas.
Con respecto a la correccin de las llamadas, la idea bsica consiste en demostrar que la precondicin de la llamada es ms fuerte que la precondicin del procedimiento y que la postcondicin del procedimiento es ms fuerte que la postcondicin de la llamada. Pero cuidado: puede
haber una parte del estado que no se vea afectada por la ejecucin del procedimiento; las expresiones de la precondicin de la llamada que no hagan referencia a variables de salida del procedimiento, debern aparecer en la postcondicin, pero no ser posible obtenerlas a partir de la
postcondicin del procedimiento, pues ste no las afecta.
Por ejemplo

{x=Xy=Yy>0h=2*x}
divMod(x,y,c,r)
{x=y*c+rr<yx=Xy=Yh=2*x}

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(E1, , En, y1, , ym, z1, , zp), Q)
F P0[u1/E1, , un/En, w1/z1, , wp/zp]
siendo F el aserto ms dbil que cumple
F Q0[u1/E1, , un/En, v1/y1, , vm/ym, w1/z1, , wp/zp] Q
De aqu podemos obtener directamente el axioma de la llamada y la regla de verificacin. Sin
embargo, la regla de verificacin as obtenida no es fcil de aplicar porque es necesario encontrar
en cada caso el aserto F. Es por ello que, a partir de esta misma idea lo que se ve afectado por el
procedimiento y lo que no definimos un mtodo de verificacin de llamadas en tres pasos.
Mtodo de verificacin de llamadas:
6. Comprobar si se cumple:
P P0[u1/E1, , un/En, w1/z1, , wp/zp]
(Ntese que para que se cumpla esto es necesario que se cumpla la parte de P0 que hace
referencia a que los parmetros de entrada y entrada/salida deben tener valor y adems
estos valores deben ser de los tipos adecuados. Esto ha de comprobarse de forma ms
cuidadosa cuando se trata de tipos sobre los que hay definida alguna relacin de compatibilidad.)
7. Extraer de
P P0[u1/E1, , un/En, w1/z1, , wp/zp]
todas las condiciones que dependen de variables que han de dar algn resultado de salida
y1, , ym, z1, , zp
y definir R como el aserto restante.
(En general no basta con eliminar los asertos donde hay variables de entrada o entrada/salida, y puede ser necesario elaborar los asertos para no perder condiciones derivadas.)

8. Comprobar el requisito
R Q0[u1/E1, , un/En, v1/y1, , vm/ym, w1/z1, , wp/zp] Q
Se puede demostrar que si el mtodo de verificacin termina, la llamada es correcta, partiendo
de la pmd y razonando sobre la fuerza de los asertos, teniendo en cuenta que F es el aserto ms
dbil que puede hacer el papel de R.
Veamos un ejemplo de verificacin de una llamada:

Diseo de algoritmos iterativos

68

dada la declaracin
procacumula(ess:Ent;ex:Ent);
{P0:x=Xs=S}
inicio
s:=s+x
{Q0:s=S+xx=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 P0[s/suma, x/2*a*b]


















 
PP0[s/suma,x/2*a*b]
  suma>a*b2*a*b=Xsuma=S

P0[s/suma,x/2*a*b]
2*a*b=Xsuma=S
cierto
P

(**)

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

las condiciones que dependen de variables de salida, en este caso suma. Para ello es necesario extender primero el aserto




 
suma>a*b2*a*b=Xsuma=S
  suma>a*b2*a*b=Xsuma=SS>a*b

con lo que obtenemos

R: 2*a*b = X S > a*b


11. Comprobar
 RQ0[s/suma,x/2*a*b]




X








 Q



2*a*b=XS>a*bsuma=S+2*a*b2*a*b=
 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 cumple la restriccin de que las variables reales de salida o entrada/salida no pueden aparecer en las
expresiones correspondientes a los parmetros reales de entrada.
{ P : suma = 5 }
Acumula(suma, suma)
{ Q: suma = 3 }
Intuitivamente el resultado debera ser 10, pero como la llamada no cumple la restriccin indicada veamos qu ocurre cuando intentamos verificarla:
12. P P0[s/suma, x/suma]















P0[s/suma,x/suma]
suma=Xsuma=S
cierto
P

ya que se cumple suma:Ent por declaracin de esa variable


13. Extraer de



 
PP0[s/suma,x/suma]
  suma=5suma=Xsuma=S

las condiciones que dependen de variables de salida, en este caso suma. Para ello es necesario extender primero el aserto




 
suma=5suma=Xsuma=S
  suma=5suma=Xsuma=SX=5S=5

con lo que obtenemos

R: X = 5 S = 5
14. Comprobar









RQ0[s/suma,x/suma]















Con lo que la llamada es correcta.

Q

RQ0[s/suma,x/suma]
X=5S=5suma=S+sumasuma=S
X=5S=5S=0suma=S
falso
Q

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 procedimiento es, en esencia, equivalente a establecer que se cumple la postcondicin aplicada sobre
los argumentos de la llamada.
Es decir dada una especificacin de procedimiento EP

proc nombreProc (e u1:W1; ; un:Wn; s v1:G1; ; vm:Gm; es w1:U1; ; wp:Up);


{ P0: P u1 = U1 un = Un w1 = W1 wp = Wp }
{ Q0: Q u1 = U1 un = Un }
fproc
entendemos que el aserto
nombreProc( E1, , En, y1, , ym, z1, , zp)
es equivalente a
Q[u1/E1, , un/En, v1/y1, , vm/ym, w1/z1, , wp/zp]
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 (u1:W1; ; un:Wn) dev v1:G1; ; vm:Gm;
{ P0: P u1 = U1 un = Un }
{ Q0: Q u1 = U1 un = Un }
ffunc
donde
los parmetros ui y vj son identificadores distintos para i= 1, , n, y j = 1, , m

los Wi y Gj son tipos del lenguaje algortmico y representan los tipos de los parmetros para
i = 1, , n, y j=1,,m

P0 no contiene expresiones con los parmetros vj 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 distintos entre s, pues representan valores distintos. La precondicin no puede tener condiciones que
se refieran a los parmetros de salida y, finalmente, la postcondicin ha de exigir que los parmetros de entrada no sean modificados. En las funciones nos referimos a los parmetros de entrada
como argumentos y a los de salida como resultados.
Por ejemplo, la divisin y el mdulo se puede especificar como una funcin
func divMod( x, y : Nat) dev c, r : Nat;
{ P0: x = X y = Y y > 0 }
{Q0: x = c*y+r r < y x = X y = Y }

Declaracin de funciones
Es similar a la declaracin de procedimientos, el cuerpo se compone de las declaraciones, una
accin algortmica y una accin de devolucin, al final, que establece los valores de los parmetros de salida.
Definimos una declaracin de funcin como un esquema DF:
func nombreFunc (u1:W1; ; un:Wn) dev v1:G1; ; vm:Gm;
{ P0 }
cte ;
var ;

Diseo de algoritmos iterativos

72

inicio
A;
{ Q0 }
dev <v1, , vm>
ffunc
donde
la cabecera de la funcin, la precondicin P0 y la postcondicin Q0 coinciden con la especificacin de la funcin EF

las declaraciones locales pueden ser vacas en cuyo caso no aparecern las palabras cte ni
var.

la accin A es una accin del lenguaje algortmico que slo emplea los parmetros que
aparecen en la cabecera y las variables y constantes de las declaraciones locales, y que respeta el modo de uso de los parmetros (no modifica el valor de los parmetros de entrada).

El ltimo requisito garantiza la ausencia de efectos colaterales. Ntese que prohibimos realizar
asignaciones a las variables de entrada; los lenguajes de programacin realmente no imponen esta
restriccin pues las variables de entrada almacenan copias de los parmetros reales.
Los valores que se devuelven deben verificar la postcondicin, y es por eso que la accin de
devolucin aparece detrs de aquella.
Veamos algunos ejemplos de declaracin de funciones:

funcdivMod(x,y:Nat)devc,r:Nat;
{P0:x=Xy=Yy>0}
inicio
<c,r>:=<0,x>
{I:x=c*y+ry>0x=Xy=Y;
C:r}
itryo
c:=c+1;
r:=ry
fit;
{Q0:x=c*y+rr<yx=Xy=Y}
dev<c,r>
ffunc

Una cosa curiosa de este ejemplo es que vemos cmo el invariante de un bucle depende de la
postcondicin (el invariante debe cumplir que I B Q); pues de no ser porque tenemos que
obtenerlo en la postcondicin, no se nos ocurrira incluir las condiciones x=X y=Y en el invariante.
Otro ejemplo:

Diseo de algoritmos iterativos

73

funcmcd(x,y:Nat)devm:Nat;
{P0:x>0y>0x=Xy=Y}
var
a,b:Nat;
inicio
<a,b>:=<x,y>;
{I:mcd(a,b)=mcd(x,y)x=Xy=Y
C:a+b}
itazbo
sia>boa:=ab
b>aob:=ba
fsi
fit;
m:=a;
{Q0:m=mcd(x,y)x=Xy=Y}
devm
ffunc

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
<y1, , ym> := nombreFunc(E1, , En)
donde

las Ei son expresiones de los tipos Wi para i = 1, , n

las yj son variables distintas que admiten los tipos Gj, para j=1, , m. Estas variables actan como los parmetros reales de salida.

Las variables yj no aparecen en las expresiones Ei 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 variables 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 variables y constantes locales que puede ampliar el estado)

al terminar la ejecucin de la accin del cuerpo (si termina) se asignan a los parmetros
reales de salida los valores correspondientes que tengan los parmetros formales en el estado auxiliar final

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 asociada DF que es correcta
pmd(<y1, , ym >:=nombreFunc(E1, , En), Q)
F P0[u1/E1, , un/En]
siendo F el aserto ms dbil que cumple
F Q0[u1/E1, , un/En, v1/y1, , vm/ym] 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 necesario utilizar este mecanismo:
tenemos una funcin cuya especificacin es
func fact( n : Nat ) dev x : Nat;
{P0: n > 0 n = N }
{Q0: x = n! n = N }
(suponemos que ya hemos especificado el factorial) y supongamos que tenemos una declaracin 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 introducir una variable auxiliar de la correccin f
{ P : num > 0 }
var f: Nat;
inicio
f := fact(num);
suma := 5 * f
fvar
{ Q : suma = 5 * num! }
Una declaracin local es correcta si lo es su cuerpo.
El cuerpo lo verificamos como una composicin secuencial. El aserto intermedio que usamos
en la composicin secuencial ser la pmd de la asegunda asignacin
R1: 5*f = 5 * num!
con lo que pasamos a verificar
{ P : num > 0 }
f := fact(num);
{ R1 : 5*f = 5 * num! }
usando el mtodo de verificacin de las llamadas a procedimientos:
15. Comprobamos
P P0[n/num]




 
P0[n/num]
  num>0num=N
  P

ya num:Nat por la declaracin.


16. Extraer de




 
PP0[n/num]
  num>0num=N

todas la condiciones que dependen de las variables de salida. Como ninguna condicin se
refiere a f, tomamos el aserto completo
R : num > 0 num = N
17. Comprobar el requisito
R Q0[x/f, n/num] R1


 

RQ0[x/f,n/num]

Diseo de algoritmos iterativos

76


  num>0num=Nf=num!num=N
lgebrayfuerza  5*f=5*num!

  R1


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 expresiones de entrada).
Uso de las funciones como asertos. Entendido como un aserto, la llamada a una funcin
es, en esencia, equivalente a establecer que se cumple la postcondicin aplicada sobre los argumentos de la llamada.
Es decir dada una especificacin de funcin EF

func nombreFunc (u1:W1; ; un:Wn) dev v1:G1; ; vm:Gm;


{ P0: P u1 = U1 un = Un }
{ Q0: Q u1 = U1 un = Un }
ffunc
entendemos que el aserto
<y1, , ym>:= nombreFunc ( E1, , En)
es equivalente a
Q[u1/E1, , un/En, v1/y1, , vm/ym]
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 distintos rdenes de consumo
Programa Consumo n=1.000

Bytes

Tiempo

Diseo de algoritmos iterativos

77

A1

log2 n

10

10 segs

| 10
1.000

A2

| 1 KB

16 mins

A3

n2

1.000.000

| 1 Meg

11 das

Los programadores de los algoritmos A2 y A3 podran esperar que las mejoras tecnolgicas
hiciesen tiles sus algoritmos. Sin embargo tomemos un volumen de datos de un milln (no descabellado: bases de datos de poblacin, resolucin de impresin, pixeles en pantalla, )
Programa Consumo n=1.000.000
A1
log2 n
| 20
A2
n
1.000.000
A3

n2

1012

Bytes
10

Tiempo
20 segs

| 1 KB

11 das

| 1.000 Gig

30.000 aos

Vemos cmo el programa A1 se ve muy poco afectado por el aumento en el volumen de datos; mientras que los programas A1 y A2 tienen aumentos prohibitivos, que los descalifican para la
tarea.
El programa A1 s puede aprovechar las mejoras tecnolgicas: con un procesador dos veces
ms rpido puede procesar 1000 veces ms informacin; mientras que el programa A3 con un
procesador 1.000 veces ms rpido slo tardara 30 aos en realizar la tarea.
Un algoritmo eficiente es un bien mayor que cualquier mejora tecnolgica; y stas se deben
entender, fundamentalmente, como un medio para procesar un mayor volumen de datos y no
como una forma de remediar la pobre eficiencia de nuestros algoritmos. En resumen

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 ejecutarse pocas veces o con datos de pequeo tamao.

Es conveniente, en todo caso, partir de un diseo claro, aunque sea ineficiente; y optimizar posteriormente.

Diseo de algoritmos iterativos

1.4.1

78

Complejidad de algoritmos

La eficiencia de los algoritmos se cuantifica por medio de medidas de complejidad. Consideramos dos medidas de complejidad diferentes:

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 complejidad en espacio se suele resolver aadiendo ms memoria).
El tiempo (y tambin el espacio) que tarda en ejecutarse un programa en una mquina concreta depende de:

El tamao de los datos de entrada

El valor de los datos de entrada (la mayor o menor dificultad que entrae el procesamiento de los datos)

La mquina y el compilador.

En nuestro estudio vamos a ignorar este tercer factor pues afecta a todos los algoritmos por
igual, como un factor constante (con una mquina el doble de rpida el algoritmo tardar la mitad); y, por lo tanto, no afecta a las comparaciones entre algoritmos.
El tamao de los datos se pueden referir a distintas cosas segn el tipo de datos y de procesamiento que se lleve a cabo: para un vector su longitud, para un nmero su valor o su nmero
de dgitos,
Con respecto al segundo factor, el valor de los datos, y una vez fijado el tamao de los datos,
podemos adoptar dos enfoques, estudiar el peor caso posible, estableciendo as una cota superior; o analizar el caso promedio, para lo cual debemos estudiar las posibles entradas y su probabilidad.

&
Definimos el coste temporal del algoritmo A con entrada x , que notaremos
&
tA( x )
&
como el tiempo consumido durante la ejecucin del algoritmo A con entrada x .
A partir del coste para unos ciertos datos podemos definir el coste en el caso peor y en el caso
promedio
Definimos la complejidad de A en el caso peor, que notaremos
TA(n)
como

Diseo de algoritmos iterativos

79

&
&
TA(n) =def max{ tA( x ) | x de tamao n }
Definimos la complejidad de A en el caso promedio, que notaremos
TMA(n)
como
&
&
TMA(n)=def p( x ) tA( x )
x de tamao n

&
&
&
siendo p( x ) la funcin de probabilidad (p( x ) [0,1]) de que la entrada sea el dato x .
Aunque la complejidad en promedio parece ms realista, su estimacin exige determinar
la distribucin de probabilidades de los datos iniciales y hacer clculos matemticos complejos.
Lo ms habitual es trabajar con la complejidad en el caso peor, que da una cota superior del
tiempo consumido por cualquier cmputo particular con datos de tamao n.
Vamos a realizar los clculos de la complejidad de manera terica a partir de las instrucciones
que aparecen en los algoritmos, viendo cmo afecta cada instruccin al coste. En cierto modo,
contaremos cuntas instrucciones se ejecutan. No nos planteamos la medida emprica del tiempo de ejecucin porque queremos conocer los resultados a priori.
Debido a que las diferencias en el coste se hacen significativas cuando el tamao de los datos
aumenta, no nos va a interesar encontrar la funcin TA(n) exacta, sino que nos bastar con encontrar otra funcin f(n) que sea del mismo orden de magnitud; para que as podamos decir
que para n grande TA(n) se comporta como f(n): medidas asintticas de la complejidad.
Veamos un primer ejemplo donde se calcula el orden de magnitud del coste temporal de un
algoritmo que determina si una matriz cuadrada de enteros es simtrica:

varv:Vector[1..N,1..N]deEnt;
b:BOOL;
{Pre.:v=VN1}
var
I,J:Ent;
inicio
b=cierto;
paraIdesde1hastaN1hacer
paraJdesdeI+1hastaNhacer
b:=bAND(v(I,J)=v(J,I))
fpara
fpara
fvar
{Post:v=Vbli,j:1i<jN:v(i,j)=v(j,i)}

Si tomamos como instrucciones significativas las asignaciones y tomamos a N como tamao


de los datos iniciales tenemos:
TA(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  ( N  1)
* (N1)
=1+
2
N * ( N  1)
=1+
2
2
N para todo N 1
y podemos decir por tanto que el coste del algoritmo A es el del orden de magnitud de N2.

1.4.2

Medidas asintticas de la complejidad

Una medida asinttica es una agrupacin de funciones que muestra un comportamiento similar cuando los argumentos toman valores muy grandes. Estos conjuntos de funciones se definen
en trminos de un funcin de referencia f. As definimos tres medidas asintticas segn que la
funcin de referencia sirva como cota superior, cota inferior o cota exacta de las funciones del
conjunto.
En estas definiciones vamos a considerar funciones T y f con el perfil
T, f: N o R+ {0}
donde N es el dominio del tamao de los datos y R+ el valor del coste del algoritmo (ya sea
temporal o espacial, pues las definiciones que siguen son vlidas para ambos casos, as como para
el caso peor y el caso promedio).
Imaginamos que T representa la complejidad de un cierto algoritmo.
Definimos la medida de cota superior f, que notaremos
O(f)
(omicrn mayscula, que abreviaremos por O grande) como el conjunto
{ T | existen c R+, n0 N tales que, para todo n n0, T(n) c f(n) }
Si T O(f) decimos que T(n) es del orden de f(n) y que f es asintticamente una cota superior del crecimiento de T (las funciones de O(f) crecen como mucho a la misma velocidad que
f).
Definimos la medida de cota inferior f, que notaremos
:(f)
(omega mayscula, que abreviaremos llamndola omega grande) como el conjunto

Diseo de algoritmos iterativos

81

{ T | existe c R+, n0 N tales que, para todo n n0, T(n) c f(n) }


Si T :(f) podemos decir que f es asintticamente una cota inferior del crecimiento de T.
Las funciones de :(f) crecen con igual o mayor rapidez que f.
(En los apuntes de Mario se establece una distincin entre :(f) y :(f).)
Definimos la medida exacta f, que notaremos
4(f)
(theta mayscula, que abreviaremos Theta grande) como el conjunto
{ T | existen c1, c2 R+, n0 N, tales que, para todo n n0, c1 f(n) T(n) c2 f(n)
Si T 4(f) decimos que T(n) es del orden exacto de f(n).
Veamos algunos ejemplos:

f es O(f)

(n+1)2 es O(n2) con c=4, n0=1

3n3+2n2 es O(n3) con c=5, n0=0

p(n) es O(nk) para todo polinomio P de grado k

3n no es O(2n). Si lo fuese existira c R+, n0 N tales que


 n n0 : 3 n c 2 n
 n n0 : (3/2)n c
falso
pues limno(3/2)n =

f es de :(f)

n es de :(2n) con c = , n0=0

(n+1)2 es de :(n2) con c = y n0=1 (tambin valdra con c=1)

P(n) :(nk) para todo polinomio P de grado k

f es 4(f)

2n es 4(n) con c1=1, c2=2 y n0=0

(n+1)2 es 4(n2) con c1=1, c2 = 4, n0=1

P(n) es 4(nk) para todo polinomio P de grado k

Diseo de algoritmos iterativos

82

En estos ejemplos se pueden comprobar que


4(f) = O(f) :(f)
o lo que es igual
4(f) O(f) :(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 aplicadas a funciones concretas como rdenes. Algunos rdenes tienen especial inters porque la funcin de referencia es simple y su comportamiento se puede estudiar fcilmente. Estos rdenes
tienen nombres particulares:

O(1) o orden constante

O(log n) o orden logartmico

O(n) o orden lineal

O(n log n) o orden cuasi-lineal

O(n2) o orden cuadrtico

O(n3) o orden cbico

O(nk) o orden polinmico

O(2n) o orden exponencial

donde usamos log n para referirnos a log2 n.


Reciben los mismos nombres las otras medidas asintticas (:, 4) 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 siguientes relaciones de inclusin entre rdenes de complejidad (jerarqua de rdenes de complejidad):

O(1) O(log n) O( n ) O(n) O(n log n)


O(n2) O(n3) O(nk)
O(2n) 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 puede ser una tarea muy compleja.
Podemos establecer una comparacin entre algoritmos con rdenes de complejidad diferentes,
para ver cmo se ven afectados con el tamao de los datos.
Supongamos dos algoritmos A y B que resuelven el mismo problema pero con complejidades
diferentes:

TA O(log n)
TB O(2n)

35
30
25
20

Serie1
Serie2

15
10
5
0
0

10

12

tenemos por tanto que


TA(n) c log n para n n0

TB(n) c 2n para n n1

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

350000
300000
250000
200000

Serie1
Serie2

150000
100000
50000
0
0

100

200

300

400

500

600

comparacin entre n2 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 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 = (2n)2 duplicar el tamao de los datos eleva al cuadrado el tiempo necesario
2 2n = 2n+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 refiere en realidad a la complejidad.
Como conclusin llegamos a la idea que ya presentamos al principio del tema:
Slo un algoritmo eficiente, con un orden de complejidad bajo puede tratar volmenes de datos grande con un consumo de recursos razonable, y aprovechar las mejoras tcnicas de forma
que produzcan un incremento en el tamao de los datos que puede tratar.
En este sentido, se suele considerar:
Un algoritmo A es

muy eficiente si su complejidad es de orden log n

eficiente si su complejidad es de orden nk (polinmico)

Diseo de algoritmos iterativos

85

ineficiente si su complejidad es de orden 2n

Y decimos que

Un algoritmo es tratable si tiene un algoritmo que lo resuelve con complejidad de orden menor que 2n. Decimos que es intratable en caso contrario.
Para algunos problemas se ha demostrado que efectivamente no existe ningn algoritmo que
los haga tratables. En cambio, para otros, simplemente no se ha encontrado ningn algoritmo,
pero no se ha podido demostrar que no exista.
1.4.4

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 consideraciones sobre la complejidad espacial de las estructuras de datos utilizadas en la implementacin
de tipos abstractos de datos.
Vamos a ir analizando la complejidad para cada una de las estructuras de nuestro lenguaje algortmico.
Antes, presentamos un par de reglas que sirven de utilidad para analizar los algoritmos.

Si T1(n) O(f1(n)) y T2(n) O(f2(n)), se cumplen las siguientes propiedades


Regla de la suma

T1(n) + T2(n) O( max( f1(n),f2(n) ) )


Regla del producto
T1(n) * T2(n) O( f1(n) f2(n) )

Demostracin
Por la definicin de O se tiene:
existen c1, c2 R+; n1, n2 N tales que
 n n1 T1(n) c1 f1(n)

 n n2 T2(n) c2 f2(n)

tomando n0 = max(n1, n2)

suma
T1(n) + T2(n) c1 f1(n) + c2 f2(n)

Diseo de algoritmos iterativos

86

(c1+c2) max( f1(n), f2(n) )

por lo que para n n0 se cumple la propiedad


producto
T1(n) T2(n)

c1 f1(n) c2 f2(n)

(c1 c2) ( f1(n) f2(n) )


por lo que para n n0 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
TA(n) O(1)

x := e <x1, , xr> := <e1, , er>


TA(n) O(1) (excepto si la evaluacin de e, ei es compleja, en cuyo caso sera del
orden del mximo de los rdenes de evaluar las expresiones)

A1 ; A2 y TAi(n) O(fi(n))
TA(n)= TA1(n)+TA2(n)
y por la regla de la suma TA O( max( f1(n), f2(n) ) )

si B1 o A1 Br o Ar fsi

TAi(n) O(fi(n)) (1 i n)
entonces TA O( max( f1(n), , fr(n) ) )
siempre que podamos suponer que la evaluacin de las barreras consume tiempo
constante. Si esta suposicin no es razonable y sabemos que
TBi(n) O(gi(n)) (1 i n)
entonces TA O( max( g1(n), , gr(n), f1(n), , fr(n) ) )

it B o A1 fit

TA1(n) O(f(n))
TB(n) O(g(n))
Sea h(n) =def max( g(n), f(n))
Entonces el tiempo de ejecucin de una vuelta del bucle ser O(h(n))

Diseo de algoritmos iterativos

87

Si podemos estimar que el nmero de vueltas en el caso peor es O(t(n)), tendremos (usando la regla del producto):
TA(n) O(h(n) t(n))
Generalmente es posible una estimacin ms fina de TA(n), estimando por separado el tiempo de cada vuelta y calculando un sumatorio.
Si el tiempo de ejecucin de la vuelta nmero i es O(h(n,i)) entonces:
TA(n) O( i : 1 i t(n) : h(n,i) )
Otras clases de bucles se analizan con ideas anlogas.
Aparte de estas reglas generales, una idea til en muchos casos prcticos es la de accin caracterstica. Se trata de localizar en el texto del algoritmo A las acciones que se ejecutan con ms
frecuencia: las acciones caractersticas. El orden de magnitud de TA(n) se podr estimar calculando el nmero de ejecuciones de acciones caractersticas (en realidad, se calcula una cota superior
de ese nmero). La idea es que, puesto que en la composicin secuencial se suman las complejidades, y la regla de la suma nos dice que la complejidad de una suma es igual al mximo de las
complejidades de los sumandos, podemos hacer los clculos quedndonos directamente con los
mximos. Por ejemplo, en un programa que contenga un bucle, normalmente la accin caracterstica ser el cuerpo del bucle.
Veamos por ltimo un par de ejemplo de clculo de la complejidad.
El primero es una implementacin ms eficiente del algoritmo que comprueba si una matriz es
simtrica

funcesSim(a:Vector[1..N,1..N]deEnt)devb:Bool;
{Pre.:a=A}
varI,J:Ent;
inicio
b:=Cierto;
I:=1;
itI<NANDb
oJ:=I+1
itJNANDb
ob:=bAND(a(I,J)=a(J,I))
J:=J+1
fit;
I:=I+1
fit
{Post.:a=A(bli,j:1i<jN:a(i,j)=a(j,i))}
devb
ffunc

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(N2). Para obtener la complejidad en el caso promedio
deberamos tener una estimacin de la probabilidad de los distintos valores de la matriz, y el
clculo sera muy complejo.
Como tamao de los datos podemos tomar N o N2; optamos por N porque eso nos facilitar
el clculo de la complejidad.
Suponemos que todas las asignaciones, evaluacin de condiciones y acceso a vectores tienen
complejidad constante O(1). Podemos obtener una expresin fina, contando el nmero de instrucciones:
cuerpo del bucle interno:
2
(podramos considerar 3 si cada acceso al vector fuese 1)
coste del bucle interno
((NI) (2+1)) + 1

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
N 1

[ (3(N-I) + 3 + 1)] + 1

donde el 1 interno es la condicin; y el 1 externo la

I 1

condicin de salida
N 1

= 3 (N-I) + 4(N-1) + 1 = 3
I 1

N ( N  1)
+ 4(N-1) + 1
2

= 3/2 N2 3/2 N + 4N 4 +1
= 3/2 N2 + 5/2 N 3
y el coste del algoritmo, considerando las dos asignaciones iniciales
TA(N) = 3/2 N2 + 5/2 N 3 + 2 = 3/2 N2 + 5/2 N 1
con lo que TA(N) O(N2)
Sin embargo, podemos llegar directamente al mismo resultado razonando sobre la instruccin
caracterstica:
Consideramos como acciones caractersticas las asignaciones que componen el cuerpo del bucle ms interno. El bucle interno se ejecuta desde I+1 hasta N, es decir, se ejecuta N-I veces para
cada valor de I, por lo tanto:

N 1

T(N) O(

I 1

(NI)) = O(N2)

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=Xy=Yy0}
p:=0;
ityz0o
sipar(y)
entonces<x,y>:=<x+x,ydiv2>
sino<p,y>:=<p+x,y1>
fsi
fit
{Post.Q:p=X*Y}

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
TA(n) 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/20, n/21, n/22, , n/2k siendo k el nmero de veces que se ha ejecutado el bucle
1 = n/2k
2k = n
k = log n
por lo tanto

si t(n) O(log n) entonces TA(n) O(log n)


Slo para este caso, vamos a ver con ms detalle la demostracin de que t(n) O(log n). Probando con distintos valores de y vemos que el comportamiento del algoritmo es el siguiente:

Diseo de algoritmos iterativos

n=0:
n=1:
n=2:
n=3:
n=4:
n=5:

y=0
y=1
y=2
y=3
y=4
y=5

y=0
y=1
y=2
y=2
y=4

90

y=0
y=1 y=0
y=1 y=0
y=2 y=1 y=0

formulamos la conjetura
t(n) 2 log n para n 2
esta conjetura se demuestra por induccin sobre n
base
n=2
n=3

t(n) = 2 = 2 log(n)
t(n) = 3 2 log(3)

(se ejecuta dos veces, para y=2, y para y=1)

32log(3)2322log(3)23(2log3)2233289cierto


paso inductivo
n > 3, par

n = 2n, n 2

t(n) = 1 + t(n) HI 1 + 2 log(n)


< 2 ( 1 + log(n) )

se ejecuta 1 vez ms las veces de 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)


= 2 ( 1 + log(n) )

se ejecuta 1+1 veces ms las veces de n

= 2 log(2 n)
< 2 log( 2n + 1 )
= 2 log n
1.4.5
max

i min

Expresiones matemticas utilizadas

min  max
( max  min  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 programas obtenidos ya est demostrada su correccin.
Lo principal que aporta este mtodo es que descompone la tarea de la programacin en distintas 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 corresponde 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 precondicin es una igualdad de la forma
x=E
donde E es una expresin que se puede obtener mediante operaciones predefinidas o especificadas.

Composicin secuencial
Usamos esta opcin cuando decidimos que para obtener la postcondicin Q es necesario
obtener un aserto intermedio R, que nos acerca a Q. En ese caso, la derivacin de A se
transforma en la derivacin de dos acciones A1 y A2 que cumplan
{ P } A1 { R }

Composicin alternativa

{ R } A2 { Q }

Diseo de algoritmos iterativos

92

Usamos esta opcin cuando decidimos que los datos de entrada no admiten un tratamiento uniforme para obtener la postcondicin.
Deberemos encontrar condiciones B1, , Bn que discriminen los casos homogneos,
de tal forma que se cumpla
P B1 Bn
Y entonces, para cada condicin Bi deberemos derivar las acciones Ai que verifiquen
{ P Bi } Ai {Q }

para i= 1, , n

Y as, el algoritmo resultante ser de la forma:


A { si B1 o A1 Bn o An 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, entonces es necesario derivar una accin de inicializacin que establezca el invariante.
Esta accin se deriva a partir de la especificacin
{P } A0 { I }
B.6. Accin de avance.
Se deriva una accin A2 que haga avanzar al bucle hacia su terminacin. Se puede derivar
a partir de la especificacin

{ I B C = T } A2 { c % T }
B.7. Es suficiente con la accin de avance?
Nos preguntamos si la accin de avance constituye el cuerpo del bucle. Para ello, debemos demostrar

{ B I } A2 { I }
Si lo cumple, entonces hemos acabado de obtener un algoritmo correcto.
B.8. Restablecimiento del invariante
Si la accin de avance no es suficiente (lo ms habitual), entonces necesitamos derivar una
nueva accin A1 que se encargue de restablecer el invariante dentro del bucle, de forma
que
{ I B } A1 ; A2 { I }
la accin A1 se puede derivar de la especificacin
{ I B } A1 { R }

donde R { pmd( A2, I )


B.9. Terminacin.
Se comprueba que despus de introducir la accin A1 se sigue cumpliendo
{ I B C = T } A1 ; A2 { 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}
varx1:W1;;xn:Wn;
inicio
A0
{I;C}
itBo
{IB}
A1;
{R}
A2;
{I}
fit;
{IB}
fvar
{Q}

Insistir otra vez en que lo que nos aporta este mtodo es que nos obliga a concentrarnos en las
distintas partes del problema, comprobando paso a paso que vamos bien; y, si nos equivocamos, tenemos puntos bien definidos a los que volver.
1.5.2

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 sustituir constantes por variables en las postcondicin.
Hay otra recomendacin general en cuanto a la construccin de invariantes a partir de una especificacin:
Si la precondicin y la postcondicin del bucle contienen una igualdad x=X, el invariante tambin debe incluirla.
No parece razonable aunque no sea imposible que una variable empiece tomando valor, que
se ve modificado por el bucle, para que al final vuelva a tomar el valor inicial.
Existe una diferencia esencial en la obtencin del invariante para una verificacin a posteriori y
una derivacin: en la derivacin tenemos menos informacin, y por lo tanto menos restricciones,
sobre el bucle, de forma que disponemos de ms libertad a la hora de seleccionar el invariante.

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 positivos.
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, repetimos la resta de y.
c veces

xyyy=r
Una vez determinado que nuestra algoritmo va a ser un bucle, aplicamos los pasos de la derivacin de bucles.

B.1. Invariante

las ecuaciones relativas a la conservacin de valores pasan a formar parte del invariante
x= X y = Y
lo siguiente es darnos cuenta de que la parte de la postcondicin relativa a la condicin de
terminacin es r < y: restaremos hasta que el resto sea menor que y. Por lo tanto el invariante es el resto de la postcondicin
I: x = X y = Y x = c*y + r r 0 y > 0

donde hemos obtenido r 0 y > 0, a partir de 0 r < y. Podramos obviar y > 0? En general, mejor que sobre, siempre y cuando las condiciones que incluimos en el invariante sean

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 obtener 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 aparecen 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 demostrar 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 demostracin; 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




IB  x=Xy=Yx=c*y+rr0y>0r<y
  x=Xy=Yx=c*y+r0r<y
  Q

y por tanto, la condicin de repeticin


B:ry

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
  IB

Diseo de algoritmos iterativos












97






dec(C)
r>0
ryy>0
IB

para esta ltima prueba nos hace falta y > 0, que est implcita en la postcondicin y
explcita en la postcondicin; podra ser aqu donde nos disemos cuenta de que debe
formar parte del invariante.

B.5. Accin de inicializacin.

Observamos que no se puede deducir el invariante de la precondicin, y que se hace necesario incluir una accin de inicializacin. Lo ms fcil es asignar valores a las variables
de tal modo que se cumplan los asertos del invariante. La frmula problemtica es
x = y*c + r
tomamos como accin de inicializacin
<c,r> := <0,x>
con lo que nos quedara por demostrar
{P } <c,r> := <0,x> { I }
que es, efectivamente, correcto

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, debemos demostrar
{ B I } r := ry { I }

 
def(ry)I[r/ry]
ciertox=Xy=Yx=c*y+(ry)y>0ry0
x=Xy=Yx=(c1)*y+ry>0ry

tenemos entonces

Ix=Xy=Yy>0
Bry
pero no podemos demostrar
I B x = (c1)*y + r

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 }
A1
{ R : x = X y = Y x = (c1)*y + r y > 0 r y }
Vemos que la nica diferencia se puede solventar con una asignacin
c := c + 1
de forma que al sustituir c/c+1 en la postcondicin, lleguemos a la precondicin.
Efectivamente se puede demostrar
I B R[c/c+1]

B.9. Terminacin.

Por ltimo, nos queda por comprobar que esta nueva accin en el cuerpo del bucle no
afecta a la terminacin, y se verifica
{IBr=T}
c := c +1;
r := r y;
{r<T}
informalmente se puede afirmar que se sigue verificando pues la accin de restablecimiento del invariante no afecta a la expresin de acotacin r. Pero, para completar el proceso,
sera necesario demostrarlo, obteniendo la pmd y verificando que I B r = T es ms
fuerte que ella.
Finalmente el diseo del algoritmo anotado queda:
var x, y, c, r : Ent;

{P:x=Xy=Yy>0x0}
<c,r> := <0,x>;
{ I : x = X y = Y x = c*y + r r 0 y > 0
C:r
}
it r y o

{Iry}
c := c + 1;
{ x = X y = Y x = (c1)*y + r r y y > 0 }
r := r y;

Diseo de algoritmos iterativos

99

{I}
fit
{Ir<y}
{Q:x=Xy=Yx=y*c+r0r<y}
En cuanto a la complejidad del algoritmo; si tomamos como accin caracterstica el cuerpo del
bucle, que es O(1), tenemos que el bucle se ejecuta X div Y veces, con lo que su complejidad es
O(X div Y).
Obtencin del invariante reemplazando constantes por variables

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 expresiones extendidos ( , , , 3, 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 nuevas 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=Vu=U}
prodEscalar
{ Q : r = i : 1 i N : u(i)*v(i) u = U v = V }
La idea del bucle es un bucle, donde, en cada pasada, sumamos al resultado parcial el resultado
de multiplicar la componente j.

B.1. Invariante

Sustituimos la constante N de la postcondicin por una nueva variable j. Adems conservamos los asertos de conservacin de valores que aparecen tanto en la precondicin como en la postcondicin, e introducimos un aserto sobre el rango de la nueva variable. Si
no lo introdujramos aqu, veramos luego que nos hace falta para demostrar que la expresin de acotacin puede decrecer.

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

 
IB
r=i:1ij:u(i)*v(i)u=Uv=V1jNj=
N
r=i:1iN:u(i)*v(i)u=Uv=V1NNj=
N
r=i:1iN:u(i)*v(i)u=Uv=V
Q


la condicin de repeticin queda entonces


B:jzN

B.3. Definicin de la expresin de repeticin

debemos probar si I def(B)


def( j z N ) falso
pues la variable j no est declarada. Debemos pues incluir una declaracin local de variables que incluya al algoritmo que estamos derivando
var j : Ent;
inicio
A
fvar
en cuyo caso
def(j z 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:Nj
Probamos que
I B def(C) dec(C)





 
def(Nj)
  cierto
  IB

Diseo de algoritmos iterativos












101






dec(Nj)
Nj>0
N>j
1jNjzNIB

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 inicializacin. Esta ser una asignacin, y como siempre intentamos que sea de tal forma que se
obtenga la condicin del invariante por anulacin de la expresin conflictiva. Probamos
<r,j> := <0,0>
tendramos que demostrar entonces
{ P } <r,j> := <0,0> { I }

 
def(0)def(0)I[r/0,j/0]
u=Uv=V0=i:1i0:u(i)*v(i)10N


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 verificacin de la accin de inicializacin

 
pmd(<r,j>:=<0,0>,I)
u=Uv=V0=i:1i0:u(i)*v(i)00N
u=Uv=V0=00N
u=Uv=VN1P

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
A2: j := j + 1;
con lo cual debemos probar el requisito c.2
{ I B Nj = T } j := j + 1 { Nj < T }















def(j+1)(Nj<T[j/j+1])
ciertoN(j+1)<T
Nj<T+1
Nj=TIBNj=T

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=Uv=Vr=i:1ij+1:u(i)*v(i)0j+1N
u=Uv=Vr=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 } A1 { R }
lo nico que no se puede obtener de R a partir de I B es el trmino adicional en el sumatorio; 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 } A1 ; A2 { Nj < T }
intuitivamente se ve que se mantiene porque A1 no afecta a j. Tambin se podra demostrar.
Con todo esto el algoritmo anotado queda:
const N = ; % Ent
var u, v : Vector[1..N] de Real;
r : Real;

{P:u=Uv=VN1}
var j : Ent;
inicio
<r,j> := <0,0>;
{ I : u = U v = V r = i : 1 i j : u(i)*v(i) 0 j N
C:Nj
it j z N o

{IjzN}
r := r + v(j+1) * u(j+1);
{R}
j := j + 1
{I}

Diseo de algoritmos iterativos

103

fit

{Ij=N}
fvar
{ Q : u = U v = V r = i : 1 i N : u(i) * v(i) }
En cuanto a la complejidad. Si tomamos N como tamao de los datos (n), considerando el
cuerpo del bucle como accin caracterstica es fcil ver que el algoritmo es O(n).
Tambin podemos afirmar que TA(n) es :(n) pues el algoritmo accede, en cualquier caso, a
todas las componentes del vector. Y de las dos afirmaciones anteriores, tenemos que TA(n)
4(n).
Podemos razonar tambin que cualquier algoritmo que resuelva este problema ha de ser :(n),
pues necesariamente ha de visitar al menos una vez cada componente. Tenemos pues que la
complejidad del algoritmo que hemos diseado coincide con la cota inferior para el problema; y,
por lo tanto, hemos obtenido una complejidad ptima.
En muchos casos se puede establecer la cota inferior razonando sobre que cualquier algoritmo
que resuelva el problema debe inspeccionar el dato de entrada completo.
Se puede hacer una generalizacin de la tcnica de sustituir una constante de la postcondicin
por una variable, que consiste en sustituir una constante por una expresin. Esa es la solucin
que se utiliza en el clculo de la raz cuadrada por defecto en tiempo logartmico.
1.5.3

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 normalmente no admiten muchas posibilidades distintas, y del coste de cada una de las ramas, que
vendr dado por la correspondiente accin que se derive.
El caso ms interesante es, otras vez, el de los bucles. Los peligros que pueden perjudicar a
la eficiencia son:

derivar 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 expresiones simples.

Diseo de algoritmos iterativos

104

Para resolver el segundo problema en muchas ocasiones es til la tcnica de introducir un invariante auxiliar.
En un punto de la derivacin, normalmente al intentar restablecer el invariante, nos encontramos con que tenemos que obtener el valor de una expresin compleja, tpicamente, una expresin que incluye asertos u operaciones extendidos. La solucin consiste en conseguir que el bucle
adems de lo que ya calculaba, indicado por el invariante, vaya calculando tambin esta expresin.
Introducimos una nueva variable y, de forma que donde se necesite el valor de la expresin E
podamos simplemente tomar el valor de y. Reforzamos el invariante con un invariante auxiliar de
la forma:
I1 : y = E
con lo que el invariante quedara
I : I0 I 1
Con este nuevo invariante ser necesario rehacer una parte de la derivacin, aunque otras partes pueden seguir siendo vlidas, teniendo en cuenta que el nuevo invariante ser un fortalecimiento del antiguo.
Veamos un ejemplo donde se necesita el uso de un invariante auxiliar: el clculo de los nmeros de Fibonacci.
fib(0) = 0
fib(1) = 1
fib(n+2) = fib(n+1) + fib(n)

si n0

partimos de la especificacin
var n, x : Ent;

{P:n=Nn0}
fibonacci
{ Q : n = N x = fib(n) }
Los nmeros de Fibonacci no se pueden especificar utilizando el lenguaje de asertos. Esto
ocurre con muchas funciones definidas de forma recursiva. Entendemos en ese caso que la especificacin se complementa con la definicin recursiva de la funcin, que hemos escrito ms arriba, y que especifica el comportamiento de dicha funcin.
La idea del algoritmo es ir obteniendo los nmeros de Fibonacci para valores sucesivos hasta
llegar al valor de n, uno en cada pasada por un bucle.

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

I0 : x = fib(i) n = N 0 i n
(lo llamamos I0 porque ya sabemos que introduciremos un invariante auxiliar)
donde adems hemos incluido un rango razonable para la nueva variable.
B.2. Condicin de terminacin
El invariante ser igual a la postcondicin cuando i = n, tomamos pues esa condicin de
terminacin
B : i = n
que efectivamente verifica
I0 B Q
de ah que la condicin de repeticin quede
B:izn

B.3. Definicin de la condicin de repeticin

La condicin de repeticin no est definida porque la variable i no est declarada. Incluimos por tanto una declaracin local que rodea a la accin a derivar
var i : Ent;
inicio
A
fvar
con lo que ahora s est definida i z n

B.4. Expresin de acotacin.

Fijndonos en la condicin de terminacin, i = n, y considerando que i debe ir incrementndose tomamos como expresin de acotacin
C:ni
que junto con la condicin sobre el rango de i que aparece en el invariante nos asegura
que esta expresin toma valores en un dominio bien fundamentado.
Probamos entonces
I0 B def(C) dec(C)









 
def(ni)
  cierto
  I0B










B.5. Accin de inicializacin

dec(ni)
ni>0
n>i
0iniznI0B

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=Nn0}
A0
{ 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:
A2 : i := i +1
Demostramos que efectivamente esta accin de avance hace avanzar el bucle
{ I0 B Ni = T } i := i +1 { Ni < T }
que efectivamente podemos demostrar

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

Se debera cumplir
{ I0 B } i := i +1 { I0 }
o lo que es lo mismo
I0 B I0 [i/i+1] x = fib(i+1) 0 i + 1 n n = N
pero no se puede demostrar
I0 B x = fib(i+1)

B.8 Restablecimiento del invariante

Tenemos
{ n = N x = fib(i) 0 i n i z n }
A1
{ n = N x = fib(i+1) 0 i+1 n }
la solucin sera una accin de la forma
x := fib(i+1)
[o lo que es igual
x := fib(i) + fib(i1)
fib(i) lo tenemos, pero para obtener fib(i1) tendramos que escribir otro bucle que lo escribiera]
lo que hacemos es aadir un invariante auxiliar, exigiendo que el bucle vaya calculando
tambin el valor de fib(i+1)
I1 : y = fib(i+1)

Diseo de algoritmos iterativos

107

con lo que el nuevo invariante queda


I I0 I1 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 I0.

B.3. La condicin de repeticin sigue estando definida

I I0 def(B)

B.4. La expresin de acotacin sigue siendo la misma, y se siguen cumpliendo sus propiedades.

I B def(C) dec(C)

B.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=Nn0}
inicializacin
{ n = N x = fib(0) 0 0 n y = fib(1) }
Lo cual se puede derivar como una asignacin mltiple
A0 : <i,x,y> := <0,0,1>
y demostrar que efectivamente es correcto { P } A0 { 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 z n }
A1
{ n = N x = fib(i+1) 0 i+1 n y = fib(i+2) }
y ahora en la precondicin s tenemos los valores necesarios para obtener las igualdades
de la postcondicin:
<x,y> := <y,y+x>
que podemos verificar que es efectivamente correcta respecto de la especificacin

Diseo de algoritmos iterativos

108

B.9. Demostramos que con la accin de restablecimiento del invariante se sigue cumpliendo la terminacin del bucle.

Informalmente podemos argumentar que la expresin de acotacin (ni) no se ve afectada por la accin A1.
Finalmente, el algoritmo anotado nos queda
var n, x : Ent;

{n=Nn0}
var
i, y : Ent;
inicio
<i,x,y> := <0,0,1>;
{ I : n = N x = fib(i) 0 i n y = fib(i+1)
C:ni
}
it i z n o

{Iizn}
<x,y> := <y,x+y>;
{ I[i/i+1] }
i := i +1
{I}
fit
{Ii=n}
fvar
{ n = N x = fib(n) }

Este ejemplo no nos debe hacer pensar que siempre es posible eliminar los bucles anidados introduciendo invariantes auxiliares. Es necesario que el bucle externo sea realmente capaz de obtener el valor para el cual se requiere un bucle interno, y para ello necesitamos que ambos bucles
tengan los mismos lmites y se ejecuten el mismo nmero de veces.

1.6 Algoritmos de tratamiento de vectores


Vamos a tratar en este apartado un conjunto de algoritmos muy habituales sobre vectores. Estos 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 momento 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 directo sea eficiente, es necesario que todo el vector est almacenado en memoria principal, lo cual
limita el tamao mximo. En la segunda parte del curso veremos otras formas de manejar colecciones de datos que soslayan los inconvenientes de los vectores, a costa de sacrificar el acceso
directo, susituyndolo por acceso secuencial.

tamao fijo

acceso directo todo el vector en memoria principal

Casi todos los algoritmos de tratamiento de vectores se ajustan a uno de los dos siguientes esquemas

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=VN1}
n:=0;
{I:0nNi:1in:tratado(v(i))

Diseo de algoritmos iterativos

110

C:Nn}
itnzN
otratar(v(n+1));
n:=n+1
fit;
finalizacin
{Q:i:1iN:tratado(v(i))}

La complejidad en O(n).
Ntese que no imponemos en la postcondicin que se conserve el valor de v porque puede
ocurrir que la accin tratar modifique el valor de las componentes del vector.
En los ejercicios ya hemos derivado ejemplos similares. Para hacer la derivacin formal de un
algoritmo genrico de recorrido, el invariante se obtiene sustituyendo una constante de la postcondicin por una variable.
Normalmente los bucles de recorrido de vectores se escriben utilizando la construccin para,
lo que permite una escritura ms compacta.
No hemos escrito el esquema como un procedimiento o una funcin porque al no conocer
cul es la funcin de tratamiento no sabemos si el vector se modificar, o si se debern obtener
resultados adicionales. Sin embargo, una vez fijada la operacin de tratamiento de las componentes, lo habitual es implementar el recorrido como una funcin o un procedimento segn corresponda.
Algunas variaciones sobre este esquema bsico:

Recorrido de un subvector. Lo habitual es implementar el recorrido como un procedimiento o una funcin que reciba como parmetro los lmites del subvector a recorrer.

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.

1.6.2

El resultado es un valor que se obtiene a partir del vector de entrada, que no se modifica (p.e. obtener el mximo de las componentes)

El resultado es 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)

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;
{P0:v=VN1}
inicio
<pos,encontrado>:=<1,P(v(1))>;
{I:v=V1posNi:1i<pos:NOTP(v(i))encontradol
P(v(pos))
C:Npos
}
itposzNANDNOTencontrado
oencontrado:=P(v(pos+1));
pos:=pos+1
fit;
{Q0:v=V1posNi:1i<pos:NOTP(v(i))encontradol
P(v(pos))
(pos=Nencontrado)
}
dev<encontrado,pos>
ffunc

Ntese que si P es una funcin, est permitido utilizarla en los asertos, como ya vimos en el
apartado sobre procedimientos y funciones. Ntese tambin que debemos entender esa funcin
como el resultado de sustituir los parmetros formales por los reales en la postcondicin de la
funcin; y, por lo tanto, si queremos relacionar ese aserto con la variable encontrado debemos
hacerlo con el operador l.
Es necesario sacar la comprobacin de la primera componente fuera del bucle para que el invariante est definido a la entrada pues si inicializsemos pos=0 y encontrado=falso tendramos
encontradolP(v(0))

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 problemas:

Diseo de algoritmos iterativos

112

hace necesario evaluar una vez ms P(v(pos)) para comprobar si hemos acabado por llegar al final o por que se cumple la propiedad. Si la comprobacin de la propiedad es costosa esto implica una penalizacin de la eficiencia. Aunque realmente en el caso peor no
es significativo pues slo implica una comprobacin ms sobre las N posibles.

algunos lenguajes de programacin (como Pascal) evalan las condiciones enteras aunque
puedan llegar a un resultado slo con evaluar una parte. Hay que tener cuidado de que no
pueda ocurrir que se intenta evaluar P(v(pos)) para un valor de pos que no pertenezca al
rango del vector. No es el caso aqu.

Otra posible forma de escribir el algoritmo sin necesidad de comprobar la primera componente fuera del bucle:

funcbuscarVector(v:Vector[1..N]deelem)devencontrado:Bool;pos:
Ent;
{P0:v=VN1}
inicio
pos:=1;
{I:v=V1posNi:1i<pos:NOTP(v(i))
C:Npos}
itposzNANDNOTP(v(pos))
opos:=pos+1
fit;
encontrado:=P(v(pos));
{Q0:v=V1posNi:1i<pos:NOTP(v(i))encontradol
P(v(pos))
(pos=Nencontrado)
}
dev<encontrado,pos>
ffunc

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 l P(v(pos))) y la condicin de terminacin que tambin aparece explcitamente en la postcondicin (pos=N encontrado). 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

{P0:v=V1MNP(v(M))}
inicio
pos:=1;
{I:v=V1posMi:1i<pos:NOTP(v(i))
C:Mpos}
itNOTP(v(pos))
opos:=pos+1
fit;
encontrado:=poszM;
{Q0:v=V1posMi:1i<pos:NOTP(v(i))P(v(pos))
encontradolposzM}
dev<encontrado,pos>
ffunc

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 asignacin (encontrado l pos z 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 sabemos que siempre se cumplir la condicin.
Tambin se puede utilizar el centinela como una marca que indica donde est el final de la parte de un vector que contiene informacin. De forma que utilicemos slo las M primeras posiciones de un vector. Esta es un tcnica muy habitual. Como no podemos esperar que el centinela
cumpla cualquier propiedad sobre la que podamos estar interesados, tendramos que usar una
condicin de repeticin de la forma:

NOTP(v(pos))ANDNOTP(v(pos))

siendo P la propiedad buscada y P una propiedad conocida para el centinela, que slo l puede verificar dentro del dominio correspondiente.
Aplicaciones de la bsqueda secuencial
Muchos algoritmos sobre vectores se pueden expresar utilizando el esquema de bsqueda secuencial con o sin centinela. Algunos ejemplos donde se trata de determinar si uno o ms vectores cumplen una determinada propiedad y lo que se hace es buscar una componente que no
cumpla la propiedad.

Diseo de algoritmos iterativos

114

Determinar si dos vectores son iguales

P : u(pos) z v(pos)
Determinar si una matriz es simtrica

P : v(f,c) z v(c,f)
Determinar si un vector de caracteres es un palndromo (es igual leda de izquierda a derecha y de derecha a izquierda).
P : v(pos) z v(Npos+1)
El bucle slo recorrera el vector hasta N div 2
Determinar si un vector est ordenado crecientemente
P : v(pos) > v(pos+1)
El recorrido slo llegara, como mximo, hasta N1.

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

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 derivacin, 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
M

Diseo de algoritmos iterativos

115

y si v(m) > x entonces debemos buscar a la izquierda de m

Q
M

Como el tamao de los datos se reduce a la mitad en cada pasada tenemos claramente una
complejidad logartmica en el caso peor, frente a la complejidad lineal de los algoritmos de
bsqueda secuencial. El caso peor es cuando el elemento no est el vector, o tenemos la mala
suerte de no encontrarlo hasta que p=q.
El tipo de los elementos del vector debe ser un tipo sobre el que est definida la igualdad y
una relacin de orden

tipo
elem=;%tipoordenadoconigualdad

Debemos tener cuidado con los casos extremos, es decir si x es menor que todos los elementos del vector o si x es mayor que todos los elementos del vector. Queremos que si no se encuentra en el vector pos se quede apuntando a la posicin anterior a la que debera ocupar el elemento;
eso quiere decir que tanto 0 como N son valores admisibles para pos. Esto plantea un problema a
la hora de escribir la postcondicin; la forma ms simple de escribir la postcondicin es:

v=Vord(v)0posNv(pos)x<v(pos+1)

Donde no hace falta indicar que todos los elementos a la izquierda de pos son menores que x y
todos los elementos a la derecha son mayores, basta con que lo sean el apuntado por pos y el inmediatamente siguiente, ya que los dems lo sern por estar ordenado el vector. El problema es
que pos puede valer 0, en cuyo caso no est definido v(pos), o puede valer N en cuyo caso no est
definido v(pos+1). Se puede utilizar un artificio en la especificacin que consiste en considerar un
vector ficticio con valores centinela, y +, en las posiciones 0 y N+1, y que coincide con v en
las dems posiciones. O bien, nos podemos aprovechar de que el cuantificador universal se considera cierto cuando el aserto de dominio define un conjunto vaco. Aplicando esta segunda idea
obtenemos la siguiente especificacin

funcbuscarVectorBin(v:Vector[1..N]deelem;x:elem)devpos:Ent;
{P0:v=Vx=XN1ord(v)}
{Q0:v=Vx=Xi:1ipos:v(i)xi:pos+1iN:
x<v(i)
0posN}
ffunc

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=Vx=Xi:1ipos:v(i)xi:qiN:v(i)
>x
0pos<qN+1ord(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 condicin de terminacin ha de ser:
B:q=pos+1


Efectivamente podemos demostrar


IBQ0


La condicin de repeticin queda por tanto


B:qzpos+1

B.3. Est definida la condicin de repeticin?


Idef(qzpos+1)


No est definida porque q no est declarada. Aadimos esta nueva variable a las declaraciones 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
IBdef(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 cuantificadores universales:
A0:  <pos,q>:=<0,N+1>;


Con lo cual es trivial demostrar


{P0}A0{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 consideramos que es igualmente probable que el valor buscado se encuentre en cualquiera de las
posiciones del subvector, tomamos el punto medio entre pos y q, de forma que reduzcamos a la mitad el tamao del subvector a explorar.
A2: m:=(pos+q)div2;

y asignamos m a pos o q segn convenga para mantener el invariante:


si
v(m)xopos:=m;
v(m)>xoq:=m
fsi


Demostremos ahora el avance del bucle:


{IBqpos=T}A0{qpos<T}


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


{IBqpos=T}
m:=(pos+q)div2;

Diseo de algoritmos iterativos

118

{R1{IBqpos=Tm=(pos+q)div2}
si
v(m)xopos:=m;
v(m)>xoq:=m
fsi
{qpos<T}

De esta forma es trivial la correccin de la primera asignacin y lo que nos queda es demostrar la correccin de la composicin alternativa.
Definicin de las barreras. Aadimos m a la declaracin de variables locales








y al menos una abierta
















def(v(m)x)def(v(m)>x)
enRango(v,m)
1mN
0pos<m<qN+1
0pos<qN+1m=(pos+q)div2qzpos+1
R1

v(m)xv(m)>xciertoR1


ahora verificamos cada rama por separado


{R1v(m)x}pos:=m{qpos<T}


 
qpos<T[pos/m]

  qm<T

  qpos=Tpos<m

  qpos=Tpos<qqzpos+1m=(pos+q)
div2

  Rv(m)x

{R1v(m)>x}q:=m{qpos<T}


 
qpos<T[q/m]

  mpos<T

  qpos=Tm<q

  qpos=Tpos<qqzpos+1m=(pos+q)
div2

  R1v(m)>x


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

Diseo de algoritmos iterativos

119

{IB}
m:=(pos+q)div2;
{R2{IBm=(pos+q)div2}
si
v(m)xopos:=m;
v(m)>xoq:=m
fsi
{I}

La verificacin de la primera asignacin es trivial.


La verificacin del condicional
def(v(m)x)def(v(m)>x)R2


se demuestra de la misma forma que el punto anterior


v(m)xv(m)>xciertoR2


demostramos ahora cada rama


{R2v(m)x}pos:=m{I}


 
I[pos/m]
v=Vx=X0m<qN+1i:1im:v(i)x
i:qiN:v(i)>xord(v)


I  v=Vx=X
ord(v)

I  i:qiN:v(i)>x
Im=(pos+q)div2qzpos+1 0m<qN+1
ord(v)v(m)xi:1im:v(i)x

porlotantoR2v(m)xI[pos/m]

{R2v(m)>x}q:=m{I}


 
I[q/m]
v=Vx=X0pos<mN+1i:1ipos:v(i)x
i:miN:v(i)>xord(v)


I  v=Vx=X
ord(v)

I  i:1ipos:v(i)x

Im=(pos+q)div2qzpos+1 0pos<mN+1
ord(v)v(m)>xi:miN:v(i)>x

porlotantoR2v(m)>xI[q/m]


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;
{P0:v=Vx=XN1ord(v)}
var
q,m:Ent;
inicio
<pos,q>:=<0,N+1>;
{I:x=Xv=Vord(v)0pos<qN+1i:1ipos:v(i)
x
i:qiN:v(i)>x
C:qpos
}
itqzpos+1
om:=(pos+q)div2;
si
v(m)xopos:=m;
v(m)>xoq:=m
fsi
fit;
{Q0:v=Vx=Xi:1ipos:v(i)xi:pos+1iN:
x<v(i)
0posN}
devpos
ffunc

Ntese que efectivamente pos se queda apuntando a la posicin anterior al primer elemento
que es ms grande que x, de forma que desplazando una posicin hacia la derecha todas las componentes i>pos tendramos el hueco donde insertar x.
Ntese, por ltimo, que habremos encontrado el elemento si pos z 0 y v(pos) = x:

(i:1iN:v(i)=x)l(posz0v(pos)=x)

En cuanto a la complejidad, tomando N como tamao de los datos, tenemos que en cada iteracin se divide a la mitad la longitud del subvector a considerar, hasta que se llega a un subvector de longitud 1.
1.6.3

Ordenacin

Los algoritmos de ordenacin sobre vectores son muy importantes dado lo habitual de su uso.
La especificacin de un procedimiento de ordenacin nos dice que, dado un vector cuyos elementos son de un tipo ordenado, hemos de obtener una permutacin ordenada (en orden no
decreciente, por ejemplo) del vector original. Formalmente:

Diseo de algoritmos iterativos

121

procordenaVector(esv:Vector[1..N]deelem);
{P0:v=VN1}
{Q0: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, basan su funcionamiento en realizar intercambios entre las componentes del vector. Por ello, vamos
a introducir una nueva sentencia en el lenguaje algortmico que nos permita escribir de una vez
dicho intercambio.

Int(v,Ei,Ej)

es equivalente a
<v(Ei),v(Ej)>:=<v(Ej),v(Ei)>

y, a efectos de verificacin es ms cmodo considerar la equivalencia


var
z:elem;
inicio
z:=valor(v,Ei);
v:=asignar(v,Ei,valor(v,Ej));
v:=asignar(v,Ej,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 vector resultante va a ser una permutacin del vector original. Es decir, se puede demostrar

{v=V}
Int(v,Ei,Ej)
{perm(v,V)}

Para demostrarlo se utiliza la segunda de las equivalencias que hemos visto ms arriba. Se deja
al lector encontrar esta demostracin.

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 TA(n) su complejidad en tiempo en el caso peor, tomando como tamao de los datos n la longitud del vector. Se
verifica:
(a) TA(n) :(n log n)
(b) TA(n) :(n2), 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=22 permutaciones, con
tres intercambios 8=23 permutaciones, y as sucesivamente, de forma que con t intercambios podemos obtener 2t permutaciones distintas. En cada iteracin decidimos si hacemos o no el intercambio 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:
2tn!
tlog(n!)

por la frmula de Stirling


tcnlogn

si hemos realizado t operaciones de intercambio entonces la complejidad del algoritmo debe ser
TA(n)tcnlogn

y aplicando la definicin de la cota inferior de la complejidad


TA(n):(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 2tn! comparaciones para as
haber considerado todas las permutaciones posibles; pero podemos argumentar, si lo que contamos son las operaciones de intercambio, que el caso peor hemos hecho un nmero de intercambios 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)defi<jv(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:nicn2

(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 lejanos 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 n2, por lo
que
TA(n) :(n2)

Algoritmos de ordenacin

Segn los resultados obtenidos en el apartado anterior y considerando su complejidad, tenemos dos grandes grupos de algoritmos de ordenacin
Algoritmos de ordenacin de complejidad O(n2)
Mtodo de insercin. Emplea intercambios entre vecinos y se basa en tener una parte del
vector ordenando e ir insertando un elemento cada vez en la parte ordenada.

Mtodo de seleccin. Emplea intercambios entre elementos lejanos y se basa en ir eligiendo cada vez el menor de los elementos que quedan en la parte no ordenada.

Mtodo de la burbuja. Consiste en considerar los (N1)*(NI) intercambios posibles entre vecinos para lograr ordenar el vector.

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 burbuja 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 ordenar 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 ordenadas, se mezclan ordenadamente para obtener el resultado final. Su complejidad es cuasilineal, O(n log n), en el caso peor. Requiere un espacio auxiliar de coste O(n).

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 recursivos. La ordenacin por montculos se explica en el tema de rboles.
Ordenacin por insercin

La idea del algoritmo es que dividimos el vector en una parte ordenada y otra desordena, y en
cada pasada insertamos el primer elemento de la parte desordenada en el lugar que le corresponde
dentro de la parte ordenada, realizando intercambios entre vecinos.
La forma en que escribamos la especificacin del algoritmo nos conducir al algoritmo a implementar pues dirigir la obtencin del invariante.
En la especificacin slo nos preocupamos por la condicin ord(v) pues sabemos que
perm(v,V) est garantizada al tratarse de un algoritmo que slo realiza intercambios.
La postcondicin:

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


La derivacin formal:

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

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

IBQ

B.3 Est definida la condicin de repeticin?

La condicin de repeticin
B:nzN

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
IBdef(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
A0:n:=1

B.6. Accin de avance


A2:n:=n+1

Probamos que efectivamente


{IBNn=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 ordenado hasta n no implica estar ordenado hasta n+1

B.8. Restablecimiento del invariante

Diseo de algoritmos iterativos

126

{IB}
A1
{I[n/n+1]}


La idea es que para restablecer el invariante tenemos que implementar un bucle que haga
lo siguiente:


n n+1

>
n-1 n n+1

>
n-2 n-1 n n+1

>

Tendramos que aplicar de nuevo el proceso de derivacin de bucles, pero vamos a abreviarlo.
La derivacin de esta accin es el ejercicio 75. All se toma como invariante una versin
reelaborada del invariante que presentamos aqu; una versin con la que es ms sencillo
realizar la verificacin.
La postcondicin es
i,j:1i<jn+1:v(i)v(j)1n+1N

La idea para obtener el invariante es considerar que en el subvector [1..n+1] todos los
elementos estn ordenados menos 1; y que ese uno es menor que todos los que estn a su
derecha. De forma que el subvector entero estar ordenado cuando el elemento problemtico sea mayor o igual que el de su izquierda o el elemento problemtico sea el primero
i,j:1i<jn+1jzm+1:v(i)v(j)
(v(m)v(m+1)m=0)0mn<N


De esta forma tenemos el invariante y la condicin de terminacin


Iint:i,j:1i<jn+1jzm+1:v(i)v(j)0mn<N


La condicin de terminacin.
Bint:m=0v(m)v(m+1)


Diseo de algoritmos iterativos

127

Por lo tanto la condicin de repeticin


Bint:mz0v(m)>v(m+1)


El problema es que no se cumple


Iintdef(Bint)


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, tenemos que llegar hasta m=0 en la ltima iteracin, donde ya no entramos en el cuerpo del
bucle. Pero aunque en esa situacin ya no sera necesario evaluar v(m)>v(m+1), lo estamos haciendo y v(0)>v(1) no est definido porque no existe v(0). Una solucin sera definir una variable booleana auxiliar de forma que no apareciese el acceso al vector dentro de
la condicin de repeticin. La otra solucin es tratar la primera componente del vector
como un caso aparte fuera del bucle, a la terminacin de ste.
El invariante hay que modificarlo, as como las condiciones de repeticin y terminacin
Iint:i,j:1i<jn+1jzm+1:v(i)v(j)1mn<N

Bint:m=1v(m)v(m+1)

Bint:mz1v(m)>v(m+1)


As, a la terminacin del bucle no sabemos si v(1) es o no menor que v(2), por lo que escribimos un condicional para averiguarlo y actuar en consecuencia.
La expresin de acotacin
Cint:m


La accin de inicializacin
m:=n


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


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 intercambiar v(1) y v(2) en caso de que sea necesario.
siv(1)>v(2)
entoncesInt(v,1,2)
sinoseguir
fsi

Una forma de soslayar el inconveniente de escribir este condicional adicional es aprovecharse de la caracterstica que tienen algunos lenguajes al realizar evaluacin perezosa de las
expresiones, evaluando slo la parte de la expresin necesaria para llegar a una solucin.
Aprovechndonos de ello, podramos utilizar el invariante y la condicin de terminacin
que elegimos inicialmente. Si n embargo, a la hora de disear el algoritmo nos debemos
abstraer de estas peculiaridades, dejando la posible modificacin como una optimizacin
posterior.

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);
{P0:v=VN1}
var
n,m:Ent;
inicio
n:=1;
{I;C}
itnzN
om:=n;
{Iint;Cint}
itmz1v(m)>v(m+1)
oInt(v,m,m+1);
m:=m1
fit;
siv(1)>v(2)
entoncesInt(v,1,2)
sinoseguir
fsi;
n:=n+1
fit
{Q0: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 decreciente, en cuyo caso tenemos O( i : 1 i N1 : i ) = O(N2). En el caso mejor, cuando el vector est ordenado crecientemente, la complejidad es O(N).
En [Kal90], pp. 172-174 se puede encontrar una derivacin ms formal de este algoritmo.
Ordenacin por seleccin

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, obtendramos de nuevo el mtodo de insercin. Para hacer avanzar el bucle, seleccionaremos el menor de
los elementos que quedan y lo colocaremos al final de la parte ordenada.

B.1 Invariante

Lo obtenemos sustituyendo la primera aparicin de N en la postcondicin, e introduciendo la condicin sobre el posible rango de valores:
i:1i<n:(j:i<jN:v(i)v(j))1nN

Ya ordenado

n-1 n

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:nzN
IBQ

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
IBdef(C)dec(C)


B.5. Accin de inicializacin


A0:n:=1

con lo que el invariante se hace trivialmente cierto porque el cuantificador opera sobre un
dominio vaco.
{P}A0{I}

B.6. Accin de avance

vamos incrementando n
A2: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 encontrar el menor de los elementos, intercambindolo entonces con el que ocupa la posicin n. Esto lo conseguimos como una composicin secuencial, de un bucle que localiza
el ndice del menor elemento del subvector, junto con un intercambio de ese elemento
con el que ocupa la posicin n, que consigue que se cumpla menor = n.
El invariante del bucle se obtiene sustituyendo la N que aparece en la expresin del
mnimo m, junto con sus condiciones de rango.
Iint:i:1i<n:(j:i<jN:v(i)v(j))
v(menor)=mink:nkm:v(k)nmNnmenorN

La primera parte del invariante hay que incluirla para luego poder obtener la postcondicin I[n/n+1].
Bint:mzN
Cint:Nm


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);
{P0:v=VN1}
var
n,m,menor:Ent;
inicio
n:=1;
{I;C}
itnzN
o<m,menor>:=<n,n>;
{Iint;Cint}
itmzN
osiv(m+1)<v(menor)
entoncesmenor:=m+1
sinoseguir
fsi;
m:=m+1
fit;
Int(v,n,menor);
n:=n+1
fit
{Q0:ord(v)perm(v,V)}
fproc

En el caso peor (y en el mejor, pues los bucles siempre se ejecutan hasta el final) este algoritmo es de orden O(n2). Aunque si contabilizsemos slo los intercambios tenemos una complejidad de O(n).
Mtodo de la burbuja

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 restablecimiento del invariante. El objetivo es el mismo, conseguir que el menor de los que quedan
pase a ocupar la posicin n; pero en lugar de hacer una bsqueda y un intercambio, se van
haciendo sucesivos intercambios empezando por el final.
I[n/n+1]i:1i<n:(j:i<jN:v(i)v(j))

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:
Iint:i:1i<n:(j:i<jN:v(i)v(j))
j:m<jN:v(m)v(j)nmN
Bint:m=n
Cint:m


Con todo esto el procedimiento queda:


procordenaVectorBurbuja(esv:Vector[1..N]deelem);
{P0:v=VN1}
var
n,m:Ent;
inicio
n:=1;
{I;C}
itnzN
om:=N;
{Iint;Cint}
itmzn
osiv(m1)<v(m)
entoncesInt(v,m1,m)
sinoseguir
fsi;
m:=m1
fit;
n:=n+1
fit
{Q0:ord(v)perm(v,V)}
fproc

La complejidad es, en cualquier caso, de orden O(n2) y, a diferencia del mtodo de seleccin, el
nmero de intercambios es tambin O(n2). Sin embargo, se puede hacer una optimizacin si observamos que cuando en un recorrido entero del bucle interno no se efecta ningn intercambio,
eso quiere decir que el subvector v[n..N] ya est ordenado, con lo cual podemos terminar. Para
hacer esta optimizacin es necesario introducir un invariante auxiliar tanto al bucle externo como
al bucle interno
I1:boi,j:ni<jN:v(i)v(j)
I1int:bli,j:mi<jN:v(i)v(j)

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 actualizando b adecuadamente.
Cambiamos adems la condicin de repeticin del bucle externo, incluyendo la comprobacin
sobre el valor de b.
Con todo esto el procedimiento queda:

procordenaVectorBurbuja(esv:Vector[1..N]deelem);
{P0:v=VN1}
var
n,m:Ent;
b:Bool;
inicio
<n,b>:=<1,falso>;
{II1;C}
itnzNANDNOTb
o<m,b>:=<N,cierto>;
{IintI1int;Cint}
itmzn
osiv(m1)<v(m)
entoncesInt(v,m1,m);b:=falso
sinoseguir
fsi;
m:=m1
fit;
n:=n+1
fit
{Q0:ord(v)perm(v,V)}
fproc

De esta forma, la complejidad en el caso peor sigue siendo O(n2), pero ahora si el vector est
ordenado la complejidad pasa a ser O(n).

Diseo de algoritmos iterativos

134

1.7 Ejercicios
Asertos en lgica de predicados
1. Supongamos las declaraciones
cte
N=; %enterot1
var
x,y,p:Ent;
v:Vector[1..N]deEnt;

Escribe asertos que formalicen los tres enunciados siguientes. Seala las variables libres y las
variables ligadas.
(a)
(b)
(c)
(d)
(e)
(f)
(g)
(h)
(i)
(j)

x aparece como componente de v.


x es mayor que todas las componentes de v.
x aparece una sola vez como componente de v.
Todas las componentes positivas de v estn comprendidas entre x e y.
v est ordenado en orden estrictamente creciente.
v est ordenado en orden no decreciente.
v est ordenado en orden estrictamente decreciente.
y es la suma de las componentes positivas de v.
y es el mximo de las componentes negativas de v.
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:1didn:v(i)=x)[n/n+1] *(e)(i:1didN:x+i)[x/xi]
(b)(i:ndidN:v(i)>0)[n/n1] (f)(i:1didN:x*i)[x/i+2]
(c)(i:1didN:v(i)=x)[x/2*i] *(g)(i:ndidN:x+i)[n/n1]
(d)(i:1didN:v(i)>x)[i/i*i] (h)(i:1didn:x*i)[n/n+1]
3. Comprueba que las dos sustituciones siguientes no producen el mismo resultado. Las va-

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) (x2+2*x+1)[x/x+a]
(b)(x2y)[x/y+1,y/x1]
(c)(xy+1yz)[x/x+3*z,y/xy+1]
(d)(k:1kn:x2+y2=k2)[x/x+1,y/y1,n/x+y]
5. Las variables que intervienen en este ejercicio son todas de tipo entero. En cada apartado,

estudia si alguno de los dos asertos es ms fuerte que el otro. Razona tus respuestas.

Diseo de algoritmos iterativos

(a)x0
(b)x0y0
(c)x<0
(d)x1ox0
(e)i:Ent:x=2*i
(f)i:Ent:x=4*i

135

x0y0
x+y=0
x2+y2=9
x1
i:Ent:x=6*i
i:Ent:x=6*i

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

(b)XQPQ
(d)XQPQ

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))
(c)def(a+b)
(e)def(v(i)modb)

(b)def(amodb)
(d)def(xdivy+ydivx)
(f)def(i:ai<b:v(i))

9. Aplica las leyes de descomposicin de cuantificadores a los casos que siguen:
(a)i:1in+1:x<v(i)
(c)x=i:n1i<N:i*v(i)
(e)x=#i:1in+1:v(i)=0

(b)i:1in+1:v(i)=i
(d)x=i:n1iN:i+v(i)
(f)x=Maxi:1in+1:v(i)

Especificaciones Pre/Post
10. Formaliza especificaciones Pre/Post para algoritmos que realicen las tareas siguientes. Dis-

tingue en cada caso entre las variables de programa, las variables auxiliares y las variables ligadas.
(a) Copiar el valor de una variable en otra.
(b) Intercambiar los valores de dos variables.
(c) Calcular el valor absoluto de un entero.
(d) Dada una cantidad entera no negativa de segundos, convertirla a horas, minutos y segundos.
(e) Calcular la raz cuadrada por defecto de un entero.
(f) Calcular la raz cuadrada exacta de un entero.
(g) Calcular el cociente y el resto de una divisin entera.
(h) Calcular el mximo comn divisor de dos enteros.
(i) Intercambiar dos componentes de un vector.
(j) Calcular el nmero de ceros que aparecen en un vector de enteros.
(k) Calcular el producto escalar de dos vectores de reales.
(l) Dados dos vectores de enteros, reconocer si uno de ellos es una permutacin del
otro.

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.


{ P1 } A {Q1 } { P2 } A { Q2 }

{ P1 } A {Q1 }

{ P1 P2 } A { Q1 Q2 }

{ P2 } A { Q2 }

{ P1 P2 } A { Q1 Q2 }

12. Para razonar con el operador pmd son vlidas las reglas que siguen. Explica su significado

operacional.
(a) pmd construye la precondicin ms dbil:
P pmd( A, Q )

(syss)
{P}A{Q}
(b) Exclusin de milagros:

pmd( A, Falso) Falso


(c) Monotona:

Q1 Q2
pmd( A, Q1 ) pmd( A, Q2 )
(d) Distributividad con respecto a :

pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )


(e) Distributividad con respecto a :

pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )


13. Postulemos la existencia de una accin llamada azar que cumpliese la especificacin si-

guiente:

varx:Ent;{cierto}azar{x=0x=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 estado inicial determina unvocamente el estado final. Volviendo a pensar en el apartado (e)
del ejercicio 12, demuestra que si la accin A es determinista entonces puede aceptarse el
axioma siguiente, que falla para A { azar.
pmd( A, Q1 Q2 ) pmd( A, Q1 ) pmd( A, Q2 )

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)
(b)
(c)
(d)

varx,y:Ent;{x>0y>0}seguir{x+y>0}
varx,y:Ent;{x+y>0}seguir{x>0}
varx,y:Ent;{x>0y>0}seguir{x*y>0}
varx,y:Ent;{x*y<0}seguir{(x>0)l(y<0)}

Asignacin
16. Verifica usando la regla de la asignacin:
varx,y:Ent
{x22*y>5}x:=2*x+y1{x3>2}

17. Verifica (a), (b) y encuentra contraejemplos para (c), (d).


(a) varx,y,m:Ent;
{x0y0}m:=x+y{mmax(x,y)}
(b) varx,y,m:Ent;
{x<0y<0}m:=max(x,y){m>x+y}
(c) varx,y,m:Ent;
{x0y0}m:=max(x,y){m>x+y}
(d) varx,y,m:Ent;
{x<0y<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*yy>0par(y)}

<y,x>:=<ydiv2,x+x>
{X*Y=p+x*yy0}

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{x2x+1>0}
(d){P}x:=x2{x=x2}
(e){P}x:=xmod2{x=xmod2}
(f){P}x:=3*x{i:1in:xz6*i}
(g){P}b:=bAND(x>0){b}
(h){P}(x,y):=(y,x){x=Xy=Y}
(i){P}(x,y):=(y,x){x<2*y}
(j){P}(x,y):=(y2,x+3){x+y>0}

Asignacin a vectores
21. La regla de la asignacin no se puede aplicar a asignaciones de la forma
v(i):=e

tratando a la expresin v(i) como si fuese una variable. Comprueba que si admitisemos aplicar la
regla de la asignacin de este modo, tendramos:
(a) Enunciados verdaderos, pero imposibles de verificar, como:
varv:Vector[1..100]deEnt;i,j:Ent;
{i=j}v(i):=0{v(j)=0}

(b) Enunciados falsos, pero que se pueden verificar (incorrectamente), como:


varv:Vector[1..100]deEnt;i,j:Ent;
{v(1)=2v(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)=1v(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=Xy=Y}

x:=xy;
{R1:}

y:=x+y;

Diseo de algoritmos iterativos

139

{R2:}

x:=yx
{Q:x=Yy=X}

24. Supuesto que x, y sean variables de tipo entero, calcula en cada caso la precondicin ms dbil

que satisface la especificacin.

(a)
(b)
(c)
(d)

{P}x:=x*x;x:=x+1{x>0}
{P}x:=x+y;y:=2*yx{y>0}
{P}y:=4*x;x:=x*xy;x:=x+4{x>0}
{P}x:=y;y:=x{x=Ay=B}

25. Lo que sigue demuestra que la composicin secuencial no es conmutativa. Verifica:


(a) varx,y:Ent;
{x=3y=Y}x:=0;y:=x+y{x=0y=Y}
(b) varx,y:Ent;
{x=3y=Y}y:=x+y;x:=0{x=0y=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;

{P}x:=y;y:=x{Q}

(b)varx,y:Ent;

{P}x:=y{Q}

28. El siguiente algoritmo anotado resuelve el problema de convertir una cantidad entera no

negativa de segundos a horas, minutos y segundos. Completa la verificacin.


vars,m,h:Ent;
{s=Ss0}

<h,s>:=<sdiv3600,smod3600>;
{s+3600*h=S0s<3600}

<m,s>:=<sdiv60,smod60>
{s+60*m+3600*h=S0s<600m<60}

Variables locales
29. Verifica usando la regla de las variables locales:
varx,y:Ent;
{x=Xy=Y}
varz:Ent;

Diseo de algoritmos iterativos

140

inicio
z:=x;x:=y;y:=z
fvar
{x=Yy=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=Xy=Y}

{x=Xy=Y}

si

sixy

xyoz:=y

entoncesz:=y

yxoz:=x

sinoz:=x

fsi

fsi

{x=Xy=Yz=max(x,y)}
{x = X  y = Y  z =
max(x,y)}
31. Especifica, disea y verifica un algoritmo que calcule el mximo de tres nmeros enteros

dados.

32. En cada uno de los casos que siguen, haz la verificacin formal.
(a)varx:Ent;

{cierto}

si

ciertoox:=0

ciertoox:=1

fsi

{x=0x=1}

(c)varx,y:Ent;

{cierto}

(x,y):=(y*y,x*x);

si

xyox:=xy

yxoy:=yx

fsi

{x0y0}

















(b)varx:Ent;

{cierto}

si

x0ox:=x+1

x0ox:=x1

fsi

{xz0}
(d)varx,y,z,p,q,r:Ent;

{x=Xy=Yz=Z}

<p,q,r>:=<x,y,z>;

si

p>qo<p,q>:=<q,p>

pqoseguir

fsi;

si

p>ro<p,r>:=<r,p>

proseguir

fsi;

si

q>ro<q,r>:=<r,q>

qroseguir

fsi
{Perm((p,q,r),(X,Y,Z))pqr}

Diseo de algoritmos iterativos

141


33. Calcula la precondicin ms dbil P que satisface:
varx:Ent;
{P}
x:=x+1;
si
x>0ox:=x2
x=0oseguir
x<0ox:=x+3
{x1}

34. Dado:
varx:Ent;
{x=X}
si
x=1ox:=1
x=1ox:=1
fsi
{x=X}

demuestra con un contraejemplo que la especificacin no se cumple, y a continuacin fortalece la precondicin de manera que la especificacin se cumpla. Haz la verificacin.
35. Usa clculo de pmds para demostrar que las dos construcciones siguientes son equivalentes

con respecto a cualquier especificacin Pre/Post:

(a)siB1oA1B2oA2fsi;A
(b)siB1oA1;AB2oA2;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=Xy=Yy0}  {Pre.P:x=Xy=Yy0}

p:=0
 p:=0;

{Inv.I:X*Y=p+x*yy0  {Inv.I:X*Y=p+x*yy0

CotaC:y} CotaC:y}

ityz0
 ityz0

o{X*Y=p+x*yy>0}  o{X*Y=p+x*yy>0}

<p,y>:=<p+x,y1>{I}  sipar(y)

fit
 entonces

{Post.Q:p=X*Y}
 
<x,y>:=<x+x,y
div2>


 
sino<p,y>:=<p+x,y1>


 fsi{I}


 fit


 {Post.Q:p=X*Y}

Diseo de algoritmos iterativos

142

37. Considera la especificacin:


varx,y,p:Ent;
{Pre.P:x=Xy=Yy0}
potencia
{Post.Q:p=XY}

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 especificacin.
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=Xy=Yx0y>0}
divEnt

{Post.Q:x=Xy=Yx=c*y+r0r<y}

39. Calcula la precondicin ms dbil posible P que haga vlida en cada caso la especificacin:
(a)varx:Ent;

{P}

it

xz0ox:=x1

fit

{x=0}

(b)varx:Ent;

{P}

it

xz0ox:=x2

fit

{x=0}


(c)varx:Ent;

{P}

itxz0

osix>0

entoncesx:=x2

sinox:=x+3

fsi

fit

{x=0}

40. Completa la verificacin del algoritmo de clculo del m.c.d. que se presenta a continuacin.










varx,y:Ent;
{Pre.P:x=Xy=Yx>Y0}
{Inv.I:x>y0mcd(x,y)=mcd(X,Y)
CotaC:y}
ityz0
o{x>y>0mcd(x,y)=mcd(X,Y)}
<x,y>:=<y,xmody>
fit
{Post.Q:x=mcd(X,Y)}

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=V1IN+1s=i:1i<I:v(i)
CotaC:NI+1}
itIzN+1
os:=s+v(I);
I:=I+1{I}
fit
fvar
{Post:v=Vs=i:1iN:v(i)}


(b)Suma de una matriz de enteros (suponemos N, M 1, ctes.).

varv:Vector[1..N,1..M]deEnt;s:Ent;

{Pre:v=V}

varI,J:Ent;
%variableslocales

(s,I):=(0,1);

{InvExt.I:v=V1IN+1
s=i,j:1i<I1jM:v(i,j)

CotaExt.C:NI+1

}

itIzN+1

oJ:=1;

{InvInt.J:v=V1IN1JM+1

s=i,j:1i<I1jM:v(i,j)+

j:1j<J:v(I,j)
CotaInt.D:MJ+1
}

itJzM+1

os:=s+v(I,J);

J:=J+1{J}

fit;

I:=I+1{I}

fit

fvar

{Post:v=Vs=i,j:1iN1jM:v(i,j)}
42.Construye y verifica algoritmos para los problemas que siguen, anotando pre- y postcondi-

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 vectores.

45. Supongamos un procedimiento acumula que satisfaga la especificacin


procacumula(ess:Ent;ex:Ent);
{Pre.:s=Sx=X}
{Post.:s=S+xx=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*xx=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 variable 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(bli,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=Vx=X}
varI:Ent;
inicio
I:=1;
encontrado:=falso;
itINANDNOTencontrado
oencontrado:=(v(I)=x);
I:=I+1
fit
{Post.:v=Vx=X(encontradoli:1iN:v(i)=x)}
devencontrado
ffunc

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 :, razona.
(a)(n+1)2 es O(n2)
(c)3n no es O(2n)

(b)3n3+2n2 es O(n3)
(d)n3+2n2 es :(n3)

52. Demuestra que si T1 O(f1) y T2 O(f2), se tiene:


(a)Regla de la suma: T1(n) + T2(n) es O(max(f1(n), f2(n))).
(b)Regla del producto: T1(n) * T2(n) es O(f1(n) * f2(n)).
53. Aplicando las definiciones de las notaciones O, : y 4, demuestra cada una de las afirma-

ciones siguientes:

(a)f O(g) g O(h) f O(h).


(b)O(f) O(g) f O(g).
(c)O(f) = O(g) f O(g) g O(f).
(d)O(f) O(g) f O(g) g O(f).
(e)f O(g) g :(f).
(f)f 4(g) f O(g) g O(f).
(g)f 4(g) f O(g) f :(g).
54. Demuestra las afirmaciones que siguen:
(a)limn

f(n)
= 0 O(f) O(g).
g(n)

(b)limn

f(n)
z 0 f 4(g).
g(n)

55. Aplicando el ejercicio anterior, demuestra:

n * (n - 1)
es 4(n2).
2
(b)p(n) es 4(nk), supuesto que p(n) sea un polinomio de grado k.
(c)loga n es 4(logb n), supuesto que a, b > 1.
(a)

56. Supongamos dos algoritmos A, B que resuelven el mismo problema, pero tales que TA(n)

es O(log n), mientras que TB(n) es O(2n). Responde razonadamente a las siguientes preguntas:
(a)Se puede asegurar que el tiempo de ejecucin de B ser superior al de A en todos
los casos?
(b)Si se duplica el tamao de los datos, cmo aumenta el tiempo de ejecucin para cada
uno de los dos algoritmos?

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 fijado?

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
oJ:=I+1
itJNANDb
ob:=bAND(a(I,J)=a(J,I))
J:=J+1
fit;
I:=I+1
fit
{Post.:a=A(bli,j:1i<jN:a(i,j)=a(j,i))}
devb
ffunc


58. Analiza la complejidad del algoritmo de multiplicacin del ejercicio 36(b), tomando como

tamao de los datos n = y. Demuestra que el tiempo de ejecucin en el caso peor es O(log
n).
(Sugerencia: Razonando por induccin sobre n, demuestra que para y = n 2, el cuerpo del bucle del algoritmo se ejecuta t(n) veces, siendo t(n) 2 (log n).)
Derivacin de algoritmos iterativos

59. Deriva un algoritmo de divisin entera que cumpla la especificacin siguiente y que utilice

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=Xy=Yx0y>0}
{Q:x=Xy=Yx=c*y+r0r<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 adecuado a partir de una descomposicin conjuntiva de la postcondicin.

Diseo de algoritmos iterativos

148

funcraz(x:Ent)devr:Ent;
{P:x=Xx0}
{Q:x=X0rr2x<(r+1)2}
ffunc
61. Dados un vector v:Vector[1..N]deEnty un nmero x:Ent, se quiere calcular la

posicin de v con el menor ndice posible que contenga el valor x. Formaliza una especificacin pre/post y deriva a partir de ella un algoritmo correcto. OJO: no se supone que el
vector est ordenado.

62. Dados una matriz a:Vector[1..N,1..M]deEnt y 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:N1v=Vu=U}
{Q:r=i:1iN:u(i)*v(i)u=Uv=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 descendente.
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:N1v=V}
{Q:v=Vs=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:N1v=V}

{Q:v=V(blord(v))}
ffunc

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


Qv=V(blord(v,1,N))

siempre que se defina


ord(v,i,j)defk,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 acotacin adecuados, y deriva un algoritmo correcto de complejidad O(N). Finalmente, trata de optimizar fortaleciendo la condicin de iteracin, de modo que se evite el recorrido completo de v
cuando v no est ordenado.
66. Considera otra vez la especificacin pre/post del ejercicio 60. Deriva un nuevo algoritmo

iterativo basndote en las dos ideas siguientes:

Generalizar la postcondicin reemplazando la expresin (r+1)2 por otra que introduzca una nueva variable s2.

Avanzar asignando el valor medio (r+s)div2 a una de las dos variables r o s, segn
convenga para el mantenimiento del invariante.
Analiza la eficiencia del algoritmo obtenido y compara con la solucin del ejercicio 60.

67. Considera la siguiente especificacin para un algoritmo que debe calcular en k el logaritmo

en base 2 por defecto de n:

funclogBin(n:Ent)devk:Ent;
{P:n=Nn1}

{Q:n=Nk02kn<2k+1}
ffunc

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


Inv. I:
n=Nm1k02k*mn<2k*(m+1)
Cond. Rep. B.: mz1
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=Nn0}
{Q:n=Nx=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:N1v=V}
{Q:v=Vr=#i,j:1i<jN:v(i)0v(j)0}

Diseo de algoritmos iterativos

150

ffunc
I0:v=V0nNr=#i,j:1i<jn:v(i)0
v(j)0

Obsrvese que se cumple: I0n=NQ. Se exige que el algoritmo obtenido tenga complejidad O(n).
70. Especifica y deriva un algoritmo O(n) que calcule el nmero de concordancias de un vector v:
Vector[1..N]deEnt dado como parmetro. Se entiende que hay que contar una concordancia por cada pareja de ndices i,j tales que 1i<jN y v(i), v(j) tengan el mis-

mo signo. Por convenio, el nmero 0 no tiene signo, a efectos de resolver este ejercicio.

71. Especifica y deriva un algoritmo iterativo que, dado un entero no negativo n, calcule r=
i:0in:i!. Se exige que el algoritmo obtenido sea O(n) y utilice nicamente

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;
{P0:N1v=Vx=X}
{Q0:v=Vx=X1pN(i:1i<p:v(i)zx)
  (v(p)=xp=N)}
ffunc

(b) Bsqueda secuencial con centinela:


funcbusca(v:Vector[1..N]deEnt;x,q:Ent)devp:Ent;
{P0:v=Vx=Xq=Q1qNv(q)=x}
{Q0:v=Vx=Xq=Q1pq
  (i:1i<p:v(i)zx)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;
{P0:N1v=Vx=Xord(v)}

{Q0:v=Vx=X0pN
  i:1ip:v(i)xi:p+1iN:x<v(i)}
ffunc

Diseo de algoritmos iterativos

151

Interpreta la postcondicin. Qu dice Q0 en los casos extremos, es decir, para p=0 y p=N?
Cmo podemos deducir de Q0 si x est o no en el vector?
Pista: Generaliza la postcondicin observando que
Q0v=Vx=X0p<qN+1i:1ip:v(i)x
i:qiN:x<v(i)q=p+1

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);
{P0:1p<Nv=Vp=Pi,j:1i<jp:v(i)v(j)}
{Q0:p=Ppermut(v,V)i,j:1i<jp+1:v(i)v(j)}
fproc

Pista: Generaliza la postcondicin observando que


Q0p=Ppermut(v,V)0mp
i,j:1i<jm:v(i)v(j)


i,j:m+1i<jp+1:v(i)v(j)

i,j:1imm+2jp+1:v(i)v(j)(m=0v(m)
v(m+1))

76. Deriva un procedimiento O(n2) para la ordenacin de un vector mediante el algoritmo de

insercin, partiendo de la especificacin siguiente:

procordena(esv:Vector[1..N]deEnt);
{P0:v=vN1}
{Q0:permut(v,V)ord(v)}
fproc

siendo


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

Pista: Generaliza la postcondicin observando que


Q0permut(v,V)i,j:1i<jn:v(i)v(j)n=N

Descompn para encontrar un invariante y aprovecha el procedimiento del ejercicio anterior para disear la accin encargada de su restablecimiento.
77. Deriva una funcin O(Np+1) que satisfaga la especificacin siguiente:
funcbuscaMin(v:Vector[1..N]deEnt;p:Ent)devm:Ent;
{P0:1p<Nv=Vp=P}
{Q0:p=Pv=VpmNv(m)=mink:pkN:v(k)}
ffunc

78. Deriva un procedimiento O(n2) para la ordenacin de un vector mediante el algoritmo de

seleccin, partiendo de la misma especificacin del ejercicio 76, pero tomando ahora

ord(v)defi:1i<N:(j:i<jN:v(i)v(j))

Diseo de algoritmos iterativos

152

Pista: Generaliza la postcondicin observando que


Q0permut(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 anterior 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 terminologa 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 resolverlo 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 funciones que se invocan a s mismos.
Para entender la recursividad a veces resulta til considerar cmo se ejecutan las acciones recursivas 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 construccin de trazas de los procedimientos y funciones recursivas. El ejemplo clsico del factorial:

funfact(n:Nat)devr:Nat;
{P0:N=n}
inicio
si
n=0or:=1
n>0or:=n*fact(n1)
fsi
{Q0:n=Nr=3i:1in:i}1
devr
ffunc

Ntese que para n=0 el aserto de dominio del producto extendido define el conjunto vaco, y por lo tanto el
producto extendido da 1 como resultado, el elemento neutro del producto.
1

Algoritmos recursivos

154

La ejecucin de fact(2)?
Programa
principal
.
.
.
x := fact(2)

Programa
principal
.
.
.
x := fact(2)

Programa
principal
.
.
.
x := fact(2)

Programa
principal
.
.
.
x := fact(2)

Programa
principal
.
.
.
x := fact(2)

Programa
principal
.
.
.
x := fact(2)

Programa
principal
.
.
.
x := fact(2)

fact(2)
.
.
.
r := n * fact(n-1)

fact(2)

fact(1)

.
.
.

.
.
.

r := n * fact(n-1)

r := n * fact(n-1)

fact(2)

fact(1)

fact(0)

.
.
.

.
.
.

.
.
.

r := n * fact(n-1)

r := n * fact(n-1)

fact(2)

fact(1)

.
.
.

.
.
.

r := n * fact(n-1)

fact(2)
.
.
.
r := n * fact(n-1)

r := n * fact(n-1)

r := 1

.
.
.

Algoritmos recursivos

155

En realidad no se realizan copias del cdigo sino simplemente de las variables locales y de la direccin de retorno, pero valga el smil.
La importancia de la recursin radica en que:

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 utilizando lenguajes funcionales, donde el mecanismo natural de resolucin de problemas es la recursin. Mi opinin personal es que determinados problemas se resuelven ms fcilmente con un
diseo iterativo mientras que otros piden una solucin recursiva; de modo que a veces resulta
forzado intentar encontrar diseos recursivos para ciertos problemas cuya solucin iterativa es
directa, y viceversa.
En este tema nos vamos a limitar al estudio de funciones recursivas. Los procedimientos no
aportan nada ms y el tratamiento es menos engorroso con las funciones. Al trasladar los resultado sobre funciones a procedimientos slo hay que tomar algunas precauciones con los parmetros de entrada/salida. Vamos a analizar los diferentes esquemas a los que se ajustan las funciones
recursivas, dependiendo del nmero de llamadas recursivas y de la funcin de combinacin.
1.1.1

Recursin simple

Decimos que una accin recursiva tiene recursin simple si cada caso recursivo realiza a lo
sumo una llamada recursiva.
El esquema de declaracin de funciones recursivas simples:

funcnombreFunc(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
{P0:Px1=X1xn=Xn}
cte;
var;
inicio
si

&

&

&

&

&

&

d( x )o y :=g( x )

&

d( x )o y :=c( x ,nombreFunc(s( x ))) %abusodenotacin


fsi;
{Q0:Qx1=X1xn=Xn}

Algoritmos recursivos

156

dev<y1,,ym>
ffunc


donde
&
d( x ) es la condicin que determina el caso directo
&
d( x ) es la condicin que determina el caso recursivo

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 llamadas recursivas depende linealmente del tamao de los datos.
Una caso particular de recursin simple se obtiene cuando la funcin de combinacin se limita
a transmitir el resultado de la llamada recursiva. Este tipo de recursin simple recibe el nombre de
recursin final (tail recursion). Ntese que en una funcin recursiva final, el resultado devuelto
ser siempre el obtenido en uno de los casos base, ya que la funcin de combinacin se limita a
transmitir el resultado que recibe, sin modificarlo. Se llama final porque lo ltimo que se hace en
cada pasada es la llamada recursiva.
Las funciones recursivas finales tienen la interesante propiedad de que pueden ser traducidas
de manera directa a soluciones iterativas, ms eficientes.
Como ejemplo de funcin recursiva final veamos el algoritmo de clculo del mcd por el mtodo de Euclides.

funcmcd(a,b:Ent)devm:Ent;
{P0:a=Ab=Ba>0b>0}
inicio
si

Algoritmos recursivos

157

a>bom:=mcd(ab,b)
a<bom:=mcd(a,ba)
a=bom:=a
fsi
{Q0:a=Ab=Bm=MCD(a,b)}
devm
ffunc

Tenemos aqu un ejemplo donde se distinguen ms de dos casos, en particular dos casos recursivos y un caso base. La funcin es recursiva simple porque en cada caso recursivo se hace una
sola llamada recursiva. Veamos cmo se ajusta al esquema de recursin simple:

d(a,b) a = b
d2(a,b) a < b
d1(a,b) a > b
g(a,b) = a
s2(a,b) = (a, ba)
s1(a,b) = (ab, a)
c(a,b,mcd(x,y)) = mcd(x,y)

dos caso recursivos


dos funciones sucesor

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 recursivas. El esquema correspondiente:

funcnombreFunc(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
{P0:Px1=X1xn=Xn}
cte;
var;
inicio
si

&

&

&

&

&

&

d( x )o y :=g( x )

&

&

d( x )o y :=c( x ,nombreFunc(s1( x )),,nombreFunc(sk( x )))


fsi;
{Q0:Qx1=X1xn=Xn}
dev<y1,,ym>
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

si (funciones sucesor) calculan la descomposicin de los datos de entrada para realizar la isima llamada recursiva, para i = 1, , k

c (funcin de combinacin) obtiene la combinacin de los resultados de las llamadas re&


&
cursivas nombreFunc( si( 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 siguiendo el esquema de recursin mltiple:

funcfibo(n:Nat)devr:Nat;
{P0:n=N}
inicio
si
n=0or:=0
n=1or:=1
n>1or:=fibo(n1)+fibo(n2)
fsi
{Q0:n=Nr=fib(n)}
devr
ffunc

En esta funcin tenemos que, segn el esquema

d1(n) n = 0

d2(n) n = 1

dos caso directos

d(n) n > 1
g(0) = 0
g(1) = 1
(g(n) = n?)
s2(n) = n2
s1(n) = n1
c(n, fibo(n1), fibo(n2)) = fibo(n1) + fibo(n2)
En la recursin mltiple el nmero de llamadas no depende linealmente del tamao de los datos; en general ser de mayor orden.
Podemos ver un ejemplo con las llamadas que se desencadenan al computar fib(4). Comprobamos que algunos resultados se computan ms de una vez:

Algoritmos recursivos

159

1+2=3
0+1=1
0

fib(2)

fib(0)

1+1=2

fib(4)
1

fib(1)

0+1=1
fib(3)
0
1
fib(1)
fib(2)

fib(0)

fib(1)

Para terminar con la introduccin recapitulamos los distintos tipos de funciones recursivas que
hemos presentado:

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 elemento nuevo a nuestro lenguaje algortmico. El problema radica en que debemos verificar las llamadas 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 intentando 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

1.2.1

160

Verificacin de la recursin simple

Hemos de verificar el cuerpo de una funcin recursiva simple, que se corresponde con el esquema:

si

&

&

&

&

&

&

d( x )o y :=g( x )

&

d( x )o 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 especificacin:

&

&

d( x )R( x )

&

&

R(s( x ))R( x )


&

&

 x R( x )

&
&
Considerando que s( x ) es el valor anterior de x .
Para obtener las reglas de verificacin vamos a empezar aplicando las reglas de verificacin de
la composicin alternativa, pues esa es la forma que toma el cuerpo de la funcin recursiva. En
primer lugar, se tiene que garantizar que las barreras estn definidas y que al menos una de ellas
se abra:

&

&

P0def(d( x ))def(d( x ))

&

&

P0d( x )d( x )

Por otra parte, debe ser correcta la rama del caso directo:

&

&

&

{P0d( x )} y :=g( x ){Q0}

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 verificar:

&

&

&

&

{P0d( x )} y :=c( x ,nombreFunc(s( x ))){Q0}

como es posible que la funcin no sea recursiva final, y que por lo tanto la llamada recursiva
aparezca dentro de una expresin, introducimos una variable auxiliar que nos facilite la verificacin:
&
{P0d( x )}
&
&
 y :=nombreFunc(s( x ));
&
& &
 y :=c( x , y )
{Q0}


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


&

{P0d( x )}

&

&

 y :=nombreFunc(s( x ));

& &

&

&

&

{def(c( x , y ))Q0[ 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 induccin, verificaremos la llamada suponiendo que existe una realizacin correcta para dicha lla&
mada con s( x ).
Aplicando el mtodo de verificacin de llamadas tenemos:
1. Se puede realizar la llamada
&
&
&
P0d( x )P0[ x /s( x )]
sin embargo, es necesario tener en cuenta un detalle en la mayora de las ocasiones irre&
&
levante y es que en P0[ x /s( x )] pueden aparecer variables auxiliares de la especificacin, con los mismos identificadores que las que aparecen en P0; y no deben confundirse.
Por ello, en realidad demostraremos:
& &
&
&
&
P0d( x )P0[ x /s( x )][ X / X ]
&
siendo X las variables que recogen los valores iniciales de la llamada recursiva
2. Extraer de

&

&

&

&

&

P0d( x )P0[ x /s( x )][ X / X ]

las condiciones que no dependen de los parmetros de salida o entrada/salida. Como P0


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 P0 [ x / s( x ) ] [ X / X ] est
&
ya en P0 d( x ). Por lo tanto el aserto R que consideramos es:

&

P0d( x )


Este ltimo razonamiento tambin es aplicable en el caso general de llamadas a procedimientos y funciones. La razn por la que en el caso general no aplicamos esta simplificacin es que la precondicin del procedimiento puede incluir condiciones que nos ayuden
a reescribir la precondicin de la llamada y sacar a la luz las condiciones implcitas.
3. Demostramos que:


&

&

&

&

& &

&

&

&

P0d( x )Q0[ x /s( x ), y / y ][ X / X ]def(c( x , y ))


&
& &
Q0[ 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 fundamentado
&
t( x ) : DW& o D
Tendremos que comprobar entonces que al entrar en la rama recursiva la expresin de acotacin est definida y sea decrementable


&

&

&

P0d( x )def(t( x ))dec(t( x ))




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




&

&

&

P0d( x )t(s( x )) % t( x )




Con todo esto la verificacin de una funcin recursiva pasa por demostrar los siguientes requisitos:
(r.1) condiciones definidas
&
&
P0def(d( x ))def(d( x ))
(r.2) condiciones exhaustivas
&
&
P0d( x ))d( x )

Algoritmos recursivos

(r.3) caso base

&

163

&

&

{P0d( x )} y =g( x ){Q0}

(r.4) definicin de la llamada


& &
&
&
&
P0d( x )P0[ x /s( x )][ X / X ]
(r.5) paso inductivo
& &
&
&
&
& &
& &
P0d( x )Q0[ x /s( x ), y / y ][ X / X ]def(c( x , y ))
&
& &
Q0[ y /c( x , y )]
(r.6) expresin de acotacin
&
&
&
P0d( x )def(t( x ))dec(t( x ))
(r.7) descenso

&

&

&

P0d( x )t(s( x )) % t( x )

Veamos un ejemplo de aplicacin de las reglas de verificacin de funciones recursivas, aplicada a la funcin factorial.
(r.1) condiciones definidas
N=ndef(n=0)def(n>0)cierto

(r.2) condiciones exhaustivas


N=nn=0n>0




 
n=0n>0
 n:Nat
declaracin ciertoN=n

(r.3) caso base


{N=nn=0}r:=1{Q0:n=Nr=3i:1in:1}




fortalecimiento





def(1)Q0[r/1]
 cierton=N1=3i:1in:1
n=Nn=0
 P0n=0

(r.4) definicin de la llamada


n=Nn>0P0[n/n1][N/N]




 
P0[n/n1][N/N]
  n1=N
  n1:Nat

Algoritmos recursivos




164

  n>0n:Nat
  n=Nn>0

(r.5) paso inductivo


n=Nn>0Q0[n/n1,r/r][N/N]def(n*r)Q0[r/n*r]


elaboramos la parte derecha





 
def(n*r)Q0[r/n*r]
  cierton=Nn*r=3i:1in:i

veamos que de la parte izquierda (hiptesis de induccin) se deduce la parte derecha





:i





 
n=Nn>0Q0[n/n1,r/r][N/N]
  n=Nn>0n1=Nr=3i:1in1
  n=Nn*r=n*(3i:1in1:i)
  n=Nn*r=3i:1in:i
  def(n*r)Q0[r/n*r]

(r.6) expresin de acotacin


Como expresin de acotacin tomamos
t(n)=n


demostramos

n=Nn>0def(n)dec(n)n>0

(r.7) descenso
n=Nn>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
&
&
&
P0def(d1( x ))def(dp( x ))def(d( x ))
(r.2) condiciones exhaustivas

Algoritmos recursivos

&

165

&

&

P0d1( x ))dp( x ))d( x )

(r.3) se ha de comprobar la correccin de todos los casos base


&
&
&
{P0dj( x )} y =gj( x ){Q0}


&
&
donde las dj( x ) son las condiciones de los casos directos y las gj( x ) las funciones que obtiene la solucin directa en cada caso, para j= 1, , p
El resto de los requisitos no se ven modificados pues hacen referencia a los casos recursivos.

Ms de un caso recursivo
Los requisitos que se ven modificados
(r.1) todas las barreras han de estar definidas
&
&
&
P0def(d( x ))def(d1( x ))def(dp( x ))
(r.2) condiciones exhaustivas
&
&
&
P0d( x ))d1( x )dp( x )
(r.4) Cada llamada recursiva ha de estar definida. Tenemos p requisitos de la forma (r.4)j
& &
&
&
&
P0dj( x )P0[ x /sj( x )][ X / X ]
(r.5) Se ha de comprobar el paso inductivo para cada caso recursivo. Tenemos p requisitos de
la forma (r.5)j
& &
&
&
&
& &
& &
P0dj( x )Q0[ x /sj( x ), y / y ][ X / X ]def(cj( x , y ))
&
& &
Q0[ y /cj( x , y )]
(r.6) Aunque la expresin de acotacin es nica, debemos verificar que todos los casos recursivos garantizan (r.6)j
&
&
&
P0dj( x )def(t( x ))dec(t( x ))
(r.7) Se ha de comprobar el descenso de la expresin de acotacin para cualquier descomposicin recursiva. Tenemos requisitos (r.7)j de la forma
&
&
&
P0dj( x )t(sj( x )) % t( x )

&
&
Siendo las dj( x ) las condiciones de los casos recursivos, las sj( x ) las funciones que calculan
& &
las descomposiciones recursivas, y las cj( x , y ) las funciones que combinan los resultados, para
j= 1, , p

Algoritmos recursivos

166

Cuando nos encontremos con una funcin recursiva que incluya ms de un caso directo y ms
de un caso recursivo, deberemos considerar los dos conjuntos de cambios que acabamos de indicar.
Veamos un ejemplo de funcin recursiva con varios casos base y varios casos recursivos: el
producto de dos nmeros naturales por el mtodo del campesino egipcio. La idea del mtodo es ir
dividiendo por 2 un operando y multiplicando por 2 el otro, hasta que un operando valga 1
0
a

si b = 0
si b = 1

(2 a) (b div 2)

si b > 1, b par

(2 a) (b div 2) + a

si b > 1, b impar

ab=

Una declaracin para este algoritmo es:

funcprod(a,b:nat)devr:Nat;
{P0:a=Ab=B}
inicio
sib=0     or:=0
b=1     or:=a
(b>1ANDpar(b))  or:=prod(2*a,bdiv2)
(b>1ANDNOTpar(b))or:=prod(2*a,bdiv2)+a
fsi
{Q0:a=Ab=Br=a*b}
devr
ffunc

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=Ab=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=Ab=B?b=0b=1(b>1ANDpar(b))(b>1ANDNOTpar(b))

Algoritmos recursivos

    b=0b=1(b>1(par(b)ORNOTpar(b)))

    b=0b=1b>1




    ciertoa=ab=B

167

(r.3)1 Correccin del primer caso base


{a=Ab=Bb=0}
r:=0
{a=Ab=Br=a*b}

(r.3)2 Correccin del segundo caso base


{a=Ab=Bb=1}
r:=a
{a=Ab=Br=a*b}


(r.4)1 Es posible hacer la llamada recursiva en el primer caso recursivo


a=Ab=B(b>1ANDpar(b))?P0[a/2*a,b/bdiv2][A/A,B/B]

 
2*a=Abdiv2=B

 ** a:Natb:Nat

 
a=Ab=B

** no es doble implicacin porque 1 div 2 N pero 1 N.


(r.4)2 Es posible hacer la llamada recursiva en el segundo caso recursivo
a=Ab=B(b>1ANDNOTpar(b))?P0[a/2*a,b/bdiv2]
[A/A,B/B]

 
2*a=Abdiv2=B

 
a:Natb:Nat

 
a=Ab=B

(r.5)1 Paso inductivo para el primer caso recursivo


P0(b>1ANDpar(b))Q0[a/2*a,b/bdiv2,r/r][A/A,B/B]?Q0
aqunohahechofaltaintroducirunavariableauxiliarrporquelafuncin
decombinacinselimitaatransmitirelresultadodelallamadarecursiva

a=Ab=B(b>1ANDpar(b))2*a=Abdiv2=Br=(2*a)*(bdiv
2)?
a=Ab=Br=a*b

a=Ab=Ba=Ab=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


P0(b>1ANDNOTpar(b))Q0[a/2*a,b/bdiv2,r/r][A/A,B/B]?
def(r+a)Q0[r/r+a]


 
def(r+a)Q0[r/r+a]

  ciertoa=Ab=Br+a=a*b

  a=ab=Br=a*(b1)

P0a=Ab=B

(b>1ANDNOTpar(b))Q0[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
P0(b>1ANDpar(b))def(b)dec(b)ciertob>0


(r.6)2 Definicin de la expresin de acotacin en el segundo caso recursivo

P0(b>1ANDNOTpar(b))def(b)dec(b)ciertob>0


(r.7)1 Decremento de la expresin de acotacin en el primer caso recursivo


P0(b>1ANDpar(b))(bdiv2)<b

secumpleporb>0


(r.7)2 Decremento de la expresin de acotacin en el segundo caso recursivo

Algoritmos recursivos

169

P0(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 )o y :=g( x )

&

&

&

d( x )o y :=c( x ,nombreFunc(s1( x )),,nombreFunc(sk( 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 especificacin, aplicamos la regla:
&

&

d( x )R( x )

&

&

&

(si( x ):R(si( 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:

&

&

&

&

&

{P0d( x )} y :=c( x ,nombreFunc(s1( x )),,nombreFunc(sk( x ))){Q0


}


para cuya verificacin introducimos k variables auxiliares:


&
{P0d( x )}
&
&
 y 1:=nombreFunc(s1( x ));


&
&
&
& &
&
 y :=c( x , y 1,, y k)

 y k:=nombreFunc(sk( x ));

Algoritmos recursivos

170

{Q0}

La verificacin de cada una de las llamadas recursivas introduce una serie de condiciones, que
podemos trasladar al final de la composicin y considerar as la verificacin de una sola instruccin:

&

&

&

&

& &

&

{P0d( x )Q0[ x /s1( x ), y / y 1][ X / X 1]

&

&

&

& &

&

Q0[ x /sk( x ), y / y k][ X / X k]}

&

&

&

&

 y :=c( x , y 1,, y k)


{Q0}

Para ver que este resultado de correccin es equivalente al anterior tendramos que ir construyendo la pmd de cada una de las asignaciones intermedias, con lo cual dichas asignaciones sern
correctas por el axioma de correccin de la asignacin.
Aplicando la regla de verificacin de la asignacin, la anterior verificacin es equivalente a:

&

&

&

&

& &

&

&

&

& &

P0d( x )Q0[ x /s1( x ), y / y 1][ X / X 1]Q0[ x /sk( x ), y / y k]

&

&

[ X / X k]


&

&

&

&

&

&

&

def(c( x , y 1,, y k))Q0[ y /c( x , y 1,, y k)]

que es precisamente el paso inductivo; supuestas correctas las llamadas recursivas con las correspondientes descomposiciones, se demuestra que es correcto el paso actual.
Con todo esto los requisitos que hay que comprobar para demostrar la correccin de una funcin con recursin mltiple:
(r.1) condiciones definidas
&
&
P0def(d( x ))def(d( x ))
(r.2) condiciones exhaustivas
&
&
P0d( x ))d( x )
(r.3) caso base

&

&

&

{P0d( x )} y =g( x ){Q0}

(r.4) definicin de la llamada


& &
& &
&
&
&
&
&
P0d( x )P0[ x /s1( x )][ X / X 1]P0[ x /sk( x )][ X / X k]

Algoritmos recursivos

171

(r.5) paso inductivo


& &
&
&
&
& &
&
&
& &
P0d( x )Q0[ x /s1( x ), y / y 1][ X / X 1]Q0[ x /sk( x ), y / y k]
& &
[ X / X k]


&

&

&

&

&

&

&

def(c( x , y 1,, y k))Q0[ y /c( x , y 1,, y k)]

(r.6) expresin de acotacin


&
&
&
P0d( x )def(t( x ))dec(t( x ))
(r.7) descenso

&

&

&

&

&

P0d( x )t(s1( x )) % t( x )t(sk( 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
&
&
&
P0def(d( x ))def(d1( x ))def(dp( x ))
(r.2) condiciones exhaustivas
&
&
&
P0d( x ))d1( x )dp( x )
(r.4) Cada llamada recursiva ha de estar definida. Para cada caso recursivo podemos tener kj
llamadas recursivas. Tenemos p requisitos de la forma (r.4)j
& &
& &
&
&
&
&
&
P0dj( x )P0[ x /sj1( x )][ X / X 1]P0[ x /sjk ( x )][ X / X k ]
j

(r.5) Se ha de comprobar el paso inductivo para cada caso recursivo. Tenemos p requisitos de
la forma (r.5)j2
& &
&
&
&
& &
&
&
& &
P0dj( x )Q0[ x /sj1( x ), y / y 1][ X / X 1]Q0[ x /sjk ( x ), y / y k ]
& &
[ X / X k ]
j

Ntese que reutilizamos las variables auxiliares en los distintos casos recursivos, porque si no deberamos definir variables yj1, , yjkj
2

Algoritmos recursivos

&

&

172

&

&

&

&

&

def(cj( x , y 1,, y kj))Q0[ y /cj( x , y 1,, y kj)]

(r.6) Aunque la expresin de acotacin es nica, debemos verificar que todos los casos recursivos garantizan (r.6)j
&
&
&
P0dj( x )def(t( x ))dec(t( x ))
(r.7) Se ha de comprobar el descenso de la expresin de acotacin para cualquier descomposicin recursiva. Tenemos requisitos (r.7)j de la forma
&
&
&
&
&
P0dj( x )t(sj1( x )) % t( x )t(sjk ( x )) % t( x )
j

&
&
Siendo las dj( x ) las condiciones de los casos recursivos, las sji( x ) las funciones que calculan
& &
&
las descomposiciones recursivas, y las cj( x , y 1, , y k) las funciones que combinan los resultados, para j= 1, , p, i = 1, , kj
Si en una funcin aparecen varios casos directos y varios casos recursivos, tendremos que
combinar los dos conjuntos de modificaciones arriba descritos.
Veamos como ejemplo de verificacin de funcin con recursin mltiple la verificacin de la
funcin fibo que presentamos al principio del tema:

funcfibo(n:Nat)devr:Nat;
{P0:n=N}
inicio
si
n=0or:=0
n=1or:=1
n>1or:=fibo(n1)+fibo(n2)
fsi
{Q0:n=Nr=fib(n)}
devr
ffunc

(r.1) Definicin de las barreras


n=Ndef(n=0)def(n=1)def(n>1)


(r.2) Condiciones exhaustivas

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


(r.3)1 Correccin del primer caso base

Algoritmos recursivos

173

{n=Nn=0}
r:=0
{n=Nr=fib(n)}

n=Nn=0def(0)n=N0=fib(n)

(r.3)2 Correccin del segundo caso base


{n=Nn=1}
r:=1
{n=Nr=fib(n)}

n=Nn=1def(1)n=N1=fib(n)

(r.4) Llamada recursiva


n=Nn>1P0[n/n1][N/N1]P0[n/n2][N/N2]


 
P0[n/n1][N/N1]

  n1=N1

  n1:Nat

  n:Natn1

  n=Nn>1


















P0[n/n2][N/N2]
n2=N2
n2:Nat
n:Natn2
n=Nn>1

(r.5) Paso inductivo


hemosdedemostrar
n=Nn>1Q0[n/n1,r/r1][N/N1]Q0[n/n2,r/r2][N/N2]

def(r1+r2)Q0(r/r1+r2)

queesequivalenteademostrar
n=Nn>1n1=N1r1=fib(n1)n2=N2r2=fib(n2)

cierton=Nr1+r2=fib(n)

yestosetieneporque




 
r1=fib(n1)r2=fib(n2)
  r1+r2=fib(n1)+fib(n2)

Algoritmos recursivos




174

  r1+r2=fib(n)

(r.6) Expresin de acotacin


t(n)=n

n=Nn>1def(n)dec(n)cierton>0


(r.7) Avance de la recursin

n=Nn>1n1<nn2<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, procedimiento 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 casos directos y recursivos, obtendr la solucin para el caso directo, se buscar una descomposicin 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 caso 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 verificacin de algoritmos recursivos:

&

&

P0def(d( x ))def(d( x ))


&
&
P0d( x )d( 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

&
{P0d( x )}A1{Q0}


De acuerdo con los esquemas de las funciones recursivas intentaremos que A1 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 A1 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 proporciona 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 determinar si la funcin siguiente escogida garantiza la terminacin de las llamadas. Para
&
ello hemos de obtener la funcin de acotacin t( x ), definida en los casos recursivos y
que toma valores no minimales en un dominio bien fundamentado. Demostraremos
entonces el requisito (r.6)

&

&

&

P0d( 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 decrementa en cada llamada requisito (r.7):

&

&

&

P0d( 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 recursivas. Es decir, comprobamos el requisito (r.4):

&

&

&

&

P0d( x )P0[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 especificacin:

&

&

&

&

& &

&

{P0d( x )Q0[ x /s( x ), y / y ][ X / X ]}


A2
{Q0}


Para ajustarnos al esquema de las funciones recursivas, intentaremos que A2 sea de la


forma:
&
& &
y :=c( x , y )


Con lo que la demostracin de la correccin de A2 es en realidad la comprobacin de


que se verifica el requisito (r.5)

&

&

&

& &

&

&

& &

P0d( x )Q0[ x /s( x ), y / y ][ X / X ]def(c( x , y ))


&
& &

 




Q0[ 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 entonces la accin A1 a partir de la especificacin:

&

&

&

& &

&

&

{P0d( x )Q0[ x /s1( x ), y / y 1][ &X / &X 1]


&
&
& &

 Q0[ x /sk( x ), y / y k][ X / X k]}
A2
{Q0}


donde las variables yj representan a los resultados de las llamadas recursivas, para
j=1,, k
(R.9) Escritura del caso recursivo. Lo ltimo que nos queda por decidir es si escribimos la
solucin del caso recursivo como una composicin secuencial

Algoritmos recursivos

177

&

{P0d( x )}
&
&
 y :=nombreFunc(s( x ));
& &
&
&
&
& &
{P0d( x )Q0[ x /s( x ), y / y ][ X / X ]}
&
& &
 y :=c( x , y )
{Q0}


cuya correccin est demostrada por el paso anterior (la llamada es correcta por la
forma de la postcondicin y el aserto intermedio es ms fuerte que la pmd de la segunda asignacin inducida por la postcondicin).
O si podemos incluir la llamada o llamadas recursivas dentro de la expresin que las
combina:

&

{P0d( x )}
&
&
&
 y :=c( x ,nombreFunc(s( x )))
{Q0}

que tambin es correcta, porque para verificarla la convertiramos a la forma anterior, sobre la que ya hemos razonado su correccin.
Este esquema se generaliza de manera inmediata para el caso de mltiples llamadas recursivas.

Veamos un primer ejemplo de aplicacin del mtodo de derivacin. Vamos a obtener una
funcin que dado un natural n calcule la suma de los dobles de los naturales hasta n:
i:0in:2*i

La especificacin:

funcsumaDoble(n:Nat)devs:Nat;
{P0:n=N}
{Q0:n=Ns=i:0in}

(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 condiciones que distinguen los casos:
d(n):n=0
d(n):nz0


demostramos la correccin
n=Ndef(n=0)def(nz0)cierto
n=Nn=0nz0cierto

Algoritmos recursivos

178

(R.3) Solucin en el caso directo


Hemos de derivar una accin para

{n=Nn=0}
A1
{n=Ns=i:0in:2*i}


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


A1:s:=0

(R.4) Descomposicin recursiva


La descomposicin recursiva ya apareca en el planteamiento recursivo, obtenemos la suma 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 sucesivas llamadas recursivas hasta llegar a 0:
t(n)=n


Probamos los requisitos


n=Nnz0def(n)dec(n)


 
dec(n)

  n>0

  nz0n:Nat

  nz0n=N

(R.6) Decremento de la funcin de acotacin


Efectivamente la descomposicin recursiva elegida hace que disminuya la funcin de acotacin:
n=Nnz0n1<ncierto

(R.7) Es posible hacer la llamada recursiva


Hemos de demostrar
n=Nnz0P0[n/n1][N/N]


 
P0[n/n1][N/N]

Algoritmos recursivos






179











n1=N
n1:Nat
n:Natn1
n:Natnz0

(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:
{P0Q0[n/n1,s/s][N/N]:n=Nnz0n1=Ns=i:1in1
:2*i}
A2
{Q0:n=Ns=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 precondicin es ms fuerte que la pmd de la asignacin inducida por Q0:


















def(s+2*n)Q0[s/s+2*n]
cierton=Ns+2*n=i:1in:2*i
n=Ns+2*n=i:1in1:2*i+2*n
n=Ns=i:1in1:2*i
P0nz0Q0[n/n1,s/s][N/N]

(R.9) Escritura del caso recursivo


Con lo anterior tenemos demostrado que es correcta la siguiente solucin para el caso recursivo:
{P0nz0}
s:=sumaDob(n1);
{P0nz0Q0[n/n1,s/s][N/N]}
s:=s+2*n
{Q0}


Nos resta plantearnos si es posible fundir la llamada recursiva en la funcin de combinacin. En este caso s es posible ya que la funcin devuelve un nico resultado que se utiliza en la combinacin:
{P0nz0}
s:=sumaDob(n1)+2*n
{Q0}

Con todo esto la funcin derivada queda:

Algoritmos recursivos

180

funcsumaDoble(n:nat)devs:Nat;
{P0:n=N}
inicio
si
n=0o
{P0n=0}
s:=0
{Q0}
nz0o
{P0nz0}
s:=sumaDoble(n1)+2*n
{Q0}
fsi
{Q0:n=Ns=i:1in:2*i}
devs
ffunc

Ejemplo: suma de las componentes de un vector de enteros


En ocasiones es necesario generalizar, incluyendo parmetros adicionales, la funcin que queremos 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;
{P0:v=VN1}
{Q0:v=Vs=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 sumaVec nos permita especificar hasta qu componente queremos calcular la suma. Por lo tanto 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;
{P0:v=VN1b=B1bN}
{Q0:v=Vb=Bs=i:aiN:v(i)}


Algoritmos recursivos

181

Pero, ya que estamos, y pensando en darle ms utilidad a nuestra funcin podemos generalizar por los dos lados y derivar una funcin que pueda obtener la suma de un subvector
cualquiera:

funcsumaVec(v:Vector[1..N]deEnt;a,b:Ent)devs:Ent;
{P0:v=VN1b=Ba=A1abN}
{Q0:v=Vb=Bs=i:aib:v(i)}


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 posibilidad de implementar una funcin con la especificacin original y otra funcin auxiliar con
la especificacin generalizada. De esta forma el cuerpo de la funcin principal consistir
nicamente en la invocacin inicial a la funcin auxiliar.
Con todo esto el planteamiento recursivo queda:
sumaVec(v,a,b)=v(a)+sumaVec(v,a+1,b)


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

P0def(a=b)def(azb)
P0a=bazb


(R.3) Solucin en el caso directo


Se deriva de la especificacin

{P0a=b}
A1
{a=Ab=Bv=Vs=i:aib:v(i)}

A1:s:=v(a)

3 Estamos incluyendo en la precondicin las restricciones que nos interesan y no nos preocupamos por garantizarlas. En esa misma lnea, en este ejemplo, no nos preocupamos por el caso b < a, para el que podramos devolver
0. Si considersemos ese caso tendramos una implementacin ms robusta.

Algoritmos recursivos

182

(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
P0azbdef(ba)dec(ba)


 
dec(ba)

  ba>0

  b>a

  baazbP0azb

(R.6) Avance
Demostramos
P0azbb(a+1)<baba1<bacierto

(R.7) Llamada recursiva


Demostramos
P0azbP0[v/v,a/a+1,b/b][V/V,A/A,B/B]


 
P0[v/v,a/a+1,b/b][V/V,A/A,B/B]

  v=Va+1=Ab=B1a+1bN

porpartestenemosque
v=Va=Ab=Bv=Va+1=Ab=B
abazba+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:
{P0azbQ0[v/v,a/a+1,b/b,s/s][V/V,A/A,B/B]}
A2
{Q0}


o lo que es igual

Algoritmos recursivos

183

{P0azbv=Va=Ab=Bs=i:a+1ib:v(i)
}
A2
{v=Va=Ab=Bs=i:aib:v(i)}


las diferencias se resuelven con la asignacin:


A2:s:=v(a)+s

(R.9) Escritura del caso recursivo


Es posible incluir la llamada recursiva en la expresin de combinacin?
{P0azb}
s:=v(a)+sumaVec(v,a+1,b)
{Q0}

De forma que la funcin resultante:


funcsumaVec(v:Vector[1..N]deEnt;a,b:Ent)devs:Ent;
{P0:v=VN1b=Ba=A1abN}
si
a=bos:=v(a)
azbos:=v(a)+sumaVec(v,a+1,b)
fsi
{Q0:v=Vb=Bs=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 valor 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 buscado 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 recursivo 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 subvector a considerar, sino que indican a partir de qu posicin pos tenemos componentes menores 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 resulta ms natural la que proponemos). De estar en algn sitio la componente buscada estar en el
intervalo v[a..b], modificaremos los lmites de forme que a la izquierda de a estn valores menores
o iguales que el buscado y la derecha de b valores mayores que el buscado, pero sin presuponer
nada sobre los valores de v[a..b], a diferencia del otro planteamiento donde v(a)x y v(b)>x, cosa
que no podemos garantizar ahora si restringimos a y b a los lmites vlidos del vector, dentro de
los cuales no sabemos si hay algn valor mayor que el buscado o algn valor menor o igual.
Si x no se encuentra en el vector queremos que pos se quede apuntando a la posicin anterior a
la que debera ocupar x.
Con todo esto la especificacin queda:

funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P0:1abNord(v,a,b)}
{Q0:a1pbv[a..p]x<v[(p+1)..b]}


donde:
hemos obviado los asertos sobre la conservacin de los parmetros de entrada (demasiado laborioso)

El aserto ord(v,a,b), que ya apareci en un ejercicio indica que el vector entre las posiciones a y b est ordenado:
ord(v,a,b)i,j:ai<jb:v(i)v(j)

Podramos exigir simplemente ord(v), pues de hecho se cumple, pero esta otra condicin
es ms coherente con la cabecera de la funcin.
Hemos introducido una nueva notacin para escribir condiciones que se cumplen en un
subconjunto contiguo de un vector. As
v[a..p]x

abrevia
i:aip:v(i)x

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

abrevia
i:p+1ib:x<v(i)


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 longitud del subvector a considerar. Esto lleva a que el valor buscado se pueda quedar a la izquierda 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 longitud 1, es decir:
d(v,x,a,b):a=b
d(v,x,a,b):azb

demostramos

P0def(a=b)def(azb)
P0a=bazb

(R.3) Solucin en el caso directo.


Parece claro que tenemos que hacer una distincin de casos segn el resultado de comparar 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:


P0a=bdef(v(a)=x)def(v(a)<x)def(v(a)>x)


puesto que

P0enRango(v,a)


y al menos una se abre:

P0a=bv(a)=xv(a)<xv(a)>xcierto


derivamos entonces el cdigo de cada una de las ramas:


{P0a=bv(a)=x}

Algoritmos recursivos

186

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

A1:p:=a

{P0a=bv(a)<x}
A2
{v[a..p]x<v[(p+1)..b]}

A2:p:=a

{P0a=bv(a)>x}
A3
{v[a..p]x<v[(p+1)..b]}

A3:p:=a1


con lo que la solucin del caso directo queda


si
v(a)xop:=a
v(a)>xop:=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 razonamiento de este tipo)

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

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

ppuedeseraa1

(a=pv[a..a]x<v[(a+1)..a]a=b)
(a1=pv[a..(a1)]x<v[a..a]a=b)
(a=pv(a)xa=b)(a1=px<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 descomponiendo 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
abazba<bam<b


vamos a aadir esta condicin a todas las demostraciones pues partimos de un estado en
el que se cumple esto, despus de haber hecho la asignacin a m

&

{P0d( x )}
m:=(a+b)div2
&
{P0d( x )am<b}
A4
{Q0}


la parte interesante de la postcondicin es


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


Vamos a derivar A4 como una composicin alternativa, segn el resultado de la comparacin


Si x < v(m) quiere decir que m est entre p+1 y b, por lo tanto podemos acotar la
bsqueda con la descomposicin:
s1(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:
s2(v,x,a,b)=<v,x,m+1,b>

Podramos pensar en distinguir el caso x = v(m), haciendo a = m. Pero esto puede llevar a
una cadena infinita de llamadas si a=b1 v(a)=x
(R.5) Funcin de acotacin
Lo que va a ir disminuyendo segn avanza la recursin es la longitud del subvector a considerar, por lo tanto tomamos como funcin de acotacin:
t(v,x,a,b)=ba


y demostramos que est definida y es decrementable


P0azbam<bdef(ba)dec(ba)


 
dec(ba)

Algoritmos recursivos






188











ba>0
b>a
abazb
P0azb

(R.6) Terminacin
Tenemos que demostrar

&

&

&

&

P0azbam<bt(s1( x ))<t( x )t(s2( 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:

&

&

&

&

P0azbam<bP0[ x /s1( x )]P0[ x /s2( 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
 1abNazbam<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+10bNab+1


Hay que cambiar las condiciones de los casos:


&
d1( x ):a=b+1
&
d2( x ):a=b

Algoritmos recursivos

189

&

d3( x ):a<b


En los razonamientos sobre la terminacin (R.5 y R.6) no hay que cambiar nada porque estbamos razonando a partir de a b a z b que es equivalente a razonar con
a < b.

Tampoco hay que cambiar nada en la descomposicin recursiva

Ahora s es posible demostrar:

&

&

&

&

P0azbam<bP0[ x /s1( x )]P0[ x /s2( x )]






















1ab+1N+1[a/a,b/m1]
1am1+1N+1
1amN+1
1ab+1N+1azbam<b











1ab+1N+1[a/m+1,b/b]
1m+1b+1N+1
0mbN
1ab+1N+1azbam<b

(R.3)2 Solucin al segundo caso base


Hemos de derivar una accin a partir de:
{P0a=b+1}
A5
{Q0}

Razonamos a partir de la postcondicin y a = b+1



b+1

=b+1



 

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

 a1pa1v[a..(a1)]v(x)<v[a..(a1)]a
 a1=pa=b+1

Intuitivamente podemos ver que este caso puede darse cuando v(a)>x y por lo tanto p debe
apuntar a la posicin anterior (para as apuntar a la posicin anterior del lugar de insercin); o, si no admitisemos como caso base a=b, porque v(b)x con lo que p debe apuntar a b, que es precisamente a1.
(R.8) Funcin de combinacin
En los dos casos de descomposicin recursiva nos limitamos a propagar el resultado de la
llamada recursiva:
&
p:=nombreFunc(s( x ));p:=p
P0am<ba<bv(m)xm+11pbv[(m+1)..p]x<
v[(p+1)..b]

Algoritmos recursivos

190


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


Se cumple lo anterior porque en P0 se incluye ord(v, a, b), por lo que v[a..m] x


De la misma forma, para la otra llamada recursiva
P0am<ba<bv(m)>xa1pm1v[a..p]x<
v[(p+1)..(m1)]

a1pbv[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)xop:=buscaBin(v,x,m+1,b)
v(m)>xop:=buscaBin(v,x,a,m1)
fsi


Y finalmente la funcin completa:


funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P0:1abNord(v,a,b)}
var
m:Ent;
inicio
si
a=b+1op:=a1
a=bosi
v(a)xop:=a
v(a)>xop:=a1
fsi
a<bom:=(a+b)div2;
si
v(m)xop:=buscaBin(v,x,m+1,b)
v(m)>xop:=buscaBin(v,x,a,m1)
fsi
fsi
{Q0:a1pbv[a..p]x<v[(p+1)..b]}
devp
ffunc


Y no se vayan todava que an hay ms: analizando este programa con cuidado nos damos
cuenta de que el caso base a = b es redundante y lo podemos eliminar.

Algoritmos recursivos

1.3.1

191

Algoritmos avanzados de ordenacin

Vamos a estudiar dos algoritmos de ordenacin de complejidad cuasi-lineal, O(n log n): la ordenacin rpida y la ordenacin por mezcla. Debido a la complejidad de estos algoritmos y con el
nimo de terminar algn da la asignatura, relajaremos los requisitos formales para la obtencin
de los algoritmos.
Vamos a disear ambos algoritmos como procedimientos para evitar as el coste que supone
realizar una copia del vector a ordenar (aunque en la ordenacin por mezcla se utiliza un vector
auxiliar) y considerando as mismo que la mayora de los lenguajes de programacin imperativa
no permiten que las funciones devuelvan tipos estructurados.
Ordenacin rpida
Ideado por C. A. R. Hoare en 1962.
La especificacin de la que partimos:

procquickSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P0:v=Va=Ab=B1ab+1N+1}
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc

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 subvector de que nos ocupamos en cada llamada recursiva.
El objetivo de esta funcin es ordenar el subvector comprendido entre las posiciones a y b, dejando 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 iguales, y ordenar recursivamente los dos fragmentos.

Anlisis de casos

a>b
Tenemos el caso directo cuando se trata de un segmento vaco.

Algoritmos recursivos

192

P0a>ba=b+1
Q0a=b+1v=V


ab

Se trata de un segmento no vaco y tenemos entonces el caso recursivo. Las ideas en las
que se basa el caso recursivo son:
considerar x = v(a) como elemento pivote

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 izquierda 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 estrategia general de resolucin de problemas divide y vencers, puesto que resuelve el problema dividindolo en dos partes ms pequeas.

Por ejemplo:
a

3,5 6,2 2,8 5,0 1,1 4,5

Particin
a

2,8 1,1 3,5 5,0 6,2 4,5


<= 3,5

>= 3,5

Funcin de acotacin
t(v,a,b)=ba+1


Hay que sumarle 1 porque tambin consideramos caso recursivo cuando a = b, y as evitamos que la funcin de acotacin se haga 0.
Suponiendo que tenemos una implementacin correcta de particin, sobre la que luego iremos,
el algoritmo anotado nos queda:
procquickSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P0:v=Va=Ab=B1ab+1N+1}
var
p:Ent;
inicio
si
a>boseguir{Q0}
abo{P0ab}
particion(v,a,b,p);
{Q}

Algoritmos recursivos

193

{QP0[b/p1]P0[a/p+1]
quickSort(v,a,p1);
quickSort(v,p+1,b);
{QQ0[b/p1]Q0[a/p+1]}
{Q0}
fsi
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc

Donde Q es la postcondicin de particin, y de la precondicin la parte que nos interesa es:


P:1abNP0ab
Q:1apbNperm(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)
a

x
<= x

>= x

Invariante
I:a+1ij+1b+1v[(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
B:ij

 

%cuandoi=j+1hemosparticionadotodoelvector

De esta forma al final del bucle se tiene:


IBIi=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 vacos.


Accin de avance
Habr que hacer un anlisis de casos comparando las componentes v(i) y v(j) con v(a)
v(i)v(a)
 

incrementamosi
v(a)v(j)
 

decrementamosj
v(i)>v(a)ANDv(j)<v(a) intercambiamosiconjyavanzamosambas


Las dos primeras condiciones son no excluyentes y las tres condiciones juntas garantizan
que al menos una de ellas se cumple.
Con todo esto el algoritmo queda:
procparticion(esv:Vector[1..N]deElem;ea,b:Ent;sp:Ent)
{P}
var
i,j:Ent;
inicio
<i,j>:=<a+1,b>;
{I;C}
itij
osi
v(i)v(a)oi:=i+1
v(j)v(a)oj:=j1
v(i)>v(a)ANDv(j)<v(a)ointercambiar(v,i,j);
<i,j>:=<i+1,j1>
fsi
fit
{Ii=j+1}
p:=j;
intercambiar(v,a,p)

Algoritmos recursivos

195

{Q}
fproc

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);
{P0:v=Va=Ab=B1ab+1N+1}
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc

Anlisis de casos
ab
Segmento de longitud 1.
Ya est ordenado
P0aba=ba=b+1
Q0(a=ba=b+1)v=V

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 ordenado todo v[a..b]

Ejemplo:

Algoritmos recursivos

196

m m+1

3,5 6,2 2,8 5,0 1,1 4,5


Ordenacin
recursiva de las
dos mitades
a

m m+1

2,8 3,5 6,2 1,1 4,5 5,0

Mezcla
a

1,1 2,8 3,5 4,5 5,0 6,2

Funcin de acotacin
t(v,a,b)=ba+1

Suponiendo que tenemos una implementacin correcta para mezcla, el procedimiento de ordenacin queda:
procmergeSort(esv:Vector[1..N]deElem;ea,b:Ent);
{P0:v=Va=Ab=B1ab+1N+1}
var
m:Ent;
inicio
si
aboseguir{Q0}
a<bo{P0a<b}
m:=(a+b)div2;
{P0am<b}
{P0[b/m]P0[a/m+1]}
mergeSort(v,a,m);
mergeSort(v,m+1,b);
{am<bQ0[b/m]Q0[a/m+1]}
mezcla(v,a,m,b)
{Q0}
fsi
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc

Nos resta obtener el procedimiento que se encarga de mezclar los dos subvectores ordenados
adyacentes.

Algoritmos recursivos

197

Diseo de mezcla
El problema es que para conseguir una solucin eficiente, O(n), necesitamos utilizar un vector
auxiliar donde iremos realizando la mezcla, para luego copiar el resultado al vector original.
La idea del algoritmo es colocarse al principio de cada segmento e ir tomando de uno u otro el
menor elemento, y as ir avanzando. Uno de los subvectores se acabar primero y habr entonces
que copiar el resto del otro subvector. Finalmente copiaremos el resultado al vector original.

procmezcla(esv:Vector[1..N]deElem;ea,m,c:Ent);
{P0:v=Va=Am=Mc=Cam<bord(v,a,m)ord(v,
m+1,b)}
var
u:Vector[1..N]deElem;
i,j,k:Ent;
inicio
u:=v;
<i,j,k>:=<a,m+1,a>;
itimANDjb
osi
u(i)u(j)o
v(k):=v(i);
i:=i+1;
u(i)>u(j)o
v(k):=u(j);
j:=j+1;
fsi;
k:=k+1;
itim
ov(k):=u(i);
<i,k>:=<i+1,k+1>
fit;
itjb
ov(k):=u(j);
<j,k>:=<j+1,k+1>
fit
{Q0:a=Ab=Bm=Mperm(V,v,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(i:b<iN:v(i)=V(i)}

2.4 Anlisis de algoritmos recursivos


Como la recursividad no introduce nuevas instrucciones en el lenguaje algortmico, en principio 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 ecuaciones 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 composicin 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

4 + T(n1)

si n > 1

T(n) =
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

7 + T(n/2)

si n > 1

T(n) =

Algoritmos recursivos

199

Las torres de Hanoi

Tamao de los datos:


n
Caso directo:
T(n) = 2
2 de evaluar todas las barreras
En el caso recursivo:
2 de evaluar ambas barreras

si n = 0

1 del movimiento

2*T(n1) de las dos llamadas recursiva

De esta forma las ecuaciones de recurrencia


2

si n = 0

3 + 2*T(n1)

si n > 0

T(n) =

Las recurrencias no nos dan informacin sobre el orden de complejidad, necesitamos una
ecuacin explcita.
Hay dos mtodos para resolver las recurrencias:
despliegue de recurrencias

1.4.1

aplicar soluciones generales que se conocen para algunos tipos comunes de recurrencias

Despliegue de recurrencias

El proceso se compone de tres pasos:

Despliegue. Sustituimos las apariciones de T en la recurrencia tantas veces como sea necesario hasta encontrar una frmula en la que el nmero de despliegues dependa de un
parmetro k.

Postulado. A partir de la frmula paramtrica resultado del paso anterior obtenemos una
frmula explcita. Para ello, obtenemos el valor de k que nos permite alcanzar un caso directo y sustituimos la referencia recursiva por la complejidad del caso directo. (Aqu estamos haciendo la suposicin de que la recurrencia para el caso recursivo es tambin vlida
para el caso directo.)

Demostracin. Se demuestra por induccin que la frmula obtenida cumple las ecuaciones de recurrencia. (Este paso lo vamos a obviar)

Apliquemos el mtodo a los dos ejemplos que hemos presentado anteriormente: el factorial y
la multiplicacin por el mtodo del campesino egipcio:

Algoritmos recursivos

200

Factorial
3

si n = 1

T(n) =
4 + T(n1)

si n > 1

Despliegue:

T(n)=4+T(n1)
=4+4+T(n2)
=4+4+4+T(n3)

=4k+T(nk)


Postulado

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


Campesino egipcio
5

si n = 0, 1

T(n) =
7 + T(n/2)

si n > 1

Despliegue:

T(n)=7+T(n/2)
=7+7+T(n/2/2)
=7+7+7+T(n/2/2/2)

=7k+T(n/2k)


Postulado

El caso directo se tiene para n = 0,1; para alcanzar n=1 tenemos que hacer
k=logn
 %comosiemprelogenbase2

T(n)=7logn+T(n/2logn)
=7logn+T(1)

Algoritmos recursivos

201

=7logn+5


por lo tanto

T(n)O(logn)

(Ntese que la igualdad k=logn slo tiene sentido cuando n es una potencia de 2 y
por lo tanto tiene un logaritmo exacto, ya que el nmero de pasos k tiene que ser un
nmero entero. Por lo tanto el anterior resultado slo sera vlido para ciertos valores de
n, sin embargo esto no es una limitacin grave, porque la complejidad T(n) del algoritmo
es una funcin montona creciente de n, y por lo tanto nos basta con estudiar su comportamiento slo en algunos puntos).
Ntese que la funcin de complejidad que hemos obtenido no es vlida para n = 0, ya
que no est definido el logaritmo de 0; sin embargo, esto no es importante ya que nos interesa el comportamiento asinttico de la funcin.
Torres de Hanoi
2

si n = 0

T(n) =
3 + 2*T(n1) si n > 0

Despliegue:

T(n)=3+2*T(n1)
=3+2*(3+2*T(n2))
=3+2*(3+2*(3+2*T(n3)))
=3+2*3+22*3+23*T(n3)

k 1

=3

2i+2kT(nk)

i 0

Postulado
El caso directo se tiene para n = 0; para alcanzarlo
k=n

k 1

T(n)=3

2i+2kT(nk)

i 0

n 1

=3

2i+2nT(nn)

i 0

n 1

(*)=3

2i+2nT(0)

i 0

=3(2n1)+22n
=52n3

Algoritmos recursivos

202

donde en (*) hemos utilizado la frmula para la suma de progresiones geomtricas:


n 1

ri=

i 0

rn 1
 
r 1

rz1

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


Es un algoritmo de coste exponencial; es habitual que las funciones recursivas mltiples tengan costes prohibitivos.
1.4.2

Resolucin general de recurrencias

Se pueden obtener unos resultados tericos aplicables a un gran nmero de recurrencias, aquellas en las que la descomposicin recursiva se obtiene por sustraccin y aquellas otras en las que
se obtiene por divisin.
Disminucin del tamao del problema por sustraccin
Si la descomposicin recursiva se obtiene restando una cierta cantidad constante, tenemos entonces que esto da lugar a recurrencias de la forma:
(D) T(n) = G(n)

si 0 n < b

(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)
=a2T(n2b)+aC(nb)+C(n)
=a2(aT(n2bb)+C(n2b))+aC(nb)+C(n)
=a3T(n3b)+a2C(n2b)+aC(nb)+C(n)

m 1

T(n)=amT(nmb)+

i 0

aiC(nib)

Algoritmos recursivos

203

Como nos interesa el comportamiento asinttico podemos tomas las siguientes hiptesis simplificadoras:

n=mbm=n/b 


esdecir,consideramosslolosnque
sonmltiplosdeb

delosposiblescasosdirectos(0n<b)nos
T(0)=G(0)=c0 
quedamosconuno(n=0)

De esta forma podemos eliminar la recurrencia de la expresin anterior, obteniendo:


m 1

T(n)=amc0+

aiC(mbib)

i 0

Donde damos nombre a cada uno de los sumandos para poder tratarlos por separado:
T1(n)=amc0
m 1

T2(n)=

aiC(b(mi))

i 0

Estudiamos ahora un caso particular frecuente en la prctica: cuando el coste de preparar las
llamadas y combinar los resultados es de la forma:
C(n)=cnk


 paraciertascR+,kN

Y ahora estudiamos dos posibilidades dependiendo del valor de a el nmero de llamadas recursivas:

a=1

T1(n)=c0
m 1

T2(n)=

c(b(mi))k

i 0

m 1

=cbk

(mi)k=cbk

i 0

m k  1k
m 
2

cbkmk+1
=cbk(n/b)k+1
=c/bnk+1


por lo tanto

T2(n)O(nk+1)


y como T2(n) domina a T1(n), que es una constante, tenemos

Algoritmos recursivos

204

T(n)=c0+T2(n)O(nk+1)


Cuando se aplica una nica llamada recursiva (a = 1) obtenemos un orden de complejidad polinmico en n, cuyo exponente es uno ms que el del coste de C(n)

a>1
T1(n)=amc0
m 1

T2(n)=

aic(b(mi))k

i 0

m 1

=

ai

i 0

am
c(b(mi))k
am

%multiplicandoarribay

abajoporam
m 1

=amcbk

i 0

m 1

=amcbk

i 0

a i ( m  i )k

am
( m  i )k

a m i

y ahora hacemos un cambio de ndices para as darnos cuenta de que esta serie converge.

j=mi


 i=0j=m 

i=m1j=1

entonces


T2(n)=amcbk

j 1

jk

aj

y se cumple

m

T2(n)=amcbk

j 1

jk
<amcbks
aj

siendo s la suma de la serie convergente



f

s=

j 1

jk

aj

tenemos por lo tanto


T2(n)O(am)

T(n)=c0am+T2(n)O(am)=O(andivb)

Algoritmos recursivos

205

Es decir, cuando tenemos varias llamadas recursivas (a>1) y el tamao del problema disminuye por sustraccin, la complejidad resultante es exponencial. Por lo tanto, la recursin mltiple
con divisin del problema por sustraccin conduce a algoritmos muy ineficientes; como ocurre
por ejemplo con el algoritmo para las torres de Hanoi donde
b=1 

a=2


T(n)O(2n)

Disminucin del tamao del problema por divisin


Si la descomposicin recursiva se obtiene dividiendo por una cierta cantidad constante, tenemos 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)
=a2T(n/b2)+aC(n/b)+C(n)
=a2(aT(n/b2/b)+C(n/b2))+aC(n/b)+C(n)
=a3T(n/b3)+a2C(n/b2)+aC(n/b)+C(n)

m 1

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

aiC(n/bi)

i 0

Como nos interesa el comportamiento asinttico podemos tomas las siguientes hiptesis simplificadoras:

n=bmm=logbn 
esdecir,consideramosslolosnqueson
potenciasdeb
T(1)=G(1)=c1 
delosposiblescasosdirectos(0n<b)nos
quedamosconuno(n=1)

De esta forma podemos eliminar la recurrencia de la expresin anterior, obteniendo:

Algoritmos recursivos

206

m 1

T(n)=amc1+

m 1

aiC(n/bi)=amc1+

i 0

aiC(bm/bi)

i 0

m 1

T(n)=amc1+

aiC(bmi)

i 0

Donde damos nombre a cada uno de los sumandos para poder tratarlos por separado:
T1(n)=amc1
m 1

T2(n)=

aiC(bmi)

i 0

podemos transformar T1(n) de la siguiente forma



T1(n)=amc1= a


log b n

c1=c1 n

log b a

donde la ltima igualdad se tiene por

a logb n = b logb a

log b n

= b

(log b n )(log b a )

= n

log b a

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


C(n)=cnk


 paraciertascR+,kN

Y, de esta forma, podemos elaborar T2(n):


m 1

T2(n)=

aiC(bmi)

i 0

m 1

=

aicb(mi)k

i 0

m 1

=c

ai

i 0

b mk
b(mi)k %multiplicandoarribayabajoporbm
b mk


m 1

=cbmk

aibik

m 1

a
k 
b

i 0

=cb

mk

i 0

Con lo que la complejidad queda



T(n)=c1 n

log b a

m 1

+cb

mk

i 0

a
k 
b

Algoritmos recursivos

207

Estudiamos el valor del sumatorio segn la relacin que exista entre a y bk.

a = bk
Tenemos entonces
T1(n)=c1nk
m 1

T2(n)=cbmk

1i 

i 0

=cbmkm
=cnklogbn


%yaquem=logbn

De esta forma

T(n)=c1nk+cnklogbnO(nklogbn)=O(nklogn)


Vemos que T2(n) domina a T1(n). Observamos asimismo que la complejidad depende de nk
que es un trmino que proviene de C(n), por lo tanto la complejidad de un algoritmo de este tipo
se puede mejorar disminuyendo la complejidad de C(n) = c nk.

a < bk
Tenemos entonces que
logba<k

y, por lo tanto
T1(n)=c1 n


log b a

<c1nk

m 1

mk

T2(n)=cb 

i 0

a
k
b

como a < bk tenemos una serie geomtrica de razn r = a/bk < 1


r m 1

r 1
a m  b mk
=c

(a /b k )  1
T2(n)=cbmk

si a < bk entonces am < bmk por lo tanto el trmino dominante en la anterior expresin es
bmk y podemos afirmar (ntese que la anterior expresin tiene signo positivo):
T2(n)O(bmk)=O( b

(log b n ) k

)=O(nk)

Algoritmos recursivos

208

Vemos que T2(n) domina a T1(n) y por lo tanto


T(n)O(nk)


Observamos asimismo que la complejidad depende de nk que es un trmino que proviene de


C(n), por lo tanto la complejidad de un algoritmo de este tipo se puede mejorar disminuyendo la
complejidad de C(n) = c nk.

a > bk
Tenemos entonces que
logba>k

y, por lo tanto

T1(n)=c1 n


log b a 

m 1

mk

T2(n)=cb 

i 0

a
k
b

como a > bk tenemos una serie geomtrica de razn r = a/bk > 1


r m 1

r 1
a m  b mk
=c

(a /b k )  1
T2(n)=cbmk

si a > bk entonces am > bmk por lo tanto el trmino dominante en la anterior expresin es
am y podemos afirmar (ntese que la anterior expresin tiene signo positivo):
T2(n)O(am)=O( a


log b n

)=O( n

log b a

)

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


T(n)=T1(n)+T2(n)O( n


log b a

)

Observamos aqu que T1(n) y T2(n) tienen el mismo orden de complejidad, que slo depende
de a y b. Por lo tanto, en este caso no se consigue mejorar la eficiencia global mejorando la eficiencia de C(n). Las mejoras se obtendrn disminuyendo a el nmero de llamadas recursivas o
aumentando b obtenindose as subproblemas de menor tamao.
Divisin del problema por sustraccin
c0

si 0 n < b

Algoritmos recursivos

209

T(n) =
a T(nb) + c nk

si n b

O(nk+1)

si a = 1

O(an div b)

si a > 1

T(n)

Cuando se aplica una nica llamada recursiva (a = 1) obtenemos un orden de complejidad polinmico en n, cuyo exponente es uno ms que el del coste de C(n).
Cuando tenemos varias llamadas recursivas (a>1) y el tamao del problema disminuye por
sustraccin, la complejidad resultante es exponencial. Por lo tanto, la recursin mltiple con divisin del problema por sustraccin conduce a algoritmos muy ineficientes; como ocurre por
ejemplo con el algoritmo para las torres de Hanoi donde
a=2

b=1 

T(n)O(2n)

Divisin del problema por divisin


c1

si 0 n < b

a T(n/b) + c nk

si n b

O(nk)

si a < bk

O(nk log n)

si a = bk

T(n) =

T(n)

O( n logb a )
si a > bk
Si a < bk o a = bk la complejidad depende de nk que es un trmino que proviene de C(n), por
lo tanto la complejidad de un algoritmo de este tipo se puede mejorar disminuyendo la complejidad de la preparacin de las llamadas y la combinacin de los resultados: C(n) = c nk.
Si a > bk no se consigue mejorar la eficiencia global mejorando la eficiencia de C(n). Las mejoras se obtendrn disminuyendo a el nmero de llamadas recursivas o aumentando b
obtenindose as subproblemas de menor tamao.
1.4.3

Anlisis de algunos algoritmos recursivos

Ordenacin por mezcla


La recurrencia es de la forma
c1
T(n) =

si 1 n 0

Algoritmos recursivos

210

2 T(n/2) + c n

si n 1

donde hemos considerado que el tamao de los datos n es la longitud del subvector a ordenar;
en el caso directo tenemos 2 pasos para evaluar las dos barreras. En cada llamada recursiva el
problema se divide aproximadamente por 2. Ntese que la ecuacin de recurrencias no se ajusta
exactamente al esquema terico (b=2 pero la expresin recurrente se aplica para n 1 y no para
n2); como las diferencias afectan a valores pequeos de n no afectan a su estudio asinttico.
En realidad no tenemos que preocuparnos por el coste en el caso directo, siempre y cuando
sea un coste constante. Tampoco nos preocupamos por las constantes aditivas que aparezcan en
el caso recursivo.
El coste de la combinacin (c nk) sale lineal porque ese es el coste del procedimientos mezcla.
Con todo esto tenemos
a = 2, b = 2, k = 1
estamos por tanto en el caso
a = bk
y entonces la complejidad es:

O(n log n)

Nmeros de Fibonacci

T(0) = c0
T(1) = c1
T(n) = T(n1) + T(n2) + c
si n 2
Esta recurrencia no se ajusta a los esquemas que hemos estudiado, y por ello hacemos una
simplificacin:
T(n) 2 T(n1) + c
Que se corresponde al esquema de divisin del problema por sustraccin:
a = 2, b = 1, k = 0
Como se tiene a > 1 la complejidad es

O(an div b) = O(2n)


complejidad exponencial.

Algoritmos recursivos

211

Fibonacci con recursin final


Es la funcin dosFib del ejercicio 99
T(0) = c0
Tn) = T(n1) + c

n>0

Divisin del problema por sustraccin


a = 1, b = 1, k = 0
Como se tiene a = 1

O(nk+1) = O(n)
Esto implica una enorme mejora con respecto a la versin anterior del algoritmo.
Suma recursiva de un vector de enteros
funcsumaVec(v:Vector[1..N]deEnt;a,b:Ent)devs:Ent;

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=ba +1

la longitud del subvector a sumar

Recurrencia:
T(1) = c1
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(nk+1) = O(n)
Bsqueda dicotmica
Tomamos como tamao de los datos:

Algoritmos recursivos

212

n=ba+1
Recurrencias
T(0) = c0
T(1) = c1
T(n) = T(n/2) + c

si n > 1

Suponiendo n = 2m

Divisin del problema por divisin:


a = 1, b = 2, k = 0
Como a = bk 1 = 20

O(nk log n) = O(n0 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=ba+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) = c0
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(nk+1) = O(n2)

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) = c0
T(n) = 2T(n/2) + c n

si n 1

con
a=b=2

k=1

disminucin del problema por divisin con a = bk

O(nk 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 programas 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(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;

&

{P( x )}
var

&

&

 x : W ;
inicio
si

&

&

&

d( x )o{P( x )d( x )}

&
& &
{Q( x , y )}
&
&
&
d( x )o{P( x )d( x )}
&
&
 x :=s( x );
&
{P( x )}
&

 y :=g( x )

Algoritmos recursivos

214
&

&

 y :=nombreFunc( x )

&
&
& &
{Q( x , y )}

{Q( x , y )}
fsi

& &
&
dev y 

{Q( x , y )}

ffunc

Intuitivamente, podemos imaginar la ejecucin de una llamada de la forma


&
&
y := nombreFunc( x )

como un bucle descendente

&
&
x o nombreFunc( x )
p
&
nombreFunc(s( x ))
p
&
nombreFunc(s2( x ))
p

p
&
&
&
nombreFunc(sn( x )) o g(sn( x )) o y
&
De hecho, habra otro bucle ascendente que ira devolviendo el valor de y ; sin embargo,
&
como en ese proceso no se modifica y recursin final podemos ignorarlo.

Formalmente, el bucle descendente da lugar a una definicin iterativa equivalente de nombreFunc de la siguiente forma:

funcnombreFuncItr(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;

&

{P( x )}
var

&

&

 x : W ;
inicio

&

&

 x := x ;

&
&
&
&
C:t( x )}
&
itd( x )

{I:P( x )nombreFunc( x )=nombreFunc( x );

Algoritmos recursivos

215

&

&

o x :=s( x )
fit;

&

{Id( 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 iteracin es como si estuvisemos delante de una llamada recursiva, donde se cumple la pre&
&
condicin sustituyendo x por s( x ). Adems por ser recursiva final, se tiene la segunda
parte del invariante: la funcin aplicada sobre cualquiera de los valores intermedios coincide con el resultado para el valor original.

La expresin de acotacin viene dada por la funcin de acotacin del algoritmo recursivo.

La correccin del algoritmo recursivo garantiza la del algoritmo iterativo obtenido de esta manera.
1.5.1

Ejemplos

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;
{P0:a=An=N}
var
a,n:Nat;
inicio
si
n=0om:=a
n>0o<a,n>:=<a*n,n1>;
m:=acuFact(a,n)
fsi
{Q0:a=An=Nm=a*n!}
devm
ffunc

Algoritmos recursivos

216

En esta versin hemos separado el clculo de la descomposicin recursiva de los datos para
que as se ajuste exactamente al esquema terico presentado.
Y la versin iterativa:

funcacuFact(a,n:Nat)devm:Nat;
{P0:a=An=N}
var
a,n:Nat;
inicio
<a,n>:=<a,n>;
itn>0
o<a,n>:=<a*n,n1>;
fit;
m:=a;
{Q0:a=An=Nm=a*n!}
devm
ffunc

En realidad no haran falta las variables locales, pero si no las utilizamos no se cumplir la parte de la especificacin que se refiere a la conservacin de los valores de los parmetros de entrada.
Considerando que
fact(n) = acuFact(1, n)
podemos convertir la versin iterativa en una funcin que calcule el factorial, eliminando el
parmetro a e inicializando a con el valor 1.
Bsqueda binaria
Partimos de la solucin que obtuvimos en el tema de derivacin de algoritmos recursivos:

funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P0:1abNord(v,a,b)}
var
m:Ent;
inicio
si
a=b+1op:=a1
a=bosi
v(a)xop:=a
v(a)>xop:=a1
fsi

Algoritmos recursivos

217

a<bom:=(a+b)div2;
si
v(m)xop:=buscaBin(v,x,m+1,b)
v(m)>xop:=buscaBin(v,x,a,m1)
fsi
fsi
{Q0:a1pbv[a..p]x<v[(p+1)..b]}
devp
ffunc

Esta funcin no se ajusta exactamente al esquema terico porque no aparecen las variables
auxiliares que recogen la descomposicin recursiva; las introduciremos en la versin iterativa.
Adems tenemos ms de un caso directo e implcitamente ms de un caso recursivo, lo que se
traduce a composiciones alternativas en la versin iterativa.
La solucin iterativa

funcbuscaBin(v:Vector[1..N]deElem;x:Elem;a,b:Ent)devp:
Ent;
{P0:1abNord(v,a,b)}
var
m,a,b:Ent;
inicio
<a,b>:=<a,b>;
ita<b
om:=(a+b)div2;
si
v(m)xo<a,b>:=<m+1,b>;
v(m)>xo<a,b>:=<a,m1>;
fsi
fit;
si
a=b+1op:=a1
a=bosi
v(a)xop:=a
v(a)>xop:=a1
fsi
fsi
{Q0:a1pbv[a..p]x<v[(p+1)..b]}
devp
ffunc

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 conversin 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, tcnicas 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 recursivo 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 algoritmos iterativos equivalentes, ms eficientes.
Nos limitaremos al estudio de funciones aunque estas tcnicas se pueden adaptar fcilmente al
diseo de procedimientos.
1.6.1

Generalizaciones

Todas las tcnicas que vamos a estudiar se basan en la idea de generalizacin (en los libros
recomendados a las generalizaciones se las denomina inmersiones).
Decimos que una funcin F es una generalizacin de otra funcin f cuando:
F tiene ms parmetros de entrada y/o devuelve ms resultados que f.

Particularizando los parmetros de entrada adicionales de F a valores adecuados y/o ignorando los resultados adicionales de F se obtiene el comportamiento de f.

En esta primera parte del tema vamos a considerar generalizaciones que slo introducen
parmetros adicionales y no resultados adicionales.
Al estudiar funciones recursivas ya nos hemos encontrado con varios ejemplos de generalizaciones, por ejemplo, acuFact es una generalizacin de fact de forma que

Algoritmos recursivos

219

acuFact(1, n) = fact(n)
En este ejemplo la generalizacin es interesante porque conduce fcilmente a una funcin recursiva final, lo cual no ocurre si partimos de la especificacin de fact.
Otro ejemplo tpico son las funciones recursivas sobre vectores, donde hemos obtenido generalizaciones aadiendo parmetros que indiquen el subvector a considerar en cada llamada recursiva. Por ejemplo, la funcin de bsqueda binaria implementada recursivamente es una
generalizacin de la misma funcin implementada de forma iterativa:

buscaBinRec(v, x, 1, N) = buscaBin(v, x)
Aunque nosotros a la hora de implementarlas le hemos dado el mismo nombre a las dos funciones, en realidad se trata de funciones distintas pues tienen parmetros distintos.
Estos dos ejemplos ponen de manifiesto las dos razones para obtener generalizaciones que,
como ya indicamos antes, son obtener soluciones recursivas ms eficientes o posibilitar los planteamientos recursivos.
Vamos a caracterizar formalmente en trminos de su especificacin qu relacin debe existir
entre una funcin y su generalizacin.
Dadas dos especificaciones

& &
& &
(Ef) func f( x ; W ) dev y : V ;
&
{ P( x ) }
& &
{ Q( x , y ) }
ffunc

& &
(EF) func F( a : W ;
& &
{ P( a , x ) }
& & &
{ Q( a , x , b ,

& & & &


& &
x ; W ) dev b : V ; y : V ;
&
y)}

ffunc
Decimos que EF es una generalizacin de Ef si se cumplen
(G1)

(G2)

&
&
&
& &
P( x ) a =ini( x ) P( a , x )
Si estamos en un estado donde es posible invocar a f entonces tambin es posible
invocar a F, asignando a los parmetros adicionales los valores que indica una
cierta funcin ini.
& & & &
&
&
& &
Q( a , x , b , y ) a = ini( x ) Q( x , y )

Algoritmos recursivos

220

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 W a una lista de tipos.
Veamos como ejemplo que acuFact es una generalizacin de fact segn la definicin que acabamos de dar:

funcfact(n:Ent)devr:Ent;
{P(n):n0}
{Q(n,r):r=n!}
ffunc

funcacuFact(a,n:Ent)devr:Ent;
{P(a,n):n0}
{Q(a,n,r):r=a*n!}
ffunc


Efectivamente acuFact es una generalizacin de fact con


ini(n)=1

Con lo que se tiene


n0a=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 Ef e intentaremos
obtener la especificacin EF de una generalizacin adecuada. Este proceso es esencialmente creativo; vamos a estudiar a continuacin la aplicacin de esta tcnica a algunos casos particulares y
presentaremos as heursticas que pueden ayudar a descubrir las generalizaciones interesantes.
1.6.2

Generalizacin para la obtencin de planteamientos recursivos

Vamos a estudiar dos tcnicas, ejemplificndolas, una ms simple que permite obtener planteamientos recursivos no finales y otra que persigue la obtencin de planteamientos recursivos
que admiten recursin final.

Algoritmos recursivos

221

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 vectores:

funcprodEsc(u,v:Vector[1..N]deEnt)devp:Ent;
{P(u,v):cierto}
{Q(u,v,p):p=i:1iN:u(i)*v(i)}
ffunc


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=0op:=0
a>0op:=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 Ef, encontrar una especificacin EF de una funcin ms general que admita una solucin recursiva final. En una funcin recursiva final el resultado 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 llegar 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 precondicin para exigir que alguno de los parmetros de entrada ya traiga calculado una parte del resultado.
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 generalizada permiten obtener la postcondicin de la funcin f, sustituyendo los parmetros 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 funciones iterativas; es por ello que las condiciones que aparecen en la caracterizacin de una generalizacin recursiva final se corresponden de manera directa con las de los bucles, como ya
pudimos darnos cuenta cuando presentamos el esquema de traduccin de una funcin recursiva
final a otra iterativa:

& & &


P( a , e , x )
& & &
d( a , e , x )
& &
Q( x , y )

invariante
condicin de repeticin
postcondicin

donde tenemos tambin que el requisito FG2 es equivalente a una de las condiciones que han
de verificar los bucles:
I B Q

P d Q

Para obtener este tipo de generalizaciones utilizamos las mismas heursticas que hemos estudiado para la obtencin de invariantes: tomar una parte de la postcondicin o generalizar la postcondicin sustituyendo constantes por variables. La condicin que obtengamos as deberemos
incluirla en la precondicin de la generalizacin.
Como ejemplo vamos a obtener otra generalizacin de la funcin que calcula el producto escalar. Intuitivamente, vemos que tenemos que utilizar la idea de la generalizacin anterior para
poder llegar a un planteamiento recursivo y adems tenemos que introducir otro parmetro que

Algoritmos recursivos

224

lleve el resultado hasta ese momento. Con esta idea planteamos la siguiente precondicin para la
generalizacin (este paso es el equivalente a obtener el invariante a partir de la postcondicin, con
la diferencia de que al obtener el invariante no necesitamos una variable nueva para acumular el
resultado y aqu s):
P(a,e,u,v):a=i:1ie:u(i)*v(i)0eN

El siguiente paso es encontrar la condicin del caso directo de forma que se garantice la condicin (FG2), es decir, para conseguir que en a est el resultado final deseado. Es equivalente a
obtener la condicin de terminacin:
d(a,e,u,v)e=N


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 sumatorio 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=Nop:=a
e<Nop:=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 generalizacin.
Tenemos entonces que

Algoritmos recursivos

225

prodEsc(u, v) = prodEscGenFin( 0, 0, u, v )
De la misma se puede seguir un proceso similar para obtener una generalizacin sustituyendo
por una nueva variable la constante 1 en lugar de N.
El uso de acumuladores es una tcnica que tiene especial relevancia en programacin funcional donde, en principio, slo se dispone de recursin. Esta es la forma de conseguir funciones
recursivas eficientes, ocurriendo incluso que el compilador sea capaz de hacer la traduccin automtica de recursiva final a iterativa. En programacin imperativa tiene menos sentido pues en
muchos casos resulta ms natural obtener directamente la solucin iterativa.
1.6.3

Generalizacin por razones de eficiencia

En ocasiones se puede mejorar la eficiencia de un algoritmo recursivo evitando el clculo repetido de una cierta expresin en todas las llamadas recursivas, o simplificando algn clculo sacando provecho del resultado obtenido para ese clculo en otra llamada recursiva. En estos casos
el uso de generalizaciones puede mejorar la eficiencia, introduciendo parmetros adicionales que
transmitan los resultados a llamadas recursivas posteriores, o con resultados adicionales que se
utilicen en llamadas recursivas anteriores. En esencia, es la misma idea que se utiliza en la introduccin de invariantes auxiliares cuando se disean algoritmos iterativos.
Vamos a distinguir dos casos, segn que la generalizacin consista en aadir parmetros o en
aadir resultados. Tambin hay ocasiones en que interesa utilizar simultneamente los dos tipos
de generalizaciones. En ambos casos vamos a suponer que partimos de funciones ya diseadas y
describiremos las soluciones generalizadas en trminos de los cambios que sera necesario introducir en las funciones originales.
Generalizacin con parmetros acumuladores
Cuando se aplica esta tcnica, la funcin ms general F posee parmetros de entrada adiciona&
&
les a , cuya funcin es transmitir el valor de una cierta expresin e( x ) que depende de los parmetros de entrada de la funcin original f, a fin de evitar su clculo. Por lo tanto la precondicin
de F deber plantearse como un fortalecimiento de la precondicin de f, y la postcondicin podr
mantenerse tal cual:

& &
&
&
&
P( a , x ) P( x ) a =e( x )
& & &
& &
Q( a , x , y ) Q( x , y )

Suponiendo disponible un algoritmo recursivo para f (que queremos optimizar), podremos disear un algoritmo ms eficiente para F del siguiente modo:

&
&
Se reutiliza el texto de f, reemplazando e( x ) por a
& &
&
Diseamos una nueva funcin sucesor s( a , x ), a partir de la orginal s( x ), de modo que
se cumpla:
&
&
&
{ a =e( x )d( x )}
&
&
& &
< a , x >:=s( a , x )
&
&
&
&
{ x =s( x ) a =e( x )}

Algoritmos recursivos

226

&
La tcnica resultar rentable cuando en el clculo de a nos podamos aprovechar de los valo& &
res de a y x para realizar un clculo ms eficiente.
Por ejemplo una funcin que calcula cuntas componentes de un vector son iguales a la suma
de las componentes que las preceden:

funcnumCortesGen(e:Ent;v:Vector[1..N]deEnt)devr:Ent;
{P(e,v):0eN}
var
s,h:Ent;
inicio
si
e=Nor:=0
e<Nos:=0;
parahdesde1hastaehacer
s:=s+v(h);
fpara;
sis=v(e+1)
entoncesr:=1+numCortesGen(e+1,v)
sinor:=numCortesGen(e+1,v)
fsi
fsi
{Q(e,v,r):r=#i:e+1iN:v(i)=j:1je:v(j)}
devr
ffunc

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 hasta ese momento se vaya transmitiendo a las siguientes llamadas:

funcnumCortesGenEfi(e,s:Ent;v:Vector[1..N]deEnt)devr:Ent;
{P(s,e,v):0eNs=i:1ie:v(i)}
inicio
si
e=Nor:=0

Algoritmos recursivos

227

e<Nosis=v(e+1)
entoncesr:=1+numCortesGenEfi(e+1,s+v(e+1),v)
sinor:=numCortesGenEfi(e+1,s+v(e+1),v)
fsi
fsi
{Q(e,v,r):r=#i:e+1iN:v(i)=j:1je:v(j)}
devr
ffunc

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 adicionales. Recordamos que la generalizacin deba cumplir:

(G1)

(G2)

&
&
&
& &
P( x ) a =ini( x ) P( a , x )
Si estamos en un estado donde es posible invocar a f entonces tambin es posible
invocar a F, asignando a los parmetros adicionales los valores que indica una
cierta funcin ini.
& & & &
&
&
& &
Q( a , x , b , y ) a = ini( x ) Q( x , y )
De la postcondicin de F, restringiendo los parmetros adicionales a los valores
&
dados por ini( x ), es posible obtener la postcondicin de f.

&
&
Donde ini es una funcin que asocia a cada valor de los parmetros x el valor ini( x ) de los
& &
&
parmetros adicionales, adecuado para que F(ini( x ), x ) emule el comportamiento de f( x )
&
En una generalizacin F con resultados acumuladores b , estos parmetros de salida adiciona&
&
les tendrn como misin transmitir ciertos valores e( y ) dependientes de los resultados y de f.

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, incluyendo 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=0os:=0
n>0os:=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 n2, conociendo del valor de (n


&
1)2, ya que x = n1:
n2=((n1)+1)2=(n1)2+2*(n1)+1

La idea es aadir resultados adicionales que devuelvan


n2 2*n+1

La generalizacin queda por tanto:


funcsumaCuadGen(n:Nat)devc,p,s:Nat
{P(n):cierto}
inicio
si
n=0o<c,p,s>:=<0,1,0>
n>0o<c,p,s>:=sumaCuadGen(n1);
<c,p,s>:=<c+p,p+2,c+p+s>
fsi
{Q(n,c,p,c):s=i:0in:i*ic=n*np=2*n+1}
dev<c,p,s>
ffunc

sumaCuadGen generaliza a sumaCuad si descartamos los dos resultados adicionales:


sumaCuad(n)=pr3(sumaCuadGen(n))

donde pr3 es la funcin que proyecta una terna ordenada en su tercera componente.

Vamos ahora con el ejemplo de los nmeros de Fibonacci, la optimizacin consiste en devolver dos nmeros en lugar de uno:

funcfibo(n:Nat)devr:Nat;
{P(n):cierto}
inicio
si
n=0or:=0
n=1or:=1
n>1or:=fibo(n1)+fibo(n2)
fsi
{Q(n,r):r=fib(n)}
devr
ffunc

Algoritmos recursivos

230

y la generalizacin
funcfiboGen(n:Nat)devr,s:Nat;
{P(n):cierto}
var
r,s:Nat;
inicio
si
n=0o<r,s>:=<0,1>
n>0o<r,s>:=fiboGen(n1)
<r,s>:=<s,r+s>
fsi
{Q(n,r,s):r=fib(n)s=fib(n+1)}
dev<r,s>
ffunc

fibo(n)=pr1(fiboGen(n))

1.6.4

Tcnicas de plegado-desplegado

En un apartado anterior hemos visto cmo generalizar una especificacin Ef de manera que la
especificacin ms general EF haga posible un diseo recursivo final. Una idea alternativa es la de
transformacin de programas. Si suponemos ya diseado un algoritmo recursivo simple que
satisfaga la especificacin Ef, puede interesar transformarlo de manera que se obtenga un algoritmo recursivo final para una funcin ms general con especificacin EF. La versin recursiva final
ser, como ya sabemos, ms eficiente por poderse traducir a un bucle iterativo.
Las tcnicas de plegadodesplegado sirven para obtener el algoritmo recursivo final para F
mediante manipulaciones algebraicas del algoritmo recursivo simple conocido para f.
Lo interesante de esta tcnica es que es posible automatizar la transformacin pues los pasos
estn bien definidos a partir de la forma de la funcin recursiva simple original. El inconveniente
es que no es aplicable a cualquier funcin recursiva simple, sino que estas se deben ajustar a unos
determinados esquemas donde las funciones que intervienen cumplan unas ciertas propiedades
asociatividad, elemento neutro, .
Vamos a estudiar tres esquemas distintos junto con el procedimiento para construir la funcin
recursiva final a partir de una funcin recursiva simple que se ajuste al correspondiente esquema y
cumpla las propiedades indicadas.
Plegado y desplegado I
Supongamos dada una funcin recursiva simple f cuya implementacin se ajuste al siguiente
esquema:

&

&

&

&

funcf( x : W )dev y : V ;

&

{P( x )}

Algoritmos recursivos

inicio
si

231

&

&

&

&

&

&

d( x )o y :=g( x )

&

d( x )o y :=h( x )f(s( x ))


fsi

&

&

{Q( x , y )}

&

dev y 
ffunc

siendo una funcin


&
&
&

: V x V o V 

que cumple las propiedades


&
&
& &
&
&
(N) u : V : 0  u = u  ( 0 constante)
&
& & &
&
&
&
&
&
&
(A) u , v , w : V : u ( v  w )=( u  v ) w 
entonces la funcin F, especificada como sigue, es una generalizacin de f que admite un algoritmo recursivo final:
& & & &
& &
funcF( a : V ; x : W )dev y : V ;
&
{P( x )}
&
&
&
{ y = a f( x )}
ffunc

Primero vamos a ver que efectivamente F es una generalizacin de f:


&

&

&

 x : W :P( x )




 
(N) =
(EF) =

&

f(
& x ) &
0 f(
& & x )
F( 0 , x )

Para demostrar que F admite un algoritmo recursivo final daremos el procedimiento que lo
construye, a partir del algoritmo para f y la especificacin de F. El algoritmo sintetizado ser correcto por construccin.
Para la sntesis del algoritmo utilizamos el anlisis de casos del algoritmo de f. En lo que sigue
&
&
presuponemos que los valores de x cumplen P( x ):

&
Caso d( x )

 
EF,desplegadodeF
Df,desplegadodef

& &

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

Algoritmos recursivos

232

&
Caso d( x )

 
EF,desplegadodeF
Rf,desplegadodef

(A) =
EF,plegadodeF =


& &

F( a , x )
&
&
=
a f( x )
&
&
&
=
a (h( x )f(s( x )))
&
&
&
( a h( x ))f(s( x ))
&
&
&
F( a h( x ),s( x ))

Por lo tanto obtenemos el siguiente algoritmo para F


&

&

&

&

&

&

funcF( a : V ; x : W )dev y : V ;

&

{P( x )}
inicio
si

&
&
&
&
&
&
&
&
&
d( x )o y :=F( a h( x ),s( x ))
d( x )o y := a g( x )

fsi

&

&

&

{ y = a f( x )}

&

dev y 
ffunc

Ejemplos de funciones recursivas que se ajustan al esquema PDP I y que admiten esta transformacin:

La funcin acuFact del ejercicio 82, y que apareci al principio de este tema como ejemplo de
generalizacin, se obtiene aplicando PDP I a la funcin fact

h(n)=n =*  

&
0 =1

La funcin prodEscGen que vimos al principio de este tema pg. 5 como ejemplo de generalizacin que hace posible la recursin con planteamiento recursivo no final se ajusta al esquema
PDP I con los siguiente valores para los parmetros del mtodo:
h(u,v,n)=u(n)*v(n)   =+   

&
0 =0

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=0op:=b
a>0op:=prodEscF(b+u(a)*v(a),a1,u,v)
fsi
{Q(b,a,u,v,p):p=b+i:1ia:u(i)*v(i)}
devp
ffunc

En el ejemplo que sigue la aplicacin de la tcnica se complica un poco porque tenemos dos
&
casos recursivos y la funcin h( x ) se define por distincin de casos. El algoritmo es el mtodo de
multiplicacin del campesino egipcio que presentamos en el tema de verificacin de algoritmos
recursivos, aunque aqu se ha eliminado uno de los casos bases por ser redundante.
funcprod(x,y:Nat)devr:Nat;
{P0:cierto}
inicio
six=0     or:=0
(x>0ANDpar(x))  or:=prod(xdiv2,y+y)
(x>0ANDNOTpar(x))or:=y+prod(xdiv2,y+y)
fsi
{Q0:r=x*y}
devr
ffunc

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;
{P0:cierto}
inicio
six=0     or:=a
(x>0ANDpar(x))  or:=prodF(a,xdiv2,y+y)
(x>0ANDNOTpar(x))or:=prodF(a+y,xdiv2,y+y)
fsi
{Q0: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 : W )dev y : V ;

&

{P( x )}
inicio
si

&

&

&

&

&

&

d( x )o y :=g( x )

&

&

d( x )o y :=h( x )(k( x )f(s( x )))


fsi

&

&

{Q( x , y )}

&

dev y 
ffunc

siendo , funciones
&
&
&
,: V x V o V 


que cumple las propiedades

&

&

&

&

&

&

(N)  u : V : 0  u = u  ( 0 constante)

&
&
& &
&
&
& & & &
&
&
&
&
&
&
(A)  u , v , w : V : u ( v  w )=( u  v ) w 
&
& & &
&
&
&
&
&
&
(A)  u , v , w : V : u ( v  w )=( u  v ) w 
& & & &
&
&
&
&
&
&
&
(D) u , v , w : V : u ( v  w )=( u  v )( u  w )
(N)  u : V : 1  u = u  ( 1 constante)




entonces la funcin F, especificada como sigue, es una generalizacin de f que admite un algoritmo recursivo final:
& & & & & &
& &
funcF( a : V ; b : V ; x : W )dev y : V ;
&
{P( x )}
&
&
&
&
{ y = a ( b f( x ))}
ffunc

Primero vamos a ver que efectivamente F es una generalizacin de f:


&

&

&

 x : W :P( x )



 

&

f( x )

Algoritmos recursivos

235
&

&

&

(N,N) = 0 (
x ))
& & 1 f(
&
(EF) = F( 0 , 1 , x )




Para demostrar que F admite un algoritmo recursivo final daremos el procedimiento que lo
construye, a partir del algoritmo para f y la especificacin de F. El algoritmo sintetizado ser correcto por construccin.
Para la sntesis del algoritmo utilizamos el anlisis de casos del algoritmo de f. En lo que sigue
&
&
presuponemos que los valores de x cumplen P( x ):
&
Caso d( x )

&

&

&


 
EF,desplegadodeF
Df,desplegadodef


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


 
EF,desplegadodeF
Rf,desplegadodef

(D) =

(A,A) =
EF,plegadodeF =

F( a , b , x ) &
&
&
=
a ( b& f( x ))
&
&
&
&
=
a &[ b [h( x )[k(
x )f(s( x ))]]]
&
&
&
&
&
a [[ b& h( x )][ b [k(
x )f(s( x ))]]]
&
&
&
&
&
[ a [ b h(
x )]][[
x )]f(s( x ))]
&
& b k(
&
&
&
&
F( a ( b h( x )), b k( x ),s( x ))

&
Caso d( x )

& &

&

Por lo tanto obtenemos el siguiente algoritmo para F


&

&

&

&

&

&

&

&

funcF( a : V ; b : W ; x : W )dev y : V ;

&

{P( x )}
inicio
si

&

&

&

&

&

&

&

d( x )o y := a ( b g( x ))

&

&

&

&

&

&

d( x )o y :=F( a ( b h( x )), b k( x ),s( x ))


fsi

&
&
dev y 
&

&

&

{ y = a ( b f( x ))}

ffunc

Como ejemplo vamos a ver una funcin que dada una base b y un natural n obtiene la representacin de n en base b en el ejercicio 92 se obtena esta funcin para el caso particular b=2.

funccambioBase(b,n:Nat)devr:Nat;
{P0:2b9}

Algoritmos recursivos

236

inicio
si
n<bor:=n
nbor:=nmodb+10*cambioBase(b,ndivb)
fsi
{Q0:r=i:Nat:((ndivbi)modb)*10i}
devr
ffunc

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;
{P0:2b9}
inicio
si
n<bor:=s+p*n
nbor:=cambioBaseF(s+p*nmodb,p*10,b,ndivb)
fsi
{Q0:r=s+p*i:Nat:((ndivbi)modb)*10i}
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 documentar la solucin eficiente se puede utilizar la solucin intuitiva. Existen entornos en el mbito
de la investigacin que realizan estas transformaciones automticamente, el usuario escribe el
diseo intuitivo y el sistema se encarga de optimizarlo.
Plegado y desplegado III

Supongamos dada una funcin recursiva simple que se ajuste al siguiente esquema:
&

&

&

&

&

&

funcf( x : W ; a : W )dev y : V ;

&

{P( x )}
inicio

Algoritmos recursivos

237

si

&
&
&
&
&
& &
d( x )o y :=h(f(s( x ), a ))

d( x )o y :=g( 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:
&
&
&
& &
n
 x :P( x ):f( x , a )=h ( x )(g(a))


Afirmamos entonces que la funcin F especificada como sigue es una generalizacin de f que
admite un algoritmo recursivo final:


&

&

&

&

&

funcF( x : W ; b :V)dev y : V ;

&

{P( x )}

&

{ y =h

&

n( x

&

( b )}

ffunc

siendo

&

&

n( x )=defminn:Nat:d(s ( x ))


es decir, n representa el nmero de llamadas recursivas necesarias para alcanzar un caso directo
&
a partir de x .
Vemos que efectivamente el valor de f se puede obtener a partir de F pues se cumple
&

&

&

&

&

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 : W por
& &
&
b : V . 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 )

 

(Df) =
&
&
(d( x )n( x )=0)=

(EF) =


&

&

&

&

f( x , a )
&
g( a&)
&
n
h ( x )(g( a ))
&
&
F( x ,g( a ))

&
Caso d( x )




(Rf)

H.I.

(EF)
&
&
(1+n(s( x ))=n( x ))

(EF)


=
=
=
=
=

f( x , a )
&
&
h(f(s( x ), a ))
&
&
h(F(s( x&),g( a )))
&
n(s( x ))
h(h &
(g( a )))
&
n( x )
h
(g( a ))
&
&
F( x ,g( a ))

Una vez demostrado que f se puede obtener a partir de F, veamos cul es el procedimiento para construir el algoritmo de F a partir del algoritmo de f.

&
Caso d( x )

 
EF,desplegadodeF
&
&
(d( x )n( x )=0)=


&

&

&

&

F( x , b )&
n( x ) &
=
h
( b )
&
b

&
Caso d( x )


 
E ,desplegadodeF =
& F
&
&
d( x )n( x )=1+n(s( x ))
 EF,plegadodeF =

F( x&, b&)
n( x )
h
( b ) &
&
n(s( x ))
=
h
(h( b ))
&
&
F(s( x ),h( b ))

Algoritmos recursivos

239

Con lo que obtenemos para F el siguiente algoritmo:


&

&

&

&

&

funcF( x : W ; b :V)dev y : V ;

&

{P( x )}
inicio
si

&

&

&

&

&

d( x )o y := b 

&

&

d( x )o 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 razones de eficiencia:
funcfiboGen(n:Nat)devr,s:Nat;
{P(n):cierto}
var
r,s:Nat;
inicio
si
n=0o<r,s>:=<0,1>
n>0o<r,s>:=fiboGen(n1)
<r,s>:=<s,r+s>
fsi
{Q(n,r,s):r=fib(n)s=fib(n+1)}
dev<r,s>
ffunc

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=0o<r,s>:=<r,s>
n>0o<r,s>:=fiboGenF(n1,s,r+s)
fsi
n

{<r,s>=h (r,s)}
dev<r,s>
ffunc

Como postcondicin no podemos poner nada sobre los nmeros de Fibonacci porque todas
las llamadas devuelven el mismo valor por algo es recursiva final : los nmeros de Fibonacci n
y n+1 siendo n el valor con el que se hizo la primera invocacin. Los que s son nmeros de Fibonacci son <r, s>.
Para obtener el n-simo nmero de Fibonacci hacemos la siguiente invocacin:

fiboGen(n)=fiboGenF(n,0,1)=<fib(n),fib(n+1)>

Aplicando a este algoritmo la transformacin de recursivo a iterativo obtendramos aproximadamente el algoritmo que derivamos en el tema de algoritmos iterativos como ejemplo de introduccin de un invariante auxiliar:
varn,x:Ent;
{n=Nn0}
var
i,y:Ent;
inicio
<i,x,y>:=<0,0,1>;
{I:n=Nx=fib(i)0iny=fib(i+1)
C:ni}
itizno
{Iizn}
<x,y>:=<y,x+y>;
{I[i/i+1]}
i:=i+1
{I}
fit
{Ii=n}
fvar
{n=Nx=fib(n)}

Algoritmos recursivos

241

La transformacin PDP III tambin se puede aplicar a la funcin sumaCuadGen que obtuvimos
en este mismo tema en el apartado dedicado a la generalizacin con parmetros acumuladores:
funcsumaCuadGen(n:Nat)devc,p,s:Nat
{P(n):cierto}
inicio
si
n=0o<c,p,s>:=<0,1,0>
n>0o<c,p,s>:=sumaCuadGen(n1);
<c,p,s>:=<c+p,p+2,c+p+s>
fsi
{Q(n,c,p,c):s=i:0in:i*ic=n*np=2*n+1}
dev<c,p,s>
ffunc

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=0o<c,p,s>:=<c,p,s>
n>0o<c,p,s>:=sumaCuadGenF(n1,c+p,p+2,c+p+s)
fsi
n

{<c,p,s>=h (c,p,s)}
dev<c,p,s>
ffunc

Con la siguiente correspondencia entre f y F:


sumaCuadGen(n)=sumaCuadGenF(n,0,1,0)=<n2,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 0inr=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(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
{P0:Px1=X1xn=Xn}
cte;
var;
inicio

&
&
&
&
&
&
&
d( x )o y :=c( x ,nombreFunc(s( x )))
sid( x )o y :=g( x )

fsi;
{Q0:Qx1=X1xn=Xn}
dev<y1,,ym>
ffunc

81. Construye una funcin recursiva simple cuadrado que calcule el cuadrado de un nmero

natural n, basndote en el siguiente anlisis de casos:


Caso directo: Si n=0, entonces n2=0
Caso recursivo: Si n>0, entonces n2=(n1)2+2*(n1)+1

82. Una funcin se llama recursiva final si su definicin se ajusta al siguiente esquema:
funcnombreFunc(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
{P0:Px1=X1xn=Xn}
cte;
var;
inicio

&
&
&
&
&
&
d( x )o y :=nombreFunc(s( x ))
sid( x )o y :=g( x )

fsi;
{Q0:Qx1=X1xn=Xn}
dev<y1,,ym>
ffunc

Es decir, una funcin recursiva final es una funcin recursiva lineal especialmente sencilla, tal
que la llamada recursiva devuelve directamente el resultado deseado.
Construye una funcin recursiva final que satisfaga la siguiente especificacin pre/post:
funcacuFact(a,n:Nat)devm:Nat;

Algoritmos recursivos

243

{P0:a=An=N}
{Q0:a=An=Nm=a*n!}
ffunc

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(x1:W1;;xn:Wn)devy1:G1;;ym:Gm;
{P0:Px1=X1xn=Xn}
cte;
var;
inicio
si

&

&

&

&

&

&

d( x )o y :=g( x )

&

&

d( x )o y :=c( x ,nombreFunc(s1( x )),,nombreFunc(sk( x )))


fsi;
{Q0:Qx1=X1xn=Xn}
dev<y1,,ym>
ffunc

Construye una funcin recursiva mltiple fib con parmetro n, que devuelva el n-simo nmero de Fibonacci. Sigue el esquema de la definicin recursiva de la sucesin de Fibonacci, segn se
conoce en matemticas, distinguiendo los casos 0 n 1 (caso directo) y n 2 (caso recursivo).
84. Dibuja un diagrama en forma de rbol, representando todas las llamadas a la funcin fib

que se originan a partir de la llamada incial fib(4). Cuenta el nmero total de llamadas y observa las llamadas repetidas.

85. Una funcin recursiva puede tener tambin varios casos directos y/o varios casos recursi-

vos, en lugar de uno solo. Aunque haya varios casos recursivos, la recursin se considera lineal si stos estn separados, de tal manera que en cada llamada a la funcin se pase o bien
por uno solo de los casos directos o bien por uno solo de los casos recursivos. Ejemplo:
(a) Funcin recursiva lineal que calcula potencias, con un caso recursivo:
funcpot(x:Ent;y:Nat)devp:Ent;
{P0:x=Xy=Y}
var
y:Nat;p:Ent;
inicio
si
y=0op:=1
y>0oy:=ydiv2;
p:=pot(x,y);
si
par(y)op:=p*p
NOTpar(y)op:=x*p*p
fsi
fsi
{Q0:x=Xy=Yp=xY}

Algoritmos recursivos

244

devp
ffunc

(b) Una funcin recursiva lineal que calcula potencias, con dos casos recursivos:
funcpot(x:Ent;y:Nat)devp:Ent;
{P0:x=Xy=Y}
var
x,y:Nat;p:Ent;
inicio
si
y=0op:=1

y>0ANDpar(y)o<x,y>:=<x*x,ydiv2);
p:=pot(x,y)
y>0ANDNOTpar(y)oy:=y1;
p:=pot(x,y)
p:=x*p
fsi
{Q0:x=Xy=Yp=xY}
devp
ffunc


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 siguiente anlisis de casos:
Caso directo: x=0
Casos recursivos: xz0ANDpar(x)
xz0ANDNOTpar(x)

91. Deriva una funcin recursiva log que calcule la parte entera de lobbn, siendo los datos b,
n:Nat tales que b2n1. El algoritmo obtenido deber usar solamente las ope-

raciones + y div.

92. Deriva una funcin recursiva bin tal que, dado un nmero natural n, bin(n) sea otro nmero

natural cuya representacin decimal tenga los mismos dgitos que la representacin binaria
de n. Es decir, debe tenerse: bin(0) = 0; bin(1) = 1; bin(2) = 10; bin(3) = 11; bin(4) = 100; etc.

Algoritmos recursivos

245

93. Verifica la siguiente funcin:


funccuca(n:Ent)devr:Ent;
{P0:n=Nn0}
inicio
si
n=0or:=0
n=1or:=1
n2or:=5*f(n1)6*f(n2)
fsi
{Q0:n=Nr=3n2n}
ffunc

94. Deriva una funcin recursiva doble que satisfaga la siguiente especificacin:
funcsumaVec(v:Vector[1..N]deEnt;a,b:Ent)devs:Ent;
{P0:N1v=Va=Ab=B1ab+1N+1}
{Q0:v=Va=Ab=Bs=i:aib:v(i)}
ffunc

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;
{P0:1ab+1N+1ord(v,a,b)}
{Q0:a1pbv[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);
{P0:v=Va=Ab=B1ab+1N+1}
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)
(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc

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 continuacin, 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);
{P0:v=Va=Ab=B1ab+1N+1}
{Q0:a=Ab=Bperm(v,V,a,b)ord(v,a,b)

(i:1i<a:v(i)=V(i))(j:b<jN:v(j)=V(j))}
fproc

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) div 2 y usamos
llamadas recursivas para ordenar v[a..m] y v[m+1..b]. A continuacin, efectuamos 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;
{P0:n=N}
{Q0:n=Nr=fib(n)s=fib(n+1)}
ffunc

En la postcondicin, fib(n) y fib(n+1) representan los nmeros que ocupan los lugares n y
n+1 en la sucesin de Fibonacci, para la cual suponemos la definicin recursiva habitual en matemticas.
n

100. Deriva una funcin recursiva que calcule el nmero combinatorio a partir de los
m
datos m,n:Nat. Usa la recurrencia siguiente:

n

m

n  1 n  1

m  1 m

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 diferentes tamaos y estn apilados de mayor a menor, con el ms grande en la base. En
ningn momento se permite que un disco repose sobre otro menor que l. Los movimientos permitidos consisten en desplazar el disco situado en la cima de una de las varillas a la
cima de otra, respetando la condicin anterior. Construye un procedimiento recursivo hanoi
tal que la llamada hanoi(n, ini, fin, aux) produzca el efecto de escribir una serie de movimientos que represente una solucin del problema de Hanoi. Supn disponible un procedimiento movimiento(i,j), cuyo efecto es escribir Movimiento de la varilla i a la varilla j.

103. La siguiente funcin recursiva se conoce como funcin de Ackermann:


funcack(m,n:Nat)devr:Nat;
inicio
si
m=0or:=n+1
m>0ANDn=0or:=ack(m1,1)
m>0ANDn>0or:=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 u .
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 desplegado para resolver la recurrencia.
(a) Funcin fact (ejercicio 79).
(b) Funcin acuFact (ejercicio 82).
(c) Funciones pot y pot (ejercicio 85).
(d) Funcin log (ejercicio 91).
(e) Funcin sumaVec (ejercicio 94).
(f) Funcin buscaBin (ejercicio 95).
*(g) Procedimiento de ordenacin mergeSort (ejercicio 97).
*(h) Procedimiento hanoi (ejercicio 102).

105. Aplica las reglas de anlisis para dos tipos comunes de recurrencia a los algoritmos recur-

sivos del ejercicio anterior. En cada caso, debers determinar si el tamao de los datos del
problema decrece por sustraccin o por divisin, as como los parmetros relevantes para
el anlisis.

*106. En cada caso, calcula a partir de las recurrencias el orden de magnitud de T(n). Hazlo

aplicando las reglas de anlisis para dos tipos comunes de recurrencia.


(a) T(1) = c1; T(n) = 4 T(n/2) + n, si n> 1
(b) T(1) = c1; T(n) = 4 T(n/2) + n2, si n> 1

Algoritmos recursivos

248

(c) T(1) = c1; T(n) = 4 T(n/2) + n3, 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 usado por badMergeSort opera en tiempo O(n2). Demuestra que bajo este supuesto el tiempo de
ejecucin de badMergeSort pasa a ser O(n2), frente a O(n log n) de mergeSort.

110. Considera de nuevo la sucesin de Fibonacci, definida en el ejercicio 83. Sean M y M las

dos races de la ecuacin x2x1=0, es decir:


1 5
1 5
M
M
2
2

Demuestra por induccin sobre n que fib( n )

1
M n  M n
5

Pista: Usa que M 2 = M + 1 y M 2 = M + 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 M n2 fib(n) M n1 se cumple para todo n
2. Concluye que T(n) es O(M 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 resultado 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;
{P0:cierto}
{Q0:p=i:1iN:u(i)*v(i)}
ffunc

115. Comprueba que acuFact (ejercicio 82) es una generalizacin de fact (ejercicio 79). Observa

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;
{P0:0mnmN}

n
i

{Q0:i:0im:v(i)= }

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;
{P0:cierto}
{Q0:s=i:0in:i*i}
ffunc

funcsumaCuad(n:Nat)devc,p,s:Nat
{P0:cierto}
{Q0:s=i:0in:i*ic=n*np=2*n+1}
ffunc


Algoritmos recursivos

250

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

recursivo final para procesaVec:


funcprocesaVec(v:Vector[1..N]deEnt)devr:Ent;
{P0:cierto}
i

{Q0:r=i:1iN:2 *v(i)}
ffunc

funcprocesaVec(n,s,p:Ent;v:Vector[1..N]deEnt)devr:Ent;
n

{P0:1nN+1p=2 }
i

{Q0:r=s+i:niN:2 *v(i)}
ffunc


Observa el papel que juega cada uno de los tres parmetros adicionales de procesaVec: n posibilita la recursin; s acumula un resultado parcial; y p mantiene una potencia de 2 para evitar su
clculo explcito en cada llamada recursiva.
Tcnicas de plegado-desplegado
121. Aplica la tcnica de plegado-desplegado para transformar la funcin recursiva lineal fact

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;
{P0:cierto}
inicio
six=0      or:=0
(x>0ANDpar(x))   or:=prod(xdiv2,y+y)
(x>0ANDNOTpar(x))or:=y+prod(xdiv2,y+y)
fsi
{Q0:r=x*y}
devr
ffunc

funcprod(a,x,y:Nat)devr:Nat;
{P0:cierto}
{Q0:r=a+x*y}
ffunc


Algoritmos recursivos

251

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

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 : W )dev y : V ;

&

{P( x )}
inicio
si

&

&

&

&

&

&

d( x )o y :=g( x )

&

d( x )o y :=h( x )f(s( x ))


fsi

&

&

{Q( x , y )}

&

dev y 
ffunc


&

&

&

&

&

&

funcF( a : V ; x : W )dev y : V ;

&

{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 plegadodesplegado.
125. Comprueba que bin es una generalizacin de bin, y deriva un algoritmo recursivo final

para bin usando plegado-desplegado. Observa que bin es la funcin recursiva simple del
ejercicio 92.


funcbin(n:Nat)devr:Nat;
{P0:cierto}
inicio
si
n<2or:=n
n2or:=10*bin(ndiv2)+(nmod2)
fsi
{Q0:r=i:Nat:((ndiv2i)mod2)*10i}
devr
ffunc

Algoritmos recursivos

252


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


126. Considera la funcin cambioBase especificada como sigue:




funccambioBase(b,n:Nat)devr:Nat;
{P0:2b9}
{Q0:r=i:Nat:((ndivbi)modb)*10i}
ffunc


donde la postcondicin quiere expresar que los dgitos de la representacin decimal de r deben
coincidir con los dgitos de n en base b. Por ejemplo, debe cumplirse cambioBase(2,9) = 1001.
(a) Construye un algoritmo recursivo lineal para cambioBase.
(b) Usando una tcnica de plegado-desplegado similar a la del ejercicio 125, construye

una generalizacin recursiva final cambioBase de cambioBase.

127. Las transformaciones de los ejercicios 125 y 126 se ajustan al siguiente esquema:


&

&

&

&

funcf( x : W )dev y : V ;

&

{P( x )}
inicio
si

&

&

&

&

&

&

d( x )o y :=g( x )

&

&

d( x )o y :=h( x )(k( x )f(s( x )))


fsi

&

&

{Q( x , y )}

&

dev y 
ffunc


&

&

&

&

&

&

&

&

funcF( a : V ; b : V ; x : W )dev y : V ;

&

{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 generalizacin de f, y deriva un algoritmo recursivo final para F usando plegado-desplegado.

Algoritmos recursivos

253

128. Comprueba que fiboGen es una generalizacin de fiboGen, y deriva un algoritmo recursivo

final para fiboGen usando plegado-desplegado.

funcfiboGen(n:Nat)devr,s:Nat;
{P0:cierto}
funccombina(u,v:nat)devu,v:Nat;
inicio
<u,v>:=<v,u+v>
dev<u,v>
ffunc
var
r,s:Nat;
inicio
si
n=0o<r,s>:=<0,1>
n>0o<r,s>:=fiboGen(n1)
<r,s>:=combina(r,s);
fsi
{Q0:r=fib(n)s=fib(n+1)}
dev<r,s>
ffunc

funcfiboGen(n,r,s:Nat)devr,s:Nat;
{P0:cierto}
n

{<r,s>=combina (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 usando plegado-desplegado.
& & & &
& &
funcf( x : W ; a : W )dev y : V ;
&
{P( x )}
inicio
si

&

&

&

&

&

d( x )o y :=g( a )

&

&

d( x )o y :=h(f(s( x ), a ))


fsi

&

&

&

{Q( x , a , y )}

&

dev y 
ffunc


Algoritmos recursivos

254
&

&

&

&

&

funcF( x : W ; b :V)dev y : V ;

&

{P( x )}

&

{ y =h
ffunc

&

n( x

&

( b )}   %n(x)indicaelmenorntalqued(s (x))

255

Tipos abstractos de datos

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 aspectos de un determinado nivel, ignorndose los restantes.
Ejemplo. Para explicar a un granjero que no ha ido nunca a la ciudad cmo ir al banco en su
tractor, conviene describir por separado:

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 invariante

Demostramos que el bucle avanza

Demostramos que al final de la ejecucin del bucle se cumple la postcondicin

256

Tipos abstractos de datos

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 sentencias 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 implementacin. 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:

Especificacin
Abstraccin
Implementacin

La abstraccin funcional es la base del mtodo de diseo descendente, del cul es un ejemplo
las instrucciones que han de darse a un robot para que cambie una rueda.

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 implementacin de las operaciones al nivel de la mquina algoritmos de aritmtica en complemento
a 2 implementados en el procesador son irrelevantes al nivel del usuario.

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 obtendr 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 usuario.

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 especificacin 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 operaciones 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;
{P0:d/m/acumplenlasrestriccionesdeunafecha,segnelcalendario}
{Q0:frepresentaalafechad/m/a}
ffunc;

funcdistancia(f1,f2:Fecha)devd:Ent;
{P0:}
{Q0:desladistancia,medidaennmerodedas,entref1yf2}
ffunc;

funcsuma(f:Fecha;d:Ent)devg:Fecha;
{P0:}
{Q0:GeslafecharesultantedesumarddasalafechaF}
ffunc;

Cuidado!dpuedeserunnmeronegativodeformaquegnoseaunafecha
posterioral1/1/0

funcda(f:Fecha)devd:Da;
{P0:}
{Q0:deseldadelafechaf}
ffunc;

funcmes(f:Fecha)devm:Mes;
{P0:}
{Q0:meselmesdelafechaf}
ffunc;

funcao(f:Fecha)deva:Ao;
{P0:}
{Q0:aeselaodelafechaf}
ffunc;

funcdaSemana(f:Fecha)devd:DaSemana;
{P0:}
{Q0:deseldadelasemanacorrespondientealafechaf}
ffunc;



Tipos abstractos de datos

260

funcprimer(d:DaSemana;m:Mes;a:Ao)devf:Fecha;
{P0:}
{Q0:feslaprimerafechadelmesmdelaoacuyodadelasemanaesd
}
ffunc;


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

primer(martes,noviembre,1871)


Una especificacin del TAD fecha debera incluir adems axiomas para las operaciones, que
omitimos aqu.
3.1.5

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 disponibles.

Definir las operaciones del TAD como funciones o procedimientos, usando la representacin elegida, y de modo que se satisfaga la especificacin.

Para hacer posible esta separacin entre especificacin e implementacin, es necesario que
nuestro lenguaje incluya mecanismos de

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 incontrolados 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 proteccin 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 cualquiera 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 facilita 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 Enero de 1900 de hecho en la mayora de los sistemas es as como se hace, con lo cual llegar 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 dadas, 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 concretos que nos ofrezca un lenguaje de programacin determinado. Podemos especificar
TADs adecuados y posponer los detalles de su implementacin.

El diseo puede incluir refinamiento de tipos. Al especificar un TAD necesario para el diseo, podemos dejar incompletos detalles que se completen en etapas posteriores. Por
ejemplo, la decisin de cules deben ser las operaciones del tipo puede irse completando a
lo largo del diseo. De esta forma, se va refinando la definicin del TAD.

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)>minocambiarminporv(i)enm
v(i)minoseguir
fpara


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

funcmayores(k:Nat;v:Vector[1..N]deEnt)devm:MCjtoEnt;
{P0:1kNN1}
var
n:Ent;
inicio
Vaco(m);
n:=0;
{Inv.I:mcontieneloselementosdev[1..n]}
itnzko
Pon(v(n+1),m);
n:=n+1
fit
{InvJ:mcontieneloskelementosmayoresdev[1..n]}
itnzNo
siv(n+1)>min(m)
entonces
quitaMin(m);
Pon(v(n+1),m)
sino
seguir
fsi
fit
{Q0:mcontieneloskelementosmayoresdev[1..N]}
devm
ffunc

Del algoritmo resultante podemos ver qu operaciones necesitamos en el TAD multiconjunto

Tipos abstractos de datos

263


funcVacodevm:MCjtoEnt
{P0:}
{Q0:mrepresenta}
ffunc

procPon(ex:Ent;esm:MCjtoEnt);
{P0:m=Me=E}
{Q0:e=Em=M{x}}
fproc

procquitaMin(esm:MCjtoEnt);
{P0:m=Mmnoesvaco}
{Q0:mesMmenosunacopiadelelementomnimodeM}
fproc

funcmin(m:MCjtoEnt)devr:Ent;
{P0:m=Mmnoesvaco}
{Q0:m=Mreselelementomnimodem}
ffunc


Pon y quitaMin se han implementado como procedimientos para evitar la copia del multiconjunto recibido como parmetro. Un multiconjunto se podra implementar como un vector de
registros de dos componentes, una que indica el nmero almacenado y otra el nmero de apariciones de ese valor; adems se podra optar entre mantenerlo o no ordenado con lo que se hara
ms eficiente la obtencin del mnimo o las operaciones poner y quitar, respectivamente.
Se pueden encontrar infinidad de ejemplos como este donde es beneficioso disear en trminos de un TAD.
Los TADs como apoyo a la programacin modular
El desarrollo de programas grandes y complejos normalmente se realiza descomponiendo el
programa en unidades menores llamadas mdulos, cada uno de los cuales encapsula una coleccin de declaraciones en un mbito de visibilidad cerrado y se comunica con otros mdulos a
travs de una interfaz bien definida.
En un diseo modular es importante que los mdulos se elijan adecuadamente en cuanto a su
tamao y conexiones mutuas. Por lo general siempre resulta adecuado disear como mdulos las
implementaciones de TADs, debido a su utilidad general, su tamao mediano, y la simplicidad de
la conexin con los mdulos usuarios.
En Delphi las clases constituyen un mecanismo de modularizacin.
La implementacin de los TADs como mdulos est relacionado con las ideas bsicas de la
orientacin a objetos, donde las clases encapsulan las definiciones de tipos de datos junto con las
operaciones que los manejan. Utilizando criterios de ingeniera del software, la organizacin del
diseo en torno a los datos resulta ventajoso frente a la organizacin en torno a las funciones,
pues los datos objetos suelen ser ms resistentes al cambio.

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 Press, 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 disponibles en los lenguajes de programacin habituales (booleanos, enteros, reales, caracteres, vectores, registros, punteros, etc.) Es decir, concebimos las estructuras de datos como estructuras de
implementacin.
Hay muchas estructuras de datos clsicas que corresponden a combinaciones interesantes de
los tipos bsicos: las estructuras lineales, los rboles, las tablas, los grafos, etc. Estas estructuras
clsicas pueden entenderse como las implementaciones de ciertos TADs bsicos asociados a ellas.
Adems, las combinaciones de estas estructuras sirven para implementar muchos otros TADs.
Existe la tendencia entre los alumnos a identificar el estudio de los TADs con el estudio de estas estructuras de datos clsicas. Y a considerar que los TADs asociados con ellas son entidades
monolticas cuyas especificaciones no pueden ser modificadas porque entonces ya no sera el
TAD x. Estos TADs representan colecciones de datos a los que se accede de un cierto modo;
como tales son muy interesantes porque son muchos los programas que manejan colecciones de
datos, y la literatura se ha encargado de identificar agrupaciones interesantes de operaciones de
acceso, e investigar posibles implementaciones representaciones concretas que hagan eficientes
dichas operaciones. Sin embargo, el concepto de TAD es ms simple y ms general a la vez, simplemente un TAD define un dominio de valores junto con las operaciones que permiten manejarlos, y esto es algo que puede variar de unas aplicaciones a otras, an en lo que se refiere a las
estructuras de datos clsicas.

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 abstraccin, 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 especifican tambin cmo construir valores de ste

Las especificaciones basadas en ecuaciones suelen dar muchas pistas para la implementacin

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 perfiles de las operaciones sin incluir las ecuaciones.

Tipos abstractos de datos

266

Algo de notacin:


: signatura de un TAD
W, Wi : tipos
c:oW

operacin constante de gnero W

f : W1, , Wn o s

operacin con perfil, donde los Wi son los tipos de los argumentos y W 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:oBool/*gen*/

 (not):BooloBool /*mod*/





 (and),(or):(Bool,Bool)oBool/*mod*/
ecuaciones















ftad

x:Bool
notCierto =Falso
notFalso  =Cierto
Ciertoandx=x
Falsoandx  =Falso
Ciertoorx  =Cierto
Falsoorx =x

Ntese que slo hacemos distincin de casos en uno de los argumentos. La idea intuitiva es
que tenemos que escribir las ecuaciones de forma que reflejen el comportamiento de las
operaciones, segn el modelo mental que estamos intentando especificar.
Un ejemplo ms complicado, donde aparece la clusula usa para indicar que importamos otro
TAD, de forma que todas las operaciones, tipos y ecuaciones de BOOL son visibles en NAT. Es
como si cogisemos la especificacin de BOOL y la pegsemos en la especificacin de NAT. En
ocasiones puede ser necesario renombrar alguna de las operaciones del TAD usado para que no
se colapsen con las operaciones que voy a definir en el TAD que lo usa.

tadNAT

usa

 BOOL

Tipos abstractos de datos





tipo
 Nat
operaciones

 Cero:oNat   /*gen*/

 Suc_:NatoNat /*gen*/

 (+),(*):(Nat,Nat)oNat /*mod*/

 (==),(/=):(Nat,Nat)oBool /*obs*/




 (),(),(<),(>):(Nat,Nat)oBool /*obs*/
ecuaciones

































ftad

267

x,y:nat
x+Cero   =x
x+Suc(y)  =Suc(x+y)
x*Cero    =Cero
x*Suc(y)  =(x*y)+x
Cero==Cero =Cierto
Cero==Suc(y)  =Falso
Suc(x)==Cero  =Falso
Suc(x)==Suc(y) =x==y
x/=y    =not(x==y)
Ceroy    =Cierto
Suc(x)Cero  =Falso
Suc(x)Suc(y)=xy
xy    =yx
x<y    =(xy)and(x/=y)
x>y    =y<x

Ntese que las ecuaciones tienen forma de definiciones recursivas, definimos el comportamiento de la operacin para el o los casos base y luego definimos una o ms reglas recursivas que
nos van acercando al caso base a los trminos generados. Por ejemplo, para la suma tenemos
como caso base
x+Cero=x

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 W: TW

Fijando un conjunto de variables X, y aplicando las operaciones del TAD podemos generar
valores de distintos tipos a los que llamamos trminos. Es decir, una signatura permite definir un

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, W (X)

conjunto de los trminos de tipo W con variables en X

Donde el conjunto de variables X hace referencia a las variables que hemos utilizado al escribir las ecuaciones.
que se define recursivamente como:

x XW x T,W (X)
donde XW X
si x pertenece a las variables de tipo W entonces pertenece al conjunto de trminos de tipo
W con variables en X

c : o W c T,W (X)

f : W1, , Wn o W ti T,Wi (X) (1 i n) f(t1, , tn) T, W (X)

Convenios

T,W es T,W (). A los trminos sin variables se les denomina trminos cerrados

puede omitirse si se da por sabida, con lo que el conjunto de trminos cerrados de tipo
W para una signatura que se da por sabida es TW

Ejemplos
En NAT hay dos tipos Ent y Bool, y se tiene
Suc(x)+Suc(Suc(y))TEnt(x,y)
Suc(Cero)+Suc(Suc(Cero))TEnt
Suc(x)==CeroTBool(x)
Suc(Cero)/=CeroTBool

3.2.3

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 ecuaciones, segn las leyes de la lgica.
Sea T un TAD con tipo principal W, dos trminos t, t: W se llaman T-equivalentes (en
smbolos t=Tt), si la ecuacin t=t se puede deducir a partir de los axiomas ecuacionales
de T. (De un TAD que use a otro tambin se pueden deducir T-equivalencias entre trminos de
los tipos importados, sin embargo, como luego veremos, no se puede introducir ninguna equivalencia nueva, por lo que en un TAD basta con hacer referencia a las equivalencias entre trminos del
tipo principal.)

269

Tipos abstractos de datos

Las reglas del clculo ecuacional que nos permiten determinar la igualdad algebraica entre
trminos son:

Reflexividad
t=t


Simetra
t=t
t=t

Transitividad
t=tt=t


t=t

Congruencia


t1=t1tn=tn

f(t1,,tn)=f(t1,,tn)

Axiomas ecuacionales
t[x1/t1,,xn/tn]=t[x1/t1,,xn/tn]

si

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











*.2
*.1
+.2
+.1


==
==
==
==

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

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

t=t y t=Tt

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)=TSuc(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 W, clasificamos las operaciones
segn el papel que queremos que jueguen en relacin con el tipo principal, como:

Generadoras (o constructoras)
Sern algunas operaciones con perfil
&
c: W oW

que estn pensadas para construir todos los valores de tipo W




Modificadoras
Sern las restantes operaciones con perfil
&
f: W oW

Tipos abstractos de datos

271

que no sean generadoras. Estas operaciones estn pensadas para hacer clculos que produzcan resultados de tipo W.

Observadoras
Sern algunas operaciones con perfil
&

g: W oW  talqueW{W;yalgnWi{W

pensadas para obtener valores de otros gneros a partir de valores de tipo W.


Volvemos sobre los ejemplos de BOOL y NAT y clasificamos las operaciones



 Cierto,Falso:oBool         /*gen*/

 (not):BooloBool           /*mod*/

 (and),(or):(Bool,Bool)oBool    /*mod*/

y para NAT



 Cero:oNat             /*gen*/

 Suc:NatoNat             /*gen*/

 (+),(*):(Nat,Nat)oNat       /*mod*/

 (==),(/=):(Nat,Nat)oBool      /*obs*/

 (),(),(<),(>):(Nat,Nat)oBool  /*obs*/

Dada esta clasificacin de las operaciones, hay un tipo especialmente interesante de trminos,
los que slo utilizan operaciones generadoras.
3.2.5

Trminos generados: TGW

Dado un TAD T con tipo principal W se llaman trminos generados a aquellos trminos de tipo W
que estn construidos usando solamente operaciones generadoras de T (y trminos generados de
otros TADs usados por T, si es necesario). (No nos preocupamos por los trminos de otros tipos
que no sean W pues esos trminos deben ser equivalentes a trminos generados en el TAD donde
se define el correspondiente tipo.)
TG,W(X)=def{tT,W(X)|todaslasoperacionesusadasentson
generadoras}

Al igual que en la notacin de T, cuando X = ponemos TG,W, y omitimos si es sabida,


con lo cual notamos TGW.
En BOOL los trminos generados son

TGBool={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



TGNat={Cero}{Sucn(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 W, se dice que el conjunto de operaciones generadoras de T
es suficientemente completo si es cierta la siguiente condicin: para todo trmino cerrado t de tipo W
debe existir un trmino cerrado y generado t de tipo W que sea T-equivalente a t

t:tT,W:t:tTG,W:t=Tt

No nos preocupamos por los trminos de otros tipos distintos de W porque esos trminos deben ser equivalentes a trminos generados en el TAD donde se define su tipo. Esto ha de ser as
para que el TAD usado quede protegido, como luego veremos.
La especificacin de cualquier TAD siempre debe construirse de manera que el conjunto de
operaciones generadoras elegido sea suficientemente completo.
Ejemplo: las generadoras de BOOL son suficientemente completas
Este tipo de demostraciones se hace por induccin sobre la estructura sintctica de los trminos induccin estructural.

Base:t/Cierto


Cierto=BOOLCierto

Base:t/Falso


Falso=BOOLFalso
Pasoinductivo:t/nott1


273

Tipos abstractos de datos




H.I.

t1/Cierto




not.1

t1/Falso




not.2


nott1
=BOOLnott1


notCierto
=BOOLFalso


notFalso
=BOOLCierto


Pasoinductivo:t/t1andt2




H.I.

t1/Cierto




and.1

t1/Falso




and.2


t1andt2
=BOOLt1andt2


Ciertoandt2
=BOOLt2


Falsoandt2
=BOOLFalso

Y de igual forma para or


Ejemplo: las generadoras de NAT son suficientemente completas

Base:t/Cero
Cero=NATCero

Pasoinductivo:t/Suc(t1)



 
Suc(t1)
H.I. =NATSuc(t1)


Pasoinductivo:t/t1+t2




 
t1+t2
H.I. =NATt1+t2
Lema1 =NATt

Ellema1dicequedadosdostrminosgeneradost1yt2existeotro
trminogeneradottalquet1+t2=NATt.Lodemostraremosms
abajo.

Pasoinductivo:t/t1*t2


 

t1*t2

274

Tipos abstractos de datos




H.I. =NATt1*t2
Lema2 =NATt

Ellema2dicequedadosdostrminosgeneradost1yt2existeotro
trminogeneradottalquet1*t2=NATt.Lodemostraremosms
abajo.

Lema1
Base:t2/Cero



 
t1+Cero
+.1 =NATt1


Pasoinductivo:t2/Suc(t3)




 
t1+Suc(t3)
+.2 =NATSuc(t1+t3)
H.I. =NATSuc(t4)


Lema2
Base:t2/Cero

 
t1*Cero

*.1 =NATCero

Pasoinductivo:t2/Suc(t3)


 
t1*Suc(t3)

*.2 =NATt1*t3+t1

H.I. =NATt4+t1

Lema1 =NATt5


Para los trminos de tipo Bool no lo demostramos porque es un tipo que se define en otro
TAD, lo que tendremos que demostrar es que el TAD usado queda protegido.
3.2.7

Razonamiento inductivo

El mismo tipo de demostraciones que hemos utilizado para probar la completitud suficiente
de las generadoras se puede utilizar para demostrar otras ecuaciones. Hay ecuaciones cuya validez
no se puede obtener simplemente con el clculo ecuacional, sino que hay que hacer suposiciones
sobre las posibles formas sintcticas de los trminos. Aqu nos aprovechamos de la completitud suficiente de las generadoras, para as razonar solamente sobre la estructura sintctica de los
trminos generados.
Ejemplo (ej. 143): Cero + y = y
Base:y/Cero

 
Cero+Cero

+.1 =NATCero
Pasoinductivo:y/Suc(y1)





 
Cero+Suc(y1)
+.2 =NATSuc(Cero+y1)
H.I. =NATSuc(y1)

275

Tipos abstractos de datos

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 lgebra donde el dominio de valores tenga un solo elemento es modelo de cualquier especificacin algebraica que no tenga operaciones parciales

Por cada operacin que aparezca en la especificacin, debe incluir una funcin con el
mismo perfil el nombre puede ser distinto. No puede incluir ms funciones.

Las funciones asociadas con las operaciones deben cumplir las ecuaciones de la especificacin.

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). Interpreta las operaciones not, (and), y (or) en este modelo de manera que las ecuaciones de BOOL
se cumplan. Compara con el modelo de trminos de BOOL.
ABool={c,f, A }

CiertoA:oABool
FalsoA:oABool
notA:ABooloABool
(andA),(orA):(ABool,ABool)oABool

CiertoA=defc
FalsoA=deff

v

notAv

v1v2

andA v

v1v2 orAv

c

f

cc

cc

c

f

c

cf

cf

c

c A 

A

c A 

c

fc

fc

c

ff

ff

f

f A 

f A 

A A


A c A 
A f f
AA A

A
c

A c
A f A 
AA A


notCierto=Falso   porlaprimerafiladenotA
notFalso=Cierto   porlasegundafiladenotA
Ciertoandx=x  porlastresprimerasfilasdeandA

Tipos abstractos de datos

276

Falsoandx=Falso porlasfilas4a6deandA
Ciertoorx=Cierto  porlastresprimerasfilasdeorA
Falsoorx=x   porlasfilas4a6deorA


Ntese que en la ltima fila de notA, y las tres ltimas de andA y notA podramos haber elegido
cualquier otra combinacin de valores pues las ecuaciones no imponen ninguna condicin sobre
la aplicacin de las operaciones a esos valores.
Ntese asimismo que en las ecuaciones donde aparecen generadoras es necesario realizar dos
sustituciones:

notCierto=Falso

seinterpretacomo

notACiertoA=FalsoAnotAc=ff=f

3.2.9

Modelo de trminos

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 W, el dominio abstracto de este tipo es



AW=defTGW


La T-equivalencia establece las ecuaciones vlidas entre valores abstractos



t=Ttt=tsededucedelaespecificacin
parat,tAS


Las operaciones abstractas definidas como sigue



Sif:W1,,WnoW
tiAWi(1in)

definimos

fA(t1,,tn)=deftAWt.q.f(t1,,tn)=Tt


Es decir consideramos como valores los trminos generados, y como resultados de las operaciones los trminos generados a los que son equivalentes.
Por ejemplo, en NAT

Tipos abstractos de datos

277


SucA(Suc2(Cero))=TSuc3(Cero)
Suc2(Cero)+ASuc3(Cero)=TSuc5(Cero)
Suc3(Cero)*ASuc2(Cero)=TSuc6(Cero)

Este modelo da la semntica inicial a la especificacin algebraica. Se llama inicial pues se puede
establecer un isomorfismo entre ese y cualquier otro modelo de la especificacin. Es el modelo
con el nmero mnimo de ecuaciones, o dicho de otra forma, cualquier modelo de la especificacin algebraica debe cumplir todas las ecuaciones que cumpla el modelo de trminos.
La especificacin de un TAD debe ser diseada de manera que su modelo abstracto describa
el tipo de datos que el diseador tiene en mente. Como veremos ms adelante, las implementaciones de un TAD estn obligadas a realizar una representacin del modelo abstracto.
3.2.10

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 W cuya especificacin algebraica usa otro TAD S con tipo
principal s. Se dice que T protege a S si se cumplen las dos condiciones siguientes:
(i) T no introduce basura en S, es decir: para todo trmino cerrado t : s que se pueda
construir en T, existe un trmino generado t:s que ya se poda construir en S y
tal que t=Tt.
(ii) T no introduce confusin en S, es decir: Si t,t:s son trminos cerrados generados
que ya se podan construir en S, y se tiene t=Tt, entonces ya se tena t=St.


(a) Demuestra que el siguiente TAD que usa a BOOL no protege a BOOL, porque

in<troduce basura y confusin en BOOL:


tadPERSONA
 usa
  BOOL
 tipo
  Persona
 operaciones
  Carmen,Roberto,Luca,Pablo:oPersona     
gen*/

/*

  feliz:PersonaoBool             
obs*/
 ecuaciones
  feliz(Carmen)  =feliz(Roberto)
  feliz(Pedro) =feliz(Luca)
  feliz(Pedro) =notfeliz(Pablo)
  feliz(Luca) =Cierto

/*

278

Tipos abstractos de datos

  feliz(Pablo) =Cierto

Introduce basura porque hay trminos de tipo Bool para los que no se puede deducir la equivalencia con ninguno de los trminos generados de BOOL es decir, que no se sabe si son ciertos o
falsos:

feliz(Carmen) feliz(Roberto)

E introduce confusin porque es posible obtener nuevas equivalencias entre trminos:











feliz.4
feliz.2
feliz.3
feliz.5
not.1



=PERSONA
=PERSONA
=PERSONA
=PERSONA
=PERSONA

Cierto
feliz(Luca)
feliz(Pedro)
notfeliz(Pablo)
notCierto
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 sintctica 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 W. Se dice que las operaciones generadoras de T son no libres
si existen trminos generados no idnticos t y t de tipo W, que sean T-equivalentes. Por el contrario, se dice que las operaciones generadoras de T son libres si no es posible encontrar trminos
generados no idnticos t, t de tipo W que sean T-equivalentes.
Decimos que dos trminos son idnticos cuando son iguales sintcticamente.
Los TAD que venimos estudiando hasta ahora, BOOL y NAT, presentan generadoras libres.
Veamos una especificacin para los enteros donde aparecen generadoras no libres:
tadENT

usa

 BOOL,NAT

tipo

 Ent

operaciones


 Cero:oEnt   /*gen*/

 Suc,Pred:EntoEnt /*gen*/

 ():EntoEnt   /*mod*/

 (+),(),(*):(Ent,Ent)oEnt /*mod*/




 (^):(Ent,Nat)oEnt/*mod*/
ecuaciones

 x,y:Ent:n:Nat

279

Tipos abstractos de datos





























ftad

Suc(Pred(x))=x
Pred(Suc(x))=x
x+Cero  =x
x+Suc(y) =Suc(x+y)
x+Pred(y)  =Pred(x+y)
xCero   =x
xSuc(y) =Pred(xy)
xPred(y)  =Suc(xy)
x    =Cerox
x*Cero   =Cero
x*Suc(y) =(x*y)+x
x*Pred(y)  =(x*y)x
x^Cero   =Suc(Cero)
x^Suc(n) =(x^n)*x

Podemos ver que las generadoras del TAD ENT no son libres pues existen trminos generados que son ENT-equivalentes entre s:




 
Suc(Pred(Suc(Cero)))
Suc =ENTSuc(Cero)

Por el contrario las generadoras de BOOL y TAD s son libres pues la especificacin no incluye ninguna ecuacin que slo involucre a generadoras, por lo tanto no es posible aplicar ninguna
regla del clculo ecuacional que no sea la reflexiva. (es correcto este razonamiento?).
Esto nos da una idea a utilizar en el diseo de especificaciones: si las generadoras son libres
entonces no escribimos ninguna ecuacin que las relacione; por el contrario si las generadoras no
son libres es necesario escribir ecuaciones que permitan determinar las equivalencias que nos
interesen entre trminos generados. En este segundo caso puede ser til pensar en una eleccin
de trminos cannicos.
Representantes cannicos de los trminos: TCW TGW

3.2.12

Sea T un TAD con tipo principal W. Se llama sistema de representantes cannicos a cualquier subconjunto TCW del conjunto TGW de todos los trminos generados y cerrados de tipo W, tal que para
cada t TGW exista un nico t TCW tal que t =T t. Es decir, si todos los elementos de TC son
algebraicamente diferentes, y para cada elemento de TG se puede encontrar uno T-equivalente en
TC; lo que hacemos es elegir un representante para cada una de las clases de equivalencia que es
posible definir entre los elementos de TG.
Si un TAD tiene generadoras libres entonces el conjunto de representantes cannicos coincide
con el de trminos generados, como ocurre en BOOL y NAT. No es as cuando tratamos con
TAD con generadoras no libres. De hecho el concepto de trminos cannicos se introduce para
conseguir un conjunto mnimo de valores que representen a todos los valores posibles, cuando
tenemos TAD con generadoras no libres.
Por ejemplo, en ENT podemos elegir como trminos cannicos:

TCEnt=def{Cero}{Sucn(Cero)|n>0}{Predn(Cero)|n>0}

Tipos abstractos de datos

280

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



TCEnt=def{Sucn(Cero)|n>0}{Predn(Suc(Cero))|n>0}

Insistir en la idea expuesta anteriormente de que cuando tenemos generadoras no libres debemos escribir ecuaciones sobre las generadoras de forma que sea posible traducir cualquier
trmino generado a un trmino cannico. El conjunto de representantes cannicos en el que se
estaba pensando cuando se escribi la especificacin de ENT era el primero que hemos escrito
ms arriba. Podemos ver intuitivamente que efectivamente con las dos ecuaciones dadas es posible traducir cualquier trmino generado a uno de la forma Cero Sucn(Cero) Predn(Cero).
Resumiendo lo dicho hasta ahora sobre las ecuaciones, debemos escribirlas teniendo en mente
las siguientes condiciones:

Las ecuaciones deben permitir las igualdades entre trminos que son posibles entre los valores del modelo en el que estamos pensando.

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 algebraicas:

Las operaciones privadas. Son operaciones que no exporta el TAD a sus clientes pero que
ayudan a especificar el comportamiento de las operaciones exportadas, y, en algunos casos, tambin a su implementacin.

Ecuaciones condicionales. Son ecuaciones de la forma


ecuacinsiecuacin
ecuacinsitdondetTBool

Veamos un ejemplo con la especificacin de los enteros con orden. Aparece aqu otro concepto nuevo: enriquecimiento de un TAD. Un enriquecimiento es un TAD que usa a otro al que
extiende aadindole operaciones nuevas, pero sin definir ningn tipo adicional.
La idea para escribir las ecuaciones de comparacin es darse cuenta de que basta con dar el
modo de calcular , y el resto de las operaciones se pueden expresar en trminos de sta se
podra hacer una eleccin diferente. Y la idea para indicar el modo de calcular es hacerlo en
trminos de la resta, viendo qu signo tiene sta. Para ello introducimos la operacin auxiliar noNeg. Y para escribir las ecuaciones de noNeg tenemos que utilizar ecuaciones condicionales.
tadENTORD

usa

 ENT

Tipos abstractos de datos

operaciones

 (==),(/=):(Ent,Ent)oBool /*obs*/




 (),(),(<),(>):(Ent,Ent)oBool /*obs*/
operacionesprivadas





 noNeg:EntoBool  /*obs*/

281

ecuaciones























ftad

x,y:Ent
noNeg(Cero)    =Cierto
noNeg(Suc(x))   =CiertosinoNeg(x)=Cierto
noNeg(Pred(Cero)) =Falso
noNeg(Pred(x))   =FalsosinoNeg(x)=Falso
xy     =noNeg(yx)
xy     =yx
x<y     =(xy)and(x/=y)
x>y     =y<x
x==y     =xyandyx
x/=y     =not(x==y)

La inclusin de ecuaciones condicionales modifica una de las reglas del clculo ecuacional

Axiomas ecuacionales



  C[x1/t1,,xn/tn]

t[x1/t1,,xn/tn]=t[x1/t1,,xn/tn]

si

t=tsiC es una ecuacin de la especificacin

Como ejercicio se propone la especificacin del TAD POLINOMIO, nada trivial, que necesita de las operaciones privadas multMono para expresar la multiplicacin de polinomios, y quita
Exp para expresar la operacin esNulo.
3.2.14

Clases de tipos

Introducimos un nuevo elemento en las especificaciones: las clases de tipos. Estas especificaciones permiten especificar restricciones sobre TADs. Son tiles como luego veremos para escribir TADs parametrizados, TADs que dependen de otros, pues permiten expresar las condiciones
que se les exigen a los parmetros de los tipos parametrizados.
Veamos un primer ejemplo:

claseANY

tipo

 Elem
fclase

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 definicin del TAD y cambiar el nombre del tipo principal; o en el TAD paramtrico que depende de
ANY, podramos hacer un renombramiento del correspondiente tipo principal a Elem, pero como escribimos el tipo paramtrico sin saber sobre qu tipos se instanciar, no tenemos esa informacin.
En las clases de tipos se pueden poner axiomas, es decir condiciones que no son ecuaciones. La
razn de que en las especificaciones de TAD slo se pongan ecuaciones es que as est definida
de forma unvoca un lgebra con un conjunto mnimo de suposiciones: el modelo inicial. Si
aceptsemos axiomas con conectivas lgicas entonces podramos encontrar ms de un lgebra
con el mismo coste:

a=bora=c


hace posible tanto un lgebra donde a sea igual a b o igual a c ambos con el mismo coste o
igual a ambas.
En las clases de tipos s podemos poner axiomas porque no tenemos la necesidad de construir
una implementacin particular de la clase de tipos, sino identificar una familia de lgebras posibles.
Un par de ejemplos ms: los tipos con igualdad. aparece aqu un nuevo elemento que es la
herencia entre clases de tipos, indicando que la clase EQ contiene todas las operaciones y tipos
especificados en la clase ANY.

claseEQ

hereda

 ANY

usa

 BOOL

operaciones






 (==),(/=):(Elem,Elem)oBool
axiomas
 %(==)esunaoperacindeigualdad.
ecuaciones


 x,y:Elem:

 x/=y=not(x==y)
fclase


Y la clase de los tipos con orden



claseORD

hereda

 EQ

operaciones


 (),(),(<),(>):(Elem,Elem)oBool

Tipos abstractos de datos





axiomas
 %()esunaoperacindeorden
ecuaciones











283

x,y:Elem:
xy=yx
x<y=(xy)and(x/=y)
x>y=y<x

Escribimos las condiciones sobre == y d como axiomas porque no sabemos cules son las
constructoras del tipo concreto y por lo tanto no podemos escribir las ecuaciones concretas.
Ntese que diferentes TADs pertenecientes a la misma clase de tipos pueden tener signaturas
diferentes, ya que se exige que aparezcan una ciertas operaciones, pero no exclusivamente esas.
3.2.15

TADs genricos

Los TADs genricos son TADs que dependen de otros uno o ms de forma que es posible
construir distintos ejemplares del TAD genrico segn el tipo de los parmetros.
Los TADs genricos representan tpicamente colecciones de elementos. Estos TADs tienen
un cierto comportamiento independiente del tipo de los elementos, aunque tambin puede haber
operaciones que dependan de los elementos. Las exigencias sobre los elementos se expresan indicando la clase de tipos a la que tienen que pertenecer los parmetros admisibles.
Como ejemplo vamos a escribir la especificacin de los conjuntos CJTO[E :: EQ]
tadCJTO[E::EQ]

usa

 BOOL

tipo

 Cjto[Elem]

operaciones


 :oCjto[Elem]               /*gen*/

 Pon:(Elem,Cjto[Elem])oCjto[Elem]         /*gen*/


 quita:(Elem,Cjto[Elem])oCjto[Elem]        
mod*/

/*

 esVaco:Cjto[Elem]oBool             /*obs*/




 ():(Elem,Cjto[Elem])oBool          /*obs*/
ecuaciones





 x,y:Elem:xs:Cjto[Elem]:
 Pon(y,Pon(x,xs))
=Pon(x,xs)six==y
 Pon(y,Pon(x,xs))
=Pon(x,Pon(y,xs))


 quita(x,)   =
**  quita(x,Pon(y,xs))=quita(x,xs)six==y

 quita(x,Pon(y,xs))=Pon(y,quita(x,xs))six/=y



 esVaco()    =Cierto
 esVaco(Pon(x,xs)) =Falso

284

Tipos abstractos de datos

 x     =Falso


 xPon(y,xs) =x==yorxxs
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:







quita.2
Pon.1
quita.2


=
=
=

Vaco
quita(0,Pon(0,Vaco))
quita(0,Pon(0,Pon(0,Vaco)))
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 corresponde a un cierto reemplazamiento del parmetro formal del TAD genrico por un parmetro 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 genrico:
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





tipo
 Pareja[A.Elem,B.Elem]
operaciones

 Par:(A.Elem,B.Elem)oPareja[A.Elem,B.Elem]/*gen*/

 pr:Pareja[A.Elem,B.Elem]oA.Elem/*obs*/




 sg:Pareja[A.Elem,B.Elem]oB.Elem /*obs*/
ecuaciones

285


 x:A.Elem:y:B.Elem:

 pr(Par(x,y))=x

 sg(Par(x,y))=y
ftad

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 parciales. En ese caso existirn trminos que no estn definidos y que deben recibir un tratamiento
cuidadoso.
Como ejemplo de TAD con operaciones parciales vamos a utilizar el TAD PILA: un apila representa una coleccin de valores donde es posible acceder al ltimo elemento aadido, implementa la idea intuitiva de pila de objetos.

tadPILA[E::ANY]

usa

 BOOL

tipo

 Pila[Elem]

operaciones


 PilaVaca:oPila[Elem] /*gen*/

 Apilar:(Elem,Pila[Elem])oPila[Elem] /*gen*/

 desapilar:Pila[Elem]oPila[Elem] /*mod*/

 cima:Pila[Elem]oElem /*obs*/

 esVaca:Pila[Elem]oBool /*obs*/

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 o en su
perfil. Cuando una operacin se especifica como parcial, su valor queda indefinido en ciertos
casos. En este ejemplo, despilar y cima quedan indefinidas en los casos indicados en la seccin
errores de la especificacin. La implementacin deber responder adecuadamente hay distintas posibilidades ante la aparicin de estos errores.
Axiomas de definicin
Aparece un nuevo tipo de axiomas en las especificaciones: los axiomas de definicin. En este
ejemplo:
defdesapilar(Apilar(x,xs))
defcima(Apilar(x,xs))

Ntese que slo se han escrito axiomas de definicin para las operaciones parciales. Por convenio, para cada operacin declarada como total en la signatura, con un perfil de la forma:

f:W1,,WnoW


suponemos aadido a la especificacin el axioma:


x1:W1xn:Wn:deff(x1,,xn)


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 partir de la especificacin del TAD, e indefinido en caso contrario.
En los axiomas de definicin cabe preguntarse si no es necesario exigir que las variables estn
tambin definidas. Sin embargo esto no es necesario; lo que se hace es modificar la regla del

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
t1=t1tn=tndeff(t1,,tn)


f(t1,,tn)=f(t1,,tn)

Axiomas ecuacionales
C[x1/t1,,xn/tn]deft1deftn
t[x1/t1,,xn/tn]=t[x1/t1,,xn/tn]

si

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 establecer igualdades entre trminos definidos, por lo tanto una igualdad entre trminos implica
la definicin de dichos trminos.
Esto implica que cuando escribamos las ecuaciones de la especificacin slo podemos utilizar trminos definidos.

Operaciones estrictas.
Convenimos en que todas las operaciones de nuestras especificaciones son estrictas, por
lo que
deff(t1,,tn)
deft1deftn

Esta regla, como las anteriores, tambin se puede aplicar hacia atrs de forma que si no
est definido algn ti entonces no est definida f(t1, , tn).

Variables definidas

Tipos abstractos de datos

288


defx


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


Axiomas de definicin

C[x1/t1,,xn/tn]deft1deftn


si

 deft[x1/t1,,xn/tn]

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 incluirse 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 definidos.
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=Ttdeftdeft

TDW(X)=def{tTW(X)|deft}


De entre los trminos generados interesan los definidos



TGDW(X)=def{tTGW(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 definido t

t:tTDW:t:tTGDW:t=Tt

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 cerrado y definido sea T-equivalente a un nico representante cannico

TCDW(X)=def{tTCW(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 referencia slo a trminos definidos
(i) T no introduce basura en S, es decir: para todo trmino cerrado y definido t:s que se
pueda construir en T, existe un trmino generado y definido t:s que ya se poda
construir en S y tal que t=Tt.
(ii) T no introduce confusin en S, es decir: Si t,t:s son trminos cerrados generados y
definidos que ya se podan construir en S, y se tiene t=Tt, entonces ya se tena
t=St.

Por ltimo, se modifica la definicin del modelo de trminos para considerar slo los
trminos definidos

Para cada tipo W, el dominio abstracto de este tipo es


AW=defTGDW


La T-equivalencia establece las ecuaciones vlidas entre valores abstractos


t=Ttt=tsededucedelaespecificacin
parat,tAW

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




Las operaciones abstractas definidas como sigue



Sif:W1,,WnoW
tiAWi(1in)

definimos

       tAWt.q.f(t1,,tn)=Ttsideff(t1,,tn)
fA(t1,,tn)=def
       indefinido       enotrocaso

por ejemplo:



cimaA(PilaVaca)  estindefinido
cimaA(desapilarA(Apilar(t2,Apilar(t1,PilaVaca))))=t1

290

Tipos abstractos de datos

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, aprovechndonos de que

t=etodeft

t=dtequivalea

t=etmt=ett=et


es decir, si ambos estn definidos y son iguales (=e) o si alguno no est definido, con
lo que el antecedente de la implicacin es falso, y la implicacin cierta
t=ftequivalea

(t=etmt=et)
(t=etmt=et)
(t=etmt=ett=et)


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 DW para cada tipo W 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 representante en realidad el DW es el conjunto de representantes vlidos del tipo W.

Una operacin concreta



fC:DW1,,DWnoDW


para cada operacin


f:W1,,WnoW


Las operaciones concretas pueden implementarse como procedimientos aunque en la especificacin 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 acceso 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 relativos 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 correcta con respecto a una especificacin dada. Introduciremos las ideas mediante un ejemplo: la
implementacin del TAD PILA[NAT]. Consideramos que el TAD NAT est implementado como uno de los tipos predefinidos del lenguaje algortmico.
Implementacin del tipo
En primer lugar, tenemos que decidir cmo implementamos el tipo Pila[Nat], es decir, tenemos que elegir un tipo representante. En este caso elegimos representar las pilas como un registro
con dos campos, uno que es un vector donde se almacenan los elementos y otro que es un ndice
que apunta a la cima de la pila dentro del vector
Por ejemplo la representacin de

Apilar(2,Apilar(7,Apilar(5,PilaVaca)))


vendra dada por:

292

Tipos abstractos de datos

IndCima
Esta implementacin tiene el inconveniente de que impone a priori un lmite al tamao mximo de la pila.
El tipo representante escogido es:

const
limite=100;
tipo
Pila[Nat]=reg
espacio:Vector[1..limite]deNat;
indCima:Nat
freg


Para luego poder plantear una implementacin correcta de las operaciones, conviene comenzar definiendo dos cosas:

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:




RPila[Nat](xs)

def


xs:Pila[Nat]

0dxs.indCimadlimite

i:1didxs.indCima:RNat(xs.espacio(i))

La primera condicin es una notacin abreviada, en realidad lo que queremos decir es que xs
es un valor del tipo que representa a Pila[Nat], es decir, el registro.
Las condiciones que se les exigen a los representantes vlidos se denominan invariante de la representacin.
Notacin:

TRW:tiporepresentantedeltipoW
RVW:conjuntoderepresentantesvlidosdeltipoW{DW,eldominioasignado
altipoW
RW:invariantedelarepresentacindeltipoW


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


293

Tipos abstractos de datos

RVW={x:TRW|RW(x)}


En segundo lugar indicamos una forma de obtener cul es el valor abstracto representado por
cada valor concreto que sea un representante vlido. Para ello definimos de forma recursiva una
funcin de abstraccin de la siguiente forma:
paraxstalqueRPila[Nat](xs)
APila[Nat](xs)=defhazPila(xs.espacio,xs.indCima)

hazPila(v,0)=defPilaVaca
hazPila(v,n)=defApilar(ANat(v(n)),hazPila(v,n1))si1dnd
lmite

ntese que la funcin de abstraccin es una funcin que va de los representantes vlidos en
los trminos generados definidos, es decir, el conjunto de valores para W dado en el modelo de
trminos
AW:RVWoTGDW


Convenios:

Para simplificar, escribiremos v(n) en lugar de ANat(v(n))

En general, muchas veces simplificaremos omitiendo RV y AV cuando V 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 abstracto. Como se puede comprobar en el siguiente ejemplo:

xs:

ys:

indCima:
espacio:

indCima:

espacio:

donde se tiene que


xs,ysRVPila[Nat];xszys



 A(xs)
=Pila[Nat]

 A(ys)
=Pila[Nat]

Tipos abstractos de datos

294


 Apilar(Suc7(Cero),Apilar(Suc2(Cero),Apilar(Suc5(Cero),
PilaVaca)))

Normalmente abreviaremos la notacin no utilizando los valores abstractos para los tipos
primitivos, con lo que escribiramos:
Apilar(7,Apilar(2,Apilar(5,PilaVaca)))


El hecho de que dos representantes vlidos diferentes puedan representar al mismo valor abstracto implica que, en general, la igualdad entre representantes no representa la igualdad entre
valores abstractos. Es por ello, que, en general, no podemos utilizar la igualdad incorporada en
los lenguajes sino que cada TAD en cuya especificacin se incluya una operacin de igualdad,
deber implementarla explcitamente.
Implementacin de las operaciones
Lo que exigimos a las operaciones es que implementen el modelo de trminos. Este hecho lo
reflejaremos en la especificacin pre/post de los subprogramas que implementan a las operaciones del TAD. Para cada operacin supondremos como parte de su precondicin que los argumentos son representantes vlidos de valores abstractos. La postcondicin, a su vez, deber
garantizar que el resultado sea un representante vlido del resultado de la correspondiente operacin abstracta aplicada sobre los valores abstractos que representan los argumentos. Formalmente,
la especificacin pre/post para una funcin fC que implementa una operacin

f:(W1,,Wn)oW


del TAD T
funcfC(x1:TRW1,,xn:TRWn)devy:TRW;
{P0:RW1(x1)RWn(xn)DOMf(x1,,xn)LIMf(x1,,xn)}

{Q0:RW(y)AW(y)=TfA(AW1(x1),,AWn(xn))}


donde
TRWi, TRW son los tipos representantes
RWi, RW son los invariantes de la representacin
AWi, AW son las funciones de abstraccin
DOMf es la condicin de dominio.
Para xi : TRWi tales que RWi(xi)
DOMf(x1, , xn) def f(AW1(x1), , AWn(xn))
las condiciones de DOM pueden de venir expresadas como operaciones sobre trminos abstractos o como condiciones sobre el tipo representante
LIMf expresa restricciones adicionales impuestas por la implementacin

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 LIMf no es la condicin trivial cierto, decimos que la implementacin es parcialmente correcta.
Hay que tener cuidado con la aparicin de fA en la postcondicin. Cuando el TAD T tiene un
conjunto de generadoras libres, entonces no hay problema. Sin embargo, si el conjunto de generadoras no es libre, entonces en el modelo de trminos no est determinado cul de los trminos
generados pertenecientes a la misma clase de equivalencia es el resultado de la funcin abstracta.
Podramos interpretar que el resultado de fA es uno de los trminos generados que pertenecen
a la misma clase de equivalencia. Otra posibilidad sera haber definido el modelo de trminos
utilizando el conjunto de trminos cannicos en lugar del conjunto de trminos generados.
Ntese que el esquema que hemos presentado generaliza a todas las posibles operaciones de
un TAD, incluyendo generadoras, modificadoras y observadoras.
Veamos algunos ejemplos de cmo se implementaran las operaciones de las pilas de naturales,
con la representacin escogida para el tipo.
funcPilaVacadevxs:Pila[Nat];
{P0:}
inicio
xs.indCima:=0
{Q0:RPila[Nat](xs)APila[Nat](xs)=PILA[NAT]PilaVaca}
devxs
ffunc


Segn el esquema terico, en la postcondicin debera aparecer PilaVacaA, pero el valor de esta funcin es el trmino generado PilaVaca, que es el que hemos utilizado.
Vamos con la otra generadora:

funcApilar(x:Nat;xs:Pila[Nat])devys:Pila[Nat];
{P0:RNat(x)RPila[Nat](xs)xs.indCima<lmite}
var
llena:Bool;

inicio
ys:=xs;
llena:=ys.indCima=lmite;
sillena
entonces
error(Nosepuedeapilarporquelapilaestllena)%**
sino
ys.indCima:=ys.indCima+1;
ys.espacio(ys.indCima):=x
fsi
{Q0:RPila[Nat](ys)APila[Nat](ys)=PILA[NAT]Apilar(ANat(x),APila[Nat](xs))}
devys

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 normalmente.
Utilizando esta funcin como ejemplo, podemos representar grficamente cmo se puede establecer una relacin entre la funcin concreta y la abstracta a travs de las funciones de abstraccin:

       ApilarA
(TGDNat,TGDPila[Nat])      TGDPila[Nat]

ANat   APila[Nat]        APila[Nat]


(RVNat,RVPila[Nat])       RVPila[Nat]

       ApilarC

La funcin cima
funccima(xs:Pila[Nat])devx:Nat;
{P0:RPila[Nat](xs)notesVaca(A(xs))}
inicio
siesVaca(xs)  %xs.indCima==0
entonces
error(Lapilavacanotienecima)%*
sino
x:=xs.espacio(xs.indCima)
fsi
{Q0:RNat(x)ANat(x)=PILA[NAT]cimaA(APila[Nat](xs))}**
devx
ffunc

** podemos abreviar esta condicin, no preocupndonos por los elementos, como:



x=PILA[NAT]cima(APila[Nat](xs))


En esta operacin aparece una condicin en la precondicin de tipo DOMf, es decir, una restriccin impuesta por la especificacin.

Tipos abstractos de datos

297

En cuanto a la operacin desapilar:



funcdesapilar(xs:Pila[Nat])devys:Pila[Nat];
{P0:RPila[Nat](xs)xs.indCima>0}
inicio
sixs.indCima==0
entonces
error(nosepuededesapilardelapilavaca)
sino
ys.espacio:=xs.espacio;
ys.indCima:=xs.indCima1
fsi
{Q0:RPila[Nat](ys)APila[Nat](ys)=PILA[NAT]despilarA(APila[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;
{P0:RPila[Nat](xs)}
inicio
r:=xs.indCima==0
{Q0:RBool(r)ABool(r)=PILA[NAT]esVacaA(APila[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 modo que el resultado de la modificadora se lo asignamos a la misma variable que contena el
valor modificado.

La solucin radica en implementar las operaciones como procedimientos, que en las operaciones sobre las pilas daran lugar a las siguientes especificaciones:

procPilaVaca(sxs:Pila[Nat]);
{P0:}
{Q0:RPila[Nat](xs)APila[Nat](xs)=PILA[NAT]PilaVaca}
fproc

procApilar(ex:Nat;esxs:Pila[Nat]);
{P0:xs=XSRNat(x)RPila[Nat](xs)xs.indCima<lmite}
{Q0:RPila[Nat](xs)APila[Nat](xs)=PILA[NAT]Apilar(ANat(x),APila[Nat](XS))}
fproc

procdesapilar(esxs:Pila[Nat]);
{P0:xs=XSRPila[Nat](xs)xs.indCima>0}
{Q0:RPila[Nat](xs)APila[Nat](xs)=PILA[NAT]despilarA(APila[Nat](XS))}
fproc

La implementacin de cima se puede mantener como una funcin, siempre que el tipo de los
elementos lo permita. Y, de la misma forma, podemos mantener como funcin la operacin esVaca.
Es trivial modificar las implementaciones como funciones para llegar a la implementacin como procedimientos.
La implementacin como modelo dbil de la especificacin (no explicar)
El inters de construir una implementacin correcta con respecto a la especificacin radica en
que cualquier equivalencia que sea posible deducir de la especificacin tambin ser vlida para la
implementacin, salvo indefiniciones impuestas por las limitaciones de la implementacin de ah
la debilidad del modelo. Formalmente:


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


t = T t
siendo
var(t) var(t) = { x1 : W1, , xn:Wn }
t, t TDW
Para cualquier implementacin correcta de T puede asegurarse entonces que:
RW1(x1) RWn(xn) def tC def tC AW(tC) =T AW(tC)

Tipos abstractos de datos

299

o, escrito de otra forma:


{RW1(x1)RWn(xn)}
r:=tc;
r:=tc;
{AW(r)=TAW(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 abstractos las operaciones por operaciones concretas. El trmino resultante ser ejecutable, siempre
que las operaciones del TAD se implementen todas como funciones:

t{f(x,g(y,e))  tc{fC(x,gC(y,eC))

Esta idea habra que formalizarla con ms cuidado pues no hemos definido cul es el trmino
concreto, tC, asociado con un trmino cualquiera, t. Lo que se pretende expresar es que la ejecucin
de dos trminos equivalentes da resultados equivalentes.
Verificacin de la correccin de las operaciones
Como ha ocurrido en el ejemplo anterior, no vamos a hacer las verificaciones de que efectivamente las implementaciones de las operaciones son correctas con respecto a las especificaciones pre/post que hemos escrito.
La demostracin de la correccin de una implementacin pasara por la verificacin de todas y
cada una de las implementaciones de las operaciones. el problema radica en que la forma de las
funciones de abstraccin suele ser compleja y, por lo tanto, las verificaciones donde estn inmersas estas funciones tambin lo sern.
3.3.2

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:


RCjto[Nat](xs)

def


xs:Cjto[Nat]

0dxs.tamaodlimite

i:1didxs.tamao:RNat(xs.espacio(i))

La funcin de abstraccin:
paraxstalqueRCjto[Nat](xs)
ACjto[Nat](xs)=defhazCjto(xs.espacio,xs.tamao)

hazCjto(v,0)=def
hazCjto(v,n)=defPon(ANat(v(n)),hazCjto(v,n1))si1dndlmite


Ntese que como las constructoras no son libres, la funcin de abstraccin puede dar como
resultado trminos generados distintos pero equivalentes.
Vectores de naturales sin repeticin
El invariante de la representacin:



RCjto[Nat](xs)

def


xs:Cjto[Nat]

0dxs.tamaodlimite

i:1didxs.tamao:RNat(xs.espacio(i))




i,j:1di<jdxs.tamao:xs.espacio(i)/=xs.espacio(j)

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



RCjto[Nat](xs)

def


xs:Cjto[Nat]

0dxs.tamaodlimite

i:1didxs.tamao:RNat(xs.espacio(i))

i,j:1di<jdxs.tamao:xs.espacio(i)<xs.espacio(j)

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 operaciones ser ms sencilla pues no habr que preocuparse por equivalencias entre los trminos generados.
Implementacin de la operacin Pon
Vectores de naturales:
procPon(ex:Nat;esxs:Cjto[Nat]);
{P0:xs=XSR(xs)tamao<limite}%nosolvidamosdeR(x)ydelos
subndices
inicio
sitamao==limite
entonces
error(Nosepuedeinsertarelelemento)
sino
xs.tamao:=xs.tamao+1;
xs.espacio(xs.tamao):=x
fsi
{Q0:R(xs)A(xs)=PILA[NAT]Pon(x,A(XS))}
fproc

En la postcondicin tambin hemos simplificado la notacin, escribiendo Pon en lugar de


PonA y x en lugar de ANat(x).
Vectores de naturales sin repeticin. Vamos a suponer implementada una funcin de bsqueda con la siguiente especificacin:

funcbusca(x:Nat;v:Vector[1..limite]deNat;a,b:Nat)devr:
Bool;
{P0:1dadb+1dN+1}
{Q0:rli:adidb:v(i)=x}
ffunc


La operacin Pon

procPon(ex:Nat;esxs:Cjto[Nat]);
{P0:xs=XSR(xs)tamao<limite}
inicio
sitamao==limite
entonces
error(Nosepuedeinsertarelelemento)
sino
sibusca(x,xs.espacio,1,xs.tamao)
entonces

Tipos abstractos de datos

302

seguir
sino
xs.tamao:=xs.tamao+1;
xs.espacio(xs.tamao):=x
fsi
fsi
{Q0:R(xs)A(xs)=PILA[NAT]Pon(x,A(XS))}
fproc

Vectores de naturales sin repeticin y ordenados. Vamos a suponer implementada una funcin
de bsqueda binaria con la siguiente especificacin:

funcbuscaBin(x:Nat;v:Vector[1..limite]deNat;a,b:Nat)devp:
Nat;
r:Bool;
{P0:1dadb+1dN+1ord(v[a..b])}
{Q0:adp+1db+1v[a..p]dx<v[p+1..b]rli:adidb:v(i)
=x}
procPon(ex:Nat;esxs:Cjto[Nat]);
{P0:xs=XSR(xs)tamao<limite}
var
p:Nat;
encontrado:Bool;
inicio
sitamao==limite
entonces
error(Nosepuedeinsertarelelemento)
sino
<p,encontrado>:=buscaBin(x,xs.espacio,1,xs.tamao);
siencontrado
entonces
seguir
sino
desplazaDrch(v,p+1,xs.tamao);
xs.tamao:=xs.tamao+1;
xs.espacio(p+1):=x
fsi
fsi
{Q0:R(xs)A(xs)=PILA[NAT]Pon(x,A(XS))}
fproc


Y la operacin desplazaDrch:
procdesplazaDrch(esv:Vector[1..limite]deNat;ea,b:Nat);
{P0:v=V1dadb+1<N+1}

Tipos abstractos de datos

303

var
k:Nat;
inicio
parakdesdeb+1bajandohastaa+1hacer
v(k):=v(k1)
fpara
{P0:v[a+1..b+1]=V[a..b]v[1..a]=V[1..a]v[b+2..limite]=
V[b+2..limite]}
fproc


Se pueden analizar las diferencias entre estas tres representaciones en trminos de las operaciones Pon y quita.
3.3.3

Verificacin de programas que usan TADs

Al aplicar las reglas de verificacin a programas que usen TADs implementados funcionalmente, las operaciones de estos se pueden tratar del mismo modo que si fuesen operaciones predefinidas.
Esto quiere decir que, en lugar de usar las reglas de verificacin para llamadas a funciones, se
puede usar la regla de la asignacin. Adems, se pueden utilizar todas aquellas propiedades de las
operaciones del TAD en cuestin que se deduzcan de la especificacin de ste, ya que se supone
correcta la implementacin disponible para el mismo.
Si la implementacin del TAD es procedimental en lugar de funcional, se pueden aplicar las
mismas tcnicas reemplazando previamente (a efectos de la verificacin) las llamadas a procedimientos por llamadas a funciones.
Por ejemplo, supuesta una implementacin procedimental del TAD PILA[NAT] queremos
verificar:
var
x:Nat;
xs,ys:Pila[Nat]
{P:xs=Apilar(1,ys)}
x:=cima(xs);
desapilar(xs);
x:=2*x+1
{Q:x=3xs=ys}


Como paso intermedio reescribiremos el programa de manera que todas las operaciones de las
pilas se usen como funciones:
var
x:Nat;
xs,ys:Pila[Nat]
{P:xs=Apilar(1,ys)}
x:=cima(xs);
xs:=desapilar(xs);

304

Tipos abstractos de datos

x:=2*x+1
{Q:x=3xs=ys}

Finalmente, hacemos la verificacin con ayuda de asertos intermedios adecuados, usando las
propiedades especificadas para el TAD PILA[NAT]:
var
x:Nat;
xs,ys:Pila[Nat]
{P:xs=Apilar(1,ys)}
x:=cima(xs);
{R:x=1xs=Apilar(1,ys)}/*1*/
xs:=desapilar(xs);
{S:x=1xs=ys}/*2*/
x:=2*x+1
{Q:x=3xs=ys}/*3*/

Los tres pasos se verifican usando la regla de la asignacin:




Verificacin de /* 1 */









PILA










PILA






pmd(x:=cima(xs),R)
def(cima(xs))R[x/cima(xs)]
def(cima(xs))cima(xs)=1xs=Apilar(1,ys)
P






pmd(xs:=desapilar(xs),S)
def(desapilar(xs))S[xs/desapilar(xs)]
def(desapilar(xs))x=1desapilar(xs)=ys
R






pmd(x:=2*x+1,Q)
def(2*x+1)Q[x/2*x+1]
2*x+1=3xs=ys
S

Verificacin de /* 2 */

Verificacin de /* 3 */






aritmtica(i.e.,NAT)

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 invocacin a procedimientos o funciones variables locales.
La mayora de los tipos predefinidos de los lenguajes de programacin se realizan por medio
de estructuras estticas. Son ejemplos:

los 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 insuficiente

En suma, las estructuras estticas los vectores resultan demasiado rgidas para la implementacin de TADs cuyas operaciones sean capaces de generar valores cuya representacin necesite
una cantidad de espacio potencialmente ilimitado.

Tipos abstractos de datos

306

Estructuras de datos dinmicas

Se llama estructura de datos dinmica a una estructura que ocupa en memoria un espacio variable durante la ejecucin del programa, y que no se determina en tiempo de compilacin.
Esta clase de estructuras ofrecen la posibilidad de implementar muchos TADs de forma mucho ms flexible: no se malgasta espacio a priori, y el nico lmite en tiempo de ejecucin es la
cantidad total de memoria disponible.
La cuestin es de qu modo podemos crear y manejar esta clase de estructuras?
Punteros
Los punteros son el medio ofrecido por diversos lenguajes de programacin para construir y
manejar estructuras de datos dinmicas.
Las constantes y variables que conocemos hasta ahora son objetos que tienen asociado:

un nombre identificador

un espacio de memoria

un contenido, que es un valor de un cierto tipo fijo en el caso de las constantes y cambiante en el caso de las variables

Podemos utilizar aqu el conocido smil de las variables como recipiente o cajas de valores.
Una variable de tipo puntero tiene igualmente asignado un identificador, un espacio de memoria que es siempre del mismo tamao, independientemente de a dnde est apuntado el puntero y su valor es la direccin de otra variable:
El valor de un puntero1 es la direccin de otra variable, tal que:
El nombre de la variable apuntada por un puntero se puede obtener a partir del identificador del puntero. Al igual que en algunos lenguajes, nosotros obtendremos ese identificador colocando el carcter ^ detrs del identificador del puntero.

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.

307

Tipos abstractos de datos

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 resuelve los dos problemas que plantebamos anteriormente sobre las estructuras estticas: slo
solicitaremos el espacio imprescindible para los datos que en cada momento necesitemos representar.
Veamos con ms detalle cmo se declaran las variables de tipo puntero y cmo es posible ubicar y anular las variables a las que apuntan.
Declaracin de punteros
Para cualquier tipo W admitimos que puede formarse un nuevo tipo

puntero a W

Diremos que los valores de este tipo son punteros a variables de tipo W.
Las variables de tipo puntero se declaran como cualquier otra variable:

var
p:punteroaW;

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 W.
Por ejemplo:

tipo
PunteroEnt=punteroaEnt;
var
p:PunteroEnt;


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


p
p^

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 W) 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:punteroaW)

Este es un procedimiento sobrecargado porque admite como parmetro cualquier tipo de


puntero.
El efecto de ubicar es:

Crear una variable de tipo W

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 colgado 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 W.

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 predefinido

procliberar(ep:punteroaW)


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^.

310

Tipos abstractos de datos

Como conclusin de este apartado, vamos a seguir grficamente con detalle un ejemplo del
uso de las operaciones sobre punteros.

var
p,q:punteroaEnt;

ubicar(p);
p^:=3;p^:=2*p^;
ubicar(q);
q^:=5;q^:=p^+q^;
p:=q;  merror
liberar(p);
liberar(q);merror

Vemos cmo justo antes de empezar la ejecucin ya estn ubicadas las variables p y q, y que
ambas contienen inicialmente basura (#).
3.5.2

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 estructuras dinmicas, ya que ubica y libera podrn usarse en tiempo de ejecucin para aadir o
eliminar registros de la estructura.
Por ejemplo:

tipo
Enlace=punteroaNodo;
Nodo=reg
num:Ent;
sig:Enlace
freg;

Ntese la referencia adelantada al tipo al que apunta el puntero, esto es necesario porque estos
dos tipos son mutuamente dependientes. Normalmente este tipo de dependencia slo se permite
cuando uno de los dos tipos es un puntero.
Con este tipo de punteros podemos crear estructuras como

Tipos abstractos de datos

311

Como resulta evidente en el anterior ejemplo, para poder construir estructuras dinmicas como esta es necesario poder indicar de alguna forma el final de las estructuras. Esto se hace con un
valor especial de tipo puntero: el puntero vaco.
definimos nil como una constante que admite el tipo

punteroaW


para cualquier tipo W, y que representa a un puntero ficticio que no apunta a ninguna variable.
Por lo tanto:

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

3.5.3

312

Implementacin de TADs mediante estructuras de datos dinmicas

Los punteros son una tcnica de programacin de bajo nivel, cuyo uso no slo es bastante engorroso, sino que adems puede conducir a estructuras muy intrincadas nada impide pensar en
nodos con varios punteros cada uno, enlazndose entre s de forma arbitrariamente compleja.
Por consiguiente:
Advertencia: el uso indiscriminado de punteros es peligroso.
Disciplina: nosotros nos limitaremos a usar punteros dentro de los mdulos de implementacin de TADs. Esta restriccin permite sacar partido de la potencia de los punteros sin sacrificar las ventajas de una metodologa de la programacin basada en la abstraccin. Adems, est
de acuerdo con la metodologa de utilizar tipos ocultos. Dado que los punteros son un mecanismo para representar tipos, y puesto que slo permitimos acceder a la representacin interna
de los tipos en los mdulos que los implementan, es razonable prohibir el uso de punteros
fuera de este mbito.
Como ejemplo de la implementacin de TADs mediante estructuras dinmicas vamos a desarrollar una implementacin para las pilas.
Ejemplo: implementacin dinmica de las pilas
La representacin grfica de lo que pretendemos implementar:
p
en

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 ei que representa un elemento, y un
puntero que seala al nodo situado inmediatamente debajo de l en la pila.
Tipo representante

en-1


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


e2
e1

Ntese que con esta representacin el nico lmite impuesto a las dimensiones de
la pila es el tamao de la memoria de la computadora.
Invariante de la representacin
Sea p : Enlace, definimos



 RPila[Elem](p)

def


 p=nil

 (pznilubicado(p)

 RElem(p^.elem)RPila[Elem](p^.sig)

 pcadena(p^.sig))

Tipos abstractos de datos

313

donde la funcin auxiliar cadena permite especificar que no hay dos punteros apuntando al
mismo nodo:

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


La idea de este invariante de la representacin es que de p arranca una cadena de punteros finita, sin repeticiones y acabada en nil, cada uno de los cuales apunta a la representacin correcta de
un elemento. Lo que no veo es cmo se indica en este invariante que la cadena ha de terminar.
Funcin de abstraccin
Sea p tal que RPila[Elem](p)

APila[Elem](p)=PilaVaca sip=nil
APila[Elem](p)=Apilar(AElem(p^.elem),APila[Elem](p^.sig))sipznil


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


Implementacin de las operaciones
Podemos considerar dos posibilidades: implementacin como funciones o como procedimientos. 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];
{P0:Cierto}
inicio
xs:=nil
{Q0:R(xs)A(xs)=PILA[ELEM]PilaVaca}
devxs
ffunc


Tipos abstractos de datos

314

funcApilar(x:Elem;xs:Pila[Elem])devys:Pila[Elem];
{P0:R(x)R(xs)}
inicio
ubicar(ys);
ys^.elem:=x;
ys^.sig:=xs;
{Q0:R(ys)A(ys)=PILA[ELEM]Apilar(A(x),A(xs))}
devys
ffunc


Ntese que esta implementacin de apilar introduce comparticin de estructura entre xs e ys.
Otra opcin sera realizar una copia de xs. A continuacin haremos algunas consideraciones sobre estas cuestiones.

funcdesapilar(xs:Pila[Elem])devys:Pila[Elem];
{P0:R(xs)notesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(nosepuededesapilardelapilavaca)
sino
ys:=xs^.sig
fsi
{Q0:R(ys)A(ys)=PILA[ELEM]despilar(A(xs))}
devys
ffunc


Otra vez comparticin de estructura. Ntese tambin que no estamos anulando la cima de xs.

funccima(xs:Pila[Elem])devx:Elem;
{P0:R(xs)notesVaca(A(xs))}
inicio
siesVaca(xs)
entonces
error(Lapilavacanotienecima)
sino
x:=xs^.elem
fsi
{Q0:R(x)A(x)=PILA[ELEM]cima(A(xs))}
devx
ffunc

funcesVaca(xs:Pila[Elem])devr:Bool;
{P0:R(xs)}
inicio
r:=xs==nil

315

Tipos abstractos de datos

{Q0:rlesVaca(A(xs))}{RBool(r)ABool(r)=PILA[ELEM]esVaca(
APila[Elem](xs))
devr
ffunc
procerror(eCadena:Vector[1..lmite]deCar);
{P0:Cierto}
inicio
escribir(Cadena);
abortar
{Q0:Falso}
fproc
fmdulo

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 desapilamos 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:


p4

p2

b
a
Qu ocurre si ahora hacemos lo siguiente?

p:=PilaVaca();

f
p1

p3

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 generado basura en la memoria dinmica.
La solucin a este problema pasa, en primer lugar, por aadir al TAD una operacin que se
encargue de liberar todo el espacio ocupado por un valor de ese tipo. Habr que invocar a esta
operacin cada vez que deseemos reinicializar una variable con un nuevo valor. A esta operacin
la denominamos anular y en el caso de las pilas se implementara de la siguiente manera:

procanular(esxs:Pila[Elem]);
{P0:R(xs)}
var
p:Enlace;
inicio
itnotesVaca(xs)o
p:=xs;
xs:=xs^.sig;
liberar(p)
fit
{Q0:R(xs)A(xs)=PILA[ELEM]PilaVaca}
fproc

Sin embargo esta solucin no es completa pues los elementos tambin pueden ser estructuras
dinmicas, en cuyo caso sera necesario anularlas:

procanular(esxs:Pila[Elem]);
{P0:R(xs)}
var
p:Enlace;
inicio
itnotesVaca(xs)o
p:=xs;
xs:=xs^.sig;
ELEM.anular(p^.elem);
liberar(p)
fit
{Q0:R(xs)A(xs)=PILA[ELEM]PilaVaca}
fproc

De esta forma estamos exigiendo que los posibles parmetros de PILA[ELEM] implementen
una operacin anular. Esta restriccin deberamos expresarla en la definicin de la clase de tipos.
Puede plantearse que esa operacin no tiene sentido si el tipo se ha implementado con una es-

Tipos abstractos de datos

317

tructura esttica. Sin embargo, siguiendo la filosofa de que el usuario no debe conocer los detalles de la representacin interna, todos los TAD deberan exportar una operacin de anular que
en el caso de las implementaciones estticas no hara nada, pues al liberar el nodo ya se devuelve
el espacio ocupado por el valor. Otro problema son los tipos predefinidos, para los cuales suponemos que est definida la operacin anular y que se comporta como en el caso de las estructuras
estticas.
En cuanto al uso de anular
Se debe invocar anular antes de reinicializar una variable con otro valor. En el ejemplo anterior, se debera sustituir:

p1:=PilaVaca();


por

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


tambin es necesario invocar anular sobre las variables locales de los procedimientos justo antes de la salida del procedimiento, siempre y cuando la variable local no comparta estructura con
un parmetro de salida de dicho procedimiento.
Evidentemente slo se debe invocar a anular si la variable contena algn valor.
Pero, qu ocurre con la estructura compartida? En el anterior ejemplo, si anulsemos p1 antes
de asignarle otro valor, corromperamos el estado de todas las dems pilas, que estn compartiendo estructura con p1.
Por lo tanto, si permitimos la comparticin de estructura no podemos anular las variables, pero si no podemos anular las variables puede ocurrir que dejemos basura en la memoria dinmica.
cul es la solucin? Algunos lenguajes incluyen un mecanismo que se denomina recogida automtica de basura. Este mecanismo controla cuntas referencias hay a cada espacio de la memoria, de forma que cuando detecta que una zona de la memoria ya no es accesible desde ningn
puntero, la libera automticamente. En los lenguajes que tienen recogida de basura no es necesario preocuparse por las anulaciones, pues estas tendrn lugar automticamente. El inconveniente
de la recogida automtica de basura es que este mecanismo ralentiza la ejecucin de los programas; como es habitual, los mecanismos que facilitan la vida de los programadores tienen un cierto
coste computacional.
Si queremos mantener la implementacin funcional, la solucin por la que debemos optar entonces es la de realizar copias de los parmetros de las operaciones para evitar as la comparticin
de estructura aunque esta implementacin de las pilas permite la comparticin de estructura,
salvo por el problema de la anulacin, en algunas implementaciones de TADs es obligatorio realizar las copias de los parmetros porque si no los argumentos dejaran de ser representantes vlidos del tipo en cuestin. Sin embargo, esta solucin aumenta innecesariamente el coste de las
operaciones, en muchos casos de manera ridcula:
Funcionara bien en un caso como este:

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: implementar las generadoras y modificadoras como procedimientos, de forma que consideremos a los
valores como objetos mutables accesibles a travs de una nica referencia.
Realizacin de las operaciones como procedimientos
Planteamos la implementacin de las generadoras y modificadoras como procedimientos:

procPilaVaca(sxs:Pila[Elem]);
{P0:Cierto}
inicio
xs:=nil
{Q0:R(xs)A(xs)=PILA[ELEM]PilaVaca}
fproc

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


procdesapilar(esxs:Pila[Elem]);
{P0:xs=XSR(xs)notesVaca(A(xs))}

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
{Q0:R(xs)A(xs)=PILA[ELEM]desapilar(A(XS))}
fproc

En esta implementacin podramos dejar las operaciones cima y esVaca como funciones. Sin
embargo, esto podra dar problemas para la operacin cima si los elementos fuesen de un tipo que
estuviese implementado con memoria dinmica, porque dara lugar a comparticin de estructura,
la solucin sera devolver una copia, o que el cliente del TAD hiciera una copia si lo considerase
necesario. En el siguiente apartado comentaremos la necesidad de incorporar una operacin de
copia en todos los TADs.
En definitiva, no prohibimos definitivamente la comparticin de estructura ya que en algunos
casos para evitarla habra que incurrir en graves ineficiencias. Por ejemplo, un rbol se construye
a partir de la informacin de la raz y dos subrboles. Para evitar la comparticin, se deberan
realizar copias de los subrboles, sin embargo, en la mayora de los casos estos se han creado con
el nico propsito de formar parte de un rbol mayor. Como implementadores del TAD debemos informar del hecho y dejar en manos del cliente la decisin de si desea realizar copias o no.
3.5.4

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 ilimitada. Puede ocurrir que en un punto de la ejecucin ya no sea posible ubicar ms variables
dinmicas. Una forma de paliar este problema es comprobar antes de ubicar una nueva
variable si existe espacio suficiente, para en caso negativo no intentar ubicarla. Para saber
si la ubicacin tendr xito los lenguajes de programacin suelen incorporar operaciones
predefinidas para obtener la memoria disponible en Pascal MemAvail y el tamao de un
cierto tipo de datos en Pascal SizeOf.

Los errores en el uso de los punteros pueden provocar fallos irrecuperables cuelgues del
ordenador y difciles de detectar

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 destruccin de estructuras estticas, de la que se encarga automticamente el compilador.

Existen adems problemas especficos de la implementacin de TADs. Lo ideal es poder resolver estos problemas de forma transparente para los clientes del TAD de forma que ni siquiera
tengan que saber si la implementacin del TAD es esttica o dinmica. Sin embargo esto no es
realista, pues resolverlo de esta forma nos obligara a realizar algunas operaciones de manera innecesariamente ineficiente necesidad de realizar copias y anulaciones. La solucin por la que
optamos es dejar una parte de las decisiones en manos del cliente del TAD, que, por lo tanto,
debe ser consciente de si se trata de una representacin esttica o dinmica.
Almacenamiento de los datos en disco
Los datos representados por estructuras dinmicas no pueden escribirse y leerse en archivos
directamente, porque los valores de los punteros direcciones en cada ejecucin particular sern
diferentes.
La escritura de estructuras dinmicas se limitar a escribir la informacin los elementos sin
guardar los valores de los punteros. La lectura deber reconstruir las estructuras dinmicas, a partir de los datos ledos del archivo, solicitando nueva memoria dinmica donde almacenar los datos ledos.
Los TADs debern exportar operaciones de escritura y lectura de archivo siempre que estas
puedan resultar necesarias para los usuarios:

guardar(x,a)  recuperar(x,a)

Asignacin
Si x, y son variables de un mismo tipo abstracto, el efecto de la asignacin es diferente dependiendo de si el tipo representante es esttico o dinmico.

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 modificaciones de uno de ellos no afectarn al otro.
En una implementacin dinmica la asignacin implica comparticin de estructura
x:=y

causa que x, y apunten a la misma estructura dinmica. Los cambios realizados en la estructura a travs de una variable afectarn a la estructura puntada por la otra.
Por ejemplo:
var
p,q:Pila[Nat];

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 liberado al desapilar de q.
Una solucin a este problema sera imponer la disciplina de que no se pueden realizar asignaciones entre valores de TADs, y que en caso de ser necesarias se exporte en el TAD una operacin encargada de ello. Esta operacin tendra semntica de copia. Sin embargo, esta solucin es
demasiado restrictiva pues en algunos casos al cliente puede no preocuparle la comparticin de
estructura. Lo que debemos hacer entonces es exportar en todos los TADs una operacin que
permita realizar copias de los valores del TAD, para que as el cliente pueda elegir entre las dos
opciones.

proccopiar(ex:W;sy:W)


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]);
{P0:R(xs)}
var
p,q1,q2:Enalce;
inicio
siesVaca(xs)
entonces
PilaVaca(ys)
sino
ubicar(ys);
ys^.elem:=xs^.elem;%ELEM.copiar(xs^.elem,ys^.elem)
p:=xs^.sig;
q1:=ys;
itp/=nilo
ubicar(q2);
q2^.elem:=p^.elem;%ELEM.copiar(p^.elem,q2^.elem)
q1^.sig:=q2;
p:=p^.sig;

Tipos abstractos de datos

322

q1:=q2
fit;
q1^.sig:=nil
fsi
{Q0: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 parmetro de entrada x : W, y si el tipo representante de W 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 tanto 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 estructura:

var
p:Pila[Nat];
pp:Pila[Pila[Nat]];

PilaVaca(pp);
PilaVaca(p);
Apilar(1,p);
apilar(p,pp); mcomparticindeestructura
desapilar(p);  msehacorrompidopp

Una solucin sera realizar copias de los parmetros de entrada antes de incorporarlos a la estructura. Sin embargo, de nuevo, esto produce una ineficiencia intolerable. La solucin es advertir
de esta circunstancia a los clientes del TAD, para que sean ellos quienes decidan si conviene o no
realizar una copia de los parmetros antes de realizar la invocacin.

Tipos abstractos de datos

323

Igualdad
El tipo representante de un tipo abstracto puede no admitir el uso de la comparacin de igualdad. Y aunque la admitiese, la identidad entre valores del tipo representante en general no corresponder a la igualdad entre los valores abstractos representados. Este problema se presenta por
igual en las implementacin estticas como ya vimos en el ejemplo de la implementacin esttica
de las pilas como en las dinmicas; aunque en las estticas puede o no ocurrir mientras que en
las dinmicas sucede siempre.
La solucin radica en que el TAD exporte una operacin de comparacin:

funciguales(x,y:W)devr:Bool;

Puede ocurrir que la propia signatura de la especificacin algebraica del TAD incluyese una
operacin de igualdad. si esto no es as, y si el mdulo de datos tampoco la exporta, los usuarios
del TAD debern entender que no se les permite realizar comparaciones de igualdad.
Como ejemplo, vemos cmo se implementara la funcin iguales para el caso de las pilas:

funciguales(xs,ys:Pila[Elem])devr:Bool;
{P0:R(xs)R(ys)}
var
p,q:Enlace;
inicio
<p,q>:=<xs,ys>;
r:=cierto;
it(p/=nil)and(q/=nil)andro
r:=p^.elem==q^.elem;%r:=ELEM.iguales(p^.elem,q^.elem)
<p,q>:=<p^.sig,q^.sig>
fit;
r:=rand(p==nil)and(q==nil);
{Q0:rlA(xs)=A(ys)}
devr
ffunc

Generacin de basura
Como ya hemos indicado al comentar la implementacin dinmica de las pilas, puede ocurrir
que zonas de la memoria dinmica estn marcadas como ocupadas pero no sean accesibles desde
ningn puntero.
Esta situacin puede ocurrir con:

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 procedimiento. 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 variable afectada sea la nica referencia a la correspondiente estructura dinmica, es decir, si dicha
estructura no est compartida por ms de una referencia.
La solucin como ya vimos ms arriba radica en que el mdulo que implementa el TAD exporte una operacin que libere la estructura dinmica que representa al valor:

procanular(esx:W);

Para los TAD que implementen el tipo con una estructura esttica operacin anular no har
nada.
Los clientes del TAD debern invocar a la operacin anular:

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. Decimos que hay estructura compartida cuando dos o ms punteros comparten todo o una parte de
la estructura dinmica a la que apuntan. El problema es que los cambios realizados en la estructu-

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 comparticin de estructura:

asignaciones entre punteros

operaciones que construyen una estructura dinmica aadindole nodos a otra ya existente.

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 colaterales inesperados y el inconveniente es que se aumenta el nmero de copias y anulaciones
necesarias con lo que se degrada, en muchos casos de forma intolerable, la eficiencia de
los algoritmos.

Permitir la comparticin de estructura.

En los lenguajes con recogida automtica de basura no existe el problema de la anulacin. El usuario es responsable de realizar las copias que sea necesario.

En los lenguajes sin recogida automtica de basura el cliente del TAD es el responsable de su buen uso, realizando las copias y anulaciones que sea necesario.

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 operaciones que permitan: crear un multiconjunto vaco; aadir un nuevo elemento a un multiconjunto; 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 permitan: 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 dado 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;
{P0:1kNN1}
{Q0: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, vectores, 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 ejercicio 131(a).

138. Usa razonamiento ecuacional en NAT para demostrar la validez de las ecuaciones siguientes:
 (a) Suc(Cero)+Suc(Cero)=Suc(Suc(Cero))
 (b) Suc(Cero)*Suc(Cero)=Suc(Cero)
 (c) (Suc(Cero)*Suc(Cero)==Suc(Cero))=Cierto
 (d) (Suc(Suc(Cero))+Suc(Suc(Cero))Suc(Suc(Suc(Cero))))=Falso
139. Sea T un TAD con tipo principal W, Dos trminos t,t:W se llaman T-equivalentes (en
smbolos t=Tt), si la ecuacin t=t se puede deducir a partir de los axiomas ecua-

cionales de T.
(a) Comprueba que la T-equivalencia es una relacin de equivalencia.
(b) Encuentra varios trminos que sean NAT-equivalentes a Suc(Cero)).

140. Observa las especificaciones de los TADs BOOL, NAT y COMPLEJO. En cada caso,

clasifica las operaciones en tres clases: generadoras, modificadoras y observadoras.

141. Dado un TAD T con tipo principal W, se llaman trminos generados a aquellos trminos de

tipo W que se pueden construir usando nicamente operaciones generadoras de T (y trminos generados de otros TADs usados por T, si es necesario). Indica cules son los trminos
generados de los TADs BOOL, NAT y COMPLEJO.

142. Sea T un TAD con tipo principal W. Se dice que el conjunto de operaciones generadoras

de T es suficientemente completo si es cierta la siguiente condicin: para todo trmino cerrado t


de tipo W debe existir un trmino cerrado y generado t de tipo W que sea T-equivalente a t.
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). Interpreta las operaciones not, (and), y (or) en este modelo de manera que las ecuaciones de
BOOL se cumplan. Compara con el modelo de trminos de BOOL.

146. Sea T un TAD con tipo principal W cuya especificacin algebraica usa otro TAD S con

tipo principal s. Se dice que T protege a S si se cumplen las dos condiciones siguientes:

(i) T no introduce basura en S, es decir: para todo trmino cerrado t : s que se pueda
construir en T, existe un trmino generado t:s que ya se poda construir en S y
tal que t=Tt.
(ii) T no introduce confusin en S, es decir: Si t,t:s son trminos cerrados generados
que ya se podan construir en S, y se tiene t=Tt, entonces ya se tena t=St.


(a) Demuestra que el siguiente TAD que usa a BOOL no protege a BOOL, porque in-

troduce basura y confusin en BOOL:


tadPERSONA
 usa
  BOOL
 tipo
  Persona
 operaciones
  Carmen,Roberto,Luca,Pablo:oPersona     
gen*/

/*

  feliz:PersonaoBool             
obs*/
 ecuaciones
  feliz(Carmen)  =feliz(Roberto)
  feliz(Pedro) =feliz(Luca)
  feliz(Pedro) =notfeliz(Pablo)
  feliz(Luca) =Cierto
  feliz(Pablo) =Cierto
ftad

/*


(b) Construye un TAD que use a BOOL introduciendo basura, pero no confusin.
(c) Construye un TAD que use a BOOL introduciendo confusin, pero no basura.
(d) Comprueba que NAT, que usa a BOOL, deja a BOOL protegido.

Importante: Siempre que un TAD T se especifique usando otro TAD S ya especificado anteriormente, debe hacerse de manera que S quede protegido.
147. Usando NAT, especifica un TAD ENT que ofrezca el tipo Ent de los nmeros enteros,

con operaciones Cero, Suc, Pred, (+), (), (*) y (^) debe especificarse de modo que represente
la operacin de exponenciacin con base entera y exponente natural.

Tipos abstractos de datos

329

148. Sea T un TAD con tipo principal W. Se dice que las operaciones generadoras de T son no

libres si existen trminos generados no idnticos t y t de tipo W, que sean T-equivalentes.


Por el contrario, se dice que las operaciones generadoras de T son libres si no es posible encontrar trminos generados no idnticos t, t de tipo W que sean T-equivalentes.
(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 W. Se llama sistema de representantes cannicos a cualquier

subconjunto TCW del conjunto TGW de todos los trminos generados y cerrados de tipo W,
tal que para cada t TGW exista un nico t TCW tal que t =T t. Construye sistemas de
representantes cannicos para los TADs BOOL, NAT y ENT. Observa que en el caso de
ENT hay varias formas posibles de elegir los representantes cannicos, debido a que las
generadoras no son libres.

150. Especifica algebraicamente un TAD ENT-ORD que enriquezca el TAD ENT aadiendo

las operaciones de comparacin (==), (/=), (), (), (<) y (>).


Pista: Es necesario utilizar una operacin privada que reconozca los enteros no negativos. Esta
operacin noNeg: Ent o 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 siguientes clases de tipos:
(a) ANY: Clase formada por todos los TADs que tengan un tipo principal Elem.
(b) EQ: Clase formada por todos los TADs de la clase ANY que posean adems opera-

ciones de igualdad y desigualdad (==) Y (/=).


(c) ORD: Clase formada por todos los TADs de la clase EQ que posean adems operaciones 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 especificacin de un TAD genrico CJTO[E :: EQ] que ofrezca el tipo Cjto[Elem] formado por
los conjuntos (finitos) de elementos de tipo Elem (dado por el TAD parmetro E), junto
con operaciones para crear un conjunto vaco, aadir y quitar un elemento a un conjunto,
reconocer el conjunto vaco, y reconocer si un elemento dado pertenece a un conjunto dado.



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 anterior, tomando como parmetro actual NAT y ENT-ORD, respectivamente.



156. Enriquece la especificacin del TAD genrico CJTO[E :: EQ], aadiendo nuevas opera-

ciones para calcular uniones, intersecciones, diferencias y cardinales de conjuntos, y operaciones de comparacin (==), (/=) entre conjuntos. Naturalmente, debes aadir tambin las
ecuaciones necesarias para especificar el comportamiento de las nuevas operaciones. Las
ecuaciones de (==) deben construirse de manera que, dados dos trminos generados cualesquiera t,t:Cjto[Elem], se verifiquen las dos condiciones siguientes:
(i) t=t es deducible si y slo si es deducible (t==t)=Cierto.
(ii) t=t no es deducible si y slo si es deducible (t==t)=Falso.

157. Un TAD genrico puede tener ms de un parmetro formal. Formula una especificacin

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 especificacin del TAD incluye clusulas que determinan su dominio de definicin.

160. Siempre que un TAD tenga operaciones parciales, puede ocurrir que ciertos trminos

estn indefinidos. Un trmino t se considera definido siempre que def t sea deducible a partir
de la especificacin del TAD, e indefinido en caso contrario. Declara el TAD PILA[NAT] de
las pilas de nmeros naturales como ejemplar del TAD genrico del ejercicio anterior, y escribe varios ejemplos de trminos definidos e indefinidos de tipo Pila[Nat].

161. Para TADs con operaciones parciales, es necesario revisar algunos de los conceptos que

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 ocurrir 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 cada 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 ecuaciones condicionales con igualdad existencial.
OJO:

En cualquier especificacin que use ecuaciones condicionales, las ecuaciones de las condiciones deben usar igualdad existencial.
163. Enriquece el TAD de las pilas aadiendo dos nuevas operaciones, especificadas infor-

malmente como sigue:

fondo: Pila[Elem] o Elem


Observadora. Obtiene el elemento del fondo de una pila no vaca.
inversa: Pila[Elem] o 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]) o 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 exigiendo que el vector est ordenado por exponentes.
En cada caso, formula el invariante de la representacin, la funcin de abstraccin, y las especificaciones Pre/Post de los procedimientos y funciones que realicen las operaciones de POLI.
Intenta comparar la eficiencia de las dos implementaciones.
Implementacin modular de TADs
171. Plantea una implementacin modular del TAD COMPLEJO del ejercicio 137, en dos

fases:

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

333

Tipos abstractos de datos

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 implementacin 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:

176. Escribe ahora un fragmento de programa que libere el espacio ocupado por la estructura

creada en el ejercicio anterior. Ten en cuenta que cada llamada liberar el espacio de un solo registro.

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 finales 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 generales que hemos estudiado en la primera parte de la asignatura: complejidad en el caso peor y en
promedio; rdenes de magnitud asintticos; Estas mismas ideas se pueden aplicar para analizar por separado la complejidad de los algoritmos que implementan cada una de las operaciones
de un TAD.
En este apartado haremos algunas consideraciones sobre la complejidad espacial y nos concentraremos en el problema especfico de analizar la complejidad de algoritmos que utilizan
TADs, donde interesa obtener la complejidad de secuencias de llamadas a las operaciones del
TAD.
Complejidad en espacio
El anlisis del espacio tiene especial relevancia en las implementaciones de TADs, ya que stas
emplean muchas veces estructuras de datos muy voluminosas.
Ya hemos visto ejemplos donde distintas elecciones del tipo representante dan lugar a requisitos de espacio diferentes. Sin embargo lo ms habitual es que las distintas representaciones correspondan a un coste en espacio del mismo orden de magnitud asinttico. En tales casos, puede
interesar analizar con ms detalle el coste real teniendo en cuenta los valores concretos de las
constantes multiplicativas, ya que las optimizaciones solamente se podrn plantear a este nivel de
anlisis.
Para ms detalles puede consultarse [Fra94] pp: 110-111.
Complejidad temporal: anlisis de sucesiones de llamadas
Al analizar la complejidad de algoritmos que utilizan TADs interesa ocuparse de sucesiones de
llamadas, ya que, en muchos casos, las operaciones modifican el tamao del valor, de forma que
el coste temporal de una llamada se ve afectado por las llamadas que sobre ese mismo valor se
han realizado con anterioridad.
Dada una implementacin de un TAD con operaciones:
f1,f2,,fr

Tipos de datos con estructura lineal

335

Sean
T1,T2,,Tr

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

ti   tiempoconsumidoporlaisimallamada
ji   ndicedelaoperacindelaisimallamada(1djidr)
ni1,ni  tamaodelosdatosantesydespusdelaisimallamada
n

  max:1didm:ni

mj   nmerodellamadasafj(1djdr)


Entonces son correctas las siguientes estimaciones:



m

ti

i 1

d
m

Tji(ni1)

i 1

d(silasTjsonmontonas)
m

Tji(n)

i 1

=


m1T1(n)++mrTr(n)

Ntese que este anlisis estndar se puede realizar sin conocer los detalles de la implementacin, basta con que sean conocidas las funciones Tj(n).
Anlisis amortizado de sucesiones de llamadas
Los resultados del anlisis estndar que acabamos de realizar siempre dan una cota superior
correcta, pero muchas veces sta es demasiado grande. El anlisis estndar es demasiado pesimista, bsicamente debido a la simplificacin que supone sustituir los diferentes tamaos ni1 por el
mximo n de todos ellos. Esto podemos observarlo con un ejemplo.
Consideremos una variante del TAD PILA[E :: ELEM] donde la operacin desapilar se sustituya por

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


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:

fj
PilaVaca
Apilar
desapilarK
esVaca
cima

Tj(n)
O(1)
O(1)
O(n)
O(1)
O(1)

suponiendo que n mida el tamao de la pila. Ntese que desapilarK es O(n) porque el caso peor
se da cuando se desapilan todos los elementos.
Para una sucesin compuesta por m1 llamadas a Apilar y m2 llamadas a desapilarK, el anlisis
estndar nos dara como estimacin de la complejidad:

O(m1+m2n)

Esta estimacin se puede mejorar bastante si suponemos que la pila est inicialmente vaca
(n0=0) y tenemos en cuenta que, bajo este supuesto, el nmero total de elementos desapilados no
puede exceder al nmero total de elementos apilados. Este razonamiento demuestra que

O(m1)


es tambin una cota superior vlida en realidad sera O(2 m1).


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 sustituir los tiempos por tiempos amortizados definidos de la siguiente forma:

tiempoamortizado=deftiempo+'potencial
ai=ti+pipi1


siendo
ai 
ti 
pi 
pi1

tiempoamortizadodelaisimallamada
tiempoconsumidoporlaisimallamada
potencialdelosdatosdespusdelaisimallamada
potencialdelosdatosantesdelaisimallamada

Tipos de datos con estructura lineal

337

El potencial de una estructura de datos X se calcula como una funcin p(X) t 0 que debe definirse intentando medir la susceptibilidad de la estructura a la ejecucin de operaciones costosas;
si el potencial es mayor entonces es posible realizar operaciones ms costosas. En muchas ocasiones el potencial viene dado por el tamao de la estructura, en cuyo caso

pi=ni


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

m

ti

i 1

d
m

(ai+pi1pi)

i 1

=
m

ai)+(p0pm)

i 1

d(suponiendop0dpm)
m

ai

i 1

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

Aj(n)=defMax{aj(X)|tamaodeX=n}  (1djdr)


siendo

aj(X)=deftj(X)+p(fj(X))p(X) (1djdr)


siendo

tj(X)=deftiempodeejecucindefj(X)  (1djdr)


y donde estamos suponiendo que si X : W entonces fj : W o W, en realidad p(fj(X)) denota


al potencial de la estructura que representa al valor del tipo principal del TAD, despus de
realizar la operacin fj.


En muchos casos se verifica que



Aj(n)=Tj(n)+'p


Tipos de datos con estructura lineal

338

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

m

ai

i 1

d
m

Aji(ni1)

i 1

d(silasAjsonmontonas)
m

Aji(n)

i 1

=


m1A1(n)++mrAr(n)

El anlisis amortizado dar una estimacin del tiempo ms ajustada que el anlisis estndar si
la funcin potencial se elige adecuadamente. Volviendo al ejemplo de las pilas con operacin
desapilarK, podemos definir el potencial de una pila como su tamao n:

'p = 'n
fj

Tj(n)

Aj(n) = Tj(n) + 'n

PilaVaca
Apilar
desapilarK
esVaca
cima

O(1)
O(1)
O(n)
O(1)
O(1)

O(2)
O(2)
O(0)
O(1)
O(1)

Ntese que la complejidad amortizada de desapilarK es O(0) porque el coste de la operacin se


compensa con el incremento negativo del tamao:

AdesapilarK(n)=Max{k+(nk)n|0dkdn}=0


Volviendo al ejemplo de las m1 llamadas a Apilar y m2 llamadas a desapilarK, tenemos que el resultado anterior se podr aplicar siempre que el tamao final sea mayor o igual que el tamao
inicial en particular, esto ocurrir siempre que la pila est vaca inicialmente, en cuyo caso:

O(m12+m20)=O(m1)


como habamos deducido razonando informalmente.

Tipos de datos con estructura lineal

4.1.2

339

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;
{P0:P(x);Cota:t(x)}
var
x:T;y:S;
%Otrasposiblesdeclaracioneslocales
inicio
sid(x)
entonces
{P(x)d(x)}
y;=r(x)
{Q(x,y)}
sino
{P(x)d(x)}
x:=s(x);
{x=s(x)P(x)}
y:=f(x);
{x=s(x)Q(x,y)}
y:=c(x,y);
{Q(x,y)}
fsi
{Q0:Q(x,y)}
devy
ffunc


&
&
Intuitivamente, la ejecucin de una llamada recursiva y := f( x ) consiste en un bucle descendente seguido de un bucle ascendente:

&
&
&
&
x of( x )    c( x ,.)o y 


 p       

f(s( x ))   c(s( x ),.)

 p       

f(s2( x ))    c(s2( x ),.)




 p        
 

f(sn1( x ))  c(sn1( x ),.)

 p        

f(sn( x ))  o r(sn( x ))

&

&

&

&

&

&

&

&

Tipos de datos con estructura lineal

340

El bucle descendente reitera el clculo de parmetros para nuevas llamadas recursivas, mientras que el bucle ascendente reitera el procesamiento de los resultados devueltos por las llamadas.
La transformacin a iterativo se basa en la idea intuitiva que hemos expuesto antes: se utilizan
dos bucles compuestos secuencialmente, de forma que en el primero se van obteniendo las descomposiciones recursivas hasta llegar al caso base, y en el segundo se aplica sucesivamente la
&
funcin de combinacin. Vemos que los datos sni( x ) esto es, los parmetros actuales de las
diferentes llamadas deben estar disponibles durante la ejecucin del bucle ascendente. Segn la
&
forma cmo se obtengan los valores de sni( x ) en el bucle ascendente distinguimos tres casos,
que vienen dados por la forma de la funcin de descomposicin recursiva:

Caso especial. La funcin s de descomposicin recursiva, posee una funcin inversa cal&
culable s1. En este caso, los datos sni( x ) pueden calcularse sobre la marcha, usando s1.

Caso 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 sni( 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 sni( x ). Para cada parmetro real sni( x ) es suficiente con apilar un
&
&
&
dato ms simple u ni , elegido de tal forma que sea fcil calcular sni( x ) a partir de u ni
&
dato obtenido de la pila y sni+1( x ) obtenido en la iteracin anterior.

Caso especial
El esquema de la transformacin es el que se muestra en el ejercicio 186:

funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
{Inv.I:R(x,x);Cota:t(x)}
itd(x)o
{Id(x)}
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(x,x)y=f(x);Cota:m(x,x)}
itx/=xo
{Jxzx}
x:=s1(x);

Tipos de datos con estructura lineal

341

y:=c(x,y)
{J}
fit
{Jx=x}
{y=f(x)}
{Q0: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=sn(x)P(x)i:0di<n:(P(si(x))
d(si(x))))


que expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, es decir, despus de un cierto nmero de aplicaciones de la funcin s, de forma que todos los valores
intermedios cumplen la precondicin de la funcin y la barrera del caso recursivo.
La cota del algoritmo descendente es la misma que se haya utilizado para la funcin recursiva.


y la cota del bucle ascendente, queda




 m(x,x)
=def

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

funcfact(n:Nat)devr:Nat;
{P0:cierto}
var
n:Nat;
inicio
n:=n;
{I:R(n,n);C:n}
itn/=0o
n:=n1
fit;

Tipos de datos con estructura lineal

342

r:=0;
{J:R(n,n)r=n!;D:nn}
itn/=no
n:=n+1;
r:=r*n
fit
{Q0:r=n!}
devr
ffunc

Caso general
Este es el caso donde nos ayudamos de una pila para almacenar los sucesivos valores del
&
parmetro x . Como se muestra en el ejercicio 187:

funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;
xs:Pila[T];
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
PilaVaca(xs);
{Inv.I:R(xs,x,x);Cota:t(x)}
itd(x)o
{Id(x)}
Apilar(x,xs);
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(xs,x,x)y=f(x);Cota:tamao(xs)}
itNOTesVaca(xs)o
{JesVaca(xs)}
x:=cima(xs);
y:=c(x,y);
desapilar(xs)
{J}
fit
{JesVaca(xs)}
{y=f(x)}
{Q0:Q(x,y)}

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=sn(x)P(x)

i:0di<n:(P(si(x))d(si(x))si(x)=elem(i,
xs)))

donde elem(i, xs) indica el elemento que ocupa el lugar i en la pila xs, contando el elemento del fondo como elem(0, xs)

Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, cuyos parmetros reales, hasta x exclusive, estn apilados en xs


La cota del algoritmo ascendente viene dada por el tamao de la pila, que se va decrementando en 1 con cada iteracin

 tamao(xs)
=def

 nmerodeelementosapiladosenxs

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 representacin binaria de un nmero decimal:

funcbin(n:Nat)devr:Nat;
{P0:n=N}
inicio
si
n<2or:=n
n2or:=10*bin(ndiv2)+(nmod2)
fsi
{Q0:n=Nr=i:Nat:((ndiv2i)mod2)*10i}
devr
ffunc


La transformacin a iterativo:

funcbin(n:Nat)devr:Nat;
{P0:n=N}
var
n:Nat;
ns:Pila[Nat];
inicio
n:=n;

Tipos de datos con estructura lineal

344

PilaVaca(ns);
{I:R(ns,n,n);C:n}
itnt2o
Apilar(n,ns);
n:=ndiv2  %n:=s(n)
fit;
r:=n;
{J:R(ns,n,n)r=i:Nat:((ndiv2i)mod2)*10i;D:
tamao(ns)}
itNOTesVaca(ns)o
n:=cima(ns);
r:=10*r+nmod2;
 %r:=c(n,r)
desapilar(ns)
fit
{Q0:n=Nr=i:Nat:((ndiv2i)mod2)*10i}
devr
ffunc

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 iterativa de f segn se muestra en el ejercicio 189:

funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;u:Z;
us:Pila[Z];
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
PilaVaca(us);
{Inv.I:R(us,x,x);Cota:t(x)}
itd(x)o
{Id(x)}
u:=g(x);

Tipos de datos con estructura lineal

345

Apilar(u,us);
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(us,x,x)y=f(x);Cota:tamao(us)}
itNOTesVaca(us)o
{JesVaca(us)}
u:=cima(us);
x:=h(u,x);
y:=c(x,y);
desapilar(us)
{J}
fit
{JesVaca(us)}
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc


Donde el aserto R que aparece en ambos invariantes:




R(us,x,x)

def


n:Nat:(x=sn(x)P(x)

i:0di<n:(P(si(x))d(si(x))g(si(x))=
elem(i,us)))

donde elem(i, us) indica el elemento que ocupa el lugar i en la pila us, contando el elemento del fondo como elem(0, us)

Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, y que en us est apilada informacin suficiente para recuperar los parmetros
reales de dichas llamadas
Y la cota del bucle ascendente como en el caso anterior viene dada por el nmero de elementos de la pila

 tamao(us)
=def

 nmerodeelementosapiladosenus

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;
{P0:n=N}
var
n:Nat;
u:Bool;
us:Pila[Nat];
inicio
n:=n;
PilaVaca(us);
{I:R(us,n,n);C:n}
itnt2o
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:((ndiv2i)mod2)*10i;D:
tamao(us)}
itNOTesVaca(us)o
u:=cima(us);
si
uon:=2*n    n:=h(u,n)
NOTuon:=2*n+1
fsi
r:=10*r+nmod2;
 %r:=c(n,r)
desapilar(us)
fit
{Q0:n=Nr=i:Nat:((ndiv2i)mod2)*10i}
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 primero 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:oCola[Elem]             /*gen*/

 Aadir:(Elem,Cola[Elem])oCola[Elem]       /*gen*/

 avanzar:Cola[Elem]oCola[Elem]         /*mod*/

 primero:Cola[Elem]oElem           /*obs*/




 esVaca:Cola[Elem]oBool            /*obs*/
ecuaciones





 x:Elem:xs:Cola[Elem]:
 defavanzar(Aadir(x,xs))
 avanzar(Aadir(x,xs))=ColaVaca      siesVaca(xs)





 avanzar(Aadir(x,xs))=Aadir(x,avanzar(xs)) siesVaca(xs)
 defprimero(Aadir(x,xs))
 primero(Aadir(x,xs))=x        siesVaca(xs)


 primero(Aadir(x,xs))=primero(xs)    siesVaca(xs)

 esVaca(ColaVaca)=Cierto

 esVaca(Aadir(x,xs))=Falso

errores

 avanzar(Colavaca)

 primero(ColaVaca)
ftad


Tipos de datos con estructura lineal

4.2.2

348

Implementacin

Implementacin esttica basada en un vector


Tipo representante
Idea grfica:

ini
fin
Inicialmente ini y fin estn al principio del vector, pero tras un cierto nmero de invocaciones a
Aadir y avanzar llegaremos a un estado como el indicado en la figura.
El tipo representante:

const
lmite=100;
tipo
Cola[Elem]=reg
ini,fin:Nat;
espacio:Vector[1..lmite]deElem;
freg;

Invariante de la representacin

Dada xs : Cola[Elem]



R(xs)

def


1dxs.inidxs.fin+1dlmite+1

i:xs.inididxs.fin:R(xs.espacio(i))

Ntese que el aserto de dominio implica: 1 d xs.ini d lmite+1 0 d xs.fin d 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)=defhazCola(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)) siidf

Implementacin procedimental de las operaciones


Debido a los problemas, que ya comentamos en el caso de las pilas, que conlleva la implementacin funcional, pasamos directamente a la implementacin con procedimientos.

procColaVaca(sxs:Cola[Elem]);  % O(1)
{P0:Cierto}
inicio
xs.ini:=1;
xs.fin:=0;
{Q0:R(xs)A(xs)=COLA[ELEM]ColaVaca}
fproc

procAadir(ex:Elem;esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(x)R(xs)xs.fin<lmite}
inicio
sixs.fin==lmite
entonces
error(Colallena)
sino
xs.fin:=xs.fin+1;
xs.espacio(xs.fin):=x;  %comparticindeestructura
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]Aadir(A(x),A(XS))}
fproc

procavanzar(esxs:Cola[Elem]);  % O(1)
{P0:xs=XSR(xs)NOTesVaca(A(xs))%xs.inidxs.fin}
inicio
siesVaca(xs)
entonces
error(Colavaca)



Tipos de datos con estructura lineal

350

sino

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

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

funcesVaca(xs:Cola[Elem])devr:Bool; % O(1)
{P0:R(xs)}
inicio
r:=xs.ini==xs.fin+1
{Q0:A(r)=COLA[ELEM]esVaca(A(xs))}
devr
ffunc

Desventaja de esta implementacin


El espacio ocupado por la representacin de la cola se va desplazando hacia la derecha al ejecutar Aadir y avanzar.
Cuando fin alcanzar el valor lmite no se pueden aadir nuevos elementos, aunque quede sitio
en espacio[1..ini1].

ini

fin

Una solucin sera que cuando llegsemos a esta situacin se desplazasen todos los elementos
de la pila hacia la izquierda haciendo ini=1. Sin embargo esto penalizara en esos casos la eficiencia de la operacin Aadir O(n). Es mejor la solucin descrita a continuacin.

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 ini=fin+1 puede implicar que la cola est llena o vaca.
Tipo representante
Idea grfica:
ini

1
L

fin

Para que se vea mejor, se podra mostrar un ejemplo de cmo evolucionara la cola ante un
cierto nmero de invocaciones de Aadir y avanzar.
El tipo representante:

const
lmite=100;
tipo
Cola[Elem]=reg
ini,fin,tam:Nat;
espacio:Vector[1..lmite]deElem;
freg;

Usamos el campo tam para guardar el nmero de elementos almacenados en cada instante. Este campo es necesario para distinguir en la situacin ini=fin+1 si tenemos una cola vaca o llena.
en [Fran93] se describen otras formas de resolver este problema.
Para escribir el invariante de la representacin, la funcin de abstraccin y las operaciones vamos a utilizar dos funciones auxiliares pred y suc que restan y suman 1 mdulo lmite. En la implementacin de las operaciones podramos utilizar directamente la operacin mod, aunque para
ello sera mejor idea definir el vector entre 0 y lmite1. Sin embargo, en la funcin de abstraccin
nos hace falta una operacin de restar 1 que cumpla lmite1 = 1, para lo cual no disponemos de
ninguna funcin.
Por lo tanto definimos las siguientes funciones auxiliares:

Tipos de datos con estructura lineal

suc,pred:[1..lmite]o[1..lmite]


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

    1    sii=lmite


    i1   sii>1
pred(i)=def

    lmite  sii=1


Necesitamos definir tambin la aplicacin repetida de suc:



dadosi[1..lmite],jt0definimos


    i      sij=0
sucj(i)=def

    sucj1(suc(i))  sij>0


Convenimos por ltimo:



suc1(i)=defpred(i)
 %esnecesarioparaescribirelinvarianteR

Invariante de la representacin

Dado xs : Cola[Elem]



 R(xs)

def


 0dxs.tamdlmite

 1dxs.inidlmitexs.fin=sucxs.tam1(xs.ini)

 i:0didxs.tam1:R(xs.espacio(suci(xs.ini)))

Funcin de abstraccin

Dado xs: Cola[elem] tal que R(xs):



A(xs)=defhazCola(xs.esp,xs.tam,xs.fin)


hazCola(e,t,f)=defColaVaca  sit=0

352

Tipos de datos con estructura lineal

353

hazCola(e,t,f)=defAadir(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 comportamiento antes descrito.

procColaVaca(sxs:Cola[Elem]);  % O(1)
{P0:Cierto}
inicio
xs.ini:=1;
xs.fin:=lmite;
xs.tam:=0;
{Q0:R(xs)A(xs)=COLA[ELEM]ColaVaca}
fproc

Ntese que con esta inicializacin se cumple la parte del invariante de la representacin:

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


para lo cual hay que aplicar el convenio



suc1(i)=pred(i)


procAadir(ex:Elem;esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(x)R(xs)xs.tam<lmite}
inicio
sixs.tam==lmite
entonces
error(Colallena)
sino
xs.tam:=xs.tam+1;
xs.fin:=suc(xs.fin);
xs.espacio(xs.fin):=x;  %comparticindeestructura
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]Aadir(A(x),A(XS))}
fproc

procavanzar(esxs:Cola[Elem]);  % O(1)
{P0:xs=XSR(xs)NOTesVaca(A(xs))%xs.tam>0}
inicio
siesVaca(xs)

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
{Q0:R(xs)A(xs)=COLA[ELEM]avanzar(A(XS))}
fproc

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

funcesVaca(xs:Cola[Elem])devr:Bool; % O(1)
{P0:R(xs)}
inicio
r:=xs.tam==0
{Q0:A(r)=COLA[ELEM]esVaca(A(xs))}
devr
ffunc

Implementacin dinmica
La idea es tener acceso directo al principio y al final de la cola para as poder realizar las operaciones de manera eficiente.
Tipo representante
Idea grfica:

Tipos de datos con estructura lineal

355

xs
prim

x1

x2

ult

xn

Existe tambin la posibilidad de implementar el nodo cabecera con memoria dinmica, con esto conseguiramos que en aquellos lenguajes donde las funciones no pueden devolver tipos estructurados, devolvieran valores de tipo cola y en Modula-2 estaramos obligados si quisiramos
que el tipo fuese oculto.
El tipo representante:

tipo
Enlace=punteroaNodo;
Nodo=reg
elem:Elem;
sig:Enlace
freg;
Cola[Elem]=reg
prim,ult:Enlace
freg;

Invariante de la representacin

Dada xs : Cola[Elem]




R(xs)defRCV(xs)RCNV(xs)

escribimos por separado las condiciones que debe cumplir y una cola vaca y otra que no lo est.


 RCV(xs)

def




 xs.prim=nilxs.ult=nil
 RCNV(xs)

def



 xs.primznilxs.ultznil
 buenaCola(xs.prim,xs.ult)

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=qubicado(p)R(p^.elem)p^.sig=nil)

 (pzqubicado(p)R(p^.elem)




 pcadena(p^.sig)buenaCola(p^.sig,q))

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

Funcin de abstraccin

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



A(xs)=defColaVaca      siRCV(xs)
A(xs)=defhazCola(xs.prim,xs.ult) siRCNV(xs)


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

hazCola(p,q)=defAadir(A(p^.elem),ColaVaca)  sip=q
hazCola(p,q)=defponerPrimero(A(p^.elem),hazCola(p^.sig,q)) sipzq

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


es necesario el predicado auxiliar ponerPrimero porque el orden de recorrido del valor representante de la cola es el inverso al orden de insercin en la misma.

Implementacin procedimental de las operaciones

procColaVaca(sxs:Cola[Elem]);  % O(1)
{P0:Cierto}
inicio
xs.prim:=nil;
xs.ult:=nil;
{Q0:R(xs)A(xs)=COLA[ELEM]ColaVaca}
fproc


Tipos de datos con estructura lineal

procAadir(ex:Elem;esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(x)R(xs)xs.tam<lmite}
var
p:Enlace;
inicio
ubicar(p);
p^.elem:=x;  %comparticindeestructura
p^.sig:=nil;
siesVaca(xs)
entonces
xs.prim:=p;
xs.ult:=p
sino
xs.ult^.sig:=p;
xs.ult:=p
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]Aadir(A(x),A(XS))}
fproc

procavanzar(esxs:Cola[Elem]);  % O(1)
{P0:xs=XSR(xs)NOTesVaca(A(xs))%xs.tam>0}
var
p:Enlace;
inicio
siesVaca(xs)
entonces
error(Colavaca)
sino
p:=xs.prim;
xs.prim:=xs.prim^.sig;
sixs.prim==nil
entonces
xs.ult:=nil  %slotenaunelemento
sino
seguir
fsi;

%ELEM.anular(p^.elem)
liberar(p)
fsi
{Q0:R(xs)A(xs)=COLA[ELEM]avanzar(A(XS))}
fproc

funcprimero(xs:Cola[Elem])devx:Elem;  % O(1)
{P0:R(xs)NOTesVaca(A(xs))}
inicio
siesVaca(xs)

357

Tipos de datos con estructura lineal

358

entonces
error(Colavaca)
sino
x:=xs.prim^.elem %comparticindeestructura
fsi
{Q0:A(x)=COLA[ELEM]primero(A(xs))}
devx
ffunc

funcesVaca(xs:Cola[Elem])devr:Bool; % O(1)
{P0:R(xs)}
inicio
r:=xs.prim==nil
{Q0:A(r)=COLA[ELEM]esVaca(A(xs))}
devr
ffunc

Como cualquier implementacin de un TAD, debemos incluir una operacin que realice copias de los valores representados:

funccopiar(xs:Cola[Elem])devys:Cola[Elem];
{P0:R(xs)}
var
p,q,r:Enlace;%precorrelaoriginalyq,rlacopia
inicio
siesVaca(xs)
entonces
ys.prim:=nil;
ys.ult:=nil;
sino
p:=xs.prim;
ubicar(q);
ys.prim:=q;
q^.elem:=p^.elem; %q^.elem:=ELEM.copiar(p^.elem)
{I:pcadena(xs.prim)
lospunterosdecadena(ys.prim)apuntanacopiasdelosnodosde
cadena(xs.prim)hastap^inclusive
q=ltimo(cadena(ys.prim));
C:|cadena(p)|
}
itp^.sig/=nilo
p:=p^.sig;
ubicar(r);
q^.sig:=r;
q:=r;

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
{Q0: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:oDCola[Elem]             /*gen*/


 PonDetrs:(Elem,DCola[Elem])oDCola[Elem]      
gen*/

/*

 ponDelante:(Elem,DCola[Elem])oDCola[Elem]     /*mod*/

 quitaUlt:DCola[Elem]oDCola[Elem]        /*mod*/

 ltimo:DCola[Elem]oElem            /*obs*/


 quitaPrim:DCola[Elem]oDCola[Elem]        
mod*/

/*

 primero:DCola[Elem]oElem           /*obs*/




 esVaca:DCola[Elem]oBool/*obs*/
ecuaciones





 x,y:Elem:xs:DCola[Elem]:
 ponDelante(y,DColaVaca)=PonDetrs(y,DColaVaca)
 ponDelante(y,PonDetrs(x,xs))=PonDetrs(x,ponDelante(y,xs))





 defquitaUlt(xs)siesVaca(xs)
 quitaUlt(PonDetrs(x,xs))=xs

 defltimo(xs)siesVaca(xs)

Tipos de datos con estructura lineal

360

 ltimo(PonDetrs(x,xs))=x














 defprimero(xs)siesVaca(xs)
 primero(PonDetrs(x,xs))=x    siesVaca(xs)

defquitaPrim(xs)siesVaca(xs)
quitaPrim(PonDetrs(x,xs))=DColaVaca  siesVaca(xs)
quitaPrim(PonDetrs(x,xs))=PonDetrs(x,quitaPrim(xs))
                     siesVaca(xs)


 primero(PonDetrs(x,xs))=primero(xs)siesVaca(xs)

 esVaca(DColaVaca)=Cierto

 esVaca(PonDetrs(x,xs))=Falso

errores

 quitaUlt(DColavaca)

 ltimo(DColaVaca)

 quitaPrim(DColavaca)

 primero(DColaVaca)
ftad


Se podra elegir como generador PonDetrs o ponDelante. Hemos elegido PonDetrs para que la
especificacin sea ms parecida a la de las colas normales, ya que PonDetrs { Aadir.
Se puede establecer de manera evidente la siguiente correspondencia entre las operaciones de
las colas y las de las colas dobles:

COLA[ELEM]
ColaVaca
Aadir
avanzar
primero
esVaca

4.3.2

DCOLA[ELEM]
DColaVaca
PonDetrs
quitaPrim
primero
esVaca
ponDelante
quitaUlt
ltimo

Implementacin

Bsicamente podemos considerar las mismas implementaciones utilizadas para las colas, a excepcin de la primera que, como ya vimos, no es muy conveniente.
Implementacin basada en un vector circular
Tipo representante
Igual que en el caso de las colas ordinarias, dndole le nombre DCola[Elem].

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)=defhazDCola(xs.esp,xs.tam,xs.fin)


hazDCola(e,t,f)=defDColaVaca sit=0
hazDCola(e,t,f)=defPonDetrs(A(e(f)),hazDCola(e,t1,pred(f)))sit>
0

Implementacin procedimental de las operaciones


Todas las operaciones consumen tiempo O(1) en el caso peor. Aquellas operaciones que tienen una anloga en COLA[ELEM] se implementan igual; el resto de manera anloga.

procponDelante(ex:Elem;esxs:DCola[Elem]); % O(1)
{P0:xs=XSR(x)R(xs)xs.tam<lmite}
inicio
sixs.tam==lmite
entonces
error(Colallena)
sino
xs.tam:=xs.tam+1;
xs.ini:=pred(xs.ini);
xs.espacio(xs.ini):=x;  %comparticindeestructura
fsi
{Q0:R(xs)A(xs)=DCOLA[ELEM]ponDelante(A(x),A(XS))}
fproc
procquitaUlt(esxs:Cola[Elem]); % O(1)
{P0:xs=XSR(xs)NOTesVaca(A(xs))%xs.tam>0}
inicio
siesVaca(xs)
entonces
error(Colavaca)
sino

Tipos de datos con estructura lineal

362


%ELEM.anular(xs.espacio(xs.fin))
xs.tam:=xs.tam1;
xs.fin:=pred(xs.fin)
fsi
{Q0:R(xs)A(xs)=DCOLA[ELEM]quitaUlt(A(XS))}
fproc

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)
{P0:x=XSR(x)R(xs)}
%algoritmoanlogoaAadirenCOLA[ELEM]
{Q0:R(x)A(x)=DCOLA[ELEM]ponDelante(A(x),A(XS))}
fproc

procltimo(xs:DCola[Elem])devx:Elem; %O(1)
{P0:R(xs)NOTesVaca(A(xs))}
%algoritmoanlogoaprimeroenCOLA[ELEM]
{Q0: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)
{P0:xs=XSR(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.ulto
p:=p^.sig
fit;
p^.sig:=nil;
ELEM.anular(xs.ult^.elem);
liberar(xs.ult);
xs.ult:=p
fsi
fsi
{Q0:R(xs)A(xs)=DCOLA[ELEM]quitaUlt(A(XS))}
fproc

Para conseguir que tambin esta operacin tenga complejidad O(1) se puede utilizar la siguiente representacin.
Implementacin dinmica con encadenamiento doble
Tipo representante
xs
prim

x1

x2

ult

xn-1

xn

Con esta representacin tenemos acceso en tiempo constante al penltimo elemento. El tipo
representante queda:

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

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:oLista[Elem]

/*gen*/ %[]


 Cons:(Elem,Lista[Elem])oLista[Elem]
[x/xs]

/*gen*/ %

/*mod*/ %[x]

 [_]:ElemoLista[Elem]


 ponDr:(Lista[Elem],Elem)oLista[Elem]
[xs\x]

/*mod*/ %




/*obs*/

 primero:Lista[Elem]oElem

El resultado de quitarle el primer elemento a la lista




 resto:Lista[Elem]oLista[Elem]

/*mod*/




 ltimo:Lista[Elem]oElem

/*obs*/

El resultado de quitarle el ltimo elemento a la lista




 inicio:Lista[Elem]oLista[Elem]

 (++):(Lista[Elem],Lista[Elem])oLista[Elem] /*mod*/

 nula?:Lista[Elem]oBool

/*obs*/

 (#):Lista[Elem]oNat

/*obs*/




 (!!):(Lista[Elem],Nat)oElem

/*obs*/

/*mod*/

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)dnd(#xs)
 Cons(x,xs)!!Suc(Cero)
=x

 Cons(x,Cons(y,ys))!!Suc(Suc(n))= Cons(y,ys)!!Suc(n)


errores

 primero(Nula)

 resto(Nula)

 ltimo(Nula)

 inicio(Nula)

 xs!!nsi(n==Cero)OR(n>(#xs))
ftad
Notacioneshabitualespararepresentarlistas(siendoxi:Elem)




 [x1,,xn]
 [x1,,xn/xs]
 [xs\x1,,xn]

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 operaciones:
Operaciones de LISTA Operaciones de DCOLA
Nula
DColaVaca
Cons
ponDelante
[_]

ponDr
PonDetrs
primero
primero
resto
quitaPrim
ltimo
ltimo
inicio
quitaUlt
( ++ )

nula?
esVaca
(# )

( !! )

4.4.2

Implementacin

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 procedimental 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:

x1

xs

x2

xn

El problema es que esta representacin slo es adecuada para implementar eficientemente las
operaciones que acceden a una lista por su extremo inicial. en particular, se pueden implementar
en tiempo O(1) las operaciones: Nula, Cons, primero, resto, nula?. Sin embargo, no permite una implementacin eficiente de las operaciones que accede a una lista por el final.
Representacin dinmica como la utilizada para las colas
La idea grfica de esta representacin
xs
prim

x1

x2

ult

xn

Tipo representante

tipo
Enlace=punteroaNodo;
Nodo=reg
elem:Elem;
sig:Enlace
freg;
Lista[Elem]=reg
prim,ult:Enlace
freg;

Invariante de la representacin
Es exactamente igual que el invariante de la representacin que vimos para las colas. Si la lista
est vaca entonces prim y ult valen nil. Si no est vaca, entonces ambos son distintos de nil, y se
debe cumplir que desde el nodo apuntado por prim se llegue al nodo apuntado por ult, que ser el
ltimo de la cadena, donde no se introduce circularidad y donde todos los punteros estn ubicados y los elementos son representantes vlidos.

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)=defNula        siRLV(xs)
A(xs)=defhazLista(xs.prim,xs.ult)  siRLNV(xs)

para construir la lista tenemos que aadir elementos hasta que lleguemos a una lista con un solo elemento, donde se cumple p=q
hazLista(p,q)=defCons(A(p^.elem),Nula)
 sip=q
hazLista(p,q)=defCons(A(p^.elem),hazLista(p^.sig,q)) sipzq

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 descender a O(1) usando una representacin doblemente enlazada.
Excepto la operacin ( ++ ) el resto de operaciones que no tienen anloga en DCOLA las implementaremos como funciones, y es por eso que nos ocuparemos de ellas en el siguiente apartado.
Para ( ++ ) se puede plantear una implementacin procedimental que modifica su primer argumento y consume tiempo O(1):

ys

xs
prim

x1

x2

prim

ult

xn

y1

y2

ult

yn

Tipos de datos con estructura lineal

369

xs
prim

ult

ys
prim

x1

x2

xn

y1

y2

ult

yn

procconc(esxs:Lista[Elem];eys:Lista[Elem])
{P0:R(xs)R(ys)xs=XSys=YS}
inicio
sinula?(xs)
entonces
xs:=ys

sino
sinula?(ys)
entonces
seguir
sino
xs.ult^.sig:=ys.prim;
xs.ult:=ys.ult
fsi
fsi
{Q0:R(xs)R(ys)A(xs)=LISTA[ELEM]A(XS)++A(ys)}
fproc

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 anulacin, es muy similar a la que implementamos para las pilas:

procanula(esLista:Lista[Elem]);
{P0: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/=nilo
q:=p^.sig;
ELEM.anular(p^.elem); %slosisehaceanulacinprofunda
liberar(p);
p:=q
fit;
xs.prim:=nil;
xs.ult:=nil
fsi
{Q0:R(xs)A(xs)=LISTA[ELEM]NulalosnodosaccesiblesdesdeXS
atravsdepunterosdetipoEnlace,sehanliberado}
fproc

Implementacin funcional de las operaciones


Las operaciones observadoras de LISTA que tienen una anloga en DCOLA se pueden implementar 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 observadoras sin anloga en DCOLA. Vamos a permitir la compartir de estructura.

funcNula()devxs:Lista[Elem];  /*O(1)*/
{P0:cierto}
inicio
xs.prim:=nil;
xs.ult:=nil
{Q0:R(xs)A(xs)=LISTA[ELEM]Nula}
devxs
ffunc

funcCons(x:Elem;xs:Lista[Elem])devys:Lista[Elem];/*O(1)
{P0:R(x)R(xs)}
var
nuevo:Enlace;
inicio
ubicar(nuevo);
ys.prim:=nuevo;
nuevo^.elem:=x;%comparticindeestructura

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
{Q0:R(ys)A(ys)=LISTA[ELEM]Cons(A(x),A(xs))}
devys
ffunc


ys

prim

ult

xs
prim

x1

x2

ult

xn

funcponDr(xs:Lista[elem];x:Elem)devys:Lista[Elem]/*O(n)por
copia*/
{P0:R(x)R(xs)}
var
nuevo:Enlace;
inicio
ys:=copia(xs);
ubicar(nuevo);
nuevo^.elem:=x;%comparticindeestructura
nuevo^.sig:=nil;
sinula?(ys)
entonces
ys.prim:=nuevo
sino
ys.ult^.sig:=nuevo
fsi;
ys.ult:=nuevo;
{Q0:R(ys)A(ys)=LISTA[ELEM]ponDr(A(xs),A(x))}
devys
ffunc


Ntese que en este caso es necesaria la copia para que se mantenga el invariante de la representacin de xs el valor nil del campo sig del ltimo nodo.

Tipos de datos con estructura lineal

372

ys

xs
prim

x1

prim

ult

x2

x1

xn

x2

ult

xn


funcresto(xs:Lista[Elem])devys:Lista[Elem]/*O(1)*/
{P0:R(xs)NOTnula?(A(xs))}
inicio
sinula?(xs)
entonces
error(Listavaca)
sino
sixs.prim==xs.ult
entonces
ys.prim:=nil;
ys.ult:=nil


sino
ys.prim:=xs.prim^.sig;
ys.ult:=xs.ult
fsi
fsi
{Q0:R(ys)A(ys)=LISTA[ELEM]resto(A(xs))}
devys
ffunc


xs

prim

ult

ys
prim

x1

x2

ult

xn




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


Tipos de datos con estructura lineal

373

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


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


xs
ys
prim

x1

x2

ult

prim

xn-1

xn

x1

x2

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


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

ult

xn-1

Tipos de datos con estructura lineal

374

xs
prim

x1

x2

ult

xn
ys

xs
prim

x1

x2

prim

ult

xn

y1

y2

ult

yn

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


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)*/
{P0:R(xs)1did#(A(xs))}
var
j:Nat;
aux:Enlace;
inicio
j:=i;
aux:=xs.prim;
itj>1andaux/=nilo
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
{Q0:A(x)=LISTA[ELEM]A(xs)!!i}
devx
ffunc


Como conclusin del apartado de implementacin mostramos una tabla con la complejidad de
las distintas operaciones:


Operacin
Nula
Cons
[_]
ponDr
primero
resto
ltimo
inicio
( ++ )
nula?
(# )
( !! )

4.4.3

Procedimiento
O(1)
O(1)
O(1)
O(1)

O(1)

O(n)
O(1)

Funcin
O(1)
O(1)
O(1)
O(n)
O(1)
O(1)
O(1)
O(n)
O(n)
O(1)
O(n)
O(i)

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 operaciones 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;
{P0:R(xs)}
inicio
sinula?(xs)

Tipos de datos con estructura lineal

376

entonces
n:=0
sino
n:=1+longitud(resto(xs))
fsi
{Q0: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)*/
{P0:R(xs)1did#(A(xs))}
inicio
si
nula?(xs)ORi==0oError(elementoinexistente)
NOTnula?(xs)ANDi==1ox:=primero(xs)  %comparticinde
estructura
NOTnula?(xs)ANDi>1ox:=consultar(resto(xs),i1)
fsi
{Q0: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];
{P0:R(xs)R(ys)}
inicio
sinula?(xs)
entonces
zs:=ys
sino
zs:=Cons(primero(xs),conc(resto(xs),ys))
fsi
{Q0: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];
{P0:cierto}
si
n==0ous:=Nula
n>0ANDnula?(xs)ous:=Nula
n>0ANDNOTnula?(xs)ous:=Cons(primero(xs),coge(n1,resto(xs)
))
fsi
{Q0:#us=min(n,#xs)i:1did#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 aparecer en nuestros asertos.

functira(n:Nat;xs:Lista[Elem])devvs:Lista[Elem];
{P0:cierto}
si
n==0ovs:=xs
n>0ANDnula?(xs)ovs:=Nula
n>0ANDNOTnula?(xs)ovs:=tira(n1,resto(xs))
fsi
{Q0:#vs=max(0,#xsn)i:1did#vs:vs!!i=xs!!(n+i)}
devus
ffunc

En ambos casos tenemos disminucin del tamao del problema por sustraccin de 1, con una
nica llamada recursiva. La combinacin de los resultados consume tiempo constante. Por lo
tanto la complejidad es O(n). Podramos pensar que esa n es el min del parmetro n y #xs, sin
embargo para un valor de n y xs dados el caso peor se da cuando #xs t n.
Inversa de una lista
La idea naive para implementar la operacin de inversin es una funcin recursiva de la forma:

(CD)inv([])=[]
(CR)inv([x/xs])=inv(xs)++[x]

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];
{P0:cierto}
inicio
sinula?(xs)
entoncesys:=Nula
sinoys:=inv(resto(xs))++[primero(xs)]
fsi
{Q0::1did(#xs):xs!!i=ys!!(#xs)i+1}
ffunc


Donde se han utilizado las operaciones de las listas como operadores, porque en el enunciado
de los ejercicios todava no habamos escrito sus implementaciones y no les habamos dado nombre. En realidad, habra que reescribirla utilizando la notacin habitual algunos lenguajes, como
C++, permiten definir funciones como operadores.
Esta implementacin tiene complejidad O(n2), siendo n = #xs, porque la concatenacin implementada funcionalmente tiene complejidad O(n) debido a la copia del primer argumento:
c0

si 0 n < b

T(n) =
a T(nb) + c nk
donde b = 1, a = 1, k = 1

si n b

O(nk+1)

si a = 1

O(an div b)

si a > 1

T(n)

por lo tanto T(n) O(n2)


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];
{P0:cierto}
{Q0: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];
{P0:cierto}
sinula?(xs)
entonces
ys:=as
sino
ys:=invSobre(Cons(primero(xs),as),resto(xs))
fsi
{Q0: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]
{P0:ordenada(xs)ordenada(ys)}
{Q0: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])]sixdy
  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 plegadodesplegado:

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) 








(CR) 










 
 =
 =

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

 
 =
 =

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



xdy




=
=
=
=

 
x>y =

mezcla(as,[x/xs],[y/ys])
as++mezcla([x/xs],[y/ys])
as++[x/mezcla(xs,[y/ys])]
as++[x]++mezcla(xs,[y/ys])
mezcla(ponDr(as,x),xs,[y/ys])
mezcla(as,[x/xs],[y/ys])
mezcla(ponDr(as,y),[x/xs],ys)

Esta implementacin tendr complejidad O(n), con n = #xs + #ys, siempre y cuando la concatenacin de los casos base y ponDr de la descomposicin recursiva tengan complejidad O(1),
lo cual slo se verifica si utilizamos una implementacin procedimental de las operaciones.
El predicado permutacin que aparece en la postcondicin se puede especificar de la siguiente
manera:



 permutacin(us,vs)

def

 x:Elem:(#i:1did#us:us!!i=x)=
(#i:1did#vs:vs!!i=x)

4.5 Secuencias
Son colecciones de elementos con una posicin distinguida dentro de esa coleccin. Es el
TAD que utilizamos para poder hacer recorridos sobre una coleccin de elementos. Insistir aqu
en la idea de que los TAD no son objetos monolticos y que si, por ejemplo, me interesase una
coleccin de elementos con acceso por posicin y que adems se pudiese recorrer, podra escribir
la especificacin de un TAD que mezclase incluyese operaciones de LISTA y SECUENCIA.
Ntese tambin que si quisiese recorrer una Lista[Elem], tendra que hacerlo con la operacin ( !!
), lo que llevara a un recorrido de complejidad cuadrtica; mientras que con las operaciones de
las secuencias podemos conseguir complejidad lineal.

Tipos de datos con estructura lineal

4.5.1

381

Especificacin

La especificacin se basa en la idea de que podemos representar las secuencias como dos listas, siendo el punto de inters el punto divisorio entre las dos partes. Ntese que esto es ligeramente distinto de la primera idea que hemos presentado, donde el punto de inters es un
elemento de la secuencia, que, en este planteamiento es el primer elemento de la parte derecha de
la secuencia.
Aparecen en esta especificacin dos elementos nuevos relacionados: el uso privado de un
TAD y la definicin de una generadora privada. El uso privado indica que los clientes de este
TAD no tiene que conocer el uso que ste hace de LISTA[ELEM]. La generadora privada se
introduce para llevar a cabo la idea de representar las secuencias como dos listas; entonces se
expresa el comportamiento de las generadoras pblicas, que son libres, en trminos de la generadora privada. Para expresar comportamiento de las operaciones no generadoras no se utilizan las
generadoras pblicas sino la privada.

tadSEC[E::ANY]

usa

 BOOL

usaprivadamente

 LISTA[E]

tipo

 Sec[Elem]

operaciones



 Crea:oSec[Elem]

/*gen*/

Generadora que inserta un elemento a la izquierda del punto de inters como el ltimo de la
parte izquierda



 Inserta:(Sec[Elem],Elem)oSec[Elem]

/*gen*/

Modificadora que elimina el elemento a la derecha del punto de inters el primero de la parte
derecha. Es parcial porque la parte derecha puede estar vaca; esta es tambin la razn que
explica la parcialidad de las dos siguientes operaciones



 borra:Sec[Elem]oSec[Elem]

/*mod*/

Devuelve el elemento situado a la derecha del punto de inters




 actual:Sec[Elem]oElem

/*obs*/

Desplaza un lugar a la derecha el punto de inters. Es una generadora porque dos secuencias
con el mismo contenido pero distinto punto de inters son secuencias diferentes



 Avanza:Sec[Elem]oSec[Elem]

/*gen*/

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





 Reinicia:Sec[Elem]oSec[Elem]

/*gen*/

 vaca?:Sec[Elem]oBool

/*obs*/

Tipos de datos con estructura lineal

382




 fin?:Sec[Elem]oBool
operacionesprivadas

 S:(Lista[Elem],Lista[Elem])oSec[Elem]      /*gen*/





 piz,pdr,cont:Sec[Elem]oLista[Elem]        /*obs*/

/*obs*/

ecuaciones


 s:Sec[Elem]:iz,dr:Lista[Elem]:x:Elem:

 Crea
=S(Nula,Nula)

 Inserta(S(iz,dr),x)
=S(iz++[x],dr)

 fin?(S(iz,dr))
=nula?(dr)

 defborra(s)siNOTfin?(s)

 borra(S(iz,Cons(x,dr))) =S(iz,dr)

 defactual(s)siNOTfin?(s)

 actual(S(iz,Cons(x,dr))) =x

 defAvanza(s)siNOTfin?(s)

 Avanza(S(iz,Cons(x,dr))) =S(iz++[x],dr)

 Reinicia(S(iz,dr))
=S(Nula,iz++dr)

 vaca?(S(iz,dr))
=nula?(iz)ANDnula?(dr)

 piz(S(iz,dr))
=iz

 pdr(S(iz,dr))
=dr

 cont(S(iz,dr))
=iz++dr

errores

 borra(s)sifin?(s)

 actual(s)sifin?(s)

 avanza(s)sifin?(s)
ftad

4.5.2

Implementacin

Implementacin esttica
Basada en un vector, siguiendo una idea similar a la estudiada para la representacin de las pilas. La idea grfica:

act

ult

Ntese que act apunta al primero de la parte derecha. Cuando el punto de inters est a la derecha del todo, su valor ser ult+1
Tipo representante

const

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


 0dx.ultdl1dxs.actdxs.ult+1

 i:1didxs.ult:R(xs.esp(i))

Ntese que:

ult = 0 act = 1
En este caso cont(A(xs)) = [ ]

ult = l 1 d act d l+1

act = ult + 1 pdr(A(xs)) = [ ]

Funcin de abstraccin


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



A(xs)=defS(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)]  sicdf

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


Operacin
Crea
Inserta

Complejidad
O(1)
O(n)

Tipos de datos con estructura lineal

borra
actual
Avanza
Reinicia
vaca?
fin?

384

O(n)
O(1)
O(1)
O(1)
O(1)
O(1)

La razn de la complejidad lineal de Inserta y borra es que suponen desplazamientos. Consideramos que esta implementacin es ineficiente, ya que nuestro objetivo es conseguir que todas las
operaciones tengan complejidad O(1).
Implementacin basada en una pareja de listas
Seguimos directamente la idea de la especificacin del TAD
Tipo representante
tipo
Sec[Elem]=reg
piz:Lista[Elem];
pdr:Lista[Elem]
freg;


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


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

R(xs)defR(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)=defS(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
Crea
Inserta

Complejidad
proc O(1)
proc O(1)

Idea

ponDr en piz

Tipos de datos con estructura lineal

385

borra
actual
Avanza

proc O(1)
func O(1)
proc O(1)

Reinicia

proc O(n2)

vaca?
fin?

func O(1)
func O(1)

resto en pdr
primero en pdr
primero y resto en pdr; ponDr
en piz
reiterar; ltimo e inicio O(n)
en piz; Cons en pdr
nula? en piz y pdr
nula? en pdr

Reinicia resulta demasiado ineficiente.


Implementacin dinmica eficiente
La idea grfica de esta representacin:

xs
prim

x1
primero
(fantasma)

ant

xi-1

xi

xn

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, consultar el actual,
xs
prim

Aparece aqu una idea nueva que es el uso de un nodo fantasma. La


ant 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 secuencia est vaca.

Tipo representante

tipo
Nodo=reg

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.priznilubicado(xs.pri)

 xs.antznilubicado(xs.ant)

 xs.antcadena(xs.pri)





 xs.pricadena(xs.pri^.sig)
 cadenaCorrecta(xs.pri^.sig)

Donde, como siempre, cadena(p) da el conjunto de enlace que parten de p hasta llegar a nil, exclusive.


           sip=nil
cadena(p)=def



    {p}cadena(p^.sig) sipznil

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



 cadenaCorrecta(p)

def


 p=nil

 (pznilubicado(p)R(p^.elem)

 pcadena(p^.sig)cadenaCorrecta(p^.sig))

Funcin de abstraccin

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




Tipos de datos con estructura lineal

387

A(xs)=defS(hazPiz(xs.pri^.sig,xs.ant^.sig),hazPdr(xs.ant^.sig))


Parece ms intuitivo invocar a hazPiz con xs.ant en lugar de xs.ant^.sig; sin embargo, esto complicara la definicin de hazPiz, porque habra que incluir un caso base ms: p=q=nil; y lo que es
peor, habra que distinguir si p = q = xs.prim. Es decir, facilita la definicin de la funcin en los
casos especiales de: piz vaca y secuencia vaca.
hazPiz(p, q) construye una lista abstracta con los elementos localizados en los nodos sealados
por los punteros de cadena(p), hasta q exclusive.


   Nula         sip=q  (estoincluyep=q=
nil)
hazPiz=def



   Cons(A(p^.elem),hazPiz(p^.sig,q)) sipzq

Ntese que R(xs) garantiza que p^.elem est bien definido en el caso recursivo de hazPiz. Y,
efectivamente, la funcin se comporta correctamente cuando la parte izquierda est vaca:



xs.pri=xs.ant

 xs.pri^.sig=xs.ant^.sig  (quepuedesernilsilasecuenciaest
vaca)
 hazPiz(xs.pri^.sig,xs.ant^.sig)=Nula


hazPdr(p) construye una lista abstracta con los elementos localizados en los nodos sealados
por los punteros de cadena(p):


    Nula           sip=nil
hazPdr(p)=def



    Cons(A(p^.elem),hazPdr(p^.sig))  sipznil

Ntese que R(xs) garantiza que p^.elem est bien definido en el caso recursivo de hazPdr. Y,
efectivamente, la funcin se comporta correctamente cuando la parte derecha est vaca: si
xs.ant^.sig = nil resulta hazPdr( xs.ant^.sig) = Nula.
Implementacin procedimental de las operaciones
procCrea(sxs:Sec[Elem]);
{P0:cierto}
var
fantasma:Enlace;
inicio
ubicar(fantasma);
fantasma^.sig:=nil;
xs.pri:=fantasma;
xs.ant:=fantasma
{Q0:R(xs)A(xs)=SEC[ELEM]Crea}
fproc

Tipos de datos con estructura lineal

388


procinserta(esxs:Sec[Elem];ex:Elem);
{P0:xs=XSR(xs)R(x)}
var
nuevo:Enlace;
inicio
ubicar(nuevo);
nuevo^.elem:=x;
nuevo^.sig:=xs.ant^.sig;/*1*/
xs.ant^.sig:=nuevo;/*2*/
xs.ant:=nuevo/*3*/
{Q0:R(xs)A(xs)=SEC[ELEM]inserta(A(XS),A(x))}
fproc

xs
prim

ant
3

x1

xi-1

xi

2
x
procborra(esxs:Sec[Elem]);
{P0:xs=XSR(xs)NOTfin?(A(xs))}
var
act:Enlace;
inicio
act:=x.ant^.sig;
siact==nil
entonces
error(Partederechavaca)
sino
xs.ant^.sig:=act^.sig;/*1*/
ELEM.anular(act^.elem);
liberar(act)
fsi
{Q0:R(xs)A(xs)=SEC[ELEM]borra(A(XS))}
fproc

xn

Tipos de datos con estructura lineal

389

xs
prim

ant
1

x1

xi-1

xi

xi+1

act

funcactual(xs:Sec[Elem])devx:Elem;
{P0:R(xs)NOFfin?(A(xs))}
var
act:Enlace;
inicio
act:=xs.ant^.sig;
siact==nil
entonces
error(Partederechavaca)
sino
x:=act^.elem; %x:=ELEM.copia(act^.elem)
fsi
{Q0:A(x)=SEC[ELEM]actual(A(xs))}
devx
ffunc
procavanza(esxs:Sec[Elem]);
{P0:xs=XSR(xs)NOTfin?(A(xs))}
var
act:Enlace;
inicio
act:=xs.ant^.sig;
siact==nil
entonces
error(Partederechavaca)
sino
xs.ant:=act
fsi
{Q0:R(xs)A(xs)=SEC[ELEM]avanza(A(XS))}
Fproc



procreinicia(esxs:Sec[Elem]);

xn

Tipos de datos con estructura lineal

390

{P0:R(xs)}
inicio
xs.ant:=xs.pri
{Q0:R(xs)A(xs)=SEC[ELEM]reinicia(A(XS))}
fproc

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

funcfin?(xs:Sec[Elem])devr:Bool;
{P0:R(xs)}
inicio
r:=xs.ant^.sig==nil
{Q0:r=SEC[ELEM]fin?(A(xs))}
devr
ffunc

4.5.3

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]);
{P0:xs=XSpiz(xs)=[]}
var
x:Elem;
inicio
%reinicia(xs);sinosupusisemosquepiz(xs)=[]
{I:i:1did#piz(xs):tratado(piz(xs)!!i);
C:#pdr(xs)}
itNOTfin?(xs)o
x:=actual(xs);
tratar(x);
avanza(xs)
fit

{Q0:cont(xs)=cont(XS)fin?(xs)

Tipos de datos con estructura lineal

391

i:1did#piz(xs):tratado(piz(xs)!!i)
fproc

Como variante de este esquema, podramos no pedir piz(xs)=[ ] en P0, y no comenzar con reinicia(xs). De esta forma se recorre la secuencia desde el punto de inters hasta el final.
Ntese que en este ejemplo no hemos cualificado las operaciones con el nombre del TAD. La
razn es que estamos usando un nico TAD y, por lo tanto, no hay posibilidad de colisiones.
Ntese tambin que aunque piz es una operacin privada del TAD, s la utilizamos en las especificaciones de los procedimientos que usan el TAD.
Esquema de bsqueda de una secuencia
Un procedimiento genrico de recorrido:
procbusca(esxs:Sec[elem];sencontrado:Bool);
{P0:xs=XSpiz(xs)=[]}
var
x:Elem;
inicio
%reinicia(xs);sinosupusisemosquepiz(xs)=[]
encontrado:=falso;
{I:i:1did#piz(xs):prop(piz(xs)!!i)
encontradooNOTfin?(xs)prop(actual(xs)));
C:#pdr(xs)}

En el invariante no ponemos encontrado l porque todava no hemos comprobado si el


elemento actual lo cumple o no.
itNOTfin?(xs)ANDNOTencontradoo
x:=actual(xs);
siprop(x)
entoncesencontrado:=cierto
sinoavanza(xs)
fsi
fit
{Q0:cont(xs)=cont(XS)
encontradoli:1did#cont(xs):prop(cont(xs)!!i)
i:1did#piz(xs):prop(piz(xs)!!i)
encontradooprop(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 como que cuando la parte izquierda de la implicacin es falso, entonces afirmo que la implicacin es
cierta, y no me preocupo por el lado derecho.
Me corrijo, en realidad en este caso es inevitable utilizar un aserto que puede estar indefinido,
porque la alternativa podra ser:

(encontrado)(encontradoprop(act(xs)))


que adolece del mismo problema.




Como variante de este esquema, podramos no pedir piz(xs)=[ ] en P0, y no comenzar con reinicia(xs). De esta forma se busca en la secuencia desde el punto de inters hacia la derecha.
Si prop(x) x == u, con u dado como parmetro, entonces se trata de la bsqueda de un valor dentro de una secuencia.
Ejemplos de aplicacin de los esquemas de recorrido y bsqueda
Conteo en una secuencia de enteros
Dada xs : Sec[Ent], queremos contar cuntas posiciones hay en ella que contengan un entero
igual a la suma de los enteros en todas las posiciones anteriores. Este es un ejemplo de aplicacin
del esquema de recorrido:

procconteo(esxs:Sec[Ent];sr:Ent);
{P0:xs=XSpiz(xs)=[]}
var
x:Elem;
s:Ent; %llevalasumahastaesemomento
inicio
s:=0;
{I:cont(xs)=cont(XS)
r=#i:1did#piz(xs):
cont(xs)!!i=6j:1dj<i:cont(xs)!!j
s=6i:1did#piz(xs):cont(xs)!!i;
C:#pdr(xs)}
itNOTfin?(xs)o
x:=actual(xs);
six==s
entonces
<r,s>:=<r+1,s+x>
sino          tratar(x)
s:=s+x
fsi;
avanza(xs)
fit;

Tipos de datos con estructura lineal

393

{Q0:cont(xs)=cont(XS)fin?(xs)
r=#i:1did#cont(xs):
cont(xs)!!i=6j:1dj<i:cont(xs)!!j}
fproc

Hay que implementarlo como un procedimiento porque se modifica el punto de inters de xs.
Bsqueda en una secuencia de enteros
Dada xs : Sec[Ent], queremos buscar la primera posicin donde aparezca un entero que no sea
mayor o igual que todos los anteriores, es decir, el primero que sea menor que el situado a su
izquierda. Grficamente:
x1dx2ddxp>xp+1

No consideramos al primero como candidato. Por ello, eso lo implementamos como una modificacin del esquema de bsqueda, donde tratamos por separado al primer elemento, lo cual
nos obliga a verificar que efectivamente hay un primer elemento i.e., la secuencia no est vaca.

procbusca(esxs:Sec[Ent];sencontrado:Bool);
{P0:piz(xs)=[]}
var
x:Elem;
m:Ent;  %paraguardarelanterior
inicio
encontrado:=Falso;
sivaca?(xs)
entonces
seguir
sino
m:=actual(xs);
avanza(xs);
{I:noDecreciente(piz(xs))m=ultimo(piz(xs))
encontradooNOTfin?(xs)actual(xs)<m;
C:#pdr(xs)}
itNOTfin?(xs)ANDNOTencontradoo
x:=actual(xs);
six<m
entonces
encontrado:=cierto
sino
m:=x;
avanza(xs)
fsi
fit
fsi
{Q0:cont(xs)=cont(XS)noDecreciene(piz(xs))

Tipos de datos con estructura lineal

394

encontradoli:2did#cont(xs):cont(xs)!!i<cont(xs)!!i
1
encontradooactual(xs)<ltimo(piz(xs))
}

Donde el predicado auxiliar noDecreciente indica que la lista est ordenada en orden no decreciente:

noDecreciente(ls)defi,j:1di<jd#ls:(ls!!i)d(ls!!j)

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])
{P0:xs=XSys=YSordenada(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)o
x:=actual(xs);
y:=actual(ys);
sixdy
entonces
inserta(x,zs);
avanza(xs)
sino
inserta(y,zs);
avanza(ys)
fsi
fit;
{I;C}%lamismacotaeinvariantedelbucleanterior
itNOTfin?(xs)o
inserta(actual(xs),zs);
avanza(xs)
fit;
{I;C}%lamismacotaeinvariantedelbucleanterior
itNOTfin?(ys)o
inserta(actual(ys),zs);
avanza(ys)
fit;


Tipos de datos con estructura lineal

395

{Q0: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 t 2 fijado de

antemano) se pueden representar por medio de nmeros, entendiendo que un nmero natural P cuya representacin en base N tenga 1 como dgito de mayor peso representa la
pila formada por los restantes dgitos de la representacin de P en base N (excepto el de
mayor peso), siendo la cima el dgito de menor peso. Por ejemplo, si N = 10, el nmero
1073 representa la pila que contiene los nmeros 0, 7, 3, con 0 en la base y 3 en la cima.
Formaliza el invariante de la representacin y la funcin de abstraccin siguiendo esta idea.
Comprueba que esta representacin permite implementar todas las operaciones de las pilas
como funciones de coste O(1).

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 ejecutar lo siguiente:
var
p,p1,p2,p3,p4:Pila[Car];
p:=PilaVaca();
p1:=Apilar(b,Apilar(a,p));
p2:=Apilar(c,p1);
p3:=Apilar(d,p1);
p4:=Apilar(e,p3);
p3:=Apilar(f,p3);
p1:=desapilar(p4);

Observa que en esta implementacin funcional de las pilas, la ejecucin de una operacin
nunca destruye estructuras creadas previamente por la ejecucin de otras operaciones. Qu suceder con el espacio ocupado por la estructura, si continuamos ejecutando lo que sigue?
p:=PilaVaca();
p1:=PilaVaca();p2:=PilaVaca();
p3:=PilaVaca();p4:=PilaVaca();
181. Usando la representacin del ejercicio 178, especifica y programa un procedimiento con

cabecera

procanular(esxs:Pila[Elem])

cuyo efecto sea liberar todo el espacio ocupado por xs antes de la llamada, dejando como nuevo valor de xs la representacin de la pila vaca.

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 procedimientos. Comenta las ventajas e inconvenientes frente a la implementacin esttica de las
pilas, estudiada en los ejercicios 165, 166 y 172.

183. Especifica un TAD parametrizado DOS_PILAS[E :: ANY], con un tipo Pilas[Elem] y ope-

raciones adecuadas. La idea es que un dato de tipo Pilas representa una pareja de pilas, que
pueden manejarse independientemente del modo usual. Desarrolla una implementacin
esttica de este TAD, representando la pareja de pilas con ayuda de un nico vector de almacenamiento. Organiza la implementacin de modo que las dos pilas crezcan en sentidos
opuestos, cada una desde uno de los dos extremos del vector.

184. Especifica un enriquecimiento SUPER_PILA del TAD PILA, aadiendo una nueva opera-

cin con la siguiente especificacin informal:




desapilarK:(Nat,Pila[Elem])oPila[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 m1 llamadas

a apilar y m2 llamadas a desapilarK es O(m1), 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;
{P0:P(x);Cota:t(x)}
var
x:T;y:S;
%Otrasposiblesdeclaracioneslocales
inicio
sid(x)
entonces
{P(x)d(x)}
y;=r(x)
{Q(x,y)}
sino
{P(x)d(x)}
x:=s(x);
{x=s(x)P(x)}
y:=f(x);
{x=s(x)Q(x,y)}
y:=c(x,y);
{Q(x,y)}
fsi
{Q0:Q(x,y)}

Tipos de datos con estructura lineal

398

devy
ffunc


Supongamos que la funcin s encargada de calcular los parmetros de la siguiente llamada recursiva posea una inversa s-1. En este caso, es posible transformar f en una definicin iterativa
equivalente, de la forma siguiente:
funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
{Inv.I:R(x,x);Cota:t(x)}
itd(x)o
{Id(x)}
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(x,x)y=f(x);Cota:m(x,x)}
itx/=xo
{Jxzx}
x:=s1(x);
y:=c(x,y)
{J}
fit
{Jx=x}
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc

siendo


 R(x,x)

def


 n:Nat:(x=sn(x)P(x)

     i:0di<n:(P(si(x))d(si(x))))

(Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas)




 m(x,x)
=def

 minn:Nat:x=sn(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 sucesivas llamadas recursivas. La versin iterativa de f queda ahora:

funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;
xs:Pila[T];
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
PilaVaca(xs);
{Inv.I:R(xs,x,x);Cota:t(x)}
itd(x)o
{Id(x)}
Apilar(x,xs);
x:=s(x)
{I}
fit;
{Id(x)}
y:=r(x);
{Inv.J:R(xs,x,x)y=f(x);Cota:tamao(xs)}
itNOTesVaca(xs)o
{JesVaca(xs)}
x:=cima(xs);
y:=c(x,y);
desapilar(xs)
{J}
fit
{JesVaca(xs)}
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc


siendo


 R(xs,x,x)

def


 n:Nat:(x=sn(x)P(x)

i:0di<n:(P(si(x))d(si(x))si(x)=elem(i,xs)))

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 elemento del fondo como elem(0, xs)
(Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, cuyos parmetros reales, hasta x exclusive, estn apilados en xs)


 tamao(xs)
=def

 nmerodeelementosapiladosenxs

(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)
(b)
(c)
*(d)

Funciones pot y pot (ejercicio 85).


Funcin mult (ejercicio 90)
Funcin log (ejercicio 91).
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 iterativa de f:

funcfit(x:T)devy:S;
{P0:P(x)}
var
x:T;u:Z;
us:Pila[Z];
%Otrasposiblesdeclaracioneslocales
inicio
x:=x;
PilaVaca(us);
{Inv.I:R(us,x,x);Cota:t(x)}
itd(x)o
{Id(x)}
u:=g(x);
Apilar(u,us);
x:=s(x)
{I}

Tipos de datos con estructura lineal

401

fit;
{Id(x)}
y:=r(x);
{Inv.J:R(us,x,x)y=f(x);Cota:tamao(us)}
itNOTesVaca(us)o
{JesVaca(us)}
u:=cima(us);
x:=h(u,x);
y:=c(x,y);
desapilar(us)
{J}
fit
{JesVaca(us)}
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc


siendo


 R(us,x,x)

def


 n:Nat:(x=sn(x)P(x)

i:0di<n:(P(si(x))d(si(x))g(si(x))=elem(i,us)))

donde elem(i, us) indica el elemento que ocupa el lugar i en la pila us, contando el elemento del fondo como elem(0, us)
(Idea: expresa que x desciende de x despus de un cierto nmero de llamadas recursivas, y que en us est apilada informacin suficiente para recuperar los parmetros
reales de dichas llamadas)


 tamao(us)
=def

 nmerodeelementosapiladosenus

(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 lineales del ejercicio 187, utilizando en todos los casos una pila de valores booleanos. Precisa quines son en cada caso las funciones denominadas g y h en el esquema.
Colas
190. Especifica algebraicamente un TAD genrico COLA[e :: ANY], partiendo de la siguiente

especificacin informal de las operaciones deseadas:




ColaVaca:oCola[Elem]

Generadora. Crea una cola vaca.

Tipos de datos con estructura lineal

402

Aadir:(Elem,Cola[Elem])oCola[Elem]

Generadora. Aade un nuevo elemento al final de una cola.

avanzar:Cola[Elem]oCola[Elem]

Modificadora. Retira el primer elemento de una cola no vaca.

primero:Cola[Elem]oElem

Observadora. Consulta el primer elemento de una cola no vaca.

esVaca:Cola[Elem]oBool

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, declarada localmente.
Idea: Primeramente, el algoritmo recorre la cola y va apilando los caracteres no blancos. A continuacin, 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 garantizar 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:oDCola[Elem]

Tipos de datos con estructura lineal

403

Generadora. Crea una cola vaca.

PonDetrs:(Elem,DCola[Elem])oDCola[Elem]

Generadora. Aade un nuevo elemento al final de una cola.

ponDelante:(Elem,DCola[Elem])oDCola[Elem]

Modificadora. Aade un nuevo elemento al principio de una cola.

quitaUlt:DCola[Elem]oDCola[Elem]

Modificadora. Retira el ltimo elemento de una cola no vaca.

ltimo:DCola[Elem]oElem

Observadora. Consulta el ltimo elemento de una cola no vaca.

quitaPrim:DCola[Elem]oDCola[Elem]

Modificadora. Retira el primer elemento de una cola no vaca.

primero:DCola[Elem]oElem

Observadora. Consulta el primer elemento de una cola no vaca.

esVaca:DCola[Elem]oBool

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 estructura desde el primer nodo. Modifica la implementacin, de manera que la estructura representante de una cola est doblemente enlazada; cada nodo deber incorporar un puntero al
siguiente y un puntero al anterior. Implementa todas las operaciones en base a la nueva representacin, obteniendo procedimientos ejecutables en tiempo O(1).

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 tomando sucesivamente: el primer carcter de X; luego el ltimo; luego el segundo; luego el penltimo; etc.
Ejemplo: para X = Bond, James Bond, resultan:

X = BoJ ,dnameB sodn

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:oLista[Elem]

Generadora. Crea una lista vaca.

Cons:(Elem,Lista[Elem])oLista[Elem]

Generadora. Cons(x,xs) genera una nueva lista aadiendo x como primer elemento, por delante de los elementos de xs.

nula?:Lista[Elem]oBool

Observadora. Reconoce si una lista es vaca o no.

primero:Lista[Elem]oElem

Observadora. Consulta el primer elemento de una lista no vaca.

resto:Lista[Elem]oLista[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])oLista[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.

[_]:ElemoLista[Elem]

Modificadora. [ x ] es la lista formada por el nico elemento x.

(#):Lista[Elem]oNat

Observadora. # xs devuelve la longitud de la lista xs.

(!!):(Lista[Elem],Nat)oElem

Observadora. xs !! i est definido si 1 d i d n, siendo n = # xs, y devuelve el


elemento de lugar i de xs.

miembro:(Elem,Lista[Elem])oBool

Observadora. Reconoce si un elemento es miembro de una lista.

ponDr:(Lista[Elem],Elem)oLista[Elem]

Modificadora. Aade un elemento a la derecha de una lista.

ltimo:Lista[Elem]oElem

Observadora. Devuelve el ltimo elemento de una lista no vaca.

inicio:Lista[Elem]oLista[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 representante de una lista, y un procedimiento anula que libere todo el espacio ocupado por la estructura 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])
{P0:R(xs)R(ys)xs=XSys=YS}
{Q0: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 representante, 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 implementacin funcional del TAD LISTA. Debemos programar usando solamente las operaciones exportadas, 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 tiempo de ejecucin de los algoritmos obtenidos.

209. Usando la especificacin del TAD LISTA y razonando por induccin sobre la estructura de

los trminos generados, demuestra que son vlidas las siguientes ecuaciones. Atencin: cada
igualdad debe entenderse en el sentido de que sus dos miembros representan la misma lista
abstracta.

xs,ys,zs:Lista[Elem]:x,y:Elem:

(a)ltimo([xs\x])=x
(b)inicio([xs\x])=xs
*(c)xs++[]=xs
*(d)xs++(ys++zs)=(xs++ys)++zs
(e)xs++[ys\y]=[(xs++ys)\y]


210. Formaliza la especificacin Pre/Post de las dos funciones que siguen, y construye algorit-

mos recursivos que las satisfagan

(a) funccoge(n:Nat;xs:Lista[Elem])devus:Lista[Elem]
{P0:cierto}
{Q0:useslalistaformadaporlosnprimeroselementosdexs}

Tipos de datos con estructura lineal

406

 ffunc
(b) functira(n:Nat;xs:Lista[Elem])devvs:Lista[Elem]
{P0:cierto}
{Q0:vseslalistaformadaquitandolosnprimeroselementosdexs}
ffunc
211. Demuestra usando induccin que la igualdad
coge(n,xs)++tira(n,xs)=xs

vale para xs, ys : Lista[Elem] y n : Nat cualesquiera. Atencin: la igualdad debe entenderse en el
sentido de que sus dos miembros representan la misma lista abstracta.
212. La siguiente funcin recursiva calcula la inversa de una lista dada:

funcinv(xs:Lista[Elem])devys:Lista[Elem];
{P0:cierto}
inicio
sinula?(xs)
entoncesys:=Nula
sinoys:=inv(resto(xs))++[primero(xs)]
fsi
{Q0::1did(#xs):xs!!i=ys!!(#xs)i+1}
ffunc


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];
{P0:cierto}
{Q0: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])oBool

Observadora. Operacin de igualdad entre listas.

(d):(Lista[Elem],Lista[Elem])oBool

Observadora. Orden entre listas.

ordenada:Lista[Elem]oBool

Observadora. Decide si una lista est ordenada.

insertaOrd:(Elem,Lista[Elem])oLista[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 operaciones extra de LISTA-ORD. Observa que no es necesario definir un tipo representante
para Lista[Elem], ya que Lista[Elem] se puede importar del mdulo que implementa a
LISTA. Tampoco es necesario usar punteros.

215. Construye una funcin recursiva simple que satisfaga la especificacin que sigue, y aplica el

mtodo de plegado-desplegado para transformarla en una funcin recursiva final ms general. Suponemos que se trata de listas ordenas de tipo Lista[Elem].

funcmezcla(xs,ys:Lista[Elem])devzs:Lista[Elem]
{P0:ordenada(xs)ordenada(ys)}
{Q0:ordenada(zs)permutacin(zs,xs++ys)}

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 ejecutables 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:oSec[Elem]

Generadora. Crea una secuencia vaca.

Inserta:(Sec[Elem],Elem)oSec[Elem]

Generadora. Inserta un nuevo elemento a la izquierda del punto de inters.

borra:Sec[Elem]oSec[Elem]

Modificadora. Elimina el elemento situado a la derecha del punto de inters, si


lo hay.

actual:Sec[Elem]oElem

Observadora. Consulta el elemento situado a la derecha del punto de inters, si


lo hay.

Avanza:Sec[Elem]oSec[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]oSec[Elem]

Generadora. Posiciona el punto de inters a la izquierda del todo.

vaca?:Sec[Elem]oBool

Observadora. Reconoce si la secuencia est vaca.

fin?:Sec[Elem]oBool

Observadora. Reconoce si el punto de inters se encuentra a la derecha del todo.


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 especificacin, suponiendo disponible un mdulo que implemente el TAD de las secuencias.
(a) Esquema de recorrido secuencial:
procrecorre(esxs:Sec[Elem]);
{P0:xs=XSpiz(xs)=[]}
{Q0:cont(xs)=cont(XS)fin?(xs)=cierto
tratarsehaaplicadoatodosloselementosdexs}
fproc
(b) Esquema de bsqueda secuencial:
procbusca(esxs:Sec[Elem];sencontrado:Bool);
{P0:xs=XSpiz(xs)=[]}
{Q0:cont(xs)=cont(XS)
(encontradolexisteenxsalgnelementoquecumplaprop)
(encontradooactual(xs)cumpleprop
loselementosdepiz(xs)nocumplenprop)}
fproc


NOTA: Si el esquema de bsqueda se usa para buscar un elemento dado z (es decir, si se tiene
prop(x) x = z), conviene modificar el planteamiento del esquema introduciendo z como
parmetro.
223. Resuelve los problemas que siguen aplicando el esquema de recorrido de secuencias. En

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 d para el tipo Elem.

procmezcla(esxs,ys:Sec[Elem];szs:Sec[Elem])
{P0:xs=XSys=YSordenada(cont(xs))piz(xs)=[]
ordenada(cont(ys))piz(ys)=[]}
{Q0:cont(xs)=cont(XS)cont(ys)=cont(YS)ordenada(cont(zs))
permutacin(cont(zs),cont(xs)++cont(ys))}
fproc
227. Una expresin aritmtica construida con los operadores binarios +, , *, / y operandos

(representados cada uno por un solo carcter) se dice que est en forma postfija si es o bien
un solo operando o dos expresiones en forma postfija una tras otra, seguidas inmediatamente de un operador. Lo que sigue es un ejemplo de una expresin escrita en la notacin
infija habitual, junto con su forma postfija:
Forma postfija: ABC/DE+*
Forma infija: (A/(BC))*(D+E))

Disea un algoritmo iterativo que calcule el valor de una expresin dada en forma postfija por
el siguiente mtodo: se inicializa una pila vaca de nmeros y se van recorriendo de izquierda a
derecha los caracteres de la expresin. Cada vez que se pasa por un operando, se apila su valor.
Cada vez que se pasa por un operador, se desapilan los dos nmeros ms altos de la pila, se componen con el operador, y se apila el resultado. Al acabar el proceso, la pila contiene un solo
nmero, que es el valor de la expresin. Representa la expresin dada como secuencia de caracteres, y supn disponible una funcin valor que asocie a cada operando su valor numrico.
228. Dado un nmero natural N t 2, se llaman nmeros afortunados a los que resultan de ejecutar

el siguiente proceso: se comienza generando una cola que contiene los nmeros desde 1
hasta N, en este orden; se elimina de la cola un nmero de cada 2 (es decir, los nmeros 1,
3, 5, etc.); de la nueva cola, se elimina ahora un nmero de cada 3; etc. El proceso termina
cuando se va a eliminar un nmero de cada m y el tamao de la cola es menor que m. Los
nmeros que queden en la cola en este momento son los afortunados. Disea un procedi-

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 veces el siguiente proceso: extraer el primer nmero de la cola, y aadirlo al final de la misma, salvo
si le tocaba ser eliminado.)
Ms aplicaciones de los tipos de datos con estructura lineal
229. Estudia posibles implementaciones del TAD CJTO (cfr. ejercicios 154 y 156) usando listas

como representantes de los conjuntos. Debes suponer que las listas se importan de un
mdulo separado, sin tener acceso a su representacin interna. Compara con las implementaciones de conjuntos planteadas en el ejercicio 167.

230. Estudia una implementacin del TAD de los polinomios (cfr. ejercicio 151) usando listas

ordenadas (importadas de un mdulo separado) como representantes de polinomios. Discute las ventajas e inconvenientes con respecto a las implementaciones basadas en vectores
que se plantearon en el ejercicio 170.

231. Severino del Pino, profesor de arameo de la Universidad Imponente, ha detectado proble-

mas de aburrimiento entre su numeroso alumnado. Su colega Tadeo de la Tecla, del departamento de informtica, ha ofrecido ayudarle diseando un sistema informtico de control
de bostezos. Tadeo propone una especificacin de un TAD parametrizado BOSTEZOS[E
:: ORD], donde el parmetro E nos da un tipo Elem equipado con operaciones de igualdad
y orden, que representa a los alumnos. Tadeo propone que BOSTEZOS disponga de las
siguientes operaciones:


Crea:oBostezos[Elem]

Generadora. Crea un sistema de bostezos vaco.

Otro:(Elem,Bostezos[Elem])oBostezos[Elem]

Generadora. Registra un nuevo bostezo en el sistema.

borra:(Elem,Bostezos[Elem])oBostezos[Elem]

Modificadora. Borra del sistema todos los bostezos registrados de un elemento


dado.

cuntos?:(Elem,Bostezos[Elem])oNat

Observadora. Consulta el nmero de bostezos de un elemento dado que estn


registrados en el sistema.

listaNegra:Bostezos[Elem]oSec[Elem]

Observadora. Devuelve la secuencia ordenada de todos los elementos que tengan 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, importadas 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 principal Consultorio junto con las operaciones que se describen informalmente a continuacin:


Crea:oConsultorio

Genera un consultorio vaco sin ninguna informacin.

NuevoMdico:(Consultorio,Mdico)oConsultorio

Altera un consultorio, dando de alta a un nuevo mdico que antes no figuraba en el consultorio.

PideConsulta:(Consultorio,Mdico,Paciente)oConsultorio

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

Consulta el paciente a quien le toca el turno para ser atendido por un mdico;
ste debe estar dado de alta, y debe tener algn paciente que le haya pedido consulta.

atiendeConsulta:(Consultorio,Mdico)oConsultorio

Modifica un consultorio, eliminando el paciente al que le toque el turno para ser atendido
por un mdico; ste debe estar dado de alta, y debe tener algn paciente que le haya pedido
consulta.

tienePacientes:(Consultorio,Mdico)oBool

Reconoce si hay o no pacientes a la espera de ser atendidos por un mdico, el cual debe estar de alta.

(a) Construye una especificacin algebraica completa del TAD CONSULTORIO, inclu-

yendo todas las ecuaciones necesarias.


(b) Plantea
un mdulo de implementacin
CONSULTORIO, indicando claramente:

CONSULTORIO

del

TAD

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 implementen, 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 caso 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 Mercado 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 cierta empresa.

empleados

Consulta los empleados de una empresa, devolviendo el resultado como secuencia ordenada de personas.

empleado?

Averigua si es cierto o no que una persona dada es empleado de una empresa dada.

pluriempleado?

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 operaciones en generadoras, modificadoras y observadoras, y los dominios de definicin
de las operaciones parciales (si las hay).
(b) Plantea un mdulo de implementacin para el TAD MERCADO, del modo indicado
en el enunciado del ejercicio anterior. Elige el tipo representante de Mercado de manera que puedas realizarlo utilizando TADs conocidos con estructura lineal, importndolos de mdulos que los implementen, y sin acceder a la representacin interna.
(c) Completa el mdulo de implementacin del apartado (b) desarrollando los procedimientos y funciones que implementan las operaciones de MERCADO, y analizando
sus tiempos de ejecucin en funcin de las siguientes medidas del tamao de un mercado: NE, el nmero de empresas; y NP, el nmero mximo de personas contratadas
por una misma empresa.
234. Especifica un TAD BANCO adecuado para modelizar el comportamiento de un banco

simplificado, incluyendo operaciones que sirvan para crear un banco vaco (i.e., sin ninguna
cuenta), abrir y cerrar cuentas, efectuar ingresos y extracciones en una cuenta, preguntar si
un cliente tiene cuenta, consultar el saldo de una cuenta, etc. Desarrolla una implementacin de este TAD, usando listas o secuencias, importadas de un mdulo separado, para
construir la representacin de los bancos.

235. Supongamos disponible un mdulo que implemente el TAD BANCO del ejercicio ante-

rior. Disea un procedimiento que procese una cola de solicitudes de servicios de clientes de un
banco, dando como resultados un nuevo estado del banco y una secuencia de incidencias. Cada
solicitud debe representar una peticin de ingreso o extraccin de una cantidad por parte de
un cliente, y cada incidencia debe representar un cliente que ha solicitado realizar una operacin indefinida. Adems del mdulo de datos BANCO, debes suponer disponibles otros
dos mdulos SOLICITUDES e INCIDENCIAS, que exporten los tipos Solicitud e Incidencia, respectivamente, junto con operaciones adecuadas. Especifica los TADs correspondientes al comportamiento abstracto de estos dos mdulos, e indica claramente qu otros
mdulos de datos necesita utilizar el procedimiento que disees.

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 registro. 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 (cada uno acompaado por la informacin de si est prestado o no, y en caso afirmativo, a qu usuario). 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 suponer disponibles otros dos mdulos SOLICITUDES e INCIDENCIAS, lo mismo que en el
ejercicio 235.

414

rboles

CAPTULO 5
RBOLES
5.1 Modelo matemtico y especificacin
Los rboles son estructuras jerrquicas formadas por nodos, de acuerdo con la siguiente construccin inductiva:

Un solo nodo forma un rbol a; se dice que el nodo es raz del rbol.
a

Dados n rboles ya construidos a1, , an , se puede construir un nuevo rbol a aadiendo


un nuevo nodo como raz y conectndolo con las races de los ai. Se dice que los ai son los
hijos de a.
a

a1

an

a1

an

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

415

rboles

o para una accin condicional


si

condicin

entonces

sino

<

:=

:=

*
-

+
2

*
3

*
x

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 fundamentalmente rboles sin punto de inters.

5.1.1

Arboles generales

Modelo matemtico
En Matemtica discreta se suele definir el concepto de rbol como grafo conexo acclico con
un nodo distinguido como raz. Sin embargo, esta definicin no refleja el orden entre los hijos, ni
tampoco las informaciones asociadas a los nodos.
Nosotros vamos a adoptar un modelo de rbol basado en la idea de representar las posiciones
de los nodos como cadenas de nmeros naturales positivos:

La raz de un rbol tiene como posicin la cadena vaca H.

416

rboles

Si un cierto nodo de un rbol tiene posicin D +*, el hijo nmero i de ese nodo tendr
posicin D.i (que indica D seguida de i).

Ejemplo:
A

B 1

A 1.1

D 3

A 2

C 1.2

E
3.1

B
3.2
D
3.3.1

3.3

3.4

E
3.3.2

El rbol puede modelizarse como una aplicacin:


a:NoV


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

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

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


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.

H N posicin de la raz.

D.i N D N

D.i N 1 d j < i D.j N

cerrado bajo prefijos.


hijos consecutivos sin huecos.

Terminologa y conceptos bsicos


Dado un rbol a : N o V

Nodo es cada posicin, junto con la informacin asociada: (D, a(D)), siendo D N.

Raz es el nodo de posicin H.

417

rboles

Hojas son los nodos de posicin D tal que no existe i tal que D.i N.
Nodos internos son los nodos que no son hojas.

Un nodo D.i tiene como padre a D, y se dice que es hijo de D.

Dos nodos de posiciones D.i, D.j (i z j) se llaman hermanos.

Camino es una sucesin de nodos tal que cada uno es padre del siguiente:
D, D.i1, , D.i1. .in

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 D es |D|+1. Es decir: n+1, siendo n la longitud del camino (nico) que va de la raz al nodo. En particular, el nivel de la raz es 1. El
rbol del ejemplo tiene nodos a 4 niveles. El nivel de un nodo es igual al nmero de nodos del camino que va desde la raz al nodo.

La talla, altura o profundidad de un rbol es el mximo de todos los niveles de nodos del
rbol. Equivalentemente: 1+n, siendo n el mximo de las longitudes de las ramas. el rbol
del ejemplo es de talla 4.

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 D al nodo de posicin E (i.e., si D es prefijo de E),
se dice que D es antepasado de E y que E es descendiente de D.

Cada nodo de un rbol a determina un subrbol a0 con raz en ese nodo. Formalmente, si el
nodo tiene posicin D, entonces:
a0:N0oV

siendo
N0=def{E+*|DEN}
a0(E)=defa(DE) paracadaEN0

Dado un rbol a, los subrboles de a (si existen), se llaman rboles hijos de a. El rbol del
ejemplo tiene 3 rboles hijos.

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 bosques siguen la misma idea que las generadoras de listas.

418

rboles

tadARBOL[E::ANY]

usa

 BOOL,NAT

tipos

 Arbol[Elem],Bosque[Elem]

operaciones


 []:oBosque[Elem]

/*gen*/

 [_/_]:(Arbol[Elem],Bosque[Elem])oBosque[Elem]

/*gen*/

 rbol:(Nat,Bosque[Elem])oArbol[Elem]

/*obs*/

 nrArboles:Bosque[Elem]oNat

/*obs*/

 esVaco:Bosque[Elem]oBool

/*obs*/

 Cons:(Elem,Bosque[Elem])oArbol[Elem]

/*gen*/

 hijo:(Nat,Arbol[Elem])oArbol[Elem]

/*mod*/

 nrHijos:Arbol[Elem]oNat

/*obs*/

 hijos:Arbol[Elem]oBosque[Elem]

/*obs*/

 raz:Arbol[Elem]oElem

/*obs*/




 esHoja:Arbol[Elem]oBool
ecuaciones

/*obs*/

 i:Nat:as:Bosque[Elem]:a:Arbol[Elem]:x:Elem:























 defhijo(i,a)siSuc(Cero)didnrHijos(a)
 hijo(i,a)    =frbol(i,hijos(a))
 nrHijos(Cons(x,as))=nrArboles(as)
 hijos(Cons(x,as))
 =as
 raz(Cons(x,as))  =x
 esHoja(a)     =nrHijos(a)==0
errores

 i:Nat:as:Bosque[Elem]:a:Arbol[Elem]:

 rbol(i,as)siNOT(Suc(Cero)didnrArboles(as))

defrbol(i,as)siSuc(Cero)didnrArboles(as)
rbol(i,[a/as])   =a sii=Suc(Cero)
rbol(i,[a/as])   =frbol(i1,as)sii>Suc(Cero)
nrArboles([])    =Cero
nrArboles([a/as])  =Suc(nrArboles(as))
esVaco(as)     =nrArboles(as)==Cero


 hijo(i,a)siNOT(Suc(Cero)didnrHijos(a))
ftad

419

rboles

5.1.2

Arboles binarios

Modelo matemtico
Los rboles binarios se definen de tal modo que cada nodo interno tiene como mximo dos
hijos. Las posiciones de los nodos de estos rboles pueden representarse como cadenas
D {1,2}*. En caso de que un nodo interno (con posicin D) tenga un solo hijo, se distingue si
ste es hijo izquierdo (con posicin D.1) o hijo derecho (con posicin D.2).
Estas ideas conducen a modelar un rbol binario etiquetado con informaciones de V como
una aplicacin:

a:NoV


donde el conjunto N de posiciones de los nodos de a debe cumplir las siguientes condiciones:

N {1,2}*, finito.

D.i N D N

D N se dan uno de los 4 casos siguientes:

cerrado bajo prefijos.

D.1 N y D.2 N
.1

D.1 N y D.2 N

dos hijos
.2

slo hijo izquierdo

.1

D.1 N y D.2 N

slo hijo derecho


.2

D.1 N y D.2 N

ningn hijo; hoja

420

rboles

Vemos que en los rboles binarios no se exige que no haya huecos en la serie de hijos de un
nodo. Por esto, algunos rboles binarios pueden no ser vlidos como rboles generales.
Cuando falta alguno de los hijos de un nodo interno, se dice tambin que el hijo inexistente es
vaco. En particular, podemos decir que las hojas tienen dos hijos vacos.
As, aceptamos la idea de rbol vaco, cuyo conjunto de posiciones es (el subconjunto vaco
de {1, 2}*.
Toda la terminologa y conceptos bsicos que hemos estudiado para los rboles generales,
pueden adaptarse sin dificultad a los rboles binarios.
Especificacin algebraica de los rboles binarios
Gracias al concepto de rbol vaco, podemos suponer que cualquier rbol binario no vaco se
construye a partir de un elemento raz y dos rboles hijos (que pueden a su vez ser o no vacos).
Esto conduce a la signatura del TAD, con operaciones para:

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

 Cons:(Arbin[Elem],Elem,Arbin[Elem])oArbin[Elem] /*gen*/

 hijoIz,hijoDr:Arbin[Elem]oArbin[Elem]
/*mod*/

 raz:Arbin[Elem]oElem /*obs*/




 esVaco:Arbin[Elem]oBool/*obs*/
ecuaciones


 iz,dr:Arbin[Elem]:x:Elem:

 defhijoIz(Cons(iz,x,dr))

 hijoIz(Cons(iz,x,dr)) =iz

 defhijoDr(Cons(iz,x,dr))

 hijoDr(Cons(iz,x,dr)) =dr

 defraz(Cons(iz,x,dr))

 raz(Cons(iz,x,dr)) =x

 esVaco(Vaco)    =cierto

 esVaco(Cons(iz,x,dr)) =falso

errores

 hijoIz(Vaco)

 hijoDr(Vaco)

 raz(Vaco)
ftad

421

rboles

5.1.3

Arboles n-arios

Se definen como generalizacin de los rboles binarios, de tal manera que cada nodo interno
tiene exactamente n hijos ordenados, cualquiera de los cuales puede ser vaco (i.e., se admiten
huecos en la sucesin de hijos de un nodo). En particular, las hojas tienen n hijos vacos.
Observemos que rbol n-ario no es lo mismo que rbol de grado n, de acuerdo con la definicin de grado o aridad de un rbol que dimos anteriormente. En un rbol de grado n, cada nodo tiene como mucho n hijos, pero distintos nodos pueden tener distinto nmero de hijos, y los
hijos existentes ocupan posiciones consecutivas.
Ejemplo. a1 es un rbol general de grado 2, pero no es un rbol binario. Los rboles a2 y a3 s
son binarios:

1.1

1.2

1.1

a1

5.1.4

1.2
a2

2.1

2.2
a3

Arboles con punto de inters

Como representacin matemtica de un rbol con punto de inters podemos adoptar una pareja de la forma:

(a,D0)


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



a:NoV
N+*


y D0 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.).

422

rboles

5.2 Tcnicas de implementacin


Vamos a centrarnos en la implementacin de los rboles binarios, y trataremos ms brevemente 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=nil
  (pznilubicado(p)R(p^.elem)
  R(p^.iz)R(p^.dr)
  penlaces(p^.iz)enlaces(p^.der))


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


Representacin que prohibe ciclos y nodos compartidos



  RNC(p)
def
  p=nil
  (pznilubicado(p)R(p^.elem)
  RNC(p^.iz)RNC(p^.dr)
  penlaces(p^.iz)enlaces(p^.der)
  enlaces(p^.iz)enlaces(p^.dr)=)

423

rboles

Esta representacin prohibe situaciones como:


x1

x2

Funcin de abstraccin
Sea a : Arbin[Elem] tal que R(a)


   Vaco            sia=nil
A(a)=def


   Cons(A(a^.iz),A(a^.elem),A(a^.dr))siaznil

Implementacin de las operaciones


Debido a la forma de las generadoras de este TAD, resulta natural realizar todas las operaciones como funciones:

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

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


Ntese que esta implementacin no garantiza RNC(a), ya que, por ejemplo:



a:=Cons(a1,x,a1)

424

rboles

funchijoIz(a:Arbin[Elem])deviz:Arbin[Elem]; /*O(1)*/
{P0:R(a)NOTesVaco(A(a))}
inicio
siesVaco(a)
entonces
error(Elrbolvaconotienehijoizquierdo)
sino
iz:=a^.iz
fsi
{Q0:R(iz)A(iz)=ARBIN[ELEM]hijoIz(A(a))}
deviz
ffunc


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


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

funcesVaco(a:Arbin[Elem])devr:Bool;
{P0:R(a)}
inicio
r:=a==nil
{Q0: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))

425

rboles

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]);
{P0:RNC(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
{Q0:a=nil
elespacioqueocupabalaestructurarepresentantedeAsehaliberado
}
fproc
funccopia(a:Arbin[Elem])devb:Arbin[Elem];
{P0: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
{Q0:RNC(b)A(a)=A(b)
laestructurarepresentantedebestubicadoenespacionuevo}
devb
ffunc

426

rboles

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. Podemos conseguir una numeracin de las posiciones por niveles, y de izquierda a derecha dentro
de cada nivel. Si comenzamos asociando el nmero 1 a la posicin H, resulta:

1
2

1
2
1.2

1.1
4
8
1.1.1

3
2.1
7

5
9
10
1.1.2 1.2.1

2.2

11 12
1.2.2 2.1.1

13 14
15
2.1.2 2.2.1 2.2.2

Esta numeracin de posiciones corresponde a una biyeccin



ndice:{1,2}*o+


que admite la siguiente definicin recursiva:



ndice(H)  =1
ndice(D.1) =2*ndice(D)
ndice(D.2) =2*ndice(D)+1


427

rboles

Con ayuda de ndice, podemos representar un rbol binario en un vector, almacenando la informacin del nodo D en la posicin ndice(D). Por ejemplo:
A
1 B

C 2

2.1 D
E 2.1.2

donde tenemos

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


que dara lugar al vector:

D
4

E
7

10

11 12 13 14

Como vemos, pueden quedar muchos espacios desocupados si el rbol tiene pocos nodos en
relacin con su nmero de niveles. Por este motivo, esta representacin slo se suele aplicar a
una clase especial de rboles binarios, que definimos a continuacin.

Un rbol binario de talla n se llama completo si y slo si todos sus nodos internos tienen
dos hijos no vacos, y todas sus hojas estn en el nivel n.

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 posiciones con nuevas hojas se obtiene un rbol completo.

Por ejemplo, el siguiente es un rbol completo

428

rboles

Y este es semicompleto:

Un rbol binario completo de talla n tiene el mximo nmero de nodos que puede tener un
rbol de esa talla. En concreto se verifica:
1. El nmero de nodos de cualquier nivel i en un rbol binario completo es mi = 2 i1
2. El nmero total de nodos de un rbol binario completo de talla n es Mn = 2 n 1. Y, por
lo tanto, la talla de un rbol binario completo con M nodos es log(M+1).
Como se puede demostrar fcilmente:
1. Induccin sobre i :

i=1  m1=1=211
i>1  mi=2mi1=H.I.22i2=2i1


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



n

Mn=

i 1

mi=

2i1=2n1

i 1

Usando estos dos resultados podemos demostrar que la definicin recursiva de ndice presentada anteriormente es correcta. La definicin no recursiva es como sigue: si D es un posicin de
nivel n+1 (n t 0)

ndice(D) =nmerodeposicionesdeniveles1..n+
nmerodeposicionesdeniveln+1hastaDinclusive

   =(2n1)+m

m
.1

.2

n+1
n+2

429

rboles

ndice(H)=1

ndice(D.1)= nmerodeposicionesdeniveles1..(n+1)+


    nmerodeposicionesdeniveln+2hastaD.1inclusive

   = (2n+11)+2(m1)+1=2n+1+2m2




   = 2ndice(D)

ndice(D.2)= nmerodeposicionesdeniveles1..(n+1)+


    nmerodeposicionesdeniveln+2hastaD.2inclusive

   = (2n+11)+2(m1)+2=2n+1+2m1

   = 2ndice(D)+1

Frmulas para la representacin indexada de rboles binarios


Recapitulando lo expuesto hasta ahora, tenemos que:

Un rbol binario completo de talla n tiene 2 n1 nodos.

Por tanto, declarando


const
max=2n1;
tipo
esp=Vector[1..max]deElem;

tendremos un vector donde podemos almacenar cualquier rbol binario de talla d n

Dado un nodo D almacenado en la posicin ndice(D) = i, tal que 1 d i d max, valen las siguientes frmulas para el clculo de otro nodos relacionados con i:

Padre: i div 2, si i > 1

Hijo izquierdo: 2i, si 2i d max

Hijo derecho: 2i + 1, si 2i+1 d max

Esta representacin es til para rboles completos y semicompletos, cuando estos se generan y procesan mediante operaciones que hagan crecer al rbol por niveles habra que
cambiar la especificacin del TAD.

No es una representacin til para implementar rboles binarios cualesquiera, con las
operaciones del TAD ARBIN[ELEM].

5.2.4

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:

430

rboles

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
Primer hijo
Hermano derecho

ARBOL BINARIO
Hijo izquierdo
Hijo derecho

Por ejemplo, el rbol general de la izquierda se representa como el rbol binario de la derecha:

E
C
F
G

La idea de esta transformacin se puede formalizar especificando las operaciones:


hazArbin:Bosque[Elem]oArbin[Elem]
hazBosque:Arbin[Elem]oBosque[Elem]


de manera que resulten dos biyecciones inversas una de la otra. La especificacin ecuacional
de estas operaciones:
hazArbin([])=ARBIN.Vaco

431

rboles

hazArbin([ARBOL.Cons(x,hs)/as])=ARBIN.Cons(hazArbin(hs),x,
hazArbin(as))

hazBosque(ARBIN.Vaco)=[]
hazBosque(ARBIN.Cons(iz,x,dr))=[ARBOL.Cons(x,hazBosque(iz))/
hazBosque(dr)]


Se puede demostrar por induccin estructural que efectivamente una operacin es la inversa
de la otra:
as:Bosque[Elem]:hazBosque(hazArbin(as))=as
b:Arbin[Elem]:hazArbin(hazBosque(b))=b


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 espacio?

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 d i d t) tiene n i1 nodos

el rbol completo tiene max = (n t 1) /( n 1) nodos

La biyeccin ndice : {1 .. n}* o + que numera las posiciones, admite la definicin


recursiva:
num(H)=1
num(D.i)=n*num(D)+(i1)   (1didn)

Dado un nodo D con posicin ndice(D)=m, 1 d m d max, se tiene:

Hijo i de D: n*m + i1, si d max

Padre de D: 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.

432

rboles

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 recorrido como una funcin que transforma un rbol en la lista de elementos de los nodos visitados
en el orden de la visita.
Clases de recorridos
Los principales recorridos de rboles binarios se clasifican como sigue:
Preorden (RID)
Inorden (IRD)
Postorden (IDR)

En profundidad
Recorridos
Por niveles

Los recorridos en profundidad se basan en la relacin padre-hijos y se clasifican segn el orden en que se consideren la raz (R), el hijo izquierdo (I) y el hijo derecho (D).
Ejemplo:
1
2
4

5
8

Preorden:
Inorden:
Postorden:
Niveles:

1, 2, 4, 5, 8, 3, 6, 9, 7
4, 2, 8, 5, 1, 9, 6, 3, 7
4, 8, 5, 2, 9, 6, 7, 3, 1
1, 2, 3, 4, 5, 6, 7, 8, 9

3
6
9

433

rboles

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 caminos de longitud mnima.

5.3.1

Recorridos en profundidad

Especificacin algebraica
Segn aparecen en las hojas con las especificaciones de los TAD:
tadRECPROFARBIN[E::ANY]

usa

 ARBIN[E],LISTA[E]

operaciones



 preOrd,inOrd,postOrd:Arbin[Elem]oLista[Elem]
ecuaciones















ftad

/*obs*/

iz,dr:Arbin[Elem]:x:Elem:
preOrd(Vaco)
=[]
preOrd(Cons(iz,x,dr))
=[x]++preOrd(iz)++preOrd(dr)
inOrd(Vaco)
=[]
inOrd(Cons(iz,x,dr))
=inOrd(iz)++[x]++inOrd(dr)
postOrd(Vaco)
=[]
postOrd(Cons(iz,x,dr))
=postOrd(iz)++postOrd(dr)++[x]

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];
{P0:cierto}
var
iz,dr:Lista[Elem];
inicio
siARBIN.esVaco(a)
entonces

434

rboles

LISTA.Nula(xs)
sino
iz:=preOrd(hijoIz(a));
dr:=preOrd(hijoDr(a));
LISTA.Cons(ARBIN.raz(a),iz);
LISTA.conc(iz,dr);
xs:=iz    %noseanulaniz,drporquecompartenestructura
conxs
fsi
{Q0:xs=preOrd(a)}
devxs
ffunc

En cuanto a la complejidad de esta operacin, suponemos una implementacin de las listas donde Nula, conc, ponDr y Cons tengan coste O(1) lo que nos obliga a utilizar implementaciones procedimentales de conc y ponDr. En ese caso obtenemos una complejidad para los tres
recorridos de O(n), como se puede ver aplicando los resultados tericos. Se trata de funciones
donde el tamao del problema disminuye por divisin:
c1

si 0 n < b

a T(n/b) + c nk

si n b

O(nk)

si a < bk

O(nk log n)

si a = bk

O( n logb a )

si a > bk

T(n) =

T(n)

si suponemos que se trata de un rbol completo, entonces tenemos:



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


a > bk coste O( n logb a ) = 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. Concretamente, 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 algoritmos iterativos de la misma complejidad. Es por ello que nos plateamos la realizacin iterativa
de los recorridos en profundidad.

435

rboles

Es posible obtener algoritmos de recorrido iterativos con ayuda de pilas de rboles. La idea
bsica es: en vez de hacer llamadas recursivas para recorrer los rboles hijos, apilamos estos en un
orden tal que cuando luego los desapilemos, sean procesados en el orden correcto.
Veamos grficamente cmo funciona esta idea en un ejemplo. Vamos a recorrer en preorden
el rbol que representa a la expresin x*(y)+(x)*y
+
*

*
_

Mostramos la pila de rboles indicando las posiciones de las races de los rboles apilados en
ella, cuando estos se consideran como subrboles de a:
Pila (cima a la derecha)

Recorrido

H
+
2, 1
+*
2, 1.2, 1.1
+*x
2, 1.2
+*x
2, 1.2.1
+*xy
2
etc.

Desarrollemos ahora formalmente esta idea.


Dada cualquier operacin de recorrido de rboles:

R:Arbin[Elem]oLista[Elem]


podemos generalizarla obteniendo otra operacin que acumula el efecto de R sobre una pila as
de rboles, concatenando las listas resultantes. Formalmente:
acumulaR:Pila[Arbin[Elem]]oLista[Elem]
acumulaR(PilaVaca)=[]
acumulaR(Apilar(a,as))=R(a)++acumulaR(as)

436

rboles

Usaremos las operaciones acumulaR para formular los invariantes de los algoritmos que siguen.
Tambin usaremos el concepto de tamao de un rbol binario, definido como la suma de nodos y
el nmero de arcos. Formalmente:
tamao:Arbin[Elem]oNat
tamao(Vaco)   =0
tamao(Cons(iz,x,dr)) =1    
esVaco(dr)

       =2+tamao(iz)
esVaco(dr)

       =2+tamao(dr)
esVaco(dr)

       =3+tamao(iz)

       +tamao(dr)
esVaco(dr)


 siesVaco(ix)AND
 siNOTesVaco(ix)AND
 siesVaco(ix)ANDNOT

 siNOTesVaco(ix)ANDNOT

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];
{P0:cierto}
var
as:Pila[Arbin[Elem]];
aux,iz,dr:Arbin[Elem];
x:Elem;
inicio
LISTA.Nula(xs);
siARBIN.esVaco(a)
entonces
seguir
sino
PILA.PilaVaca(as);
PILA.Apilar(a,as);
{I:preOrd(a)=xs++acumulapreOrd(as);
C:sumadelostamaosdelosrbolesdeas}
itNOTPILA.esVaca(as)o
aux:=PILA.cima(as);   /*auxnoesvaco*/
PILA.desapilar(as);
x:=ARBIN.raz(aux);
iz:=ARBIN.hijoIz(aux);
dr:=ARBIN.hijoDr(aux);
LISTA.ponDr(xs,x);
   /*visitarx*/
siARBIN.esVaco(dr)    /*apilardr,iz*/
entonces
seguir

437

rboles

sino
PILA.apilar(dr,as)
fsi;
siARBIN.esVaco(iz)
entonces
seguir
sino
PILA.apilar(iz,as)
fsi
fit
fsi
{Q0:xs=preOrd(a)}
devxs
ffunc

Recorrido en postorden:
funcpostOrd(a:Arbin[Elem])devxs:Lista[Elem];
{P0:cierto}
var
as:Pila[Arbin[Elem]];
aux,iz,dr:Arbin[Elem];
x:Elem;
inicio
LISTA.Nula(xs);
siARBIN.esVaco(a)
entonces
seguir
sino
PILA.PilaVaca(as);
PILA.Apilar(a,as);
{I:postOrd(a)=xs++acumulapostOrd(as);
C:sumadelostamaosdelosrbolesdeas}
itNOTPILA.esVaca(as)o
aux:=PILA.cima(as);   /*auxnoesvaco*/
PILA.desapilar(as);
x:=ARBIN.raz(aux);
iz:=ARBIN.hijoIz(aux);
dr:=ARBIN.hijoDr(aux);
siARBIN.esVaco(iz)ANDARBIN.esVaco(dr)
entonces
LISTA.ponDr(xs,x);    /*visitarx*/
sino         /*apilarx,dr,iz*/
PILA.Apilar(ARBIN.Cons(ARBIN.Vaco,x,ARBIN.Vaco),as);
siARBIN.esVaco(dr)
entonces
seguir
sino

438

rboles

PILA.apilar(dr,as)
fsi;
siARBIN.esVaco(iz)
entonces
seguir
sino
PILA.apilar(iz,as)
fsi
fsi
fit
fsi
{Q0:xs=postOrd(a)}
devxs
ffunc

Recorrido en inorden:
funcinOrd(a:Arbin[Elem])devxs:Lista[Elem];
{P0:cierto}
var
as:Pila[Arbin[Elem]];
aux,iz,dr:Arbin[Elem];
x:Elem;
inicio
LISTA.Nula(xs);
siARBIN.esVaco(a)
entonces
seguir
sino
PILA.PilaVaca(as);
PILA.Apilar(a,as);
{I:inOrd(a)=xs++acumulainOrd(as);
C:sumadelostamaosdelosrbolesdeas}
itNOTPILA.esVaca(as)o
aux:=PILA.cima(as);   /*auxnoesvaco*/
PILA.desapilar(as);
x:=ARBIN.raz(aux);
iz:=ARBIN.hijoIz(aux);
dr:=ARBIN.hijoDr(aux);
siARBIN.esVaco(iz)ANDARBIN.esVaco(dr)
entonces
LISTA.ponDr(xs,x);    /*visitarx*/
sino         /*apilardr,x,iz*/
siARBIN.esVaco(dr)
entonces
seguir
sino
PILA.apilar(dr,as)

439

rboles

fsi;
PILA.Apilar(ARBIN.Cons(ARBIN.Vaco,x,ARBIN.Vaco),as);
siARBIN.esVaco(iz)
entonces
seguir
sino
PILA.apilar(iz,as)
fsi
fsi
fit
fsi
{Q0:xs=inOrd(a)}
devxs
ffunc

Si todas las operaciones involucradas de pilas, rboles y listas tienen complejidad O(1) entonces los tres recorridos tienen complejidad O(n), siendo n el nmero de nodos. En el recorrido en
preorden cada nodo pasa una sola vez por la cima de la pila, por lo tanto se realizan n iteraciones.
En los recorridos en inorden y postorden los nodos internos pasan 2 veces y las hojas una sola vez,
por lo tanto se realizan menos de 2n pasadas.
En cuanto a la complejidad en espacio, debemos preguntarnos cul es el mximo nmero de
elementos que debe almacenar la pila; considerando que en una implementacin dinmica cada
nodo de la pila ocupa espacio 2: 1 por el puntero al rbol y 1 por el puntero al siguiente nodo. El
caso peor se da para rboles de la forma:
A

Para un rbol de esta forma con n nodos:

preOrd llega a crea una pila con n/2 nodos:

H
I
G
E
C

440

rboles

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 caso peor de construir pilas con n elementos.
Con un rbol totalmente degenerado hacia la derecha tenemos el caso mejor para inOrd porque la pila alcanza un tamao mximo de 2. En cambio para postOrd, sigue siendo el caso peor
con una pila de tamao n.
Cuando tenemos un rbol completo con n nodos, tenemos el caso intermedio para preOrd con
un tamao mximo para la pila de log n. Este es el caso mejor para postOrd con tamao 2log n. Y
tambin caso intermedio para inOrd con tamao 2log n.

5.3.2

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
+
*

*
_

Mostramos la cola de rboles indicando las posiciones de las races de los rboles almacenados
en ella, cuando estos se consideran como subrboles de a:
Cola (ltimo a la derecha)

Recorrido

H
+
1, 2
+*
2, 1.1, 1.2
+**
1.1, 1.2, 2.1, 2.2
+**x
1.2, 2.1, 2.2
+**x
2.1, 2.2, 1.2.1
etc.

441

rboles

Especificacin algebraica
Necesitamos utilizar una cola de rboles auxiliar. La cola se inicializa con el rbol a recorrer. El
recorrido de la cola de rboles tiene como caso base la cola vaca. Si de la cola se extrae un rbol
vaco, se avanza sin ms. Y si de la cola se extrae un rbol no vaco, se visita la raz, y se insertan
en la cola el hijo izquierdo y el derecho, por este orden
tadRECNIVELESARBIN[E::ANY]

usa

 ARBIN[E],LISTA[E]

usaprivadamente

 COLA[ARBIN[E]]

operaciones



 niveles:Arbin[Elem]oLista[Elem]          /*obs*/
operacionesprivadas




 nivelesCola:Cola[Arbin[Elem]]oLista[Elem]      /*obs*/
ecuaciones





















ftad

a:Arbin[Elem]:as:Cola[Arbin[Elem]]:
niveles(a)  =nivelesCola(Aadir(a,ColaVaca))
nivelesCola(as)=[] siesVaca(as)
nivelesCola(as)=nivelesCola(Avanzar(as))
        siNOTesVaca(as)ANDesVaco(primero(as))
nivelesCola(as)=[raz(primero(as))]++
      nivelesCola(Aadir(hijoDr(primero(as)),
      Aadir(hijoIz(primero(as)),
       avanzar(as))))
        siNOTesVaca(as)ANDNOTesVaco(primero(as))

Implementacin iterativa con ayuda de una cola


A partir de la especificacin algebraica es sencillo llegar a una implementacin iterativa. el invariante 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];
{P0:cierto}
var
as:Cola[Arbin[Elem]];
aux,iz,dr:Arbin[Elem];
x:Elem:
inicio
LISTA.Nula(xs);
siARBIN.esVaco(a)

442

rboles

entonces
seguir
sino
COLA.ColaVaca(as);
COLA.Aadir(a,as);
{I:niveles(a)=xs++nivelesCola(as)
todoslosrbolesdelacolaassonnovacos;
C:sumadelostamaosdelosrbolesdeas}
itNOTCOLA.esVaca(as)o
aux:=COLA.primero(as);
COLA.avanzar(as);
x:=ARBIN.raz(aux);
iz:=ARBIN.hijoIz(aux);
dr:=ARBIN.hijoDr(aux);
LISTA.ponDr(xs,x);
   /*visitarx*/
siARBIN.esVaco(iz)
entonces
seguir
sino        /*izalacola*/
COLA.Aadir(iz,as)
fsi;
siARBIN.esVaco(dr)
entonces
seguir
sino        /*dralacola*/
COLA.Aadir(dr,as)
fsi
fit
fsi
{Q0:xs=niveles(a)}
devxs
ffun


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 a0 del rbol inicial a, tal que la raz de a0 est a
nivel n, puede asegurarse que el resto de la cola slo contiene subrboles de nivel n (ms a la
derecha que a0) o n+1 (hijos de subrboles de nivel n a la izquierda de a0). 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.

443

rboles

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 aprovechar los muchos punteros nulos que quedan desaprovechados en la representacin dinmica del
rbol binario. En particular, se puede demostrar por induccin que un rbol binario con n nodos
tiene 2n+1 subrboles, de los cuales n+1 son vacos; por lo tanto, su representacin dinmica
tendr n+1 punteros nulos.
Los punteros nulos se sustituyen por punteros que facilitan un tipo particular de recorridos. Se
necesita algo ms de espacio para cada nodo pues es necesario indicar si los punteros son normales o hilvanes.

5.3.4

Transformacin de la recursin doble a iteracin

El esquema de la transformacin est en los apuntes. La idea es que la ejecucin de un algoritmo doblemente recursivo se corresponde con un recorrido en postorden del rbol de llamadas
determinado por la llamada inicial. La pila representa en cada momento el camino desde la raz
hasta el nodo que se est visitando en un momento dado.

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 aplicable 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.

444

rboles

5.4.1

Arboles ordenados

Un rbol ordenado es un rbol binario que almacena elementos de un tipo de la clase ORD.
Se dice que el rbol a est ordenado si se da alguno de los dos casos siguientes:
a es vaco. 

a no es vaco, sus dos hijos estn ordenados, todos los elementos del hijo izquierdo son
estrictamente menores que el elemento de la raz, y el elemento de la raz es estrictamente
menor que todos los elementos del hijo derecho.

Por ejemplo, el siguiente es un rbol ordenado:

20
12

31
17

43

26
35

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 produce una lista ordenada de elementos. De hecho, esta es una forma de caracterizar a los rboles
ordenados:
Un rbol binario a es un rbol ordenado si y slo si xs = inOrd(a) est ordenada, es decir.
i,j:1di<jd#xs:xs!!i<xs!!j

Es posible construir distintos rboles ordenados con la misma informacin, o, dicho de otro
modo, dos rboles de bsqueda distintos pueden dar el mismo resultado al recorrerlos en inorden. Por ejemplo, el siguiente rbol produce el mismo recorrido que el presentado anteriormente:

26
20
12
7

35
31

17

43

445

rboles

Las operaciones que nos interesan sobre rboles ordenados son: la insercin, la bsqueda y el
borrado. En esencia, las tres operaciones tienen que realizar una bsqueda en el rbol ordenado,
donde se saca partido de dicha caracterstica. Resulta adems interesante, equipar a los rboles
ordenados con una operacin de recorrido y otra que nos indique si un determinado elemento
est o no en el rbol.
Insercin en un rbol ordenado
En principio, suponemos que si intentamos insertar un elemento que ya est en el rbol, el resultado es el mismo rbol. Al presentar los rboles de bsqueda refinaremos esta idea.
La insercin se especifica de forma que si el rbol est ordenado, siga estndolo despus de la
insercin. La insercin de un dato y en un rbol ordenado a:

Si a 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 obtener la informacin de ese nodo mediante la operacin que obtiene la raz del rbol. Si ningn
nodo del rbol, contiene el elemento buscado, se devuelve el rbol vaco.
La idea de la bsqueda es similar a la insercin: si el elemento buscado est en la raz, ya
hemos terminado; si es menor que la raz buscamos en el hijo izquierdo; y si es mayor que la raz,
buscamos en el hijo derecho. Algebraicamente, podemos especificarla como:











busca(y,Vaco)=Vaco
busca(y,Cons(iz,x,dr))=Cons(iz,x,dr)siy==x
busca(y,Cons(iz,x,dr))=busca(y,iz)siy<x
busca(y,Cons(iz,x,dr))=busca(y,dr)siy>x

446

rboles

Borrado en un rbol ordenado


La operacin de borrado es la ms complicada, porque tenemos que reconstruir el rbol resultado 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 D, el comportamiento de la operacin depende del nmero de hijos de D:

Si D es una hoja, se elimina el nodo D

Si D tiene un solo hijo, se elimina el nodo D y se coloca en su lugar el rbol hijo, cuya raz
quedar en la posicin D

Si D tiene dos hijos se procede del siguiente modo:

Se busca el nodo con el valor mnimo en el hijo derecho de D. Sea D la posicin de


ste. (Alternativamente, se podra buscar el mayor nodo del hijo izquierdo de D.)

El elemento del nodo D se reemplaza por el elemento del nodo D.

Se borra el nodo D. Ntese que, por ser el mnimo del hijo derecho de D, D no puede tener hijo izquierdo, por lo tanto, estaremos en la situacin de eliminar una hoja o
un nodo con un solo hijo el derecho.

Especificado algebraicamente:


















borra(y,Vaco) =Vaco
borra(y,Cons(iz,x,dr))=drsiy==xANDesVaco(iz)
borra(y,Cons(iz,x,dr))=izsiy==xANDesVaco(dr)
borra(y,Cons(iz,x,dr))=Cons(iz,z,borra(z,dr))
       siy==xANDNOTesVaco(iz)AND
       NOTesVaco(dr)ANDz=min(dr)
borra(y,Cons(iz,x,dr))=Cons(borra(y,iz),x,dr)siy<x
borra(y,Cons(iz,x,dr))=Cons(iz,x,borra(y,dr))siy>x

Por ejemplo, aplicamos algunas operaciones de borrado al primer ejemplo que presentamos de
rbol ordenado:

borra(35, a)
20
12
17

31
43

26

borra(43, a)
20
12
7

borra(31, a)

31
17

26

43

447

rboles

20
12
7

35
17

26

43

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, renombramos 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 resultado es que este TAD exporta el tipo Arbus[Elem] equipado con las operaciones: Vaco, raz, esVaco, recorre, inserta, busca, borra y est?.
Ntese tambin que la operacin privada ordenado? no se utiliza en la especificacin de las operaciones exportadas. La razn de incluir esta operacin es poder luego utilizarla en los razonamientos 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])oArbus[Elem]/*gen*/

 busca:(Elem,Arbus[Elem])oArbus[Elem]/*mod*/

 borra:(Elem,Arbus[Elem])oArbus[Elem]/*mod*/




 est?:(Elem,Arbus[Elem])oBool /*obs*/
operacionesprivadas

 ordenado?:Arbus[Elem]oBool/*obs*/

 min:Arbus[Elem]oElem /*obs*/




 mayor,menor:(Arbus[Elem],Elem)oBool/*obs*/
ecuaciones















x,y,z:Elem:a,iz,dr:Arbus[Elem]:
defmin(a)siNOTesVaco(a)
min(Cons(iz,x,dr))=x siesVaco(iz)
min(Cons(iz,x,dr))=min(iz) siNOTesVaco(iz)
menor(Vaco,y)=cierto
menor(Cons(iz,x,dr),y)=x<yANDmenor(iz,y)ANDmenor(dr,y)

448

rboles


 mayor(Vaco,y)=cierto

 mayor(Cons(iz,x,dr),y)=x>yANDmayor(iz,y)ANDmayor(dr,y)

 ordenado?(Vaco) =cierto

 ordenado?(Cons(iz,x,dr)) =ordenado?(iz)ANDmenor(iz,x)AND

  ordenado?(dr)ANDmayor(dr,x)

 inserta(y,Vaco)=Cons(Vaco,y,Vaco)

 inserta(y,Cons(iz,x,dr)) =Cons(iz,x,dr)siy==x

 inserta(y,Cons(iz,x,dr)) =Cons(inserta(y,iz),x,dr)siy<x

 inserta(y,Cons(iz,x,dr)) =Cons(iz,x,inserta(y,dr))siy>x

 busca(y,Vaco)=Vaco

 busca(y,Cons(iz,x,dr))=Cons(iz,x,dr)siy==x

 busca(y,Cons(iz,x,dr))=busca(y,iz)siy<x

 busca(y,Cons(iz,x,dr))=busca(y,dr)siy>x

 borra(y,Vaco)=Vaco

 borra(y,Cons(iz,x,dr))=drsiy==xANDesVaco(iz)

 borra(y,Cons(iz,x,dr))=izsiy==xANDesVaco(dr)

 borra(y,Cons(iz,x,dr))=Cons(iz,z,borra(z,dr))

  siy==xANDNOTesVaco(iz)AND

  NOTesVaco(dr)ANDz=min(dr)

 borra(y,Cons(iz,x,dr))=Cons(borra(y,iz),x,dr)siy<x

 borra(y,Cons(iz,x,dr))=Cons(iz,x,borra(y,dr))siy>x

 est?(y,a) =NOTesVaco(busca(y,a))

errores

 min(Vaco)
ftad

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 decreciente.

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
tn = 2 (ln n + J + 1)
siendo J = 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 Structures, 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.

449

rboles

Este anlisis es cualitativamente similar al anlisis de la complejidad del quicksort, donde el caso
peor se tiene cuando el vector ya est ordenado, porque en ese caso la particin del subvector
slo reduce en 1 el tamao del problema. Este caso se corresponde con el rbol degenerado.
Si se quiere garantizar complejidad de O(log n) en el caso peor, es necesario restringirse a trabajar con alguna subclase de los rboles ordenados en la cual la talla se mantenga logartmica con
respecto al nmero de nodos. De esto nos ocuparemos en el siguiente apartado del tema, dedicado a los rboles AVL.
No nos ocupamos de la implementacin de los rboles ordenados, ya que slo los hemos introducido para presentar los conceptos bsicos de los rboles de bsqueda, que describimos a
continuacin, y de cuya implementacin s nos ocuparemos.

5.4.2

Arboles de bsqueda

La diferencia entre los rboles de bsqueda y los rboles ordenados radica en las condiciones
que se exigen a los elementos. En los rboles ordenados simplemente se exige que el tipo de los
elementos pertenezca a la clase ORD, es decir est equipado con operaciones de igualdad y comparacin. Sin embargo, en muchas aplicaciones las comparaciones entre elementos se puede establecer en base a una parte de la informacin total que almacenan dichos elementos: un campo
clave. Esta idea proviene de las bases de datos.
Supongamos por ejemplo que tuvisemos que manejar una coleccin de datos de personas
donde uno de los campo almacenados fuese su DNI. Errores administrativos al margen, podemos suponer que el DNI es nico y que, por lo tanto podemos identificar a una persona simplemente a partir de su DNI. Si almacensemos la coleccin de datos en un rbol ordenado,
podramos utilizar simplemente el DNI como clave de acceso.
En esta situacin, lo que deberamos exigirle a los elementos de un rbol de bsqueda es que
dispusieran de una operacin observadora cuyo resultado fuese de un tipo ordenado:
clave:ElemoClave


Siendo Clave un tipo que pertenece a la clase ORD. Definiramos as la clase de los tipos que
tienen un operacin de la forma clave, y especificaramos e implementaramos los rboles de
bsqueda en trminos de esta clase. Ntese que con este planteamiento los rboles ordenados
son un caso particular de los rboles de bsqueda donde la operacin clave es la identidad.
Sin embargo, podemos generalizar an ms el planteamiento si consideramos que la clave y el
valor son datos separados, de forma que en cada nodo del rbol se almacenen dos datos. Ntese
que este planteamiento engloba al anterior aunque tiene el inconveniente de que las claves se almacenaran dos veces. As, el TAD ARBUS tendr dos parmetros: el TAD de las claves y el
TAD de los valores.
Al separar las claves y los valores, se nos plantea la cuestin de qu hacer cuando intentamos
insertar un elemento asociado con una clave que ya est en el rbol. Dependiendo del tipo de los
elementos tendr sentido realizar distintas operaciones.
Con el objetivo de que el TAD sea lo ms general posible, la solucin que adoptamos es exigir
que el tipo de los valores tenga una operacin modificadora que combine elementos, con la signatura:

():(Elem,Elem)oElem

450

rboles

De forma que cuando insertemos un elemento asociado a una clave que ya existe, utilicemos
esta operacin para combinar los valores y obtener el nuevo dato que se debe almacenar en el
rbol. As, en cada ejemplar del TAD tendremos un comportamiento especfico del tipo de los
valores, segn la operacin que haya sido designada para realizar la combinacin. Por ejemplo, si
queremos que el nuevo elemento reemplace al antiguo entonces definiremos esta funcin como:
xy=y

Definimos entonces la siguiente clase de tipos, a la que deben pertenecer los valores de un
rbol de bsqueda
claseCOMB

hereda

 ANY

operaciones

 ():(Elem,Elem)oElem

%Operacindecombinacindeelementos.
fclase

Es decir, estamos exigiendo que los valores de un rbol de bsqueda tengan una operacin
con esa signatura que se llame combina. Sera ms flexible si en el lenguaje de implementacin pudisemos, al declarar una variable concreta de tipo Arbus, indicar explcitamente cul es la operacin que se corresponde con combina.
La idea de separar las claves de los datos, e indicar cul es la forma de combinar datos, se puede aplicar a todos los TAD que manejan colecciones de datos. Aunque tiene ms sentido cuando
las claves estn ordenadas y nos ayudan a construir una implementacin ms eficiente.
Con todo esto, la especificacin de los rboles de bsqueda queda igual que la de los rboles
ordenados, pero sustituyendo los elementos por parejas de (clave, valor), y utilizando la operacin
de combinacin cuando se inserta un elemento asociado con una clave que ya existe.
Especificacin algebraica
tadARBUSCA[C::ORD,V::COMB]
 renombraC.ElemaCla
 V.ElemaVal
 usa
  RECPROFARBIN[PAREJA[C,V]]renombrandoinOrdarecorre
 
   Arbin[Pareja[Cla,Val]]aArbus[Cla,Val]
 
   ocultandopreOrd,postOrd,
 
   Cons,hijoIz,hijoDr
 tipo
  Arbus[Cla,Val]
 operaciones
  inserta:(Cla,Val,Arbus[Cla,Val])oArbus[Cla,Val] /*gen*/
  busca:(Cla,Arbus[Cla,Val])oArbus[Cla,Val] /*mod*/
  borra:(Cla,Arbus[Cla,Val])oArbus[Cla,Val] /*mod*/
  est?:(Cla,Arbus[Cla,Val])oBool /*obs*/
 operacionesprivadas
  ordenado?:Arbus[Cla,Val]oBool /*obs*/
  min:Arbus[Cla,Val]oPareja[Cla,Val] /*obs*/
  mayor,menor:(Arbus[Cla,Val],Cla)oBool
/*obs*/
 ecuaciones
  c,c,d:Cla:x,x,y:Val:a,iz,dr:Arbus[Cla,Val]:

451

rboles

  defmin(a)siNOTesVaco(a)
  min(Cons(iz,Par(c,x),dr))  =Par(c,x) siesVaco(iz)
  min(Cons(iz,Par(c,x),dr))  =min(iz) siNOTesVaco(iz)
  menor(Vaco,c)  =cierto
  menor(Cons(iz,Par(c,x),dr),c) =c<cANDmenor(iz,c)AND
menor(dr,c)
  mayor(Vaco,c)  =cierto
  mayor(Cons(iz,Par(c,x),dr),c) =c>cANDmayor(iz,c)AND
mayor(dr,c)
  ordenado?(Vaco)  =cierto
  ordenado?(Cons(iz,Par(c,x),dr)) =ordenado?(iz)ANDmenor(iz,c)AND
 
ordenado?(dr)ANDmayor(dr,c)
  inserta(c,x,Vaco)  =Cons(Vaco,Par(c,x),Vaco)
  inserta(c,x,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(c,xx),dr)
 
    sic==c
  inserta(c,x,Cons(iz,Par(c,x),dr)) =Cons(inserta(c,x,iz),
Par(c,x),dr)
 
    sic<c
  inserta(c,x,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(c,x),inserta(c,
x,dr))
 
    sic>c
  busca(c,Vaco)  =Vaco
  busca(c,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(c,x),dr)sic==c
  busca(c,Cons(iz,Par(c,x),dr)) =busca(c,iz)sic<c
  busca(c,Cons(iz,Par(c,x),dr)) =busca(c,dr)sic>c
  borra(c,Vaco)      =Vaco
  borra(c,Cons(iz,Par(c,x),dr)) =drsic==cANDesVaco(iz)
  borra(c,Cons(iz,Par(c,x),dr)) =izsic==cANDesVaco(dr)
  borra(c,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(d,y),borra(d,dr))
 
 sic==cANDNOTesVaco(iz)AND
 
NOTesVaco(dr)ANDPar(d,y)=min(dr)
  borra(c,Cons(iz,Par(c,x),dr)) =Cons(borra(c,iz),Par(c,x),dr)si
c<c
  borra(c,Cons(iz,Par(c,x),dr)) =Cons(iz,Par(c,x),borra(c,dr))si
c>c
  est?(c,a) =NOTesVaco(busca(c,a))
 errores
  min(Vaco)
ftad

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, siguiendo directamente la idea de la especificacin algebraica:

funcinserta(x:Elem;a:Arbus[Elem])devb:Arbus[Elem];
{P0:R(x)R(a)}
inicio
siesVaco(a)
entonces
b:=Cons(Vaco,x,Vaco)
sino

452

rboles

si
ORD.igual(x,raz(a))ob:=a
ORD.menor(x,raz(a))ob:=Cons(inserta(x,hijoIz(a)),raz(a),
hijoDr(a))
ORD.mayor(x,raz(a))ob:=Cons(hijoIz(a),raz(a),
inserta(x,hijoDr(a)))
fsi
fsi
{Q0:R(b)A(b)=ARBORD[ELEM]inserta(x,A(a))}
devb
ffunc

En el rbol resultado b tendremos que los nodos recorridos en la bsqueda se habrn copiado,
como efecto de la operacin Cons, mientras que el resto de los nodos se compartirn. Un problema similar se plantea con la operacin de borrado
Vemoslo con un ejemplo. Dado el rbol a
17

27

21

33

25


si ejecutamos la operacin b := inserta(19, a), el resultado ser


a
17

17
9

27
27
21

33

21
25
19


A diferencia de la comparticin de estructura que producen las funciones Cons, hijoIz o hijoDr,
esta otra es arbitraria y en la prctica impedira cualquier intento de anulacin de los rboles invo-

453

rboles

lucrados, a no ser que decidisemos dejar de usar al mismo tiempo todos los rboles que sabemos
que pueden compartir algn nodo. Una solucin a este problema sera hacer que el resultado no
compartiese ningn nodo con el argumento, para lo cual deberamos realizar copias al hacer las
llamadas recursivas:

b:=Cons(inserta(x,hijoIz(a)),raz(a),copia(hijoDr(a)))
b:=Cons(copia(hijoIz(a)),raz(a),inserta(x,hijoDr(a)))

Pero de esta forma tenemos que el coste de la operacin pasa a O(n), con lo cual perdemos la
ventaja que estamos pretendiendo obtener con los rboles ordenados.
La solucin es por tanto implementar inserta y borra como procedimientos que acceden a la estructura interna de la representacin; es decir, no podemos implementarlo como un mdulo
cliente de los rboles binarios, sino como un mdulo donde se vuelva a definir el tipo.
Tipo representante
La definicin del tipo es similar a la de los rboles binarios, aunque cada nodo contiene dos
campos de informacin en lugar de uno. Lo escribimos tal y como aparecera en el mdulo de
implementacin, para indicar que el tipo de los valores y las claves se importa de los mdulos
abstractos COMB y ORD.

mduloimplARBUSCA[CLA,VAL]
importa
COMB,ORD
privado
tipo
Val=COMB.Elem;
Cla=ORD.Elem;
Nodo=reg
cla:Cla;
val:Val;
hi,hd:Enlace
freg;
Enlace=punteroaNodo;
Arbus[Cla,Val]=Enlace;

%Implementacindelasoperaciones

fmdulo

Invariante de la representacin
Exigimos que cumplan el invariante de la representacin de los rboles binarios sin comparticin de estructura, y que el resultado del recorrido en inorden est ordenado.
Dado p : Enlace



 RArbus[Cla,Val]

def

454

rboles

 RNCArbin[Elem](p)




 i,j:1di<jd#inOrd(p):inOrd(p)!!i<inOrd(p)!!j

Ntese que podemos exigir el invariante sin comparticin de estructura porque en ARB-BUS
no est disponible la operacin Cons.
Funcin de abstraccin
La misma que se utiliza con rboles binarios:
Implementacin de las operaciones

funcbusca(d:Cla;aArbus[Cla,Val])devb:Arbus[Cla,Val];
{P0:R(d)R(a)}
inicio
sia==nil
entonces
b:=nil
sino
si
ORD.igual(d,a^.cla)ob:=a
ORD.menor(d,a^.cla)ob:=busca(d,a^.hi)
ORD.mayor(d,a^.cla)ob:=busca(d,a^.hd)
fsi
fsi
{Q0:R(b)A(b)=ARBUSCA[CLA,VAL]busca(A(d),A(a))}
devb
ffunc


procinserta(ed:Cla;ey:Val;esa:Arbus[Cla,Val]);
{P0:R(d)R(y)R(a)a=A}
inicio
sia==nil

entonces
ubicar(a);
a^.cla:=d;
a^.val:=y;
a^.hi:=nil;
a^.hd:=nil;
sino
si
ORD.igual(d,a^.cla)oa^.val:=COMB.combina(a^.val,y)
ORD.menor(d,a^.cla)oinserta(d,y,a^.hi) /*ref*/
ORD.mayor(d,a^.cla)oinserta(d,y,a^.hd) /*ref*/
fsi

455

rboles

fsi
{Q0:R(a)A(a)=ARBUSCA[CLA,VAL]inserta(A(d),A(y),A(A))}
fproc


En las dos instrucciones marcadas como ref es donde se muestra la necesidad de que esta operacin tenga acceso a la representacin interna de los rboles. En las siguientes operaciones tambin ocurre esto cada vez que pasamos como parmetro actual uno de los hijos del nodo en
cuestin.
Para la operacin de borrado vamos a utilizar dos procedimientos auxiliares privados.

procborra(ed:Cla;esa:Arbus[Cla,Val]);
{P0:R(d)R(a)a=A}
inicio
sia==nil
entonces
seguir
sino
si
ORD.igual(d,a^.cla)oborraRaz(a)
ORD.menor(d,a^.cla)oborra(d,a^.hi)
ORD.mayor(d,a^.cla)oborra(d,a^.hd)
fsi
fsi
{Q0:R(a)A(a)=ARBUSCA[CLA,VAL]borra(A(d),A(A))}
fproc


Las operaciones auxiliares de borrado:



procborraRaz(esa:Arbus[cla,Val]);
{P0:R(a)a=Aa/=nil}
var
vacoIz,vacoDr:Bool;
aux:Arbus[Cla,Val];
inicio
vacoIz:=a^.hi==nil;
vacoDr:=a^.hd==nil;
si
vacoIzoaux:=a;
%COMB.anula(a^.val);
a:=a^.hd;
liberar(aux)
vacoDroaux:=a;
%COMB.anula(a^.val);
a:=a^.hi;
liberar(aux)

456

rboles

NOTvacoIzANDNOTvacoDroborraConMin(a,a^.hd)
fsi
{Q0:R(a)A(a)=ARBUSCA[CLA,VAL]borra(A(A^.cla),A(A))}
fproc


Dejando fijo el puntero a, descendemos por sus hijos izquierdos con el parmetro b hasta llegar al menor, y en ese punto realizamos el borrado

procborraConMin(esa,b:Arbus[cla,Val]);
{P0:a=AR(a)a/=nila^.hi/=nila^.hd/=nil
besundescendientedea^.hdvaunacadenadehijosizquierdos}
var
aux:Arbus[Cla,Val];
inicio
sib^.hi/=nil
entonces
borraConMin(a,b^.hi)
sino
a^.cla:=b^.cla;
%COMB.anula(a^.val);
a^.val:=b^.val;
aux:=b;
b:=b^.hd;
liberar(aux)
fsi
{Q0:R(a)A(a)=ARBUSCA[CLA,VAL]borra(A(A^.cla),A(A))}
Fproc


5.5 Arboles AVL


5.5.1

Arboles equilibrados

En el tema anterior vimos cmo en los rboles de bsqueda se consigue una complejidad logartmica para las operaciones de bsqueda, insercin y borrado en el caso promedio, aunque en
le caso peor llega a ser O(n). Si se quiere garantizar complejidad logartmica en el caso peor, es
necesario restringirse a trabajar con alguna subclase de la clase de los rboles ordenados en la cual
la talla se mantenga logartmica con respecto al nmero de nodos.
Una familia F de rboles binarios se llama equilibrada si existen constantes n0 , c + tales
que para todo n t n0 y todo rbol a F con n nodos se tenga talla(a) d c log n.
Efectivamente, para rboles pertenecientes a una familia equilibrada la complejidad de las operaciones antes citadas en el caso peor es O(t) siendo t la talla, y por lo tanto resulta O(log n).
Algunos ejemplos de familias equilibradas son:
La familia de los rboles semicompletos

457

rboles

La familia de los rboles equilibrados en nmero de nodos. Un rbol pertenece a esta familia si cualquier subrbol suyo cumple que el nmero de nodos del hijo izquierdo y el
nmero de nodos del hijo derecho difieren a lo sumo en 1:

La familia de los rboles equilibrados en altura. Un rbol pertenece a esta familia si cualquier subrbol suyo cumple que la talla del hijo izquierdo y la talla del hijo derecho difieren a lo sumo en 1:

Ntese que, como se puede observar en los anteriores ejemplos, todos los rboles semicompletos son equilibrados en altura, pero que existen rboles equilibrados en altura que no son semicompletos. La idea es que para conseguir el coste logartmico no es necesario exigir una
condicin tan fuerte como la semicompletitud sino que basta con una condicin ms dbil: el
equilibrio en altura.
En 1962, G. M. Adelson-Velskii y E. M. Landis demostraron que la familia de los rboles
equilibrados en altura son equilibrados en el sentido de la definicin dada ms arriba. En honor
suyo, la familia suele llamarse familia de los rboles AVL. En efecto, los inventores de esta familia
demostraron que un rbol AVL con n nodos tiene una talla t acotada como sigue:

log(n+1)dt<14404log(n+2)0328


El caso peor, es decir, los rboles AVL con el mayor desequilibrio posible, se alcanza para los
llamados rboles de Fibonacci. Para cada talla t t 0, el rbol de Fibonacci at y su nmero de nodos nt
se construyen recursivamente como sigue:

t=0

a0 = Vaco

n0 = 0

t=1

a1 = Cons(a0, 1, a0)

n1 = 1

tt2

at = Cons( at2, i, at1[+ i])

nt = nt2 + 1 + nt1

donde
i es el mnimo valor t 1 que no aparece en at2
at1[+ i] es el rbol que se obtiene sumando i a cada nodo de at1

458

rboles

Por ejemplo:

a1

a2

1
2

a3

2
1

3
4

a4

3
1

5
2

6
7

Los nmeros nt se llaman nmeros de Leonardo. Para los rboles de Fibonacci at se alcanza el
caso peor de la estimacin anterior

t|14404log(n+2)0328|15logn

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

459

rboles

  menor(Cons(iz,Par(v,x),dr),u)  =v<uANDmenor(iz,u)ANDmenor(dr,
u)

  mayor(Vaco,u) =cierto
  mayor(Cons(iz,Par(v,x),dr),u)  =v>uANDmayor(iz,u)ANDmayor(dr,
u)

  equilibrado?(Vaco)  =cierto
  equilibrado?(Cons(iz,ux,dr)) =equilibrado?(iz)ANDequilibrado?(dr)
AND
 
 |talla(iz)talla(dr)|d1

  talla(Vaco) =0
  talla(Cons(iz,us,dr)) =1+max(talla(iz),talla(dr))


5.5.2

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 nodos 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:

rotDr

y
x

c
b

x
y

a
rotIz

Una propiedad interesante de este proceso es que si cualquiera de las operaciones de rotacin
se aplica a un rbol ordenado, el resultado es un nuevo rbol ordenado con el mismo recorrido
en inorden que el rbol original.

460

rboles

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 desequilibrado para proceder a reequilibrarlo con ayuda de rotaciones. Para caracterizar las distintas situaciones que pueden darse introducimos el concepto de factor de equilibrio, que toma uno entre
tres valores posibles: equilibrado, desequilibrado a la izquierda y desequilibrado a la derecha.
Formalmente, introducimos en la especificacin el tipo privado FactEq, con las siguientes generadoras:

 tipoprivado
  FactEq 
 operacionesprivadas


DI:oFactEq

    /*gen*/



EQ:oFactEq

    /*gen*/




DD:oFactEq

    /*gen*/

Y una operacin que obtiene el factor de equilibrio de un rbol dado:


  factEq:Arbus[Cla,Val]oFactEq/*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 explcitamente en cada nodo. La razn es que aunque sera posible averiguar dicho factor calculando las
tallas de los subrboles involucrados, este mtodo encarecera demasiado los algoritmos.
Insercin
La idea es que este proceso, como ya indicamos antes, consiste en una insercin ordinaria seguida de reequilibrado, si ste es necesario.
Algebraicamente, se corresponde con la siguiente especificacin:

  inserta(v,y,Vaco)  =Cons(Vaco,Par(v,y),Vaco)

  inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,xy),dr)
  siv==u

  inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,x),dr)

rboles

461

  siv<uANDiz=inserta(v,y,iz)ANDtalla(iz)==talla(iz)

  inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,x),dr)
  siv<uANDiz=inserta(v,y,iz)ANDtalla(iz)==talla(iz)+1
AND
  factEq(Cons(iz,Par(u,x),dr)/=DI

  inserta(v,y,Cons(iz,Par(u,x),dr))=reeqIz(Cons(iz,Par(u,x),dr))
  siv<uANDiz=inserta(v,y,iz)ANDtalla(iz)==talla(iz)+1
AND
  factEq(Cons(iz,Par(u,x),dr)==DI /*Iz*/

  inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,x),dr)
  siv>uANDdr=inserta(v,y,dr)ANDtalla(dr)==talla(dr)

  inserta(v,y,Cons(iz,Par(u,x),dr))=Cons(iz,Par(u,x),dr)
  siv>uANDdr=inserta(v,y,dr)ANDtalla(dr)==talla(dr)+1
AND
  factEq(Cons(iz,Par(u,x),dr)/=DD

  inserta(v,y,Cons(iz,Par(u,x),dr))=reeqDr(Cons(iz,Par(u,x),dr))
  siv>uANDdr=inserta(v,y,dr)ANDtalla(dr)==talla(dr)+1
AND
  factEq(Cons(iz,Par(u,x),dr)==DD /*Dr*/



El reequilibrado se hace necesario cuando la insercin modifica la talla del subrbol donde se
realiza, y el rbol original estaba desequilibrado hacia ese subrbol. Ntese que en este caso basta
con reequilibrar el subrbol ms prximo a la nueva hoja que se haya desequilibrado y corregir el
desequilibrio con ayuda de rotaciones; ya que, por efecto de las rotaciones, el subrbol reequilibrado conserva la misma talla que tena antes de desequilibrarse, por lo que el rbol total queda
equilibrado.
Se produce desequilibrio cuando algn rbol a que era equilibrado antes de la insercin se
convierte despus de la insercin en un rbol no equilibrado a. Suponemos que a es el subrbol
ms profundo en el cual la insercin causa desequilibrio. Esto slo es posible en dos casos:

Caso [Iz]: a tena factor de equilibrio DI. La insercin se ha producido en el hijo izquierdo
de a, y a ha quedado:

462

rboles

t+1




Caso [Dr]: a tena factor de equilibrio DD. La insercin se ha producido en el hijo derecho de a, y a ha quedado:
x

t+1




Ntese que el subrbol izquierdo, en el caso [Iz], y el subrbol derecho, en el caso [Dr], no
pueden ser vacos, pues de lo contrario la insercin no podra haber causado desequilibrio.
Vamos a tratar slo el caso [Dr], dejando [Iz], que es simtrico, como ejercicio.
Hemos de distinguir dos situaciones, segn en qu hijo del subrbol derecho se ha producido
la insercin:

[DrDr]: se ha insertado en el subrbol derecho del subrbol derecho de a.


Se reequilibra realizando una rotacin a la izquierda del rbol.




463

rboles

rotIz

en
y
t

a0

c0
t

c0

b0

a0

b0

Ntese que b0 debe tener talla t, porque el rbol considerado es el subrbol ms profundo
que se ha desequilibrado al insertar.
Observamos que el resultado final queda AVL con factor de equilibrio EQ y la misma talla t+2 que el rbol original a. Por lo tanto, si a era un subrbol de un rbol mayor, tambin ste ha quedado equilibrado.


Podemos verlo aplicado a un ejemplo:

Insertar
4

11

10

10

rotIz
8
4
2

11

en
10

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

464

rboles

rotDr

Insertar
x

en 2

y
y
rotIz
en
y

Aunque hemos hecho la transformacin utilizando las dos rotaciones de la solucin general, para mostrar que efectivamente dicha solucin funciona, en la prctica esto se puede
implementar como un caso especial que se realiza en un solo paso.
En el caso no trivial se tiene:

rotDr
x

en 2
y

a0

z
t

z
t-1 b0

c0

d0

t-1

a0

c0

nuevo < z nuevo > z

rotIz
en
z
y

a0

t-1 b0
nuevo < z

t-1 b0

c0 t-1
nuevo > z

d0

t-1

d0

465

rboles

Tanto si el nuevo nodo ha quedado en b0 como si ha quedado en c0, 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:

Insertar

4
2

8
6

10

10
7

rotIz

6
4
2

en
8

rotDr
en 2

10

8
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)) =drotIz(Cons(iz,ux,dr))sifactEq(dr)/=DI

  %Caso[DrIz]:
  reeqDr(Cons(iz,ux,dr))=drotIz(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 supresin s que puede presentarse.
El algoritmo de insercin AVL se puede programar recursivamente siguiendo este mtodo.
Conviene utilizar un procedimiento auxiliar con un parmetro s crece? : Bool, que informa de si una
insercin ha aumentado la talla. Esta informacin permite reequilibrar y actualizar los factores de
equilibrio. La correccin del desequilibrio debe aplicarse al primer subrbol desequilibrado que se
encuentre yendo desde la nueva hoja hacia la raz.

466

rboles

En cuanto a la complejidad del algoritmo de insercin, el anlisis matemtico an no est resuelto del todo, pero las pruebas empricas indican que:

La talla media que puede esperarse para un rbol AVL generado por la insercin aleatoria
de n claves diferentes es 025 + log n.

Un promedio de 1 de cada 2 inserciones requiere reequilibrio. Todos los tipos de desequilibrio son equiprobables, y, como acabamos de ver, un desequilibrio siempre se puede corregir con 1 o 2 rotaciones.

5.5.3

Implementacin

Al igual que ocurre con los rboles de bsqueda es necesario implementar los rboles AVL
con acceso al tipo representante de los rboles binarios.
Tipo representante
El tipo representante es similar al de los rboles de bsqueda, pero aadiendo el campo donde
se almacena el factor de equilibrio.

mduloimplAVL[CLA,VAL]
importa
COMB,ORD
privado
tipo
FactEq=DI|EQ|DD;
Val=COMB.Elem;
Cla=ORD.Elem;
Nodo=reg
feq:FactEq;
cla:Cla;
val:Val;
hi,hd:Enlace
freg;
Enlace=punteroaNodo;
Arbus[Cla,Val]=Enlace;

%Implementacindelasoperaciones
fmdulo;

Invariante de la representacin
Exigimos que cumplan el invariante de la representacin de los rboles binarios sin comparticin de estructura, y que el resultado del recorrido en inorden est ordenado.
Dado p : Enlace



 RAVLArbus[Cla,Val]

def


 RNCArbin[Elem](p)

 i,j:1di<jd#inOrd(p):inOrd(p)!!i<inOrd(p)!!j

467

rboles





 p:Enlaces(p):p^.feqrepresentacorrectamente
 elfactordeequilibriodep

Implcitamente en la ltima condicin estamos exigiendo que sea equilibrado en altura, por la
forma cmo estn definidos los factores de equilibrio.
Ntese que podemos exigir el invariante sin comparticin de estructura porque en AVL no
est disponible la operacin Cons.
Funcin de abstraccin
La misma que se utiliza con rboles binarios:
Implementacin de las operaciones
A excepcin de la insercin y el borrado, todas las operaciones se implementan de la misma
forma que en los rboles de bsqueda. Para no extendernos slo presentamos la implementacin
de algunas de las operaciones. En el texto de Franch se pueden encontrar el resto.

procinserta(ed:Cla;ey:Val;esa:Arbus[Cla,Val]);
{P0:R(d)R(y)R(a)a=A}
var
crece?:Boolean;
inicio
insertaAVL(d,y,a,crece?)
{Q0R(a)A(a)=AVL[CLA,VAL]inserta(A(d),A(y),A(A))}
fproc



Procedimiento privado auxiliar de insercin



procinsertaAVL(ed:Cla;ey:Val;esa:Arbus[Cla,Val];screce?:
Bool);
{P0:R(d)R(y)R(a)a=A}
inicio
sia==nil
entonces
ubicar(a);
a^.cla:=d;
a^.val:=y;
a^.hi:=nil;
a^.hd:=nil;
a^.feq:=EQ;
crece?:=cierto
sino
si
ORD.igual(d,a^.cla)oa^.val:=COMB.combina(a^.val,y);
crece?:=falso
ORD.menor(d,a^.cla)oinsertaAVL(d,y,a^.hi,crece?)

468

rboles

siNOTcrece?
entonces
seguir
sino
casoa^.feqde
DDoa^.feq:=EQ;crece?:=falso
EQoa^.feq:=DI
DIoreeqIz(d,a);crece?:=falso
fcaso
fsi
ORD.mayor(d,a^.cla)oinsertaAVL(d,y,a^.hd,crece?)
siNOTcrece?
entonces
seguir
sino
casoa^.feqde
DIoa^.feq:=EQ;crece?:=falso
EQoa^.feq:=DD
DDoreeqDr(d,a);crece?:=falso
fcaso
fsi
fsi
fsi
{Q0:R(a)A(a)=AVL[CLA,VAL]inserta(A(d),A(y),A(A))
crece?=AVL[CLA,VAL](talla(A(a))==1+talla(A(A)))}
fproc


Vemos que en este procedimiento, cuando se realiza un reequilibrado se pone la variable crece?
a falso, con lo que ya no se producirn ms reequilibrados a la vuelta de las llamadas recursivas.
en la implementacin de borra el reequilibrado debe tener otro parmetro adicional que nos indique si dicha operacin ha hecho decrecer la talla del rbol, con lo que se debera propagar dicha
informacin hacia las llamadas anteriores.
Procedimiento auxiliar privado de reequilibrado por la derecha.

procreeqDr(ed:Cla;esa:Arbus[Cla,Val]);
{P0:a=ARARBUSCA[CLA,VAL](a)
talla(hijoDr(A(a)))=ARBUSCA[CLA,VAL]talla(hijoIz(A(a)))+2
deslaclavedelnodoqueproduceeldesequilibrio}
inicio
sia^.hi==nilAND(a^.hd)^.hd==nil
entonces    /*desequilibriotrivial*/
reorgDr(a);
a^.feq:=EQ;
a^.hi^.feq:=EQ;
a^.hD^.feq:=EQ

469

rboles

sino
sia^.hd^.feq==DD
entonces
rotIz(a);
a^.feq:=EQ;
a^.hi^.feq:=EQ
sino
rotDr(a^.hd);
rotIz(a);
a^.feq:=EQ;
si
ORD.menor(d,a^.cla)oa^.hi^.feq:=EQ;
a^.hd^.feq:=DD
ORD.mayor(d,a^.cla)oa^.hi^.feq:=DI;
a^.hd^.feq:=EQ
fsi
fsi
fsi
{Q0:RAVL[CLA,VAL](a)recorre(A(a))=ARBUSCA[CLA,VAL]recorre(A(A))}
fproc

Procedimiento auxiliar privado de reorganizacin a la derecha:



procreorgDr(esa:Arbus[Cla,Val]);
{P0:a=ARARBUS[CLA,VAL](a)a^.hi==nila^.hd/=nil
a^.hd^.hd==nila^.hd^.hi/=nil
a^.hd^.hi^.hi==nila^.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
{Q0:RAVL[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]);
{P0:arepresentaunrboldelaformaCons(Cons(a,ux,b),vy,c)}
var
aux:Enlace;
inicio
aux:=arb;

470

rboles

arb:=arb^.hi;
arb^.hi:=arb^.hd;
arb^.hd:=aux
{Q0:arbrepresentaelrbolCons(a,ux,Cons(b,vy,c)),
siendoa,ux,b,vy,closmismodeP0}
fproc

Borrado
La idea es que este proceso, como ya indicamos antes, consiste en un borrado ordinario seguida de reequilibrado, si ste es necesario.
Mediante ecuaciones, podemos especificar esta operacin de la siguiente forma:
  borra(v,Vaco) =Vaco

  borra(v,Cons(iz,Par(u,x),dr))  =quitaRaz(Cons(iz,Par(u,x),dr))
  siv==u

  borra(v,Cons(iz,Par(u,x),dr))  =Cons(iz,Par(u,x),dr)
  siv<uANDiz=borra(v,iz)ANDtalla(iz)==talla(iz)

  borra(v,Cons(iz,Par(u,x),dr))  =Cons(iz,Par(u,x),dr)
  siv<uANDiz=borra(v,iz)ANDtalla(iz)==talla(iz)1AND
  factEq(Cons(iz,Par(u,x),dr)/=DD

  borra(v,Cons(iz,Par(u,x),dr))  =reeqDr(Cons(iz,Par(u,x),dr))
  siv<uANDiz=borra(v,iz)ANDtalla(iz)==talla(iz)1AND
  factEq(Cons(iz,Par(u,x),dr)==DD /*Dr*/

  borra(v,Cons(iz,Par(u,x),dr))  =Cons(iz,Par(u,x),dr)
  siv>uANDdr=borra(v,dr)ANDtalla(dr)==talla(dr)

  borra(v,Cons(iz,Par(u,x),dr))  =Cons(iz,Par(u,x),dr)
  siv>uANDdr=borra(v,dr)ANDtalla(dr)==talla(dr)1AND
  factEq(Cons(iz,Par(u,x),dr)/=DI

  borra(v,Cons(iz,Par(u,x),dr))  =reeqIz(Cons(iz,Par(u,x),dr))
  siv>uANDdr=borra(v,dr)ANDtalla(dr)==talla(dr)1AND
  factEq(Cons(iz,Par(u,x),dr)==DI /*Iz*/


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

471

rboles

  siesVaco(dr)

  quitaRaz(Cons(iz,ux,dr))  =Cons(iz,Par(v,y),dr)
  siNOTesVaco(iz)ANDNOTesVaco(dr)ANDPar(v,y)=min(dr)AND
  dr=borra(v,dr)AND
  talla(dr)==talla(dr)

  quitaRaz(Cons(iz,ux,dr))  =Cons(iz,Par(v,y),dr)
  siNOTesVaco(iz)ANDNOTesVaco(dr)ANDPar(v,y)=min(dr)AND
  dr=borra(v,dr)AND
  talla(dr)==talla(dr)1ANDfactEq(Cons(iz,ux,dr))/=DI

  quitaRaz(Cons(iz,ux,dr))  =reeqIz(Cons(iz,Par(v,y),dr))
  siNOTesVaco(iz)ANDNOTesVaco(dr)ANDPar(v,y)=min(dr)AND
  dr=borra(v,dr)AND
  talla(dr)==talla(dr)1ANDfactEq(Cons(iz,ux,dr))==DI


La idea del reequilibrado consiste ahora en localizar el subrbol ms profundo a lo largo del
camino de bsqueda que se haya desequilibrado, y corregir el desequilibrio con ayuda de rotaciones. Puede suceder ahora que el reequilibrio produzca un subrbol cuya talla sea una unidad menor que la que haba antes del borrado, causndose un nuevo desequilibrio. En este caso el
proceso de reequilibrio tiene que reiterarse.
Se produce desequilibrio cuando algn rbol a que era equilibrado antes del borrado se convierte despus de la insercin en un rbol no equilibrado a. Suponemos que a es el subrbol ms
profundo en el cual el borrado causa desequilibrio. Esto slo es posible en dos casos:

Caso [Iz]: a tena factor de equilibrio DI. El borrado se ha producido en el hijo derecho
de a, y a ha quedado:

t+1

t-1




Caso [Dr]: a tena factor de equilibrio DD. El borrado se ha producido en el hijo izquierdo de a, y a ha quedado:

472

rboles

t-1

t+1

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 nodos, ya que en caso contrario un borrado en el hijo izquierdo, no podra haber causado desequilibrio. Tenemos pues que a es de la forma:
x
y
t-1

a0
t+1
c0

b0

Hacemos la distincin de casos dependiendo de la talla de los subrboles b0 y c0.

talla(b0) = t, talla(c0) = t
Es decir, tenemos que el factor de equilibrio del hijo derecho de a es EQ.
El rbol a puede reequilibrarse como en el caso [DrDr] de la insercin AVL:

rotIz

en
y
t-1

a0

c0
t

b0

c0

t-1

a0

b0

El rbol resultante es AVL con factor de equilibrio DI y la misma talla t+2 del rbol a
original. Si a era un subrbol de un rbol mayor, ste tambin ha quedado reequilibrado.

473

rboles

talla(b0) = t1, talla(c0) = 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.
rotIz

en
y

a0

t-1

c0
b0

t-1

c0

t-1

a0

b0

t-1

En este caso, el rbol AVL resultante tiene factor de equilibrio EQ y talla t+1, una unidad
menos que a. Por lo tanto, si a era subrbol de un rbol mayor, ste puede necesitar an
ser reequilibrado.

talla(b0) = t, talla(c0) = t1
Es decir, tenemos que el factor de equilibrio del hijo derecho de a es DI. El reequilibrio
puede lograrse con dos rotaciones, como en el caso [DrIz] de la insercin AVL
rotDr
x

t-1

en 2
y

a0

t-1

c0

t
d0

a0

t-1

t-1?

d0

e0

t
t-1?

rotIz
en

z
y

x
t

t
a0

d0

e0

c0

e0

c0

474

rboles

Donde hay combinaciones posibles para las tallas de d0 y e0. Si f es el factor de equilibrio
de

z
d0

e0

se tiene
f
EQ
DI
DD

talla(d0)
t1
t1
t2

talla(e0)
t1
t2
t1

Ntese que al menos uno de los dos rboles d0 , e0 debe tener talla t1, con lo cual es seguro que el rbol resultante de las dos rotaciones es AVL con factor de equilibrio EQ y talla
t+1, una unidad menor que la del rbol original a. Si a era un subrbol de otro rbol mayor, ste puede seguir necesitando reequilibrado.
Observamos, por lo tanto, que la operacin de reequilibrado es prcticamente igual que en el
caso del reequilibrado provocado por una insercin. La nica diferencia radica en que ahora hay
un caso ms: fatEq(dr) = EQ. A nivel de implementacin tambin podemos establecer diferencias
porque en el reequilibrado por insercin no hace falta considerar el caso adicional, y porque en el
reequilibrado por borrado nos hace falta un parmetro adicional que indique si la talla del rbol
ha disminuido. Aunque, tambin existe la posibilidad de hacer una nica implementacin.
El algoritmo de borrado AVL se puede programar recursivamente siguiendo este mtodo.
conviene utilizar un procedimiento auxiliar con un parmetro s encoge? : Bool, que informa de si un
borrado ha disminuido la talla. Esta informacin permite reequilibrar y actualizar los factores de
equilibrio.
Para terminar con este apartado dedicado a las operaciones de insercin y borrado en rboles
AVL, indicar que existe una diferencia fundamental entre ellas:

En cada insercin AVL, hay que reequilibrar a los sumo 1 subrbol (1 o 2 rotaciones).

En un borrado AVL, puede llegar a ser necesario reequilibrar todos los subrboles encontrados a lo largo de la trayectoria de bsqueda, que tiene una longitud O(log n), si n s el
nmero de nodos. Cada reequilibrado requiere 1 o 2 rotaciones. Los rboles de Fibonacci
dan lugar a este caso psimo en el comportamiento del borrado.

Sorprendentemente, las pruebas empricas indican que en promedio se efectan:

1 rotacin cada 2 inserciones

1 rotacin cada 5 borrados.

475

rboles

5.6 Ejercicios
Arboles: modelo matemtico y especificacin
238. Dibuja los rboles siguientes:
 *(a) Tu rbol genealgico.
 (b) El rbol taxonmico de los seres vivos.

(c) Un rbol que represente la organizacin del texto de Xavier Franch Estructuras de da-


(d)

 (e)
*(f)

 *(g)

tos: especificacin, diseo e implementacin, con sus divisiones en captulos, secciones y subsecciones.
Un rbol que represente la organizacin jerrquica del sistema de directorios y archivos en el selvtico PC del profesor Tadeo de la Tecla.
El rbol de llamadas inducido por la llamada inicial fib(4) (cfr. ejercicio 83).
El rbol de anlisis sintctico de la expresin aritmtica (2+x)*(y3)
El rbol de anlisis sintctico de la siguiente expresin condicional:
six<y
entonces
x:=(2+x)*(y3)
sino
y:=2*x3*y
fsi

239. Los rboles generales se caracterizan porque cada nodo tiene un nmero arbitrario de hijos,

ordenados en secuencia. Construye un TAD parametrizado ARBOL[E :: ANY] que represente el comportamiento de los rboles generales, con operaciones adecuadas para construirlos y acceder a los datos almacenados en sus nodos.

240. Muestra mediante un ejemplo que los dos conceptos siguientes no son equivalentes:
(a) Arbol general de grado dos: Arbol general con la propiedad de que cada nodo tiene como
mximo dos hijos.

(b) Arbol binario: Arbol con la propiedad de que cada nodo tiene exactamente dos hijos,
izquierdo y derecho. Se admite el rbol vaco, sin ningn nodo.
241. Construye un TAD parametrizado ARBIN[E :: ANY] que especifique formalmente el

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]oNat

Calcula la talla (altura, profundidad) de un rbol binario, definida como el


nmero de nodos de la rama ms larga.

numNodos:Arbin[Elem]oNat

Calcula el nmero de nodos de un rbol binario.

numHojas:Arbin[Elem]oNat

Calcula el nmero de hojas de un rbol binario.

476

rboles

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-

Se pide:

tivas del nivel n, de manera que al rellenar dichas posiciones con nuevas hojas se obtiene un rbol completo.

 (a) Dibuja ejemplos de rboles binarios completos, semicompletos y no semicompletos.


(b) Demuestra que un rbol binario completo de talla t t 1 tiene 2t1 hojas y un total de

2t1 nodos.

244. Suponiendo conocidas las operaciones del TAD ARBIN y la operacin talla del ejercicio

242, construye ecuaciones que especifiquen formalmente el comportamiento de las operaciones siguientes:


esCompleto:Arbin[Elem]oBool

Reconoce si un rbol binario dado es completo.

esSemiCompleto:Arbin[Elem]oBool

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 D de a est representado por un


nodo E de b.

El paso de un nodo D a su primer hijo en a se corresponde con el paso de E a su hijo


izquierdo en b.

El paso de un nodo D a su hermano derecho en a se corresponde con el paso de E 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 binarios que representan los diferentes rboles del bosque as, y enganchar cada uno de ellos
como hijo derecho del precedente. Dibuja un ejemplo de esta construccin.

247. Formaliza las ideas de los dos ejercicios anteriores, especificando por medio de ecuaciones

las dos operaciones siguientes:




hazArbin:Bosque[Elem]oArbin[Elem]

Construye el rbol binario que representa un bosque dado.

hazBosque:Arbin[Elem]oBosque[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

477

rboles

(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: RNC, que prohibe que diferentes subrboles de
un mismo rbol compartan estructura; y R, que no lo prohibe. Formaliza tambin la funcin de abstraccin.

250. Desarrolla una implementacin de ARBIN basada en la representacin del ejercicio ante-

rior. Comprueba que el coste temporal de todas las operaciones es O(1) y que el espacio
ocupado por la representacin es O(n), siendo n el nmero de nodos del rbol.

251. La implementacin de ARBIN discutida en el ejercicio anterior puede generar estructuras

compartidas. comprubalo dibujando un grfico que represente las estructuras representantes de a1, a2, a3 al finalizar la ejecucin del siguiente fragmento de programa:

var
a1,a2,a3:Arbin[Ent];
inicio
a1:=Cons(Vaco(),1,Vaco());
a2:=Cons(Vaco(),2,Vaco());
a3:=Cons(a1,3,a2);
a1:=Cons(a1,4,a3)

252. Programa el procedimiento y la funcin que se especifican a continuacin:


(a) procanula(esa:Arbin[Elem]);

{P0:RNC(a)a=A}

{Q0:a=nil

elespacioqueocupabalaestructurarepresentante
deAsehaliberado}

fproc

(b)







funccopia(a:Arbin[Elem])devb:Arbin[Elem];
{P0:R(a)}
{Q0:RNC(b)A(a)=A(b)
laestructurarepresentantedebestubicadoenespacionuevo}
ffunc

Nota: Para programa anula y copia es necesario tener acceso a la representacin dinmica de los
rboles. En la prctica, convendra que el mdulo de implementacin de ARBIN exportase estos
procedimientos.
253. Enriquece la especificacin del TAD ARBIN con una operacin de igualdad

(==):(Arbin[Elem],Arbin[Elem])oBool


Extiende la implementacin del ejercicio 250 aadiendo una funcin que implemente ( == ) y
analiza su coste temporal.

478

rboles

254. La implementacin dinmica de ARBIN abordada en el ejercicio 250 se puede realizar re-

emplazando la memoria dinmica por un vector de tipo adecuado que la simule. Estudia los
detalles en el texto de Xavier Franch (subseccin 5.2.1, pp. 229-232) y desarrolla la implementacin resultante.

255. Demuestra que la definicin recursiva dada a continuacin establece una aplicacin biyecti-

va ndice ; {1,2}* o + entre el conjunto de todas las posiciones posibles de los rboles binarios y el conjunto de los nmeros naturales positivos.

ndice(H)  =1
ndice(D.1) =2*ndice(D)
ndice(D.2) =2*ndice(D)+1

256. Para representar un rbol binario de tipo Arbin[Elem] puede usarse un vector de tipo
Vector[1..max]deElem, siguiendo el criterio de que el elemento que ocupa la posicin D

del rbol se almacene en el lugar ndice(D) del vector. Esta representacin resulta til para
algunos algoritmos que operan con rboles semicompletos. Segn el resultado del ejercicio
243(b), un valor max = 2t 1 basta para representar cualquier rbol semicompleto de talla
menor o igual que t. Plantea las frmulas numricas que habra que utilizar para calcular el
padre y los hijos de un nodo dado, usando esta representacin.

257. Para desarrollar una implementacin dinmica de los rboles generales, suele utilizarse la

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 binarios, al estilo estudiado en el ejercicio 245. compara las dos opciones desde el punto de vista del
espacio ocupado por la estructura representante del rbol.
259. Define los conceptos de rbol n-ario completo y rbol n-ario semicompleto. Enuncia y demuestra

para esta clase de rboles los resultados anlogos a los obtenidos en el ejercicio 243(b).

260. Extiende los resultados de los ejercicios 255 y 256 al caso de los rboles n-arios.

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.

479

rboles

263. Especifica y programa una funcin recursiva que calcule la frontera de un rbol dado como

parmetro. Por frontera se entiende la lista formada por los elementos almacenados en las
hojas del rbol, tomados de izquierda a derecha. Se supone disponible un mdulo que implementa el TAD LISTA.

264. Programa un funcin recursiva que calcule el valor numrico de una expresin aritmtica a

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]
o Lista[Elem]. Presenta la especificacin resultante como enriquecimiento del TAD
ARBIN.

267. Suponiendo disponibles dos mdulos ARBIN y LISTA que implementen los TADs del

mismo nombre, y sin acceder a la representacin, programa funciones recursivas que realicen las operaciones de recorrido especificadas en el ejercicio anterior.

268. El recorrido de un rbol binario puede realizarse tambin con ayuda de un procedimiento

recursivo del estilo


procrecorre(ea:Arbin[Elem];esxs:Sec[Elem]);
{P0:xs=XSfin?(xs)=cierto}
{Q0:fin?(xs)=Ciertocont(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]oLista[Elem]


Nota: Debido a la dependencia mutua entre rboles generales y bosques, debers especificar
tambin dos operaciones auxiliares que describen el recorrido de bosques:

preBos,postBos:Bosque[Elem]oLista[Elem]
270. Supongamos que R : Arbin[Elem] o Lista[Elem] sea una cualquiera de las tres operaciones

de recorrido en profundidad de rboles binarios. Especifica por medio de ecuaciones una


nueva operacin

acumulaR:Pila[Arbin[Elem]]oLista[Elem]


480

rboles

tal que acumulaR(as) construya la concatenacin de todos los recorridos R(a) correspondientes a
los rboles a apilados en as, empezando por la cima.
271. Los recorridos en profundidad de rboles binarios pueden realizarse con algoritmos iterati-

vos, manteniendo para el bucle principal un invariante de la forma:




R(a)=xs++acumulaR(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] o Lista[Elem] una cualquiera de las tres operaciones de recorrido en

profundidad de rboles binarios. Considera la operacin ms general R : (Pila[Arbin[Elem]], Lista[Elem]) o Lista[Elem] especificada por la ecuacin

R(as,xs)=xs++acumulaR(as)


siendo acumulaR 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 acumulaR 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] o 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]] o 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.

481

rboles

Arboles binarios hilvanados


276. Usando induccin sobre n t 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 estructura 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 eficientemente 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 o 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 evaluacin 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, considerando para los operadores las prioridades habituales y asociatividad por la izquierda (excepto el operador de exponenciacin, que asociar por la derecha).
Sugerencia: consulta la seccin 7.1.1 del texto de Xavier Franch, donde se resuelve un problema
similar a ste.
280. Demuestra por medio de un ejemplo que dos rboles binarios diferentes pueden tener el

mismo recorrido en preorden. Sin embargo, s es posible reconstruir un rbol binario a si se


conoce una lista de parejas

ps=[(x1,t1),,(xn,tn)]


donde xi es el elemento del nodo visitado en i-simo lugar durante el recorrido en preorden, y
ti es el nmero de nodos del hijo izquierdo de dicho nodo. Construye algoritmos que realicen la
reconstruccin del rbol a partiendo de ps, en los dos supuestos siguientes:


(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

482

rboles

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

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 D, se considera que D 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
T
G
R
E

1.1
1.2
2.1.1
2.1.2
2.2


(b) Construye el resultado de codificar la cadena de caracteres RETA utilizando el

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 ejemplos, 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 enteros
1 y 2. Construye funciones que resuelvan los problemas siguientes:


483

rboles

(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, representando los rboles por medio de estructuras con dos clases de nodos: nodos hoja, con
un campo de tipo Elem; y nodos internos, con dos campos de tipo Enlace.
Sugerencia: Para la especificacin, utiliza dos operaciones generadoras:
Hoja:ElemoHArbin[Elem]
Nodo:(HArbin[Elem],HArbin[Elem])oHArbin[Elem]

Eliminacin de la recursin doble


286. Una funcin recursiva doble cuya definicin se ajuste al esquema:


funcf(x:T)devy:S;
{P0:P(x);Cotat(x)}
var
x1,x2:T;
y1,y2:S;
%Otrasposiblesdeclaracioneslocales
inicio
sid(x)
entonces
{P(x)d(x)}
y:=r(x)
{Q(x,y)}
sino
{P(x)d(x)}
x1:=s1(x);x2:=s2(x);
{x1=s1(x)x2=s2(x)P(x1)P(x2)}
y1:=f(x1);y2:=f(x2);
{x1=s1(x)x2=s2(x)Q(x1,y1)Q(x2,y2)}
y:=c(x,y1,y2)
{Q(x,y)}
fsi

484

rboles

{Q0:Q(x,y)}
devy
ffunc


Se puede transformar en una funcin iterativa equivalente definida como sigue:



funcfit(x:T)devy:S;
{P0: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)o
nodo:=cima(pila);
m:=nodo.marca;
u:=nodo.param;
si
m=0ANDd(u)oy:=r(u);
desapilar(pila)
m=0ANDNOTd(u)onodo.marca:=1;
desapilar(pila);
Apilar(nodo,pila);
nuevoNodo.marca:=0;
nuevoNodo.param:=s1(u)
Apilar(nuevoNodo,pila)
m=1onodo.marca:=2;
nodo.resul:=y;
desapilar(pila);
Apilar(nodo,pila);
nuevoNodo.marca:=0;
nuevoNodo.param:=s2(u)

485

rboles

Apilar(nuevoNodo,pila)
m=2ov:=nodo.resul;
y:=c(u,v,y);
desapila(pila)
fsi
fit;
{y=f(x)}
{Q0:Q(x,y)}
devy
ffunc


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 llamada, 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 orden 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 informacin acerca de los resultados obtenidos en la parte de recorrido ya realizada. Ms exactamente:
El nodo cima siempre cumple marca [0..2]; los dems nodos verifican que marca
[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 = s1(u).

Si un nodo cumple marca = 2 y param = u, entonces el nodo cumple resul = f(s1(u)) y el


nodo apilado justo sobre l (si existe) cumple param = s2(u).

Siempre que el nodo cima cumple marca = 2 y param = u, se verifica tambin que y =
f(s2(u)).
El invariante, que no detallamos, debera formalizar estas condiciones. Cuando la pila queda
vaca y el bucle termina, en y queda el resultado f(x) correspondiente a la llamada inicial. En cuanto a la expresin de acotacin, es posible definirla como el nmero de visitas a nodos del rbol de
llamadas que estn an pendientes de realizar en cada momento.

(a) Aplica la transformacin descrita a la funcin recursiva doble fib del ejercicio 83.

Estudia paso a paso la evolucin de la pila de nodos para una llamada inicial
concreta, tal como fibit(3) o fibit(4).
(b) Aplica la transformacin a las funciones recursivas dobles consideradas en los ejercicios 261-264.

rboles

486

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 estrictamente menor que todos los elementos del hijo derecho.
Observa que esta definicin equivale a pedir que el recorrido en inorden de a produzca una lista 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 enteros que se obtiene a partir del rbol vaco, insertando sucesivamente los nmeros siguientes:
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 operacin de borrado deja el rbol inalterado. Dibuja los rboles resultantes de borrar los enteros que se indican a continuacin en el rbol obtenido en el ejercicio 288:
 (a) Borrar 35: Este elemento aparece en una hoja. Esta hoja se elimina. 
(b) Borrar 43: Este elemento aparece en un nodo interno con un solo hijo. Este nodo se
elimina y se sustituye por su hijo.

(c) Borrar 31: Este elemento aparece en un nodo interno con dos hijos. Se reemplaza el
elemento de este nodo por el menor elemento de su hijo derecho. Mediante otro borrado, se quita del hijo derecho el nodo que contena este elemento.

291. Escribe una especificacin algebraica de una TAD ARB-ORD[E :: ORD] que represente el

comportamiento de los rboles ordenados, con operaciones para crear un rbol vaco, insertar, buscar, borrar y preguntar si un elemento aparece en un rbol.
Sugerencia: Usa REC-PROF-ARBIN[E], ocultando aquellas operaciones que no convenga poner a disposicin de los clientes de ARB-ORD[E]. En particular, las operaciones pblicas de
ARB-ORD[E] deben ser tales que los clientes de este TAD slo puedan construir rboles ordenados.
292. Sean a :Arbin[elem], x : Elem. Si a no es un rbol ordenado, algunas operaciones de ARB-

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.

487

rboles

(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 ARBORD no tropiezan con este problema, debido a que las operaciones exportadas slo permiten
construir rboles ordenados.
293. Los rboles de bsqueda, de tipo Arbus[Cla, Val], son semejantes a los rboles ordenados;

pero ahora cada nodo almacena dos informaciones: una clave de tipo Cla, y un valor de tipo
Val. El tipo Cla debe poseer igualdad y orden, y el tipo Val debe estar dotado de una operacin (): (Val, Val) o Val que combine dos valores. Especifica un TAD
ARBUSCA[C::ORD, V::COMB] que represente el comportamiento de los rboles de
bsqueda, donde la clase de tipos COMB requiere que el tipo-parmetro V posea una operacin de combinacin ( ). ARBUS[C, V] debe exportar operaciones para crear un rbol
vaco, insertar un valor asociado a una clave dada, buscar una clave dada, y preguntar si una
clave dada aparece en un rbol. La operacin de insercin debe usar ( ) para combinar el
nuevo valor insertado con el antiguo, en el caso de que la clave de insercin ya aparezca en
el rbol.

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

27

21

33

25

295. Desarrolla una implementacin de ARB-ORD basada en una representacin dinmica simi-

lar a la del ejercicio 249, realizando las operaciones busca y est? como una funcin, y las
operaciones inserta y borra como procedimientos con un parmetro es a : Arbusca[Elem]. Advertencia: Esta implementacin no puede plantearse modularmente, importando el tipo representante de un mdulo ARBIN[ELEM]. Para programar correctamente inserta y borra es
necesario tener acceso a la estructura representante del rbol.

296. Extiende el ejercicio anterior para obtener una implementacin de ARBUSCA basada en

una representacin dinmica semejante a la del ejercicio 249. Ahora, cada nodo deber contener 4 campos: una clave, un valor, y dos enlaces apuntando a los hijos.

488

rboles

297. Modifica las implementaciones desarrolladas en los dos ejercicios anteriores, de manera que

la funcin busca y los procedimientos inserta y borra sean iterativos en lugar de recursivos.

298. Una familia

F de rboles binarios se llama equilibrada si existen constantes n0 , c +


tales que para todo n t n0 y todo rbol a F con n nodos se tenga talla(a) d c log n. 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 arbitrarios, 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]oArbus[Elem]


tal que hazArbusca(xs) sea el rbol ordenado resultante de insertar sucesivamente los datos de
la lista xs, comenzando con un rbol vaco.
Sugerencia: Especifica hazArbusca(xs) = reiteraInserta(xs, Vaco), y aade ecuaciones que especifiquen la operacin ms general reiteraInserta.
300. Sea Elem como en el ejercicio anterior. Sea a : Arbus[elem] un rbol ordenado.
(a) Demuestra que en general no es cierto que a = hazArbus(inOrd(a)). Qu forma tiene
el rbol hazArbusca(inOrd(a))?

(b) Demuestra que siempre se cumple que a = hazArbusca(preOrd(a)), razonando por in-

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 informacin adicional. Compara con el ejercicio 280. Desarrolla dos variantes del algoritmo, adecuadas a dos posibles presentaciones del recorrido en preorden: como lista o como
secuencia.

302. Programa un procedimiento recursivo que realice el recorrido en inorden de un rbol bina-

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);
{P0:p=Pv=V1dpdN+1numNodos(a)dNp+1}
{Q0:Pdp+1dN+1contenido(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 cualquier nodo debe ser estrictamente mayor que los elementos de los nodos del hijo izquierdo, y
menor o igual que los elementos del hijo derecho. Equivalentemente, el recorrido en inorden
del rbol debe dar una lista de elementos ordenada en orden no decreciente. Especifica un
TAD parametrizado ARB-ORD-REP[E :: ORD] que represente el comportamiento de este
nuevo tipo de rboles ordenados.

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 repeticiones 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 alfabticamente por palabras, donde cada palabra aparece acompaada del nmero de veces que ha
aparecido en el texto. Suponemos que el texto a analizar viene dado como secuencia de tipo Sec[Palabra], siendo Palabra un tipo disponible de la clase ORD. Se pide construir un algoritmo que resuelva el problema con ayuda de un rbol de bsqueda de tipo
Arbus[Palabra, Nat], y analizar su complejidad. El listado pedido se dar como secuencia de
parejas, de tipo Sec[Pareja[Palabra, Nat]].

308. Dado un texto organizado por lneas, el problema de las referencias cruzadas pide producir un

listado ordenado alfabticamente por palabras, donde cada palabra del texto vaya acompaada de una lista de referencias, que contendr los nmeros de todas las lneas del texto en las
que aparece la palabra en cuestin (con posibles repeticiones si la palabra aparece varias veces en una misma lnea). Suponiendo que el texto a analizar venga dado como secuencia de
tipo Sec[Sec[Palabra]], construye un algoritmo que resuelve el problema con ayuda de un
rbol de bsqueda de tipo Arbus[Palabra, Lista[Nat]], y analiza su complejidad. El listado
pedido se dar como secuencia de parejas, de tipo Sec[Pareja[Palabra, Lista[Nat]]].

309. Estudia una implementacin del TAD POLI de los polinomios con coeficientes enteros en

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 representante. En cada caso, analiza el tiempo de ejecucin de las operaciones y el espacio ocupado
por la representacin.
(a) El TAD BOSTEZOS del ejercicio 231.
(b) El TAD CONSULTORIO del ejercicio 232.

490

rboles

(c) El TAD MERCADO del ejercicio 233.


(d) El TAD BANCO del ejercicio 234.
(e) El TAD BIBLIOTECA del ejercicio 236.

Arboles AVL
311. Especifica mediante ecuaciones operaciones booleanas que reconozcan los rboles binarios

equilibrados en talla y los rboles AVL, respectivamente.

312. Observa que todos los rboles completos son equilibrados. Dibuja un rbol AVL que no

sea completo, y un rbol ordenado que no sea AVL.

313. Construye todos los rboles ordenados posibles formados por cuatro nodos con elementos

diferentes (e.g. 1, 2, 3, 4). Cuntos de ellos son rboles AVL?

314. Busca una ley de recurrencia para la sucesin bn , siendo bn el nmero de rboles de bsque-

da que se pueden formar con n nodos con elementos diferentes (e.g. 1, 2, , n).

315. Define recursivamente dos sucesiones nt : Nat, at : Arbus[Nat], de manera que para todo t

0, at sea un rbol AVL lo ms desequilibrado posible con nt nodos. Estos at se llaman


rboles de Fibonacci, y los nt se llaman nmeros de Leonardo.

316. Especifica ecuacionalmente una operacin que calcule el factor de equilibrio de un rbol

binario dado.

317. Especifica ecuacionalmente las operaciones de rotacin a la derecha y rotacin a la izquierda que

se definen grficamente en la figura siguiente. Se supone que x, y : Elem; a, b, c : Arbus[Elem].


y


318. En cada uno de los casos siguientes, dibuja el rbol resultante de la insercin ordinaria, y a

continuacin efecta las rotaciones necesarias para conseguir el efecto de una insercin
AVL. Suponemos que los datos son enteros, y que la funcin clave es la identidad.
(a) insertaAVL(11, a), siendo a:
4
2

8
6

10

491

rboles

 (b) insertaAVL(7, a), siendo a como en el apartado anterior:


(c) insertaAVL(10, b), siendo b:

50
30
20

60
40

15

55
45

65


319. Especifica ecuacionalmente la operacin de insercin en rboles AVL.


320. Modifica el tipo representante utilizado en anteriores implementaciones de rboles binarios

(ver ejercicios 249, 250 y 296) de manera que sea posible determinar eficientemente los factores de equilibrio sin calcular tallas. Formula un invariante de la representacin y una funcin de abstraccin adecuados para representar rboles AVL con ayuda del nuevo tipo.

321. Desarrolla un algoritmo recursivo que implemente la insercin AVL. Puedes consultar la

Seccin 5.6.2 del texto de Xavier Franch.

322. Dibuja el rbol resultante de efectuar el borrado ordinario borra(1, a5), siendo a5 el rbol de

Fibonacci de lugar 5 (ver ejercicio 315). A continuacin, efecta las rotaciones necesarias
para reequilibrar dos subrboles a lo largo de la trayectoria de bsqueda para que el rbol
total quede reequilibrado.

323. Especifica ecuacionalmente la operacin de borrado en rboles AVL.


324. Desarrolla una implementacin de la operacin de borrado AVL siguiendo un planteamien-

to similar al del ejercicio 321, y usando el mismo tipo representante. Consulta la Seccin
5.6.2 del texto de Franch.

325. Adapta todo lo desarrollado en los ejercicios 316-324 para obtener una especificacin e

implementacin del comportamiento de los rboles de bsqueda AVL de tipo Arbus[Cla,


Val].

Colas de prioridad
326. Una cola de prioridad tiene un comportamiento similar al de una cola, con la diferencia de que

el tipo de los elementos almacenados dispone de operaciones de igualdad y orden. Si x < y,


entenderemos que el elemento x precede a (o tiene mayor prioridad que) el elemento y. Si x ==
y, entenderemos que x e y tienen la misma prioridad, aunque no sean necesariamente idnticos. Construye un TAD parametrizado COLA-PRIO[E :: ORD] que formalice el comportamiento de las colas de prioridad, con operaciones para crear una cola de prioridad
vaca, aadir un elemento, consultar el elemento de mayor prioridad, eliminar el elemento
de mayor prioridad, y averiguar si la cola es vaca. Ten en cuenta que se considera como
elemento de mayor prioridad al que sea mnimo con respecto al orden <. La operacin que
aade un nuevo elemento a una cola de prioridad slo debe estar definida si la prioridad del
nuevo elemento es diferente de las prioridades de todos los elementos ya presentes en la
cola. Es decir, prohibimos en nuestra especificacin que en una cola de prioridad se encuentren elementos con prioridades repetidas.

492

rboles

En los ejercicios siguientes suponemos que el tipo Elem dispone de operaciones de igualdad y
orden, que se interpretan en el sentido de una comparacin de prioridades.
327. Plantea una implementacin secuencial de las colas de prioridad, utilizando listas o secuen-

cias ordenadas como tipo representante. Estima los rdenes de magnitud de los tiempos de
ejecucin de las operaciones, en el caso peor.

Montculos
328. Sea a : Arbin[Elem]. Se dice que a es un montculo (de mnimos) syss a es semicompleto, la

raz de a (si existe) precede en prioridad a los elementos almacenados en los hijos, y los
hijos (si existen) son a su vez montculos.
*(a) Dibuja un montculo de nmeros naturales que contenga los nmeros del intervalo
[1..7]. 

(b) Especifica ecuacionalmente una operacin booleana
montculo?:Arbin[Elem]oBool

que reconozca si un rbol binario dado es o no un montculo.


329. Para insertar un nuevo elemento x en un montculo a se utiliza el siguiente algoritmo:
(I.1) Se aade x como nueva hoja en la primera posicin libre del ltimo nivel. El resultado es un rbol semicompleto, si a lo era.

(I.2) Se deja flotar x; i.e., mientras x no se encuentre en la raz y sea menor que su padre, se

intercambia x con su padre. El resultado es un montculo, suponiendo que a lo fuese


y que x fuese diferente (en prioridad) de todos los elementos de a.




*(a) Construye un montculo de naturales por insercin sucesiva de los nmeros 5, 3, 4, 7,


1, 6, 2, en este orden.
(b) Especifica ecuacionalmente dos operaciones
ponerHoja:(Elem,Arbin[Elem])oArbin[Elem]
flotar:Arbin[Elem]oArbin[Elem]

de modo que la siguiente ecuacin especifique correctamente la operacin de insercin para montculos:
insertar:(Elem,Arbin[Elem])oArbin[Elem]
insertar(x,a)=flotar(ponerHoja(x,a))

(se trata de que insertar(x, a) sea un montculo, siempre que a lo sea y x sea diferente
en prioridad de todos los elementos de a).
330. Para eliminar el elemento de la raz de un montculo a, se utiliza el siguiente algoritmo:
(E.1) Si a es vaco, la operacin no tiene sentido. Si a tiene un solo nodo, el resultado de la

eliminacin es el montculo vaco. Si a tiene dos o ms nodos, se quita la ltima hoja


y se pone el elemento x que sta contenga en el lugar de la raz, que queda eliminada.
Esto da un rbol semicompleto, si a lo era. Se pasa a (E.2).

(E.2) Se deja hundirse x; i.e., mientras x ocupe una posicin con hijos y sea mayor que alguno de sus hijos, se intercambia x con el hijo elegido, que es aquel que sea menor que x
(si hay un solo hijo con esta prioridad) o el hijo menor (si ambos son menores que x).
El resultado es un montculo, suponiendo que a lo fuese.

493

rboles


(a) Realiza sucesivas eliminaciones de la raz en el montculo obtenido en el ejercicio

329(a), hasta llegar al montculo vaco. Observa el orden en el que van siendo extrados los nmeros.

(b) Especifica ecuacionalmente dos operaciones
preparar:Arbin[Elem]oArbin[Elem]
hundir:Arbin[Elem]oArbin[Elem]

de modo que las siguientes ecuaciones especifiquen correctamente la operacin de


eliminacin del mnimo para montculos:
eliminarRaz:Arbin[Elem]oArbin[Elem]
eliminarRaz(a)=Vaco      sinumNodos(a)==1
eliminarRaz(a)=hundir(preparar(a))  sinumNodos(a)>1

Implementacin de colas de prioridad usando montculos


331. Plantea una implementacin modular del TAD COLA-PRIO usando montculos como

tipo representante. Suponiendo que el mdulo MONTICULO exporte operaciones insertar


y eliminarRaz ejecutables en tiempo logartmico (con respecto al nmero de elementos almacenados), qu puede asegurarse sobre los tiempos de ejecucin de las operaciones de
COLA-PRIO?

332. Define un tipo representante adecuado para una representacin de los montculos basada

en vectores. Formula el invariante de la representacin y la funcin de abstraccin.

Recuerda que un rbol completo de talla t tiene 2t1 nodos. Para los ejercicios que siguen presuponemos las declaraciones:

const
max=2t1;
tipo
Vec=Vector[1..max]deElem;
333. Programa los procedimientos especificados informalmente a continuacin.

procflota(esv:Vec;ei,n:Nat);
{P0:v=V1didndmax
dejandoflotarv(i)puedelograrsequev[1..n]lleguea
representarunmontculo}
{Q0:v[1..n]representaunmontculo
vsehaobtenidoapartirdeVdejandoflotarV(i)}
fproc

prochunde(esv:Vec;ei,n:Nat);
{P0:v=V1didndmax
dejandohundirsev(i)puedelograrsequev[1..n]lleguea
representarunmontculo}

494

rboles

{Q0:v[1..n]representaunmontculo
vsehaobtenidoapartirdeVdejandohundirseV(i)}
fproc


Observa que estos procedimientos corresponde a generalizaciones de las operaciones flotar y


hundir introducidas en los ejercicios 329 y 330, y que su tiempo de ejecucin es O(log n) en el caso
peor.
334. Usando los procedimientos del ejercicio 333 y el tipo representante del ejercicio 332, cons-

truye implementaciones de las operaciones de insercin y eliminacin de la raz en un


montculo, que sean ejecutables en tiempo logartmico.

335. Usando los procedimientos del ejercicio 333, programa un procedimiento ejecutable en

tiempo O(log n) que satisfaga la especificacin siguiente. Observa que este procedimiento
puede servir para implementar una operacin que modifique el montculo cambiando uno
de sus datos por otro de diferente prioridad.
procaltera(esv:Vec;ei,n:Nat;ex:);
{P0:v=V1didndmax
v[1..n]representaunmontculo
laprioridaddexesdiferentedelasprioridadesde
losdatosdev[1..n]}
{Q0:v[1..n]representaunmontculo
vsehaobtenidoapartirdeVasignandoxaV(i)y
dejndoloflotarohundirse,segnconvenga}
fproc

336. Desarrolla una implementacin de las colas de prioridad basada en vectores, con un inva-

riante de la representacin que fuerce al vector a representar un montculo.


Observa: A diferencia del ejercicio 334, se exportan colas de prioridad en vez de montculos. A
diferencia del ejercicio 331, no se importan montculos de otro mdulo.
Algoritmos de ordenacin con colas de prioridad y montculos
337. Una sucesin de n elementos sin prioridades repetidas puede ordenarse construyendo una

cola de prioridad a partir de la cola vaca por insercin reiterada de los elementos, y reiterando seguidamente la operacin de extraer el elemento mnimo (con prioridad mxima)
hasta que la cola se vace. Suponiendo disponible un mdulo que implemente el TAD
COLA-PRIO, construye un procedimiento de ordenacin para vectores de tipo Vec siguiendo este mtodo, y razona que el tiempo de ejecucin es O(n log n).

338. Una generalizacin del problema del ejercicio anterior consiste en obtener los k elementos

menores de la sucesin dada, ordenados de menor a mayor, siendo 0 d k d n. Modifica el


procedimiento del ejercicio anterior para resolver este nuevo problema, y razona que el
tiempo de ejecucin sigue siendo O(n log n).
En los dos ejercicios anteriores, el algoritmo necesita espacio auxiliar O(n) para la cola de prioridad, adems del espacio ocupado por el vector a ordenar. En 1964, J.W.J. Williams y R.W. Floyd
construyeron un algoritmo de ordenacin de vectores basado en montculos, que no necesita
espacio auxiliar. A cambio, el algoritmo no es modular, porque utiliza el espacio del mismo vector

495

rboles

que se est ordenando para representar el montculo. Los ejercicios que siguen desarrollan esta
idea.
339. El siguiente procedimiento reorganiza un segmento de vector de manera que pase a repre-

sentar un montculo:


prochazMont(esv:Vec;en:Nat);
{P0:v=V0dndmax
i,j:1di<jdn:v(i)zv(j)}
var
i:Nat;
inicio
sind1
entonces
seguir
sino
paraibajandodesdendiv2hasta1hacer
{I:k:i+1dkdn:v[k..n]representaunmontculo}
hunde(v,i,n)  /*verej.334*/
fpara
fsi
{Q0:v[1..n]representaunmontculov[n+1..max]=V[n+1..max]
v[1..n]esunapermutacindeV[1..n]}
fproc


Se puede demostrar que este procedimiento consume tiempo O(n); ver el texto de Xavier
Franch, Seccin 5.5.2. Dibuja el rbol semicompleto cuyo recorrido por niveles da la sucesin de
nmeros 10, 8, 2, 4, 7, 5, 9, 3, 6, 1 (en este orden) y convirtelo en un montculo siguiendo el algoritmo del procedimiento hazMont.
340. El siguiente procedimiento ordena un vector con ayuda de un montculo representado de-

ntro del mismo vector:




procordenaMont(v:Vec;n:Nat);
{P0:v=V0dndmax
i,j:1di<jdn:v(i)zv(j)}
var
i:Nat;
inicio
hazMont(v,n);
sind1
entonces
seguir

sino
paraibajandodesdenhasta2hacer

496

rboles

{I:v[1..n]esunapermutacindeV[1..n]
v[n+1..max]=V[n+1..max]
v[i+1..n]contienelosnidatosdeV[1..n]de
menorprioridad,ordenadoenordendecreciente
v[1..i]representaunmontculo}
fpara
fsi
{Q0:v[1..n]esunapermutacindeV[1..n]
v[n+1..max]=V[n+1..max]
v[1..n]estordenadoenordendecreciente}
fproc


Usando el ejercicio 339, razona que este algoritmo de ordenacin requiere tiempo O(n log n).
Ensaya el funcionamiento del algoritmo para ordenar un vector que contenga la sucesin de
nmeros 10, 8, 2, 4, 7, 5, 9, 3, 6, 1, en este orden.
341. Desarrolla modificaciones del algoritmo del ejercicio 340, de manera que:
(a) Se permitan prioridades repetidas y se obtenga al final un vector ordenado en orden

no decreciente. Para esto, basta con modificar la propiedad de montculo, exigiendo


que el elemento de cada nodo sea menor o igual que los elementos de sus dos hijos (si
existen), y adaptar las operaciones de los montculos a este nuevo criterio.

(b) Se ordenen nicamente los k menores elementos del vector, como en el ejercicio
338.
Colas de prioridad con prioridades repetidas
342. Construye una especificacin algebraica del TAD de las colas de prioridad con posibles

repeticiones de prioridades. La especificacin deber determinar que los elementos de igual


prioridad sean atendidos por orden de llegada.

343. El TAD especificado en el ejercicio anterior no se puede implementar directamente me-

diante montculos, ya que puede ocurrir que ciertos elementos con prioridades iguales no
salgan del montculo en el mismo orden en que entraron. Construye un ejemplo que ilustre
este fenmeno.

497

Tablas

CAPTULO 6
TABLAS
6.1 Modelo matemtico y especificacin
Desde el punto de vista matemtico, las tablas son aplicaciones que hacen corresponder a claves, c C C conjunto de claves, valores, v V V conjunto de valores. Podemos verlas como una generalizacin de los rboles de bsqueda, como colecciones de pares (clave, valor),
donde el acceso se realiza por las claves y donde, a diferencia de los rboles de bsqueda, no se
supone una estructura de rbol aunque pueden ser implementadas como tales.
Las aplicaciones de las tablas en Informtica incluyen, entre otras: las tablas de smbolos de los
compiladores, las tablas usadas en los sistemas de archivos, las tablas usadas en los sistemas de
gestin de bases de datos.
Para dar un modelo matemtico de las tablas hay tres posibilidades naturales (cfr. [Franch 93],
pp. 160-162):

Funciones totales T : C o V, suponiendo en V un elemento distinguido NoDef.

Funciones parciales T : C o V.

Conjuntos T V, suponiendo en este caso que V es un conjunto de elementos con claves asociadas mediante una operacin clave: V o C, y conviniendo en que C no puede
contener dos elementos distintos con la misma clave.

Las operaciones que nos interesan en las tablas son las que permiten: crear una tabla vaca,
insertar una pareja (clave, valor), comprobar si la tabla est vaca, comprobar si una clave est en
la tabla, consultar el valor asociado a una clave, y borrar un par (clave, valor).

6.1.1

Tablas como funciones parciales

La especificacin de las tablas como funciones parciales



tadTABLA[C::EQ,V::ANY]

renombra

 C.ElemaClave

 V.ElemaValor

usa

 BOOL

tipo

 Tabla[Clave,Valor]

operaciones


 TablaVaca:oTabla[Clave,Valor]

 Inserta:(Tabla[Clave,Valor],Clave,Valor)oTabla[Clave,Valor]/*gen*/

 esVaca:Tabla[Clave,Valor]oBool

/*obs*/

 est:(Tabla[Clave,Valor],Clave)oBool

/*obs*/

 consulta:(Tabla[Clave,Valor],Clave)oValor

/*obs*/

/*gen*/

498

Tablas




 borra:(Tabla[Clave,Valor],Clave)oTabla[Clave,Valor] /*mod*/
ecuaciones


 t:Tabla[Clave,Valor]:i,j:Clave:x,y:Valor:

 Inserta(Inserta(t,i,x),j,y)
=
Inserta(t,j,y)
sii==j

 Inserta(Inserta(t,i,x),j,y)
=
Inserta(Inserta(t,j,y),i,x)
sii/=j
=cierto

 esVaca(TablaVaca)

 esVaca(Inserta(t,i,x))
=falso

 est(TablaVaca,j)
=falso

 est(Inserta(t,i,x),j)
=i==jORest(t,j)

 defconsulta(t,i)siest(t,i)

 consulta(Inserta(t,i,x),j)=x
sii==j
f

 consulta(Inserta(t,i,x),j)= consulta(t,j)
sii/=j

 borra(TablaVaca,j)
=TablaVaca

 borra(Inserta(t,i,x),j) =borra(t,j)
sii==j

 borra(Inserta(t,i,x),j) =Inserta(borra(t,j),i,x) sii/=j

errores

 consulta(t,i)siNOTest(t,i)
ftad

6.1.2

Tablas como funciones totales

Para poder especificar las tablas como funciones totales la representacin en la que nos centraremos en el resto de este tema debemos suponer a las claves equipadas con un valor especial
NoDef, restriccin que expresamos mediante una clase de tipos:

claseEQND

hereda

 EQ

operaciones

 NoDef:oElem
fclase


Ahora ya podemos definir las tablas como funciones totales



tadTABLA[C::EQ,V::EQND]

renombra

 C.ElemaClave

 V.ElemaValor

usa

 BOOL

tipo

 Tabla[Clave,Valor]

operaciones


 TablaVaca:oTabla[Clave,Valor]

/*gen*/

499

Tablas

 Inserta:(Tabla[Clave,Valor],Clave,Valor)oTabla[Clave,Valor]/*gen*/

 esVaca:Tabla[Clave,Valor]oBool

/*obs*/

 est:(Tabla[Clave,Valor],Clave)oBool

/*obs*/

 consulta:(Tabla[Clave,Valor],Clave)oValor

/*obs*/




 borra:(Tabla[Clave,Valor],Clave)oTabla[Clave,Valor] /*mod*/
ecuaciones


 t:Tabla[Clave,Valor]:i,j:Clave:x,y:Valor:

 Inserta(Inserta(t,i,x),j,y)
=
Inserta(t,j,y)
sii==j

 Inserta(Inserta(t,i,x),j,y)
=
Inserta(Inserta(t,j,y),i,x)
sii/=j

 esVaca(TablaVaca)
=cierto

 esVaca(Inserta(t,i,x))
=falso

 est(TablaVaca,j)
=falso

 est(Inserta(t,i,x),j)
=i==jORest(t,j)

 consulta(TablaVaca,j)
=NoDef

 consulta(Inserta(t,i,x),j)=x
sii==j

 consulta(Inserta(t,i,x),j)=consulta(t,j)
sii/=j

 borra(TablaVaca,j)
=TablaVaca

 borra(Inserta(t,i,x),j) =borra(t,j)
sii==j

 borra(Inserta(t,i,x),j) =Inserta(borra(t,j),i,x) sii/=j
ftad

6.1.3

Tablas ordenadas

Para ciertas aplicaciones de las tablas resulta conveniente que exista un orden entre las claves,
y que se disponga de una operacin que extraiga la informacin almacenada en la tabla en forma
de lista (o secuencia) de parejas de tipo (Clave, Valor), ordenada por claves. En ocasiones, tambin resulta til que una insercin con clave ya presente en la tabla combine el nuevo valor con el
antiguo, en lugar de limitarse a reemplazar el valor antiguo por el nuevo. Llamaremos tablas ordenadas a las tablas que incorporen estas dos ideas. Este TAD es prcticamente igual al de los rboles de bsqueda, con la diferencia de que la consulta en un rbol de bsqueda da un rbol como
resultado, mientras que la consulta en un tabla devuelve un valor. Es directo implementar las
tablas ordenadas como rboles de bsqueda.
La especificacin hace uso de una clase que caracteriza a los tipos combinables equipados con
el valor NoDef:

claseEQNDCOMB

hereda

 EQND,COMB

axiomas


 x:Elem:


 NoDefx=x
fclase


La especificacin de las tablas ordenadas

500

Tablas


tadTABLAORD[C::ORD,V::EQNDCOMB]

renombra

 C.ElemaClave

 V.ElemaValor

usa

 BOOL

 LISTA[PAREJA[C,V]]

tipo

 Tabla[Clave,Valor]

operaciones


 TablaVaca:oTabla[Clave,Valor]

/*gen*/

 Inserta:(Tabla[Clave,Valor],Clave,Valor)oTabla[Clave,Valor]/*gen*/

 esVaca:Tabla[Clave,Valor]oBool

/*obs*/

 est:(Tabla[Clave,Valor],Clave)oBool

/*obs*/

 consulta:(Tabla[Clave,Valor],Clave)oValor

/*obs*/

 borra:(Tabla[Clave,Valor],Clave)oTabla[Clave,Valor] /*mod*/




 lista:Tabla[Clave,Valor]oLista[Pareja[Clave,Valor]]
operacionesprivadas




 minClave:Tabla[clave,Valor]oClave
ecuaciones

/*obs*/


 t,t:Tabla[Clave,Valor]:i,j:Clave:x,y:Valor:

 Inserta(Inserta(t,i,x),j,y)
=
Inserta(t,i,xy)
sii==j

 Inserta(Inserta(t,i,x),j,y)
=
Inserta(Inserta(t,j,y),i,x)
sii/=j
=cierto

 esVaca(TablaVaca)

 esVaca(Inserta(t,i,x))
=falso

 est(TablaVaca,j)
=falso

 est(Inserta(t,i,x),j)
=i==jORest(t,j)

 consulta(TablaVaca,j)
=NoDef

 consulta(Inserta(t,i,x),j)=consulta(t,j)x
sii==j

 consulta(Inserta(t,i,x),j)=consulta(t,j)
sii/=j

 borra(TablaVaca,j)
=TablaVaca

 borra(Inserta(t,i,x),j) =borra(t,j)
sii==j

 borra(Inserta(t,i,x),j) =Inserta(borra(t,j),i,x) sii/=j

 defminClave(t)siNOTesVaca(t)

 minClave(Inserta(TablaVaca,j,y))=j

 minclave(Inserta(Inserta(t,i,x),j,y))=minClave(Inserta(t,i,x))sii
dj

 minclave(Inserta(Inserta(t,i,x),j,y))=minClave(Inserta(t,j,y))sii
>j

 lista(t)
=[]siesVaca(t)

 lista(t)
=[Par(i,x)/lista(t)]

siNOTesVaca(t)ANDi=minClave(t)AND

 x=consulta(t,i)ANDt=borra(t,i)

501

Tablas

ftad

6.1.4

Casos particulares: conjuntos y vectores

Conjuntos
El comportamiento del TAD CJTO[E :: EQ] se puede considerar equivalente al del TAD
TABLA[E :: EQ, V :: EQ-ND], suponiendo que V sea un tipo especial que slo contenga los dos
valores Si y Nodef. Se puede establecer la siguiente correspondencia entre las operaciones de ambos TADs:
CJTO[E :: EQ]
Vaco( )
Pon(x, xs)
quita(x, xs)
esVaco(xs)
pertenece(x, xs)

TABLA[E :: EQ, V :: EQ-ND]


TablaVaca( )
Inserta(xs, x, S)
borra(xs, x)
esVaca(xs)
est(xs, x)

Vectores
Los vectores tambin pueden considerarse como una clase especial de tablas, donde las claves
({ ndices) deben ser de un tipo discreto. Un tipo de datos es discreto si el tipo contiene una cantidad
finita de valores y es isomorfo a un intervalo [1..n] de los enteros, disponiendo de igualdad, orden
y operaciones para determinar los valores primero y ltimo y los valores siguiente y anterior de un
valor dado. La clase DIS de los tipos de datos discretos se puede especificar como subclase de la
clase de tipos ORD.

claseDIS

hereda

 ORD

operaciones


 card:oNat

 ord:ElemoNat

 elem:NatoElem

 prim,ult:oElem




 suc,pred:ElemoElem
axiomas

 x,y:Elem:i:Nat:

 cardt1

 1dord(x)dcard













defelem(i)si1didcard
ord(elem(i))=di
elem(ord(x))=x
prim=elem(1)
ult=elem(card)

502

Tablas





 defsuc(x)six/=ult
 suc(x)

=delem(ord(x)1)
 x==y
=ord(x)==ord(y)


 xdy 
fclase


=ord(x)dord(y)

Equipados con la clase DIS, podemos construir una especificacin algebraica del TAD
VECTOR[I :: DIS, D :: ANY] pensado para representar el comportamiento de los vectores, con
operaciones que permitan crear un vector vaco, modificar un vector asignando un nuevo dato en
la posicin de un ndice dado, y consultar en un vector el dato asociado a un ndice dado:

tadVECTOR[I::DIS,D::ANY]

renombra

 I.ElemaIndice

 D.ElemaDato

usa

 BOOL

tipo

 Vector[Indice,Dato]

operaciones


 Crea:oVector[Indice,Dato]

 Asigna:(Vector[Indice,Dato],Indice,Dato)oVector[Indice,Dato]/*gen*/




 valor:(Vector[Indice,Dato],Indice)oDato
operacionesprivadas




 def?:(Vector[Indice,Dato],Indice)oBool /*obs*/
ecuaciones

/*gen*/
/*obs*/


 v:Vector[Indice,Dato]:i,j:Indice:x,y:Dato:

 Asigna(Asigna(v,i,x),j,y)=Asigna(v,j,y)
sii==j

 Asigna(Asigna(v,i,x),j,y)=Asigna(Asigna(v,j,y),i,x) sii
/=j

 def?(Crea,j)
=falso

 def?(Asigna(v,i,x),j)
=i==jORdef?(v,j)

 defvalor(v,i)sidef?(v,i)

 valor(Asigna(v,i,x),j)
=x
sii==j
f

 valor(Asigna(v,i,x),j)
= valor(v,j)
sii/=j

errores

 valor(v,i)siNOTdef?(v,i)
ftad


Podemos establecer la siguiente correspondencia entre las operaciones de los vectores y las tablas, que nos sugiere cmo los vectores se pueden implementar de manera directa en forma de
tablas
VECTOR[I :: DIS, D :: ANY]
Crea( )

TABLA[E :: EQ, V :: EQ-ND]


TablaVaca( )

503

Tablas

Asigna(xs, i, x)
valor(xs, i)

Inserta(xs, i, x)
consulta(xs, i)

Al entender a los vectores como TAD se pueden plantear implementaciones diferentes de la


predefinida, ganando en eficiencia en ciertos casos, como por ejemplo los vectores dispersos.

6.2 Implementacin con acceso basado en bsqueda


Utilizando las estructuras de datos que ya conocemos, podemos plantear diversas implementaciones para el TAD TABLA como colecciones de parejas (Clave, Valor), donde el acceso por
clave se implementa como una bsqueda en la coleccin:



Vectores de parejas (clave, valor), desordenados u ordenados por clave. Las complejidades que se pueden obtener para las operaciones:
Operacin
TablaVaca
Inserta
esVaca
est
consulta
borra

Vector desordenado
O(1)
O(n) / O(1) **
O(1)
O(n)
O(n)
O(n)

Vector ordenado
O(1)
O(n)
O(1)
O(log n)
O(log n)
O(n)

** La complejidad de la insercin en un vector desordenado es O(n) si no permitimos claves repetidas y O(1) si las permitimos, con el consiguiente desaprovechamiento de espacio
y haciendo necesario que las bsquedas comiencen por el final de la zona ocupada del
vector.

Secuencias de parejas (clave, valor) implementadas con memoria dinmica, desordenadas


u ordenadas por clave. Las complejidades que se pueden obtener para las operaciones:
Operacin
TablaVaca
Inserta
esVaca
est
consulta
borra

Secuencia desordenada
O(1)
O(n) / O(1) **
O(1)
O(n)
O(n)
O(n)

Secuencia ordenada
O(1)
O(n)
O(1)
O(n)
O(n)
O(n)

504

Tablas

** De nuevo, la complejidad de la insercin en un secuencia desordenada es O(n) si no


permitimos claves repetidas y O(1) si las permitimos, con el consiguiente desaprovechamiento de espacio y haciendo necesario que las bsquedas tengan en cuenta esta circunstancia. En el caso de las secuencias ordenadas la complejidad de la bsqueda mejora en el
caso promedio, aunque no as en el caso peor.

Arboles de bsqueda. Suponiendo rboles equilibrados se puede conseguir:


Operacin
TablaVaca
Inserta
esVaca
est
consulta
borra

Arbol de bsqueda
O(1)
O(log n)
O(1)
O(log n)
O(log n)
O(log n)

6.3 Implementacin con acceso casi directo: tablas dispersas


En todas las implementaciones consideradas en el apartado anterior, las operaciones de insercin y consulta requieren bsqueda. En este apartado estudiamos tcnicas que permiten realizar
todas las operaciones en tiempo O(1) en promedio, aunque en el caso peor Inserta, est, consulta y
borra son O(n).
En los vectores, que son estructuras tpicas de acceso directo, todas las operaciones se ejecutan en tiempo constante. La idea es conseguir algo parecido para las tablas, tratando las claves
como ndices de un vector. Esta idea no puede aplicarse directamente cuando el conjunto de todas las claves posibles sea demasiado grande.
Por ejemplo, si las claves son cadenas de caracteres con un mximo de 8 caracteres elegidos de
un conjunto de 52 caracteres, habra un total de
L = 6 i : 1 d i d 8 : 52 i
siendo 52 i el nmero de cadenas distintas con i caracteres. En una aplicacin prctica, la cantidad de cadenas que lleguen a usarse como claves ser mucho menor, y es absolutamente impensable reservar un vector de tamao L para implementar la tabla.
Lo que s tiene sentido es trata de representar una tabla como un vector de tipo
Vector[0..N1]dePareja[Clave,Valor]

siendo N suficientemente grande, aunque mucho menor que el nmero total de claves posibles.
Funcin de localizacin
Para operar con la tabla se necesitar interponer una funcin que asocie a cada clave un ndice
del vector:

505

Tablas

h:Claveo[0..N1]

Dada una clave c, el ndice h(c) es la posicin del vector en la que en un principio intentaremos
localizarla clave. La funcin h que usemos para esto se llamar funcin de localizacin (en ingls, hashing function).
Al ser el nmero total de claves posibles mayor que N, h no puede ser inyectiva. Se deber
procurar que se cumplen las dos condiciones siguientes:

Eficiencia. El coste de calcular h(c) para una clave c dada debe ser bajo.

Uniformidad. El reparto de claves entre posiciones debe ser lo ms uniforme posible.


Idealmente, para una clave c elegida al azar la probabilidad de que h(c) = i debe valer 1/N
para cada i [0..N1]. Una funcin de localizacin que cumpla esta condicin se llama
uniforme.

Grficamente, la situacin es:

0
1

N-1

Clave

[0..N-1]

Por ejemplo, supongamos que N = 16 y que las claves son cadenas de caracteres. Una posible
funcin de localizacin es la definida como:

h(c)=deford(ult(c))mod16


donde ult(c) es el ltimo carcter de c, y ord es la funcin que devuelve el cdigo ASCII de un
carcter. Usando esta funcin de localizacin, obtenemos:

h(Fred)=ord(d)mod16=100mod16=4
h(Joe)=ord(e)mod16=101mod16=5
h(John)=ord(n)mod16=110mod16=14


aunque esta funcin de localizacin no es muy buena, la seguiremos usando para algunos
ejemplos debido a su sencillez. Ms adelante presentaremos algunos mtodos para definir buenas
funciones de localizacin.

506

Tablas

Colisiones
Como hemos visto, el dominio de una funcin de localizacin siempre suele tener un cardinal
mucho mayor que su rango. Por lo tanto, una funcin de localizacin h no puede ser inyectiva.
Cuando se encuentran claves c, c tales que:
c z c h(c) = h(c)
se dice que se ha producido una colisin. Se dice tambin que c y c son claves sinnimas con respecto a h.
Por ejemplo, para la anterior funcin de colisin:
h(Fred) = h(David) = h(Violet) = h(Roland) = 4
las cuatro cadenas colisionan en el ndice 4 y son sinnimas con respecto a h.
La probabilidad de que no se produzcan colisiones es muy baja. Usando clculo de probabilidades puede demostrarse la llamada paradoja de los cumpleaos, que dice: en un grupo de 23 o
ms personas, la probabilidad de que al menos dos de ellas tengan su cumpleaos en el mismo
da del ao es mayor que 1/2.
Tablas dispersas
Se llaman as a las tablas implementadas de manera que:

La representacin concreta de una tabla es un vector t : Vector [0..N1] de Pareja[Clave,


Valor]
Se utiliza una funcin de localizacin para el paso de claves a ndices.

Las distintas implementaciones de tablas dispersas se diferencian en dos cosas:

El mtodo elegido para el tratamiento de colisiones, cuando stas se presenten.

La funcin de localizacin elegida

Vamos a centrarnos primero en estudiar la primera de las caractersticas, suponiendo fijada


una funcin de localizacin h, y estudiaremos la segunda caracterstica ms adelante. Segn la
tcnica que se utilice para el tratamiento de las colisiones, hablamos de:

tablas abiertas, y

tablas cerradas

6.3.1

Tablas dispersas abiertas

Para cada ndice i, t(i) almacena una lista de parejas (Clave, Valor) con claves sinnimas c, tal
que h(c) = i. Estas listas se llaman agrupaciones o, simplemente, listas de colisiones. Las colisiones que
se producen durante una operacin de insercin se resuelven fcilmente alargando las agrupaciones. Durante las operaciones de borrado, las agrupaciones se acortan.
Por ejemplo, una tabla dispersa abierta implementada sobre un vector con ndices del intervalo
[0..15] y donde se utilizada la funcin de localizacin presentada anteriormente, despus de las
siguientes operaciones:
TablaVaca(t);
inserta(t,Fred,25);inserta(t,Alex,18);inserta(t,Philip,10);

507

Tablas

inserta(t,Joe,38);inserta(t,John,36);inserta(t,Hanna,19);
inserta(t,David,40);inserta(t,Martin,28);inserta(t,Violet,20);
inserta(t,George,48);inserta(t,Helen,90);inserta(t,Manyu,24);
inserta(t,Roland,14);


resulta:
0

Philip

10

Fred

25

David

40

Violet

20

Joe

38

George 48

Manyu

24

Alex

18

Hanna

19

John

36

Martin

28

Helen

90

1
2
3
Roland 14

6
7
8
9
10
11
12
13
14
15

Aunque hemos dibujado las listas con punteros por mayor claridad, no presuponemos nada
sobre la implementacin de las agrupaciones. Ntese que la insercin en las listas se realiza por el
final porque es necesario comprobar en cada insercin si ya existe la clave.
Una caracterstica interesante de un valor concreto de una tabla dispersa es su tasa de ocupacin,
que en el caso de las tablas abiertas de define como el cociente entre el nmero de parejas (Clave,
Valor) almacenadas en la tabla, y el nmero total de posiciones del vector.
La situacin final:

Tamao del vector N = 16

Nmero de parejas almacenadas n = 13

Tasa de ocupacin D = n/N = 13/16 = 08125

La realizacin de las operaciones de las tablas en una tabla abierta es sencilla, empleando
tcnicas bsicas de procesamiento de listas. Las colisiones se manejan fcilmente. Otra ventaja

508

Tablas

es que la tabla puede llegar a almacenar ms de N parejas, aunque en este caso la rapidez de los
accesos puede degenerar, hacindose O(n) como en los mtodos de bsqueda secuencial.
Implementacin de las tablas dispersas abiertas
Tipo representante

mduloimplTABLA[CLAVE,VALOR]
importa
EQ,EQND,SEC[ELEM]
privado
const
N=??;
tipo
Clave=EQ.Elem;
Valor=EQND.Elem;
Indice=[0..N1];
Pareja=reg
clave:Clave;
valor:Valor
freg;
Agrupacin=SEC.Sec[Pareja];
Tabla[Clave,Valor]=VectorIndicedeAgrupacin

%implementacindelasoperaciones

fmdulo

Invariante de la representacin
Para t : Tabla[Clave, Valor]:



 R(t)

def


 i:0di<N:

 ((t(i):Agrupacin)

 (j:1djd#cont(t(i)):h((cont(t(i))!!j).clave)=i)


 (j,k:1dj<kd#cont(t(i)):(cont(t(i))!!j).clavez

 (cont(t(i))!!k).clave)




 (j:1djd#cont(t(i)):(cont(t(i))!!j).valorzNoDef))

Donde se expresa que t(i) es tipo Agrupacin, cada pareja de t(i) tiene una clave c tal que h(c) =
i, parejas diferentes de t(i) tienen diferente clave y cada pareja de t(i) tiene un valor z NoDef.

509

Tablas

Funcin de abstraccin
Para t : Tabla[Clave, Valor] tal que R(t):

A(t)=defhazTabla(t,0)


  

TablaVaca 

sii=N
hazTabla(t,i)=def 

  

aadeAgrupacin(cont(t(i)),hazTabla(t,i+1))sii<
N

aadeAgrupacin([],t)=deft
aadeAgrupacin([x/xs],t)=defaadeAgrupacin(xs,Inserta(t,x.clave,
x.valor))

Implementacin de las operaciones


Suponemos disponible una funcin de localizacin, implementada como una funcin privada:
h:ClaveoIndice

La construccin de una tabla vaca:


procTablaVaca(st:Tabla[clave,Valor]);
{P0:cierto}
var
i:Indice;
inicio
paraidesde0hastaN1hacer
SEC.Crea(t(i))
fpara
{Q0:R(t)A(t)=TABLA[CLAVE,VALOR]TablaVaca}


Funcin que detecta si una tabla est vaca.


funcesVaca(t:Tabla[Clave,Valor])devvaca:Bool;
{P0:R(t)}
var
i:Indice;
inicio
vaca:=cierto;
i:=0;
itvacaANDi<No
vaca:=SEC.vaca?(t(i));
i:=i+1
fit
{Q0:vacalA(t)=TABLA[CLAVE,VALOR]TablaVaca}
devvaca

510

Tablas

ffunc

El resto de las operaciones se realiza con ayuda de un procedimiento privado auxiliar que realiza una bsqueda en una agrupacin, una versin del algoritmo de bsqueda en una secuencia:
procbusca(eclave:Clave;esxs:Agrupacin;sencontrado:Bool);
{P0:xs=XS}
var
x:Pareja;
inicio
SEC.reinicia(xs);
encontrado:=falso;
itNOTSEC.fin?(xs)ANDNOTencontradoo
x:=SEC.actual(xs);
siEQ.igual(x.clave,clave)
entoncesencontrado:=cierto
sinoSEC.avanza(xs)
fsi
fit
{Q0:cont(xs)=cont(XS)
encontradollaclavedeunelementodexscoincideconclave
encontradooelactualdexseselprimerocuyaclavecoincidecon
clave}
fproc

Disponiendo de busca la realizacin de Inserta, est, consulta y borra resulta inmediata. Veamos
cmo es la implementacin de Inserta
procInserta(est:Tabla[Clave,Valor];ec:Clave;ev:Valor);
{P0:t=TR(t)}
var
i:Indice;
encontrado:Bool;
par:Pareja;
inicio
i:=h(clave);
busca(clave,t(i),encontrado);
siencontrado
entonces
SEC.borra(t(i))
sino
seguir
fsi;
par.clave:=clave;
par.valor:=valor;
SEC.Inserta(t(i),par)
{Q0:R(t)A(t)=TABLA[CLAVE,VALOR]Inserta(A(T),A(c),A(v))}
fproc

511

Tablas

6.3.2

Tablas dispersas cerradas

La idea bsica de esta representacin es que la informacin almacenada en la tabla se representa por medio de un vector de parejas (Clave, Valor). Cualquier acceso a una tabla va una clave
dada c comienza calculando el llamado ndice primario:
i0=h(c)

Si el ndice primario produce colisin con otra clave sinnima, se ensaya un ndice alternativo.
Como la colisin puede reiterarse, es necesario ensayar una sucesin de ndices:
i1=prueba(1,c),i2=prueba(2,c),,im=prueba(m,c),

llamada sucesin de pruebas. A esta tcnica se la conoce como relocalizacin. Una tcnica de relocalizacin concreta debe cumplir que para toda clave c se puede calcular la sucesin de pruebas:
im=prueba(m,c) 0dm<N

que es una permutacin de [0..N1]. Por lo tanto, las sucesiones de pruebas de dos claves distintas slo difieren en el orden, pero no en los elementos.
Por ejemplo, un mtodo muy sencillo de relocalizacin consiste en definir la sucesin de
pruebas como:
i0=h(c)
im=(im1+1)modN

1dm<N

o lo que es lo mismo
prueba(m,c)=(h(c)+m)modN

Este mtodo es conocido como relocalizacin lineal.


Las tablas implementadas en un vector de parejas, con ayuda de una funcin de localizacin y
alguna tcnica de tratamiento de colisiones mediante sucesin de pruebas, se llaman tablas dispersas
cerradas.
Claves ficticias
Como veremos a continuacin, los algoritmos de acceso a tablas dispersas cerradas necesitan
distinguir 3 clases de posiciones en el vector de parejas:

Posiciones vacas: an no se ha introducido informacin.

Posiciones ocupadas: contienen una pareja (Clave, Valor).

Posiciones borradas: han contenido informacin que posteriormente se borr. La razn de


no marcar estas posiciones como vacas es que interrumpiran la sucesin de pruebas, u
obligaran a reorganizaciones del vector despus de cada borrado.

Para marcar los distintos tipos de posiciones del vector, podemos utilizar un campo adicional
en cada uno de los registros almacenados en l, o suponer que el tipo de las claves est equipado
con dos valores especiales que denominados claves ficticias
ClaveVaca,ClaveBorrada:Clave

diferentes entre s. Llamaremos claves ordinarias a las que sean distintas de estas dos. La funcin
prueba no est definida sobre las claves ficticias.
Si suponemos disponible una funcin

512

Tablas

clave:(Tabla[Clave,Valor],[0..N1])oClave

que calcula la clave de la posicin i-sima de la tabla, podemos definir predicados que caractericen a los distintos tipos de posiciones de una tabla dispersa cerrada:
vaca(t,i)defclave(t,i)==ClaveVaca
borrada(t,i)defclave(t,i)==ClaveBorrada
ocupada(t,i)defNOTvaca(t,i)ANDNOTborrada(t,i)
usada(t,i)defNOTvaca(t,i)ocupada(t,i)ORborrada(t,i)
disponible(t,i)defNOTocupada(t,i)vaca(t,i)ORborrada(t,i)

Tamao y tasa de ocupacin


En la definicin del tamao de una tabla cerrada, debemos considerar todas las posiciones usadas ocupadas o borradas. Dada t : Tabla[Clave, Valor], definimos el tamao n = |t| como el nmero
de posiciones usadas:
|t|=def#i:0di<N:usada(t,i)


La tasa de ocupacin en una tabla dispersa cerrada viene dada entonces por:
D=n/N

nmero de posiciones ocupadas entre nmero de posiciones del vector. Obviamente, en una
tabla cerrada 0 d n d N, y con ello 0 d D d 1.
Idea de los algoritmos
Todos los algoritmos sobre tablas se basan en una caracterstica que debe cumplir el invariante
de la representacin: cada clave ordinaria c que aparezca en la tabla aparece necesariamente en
una posicin de la ruta de c, definida de la siguiente forma:
Dadas t :Tabla[clave, Valor] y c : Clave, definimos la ruta de c como el segmento inicial de la sucesin de pruebas de c anterior a la primera posicin vaca (si la hay):
ruta(c,t)=def[prueba(0,c),,prueba(m1,c)]


siendo m el menor i comprendido entre 0 y N1 tal que vaca(t, prueba(i, c)), si existe; si no existe, tomamos m = N.

c
h
i0

i1

im-1

Ruta de c:
Posiciones ocupadas o borradas

im
Posicin vaca
o bien m = N

Observamos que ruta(c, t) = [ ] cuando se cumple vaca(t, prueba(0, c)); i.e., cuando el ndice primario de c corresponde a una posicin vaca.

513

Tablas

Bsqueda
Partiendo del ndice primario, se sigue la sucesin de pruebas hasta encontrar la clave buscada
o una posicin vaca. Las posiciones borradas no interrumpen la bsqueda:
c

i0
i1

Posiciones ocupadas por claves


distintas de c, o borradas

im-1

Posicin con clave c,


o posicin vaca o m = n
y bsqueda fallida

Insercin
Partiendo del ndice primario, se sigue la sucesin de pruebas hasta agotar la tabla o encontrar
una posicin apta para la insercin. Si se agota la tabla, se produce error. Si se encuentra una posicin apta, debe darse uno de los tres casos siguientes:

La posicin est vaca. Entonces se inserta en esa posicin y se incrementa n en 1.

La posicin est ocupada por la clave de insercin. Entonces se cambia el valor asociado.

La posicin est borrada. Se colocan la clave y el valor asociado. Se sigue buscando en la


serie de pruebas, hasta encontrar una posicin vaca, o encontrar la clave de insercin, o
completar n pruebas. Si se encuentra la clave de insercin, hay que borrar la posicin correspondiente.
c

i0
i1

Posiciones ocupadas por claves


distintas de c

im-1

Posicin apta para


insercin con clave c

im

in-1

Posible presencia
de la clave c

514

Tablas

Consulta
Se realiza una bsqueda con la clave de consulta. Si se encuentra la clave, se devuelve el valor
asociado. En otro caso, se devuelve NoDef.
Borrado
Se realiza una bsqueda con la clave de borrado. Si la bsqueda tiene xito, la posicin correspondiente se borra, i.e., se pone ClaveBorrada.
Ejemplo
Veamos cmo evoluciona una tabla dispersa cerrada, con ndices en el intervalo [0..9], usando
relocalizacin lineal y con una funcin h de localizacin definida como
h(c)=cmod10

ante la serie de llamadas:


Operacin
TablaVaca(t);
Inserta(t,23,50);
Inserta(t,33,60);
Inserta(t,106,70);
x:=consulta(t,
53);
Inserta(t,206,80);
Inserta(t,43,90);
y:=consulta(t,
33);
Inserta(t,53,100);
borra(t,33);
Inserta(t,53,110);
Inserta(t,79,
1000);
Inserta(t,99,
2000);
z:=consulta(t,
53);
u:=consulta(t,
109);


Serie de pruebas

resultado

3
3,4
6
3,4,5

NoDef

6,7
3,4,5
3,4

60

3,4,5,6,7,8
3,4
3,4,5,6,7,8
9
9,0
3,4

110

9,0,1

NoDef

515

Tablas

99

3
23

2000

4
33

43

50

60

90

106

206
70

Borr

8
53
80

79
100

1000

Borr
60

100

53
110
Con lo que la tasa de ocupacin resultante queda:
D=nmerodeposicionesusadas/tamaodelvector=8/10=08

Implementacin de las tablas dispersas cerradas


Tipo representante

mduloimplTABLA[CLAVE,VALOR]
importa
EQ,EQND
privado
const
N=??;

%Capacidaddelatabla;serecomiendaprimo>20
tipo
Clave=EQ.Elem;
Valor=EQND.Elem;
Indice=[0..N1];
IndiceN=[0..N];
Pareja=reg
clave:Clave;
valor:Valor
freg;
Tabla[Clave,Valor]=reg
tamao:IndiceN;
parejas:VectorIndicedePareja
freg;


Donde hemos optado por suponer que el tipo Clave dispone de dos claves ficticias

ClaveVaca,ClaveBorrada:Clave


516

Tablas

Invariante de la representacin
Dada t : Tabla[Clave, Valor]
R(t) def I0 I1 I2 I3

siendo:
I0 def t.tamao = |t| (nmero de posiciones usadas)
I1 def el valor ficticio NoDef no aparece en t.parejas
I2 def cada clave ordinaria c aparece en t.parejas a lo sumo en una posicin
I3 def cada clave ordinaria c que aparezca en t.parejas lo hace en una posicin perteneciente a ruta(c, t)
Funcin de abstraccin
Dada t : Tabla[Clave, Valor] tal que R(t):

A(t)=defhazTabla(t,0)


  

TablaVaca 

sii=N
hazTabla(t,i)=def hazTabla(t,i+1) 
sii<NANDNOT
ocupada(t,i)

  

Inserta(hazTabla(t,i+1),t.parejas(i).clave,

  

t.parejas(i).valor)sii<NANDocupada(t,
i)

Implementacin de las operaciones


Suponemos que se ha fijado un mtodo de tratamiento de colisiones y que se dispone de una
funcin que calcula el ndice de lugar m de la sucesin de pruebas de una clave dada c:

funcprueba(m:Indice;c:Clave)devi:Indice;
{P0:cierto}
{Q0:ieselmsimondicedelasucesindepruebasdec}
ffunc

Disponemos tambin de una funcin auxiliar privada que permite comparar la clave de una
posicin dada de una tabla con una clave dada:

funcigualClave(t:Tabla[Clave,Valor];i:Indice;c:Clave)devr:
Bool;
inicio
r:=EQ.igual(t.parejas(i).clave,c)
devr
ffunc

517

Tablas

Y usando la anterior, funciones que nos permiten determinar el tipo de una posicin: vaca, borrada, ocupada, usada, disponible.


Creacin de una tabla vaca:



procTablaVaca(st:Tabla[Clave,Valor]);
{P0:cierto}
var
i:Indice;
inicio
t.tamao:=0;
paraidesde0hastaN1hacer
t.parejas(i).clave:=ClaveVaca
fpara
{Q0:R(t)A(t)=TablaVaca}
fproc

Funcin que detecta si una tabla est vaca.



funcesVaca(t:Tabla[Clave,Valor])devvaca:Bool;
{P0:R(t)}
var
i:Indice;
inicio
vaca:=cierto;
i:=0;
itvacaANDi<No
vaca:=disponible(t,i);
i:=i+1
fit
{Q0:vacalA(t)=TABLA[CLAVE,VALOR]TablaVaca}
devvaca
ffunc


Las operaciones est, consulta y borra se programan con ayuda de una funcin auxiliar de
bsqueda, que llamaremos busca. La idea es seguir la sucesin de pruebas hasta encontrar la clave
buscada o una posicin vaca. Las posiciones borradas no interrumpen la bsqueda.

funcbusca(t:Tabla[Clave,Valor];c:Clave)devi:IndiceN;
{P0:R(t)cnoesunaclaveficticia}
var
m,lm:Indice;
inicio
m:=0;
i:=hash(c);


%i=prueba(0,c)

518

Tablas

lm:=t.tamao1;
c

%lmacotalalongituddelarutade

itm/=lmANDNOTvaca(t,i)ANDNOTigualClave(t,i,c)o
m:=m+1;
i:=prueba(m,c)
fit;
siigualClave(t,i,c)
entonces


%claveencontrada
seguir
sino
i:=N


%clavenoencontrada
fsi
{Q0:(NOTest(A(t),c)i=N)
(est(A(t),c)t.parejas(i).clave==c)}
devi
ffunc


Utilizando busca resulta inmediata la implementacin de est, consulta y borra.


Finalmente, el algoritmo de insercin

procInserta(est:Tabla[Clave,Valor];ec:Clave;ev:Valor);
{P0:t=TR(t)cnoesunaclaveficticia
(est(A(t),c)i:0didN1:disponible(t,i))}
var
m,lm,i:Indice;
n:IndiceN;
inicio
n:=t.tamao;
m:=0;i:=hash(c); %i=prueba(0,c)
lm:=N1;


%acotalalongituddelasucesinde
pruebas
itm/=lmANDocupada(t,i)ANDNOTigualClave(t,i,c)o
m:=m+1;
i:=prueba(m,c)
fit;
si
ocupada(t,i)ANDNOTigualClave(t,i,c)
oerror(Tablallena)
igualClave(t,i,c)
ot.parejas(i).valor:=v
vaca(t,i)
ot.parejas(i).clave:=c;
t.parejas(i).valor:=v;
t.tamao:=n+1
borrada(t,i)
ot.parejas(i).clave:=c;
t.parejas(i).valor:=v;
lm:=n1; 
%lmacotalalongituddelarutadec

519

Tablas

sim==lm
entonces
seguir
sino
m:=m+1;i:=prueba(m,c);
itm/=lmANDNOTvaca(t,i)ANDNOTigualClave(t,i,c)o
m:=m+1;i:=prueba(m,c)
fit;
siigualclave(t,i,c)
entonces
t.parejas(i).clave:=ClaveBorrada
sino
seguir
fsi
fsi
fsi
{Q0:R(t)A(t)=TABLA[CLAVE,VALOR]Inserta(A(T),A(c),A(v))}
fproc


En la prctica, la relocalizacin no se implementa mediante llamadas a una funcin prueba, como hemos supuesto en las anteriores implementaciones, sino que el clculo de los ndices de la
sucesin de pruebas se incorpora dentro del cdigo de la funcin busca y el procedimiento Inserta,
del modo adecuado a la tcnica de relocalizacin que se est utilizando. Presentamos a continuacin las versiones de busca e Inserta que incorporan relocalizacin lineal sin llamadas a una funcin
prueba.

funcbusca(t:Tabla[Clave,Valor];c:Clave)devi:IndiceN;
{P0:R(t)cnoesunaclaveficticia}
var
li:Indice;
inicio
i:=hash(c);



li:=(i+t.tamao1)modN;

rutadec

%acotaelltimondiceenla

iti/=liANDNOTvaca(t,i)ANDNOTigualClave(t,i,c)o
i:=(i+1)modN
fit;
siigualClave(t,i,c)
entonces


%claveencontrada
seguir
sino
i:=N


%clavenoencontrada
fsi
{Q0:(NOTest(A(t),c)i=N)
(est(A(t),c)t.parejas(i).clave==c)}
devi
ffunc


520

Tablas

Y el algoritmo de insercin

procInserta(est:Tabla[Clave,Valor];ec:Clave;ev:Valor);
{P0:t=TR(t)cnoesunaclaveficticia
(est(A(t),c)i:0didN1:disponible(t,i))}
var
i0,i,li:Indice;
n:IndiceN;
inicio
n:=t.tamao;
i0:=hash(c);
i:=i0;
li:=(i0+N1)modN;
%acotaelltimondicedela
sucesindepruebas
iti/=liANDocupada(t,i)ANDNOTigualClave(t,i,c)o
i:=(i+1)modN
fit;
si
ocupada(t,i)ANDNOTigualClave(t,i,c)
oerror(Tablallena)
igualClave(t,i,c)
ot.parejas(i).valor:=v
vaca(t,i)
ot.parejas(i).clave:=c;
t.parejas(i).valor:=v;
t.tamao:=n+1
borrada(t,i)
ot.parejas(i).clave:=c;
t.parejas(i).valor:=v;
li:=(i0+n1)modN;%acotaelltimondiceenlarutadec
sii==li
entonces
seguir
sino
i:=(i+1)modN
iti/=liANDNOTvaca(t,i)ANDNOTigualClave(t,i,c)o
i:=(i+1)modN
fit;
siigualclave(t,i,c)
entonces
t.parejas(i).clave:=ClaveBorrada
sino
seguir
fsi
fsi

521

Tablas

fsi
{Q0:R(t)A(t)=TABLA[CLAVE,VALOR]Inserta(A(T),A(c),A(v))}
fproc

6.3.3

Funciones de localizacin y relocalizacin

Mtodos de localizacin
Como se ha dicho ms atrs, una buena funcin de localizacin debe ser uniforme y fcil de
calcular eficientemente. En general, lo ms recomendable es calcular el resto mdulo el tamao N
de la tabla. Es decir:


Si la clave es un nmero natural c


h(c)=defcmodN

Si la clave es una cadena de caracteres


c=c1c2ck

la idea es parecida, primero se transforma c en un nmero interpretando sus caracteres


como dgitos en base B:
h(c)=defnum(c)modN
num(c)=def6i:0di<k:Biord(cki)
=ord(ck)+Bord(ck1)++Bk1ord(c1)


Conviene que N sea un nmero primo mayor que 20. No conviene que N sea una potencia de
la base de numeracin B, pues esto tiende a hacer colisionar las claves cuyos cdigos numricos
coinciden en los dgitos de menor peso.
El cdigo de h(c) se puede hacer ms eficiente expresndolo como
h(c)=(num(c)mod2w)modN=((6i:0di<k:Biord(cki))mod2w)mod
N


siendo w el nmero de bits de una palabra mquina. As, el resto mod 2w puede ser ejecutado
muy rpidamente por la circuitera.
Se recomienda el valor B = 131 porque para este valor Bi tiene un ciclo mximo mod 2k para 8
d k d 64.
Mtodos de relocalizacin
Cada uno se caracteriza por una tcnica diferente para el clculo de la sucesin de pruebas.
Desde el punto de vista de la eficiencia, lo deseable es que un mtodo de relocalizacin desacople
lo ms posible las sucesiones de pruebas.
Relocalizacin lineal
En ingls Linear probing hashing.
La serie de pruebas es:

522

Tablas

prueba(m,c)=def(h(c)+m)modN


0dm<N

Se cumple:
i0=h(c)
im=(im1+1)modN


El primer ndice determina la serie de pruebas. Se dice por esto que hay agrupamientos primarios.
Hay una nica sucesin de pruebas, mdulo permutaciones cclicas.
En general, se dice que un mtodo de relocalizacin produce agrupamientos k-arios si el nmero de series de pruebas, mdulo permutaciones circulares es 4(Nk1); o equivalentemente, si cada
serie de pruebas est determinada por sus k primeros ndices.
En la relocalizacin lineal, el hecho de que los agrupamientos correspondientes a diferentes
claves pueden llegar a solaparse, junto con la presencia de posiciones borradas, degrada la eficiencia de los accesos.
Relocalizacin cuadrtica
En ingls Quadratic probing hashing.
La serie de pruebas es
prueba(m,c)=def(h(c)+m2)modN


0dm<N

Para un clculo ms eficiente, se aprovecha que los cuadrados se pueden obtener como suma
de impares consecutivos:
i0=h(c)


im=(im1+jm1)modN


j0=1

jm=jm1+2

Con esta construccin, la sucesin de pruebas en general no es una permutacin de [0..N1] y,


por lo tanto, no recorre toda la tabla; pueden repetirse ndices, de manera que la mitad del espacio
de la tabla quede sin aprovechar. Se sabe que si N es un nmero primo de la forma 4k+3, la siguiente sucesin cuadrtica de pruebas s es una permutacin de [0..N1]:
prueba(0,c)=h(c)
prueba(2j1,c)=(h(c)+j2)modN
2

prueba(2j,c)=(h(c)j )modN


1djd(N1)/2

1djd(N1)/2

Algunos primos de la forma N = 4k+3


k
0
1
2
4
5
7

N
3
7
11
19
23
31

k
10
14
31
62
125
254

N
43
59
127
251
503
1019

523

Tablas

En este mtodo se dice que hay agrupamientos secundarios, porque los dos primeros ndices de
una sucesin de pruebas la determinan. Hay 4(N) sucesiones de pruebas, mdulo permutaciones
circulares.
Relocalizacin doble
En ingls Double hashing.
Este mtodo usa una segunda funcin de localizacin k para calcular un incremento que, junto con el ndice primario, determina la sucesin de pruebas:
prueba(m,c)=(h(c)+mk(c))modN0dm<N


Se cumple:
i0=h(c)
im=(im1+d)modN


d=k(c)[1..N1]

Con una buena eleccin de h, k el comportamiento de este mtodo es muy similar en la prctica al que resultara del modelo terico conocido como relocalizacin uniforme (en ingls, uniform probing hashing). Este modelo presupone que la sucesin de pruebas es una permutacin aleatoria de
[0..N1], dependiente de la clave de acceso, con lo cual no se producen agrupamientos.
Una buena eleccin de N, h, k es:

N primo, tal que N2 primo

h(c) =def num(c) mod N

k(c) =def (num(c) mod N2) + 1

Por ser N primo, N y d son primos entre s, y, por lo tanto, la serie de pruebas recorre toda la
tabla i.e., es una permutacin de [0..N1].

6.3.4

Eficiencia

Los resultados sobre eficiencia vienen expresados en trminos del tamao n (nmero de parejas en las tablas abiertas y nmero de posiciones usadas en las tablas cerradas) y la tasa de ocupacin D = n/N.
En el caso peor, una bsqueda o insercin puede requerir tiempo O(n), tanto en el caso de las
tablas abiertas como en el caso de las tablas cerradas.
Las evaluaciones experimentales, sin embargo, muestran para las tablas dispersas un rendimiento superior al de los rboles de bsqueda equilibrados.
Los anlisis probabilsticos permiten estimar la complejidad en promedio esperada para los
distintos modelos de tabla dispersa. Para realizar esta clase de anlisis se hacen los siguientes supuestos:

La funcin de localizacin se supone uniforme; es decir, para cada i [0..N1], la probabilidad de que una clave aleatoria c cumpla h(c) = i debe ser 1/N. (En ciertas aplicaciones,
por ejemplo en compiladores, no est claro si esta suposicin es realista).

Llamamos Cn al nmero promedio de accesos a posiciones de una tabla de tamao n para


localizar una clave no ficticia presente en la tabla.

524

Tablas

Llamamos Cn al nmero promedio de accesos a posiciones de una tabla de tamao n para


insertar con una nueva clave no presente en la tabla.

Se cumple entonces:
(a) Para tablas abiertas
Cn | 1 + D/2

Cn | D

(b) Para tablas cerradas con relocalizacin lineal


Cn |

1
1
1 

2 1D

Cn |

1
1

1 
2 1  D 2

(c) Para tablas cerradas con relocalizacin cuadrtica o doble:


1
Cn |  ln(1  D )
D

Cn |

1
D
1D

Suponiendo D = 08, las frmulas aproximadas anteriores quedan:


(a) Cn | 14

Cn | 08

(b) Cn | 3

Cn | 13

(c) Cn | 20118

Cn | 5

Observamos que, para una tasa de ocupacin fijada, las tablas abiertas son ms eficientes que
las cerradas. Como contrapartida, las tablas abiertas consumen ms memoria.
En ambos tipos de tablas la eficiencia puede degradarse si la tasa de ocupacin aumenta en exceso. Algunas posibilidades para prevenir esta circunstancia son:

En las tablas cerradas, se puede incluir una operacin que limpie la tabla, vaciando las
posiciones borradas y reestructurando las ocupadas, de forma que todas las informaciones
con claves sinnimas queden en posiciones consecutivas de su serie de pruebas, a partir
de la posicin primaria que corresponda.

En ambos tipos de tablas, se puede incluir una operacin que duplique el tamao del vector. Para ello, es necesario cambiar la funcin de localizacin h a otra con rango doble del
anterior, y reorganizar el vector representante de la tabla.

Ambas operaciones son costosas, pero debido a la disminucin que producen en la tasa de
ocupacin, son operaciones beneficiosas desde el punto de vista de un anlisis amortizado del
tiempo requerido por una serie de operaciones de acceso a la misma tabla.

525

Tablas

6.4 Ejercicios
Tablas: Modelos matemticos y especificacin algebraica
344. Construye

una especificacin algebraica de un TAD TABLA[C :: EQ, V :: ANY] que represente el comportamiento de las tablas entendidas como funciones parciales que asignan valores a claves. La operacin:
consulta:(Tabla[Clave,Valor],Clave)oValor

ser parcial y estar definida solamente cuando la clave de consulta est en la tabla.
345. Construye

una especificacin algebraica de un TAD TABLA[C :: EQ, V :: EQ-ND] que


represente el comportamiento de las tablas entendidas como funciones totales que asignan valores a claves. La operacin:
consulta:(Tabla[Clave,Valor],Clave)oValor

ahora ser total, y devolver un valor ficticio NoDef cuando la clave de consulta no est en la
tabla. Especifica tambin la clase de tipos EQ-ND como subclase de EQ, de manera que a los
tipos de esta clase se les exija disponer de un elemento distinguido NoDef.
Casos especiales: Conjuntos y vectores
346. El

comportamiento del TAD CJTO[E :: EQ] se puede considerar equivalente al del TAD
TABLA[E :: EQ, V :: EQ-ND], suponiendo que V sea un tipo especial que slo contenga
los dos valores Si y Nodef. Razona por qu, estudiando cmo se corresponden las operaciones disponibles de ambos TADs.

347. Diremos

que un tipo de datos es discreto si el tipo contiene una cantidad finita de valores y
es isomorfo a un intervalo [1..n] de los enteros, disponiendo de igualdad, orden y operaciones para determinar los valores primero y ltimo y los valores siguiente y anterior de un valor dado. Especifica la clase DIS de los tipos de datos discretos como subclase de la clase
de tipos ORD.

348. Construye

una especificacin algebraica del TAD VECTOR[I :: DIS, D :: ANY] pensado


para representar el comportamiento de los vectores, con operaciones que permitan crear un
vector vaco, modificar un vector asignando un nuevo dato en la posicin de un ndice dado, y consultar en un vector el dato asociado a un ndice dado. A la vista de las especificaciones, cules son las analogas y diferencias entre vectores y tablas?

349. Para

la verificacin formal de algoritmos en los que intervengan vectores se puede razonar


usando la especificacin algebraica de estos, teniendo en cuenta que cualquier asignacin de
la forma v(i) := e (donde tanto i como e pueden ser expresiones compuestas) debe entenderse como una abreviatura de la asignacin v := Asigna(v, i, e), que expresa una modificacin global de v. Comprueba que la regla de verificacin para asignaciones estudiada en el tema 1.2
es consistente con esta idea, y repasa los ejercicios 21 y 22.

526

Tablas

Tablas: Implementaciones con acceso basado en bsqueda


350. Plantea

una representacin de las tablas basada en vectores que almacenen parejas de tipo
(Clave, Valor), realizando las operaciones generadoras y modificadores como procedimientos con un parmetro es de tipo Tabla[Clave, Valor]. Estudia la eficiencia que podra obtenerse para una implementacin del TAD TABLA basada en esta representacin,
considerando por separado los dos casos siguientes:
(a) El invariante de la representacin exige que el vector representante se mantenga ordenado en orden creciente de claves (suponiendo que el tipo de las claves sea de la
clase ORD).

(b) El invariante de la representacin permite que el vector representante se mantenga
desordenado.

351. Haz

un estudio similar al del ejercicio anterior, considerando ahora una representacin de


las tablas como listas o secuencias de parejas de tipo (Clave, Valor).

352. Plantea

ahora una representacin de las tablas como rboles de bsqueda de tipo Arbus[Clave, Valor]. Cmo conviene definir la operacin ( ) de combinacin de valores,
que se usa en caso de insercin con una clave repetida? Qu eficiencia puede esperarse para las operaciones de una implementacin del TAD TABLA basada en esta representacin,
suponiendo que los rboles de bsqueda sean equilibrados?

353. Para

ciertas aplicaciones de las tablas resulta conveniente que exista un orden entre las claves, y que se disponga de una operacin que extraiga la informacin almacenada en la tabla
en forma de lista (o secuencia) de parejas de tipo (Clave, Valor), ordenada por claves. En
ocasiones, tambin resulta til que una insercin con clave ya presente en la tabla combine
el nuevo valor con el antiguo, en lugar de limitarse a reemplazar el valor antiguo por el
nuevo. Llamaremos tablas ordenadas a las tablas que incorporen estas dos ideas. Construye
una especificacin algebraica de las tablas ordenadas como TAD parametrizado, y desarrolla una implementacin basada en rboles de bsqueda. Qu diferencia de comportamiento hay entre este TAD y el TAD de los rboles de bsqueda?

Tablas: Implementaciones basadas en funciones de dispersin


354. Para

implementar tablas dispersas que usen cadena de caracteres como claves, podra considerarse la siguiente funcin de localizacin:
h(c)=deford(ult(c))mod16

donde ord es la funcin que hace corresponder a cada carcter el nmero de orden que le corresponde, segn el cdigo ASCII. Calcula el ndice que h hace corresponder a cada una de las
cadenas de caracteres que siguen, y observa qu colisiones se producen:
Fred, Alex, Philip, Joe, John, Hanna, David,
Martin, Violet, George, Helen, Manyu, Roland
355. Una tabla dispersa abierta

es una tabla implementada utilizando una funcin de localizacin h


y un vector que almacena listas de parejas de tipo (Clave, Valor). Las colisiones se resuelven
almacenando todas las claves sinnimas c tales que h(c) = i en la lista asociada al ndice i del
vector. Plantea una implementacin de las tablas como tablas dispersas abiertas, y estudia la

527

Tablas

eficiencia que puede lograrse para las operaciones (realizando las generadoras y modificadoras como procedimientos con un parmetro es de tipo Tabla[Clave, Valor]).
356. Sea t : Tabla[Cadena, Nat]

una tabla que se est utilizando para almacenar valores de tipo


Nat (que representan edades) asociados a claves de tipo Cadena (que representan nombres).
Supongamos que t est implementada como tabla dispersa abierta usando un vector con ndices del intervalo [0..15] y la funcin de localizacin h del ejercicio 354. Haz un dibujo que
muestre el estado de la estructura representante de t despus de ejecutar la siguiente serie de
llamadas:
TablaVaca(t);
inserta(t,Fred,25);inserta(t,Alex,18);inserta(t,Philip,10);
inserta(t,Joe,38);inserta(t,John,36);inserta(t,Hanna,19);
inserta(t,David,40);inserta(t,Martin,28);inserta(t,Violet,20);
inserta(t,George,48);inserta(t,Helen,90);inserta(t,Manyu,24);
inserta(t,Roland,14);

Cul es en estos momentos la tasa de ocupacin de la tabla?


357. Una tabla dispersa cerrada

es una tabla implementada utilizando una funcin de localizacin h


y un vector que almacena parejas de tipo (Clave, Valor), y tratando las colisiones del siguiente modo: Si el ndice primario i0 = h(c) asociado a una clave c produce colisin, se prueban otros ndices i1 = prueba(1, c), i2 = prueba(2, c), , im = prueba(m, c), etc. Esta tcnica se
llama relocalizacin, y la sucesin de ndices im que se van probando se llama sucesin de pruebas.
Cuando la funcin prueba est definida como prueba(m, c) =def (h(c) + m) mod N (siendo N la
dimensin del vector usado por la implementacin), se dice que se tiene relocalizacin lineal.
Sea t : Tabla[Nat, Nat] una tabla que usa datos de tipo Nat tanto para las claves como para los
valores. Supongamos que t est implementada como tabla dispersa cerrada usando un vector con
ndices del intervalo [0..9] (i.e., N = 10), usando relocalizacin lineal y la funcin de localizacin h
definida como h(c) =def c mod 10. Estudia cmo va variando el vector representante de la tabla al
ejecutar la siguiente serie de llamadas:
TablaVaca(t);
Inserta(t,23,50);Inserta(t,33,60);Inserta(t,106,70);
x:=consulta(t,53);
Inserta(t,206,80);Inserta(t,43,90);
y:=consulta(t,33);
Inserta(t,53,100);
borra(t,33);
Inserta(t,53,110);Inserta(t,79,1000);Inserta(t,99,2000);
z:=consulta(t,53);u:=consulta(t,109);

Observa cmo se resuelven las colisiones y cul es en cada momento la tasa de ocupacin de
la tabla.
358. Define

un tipo representante adecuado para una implementacin del TAD TABLA que use
tablas dispersas cerradas. Formula el invariante de la representacin y la funcin de abstraccin.

359. Usando

la representacin del ejercicio anterior, programa el procedimiento que implementa


la operacin TablaVaca y la funcin que implementa la operacin esVaca.

528

Tablas

360. Usando

la representacin del ejercicio 358, programa una funcin auxiliar busca que satisfaga la siguiente especificacin pre/post:
funcbusca(t:Tabla[Clave,Valor];c:Clave)devi:Nat;
{P0:R(t)cnoesunaclaveficticia}
{Q0:(NOTest(A(t),c)i=N)

(est(A(t),c)t.parejas(i).clave==c)}
ffunc

Idea: Para buscar la clave c, se comienza con el ndice primario i0 = h(c) y se sigue la sucesin de
pruebas hasta encontrar la clave buscada o una posicin vaca. Las posiciones borradas no interrumpen la bsqueda. El invariante de la representacin garantiza que este algoritmo es correcto.
361. Usando

la funcin auxiliar del ejercicio anterior y la representacin del ejercicio 358, programa el procedimiento que implementa la operacin borra y la funcin que implementa la
operacin consulta.

362. Usando

la representacin del ejercicio 358, programa el procedimiento que implementa la


operacin Inserta.
Idea: Si c es la clave de insercin, el algoritmo de insercin comienza considerando el ndice
primario i0 = h(c), y sigue la serie de pruebas hasta encontrar la clave de insercin o una posicin
que est vaca o borrada. Si esta bsqueda tiene xito, se inserta. Si se ha insertado en una posicin borrada, se hace an otra bsqueda para ver si la clave de insercin aparece ms adelante en
alguna otra posicin de la serie de pruebas. En caso afirmativo, se borra esta posicin, que corresponde a otra insercin anterior con la misma clave. Este procedimiento es correcto si el invariante de la representacin se cumple inicialmente, y su ejecucin preserva el invariante de la
representacin.
363. En

la prctica, la relocalizacin no se implementa mediante llamadas a una funcin prueba,


como hemos supuesto en los ejercicios 360 y 362, sino que el clculo de los ndices de la
sucesin de pruebas se incorpora dentro del cdigo de la funcin busca y el procedimiento
Inserta, del modo adecuado a la tcnica de relocalizacin que se est utilizando. Modifica los
algoritmos obtenidos en los ejercicios 360 y 362 para obtener versiones de busca e Inserta
que incorporen relocalizacin lineal sin llamadas a una funcin prueba.

364. Como

en el ejercicio 357, consideramos t : Tabla[Nat, Nat] implementada como tabla cerrada, pero ahora suponemos que N = 100 y que la funcin de localizacin est definida
como h(c) =def c mod 100. Estudia el efecto de la sucesin de llamadas.
TablaVaca(t);
Inserta(t,10,1010);Inserta(t,210,2110);
Inserta(t,110,1110);Inserta(t,310,3110);
x:=consulta(t,10);y:=consulta(t,310);z:=consulta(t,56);
Inserta(t,109,1109);
u:=consulta(t,9);
Inserta(t,9,9999);Inserta(t,410,4111);Inserta(t,12,1212);
Inserta(t,99,1099);Inserta(t,1999,1999);Inserta(t,0,0);
(a)
(b)

utilizando relocalizacin lineal.


utilizando relocalizacin cuadrtica.

365. Disea

un procedimiento que limpie una tabla cerrada, de tal manera que las posiciones
borradas se eliminen y las restantes informaciones se reubiquen en la tabla. Todas las in-

529

Tablas

formaciones correspondientes a claves sinnimas debern quedar situadas en posiciones


consecutivas de la sucesin de pruebas, a partir de la posicin primaria que corresponda.
(Este procedimiento podra ser exportado por un mdulo de implementacin del TAD
TABLA, junto con las funciones y procedimientos que realizan las operaciones del TAD).
Tablas: Aplicaciones
366. Se

llaman vectores dispersos a los vectores implementados por medio de tablas dispersas. Esta
tcnica es recomendable cuando el conjunto total de ndices posibles es muy grande, y la
gran mayora de los ndices tiene asociado un valor por defecto (por ejemplo, cero). Disea
funciones que ejecuten el producto escalar y la suma de vectores dispersos de nmeros reales
con ndices enteros, suponiendo como valor por defecto el cero. Para ello debers suponer
disponible un mdulo que implemente los vectores dispersos por medio de tablas (con
NoDef = 0). Las operaciones exportadas por este mdulo sern las del TAD VECTOR, y la
representacin interna de los vectores no ser visible para los usuarios del mdulo.

367. Desarrolla

una implementacin del TAD POLI de los polinomios con coeficientes enteros
en una indeterminada (cfr. ejercicio 151), representando los polinomios como vectores dispersos de coeficientes, indexados por exponentes y con el valor por defecto cero. El planteamiento de la implementacin debe ser modular, importando los vectores dispersos de
un mdulo que los implemente.

368. Resuelve

de nuevo el problema de las concordancias (ver ejercicio 307), utilizando en lugar de un


rbol de bsqueda una tabla ordenada del tipo que hemos considerado en el ejercicio 353.
Analiza el tiempo de ejecucin del algoritmo que obtengas.

369. Una

variante del problema de las concordancias consiste en pedir como resultado un listado de las k palabras que han aparecido ms frecuentemente en el texto, ordenadas en orden
decreciente de frecuencias de aparicin. Disea un algoritmo que resuelva este problema,
combinando las ideas de los ejercicios 368 y 338

370. Desarrolla

implementaciones modulares de los TADs siguientes, usando tablas con claves y


valores de tipo conveniente para la construccin del tipo representante. Analiza en cada caso el tiempo de ejecucin de las operaciones y el espacio ocupado por la representacin,
haciendo suposiciones adecuadas acerca del tipo de tabla utilizado y las caractersticas de su
implementacin.
(a) El TAD BOSTEZOS del ejercicio 231.
(b) El TAD CONSULTORIO del ejercicio 232.
(c) El TAD MERCADO del ejercicio 233.
(d) El TAD BANCO del ejercicio 234.
(e) El TAD BIBLIOTECA del ejercicio 236.

530

Grafos

CAPTULO 7
GRAFOS

7.1 Modelo matemtico y especificacin


Un grafo est compuesto por un conjunto de vrtices (o nodos) y un conjunto de aristas (arcos en
los grafos dirigidos) que definen conexiones entre los vrtices. A partir de esta idea bsica, se
definen distintos tipos de grafos:

Dirigidos o no dirigidos. Segn que las aristas estn o no orientadas.

Valorados y no valorados. Segn que las aristas tengan o no peso.

Simples o multigrafos. Segn que se permita una o ms aristas entre dos vrtices.

Los grafos son objeto de estudio en la asignatura Matemtica discreta. Desde el punto de vista
matemtico, un grafo se puede modelar como una pareja formada por un conjunto de vrtices y
un conjunto de aristas:
G=(V,A)

AVuV

(Segn si el grafo est dirigido o no, A es una relacin o un conjunto de conjuntos de dos
elementos).
Otro posible modelo para los grafos consiste en representarlos como un aplicacin:
g:VuVoBool 
g:VuVoW

paralosgrafosnovalorados


paralosgrafosvalorados

De Matemtica discreta podemos recordar conceptos como:

Adyacencia, incidencia. En un grafo no dirigido, un vrtice es adyacente a otro si existe


una arista que los una; en un grafo dirigido, un vrtice u es incidente a otro v si existe una
arista desde u a v.

Grado de entrada y de salida de un vrtice.

Los rboles son un caso particular de los grafos

Resultados sobre la relacin entre el nmero de vrtices NV y el de aristas NA.

Grafo completo:

dirigido: NA = NV (NV 1) = NV2 NV

no dirigido: NA = (NV2 NV) / 2

Arbol: NA = NV 1

Bosque con k rboles: NA = NV k

Un camino es una sucesin de vrtices v0 , v1 , , vn tal que para 1 d i d n (vi1 , vi) es un arco (o una arista si el grafo no es dirigido). La longitud del camino es n (el nmero de arcos
o aristas).

Camino abierto: vn z v0

531

Grafos

Circuito, camino cerrado o circular: vn = v0

Camino simple: no repite vrtices (excepto quiz v0 = vn)

Camino elemental: no repite arcos

Ciclo: camino circular simple

Grafo conexo: grafo no dirigido tal que existe un camino entre cada 2 vrtices

Grafo fuertemente conexo: grafo dirigido tal que existe un camino entre cada 2 vrtices.

Especificacin algebraica
Los grafos se pueden especificar partiendo la idea de generarlos a partir del grafo vaco, usando operaciones que aadan vrtices y aristas, e incluyendo otras operaciones para decidir si dos
vrtices dados son adyacentes, calcular los vrtices vecinos de un vrtice dado, etc. Los detalles de
la especificacin cambian segn la clase de grafos que se quiera especificar.
En el caso ms general de los grafos valorados, necesitamos especificar los grafos con dos
parmetros: el tipo de los vrtices y el tipo de los valores de los arcos.
A los vrtices les exigimos que pertenezcan a la clase de los tipos discretos, una clase que ya
presentamos al hablar del TAD VECTOR, y que sirve como generalizacin de los tipos que se
pueden utilizar como ndices de los vectores.

claseDIS

hereda

 ORD

operaciones


 card:oNat

 ord:ElemoNat

 elem:NatoElem

 prim,ult:oElem




 suc,pred:ElemoElem
axiomas

 x,y:Elem:i:Nat:

 cardt1

 1dord(x)dcard



















defelem(i)si1didcard
ord(elem(i))=di
elem(ord(x))=x
prim=elem(1)
ult=elem(card)
defsuc(x)six/=ult
suc(x)


=delem(ord(x)1)
x==y

=ord(x)==ord(y)


 xdy 
fclase


=ord(x)dord(y)

532

Grafos

La razn de exigir esta condicin a los vrtices de los grafos est en que algunos algoritmos
sobre grafos devuelven vectores bidimensionales indexados por vrtices. Para que tuviese ms
sentido esta restriccin, se deberan incluir dichas operaciones en la especificacin del TAD.
En cuanto a las etiquetas de los arcos, les exigimos que pertenezcan a un tipo ordenado, que
exporte una operacin de suma, y que incluya el valor NoDef, que renombramos a f, y el valor 0.
La razn de estas restricciones est otra vez en los algoritmos que queremos implementar sobre
los grafos, donde necesitaremos sumar valores, comparar valores, y tener disponible un valor
mnimo y un valor mximo. En la especificacin de los grafos que presentamos a continuacin
slo aparece el valor mximo, f, para conseguir que la operacin que consulta sobre el coste de
una arista sea una operacin total, que de f como coste de una arista que no est en el grafo.
claseVALORD

hereda

 EQND,ORD

renombra

 ElemaValor



 Nodefaf
operaciones

 0:oValor




 (+):(Valor,Valor)oValor
axiomas

 x,y,z:Valor:

 0dx











xdf
x+y

=y+x
(x+y)+z=x+(y+z)
x+0

=0


 x+f
fclase


=f

Incluimos operaciones para: construir un grado vaco, Vaco; aadir una arista, indicando los
vrtices que une, y su coste, PonArista; quitar la arista que une a dos vrtices dados, quitaArista;
obtener el coste de la arista que une a dos vrtices dados, costeArista; consultar si existe una arista
que une a dos vrtices dados, hayArista?; obtener los vrtices sucesores de uno dado, sucesores; y
obtener los vrtices predecesores de uno dado, predecesores. No prohibimos que haya bucles, con
origen y destino en el mismo vrtice. Ntese que no incluimos ninguna operacin para insertar
un vrtice, por lo que no puede existir ningn grafo componente conexa con un solo vrtice, a
no ser que tenga un bucle. Especificamos PonArista de forma que no pueda haber dos aristas
entre los mismos vrtices; en la especificacin de los multigrafos habra que suprimir esta
restriccin.
Finalmente, la especificacin de las grafos dirigidos valorados:

tadWDGRAFO[V::DIS,W::VALORD]
 renombra
  V.ElemaVrtice

533

Grafos









usa
 BOOL,SEC[PAREJA[V,W]]
usaprivadamente
 CJTO[PAREJA[V,W]]
tipo
 DGrafo[Vrtice,Valor]
operaciones

  Vaco:oDGrafo[Vrtice,Valor]
  PonArista:(DGrafo[Vrtice,Valor],Vrtice,Vrtice,Valor)

/*gen*/

      oDGrafo[Vrtice,Valor]
  quitaArista:(DGrafo[Vrtice,Valor],Vrtice,Vrtice)

/*gen*/

      oDGrafo[Vrtice,Valor]

/*mod*/

  costeArista:(DGrafo[Vrtice,Valor],Vrtice,Vrtice)oValor/*obs*/
  hayArista?:(DGrafo[Vrtice,Valor],Vrtice,Vrtice)oBool /*obs*/
  sucesores:(DGrafo[Vrtice,Valor],Vrtice)
      oSec[Pareja[Vrtice,Valor]]
  predecesores:(DGrafo[Vrtice,Valor],Vrtice)

/*obs*/

      oSec[Pareja[Vrtice,Valor]]
 operacionesprivadas
  cjtoSuc:(DGrafo[Vrtice,Valor],Vrtice)

/*obs*/

      oCjto[Pareja[Vrtice,Valor]]
  cjtoPred:(DGrafo[Vrtice,Valor],Vrtice)

/*obs*/

      oCjto[Pareja[Vrtice,Valor]]
  enumera:Cjto[Pareja[Vrtice,Valor]]

/*obs*/

      oSec[Pareja[Vrtice,Valor]]
/*obs*/
  insertaOrd:(Pareja[Vrtice,Valor],Sec[Pareja[Vrtice,Valor]]
      oSec[Pareja[Vrtice,Valor]]
 ecuaciones

/*obs*/

  g:DGrafo[Vrtice,Valor]:u,u1,u2,v,v1,v2:Vrtice:
  c,c1,c2:Valor:ps:Sec[Pareja[Vrtice,Valor]]:
  xs:Cjto[Pareja[Vrtice,Valor]]:

  PonArista(PonArista(g,u1,v1,c1),u2,v2,c2)=PonArista(g,u2,v2,c2
)
    siu1==u2ANDv1==v2
  PonArista(PonArista(g,u1,v1,c1),u2,v2,c2)
   =PonArista(PonArista(g,u2,v2,c2),u1,v1,c1)
    siNOT(u1==u2ANDv1==v2)
  quitaArista(Vaco,u,v)

  quitaArista(PonArista(g,u1,v1,c),u2,v2)
    siu1==u2ANDv1==v2

=Vaco
=quitaArista(g,u2,v2)

  quitaArista(PonArista(g,u1,v1,c),u2,v2)

   =PonArista(quitaArista(g,u2,v2),u1,v1,c)
    siNOT(u1==u2ANDv1==v2)
  costeArista(Vaco,u,v)

=f

534

Grafos

  costeArista(PonArista(g,u1,v1,c),u2,v2)
    siu1==u2ANDv1==v2

=c

  costeArista(PonArista(g,u1,v1,c),u2,v2)
    siNOT(u1==u2ANDv1==v2)

=costeArista(g,u2,v2)

  hayArista?(g,u,v)
f

=costeArista(g,u,v)/=

















=enumera(cjtoSuc(g,u))
=enumera(cjtoPred(g,v))

 sucesores(g,u)
 predecesores(g,v)

%enumeradevuelveunasecuenciaconparteizquierdavaca
 enumera(CJTO.Vaco)

=SEC.Crea
 enumera(Pon(Par(v,c)),xs))
  =insertaOrd(Par(v,c),enumera(quita(Par(v,c),xs)))
%insertaOrddevuelveunasecuenciaconparteizquierdavaca
 insertaOrd(Par(v,c),ps)
  =reinicia(inserta(Par(v,c),ps))
   sifin?(ps)

  insertaOrd(Par(v,c),ps)
   =reinicia(inserta(Par(v,c),ps))
    siNOTfin?(ps)ANDactual(ps)=Par(v1,c1)ANDv<v1
  insertaOrd(Par(v,c),ps)
   =insertaOrd(Par(v,c),avanza(ps))
    siNOTfin?(ps)ANDactual(ps)=Par(v1,c1)ANDvtv1
  cjtoSuc(Vaco,u)

=CJTO.Vaco

  cjtoSuc(PonArista(g,u1,v,c),u)
   =Pon(Par(v,c),cjtoSuc(quitaArista(g,u,v),u))
    siu==u1
  cjtoSuc(PonArista(g,u1,v,c),u)
cjtoSuc(g,u)
    siu/=u1

  cjtoPred(Vaco,v)

=CJTO.Vaco

=

  cjtoPred(PonArista(g,u,v1,c),v)
   =Pon(Par(u,c),cjtoPred(quitaArista(g,u,v),v))
    siv==v1
  cjtoPred(PonArista(g,u,v1,c),v)
    siv/=v1
ftad


=cjtoPred(g,v)

535

Grafos

7.2 Tcnicas de implementacin


Matriz de adyacencia
El grafo se representa como una matriz bidimensional indexada por vrtices. En los grafos valorados, en la posicin (i, j) de la matriz se almacena el peso de la arista que va del vrtice i al
vrtice j, f si no existe tal arista.
Por ejemplo, el grafo:
A

1
2

5
2

3
4

2
D

vendra representado por la matriz:


A
A

B
C

f
2

B
1

C
5

D
2

f
4

Vector de listas/secuencias de adyacencia


Se representa como un vector indexado por vrtices, donde cada posicin del vector contiene
la lista de aristas que parten de ese vrtice la lista de sucesores, representadas como el vrtice
de destino y la etiqueta de la arista. En el grafo del ejemplo anterior:

(B, 1)

(D, 3)

(A, 2)

(B, 4)

(C, 5)

(D, 2)

(D, 2)

536

Grafos

Vector de multilistas de adyacencia


Se representa el grafo como un vector indexado por vrtices, donde cada posicin del vector
contiene dos listas: una con las aristas que inciden en ese vrtice lista de predecesores, y otra
con las aristas que parten de l lista de sucesores. La representacin de cada aristas se compone
de el vrtice de partida, el de destino y el peso. En el grafo del ejemplo anterior:

(A, B, 1)

(A, C, 5)

(B, D, 3)

B
C

(A, D, 2)

(C, A, 2)

(C, D, 2)
(D, B, 4)

En este caso no se puede realizar una implementacin modular que importe las listas de otro
mdulo; hay que construir directamente un tipo representante usando registros y punteros.
Variantes

Grafos no dirigidos.

La matriz de adyacencia es triangular y admite una representacin optimizada.

En las listas de adyacencia puede ahorrarse espacio suponiendo un orden entre vrtices y poniendo v en la lista de u slo si u < v con lo que nos ahorramos representar
dos veces la misma arista.

Grafos no valorados.

La matriz de adyacencia puede ser de booleanos

Las listas de adyacencia se reducen a listas de vrtices.

Multigrafos.

Las matrices de adyacencia no son una representacin vlida.

Las listas de adyacencia deben ser listas de ternas (identificador, vrtice, valor), donde el
identificador identifica unvocamente al arco.

Eficiencia de las operaciones


Dados los parmetros de tamao:

NV:

nmero de vrtices

NA:

nmero de aristas

GE:

mximo grado de entrada

537

Grafos

GS:

mximo grado de salida


Operacin
Vaco
PonArista
quitaArista
costeArista
hayArista
sucesores
predecesores

Matriz
O(NV2) (1)
O(1)
O(1)
O(1)
O(1)
O(NV) (1)
O(NV) (1)

Lista
O(NV)
O(GS) (2)
O(GS) (2)
O(GS) (2)
O(GS) (2)
O(GS) (2) (C)
O(NV+NA) (3)

Multilista
O(NV)
O(GS+GE) (4)
O(GS+GE) (4)
O(GS) (2)
O(GS) (2)
O(GS) (2) (C)
O(GE) (5) (C)

(1) Recorrido de todos los vrtices


(2) Recorrido de la lista de sucesores de un vrtice. Ntese que GS d NV1 y que por lo tanto
O(GS) O(NV).
(3) Recorrido de todos los vrtices, y para cada uno, recorrido de su lista de sucesores. El
tiempo es O(NV+NA) porque cada arista u o v se atraviesa una sola vez en el recorrido
de los sucesores de u.
(4) Para poner o quitar la arista u o v hay que recorrer los sucesores de u y los predecesores
de v para as determinar la modificacin oportuna de los enlaces en la multilista de adyacencia. Ntese que GE, GS d NV1 y que por lo tanto O(GE+GS) O(NV).
(5) Recorrido de los predecesores de un vrtice.
Los tiempos marcados como (C) se reducen a O(1) si no se hace una copia de la lista de sucesores/predecesores devuelta como resultado.
Implementacin de las matrices de adyacencia
Tipo representante

mduloimplWDGRAFO[VERTICE,ARISTA]
importa
VALORD,DIS,VECTOR[ELEM,ELEM,ELEM]
privado
tipo
Vrtice=DIS.Elem;
Valor=VALORD.Valor;
DGrafo[Vrtice,Valor]=VECTOR.Vector[Vrtice,Vrtice,Valor]

fmdulo


Estamos suponiendo implementado un mdulo genrico para los vectores bidimensionales. El


problema que se planteara sera la utilizacin de este mdulo con ndices que fuesen un subrango

538

Grafos

de un tipo predefinido, en cuyo caso estaramos obligados a implementar tambin un mdulo


para representar a ese subrango.
El invariante de la representacin es cierto pues cualquier matriz del tipo adecuado es un representante vlido. En cuanto a la funcin de abstraccin, se deja como ejercicio.
Algunas operaciones
Construir un grafo vaco:

procVaco(sg:DGrafo[Vrtice,Valor]);
var
u,v:Vrtice;
inicio
VECTOR.Crea(g);
u:=DIS.prim();
itDIS.menorIgual(u,DIS.ult())o
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o
VECTOR.Asigna(g,u,v,VALORD.infi());
v:=DIS.suc(v);
fit;
u:=DIS.suc(u)
fit
fproc

%g(u,v):=f

Hay un error porque el sucesor del ltimo no est definido, y eso es lo que intentamos obtener
en la ltima iteracin. Se resolvera utilizando un bucle repeat .. until o incluyendo la obtencin del
sucesor dentro de una condicin que protegiera la situacin conflictiva.
Quitar una arista:

procquitaArista(esg:DGrafo[Vrtice,Valor];eu,v:Vrtice)
inicio
VECTOR.Asigna(g,u,v,VALORD.infi()); %g(u,v):=f
fproc


Obtener los predecesores de un vrtice:



funcpredecesores(g:DGrafo[Vrtice,Valor];v:Vrtice)

 devps:Sec[Pareja[Vrtice,Valor]]
var
u:Vrtice;
w:Valor;
inicio
SEC.Crea(ps);

539

Grafos

u:=DIS.prim();
itDIS.menorIgual(u,DIS.ult())o
w:=VECTOR.valor(g,u,v);
%w:=g(u,v)
siVALORD.igual(w,VALORD.infi())
entonces
seguir
sino
SEC.inserta(ps,PAREJA.Par(u,w))
fsi;
u:=DIS.suc(u)
fit;
SEC.reinicia(ps);
devps
ffunc;

Implementacin de las listas de adyacencia


Tipo representante

mduloimplWDGRAFO[VERTICE,ARISTA]
importa
VALORD,DIS,VECTOR[ELEM,ELEM],SEC[ELEM],PAREJA[ELEM,ELEM]
privado
tipo
Vrtice=DIS.Elem;
Valor=VALORD.Valor;
DGrafo[Vrtice,Valor]=VECTOR.Vector[Vrtice,Sec[Pareja[Vrtice,
Valor]]]

fmdulo

Invariante de la representacin
Dado g : DGrafo[Vrtice, Valor]



 R(g)

def

 u:Vrtice:lasparejasdeg(u)aparecenordenadasenorden
creciente

 conrespectoalacomponenteVrtice

Mantenemos las secuencias de sucesores ordenadas, para mejorar la eficiencia de las operaciones. Para escribir formalmente este aserto es necesario definir un cuantificador sobre el dominio
de los tipos de la clase DIS.

540

Grafos

Algunas operaciones
Insercin de una arista:
procPonArista(esg:DGrafo[Vrtice,Valor];eu,v:Vrtice;ew:Valor
);
var
xs:Sec[Pareja[Vrtice,Valor]];
encontrado:Bool;
inicio
xs:=VECTOR.valor(g,u);

%xs:=g(u);
{piz(xs)=[]}
busca(xs,v,encontrado); %proc.auxiliardebsquedaensecuencia
siencontrado
entonces
SEC.borra(xs)
sino
seguir
fsi;
SEC.inserta(xs,PAREJA.Par(v,w));
SEC.reinicia(xs);
VECTOR.asigna(g,u,xs)
%g(u):=xs
fproc

El procedimiento auxiliar busca recorre la secuencia, comparando el vrtice buscado con el


primer componente de cada pareja de la secuencia. Se para cuando encuentra el vrtice buscado,
o cuando se encuentra con uno mayor.
Todas las secuencias del vector estn reiniciadas entre operacin y operacin. Podramos aadirlo al invariante de la representacin.
En cuanto a la gestin de la memoria dinmica, estamos suponiendo que VECTOR.valor no
hace copia del valor devuelto. Si las secuencias estn implementadas como un puntero a un nodo
cabecera, no sera necesaria la reinsercin.
Obtencin del coste de una arista:

funccosteArista(g:DGrafo[Vrtice,Valor];u,v:Vrtice)devw:
Valor;
var
xs:Sec[Pareja[Vrtice,Valor]];
encontrado:Bool;
inicio
xs:=VECTOR.valor(g,u);

%xs:=g(u);
busca(xs,v,encontrado); %proc.auxiliardebsquedaensecuencia
siencontrado
entonces
w:=PAREJA.sg(SEC.actual(xs))
sino
w:=VALORD.infi()

541

Grafos

fsi;
devw
ffunc

Estamos suponiendo que las secuencias se implementan con un nodo cabecera, un registro
representado con memoria esttica, y que por lo tanto los cambios en xs no afectan a g(u). Si lo
que tuvisemos fuese un puntero al nodo cabecera, entonces deberamos reiniciar xs.
La funcin que obtiene los predecesores de un vrtice

funcpredecesores(g:DGrafo[Vrtice,Valor];v:Vrtice)

 devps:Sec[Pareja[Vrtice,Valor]]
var
u:Vrtice;
w:Valor;
xs:Sec[Pareja[Vrtice,Valor]];
inicio
SEC.Crea(ps);
u:=DIS.prim();
itDIS.menorIgual(u,DIS.ult())o
xs:=VECTOR.valor(g,u); 
%xs:=g(u)
busca(xs,v,encontrado);
siencontrado
entonces
w:=PAREJA.sg(SEC.actual(xs));
SEC.inserta(ps,PAREJA.Par(u,w))
sino
seguir
fsi;
u:=DIS.suc(u)
fit;
SEC.reinicia(ps);
devps
ffunc;

7.3 Recorridos de grafos


En analoga con los rboles, los grafos admiten recorridos en profundidad y en anchura, tanto
si son dirigidos como si no. En el caso no dirigido, hay que considerar cada arista como una pareja de aristas orientadas. En muchos casos, los recorridos no dependen de los valores de los arcos,
por lo que en este tema nos limitaremos a grafos no valorados. El TAD grafo no impone un orden determinado a los sucesores (o predecesores) de un vrtice (aunque esto no es necesariamente cierto, pues los grafos se pueden especificar con un orden entre los sucesores/predecesores de
cada vrtice). Como consecuencia, no es posible especificar de manera unvoca una lista de vrtices como resultado de un recorrido. Esta indeterminacin no causa inconvenientes prcticos. Es
suficiente que los algoritmos de recorrido devuelvan una de los recorridos en profundidad y anchura.

542

Grafos

Los grafos dirigidos acclicos admiten un tercer tipo de recorrido, denominado recorrido de ordenacin topolgica.
Vamos a hacer implementaciones modulares de las operaciones, sin acceder a la representacin interna de los grafos.

7.3.1

Recorrido en profundidad

Se puede considerar como una generalizacin del recorrido en preorden de un rbol. La idea
es:

Visitar el vrtice inicial

Si es posible, avanzar a un sucesor an no visitado

En otro caso, retroceder al vrtice visitado anteriormente, e intentar continuar el recorrido desde ste.

Una vez visitados todos los descendientes del vrtice inicial, se quedan vrtices no visitados
hay que iniciar un recorrido de otra componente del grafo.
Por ejemplo, el recorrido del grafo:
B
A
D

E
F
H

Representado como un bosque:

A
B
1

C
2

I 8
D
3
5 F

E 4

6 H

J 9
G 7

K 10
L 11

Posibles aplicaciones del recorrido en profundidad:

Determinar si un grafo es conexo (sirve en el caso de grafos no dirigidos)

Determinar si un grafo es acclico (sirve en el caso de grafos no dirigidos)

543

Grafos

Buscar un vrtice con una propiedad determinada.

El algoritmo recursivo de recorrido en profundidad

Devuelve una secuencia en la cual aparece cada vrtice del grafo una sola vez.

Usa un conjunto de vrtices para llevar cuenta de los vrtices visitados. Las operaciones
de CJTO necesarias son O(1), lo que se puede conseguir con una implementacin basada
en tablas dispersas (un vector de booleanos).

Usa un procedimiento auxiliar privado encargado del recorrido de una sola componente.
funcrecorreProf(g:DGrafo[Vrtice])devxs:Sec[Vrtice];
{P0:cierto}
var
v:Vrtice;
vs:Cjto[Vrtice];
inicio
CJTO.Vaco(vs);
SEC.Crea(xs);
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o
siCJTO.pertenece(v,vs)
entonces
seguir
sino
recorreProfComp(g,v,vs,xs)
fsi;
v:=DIS.suc(v)
fit
{Q0:cont(xs)representaunrecorridoenprofundidaddeg}
devxs
ffunc



El procedimiento auxiliar que recorre en profundidad una componente:



procrecorreProfComp(eg:DGrafo[Vrtice];ev:Vrtice;
esvs:Cjto[Vrtice];esxs:Sec[Vrtice]);
{P0:vs=VSxs=XSNOTCJTO.pertenece(v,vs)SEC.fin?(xs)=cierto

vscontienelosvrticesdexs
}
var
ss:Sec[Vrtice];
u:Vrtice;
inicio
CJTO.Pon(v,vs);
SEC.inserta(xs,v);

544

Grafos

ss:=DGRAFO.sucesores(g,v);
itNOTSEC.fin?(ss)o
u:=SEC.actual(ss);
SEC.avanza(ss);
siCJTO.pertenece(u,vs)
entonces
seguir
sino
recorreProfComp(g,u,vs,xs)
fsi
fit
{Q0:SEC.fin?(xs)=cierto
cont(xs)escont(XS)prolongadoconelresultadodeunrecorrido
enprofundidaddelosvrticesdegaccesiblesdesdevqueno
estabanenVSvscontienelosvrticesdexs}
fproc


La complejidad de la operacin depende de la representacin elegida para los grafos:

7.3.2

Si se usan listas o multilistas de adyacencia, el tiempo es O(NV+NA), ya que cada vrtice se


visita una sola vez, pero se exploran todas las aristas que salen de l.
Si se usan matrices de adyacencia el tiempo es O(NV2), ya que cada vrtice se visita una sola vez, pero el clculo de sus sucesores requiere tiempo O(NV).

Recorrido en anchura

El recorrido en anchura, o por niveles, generaliza el recorrido de rboles con igual denominacin. La idea es:

Visitar el vrtice inicial.

Si el ltimo vrtice visitado tiene sucesores an no visitados, realizar sucesivamente un recorrido desde cada uno de estos. (Esta descripcin informal no describe correctamente el
recorrido en anchura).

En otro caso, continuar con un recorrido iniciado en cualquier vrtice no visitado an.

El recorrido en anchura encuentra caminos de longitud mnima. Por este motivo, es tpico
aplicarlo a problemas de planificacin, donde:

los vrtices representan estados

los arcos representan transiciones entre estados

los caminos representan cambios de estado causados por sucesiones de transiciones

Entre las aplicaciones de este estilo se incluyen juegos, laberinto, etc.

545

Grafos

Por ejemplo, para el mismo grafo del ejemplo anterior:


B
A
D

E
F
H

representado como un bosque, resultado del recorrido por niveles


0
A
B
1

C
2

D
3

8
I
E
G
6

H
7

J
9

K 10
L 11

La implementacin es similar a la del recorrido en profundidad, con ayuda de un procedimiento auxiliar que recorre una componente del grafo. La diferencia est en que en lugar de utilizar un
procedimiento recursivo, lo hacemos iterativo con ayuda de una cola a la manera del recorrido
por niveles de un rbol. si cambisemos esa cola por una pila tendramos la versin iterativa del
recorrido en profundidad.

funcrecorreNiv(g:DGrafo[Vrtice])devxs:Sec[Vrtice];
{P0:cierto}
var
v:Vrtice;
vs:Cjto[Vrtice];
inicio
CJTO.Vaco(vs);
SEC.Crea(xs);
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o
siCJTO.pertenece(v,vs)
entonces
seguir

546

Grafos

sino
recorreNivComp(g,v,vs,xs)
fsi;
v:=DIS.suc(v)
fit
{Q0:cont(xs)representaunrecorridopornivelesdeg}
devxs
ffunc


El procedimiento auxiliar que recorre por niveles una componente:



procrecorreNivComp(eg:DGrafo[Vrtice];ev:Vrtice;
esvs:Cjto[Vrtice];esxs:Sec[Vrtice]);
{P0:vs=VSxs=XSNOTCJTO.pertenece(v,vs)SEC.fin?(xs)=cierto

vscontienelosvrticesdexs
}
var
us:Cola[Vrtice];
ss:Sec[Vrtice];
u,w:Vrtice;
inicio
CJTO.Pon(v,vs);
COLA.ColaVaca(us);
COLA.Aadir(v,us);
itNOTCOLA.esVaca(us)o
u:=COLA.primero(us);
COLA.avanzar(us);
SEC.inserta(xs,u);
ss:=DGRAFO.sucesores(g,u);
itNOTSEC.fin?(ss)o
w:=SEC.actual(ss);
SEC.avanza(ss);
siCJTO.pertenece(w,vs)
entonces
seguir
sino
CJTO.Pon(w,vs);
COLA.Aadir(w,us)
fsi
fit
fit
{Q0:SEC.fin?(xs)=cierto
cont(xs)escont(XS)prolongadoconelresultadodeunrecorrido
pornivelesdelosvrticesdegaccesiblesdesdevqueno
estabanenVSvscontienelosvrticesdexs}

547

Grafos

fproc

El anlisis de la complejidad es el mismo que hemos hecho para el recorrido en profundidad.

7.3.3

Recorrido de ordenacin topolgica

Este tipo de recorridos slo es aplicable a grafos dirigidos acclicos.


Dado un grafo dirigido acclico G, la relacin entre vrtices definida como
u % G v def existe un camino de u a v en G (i.e., u es antepasado de v)
es un orden parcial. Se llama recorrido de ordenacin topolgica de G a cualquier recorrido de G que
visite cada vrtice v solamente despus de haber visitado todos los vrtices de u tales que u % G v.
En general, son posibles varios recorridos de ordenacin topolgica para un mismo grafo G.
Este tipo de recorrido sirve para tener en cuenta relaciones de precedencia entre los vrtices
del grafo (por ejemplo, los prerrequisitos de un plan de estudios).
Por ejemplo, algunos recorridos en ordenacin topolgica del grafo:


B
C

F
D

H
I

J
K

xs : [ A, B, C, D, E, F, G, H, I, J, K, L]
xs : [ A, B, D, F, H, J, C, E, G, I, L, K]
La idea del algoritmo es reiterar la eleccin de un vrtice an no visitado y tal que todos sus
predecesores hayan sido ya visitados. El problema es que el algoritmo resultante de seguir directamente esta idea es poco eficiente.
Una forma de lograr un algoritmo ms eficiente es:

Mantener un vector P indexado por vrtices, tal que P(v) es el nmero de predecesores de
v an no visitados.

Mantener los vrtices v tal que P(v) = 0 en un conjunto M.

Organizar un bucle que en cada vuelta:

aade un vrtice de M al recorrido

actualiza P y M

Para la implementacin de esta idea necesitamos suponer al mdulo que implementa a los
conjuntos equipado con una operacin que permite extraer un elemento cualquiera del conjunto:


548

Grafos

funcelige(m:Cjto[Vrtice])devu:Vrtice;
{P0:NOTesVaco(m)}
{Q0:pertenece(u,m)}
ffunc
procordenaTop(eg:DGrafo[Vrtice];sxs:Sec[Vrtice]);
{P0:gesacclico}
var
u,v:Vrtice;
p:Vector[Vrtice,Nat];
m:Cjto[Vrtice];
ss:Sec[Vrtice];
inicio
%inicializacindepym,primerafaseO(NV)
CJTO.Vaco(m);
VECTOR.Crea(p);
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o
VECTOR.Asigna(p,v,0);

%p(v):=0
CJTO.Pon(v,m);
v:=DIS.suc(v)
fit;
%inicializacindepym,segundafase,O(NV+NA)oO(NV2)
u:=DIS.prim();
itDIS.menorIgual(u,DIS.ult())o
ss:=DGRAFO.sucesores(g,u);
itNOTSEC.fin?(ss)o
v:=SEC.actual(ss);
SEC.avanza(ss);
VECTOR.Asigna(p,v,VECTOR.valor(p,v)+1);%p(v):=p(v)+1
CJTO.quita(v,m)
fit
fit;
SEC.Crea(xs);
%bucleprincipal,O(NV+NA)oO(NV2)
itNOTCJTO.esVaco(m)o
u:=CJTO.elige(m);
CJTO.quita(u,m);
SEC.inserta(u,xs);
ss:=DGRAFO.sucesores(u);
itNOTSEC.fin?(ss)o
v:=SEC.actual(ss);
SEC.avanza(ss);
VECTOR.Asigna(p,v,VECTOR.valor(p,v)1);%p(v):=p(v)1
siVECTOR.valor(p,v)==0
entonces
CJTO.Pon(v,m)
sino
seguir
fsi

549

Grafos

fit
fit
{Q0:cont(xs)representaunrecorridodeordenacintopolgicadeg}
fproc

En cuanto a la complejidad, el algoritmo opera en tiempo:

O(NV2) si el grafo est representado como matriz de adyacencia.

O(NV+NA) si el grafo est representado como vector de listas de adyacencia.

Este algoritmo se puede modificar para detectar si el grafo es acclico o no, en lugar de exigir
aciclicidad en la precondicin. Si el conjunto M es queda vaco antes de haber visitado NV vrtices, el grafo no es acclico.

7.4 Caminos de coste mnimo


En este apartado vamos a estudiar algoritmos que calculan caminos de coste mnimo en grafos
dirigidos valorados.
El coste de un camino se calcula como la suma de los valores de sus arcos. Se presupone por
tanto que existe una operacin de suma entre valores. En las aplicaciones prcticas, los valores
suelen ser nmeros no negativos. Sin embargo, la correccin de los algoritmos que vamos a estudiar slo exige que el tipo de los valores satisfaga los requisitos expresados en la especificacin de
la clase de tipos VAL-ORD que presentamos al principio del tema.

7.4.1

Caminos mnimos con origen fijo.

Dado un vrtice u de un grafo dirigido valorado g, nos interesa calcular todos los caminos
mnimos con origen u y sus correspondientes costes.
Para resolver este problema vamos a utilizar el algoritmo de Dijkstra (1959). El algoritmo mantiene un conjunto M de vrtices v para los cuales ya se ha encontrado el camino mnimo desde u.
La idea del algoritmo es:

Se inicializa M := {u}. Para cada vrtice v diferente de u, se inicializa un coste estimado


C(v) := costeArista(G, u, v).

Se entra en un bucle. En cada vuelta se elige un vrtice w que no est todava en M y cuyo
coste estimado sea mnimo. Se aade w a M, y se actualizan los costes estimados de los
restantes vrtices v que no estn en M, haciendo C(v) := min( C(v), C(w) + costeArista(G, w,
v) ).

Al terminar, se han encontrado caminos de coste mnimo C(v) desde u hasta los restantes
vrtices v.
Un par de comentarios sobre la implementacin del algoritmo:

Si en algn momento llega a cumplirse que para todos los vrtices v que no estn en M
C(v) = f, el algoritmo puede terminar.

Adems de calcular C, calculamos otro vector p, indexado por los ordinales de los vrtices, que representa los caminos mnimos desde u a los restantes vrtices, segn el siguiente criterio:

550

Grafos

p(ord(v)) = 0 si v no es accesible desde u.

p(ord(v)) = ord(w) si v es accesible desde u y w es el predecesor inmediato de v en el


camino mnimo de u a v calculado por el algoritmo.

Como ejemplo del funcionamiento del algoritmo, vamos a ejecutarlo para el grafo dirigido de
la figura siguiente, tomando como vrtice inicial u = 1.
1

30
50

2
40

100

40

70

30

10

3
10

5
4

20




It.
0

M
{1}

1
2
3
4
5

{1, 2}
{1, 2, 5}
{1, 2, 5, 4}
{1, 2, 5, 4, 3}
{1, 2, 5, 4, 3, 6}

2
5
4
3
6

c(1)/p(1) c(2)/p(2) c(3)/p(3)


0/1
30/1
f/0
0/1
30/1
70/2
0/1
30/1
70/2
0/1
30/1
60/4
0/1
30/1
60/4
0/1
30/1
60/4

c(4)/p(4) c(5)/p(5) c(6)/p(6)


50/1
40/1
100/1
50/1
50/1
50/1
50/1
50/1

40/1
40/1
40/1
40/1
40/1

100/1
100/1
100/1
90/3
90/3

De aqu se deduce, por ejemplo, que un camino mnimo de 1 a 6, con coste c(v) = 90, es [1, 4,
3, 6].
Este es un ejemplo tpico de algoritmo voraz, porque sigue la estrategia de construir la solucin
haciendo en cada momento lo que parece localmente ptimo (eleccin de w en cada vuelta del bucle
principal) sin reconsiderar nunca las decisiones tomadas.
En los apuntes de Mario se demuestra la correccin de este algoritmo.
procDijkstra(eg:DGrafo[Vrtice,Valor];eu:Vrtice;
sc:Vector[Vrtice,Valor];
sp:Vector[1..DIS.card()]de[0..DIS.card()]);
{P0:cierto}
var
m:Cjto[Vrtice];

%Vrticesparalosqueyatenemosel
caminomnimo
n:Nat; 

%cardinaldem
v,w:Vrtice;
fin:Bool;
minCoste,nuevoCoste:Valor;
inicio
%inicializacindem,n,c,pyfin
CJTO.Vaco(m);
CJTO.Pon(u,m);
n:=1;

Grafos

551

v:=DIS.prim();
itmenorIgual(v,DIS.ult())o


%O(NV)conMA;O(NV+NA)
conVLA
VECTOR.Asigna(c,v,WDGRAFO.costeArista(g,u,v));
siVALORD.igual(VECTOR.valor(c,v),VALORD.infi())
entonces
p(DIS.ord(v)):=0

%vnotienepredecesor
sino
p(DIS.ord(v)):=DIS.ord(u)
fsi;
v:=DIS.suc(v);
fit;
VECTOR.asigna(c,u,0);
p(DIS.ord(u)):=DIS.ord(u);
fin:=falso;
%bucleprincipal,O(NV2)
itn<DIS.card()ANDNOTfino

%O(NV)iteraciones
%elegimoswmtalquec(w)seamnimo
minCoste:=VALORD.infi();
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o%O(NV)
siNOTCJTO.pertenece(v,m)ANDVALORD.menor(VECTOR.valor(c,v),
minCoste)
entonces
minCoste:=VECTOR.valor(c,v);
w:=v
sino
seguir
fsi;
v:=DIS.suc(v);
fit;
siVALORD.igual(minCoste,VALORD.infi())
entonces


%Noquedanmsvrticesaccesiblesdesde
u.Terminamos
fin:=cierto

sino 

%aadimoswam;actualizamoscyp
CJTO.Pon(w,m);n:=n+1;
v:=DIS.prim();
itDIS.menorIgual(v,DIS.ult())o %O(NV)conMA
siNOTCJTO.pertenece(v,m)
entonces
nuevoCoste:=VALORD.suma(VECTOR.valor(c,w),
WDGRAFO.costeArista(g,w,v));
si
entonces
VECTOR.Asigna(c,v,nuevoCoste);
p(DIS.ord(v)):=DIS.ord(w)
sino
seguir
fsi
sino
seguir
fsi;
v:=DIS.suc(v)
fit
fsi
fit
{Q0:v:Vrtice:c(v)eselcostedeuncaminomnimodeuav

552

Grafos

p(ord(v))indicaelnmerodeordendelvrticepredecesor
inmediatodevendichocamino}
fproc


Al definir el tipo del vector donde almacenamos los caminos Vector [1..DIS.card( )] de
[0..DIS.card( )] estamos usando la informacin que aparece en la especificacin de la clase DIS,
donde se dice que los ordinales de los elementos estn en el intervalo [1..card].
Complejidad del algoritmo de Dijkstra
La realizacin anterior est pensada para una representacin de los grafos con matrices de adyacencia. El coste de las diferentes etapas:

Incializacin de c y p: O(NV). NV iteraciones donde cada iteracin slo involucra operaciones con coste constante. Esto es cierto si los grafos se implementan con matrices de adyacencia, donde efectivamente costeArista es O(1).

El bucle principal se compone de O(NV) iteraciones. En cada iteracin:

La seleccin de w se realiza con un bucle de coste O(NV). Siempre y cuando utilicemos una implementacin de los conjuntos donde pertenece sea O(1).

La actualizacin de c y p se realiza con un bucle que realiza O(NV) iteraciones. Cada


iteracin tiene coste constante siempre y cuando costeArista tenga coste O(1).

Por lo tanto, el coste total es O(NV2).




Si se utilizan grafos representados con listas de adyacencia, es necesario realizar algunos cambios en el algoritmo para obtener esta misma complejidad.. En lugar de recorrer todo el dominio
de los vrtices, consultado el coste de cada posible arista, se recorren exclusivamente los sucesores del vrtice considerado en cada caso. La razn es que el coste de la operacin costeArista es
O(1) en matrices de adyacencia y O(NV) en listas de adyacencia. Por lo tanto los cambios son:

Inicializaciones, para obtener tiempo O(NV)

c se inicializa con f y p se inicializa con 0, excepto c(u) := 0 y p(ord(u)) := ord(u). O(NV)

Para cada pareja (c, v) perteneciente a sucesores(g, u) se hace: c(v) := c; p(v) := u. O(GS)
O(NV).

Bucle principal, para obtener tiempo O(NV2)


Se cambia el bucle interno que actualiza c y p, escribindolo como bucle que recorre los
sucesores de w. Si usamos una implementacin de sucesores que no realice una copia de la
secuencia, entonces el coste de esta operacin es O(1), si realizamos copia entonces es
O(GS). Con lo que la actualizacin de c y p se reduce a recorrer la secuencia de sucesores
de w, que es una operacin con coste O(GS). Por lo tanto, tenemos O(1) + O(GS) o O(GS)
+ O(GS), en cualquier caso O(GS). Como adems se tiene O(GS) O(NV), tenemos que el
coste de la actualizacin es O(NV).

En algunos puntos del anlisis con listas de adyacencia hemos utilizado O(GS), indicando luego
que este valor est acotado por O(NV). Sin embargo, tambin se cumple que O(GS) O(NA), y
esto nos hace pensar que aunque en el caso general NA es O(NV2), si tenemos un grafo disperso
donde se cumple que NA << NV2, podemos obtener mejores resultados utilizando listas de adyacencia. Siguiendo esta idea se pueden realizar una optimizacin del algoritmo de Djikstra que
consiste en sustituir el conjunto m por una cola de prioridad formada por parejas (c, v), donde v es

553

Grafos

un vrtice y c = c(v). Para conseguir la eficiencia deseada, hay que implementar la cola de prioridad
por medio de un montculo, y dotarla de operaciones algo diferentes a las operaciones habituales
de las colas de prioridad. En [Fra94] pp. 323-325 se dan ms detalles. Se consigue complejidad
O((NV+NA) log NV), que es mejor que O(NV2) para grafos dispersos.


En cuanto al espacio, todas las versiones del algoritmo de Dijkstra que hemos estudiado requieren espacio adicional O(NV) para el conjunto de vrtices m, adems del espacio ocupado por
el propio grafo.

7.4.2

Caminos mnimos entre todo par de vrtices

El problema es, dado un grafo dirigido valorado g, se quieren calcular todos los caminos
mnimos entre todas las parejas de vrtices de g, junto con sus costes.
Aplicando reiteradamente el algoritmo de Dijkstra, se obtiene una solucin cuya complejidad
depende de la variante del algoritmo de Dijkstra que se haya utilizado: O(NV3) o bien O(NV NA
log NV).
La otra posibilidad es utilizar el algoritmo de Floyd (1962). Este algoritmo tiene la ventaja de ser
ms elegante y compacto. Adems slo necesita espacio adicional O(1) (aparte del ocupado por el
grafo y la solucin calculada), mientras que cualquier versin del algoritmo de Dijkstra necesita
espacio auxiliar O(NV).
Mientras que el algoritmo de Dijkstra es voraz, el de Floyd es dinmico. Esto quiere decir que las
iteraciones realizadas por el algoritmo se van aproximando a la solucin, pero ninguna parte de
sta es definitiva hasta que no se termina.
La idea bsica del algoritmo consiste en mejorar la estimacin c(u, v) del coste de un camino
mnimo de u a v mediante la asignacin:

c(v,w):=min(c(v,w),c(v,u)+c(u,w))


Diremos que el vrtice u acta como pivote en esta actualizacin de c(v, w). El algoritmo comienza con una inicializacin natural de c, y a continuacin reitera la actualizacin de c(v, w) para
todas las parejas de vrtices (v, w) y con todos los pivotes u.
Al igual que en el algoritmo de Dijkstra, construimos un resultado adicional que representa los
caminos mnimos entre cada par de vrtices. Este resultado viene representado por un vector
bidimensional s indexado por parejas de ordinales de los vrtices, segn el siguiente criterio:

s(ord(v), ord(w)) = 0 si v no hay caminos de v a w.

s(ord(v), ord(w)) = ord(u) si w es accesible desde v y u es el sucesor inmediato de v en el camino mnimo de v a w calculado por el algoritmo.

Apliquemos el algoritmo al siguiente grafo:

554

Grafos

C
0:

Estado inicial
Matriz de costes c

Matriz de sucesores s

B
3

C
9

A
0

f
0

f
7

1
2
3
4
5

1
1
0
0
0
0

2
2
2
0
0
0

3
3
3
3
0
0

4
0
0
0
4
0

5
0
0
0
5
5

1:

Despus de actualizar c y s usando A como vrtice pivote.


Ningn arco entra en A por lo tanto usar A como pivote no mejora nada. c y s quedan inalterados.

2:

Despus de actualizar c y s usando B como vrtice pivote.


Esto permite mejorar el coste del camino entre A y C. c(A, C) y s(1, 3) se modifican, el resto de las posiciones de las matrices no se modifican.
Matriz de costes c

Matriz de sucesores s

B
3

C
7

A
0

f
0

f
7

1
2
3
4
5

1
1
0
0
0
0

2
2
2
0
0
0

3
2
3
3
0
0

4
0
0
0
4
0

5
0
0
0
5
5

3, 4, 5: Actualizaciones de c y s usando como vrtices pivote C, D y E respectivamente.


No mejoran nada, c y s quedan como en la figura anterior.
Finalmente la implementacin del algoritmo:


Grafos

555

procFloyd(eg:DGrafo[Vrtice,Valor];
sc:Vector[Vrtice,Vrtice,Valor];
ss:Vector[1..DIS.card(),1..DIS.card()]de[0..DIS.card()]
);
{P0:cierto}
var
u,v,w:Vrtice;
c:Valor;
inicio
%inicializacindecys.O(NV2)oO(NV+NA)
v:=DIS.prim();
itmenorIgual(v,DIS.ult())o
w:=DIS.prim();
itmenorIgual(w,DIS.ult())o
VECTOR.Asigna(c,v,w,WDGRAFO.costeArista(g,v,w));
siVALORD.igual(VECTOR.valor(c,v,w),VALORD.infi())
entonces
s(DIS.ord(v),DIS.ord(w)):=0
sino
s(DIS.ord(v),DIS.ord(w)):=DIS.ord(w)
fsi;
VECTOR.Asigna(c,v,v,VALORD.Cero());
s(DIS.ord(v),DIS.ord(v)):=DIS.ord(v)
w:=DIS.suc(w)
fit;
v:=DIS.suc(v);
fit;
%bucleprincipal,O(NV3)
u:=DIS.prim();
itmenorIgual(u,DIS.ult())o
%actualizamoscydusandoucomopivote
v:=DIS.prim();
itmenorIgual(v,DIS.ult())o
w:=DIS.prim();
itmenorIgual(w,DIS.ult())o
c:=VALORD.suma(VECTOR.valor(c,v,u),VECTOR.valor(c,u,w));
siVALORD.menor(c,VECTOR.valor(c,v,w))
entonces 

%podemosmejorarelcaminopasandoporu
VECTOR.Asigna(c,v,w,c);
s(DIS.ord(v),DIS.ord(w)):=s(DIS.ord(v),DIS.ord(u))
sino
seguir
fsi;
w:=DIS.suc(w)
fit;
v:=DIS.suc(v);
fit;
u:=DIS.suc(u)
fit
{Q0:v,w:Vrtice:
c(v,w)eselcostedeuncaminomnimodevaw
s(ord(v),ord(w))indicaelnmerodeordendelvrticesuecsor
inmediatodevendichocamino}
fproc


La complejidad del algoritmo de Floyd es claramente O(NV3) si el grafo est representado como matriz de adyacencia. En el caso de que g est representado como vector de listas de adyacen-

Grafos

556

cia, conviene modificar el bucle doble de inicializacin de c y s, cambiando el bucle interno que
recorre todos los vrtices w por un recorrido de los sucesores del vrtice v. As, la inicializacin
requiere tiempo O(NV+NA), y el bucle principal sigue consumiendo tiempo O(NV3).

557

Ejercicios

7.5 Ejercicios
Grafos: Modelos matemticos y especificacin algebraica
371. Especifica un TAD WDGRAFO[V :: DIS, W :: VAL-ORD] que represente el comporta-

miento de los grafos dirigidos valorados con vrtices tomados del tipo discreto V y arcos
etiquetados por valores tomados del tipo con valores ordenados W. Especifica convenientemente la clase de tipos VAL-ORD, e incluye en WDGRAFO operaciones para crear un
grafo vaco (sin aristas), poner y quitar aristas, consultar el coste asociado a una arista, recorrer los sucesores inmediatos de un vrtice, y recorrer los predecesores inmediatos de un
vrtice.

372. Modifica la especificacin del ejercicio anterior para obtener TADs que describan el com-

portamiento de otras clases de grafos:


(a)
(b)
(c)
(d)
(e)

WGRAFO[V :: DIS, W :: VAL-ORD] para los grafos valorados no dirigidos.


DGRAFO[V :: DIS] para los grafos dirigidos no valorados.
GRAFO[V :: DIS] para los grafos no dirigidos no valorados.
WMGRAFO[V :: DIS, W :: VAL-ORD] para los multigrafos valorados.
MGRAFO[V :: DIS] para los multigrafos no valorados.

373. Especifica un TAD REL[X, Y :: DIS] que describa el comportamiento de las relaciones

binarias entre datos de tipo X.Elem y datos de tipo Y.Elem (siendo X, Y tipos discretos).
REL deber estar equipado con un tipo Rel[X.Elem, Y.Elem] y operaciones con los perfiles
siguientes:

Vaca:oRel[X.Elem,Y.Elem]

Generadora. Construye una relacin vaca.

Inserta:(Rel[X.Elem,Y.Elem],X.Elem,Y.Elem)oRel[X.Elem,Y.Elem]

Generadora. Aade una nueva pareja a la relacin.

borra:(Rel[X.Elem,Y.Elem],X.Elem,Y.Elem)oRel[X.Elem,Y.Elem]

Modificadora. Quita una pareja de una relacin.

est?:(Rel[X.Elem,Y.Elem],X.Elem,Y.Elem)oBool

Observadora. Reconoce si una pareja est en una relacin.

fila:(Rel[X.Elem,Y.Elem],X.Elem)oSec[Y.Elem]

Observadora. Enumera una fila de una relacin.

columna:(Rel[X.Elem,Y.Elem],Y.Elem)oSec[X.Elem]

Observadora. Enumera una columna de una relacin.


Observa la analoga entre el TAD DGRAFO[V :: DIS] del ejercicio 372(b) y los ejemplares
REL[V :: DIS, V :: DIS] de REL.
374. Modifica la especificacin del ejercicio anterior para obtener un TAD WREL[X, Y :: DIS,

W :: VAL-ORD] que describa el comportamiento de las relaciones binarias valoradas.

558

Ejercicios

Compara el TAD WDGRAFO[V :: DIS, W :: VAL-ORD] del ejercicio 371 con los ejemplares WREL[V :: DIS, V :: DIS, W :: VAL-ORD] de WREL.
Grafos: Tcnicas de implementacin
375. Dibuja la matriz de adyacencia y el vector de listas/secuencias de adyacencia que represen-

ten el grafo dirigido valorado de la figura siguiente.


A

1
2

5
2

3
4

2
D

376. Plantea dos implementaciones del TAD de los grafos dirigidos valorados, usando dos re-

presentaciones alternativas:
(a) Matriz de adyacencia indexada por pares de vrtices.
(b) Vector de listas/secuencias de adyacencia, indexado por vrtices.
Compara la eficiencia de las dos implementaciones, considerando los tiempos de ejecucin de
las operaciones y el espacio ocupado por la representacin.
377. Dibuja la representacin del grafo del ejercicio 375 como vector de multilistas de adyacen-

cia. Plantea una implementacin del TAD de los grafos dirigidos valorados basada en esta
representacin, y analiza su eficiencia.
Sugerencia: Consulta el texto de Xavier Franch, seccin 6.2.2, apartado c).

378. Estudia cmo modificar las tcnicas de implementacin del ejercicio 376 para adaptarlas a

las otras variedades de grafos del ejercicio 372, as como a las relaciones de los ejercicios
382 y 383.

379. Especifica un TAD que describa las matrices simtricas. Disea una implementacin basada

en la representacin de una matriz simtrica NuN como vector, evitando representar dos
veces los elementos que ocupen posiciones simtricas en la matriz. Plantea una implementacin optimizada de los grafos no dirigidos (valorados o no) usando matrices de adyacencia simtricas como tipo representante.

380. En un grafo completo (i.e., grafo que posea todos los arcos posibles entre sus vrtices) el

nmero NA de arcos es del orden NV2, siendo NV el nmero de vrtices. Un grafo se llama
disperso si NA es significativamente menor que NV2. Estudia implementaciones de los grafos
basadas en una representacin de la matriz de adyacencia como matriz dispersa (cfr. ejercicio
366). Obviamente, esta tcnica de implementacin es recomendable para los grafos dispersos con un conjunto de vrtices de cardinal grande.

559

Ejercicios

Recorridos en grafos
381. Para un grafo dirigido, el recorrido en profundidad y el recorrido por niveles se efectan de manera

anloga al caso de los rboles, pero cuidando de que cada vrtice se visite una sola vez aunque haya ciclos en el grafo. Para ello, se usa un conjunto de vrtices auxiliar que contiene
los vrtices ya visitados. Dibuja en forma de bosques los resultados de recorrer en profundidad y por niveles el grafo dirigido de la figura siguiente, indicando el orden de visita de
los vrtices.
B
A
D

E
F
H

382. Disea un algoritmo de recorrido en profundidad para grafos dirigidos no valorados, cons-

truido como funcin recursiva que devuelva el recorrido en forma de secuencia de vrtices.
Usa un conjunto de vrtices auxiliar para llevar cuenta de los vrtices ya visitados. analiza la
complejidad del algoritmo en funcin de la representacin adoptada para el grafo.

383. Disea un algoritmo de recorrido por niveles para grafos dirigidos no valorados, construido

como funcin iterativa que devuelva el recorrido en forma de secuencia de vrtices. Usa
una cola de vrtices para controlar el recorrido, y un conjunto de vrtices auxiliar para llevar la cuenta de los vrtices ya visitados. Analiza la complejidad del algoritmo en funcin
de la representacin adoptada para el grafo.

384. Modifica el algoritmo del ejercicio 383 cambiando la cola por una pila, de manera que re-

sulte un algoritmo iterativo para el recorrido en profundidad de grafos dirigidos no valorados.

385. Desarrolla una implementacin del TAD CJTO[E :: DIS] usando como tipo representante

el tipo Vector E de Bool, de manera que las operaciones Pon, quita y pertenece sean ejecutables en tiempo constante, y las operaciones Vaco y esVaco sean ejecutables en tiempo lineal. Esta implementacin de CJTO es recomendable para los conjuntos auxiliares utilizados
en los algoritmos de recorrido de los ejercicios anteriores.

386. Dado un grafo dirigido acclico G, la relacin entre vrtices definida como

u % G v def existe un camino de u a v en G (i.e., u es antepasado de v)


es un orden parcial. Se llama recorrido de ordenacin topolgica de G a cualquier recorrido de G que
visite cada vrtice v solamente despus de haber visitado todos los vrtices de u tales que u % G v.
En general, son posibles varios recorridos de ordenacin topolgica para un mismo grafo G.
Construye algunos de ellos para el grafo de la figura siguiente.


560

Ejercicios

B
C

F
D

H
I

J
K

387. Dado un grafo dirigido y acclico G, se desea construir una secuencia de vrtices xs que

represente un recorrido de ordenacin topolgica de G. Disea un procedimiento que resuelva este problema, y analiza su complejidad en funcin de la representacin adoptada
para el grado.
Idea: El algoritmo debe utilizar un bucle que elija en cada vuelta un vrtice cuyos antepasados
ya hayan sido visitados y que no haya sido visitado todava, para aadirlo al recorrido. Conviene
usar un conjunto de vrtices auxiliar M que contiene en cada momento los vrtices que tienen la
propiedad de que todos sus antepasados han sido ya visitados.
388. Modifica el algoritmo del ejercicio anterior, obteniendo una variante que no exija en su

precondicin que G sea acclico, y que detecte durante la ejecucin si G es acclico o no.
Idea: Si el conjunto M mencionado en el ejercicio anterior se queda vaco antes de haber visitado todos los vrtices, entonces el grafo no es acclico; en caso contrario, el grafo es acclico, y el
algoritmo termina completando un recorrido de ordenacin topolgica.
Bsqueda de caminos mnimos en grafos
389. Dados un grafo dirigido valorado G y un vrtice u de G, el algoritmo de Dijkstra (1959) busca

caminos de coste mnimo con origen en u y destinos en los dems vrtices v de G. El algoritmo mantiene un conjunto M de vrtices v para los cuales ya se ha encontrado el camino
mnimo desde u. La idea del algoritmo es:

Se inicializa M := {u}. Para cada vrtice v diferente de u, se inicializa un coste estimado


C(v) := costeArista(G, u, v).
Se entra en un bucle. En cada vuelta se elige un vrtice w que no est todava en M y
cuyo coste estimado sea mnimo. Se aade w a M, y se actualizan los costes estimados
de los restantes vrtices v que no estn en M, haciendo C(v) := min( C(v), C(w) + costeArista(G, w, v) ).

Al terminar, se han encontrado caminos de coste mnimo C(v) desde u hasta los restantes
vrtices v. Ejecuta el algoritmo de Dijkstra para el grafo dirigido de la figura siguiente, tomando
como vrtice inicial u = 1.

561

Ejercicios

30
50

2
40

100

40

70

30

10

3
10

5
20


390. Disea un procedimiento iterativo que implemente el algoritmo de Dijkstra, devolviendo

un vector C indexado por vrtices, tal que C(v) indique el coste del camino mnimo de u a v;
y otro vector P indexado por ordinales de vrtices, tal que P(ord(v)) indique el ordinal del
vrtice predecesor de v en el camino mnimo desde u hasta v (por convenio, ser 0 si no hay
camino de u a v). Analiza la complejidad del algoritmo en funcin de la representacin
adoptada para el grafo, y estudia las optimizaciones que se describen en el texto de Xavier
Franch, seccin 6.4.1.

391. Aplica el algoritmo de Dijkstra al grafo dirigido y valorado de la figura siguiente, tomando

A como vrtice inicial. Observa que el algoritmo se detiene despus de haber encontrado
caminos mnimos para todos los vrtices accesibles desde A.

392. Si se desea obtener caminos de coste mnimo entre todas las posibles parejas de vrtices de

un grafo dirigido y valorado dado, una posibilidad es aplicar reiteradamente el algoritmo de


Dijkstra. Otro mtodo ms compacto y elegante es el algoritmo de Floyd (1962), que usa una
matriz C indexada por parejas de vrtices para calcular en C(u, v) el coste de un camino
mnimo de u a v. La idea del algoritmo es:

Se inicializan C(u, u) := 0 y C(u, v) := costeArista(G, u, v) para u /= v.

Se entra en un bucle anidado que recorre todas las ternas de vrtices u, v, w, actualizando
C(v, w) := min( C(v, w), C(v, u) + C(u, w) ).
Al terminar, est garantizado que todos los valores C(v, w) corresponden a costes de caminos
mnimos. ejecuta el algoritmo de Floyd para el grafo del ejercicio 391.

393. Disea un procedimiento iterativo que implemente el algoritmo de Floyd, devolviendo

adems de la matriz C mencionada en el ejercicio 392, otra matriz S indexada por parejas
de ordinales de vrtices, tal que S(ord(v), ord(w)) indique el ordinal del vrtice sucesor de v en
un camino mnimo de v a w (por convenio, ser 0 si no hay camino de v a w). Analiza la
complejidad del algoritmo en funcin de la representacin adoptada para el grafo.

562

Bibliografa

BIBLIOGRAFA
[Bal93]
[Brass08]
[BS04]

Balczar, J. L.: Programacin metdica. McGraw-Hill, 1993


Brass, P.: Advanced Data Structures. Cambridge University Press, 2008
Baldwin, D., Scragg, G.W.: Algorithms and Data Structures. The Science of
Computing. Charles River Media, 2004.
[CCMRSV93] Castro, J., Cucker, F., Messeguer, X., Rubio, A., Solano, L.,Valles, B.: Curso de
Programacin. McGraw-Hill, 1993.
[Chang03]
Chang, S.K. (Editor): Data Structures and Algorithms. World Scientific, 2003.
[Fra94]
Franch, X.: Estructuras de datos. Especificacin, diseo e implementacin.
Ediciones UPC. 1994
[HS94]
Horowitz, E., Sahni, S.: Fundamentals of Data Structures in Pascal, Computer
Science Press, 1994
[HSM07]
Horowitz, E., Sahni, S., Mehta, D.: Fundamentals of Data Structures in C++,
Segunda Edicin. Silicon Press, 2007
[Kal90]
Kaldewaij, A.: Programming: the Derivation of Algorithms. Prentice Hall,
1990.
[Kingston90] Kingston, J.: Algorithms and data structures: design, correctness, analysis. Addison-Wesley, 1990.
[MS08]
Mehlhorn, K., Sanders, P.: Algorithms and Data Structures. The Basic Toolbox. Springer Verlag. 2008
[MOV94]
Mart Oliet, N., Ortega Malln, Y., Verdejo Lpez, J.A.: Estructuras de datos y
mtodos algortmicos. Ejercicios resueltos. Prentice-Hall, 2004
[Pe05]
Pea Mar, R.: Diseo de programas. Formalismo y abstraccin. Prentice Hall,
2005

También podría gustarte