Você está na página 1de 101

Colección de Problemas

de Programación II

Universidad Nacional de Educación a Distancia

Escuela Técnica Superior de Ingenierı́a Informática

José Ignacio Mayorga Toledano

Fernando López Ostenero

Miguel Rodrı́guez Artacho

última revisión, Mayo 2003


2
Índice general

.Introducción a la Colección de Problemas 7

Parte I Ejercicios 9
Lógica y Especificación 10
1.1. Lógica, estados, asertos e inferencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.1.1. Enunciados lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.1.2. Precondiciones de sentencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2. Predicados y formalización de enunciados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2.1. Extensión de un conjunto de estados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2.2. Intensión de un conjunto de estados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.2.3. Ordenación de predicados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.2.4. Formalización I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.2.5. Formalización II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3. Especificación pre-post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.3.1. Especificación formal de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

Recursividad 18
2.1. Inducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.1.1. Palabras de longitud m . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.1.2. El cuadrado de n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.2. Verificación de algoritmos recursivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.2.1. Precondiciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

Iteración 23
3.1. Verificación de algoritmos iterativos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.1.1. Verificación “a posteriori” de un bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2. Derivación de algoritmos iterativos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.2.1. Derivación formal de un bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

Parte II Preguntas Tipo Test 27


Eficiencia de los Algoritmos 28
Problemas
. resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.1. Factorial atı́pico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.2. Función con cien vueltas de bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Problemas
. propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.3. Muchas Sumas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.4. Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Soluciones
. a los problemas propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

3
4

Especificación de Algoritmos 32
Problemas
. resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.1. Múltiplos de k . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.2. Solución de un sistema de ecuaciones lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.3. La Celebridad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Problemas
. propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.4. Módulo de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.5. Cálculo de la media y la moda de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.6. Posición del primer máximo de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.7. Producto escalar de dos vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.8. Rotación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.9. Descomposición de un cuadrado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.10. Al menos K elementos son cero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Soluciones
. a los problemas propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

Verificación 38
Problemas
. resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.1. Cálculo de la potencia n-ésima de 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.2. Búsqueda dicotómica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Problemas
. propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.3. Cálculo del máximo común divisor de dos enteros . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.4. Búsqueda transversal: precondición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.5. Aproximación al logaritmo en base 2 de un natural . . . . . . . . . . . . . . . . . . . . . . . . 41
6.6. Posición de un máximo de un vector: invariante . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.7. Solución de un sistema de ecuaciones lineales: invariantes . . . . . . . . . . . . . . . . . . . . 42
Soluciones
. a los problemas propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

Derivación 44
Problemas
. resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
7.1. Expresión que garantiza una especificación pre-post . . . . . . . . . . . . . . . . . . . . . . . . 44
7.2. Comprobación del orden de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Problemas
. propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.3. Expresión binaria de un número en base 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.4. Triple producto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
7.5. Conteo de elementos coincidentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
7.6. Búsqueda dicotómica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Soluciones
. a los problemas propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

Parte III Algoritmos Desarrollados 51


Derivación de algoritmos recursivos 52
8.1. El Esferulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
8.1.1. Especificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
8.1.2. Diseño: Inmersión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
8.1.3. Diseño de la función inmersora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
8.1.4. Verificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.2. El monte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.3. Las Dobles Parejas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
8.3.1. Especificación Formal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
8.3.2. Verificación Formal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
8.4. La fábrica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
8.4.1. Especificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8.4.2. Inmersión no final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8.4.3. Análisis por casos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8.4.4. Verificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
5

8.4.5. Transformación a final: Plegado y Desplagado . . . . . . . . . . . . . . . . . . . . . . . 65


8.5. Vector que funciona . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
8.5.1. Especificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
8.5.2. Inmersión no final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
8.5.3. Análisis por casos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
8.5.4. Verificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
8.5.5. Transformación a final: Plegado y Desplagado . . . . . . . . . . . . . . . . . . . . . . . 70
8.6. Fabricando el número perfecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
8.6.1. Especificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
8.6.2. Una función auxiliar: especificación de la función sumadiv . . . . . . . . . . . . . . . . 73
8.6.3. Inmersión: isumadiv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
8.6.4. Especificación de isumadiv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
8.6.5. Llamada inicial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
8.6.6. Análisis por casos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
8.6.7. Código de la función . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
8.6.8. Verificación de la corrección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

Obtención de algoritmos iterativos por transformación de algoritmos recursivos 79


9.1. Viaje Esferulado: De Recursivo A Iterativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
9.2. Dobles Parejas, transformación a iterativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.2.1. Transformación a una función recursiva final . . . . . . . . . . . . . . . . . . . . . . . 81
9.2.2. Transformación a un programa iterativo . . . . . . . . . . . . . . . . . . . . . . . . . . 83

Derivación de algoritmos iterativos 86


10.1. El Último Esferulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
10.1.1. Especificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
10.1.2. Derivación del bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
10.1.3. Obtención de S 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
10.1.4. Diseño definitivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
10.2. El Número Perfecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
10.2.1. Especificación Formal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
10.2.2. Obtención de un Invariante y una función de cota . . . . . . . . . . . . . . . . . . . . 89
10.2.3. Obtención de la Protección del Bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
10.2.4. Obtención de la Inicialización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
10.2.5. Obtención del Cuerpo del Bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
10.2.6. Finalización del Algoritmo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
10.3. La fábrica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
10.3.1. Especificación formal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
10.3.2. Derivación de una función iterativa que satisfaga la especificación . . . . . . . . . . . . 94
10.3.3. Inicialización del bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
10.3.4. Cuerpo del bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
10.3.5. Verificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

Bibliografı́a
. 99

Lista
. de Figuras 101
6
Introducción a la Colección de
Problemas

La presente Colección de Problemas se presenta con la intención de cumplir dos objetivos principales:

Ayudar al estudiante de Programación II al estudio de la asignatura mediante la resolución y explicación


de ejercicios que abarquen lo más ampliamente el temario de ésta.
Servir de complemento a la Guı́a Didáctica1 con la inclusión de una parte dedicada a la resolución
de ejercicios fáciles que afiancen conceptos y sirvan de introducción a la resolución de otros de mayor
envergadura.

El documento se divide en tres partes, cuyos contenidos y motivaciones se explican a continuación:


La primera de las tres partes se compone de ejercicios introductorios a las distintas materias tratadas
por la asignatura, explicados en su completitud. Se trata de pequeños enunciados, con diferentes grados
de dificultad, variando desde los que meramente muestran el uso de la notación hasta los que podrı́an
suponer una parte de un problema completo. Se pretende guiar al alumno en el estudio de la materia,
desde el principio y con ejemplos significativos que acerquen la teorı́a y la práctica. Para ello, esta parte
se coordina estrechamente con la Guı́a Didáctica.
La segunda parte sirve para ayudar al alumno a continuar con el estudio de la asignatura y que supone
una extensa baterı́a de pequeños ejercicios que servirán para afianzar los conocimientos del alumno y
para que éste adquiera soltura en el uso de los conceptos y métodos de resolución de problemas. En
muchos de estos ejercicios, se desarrolla la solución, mientras que en otros se plantea el enunciado y se
indica la respuesta correcta, dejando al alumno, como ejercicio, el razonar sobre la misma.
La tercera parte de esta Colección está dedicada al desarrollo de problemas completos, esto es, desde
su especificación formal hasta la obtención de un algoritmo que los implemente. Se aborda su diseño
recursivo y su transformación a iterativo para dar al alumno una visión panorámica de las técnicas
empleadas en la asignatura y para presentar el tipo de problemas que, en distinta extensión, pueden
aparecer en las Pruebas Presenciales de Programación II.
Finalmente, cabe decir que en toda la Colección de Problemas prima la componente didáctica, siendo prio-
ritario el método sobre el resultado, si bien, en algunos casos, como, por ejemplo, en algunos ejercicios, se
da un enfoque pragmático para ayudar al alumno a desarrollar modos de resolución que le permitan sacar
el máximo partido de sus conocimientos de cara a los exámenes.

1 la última edición impresa de esa Guı́a es del curso 1998-99, ya que existe y está accesible una versión interactiva desde

entonces

7
8
Parte I
Ejercicios

El objetivo de esta parte de la Colección de Problemas consiste en ilustrar y complementar a la Guı́a


Didáctica, a la que acompaña como herramienta para el estudio de la Asignatura.
La naturaleza de los ejercicios planteados en los próximos capı́tulos es introductoria, esto es, junto con los
que la Guı́a Didáctica propone a partir de la bibliografı́a para el estudio de la teorı́a, constituyen la primera
aproximación a la práctica de los conceptos ilustrados. Más adelante, se podrán abordar, con esta preparación
complementaria, problemas de mayor entidad como los que ilustran las dos partes restantes de esta Colección
o como algunos de los ejercicios propuestos en la bibliografı́a de la Asignatura.
Para facilitar el uso conjunto de la Guı́a Didáctica y la Colección de Problemas, la presente parte se dividirá en
los mismos capı́tulos (y, cuando las circunstancias lo aconsejen o permitan, en las mismas secciones) que la
citada Guı́a.
Capı́tulo 1

Lógica y Especificación

1.1. Lógica, estados, asertos e inferencia

Ejercicio 1.1.1 Enunciados lógicos

Enunciado. Expresar mediante la lógica de predicados los siguientes enunciados en lenguaje natural:

Todos los elementos del conjunto Q son impares

El producto de los i primeros elementos (i ≤ n) del vector a definido entre 1 y n es igual a p

Todas las sumas de elementos consecutivos del vector a empezando por el primero dan como resultado
un número par.

Solución. Las expresiones pedidas son las siguientes:

∀ x ∈ Q . x mod 2 6= 0

p = Πiα=1 a[α]

∀ α ∈ {1· · n} . (Σ β ∈ {1· · α} . a[β]) mod 2 = 0

Ejercicio 1.1.2 Precondiciones de sentencias

Enunciado. Dado el siguiente fragmento de código, hallar su precondición:

{Q ≡ ?}
si z < 1 ent z := z 2
si no z := z + 2
fsi
{R ≡ z > 0}

Solución. Según la postcondición tenemos por un lado que o bien debe cumplirse que Rzz+2 ∧ ¬(z < 1)
2
o sino Rzz ∧ (z < 1). Como todo número al cuadrado es positivo, tenemos que ∀z.z 2 ≥ 0 y por tanto la
disyunción anterior queda (z < 1) ∨ ((z + 2 > 0) ∧ ¬(z < 1)), y aplicando la negación y pasando el 2 queda
(z < 1) ∨ ((z > −2) ∧ ¬(z ≥ 1)). De la expresión de la derecha se puede eliminar la menos restrictiva, ya que

10
11

se trata de una conjunción, con lo que se impone que z ≥ 1 y por tanto (z < 1) ∨ (z ≥ 1), lo que equivale a
cierto.
De la precondición encontrada se deduce que cualquiera que sea el valor previo de z, el valor después de
ejecutar la sentencia cumplirá la postcondición señalada.

Enunciado. Hallar la precondición de la siguiente asignación:


{Q ≡ ?}
z = z2 + 6
{R ≡ (z < 40) ∧ (z mod 2 6= 0) ∧ (z ∈ IN )}

Solución. Hay que hacer notar que siempre que haya una condición de ser par o impar es necesario imponer
el dominio de los números naturales. Propagando hacia atrás tenemos: (z 2 + 6 < 40) ∧ (z 2 + 6 mod 2 6=
0) ∧ (z 2 + 6 ∈ IN )}. Y estudiando por separado cada conjunción:
Si z 2 + 6 < 40 entonces z 2 < 36 y z < 6
Si z 2 + 6 es impar, necesariamente z 2 es impar, y por tanto z es impar 1 , con lo que tenemos que
(z < 6) ∧ (z mod 2 6= 0) ∧ (z ∈ IN ), o lo que es lo mismo (z = 1) ∨ (z = 3) ∨ (z = 5)

1.2. Predicados y formalización de enunciados

Ejercicio 1.2.1 Extensión de un conjunto de estados

Enunciado. Para cada uno de los siguientes predicados, y suponiendo que los estados se definen sobre las
variables x e y (y ninguna más), y en el dominio de los enteros tales que su valor absoluto es menor que 5,
lı́stese la extensión del conjunto de estados definidos por dicho predicado:
1. {x ≥ y ∧ 2x ≤ 3y}
2. {(x < y) −→ (3y ≤ 4x)}

Solución. La extensión de un conjunto es la lista de estados que lo componen. El enunciado nos indica
que los estados están definidos por las variables x e y solamente, y que éstas (condición de dominio) cumplen
que:{x, y ∈ ZZ ∧ x, y ∈ [−4, 4]}. Ası́, las extensiones de los conjuntos de estados solicitados son:
1. La expresión que define al conjunto de estados solicitado está compuesta por dos subexpresiones que se
relacionan mediante la conectiva lógica ∧. Esto quiere decir que el conjunto resultante puede calcularse
como la intersección de los conjuntos que definen las subexpresiones. Se deja al lector como ejercicio
que lo calcule de esta manera. El resultado buscado es: (0, 0), (1, 1), (2, 2), (3, 2), (3, 3), (4, 3), (4, 4)
2. Para resolver este ejercicio vamos primero a transformar el predicado que define al conjunto solicitado.
Para ello hemos de recordar la equivalencia lógica: A −→ B ≡ ¬A ∨ B. Ası́, la expresión (x < y) −→
(3y ≤ 4x) se transforma en ¬(x < y) ∨ (3y ≤ 4x), o lo que es lo mismo, (x ≥ y) ∨ (3y ≤ 4x). De
esta forma hemos conseguido simplificar el problema, convirtiéndolo en el cálculo de la unión de los
conjuntos que definen las subexpresiones. El resultado buscado es:
1 Comprobad que 1,3,5,7 y 9 tienen cuadrados impares y 2,4,6 y 8 los tienen pares
12

(−4, −4), (−4, −3), (−4, −2), (−4, −1), (−4, 0), (−4, 1), (−4, 2),
(−4, 3), (−4, 4), (−3, −4), (−3, −3), (−3, −2), (−3, −1), (−3, 0),
(−3, 1), (−3, 2), (−3, 3), (−3, 4), (−2, −4), (−2, −3), (−2, −2),
(−2, −1), (−2, 0), (−2, 1), (−2, 2), (−2, 3), (−2, 4), (−1, −4),
(−1, −3), (−1, −2), (−1, −1), (−1, 0), (−1, 1), (−1, 2), (−1, 3),
(−1, 4), (0, −4), (0, −3), (0, −2), (0, −1), (0, 0), (0, 1), (0, 2),
(0, 3), (0, 4), (1, −4), (1, −3), (1, −2), (1, −1), (1, 0), (1, 1),
(1, 2), (1, 3), (1, 4), (2, −4), (2, −3), (2, −2), (2, −1), (2, 0),
(2, 1), (2, 2), (2, 3), (2, 4), (3, −4), (3, −3), (3, −2), (3, −1),
(3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (4, −4), (4, −3), (4, −2),
(4, −1), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4)

Ejercicio 1.2.2 Intensión de un conjunto de estados

Enunciado. Sea un estado cualquiera definido por las variables x e y, sobre el dominio de los enteros cuyo
valor absoluto es menor que 5. Calcúlese una expresión intensional que defina, al menos, a los siguientes
conjuntos (o sea, que defina los estados enumerados y, quizá, alguno más, pero nunca menos que estos):
1. (1, −1), (2, −2), (3, −3), (4, −4)
2. (−4, 1), (−4, 2), (−4, 3), (−4, 4), (−3, 1), . . . , (−3, 4), (−2, 1), . . . , (−2, 4), (−1, 1), . . . , (−1, 4), (0, 1), . . . ,
(0, 4), (1, 1), . . . , (1, 4), (2, 1), . . . , (2, 4), (4, −4), . . . , (4, −1)

Solución. La intensión o definición intensional (por oposición a extensión o definición extensional) de un


conjunto es una expresión, a partir de la cual se puede saber si un par de valores del dominio (o sea, un
valor para x y otro para y) pertenece al conjunto. La expresión definirá el conjunto como un subconjunto del
producto cartesiano de los dominios de las variables que configuran el estado. Para definirla de forma sencilla
podemos, como se veı́a en el ejercicio 1.2.1, utilizar uniones e intersecciones de conjuntos. Las expresiones
solicitadas serán:
1. Podrı́amos definir aquı́ la intensión como una ecuación: x + y = 0. Esta expresión define, no sólo a los
estados objeto del ejercicio, sino también a otros tantos: (0, 0), (−1, 1), (−2, 2), (−3, 3), (−4, 4). Otra
expresión válida podrı́a ser x > 0 ∧ y < 0, que designa al cuadrante inferior izquierdo del producto
cartesiano, es decir, los cuatro pares que el enunciado propone como extensión del conjunto y otros doce
más. La expresión más concisa que define al conjunto del enunciado (y sólo a éste) serı́a x > 0∧y = −x.
2. En este conjunto vemos dos subconjuntos claramente diferenciados: aquel que engloba a los puntos
(todos) donde la x es negativa (hasta que y vale 2 inclusive), y la parte en que la x vale exactamente
4 y la y es negativa. Como hemos de definir una unión conjuntista, lo haremos mediante la conectiva
’∨’, definiendo como subconjuntos a unir los dos antedichos. La expresión podrı́a ser: (x < 3 ∧ y >
0) ∨ (x = 4 ∧ y < 0). Existe una expresión equivalente (en estas condiciones sólo) que se deja al lector
como complemento a este ejercicio.
13

Ejercicio 1.2.3 Ordenación de predicados

Enunciado. Sea un estado definido por las variables x, y, z y t. Ordénense según su potencia (de mayor
a menor restrictividad, o sea, en orden de implicación):
1. a) {A ≡ x < y ∨ z < t}
b) {B ≡ x ≤ y ∨ z ≤ t}
c) {C ≡ x ≤ y}
d ) {D ≡ z ≤ t}
e) {E ≡ x < y ∧ z < t}
2. a) {F ≡ t = z}
b) {G ≡ x < y}
c) {H ≡ x < y ∧ t = z}
d ) {I ≡ (x ≥ y) −→ (t = z)}
3. a) {J ≡ (x < y) −→ (z ≥ y)}
b) {K ≡ x ≥ y}
c) {L ≡ z ≥ y}
d ) {M ≡ x > y ∧ z > y}

Solución. Para ver qué predicados implican a o son implicados por uno dado, y dado que, por lo general, las
extensiones de los conjuntos de estados que definen no son calculables (por ser dichos conjuntos de cardina-
lidad infinita), necesitamos establecer comparaciones entre las restricciones que imponen dichos predicados,
esperando encontrar subsunciones de unos en otros, o lo que es lo mismo, subexpresiones implicadas por o
que implican a otras subexpresiones (o a las expresiones completas) de los predicados. En este sentido, hay
ciertos patrones que se pueden buscar para determinar subexpresiones implicadas por otras subexpresiones.
Para cualesquiera subexpresiones Q, R:
Q ⇒ Q ∨ R, es decir, es menos restrictiva una disyunción que cualquiera de sus disyuntantes.
Q ∧ X ⇒ Q, o sea, es más restrictiva una conjunción que cualquiera de sus conjuntantes.

Aplicando estos asertos a los enunciados anteriores tenemos:


1. A ⇒ B (porque Q < R ⇒ (Q < R ∨ Q = R)); C ⇒ B; D ⇒ B; E ⇒ D, E ⇒ C, E ⇒ B, E ⇒ A
2. Podemos reescribir I, utilizando la equivalencia lógica Q −→ R ≡ ¬Q ∨ R, como {I ≡ x < y ∨ t = z},
con lo que se simplifican las comparaciones. A la luz de éstas, F ⇒ I; G ⇒ I; H ⇒ F, H ⇒ G, H ⇒ I

3. K ⇒ J; L ⇒ J; M ⇒ L, M ⇒ K, M ⇒ J;
14

Ejercicio 1.2.4 Formalización I

Enunciado. Formalizar los siguientes enunciados:

1. E1 ≡ el conjunto de todos los naturales x mayores que el valor absoluto de un cierto entero y.

2. E2 ≡ aquellos naturales x que no superan un cierto valor igual al factorial de otro natural y.

3. E3 ≡ b es un valor booleano resultado bien de comparar x con y por igualdad, bien tal que el doble de
y no supere al triple de x.

4. E4 ≡ el conjunto de las parejas (x, y) de enteros tales que su suma es igual a otro entero z

Solución.

1. {E1 ≡ x ∈ IN · x >| y | ∧ y ∈ ZZ}

2. {E2 ≡ x ∈ IN · x < y! ∧ y ∈ IN }

3. {E3 ≡ b = (x = y) ∨ (2y ≤ 3x)}

4. {E 4 ≡ (x, y) ∈ ZZ × ZZ | x + y = z ∧ z ∈ ZZ}

Ejercicio 1.2.5 Formalización II

Enunciado. Formalizar los siguientes enunciados. Considérese, para simplificar, que por a designamos a
un vector de n componentes (del tipo que corresponda, en cada caso) (n ∈ IN ), por b a un booleano, y por
x e y a naturales o enteros, según sea.

1. Un booleano b que sea cierto cuando todos los valores de un vector sean menores que un natural y.

2. Un booleano que sea falso cuando exista un valor entre los de un vector a que no sea menor que y.

3. Un booleano que indique si todos los valores de un vector son menores que y.

4. Un natural x que indique la posición de un elemento de un vector que no sea menor que y.

5. Un natural que indique la posición del último elemento de un vector que no sea menor que y.

6. Un booleano que indique si hay alguna posición de un vector de enteros tal que la suma del contenido
más el ı́ndice de la posición se anule.

7. Un booleano que indique si todos los elementos de un vector son pares (sin usar la función par(x)).

8. Un natural que indique cuántos elementos de un vector son impares y menores que y.
15

Solución.
1. En este caso se nos pide que el booleano sea cierto cuando se cumpla la propiedad enunciada más
adelante, pero no se dice nada sobre su valor cuando tal propiedad no se mantenga. Ası́ pues, el esquema
del predicado será b −→ P . Por otra parte, la propiedad P es una conjunción de la comprobación de una
propiedad básica para cada uno de los elementos del vector, o sea: {P ≡ P (a[1])∧P (a[2])∧· · ·∧P (a[n])},
Esto se expresa de forma simplificada mediante un cuantificador universal : ∀ α ∈ {1 · · n} . P (a[α]);
como la propiedad básica es a[α] < y, con α recorriendo los indices del vector, el predicado resulta:
b −→ ∀ α ∈ {1· · n} . a[α] < y.
2. Este caso es análogo al anterior, salvo que la propiedad sobre todo el vector es una disyunción de
propiedades básicas comprobadas sobre sus elementos, lo que se expresa como un cuantificador exis-
tencial. El predicado será: ¬b −→ ∃ α ∈ {1· · n} . a[α] ≥ y. Nótese que hemos invertido el sentido de la
propiedad básica que hay que determinar para cada elemento del vector por comodidad.
3. En este caso el booleano no sólo debe ser cierto en caso de determinación positiva de la propiedad,
sino que ha de ser falso en caso contrario. El esquema será entonces (b −→ P ) ∧ (¬b ⇐= ¬P ). Esto se
expresa como b ←→ P , lo que es equivalente a b = P . El predicado queda: b = ∀ α ∈ {1· · n} . a[α] < y.
4. Suponiendo que tal posición exista, el predicado serı́a: a[x] ≥ y.
5. Aquı́ debemos afrontar un problema de rango. Si no existe ningún elemento que no sea menor que y,
hemos de obtener un valor coherente para el ı́ndice de la posición, ya que el vector se indexa de 1 a n,
no se podrı́a realizar ningún acceso sobre a[0], ya que este elemento no está definido. De esta forma,
necesitamos permitir que x valga 0, pero ha de evitarse el acceso (erróneo) a la posición a[0]. Esto se
logra como sigue: x ∈ {0· · n − 1} ∧ ∀ α ∈ {x + 1· · n} . a[α] < y ∧ (x > 0 −→ a[x] ≥ y).
6. b = ∃ α ∈ {1· · n} . α + a[α] = 0.
7. b = ∀ α ∈ {1· · n} . a[α] mod 2 = 0
8. En este caso necesitamos usar el cuantificador de conteo, que obtiene cuántos elementos de un cierto
conjunto o rango cumplen la propiedad básica que se comprueba para cada uno de ellos. En realidad,
es un sumatorio sobre la función caracterı́stica del conjunto respecto a la propiedad enunciada.

