Escolar Documentos
Profissional Documentos
Cultura Documentos
de Programación II
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
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
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:
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
Lógica y Especificación
Enunciado. Expresar mediante la lógica de predicados los siguientes enunciados en lenguaje natural:
Todas las sumas de elementos consecutivos del vector a empezando por el primero dan como resultado
un número par.
∀ x ∈ Q . x mod 2 6= 0
p = Πiα=1 a[α]
{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.
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)
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)
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)
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.
3. K ⇒ J; L ⇒ J; M ⇒ L, M ⇒ K, M ⇒ J;
14
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.
2. {E2 ≡ x ∈ IN · x < y! ∧ y ∈ IN }
4. {E 4 ≡ (x, y) ∈ ZZ × ZZ | x + y = z ∧ z ∈ ZZ}
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.
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)}
Recursividad
2.1. Inducción
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 .
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 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:
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
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,
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
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:
23
24
{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
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
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 ).
{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
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 )
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
4.3 : A
4.4 : B
Capı́tulo 5
Especificación de Algoritmos
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}
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
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.
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
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
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.
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.
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)} . β − α}
5.4 : A
5.5 : C
5.6 : C
5.7 : A
5.8 : A
5.9 : A1
5.10 : C
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
{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).
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:
Problemas propuestos
{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)}
A. {a < b}
B. {m = mcd(a, b)}
C. {a > 0 ∧ b > 0}
D. {a > 0 ∧ b ≥ 0}
{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}
{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}
{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]}
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
{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 } ≡
x:=x−p
{xy + pE = N } ≡
{(x − p)y + pE = N } ≡
{xy − py + pE) = N } ≡
{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:
si p>0
{pq = p(E − y)} =⇒
{q = E − y} =⇒
{E = q + y}
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]
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:
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
[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:
Problemas propuestos
{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
{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)}
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
8.1. El Esferulado
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.
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
Q ∧ (i = N ) ∧ (c = 0) ≡ R
Caso Recursivo: El caso recursivo se verifica cuando i < N , en tal caso tenemos que:
donde:
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
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:
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
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 .
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
Pβ
(∀ β ∈ {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
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
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.
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 − α])}
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.]:
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
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).
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
{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[α]))}
Con lo cual:
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[α])}
Q(x̄) ∧ Bt (x̄) ≡ (0 ≤ i ≤ N ) ∧ (i ≤ 3)
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 .
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
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])}
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])}
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)
{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])}
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
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
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:
w0 = h[i, 2] − h[i, 1] + w
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
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
n
X
Q≡0≤i≤n∧w = (h[α, 2] − h[α, 1])
α=i+1
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
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 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.
{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)}
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.
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)}
nf unciona(v) = if unciona(v, n)
69
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
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
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̄).
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
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
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
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)
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:
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
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:
{Q ≡ cierto}
fun perf ecto (n : nat) dev (b : bool)
{R ≡ b = (n = Σ (α ∈ {1· · n − 1} ∧ ((n mod α) = 0)) . α)}
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.)
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).
Para generalizarla, substituiremos una constante por una variable, por ejemplo, n − 1 por i:
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
{Q ≡ 0 ≤ i ≤ n − 1}
fun isumadiv (n, i : nat) dev (s : nat)
{R ≡ s = Σ (α ∈ {1· · i} ∧ ((n mod α) = 0)) . α}
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).
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,
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.
i=0 0
i > 0 i divide a n i + isumadiv(n − i − 1)
i no divide a n isumadiv(n, i − 1)
{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)) . α}
Bt (x) ≡ i = 0
Bt (x) ∨ Bnt (x) ≡ i = 0 ∨ i > 0
Bnt (x) ≡ i > 0
Q(x) ≡ n > 0 ∧ 0 ≤ i ≤ n − 1
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.
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
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
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
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
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.
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}
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:
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.
{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}
{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[α])}
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
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
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):
La terminación se obtiene del valor que se devuelve en el caso trivial, esto es:
84
{Term ≡ b := u}
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
{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á:
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.
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
{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}
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.
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.
N
X β
X
R0 ≡ b = ( a[α] = c) ∧ (∀ β ∈ {1· · N } . ( a[α]) ≥ 0)
α=1 α=1
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 )
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);
{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}
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.
{Q ≡ n > 0}
fun perf−it
(n :nat) dev (b : bool)
Pn div 2
{R ≡ b = n = α=1 α | n mod α = 0 }
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
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.
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}
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ı́.
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.
mientras ¬B hacer
{P }{cota}
restaurar;
avanzar
fmientras
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.
caso n mod (i + 1) = 0 → s := s + (i + 1)
dc n mod (i + 1) 6= 0 → seguir
fcaso
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))
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)) . α))}
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.
{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
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
i
X
P ≡ s= (h[α, 2] − h[α, 1])
α=1
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
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
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
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
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
?
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
{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])}
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
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
99
100
Índice de figuras
10.5. restaurar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
10.6. perf−it . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
101