x = N α ∈ {1· · n} . a[α] < y ∧ a[α] mod 2 6= 0

1.3. Especificación pre-post

Ejercicio 1.3.1 Especificación formal de funciones

Enunciado. Especifı́quense las siguientes funciones:


1. Una función que devuelva la suma de los elementos de un vector (que podrı́a ser vacı́o) de naturales.
2. Una función que compruebe si un elemento dado pertenece a los valores de un vector.
3. Una función que devuelva la posición de un elemento (cuya existencia ha de estar garantizada en el
vector) que sea igual a un parámetro de entrada. ¿Qué se puede decir de la precondición de esta función
respecto a la del ejercicio precedente?
16

4. Una función que devuelva el máximo común divisor de dos naturales (sin usar la función mcd).
5. Una función que devuelva la posición del último máximo de un vector (sin usar el cuantificador max).
6. Una función que verifique si existe un solo máximo entre los elementos de un vector.

Solución.
1. Comencemos por la postcondición: la suma de un vector de naturales será otro natural, y la abreviatura
para tal operación es el sumatorio, siendo su rango el que define a los ı́ndices válidos del propio vector.
En cuanto a la precondición, el enunciado indica que el vector podrı́a ser vacı́o. Como el vector está in-
dexado (por convención) de 1 a n, eso implica que n podrı́a ser nula.
La especificación queda:
{Q ≡ n ≥ 0}
fun SumaV ector (v : vector[1 · · n] de nat) dev (suma : nat)
{R ≡ suma = Σ α ∈ {1· · n} . v[α]}
2. La postcondición debe indicar si el valor de prueba está o no en el vector. Como ya hemos visto (en
la solución al ejercicio 1.2.5-3) eso significa que el resultado de la función es un booleano que se iguala
a la propiedad que se quiere comprobar, o sea, una disyunción compuesta por la verificación (de la
presencia o ausencia del valor pasado como parámetro de entrada) en cada uno de los elementos del
vector. La disyunción extendida a todo el vector se expresa mediante un cuantificador existencial.
La precondición no necesita imponer restricciones (salvo que se desee acotar el rango permisible para
el tamaño del vector), por lo que sera cierto.
La función queda especificada como sigue:
{Q ≡ cierto}
fun existe? (v : vector[1 · · n] de nat; x : nat) dev (b : bool)
{R ≡ b = ∃ α ∈ {1· · n} . v[α] = x}
3. La postcondición de esta función debe decir que la posición devuelta como resultado contiene el elemen-
to que se buscaba. La forma más sencilla de expresar tal aserto es: a[pos] = x, siendo pos el resultado
de la función y x el dato que se buscaba. Para que la postcondición pueda ser la anteriormente escrita,
la precondición debe garantizar la presencia del elemento entre los del vector, lo que era además una
exigencia del enunciado. Nótese que la precondición sólo debe decir que el elemento está en el vector,
pero no dónde se encuentra.
La especificación será:
{Q ≡ ∃ α ∈ {1· · n} . a[α] = x}
fun buscar (a : vector[1 · · n] de nat; x : nat) dev (pos : nat)
{R ≡ a[pos] = x}
4. La postcondición de esta función debe expresar que el resultado (llamémosle mcd) divide a los dos
naturales que constituyen su entrada (a y b), y que no hay ningún natural que, dividiendo a ambos,
sea mayor que mcd. Deduzcámosla:
{R(a,b,mcd) ≡ R0 (a, b, mcd) ∧ R00 (a, b, mcd)}

R0 (a, b, mcd) ≡ a mod mcd = 0 ∧ b mod mcd = 0


R00 (a, b, mcd) ≡ ¬∃ α ∈ IN . ((a mod α = 0) ∧ (b mod α = 0) ∧ (α > mcd))
| {z }
¬∃α . P (α)≡∀α . ¬P (α)
17

≡ ∀ α ∈ IN . ¬((a mod α = 0) ∧ (b mod α = 0) ∧ (α > mcd))


| {z }
por De Morgan
≡ ∀ α ∈ IN . (¬(a mod α = 0) ∨ ¬(b mod α = 0) ∨ ¬(α > mcd))
| {z }
propiedad asociativa de la disyunción
≡ ∀ α ∈ IN . ((¬(a mod α = 0) ∨ ¬(b mod α = 0)) ∨ ¬(α > mcd))
| {z }
De Morgan y ¬(A>B)≡(A≤B)

≡ ∀ α ∈ IN . (¬((a mod α = 0) ∧ (b mod α = 0)) ∨ (α ≤ mcd))


| {z }
(¬A∨B)≡(A−→B)

≡ ∀ α ∈ IN . ((a mod α = 0) ∧ (b mod α = 0)) −→ (α ≤ mcd)

Con lo que la especificación de la función queda:


Condiciones de dominio
z }| {
{Q ≡ a>0∧b>0 }
fun maxcd (a, b : nat) dev (mcd : nat)
{R ≡ a mod mcd = 0 ∧ b mod mcd = 0 ∧ ∀ α ∈ IN .
(((a mod α = 0) ∧ (b mod α = 0)) −→ (α ≤ mcd))}
5. La postcondición debe aquı́ expresar dos propiedades del elemento cuya posición buscamos:
dicho elemento debe tener un valor mayor o igual al del resto de los del vector (o sea, debe ser
mayor que los demás elementos del vector).
todos los posteriores en orden creciente de ı́ndice en el vector deben ser estrictamente menores
que éste (o, lo que es lo mismo, si hay varios elementos iguales entre sı́ y mayores que el resto,
debe devolverse el último de ellos).
Para expresar la primera condición usaremos un cuantificador universal (pues no podemos usar el
cuantificador max) cuyo rango será el conjunto de ı́ndices del vector. La segunda condición también se
puede expresar con un cuantificador universal cuyo rango comprenderá a los ı́ndices posteriores al que
buscamos. Este rango se hará vacı́o si el último máximo está en la última posición del vector.
En cuanto a la precondición, no es necesaria ninguna condición (salvo, quizá, la referente al rango
premisible de longitudes para el vector).
La especificación completa será:
{Q ≡ cierto}
fun P osM ayor (v : vector[1 · · n] de nat) dev (pos : nat)
{R ≡ ∀ α ∈ {1· · n} . v[α] ≤ v[pos] ∧ ∀ α ∈ {pos + 1· · n} . v[α] < v[pos]}
6. La postcondición de este enunciado es semejante a la del ejercicio anterior, pero donde no nos interesa
qué posición ocupe el máximo. Además, en ella usaremos dos cuantificadores imbricados: un existencial
para expresar que buscamos un elemento cualquiera que cumpla la propiedad que será a su vez descrita
mediante un universal. El resultado será un booleano que indique si se cumple la condición solicitada:
{Q ≡ cierto}
fun U nM axSolo? (a : vector[1 · · n] de nat) dev (b : bool)
{R ≡ b = ∃ α ∈ {1· · n} . ∀ β ∈ {1· · n} . a[α] > a[β]}
Capı́tulo 2

Recursividad

2.1. Inducción

Ejercicio 2.1.1 Palabras de longitud m

Enunciado.1 Demostrar, por inducción, que el número de palabras de longitud m que se pueden construir
con un alfabeto que consta de n caracteres es nm .

Solución. Antes de hacer la demostración, definiremos algunos términos útiles:

Llamaremos Σ a un alfabeto (conjunto de sı́mbolos).

Sean a, b ∈ Σ dos sı́mbolos. Entonces a · b será una cadena válida (o palabra) de longitud 2. El sı́mbolo
de operación • designa a la operación de concatenación de sı́mbolos. Por simplicidad, no escribiremos
tal sı́mbolo. Ası́, {aa, ab, ba, bb} constituyen todas las palabras de longitud 2 que se pueden construir a
partir de los sı́mbolos a y b. Designaremos por Σ2 al conjunto de todas las palabras de longitud 2 que
pueden formarse a partir de los sı́mbolos del alfabeto Σ.

Designaremos, análogamente, por Σn al conjunto de palabras de longitud n que pueden formarse


mediante concatenación de los sı́mbolos del alfabeto Σ.

Designaremos por kSk a la cardinalidad (el número de elementos que tiene) del conjunto S. Ası́, kΣk
será el número de sı́mbolos del alfabeto Σ, y kΣn k el número de palabras de longitud n construı́das
concatenando sı́mbolos de Σ.

De esta forma, podemos rescribir el enunciado que debemos demostrar como sigue:

∀ n, m ∈ nat ∧ n > 0 . kΣk= n ⇒kΣmk= nm

Demostración (por inducción sobre la longitud de las palabras)

1. Base de inducción.

a) sea m = 0 en
Lema 2.1.1.1 Sólo se puede construir una palabra de longitud 0
1 Este enunciado aparece en [Balc93, problema III.1, pag(s) 94].

18
19

Demostración (por reducción al absurdo)


Supongamos que existen dos palabras distintas (w y w0 ) de longitud 0.

w 6= w0 ⇒ [(car(w) = conjunto de caracteres de w)]


∃ c ∈ car(w) . ∀ c0 ∈ car(w0 ) . c 6= c0 ⇒ [(long(w) = 0 ⇒ car(w) = ∅)]
¬∃ c ∈ car(w) . c 6∈ car(w0 ) ⇒ w = w0

Habitualmente, se designa a la palabra vacı́a por el sı́mbolo λ.


Ası́, el número de palabras de longitud 0 es 1 (n0 = 1). Para el resto de la demostración nos
apoyaremos en el siguiente lema:

Lema 2.1.1.2 sea Σ un alfabeto, y k ∈ nat, k > 0 en


El conjunto Σk de todas las palabras de longitud k puede formarse realizando la concatenación de
Σk−1 con cada uno de los sı́mbolos de Σ (Σk−1 × Σ).

Demostración (por inducción sobre k )


1) Base de inducción.
sea k=1 en Σ0 × Σ = Σ, ya que ∃!λ ∈ Σ, y ∀ α ∈ Σ . λα = α, por ser λ la cadena vacı́a2 .
Como no hay más sı́mbolos que los contenidos en Σ (por definición de alfabeto), todas las
palabras de Σ1 se pueden formar por concatenación de Σ0 × Σ.
sea k = 2 en
?
Σ2 = Σ1 Σ. Como Σ1 = Σ, eso querrı́a decir que Σ2 = Σ × Σ, lo que es cierto, ya
que Σ2 es el conjunto de palabras de longitud 2 formadas mediante sı́mbolos de Σ, esto
es, formadas mediante concatenación de 2 sı́mbolos del alfabeto, o, lo que es lo mismo,
formadas concatenando Σ con Σ.
2) Hipótesis de inducción. ∀ l ∈ IN ∧ 0 < l < k . (w ∈ Σl ⇒ (w = Rc) ∧ (R ∈ Σl−1 ∧ c ∈ Σ))
3) Paso de inducción. Demostración (por reducción al absurdo) sea w ∈ Σk en una palabra
de longitud k, tal que no pueda ser obtenida por concatenación de una palabra de longitud
k − 1 con un sı́mbolo del alfabeto.
w será de la forma w = w1 w2 · · · wk−1 wk ; como la operación de concatenación es asociativa,
podemos expresar w como w = W wk . Por hipótesis, w ∈ Σk ∧ ¬(W ∈ Σk−1 ∧ wk ∈ Σ).
Veamos los casos:
a 0 W 6∈ Σk−1 , pero, por hipótesis de inducción, todas las palabras de longitud k−1 se pueden
obtener por concatenación de palabras de longitud k − 2 con sı́mbolos del alfabeto, por
lo que W estarı́a en Σk−1
b 0 wk 6∈ Σ, lo que es imposible, ya que no hay más sı́mbolos que los del alfabeto.
Por tanto, W ∈ Σk−1 y wk ∈ Σ. Como no hemos hecho suposiciones adicionales sobre w, ésta
podrı́a ser cualquier palabra de Σk , por lo que todas las palabras de Σk pueden formarse por
concatenación de palabras de Σk−1 con algún carácter de Σ.
b) sea m = 1 en. Por el lema 2.1.1.2, Σ1 = Σ0 × Σ, por lo que kΣ1k=kΣ0k ∗ kΣk, o sea, kΣ1k= n,
y n1 = n. (Trivialmente, si hay n signos diferentes y cada palabra puede tener un solo signo,
habrá n palabras diferentes. Por tanto, el número de palabras de longitud 1 es n).
2 El sı́mbolo ∃! se lee como: existe un único..
20

c) Sea m = 2. El conjunto de palabras de longitud 2, por el lema 2.1.1.2 puede obtenerse como el
producto cartesiano de las palabras de longitud 1 por cada uno de los caracteres del alfabeto. Por
tanto, kΣ2k=kΣk × kΣk, esto es, kΣ2k= nn, o sea, kΣ2k= n2 .
Nótese que el caso m = 2 no es necesario demostrarlo, pudiendo estar la base de inducción formada
sólo por los casos m ≤ 1.
2. Hipótesis de inducción. ∀ l < m . kΣlk=kΣkl
3. Paso de inducción. Sea l = m − 1. Para obtener todas las palabras de longitud Σm a partir de Σm−1
no tenemos más que realizar el producto cartesiano Σm−1 × Σ (véase el lema 2.1.1.2). De esta forma,

kΣmk=kΣm−1kkΣk ⇒ h.i. y enunciado


kΣmk= nm−1 n ⇒ kΣmk= nm q.e.d .

Ejercicio 2.1.2 El cuadrado de n

Enunciado.3 Demostrar n2 = Σ α ∈ {1· · n} . 2α − 1.

Solución. Demostración (por inducción sobre los naturales)


Base de inducción.
P0
• sea n = 0. en 02 = α=1 2α − 1, ya que el sumatorio, aplicado a un rango vacı́o, se anula.
2
P1
• sea n = 1. en 1 = α=1 2α − 1, ya que el sumatorio se reduce a la suma de un solo elemento
(1).
Pk
Hipótesis de inducción. ∀ k < n . k 2 = α=1 2α − 1
Paso de inducción. sea k = n − 1 en.
p.asoc.suma
(n − 1)2 = n2 − 2n + 1 ⇒
hip. ind.
(n − 1)2 = n2 − (2n − 1) ⇒
n−1
X despejando n2
2α − 1 = n2 − (2n − 1) ⇒
α=1
n−1
X agrupando términos
n2 = ( 2α − 1) + (2n − 1) ⇒
α=1
n
X
n2 = 2α − 1 q.e.d .
α=1

3 Este enunciado aparece en [Balc93, problema III.3, pag(s) 94].


21

2.2. Verificación de algoritmos recursivos

Ejercicio 2.2.1 Precondiciones

Enunciado. Para los siguientes algoritmos, encontrar precondiciones que garanticen el funcionamiento
correcto de estos.

1. {Q ≡ ?}
fun mcd (a, b : nat) dev (m : nat) ≡
caso a = b → 0
dc a < b → a
dc a > b → md(a − b, b)
fcaso
ffun
{R ≡ m = a mod b}

2. {Q ≡ ?}
fun cuadrado (n : nat) dev (r : nat) ≡
caso n = 1 → 1
dc n > 1 → 2n − 1 + sq(n − 1)
fcaso
ffun
{R ≡ r = n2 }

Solución.
1. Para este tipo de ejercicios nos apoyaremos en los puntos que hay que demostrar para verificar la
corrección de un algoritmo, tal y como se presentan en [Peña93, Tabla 3.2, pag(s) 62].
En primer lugar, observamos que la alternativa es completa por sı́ misma, lo que excluye la necesidad
de la intervención de la precondición para garantizar tal propiedad.
El segundo punto por demostrar requiere que la precondición y la protección del caso recursivo garan-
ticen la precondición de la llamada interna. No podemos extraer mucha información de aquı́, pero, al
menos, vemos que la condición de dominio sobre la substracción se cumple, ya que a > b.
El tercer punto es la comprobación de la base de inducción. De nuevo, la protección hace que no
necesitemos la precondición para demostrar su corrección, ya que a = b ⇒ a mod b = 0 en un caso, y
a < b ⇒ a mod b = a en el otro.
El cuarto punto, la demostración del paso de inducción, nos sucede algo similar, ya que a > b ⇒
a mod b = (a − b) mod b.
El quinto punto trata de la definición de un preorden bien fundado. Vistas la llamada interna y la
protección del caso recursivo, tal función quedará definida como t : IN × IN −→ ZZ; t(a, b) = a − b, que
es positiva por la protección del caso recursivo citada.
Por fin, el sexto paso nos pide demostrar que los datos decrecen en el preorden definido. Y es de aquı́ de
donde podemos sacar la información que nos hacı́a falta para completar la precondición, ya que, para
que a − b decrezca, debe suceder que b > 0, que será la precondición que buscábamos.
22

2. Este caso es algo más sencillo, ya que la disyunción de las protecciones de la alternativa, para ser
completa, requiere que n ≥ 1. De hecho, esta condición no es necesaria para el cálculo del cuadrado de
un natural, sino que se ha impuesto para ilustrar el ejercicio. Véase que, si no se impone tal condición
(o sea, se hace que la precondición sea cierto), ha de variarse el caso trivial para que recoja como
entrada válida al cero, y se puede variar la alternativa, de manera que el cálculo del cuadrado de 1
quede englobado en el caso recursivo. Se deja la derivación y verificación de dicho algoritmo como
pequeño ejercicio.
Capı́tulo 3

Iteración

3.1. Verificación de algoritmos iterativos

Ejercicio 3.1.1 Verificación “a posteriori” de un bucle

Enunciado. Dado el siguiente programa iterativo, se pide verificar el paso del bucle.

{Q ≡ n ≥ 0}
fun suma (a : vect) dev (s : ent) ≡
var s, i : ent fvar
< s, i >:=< 0, 1 >
Pi−1
{P ≡ (s = j=1 a[j]) ∧ (1 ≤ i ≤ n + 1)}
mientras i ≤ n hacer
s := s + a[i]
i := i + 1
fmientras;
dev s
ffun Pn
{R ≡ s = i=1 a[i]}

Solución. La verificación del paso del bucle, expresada como {P ∧ B}S{P }, permite demostrar que el
invariante P se cumple a cada paso del bucle y por tanto, que el bucle queda perfectamente definido por
dicho invariante.
s+a[i],i+1
Para demostrar la expresión indicada, es preciso demostrar que el predicado Ps,i es deducible a partir
de P ∧ B, donde la sustitución anterior corresponde a la precondición de las dos instrucciones del cuerpo del
bucle. Pi−1 s+a[i],i+1
Por un lado tenemos que P ∧ B ≡ (s = j=1 a[j]) ∧ (1 ≤ i ≤ n + 1) ∧ (i ≤ n) y por otro que Ps,i ≡
Pi+1−1
(s + a[i] = j=1 a[j]) ∧ (1 ≤ i + 1 ≤ n + 1).
De esta última conjunción podemos afirmar que:

El segundo término es trivialmente cierto ya que por ser i ≤ n es i + 1 ≤ n + 1


Pi−1
El primero requiere compobar que es equivalente a (s + a[i] = j=1 a[j] + a[i]) y por ser P cierto es
fácil comprobar que los sumandos de ambos lados de la igualdad son idénticos.
s+a[i],i+1
Queda demostrada la invarianza del bucle al comprobar que Ps,i es cierto bajo hipótesis de P ∧ B.

23
24

3.2. Derivación de algoritmos iterativos

Ejercicio 3.2.1 Derivación formal de un bucle


Enunciado. Se tiene el siguiente fragmento de código iterativo:

{Q ≡ n ≥ 0}
fun suma (a : vect) dev (s : ent) ≡
var s, i : ent fvar
< s, i >:=< 0, 1 >;
Pi−1
{P ≡ (s = α=1 a[α]) ∧ (1 ≤ i ≤ n + 1)}
mientras i ≤ n hacer
restablecer;
avanzar
fmientras;
dev s
ffun Pn
{R ≡ s = i=1 a[i]}

Se quiere derivar formalmente las instrucciones necesarias para efectuar los cálculos indicados por el inva-
riante.

Solución. Una vez hallados B y P , se propone una sentencia que haga avanzar el bucle. En este caso se
propone i := i + 1. El bucle queda:

mientras i ≤ n hacer
instrucciones para completar restablecer
{T 0 }
restablecer
{T }
avanzar
fmientras;
dev s

El predicado P
T es la postcondición de la instrucción de restablecer y se deduce de P mediante la sustitución
i
Pii+1 ≡ (s = α=1 a[α]) ∧ (1 ≤ i + 1 ≤ n + 1)
? Pi−1
Se trata ahora de demostrar si P ∧ B ⇒ T , es decir, si se verifica que (s = α=1 a[α]) ∧ (1 ≤ i ≤ n + 1) ∧ (i ≤
? Pi
n) ⇒ (s = α=1 a[α]) ∧ (1 ≤ i + 1 ≤ n + 1). Como puede verse, la implicación es cierta para i, ya que
Pi−1 Pi
(1 ≤ i ≤ n + 1) ∧ (i ≤ n) ⇒ (1 ≤ i + 1 ≤ n + 1), pero no es cierto que (s = α=1 a[α]) ⇒ (s = α=1 a[α]).
Para que ocurra esta última implicación es preciso que el valor de s se incremente exactamente en a[i], ya
que al miembro izquierdo le falta este sumando.
s+a[i] Pi−1
Si proponemos por tanto la sustitución Ts tenemos que por cumplirse P , se verifica que s = α=1 a[α]
Pi
y por tanto que s + a[i] = α=1 a[α].
25

El algoritmo queda:
mientras i ≤ n hacer
s := s + a[i]
{T }
i=i+1
fmientras;
dev s

Como T 0 es deducible de P ∧ B, no es necesario incluir sentencias para completar restablecer, por lo que el
diseño está completo y verificado por propia construcción.
26
Parte II
Preguntas Tipo Test

La presente parte recoge ejercicios sobre las distintas materias y técnicas básicas que componen la Asignatura
planteados a modo de preguntas en las que hay que elegir una respuesta de entre cuatro opciones. En estos
casos, el alumno debe ser capaz tanto de elegir una buena respuesta como de descartar las que no lo sean. En
muchos de estos pequeños problemas se desarrolla la solución, mientras que en otros se plantea el enunciado
y se indica la respuesta correcta, dejando al alumno, como ejercicio adicional, el razonar sobre la misma.
Capı́tulo 4

Eficiencia de los Algoritmos

Los siguientes problemas tratan del cálculo de los recursos que un algoritmo utiliza para llevar a cabo su
tarea. Tienen que ver con la eficiencia temporal de los algoritmos, y tocan temas como la determinación del
tamaño de un problema, el coste asintótico temporal de diversos algoritmos, funciones que no terminan, o
el cálculo de recurrencias. Utilizaremos la expresión “coste” como abreviatura de “coste temporal asintótico
en el caso peor” a lo largo de las explicaciones subsiguientes.

Problemas resueltos

4.1. Factorial atı́pico


Enunciado. Dada la función
{Q ≡ cierto}
fun f f (x : nat) dev (z : nat) ≡
caso x < 2 → 1
dc x > 1 → f f (x − 1) + (x − 1) × f f (x − 1)
fcaso
ffun
{R ≡ z = x!}

Calcúlese su coste temporal asintótico temporal en el caso peor.



A. Θ( x)

B. Θ(x2 )

C. Θ(2x )

D. Θ(xx )

Solución. La función, que calcula el factorial de un número natural, se invoca un número indeterminado
de veces hasta llegar a un caso no recursivo; por tanto para calcular el coste de esta función plantearemos
y resolveremos una recurrencia (como se indica en [Peña93, 1.5 Resolución de recurrencias, pag(s) 15 a
18] ó [Peña97, 1.5 Resolución de recurrencias, pag(s) 16 a 20]). Para ello necesitamos identificar varios
parámetros: el tipo de reducción en el tamaño de los datos para cada nueva llamada recursiva, el número de
éstas que se realizan simultáneamente, y el coste de la parte no recursiva del algoritmo.
Vayamos por partes. La reducción del tamaño del problema se realiza por sustracción: a cada nueva llamada,
el valor de x con el que se realiza ésta decrece en una unidad, por lo que el parámetro b tendrá valor 1.
En el caso recursivo se realizan dos llamadas en la misma opción de la estructura alternativa, por lo que el
parámetro a de la fórmula valdrá 2.

28
29

Además, el resto de operaciones a realizar al margen de las llamadas recursivas, esto es, preparar los datos para
hacer la llamada (restando uno al valor en curso de x), combinar los resultados de las llamadas (multiplicando
el resultado de una por x y sumando dicha cantidad al resultado de la otra), y finalmente devolver el valor
adecuado (1 en el caso no recursivo y el calculado mediante las sucesivas invocaciones a la función en el caso
recursivo), puede considerarse de coste constante al tratarse de operaciones básicas; de tal forma, cnk , el
factor asociado a ese coste de la parte no recursiva del algoritmo, será constante, por lo que k deberá ser 0.
De lo dicho arriba, y siguiendo la notación de [Peña93, Resolución de recurrencias, pag(s) 15] ó [Peña97,
Resolución de recurrencias, pag(s) 17], tenemos asignados los valores: a = 2; b = 1 (reducción por sustracción)
y k = 0, por tanto, nuestra recurrencia queda:

c , si 0 ≤ n < 1
T (n) =
2T (n − 1) + c , si n ≥ 1
cuya resolución es T (n) ∈ Θ(2n ).

4.2. Función con cien vueltas de bucle


Enunciado. Calcúlese el coste de la siguiente función:

{Q ≡ cierto}
fun H (n : ent) dev (q : ent) ≡
var p : ent; fvar
p := 1;
mientras p < 100 hacer {P ≡ n = N × 2p−1 }
n := 2 ∗ n; p := p + 1
fmientras;
q := n div p;
dev q
ffun
{R ≡ q = N × 2100 div 100}

A. O(n)
B. O(1)
C. O(p)
D. es indiferente O(n) u O(p)

Solución. Examinando la entrada de la función concluı́mos que el tamaño del problema deberı́a ser n, ya
que no hay ningún otro parámetro que pueda variar para denotar el crecimiento del tamaño aludido para
diferentes invocaciones del algoritmo (para estimar su coste asintótico debemos poder hacer una estimación
de cómo afecta el crecimiento del tamaño del problema al consumo de recursos por parte del algoritmo).
¿Qué hace la función H con tal parámetro de entrada? En el bucle lo multiplica por dos a cada vuelta,
obteniendo ası́ n×2100 , ya que se realizan cien iteraciones. Tras salir del bucle, se calcula el valor de la salida, q,
que será el resultado de realizar la división entera de n por cien, esto es, el valor de q resultará n×2100 div 100.
Tal cálculo se realizará siempre consumiendo los mismos recursos, ya que cada vez, con cada entrada, se
realizarán las cien iteraciones antes citadas. Si n crece al doble de su tamaño para la siguiente aplicación del
problema, el resultado se multiplicará también por dos, pero se habrán realizado las mismas cien vueltas de
30

bucle, o lo que es lo mismo, el coste de la función no depende del tamaño del problema, siendo por tanto
constante, es decir, O(()1).

Problemas propuestos

4.3. Muchas Sumas

Enunciado. ¿Cuál será el coste asintótico temporal en el caso peor de la siguiente función, suponiendo
que el coste de la función SumaM as, declarada como fun SumaM as (a, b, d : nat) dev (r : nat) es lineal
respecto a su parámetro (d)?

{Q ≡ cierto}
acc M uchasSumas (a, b, d : nat) ≡
caso d ≤ 1 → 0
dc d > 1 → M uchasSumas(a − d, b + d, d div 2)+
+M uchasSumas(a + d, b + d, d div 2)+
+M uchasSumas(a + d, b − d, d div 2)+
+M uchasSumas(a − d, b − d, d div 2)+
+SumaM as(a, b, d)
fcaso
facc

A. T (d) ∈ Θ(d2 )

B. T (d) ∈ Θ(2d )

C. T (d) ∈ Θ(d4 )

D. T (d) ∈ Θ(d × log d)

4.4. Fibonacci

Enunciado. Elı́jase una función limitadora para la iteración del siguiente algoritmo:
31

{Q ≡ k > 0}
fun f ib (k : ent) dev (v : vector[1 · · N ] de ent) ≡
var f act, f pos, aux, i : ent; fvar
hf act, f pos, ii := h0, 1, 0i;
mientras i 6= k hacer {P ≡ Se han tratado los i primeros términos}
v[i + 1] := f act;
aux := f pos;
f pos := f pos + f act;
f act := aux;
i := i + 1
fmientras;
dev v
ffun
{R ≡ v contiene los k primeros términos de la sucesión de F ibonacci}

A. t(i, N, k) = i
B. t(i, N, k) = k − i
C. t(i, N, k) = N
D. t(i, N, k) = N − k + i

Soluciones a los problemas propuestos

4.3 : A

4.4 : B
Capı́tulo 5

Especificación de Algoritmos

El presente capı́tulo se ocupa de la especificación formal de algoritmos mediante su precondición y su post-


condición (especificación pre–post). Se tratan temas como la formalización de enunciados y la construcción
de pre y postcondiciones.

Problemas resueltos

5.1. Múltiplos de k
Enunciado. Especifı́quese un algoritmo que calcule cuántos elementos de un vector
v : vector[1 · · N ] de ent son múltiplos de un cierto natural k.

A. {Q ≡ k < N }
fun múltiplos (v : vector[1 · · N ] de nat; k : nat) dev (r : nat)
{R ≡ r = N α ∈ {1· · N } . v[k] mod α = 0}

B. {Q ≡ k > 0}
fun múltiplos (v : vector[1 · · N ] de nat; k : nat) dev (r : nat)
{R ≡ r = N α ∈ {1· · N } . v[α] mod k = 0}

C. {Q ≡ cierto}
fun múltiplos (v : vector[1 · · N ] de nat; k : nat) dev (r : nat)
{R ≡ r = N α ∈ {1· · N } . v[α] div k = 0}

D. Ninguna de las otras.

Solución. Construiremos la solución paso a paso, y veremos con cuál coincide.


Un natural n1 es múltiplo de otro n2 ←→ ∃ α ∈ IN . n1 = α × n2 . En dichas condiciones, n1 div n2 = α
y n1 mod n2 = 0, ya que no hace falta añadir ninguna cantidad al producto para obtener n1 . Por tanto,
.
n1 = n2 ←→ n1 mod n2 = 0.
Dado que deseamos contar cuántos de los elementos del vector cumplen tal propiedad respecto a k, utiliza-
remos el cuantificador conteo, por lo que nuestra postcondición será:

{R ≡ r = N α ∈ {1· · N } . v[α] mod k = 0}

En la precondición debe aparecer la expresión k > 0, para evitar errores de dominio; de hecho, esta es la
precondición más débil que hace que los datos sean válidos para la función.
Por tanto, la opción correcta es la B.

32
33

5.2. Solución de un sistema de ecuaciones lineales


Enunciado. Indı́quese cuál serı́a la especificación de un algoritmo que resolviese un sistema de ecuaciones
lineales, o sea, dadas una matriz cuadrada , a, de dimensión N × N y un vector, c, de dimensión n, calcular
el vector x (de dimensión n) que verifique A × x = c.
Ayuda: A es triangular superior y no singular (los elementos de la diagonal son no nulos y los que están por
debajo de ésta son todos cero); en estas condiciones, la fórmula original es expresable según: A × x = c, o lo
que es equivalente, Σ α ∈ {i· · N } . a[i, α] × x[α] = c[i].

A. {Q ≡ ∀ α ∈ {1· · N } . a[α, α] 6= 0 ∧ ∀ β ∈ {1· · α − 1} . a[α, β] = 0}


fun solución (a : vector [1 · · N, 1 · · N ] de real; c : vector[1 · · N ] de real)
dev(x : vector[1 · · N ] de real)
{R ≡ ∀ α ∈ {1· · N } . x[α] = (c[α] − Σ β ∈ {α + 1· · N } . a[α, β] × x[β])/a[α, α]}
B. {Q ≡ cierto}
funsolución(a : vector [1 · · N, 1 · · N ] de real; c : vector[1 · · N ] de real)
dev(x : vector[1 · · N ] de real)
{R ≡ ∃ α ∈ {1· · N } . x[α] = (c[α] − Σ β ∈ {α + 1· · N } . a[α, β] × x[β])/c[α]}
C. {Q ≡ ∀ α ∈ {1· · N } . a[α, α] 6= 0 ∧ ∀ β ∈ {1· · α − 1} . a[α, β] = 0}
funsolución(a : vector [1 · · N, 1 · · N ] de real; c : vector[1 · · N ] de real)
mathbf dev(x : vector[1 · · N ] de real)
{R ≡ ∀ α ∈ {1· · N } . x[α] = (c[α] − Σ β ∈ {1· · N } . a[α, β] × x[β])/a[α, β]}
D. {Q ≡ ∀ α ∈ {1· · N } . a[α, α] 6= 0}
funsolución(a : vector [1 · · N, 1 · · N ] de real; c : vector[1 · · N ] de real)
dev(x : vector[1 · · N ] de real)
{R ≡ ∀ α ∈ {1· · N } . x[α] = (Σ β ∈ {α· · N } . a[α, β] × c[β])}

Solución. La precondición debe decir que la matriz A es triangular superior, o sea,

{Q ≡ ∀ α ∈ {1· · N } . a[α, α] 6= 0 ∧ ∀ β ∈ {1· · α − 1} . a[α, β] = 0}


En cuanto a la postcondición, usando la ayuda que se nos sugiere:
para la ecuación i
A×x=c ≡ Σ α ∈ {i· · N } . a[i, α] × x[α] = c[i]
Desplegando el sumatorio obtenemos:
despejando x[i]
a[i, i] × x[i] + a[i, i + 1] × x[i + 1] + · · · + a[i, n] × x[n] = c[i] =⇒

x[i] = (c[i] − (a[i, i + 1] × x[i + 1] + · · · + a[i, n] × x[n]))/a[i, i]


Si en la anterior ecuación expresamos la suma de productos por un sumatorio queda:

x[i] = (c[i] − Σ α ∈ {i + 1· · N } . a[i, α] × x[α])/a[i, i]


Como la i utilizada en las anteriores expresiones, para denotar los términos asociados a una ecuación determi-
nada de las del sistema que hay que resolver, puede ser cualquiera, podemos reescribir la anterior fórmula de
manera que sea válida para cualquiera de las ecuaciones del sistema, sin más que cuantificarla universalmente
para el rango apropiado (el conjunto de ecuaciones que forman el sistema), resultando:
34

PN
c[α] − β=α a[α, β] × x[β]
∀ α ∈ {1· · N } . x[α] =
a[α, α]
Lo que, expresado en la notación habitual para tales casos, representa la postcondición que buscamos:
{R ≡ ∀ α ∈ {1· · N } . x[α] = (c[α] − (Σ β ∈ {α· · N } . a[α, β] × x[β])/a[α, α])}
La alternativa A es por lo tanto la solución correcta.

5.3. La Celebridad
Enunciado. De entre N+1 personas una celebridad es alguien a quien todos conocen pero que no conoce a
nadie. Sea C : vector [0 · · N ,0 · · N ] de bool una matriz en la que la el elemento C[i, j] indica si la persona
i conoce a la persona j, y supongamos que existe una celebridad entre estas personas. Indı́quese cuál serı́a
la especificación formal del algoritmo que determina qué elemento es dicha celebridad.

A. {Q ≡ ∃ (α ∈ {0· · N } ∧ (∀ (β ∈ {0· · N } ∧ (β 6= α)) . C[β, α] ∧ ¬C[α, β])) . }


fun celebridad (C : vector [0 · · N, 0 · · N ] de bool) dev (k : nat)
{R ≡ 0 ≤ k ≤ N ∧ ∀ (β ∈ {0· · N } ∧ (β 6= k)) . C[β, k] ∧ ¬C[k, β]}

B. {Q ≡ ∀ (α ∈ {0· · N } ∧ (∀ (β ∈ {0· · N } ∧ (β 6= α)) . C[β, α] ∧ ¬C[α, β])) . }


fun celebridad (C : vector [0 · · N, 0 · · N ] de bool) dev (k : nat)
{R ≡ 0 ≤ k ≤ N ∧ ∀ β ∈ {0· · N } . ∀ γ 6= k . C[β, γ] ∧ ¬C[γ, β]}

C. {Q ≡ ∃ (α ∈ {0· · N } ∧ (∃ (β ∈ {0· · N } ∧ (β 6= α)) . C[β, α] ∧ ¬C[α, β])) . }


fun celebridad (C : vector [0 · · N, 0 · · N ] de bool) dev (k : nat)
{R ≡ 0 ≤ k ≤ N ∧ ∃ (β ∈ {0· · N } ∧ (β 6= k)) . C[β, α] ∧ ¬C[α, β]}

D. {Q ≡ ∀ α ∈ {0· · N } . ∃ (β ∈ {0· · N } ∧ (β 6= α)) . C[β, α] ∧ ¬C[α, β]}


fun celebridad (C : vector [0 · · N, 0 · · N ] de bool) dev (k : nat)
{R ≡ 0 ≤ k ≤ N ∧ ∃ (β ∈ {0· · N } ∧ (β 6= k)) . C[β, k] ∧ ¬C[k, β]}

Solución. La precondición debe exigir que en la matriz de conocidos, C, haya una celebridad, esto es,
alguien conocido por todos pero que no conozca a nadie. Dado que los elementos de la matriz son booleanos,
esta condición se puede expresar directamente como un aserto en la siguiente forma:
{Q ≡ ∃ α ∈ {0· · N } . ∀ (β ∈ {0· · N } ∧ (β 6= α)) . C[β, α] ∧ ¬C[α, β]}
La postcondición, en esencia similar a la precondición, debe enunciar que la celebridad es el elemento k,
amén de fijar el rango en que dicha variable puede tomar valores; todo ello se expresa como sigue:
{R ≡ 0 ≤ k ≤ N ∧ ∀ (φ ∈ {0· · N } ∧ (φ 6= k)) . C[φ, k] ∧ ¬C[k, φ]}
Ambos asertos se recogen en la solución A propuesta.

Problemas propuestos

5.4. Módulo de un vector


Enunciado. Especifı́quese un algoritmo que calcule el módulo de un vector, (cuya declaración fuese
v : vector[1 · · N ] de ent), tomando cada uno de sus elementos como una componente.
35

A. {Q ≡ cierto}
fun móduloq(v : vector[1 · · N ] de ent) dev (m : real)
PN 2
{R ≡ m = α=1 v[α] }

B. {Q ≡ m ≥ 0 ∧ m < N }
fun módulo (v : vector[1 · · N ] de ent) dev (m : real)
PN p
{R ≡ m = α=1 v[α]2 }
C. {Q ≡ N ≥ 0}
fun módulo (v : vector[1 · · N ] de ent) dev (m : real)
√ PN
{R ≡ m = α=1 v[α]2 }
D. Ninguna de las otras

5.5. Cálculo de la media y la moda de un vector


Enunciado. Especifı́quese un algoritmo que calcule la media y la moda de los elementos, que son enteros,
de un vector (v : vector[1 · · N ] de nat).
A. {Q ≡ cierto}
fun media moda (v : vector[1 · · N ] de nat) dev (h media : real; moda : ent i)
{R ≡ media = Σ α ∈ {1· · N } . v[α]/N ∧ moda = max β ∈ IN . N γ ∈ {1· · N } . v[γ] = β}
B. {Q ≡ cierto}
fun media moda (v : vector[1 · · N ] de nat) dev (h media : real; moda : ent i)
{R ≡ media = Σ α ∈ {1· · N } . v[α]/N ∧ moda = max β ∈ {1· · N } . N γ ∈ {1· · N } . v[γ] = β}
C. {Q ≡ cierto}
fun media moda (v : vector[1 · · n] de nat) dev (h media : real; moda : ent i)
{R ≡ media = (Σ α ∈ {1· · n} . v[α]) / n ∧ moda = max β ∈ IN . N γ ∈ {1· · n} . v[γ] = β}
D. Ninguna de las otras

5.6. Posición del primer máximo de un vector


Enunciado. Especifı́quese un algoritmo que dado un vector, declarado como v : vector[1 · · N ] de ent,
calcule la posición del primer máximo de entre los elementos que contiene éste.
A. {Q ≡ cierto}
fun posicion−pmax (v : vector[1 · · N ] de ent) dev (p : nat)
{R ≡ 1 ≤ p ≤ N ∧ ∀ α ∈ {1· · N } . v[p] ≥ v[α] ∧ ∀ β ∈ {1· · p − 1} . v[β] < v[p]}
B. {Q ≡ v está ordenado crecientemente}
fun posicion−pmax (v : vector[1 · · N ] de ent) dev (p : nat)
{R ≡ 1 ≤ p ≤ N ∧ ∀ α ∈ {1· · N } . v[p] ≤ v[α] ∧ ∀ β ∈ {1· · p − 1} . v[β] < v[p]}
C. {Q ≡ N > 0}
fun posicion−pmax (v : vector[1 · · N ] de ent) dev (p : nat)
{R ≡ 1 ≤ p ≤ N ∧ ∀ α ∈ {1· · N } . v[p] ≥ v[α] ∧ ∀ β ∈ {1· · p − 1} . v[β] < v[p]}
D. Ninguna de las otras
36

5.7. Producto escalar de dos vectores


Enunciado. Especifı́quese un algoritmo que calcule el producto escalar de dos vectores (cuyas declaraciones
son v,w : vector[1 · · N ] de ent).

A. {Q ≡ cierto}
fun prodesc (v, w : vector[1 · · N ] de ent) dev (pesc : ent)
{R ≡ pesc = Σ α ∈ {1· · N } . v[α] × w[α]}
B. {Q ≡ cierto}
fun prodesc (v, w : vector[1 · · N ] de ent) dev (pesc : ent)
{R ≡ pesc = Σ α ∈ {1· · N − 1} . v[α] × w[α + 1]}
C. {Q ≡ cierto}
fun prodesc (v, w : vector[1 · · N ] de ent) dev (pesc : ent)
{R ≡ pesc = Σ α ∈ {1· · N } . v[α]2 × w[α]2 }
D. Ninguna de las otras.

5.8. Rotación
Enunciado. Especifı́quese una función que dado un vector (cuya declaración sea v : vector[0 · · N ] de ent,
con N ≥ 0), y un valor natural K, desplace v circularmente K posiciones a la derecha (los elementos que
“salen” por la derecha “entran” por la izquierda).

A. {Q ≡ 0 ≤ K ≤ N }
fun rotar (v : vector[0 · · N ] de ent; K : nat) dev (w : vector[0 · · N ] de ent)
{R ≡ ∀ α ∈ {0· · N } . w[(α + K) mod (N + 1)] = V [α]}
B. {Q ≡ 0 ≤ K ≤ N }
fun rotar (v : vector[0 · · N ] de ent; K : nat) dev (w : vector[0 · · N ] de ent)
{R ≡ ∀ α ∈ {0· · N } . w[(α + K) mod N ] = V [α]}
C. {Q ≡ 0 ≤ K ≤ N }
fun rotar (v : vector[0 · · N ] de ent; K : nat) dev (w : vector[0 · · N ] de ent)
{R ≡ ∀ α ∈ {0· · N + 1} . w[(α + K) mod N ] = V [α]}
D. Ninguna de las otras.

5.9. Descomposición de un cuadrado


Enunciado. Especifı́quese un algoritmo que calcule el número de formas diferentes en las cuales un natural
N puede ser escrito como la suma de dos cuadrados.

A. {Q ≡ cierto}
fun DosCuadrados (N : nat) dev (s : nat)
{R ≡ s = N {α, β ∈ {0· · N } ∧ α ≤ β} . α2 + β 2 = N }
B. {Q ≡ cierto}
fun DosCuadrados (N : nat) dev (s : nat)
{R ≡ s = N α, β ∈ {0· · N } . α2 + β 2 = N }
37

C. {Q ≡ cierto}
fun DosCuadrados (N : nat) dev (s : nat)
{R ≡ s = max {α, β ∈ {0· · N } ∧ α ≤ β} . α2 + β 2 = N }
D. Ninguna de las otras.

5.10. Al menos K elementos son cero


Enunciado. Especifı́quese una función que, dado un vector (cuya declaración sea v : vector[0 · · N ] de ent,
con N ≥ 0), calcule la longitud del menor segmento del vector que contenga, al menos, dos ceros.

A. {Q ≡ N ≥ 0 ∧ K ≥ 0}
fun SegmentoM asCorto (v : vector[0 · · N ] de ent; K : nat) dev (l : nat)
{R ≡ l = min {α, β ∈ {0· · N } ∧ (α ≤ β)
∧ ((N γ ∈ {α· · β − 1} . v[γ] = 0) < K − 1)} . β − α}
B. {Q ≡ N ≥ 0}
fun SegmentoM asCorto (v : vector[0 · · N ] de ent; K : nat) dev (l : nat)
{R ≡ l = min {α, β ∈ {0· · N } ∧ (α ≤ β)
∧ ((N γ ∈ {α· · β − 1} . v[γ] = 0) > K)} . β − α}
C. {Q ≡ N ≥ 1 ∧ K ≥ 1}
fun SegmentoM asCorto (v : vector[0 · · N ] de ent; K : nat) dev (l : nat)
{R ≡ l = min {α, β ∈ {0· · N } ∧ (α ≤ β)
∧ ((N γ ∈ {α· · β − 1} . v[γ] = 0) ≥ K)} . β − α}

D. Ninguna de las otras.

Soluciones a los problemas propuestos

5.4 : A

5.5 : C

5.6 : C

5.7 : A

5.8 : A

5.9 : A1

5.10 : C

1B cuenta dos veces las soluciones


Capı́tulo 6

Verificación
El presente capı́tulo se ocupa de la verificación, esto es, de analizar algoritmos basándonos en asertos que
definen su funcionamiento pretendido. En él, se abordan temas como obtener precondiciones que garanticen
la corrección de un algoritmo dado u obtener invariantes para bucles de algoritmos.

Problemas resueltos

6.1. Cálculo de la potencia n-ésima de 2


Enunciado. ¿Cuál es la precondición más débil que garantiza que se cumpla la postcondición del siguiente
algoritmo?

{Q ≡ ?}
fun tab (N : nat) dev (y : nat) ≡
hx, yi := h0, 1i;
{P ≡ x ≥ 0 ∧ y = 2x }
mientras x 6= N hacer
x := x + 1;
y := y + y
fmientras;
dev y
ffun
{R ≡ y = 2N }

A. {Q ≡ cierto}
B. {Q ≡ y > 0}
C. {Q ≡ ∃α ∈ IN ,2α = N }
D. {Q ≡ falso}

Solución. En este ejercicio vamos a utilizar una estrategia de solución diferente a la habitualmente plan-
teada. Hasta ahora, resolvı́amos el ejercicio, y luego comparábamos el resultado con las opciones dadas como
posibles respuestas. En ciertos casos, como aquı́, podemos descartar ciertas opciones que son absurdas o
incorrectas de partida, esto es, que no pueden representar una solución posible para el ejercicio en cuestión.
Para el resto de opciones, se comprobarı́a entonces su validez (y, en este caso, se elige la mejor opción).
Veamos las posibilidades:
La opción D se puede anular inmediatamente, ya que una precondición falso no es satisfactible por ningún
estado (define al conjunto vacı́o de estados), por lo que no habrı́a forma de invocar a la función en un estado
que satisficiera su precondición.

38
39

La opción B exige una condición sobre un parámetro de salida, lo que viola las reglas de construcción de
precondiciones(los parámetros de salida de la función no están definidos antes de aplicar ésta), por lo que
también puede eliminarse.
La opción C exige que N sea potencia de 2; dado que la postcondición expresa que la función calcula la
potencia N -ésima de 2, siendo N un parámetro de entrada, esta precondición es correcta (se comprueba
trivialmente: el valor que el cuantificador existencial postula que debe existir, es N ).
La opción A no impone restricciones sobre la entrada. Comprobemos qué hace la función para ver si tal
precondición es suficiente para su funcionamiento.
Para ello, debemos determinar qué hace el bucle. Éste, a cada vuelta, suma y consigo mismo, dejando el
valor de nuevo en y, o lo que es equivalente, a cada vuelta multiplica el valor de y por 2. Dado que en la
inicialización y recibe el valor 1, su contenido en una vuelta determinada del bucle será 2 número de vuelta .
La otra variable que interviene en la función, x, se inicializa a 0 y aumenta en 1 su valor a cada vuelta,
o lo que es lo mismo, x contiene en todo instante el número de vueltas que ha efectuado el bucle. Ası́, el
funcionamiento de la iteración puede resumirse en la expresión {y = 2x }, que se cumple para cada vuelta
del bucle, incluı́das la primera (cuando x vale 0) y la última (cuando x vale N ). Dicha expresión recibe el
nombre de invriante (por razones obvias).
Nótese que cuando acaba el bucle x vale N , y y vale 2N , lo que coincide con la postcondición, por lo cual,
no necesitamos exigir nada a los datos de entrada (N ) y nos vale como precondición la opción A, o sea,
cierto(la opción C, como se comentó anteriormente, es correcta, pero es más fuerte (más restrictiva) que la
A).

6.2. Búsqueda dicotómica


Enunciado. Indı́quese el invariante para la iteración del siguiente algoritmo:

{Q ≡ N ≥ 0 ∧ A está ordenado crecientemente y x está en el vector}


fun BBC (A : vector[1 · · N ] de ent; x : ent) dev (k : nat) ≡
var i, d, m : nat fvar
hi, di := h0, N + 1i;
{P ≡?}
mientras i + 1 6= d hacer
m := (i + d) div 2;
caso A[m] ≥ x → i := m
dc A[m] < x → d := m
fcaso
fmientras;
k := i;
dev k
ffun
{R ≡ 0 ≤ k ≤ N ∧ A[k] ≤ x < A[k + 1]}

A. {P ≡ ∀ α ∈ {1· · k} . A[α] ≤ x ∧ ∀ α ∈ {k· · N } . x ≤ A[α]}


B. {P ≡ 0 ≤ i < d ≤ N + 1 ∧ A[i] ≤ x < A[d]}
C. {P ≡ 0 < i ≤ N + 1 ∧ 0 ≤ d ≤ N ∧ i + 1 ≤ d ∧ ∀ α ∈ {1· · i} . A[α] ≤ x ∧ ∀ α ∈ {d· · N } . x < A[α]}
D. {P ≡ 0 ≤ i ≤ N + 1 ∧ 0 ≤ d ≤ N ∧ i + 1 ≥ d ∧ ∀ α ∈ {1· · i} . A[α] ≤ x ∧ ∀ α ∈ {d· · N } . x < A[α]}
40

Solución. El invariante que buscamos puede obtenerse a partir de la postcondición, sin más que debilitarla
cambiando las constantes k y k + 1 por sendas variables, i y d, que definirán el rango en el que se va a
realizar la búsqueda (marcando sus extremos izquierdo y derecho respectivamente). Evidentemente también
debemos acotar el rango en que es permisible que se muevan las variables i y d. Este rango irá decreciendo
paulatinamente hasta que, al finalizar el bucle, se cumpla i+1 = d (al negarse la protección del bucle). En este
momento la conjunción del invariante y la negación de la protección del bucle equivalen a la postcondición:

{0 ≤ i < d ≤ N + 1 ∧ A[i] ≤ x < A[d]} ∧ {i + 1 = d}

≡ {0 ≤ k ≤ N ∧ A[k] ≤ x < A[k + 1]}

De lo dicho se deduce fácilmente que el invariante ha de ser el propuesto como alternativa B.


Queda como ejercicio para el lector el demostrar que tal expresión es invariante para el bucle de la función.

Problemas propuestos

6.3. Cálculo del máximo común divisor de dos enteros


Enunciado. ¿Cuál de las siguientes es la precondición más débil (de entre las ofrecidas como posibles
soluciones) que garantiza que se cumpla la postcondición del siguiente algoritmo?

{Q ≡ ?}
fun mxd (a, b : ent) dev (m : ent) ≡
caso a = b → a
dc a > b → mxd(a − b, b)
dc a < b → mxd(a, b − a)
fcaso
ffun
{R ≡ m = mcd(a, b)}

(donde mcd significa ”máximo común divisor”)

A. {a < b}

B. {m = mcd(a, b)}

C. {a > 0 ∧ b > 0}

D. {a > 0 ∧ b ≥ 0}

6.4. Búsqueda transversal: precondición


Enunciado. ¿Cuál de las siguientes es la precondición más débil (de entre ellas) que garantiza que se
cumpla la postcondición del siguiente algoritmo?
41

{Q ≡ ?}
fun bac (x : ent; v : vector [0 · · M, 0 · · N ] de ent) dev (a, b : ent) ≡
ha, bi := h0, N i;
{P ≡ 0 ≤ a ≤ I ≤ M ∧ 0 ≤ J ≤ b ≤ N ∧ V [I, J] = x}
mientras v[a, b] 6= x hacer
caso v[a, b] < x → a := a + 1
dc v[a, b] > x → b := b − 1
fcaso
fmientras;
dev ha, bi
ffun
{R ≡ 0 ≤ a ≤ M ∧ 0 ≤ b ≤ N ∧ v[a, b] = x}

A. {Q ≡ cierto}

B. {Q ≡ ∀ α ∈ {0· · M − 1} . ∀ γ ∈ {0· · N } . v[α, γ] < v[α + 1, γ]


∧ ∀ α ∈ {0· · M − 1} . ∀ γ ∈ {0· · N − 1} . v[α, γ] < v[α, γ + 1]
∧ ∃ α ∈ {0· · M } . ∃ β ∈ {0· · N } . v[α, β] = x}

C. {Q ≡ ∃ α ∈ {0· · M } . ∃ β ∈ {0· · N } . v[α, β] = x}

D. {Q ≡ ∀ α ∈ {0· · M − 1} . ∀ γ ∈ {0· · N } . v[α, γ] ≤ v[α + 1, γ]


∧ ∀ α ∈ {0· · M − 1} . ∀ γ ∈ {0· · N − 1} . v[α, γ] ≤ v[α, γ + 1]
∧ ∃ α ∈ {0· · M } . ∃ β ∈ {0· · N } . v[α, β] = x}

6.5. Aproximación al logaritmo en base 2 de un natural


Enunciado. Indı́quese el invariante para la iteración presente en el siguiente algoritmo:

{Q ≡ x = X ∧ X ≥ 1}
fun tp (x : nat) dev (k : nat) ≡
k := 0;
{P ≡?}
mientras x 6= 1 hacer
x := x div 2;
k := k + 1
fmientras
dev k
ffun
{R ≡ 2k ≤ X < 2k+1 }

A. {P ≡ 2k × x ≤ X ≤ 2k+1 × x}

B. {P ≡ k ≥ 0 ∧ 2k × x ≤ X < 2k × (x + 1) ∧ x ≥ 1}

C. {P ≡ k ≥ 0 ∧ 2k × x ≥ X ∧ x ≥ 1}

D. Ninguna de las otras


42

6.6. Posición de un máximo de un vector: invariante


Enunciado. Indı́quese el invariante para la iteración del siguiente algoritmo:

{Q ≡ 1 ≤ N }
fun C (a : vector[1 · · N ] de ent) dev (i : nat) ≡
var j : nat; fvar
hi, ji := h1, N i;
mientras i 6= j hacer {P ≡ ?}
caso a[i] ≤ a[j] → i := i + 1
dc a[i] > a[j] → j := j − 1
fcaso
fmientras;
dev i
ffun
{R ≡ 1 ≤ i ≤ N ∧ ∀ β ∈ {1· · N } . a[β] ≤ a[i]}

A. Ninguna de las otras

B. {P ≡ i > 0 ∧ ∃ α ∈ {i· · j} . ∃ β ∈ {1· · N } . a[β] ≤ a[α]}

C. {P ≡ i > 1 ∧ j > N ∧ ∃ α ∈ {i· · j} . ∀ β ∈ {1· · N − j + i} . a[β] ≤ a[α]}

D. {P ≡ i > 0 ∧ j ≤ N ∧ ∃ α ∈ {i· · j} . ∀ β ∈ {1· · N } . a[β] ≤ a[α]}

6.7. Solución de un sistema de ecuaciones lineales: invariantes


Enunciado. Indı́quese cuales serı́an los invariantes de lo bucles interno y externo del siguiente algoritmo
que resuelve un sistema de ecuaciones lineales, o sea, dadas una matriz cuadrada , a, de dimensión N × N y
un vector, c, de dimensión n, calcula el vector x (de dimensión n) que verifique A×x = c, siendo A triangular
superior y no singular (diagonal no nula, resto de elementos bajo ésta nulos).

{Q ≡ ∀ α ∈ {1· · N } . a[α, α] 6= 0 ∧ ∀ β ∈ {1· · α − 1} . a[α, β] = 0}


fun solucion (a : vector [1 · · N, 1 · · N ] de real;
c : vector[1 · · N ] de real) dev (x : vector[1 · · N ] de real) ≡
var k, m : ent; s : realfvar;
k := N + 1;
mientras k 6= 1 hacer {Pext ≡ ?}
m := k − 1; s := 0;
mientras m 6= N hacer {Pint ≡ ?}
s := s + a[k + 1, m + 1] × x[m + 1]; m := m + 1
fmientras;
x[k − 1] := (c[k − 1] − s)/a[k − 1, k − 1]; k := k − 1
fmientras;
dev x
ffun
{R ≡ ∀ α ∈ {1· · N } . x[α] = (c[α] − Σ β ∈ {α + 1· · N } . a[α, β] × x[β])/a[α, α]}
43

 {Pint ≡ k − 1 ≤ m ≤ N ∧ s = Σ α ∈ {k· · m} . a[k − 1, α]x[α]}
A. {Pext ≡ k − 1 ≤ m ≤ N ∧ s = Σ α ∈ {k· · m} . a[k − 1, α]x[α]1 ≤ k ≤ N + 1 ∧
∀ α ∈ {k· · m} . x[α] = (c[α] − (Σ β ∈ {α + 1· · N } . a[α, β]x[β])/a[α, α])}


 {Pint ≡ k − 1 ≤ m ≤ N ∧ s = Σ α ∈ {k − 1· · m} . a[k, α]x[k]}
B. {Pext ≡ k − 1 ≤ m ≤ N ∧ s = Σ α ∈ {k − 1· · m} . a[k, α]x[k] ∧ 1 ≤ k ≤ N + 1 ∧
∀ α ∈ {k· · m} . x[α] = (c[α] − (Π β ∈ {α + 1· · N } . a[β, α]x[β])/a[α, α])}


 {Pint ≡ k − 1 < m ≤ N ∧ s = Σ α ∈ {k + 1· · m} . a[k − 1, α − 1]x[α]}
C. {Pext ≡ k − 1 < m ≤ N ∧ s = Σ α ∈ {k + 1· · m} . a[k − 1, α − 1]x[α]1 ≤ k ≤ N + 1∧
∀ α ∈ {k· · m} . x[α] = (c[α] − (Σ β ∈ {α· · N } . a[α, β]x[α])/a[α, β])}


 {Pint ≡ k < m ≤ N ∧ s = Σ α ∈ {k· · m} . a[α, k]x[k]}
D. {Pext ≡ k < m ≤ N ∧ s = Σ α ∈ {k· · m} . a[α, k]x[k] ∧ 1 ≤ k ≤ N + 1∧
∀ α ∈ {k· · m} . x[α] = (c[α] − (Π β ∈ {α· · N } . a[β, α] + x[β])/a[β, β])}

Soluciones a los problemas propuestos

6.3 : C

6.4 : D

6.5 : B

6.6 : D

6.7 : A
Capı́tulo 7

Derivación
El presente capı́tulo trata de la derivación de algoritmos, esto es, de la obtención de algoritmos de los que,
por su forma de construcción, se pueda asegurar que cumplen sus especificaciones. Se ocupa de temas tales
como la obtención de expresiones que satisfacen especificaciones, la confección de inicializaciones adecuadas
para algoritmos iterativos o el diseño de casos bien directos bien no triviales para algoritmos recursivos.
Recuérdese que se pretende que ésta sea una actividad guiada por la verificación.

Problemas resueltos

7.1. Expresión que garantiza una especificación pre-post


Enunciado. ¿Qué expresión E hace que se cumpla:

{Q ≡ x × y + p × q = N }
x := x − p; q := E
{R ≡ x × y + p × q = N }?

A. E = y + q
B. E = x + p
C. E = q × y
D. Ninguna de las otras

Solución. La asignación es una operación que produce una transformación en el estado de la ejecución
del programa, que se representa por el valor de las variables que intervienen en éste, de tal manera que si
x
P es una propiedad que se cumple para valores de x, y Ee(y) es una expresión en la que se han substituido
todas las apariciones como variable libre de x por una expresión de y, e(y), entonces se cumple P (e(y)). De
esta forma, la asignación funciona como una substitución textual de la variable sobre la que se asigna por
la expresión que se le asigna a dicha variable. A su vez, la composición secuencial es un transformador de
estados que aplica sucesivamente cada uno de los transformadores (instrucciones) que lo componen.
A su vez, una expresión booleana que dependa de las variables que intervien en el algoritmo, define al estado
en el que éste se encuentra. Por tanto, si aplicamos cualquier operador de transformación de estados a un
aserto que define el actual, lograremos un aserto que describe el estado al que llegamos al realizar la operación
dada por el operador de transformación referido.
Para obtener la solución, debemos razonar, utilizando la definición axiomática de los transformadores de
estados que se utilicen en el código, sobre las condiciones necesarias para alcanzar la postcondición a partir
de la precondición. Este razonamiento se realiza “hacia atrás”, es decir, partiendo de la postcondición,
y averiguando en que estado se debe encontrar la ejecución antes de cada transformación para llegar al
pretendido.

44
45

Dado que la composición secuencial de ambas asignaciones funciona como la aplicación sucesiva de ambas,
necesitamos saber cuál es el estado en que debe encontrarse una ejecución para llegar a uno dado, cuando
se realiza una asignación.
Sea {A} el estado anterior a la realización de la asignación, por ejemplo g := H, y sea {B} el estado
posterior a conseguir. Entonces, debe pasar que {A} ⇒ {B}H h . Previamente, como era de esperar, deben
garantizarse las condiciones de dominio, o sea, que la asignación sea factible (H bien definida y del mismo
tipo que g) Por tanto, para obtener la solución al problema nos basta con ir aplicándole al estado inicial las
transformaciones implicadas por las asignaciones que constituyen el segmento de código a estudiar, y en su
momento, determinar qué expresión debe substituir a E para llegar al aserto indicado como estado final.
En el desarrollo posterior, iremos obteniendo en cada paso el aserto que debe implicarse para obtener al final
un valor de E consistente con los asertos inicial y final.

q:=E
{xy + pq = N } ≡

Aplicando la regla de la asignación

x:=x−p
{xy + pE = N } ≡

Por la regla de la asignación

{(x − p)y + pE = N } ≡

Propiedad distributiva del producto respecto a la resta

{xy − py + pE) = N } ≡

sacando factor común p y ordenando los términos

{xy − p(E − y) = N }

Queremos lograr un aserto tal que sea implicado por la precondición. El más débil de ellos será la propia
precondición, {Q ≡ xy + pq = N }.
Como tenemos {xy − p(E − y) = N } entonces:

{xy + pq = xy + p(E − y)} =⇒

restando xy en cada parte de la igualdad

si p>0
{pq = p(E − y)} =⇒

{q = E − y} =⇒

sumando y en cada parte de la igualdad y ordenando los términos

{E = q + y}

que corresponde a la solución A propuesta.


46

7.2. Comprobación del orden de un vector


Enunciado. Indı́quese cuál de los siguientes casos recursivos es consistente con la especificación dada,
ası́ como con el caso directo que aparece en el código del enunciado, para completar el siguiente algoritmo
recursivo:
{Q ≡ k ≤ N ∧ N ≥ 0}
fun test (v : vector[1 · · N ] de ent; k : nat) dev (b : bool) ≡
caso k ≤ 1 → cierto
dc Bnt → c(test(s(x)), x)
fcaso
ffun
{R ≡ b = ∀ α ∈ {1· · k − 1} . a[α] ≤ a[α + 1]}


A. Bnt 1 ≡ k > 1 ; triv1 ≡ test(a, k) ∧ a[k − 1] ≤ a[k]

B. Bnt 1 ≡ k > 1 ; triv1 ≡ test(a, k − 1) ∧ a[k − 1] ≤ a[k]

C. Bnt 1 ≡ k > 1 ; triv1 ≡ test(a, k − 1) ∧ a[k − 1] = a[k]

D. Ninguna de las otras respuestas es correcta.

Solución. En [Peña93, Diseño recursivo, pag(s) 62] o en [Peña97, Diseño recursivo, pag(s) 71] aparece una
tabla que resume los puntos que han de seguirse para verificar la corrección de un algoritmo. Los seguiremos
para completar el caso recursivo que nos falta.
El punto 1 requiere que la precondición garantice que todos los casos posibles estan cubiertos por la alter-
nativa. Como la protección del caso trivial es k ≤ 1 y la declaración de la función nos indica que k ∈ IN , la
protección del caso recursivo deberá ser k > 1.
Conseguida la protección, pasemos a ver cómo tiene que ser el resultado que se devuelva mediante el caso
recursivo.
El punto 2 de la tabla citada dice que la precondición de la llamada recursiva interna debe ser implicada por
la conjunción de la función junto con la protección del caso recursivo.
El punto 6 requiere que los datos que se pasen a la llamada interna sean menores que los que se le pasaron
a la llamada que la realiza, y por el punto 5, este decrecimiento (que implica el del tamaño del problema)
debe producirse en un preorden bien fundado.
El punto 4 nos indica que el caso recursivo debe apoyarse en la llamada interna para resolver el problema
y que, una vez resuelta ésta, combinando el resultado que devuelva debe completar el cálculo por realizar.
Eso quiere decir que utilizaremos la postcondición de la llamada interna como hipótesis de inducción en la
demostración de la corrección del algoritmo, y que el caso recursivo debe dar el paso de inducción sirviéndose
de ésta, y con esto conseguir la postcondición de la función.
Sentadas las bases para el razonamiento, pasemos a aplicarlas a nuetro caso.
Si la postcondición tiene que ser conseguida a partir de la de la llamada interna (hipótesis de inducción),
deberemos poder descomponer la postcondición global en dos partes, una conseguible mediante una llamada
más, y otra que sea abordable mediante la combinación de dicho resultado con los datos habidos para producir
la llamada.
En el caso del problema que nos atañe, la postcondición es:

{R ≡ b = ∀ α ∈ {1· · k − 1} . a[α] ≤ a[alpha + 1]}


47

que, separando el último elemento del cuantificador y combinándolo con éste mediante la conectiva lógica
que define a la operación cuantificada, equivale a

{b = (∀ α ∈ {1· · (k − 1) − 1} . a[α] ≤ a[alpha + 1]) ∧ (a[k − 1] ≤ a[k])}


expresión cuyo primer conjuntante traduciremos a forma de llamada recursiva, quedando

{test(a, k − 1) ∧ (a[k − 1] ≤ a[k])}


Comprobemos su corrección:

[punto 2] {0 ≤ k ≤ N ∧ (k > 1) ⇒ 0 ≤ k − 1 ≤ N }
[punto 5] Tomaremos como tamaño del problema k; el pbf lo definimos como t(k) < t(k 0 ) ←→ k < k 0 , que
se corresponde con (IN, <).
[punto 6] t decrece con cada llamada, ya que t(k) > t(k − 1)
[punto 4] como la llamada interna es test(a, k − 1), tomaremos como hipótesis de inducción
{b = (∀ α ∈ {1· · (k − 1) − 1} . a[α] ≤ a[alpha + 1])}, por lo tanto:

{b = test(a, k − 1) ∧ a[k − 1] ≤ a[k]} ⇒


{b = (∀ α ∈ {1· · k − 2} . a[α] ≤ a[α + 1) ∧ (a[k − 1] ≤ a[k])} ⇒
{b = ∀ α ∈ {1· · k − 1} . a[α] ≤ a[alpha + 1]}

expresión que coincide con la postcondición.

De todo lo explicado se deduce que la solución correcta es la B.

Problemas propuestos

7.3. Expresión binaria de un número en base 10


Indı́quense los casos triviales necesarios para completar el siguiente algoritmo recursivo:

{Q ≡ cierto}
fun dec2bin (n : nat) dev (m : nat) ≡
caso Bt1 → triv1
..
.
dc Btm → trivm
dc n > 0 → dec2bin(n div 2) × 10 + n mod 2
fcaso
ffun
{R ≡ m = Σα ≥ 0.(n div 2α ) mod 2 × 10α }
48

A. Bt 1 ≡ n = 0 ; triv1 ≡ 0

Bt 1 ≡ n = 0 ; triv1 ≡ 1
B.
Bt 2 ≡ n < 0 ; triv2 ≡ 0

Bt 1 ≡ n = 0 ; triv1 ≡ n
C.
Bt 2 ≡ n < 0 ; triv2 ≡ 0
D. Ninguno de los otros

7.4. Triple producto


Enunciado. Indı́quense los casos triviales necesarios para completar el siguiente algoritmo recursivo:

{Q ≡ n > 0}
fun triplep (n, k : nat) dev (b : bool) ≡
caso n < k(k + 1)(k + 2) → falso
dc n = k(k + 1)(k + 2) → cierto
dc Bnt → c(test(s(x)), x)
fcaso
ffun
{R ≡ b = (n puede expresarse como producto de 3 naturales consecutivos)}

A. Ninguno de los otros



B. Bt 1 ≡ n > k(k + 1)(k + 2) ; triv1 ≡ falso

C. n > k × (k + 1) × (k + 2)triplep(n, k − 1)

D. Bnt 1 ≡ n > k × (k + 1) × (k + 2) ; triv1 ≡ triplep(n, k + 1)

7.5. Conteo de elementos coincidentes


Enunciado. Indı́quese la inicialización que garantiza la corrección del siguiente algoritmo.

{Q ≡ ∀ α ∈ {0· · N − 1} . v[α] < v[α + 1] ∧ ∀ α ∈ {0· · M − 1} . w[α] < w[α + 1]}


fun coincidencias (v : vector[0 · · N ] de ent;
w : vector[0 · · M ] de ent) dev (c : nat) ≡
var i, j : nat fvar;
Inicialización;
{P ≡ 0 ≤ i ≤ N ∧ 0 ≤ j ≤ M ∧ c = N α ∈ {0· · i}, β ∈ {0· · j} . v[α] = w[β]}
mientras i 6= N ∧ j 6= M hacer
caso w[j] > v[i] → i := i + 1
dc w[j] = v[i] → hc, i, ji := hc + 1, i + 1, j + 1i
dc w[j] < v[i] → j := j + 1
fcaso
fmientras
dev c
ffun
{R ≡ c = N α ∈ {0· · M − 1}, β ∈ {0· · N − 1} . v[α] = w[β]}
49

A. {Inicialización ≡ hc, i, ji := h0, 0, 0i}


B. {Inicialización ≡ hc, i, ji := h1, 0, M i}
C. {Inicialización ≡ hc, i, ji := h0, N, M i}
D. {Inicialización ≡ hc, i, ji := h1, 1, 1i}

7.6. Búsqueda dicotómica


Enunciado. Indı́quese la inicialización que hace que el siguiente programa iterativo sea correcto:

{Q ≡ N ≥ 0 ∧ A está ordenado crecientemente y x está en el vector}


fun BBC (A : vector[1 · · N ] de ent; x : ent) dev (k : nat) ≡
var i, d, m : nat fvar
hi, di := h0, N + 1i;
{P ≡ 0 ≤ i < d ≤ N + 1 ∧ A[i] ≤ x < A[d]}
mientras i + 1 6= d hacer
m := (i + d) div 2;
caso A[m] ≥ x → i := m
dc A[m] < x → d := m
fcaso
fmientras;
k := i;
dev k
ffun
{R ≡ 0 ≤ k ≤ N ∧ A[k] ≤ x < A[k + 1]}

A. {Inicialización ≡ hj, ii := h0, N + 1i}


B. {Inicialización ≡ hj, ii := hN + 1, 0i}
C. {Inicialización ≡ hh, j, ii := h0, 0, N i}
D. {Inicialización ≡ hh, j, ii := hN, 0, 0i}

Soluciones a los problemas propuestos

7.3 : A

7.4 : D

7.5 : A

7.6 : B
50
Parte III
Algoritmos Desarrollados

En esta parte se recogen desarrollos completos de algoritmos, es decir, desde la especificación hasta la
verificación de su corrección, pasando por su diseño. Por lo general, su extensión y el tipo de explicaciones
detalladas que los acompañan se orientan a la didáctica de las técnicas involucradas, pero, a la vez, dan
una idea y una orientación claras sobre cómo abordar la(s) pregunta(s) de desarrollo (problema) que pueden
aparecer en los exámenes de la Asignatura.
Capı́tulo 8

Derivación de algoritmos recursivos


En este capı́tulo se trata de abordar el diseño de algoritmos recursivos y su verificación formal. Los algoritmos
desarrollados se utilizarán en el capı́tulo 9 para ilustrar las técnicas de transformación de algoritmos recursivos
en sus equivalentes iterativos.

8.1. El Esferulado

Enunciado: Diséñese una función completamente verificada tal que, dado


a : vector[1 · · N ] de ent, devuelva un booleano que indique si la suma de los ele-
mentos del vector es cero y las sumas parciales hasta cada uno de los elementos son
positivas.

8.1.1. Especificación
Del enunciado1 se deriva la siguiente especificación del algoritmo:
{Q ≡ cierto}
fun desf i (a : vector[1 · · N ] de ent) dev (b : bool)
PN Pβ
{R ≡ b = α=1 a[α] = 0 ∧ ∀ β ∈ {1· · N } . ( α=1 a[α]) ≥ 0}

No se puede abordar el diseño directo sin una inmersión ya que habrı́a que modificar el tamaño del vector
en cada llamada. Nos planteamos por tanto una inmersión de especificaciones.

8.1.2. Diseño: Inmersión


El diseño se realiza teniendo en cuenta que precisamos de tres parámetros para realizar la inmersión: Un
contador i para recorrer el vector, una variable booleana bb y un contador c. Además, si posteriormente
precisásemos un paso a iterativo, lo mejor serı́a intentar el diseño de una función manteniendo la postcon-
dición constante. Para ello efectuamos una inmersión de especificaciones mediante un reforzamiento de la
precondición.
La razón de reforzar la precondición y utilizar ésta última versión reforzada como precondición es que si el
algoritmo parte de una situación cercana a la postcondición, ésta será más fácil de alcanzar y el algoritmo
será por tanto más sencillo de programar. En nuestro caso, la clausula que más nos acerca a la postcondición
serı́a una variante debilitada de ésta última, es decir, creamos una precondición con el resultado de debilitar
la postcondición.
Un primer paso vendrı́a dado por introducir una variable i con valor inicial de N . De esta forma obtenemos
la siguiente expresión:
1 Este enunciado aparece en el libro [Balc93, capı́tulo VII, problema 11, pag(s) 266]

52
53

i
X β
X
Rdebilitada ≡ b=( a[α] = 0) ∧ (∀ β ∈ {1· · i} . ( a[α]) ≥ 0) ∧ (i = N )
α=1 α=1

Esta expresión es aún demasiado fuerte ya que impone que las sumas parciales sean cero. Volvemos a debilitar
introduciendo una nueva variable c y obtenemos:

i
X β
X
Rdebilitada ≡ b=( a[α] = c) ∧ (∀ β ∈ {1· · i} . ( a[α]) ≥ 0) ∧ (i = N ) ∧ (c = 0)
α=1 α=1

Con esta expresión como precondición estamos realmente cerca de la postcondición final. Eliminamos las
clausulas de ligadura de las variables i y c e imponemos una condición de dominio para i, de esta forma
obtenemos:

i
X β
X
Rdebilitada ≡ b=( a[α] = c) ∧ (∀ β ∈ {1· · i} . ( a[α]) ≥ 0) ∧ (i ≤ N )
α=1 α=1

La variable b no aparece entre los argumentos de la función y sı́ en los de salida, por lo que para introducirla
en la precondición debemos cambiarle el nombre (P.Ej. bb) para no confundirla con la variable b de salida.
En la precondición bb representa una meta parcial. En la verificación demostraremos que cuando i = N
alcanzaremos la postcondición y entonces b será bb más algunas consideraciones.
La especificación de la inmersión incluye como argumentos las nuevas variables libre de la precondición y
queda por tanto:
{Q ≡ bb = Σ α ∈ {1· · i} . a[α] = c ∧ ∀ β ∈ {1· · i} . (Σ α ∈ {1· · β} . a[α]) ≥ 0 ∧ (i ≤ N )}
fun idesf i (a : vector[1 · · N ] de ent; i, c : nat; bb : bool) dev (b : bool)
{R ≡ b = Σ α ∈ {1· · N } . a[α] = 0 ∧ ∀ β ∈ {1· · N } . (Σ α ∈ {1· · β} . a[α]) ≥ 0}

Una vez especificada la función inmersora, para especificar la función inicial utilizando la inmersión satisfa-
cemos las clausulas de la precondición imponiendo el dominio nulo al sumatorio, es decir, i = 0. En tal caso
la suma de 0 miembros de un vector es 0 y por tanto c = 0 con lo que bb = cierto ya que se cumplen las dos
clausulas de la precondición.

{Q ≡ cierto}
fun desf i (a : vector[1 · · N ] de ent) dev (b : bool) ≡
dev idesf i(a, 0, 0, cierto)
ffun
{R ≡ b = Σ α ∈ {1· · N } . a[α] = 0 ∧ ∀ β ∈ {1· · N } . (Σ α ∈ {1· · β} . a[α]) ≥ 0}

Figura 8.1: esf erulado: función inicial utilizando una función inmersora

8.1.3. Diseño de la función inmersora


Una vez especificada la función que nos piden en el enunciado, pasamos a diseñar la inmersión como un
diseño recursivo normal. Para ello analizamos los casos directo y recursivo.
54

Caso Directo: Observamos que si se verifica en la precondición que i = N y c = 0, se cumple la


postcondición directamente, esto es:

Q ∧ (i = N ) ∧ (c = 0) ≡ R

Caso Recursivo: El caso recursivo se verifica cuando i < N , en tal caso tenemos que:

i < N → idesf i(a, i + 1, E1 , E2 )

donde:

• a es el vector inicial y no varia


• i es una variable que debe llegar hasta N . Esta variable define la estructura de preorden bien
fundado necesaria para la verificación. Se puede proponer como función de cota a t(x̄) = N − i.
Comprobaremos su validez en posteriores apartados.
• E1 , E2 son expresiones sobre la tupla de parámetros. Analizaremos más adelante cómo obtenerlas.

Hechas estas consideraciones, el diseño definitivo nos queda:

{Q ≡ bb = Σ α ∈ {1· · i} . a[α] = c ∧ ∀ β ∈ {1· · i} . (Σ α ∈ {1· · β} . a[α]) ≥ 0 ∧ (i ≤ N )}


fun idesf i (a : vector[1 · · N ] de ent; i, c : nat; bb : bool) dev (b : bool) ≡
caso i = N → b = bb ∧ (c = 0)
dc i < N → b = idesf i(a, i + 1, E1 , E2 )
fcaso
ffun
{R ≡ b = Σ α ∈ {1· · N } . a[α] = 0 ∧ ∀ β ∈ {1· · N } . (Σ α ∈ {1· · β} . a[α]) ≥ 0}

Figura 8.2: Esf erulado, diseño definitivo

La clausula (c = 0) se ha pasado a la parte derecha del caso trivial para no tener dos casos directos2 , es
decir, uno donde la función devolveria el valor de bb calculado y otro en que se devolverı́a f also :

caso (i = N ) ∧ (c = 0) → b = bb
dc (i = N ) ∧ (c 6= 0) → b = f also
dc i < N → b = idesf i(a, i + 1, c + a[i + 1], bb ∧ (c + a[i + 1] ≥ 0))
fcaso

Figura 8.3: Esf erulado. Instrucción Alternativa

Las expresiones E1 y E2 deberán cumplir la precondición que se encuentren cuando se invoque la llamada
recursiva donde están, y por tanto:

2 Habrá que tener en cuenta esto a la hora de comprobar que Q(x̄) ∧ Bt (x̄) ⇒ R(x̄, triv(x̄))
55

E1 = Σ α ∈ {1· · i + 1} . a[α]
= Σ α ∈ {1· · i} . a[α] +a[i + 1]
| {z }
c
= c + a[i + 1]
Y también:

E2 = ∀ β ∈ {1· · i + 1} . Σ α ∈ {1· · β} . a[α] ≥ 0

= ∀ β ∈ {1· · i} . Σ α ∈ {1· · β} . a[α] ≥ 0 ∧ Σ α ∈ {1· · i + 1} . a[α] ≥ 0


| {z }
bb
= bb ∧ (Σ α ∈ {1· · i} . a[α] + a[i + 1]) ≥ 0
| {z }
c
= bb ∧ (c + a[i + 1]) ≥ 0
La función que obtenemos es recursiva final y con postcondición constante. El diseño final es como sigue:

{Q ≡ bb = Σ α ∈ {1· · i} . a[α] = c ∧ ∀ β ∈ {1· · i} . (Σ α ∈ {1· · β} . a[α]) ≥ 0 ∧ (i ≤ N )}


fun idesf i (a : vector[1 · · N ] de ent; i, c : nat; bb : bool) dev (b : bool) ≡
{t(x̄) = N − i}
caso i = N → b = bb ∧ (c = 0)
dc i < N → b = idesf i(a, i + 1, c + a[i + 1], bb ∧ (c + a[i + 1] ≥ 0))
fcaso
ffun
{R ≡ b = Σ α ∈ {1· · N } . a[α] = 0 ∧ ∀ β ∈ {1· · N } . (Σ α ∈ {1· · β} . a[α]) ≥ 0}

Figura 8.4: Esf erulado. Versión definitiva

8.1.4. Verificación
Para la verificación consideraremos:
x̄ = {a, i, c, bb} y ȳ = {b}.
c(x̄, ȳ) = ȳ (La función c no existe por ser recursiva final)
Los pasos de la verificación son:
1. Q(x̄) ⇒ Bt (x̄) ∨ Bnt (x̄)
Está claro que si tenemos Q ≡ A ∧ (i ≤ N ) siendo A una clausula donde no se establece ningún
dominio para las variables i ni c y siendo además i ∈ IN tenemos que:
Q(x̄) ≡ (bb = · · ·) ∧ (i ≤ N )
⇒ (bb = · · ·) ∧ ((i < N ) ∨ (i = N ))
⇒ (i < N ) ∨ (i = N )
⇒ Bt (x̄) ∨ Bnt (x̄)
56

2. Q(x̄) ∧ Bnt (x̄) ⇒ Q(s(x̄))

s(x̄)
Como Q(s(x̄)) es equivalente por definición a escribir Qx̄ vamos a demostrar que si i < N entonces
i+1,c+a[i+1],bb ∧ (c+a[i+1])≥0
se verificará Qi,c,bb .

Q(x̄) ∧ Bnt (x̄) ≡ (bb = · · ·) ∧ (i ≤ N ) ∧ (i < N )

Pi Pβ
⇒ bb = ( α=1 a[α] = c) ∧ (∀ β ∈ {1· · i} . ( α=1 a[α] ≥ 0)) ∧ (i < N ))
i
X β
X Pi+1
⇒ bb0 = ( a[α] = c) ∧ (∀ β ∈ {1· · i} . ( a[α]) ≥ 0) ∧ ( α=1 a[α] = c0 ) ∧
α=1 α=1
| {z }
bb

(∀ β ∈ {1· · i + 1} . ( α=1 a[α]) ≥ 0)

Pi Pβ
⇒ bb0 = bb ∧ ( α=1 a[α] + a[i + 1] = c0 ) ∧ (∀ β ∈ {1· · i} . ( α=1 a[α]) ≥ 0)∧
i+1
X
( a[α] ≥ 0)
α=1
| {z }
c+a[i+1]≥0

⇒ bb0 = bb ∧ (c + a[i + 1] ≥ 0) ∧ (c0 = c + a[i + 1])

3. Q(x̄) ∧ Bt (x̄) ⇒ R(x̄, triv(x̄))


Tenemos que tener en cuenta que como hemos indicado anteriormente, la clausula (c = 0) la habı́amos
pasado a la solución trivial.
i
X β
X
Q(x̄) ∧ Bt (x̄) ⇒ bb = ( a[α] = c) ∧ (∀ β ∈ {1· · i} . ( a[α] ≥ 0)) ∧
α=1 α=1
(i = N ) ∧ (c = 0)
N
X β
X
⇒ bb = ( a[α] = 0) ∧ (∀ β ∈ {1· · N } . ( a[α] ≥ 0))
α=1 α=1

4. Q(x̄) ∧ Bnt (x̄) ∧ R(s(x̄), ȳ 0 ) ⇒ R(x̄, c(x̄, ȳ 0 ))

Estamos en un caso singular de recursividad final con postcondición constante, por lo que R(s(x̄), ȳ 0 ) ≡
R(x̄, c(x̄, ȳ 0 ))
5. Hallar t(x̄) : DT1 −→ ZZ tal que t(x̄) ≥ 0 . Como Q(x̄) ≡ (bb = · · ·) ∧ (i ≤ N ) ⇒ (0 ≤ N − i) hacemos
t(x̄) = N − i ≥ 0 y por tanto Q(x̄) ⇒ (t(x̄) ≥ 0)
6. Q(x̄) ∧ Bnt (x̄) ⇒ t(s(x̄)) < t(x̄)
Q(x̄) ∧ Bnt (x̄) ≡ (bb = · · ·) ∧ (i < N )
⇒ (0 < N − i)
⇒ 0≤N −i−1<N −i
57

8.2. El monte

Enunciado. Un vector es un monte si su primera mitad es creciente, la segunda decreciente,


y el vector completo es un palı́ndromo. Se pide diseñar una función recursiva completamente
verificada que determine si un vector de N elemementos (con N > 0) es un monte.

Especificación Formal
Al estudiar el enunciado del problema dado en el apartado anterior vemos que parte de la descripción
de la propiedad de “ser monte” de un vector es redundante, ya que se nos dice que el vector debe ser
palı́ndromo, esto es, que aquellos elementos que se encuentren a la misma distancia del centro han de ser
iguales, y que la primera mitad ha de ser creciente y la segunda decreciente. Pues bien, si se cumplen las dos
primeras condiciones la tercera se satisfará necesariamente, por lo cual, simplificaremos la descripción de la
postcondición al eliminarla.
En cuanto a la precondición, el propio enunciado nos exige que el tamaño del vector no sea nulo.

{Q ≡ N > 0}
fun monte (v : vector[1 · · N ] de ent) dev (b : bool)
{R ≡ b = (∀ α ∈ {1· · N div 2} . v[α] ≤ v[α + 1] ∧ v[α] = v[N + 1 − α])}

Para poder establecer una recurrencia sobre los datos del problema de manera que su tamaño decrezca,
necesitamos realizar una inmersión de la especificación original. Para ello, substituiremos una constante (el
extremo inferior del rango del cuantificador, en este caso), por una variable, que deberemos acotar en la
precondición para expresar qué valores son admisibles para ella. Llamaremos a la función inmersora imonte
para recordar su procedencia y categorı́a.

{Q ≡ 1 ≤ i ≤ N }
fun imonte (v : vector[1 · · N ] de ent; i : nat) dev (b : bool)
{R ≡ b = (∀ α ∈ {i· · N div 2} . v[α] ≤ v[α + 1] ∧ v[α] = v[N + 1 − α])}

La función imonte realizará el cálculo que nos interesa cuando la invocación inicial sea imonte(a, 1), donde
a es el vector del que se desea determinar si es un monte.

Análisis Por Casos


Caso Directo i > N div 2
En este caso el rango del cuantificador se hace vacı́o, por lo cual el resultado será cierto, que es el elemento
neutro de la operación de conjunción lógica que representa el cuantificador universal.

Caso Recursivo i ≤ N div 2


Por la postcondición,

b = (∀ α ∈ {i· · N div 2} . v[α] ≤ v[α + 1] ∧ v[α] = v[N + 1 − α])


58

Composición Algorı́tmica
En la figura 8.5 se muestra el código de la función imonte.

{Q ≡ 1 ≤ i ≤ N }
fun imonte (v : vector[1 · · N ] de ent; i : nat) dev (b : bool) ≡
caso i > N div 2 → cierto
dc i ≤ N div 2 → v[i] ≤ v[i + 1] ∧ v[i] = v[N + 1 − i] ∧ imonte(v, i + 1)
fcaso
ffun
{R ≡ b = (∀ α ∈ {i· · N div 2} . v[α] ≤ v[α + 1] ∧ v[α] = v[N + 1 − α])}

Figura 8.5: Función imonte

Verificación Formal
Para la verificación formal del algoritmo seguiremos los pasos propuestos en [Peña93, 3.3.2 Corrección y
coste de programas recursivos, pag(s) 61 y ss.]:

1. Q(x̄) ⇒ Bt (x̄) ∨ Bnt (x̄)



Q(x) ≡ 1 ≤ i ≤ N 
Bt (x) ≡ i > N div 2 1 ≤ i ≤ N =⇒ (i > N div 2) ∨ (i ≤ N div 2)
Bnt (x) ≡ i ≤ N div 2

2. Q(x̄) ∧ Bnt (x̄) ⇒ Q(s(x̄))



Q(x) ≡ 1≤i≤N 

Bnt (x) ≡ i ≤ N div 2

1 ≤ i ≤ N div 2 =⇒ i + 1 ≤ N
Q(s(x)) ≡ 1 ≤ i0 ≤ N 
i0

= i+1

3. Q(x̄) ∧ Bt (x̄) ⇒ R(x̄, triv(x̄)) (Base de la inducción)

Q(x̄) ∧ Bt (x̄) ≡ (1 ≤ i ≤ N ) ∧ (i > N div 2)

N div 2
R(x̄, triv(x̄)) ≡ cierto = ∀α=1 (v[α] ≤ v[α + 1] ∧ v[α] = v[N + 1 − α])

Por 8.1, i > N div 2, lo que implica que el rango del cuantificador universal se anula y por tanto el
resultado es el elemento neutro de la conjunción, es decir, cierto.

4. Q(x̄) ∧ Bnt (x̄) ∧ R(s(x̄), ȳ0 ) ⇒ R(x̄, c(ȳ0 , x̄)). Paso de Inducción, donde R(s(x̄), ȳ 0 ) representa la
hipótesis de inducción.

N div 2
R(s(x̄), ȳ 0 ) ≡ b0 = ∀α=i+1 (v[α] ≤ v[α + 1] ∧ v[α] = v[N + 1 − α])
Q(x̄) ∧ Bnt (x̄) ≡ 1 ≤ i ≤ N ∧ i ≤ N div 2 ⇒ 1 ≤ i ≤ N div 2
59

? N div 2
R(x̄, c(ȳ 0 , x̄)) ≡ b = ∀α=i (v[α)] ≤ v[α + 1] ∧ v[α] = v[N + 1 − α])
c(ȳ 0 , x̄) = ((v[i] ≤ v[i + 1] ∧ v[i] = v[N + 1 − i]) ∧b0 )
| {z }
comparación (?)
b = comparación (?) ∧ b0 ⇒
⇒ por hipótesis de inducción
b = (v[i] ≤ v[i + 1] ∧ v[i] = v[N + 1 − i])∧
N div 2
∀α=i+1 (v[α] ≤ v[α + 1] ∧ v[α] = v[N + 1 − α]) ⇒
N div 2
b = ∀α=i (v[α] ≤ v[α + 1] ∧ v[α] = v[N + 1 − α])
| {z }
extendiendo el cuantificador hasta i

5. t : IN → ZZ | Q(x̄) ⇒ t(x̄) ≥ 0 Estructura de p.b.f.



Q(x̄) ≡ 1≤i≤N
⇒N −i≥0
t : IN → ZZ = t(i) = N − i

6. Q(x̄) ∧ Bnt (x̄) ⇒ t(s(x̄)) < t(x̄)



Q(x̄) ≡ 1≤i≤N 
t : IN → ZZ = t(i) = N − i ⇒N −i−1<N −i
s(x̄) = N − (i + 1)

7. Estudio del coste asintótico temporal en el caso peor


En este algoritmo el tamaño de los datos decrece por substracción, siendo pues la fórmula de recurrencia:

cnk

, si i > N div 2
T (n) = ⇒
aT (n − b) + cnk , si i ≤ N div 2

 Θ(nk ) , si a < 1
T (n) ∈ Θ(nk+1 ) , si a = 1
Θ(an div b ) , si a > 1

Como el coste del caso trivial es constante inferimos que k = 0. Además b = 1, ya que a cada nueva
invocación de la función el tamaño de los datos decrece en una unidad, y a = 1 ya que por cada llamada
externa el algoritmo produce una interna. Ası́ pues estamos en el caso a = 1 y, por lo tanto, el coste
del algoritmo es Θ(n).

8.3. Las Dobles Parejas

Enunciado: Se desea una función recursiva completamente verificada tal que, dado un
vector definido v : vector[1 · · N ] de ent, devuelva un booleano que indique si se puede hallar
dentro de dicho vector dos parejas consecutivas de elementos tales que sus sumas sean
idénticas (es decir, que la suma de los valores de la primera pareja sea la misma que la suma
de los valores de la segunda pareja).
60

8.3.1. Especificación Formal


De este enunciado se puede derivar la siguiente especificación:

{Q ≡ N ≥ 0}
fun dp (v : vector[1 · · N ] de ent) dev (b : bool)
{R ≡ b = (∃ α ∈ {4· · N } . (v[α − 3] + v[α − 2] = v[α − 1] + v[α]))}

El tamaño del vector puede ser nulo, por ello en la precondición no se exige N > 0. Por otro lado resulta obvio
que al estar pidiendo la existencia de dos parejas (esto es, cuatro elementos) la condición no se cumplirá si
no existen, al menos, esos cuatro elementos. Es por esto que el rango del cuantificador existencial de la
postcondición sea 4 · · N .
Ahora para poder trabajar recursivamente con los datos del problema, de forma que su tamaño decrezca
según un preorden bien fundado, es necesario realizar una inmersión de la especificación original. Para ello
podemos sustituir una constante por una variable que deberemos acotar en la precondición para definir los
valores que puede tomar. Sustituiremos, en este caso, el lı́mite superior del rango del cuantificador.
Ası́ obtenemos la siguiente especificación para la función inmersora (a la que llamaremos idp):

{Q ≡ 0 ≤ i ≤ N }
fun idp (v : vector[1 · · N ] de ent; i : nat) dev (b : bool)
{R ≡ b = (∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] =
v[α − 1] + v[α]))}

Análisis Por Casos


Caso Directo i ≤ 3
En este caso el rango del cuantificador se hace vacı́o, por lo cual el resultado será falso, que es el elemento
neutro de la operación de disyunción lógica que representa el cuantificador existencial.

Caso Recursivo i > 3


Según la postcondición,

b = (∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])

Con lo cual:

i > 3 → (v[i − 3] + v[i − 2] = v[i − 1] + v[i] ∨ idp(v, i − 1))

Es decir: comprobamos si en el ı́ndice que estamos analizando encontramos una doble pareja o si ésta se
encuentra entre los ı́ndices menores al que nos ocupa.
A continuación se muestra el código de la función inmersora idp.
61

{Q ≡ 0 ≤ i ≤ N }
fun idp (v : vector[1 · · N ] de ent; i : nat) dev (b : bool) ≡
caso i ≤ 3 → b = falso
dc i > 3 → b = (v[i − 3] + v[i − 2] = v[i − 1] + v[i]) ∨ idp(v, i − 1)
fcaso
ffun
{R ≡ b = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}

8.3.2. Verificación Formal


El siguiente paso del problema será verificar que el algoritmo que hemos especificado realmente hace lo que
esperamos de él. Esto es: demostrar que nuestra especificación es correcta. Para ello vamos a seguir las pautas
que encontramos en [Peña93, Tabla 3.2, pag(s) 62] o [Peña97, Tabla 3.2, pag(s) 71]

1. Q(x̄) ⇒ Bt (x̄) ∨ Bnt (x̄) (Completitud de la alternativa)



Q(x) ≡ 0 ≤ i ≤ N 
Bt (x) ≡ i≤3 0 ≤ i ≤ N =⇒ (i ≤ 3) ∨ (i > 3)
Bnt (x) ≡ i>3

Lo cual es obvio, ya que el consecuente se trata de la disyunción de un predicado y su negación.

2. Q(x̄) ∧ Bnt (x̄) ⇒ Q(s(x̄)) (Conservación de la precondición)



Q(x) ≡ 0≤i≤N  
Bnt (x) ≡ i>3

(0 ≤ i ≤ N ) ∧ (i > 3) =⇒ 0 ≤ i − 1 ≤ N
Q(s(x)) ≡ 0 ≤ i0 ≤ N 
i0

= i−1

3. Q(x̄) ∧ Bt (x̄) ⇒ R(x̄, triv(x̄)) (Base de la inducción)

Q(x̄) ∧ Bt (x̄) ≡ (0 ≤ i ≤ N ) ∧ (i ≤ 3)

R(x̄, triv(x̄)) ≡ falso = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])

Como i ≤ 3, tenemos que el rango del cuantificador existencial resulta ser el conjunto vacı́o, con lo que
el resultado es el elemento neutro de la disyunción, es decir, falso

4. Q(x̄) ∧ Bnt (x̄) ∧ R(s(x̄), ȳ0 ) ⇒ R(x̄, c(ȳ0 , x̄)) Paso de Inducción, donde R(s(x̄), ȳ 0 ) representa la
hipótesis de inducción .

R(s(x̄), ȳ 0 ) ≡ b0 = ∃ α ∈ {4· · i − 1} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])


Q(x̄) ∧ Bnt (x̄) ≡ 0≤i≤N ∧i>3⇒3<i≤N
?
R(x̄, c(ȳ 0 , x̄)) ≡ b = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])
c(ȳ 0 , x̄) = ((v[i − 3] + v[i − 2] = v[i − 1] + v[i]) ∨b0 )
| {z }
comparación ()
b = comparación () ∨ b0 ⇒
62

⇒ por hipótesis de inducción


b = (v[i − 3] + v[i − 2] = v[i − 1] + v[i])∨
∃ α ∈ {4· · i − 1} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])
b = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])
| {z }
extendiendo el cuantificador hasta i

5. t : IN → ZZ | Q(x̄) ⇒ t(x̄) ≥ 0 Estructura de p.b.f.



Q(x̄) ≡ 0≤i≤N
⇒i≥0
t : IN → ZZ = t(i) = i

6. Q(x̄) ∧ Bnt (x̄) ⇒ t(s(x̄)) < t(x̄) Decrecimiento del tamaño de los subproblemas

Q(x̄) ≡ 0≤i≤N 
t : IN → ZZ = t(i) = i ⇒i−1<i
s(x̄) = i−1

7. Estudio del coste asintótico temporal en el caso peor


En este algoritmo el tamaño de los datos decrece por substracción, siendo pues la fórmula de recurrencia:

cnk

, si i > N div 2
T (n) = ⇒
aT (n − b) + cnk , si i ≤ N div 2


 Θ(nk ) , si a < 1
T (n) ∈ Θ(nk+1 ) , si a = 1
Θ(an div b ) , si a > 1

Como el coste del caso trivial es constante inferimos que k = 0. Además b = 1, ya que a cada nueva
llamada a la función el tamaño de los datos decrece en una unidad, y a = 1 ya que por cada llamada
externa el algoritmo produce una interna. Ası́ pues estamos en el caso a = 1 y, por lo tanto, el coste
del algoritmo es Θ(n).

8.4. La fábrica

Enunciado: En una fábrica cada empleado debe fichar al entrar y al salir, cobrando una
cantidad fija por cada hora trabajada. Para cada trabajador se tiene la siguiente estructura:
h:vector [1..n,1.,2] de nat, donde h[i, 1] representa la hora de entrada del trabajdor en el
dı́a i-ésimo y h[i, 2] la hora de salida de dicho trabajdor ese mismo dı́a.
Se pide diseñar un programa recursivo que obtenga el total de horas trabajadas para un
trabajdor determinado.
63

8.4.1. Especificación
horas de trabajo vienen dadas por la expresión h[α, 2] − h[α, 1], ası́ pues el
Para un dı́a cualquiera α, las P
n
total de horas trabajadas será α=1 (h[α, 2] − h[α, 1]) con lo que la especificación de la función queda como
sigue:

{Q ≡ cierto}
fun f abrica (h : vector [1 · · n, 1 · · 2] de nat) dev (s : nat)
{R ≡ s = Σ α ∈ {1· · n} . (h[α, 2] − h[α, 1])}

8.4.2. Inmersión no final


Para poder aplicar la recursión debilitamos la postcondición limitando el número de dı́as sobre los que
efectuamos la suma. La constante n del predicado R(h, s) pasa a ser una variable libre i en un nuevo
predicado R0

i
X
0
R (h, i, s) ≡ s = (h[α, 2] − h[α, 1])
α=1

De esta manera R0 igual a R cuando se cumple que i = n. Si hacemos de i el nuevo parámetro inmersor, el
valor inicial deberá ser n para que la función inmersora equivalga a la función original. La nueva variable no
puede exceder el rango del vector, de manera que está acotada entre 0 y n.
La especificación de la nueva función ifabrica queda:

{Q ≡ 0 ≤ i ≤ n}
fun if abrica (h : vector [1 · · n, 1 · · 2] de nat; i : nat) dev (s : nat)
{R ≡ s = Σ α ∈ {1· · n} . (h[α, 2] − h[α, 1])}

8.4.3. Análisis por casos


Planteamos dos casos posibles según el valor de la variable i.
P0
Si i = 0 tenemos que α=1 (h[α, 2] − h[α, 1]) = 0 ya que el rango [1.,0] es vacı́o y el resultado es el
elemento neutro de la suma, que es 0.

Si i > 0 tenemos que:

i−1
X
Pi
α=1 (h[α, 2] − h[α, 1]) = (h[α, 2] − h[α, 1]) +(h[i, 2] − h[i, 1]) = if abrica(h, i − 1) + h[i, 2] − h[i, 1]
α=1
| {z }
if abrica(h,i−1)

Con esta expresión de la llamada recursiva podemos ya escribir la inmersión:


64

{Q ≡ 0 ≤ i ≤ n}
fun if abrica (h : vector[1 · · n, 1.,2] de nat; i : nat) dev (s : nat) ≡
caso i = 0 → s = 0
dc i > 0 → s = h[i, 2] − h[i, 1] + if abrica(v, i − 1)
fcaso
ffun
Pi
{R ≡ s = α=1 (h[α, 2] − h[α, 1])}

La llamada inicial será f abrica(h) = if abrica(h, n).

8.4.4. Verificación
1. Completitud de la alternativa: Q ≡ 0 ≤ i ≤ n =⇒ 0 ≤ i =⇒ 0 < i ∨ 0 = i.
2. Satisfacción de la precondición para la llamada interna: 0 ≤ i ≤ n ∧ i > 0 ⇒ 0 < i ≤ n ⇒ 0 <
i⇒0≤i−1
Y por tanto 0 ≤ i − 1 ≤ n
P0
3. Base de la inducción: 0 ≤ i ≤ n ∧ i = 0 ⇒ i = 0 ⇒ s = 0 y como α=1 (h[α, 2] − h[α, 1]) = 0 pues
se cumple R(triv(x)).
4. Paso de inducción: Supongamos que para un problema de tamaño i − 1 se verifica la función, esto es,
se cumple la postcondición para un cierto valor s0 . Por un lado tenemos que por hipótesis de inducción:

R(s(x),y 0 )
Q(x) Bnt (x)
z }| {
z }| { z }| { i−1
X i−1
X
0 ≤ i ≤ n ∧ i > 0 ∧ s0 = (h[α, 2] − h[α, 1]) =⇒ i > 0 ∧ s0 = (h[α, 2] − h[α, 1])
α=1 α=1

y por otro lado se nos pide demostrar R(x, c(x, y 0 )) donde:

c(x, y 0 ) = s0 + h[i, 2] − h[i, 1]

y esto es cierto ya que


i−1
X
s0 + h[i, 2] − h[i, 1] = (h[α, 2] − h[α, 1]) + h[i, 2] − h[i, 1]
α=1
i
X
= (h[α, 2] − h[α, 1])
α=1
= s

con lo que hasta aquı́ tenemos demostrada la corrección parcial y ahora nos falta la terminación.
5. Preorden bien fundado: Hay que crear un preorden inducido en el dominio de los datos que cumplen
la precondición. Se toma la función f (h, i) = i. Se comprueba que f (h, i) ∈ IN por ser 0 ≤ i ≤ n y
además f (x) > f (s(x)) ya que i > i − 1. (Nótese que la función sucesor s(x) nada tiene que ver con la
variable s.)
65

8.4.5. Transformación a final: Plegado y Desplagado


La llamada recursiva tiene la siguiente expresión:

if abrica(h, i − 1) + (h[i, 2] − h[i, 1])

Se sustituye el segundo sumando por una nueva variable de inmersión w de acuerdo con la heurı́stica
deKodratoff y se define la nueva función inmersora iifabrica de la siguiente forma:

iif abrica(h, i, w) = if abrica(h, i) + w

8.4.5.1. Desplegado y Plegado

Desplegamos la función iifabrica sustituyéndola por su expresión:

iif abrica(h, i, w) = (caso i = 0 → s = 0


dc i > 0 → s = h[i, 2] − h[i, 1] + if abrica(h, i − 1)) + w

Sumamos la nueva variable en cada una de las expresiones

iif abrica(h, i, w) = (caso i = 0 → s = 0 + w


dc i > 0 → s = (h[i, 2] − h[i, 1] + if abrica(h, i − 1)) + w)

Por la propiedad asociativa de la suma podemos reagrupar los términos

iif abrica(h, i, w) = (caso i = 0 → s = 0 + w


dc i > 0 → s = (h[i, 2] − h[i, 1] + w) + if abrica(h, i − 1))
| {z }
[a]

Donde la expresión [a] es la función iif abrica(h, i, w0 ) donde:

w0 = h[i, 2] − h[i, 1] + w

y sustituyendo (plegado) tenemos la expresión de la función final:

iif abrica(h, i, w) = (caso i = 0 → s = w


dc i > 0 → s = iif abrica(h, i − 1, h[i, 2] − h[i, 1] + w)
66

8.4.5.2. Especificación

Para que la función esté correctamente especificada es necesario que se modifiquen la precondición y post-
condición para tener en cuenta a la nueva variable de inmersión.
Si queremos dejar la postcondición constante (Sólo dependientes de los parámetros de entrada que hay en la
especificación original, no en las sucesivas inmersiones) tenemos que la expresión de R es:

n
X
s= (h[α, 2] − h[α, 1])
α=1

y como if abrica(h, i, w) = if abrica(h, i) + w tenemos que:

n
X i
X
(h[α, 2] − h[α, 1]) = (h[α, 2] − h[α, 1]) + w
α=1 α=1

y operando queda:

n
X i
X
w = (h[α, 2] − h[α, 1]) − (h[α, 2] − h[α, 1])
α=1 α=1
Xn
= (h[α, 2] − h[α, 1])
α=i+1

Ası́ pues la precondición de la función obtenida iifabrica queda:

n
X
Q≡0≤i≤n∧w = (h[α, 2] − h[α, 1])
α=i+1

8.4.5.3. Algoritmo completo

El algoritmo completo queda como sigue:

Pn
{Q ≡ 0 ≤ i ≤ n ∧ w = α=i+1 (h[α, 2] − h[α, 1])}
fun iif abrica (h : vector[1 · · n, 1.,2] de nat; i : nat; w : nat) dev (s : nat) ≡
caso i = 0 → s = w
dc i > 0 → s = iif abrica(h, i − 1, h[i, 2] − h[i, 1] + w)
fcaso
ffun Pn
{R ≡ s = α=1 (h[α, 2] − h[α, 1])}
67

8.5. Vector que funciona

Enunciado:Se dice que un vector de enteros funciona cuando el número de sus componentes
estrictamente positivas es estrictamente superior al número de sus componentes estrictamen-
te negativas.
Se desea diseñar un algoritmo que dado un vector v: vector [1..n] de ent, determine si dicho
vector v funciona según la definición dada.

8.5.1. Especificación

La especificación formal de la función es aquella que recoge completamente la semántica de la función. En


este caso tenemos por un lado que determinar el número de componentes estrictamente positivas y negativas
del vector y compararlas.

El predicado “Número de componentes estricamente positivas” se puede formalizar como sigue: N α ∈


{1 . . . n}.v[α] > 0. El respectivo para las componentes negativas es análogo.

La función debe comprobar que el valor del primero sea superior estrictamente al segundo, de manera que
la función queda especificada como sigue:
{Q ≡ cierto}
fun f unciona (v : vector[1 · · n] de ent) dev (b : bool)
{R ≡ b = (N α ∈ {1· · n} . v[α] > 0 >
N α ∈ {1· · n} . v[α] < 0)}

Para poder realizar el desarrollo de la función es necesario que el resultado de la misma pueda conducir a
resultados parciales. No es posible con la especificación dada obtener resultados parciales que puedan ser
acumulados en llamadas resursivas y que construyan al final la solución. La razón es que no se puede aplicar
el operador mayor estricto que sobre un subconjunto de datos y acumular dicha información, ya que este
conocimiento parcial no serı́a acumulable.

Para realizar la función anterior es por tanto necesario especificar una función más general que pueda ser
derivada con las técnicas conocidas y que se particularice para el caso del enunciado. Una generalización del
problema serı́a la especificación de una función que calcule la diferencia entre el número de componentes
estrictamente positivas y el de componentes estrictamente negativas. Con este cálculo, podemos calcular la
especificación del resultado sin más que comparar con 0 el valor de la diferencia.

En efecto, si consideramos la expresión:

(N α ∈ {1 . . . n}.v[α] > 0) − (N α ∈ {1 . . . n}.v[α] < 0)

El predicado de la primera especificación equivale desde el punto de vista lógico a la expresión:


68

((N α ∈ {1 . . . n}.v[α] > 0) − (N α ∈ {1 . . . n}.v[α] < 0)) > 0

Con lo que la equivalencia entre las funciones queda:

f unciona(v) ≡ (nf unciona(v) > 0)

y la especificación de la nueva función:

{Q ≡ cierto}
fun nf unciona (v : vector[1 · · n] de ent) dev (s : ent)
{R ≡ s = (N α ∈ {1· · n} . v[α] > 0 − N α ∈ {1· · n} . v[α] < 0)}

que en adelante denominaremos funciona, para mayor claridad.

8.5.2. Inmersión no final

Para el desarrollo de la función es necesario realizar una inmersión de diseño, para lo que se debilita la
postcondición y se introduce la nueva variable inmersora i.

(N α ∈ {1 . . . n}.v[α] > 0) − (N α ∈ {1 . . . n}.v[α] < 0) ⇒


(N α ∈ {1 . . . i}.v[α] > 0) − (N α ∈ {1 . . . i}.v[α] < 0) ∧ (i = n)

La nueva función inmersora tiene un nuevo parámetro i y equivale a la original en el caso en que i = n. La
especificación queda como sigue:
{Q ≡ 1 ≤ i ≤ n}
fun if unciona (v : vector[1 · · n] de ent; i : nat) dev (s : ent)
{R ≡ s = (N α ∈ {1· · i} . v[α] > 0 − N α ∈ {1· · i} . v[α] < 0)}

Y la llamada inicial de la función queda:

nf unciona(v) = if unciona(v, n)
69

8.5.3. Análisis por casos

La variable de inmersión es i y por tanto sobre ella debemos actuar para el análisis por casos. Tenemos 2
alternativas:

Si i = 0 el rango del cuantificador se anula, por lo que nos dá como resultado el elemento neutro del
cuantificador, es decir, 0.
Si i > 0, tenemos tratados los elementos hasta i − 1. El valor del elemento i del vector condiciona la
respuesta de la función. Existen 3 posibles alternativas:

• Si v[i] > 0 entonces hay un elemento estrictamente positivo más que tener en cuenta. En este
caso, la acción a realizar será la de añadir 1 al resultado de la función
• Si v[i] < 0 hay que tener en cuenta a un elemento estrictamente negativo. En este caso, al haber
una componente negativa más, el valor final de la función se reduce en 1.
• Si v[i] = 0 el valor de la variable de salida s no se verá alterado.

Con este análisis de casos es ya posible plantear una solución algorı́tmica a la función inmersora:
{Q ≡ 0 ≤ i ≤ n}
fun if unciona (v : vector[1 · · n] de nat; i : nat; ) dev (s : ent) ≡
caso i = 0 → 0
dc i > 0 → caso v[i] > 0 → 1 + if unciona(v, i − 1)
dc v[i] < 0 → −1 + if unciona(v, i − 1)
dc v[i] = 0 → if unciona(v, i − 1)
fcaso
fcaso
ffun
{R ≡ s = (N α ∈ {1· · i} . v[α] > 0 − N α ∈ {1· · i} . v[α] < 0)}

8.5.4. Verificación

1. Completitud de la alternativa: De Q se deduce que 0 ≤ i y por tanto 0 < i ∧ 0 = i. Análogamente


se procede para con la segunda alternativa, que en su caso es trivial.
2. Satisfacción de la precondición para la llamada interna: Siendo x̄ =< v, i > tenemos que
Q(s(x̄)) ≡ 0 ≤ i − 1 ≤ n y como i > 0 pues entonces i − 1 ≥ 0.
3. Base de la inducción: La expresión del predicado trivial es triv(x̄) ≡ 0. Sustituyendo triv(x̄) por ȳ
en la expresión del predicado R(x̄, ȳ) y considerando que x̄ =< v, i > y que ȳ =< s > tenemos que:

R(v, i, 0) ≡
0 = (N α ∈ {1 . . . i}.v[α] > 0) − (N α ∈ {1 . . . i}.v[α] < 0) ∧ (i = 0) ⇒
0 = (N α ∈ {1 . . . 0}.v[α] > 0) − (N α ∈ {1 . . . 0}.v[α] < 0) ⇒
0 = (0 − 0) ⇒ cierto
70

Ya que se anulan los rangos de los cuantificadores.

4. Paso de inducción: Suponemos cierta la hipótesis de inducción R(s(x̄), ȳ 0 ), es decir R(v, i − 1, s0 ) y


que por tanto se cumple que s0 = (N α ∈ {1 . . . i − 1}.v[α] > 0) − (N α ∈ {1 . . . i − 1}.v[α] < 0)
Se nos plantea demostrar que R(x̄, c(x̄, ȳ 0 )), para lo cual tendremos que hacer tantas demostraciones
como posibles formas de realizar la función de combinación c.
Consideramos los tres casos que se nos plantean en el caso no trivial:

v[i] > 0 En este caso la función de combinación es sumar 1 al resultado de invocar la función
recursivamente, por lo que la demostración del paso de inducción es la de comprobar la certeza del
predicado R(v, i, s0 + 1) bajo hipótesis de inducción y de la protección del bucle correspondiente.
En efecto, la expresión 1 + s0 = (N α ∈ {1 . . . i}.v[α] > 0) − (N α ∈ {1 . . . i}.v[α] < 0) es la
resultante de sustituir en el predicado anterior y por hipotesis de inducción sabemos que s0 =
(N α ∈ {1 . . . i − 1}.v[α] > 0) − (N α ∈ {1 . . . i − 1}.v[α] < 0). Para el elemento i que nos falta
sabemos que v[i] > 0 por lo que el operador de conteo debe sumar 1 a la expresión de s0 , con lo
que queda demostrado el paso de inducción.
v[i] < 0 Se resuelve de forma análoga
v[i] = 0 Ídem.

5. Preorden bien fundado: Elegimos t(x̄) = i y comprobamos que t(x̄) ≥ 0 al ser i ≥ 0 por la
precondición. También sabemos que i − 1 < i con lo que queda demostrado que t(s(x̄)) < t(x̄).

8.5.5. Transformación a final: Plegado y Desplagado

La técnica de plegado y desplegado obtiene de forma automática una función resursiva final a partir de
una función recursiva no final. En primer lugar es necesario determinar la expresión sintáctica de la función
inmersora, lo que deducimos a partir del árbol sintáctico de la expresión del caso recursivo de la función no
final. Se utiliza para ello una heurı́stica.

En la expresión general de la función ifunciona tenemos que calcular una expresión para el caso recursivo.
Aunque hay tres expresiones, desde el punto de vista sintáctico todas son equivalentes a if unciona(v, i−1)+k,
siendo k una constante con valores k ∈ {−1, 0, 1}. El análisis sintáctico para la expresión de este caso resursivo
general es:

+

 ee
k ifunciona
ee
v i-1

Figura 8.6: Árbol sintáctico de la función ifunciona

Si sustituimos la constante k por una nueva variable de inmersión u, obtenemos la expresión iif unciona(v, i, u) =
if unciona(v, i) + u para la nueva función inmersora, ya que la raiz del árbol es la suma.
71

8.5.5.1. Desplegado y Plegado

Para calcular la expresión de ésta vamos a proceder mediante la técnica de plegado y desplegado. En primer
lugar sustituimos if unciona(v, i) por su expresión. A este paso se le denomina desplegado.

iif unciona(v, i, u) = (
caso i = 0 → 0
dc i > 0 → caso v[i] > 0 → 1 + if unciona(v, i − 1)
dc v[i] < 0 → −1 + if unciona(v, i − 1)
dc v[i] = 0 → if unciona(v, i − 1)
fcaso
fcaso
)+u

Pasamos la variable u a los casos trivial y recursivo.

iif unciona(v, i, u) =
caso i = 0 → 0 + u
dc i > 0 → caso v[i] > 0 → 1 + if unciona(v, i − 1) + u
dc v[i] < 0 → −1 + if unciona(v, i − 1) + u
dc v[i] = 0 → if unciona(v, i − 1) + u
fcaso
fcaso

Si consideramos ahora la expresión de la función inmersora, podemos plantearnos sustituir las expresiones de
los casos resursivos por expresiones de la función iifunciona. Si sustituimos u por u + 1 entonces la expresión
iif unciona(v, i−1, u+1) tiene el mismo valor que la parte derecha del caso v[i] > 0, es decir, que la expresión:

1 + if unciona(v, i − 1) + u

y la expresión

iif unciona(v, i − i, 1 + u)

son iguales. A esta transformación se la denomina plegado.

De forma análoga puede procederse con el resto, quedando la función recursiva final de la siguiente manera:
72

iif unciona(v, i, u) =
caso i = 0 → u
dc i > 0 → caso v[i] > 0 → iif unciona(v, i − 1, u + 1)
dc v[i] < 0 → iif unciona(v, i − 1, u − 1)
dc v[i] = 0 → iif unciona(v, i − 1, u)
fcaso
fcaso

8.5.5.2. Especificación

Es evidente que una vez diseñada la función, el valor que devuelva la misma va a depender de los valores de
entrada i y u. Recordemos que para el caso de la primera inmersión de diseño el valor de i que conservaba
el valor de la función original era i = n.

No cualquier valor de u va a proporcionar que la función cumpla con la especificación. Todo depende de la
precondición que exijamos a la fución. En concreto para mantener el valor de la función inicial tenemos que
usar los valores de las variables siguientes:

iif unciona(v, n, 0) = if unciona(v, n) = f unciona(v)

No es posible conservar la postcondición {R ≡ s = (N α ∈ {1 · · i} . v[α] > 0 − N α ∈ {1 · · i} . v[α] < 0)}


llamando a la función con un valor cualquiera de i. Con la función definida anteriormente y llamando con
un valor cualquiera de v, i y u el resultado que obtenemos es: s = u + (N α ∈ {1· · i} . v[α] > 0 − N α ∈
{1· · i} . v[α] < 0). Tenemos la opción de dejar esta postcondición totalmente general que permite conservar
la precondición, pero vamos a tratar de dejar una postcondición constante.

8.5.5.3. Algoritmo completo

Si queremos que la postcondición sea constante es necesario reforzar la precondición. Para que el valor de la
postcondición sea s = (N α ∈ {1 . . . n}.v[α] > 0) − (N α ∈ {1 . . . n}.v[α] < 0) es necesario que el valor de u
de la expresión anterior sea tal que complemente los valores que faltan desde i + 1 hasta n.

En efecto, si hacemos u = (N α ∈ {i+1 . . . n}.v[α] > 0)−(N α ∈ {i+1 . . . n}.v[α] < 0) podemos comprobar que
(N α ∈ {i+1 . . . n}.v[α] > 0)−(N α ∈ {i+1 . . . n}.v[α] < 0)+(N α ∈ {1 . . . i}.v[α] > 0)−(N α ∈ {1 . . . i}.v[α] <
0) = (N α ∈ {1 . . . n}.v[α] > 0) − (N α ∈ {1 . . . n}.v[α] < 0) con lo que queda claro qué debemos exigir a u
para invocar a la función y que ésta pueda especificarse como se ha hecho. El algoritmo final queda como
sigue:
73

{Q ≡ 0 ≤ i ≤ n ∧ u = (N α ∈ {i + 1· · n} . v[α] > 0 − N α ∈ {i + 1· · n} . v[α] < 0)}


fun iif unciona (v : vector[1 · · n] de nat; i : nat; u : ent; ) dev (s : ent) ≡
caso i = 0 → u
dc i > 0 → caso v[i] > 0 → iif unciona(v, i − 1, u + 1)
dc v[i] < 0 → iif unciona(v, i − 1, u − 1)
dc v[i] = 0 → iif unciona(v, i − 1, u)
fcaso
fcaso
ffun
{R ≡ s = (N α ∈ {1· · n} . v[α] > 0 − N α ∈ {1· · n} . v[α] < 0)}

8.6. Fabricando el número perfecto

Enunciado: Se dice que un número n es perfecto si la suma de todos sus divisores, salvo él
mismo, coincide con n. Por ejemplo, el número 6 es un número perfecto, ya que sus divisores
son 1, 2, 3 y 6 y sumando 1+2+3 obtenemos 6. (Nótese que no hemos sumado el 6). Se desea
una función recursiva, completamente verificada, que decida si un número dado es perfecto

8.6.1. Especificación

perf ecto(n) nos dirá si n es la suma de todos sus divisores. Esto se formaliza como sigue:

{n = Σ (α ∈ {1· · n − 1} ∧ ((n mod α) = 0)) . α}

La especificación de la función será:

{Q ≡ cierto}
fun perf ecto (n : nat) dev (b : bool)
{R ≡ b = (n = Σ (α ∈ {1· · n − 1} ∧ ((n mod α) = 0)) . α)}

8.6.2. Una función auxiliar: especificación de la función sumadiv

Para diseñar esta fución de manera más sencilla, utilizaremos una función auxiliar que realice el trabajo de
calcular el sumatorio que compone el núcleo de la expresión de la precondición anterior. Este tipo de diseño,
por descomposición en funciones o tareas más sencillas y posterior composición para obtener el resultado
deseado, facilita el trabajo ya que nos podemos concentrar, en cada momento, en una sola tarea. La técnica
no sólo se aplica al diseño, ¡sino también a la especificación! (por no hablar de la implementación).
74

Llamaremos a la función auxiliar sumadiv(n), y deberá devolvernos un natural que sea suma de todos los di-
visores de n excepto él mismo. La formalización de tal propiedad es: Σ (α ∈ {1·· n−1} ∧ ((n mod α) = 0)) . α,
por lo que la especificación de la función será:

{Q ≡ cierto}
fun sumadiv (n : nat) dev (s : nat)
{R ≡ s = Σ (α ∈ {1· · n − 1} ∧ ((n mod α) = 0)) . α}

Si comparamos las postocondiciones de perf ecto y sumadiv veremos que la primera se puede expresar en
función de la segunda:
perf ecto(n) = (sumadiv(n) = n)
(en aplicación de la técnica de composición a la tarea de especificar la función objetivo de nuestro problema.)

8.6.3. Inmersión: isumadiv

A continuación, vamos a tratar de lograr una función inmersora para sumadiv que nos facilite su diseño. Para
ello, utilizaremos una técnica habitual de especificación y diseño: trabajar sobre una función más general,
lo que lograremos mediante la introducción de nuevos parámetros en la función inicial. Después, habremos
de expresar la función sumergida respecto a la inmersora de forma que esta última realice el cálculo que
deseábamos obtener con la primera, lo que haremos mediante una llamada inicial adecuada que particularice
la función generalizada al caso que nos interesa (cerrando ası́ el cı́rculo que comenzábamos al realizar la
inmersión).

8.6.4. Especificación de isumadiv

Partimos de la postcondición de la función sumadiv:

{R ≡ s = Σ (α ∈ {1· · n − 1} ∧ ((n mod α) = 0)) . α}

Para generalizarla, substituiremos una constante por una variable, por ejemplo, n − 1 por i:

{R ≡ s = Σ (α ∈ {1· · i} ∧ ((n mod α) = 0)) . α ∧ i = n − 1} (8.1)

A continuación, debilitamos la postcondición eliminando el último conjuntante, i = n − 1:

{R ≡ s = Σ (α ∈ {1· · i} ∧ ((n mod α) = 0)) . α} (8.2)

Esto nos exige introducir un rango para i en la precondición. El extremo superior del mismo nos vale con
que sea n − 1, que es la constante que hemos substituido. El inferior nos conviene que sea 0, ya que eso
permitirá anular el rango del cuantificador para obtener un caso trivial.3 . Tras estos pasos, la especificación
3 Esta no es la única solución, pero sı́ la más elegante
75

de la función isumadiv queda como sigue:

{Q ≡ 0 ≤ i ≤ n − 1}
fun isumadiv (n, i : nat) dev (s : nat)
{R ≡ s = Σ (α ∈ {1· · i} ∧ ((n mod α) = 0)) . α}

8.6.5. Llamada inicial

Para obtener la llamada inicial que hace que isumadiv(n, i) = sumadiv(n), nos fijaremos en la expre-
sión (8.1). En ella vemos que si i = n − 1, la expresión coincide con la postcondición de sumadiv(n), por lo
cual, la llamada inicial será isumadiv(n, n − 1) = sumadiv(n).

8.6.6. Análisis por casos

El rango de la variable i es {0 · · n − 1}. Si i = 0 el rango del sumatorio se anula (será {1 · · 0}, que es
el conjunto vacı́o). En este caso no habrá elementos cuantificados. Por convención, entonces se devuelve el
elemento neutro de la operación de la que el cuantificador es una expresión resumida, en este caso, el 0.

Por otra parte, si i > 0, habrá, al menos, un elemento en el rango, por lo que podremos dividir el cálculo del
sumatorio en dos partes, siendo la suma total la del elemento en cuestión añadida a la del resto de elementos
(lo que nos permitirá una expresión recursiva). En resumidas cuentas,

(Σ (α ∈ {1· · i} ∧ ((n mod α) = 0)) . α) = (Σ (α ∈ {1· · i − 1} ∧ ((n mod α) = 0)) . α) + (i si (n mod i) = 0)

Dado que la suma hasta i − 1 se puede expresar como una llamada recursiva interna (de acuerdo con la
postcondición, que podemos ver en (8.2) y que nos dice que en s estará la suma de los primeros i elementos
que cumplan la condición de dividir a n), tendremos que, en el caso i > 0 el valor que vamos a devolver
será isumadiv(n, i − 1) + (i si(n mod i = 0)). Como vemos, hay que distinguir dos casos: si el elemento i
divide a n, entonces sumaremos i al resultado que hemos de devolver. En otro caso no hemos de añadir nada
a dicha magnitud.

De esta forma, el análisis por casos queda:

i=0 0
i > 0 i divide a n i + isumadiv(n − i − 1)
i no divide a n isumadiv(n, i − 1)

8.6.7. Código de la función

El análisis por casos del anterior apartado se codifica de la siguiente forma:


76

{Q ≡ 0 ≤ i ≤ n − 1}
fun isumadiv (n, i : nat) dev (s : nat) ≡
caso i = 0 → 0
dc i > 0 → caso (n mod i) = 0 → i + isumadiv(n, i − 1)
dc (n mod i) 6= 0 → isumadiv(n, i − 1)
fcaso
fcaso
ffun
{R ≡ s = Σ (α ∈ {1· · i} ∧ ((n mod α) = 0)) . α}

8.6.8. Verificación de la corrección

8.6.8.1. Completitud de la alternativa

Tenemos que demostrar que Q(x) ⇒ Bt (x) ∨ Bnt (x)


Bt (x) ≡ i = 0
Bt (x) ∨ Bnt (x) ≡ i = 0 ∨ i > 0
Bnt (x) ≡ i > 0

Q(x) ≡ n > 0 ∧ 0 ≤ i ≤ n − 1

Ası́ Q(x) ⇒ Bt (x) ∨ Bnt (x) ≡ n > 0 ∧ 0 ≤ i ≤ n − 1 ⇒ 0 ≤ i lo que es trivialmente equivalente a


i=0 ∨ i>02

8.6.8.2. Conservación de la precondición para la llamada interna

En la función isumadiv tenemos dos llamadas internas en sendas ramas de una alternativa. Sin embargo,
para los propósitos de este apartado ambas llamadas son iguales, ya que lo que nos interesa es ver si los
nuevos datos (los del sucesor de los actuales) cumplen la precondición de la función.

Lo que debemos demostrar es que Q(x) ∧ Bnt (x) ⇒ Q(s(x))

Los datos que tenemos se resumen en:



x ≡ (n, i) 

Q(x) ≡ 0 ≤ i ≤ n − 1



Bnt (x) ≡ i > 0 ası́, Q(x) ∧ Bnt (x) ⇒ Q(s(x)) ya que, como i ∈ IN , i > 0 ⇒ i−1 ≥ 0 2
s(x) ≡ (n, i − 1)




Q(s(x)) ≡ 0 ≤ i − 1 ≤ n − 1

77

8.6.8.3. Base de la inducción

Se trata de demostrar que Q(x) ∧ Bt (x) ⇒ R(x, triv(x)). Los términos que intervienen son los siguientes:


Bt (x) ≡ i=0 

triv(x) ≡ 0

y con i = 0 en R(x, triv(x)),
R(x, triv(x)) ≡ 0 = Σ (α ∈ {1· · i} ∧ ((n mod α) = 0)) . α 

Q(x) ∧ Bt (x) ≡ {0 ≤ i ≤ n − 1 ∧ i = 0} (que equivale a i = 0)

0 = {Σ (α ∈ {1· · 0} ∧ ((n mod α) = 0)) . α}. Como {1 · · 0} ≡ ∅ el sumatorio tomará el valor del elemento
neutro de la operación de la que es una extensión a un rango, aquı́, la suma y, por tanto, el 0, lo que hace
que la equivalencia sea trivial 2

8.6.8.4. Paso de inducción

Dado que el caso no trivial está definido mediante una alternativa, lo primero que habremos de demostrar
es que ésta es completa y que sus protecciones están bien definidas. Lo que hay que demostrar es que
Q(x) ∧ Bnt (x) ⇒ (((n mod α) = 0) ∨ ((n mod α) 6= 0)). La prueba parece trivial, ya que un número puede
ser o no ser divisor de otro. Sin embargo, el quid de la cuestión estriba en que la propia operación mod
esté definida. Esa es la razón de necesitar la participación de la protección del caso no trivial, ya que la
precondición por si sola permitirı́a el caso i = 0 y, por supuesto, la operación n mod 0 no está definida.
Salvado este obstáculo, la implicación (0 ≤ i ≤ n − 1 ∧ i > 0) ⇒ (((n mod α) = 0) ∨ ((n mod α) 6= 0)) es
trivial, ya que, para i > 0 la operación (n mod α) está definida y su resultado tendra que ser o no cero.

Para demostrar el resto del paso de inducción, deberemos probar que, a través de ambas ramas de la alter-
nativa, se cumple que: Q(x) ∧ Bnt (x) ∧ R(s(x), y 0 ) ⇒ R(x, c(y 0 , x)):


Q(x) ≡ 0≤i≤n−1 

Bnt (x) ≡ i>0




h.i. Pi−1 
R(s(x), y 0 ) ≡ s0 = α=1∧(n mod α)=0 α

? Pi
⇒ R(x, c(y 0 , x)) ≡ s = α=1∧(n mod α)=0 α
i + s0

si (n mod i) = 0 
c(y 0 , x) ≡

s0

si (n mod i) 6= 0



con s0

= isumadiv(n, i − 1)

Para probarlo necesitaremos ver qué sucede en cada una de las ramas de la alternativa:

(n mod i) 6= 0. En este caso, i no es divisor de n, por lo que no habrá que sumarlo y, por tanto, el
conjunto de los divisores de n en {1 · · i} coincide con el conjunto de los divisores de n en {1 · · i − 1},
por lo que s = Σ (α ∈ {1· · i} ∧ ((n mod α) = 0)) . α 2

(n mod i) = 0. Ahora i es divisor de n, por lo que el conjunto de los divisores de n en {1 · · i}


podrá definirse como divisores(n, {1 · · i}) = divisores(n, {1 · · i − 1}) ∪ {i}. Ası́, habrá que sumar i
a la suma de los divisores de n en {1 · · i − 1} para obtener la suma de los divisores de n en {1 ·
· i}, o lo que es lo mismo, s = (Σ (α ∈ {1 · · i − 1} ∧ ((n mod α) = 0)) . α) + i. Como tenı́amos
que s0 = Σ (α ∈ {1 · · i − 1} ∧ ((n mod α) = 0)) . α, por hipótesis de inducción, y en esta rama
78

de la alternativa c(y 0 , x) ≡ s = i + s0 , lo que, según la hipótesis de inducción citada resulta ser


s = i + Σ (α ∈ {1· · i − 1} ∧ ((n mod α) = 0)) . α, es decir,

s = Σ (α ∈ {1· · i} ∧ ((n mod α) = 0)) . α 2

8.6.8.5. Inducción de un preorden bien fundado sobre los datos de entrada

Tenemos que encontrar t : DT1 −→ ZZ | Q(x) ⇒ t(x) ≥ 0, lo que nos permitirá definir una estructura
de preorden bien fundado. Sabemos que x ≡ (n, i), por lo que proponemos t(x) = i, y es evidente que
0≤i≤n−1⇒i≥0 2

8.6.8.6. Decrecimiento de los datos de entrada respecto al preorden bien fundado

Se trata de probar que Q(x) ∧ Bnt (x) ⇒ t(s(x)) < t(x):


x ≡ (n, i) 

Q(x) ≡ 0≤i≤n−1



s(x) ≡ (n, i − 1) ⇒ t(s(x)) < t(x), ya que i ∈ IN 2
t(x) ≡ i




t(s(x)) ≡ i−1

Capı́tulo 9

Obtención de algoritmos iterativos por


transformación de algoritmos recursivos

En este capı́tulo se transforman los algoritmos recursivos desarrollados en el capı́tulo 8 en sus equivalentes
iterativos como forma de introducir algunas de las técnicas generales para dicho propósito.

9.1. Viaje Esferulado: De Recursivo A Iterativo

Partimos de la especificación de la función recursiva obtenida en el apartado 8.4.

Pi Pβ
{Q ≡ bb = α=1 a[α] = c ∧ ∀ β ∈ {1· · i} . ( α=1 a[α]) ≥ 0 ∧ (i ≤ N )}
fun idesf i (a : vector[1 · · N ] de ent; i, c : nat; bb : bool) dev (b : bool) ≡
caso i = N → b = bb ∧ (c = 0)
dc i < N → b = idesf i(a, i + 1, c + a[i + 1], bb ∧ (c + a[i + 1]) ≥ 0)
fcaso
ffun
{R ≡ b = Σ α ∈ {1· · N } . a[α] = 0 ∧ ∀ β ∈ {1· · N } . (Σ α ∈ {1· · β} . a[α]) ≥ 0}

Figura 9.1: Función Esf erulado.

Por transformación a iterativo de una recursiva final obtenemos el siguiente diseño al que llamaremos desf i−
ite. La transformación es directa al ser una función recursiva final.

La función iterativa, desf i−it, no necesita recibir ocmo parámetros más que el vector, ya que, el resto de
variables puden ser locales a la misma. Por lo tanto, la precondición no necesita exigir nada a su único
parámetro, el vector a. Además, la postcondición es la misma que la de la función recursiva final, que ya era
constante respecto a los parámetros de inmersión. Ası́, la especificación de la función quedarı́a como:
{Q ≡ cierto}
fun desf i−it (a : vector[1 · · n] de ent) dev (b : bool)
{R ≡ b = Σ α ∈ {1· · N } . a[α] = 0 ∧ ∀ β ∈ {1· · N } . (Σ α ∈ {1· · β} . a[α]) ≥ 0}

La función necesitará variables locales para realizar los cálculos de los que se encargaban los parámetros
de su versión recursiva. Mantendremos los nomnbres de estos en aquellas para facilitar la comprensión del
resultado, por lo que contaremos con variables:

79
80

var
i, c : nat;
bb : bool
fvar

La llamada inicial de la función idesf i para que obtenga el valor que pretendı́amos obtener con desf i (véase
su especificación en el apartado 8.1.1) serı́a idesf i(a, 0, 0, cierto). Esta llamada inicial nos proporciona los
valores adecuados para la inicialización:
Inic : < i, c, bb >:=< 0, 0, cierto >;

Asimismo, la terminación de la función vendrá dada por el cálculo realizado en el caso final de la recursividad,
es decir:
T erm : b := bb ∧ (c = 0);

Sabemos, además, que el bucle tendrá que realizarse mientras se cumpla la protección del caso no trivial, lo
que nos da una cota t = N − i. Recapitulando, tendremos una función como la siguiente, a falta de obtener
el cuerpo de su bucle:
{Q ≡ cierto}
fun desf i−it (a : vector[1 · · N ] de ent) dev (b : bool) ≡
< i, c, bb >:=< 0, 0, cierto >;
mientras i < N hacer {P ≡?}; {cota ≡ t = N − i}
restablecer;
avanzar
fmientras
dev (bb ∧ (c = 0))
ffun
{R ≡ b = Σ α ∈ {1· · N } . a[α] = 0 ∧ ∀ β ∈ {1· · N } . (Σ α ∈ {1· · β} . a[α]) ≥ 0}

El invariante es inmediato sin más que tomar la precondición de la función recursiva final (se deja como
ejercicio la demostración de su invarianza):
Pi Pβ
{P ≡ bb = α=1 a[α] = c ∧ ∀ β ∈ {1· · i} . ( α=1 a[α]) ≥ 0 ∧ (i ≤ N )}

Los valores de los parámetros de la llamada recursiva interna nos darán las asignaciones adecuadas para
el cuerpo del bucle. Ası́, avanzar, que se establece sobre el parámetro i (sobre el que se establece la cota,
que se corresponde con el preorden bien fundado que ya vimos en el punto 5 de la verificación de idesf i
(véase 8.1.4, en las páginas 55 y siguiente), será:

avanzar : i := i + 1;

En cuanto a restablecer, consiste en modificar las otras dos variables, c y bb según los parámetros de la
función recursiva final de los que tomasn nombre y comportamiento:

restablecer : < c, bb >:=< c + a[i + 1], bb ∧ (c + a[i + 1] ≤ 0) >


81

Ambas variables se pueden modificar en paralelo, como indica la sentencia, porque, dada la forma de cálculo
de bb (que repite el de c), no hay influencias mutuas (o efectos secundarios). De hecho, se deja al lector la
simplificación del bucle.

El diseño iterativo definitivo se puede ver en la figura 9.2

{Q ≡ cierto}
fun desf i−it (a : vector[1 · · N ] de ent) dev (b : bool) ≡
< i, c, bb >:=< 0, 0, cierto >;
mientras i < N hacer
Pi Pβ
{P ≡ bb = α=1 a[α] = c ∧ ∀ β ∈ {1· · i} . ( α=1 a[α]) ≥ 0 ∧ (i ≤ N )}
{cota ≡ t = N − i}
i := i + 1;
< c, bb >:=< c + a[i + 1], bb ∧ (c + a[i + 1] ≤ 0) >
fmientras
dev (bb ∧ (c = 0))
ffun
{R ≡ b = Σ α ∈ {1· · N } . a[α] = 0 ∧ ∀ β ∈ {1· · N } . (Σ α ∈ {1· · β} . a[α]) ≥ 0}

Figura 9.2: Esf erulado iterativo: versión final

9.2. Dobles Parejas, transformación a iterativo

Partiremos de la función obtenida en el apartado 8.3.1

{Q ≡ 0 ≤ i ≤ N }
fun idp (v : vector[1 · · N ] de ent; i : nat) dev (b : bool) ≡
caso i ≤ 3 → b = falso
dc i > 3 → b = v[i − 3] + v[i − 2] = v[i − 1] + v[i] ∨ idp(v, i − 1)
fcaso
ffun
{R ≡ b = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}

9.2.1. Transformación a una función recursiva final

Estamos ante una función recursiva no final. Para transformarla a un programa iterativo primeramente
la convertiremos en una función recursiva final ( a la que denominaremos iidp ) empleando la técnica de
desplegado-plegado expuesta en [Peña93, 3.5 Técnica de desplegado y plegado, pag(s) 81-87] o [Peña97, 3.5
Técnica de desplegado y plegado, pag(s) 94-99]

El primer paso consiste en definir una función que generalice nuestra función idp. Para ello nos podemos
basar en el árbol sintáctico de dicha función idp. Ası́ obtenemos nuestra función iidp definida ası́:
82

iidp(v, i, u) = (u ∨ idp(v, i))

Además tenemos que

idp(v, i) = iidp(v, i, falso)

Siendo falso el elemento neutro de la operación ∨.

Tras la generalización debemos pasar a la fase de desplegado, consistente en emplear la anterior equivalencia
y sustituir la definición de idp por la de la nueva iidp:
fun iidp (v : vector[1 · · N ] de ent; i : nat; u : bool) dev (b : bool) ≡
caso i ≤ 3 → b = u ∨ falso
dc i > 3 → b = u ∨ (v[i − 3] + v[i − 2] = v[i − 1] + v[i] ∨ idp(v, i − 1))
fcaso
ffun

A continuación operamos para simplificar teniendo en cuenta las propiedades de la función lógica ∨:
fun iidp (v : vector[1 · · N ] de ent; i : nat; u : bool) dev (b : bool) ≡
caso i ≤ 3 → b = u
dc i > 3 → b = (u ∨ (v[i − 3] + v[i − 2] = v[i − 1] + v[i]))
∨idp(v, i − 1)
fcaso
ffun

Y como el resultado del caso no trivial presenta el mismo aspecto que la función iidp podemos pasar a la
fase de plegado:
fun iidp (v : vector[1 · · N ] de ent; i : nat; u : bool) dev (b : bool) ≡
caso i ≤ 3 → b = u
dc i > 3 → b = iidp(v, i − 1, u ∨ (v[i − 3] + v[i − 2] = v[i − 1] + v[i]))
fcaso
ffun

Y ahora completamos la definición de iidp con la precondición y la postcondición:


{Q ≡ 0 ≤ i ≤ N ∧ u = ∃ α ∈ {i + 1· · N } . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}
fun iidp (v : vector[1 · · N ] de ent; i : nat; u : bool) dev (b : bool) ≡
caso i ≤ 3 → b = u
dc i > 3 → b = iidp(v, i − 1, (u ∨ v[i − 3] + v[i − 2] = v[i − 1] + v[i]))
fcaso
ffun
{R ≡ b = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}
83

9.2.2. Transformación a un programa iterativo

Ahora al tratar ya con una función recursiva final, el paso de esta función a un programa iterativo es
inmediato, sin más que seguir lo expuesto en [Peña93, 3.6 Transformación recursivo-iterativo, pag(s) 87 y
ss.] o [Peña97, 3.6 Transformación recursivo-iterativo, pag(s) 100 y ss.]. Véamoslo en detalle:

En primer lugar, se trata de obtener una función que responda al esquema iterativo siguiente:
{Q}
fun dp−it (v : vector[1 · · N ] de ent) dev (b : bool) ≡
Inic;
mientras B hacer {P }; {cota}
restablecer;
avanzar
fmientras
T erm;
dev b
ffun
{R}

Nótese que el número de parámetros es menor que el de la función recursiva final que vamos a convertir
en iterativa. Esto es ası́ porque en la función iterativa usaremos variables locales para realizar el trabajo de
aquellos parámetros. De hecho, los parámetros, y, por ende, la especificación, son los mismos que los de la
inicial de la función dp (véase la sección 8.3.1 en la página 60):

{Q ≡ cierto}
fun dp−it (v : vector[1 · · N ] de ent) dev (b : bool) ≡
var i : nat; u : bool
fvar
Inic;
mientras B hacer {P }; {cota}
restablecer;
avanzar
fmientras
T erm;
dev b
ffun
{R ≡ b = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}

La inicialización la obtendremos a partir de la llamada inicial de la función iidp, esto es, iidp(v, N, falso).
Dado que, en tal llamada, ambos parámetros reciben valor “a la vez”, la asignación se puede realizar en
paralelo (no es necesario que sea secuencial):

{Inic ≡< i, u >:=< N, falso >}

La terminación se obtiene del valor que se devuelve en el caso trivial, esto es:
84

{Term ≡ b := u}

A falta del bucle, tenemos el siguiente código:


{Q ≡ cierto}
fun dp−it (v : vector[1 · · N ] de ent) dev (b : bool) ≡
var i : nat; u : bool;
fvar
< i, u >:=< N, falso >;
mientras B hacer {P }; {cota}
restablecer;
avanzar
fmientras
b := u;
dev b
ffun
{R ≡ b = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}

El invariante del bucle coincidirá con la precondición (reforzada) de la función recursiva final. La condición
de terminación del bucle se pueden obtener mediante inspección de la protección del caso recursivo de la
alternativa, {Bnt ≡i¿3}. Dicha protección nos dará, directamente, la del bucle. Para obtener la cota, sólo
tenemos que fijarnos en la forma de decrecimiento de los datos en la llamada recursiva (i −→ i − 1) ası́ como
en la protección del caso trivial, que marcará el lı́mite de la función (la cota debe decrecer a cada vuelta
del bucle, pero no debe llegar a hacerse negativa). En este caso, dado que el parámetro i decrece a cada
llamada recursiva y que la protección del caso trivial es {Bt ≡ i ≤ 3}, la cota puede definirse sobre el valor
de la variable i, que se comporta como su parámetro homóniomo de la función iidp. Ası́, la función con el
esqueleto de bucle y su documentación queda como sigue:
{Q ≡ cierto}
fun dp−it (v : vector[1 · · N ] de ent) dev (b : bool) ≡
var i : nat; u : bool;
fvar
< i, u >:=< N, falso >;
mientras i > 3 hacer
{P ≡ 0 ≤ i ≤ N ∧ u = ∃ α ∈ {i + 1· · N } . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}
{cota ≡ t(i) = i}
restablecer;
avanzar
fmientras
b := u;
dev b
ffun
{R ≡ b = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}

Por último, nos queda hacer avanzar el bucle y restablecer adecuadamente el invariante a cada vuelta del
mismo. Para avanzar, nos fijaremos en el decrecimiento del tamaño de los datos en el preorden bien fundado
85

elegido o, lo que es equivalente, en la función de cota. Ası́, será:

{avanzar ≡ i := i + 1; }

En cuanto a restablecer, basta con examinar cómo varı́a el resto de parámetros distintos del que se usa para
avanzar. En este caso, sólo hay uno, u, cuyo valor para la nueva llamada recursiva será el que deba tomar
la variable homónima en el algoritmo iterativo para restablecer la invarianza. Ası́, será:

{restablecer ≡ u := (u ∨ (v[i − 3] + v[i − 2] = v[i − 1] + v[i]))}

El código completo es el siguiente:


{Q ≡ cierto}
fun dp−it (v : vector[1 · · N ] de ent) dev (b : bool) ≡
var i : nat; u : bool;
fvar
< i, u >:=< N, falso >;
mientras i > 3 hacer
{P ≡ 0 ≤ i ≤ N ∧ u = ∃ α ∈ {i + 1· · N } . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}
{cota ≡ t(i) = i}
u := (u ∨ (v[i − 3] + v[i − 2] = v[i − 1] + v[i]));
i := i + 1;
fmientras
b := u;
dev b
ffun
{R ≡ b = ∃ α ∈ {4· · i} . (v[α − 3] + v[α − 2] = v[α − 1] + v[α])}
Capı́tulo 10

Derivación de algoritmos iterativos

En este capı́tulo se acomete el desarrollo directo (es decir, a partir de la especificación) de algoritmos
iterativos, ası́ como la verificación formal de su corrección.

10.1. El Último Esferulado

Derivaremos un algoritmo iterativo para el enunciado que aparece en el apartado 8.1

Para la derivación directa vamos a seguir las guias del diseño formal de programas iterativos tal y como
aparecen en [Peña93, 4.3 Derivación formal de programas imperativos, pag(s) 114]

10.1.1. Especificación

Partimos de la especificación de la función:

{Q ≡ cierto}
fun idesf i (a : vector[1 · · N ] de ent) dev (b : bool)
PN Pβ
{R ≡ b = α=1 a[α] = 0 ∧ ∀ β ∈ {1· · N } . ( α=1 a[α]) ≥ 0}

Vamos a intentar construir un algoritmo simple de un bucle según un esquema como el que aparece en la
figura 10.1.

{Q}
fun desf i − ite (a : vector[1 · · N ] de ent) dev (b : bool) ≡
Inicializacion;
mientras B hacer
{P }
S;
fmientras
{R0 }
S0;
ffun
PN Pβ
{R ≡ b = α=1 a[α] = 0 ∧ ∀ β ∈ {1· · N } . ( α=1 a[α]) ≥ 0}

Figura 10.1: Esquema iterativo para el esferulado

86
87

Para la formación de invariantes se seguirán los pasos recomendados en los textos de la asignatura, por
ejemplo mediante el debilitamiento de la precondición. Una vez que hayamos obtenido el invariante del
bucle pasaremos a derivar el bucle a partir del invariante. La postcondición del bucle no coincide con la
postcondición de la función ya que en el enunciado se nos exige que solamente la última suma parcial sea
cero y por tanto esta restricción no debe ocurrir dentro del bucle.

10.1.1.1. Obtención del Invariante

Debilitamos la postcondición: Introducimos la variable i y para i = N tenemos la postcondición expresada


como:

i
X β
X
b= a[α] = 0 ∧ (∀ β ∈ {1· · i} . ( a[α]) ≥ 0) ∧ (i = N )
α=1 α=1
| {z }
[1]

El aserto [1] no podrá verificarse dentro del bucle ya que impone una restricción demasiado fuerte a los
elementos del vector a. Introducimos una nueva variable c en lugar del 0 y añadimos la clausula (c = 0).
Debilitamos eliminando las clausulas (i = 0) ∧ (c = 0). Como es habitual se impone también una restricción
de dominio sobre la nueva variable i para evitar accesos ilegales al vector. De esta forma obtenemos un
posible invariante del bucle propuesto:

i
X β
X
P ≡b=( a[α] = c) ∧ (∀ β ∈ {1· · i} . ( a[α]) ≥ 0) ∧ (i ≤ N )
α=1 α=1

Por otra parte como función de cota o función limitadora proponemos t(x̄) = N − i. A cada paso del bucle
incrementamos i.

Como postcondición del bucle tendremos el caso en que i = N , con lo que:

N
X β
X
R0 ≡ b = ( a[α] = c) ∧ (∀ β ∈ {1· · N } . ( a[α]) ≥ 0)
α=1 α=1

10.1.2. Derivación del bucle

Para la derivación del bucle hay que tener en cuanta que la postcondición del bucle es R0 y no R, es decir,
no se impone que c = 0.

1. Conociendo P y R0 tenemos que hallar B tal que {P ∧ ¬B} ⇒ R0 . Sea B ≡ (i < N ) . Como (i ≤ N )
por el invariante, se deduce que si (i ≥ N ) ∧ (i ≤ N ) entonces (i = N ) y por tanto R0 .
88

2. Necesitamos unas instrucciones Inic que hagan válido {Q}Inic{P }. Estos valores iniciales son los que
hacen nulos los dominios de cuantificadores y sumatorios y son i = 0, c = 0 y b = cierto con lo que
0,0,cierto
Pi,c,b ≡ cierto
3. Pasamos a derivar S. Sabemos que S consta de dos partes: restablecer y avanzar. Dado el diseño de la
función limitadora que vimos antes, la instrucción avanzar incrementa a cada paso del bucle el valior
del ı́ndice i, por lo que:

avanzar ≡ i := i + 1

Por otra parte, avanzar destruye la invarianza del bucle, por lo que hay que buscar valores para c0 y
b0 en la expresión:

i+1
X β
X
P ≡ b0 = ( a[α] = c0 ) ∧ (∀ β ∈ {1· · i + 1} . ( a[α]) ≥ 0) ∧ (i ≤ N )
α=1 α=1
| {z }
c+a[i+1]

Desarrollando obtenemos:
i
X β
X
b0 = (c0 = c + a[i + 1]) ∧ ( a[α] = c) ∧ (∀ β ∈ {1· · i} . ( a[α]) ≥ 0) ∧
α=1 α=1
| {z }
b
i+1
X
( a[α] ≥ 0) ∧ (i ≤ N )
α=1
0
= (c = c + a[i + 1]) ∧ (b ∧ (c + a[i + 1] ≥ 0) ∧ (i ≤ N )

De la expresión anterior obtenemos la secuencia de asignaciones necesaria para restaurar la propiedad


invariante del predicado P :


c := c + a[i + 1];
restaurar ≡
b := b ∧ (c + a[i + 1]) ≥ 0;

10.1.3. Obtención de S 0

Para alcanzar R necesitamos una serie de instrucciones S 0 tales que {R’}S’{R}. Para esto basta con hacer
S ≡ (c = 0) y de esta forma R0 ∧ (c = 0) ⇒ R. Por lo que:

S 0 ≡ b := b ∧ (c = 0);

10.1.4. Diseño definitivo

El algoritmo final se describe en la figura 10.2


89

{Q ≡ cierto}
fun desf i − ite (a : vector[1 · · N ] de ent) dev (b : bool) ≡
< i, c, b >:=< 0, 0, cierto >
mientras i < N hacer
Pi Pβ
{P ≡ b = ( α=1 a[α] = c) ∧ (∀ β ∈ {1· · i} . ( α=1 a[α]) ≥ 0) ∧ (i ≤ N )}
c := c + a[i + 1];
b := b ∧ (c + a[i + 1]) ≥ 0;
i := i + 1;
fmientras
b := b ∧ (c = 0);
ffun
PN Pβ
{R ≡ b = α=1 a[α] = 0 ∧ ∀ β ∈ {1· · N } . ( α=1 a[α]) ≥ 0}

Figura 10.2: Esf erulado. Resultado de la derivación directa

10.2. El Número Perfecto

Enunciado. Un número se dice perfecto si es suma de sus divisores. Diséñese una función
iterativa completamente verificada que determine si un número es perfecto.

10.2.1. Especificación Formal

{Q ≡ n > 0}
fun perf−it
 (n :nat) dev (b : bool) 
Pn div 2
{R ≡ b = n = α=1 α | n mod α = 0 }

10.2.2. Obtención de un Invariante y una función de cota

La postcondición:

n div 2
X
{R ≡ b = (n = ( α | n mod α = 0))}
α=1

exige que b contenga el resultado de una comparación, una de cuyas partes implica un cálculo. Los resultados
intermedios de dicho cálculo no tienen por qué cumplir la condición que nos interesa determinar como
resultado del algoritmo, por lo que nuestro invariante no la reflejará. En vez de eso, el bucle realizará el
cómputo descrito que será recogido por el invariante:
90

b
X
{P ≡ m = ( α | n mod α = 0)}
α=a

donde a y b representan los extremos del intervalo. Tenemos ası́ dos posibles formas de realizar el bucle y,
por tanto, dos posibles invariantes, surgiendo cada uno de fijar un extremo y hacer móvil al otro:

i
X
{P1 ≡ m = ( α | n mod α = 0)}
α=1

n div 2
X
{P2 ≡ m = ( α | n mod α = 0)}
α=i

Dependiendo de nuestra elección variarán:

la protección del bucle.


la función de cota.
la inicialización (cuando menos la del ı́ndice que realizará el recorrido).
el cuerpo del bucle (avanzar y restaurar)

En el posterior desarrollo utilizaremos la primera de las variantes, quedando como ejercicio para el lector el
desarrollo y verificación del algoritmo resultante de utilizar la segunda.

Al introducir la variable i en el invariante debemos imponer unas condiciones de dominio para ella, con lo
cual el invariante queda:

i
X
{P ≡ m = ( α | n mod α = 0) ∧ 1 ≤ i ≤ n div 2}
α=1

Como función de cota podemos proponer t(i) = n div 2 − i + 1, ya que i será el ı́ndice que haga avanzar al
bucle y éste se halla limitado por n div 2 − i, con lo que garantizamos que la función limitadora sea siempre
positiva.

La situación está descrita por la función que aparece en la figura 10.3.

Como R0 tendremos, cuando i = n div 2:

n div 2
X
{R’ ≡ m = ( α | n mod α = 0)}
α=1

aserto a partir del que derivaremos el bucle, para posteriormente buscar un conjunto de instrucciones que
nos permitan establecer la postcondición a partir de R0 .
91

{Q}
fun perf−it (n : nat) dev (b : bool) ≡
Inic
mientras ¬B hacer
Pi
{P ≡ m = ( α=1 α | n mod α = 0) ∧ 1 ≤ i ≤ n div 2}
{cota : t(i) = n div 2 − i + 1}
S
fmientras
{R0 }
S0
dev b
ffun
{R}

Figura 10.3: Forma abstracta de la función perf−it

10.2.3. Obtención de la Protección del Bucle

Se trata de determinar B de forma que {P ∧ B} ⇒ R0 .


Pi 
{P ≡ s = ( α=1 α | n mod α = 0) ∧ 1 ≤ i ≤ n div 2} 
| {z } 
[†] ⇒ ¬B ≡ (i = n div 2)
0
Pn div 2  | {z }
{R ≡ s = ( α=1 α | n mod α = 0)

[‡]

[†] ∧ [‡] ⇒ B ≡ i < n div 2

10.2.4. Obtención de la Inicialización

Se trata de determinar unas instrucciones Inic tales que {Q}Inic{P }. Habitualmente esos valores, como en
los casos directos de los algoritmos recursivos, harán vacı́os los rangos de los cuantificadores. En este caso,
necesitamos hacer i = 0, lo que implica s = 0 (elemento neutro de la suma). Como b no interviene en el
bucle no necesitamos inicializarla aquı́.

10.2.5. Obtención del Cuerpo del Bucle

Consiste en derivar un conjunto de instrucciones tales que {P ∧ ¬B}S{P }.

El bucle constará de dos tipos de instrucciones, aquellas que hacen avanzar el cálculo, y las que se ocupan
de restaurar la invarianza rota al avanzar, como se ve en la figura 10.4

De la función de cota, t(i) = n div 2 − i + 1, y del invariante deducimos que avanzar habrá de ser avanzar ≡
i := i + 1.

Ahora tenemos, llamando m0 al nuevo valor de m para evitar confusiones:


92

mientras ¬B hacer
{P }{cota}
restaurar;
avanzar
fmientras

Figura 10.4: Forma general de un bucle

Pi+1
{Pii+1 ≡ m0 = ( α=1 α | n mod α = 0) ∧ 1 ≤ i + 1 ≤ n div 2} De ¬B ≡ i < n div 2 deducimos que podemos
| {z }
[10.2.5]
realizar de manera segura la operación [10.2.5] que incrementa el ı́ndice.

Sin embargo, tal operación


Pi+1 destruye la invarianza, ya que en m tenı́amos
Pi el valor hasta i. Para restaurarla
necesitamos que m0 = α=1 · · ·, para lo cual, partiendo de m = α=1 · · ·, sólo nos queda incorporar el
elemento i + 1-ésimo. Como en el invariante existe una condición para la suma, tendremos que proteger
la asignación adecuada con una instrucción condicional, resultando entonces restaurar como muestra la
figura 10.5.

caso n mod (i + 1) = 0 → s := s + (i + 1)
dc n mod (i + 1) 6= 0 → seguir
fcaso

Figura 10.5: restaurar

Nótese que ambas protecciones son alternativas, o lo que es igual, que n > 0} ⇒ ((n mod (i + 1) = 0) ∨
| {z
Q(x̄)
(n mod (i + 1) 6= 0))

10.2.6. Finalización del Algoritmo

Necesitamos obtener S 0 tal que {R0 }S 0 {R}.


Pn div 2 )
{R0 ≡ m = ( α=1 n | α = 0)}
Pn div 2 ⇒ S 0 ≡ b := (n = m)
{R ≡ b = (n = (m = ( α=1 n | α = 0)))}

De esta forma, el algoritmo definitivo, diseñado de manera que ya está verificado, es el que aparece en la
figura 10.6.

Queda como ejercicio para el lector el cálculo del coste del algoritmo obtenido.
93

{Q ≡ n > 0}
fun perf−it (n : nat) dev (b : bool) ≡
var i, m : natfvar;
hi, mi := h0, 0i;
mientras i < n div 2 hacer {P ≡ m = (Σ (α ∈ {1· · i} ∧ (n mod α = 0)) . α)}
{cota ≡ t(i) = n div 2 − i + 1}
caso n mod (i + 1) = 0 → m := m + i + 1
dc n mod (i + 1) 6= 0 → seguir
fcaso;
i := i + 1
fmientras;
b := (m = n);
dev b
ffun
{R ≡ b = (n = (Σ (α ∈ {1· · n div 2} ∧ (n mod α = 0)) . α))}

Figura 10.6: perf−it

10.3. La fábrica

Enunciado: En una fábrica cada empleado debe fichar al entrar y al salir, cobrando una
cantidad fija por cada hora trabajada. Para cada trabajador se tiene la siguiente estructura:
h:vector [1..n,1.,2] de nat, donde h[i, 1] representa la hora de entrada del trabajdor en el
dı́a i-ésimo y h[i, 2] la hora de salida de dicho trabajdor ese mismo dı́a.
Se pide derivar un programa iterativo que obtenga el total de horas trabajadas para un
trabajdor determinado.

10.3.1. Especificación formal

La especificación es la dada en la sección 8.4.1.

{Q ≡ cierto}
fun f abrica (h : vector [1 · · n, 1 · · 2] de nat) dev (s : nat)
{R ≡ s = Σ α ∈ {1· · n} . h[α, 2] − h[α, 1]}
94

10.3.2. Derivación de una función iterativa que satisfaga la especificación

El esquema general de derivación es el siguiente:

Inic;
mientras ¬B hacer
{P }{cota}
restablecer;
avanzar;
fmientras

Para hallar el invariante contamos con la información de la postcondición y de las condiciones que debe
cumplir un bucle correctamente verificado. Puesto que debe cumplirse P ∧ ¬B ⇒ R quiere decir que ¬B es
una condición bajo la cual es posible deducir R a partir de P , por lo que P puede ser una versión debilitada
de R.

A partir de la expresión:

n
X
R≡s= (h[α, 2] − h[α, 1])
α=1

debilitando R mediante la sustitución de la constante n por una variable libre i nos queda:

i
X
R ≡ s= (h[α, 2] − h[α, 1]) ∧ (i = n)
α=1

de donde podemos proponer como invariante a la siguiente expresión:

i
X
P ≡ s= (h[α, 2] − h[α, 1])
α=1

D esta manera ¬B ≡ (i = n) y cuando se cumpla ¬B tendremos restablecida la postcondición, ya que de


hecho P ∧ ¬B = R. En estas condiciones B ≡ ¬(i = n), o lo que es lo mismo i 6= n.

Es necesario también imponer algunas restricciones al rango de la nueva variable i, ya que los valores no
pueden exceder la definición del rango del vector, por lo que i ≤ n. Además, al ser i natural tenemos también
que 0 ≤ i. Como consecuencia de lo anterior, de 0 ≤ i ≤ n se deduce que i 6= n ∧ i ≤ n ⇒ i < n con lo que
B ≡ (i < n). Por tanto, el invariante quedará como sigue:
Pi
{P ≡ 0 ≤ i ≤ n ∧ s = α=1 h[α, 2] − h[α, 1]}
95

10.3.3. Inicialización del bucle

Necesitamos un conjunto de instrucciones sencillas (habitualmente asignaciones) que permitan establecer el


Pi
invariante antes de comenzar el bucle. Como P ≡ 0 ≤ i ≤ n ∧ s = α=1 (h[α, 2] − h[α, 1]) tenemos una
forma simple de hacerlo aprovechando la variable i para anular el rango del cuantificador, ası́, si asignamos
i = 0 necesitarı́amos también s = 0 puesto que ése es el elemento neutro de la suma. La inicialización queda
pues:

< i, s >:=< 0, 0 >

0,0
Comprobamos ahora que Q ⇒ Pi,s y efectivamente:

0
X
0,0
Pi,s ≡0≤0≤n∧0= (h[α, 2] − h[α, 1])
α=1

por ser vacı́o el rango del sumatorio (por supuesto, n ≥ 0, ya que n ∈ IN ).

10.3.4. Cuerpo del bucle

10.3.4.1. avanzar

Necesitamos una forma de conseguir que i, que comienza valiendo 0, alcance la condición i ≥ n ≡ ¬B. Como
se trata de un vector cuyas componentes han de analizarse parece razonable probar con:

def
avanzar = i := i + 1

Como antes, calculamos el efecto sobre el invariante de dicha instrucción. Llamaremos T al estado de las
variables del bucle entre las instrucciones de restablecer y avanzar.

i+1
X
T ≡ Pii+1 ≡ s = 0 ≤ i + 1 ≤ n ∧ (h[α, 2] − h[α, 1])
α=1

10.3.4.2. restablecer

Si comparamos T con P observamos que1 :


1 Renombramos s con s0 para evitar confusiones
96

i+1
X
T ≡ 0 ≤ i + 1 ≤ n ∧ s0 = (h[α, 2] − h[α, 1])
α=1
i
X
P ≡0≤i≤n∧s= (h[α, 2] − h[α, 1])
α=1

ası́ pues , si estudiamos la diferencia entre los parámetros s0 y s tenemos que

i+1
X i
X
s0 − s = (h[α, 2] − h[α, 1]) − (h[α, 2] − h[α, 1])
α=1 α=1
= (h[i + 1, 2] − h[i + 1, 1])

que es la expresión que falta para restablecer la invarianza del bucle (ya que la protección del mismo, i < n,
garantiza que al avanzar se mantenga el primer conjuntante: i < n ⇒ 0 ≤ i + 1 ≤ n).

def
restablecer = s := s + h[i + 1, 2] − h[i + 1, 1]

10.3.5. Verificación

Comprobamos ahora la invarianza de P al ejecutarse el cuerpo del bucle:

?
P ∧ B =⇒Tss+h[i+1,2]−h[i+1,1]

donde

i+1
X
Tss+h[i+1,2]−h[i+1,1] ≡ 0 ≤ i + 1 ≤ n ∧ s + h[i + 1, 2] − h[i + 1, 1] = (h[α, 2] − h[α, 1])
α=1
i
X
⇐= 0≤i≤n∧s= (h[α, 2] − h[α, 1]) ∧ i < n
α=1
≡ P ∧B

De esta forma el programa queda:


97

{Q ≡ cierto}
fun f abrica−it (h : vector [1 · · N, 1 · · 2] de nat) dev (s : nat) ≡
var i : nat fvar
< i, s >:=< 0, 0 >
mientras i < n hacer
Pi
{P ≡ 0 ≤ i ≤ n ∧ α=1 (h[α, 2] − h[α, 1])}{t = n − i}
s := s + h[i + 1, 2] − h[i + 1, 1];
i := i + 1;
fmientras
dev s
ffun
{R ≡ s = Σ α ∈ {1· · n} . (h[α, 2] − h[α, 1])}

10.3.5.1. Demostración de la terminación del programa

Se ha propuesto como función limitadora t = n − i. Hay que demostrar que la función limitadora es siempre
no negativa y que decrece con cada vuelta del bucle:

P ∧B ⇒t≥0

i
X
0≤i≤n∧s= (h[α, 2] − h[α, 1]) ∧ i > n ⇒ n − i ≥ 0
α=1

{P ∧ B ∧ t = t0 }S{t < t0 }
Hay que demostrar que

{i < n ∧ n − i = t0 }S{t < t0 }

Como i0 = i + 1, tendremos t = n − i0 ⇒ t = n − (i + 1), con lo que i < n ⇒ n − i + 1 ≥ 0 es decir, la


operación está bien definida y además (por construcción) t < t0 .

10.3.5.2. Cálculo del coste

El contenido del bucle consta de dos asignaciones simples de orden O(1). El bucle se repite para valores
entre 0 y n, por lo que se itera n veces, por lo que aplicando la regla del producto tenemos que T (n) ∈
O(1) × O(n) ⇒ T (n) ∈ O(n)
98
Bibliografı́a

[Balc93] J.L. Balcázar. Programación Metódica. McGraw-Hill, 1993.


[CCM+ 93] J. Castro, F. Cucker, X. Messeguer, A. Rubio, L. Solano y B. Valles. Curso de Programación.
McGraw-Hill, 1993.
[Col99] J.I. Mayorga Toledano , M. Rodrı́guez Artacho y F. López Ostenero Colección de problemas
de Programación II (este volumen) . 2003.
[Peña93] R. Peña. Diseño de Programas: formalismo y abstracción. Prentice Hall, 1993.
[Peña97] R. Peña. Diseño de Programas: formalismo y abstracción, 2a Edición . Prentice Hall, 1997.

99
100
Índice de figuras

8.1. esf erulado: función inicial utilizando una función inmersora . . . . . . . . . . . . . . . . . . . 53

8.2. Esf erulado, diseño definitivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

8.3. Esf erulado. Instrucción Alternativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

8.4. Esf erulado. Versión definitiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

8.5. Función imonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

8.6. Árbol sintáctico de la función ifunciona . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

9.1. Función Esf erulado. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

9.2. Esf erulado iterativo: versión final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

10.1. Esquema iterativo para el esferulado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

10.2. Esf erulado. Resultado de la derivación directa . . . . . . . . . . . . . . . . . . . . . . . . . . 89

10.3. Forma abstracta de la función perf−it . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

10.4. Forma general de un bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

10.5. restaurar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

10.6. perf−it . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

101

Você também pode gostar