Вы находитесь на странице: 1из 105

25905053.

doc

Erratas y Escolios
PeDos

Recensión de los cuatro primeros capítulos de


Diseño de Programas
Formalismo y Abstracción
de Ricardo Peña Marí

Roberto Alonso Menlle


Alumno del C.A de la UNED en Ceuta.
r.factorial@terra.es

Está expresamente permitida la reproducción total o parcial de esta obra y su tratamiento o


transmisión por cualquier medio o método y su inclusión, total o parcial, en cualquier otro tipo de
documento siempre y cuando el producto intermedio o final, físico o virtual, siga siendo objeto de
distribución libre y gratuita y sin más trámite que la cortesía habitual de citar la fuente... o sin dicha
cortesía.

Este documento está preparado para imprimir a dos caras. Para imprimir a una sola: Archivo /
Configurar Página... / Diseño de página y desmarcar la casilla 'Pares e impares diferentes'
Si se modifica el texto, suprimiendo o añadiendo líneas, conviene seleccionar todo (<Ctrl> <e>); picar
con el botón secundario y seleccionar 'Actualizar campos'. Es posible entonces que aparezcan frases
resaltadas como 'error, no se encuentra el origen de la referencia'. Bastará marcar el aviso y
suprimirlo.

Salto de página.../...

1
25905053.doc

... la presentación de las matemáticas debe estar tan libre de excesos de rutina
como del dogmatismo prohibitivo que rehuye revelar el motivo o la meta
y que constituye así un obstáculo de mala fe para un esfuerzo honesto

[En el estudio de las matemáticas] ahora más que nunca


existe el peligro de frustración y desilusión.

Del prologo de ¿Qué es la matemática?


Richard Courant
Herbert Robbins
Profesores de Matemáticas de la Universidad de Nueva York

Salto de página.../...

2
25905053.doc

ÍNDICE
ERRATAS enlace al escolio correspondiente completo.
Algunas, más que erratas, redacciones que dan lugar a interpretación errónea.
Pag 8. Asimetría entre  (f(n)) y 
(f(n)). Primer párrafo después de definición 1.2 24

Pag 8. Definición 1.3. (f(n)) 26


Pag 9. Definición 1.5. Producto de órdenes 27
Pag 18. Recuadro 1.3. Reducción por sustracción; cálculo del orden del coste 29
Pag 19. Recuadro 1.4. Reducción por división; función recurrente de coste 31
Pag 20. Recuadro 1.5. Reducción por división; cálculo del orden del coste 32
Pag 33. Semántica de predicados y definición 2.4. 39
Pag 42. Debilitamiento predicado por sustitución de constante por variable 42
Pag 62. Líneas 14 y ss. Finales más eficientes que no finales 59

ESCOLIOS GENERALES 6

ESCOLIOS PARTICULARES
ÍNDICE TEMA 1. La eficiencia de los algoritmos 8
Pag 3. Tamaño de los datos de entrada 8
Pag 3. Casos mejor, promedio y peor 8
Pag 5. Punto 1.2. Medidas asintóticas 8
Pag 6. Definición 1.1. (f(n)) e introducción a los Órdenes 9
Apéndices al escolio de introducción a los órdenes 16
Pag 7. Ejercicio 1.1. sii 18
Pag 7. Ejercicio 1.2. Comparación de órdenes por limite de cocientes 18
Pag 7. Ejercicio 1.3. Jerarquía de órdenes de complejidad 20
Pag 8. Definición 1.2. (f(n)) 24
Pag 8. Asimetría entre (f(n)) y (f(n)). Primer párrafo después de definición 1.2 24
Pag 8. Definición 1.3. (f(n)) 26
Pag 9. Definición 1.5. Producto de órdenes 27
Pag 9. Ejercicio 1.6. Regla de la suma 27
Pag 11. Figura 1.1 27
Pag 13 a 15. Reglas prácticas cálculo de la eficiencia 27
Pag 16. Punto 1.5. Resolución recurrencias coste 27
Pag 17. Recuadro 1.2. Reducción por sustracción; función recurrente de coste 28
Pag 18. Recuadro 1.3. Reducción por sustracción; cálculo del orden del coste 29
Pag 19. Recuadro 1.4. Reducción por división; función recurrente de coste 31
Pag 20. Recuadro 1.5. Reducción por división; cálculo del orden del coste 32

ÍNDICE TEMA 2. Especificación de problemas 34


Pag 28. Ejemplo de especificación 34
Pags 29 a 40. Escolios de Lógica Matemática 34
Pag 32. Variables ligadas 38
Pag 32. Convenio de precedencia de operadores 38
Pag 33. Semántica de predicados y definición 2.4. 39
Pag 34. Primer párrafo. Símbolo i 41
Pag 34. Definición 2.5. Cambio de variable en  41
Pag 35. Definición 2.6. Predicado bien definido 42
Pag 35. [P]. Cuarta línea desde el final 42
Pag 37. Definiciones 2.8 a 2.11 42
Pag 41. Definición 2.12. estados(P) 42
Pag 42. Debilitamiento predicado por sustitución de constante por variable 42
Pag 43. Segundo párrafo 43
Pag 43. Dominio vacío de un cuantificador 43
Pag 45. Definición 2.14. Sustitución textual 45

ÍNDICE TEMA 3. Diseño recursivo 47


Pag 55. Introducción recursividad. Un ejemplo hablado 47
Pag 60. Introducción recursividad. Función sum, recursiva lineal no final 48
Pag 60. Introducción recursividad. Función sumF, recursiva lineal final 51
Pag 61. Introducción recursividad. Una función no lineal 54
Pag 61. Figura 3.2. Terminología de funciones recursivas 56
Pag 62. Líneas 14 y ss. Finales más eficientes que no finales 59
Pag 63. Punto 3.2. x
DT1.Q(x)R(x,f(x)) 61
Pag 64 y ss. Preórdenes; pbf 61

3
25905053.doc

Pag 67. Teorema 3.2 Principio de inducción noetheriana 69


Pag 69. Análisis por casos y composición. 72
Pag 71. Tabla 3.2. Verificación de funciones recursivas lineales 72
Apéndice a tabla 3.2. Ejemplo de verificación de recursiva lineal final. 79
Pag 81. Técnicas de inmersión 81
Pag 85. Inmersión final 82
Pag 94. Desplegado y plegado 82
Apéndice a pag 94. Desplegado y plegado. Postcondición no constante 87
Pag 101. Figura 3.14. Transformación de recursiva final a iterativa 88
Pag 101. Expresión 3.19 93
Pag 102. Figura 3.15. Transformación de recursiva no final a iterativa. 93
Nota final sobre los diferentes ejemplos usados 95

ÍNDICE TEMA 4. Diseño iterativo 97


Pag 112 y siguientes. Derivación de iterativas 97
Pag 121. Definición 4.1. Invariante 97
Pag 125. Figura 4.1. Esquema de programa iterativo 100
Pag 126. Figura 4.2. Verificación de funciones iterativas 101
Pag 132. Derivación de bucles a partir de invariantes 102
Pag 133. Derivación del invariante a partir de R 102

Error: Reference source not found 103

ÍNDICE DE FIGURAS
Tema 1
Introducción a los órdenes. Funciones cuadráticas Figura 1 Figura 2 11
Funciones cuadráticas acotadas superiormente Figura 3 Figura 4 12
Varias funciones cota para una única acotada Figura 5 13
Haces de funciones cuadráticas y cúbicas Figura 6 13
Funciones de órdenes infinitesimales Figura 7 18
Intratabilidad de funciones exponenciales y factoriales Figura 8 Figura 9 Figura 21
10
Comparación órdenes logarítmicos / No asimetría entre ϑ (f(n)) y Ω (f(n)) Figura 25
11
Escasa influencia de la base en problemas intratables Figura 12 31
Tema 3
Ejemplo de recursión lineal. Función sum Mapa 1 recursión 50
Ejemplo de recursión lineal final. Función sumF Mapa 2 recursión 53
Ejemplo de recursión multiple. Función suma Mapa 3 recursión 55
Comparativa tiempos: no final, final e iterativo 60
Ejemplo pbf tamaños problema función lineal reducción 1 Mapa 1 tamaños pbf 62
Ejemplo pbf tamaños problema función lineal reducción 2 Mapa 2 tamaños pbf 63
Ejemplo pbf tamaños problema función no lineal Mapa 3 tamaños pbf 65
Tema 4
Tabla evolución de un bucle y su invariante 99

Salto de página.../...

4
25905053.doc

Supongo que después de tantos añadidos y modificaciones sobre el original, se podría reescribir todo
el material con una nueva ordenación más lógica y accesible. A pesar de ello, mantengo la ordenación
inicial en la que da entrada a cada escolio la localización de la página comentada de Peña. Esto es,
este documento sólo contiene aclaraciones a algunas partes del libro de texto, siendo imprescindible
leer primero el párrafo original de Peña.

En bastantes casos, hago uso de conceptos y definiciones que todavían no han aparecido a esas alturas
del libro... consecuencia lógica de los muchos repasos y añadidos. Destaco en gris las operaciones y/o
modificaciones más importantes que conviene resaltar en determinados pasos.

Todas las referencias a ‘Peña #’ indican una página del libro comentado.

Todas las referencias a CD se refieren a la Colección de Problemas de Programación 2, del equipo


docente de la asignatura y editado en el CD-ROM del curso 1999/2000 (en el CD del curso
2001/2002, las numeraciones son coincidentes en los pocos casos que comprobé).

Todas las referencias similares a ExP2_1997_1sem_C, lo son a alguno de los exámenes


compretamente desarrollados que también colgué en este foro y que son apéndice de este
documento...o estos escolios apéndice de los ExPeDos. Con ese nombre, por ejemplo, se trataría del
examen de 1997, 1ª semana tipo C. Los exámenes corespondientes a Septiembre Original tipo X o
Septiembre Reserva llevan terminaciones SepO_X y SepR, respectivamente.

Gracias a los siguientes compañeros por haber aportado ideas o señalado errores en los Escolios y/o
ExPeDos: Blanca Pérez Simón, Juan Carlos (mrcaos), Julián Palermo, Jesús (JeRo), Susana Gómez
Castro, María Jesús (MJV), Iria (gallufa), Félix (fix), José Luis Aguado y a alguno más que creo me
dejo en el teclado por no haber apuntado sus nombres. Gracias también a mis compañeros de trabajo
Irene Menchén Benítez, por resolver muchas pequeñas e intempestivas dudas matemáticas y Clemente
de Blas Ortega, informático, por revisar parte del material. Por último, gracias al profesor Antonio
Alonso Núñez por su mucha paciencia al teléfono. Nombrando a tanta gente no absoluto pretendo
difuminar responsabilidades o aparentar un sólido respaldo teórico o académico. Todas las posibles
erratas, errores u horrores que aparezcan serán únicamente imputables a mi descuido, temeridad o
ignorancia. Muy especialmente en los dos últimos temas, que en su práctica totalidad, no han pasado
más revisión que las sucesivas reescrituras que sufrieron.

Ceuta.
Curso 2001/2002
Salto de página.../...

5
25905053.doc

ÍNDICE

ESCOLIOS GENERALES
1. Prerrequisitos.
Según CD, se supone que el libro es autosuficiente para el estudio de la asignatura. Se supone...
aunque yo supongo que sería un suicicio académico (suspenso seguro) no cursar simultáneamente o
con anterioridad Lógica Matemática, a no ser que se tenga muy buena base con lógica de predicados...
que se consigue estudiando Lógica Matemática.
Además, casi todo el epígrafe 2.2 del libro es mejor estudiarlo por 'Fundamentos de Lógica
Matemática' (J. Aranda et alii. ed. Sanz y Torres), y en concreto, de ese epígrafe 2.2, las páginas 36 a
40, ni mirarlas.

2. Orden de estudio.
El orden que sigue el libro es bastante confuso y creo que contrario al sentido común.
Aunque cada cual tendrá sus propias preferencias, me parece bastante más lógico seguir el siguiente
plan de estudio:
Realizar una una primera lectura (relativamente) ligera, omitiendo los temas 1 y 4 completos, así
como algunos epígrafes sueltos que resulten demasiado difíciles (¡sin descartar todos!). La inmensa
mayoría de los conceptos no se aprehederan, pero algunas ideas y la notación irán dejando poso.
Una segunda aún no definitiva, pero a fondo de verdad, realizando algunos problemas del libro y del
CD, con el siguiente orden (por epígrafes del libro):
3.1 a 3.5 (ambos inclusive)
4.1 a 4.3, 3.6 y 4.4 a 4.5
2.1 a 2.3
Tema 1
Y ¿finalmente? una tercera, ya en el orden que cada cual estime más oportuno, de los epígrafes que
resultaran más difíciles, realizando los restantes ejercicios del CD y exámenes de cursos pasados. Lo
que sin duda exigirá volver con frecuencia sobre lo que a esas alturas será nuestro querido, amado y
sobado libro ;-)
ÍNDICE

3. Dominio N de los índices.


El autor no aclara que notación para los conjuntos naturales usa. Por el contenido, supongo que
cuando se refiere a N, se refiere a la interpretación clásica en la que N ≡ {1, 2, 3 …}. La misma
notación creo que se sigue en CD (aunque de una manera mucho menos estricta. En ocasiones
claramente incluyen al 0). El 0 entonces NO pertenece a N, lo que por otra parte es lógico: N es el
conjunto por antonomasia para la ordenación. Primero, segundo,…decimocuarto…; y eso es lo que
hacen los índices en un vector: definir un orden. No existe la posición ordinal 'cerésima'.
Se produce entonces, en algunos ejercicios y exámenes pasados, un aparente error: se define en un
programa un argumento i: nat (i es de tipo natural; pertenece a N) y en alguna parte del desarrollo del
programa nos encontramos una expresión similar a (i mod 2 = 0). Con el resultado devuelto por la
función mod, que tiene como argumentos dos números naturales, comprobamos la igualdad con el 0
que no está definido en el conjunto de los naturales. En sentido estricto, tendríamos una operación no
legal. Pero por convenio podemos aceptarla si consideramos la expresión completa (i mod 2 = 0)
como un todo: una función booleana que devuelve el valor veritativo (V o F) del predicado 'i es par'.
El único error es no declarar el convenio.
En la notación clásica N es el conjunto de los naturales y N0 ≡ N ∪ {0} (todos los naturales más el cero)
Otra notación moderna usa, respectivamente, N y N*. Viene a complicar las cosas una tercera notación, en la
que N* son los naturales sin el cero y N los naturales con el cero.
Cualquiera de ellas es perfectamente válida, siempre que se especifique cuál se usa.

6
25905053.doc

Para colmo, no es éste el único convenio no declarado explícitamente ¿qué trabajo costaba incluir un
apéndice especificando los que se siguen, junto con un resumen de la notación usada en todo el libro?.
Supongo que debe ser un trabajo y un esfuerzo editorial impresionante, pues no es el único libro que
usamos que padece tal problema.
ÍNDICE

4. Logaritmos.
Como ejemplo de lo anterior, siempre que aparezca logn se ha de entender como logaritmo en base 2
de n. Se supone que el alumno conoce perfectamente este convenio de aplicación general en todos los
textos informáticos… pues resulta que yo me enteré al leer el libro de texto de EsDA de segundo.

5. Símbolos de Ordenes de Complejidad.


En CD, exámenes y otros textos, para 'orden de complejidad de f(n)' además del símbolo usado aquí,
ϑ(f(n)), nos podemos encontrar otros simplificación del anterior, generalmente una O mayuscula.
ϑ(f(n))≡ O(f(n)). Asimismo para 'orden exacto de f(n)' podemos encontrarnos θ (f(n)), Θ(f(n)), Θ (f(n))
o similar. Desafortunadamente, y no entiendo cómo se eligieron estos símbolos, tanto ϑ como Θ (o sus
equivalentes), son grafías diferentes de la misma letra griega: zita mayúscula.

6. Símbolo →
Casi siempre representa a la conectiva lógica condicional (implicación material), especialmente
cuando aparece en los predicados de una especificación o en un aserto. Pero en otras ocasiones puede
significar implicación matemática o lógica. El ejemplo más claro lo tenemos en los casos de los
diseños recursivos:
Caso Bt → triv(x). Si estamos en caso trivial, necesariamente devuelve triv(x)
[] Bnt → c(f(s(x)),x). Si estamos en caso no trivial, necesariamente haz c(f(s(x)),x)

7. Diferentes notaciones para un mismo problema.


Al ir resolviendo distintos problemas, cada cuál encontrará, dentro de los márgenes que la materia
permite, una notación y/o mecánica que le resultará más cómoda de manejar. Por ejemplo: en
recursivas procuro evitar recorrer el vector en sentido ascendente; en los sumatorios casi nunca uso la
notación clásica Σ α =1i y sí la forma más fea Σα∈ {1..i} (claro que esto por imposición del Señor de
las Ventanas, si es que no quiero usar su editor de fórmulas –que no quiero-).
De todos modos, conviene leer y realizar problemas de procedencia diferente (exámenes muy
antiguos, Peña, CD, otros libros…) para acostumbrarse a otras notaciones y/o procedimientos que
pueden resultar confusos en una primera lectura a pesar de su corrección. Mala cosa si esa confusión
se produce en un examen.
También en examen se debe tener muy presente (… y yo casi siempre lo olvido) que nos podemos
encontrar preguntas similares a '¿Es X una solución para el problema Y?'. Si a pesar de existir más
soluciones (o una más genérica, o más simple), X es una solución particular, por innecesariamente
compleja que sea, la respuesta a la pregunta es afirmativa.

ÍNDICE
Salto de página.../...

7
25905053.doc

ÍNDICE

ESCOLIOS PARTICULARES
TEMA 1. La eficiencia de los algoritmos
Pag 3. Tamaño de los datos de entrada
A lo largo de todo el libro, aparecen expresiones similares a 'tamaño de los datos de entrada' o 'tamaño
del problema', muy usadas con un significado muy concreto, tanto en ésta como otras asignaturas,
pero que tomadas literalmente pueden inducir a error. En ningún momento significa el tamaño de la
tupla (conjunto ordenado) que forman la lista de parámetros formales.

Por ejemplo. Supongamos que mediante una función de nombre ordena queremos ordenar
ascendentemente todos los elementos desordenados de un vector denominado arreglo con un millón
de posiciones.
Invocamos: ordena(arreglo, 1000000).

Ahora supongamos que sobre el mismo vector original desordenado y por las razones que sea, sólo
queremos ordenar las cuatro primeras posiciones:
Invocamos: ordena(arreglo, 4).

En el segundo caso, la tupla de los datos de entrada sigue teniendo el mismo número de elementos:
dos. El primer parámetro sigue siendo el mismo vector que se pasa completo como argumento, pero el
segundo limita drásticamente el tamaño del conjunto de los datos sobre los que se va a trabajar o
tamaño del problema.

Pag 3. Casos mejor, promedio y peor


Por ejemplo, supongamos que usando la función ordena vista en el escolio anterior, ordenamos
ascendentemente el vector arreglo de un millón de posiciones en tres situaciones diferentes de
partida; una primera en la que el vector de entrada resulta ya estar ordenado, una segunda en la que
está semiordenado de un modo aleatorio y finalmente con el vector absolutamente desordenado (esto
es, ordenado decrecientemente). Si el algoritmo tiene un comportamiento natural tardará, menos
tiempo en la primera situación, más en la segunda (posiblemente mucho más), y más (posiblemente
mucho, mucho más...) en la tercera. Estos serán respectivamente los casos mejor, promedio y peor.
(Existen algoritmos de ordenación eficientes que tiene un comportamiento antinatural, el mejor caso
se produce cuando el vector está completamente desordenado).
ÍNDICE
Normalmente será imposible encontrar un función de tiempo que exprese la relación entre tiempo y
grado de orden del vector de entrada; por lo que el estudio del caso promedio en general se hará en
base a suposiciones de tipo probabilístico. Será en cambio muy fácil determinar qué situación será la
mejor y cual la peor, y a partir de ahí intentar, de modo más o menos fácil, establecer una cota inferior
para el caso mejor (a veces también una superior y otras su valor exacto) y una cota superior para el
peor (y a veces una inferior y otras su valor exacto). Volveremos sobre ello.

Pag 5. Punto 1.2. Medidas asintóticas


Ojo a los nombres en éste y siguiente epígrafe. El estudio que se realiza de medidas asintóticas y
órdenes de complejidad es genérico para funciones en sentido matemático y no para funciones tal
como se entenderán en el resto del libro, programas que realizan una acción sobre unos datos de
entrada. Por tanto, donde diga f(n) ha de entenderse como tal función matemática... función que más
adelante en el libro se le llamará T(n): 'función tiempo de ejecución del programa (función
implementada) f(n)'. Cuando lleguemos a ese punto, f(n), programa, y T(n), función tiempo ejecución,

8
25905053.doc

serán conceptos íntimamente ligados entre sí, pero en absoluto iguales. Por ejemplo: f(n) puede ser un
programa (función implementada) que reciba como entrada un vector desordenado y como salida lo
entregue ordenado. En cambio, su función tiempo de ejecución, T(n), recibirá como entrada el número
de elementos de dicho vector y entregará como salida el tiempo que ocupe la ejecución de f(n).

Una notación no ambígua para la función tiempo de ejecución, T, del programa f(n) sería:
Tf(n)

ÍNDICE

Pag 6. Definición 1.1. ϑ (f(n)) e introducción a los Órdenes


Nada más comenzar el estudio, recibimos la primera en la frente.
Para facilitar todavía más la interpretación de este recuadro gris del libro, la definición enlaza tres tal
que, omite paréntesis no imprescindibles y usa una notación no estándar (al menos eso creo: ¿Cómo
se ha de leer correctamente ∃ c ∈ R +, n0 ∈ N. ∀n ≥ n0?. No es tema baladí y tiene mucha más
importancia de la que parece.).

Bien, dice el libro:


Sea f : N → R + ∪ {0}. El conjunto de las funciones del orden de f(n), denotado ϑ (f(n)), se
define como sigue:
ϑ (f(n)) = {g : N → R + ∪ {0} | ∃ c ∈ R +, n0 ∈ N . ∀n ≥ n0 . g(n) ≤ cf(n)}

Asimismo, diremos que una función g es del orden de f(n) cuando g∈ϑ (f(n))

Posteriormente veremos un manera más clara de realizar esta definición simbólica. Ahora,
simplemente conformémonos con intentar averiguar qué quiere decir ésta, realizando las mínimas
modificaciones usando las herramientas que conocemos.

Vamos poco a poco.


• Los puntos 'tal que' separan un predicado (a la derecha) de la declaración de dominio de las
variables (a la izquierda) que participan en él. Por tanto se puede escribir de un modo más
cómodo, similar al usado en Lógica Matemática, donde se encierra entre paréntesis o corchetes el
predicado y a su izquierda, se escribe la declaración de dominio.
• Además, añadiré todos los paréntesis, de diferentes tamaños y colores, que considere necesarios.
• Tanto f como g son funciones que tienen como dominio a todo N (sin el cero) y como rango a
todos los reales no negativos (R+ ∪ {0}); o lo que es lo mismo, son funciones reales no negativas
de variable natural.
Con todo esto, se puede reescribir la definición de una manera ligeramente diferente, pero diciendo lo
mismo:
Sea f : N → R + ∪ {0}. El conjunto de las funciones del orden de f(n), denotado ϑ (f(n)),
se define como sigue:

ϑ (f(n)) = {(g : N → R + ∪ {0}) |( (


∃ c ∈ R +, n0 ∈ N). ∀n ≥ n0 [g(n) ≤ )}
cf(n)]
Asimismo, diremos que una función g es del orden de f(n) cuando g∈ϑ (f(n))

ÍNDICE
Y todavía de una tercera manera, donde sólo para aumentar la legibilidad, omito todas las
declaraciones de dominio y rangos de función; ¡¡lo que no quiere decir que se puedan ignorar!!:

El conjunto de las funciones del orden de f(n), denotado ϑ (f(n)), se define como sigue:

ϑ (f(n)) = { |(
g (
∃ c, n0 ). ∀n ≥ n0 [g(n) ≤ )}
cf(n)]
Asimismo, diremos que una función g es del orden de f(n) cuando g∈ϑ (f(n))
9
25905053.doc

El punto grueso (que en LoMa no solemos usar), separa la declaración de sobre qué variables se va a
enunciar una propiedad de la propia enunciación de dicha propiedad, encerrada entre paréntesis de
color azul; lo que constituye el predicado que hay que entender: ∀n ≥ n0 [g(n) ≤ cf(n)]. Aunque en
este caso es algo más complejo, puesto que a su vez el propio predicado tiene una subdeclaración de
dominio

... pero bueno, aunque después veremos otra más precisa y clara, realicemos una primera
aproximación verbal casi literal a esta definición simbólica:

El conjunto de las funciones del orden [de complejidad] de f(n), denotado ϑ (f(n)), se define
como el conjunto formado por todas las funciones g que cumplen lo siguiente: para cada una de
ellas, existen un real positivo c, y un natural n0, tales que para todo n natural mayor o igual a un
n0, la función g(n) es menor o igual que f(n) multiplicada por la constante c.
ÍNDICE
Pero todo esto es palabrería. De alguna parte tiene que salir para que esté justificada; de algo tangible
o visualizable. Y eso es lo que hay que comprender correctamente; de lo contrario, la simple
definición no vale absolutamente para nada. Partamos desde el principio por nuestra cuenta
(procedimiento que no será la última vez que tendremos que aplicar a lo largo de todo el libro… Sin
comentarios).

Para ello, veamos en las siguientes figuras cómo se comportan una serie de funciones, cinco de ellas
cuadráticas bastante diferentes entre sí y una sexta cúbica. Antes del nombre de la función pongo el
color de su gráfica. Para poder dibujarlas, usé funciones reales de variable real –a pesar de lo cual
llamo n a la variable independiente, en vez de x, como suele ser habitual para las variables reales-. A
los efectos que nos interesa, es absolutamente intrascendente; bastará tener en cuenta que de las
gráficas sólo son válidos los puntos que se corresponden a una abscisa entera.
ÍNDICE

azul f(n) = n2
magenta g(n) = 4n2 + 1704
celeste h(n) = 48n2 – 816n
verde j(n) = (n2)/3 +81n –1998
siena k(n) = 162n2 + 2268
rojo l(n) = n3
Salto de página.../...

10
25905053.doc

Si no se ven las imágenes: menú Herramientas / Opciones /Ver / desactivar la casilla Marcadores de imagen
10000 1e+07

8000 8e+06

6000 6e+06

4000 4e+06

2000 2e+06

0 20 40 60 80 100 0 200 400 600 800 1000

Figura 1 Figura 2
ÍNDICE DE FIGURAS

Nota. Los dominios representados deberían partir de 1 en vez de 0, pero excepto en dominios muy pequeños
Scientific Notebook (la herramienta usada para las gráficas) rotuló siempre el origen de coordenadas con un
cero para graduar ordenadas. No me complico la vida y lo dejó estar tal como está.

En la Figura 1, vemos las gráficas de cerca, ya que el dominio representado es pequeño (0,100).
A pesar de la aparente variedad, de las diferencias en los puntos de arranque y la cantidad de cruces
entre gráficas, ¿tienen algunas de ellas una, o más, características comunes que permitan agruparlas
por familias?.

Si nos atenemos a lo rápido que sobrepasan una cota, por ejemplo la 8000, claramente habría dos
familias, la formada por las funciones de gráficas siena, roja, celeste y magenta y las de gráficas verde
y azul.

Si nos fijamos en cambio en la pendiente que llegan a alcanzar (velocidad de crecimiento) tendríamos
esas dos familias y una tercera formada únicamente por la gráfica magenta que abandona la primera
familia, donde sólo quedan siena, rojo y celeste.

Este segundo criterio parece más fiable que el primero, y podemos conjeturarlo como hipótesis de
partida.

Esta sensación se acentúa, pero ya matizada, si nos alejamos lo suficiente para ver las gráficas con un
dominio mayor en la Figura 2. Parece que la hipótesis se va confirmando; esas tres funciones de fuerte
pendiente (siena, rojo, celeste) se desmarcan claramente de las restantes y puede que sean del mismo
tipo... excepto por el detalle (presente en la anterior Figura 1, pero menos obvio) de la mayor
curvatura de la cúbica roja n3; curvatura que es suficiente para que acabe creciendo más deprisa que
las dos cuadráticas, llegando a cruzarse con la celeste en n ≈ 60 y con la siena en aproximadamente n
≈ 200. Además, si alejásemos todavía más el punto de vista, la configuración general ya no variará
jamás. Ninguna gráfica se vuelve a cruzar y la roja seguirá siempre con un crecimiento más fuerte que
todas las demás.
ÍNDICE
Hay que afinar la hipótesis, pero ahora tenemos una pista que aparenta ser correcta y nos permite
conjeturar que lo que caracteriza a las cuadráticas es una curvatura propia que refleja la
aceleración del crecimiento, no la velocidad. Curvatura que impone el exponente del monomio de
mayor grado (el 2 de an2). La cúbica, justamente por tener un exponente mayor, posee un curvatura
diferente que refleja una aceleración del crecimiento mayor.
Veamos ahora que ocurre si tomamos una cualquiera de estas cuadráticas, por ejemplo f(n) = n2, y la
multiplicamos por un coeficiente mayor que los restantes coeficientes del monomio de mayor grado

11
25905053.doc

de las otras funciones. Por ejemplo 181, con lo que obtendríamos una función t(n) = 181n2 de gráfica
negra en las siguientes Figura 3 y Figura 4. La primera con el mismo punto de vista que en la Figura 2
y la segunda vista desde muy cerca, donde apenas entra en el encuadre la gráfica verde.
ÍNDICE
1e+07 30000

8e+06 25000

20000
6e+06
15000
4e+06
10000

2e+06
5000

0 200 400 600 800 1000 0 5 10 15 20 25

Figura 3 Figura 4
ÍNDICE DE FIGURAS

Observando estas dos figuras, vemos que la nueva función negra t(n) = 181n2 acota superiormente:
• a las funciones de gráficas celeste, verde y azul en sus respectivos dominios completos.
• a la magenta a partir de n0 ≈ 3
• a la siena a parir de n0 ≈ 11.

Resumiendo: cada una de las anteriores funciones es acotada superiormente por t(n) = 181n2 a partir
de un determinado punto crítico n0 específico para cada función y todas quedan acotadas a partir de n0
≈ 11 (aunque en sentido estricto, las celeste y verde sólo se pueden considerar acotadas a partir del
comienzo de sus respectivos dominios).

Además, esta nueva gráfica negra no consigue acotar por arriba a la roja cúbica a partir de ningún n0;
ni tampoco lo conseguiremos por muy grande que sea el coeficiente c por el que multipliquemos
cualquier cuadrática. Antes o después se acaba demostrando que posee una aceleración de crecimiento
mayor que el de las cuadráticas y las acabará sobrepasando.

Ya tenemos bagaje suficiente para describir informalmente y por nuestra cuenta a la familia de
funciones cuadráticas: La formarán todas aquellas funciones que tengan una aceleración de
crecimiento del mismo orden de magnitud, o bien que tengan una gráfica de curvatura característica,
de la que es modelo la de la función f(n) = n2.

También podemos definirla de una manera particularmente interesante: la familia de las cuadráticas
estará formada por todas aquellas funciones que a partir de un determinado punto (propio para cada
función) son acotables superiormente por una función que sea producto de una constante c adecuada
por el representante de la familia f(n) = n2. Esta función cf(n) = cn2 será una cota superior de todas las
funciones de la familia cuadrática. Para mayor claridad, solemos escoger para ser multiplicada a la
función más simple de la familia, pero perfectamente podemos usar cualquiera de ellas. Claro que en
ese caso, el coeficiente c necesario para transformar a la función elegida en cota superior será
diferente.

Quedan tres detalles por aclarar, íntimamente relacionados entre sí.

Si demostramos que con cf(n) podemos acotar una función g(n) a partir de un determinado punto
crítico n0 ¿Podremos seguir acotando a g(n) con otra función c'f(n) con c' distinta de c?, y si es así ¿el
punto crítico n0 será distinto?, y por último ¿a partir de qué cifra c la función cf(n) se convierte en cota
de g(n)?.

12
25905053.doc

Supongamos que queremos comprobar si la función g(n) = 3n2+2n + 183 pertenece a la anterior
familia cuadrática, a la que ya sabemos que pertenece f(n) = n2. Sigamos el mismo criterio, pero esta
vez con tres c diferentes, (4, 5 y 6). Obtenemos las siguientes funciones y Figura 5

marrón g(n) = 3n2 + 2n +183 función a comprobar si es de la familia cuadrática.


azul f(n) = n2 función que sí es de la familia

celeste 4f(n) = 4n2 


magenta 5f(n) = 5n2  funciones de cota obtenidas a partir de f(n)
marino 6f(n) = 6n2 

ÍNDICE
1000 1e+07

800 8e+06

600 6e+06

400 4e+06

200 2e+06

0 5 10 15 20 0 200 400 600 800 1000

Figura 5 Figura 6
ÍNDICE DE FIGURAS

Comprobamos que las tres funciones propuestas, celeste, magenta y marino, son en efecto cotas
superiores de la marrón (que por tanto pasa a engrosar la familia de las cuadráticas), y que en cada
caso se obtiene un diferente punto crítico n0 (n0 ≈ 8, n0 ≈ 10, n0 ≈ 15). Contestadas de un plumazo
dos de las tres preguntas. Y lo más importante, ese punto crítico es función del coeficiente c
elegido. Esta función (o ligadura de n0 respecto de c) podemos indicarla de manera clara como
n0(c) (leído 'n0 es función de c').

En cuanto a la tercera pregunta, no la demostramos aquí; pero para acotar superiormente a la función
marrón g(n)= 3n2 + 2n +183, basta multiplicar f(n) = n2 por cualquier real c estrictamente mayor que
3 (coeficiente del monomio de mayor grado de g(n)). Por ejemplo valdría c=3'000000001... aunque
eso sí, el punto crítico n0 estaría muy, muy a la derecha. Pero existe; y a partir de ahí, cf(n) acota
superiormente a g(n). De todo esto se sigue que, una vez establecida una c suficiente para convertir a
cf(n) en cota superior, todos los infinitos reales mayores que c también sirven para crear una función
cota a partir de f(n).

Aprovechando que por Valladolid pasa el Pisuerga, dibujé también la Figura 6 que, vistas a mayor
distancia, contiene a todas las cuadráticas de la Figura 5 y además a las siguientes cúbicas:

verde h(n) = 2n3


sienaj(n) = n3 + 35 n2
gris k(n) = 5n3 + 122 n2 –327n

Observamos que cada una de las dos familias (cuadráticas y cúbicas) tiene un comportamiento
claramente diferente, determinado por la aceleración de crecimiento o curvatura, quedando todos los
miembros de cada una confinados en un haz.

13
25905053.doc

En esta figura puede alegarse, y con razón, que quedan confinadas porque todas las funciones
cuadráticas representadas tienen coeficientes similares para el monomio de mayor peso, del mismo
modo que las cúbicas lo tienen entre ellas. En el caso que esos coeficientes fueran más disímiles, los
haces serían más abiertos, pudiéndose llegar incluso a una situación donde no estén claramente
definidos los haces o incluso peor, donde una cúbica estuviera siempre claramente por debajo del haz
cuadrático. No hay problema, los haces siguen estando ahí. Bastará alejar más el punto de vista,
ampliando lo suficiente el dominio, para que se vuelvan a ver como tales haces perfectamente
diferenciados, con todas las cúbicas por encima de las cuadráticas a partir de un determinado punto.
ÍNDICE
A esta familia de funciones cuadráticas acotables por cf(n) la llamaremos… Orden de
Complejidad cuadrático, y la denotaremos como ϑ (n2).

De modo similar podemos definir los siguientes Órdenes de Complejidad:


ϑ (1) Orden Constante
ϑ (logn) Orden Logarítmico
ϑ (n) Orden Lineal
ϑ (nlogn) Orden Lineal-logarítmico
ϑ (n2) Orden Cuadrático (o potencial, pero en ese caso habrá que especificar la potencia)
ϑ (n k) Orden Potencial k (con k > 2; para cada k, un orden. A mayor k, 'mayor' orden)
ϑ (2n) Orden Exponencial
ϑ (n! ) Orden Factorial
ϑ (n n ) Orden Potencial-exponencial

Observaciones.
• Para el Orden Constante se podría escribir ϑ (0), pero como lo usaremos para costes temporales
de algoritmos, es más apropiado el 1, porque malamente existirán algoritmos tan
endiabladamente rápidos que tengan un coste temporal 0.
• Tanto Orden Constante como Lineal están formados por funciones de curvatura o aceleración 0
(son rectas), pero es evidente que no se pueden considerar del mismo orden, toda vez que las
primeras se caracterizan por tener velocidad de crecimiento 0 mientras que las segundas tienen
velocidad constante distinta de 0.
ÍNDICE
Al haber definido Orden de Complejidad según un criterio de acotación, cae de cajón que un orden
determinado incluye tanto a las funciones 'de su propia familia' (del mismo tipo de curvatura), como a
todas las familias que tengan una más baja aceleración del crecimiento:
Cualquier función logarítmica está acotada superiormente (para valores suficientemente grandes del
dominio) por cualquier función lineal y esta a su vez por cualquier cuadrática, etc, etc... Por eso
podemos decir que el Orden logarítmico es un subconjunto del orden lineal, que a su vez lo es del
cuadrático... Volveremos sobre ello en próximos escolios.

Centrándonos ahora más en el lado informático que en el matemático de los órdenes, unas pocas
puntualizaciones:

Si g(n)∈ϑ (f(n)), la función cf(n) establece una cota superior para el tiempo de ejecución en cualquier
circunstancia, tanto de tamaño como de caso (caso peor, mejor y promedio). Pero como
programadores y usuarios, lo que nos preocupará en general (habrá excepciones) es cómo se comporta
temporalmente un determinado algoritmo en el peor de los casos. Por eso usaremos el orden ϑ casi en
exclusiva para acotar el peor de los casos... cosa bastante lógica.
ÍNDICE
Si g(n) ∈ϑ (f(n)), cometemos un abuso de lenguaje cuando decimos que g(n) está acotada
superiormente por f(n) (¡sin la c!). Da lo mismo. Nos entendemos perfectamente... siempre que
hayamos comprendido el concepto de Orden de Complejidad.

14
25905053.doc

Aunque ya fue comentado, volver a remarcarlo, porque este pequeña tontería me trajo por la calle de
la amargura en los primeros problemas con los que me enfrenté. En el contexto de nuestra asignatura,
todas estas funciones son en realidad funciones de tiempo de ejecución de algoritmos. Cometemos
otro abuso de lenguaje al decir que un algoritmo/función_informática pertenece a un orden de
complejidad determinado. Lo que realmente pertenece al orden de complejidad es la función
matemática que define el tiempo de ejecución en función del tamaño de los datos de entrada; función
que normalmente, y en rigor, denotaremos como T(n).

Refiriéndose a una función de tiempo dada T(n), con frecuencia nos encontramos con expresiones
similares a 'realizar un estudio asintótico de la función T(n)'. No se nos está pidiendo otra cosa que
determinar a qué orden de complejidad pertenece la función T(n). Porque una vez establecido que
pertenece a un determinado orden, es seguro que dentro de él existe alguna otra función que con
respecto a T(n) se comporta como una asíntota (en realidad, serán infinitas las asíntotas) o incluso
otras que sin ser asíntotas, se comportan como tales en el dominio de nuestra función de tiempo (que
por definición será un dominio finito). Además resulta que al realizar un estudio asintótico, sólo nos
preocupará a qué orden pertenece la función estudiada. No nos importa los más mínimo cuáles son
concretamente esos dichosos puntos c y n0. Nos basta con saber que existen.

Resumiendo todo lo anterior de manera informal, pero correcta, podemos definir un Orden de
Complejidad de alguna de las siguientes maneras:

El conjunto infinito de funciones en las que una vez ignoradas constantes multiplicativas y a
partir de un valor de n suficientemente grande:
• sus gráficas poseen el mismo tipo de curvatura o
• tienen la misma aceleración de crecimiento o
• están acotadas superiormente por un múltiplo del 'representante canónico' o
• cada una de ellas tiene un comportamiento asintótico con respecto a alguna otra del
mismo orden.
ÍNDICE
Para nosotros serán de uso muy común las dos últimas; la tercera en PeDos y la cuarta en EsDA.

Sabiendo todo esto, volvamos ahora al principio e intentemos una definición formal partiendo de la
siguiente que deja varios cabos sin atar:
Una función g(n) pertenecerá al conjunto infinito Orden de Complejidad de f(n), si podemos
encontrar algún real c que haga que se verifique g(n) ≤ cf(n) para todo n mayor que un
determinado n0. A este conjunto infinito lo denotamos como ϑ (f(n)).

Atando esos cabos sueltos:


ϑ (f(n)), leído 'zeta de efe de n (u orden de efe de n)', es, por definición, un subconjunto del
conjunto de las funciones g que aplican los naturales en los reales no negativos; funciones g
para cada una de las cuales existe un real positivo c y existe también un natural n0 crítico
que es función de c; números tales que el hecho que n tome valores mayores o iguales que
ese n0 crítico implica que g(n) sea menor o igual a f(n).

Simbólicamente:

ϑ (f(n))= {(
g : N → R + ∪ {0}) |(( (
∃ c∈R + ∧∃ n0(c)∈N). ∀n≥ n0 ⇒ [g(n) ≤ cf(n)] ))}
Expresión que es completamente equivalente a la de Peña 6, pero creo que más clara.
Siguiendo el mismo convenio que al principio de eliminar declaraciones de dominio sólo para
aumentar la legibilidad y eliminando un paréntesis no necesario (eliminar más puede ser
contraproducente):

15
25905053.doc

ϑ (f(n))= {(
g : N → R + ∪ {0}) |( (
∃ c ∧∃ n0(c)). ∀n≥ n0 ⇒ [g(n) ≤ cf(n)] )}
Y este es todo, absolutamente todo, el secreto de los órdenes de complejidad. Sin trampas ni cartón.
ÍNDICE

* * * *

Apéndices al escolio de introducción a los órdenes


Se trata de distintos apuntes de ampliación, cuya lectura se puede omitir.

Apéndice 1
El nombre de órdenes de complejidad me parece un pequeño dislate. Al decir que un algoritmo es de
orden cuadrático (estoy incurriendo en abuso de lenguaje) sólo hacemos referencia al tiempo de
ejecución del algoritmo; tiempo que tiene una determinada manera de crecer característica en función
del número de datos de entrada (tamaño del problema). En absoluto nos referimos a la propia
complejidad del algoritmo. Veremos en EsDA algoritmos de ordenación con una más que aceptable
eficiencia temporal (que normalmente no se usan en memoria principal, pero pertenecen al orden
nlogbn, ¡con la base b que se considere oportuna!, siempre que se disponga de suficiente memoria)
pero que son de una complejidad de escritura más que notable; mientras que hay otros que realizan
absolutamente lo mismo, extraordinariamente sencillos (no más de 15 líneas), pero de una eficiencia
mucho más pobre y orden cuadrático.
Ignoro por completo si el nombre de orden de complejidad es ya un tópico en informática o si por
contra es un término implantado más o menos recientemente y no universalmente aceptado. Sería
mucho más lógico hablar de Ordenes Temporales... o para no inventar nombres nuevos, Ordenes de
Infinitud, que fueron definidos bastante antes de que ENIAC hiciera sonar por primera vez su
campana avisando que había terminado un cálculo. Además, un orden de infinitud sí que ya sabíamos,
o deberíamos saber, lo que era; incluso aunque no lo conociéramos por tal nombre y lo usáramos
intuitivamente para resolver directamente un límite que había llegado a indeterminación, sin tener que
realizar operación alguna ni aplicar L´Hôpital.
ÍNDICE
Resumiendo: Órdenes de complejidad y órdenes de infinitud son exactamente la misma cosa, con las
dos únicas diferencias -perfectamente salvables- de que el primero se define para N y el segundo para
R y que las definiciones formales serán diferentes, pues el uso que se les va a dar es ligeramente
diferente. Básicamente, complejidad se usará para estudiar cotas y se definen según un criterio de
cota; infinitud para estudiar aceleraciones de crecimiento y se define por un criterio asintótico del
limite en el infinito del cociente de dos funciones.

Cualquiera de las dos definiciones son intercambiables, y de cada una se sigue la otra como corolario.
Por ejemplo, en los ejercicios 1.2 y 1.5 se nos pide que demostremos a partir de la definición de orden
de complejidad lo que constituye la definición de orden de infinitud.

Son tan iguales ambos conceptos, que el orden de infinitud se define (Rey Pastor, páginas 494 y 495
de Análisis Matemático) justamente como lo que para nuestro libro son sendos teoremas a demostrar
en los ejercicios 1.2 y 1.5.
Esto es: matemáticamente se definen los órdenes de infinitud cómo el límite de un cociente entre dos
funciones. Si dicho límite es finito, el orden de la función del numerador está incluido en el orden de
la del denominador. De esta manera, ¡resulta que la definición de Peña no es una definición, es un
corolario!.

ÍNDICE

Apéndice 2

16
25905053.doc

Volviendo a la definición 1.1 del libro, dos apuntes de lógica.


ϑ (f(n)) = {g | [∃ c ∧∃ n0] . [∀n ≥ n0 [g(n) ≤ cf(n)]]}
En realidad el predicado es de traducción verbal algo más compleja que la vista hasta ahora.
Para el primer existencial no hay un posterior universal que se refiera a la misma variable c. Por tanto,
lo que se está enunciando en el predicado es la existencia de al menos un real c. Y como ya hemos
visto, son más; toda una infinidad de c. Por tanto, una manera más correcta de escribir la definición
exigiría cambiar c, por c0 y ampliar el dominio para todo c mayor que c0 modificando
consecuentemente el predicado. Pero esto ya sería complicar demasiado la definición... A efectos
prácticos llega, sobra y basta con la existencia de un c cualquiera que haga cierto al predicado.

Veamos en cambio qué ocurre con n0. Para su existencial sí existe un posterior universal que le afecta
de un modo retorcido e indirecto, pues la propiedad que se enuncia en el predicado lo es para todo n
igual o mayor que n0, variables n que después se usarán para una comparación que es función de la
variable c. Resultado: tenemos que la variable n0 es un valor concreto de lo que en el libro de LoMa
se denomina una función de Skolem cuya variable independiente es c.
Pues que bien. Todo este follón lo hemos resuelto más arriba escribiendo ∃ n0(c), que es una manera
abreviada y correcta de escribir ∃ n0 = ϕ (c), donde ϕ representa la función de Skolem.

Íntimamente relacionado con esto y entrando en el terreno de la lógica de descriptores o predicados


con identidad, como n0 tiene delante un existencial y después hay un universal que se refiere a él,
aunque sea por un camino tortuoso, resulta que ∃ n0 no debemos leerlo como 'existe (alg)un n0' sino
con más propiedad como 'existe un único n0 (mínimo)'.
ÍNDICE

Apéndice 3
Tomada la definición del libro absolutamente al pie de la letra, permitiría la diferenciación de nuevos
órdenes de interés matemático para la resolución directa, sin operaciones ni transformaciones, de
determinados problemas con indeterminación en el cálculo de límites. Estos nuevos Órdenes estarían
incluidos en el Orden Constante y pertenecerían a dos grandes categorías, en las que no se incluye
ninguna función T(n) tiempo de ejecución de algoritmo... que yo sepa :
• Los Órdenes de las funciones crecientes con límite k (k real estrictamente mayor que cero)
• Los Órdenes de las funciones decrecientes con límite k (aunque serían todas reducibles a
funciones equivalentes con límite 0).

Dentro de esta última categoría, son triviales los Órdenes, y en este orden de inclusión:
ϑ (1/nn) ⊂ ϑ (1/n!) ⊂ ϑ (1/2n) ⊂ ϑ (1/n k) ⊂ ϑ (1/ n2) ⊂ ϑ (1/nlogn) ⊂ ϑ (1/n) ⊂ ϑ (1/logn)

Como muestra del significado de estos órdenes, sean las siguientes funciones y sus correspondiente
gráficas de la Figura 7.
negro f(n) = 1 / n3
verde g(n) = 1 / n2
marrón h(n) = 1 / nlogn
azul j(n) = 1 / n
magenta k(n) = 1 / (log1,3n)

ÍNDICE

17
25905053.doc

0.4

0.3

0.2

0.1

0 10 20 30 40 50

Figura 7
ÍNDICE DE FIGURAS

A estos órdenes se les denomina Órdenes Infinitesimales, aunque matemáticamente y por razones
prácticas y conceptuales, se definen de tal manera que la cadena de inclusiones sea a la inversa. Esto
es, aplicando el criterio de la acotación, se definirían según una adecuada c que hiciera cierta la
desigualdad g(n) ≥ cf(n) (con ≥ , en vez de ≤ original). Porque matemáticamente lo que pretende
un Orden Infinitesimal es caracterizar cuán fuerte es el comportamiento asintótico de sus funciones
respecto al eje de abscisas. Y las funciones que más fuerte se adhieren, sin tocarlo, al eje de las equis
son las inversas de las potenciales exponenciales y las que menos, las inversas de las logarítmicas.

ÍNDICE

Pag 7. Ejercicio 1.1. sii


El símbolo sii se lee 'si y sólo si'; siendo equivalente al símbolo ⇔.

O al menos eso es lo que deduje en el presente escolio, de lectura perfectamente omisible.


Intentemos averiguar los profundos arcanos que se esconden tras las tres letras.

Pistas: por los tres problemas de este ejercicio y el 1.4, podemos conjeturar que es una conectiva
lógica de tipo condicional. Afinando más, el primero de los problemas es un condicional puro o
implicación material (→). Puede ser entonces que sii sea una implicación matemática o lógica (⇒) o
incluso una equivalencia lógica o doble implicación (⇔).
ÍNDICE
A priori, parece más razonable la última. Sería hilar demasiado fino distinguir entre material y lógica.
Entonces, si suponiendo que sii significa 'si y sólo sí' resulta que son correctas las afirmaciones de los
problemas 2 y 3, tendremos (y aquí ya me permito un poco de cachondeo lógico) que podemos
afirmar ⇔ → sii, pero en absoluto que sii → ⇔.
Coñas lógicas al margen, veamos si con esta interpretación ambos problemas proclaman algo lógico:

Problema 2.
ϑ (f(n)) = ϑ(g(n)) sii f(n) ∈ ϑ(g(n)) y g(n) ∈ ϑ(f(n))
Con un poco de picardía podemos enunciarlo de la siguiente manera equivalente 'dos ordenes son
iguales si y sólo si uno es subconjunto del otro y viceversa'; afirmación que es absolutamente cierta.

Problema 3.
ϑ(f(n)) es subconjunto de ϑ(g(n)) si y solo si f(n) pertenece a ϑ(g(n)), pero g(n) no pertenece a
ϑ(f(n)). Lo que es trivialmente cierto en todos los casos.
ÍNDICE

Pag 7. Ejercicio 1.2. Comparación de órdenes por limite de cocientes

18
25905053.doc

Lo ya comentado en un apéndice al escolio Pag 6. Definición 1.1.  (f(n)) e introducción a los


Órdenes. Este ejercicio y el 1.5 demuestran como un teorema consecuencia de la definición 1.1 lo que
es la definición de Orden de Infinitud.

Éste y otros límites son muy útiles para comparar órdenes de dos funciones diferentes, muy
especialmente en análisis matemático (en realidad, insisto, no es que sean útiles: constituyen el
criterio decisor de adscripción a un determinado orden). En cambio, su utilidad práctica en PeDos
queda casi limitada a la resolución de algunas preguntas tipo test de los exámenes, ya que en los
problemas a desarrollar, normalmente averiguaremos a qué orden pertenece una determinada función
de tiempo, con lo que trivialmente se pueden establecer todas las comparaciones que se quieran.

Las fórmulas de este ejercicio y del 1.5 resultan más cómodas de memorizar con dos pequeñas
modificaciones:

• Intercambiar los nombres de las funciones, de modo que la función 'de referencia', que es la del
denominador, se llame f(n) y la candidata a la pertenencia a O(f(n)), aparezca en el numerador
con nombre g(n).

• sustituir ⊂, ⊄ por ∈,∉ respectivamente y eliminando el símbolo de orden a g(n). Así:


[g(n) ∉ Θ (f(n))] ≡ [Θ(g(n)) ⊄ Θ(f(n))].

Con estos cambios, podemos escribir (léase lim como 'límite cuando n tiende al infinito'):
Salto de página.../...

19
25905053.doc

 g(n) ∈ ϑ (f(n)) 
lim [g(n)/f(n)] = 0 ⇒ g(n) ∉ Ω (f(n))  g(n) crece más lentamente que f(n)
 g(n) ∉ Θ(f(n)) 

 g(n) ∉ ϑ(f(n)) 
lim [g(n)/f(n)] = ∞ ⇒  g(n) ∈ Ω (f(n))  g(n) crece más rápido que f(n)
 g(n) ∉ Θ(f(n)) 

 g(n) ∈ ϑ(f(n)) 
lim [g(n)/f(n)] = k ⇒  g(n) ∈ Ω(f(n))  g(n) crece a la misma velocidad que f(n)
(k >0)  g(n) ∈ Θ (f(n)) 

Uniendo primero y segundo obtenemos:


lim[g(n)/f(n)] = finito ⇒ g(n) ∈ ϑ(f(n))

Que se lee: si el límite en el infinito del cociente de dos funciones es finito, la función del
numerador pertenece al orden de complejidad del denominador.

Normalmente será lo que nos importe, comprobar a qué orden pertenece g(n) en el peor de los casos,
sin preocuparnos si ese orden es exacto o no.
ÍNDICE

Pag 7. Ejercicio 1.3. Jerarquía de órdenes de complejidad


Esta cadena de inclusiones se obtiene directamente por cálculo de límites, y podemos completarla
directamente así:

ϑ(1) ⊂ ϑ(log n) ⊂ ϑ(n) ⊂ ϑ(nlogn) ⊂ ϑ(n2) ⊂ …⊂ ϑ(n2+ k) ⊂ … ⊂ ϑ(2n) ⊂ ϑ(n!) ⊂ ϑ(nn)

• ϑ(1) representa a todos los algoritmos de función tiempo de ejecución constante.


• log n ≡ log2 n
• En ϑ(n2+ k) con k real no negativo. Cuanto mayor sea k, más a la derecha en la cadena.
• ϑ(nlogn) Tanto la regla de la multiplicación, como el hecho de formar una familia de funciones
con una curva característica (logarítmica deformándose a lineal... o lineal deformándose a
logarítmica), lo convierten en orden por derecho propio. Además, será de aparición muy
frecuente en algoritmos que logran una aceptable mejora de eficiencia (mayor cuanto mayor sea
el tamaño del problema) sobre otros de los que derivan, pero que son de peor coste y orden
cuadrático.

Surge una duda con las exponenciales de base k mayor que 2 (kn). ¿Se incluyen a la derecha en la
cadena? o ¿se consideran del mismo orden que 2 n?. En principio, aplicando límites según el escolio
anterior, tenemos claramente ϑ(2n) ⊂ ϑ(3n) ⊂ ϑ(4n) ⊂ … Pero según la respuesta oficial a la pregunta
3 de ExP2_1999_SepO_B, la cadena es: ϑ(2n) ⊆ ϑ(3n) ⊆ϑ(4n) ⊆… Cadena para la que hay que
encontrar una razón de peso que justifique el que parcialmente nos saltemos a la torera y ¿por
convenio? el riguroso y exacto criterio del límite del cociente. En negro sobre blanco, no he
encontrado nada (o no he sabido buscar) ni en Peña ni en CD.
ÍNDICE
A falta de opinión más autorizada, la única razón que creo plausible para este convenio hace uso de la
intratabilidad de los problemas de complejidad exponencial y superiores. Discutámoslo con el
siguiente ejemplo.
Supongamos que tenemos cuatro algoritmos, f(n), g(n), h(n) y k(n) cuyas funciones de tiempo son las
siguientes:
negro Tf(n) = 2n unidades de tiempo

20
25905053.doc

azul Tg(n) = 4n unidades de tiempo


magenta Th(n) = n! unidades de tiempo
rojo Tk(n) = n n unidades de tiempo

y que lanzamos los cuatro algoritmos sobre sendas máquinas idénticas cuyas unidades de tiempo son
los milisegundos e identificadas por los mismos colores que las gráficas de las respectivas funciones
de tiempo.
ÍNDICE

20 ms
20

15

10

0 1 2 3 4 5

Figura 8
ÍNDICE DE FIGURAS
Esta figura muestra a qué tamaños de problemas nos podemos enfrentar con cada uno de los
algoritmos si sólo queremos que las máquinas trabajen un máximo de 20 milisegundos. Tomando
valores aproximados (redondeo a ojo, sin consideración ni misericordia) resulta que:
Las máquinas negra y magenta pueden tratar problemas de tamaño 4, la roja tamaño 3 y la azul sólo 2.
Además, también podemos observar que para tamaños muy, muy pequeños, las exponenciales (negra
y azul) son más costosas en tiempo que las otras dos y que para valores también muy pequeños pero
ligeramente mayores, la exponencial negra ya logra una ligera ventaja, como era de esperar.

Pero esta figura no revela la verdadera magnitud del crecimiento de ninguna de las funciones ni su
comportamiento para problemas con tamaños que no sean tan ridículos por pequeños. Para
comprobarlo, imaginemos que los cuatro algoritmos realizan un cálculo que consideramos realmente
importante y que podemos esperar lo necesario para obtener la solución. Digamos que nuestra
paciencia alcanza hasta... los 10 años (3'1536*10 11 milisegundos); que ya es paciencia. Superada esa
cota de tiempo, consideraremos que los problemas son intratables; al menos para nuestra monacal
capacidad de espera (figura 9)
.
Pero si resulta que los cálculos son realmente necesarios para el futuro de la humanidad y que
podemos/queremos dejar funcionando las máquinas durante siglos, para que al menos un ignorado
descendiente nuestro pueda ver los resultados, seamos previsores y establezcamos la cota de
intratabilidad en un millón de años. Como suena. En total 3'1536*10 16 ms. Todo sea en aras de la
supervivencia de la especie (figura 10).
Salto de página.../...

21
25905053.doc

10 años 1 millón de años


3e+11 3e+16

2.5e+11 2.5e+16

2e+11 2e+16

1.5e+11 1.5e+16

1e+11 1e+16

5e+10 5e+15

0 10 20 30 40 50 0 10 20 30 40 50

Figura 9 Figura 10
ÍNDICE DE FIGURAS

Redondeando otra vez, vemos que al cabo de nuestra paciente espera de 10 años, la máquina que
mejor pudo aprovechar el tiempo (negra), no fue capaz de tratar más que 40 miserables datos. Eso sí,
como era de esperar, las máquinas roja y magenta, en las que corren algoritmos de complejidad
superior a las de las máquinas negras y azul, ahora ya han mostrado por completo su carácter y han
tratado menos datos desde el primer segundo (no se aprecia en esta figura, pero basta fijarse en la
Figura 8).

En cuanto a nuestro hipotético y eónico nieto, verá que tras un millón de años (que no olvidemos ¡son
100.000 períodos de 10 años!) la caja negra, ¡con el torpe algoritmo más eficiente! habrá procesado
nada más que unos 56 datos...

Como curiosidad, y antes de seguir con lo que nos interesa, (y sin usar gráficas porque S.N. se queda colgado
al intentar dibujarlas usando tamañas cifras), veamos cuantos datos podrían procesar cada par
máquina/algoritmo durante toda una Edad de Universo (unos 15.000 millones de años, 4'7304*10 20 ms. El
tamaño de problema que pongo, es el máximo posible que necesita un tiempo que no sobrepasa ese límite
absolutamente infranqueable... como si en el Big-Bang pudiera funcionar Windows sobre un Pentium III :-)
función tamaño máximo tratable en toda una Edad del Universo
negro Tf(n) = 2n 68
azul Tg(n) = 4n 34
magenta Th(n) = n! 21
rojo Tk(n) = n n 16

Ahora que tenemos una idea más o menos clara sobre la intratabilidad a partir de tamaños realmente
bajos de los problemas de complejidad exponencial o superior, retomemos la justificación del
convenio ϑ(2n) ⊆ ϑ(3n) ⊆ϑ(4n) ⊆…
ÍNDICE
Habíamos dicho que a nivel matemático no queda duda: la cadena de inclusiones es ϑ(2n) ⊂ ϑ(3n) ⊂
ϑ(4n) ⊂ … Pero por otra parte, acabamos de comprobar que a nivel práctico poco nos importa lo que
diga la rigurosa definición matemática y que podemos considerarlos del mismo e intratable orden.
Porque para tamaños poco más que ridículamente pequeños, es seguro que estaremos criando malvas
antes de ver la solución que nunca llegará. La única manera de hacer casar ambas visiones es admitir
por convenio ϑ(2n)⊆ ϑ(3n)⊆ ϑ(4n) ⊆..., convenio que refleja tanto la diferencia de órdenes (inclusión)
por razones matemáticas, como la igualdad por razones prácticas.

Ahora bien: ya que la intratabilidad fue razón de peso para escribir ⊆ ¿por qué no aplicar el mismo
criterio para las factoriales y potenciales-exponenciales escribiendo ... ⊆ ϑ(kn) ⊆ ϑ(n!) ⊆ ϑ(nn)?. Por
otra razón de mayor peso vista en la Figura 8: las exponenciales con diferentes bases son intratables
para valores no muy bajos de n, pero sus gráficas no se cruzan; así, podemos verlas como una única
familia de gráficas con curvatura característica común (aunque estrictamente no lo sean). En cambio,

22
25905053.doc

las de órdenes superiores comienzan por debajo de las exponenciales para antes o después, más bien
antes, cruzarlas para crecer todavía más deprisa (y aunque en la Figura 8 casi no se aprecia, la
potencial-exponencial también cruza a la factorial). Admitir aquí el mismo convenio de inclusión o
identidad sería llevar las cosas demasiado lejos.
ÍNDICE
¡Y esto todavía puede dar más juego!. La logaritmación es la función inversa de la exponenciación;
por tanto, en buena lógica, para logaritmos de diferentes bases, debería seguirse el mismo convenio de
usar entre ellos el símbolo ⊆ en vez de ⊂. ¿Se sigue?.

Rotundamente no. Una función de complejidad logarítmica nunca jamás se podrá considerar
intratable, ya que son todas de una eficiencia absolutamente extraordinariamente (sólo superada por
las funciones constantes). Tan extraordinaria, exactamente en los mismos órdenes de magnitud, como
extraordinaria es la ineficiencia de las exponenciales. Por ejemplo, trabajando directamente sobre los
datos de las figuras anteriores, sin realizar cálculo alguno, e intercambiando el significado de los ejes
(ahora abscisas es tiempo y ordenadas tamaño), tenemos que: Si un algoritmo de complejidad log2n
(gráfica negra) en 4 segundos procesa unos 17 datos (Figura 8), resultará que en aproximadamente 38
segundos habrá devorado y digerido un problema con 315 millardos de datos (Figura 9). Y esto,
usando un algoritmo de complejidad log2n (nótese que incurro en el abuso de lenguaje ya
mencionado), que es el orden de más rápido crecimiento (por tanto menos eficiente) de todos los
órdenes logarítmicos. Para más detalles, ver la Figura 11 en la página 25.

Uniendo todo lo anterior, podemos finalmente completar la jerarquía de órdenes de complejidad:

ϑ(1) ⊂ ϑ(log2+ k n) ⊂ ϑ(log2n) ⊂ ϑ(n) ⊂ ϑ(nlogn) ⊂ ϑ(n2) ⊂ ϑ(n2+ k) ⊂ ϑ(2n) ⊆ ϑ((2+k)n) ⊂ ϑ(n!)
⊂ ϑ(nn), con k real no negativo.

La cadena de inclusiones es evidente a partir de las definiciones anteriores. Si la función coste que
estamos estudiando es T(n) = 3nlogn, es claro que pertenece a ϑ(nlog n). Por tanto, en ese orden
existen funciones lineales-logarítmicas que son cotas superiores de T(n); pero también serán cotas
superiores (siempre a partir de algún determinado n0) todas las funciones que pertenezcan a ordenes
superiores. Así cualquier cuadrática, cúbica o factorial acota superiormente a T(n).
Esta jerarquía de órdenes de complejidad es muy conveniente destacarla y memorizarla…o tener
habilidad con la chuleta.
ÍNDICE
Y si memoria o chuleta fallan, siempre se puede echar mano de la cuenta de la vieja. Nos preguntan,
por ejemplo, qué relación existe entre ϑ(n!) y ϑ(2n). Damos entonces valores arbitrarios a n y vemos
como se comportan cada uno de dichos órdenes al crecer n.
2 3 4 5 6 7
n! 2!=2 3!=6 4!=24 5!=120 6!=720 7!=5040
2 n 2
2 =4 3
2 =8 4
2 =16 5
2 =32 6
2 =64 27=128

Normalmente no es necesario escribir tantos casos. Esto es sólo un ejemplo para comprobar que existe
un número (4) a partir del cual el tiempo de ejecución se dispara para n!. T(n!) > T(2n), para todo n ≥
4. Por tanto, aunque no recordemos la jerarquía, podemos asegurar que ϑ(2n) ⊂ ϑ(n!) sin temor a
equivocarnos.

Importa destacar otra vez, que lo que nos preocupa es el estudio asintótico, esto es, cómo se comporta
el tiempo de ejecución para valores de n sucesivamente mayores. Por tanto, si en un determinado
problema conocemos a priori cuál será el tamaño máximo que pueden tener los datos, nos puede
resultar perfectamente útil un algoritmo supuestamente deficiente en tiempo por pertenecer a un orden
elevado, pero que para problemas de tamaño igual o menor a nuestro máximo, muestra un
comportamiento no asintótico (mejor en ese dominio), que el de otro algoritmo que haga lo mismo y

23
25905053.doc

que pertenezca a un orden inferior. Dicho más claro: nos quedamos con el algoritmo de orden superior
si en ese dominio su gráfica está siempre por debajo de la de orden inferior.
ÍNDICE

Pag 8. Definición 1.2. Ω (f(n))


La traducción es muy similar a la dada en el escolio de la definición 1.1. Usando las mismas
modificaciones notacionales allí usadas:
Sea f : N → R + ∪ {0}. El conjunto de las funciones Ω(f(n)), leído omega de f(n), se define como
sigue:

Ω(f(n))= {( g : N → R +∪{0}) |(( (


∃ c∈R + ∧∃ n0(c)∈N). ∀n≥ n0 ⇒ [g(n) ≥ cf(n)] ))}
Asimismo, diremos que una función g es de omega de f(n) cuando g∈ Ω(f(n))

Y usando el mismo convenio de omitir declaraciones de dominio y rangos sólo para aumentar la
legibilidad, tenemos:

El conjunto de las funciones omega de f(n), denotado Ω(f(n)), se define como sigue:

Ω(f(n))= { |(
g (
∃ c ∧∃ n0(c)). ∀n≥ n0 ⇒ [g(n) ≥ cf(n)] )}
La única diferencia con la definición 1.1 consiste que donde allí decía g(n) ≤ cf(n), ahora dice g(n) ≥
cf(n). Esto es, si antes necesitábamos de la existencia de una c (en Figura 3 y Figura 4 c = 181) que
sirviera para acotar superiormente a unas determinadas funciones, ahora nos interesa una c que
permita acotarla inferiormente. En las mismas figuras, o más claro en Figura 1 y Figura 2, resulta que
la función j(n) de gráfica verde es cota inferior para todas las demás funciones, incluida la cúbica roja,
para c = 1 y n0 ≈ 85. Todo esto tiene como consecuencia que la cadena de inclusiones de Órdenes
Omega es la misma que para Órdenes de Complejidad ϑ, pero con el signo de inclusión invertido:
ÍNDICE
Ω(1) ⊃ Ω(log n) ⊃ Ω(n) ⊃ Ω(nlog n) ⊃ Ω(n2) ⊃ …⊃ Ω( n2+ k) ⊃ … ⊃ Ω(2n) ⊃ Ω(n!) ⊃ϑ(nn)

Y usando los mismos convenios vistos en el escolio sobre la página 7, nos queda la siguiente cadena:

Ω(1) ⊃ Ω(log2+ k n) ⊃ Ω(log2 n) ⊃ Ω(n) ⊃ Ω(nlog n) ⊃ Ω(n2) ⊃ Ω(n2+ k) ⊃ Ω(2n) ⊇ Ω((2+k)n) ⊃ Ω(n!)
⊃ϑ (nn), con k real no negativo.

El significado de la jerarquía de traducción inmediata. Por ejemplo: toda función de orden cuadrático
está acotada inferiormente por alguna otra función cuadrática y por todas las de órdenes inferiores
(más a la izquierda en la anterior cadena)… de lo que se sigue un corolario de tipo práctico:
Es mejor no pensar en diferentes jerarquías para los órdenes ϑ y Ω y sí en una única sobre la que se
aplica el siguiente criterio: dada una función g(n) perteneciente a un orden O(f(n)) (nótese que no
distingo tipo de orden) todas las funciones de órdenes a la derecha O( f(n)) son cotas superiores de
g(n) y hacia la izquierda, inferiores.
ÍNDICE

Pag 8. Asimetría entre ϑ (f(n)) y Ω(f(n)). Primer párrafo después de definición 1.2
Vamos ahora con algo que creo que está mal (o que yo interpreto mal) y que contiene una
incorrección o cuando menos es de redacción confusa y fuente de confusión. En sentido estricto y
desde el punto de vista matemático entiendo que no existe asimetría alguna como afirma el libro.

Supongamos que tenemos el algoritmo/función ordena mencionado en los escolios a la página 3,


cuya función de tiempo es Tordena(n) [función de tiempo a la que en estas primeras páginas del libro se la
hubiera llamado con nombre genérico de función f(n), o incluso t(n)].

24
25905053.doc

Por procedimientos que ahora no nos interesan, realizamos tres estudios detallados de cómo se
comporta Tordena(n) en función del tamaño del problema en los tres casos típicos: peor, promedio y
mejor. Por las razones que sean (hipótesis que hubo que aceptar en el cálculo, posibilidad de varios
peores y mejores casos, 'imposibilidad' matemática de mayor precisión...) se llega a la siguiente
conclusión, donde n es el tamaño del problema y u.t. unidades de tiempo. Atención a los subíndices
de los logaritmos:

El caso peor, Tordena(n)  no excederá las 5log2n u.t. negro


 no bajará de las 5log325n u.t. gris

El caso promedio, Tordena(n)  no excederá las 4log44n u.t. marrón


 no bajará de las 4log52n u.t. siena

El caso mejor, Tordena(n)  no excederá las 2log827n u.t. azul


 no bajará de las 2log10n u.t. celeste
ÍNDICE

100

80

60

40

20

0 200000 400000 600000 800000 1e+06

Figura 11
ÍNDICE DE FIGURAS

Es claro entonces que cualquier ejecución de la función ordena aparecerá reflejada en la Figura 11
como un punto que necesariamente estará en el espacio comprendido entre las gráficas negra y
celeste.

Veamos el caso peor.


Tordena(n) está acotada superiormente por la función de gráfica negra; esta cota es absoluta para
cualquier ejecución del algoritmo. Aunque en sentido estricto, la cota la establece la función 5log 2 n,
como ésta pertenece a ϑ(log2 n), cometeremos abuso de lenguaje y diremos que ϑ(log2n) acota
absolutamente a Tordena(n) en cualquier caso. En particular, al peor.
Tordena(n) está acotada inferiormente por la función de gráfica gris. Esta cota ya no es absoluta, habrá
muchas otras ejecuciones de la función cuyo valores de tiempo estén por debajo de la línea gris.
Como es cota inferior, 5log3 25n pertenece a Ω(log3 n). Cometemos el mismo abuso de lenguaje y
diremos que este orden omega acota inferiormente el caso peor.

Ahora el caso mejor.


Tordena(n) está acotada superiormente por la gráfica azul. Esta cota no es absoluta. Diremos que ϑ(log8
n) acota superiormente el caso mejor.
E inferiormente, está acotada de manera absoluta por ϑ(log10 n) en todos los casos. En particular, el
mejor.
ÍNDICE

25
25905053.doc

Gráficamente la simetría es completa. Todas las cotas son relativas a su caso, excepto la superior del
caso peor y la inferior del mejor, que son absolutas para todos. Hay, eso sí, una asimetría en la letra
griega que denota a dichas cotas absolutas, zita y omega.

¿Por qué la redacción del libro?. Supongo que por razones de tipo práctico. Con relativa frecuencia el
estudio del caso peor no genera una función que exprese el coste temporal exacto, sino dos que lo
acotan superior e inferiormente. En cambio, el caso mejor normalmente será mucho más fácil de
estudiar (?) y conseguiremos una función de tiempo exacta. Lo que no cambia las cosas desde el punto
de vista matemático. Esa función exacta será cota inferior absoluta para todos los caso y superior
relativa para la mejor.
ÍNDICE

Pag 8. Definición 1.3. θ (f(n))


Teniendo en cuenta las dos cadenas de inclusiones vistas, esta definición es casi inmediata. Bueno,
algo menos que casi… puesto que de las definiciones 1.1 y 1.2 creo que se sigue un error en esta 1.3.
Lo comentamos al final del escolio.

Lo que esta definición quiere decir es: una función g(n) pertenecerá al orden exacto θ (f(n)) si y sólo
si g(n) es a la vez cota superior e inferior del tiempo de ejecución.
Lo que significa que, al contrario de lo que ocurría en el escolio anterior, al estudiar algún caso
obtenemos una función que expresa exactamente cuánto tardará el algoritmo en función del tamaño
del problema. Ésta será la situación típica de los problemas de PeDos, donde al no existir
instrucciones de bifurcación, no hay conjeturas que realizar sobre cuántas veces se entrará en una
rama determinada (ya se entenderá esto correctamente al estudiar EDyA)

Al contrario que en los órdenes ϑ y Ω, aquí no podemos establecer una única cadena de inclusiones
(¿trivial?). Pero, siguiendo lo dicho en el escolio a la Pag 8. Definición 1.2. Ω(f(n)), si g(n) pertenece
a θ(n2), podemos escribir:
Ω(1) ⊃ Ω(log2+ k n) ⊃ Ω(log2 n) ⊃ Ω(n) ⊃ Ω(nlog n) ⊃ θ(n2) ⊂ ϑ(n2+ k) ⊂ ϑ(2n) ⊆ ϑ((2+k)n) ⊂ ϑ(n!) ⊂
ϑ(nn).
Que es lo mismo que decir que todas las funciones de orden exacto θ(n2) están acotadas inferiormente
por todos los órdenes que están a la izquierda y superiormente por todos los que están a la derecha.
Gráficamente podemos ver un determinado orden exacto como un haz de funciones de curvatura típica
sobre los que no es posible establecer inclusiones de un orden exacto en otro. Al modo intuido en la
Figura 6.
ÍNDICE
Vamos con el posible error de interpretación… o de definición.
Según las definiciones 1.1 y 1.2, dada una función cualquiera g(n) perteneciente a O(f(n)), resultará
que dentro de ese orden existirán funciones que son cotas superiores de g(n) y otras diferentes que son
cotas inferiores. Basta encontrar los adecuados reales c. Por ejemplo: En las Figura 1 y Figura 2, y sin
necesidad de buscar nuevas c, resulta que a partir de aproximadamente n0= 20 la función g(n) de
gráfica magenta está acotada superiormente por las gráficas siena y celeste e inferiormente por las
verde y azul... todas ellas cuadráticas igual que g(n). Por tanto, podemos decir que g(n) pertenece a
θ(n2). De esto se sigue… que absolutamente todas las funciones pertenecerían a un orden exacto; cosa
que dudo mucho quiera decir el libro. Por eso traduje al principio de este escolio la definición 1.3 de
una manera que no se corresponde con la literalidad de la definición.

Se puede ver más claro con el siguiente ejemplo, tomado prestado con modificaciones de la pregunta
8 de ExP2_1998_2sem_C:
Por definición, cualquier función cuadrática pertenece a los siguientes dos órdenes:
• ϑ(n2), que es un conjunto que incluye a todas las funciones que asintóticamente crecen igual o
más lentamente n2: {1, logn, n, nlogn, n2}

26
25905053.doc

• Ω(n2), que es un conjunto que incluye a todas las funciones que asintóticamente crecen igual o
más rápido que n2: {n2, 2n, n!, nn}
Conjuntos que es evidente tienen un elemento común, en negrilla, y por tanto su intesección es no
vacía... de lo que se sigue, que según la definición 1.3 de Peña, tendríamos que cualquier función
cuadrática es de orden exacto cuadrático.
ÍNDICE

Pag 9. Definición 1.5. Producto de órdenes


Errata señalada por José Javier Romero Martín
Al final del recuadro
Dice: Esta definición se extiende de modo inmediato a la suma de órdenes Ωy θ.
Debe decir: Esta definición se extiende de modo inmediato al producto órdenes Ωy θ.
En cuanto a esta definición y la anterior, pasamos de ellas como de la tiña. Los ejercicios 1.6 y 1.7
dicen exactamente lo mismo y de una manera mucho más cómoda.

Pag 9. Ejercicio 1.6. Regla de la suma


Es trivial y evidente. Pensemos en una función cuadrática n2 y dibujemos su gráfica. Dibujemos ahora
la de n2+n+3. Observaremos que conforme aumente n, las ramas parabólicas de ambas gráficas se
confunden.
Una observación respecto a las funciones. Todas las que veremos en Programación 2, son funciones
reales de variable natural. No tiene sentido en una función de tiempo concreta escribir (-π )2 , pues no
habrá problemas con un tamaño de datos de entrada real. Normalmente, el tamaño del problema lo
determina el tamaño de un vector (un arreglo), que por definición tiene un número natural finito de
elementos. Es absurdo pensar en un vector de -3'1416 elementos. Por tanto, lo que acabo de
mencionar sobre dibujar la gráfica, no es más que una generalización, pues de esa gráfica (continua en
todo el dominio de definición, o en parte de él), en esta asignatura sólo nos interesan los puntos (n,
f(n)) tales que n∈N.
Por eso en el libro se usa f(n) en vez de f(x)
ÍNDICE

Pag 11. Figura 1.1


Atención a la interpretación de esta figura. La escala del eje de ordenadas (eje y) es logarítmica, donde
cada subdivisión representa un intervalo 10 veces mayor que la subdivisión precedente.
Es la única manera posible de representar en una misma figura las gráficas de funciones de
crecimiento tan dispar con un dominio superior a unas pocas unidades y sin que las gráficas de
crecimiento comparativamente lento se confundan con el eje de abscisas; incluida una gráfica
cuadrática.
Es muy recomendable jugar un poco con las gráficas de S.N. para hacerse una idea correcta de la
impresionante diferencia en las velocidades de crecimiento.
ÍNDICE

Pag 13 a 15. Reglas prácticas cálculo de la eficiencia


En los problemas (no en los test) que normalmente nos encontramos en los exámenes, no hay que
complicarse demasiado ni entrar en tanto nivel de detalle. Sólo tenemos instrucciones de asignación
de coste constante independientes de n, y un bucle o una llamada recursiva que depende directamente
de n (o indirectamente de i), mientras que sus protecciones (B, Bnt, Bt) son de coste constante. Por lo
que todas serán de orden lineal.

Pag 16. Punto 1.5. Resolución recurrencias coste

27
25905053.doc

Como puede tener un poco de guasa realizar un cálculo recursivo de costes antes de conocer
correctamente qué es una recursión, conviene no desesperarse en este punto. Intentar medio
comprenderlo, o directamente saltarlo, y volver sobre él una vez leído y comprendido 3.1 hasta 3.3.3.

Pag 17. Recuadro 1.2. Reducción por sustracción; función recurrente de coste
(Ver también el apartado 2 del problema de ExP2_1997_SepO_C)
Cuidado que a partir de aquí, se comienza a usar la expresión correcta de una función de coste: T(n),
en vez de la notación generalista usada hasta ahora, f(n).

Para evitar ambigüedades, a veces uso la notación Tf(n), o incluso la incorrecta T(f(n)) donde f(n)
representa al algoritmo o función (informática) cuyo coste expresa la función (matemática) T(f(n)). No
es una notación correcta, puesto que el dominio de T lo forman los tamaños del problema (número de
datos a procesar) y no la propia función (que expresada así, significa los datos de salida). A pesar de
ello, en ejercicios a veces la uso. Prefiero una notación incorrecta, pero evidente de significado, a una
ambigua que tanto sirve para un algoritmo como para una función de coste.

Aunque no necesariamente tiene que ser siempre así, serán los algoritmos/funciones recursivos los
que obliguen a un cálculo de coste recursivo. Es aquí donde esa ambigüedad notacional se hace
mucho más patente. No deben confundirse ambas recursiones. Una es la propia función recursiva f(n),
que es un algoritmo que 'hace algo' (lo que sea) con unos determinados datos de entrada. La otra,
T(n), Tf(n), T(f(n)) o ¡¡incluso t(n)!!, es una función de coste, también recursiva, que únicamente
calcula el coste de f(n). Ambas estarán íntimamente ligadas, pero en absoluto son la misma cosa. Las
admiraciones en t(n) creo que no necesitan justificación.
ÍNDICE
Veamos el esquema de una función f(n) recursiva con reducción del tamaño por sustracción:
f(n)
Bt → expresión_aritmética_o_booleana
Bnt →f(n-b) • f(n-b) • ... • f(n-b) • expresión_aritmética_o_booleana
a veces (a operandos)

Donde • representa a un operador válido (+, *, ∧, ...)


(Desde luego, si hay varias llamadas recursivas internas, no todas son iguales; f(n-b) sólo representa la
llamada con el tamaño del problema reducido).

Ahora sí podemos traducir el recuadro 1.2, al que le completo las condiciones de caso:
T(n) =  cnk si 0 ≤ n < b ≡ (n-b) < 0. En resumidas cuentas, si es caso trivial.
 aT(n-b) + cn si n ≥ b
k
≡ (n-b) ≥ 0. En resumidas cuentas, si es caso no trivial
ÍNDICE
Donde:
• T(n) es la función de coste de la función recursiva f(n).
• cnk es el monomio que describe el orden de complejidad de los costes no recursivos de cada caso
o costes asociados a cada llamada: evaluación de las protecciones (condiciones booleanas que
permiten o no entrar en el caso trivial), operación • (la llamada función combinación) y
resolución de la expresión aritmética o lógica (operación auxiliar actual). Como realizamos
estudio asintótico, la constante multiplicativa c podemos ignorarla. Averiguar la constante k, es
encontrar una k que haga cierto nk ∈ θ(no_recursivo). Así, si los costes asociados a cada llamada
son de orden lineal θ(n), tendremos que para que nk ∈ θ(n), k = 1.
Si son de coste constante, sólo k = 0 hace cierto nk ∈ θ (1). En todos nuestros problemas, las
partes no recursivas serán constantes (k = 0)
• a es el número de llamadas recursivas que se generan cada vez que estamos en el caso no trivial,
no el número total de llamadas recursivas que se generan en la ejecución del programa, como
puede parecer al leer la segunda línea después de (1.2). En los problemas que manejamos (no los
test) será a = 1. En este caso se dice que es una recursión lineal. Si a>1 se dirá que es una

28
25905053.doc

recursión no lineal o múltiple (varias llamadas en cada caso no trivial; veremos algún ejemplo
más adelante).
• n es el descriptor del tamaño del problema. Número de datos a procesar. En esta asignatura, casi
siempre el número de elementos de un vector a procesar en cada llamada.
• b es la cantidad que se sustrae al tamaño del problema en cada nueva llamada recursiva. Por
ejemplo, en un programa que recorramos ascendentemente el vector, saltando el índice de dos en
dos, uno de los argumentos de la llamada interna será de la forma i + 2. En este caso n = i, b = 2.
Atención al detalle, aunque la función sucesor es i + 2, en cada nueva llamada el tamaño del
problema queda reducido en dos unidades.

Como en cada llamada se reduce el tamaño n, llegará un momento que la reducción b es mayor que el
propio tamaño n; claramente no habrá nuevas llamadas recursivas y se entrará en el caso no trivial...
por eso en el recuadro 1.2 indican de esta manera tan enrevesada la condición de caso trivial.
ÍNDICE
Si hay más de una llamada recursiva (a > 1) en el caso no trivial, no todas con la misma reducción del
tamaño del problema, se tomará la b menor de todas las llamadas internas, la que determina una
menor reducción del tamaño del problema. Más extenso, en el escolio siguiente.

Destacar que aquí (línea 6 bajo el recuadro 1.2) ya se asume que el orden calculado es orden exacto
(θ), al no existir instrucciones de bifurcación... cosa bastante lógica en una recursión. Bueno, en
realidad todas las recursiones sí tienen una bifurcación; en la última llamada cuando se entra en el
caso trivial. Pero al contrario que las bifurcaciones iterativas, ésta es siempre de un coste fijo conocido
a priori.
De la fórmula recursiva anterior, tras casi dos páginas de cálculos, se llega a las siguiente fórmulas:
ÍNDICE

Pag 18. Recuadro 1.3. Reducción por sustracción; cálculo del orden del coste
Fórmulas que permitirán calcular directamente el coste de una función recursiva, con reducción por
sustracción, sin atender inicialmente a más detalle que el número de llamadas recursivas generado en
cada caso no trivial (lo que en las recurrencias anteriores se denotó como a). Sólo en el supuesto de
que a sea mayor que 1, se atenderá también al tamaño de la reducción (b)
T(n) ∈ θ (nk +1) si a = 1
T(n) ∈ θ (a n div b
) si a > 1

Sin usar los cálculos tan complejos e innecesarios del libro, justifiquemos estas fórmulas.
• a = 1 → θ(T(n)) = θ (nk +1)
(Conscientemente escribí la misma fórmula de una manera equivalente)
Adaptando el esquema visto en el escolio anterior a esta situación de una única llamada interna:
f(n)
Bt → expresión_aritmética_o_booleana
Bnt →f(n-b) • expresión_aritmética_o_booleana

En cada llamada, acabe siendo caso trivial o no, la función demora un cierto tiempo que
pertenece a θ(nk ). Si en total se realizan n llamadas recursivas, el coste total de la primera
llamada inicial será nθ(nk ) = θ(n*nk) = θ(nk + 1). [La primera igualdad, ¿necesita demostración?: n
∈ θ(n)].

Es cierto que con frecuencia no se realizarán exactamente n llamadas internas (especialmente si


la reducción b es mayor que 1), pero ello no obsta para que se realice un número de llamadas que
es función directa del tamaño inicial del problema.

• a > 1 → θ(T(n)) = θ (an div b)


Volvamos al esquema anterior, esta vez con más de una llamada.

29
25905053.doc

f(n)
Bt → expresión_aritmética_o_booleana
Bnt →f(n-b)•... • f(n-b) • expresión_aritmética_o_booleana
a veces
ÍNDICE
El orden del coste asociado a cada llamada ya lo conocemos: θ(nk ). Ahora, ¿cuántas llamadas se
producirán en total sabiendo que en cada llamada se realizan a llamadas internas y que éstas
reducen el tamaño en b unidades?.
Veamos primero el siguiente ejemplo: partimos de n = 9, b=3 y a=2. Esquemáticamente,
tendremos el siguiente mapa de tamaños en memoria, donde cada línea representa un nivel de
recursión y cada cifra el tamaño del problema para cada una de las llamadas que se producen en
ese nivel.
9
7 7
4 4 4 4 4 4
1 1 11 1 11 1 1 1 1 11 1 11 1 1

Bien, desde luego el número total de llamadas será de la forma 2*2*... ¿pero cuantos factores?:
tantos como niveles de recursión, que resultan ser 9div3 (o bien ndivb). Por tanto, finalmente
habrá en total 2 ndivb llamadas, cada una de coste θ(nk )

Generalizando. Si en cada nivel de recursión se reduce el tamaño del problema en b unidades,


harán falta ndivb niveles para reducir el tamaño a 1. En cada nivel, al multiplicar por a el
número de llamadas, el número total de será a ndivb . Finalmente: habíamos visto que el coste de
cada una es de orden θ(nk ), de lo que se sigue que el coste global de la recursión será de orden
andivbθ(nk ) = θ(andivbnk ). Si el orden nk es constante, todo queda reducido a θ (an div b).

¿Y si no es constante?: También podemos ignorarlo, gracias a un pequeño detalle sin


importancia. El factor andivb pertenece al orden exponencial, que por intratable permite prescindir
del otro factor, que lo único que logrará será adelantar o atrasar la frontera de la intratabilidad. A
no ser claro, claro está, que este segundo factor sea a su vez de orden exponencial, lo que
convertiría al problema en exponencial-potencial... mas intratable todavía. Y no puede ser de un
orden que anule la intratabilidad porque no existe ninguno por debajo del constante (al menos a
nivel práctico de problemas informáticos; ya habíamos visto en el apéndice 3 del escolio a la Pag
6 'Definición 1.1.ϑ(f(n)) e introducción a los Órdenes' que matemáticamente la definición de 1.1
del libro permitiría definirlos).

ÍNDICE
Pero, en el caso de varias llamadas internas ¿qué ocurre si cada una de ellas reduce el problema de
manera diferente?. Por ejemplo, sea el caso no trivial f(n-2) • f(n-3) • f(n-5) • lo_que_sea; ¿qué
reducción b consideraremos para aplicar estas fórmulas?, ¿2, 3 ó 5?.
La que haga decrecer el problema más lentamente.
La primera llamada a la función, generará tres llamadas recursivas internas; en el siguiente nivel de la
cadena descendente de llamadas, se generarán 9 llamadas (tres por cada una del nivel superior), y así
sucesivamente, generándose un árbol ternario descendente. Cuando las llamadas internas con
reducción 5 lleguen al caso trivial, ya podrían iniciar la cadena ascendente de combinaciones;
combinaciones que no se pueden realizar porque las llamadas con reducciones 3 y 2 todavía seguirán
generando llamadas recursivas. No se podrá iniciar la cadena ascendente hasta que todas las cadenas
descendentes, en particular la de reducción 2, hayan llegado al caso trivial.

En bastantes ejercicios nos encontramos que tanto T(n) como θ(f(n)) los expresamos como T(i) y
θ(f(i)).

30
25905053.doc

Aunque creo que es notacionalmente válido, es incorrecto como lo es T(f(n)). i es una variable de
inmersión que nos permite ir recorriendo un vector y que es función directa de n, que es quién de
verdad determina el tamaño del problema.

Para terminar, pasemos a lo que creo es un error matemático en este recuadro 1.3. El tamaño de
problema n es una variable de valor no determinado apriorísticamente; es perfectamente válido
referirse al tamaño de cualquiera de las manera siguientes: n, 5n, n2, ndivb... pues son modos
equivalentes de expresar una cantidad indeterminada. Un vector de tamaño n es cinco veces menor
que uno de tamaño 5n; tiene un número de elementos es raíz del de otro de n2 y es b veces mayor que
otro de tamaño ndivb. Todos son tamaños y a todos les llamamos n. No existe el orden θ(an div 2).
Bueno; sí, existe: θ(an ).
ÍNDICE
¿Hay alguna justificación práctica para considerarlo un suborden propio?. Puede que sí. Ignoro si hay
algoritmos así, pero supongamos que sobre un vector de un millón de elementos va a actuar un
algoritmo de esta complejidad. Si la reducción b es pequeña, claramente el problema es intratable;
pero si conseguimos modificar el algoritmo para que la reducción sea drástica, aún a costa de generar
excesivas llamadas internas, el problema puede llegar a ser gigantesco pero tratable. Por ejemplo,
logramos una reducción b = 500.000, pero para ello tenemos que realizar otras tantas llamadas
internas. Obtendríamos un coste temporal de 500.0002 = 250 millardos de unidades de tiempo; si esa
u.t. es la milésima de segundo, el algoritmo tendría que estar ejecutándose durante casi 8 años. Un
tiempo más que considerable, pero abordable en una vida... una vez resueltos los problemas de
cantidad de memoria necesaria.
Como ejemplo de la comparativamente poca importancia que tiene la base para la intratabilidad de
problemas exponenciales, sirva la siguiente figura, donde las ordenadas abarcan un período de diez
años:
negro Tf(n) = 5n unidades de tiempo
azul Tg(n) = 10n unidades de tiempo
magenta Th(n) = 1000n unidades de tiempo
rojo Tk(n) = 100000n unidades de tiempo

ÍNDICE
3e+11

2.5e+11

2e+11

1.5e+11

1e+11

5e+10

0 2 4 6 8 10 12 14 16

Figura 12
ÍNDICE DE FIGURAS
ÍNDICE

Pag 19. Recuadro 1.4. Reducción por división; función recurrente de coste
Atención a la errata señalada más abajo.
Cálculo recursivo de la función de coste de una función recursiva f(n) con reducción del problema por
división.

Esquema de una función f(n) de este tipo:


f(n)

31
25905053.doc

Bt → expresión_aritmética_o_booleana
Bnt →f(n/b) • f(n/b) • ... • f(n/b) • expresión_aritmética_o_booleana
a veces (a operandos)

Donde • representa a un operador válido (+, *, ∧, ...)

Idéntica notación y similares consideraciones a las realizadas en el escolio a la página 17, recuadro
1.2.
No es necesario justificar ni explicar nada. Pero atención a la errata:

T(n) = cnk si 0 ≤ n < b ≡ n/b < 0. En resumidas cuentas, si es caso trivial.


aT(n/b) + cnk si n ≥ b ≡ n/b ≥ 1. En resumidas cuentas, si es caso no trivial

El 0 que aquí destaco en gris, en el libro es un 1. Si fue válido el cero en los problemas de reducción
por sustracción, aquí también tiene que serlo. La duda está en si en ambos casos se puede admitir. La
respuesta creo que es sí. A fin de cuentas, si n=0, resultaría un teórico coste cero, que por ser lineal, es
de orden 1. Esto no significa en absoluto que no se consuma tiempo, pues la sola comprobación de las
protecciones de los casos (saber si se entra o no en el caso trivial) consume tiempo.
Hasta ahora no hemos hablado de precondiciones ni restricciones en los dominios. Si así hubiera sido,
habría que considerar el coste de evaluar si la llamada cumple los requisitos requeridos. Otra vez un
coste.

De la anterior recurrencia se llega al siguiente recuadro de fórmulas.


ÍNDICE

Pag 20. Recuadro 1.5. Reducción por división; cálculo del orden del coste
Atención a la errata señalada más abajo.
Fórmulas que permitirán calcular directamente el coste de una función recursiva, con reducción
división, sin atender a más detalles que el número de llamadas recursivas generado en cada caso no
trivial (lo que en las recurrencias anteriores se denotó como a) y al tamaño de la reducción (b)
Consideraciones similares a las anteriores. Como antes, la primera línea que figura en el libro, carece
de interés práctico.

En el libro dice:
T(n) ∈ θ (nk log n) ,si a = bk
T(n) ∈ θ (n ^ log b a) ,si a > bk

pero debe decir:


T(n) ∈ θ (nk log b n) ,si a = bk
T(n) ∈ θ (n ^ log b a) ,si a > bk

En la primera fórmula, resulta oscura la expresión condicional 'si a = bk '. Supongamos que las partes
no recursivas de orden θ(nk ) pertenecen en realidad a cada uno de los siguientes supuestos:
• Constante: θ(nk ) = θ(n0) = θ(1) ; el condicional se convierte en a = b0 = 1.
• Lineal: θ(nk ) = θ(n1) = θ(n) ; el condicional se convierte en a = b1 = b.
• Cuadrático: θ(nk ) = θ(n2) ; el condicional se convierte en a = b2.
• Cúbico: θ(n ) = θ(n )
k 3
; el condicional se convierte en a = b3.
... ... ...
Por tanto esta fórmula será de aplicación cuando el número a de llamadas sea igual a la reducción del
problema b, o cuando el número a de llamadas sea potencia de la reducción b y esta potencia sea del
mismo orden que la complejidad de lo no recursivo. Mnemónicamente: si a ∈ θ(nk ).
ÍNDICE

32
25905053.doc

Para la segunda fórmula (si a > bk)


• Constante: θ(nk ) = θ(n0) = θ(1) ; el condicional se convierte en a > b0 ⇒ a > 1.
• Lineal: θ(nk ) = θ(n1) = θ(n) ; el condicional se convierte en a > b1 ⇒ a > b.
• Otro: θ(nk ) ⇒ k > 1 ; el condicional se convierte en a > bk ⇒ a > b.

Es válida para funciones que tengan un número a de llamadas internas superior a la reducción b
elevada al orden de lo no recursivo. Mnemónicamente: si la complejidad de a es mayor que la de lo
no recursivo. Por ejemplo estaríamos en esta situación en una función con reducción b=2 si lo no
recursivo es lineal y el número de llamadas internas es superior a dos. O si la complejidad de lo no
recursivo es cúbica y el número de llamadas es superior a 8, etc.

No he conseguido encontrar una manera más cómoda de demostrar estas fórmulas. Llego a lo que para
mi es un callejón sin salida. Por si alguien puede seguir, este es mi desarrollo inicial:

Si un problema tiene tamaño n y en cada llamada se divide n por b, se alcanzará tamaño 1 (caso
trivial) tras logbn niveles de recursión (ver nota al final). En el nivel_0 (llamada externa no recursiva)
se producen a0 llamadas, en el nivel_1 se producen a1 llamadas, en el nivel_2 serán a1, … en el último
a^(log bn).

En total habrá a0 + a1 + a2 + … + a^(log bn). Una sucesión geométrica de razón a cuya suma es:
[1-a^(log bn)] / 1-a. Esta expresión vamos a usarla dentro de un orden de complejidad, por lo que
podemos eliminar constantes (1) y constante multiplicativas (1 / 1-a), con lo que nos queda un total
aproximado de a ^ (log bn) llamadas a la función ( ^ como símbolo de potencia):
Si los costes asociados a cada llamada pertenece a θ(nk ), el coste total (función del tiempo) será:
T(n) ∈ θ(nk )[a ^ (log bn)]= θ(nk [a ^ (log bn)])
A partir de aquí, todos los intentos posteriores fueron en vano; incluidos aquellos en los que
consideraba la expresión 1-a^(log bn) / 1-a sin eliminar constantes ni constantes multiplicativas.

Nota: por logbn se ha de entender siempre la parte entera del logaritmo. Por tratarse de un estudio
asintótico, despreciamos la pequeña diferencia que se puede producir al considerar sólo la parte
entera,  logbn o la parte entera con redondeo por exceso  logbn .

ÍNDICE

Salto de página.../...

33
25905053.doc

ÍNDICE

TEMA 2. Especificación de problemas


Pag 28. Ejemplo de especificación
Por culpa de la n que figura en la precondición y en la lista de parámetros formales, el ejemplo es
poco afortunado y puede conducir a confusión.

Es cierto que en un entorno de trabajo real es muy conveniente (cuando no imprescindible) especificar
qué tamaños mínimos y máximos de datos puede soportar un programa dado, e indicar cómo tratará el
programa, si es que las acepta, las entradas de tamaño nulo. Pero aquí estamos en un entorno teórico y
en ninguna otra parte del libro se vuelven a presentar ejemplos donde se realicen restricción sobre el
tamaño del vector,

Sobre este tema de las especificaciones y a lo largo de todo el libro, se sigue un doble convenio no
declarado:
1. En la lista de parámetros formales, sólo se indica el nombre del vector (o vectores) de entrada,
sin necesidad de declarar su tamaño (excepto, otra vez, implícitamente a través de la variable o
variables de inmersión; habitualmente i).
2. La función sólo es válida para llamadas externas (primera llamada en recursivas, o única en
iterativos) destinadas a tratar el vector completo, nunca una parte de él.
O bien, la redacción alternativa:
2'.Siempre se considerará como vector completo sólo aquella parte que se vaya a procesar. Esto es,
si como parámetro formal de una función tenemos un vector v , y en la llamada externa forzamos
que sobre un vector real de 100 elementos sólo se consideren los índices comprendidos entre el
12 y el 20, para la función el vector v tendrá únicamente 9 elementos.

Este segundo convenio es de capital importancia, porque casi siempre vamos a tratar con funciones en
las que hay que realizar o se ha realizado una inmersión de diseño, por lo que acabaríamos teniendo
especificaciones como la del siguiente ejemplo, de una función que comprueba si la suma de todos los
elementos de un vector es igual a 10
ÍNDICE
{Q≡ 1 ≤ i ≤ n}
fun suma10 (a:vector; i: entero) dev (b:booleano)
{R ≡ b=(10= Σα∈ {1..i}.a[α] )}
y donde es perfectamente lícito realizar una primera llamada que sólo compruebe las i primeras
posiciones, con i estrictamente menor que n ('apelamos momentáneamente a la fe del lector' ;-)
Si el vector es a≡ [2,4,1,3,0,7] y se realiza la llamada externa suma10(a,6), nos devolverá falso, pues
la suma es 17, mientras que si pasamos suma10(a,4) devolverá cierto. Ambas primeras llamadas
pasan perfectamente la precondición. Sólo admitiendo el segundo convenio, la segunda llamada sería
incorrecta.
ÍNDICE

Pags 29 a 40. Escolios de Lógica Matemática


Prácticamente todo lo contenido en estas páginas está incluido en la asignatura Lógica Matemática de
una manera mucho más comprensible... que no induce a 'inexplicables errores por parte del inepto del
alumno'.
Copio y pego parte de los escolios que escribí de esa asignatura, mucho más breves y esquemáticos
que los presentes.
Escolios sobre el capítulo III, Lógica de predicados de primer orden del libro 'Fundamentos de
Lógica Matemática' de J. Aranda, J.L. Fernández, J. Jiménez y F. Morilla. Ed. Sanz y Torres.
Madrid 1999.

34
25905053.doc

LoMa. Pag. 92. III.2.1


Si en una sentencia todas las variables que intervienen en las fórmulas atómicas están restringidas por
medio de un cuantificador (∀, ∃ ), diremos que la sentencia es cerrada, pues al haber establecido con
los cuantificadores una restricción o especificación de las condiciones del dominio podremos evaluar
el valor veritativo de la sentencia para todos y cada uno de los posibles valores de las variables del
dominio.
Por el contrario, si la sentencia es abierta, no hemos establecido restricciones sobre el dominio, con lo
que no podremos dar un interpretación (veritativa) de la sentencia mientras no sepamos qué valores
pueden tomar las variables.

LoMa. Pag 94. III-5 a III-10.


Todas estas identidades son de una trivialidad pasmosa, como pasmosa es la facilidad con la que te
olvidas de algo tan trivial en momentos críticos. Por eso es mucho más práctico aplicar el sentido
común lingüístico y no pretender memorizarlas, usando eso sí la traducción más cómoda y que
contenga las mínimas negaciones.

Veamos por ejemplo las dos primeras (en cursiva la lectura recomendable):

∀xPx ≡ ¬∃ x(¬Px) Ningún x no posee la propiedad P


Al pie de la letra habría que leerla 'no existe algún x no posee la propiedad P. Supongamos que esa
propiedad P es 'no ser igual a cero'; tendríamos la siguiente frase de difícil comprensión 'no existe
algún x que no es no igual a cero'. Por eso es preferible usar la expresión indicada más arriba en
cursiva sustituyendo además 'no es no cero' por 'es cero': ningún x es cero.

∃ xPx ≡ ¬∀x(¬Px) Algún x no posee la propiedad P.


Usando el ejemplo anterior: algún x es cero
En los ejemplos puestos se observa que he procurado evitar expresiones, tan corrientes y no ambiguas
del lenguaje hablado, como 'no existe ningún', puesto que contienen una doble negación que todos
entendemos perfectamente como la negación simple 'ninguno', pero que puede ocasionar serios
problemas si el predicado a su vez contiene negaciones. Problemas que se agravan si el dominio se
hace vacío.
ÍNDICE
Aunque esto ya entre en el terreno de la lógica de descriptores o de predicados con identidad,
conviene tener cuidado con las siguientes expresiones que aparecen con bastante frecuencia en
definiciones matemáticas:
∃ x∀y.Pxy : existe algún x (sea o no uno único), común para todo y, tales que verifican P. Por
ejemplo, existe (hay) pastel para todos los niños que son alumnos de esta clase. No estamos diciendo
cuantos pasteles hay, sólo que al menos existe uno, y que sean los que sean, son para todos
∀y∃ x.Pxy Para cada y, existe alguna x tales que verifican P. Por ejemplo, para cada uno de los
niños de la clase hay un pastel... que es bastante más golosa que la frase anterior.

LoMa. Pag. 96 y ss. Interpretación.


Los casos mencionados aquí son simples. Veamos uno bastante más complejo sacado del examen de
la segunda semana de junio de 2000, tipo E, aprovechando el ejercicio para discutir otros temas (antes
sería conveniente pelearse con los problemas 182, 187 y otros similares de la colección de problemas
del CD -números de la versión de 1998-).
Nos dan los siguientes predicados (inmediatamente después de cada predicado su clausulación):
P3 ≡ ∀x∃ y(Rxy →Sxy) ≡ ∀x∃ y(¬Rxy ∨Sxy) ≡ ∀x(¬Rxfx ∨Sxfx) ≡ ¬Rxfx ∨Sxfx.
P4 ≡ ∀x∃ y(Rxy→Qx) ≡ ∀x∃ y(¬Rxy ∨Qx) ≡ ¬Rxfx ∨Qx.
P5 ≡ ∃ x∀y(Qx→¬Sxy) ≡ ∃ x∀y(¬Qx ∨¬Sxy) ≡ ¬Qa ∨¬Say.

Y la siguiente interpretación M: U={0,1,2}, Q={0}, R={(2,2)}, S={(0,1),(1,2),(2,1)}.


ÍNDICE

35
25905053.doc

Al presentarnos así la interpretación, quiere decir que tenemos un conjunto Universal de sólo tres
elementos (los señalados: 0, 1 y 2), que al predicado Q sólo lo hará cierto el sujeto 0, a R sólo el par
(2,2) -esto es, cuando x=2, y=2-, y a S lo hacen cierto los pares indicados.

Intercalo, con tipo de letra, tamaño y color diferentes acotaciones a este ejercicio con la notación y
terminología usada en Programación II sin entrar en mayores detalles (Peña 33 a 41)
En este ejercicio, tanto R como S son predicados con dos variables y dada la interpretación ofrecida,
ambas son del mismo tipo, pues pertenecen al mismo universo del discurso. Normalmente en PII
deberemos especificar el dominio (universo) de cada una de las variables.
Para el predicado Q: ID ≡ {x} y D ≡ U (ID: conjunto de identificadores, D: dominio)
Para los predicados R, S: ID ≡ {x,y} y D ≡ UxU (producto cartesiano)
En ambos casos los dominios coinciden con los respectivos conjuntos ε de todos los posibles estados y
se cumple #ε Q =3 y #ε R = #ε S = 9.
estados(Q)≡ {0}, estados(R)≡ {(2,2)}, estados(S)≡ {(0,1),(1,2),(2,1)}.

¿Cuántas funciones de Skolem hay que introducir en P3 para clausularla? (en el examen preguntaba
en P4). Una; puede parecer que dos, pero se introduce dos veces la misma función. Otra cosa sería si
por haber más cuantificadores hubieran aparecido g(x) y f(x), en cuyo caso sí serían dos funciones.

¿La interpretación M satisface a ¬P4?: se trata de demostrar que ∀x∈U, ¬P4 es una tautología. O bien
si encontramos un contraejemplo podemos asegurar que M no satisface a ¬P4.
¬P4 ≡ Rxf(x) ∧ ¬Qx. Para x=0, Q se hace verdadero, ¬Q falso, lo que hace toda la conjunción falsa.
Por tanto, M no satisface a ¬P4.
ÍNDICE

ε ¬p4 = UxU, estados(¬P4)≡ {∅}

¿M satisface a P4?. El gran escollo. Veamos varias maneras de resolverlo


P4 ≡ ¬Rxfx ∨Qx.
Para x=0, Q cierto, P4 cierto (por ser P4 una disyunción).
Para x=1, Q falso, pero ¬R1* (* será cualquier valor) es cierto, entonces P4 cierto.
Para x=2, Q falso, ¿¬R2*?. Aquí hay que recordar qué es la función de Skolem en este caso concreto:
es algún (o algunos) elemento(s) del universal que en función de x hace falso a R. No quiere decir que
tengamos que probar todas las combinaciones del 2 con los otros elementos del universal. (Lo
veremos más claros con los otros métodos para resolver el problema). Y bien, en este caso, existen
dos elementos, el 0 y el 1 que hacen a ¬R cierto. Por tanto, también en este caso P4 es cierto y
podemos asegurar que M satisface P4.
Otro método, muy claro pero largo. Usando III-11 a III-14 del libro podemos escribir, partiendo de la
forma no clausulada:
P4 ≡ ∀x∃ y(Rxy→Qx) ≡ ∀x∃ y(¬Rxy ∨Qx) ≡
≡ [(¬R00∨Q0) ∨(¬R01∨Q0) ∨(¬R02∨Q0)] ∧[(¬R10∨Q1) ∨(¬R11∨Q1) ∨(¬R12∨Q1)] ∧
∧[(¬R20∨Q2) ∨(¬R21∨Q2) ∨(¬R22∨Q2)]
que es una tautología, pues tenemos [V∨V∨V]∧[V∨V∨V]∧[V∨V∨F].
Como ejercicio: ¿por qué no es válida la siguiente transformación?:
P4 ≡ ∀x∃ y(Rxy→Qx) ≡ ∀x∃ y(¬Rxy ∨Qx) ≡
≡ [(¬R00∨Q0) ∧(¬R01∨Q0) ∧(¬R02∨Q0)] ∨[(¬R10∨Q1) ∧(¬R11∨Q1) ∧(¬R12∨Q1)]
∨[(¬R20∨Q2) ∧(¬R21∨Q2) ∧(¬R22∨Q2)]
Y el tercer método, quizá el más útil en este tipo de problemas. Trabajar directamente sobre la forma
no clausulada, pero sin condicionales.
P4 ≡ ∀x∃ y(¬Rxy ∨ Qx). Para x igual a 0 y a 1, no tenemos problemas, se satisface (lo hemos visto
más arriba). Y para x=2, ¿existe algún y perteneciente al universal que haga cierto P4?. Sí, tenemos
dos (y=0 e y=1, aunque nos llega y sobra con uno). Por tanto, lo ya repetido otras veces, M satisface a
P4
ÍNDICE
ε p4 = UxU, estados(P4)≡ {(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1)}

36
25905053.doc

¿M satisface a P3?. Queda como problema, muy similar al anterior, que también tiene respuesta
afirmativa.

¿Es ¬P4 satisfacible?. Como ya hemos visto que M no satisface a ¬P4; existe entonces la tentación de
decir que no. Pero para que sea satisfacible, basta con que exista una asignación de variables (dentro o
fuera de M, pero por supuesto dentro del universo del discurso) que haga a ¬P4 verdadera, como es el
caso.
Esto entra dentro de lo que habría que establecer como convenio, pero en principio creo que si la
pregunta fuese ¿es ¬P4 satisfacible en la interpretación M?, la respuesta seguiría siendo afirmativa, a
pesar que la interpretación M, como un todo, no satisface ¬P4.

Por ejemplo, dentro de la interpretación M el estado σ = (x=2,y=2), satisface ¬P4:


[¬P4] (x=2,y=2) = V.
Fuera de la interpretación de M, con los datos que tenemos, no podemos decidir.

LoMa. Pag. 103 III-42.


Conviene no olvidar nunca este convenio notacional ∀xy ≡∀ x∀y, ∃ xy ≡ ∃ x∃ y.
En el momento que predicamos algo (P) de dos o más sujetos (xy) y lo ponemos en un único
predicado, estamos entrando de cabeza, sin quererlo ni saberlo, en lógica de relaciones. Si tú y yo
somos L, siendo L el predicado ‘ser estudiante de lógica’, es que pertenecemos a la clase de
equivalencia formada por todos los estudiantes de lógica, nos guste o no.
ÍNDICE

LoMa. Pag. 109, párrafo anterior a III-59.


Tal como está redactado, suena como si estuviéramos haciendo trampa. Pero veamos otra manera: si
escribimos ∃ xPx, esto lo leemos ‘En todo el universo del discurso sí existe algún x tal que esa x sea
P’. Pues bien, a ‘esa x’ dejaremos de llamarla así para llamarla a. ¿Qué quién es a?. No nos preocupa,
nosotros nos basta con saber que a ∈ al universo del discurso y que verifica la propiedad ‘ser P’. En
lo que sigue de libro de texto, a todas las variables/falsas_constantes se les denomina 'constantes de
Skolem. Esta x que es a, no tiene que ser única (el caso de unicidad de la x que es a ya lo veremos
más adelante), pero es algo que tampoco nos preocupa.
ÍNDICE

LoMa. Pag. 110, final de segundo caso (…al ser imposible..).


También aquí la redacción puede resultar confusa. Tal vez sirva (¿?) completar la frase de la siguiente
manera: al ser imposible asignar un valor arbitrario para dicha constante de Skolem, ya que la variable
a sustituir esta ligada tanto al existencial como al universal. Para cada posible valor de la x existirá
otro valor que cumpla lo que sigue, pero dependerá en cada caso de la x que tomemos del universo
del discurso.

LoMa. Pag. 110. último párrafo.


Antes de seguir, hay que fijar el convenio notacional que se va a usar pero que no está
convenientemente explicado. Siguen ahora unos ejemplos que usan dicho convenio, donde g y f serán
funciones y a una constante de Skolem. Para mayor claridad, pongo el punto que normalmente
omitimos y que se lee ‘tal que’.
∀x∃ y∀z∃ t.Rxyzt ≡ (∀x∃ y)^(Az∃ t).Rxyzt ≡ Rxf(x)zg(z) y está ligada a x, t está ligada SÓLO a z.
∀x∃ y.Pxy ≡ (∃ f)∀x.Pxf(x)
∃ y.Rxy ≡ ∀x∃ y.Rxy ≡ Rxf(x)
∃ x∀y.Rxy ≡ Ray

Y otros cuatro ejemplos que no son convenio notacional, pero es conveniente destacarlos y que son
fáciles de demostrar convirtiendo P→Q en ¬P∨Q
∀x(Px→∃yQy) ≡ ∀x∃ y(Px→Qy) ≡ Px → Qa
∀x(∀yPy →Qx) ≡ ∀x∃ y(Py→Qx) ≡ Pa→Qx

37
25905053.doc

∀x(∃ yPy→Qx) ≡ Py → Qx
∀x∃ y(Pxy → Qy) ≡ Pxf(x) → Qf(x).

Pag 32. Variables ligadas. Primer párrafo bajo la definición 2.3


Si tenemos dos cuantificadores no anidados, aunque no es recomendable en la práctica, no existe
problema alguno por usar el mismo nombre griego para las variables ligadas. Por ejemplo, sacado del
apartado 1 del problema de ExP2_1998_SepR, tenemos la siguiente especificación que usa el mismo
nombre α para dos variables ligadas distintas.
{Q ≡ cierto} fun vfun(v: vector: [1..n] de ent) dev (b: bool)
{R ≡ b = [Nα ∈ {1..n}. (v[α] > 0)] > [Nα ∈ {1..n}. (v[α] < 0)]}
ÍNDICE

Pag 32. Convenio de precedencia de operadores y ejemplo 2.4


Faltan en este convenio las operaciones y comparaciones algebraicas. Por ejercicios resueltos de CD y
exámenes pasados con respuestas oficiales, supongo que completo será:
< ¬, operaciones_algebraicas, comparaciones estrictas, comparaciones, =, ∧, ∨, →, ↔, ∂ >.
donde operaciones_algebraicas, incluyendo signo de número negativo, sigue su propio convenio ya
conocido en matemáticas

Parece ser que no existe un convenio establecido para ⇒. Por lógica podría parecer que su sitio es
entre → y ↔, aunque también es posible que su sitio sea en el extremo derecho. Será entonces el
contexto quien decida la precedencia de la implicación.

La no existencia del convenio completo puede acarrear serios problemas en la interpretación de


algunos ejercicios, como por ejemplo la pregunta 3 de ExP2_1997_SepO_C. Personalmente opino
que es absolutamente inadmisible la escritura no por mero error de una expresión lógica de
interpretación ambigua. Para eso se han inventado los paréntesis. Úsense preceptivamente cuando
no exista convenio de prevalencia, y cuando sí... también, aunque con mesura.

Siempre tengo dudas para recordar quién es más prevalente, ∧o ∨. Basta entonces recordar que a ∧se le
llama también producto lógico y a ∨ suma lógica, y que tienen la misma prevalencia que sus
homónimos algebraicos.

Para colocar correctamente los paréntesis en una expresión como la del ejemplo 2.4, se pueden seguir
dos caminos:
ÍNDICE
Trabajar de mayor a menor prioridad. De dentro hacia fuera. O de más corto a más largo alcance
(método que me parece más seguro): (sombreados los nuevos paréntesis introducidos en cada paso)
∀α ∈ D1.∀β ∈ D2.P1 ∧P2 ∨¬P3 → P4 ↔ P5 ∨P6
∀α ∈ D1.∀β ∈ D2.(P1 ∧P2) ∨(¬P3) → P4 ↔ (P5 ∨P6)
∀α ∈ D1.∀β ∈ D2.[(P1 ∧P2) ∨(¬P3)] → P4 ↔ (P5 ∨P6)
∀α ∈ D1.∀β ∈ D2.{[(P1 ∧P2) ∨(¬P3)] → P4} ↔ (P5 ∨P6)
∀α ∈ D1.∀β ∈ D2.({[(P1 ∧P2) ∨(¬P3)] → P4} ↔ (P5 ∨P6))
∀α ∈ D1.[∀β ∈ D2.({[(P1 ∧P2) ∨(¬P3)] → P4} ↔ (P5 ∨P6))]
{∀α ∈ D1.[∀β ∈ D2.({[(P1 ∧P2) ∨(¬P3)] → P4} ↔ (P5 ∨P6))]}

Trabajar de menor a mayor prioridad. De fuera hacia dentro. O de más largo a más corto alcance:
{∀α ∈ D1.∀β ∈ D2.P1 ∧P2 ∨¬P3 → P4 ↔ P5 ∨P6}
{∀α ∈ D1.[∀β ∈ D2.P1 ∧P2 ∨¬P3 → P4 ↔ P5 ∨P6]}
{∀α ∈ D1.[∀β ∈ D2.(P1 ∧P2 ∨¬P3 → P4 ↔ P5 ∨P6)]}
{∀α ∈ D1.[∀β ∈ D2.({P1 ∧P2 ∨¬P3 → P4} ↔ P5 ∨P6)]}
{∀α ∈ D1.[∀β ∈ D2.({[P1 ∧P2 ∨¬P3] → P4} ↔ P5 ∨P6)]}

38
25905053.doc

{∀α ∈ D1.[∀β ∈ D2.({[(P1 ∧P2) ∨(¬P3)] → P4} ↔ (P5 ∨P6))]}

Normalmente es preferible acostumbrase a uno de los dos métodos, y no mezclarlos (aunque en la


práctica se haga... con resultados no siempre deseables) para evitar errores tontos.

Ante una expresión mínimamente compleja y sin paréntesis, antes de trabajar con ella conviene
parentizarla completamente, usando diferentes tipos de paréntesis y si es necesario de distintos
colores, para evitar errores muy fáciles de cometer (véase si no la pregunta 4 de
ExP2_1998_SepO_B),
ÍNDICE

Pag 33. Semántica de predicados y definición 2.4.


Antes de entrar en la unión disjunta con el ejemplo que me interesa, veamos uno excelente y breve,
tomado de un correo del tutor David Fernandez Amoros.

Supongamos que tenemos tres variables, 0:nat; 0:ent; 0:real (admintiendo la definición de Natural que
incluye al 0). Si realizamos una unión normal, nos quedaríamos con un único 0 (que sería por fuerza
real), perdiendo los matices asociados a cada una de dichas variables. Si en cambio realizamos una
unión disjunta, consideramos cada uno de esos tres ceros como elementos distintos y el conjunto
unión tendrá tres elementos 0, cada uno con una serie de particularidades privativas de su tipo. Por
ejemplo, el 0 natural no tiene predecesores (es minimal), el 0 entero tiene infinitos predecesores y un
predecesor estricto; mientras que el 0 real tiene infinitos predecesores pero ninguno estricto... lo que
tiene importantes consecuencias para la definición de un pbf según que variable consideremos.

Ya que en los problemas tipicos que nos encontraremos, no tendremos oportunidad de ver ejemplos
tan claros como el anterior, vamos con otro más complejo y que sirve de introducción a un posible
error de notación.

Sean los dos siguientes conjuntos S={1,2,3} y T={2,3,4}. La unión de toda la vida es
S∪T={1,2,3,4}. La unión disjunta, en cambio, considera a los dos conjuntos como disjuntos (de
intersección vacía), aunque tengan elementos comunes, a los que tratará como si fueran diferentes. Si
⊕ representa la unión disjunta, la de los anteriores conjuntos es S⊕T={1,2,3,2',3',4}. Uso primas para
evidenciar que las cifras que las llevan no está repetidas por error.

Otro ejemplo mucho mejor. Supongamos que tenemos una función que tiene la siguiente declaración
de tipos y variables:

TYPE
indice_a = {1..3}
indice_v = {2..4}
a = vector[indice_a] de ent
v = vector[indice_v] de ent
VAR
i : indice_a
j : indice_v
a[i] : ent
v[j] : ent

Son aquí identificadores de variables i, j, a[i], v[j]


Identificadores que tomarán determinados valores durante la ejecución del programa. i podrá tomar
valores entre 1 y 3, j entre 2 y 4 y finalmente a[i] y a[j] podrán tomar valores enteros.

39
25905053.doc

Volvamos a la unión disjunta. No podemos decir que el dominio de los índices es {1..4}. Es bastante
más lógico decir que dicho dominio es {1..3}⊕{2..4}, porque de no hacerlo así, cuando vayamos a
escribir simbólicamente la definición de estado (definición 2.4) para esta función, podríamos poner el
siguiente disparate
σ : {i, j, a[i], v[j]} → {1,2,3,4, a[1],a[2],a[3],a[4], v[1],v[2],v[3],v[4]} , en vez del más
¿correcto?:
σ : {i, j, a[i], v[j]} → {1,2,3, 2',3',4, a[1],a[2],a[3] v[2],v[3],v[4]}

¿Correcto?. Creo que no. La definición simbólica 2.4 es incorrecta por insuficiente al precisar de la
aclaración verbal que le sigue. Sin ella, la definición tendría un error; no, dos relacionados. Habla de
estado como una aplicación entre dos conjuntos, cuando en realidad es una aplicación entre tuplas
ordenadas, o más correctamente, si son n-tuplas, son n aplicaciones entre elementos de mismo índice.
ÍNDICE
Sobre el ejemplo anterior. Tomada la definición simbólica del libro al pie de la letra, es posible
aplicar el identificador i en el valor v[4]. Resultaría que el identificador de un índice, usado para
referenciar posiciones del vector a, se aplica en el valor ¡entero! contenido en la última posición del
vector v. Posición definida por un valor que no forma parte del dominio de i. Un disparate.
La solución es bien sencilla, definir estado de la siguiente manera:
σ : 〈IDi〉 → 〈Di〉 , donde estos paréntesis agudos significan tupla ordenada y equivale a:
σ : 〈ID1 , ID2 ,.... IDi 〉 → 〈 D1 , D2 , ... Di 〉 que a su vez equivale a:
σ : 〈ID1 → D1 , ID2 → D2 , ... IDi → Di 〉
Cualquiera de estas tres expresiones deja bien patente que un determinado identificador xj sólo se
puede aplicar en el conjunto del dominio que le corresponda: Dj . De hecho, y esto es lo absolutamente
asombroso e incomprensible, en el libro se usa esta notación clara y no ambigua en los diferentes
problemas iterativos exactamente con este significado. No logro entender por qué aquí se sigue otro
criterio.

Lo que sigue ahora es una estudio más en profundidad sobre esta definición y sus implicaciones...
respetando la que creo notación errónea del libro. A fin de cuentas, será con ella con la que nos
examinemos. En lo que resta de escolio fueron fundamentales las correcciones de un compañero del
que sólo conozco su nombre de pila, Jesús (JeRo).
ÍNDICE
Antes de entrar con la definición, un ejemplo que servirá para fijar la nomenclatura: sea un predicado
P, con n variables (identificadores) cada una perteneciente a un dominio: x1 ∈ D1 , x2 ∈ D2 , … ,xn ∈
Dn. El predicado tendrá la forma P(x1,x2, … ,xn). Los diferentes valores que pueda tomar cada una de
esos identificadores los llamaremos v1,v2,…,vn; por supuesto pertenecientes a los mismos dominios
que los identificadores de mismo índice.

Volviendo a la definición, trabajando con este ejemplo:


ID ≡ {x1,x2, … ,xn}
D ≡ {D1 ∪ D2 ∪… ∪ Dn}. (aquí ∪ como unión disjunta). Los diferentes dominios Di son
subconjuntos disjuntos del dominio universal D. Si D1≡ {1,2,3}, D2≡ {3,4}, tendremos que D
≡ {{1,2,3}∪{3,4}}, considerando al 3 del primer dominio, distinto al del segundo.

Esto permite traducir la definición; σ : ID → D. 'Estado σ es una aplicación desde el conjunto de


todos los identificadores de variable en el conjunto D, de modo que cada identificador xi, sólo se
aplique en un elemento vi de su propio dominio Di'

Al conjunto de todas las posibles aplicaciones estado, se le denomina ε . ε = {σ}. Y habrá tantas
aplicaciones diferentes como elementos tenga el producto cartesiano de los diferentes dominios: # ε =
#D1 * #D2 *… * #Dn.

40
25905053.doc

En resumidas cuentas: conocer o asignar un estado no es otra cosa que a cada identificador de variable
que aparece en P asignarle un valor concreto de entre todos los posibles que pueda tomar cada
variable. Asignaciones diferentes, producen estados diferentes.
No nos importa, por lo de pronto, el valor veritativo correspondiente a dicho estado.

Vamos con otro ejemplo que usaré también para las definiciones 2.5 y 2.6. Sean dos subconjuntos de
Z: A={0..9}, B={0..99}, las variables (identificadores) ligadas a ellos: α ∈ A, β ∈ B y los siguientes
predicados, sintácticamente correctos:
P ≡ ∀α ∧∀β . α ∧β
Q ≡ ∀α ∧∀β. (α = 2 ) ∧(β mod α = 0)
R ≡ ∀α ∧∀β. (β = 15 ) ∧(β mod α = 0)
ÍNDICE
En este ejemplo tenemos que para los tres predicados: ID ≡ {α,β} y D ≡ A ∪ B ≡ {0,1,…,8,9,0’,1’,
…,8’,9’,10,11,…,98,99}
• P: No se establece ninguna restricción sobre los dominios A y B; P define un conjunto de
10*100 = 1000 aplicaciones diferentes (estados): P≡ {(α=0,β=0),(α=0,β=1),…,(α=9,β=98),
(α=9,β=99)}. Cualesquiera de estos estados hacen cierto el predicado.
• Q: Se establece una restricción en el dominio de A. Se ve mejor si lo escribimos de esta otra
manera: Q ≡ α=2 ∧ ∀β . (β mod α = 0). Por la restricción realizada sobre el dominio, Q define
un conjunto de 100 aplicaciones (estados) diferentes Q≡ {(α=2,β=0),(α=2,β=1),...,(α=2,β=98),
(α=2,β=99)}, aunque sólo cincuenta de ellos hacen cierto al predicado; aquellos con β par
(incluido el 0).
• R: Define un conjunto de 10 estados, R≡ {(α=0,β=15),(α=1,β=15),…,(α=8,β=15),(α=9,β=15)},
solo tres hacen cierto a R y un cuarto (α=0,β=15), realiza una asignación de estado no definida
(15 mod 0 no está definida como operación), a pesar de que cada valor está perfectamente
definido en su dominio. Volveremos sobre esto.

Por tanto una manera más correcta de escribir lo anterior es entre llaves, porque cada uno de esos
predicados define un conjunto de estados.
{P ≡ ∀α ∧∀β . α ∧β} ≡ ε P ⇒ #{P} = #ε P = 1000
{Q ≡ ∀α ∧∀β. (α = 2 ) ∧(β mod α = 0)} ≡ ε Q ⇒ #{Q} = #ε Q = 100
{R ≡ ∀α ∧∀β. (β = 15 ) ∧(β mod α = 0)} ≡ ε R ⇒ #{R} = #ε R = 10

Un posible estado de Q, que lo hace falso, sería σ = (α=2,β=3). (Ojo, no se escribe σ (α=2,β=3)).
Al valor concreto que le corresponde al identificador de variable xi, en un estado dado σ, lo
denotaremosσ ( xi).
En el anterior estado σ = (α=2,β=3), tendremos que σ (β) = 3.
Y por extensión, supongo que sería correcta la siguiente expresión que define el valor del predicado Q
en el anterior estado: σQ(α=2,β=3) = Falso.
ÍNDICE

Pag 34. Primer párrafo. Símbolo ⊥ i


⊥ i. : Valor indefinido dentro del dominio Di.
No confundir que exista un valor indefinido dentro de un dominio con la no definición de un estado
(esto último caso lo acabamos de ver en el predicado R para (α=0,β=15))
A pesar de que la diferencia aparenta ser de matiz, es bastante importante.
A partir de las definiciones anteriores, supongamos que definimos un nuevo dominio ligado α: C={11
mod α} (recordemos que α toma valores entre 0 y 9). Dentro de este dominio, hay un valor claramente
indefinido: 11 mod α. En este caso, tenemos que DC contiene un valor indefinido concreto
perfectamente identificable

Pag 34. Definición 2.5. Cambio de variable en σ

41
25905053.doc

Dado un estado σ , representamos por σ[v/x](y) a la asignación a la variable y, y sólo a ella, del valor
v. Las demás variables quedan sin modificar.
En nuestro ejemplo Q ≡ ∀α ∧∀β. (α = 2 ) ∧(β mod α = 0), sea el estado : σ = (α=2, β=3)
σ[8/β](β) = σ (β) = 8.
σ[8/α](β) = σ(β) = 3, pues la asignación propuesta no afecta en absoluto a β.
.
ÍNDICE

Pag 35. Definición 2.6. Predicado bien definido


Hemos visto ya dos ejemplos de cada una de las causas por la que un predicado puede no estar bien
definido.
1 La no definición de una variable en uno de los dominios (ejemplo del dominio C={11 mod α})
2 La no definición del predicado por no definición de una función en un estado correctamente
definido en el caso del predicado R. (Aunque aquí, se incurre en definición circular, pero bueno,
admitámosla como correcta).

De esta definición se sigue, por otra parte algo trivial, que es posible que un predicado está bien
definido en un estadoσ, pero no en otro σ ’. Y tan trivial, lo vimos continuamente en Análisis
Matemático, donde la función f(x)= 1/x (predicado f ≡∀ x∈R. 1/x) está bien definida en todo el
dominio, excepto para x=0 (estado σ = (x=0)) donde la función (el predicado) no está definida (a pesar
de que el valor x=0 sí lo está en el dominio).

Aquí ya hay una pista del porqué creo que es una definición circular ¿qué diferencia hay entre una
función (lógica o algebraica) y un predicado?. Ninguna; a no ser que función se use exclusivamente
para un denotar a un subpredicado.
ÍNDICE

Pag 35. [P]σ . Cuarta línea desde el final


[P]σ = V|F… (Los corchetes con doble barrado). Traducción: El predicado P está definido en el
estado descrito por sigma y su valor semántico es el que sigue (o verdadero o falso).

Pag 37. Definiciones 2.8 a 2.11


Estas tres definiciones son de traducción inmediata, pero conviene memorizar la simbología usada
porque nos la podemos encontrar en los test.
El resto del texto comprendido entre las páginas 36 y 40, incluida la definición 2.7, es mejor ni
leerlo. Visto en lógica matemática de una manera mucho más clara.

Pag 41. Definición 2.12. estados(P)


En resumidas cuentas: estados(P) es el conjunto de los estados que satisfacen o hacen cierto a P.
Destacar la posible confusión que puede plantear este nombre. Muchas veces no sabemos si cuando
hablan genéricamente de los estados de un predicado P se están refiriendo al conjunto de todos los
estados posibles del predicado (denotado como ε P) o sólo de los que lo satisfacen, estados(P).
En nuestros tres predicados ejemplo del escolio sobre la pag 33, definición 2.4:
#estados(P) = 1000 = #ε P,
#estados(Q) = 50; pero #ε Q =100
#estados(R) = 3 pero #ε R =10
ÍNDICE

Pag 42. Debilitamiento predicado por sustitución de constante por variable


Ejemplo final de la página.
Esta técnica va a ser muy usada y es necesario justificarla adecuadamente. Primero para ver que,
aunque añadimos una conjunción, estamos debilitando y no fortaleciendo como dice la norma
general.

42
25905053.doc

Segundo, porque no podemos admitir, como se dice en Peña 42 (final de página), la comparación
entre dos predicados incomparables; sería una contradicción lógica. Si la técnica es válida, es porque
P y P' en realidad sí son comparables.

Al realizar este tipo de debilitamiento, no estamos añadiendo conjuntado un predicado cualquiera (con
lo que entonces sí estaríamos reforzando): estamos añadiendo un predicado con una variable i que
está ligada a la variable libre n; de tal modo que el operador ∧está introduciendo sin que nos demos
cuenta toda una colección de ∨al aplicar distributiva sobre el nuevo predicado introducido.
Para verlo claramente tomemos el mismo ejemplo del libro, pero limitando el sumatorio inicial desde
1 hasta 3,
{P ≡ s = Σ (i ∈ {1..3}.a[i])}⇒ {P' ≡ [s = Σ(i ∈ {1..j}.a[i])] ∧(0 ≤ j ≤ 3)}
Escribir (0 ≤ j ≤ 3) es una manera abreviada de escribir (j =0) ∨(j =1) ∨(j=2) ∨(j =3), entonces:

P' ≡ [s = Σ(i ∈ {1..j}.a[i])] ∧[(j =0) ∨(j =1) ∨(j =2) ∨(j =3)]

Aplicamos distributiva:
P' ≡ {[s = Σ(i ∈ {1..j}.a[i])] ∧(j =0)} ∨{[s = Σ(i ∈ {1..j}.a[i])] ∧(j =1)} ∨
∨{[s = Σ(i ∈ {1..j}.a[i])] ∧(j =2)} ∨{[s = Σ(i ∈ {1..j}.a[i])] ∧(j =3)}

Sustituyendo j por el valor indicado en cada caso:


P' ≡ [s = Σ(i ∈ {1..0}.a[i])] ∨[s = Σ(i ∈ {1..1}.a[i])] ∨[s = Σ(i ∈ {1..2}.a[i])] ∨
∨[s = Σ(i ∈ {1..3}.a[i])]

Queda comprobado que P y P' sí son comparables. El predicado P, sólo se hará cierto si s contiene la
suma de las tres posiciones del vector a, justo cuando i valga 3, que será después de haber pasado por
el 1 y el 2. Mientras que P' será cierto cuando s no contenga suma alguna, cuando contenga el valor
del primer elemento, la suma de los dos primeros o finalmente la de los tres. Simbólicamente:
[estados(P) = {3}] ⇒ [estados(P') = {0..3}]
ÍNDICE

Pag 43. Segundo párrafo


Destacar por su importancia para diferentes ejercicios que cierto es el predicado más débil de todos
los posibles y falso el más fuerte. Así las implicaciones falso ⇒ P y P ⇒ cierto, son siempre ciertas
para cualquiera que sea el predicado P. Esta es otra manera diferente de enunciar el siguiente clásico
aforismo de lógica: 'de una contradicción se sigue cualquier cosa y de cualquier cosa se sigue una
tautología'.

Pag 43. Dominio vacío de un cuantificador Segundo párrafo de 'Convenios sobre...'


Conviene tener muy en cuenta lo mencionado en LoMa. Pag 94. III-5 a III-10 del escolio 'Pags 29 a
40. Escolios de Lógica Matemática' referido a la posible y útil lectura de un universal como
negación de un existencial y negación del predicado.

∀α∈{1..n}.P
Literalmente se lee 'para todo alfa comprendido entre 1 y n, el elemento de posición alfa cumple
la propiedad enunciada por el predicado P', Una lectura perfectamente válida y equivalente es
'ningún elemento del vector incumple P'. Bastará que haya uno que lo incumpla para falsar el
predicado.

Si ahora restringimos el dominio haciéndolo vacío:


∀α∈ {1..0}.P Literalmente se lee 'para todo alfa comprendido entre 1 y 0 el elemento de posión
alfa cumple la propiedad P', que casi no nos dice nada. Pero si lo leemos como 'ningún elemento
del vector incumple P', es bastante evidente que como el vector no contiene elementos (al menos

43
25905053.doc

en el dominio considerado), no existe ninguno que pueda falsar el predicado. Por tanto el
predicado es cierto.
ÍNDICE
Destacar que ∀α∈{1..n}.P es una manera abreviada de escribir P(1) ∧ P(2) ∧ ... ∧ P(n), y que
justamente el valor veritativo de un predicado cuantificado universalmente cuando el dominio se
hace vacío es cierto. Neutro del operador lógico ∧.

∃α∈{1..n}.P
Existe algún alfa que cumple P. Basta que exista uno para que el predicado sea cierto.
∃α∈ {1..0}.P Al ser el dominio vacío, no existe alfa alguno que pueda hacer cierto el predicado.
Da lo mismo cual sea el predicado. Siempre será falso al no existir elementos que puedan
satisfacerlo
∃α∈{1..n}.P ≡ P(1) ∨P(2) ∨... ∨P(n). Si n = 0, P ≡ falso; elemento neutro de ∨.

Nα∈{1..n}.P
Cuantificador de conteo N. Cuenta cuántos elementos del dominio cumplen P.

Nα∈{1..0}.P. Al ser vacío el dominio, ningún elemento puede cumplir la propiedad P. En


consecuencia N = 0; esta vez, 0, el elemento neutro de la suma.

Hasta aquí, en estos tres cuantificadores, creo no se puede hablar propiamente de convenio. Hemos
visto que los valores obtenidos al hacer el dominio vacío (cierto, falso, 0) se corresponden al pie de la
letra con el enunciado verbal del predicado y sus cuantificadores (cuantificadores que cierran el
predicado, diríamos en LoMa) y que dicho valor tiene perfecto sentido lógico en esa situación de
dominio vacío. El único posible convenio que existe es el notacional por el cual se admite {1..0} para
indicar el dominio vacío.
ÍNDICE
Σα∈{1..n}.P
Sumatorio. Similar al anterior pero diferente. El cuantificador de conteo, simplemente cuenta
cuantos elementos hacen cierto al predicado. El sumatorio suma el valor de dichos elementos
(por supuesto, sólo se puede usar sobre dominios numéricos. Normalmente veremos sumatorios
de elementos de vector que cumplen una determinada condición). Por tanto:

Σα∈ {1..0}.P Como antes, no existe ningún elemento que cumpla P. Pero ahora ya no existe
una suma de 0 elementos (la suma sólo es operación binaria que necesita de dos operandos). Por
convenio admitimos en este caso que la suma es cero, elemento neutro de la suma. Del mismo
modo, por convenio admitimos que la suma de un sólo elemento es precisamente el valor de
dicho elemento.
ÍNDICE
Πα∈ {1..n}.P
Multiplicatorio. Similar al anterior pero con multiplicaciones en vez de sumas. Multiplica todos
los elementos de posición alfa que verifican la propiedad P.

Πα∈ {1..0}.P Como antes, admitimos por convenio que el resultado es el elemento neutro de la
multiplicación, el 1. También por convenio admitiremos que la multiplicación de un único
elemento es el valor de dicho elemento.

Justifiquemos someramente por razones prácticas el convenio en el caso del sumatorio (para el
multiplicatorio sería idéntico).

Vemos con frecuencia que para nosotros una suma es una operación de asignación del tipo
s:=s+nuevo_sumando. Esto es, tenemos una posición de memoria de nombre s donde vamos
acumulando el resultado de la suma de lo que ya existe en s más un nuevo_sumando. Antes de

44
25905053.doc

empezar a acumular nada en s (0 sumandos sumados), previamente tenemos que inicializar la posición
s a 0 (s := 0), de lo contrario los subsiguientes totales sumados serán erróneos al comenzar con un
primer sumando_0 de valor desconocido (el que tuviera la posición de memoria s). Del mismo modo,
una vez inicializado, la primera suma será (s=0)+nuevo_sumando, por lo que en s tendremos la suma
de un único sumando.
ÍNDICE

Pag 45. Definición 2.14. Sustitución textual


Esta sustitución textual es muy útil en diseño iterativo si necesitamos averiguar una precondición (o el
aserto adecuado antes de asignaciones) que existe antes de unas instrucciones dadas de asignación
partiendo solamente de la postcondición o del aserto existente inmediatamente después de dichas
instrucciones. Ver ejemplo 4.1 en página 115.

Veamos otro ejemplo, muy similar a uno del examen de la primera semana de 2000:
Dadas las siguientes tres instrucciones de asignación y el aserto que define los estados válidos
alcanzados después de ellas (o postcondición), averiguar el aserto correspondiente (o precondición)
existente antes de las instrucciones:

{Q ≡ ?}
y := x - y;
{1}
x := 2x + y;
{2}
x := -x + y;
{R ≡ x = P ∧y = T}

No tenemos ni idea en principio de cuál puede ser Q, pero desde luego será un predicado en donde
aparecerá x e y en función de P y T, de tal modo que en cada instrucción se va modificando Q,
convirtiéndose en el aserto que define el estado del algoritmo inmediatamente después de dicha
instrucción ({1}, {2} y {R}).
Para averiguar la Q pedida, hacemos el camino al revés. Partimos de R, sustituyendo todas las
apariciones de x por –x+y. Obtenemos el aserto {2}. Sustituimos en {2} las x por 2x + y. Obtenemos
el aserto {1}. Realizamos la última sustitución y obtenemos Q. Simbólicamente:
ÍNDICE
Q ≡ {{{R}x-x +y }x2x + y}yx - y ≡

Primera sustitución:
≡ {{{x = P ∧y = T}x-x +y}x2x + y}yx - y ≡ {{-x + y = P ∧y = T}x2x + y}yx - y ≡ {{-x + T = P ∧y = T}x2x + y}yx - y

≡ {{x = T - P ∧y = T}x2x + y}yx - y ≡ {{2}x2x + y}yx – y ({2} porque es el nombre que le hemos dado a este
aserto en el enunciado, el sombreado un poco más arriba)

Segunda sustitución:
≡ {{x = T - P ∧y = T}x2x + y}yx - y ≡ {{2x + y = T - P ∧y = T}yx - y ≡ {{2x + T = T - P ∧y = T}yx - y ≡
≡ {{x = - P/2 ∧y = T}yx - y ≡ {1}yx - y

Tercera sustitución:
{{x = - P/2 ∧ y = T}yx - y ≡ {x = - P/2 ∧ x - y = T}≡ {x = - P/2 ∧ -P/2 - y = T}≡ {x = - P/2 ∧-P/2 - y =
T}≡
≡ {x = - P/2 ∧ y = -T - P/2}≡ Q

Éste último predicado será, en buena lógica, la Q que estamos buscando.

45
25905053.doc

Obsérvese que antes de realizar otra nueva sustitución textual, procedemos a reordenar los predicados
hasta conseguir x e y como funciones de T y P. No es un procedimiento estrictamente necesario, pero
evita posibles errores de operación.

Como con estas sustituciones literales 'hacia atrás' hemos averiguado cuáles eran los asertos
correspondientes antes de cada asignación, podemos comprobar que el último aserto (Q) es correcto,
por el simple procedimiento de, partiendo de Q, ejecutar secuencialmente las instrucciones del
algoritmo.

{Q ≡ x = - P/2 ∧ y = -T - P/2}
y := x - y;
x := 2x + y;
x := -x + y;
¿{R ≡ x = P ∧y = T}?
ÍNDICE

Comenzamos la ejecución:
{Q ≡ x = - P/2 ∧ y = -T - P/2}

Primera instrucción:
y := x – y = - P/2 +T+ P/2 = T ⇒ σ =(x = - P/2 ∧y = T)

Segunda instrucción:
x :=2x + y = 2*(-P/2) + T ⇒ σ =(x = - P + T ∧y = T)

Tercera instrucción
x := -x + y = P – T + T = P ⇒ σ =(x = P ∧y = T)

Este último estado del algoritmo coincide con el estado enunciado por la R del problema. Por tanto
nuestra derivación fue correcta.
ÍNDICE

Salto de página.../...

46
25905053.doc

ÍNDICE

TEMA 3. Diseño recursivo


Pag 55. Introducción recursividad. Un ejemplo hablado
Comienza el capítulo diciendo 'La recursividad es una característica de la mayoría de los lenguajes de
programación (...)'.

Bien, esto es cierto; pero también es cierto que es una característica, y de las más potentes, que tienen
todos los lenguajes humanos sin excepción, tanto orales como signados (lenguajes de sordomudos).
Lo que me va a permitir hacer una introducción al funcionamiento de la recursión con un ejemplo
verbal.
Supongamos que leemos u oímos la frase 'él sabe que ella cree que él coquetea con María'. No
tenemos ningún problema para comprenderla correctamente, ¿dónde está entonces la recursión?.
Precisamente en el orden en el que la analizamos para entenderla:

No podemos saber lo que él sabe hasta que sepamos lo que ella cree, cosa que tampoco sabremos
hasta que oigamos que él coquetea con María. En cuanto lleguemos a este punto, ya sabemos lo que
ella cree y de esta manera podemos finalmente saber lo que él sabe. Un perfecto ejemplo de recursión.

Hasta aquí el ejemplo simple. Lo que resta de escolio se puede saltar. Es sólo un ejercicio más amplio
y detallado sobre dicho ejemplo.

El esquema planteado anteriormente puede parecer un simple ejercicio teórico, pero si algunos de los
actuales modelos neurológicos del conocimiento son correctos (y cada vez hay más indicios
favorables, al menos de la validez parcial), resulta que nuestro cerebro funciona de una manera
modular y que algunos de los módulos neurológicos realizan auténticas recursiones. Veamos el
mismo ejemplo de antes, 'él sabe que ella cree que él coquetea con María', con algo más de detalle a
nivel sintáctico; sólo a grandes rasgos y sin tener en cuenta la posible participación de otros módulos
neurológicos (lexicón, listémico, memoria a corto plazo, análisis semántico, detección de
translocaciones, conversor señal-audio/'mentalés', etc).

0) Comenzamos a oír la frase (llamada0 o llamada externa). Entre otros, se activa en ese momento
lo que aquí llamaremos el 'módulo de análisis sintáctico' (MAS) que trabaja en íntima colaboración
(unas veces en paralelo, otras no) con algunos de los módulos neurológicos antes mencionados. Se
activa el módulo porque la cadena de datos entra en ese módulo neuronal. A nuestros efectos
prácticos, es como se realizara una llamada a la función MAS con la cadena de entrada como
argumento
.
MAS detecta una oración completa, sintácticamente correcta, en cuanto detecta en la cadena de
entrada de datos el primer que –equivalente a algo- y realiza el siguiente análisis sintáctico
(imprescindible si de verdad queremos entender el mensaje):
ÍNDICE
él[sujeto] sabe[verbo] algo[OD, objeto directo representado por que]. Estructura que abreviadamente
llamaremos S-V-OD. Determina además que el objeto directo que (lo que indiqué ahora como algo)
es una posible frase completa con significado que precisa de análisis detallado. Llama entonces a
MAS (llamada1) pasando algo como argumento (bien puede ser que existan varios módulos
neuronales MAS, cada uno con su unidad de memoria a corto plazo; o bien que exista un único MAS,
y al realizar una autoinvocación guarda el estado actual en un módulo de memoria a corto plazo)
ÍNDICE

1) MAS analiza esa frase que es objeto directo y detecta que es una oración completa
sintácticamente correcta en cuanto recibe el segundo que, realizando el siguiente análisis:

47
25905053.doc

(que)[nexo de subordinación] ella[sujeto] cree[verbo] algo' [OD]. Estructura que abreviadamente es


S'-V'-OD'. Otra vez el objeto directo algo' es susceptible de análisis. Llama a MAS con algo' como
argumento (llamada2).

2) Por fin de mensaje, MAS detecta fin de oración con estructura sintáctica correcta. El último
objeto directo ya se puede analizar completamente sin volver a invocar a MAS:
(que)[nexo] él[sujeto] coquetea[verbo] con María[CC]
En este momento MAS ya puede devolver esta estructura [S''-V''-CC''] al nivel anterior (devolver
salida a llamada1) quien la recibe como objeto directo:

1') con los datos devueltos MAS ya puede construir la estructura completa del primer nivel
recursivo:
S'-V'-OD'{S''-V''-CC''} . La frase ella cree que él coquetea con María, tiene un objeto directo con
significado completo.
Esta salida es devuelta al nivel 0 (devolver salida a llamada0), quien la recibe como objeto directo:

0') S-V-OD{ S'-V'-OD'{S''-V''-CC''}}

Aunque pueda ser oída al vuelo en la cola de la pescadería, la frase del ejemplo, que no el análisis ni el
estudio recursivo, está tomada de la página 404 de un libro que creo de lectura recomendable para todo
estudiante de informática, a pesar del aparente escaso interés que pueda suscitarle el título: 'El instinto
del lenguaje (cómo crea el lenguaje la mente)' de Steven Pinker (ed Alianza Editorial). Del mismo autor
y todavía más recomendable, por no centrarse únicamente en el lenguaje (y a pesar de sus muchas
páginas...), es un título posterior, del año 1997: 'Cómo funciona la mente' (ed Destino, colección Áncora
y Delfín).
ÍNDICE

Pag 60. Introducción recursividad. Función sum, recursiva lineal no final


Volviendo a la informática; para comprender correctamente qué es esto de la recursión y cómo
funciona, veamos un ejemplo concreto, que usaremos en más ocasiones.
Supongamos que tenemos la siguiente función que, según la postcondición, nos devuelve en s la suma
de todos los elementos de un vector a.

{Q≡ 1 ≤ i ≤ n}
fun sum (a:vector[1..n] de ent; i: entero) dev (s:entero)=
caso i=1 → a[i]
[] i>1 → a[i]+sum(a, i-1)
fcaso
ffun
{R ≡ s= Σα∈{1..i}.a[α]}
ÍNDICE
(en lugar de este predicado, se puede usar el equivalente, más cómodo de leer, s = Σiα =1a[α ])

Con ella, veamos qué ocurre en la memoria de la máquina cuando lanzamos la función para que sume
los cuatro elementos de un vector a ≡ [8, 6, 7, 9]. Comenzamos realizando por teclado (o dentro de
otro trabajo) una primera llamada externa (todavía no recursiva):

Cadena descendente de llamadas recursivas


ÍNDICE

0) sum(a, 4).
El código y el vector se ponen en memoria. Se evalúan las protecciones de los casos y se entra en el
caso no trivial, pues es cierto que 4>1 y falso que 4=1. Habría que realizar la suma: a[i]+sum(a,i-1),

48
25905053.doc

que con los datos de este ejemplo es a[4]+sum(a,4-1)= 9+sum(a,3), pero no se puede realizar pues no
conocemos el segundo sumando, al estar expresado como una llamada a la propia función.

Dibujo ahora una figura que representa un mapa de memoria, donde de arriba abajo aparecen:

Llamada en curso (llamada actual).


El vector. En negrilla el dato con el que se va a operar en la llamada en curso.
Índices del vector (en gris). En negrilla el índice actual
Consecuente del caso en el que se entre.
Consecuente del caso en el que se entre con los datos reales. En negrilla el dato del vector.
Valor de la devolución s.
Verificación de la postcondición.

sum(a, 4)
8 6 7 9
1 2 3 4
a[4]+sum(a,4-1)
9+sum(a,3)
s= ?
s=
Σ4α=1a[α]=?

Las dos últimas líneas sin sombrear, pues por lo de pronto están indefinidas.

1). sum(a,3)
ésta ya es una llamada interna. El ejecutable volverá a instalarse en memoria, así como los datos que
se le pasaron como argumentos (vector a e i=3). Los anteriores código y vector permanecen en
memoria a la espera de devolución de resultados parciales.
En la siguiente figura, aparecen sin sombrear datos y código de la llamada anterior y sombreados los
correspondientes a la llamada que actualmente está en curso (sum(a,3)), en la que también se entra
en el caso no trivial: a[3]+sum(a,3-1). Destacar que en esta llamada actual se pasa como argumento el
vector a completo, pero que la función ignorará el elemento 9, de índice 4 (razón por la que lo escribo
atenuado)
ÍNDICE
sum(a, 4) sum(a,3)
8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4
a[4]+sum(a,4-1) a[3]+sum(a,3-1)
9+sum(a,3) 7+sum(a,2)
s= ? s= ?
s= Σ4α=1a[α]=? s= Σ3α=1a[α]=?

2) sum(a,2)

sum(a, 4) sum(a,3) sum(a,2)


8 6 7 9 8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4 1 2 3 4
a[4]+sum(a,4-1) a[3]+sum(a,3-1) a[2]+sum(a,2-1)
9+sum(a,3) 7+sum(a,2) 6+sum(a,1)
s= ? s= ? s= ?
s= Σ4α=1a[α]=? s= Σ3α=1a[α]=? s= Σ2α=1a[α]=?
ÍNDICE

3,3') sum(a,1)

49
25905053.doc

que por ser cierto 1=1 y falso 1>1, nos lleva al caso trivial, que devuelve directamente el valor de
a[1], sin realizar nuevas llamadas:

sum(a, 4) sum(a,3) sum(a,2) sum(a,1)


8 6 7 9 8 6 7 9 8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
a[4]+sum(a,4-1) a[3]+sum(a,3-1) a[2]+sum(a,2-1) a[1]
9+sum(a,3) 7+sum(a,2) 6+sum(a,1) 8
s= ? s= ? s= ? s=8
s= Σ4α=1a[α]=? s= Σ3α=1a[α]=? s= Σ2α=1a[α]=? s= Σ1α=1a[α]=
=8
Mapa 1 recursión
ÍNDICE DE FIGURAS

pero ¡sorpresa!, ahora la función sum devuelve un valor concreto: el parámetro s de devolución deja
de estar indefinido y ya es posible verificar la postcondición.
Hasta ahora, lo único que teníamos era una cadena descendente de llamadas recursivas que termina
justamente al llegar al caso trivial, donde al tener un dato concreto del tipo correcto (y que no produce
nuevas llamadas), se origina una cadena ascendente de sumas.
Recordemos que tenemos en memoria cuatro copias de código y cuatro de datos. A partir de donde
nos quedamos antes (la última tabla), dibujo ahora la cadena ascendente de sumas:

Cadena ascendente de combinaciones

2') el valor devuelto por el caso trivial se suma en la función que invocó a la última llamada (y se
libera la memoria ocupada por datos y código de la última llamada):
sum(a, 4) sum(a,3) sum(a,2)
8 6 7 9 8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4 1 2 3 4
a[4]+sum(a,4-1) a[3]+sum(a,3-1) a[2]+sum(a,2-1)
9+sum(a,3) 7+sum(a,2) 6+sum(a,1)
s= ? s= ? s=6+8=14
s= Σ4α=1a[α]=? s= Σ3α=1a[α]=? s= Σ2α=1a[α]=
=8+6=14
ÍNDICE

1') Idem:
sum(a, 4) sum(a,3)
8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4
a[4]+sum(a,4-1) a[3]+sum(a,3-2)
9+sum(a,3) 7+sum(a,2)
s= ? s= 7+14=21
s= Σ4α=1a[α]= s= Σ3α=1a[α]=
= =8+6+7=21

0')
sum(a, 4)
8 6 7 9
1 2 3 4
a[4]+sum(a,4-1)
9+sum(a,3)
s= 9+21=30
s= Σ4α=1a[α]=

50
25905053.doc

=8+6+7+9=30
ÍNDICE
Y este último valor, 30, es el que devuelve la función inicialmente llamada por nosotros (llamada
externa, no recursiva).

Obsérvese que los pasos están inicialmente numerados desde el 0 (llamada externa) hasta el 3 (entrada
en el caso trivial) y posteriormente desde 3' hasta 0' para indicar que se trata de la cadena ascendente
de combinaciones. El caso trivial es a la vez el último de la cadena descendente y el primero de la
ascendente (a pesar de que en él realmente no se produce combinación).

Si no se realiza y/o comprende un diseño como el anterior (o cualquier otro en el que se pueda
observar la evolución de las distintas llamadas) , pueden derivarse aparentes paradojas en el diseño
recursivo que nos pueden traer de cabeza. Por ejemplo:
Vemos que en este problema recorremos el vector descendentemente, con i tomando valores desde n
hasta 1, por tanto, cuando llegamos a una llamada recursiva interna con una i determinada, es porque
esta i ya ha tomado valores desde n hasta i+1. Pero por otra parte, en R se comprueba si se verifica el
predicado s= Σα∈{1..i}.a[α]…para valores desde 1 hasta i ¡lo todavía no recorrido!.

Si se estudia el anterior mapa de memoria, resulta que no existe paradoja alguna, pues la
comprobación de si se cumple la postcondición se realiza en la cadena ascendente de combinaciones
de las devoluciones anteriores con los respectivos cálculos actuales.

Pag 60. Introducción recursividad. Función sumF, recursiva lineal final


Gracias al profesor Quesada, del C.A. de Victoria-Gasteiz, por haber señalado un error grave...
diciéndolo suavemente. Porque en realidad era una burrada. No tenía en cuenta la cadena de
devoluciones, por lo que llegaba a conclusiones tan pintorescas como que carecía de sentido el paso
de inducción (Tabla 3.2, punto 4).
ÍNDICE
A partir de la función sum, en un próximo escolio (Pag 94. Desplegado y plegado) derivaremos la
versión final, llamada sumF, que aquí usaremos como ejemplo de la dinámica del funcionamiento de
una recursiva final.
{Q sumF ≡ (1 ≤ i ≤ n) ∧(w = Σα∈{i+1..n}.a[α])}

fun sumF (a:vector; i, w: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sumF(a, i-1, a[i]+w)
fcaso
ffun
{RsumF ≡ s= Σα∈{1..n}.a[α]}
ÍNDICE
Vemos dos características: la postcondición es constante (no dependiente de los parámetros de
inmersión; aunque como veremos en Pag 94. Desplegado y plegado esto no siempre tiene que ser así)
y lo más importante: en el caso trivial sólo tenemos una llamada recursiva. Ha 'desaparecido' la
función combinación entre la operación auxiliar actual y la llamada siguiente. Desaparecido entre
comilla, porque realmente la tenemos incrustada y ligeramente modificada como argumento de la
función. A pesar de ello, diremos que en las finales no hay función combinación (al menos en el
sentido que la entendemos en las no finales). Pero esto se verá más claro cuando lleguemos al
desplegado y plegado.

Lo único que ahora nos importa saber es que en la versión final, aparece un nuevo argumento, w, que
llevará acumuladas las sumas parciales. Esto exige que la primera llamada sea sumF(a,n,0). El
parámetro de llamada w ha de ser 0, el elemento neutro de la suma; de lo contrario el resultado final
estaría distorsionado por el valor que hubiéramos introducido en w.

51
25905053.doc

Bueno, también nos importa saber que ahora la precondición es más compleja, ya que hemos de
especificar qué es w. Obsérvese que tal como está enunciado el conjuntando que se refiere a w,
precisamente especifica implícitamente que la primera llamada a de ser con w=0. (Mismo comentario
que antes respecto a R. Habrá derivaciones finales en las que la precondición no tiene que ser
necesariamente así. Ya lo veremos):

Mediante otro supuesto mapa de memoria, veamos cuál es la dinámica de ejecución al realizar una
primera llamada externa sumF(a,4,0) para sumar el vector a=[8,6,7,9]
En el 'mapa de memoria' añado una línea:
Llamada en curso (llamada actual).
El vector. En negrilla el dato con el que se va a operar en la llamada en curso.
Índices del vector (en gris). En negrilla el índice actual
Consecuente del caso en el que se entre.
Consecuente del caso con la combinación de datos reales. En negrilla, los operandos.
Consecuente del caso con la combinación de datos reales ya operada (llamada siguiente)
Valor de la devolución s.
Verificación de la postcondición.
ÍNDICE
0) sumF(a,4,0)
Llamada externa. Entrada en caso no trivial (4 > 1). Código y vector se copian en memoria:
sumF(a,4, 0)
8 6 7 9
1 2 3 4
sumF(a, 4-1, a[4]+0)
sumF(a, 3, 9+0)
sumF(a, 3, 9)
s= ¿?
s= Σ4α=1 a[α]=¿?

Vemos que la 'combinación', entendida de modo ligeramente diferente a las no finales, se produce en
la misma lista de argumentos a pasar a la llamada recursiva: 9+0.
ÍNDICE

1) sumF(a,3,9)
Primera llamada recursiva interna. Entrada en caso no trivial (3 > 1). Otra copia de código y
vector se ponen en memoria. Los de la anterior llamada 0 permanecen también en memoria.

sumF(a,4, 0) sumF(a, 3, 9)
8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4
sumF(a, 4-1, a[4]+0) sumF(a, 3-1, a[3]+9)
sumF(a, 3, 9+0) sumF(a, 2, 7+9)
sumF(a, 3, 9) sumF(a, 2, 16)
s= ¿? s= ¿?
s= Σ4α=1a[α]=¿? s= Σ4α=1a[α]=¿?

2) sumF(a,2,16)
Segunda llamada interna. Entrada en caso no trivial (2 > 1)

sumF(a,4, 0) sumF(a, 3, 9) sumF(a, 2, 16)


8 6 7 9 8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4 1 2 3 4
sumF(a, 4-1, a[4]+0) sumF(a, 3-1, a[3]+9) sumF(a, 2-1, a[2]+16)
sumF(a, 3, 9+0) sumF(a, 2, 7+9) sumF(a, 1, 6+16)
sumF(a, 3, 9) sumF(a, 2, 16) sumF(a, 1, 22)

52
25905053.doc

s= ¿? s= ¿? s= ¿?
s= Σ4α=1a[α]=¿? s= Σ4α=1a[α]=¿? s= Σ4α=1a[α]=¿?

3, 3') sumF(a,1,22)
Tercera llamada interna. Entrada en caso trivial (1 = 1), se realiza la combinación de a[1] y w,
que lleva acumuladas todas las anteriores sumas, y el resultado se entrega como salida.

sumF(a,4, 0) sumF(a, 3, 9) sumF(a, 2, 16) sumF(a,1,22)


8 6 7 9 8 6 7 9 8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
sumF(a, 4-1, a[4]+0) sumF(a, 3-1, a[3]+9) sumF(a, 2-1, a[2]+16) a[1]+22
sumF(a, 3, 9+0) sumF(a, 2, 7+9) sumF(a, 1, 6+16) 8+22 =30
sumF(a, 3, 9) sumF(a, 2, 16) sumF(a, 1, 22)
s= ¿? s= ¿? s= ¿? s= 8+22 =30
s= Σ4α=1a[α]=¿? s= Σ4α=1a[α]=¿? s= Σ4α=1a[α]=¿? s= Σ4α=1a[α]=
=8+6+7+9=30
Mapa 2 recursión
ÍNDICE DE FIGURAS

Hemos llegado al caso trivial, tenemos por fin una devolución que podemos comprobar si verifica la
postcondición. Además, resulta que esa devolución es la suma acumulada de todos los elementos del
vector. ¿Podemos entregarla directamente al usuario ?. NO, porque esta última llamada sumF(a,1,22)
a quien ha de devolver el resultado producido es a la función que la llamó, liberando en ese momento
la memoria que ocupaba:

2')
sumF(a,4, 0) sumF(a, 3, 9) sumF(a, 2, 16)
8 6 7 9 8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4 1 2 3 4
sumF(a, 4-1, a[4]+0) sumF(a, 3-1, a[3]+9) sumF(a, 2-1, a[2]+16)
sumF(a, 3, 9+0) sumF(a, 2, 7+9) sumF(a, 1, 6+16)
sumF(a, 3, 9) sumF(a, 2, 16) sumF(a, 1, 22)
s= ¿? s= ¿? s= 30
s= Σ4α=1a[α]=¿? s= Σ4α=1a[α]=¿? s= Σ4α=1a[α]=
=8+6+7+9=30

Si en las no finales teníamos aquí una cadena ascendente de combinaciones, en las finales tenemos
una cadena ascendente de devoluciones, en la que cada función devuelve a la anterior la solución a la
que se llegó en el caso trivial; hasta llegar finalmente a la llamada 0, quien puede entregar la solución
a quien la llamó: el usuario.
ÍNDICE

1')
sumF(a,4, 0) sumF(a, 3, 9)
8 6 7 9 8 6 7 9
1 2 3 4 1 2 3 4
sumF(a, 4-1, a[4]+0) sumF(a, 3-1, a[3]+9)
sumF(a, 3, 9+0) sumF(a, 2, 7+9)
sumF(a, 3, 9) sumF(a, 2, 16)
s= ¿? s= 30
s= Σ4α=1a[α]=¿? s= Σ4α=1a[α]=
=8+6+7+9=30

0')

53
25905053.doc

sumF(a,4, 0)
8 6 7 9
1 2 3 4
sumF(a, 4-1, a[4]+0)
sumF(a, 3, 9+0)
sumF(a, 3, 9)
s= 30
s= Σ4α=1a[α]=
=8+6+7+9=30
ÍNDICE

En general en PeDos manejaremos sólo recursiones de este tipo, lineales (finales o no), en las que
cada caso se realiza una o ninguna llamada recursiva (que por tanto que serán siempre de complejidad
lineal, aunque es posible que exista alguna constante sumada o una constante multiplicativa)

ÍNDICE

Pag 61. Introducción recursividad. Una función no lineal Primer párrafo y fun
suma
La función ejemplo del libro, reescrita sin usar la declaración sea es:

fun suma (a:vector[1..n] de ent; i,j: entero) dev (s:entero)=


caso i > j → 0
[] i = j → a[i]
[] i < j → suma(a, i, (i+j)div2) + suma(a, 1+(i+j)div2, j)
fcaso
ffun

Antes de entrar en discusión, llamar la atención sobre tres pequeños detalles de esta función.

a) Tiene dos casos triviales. El primero de ellos, i>j, funciona como protección ante llamadas
externas erróneas (no muy lógica de cara al usuario; si llamamos con los índices invertidos, no
deberíamos devolver un cero, porque lo mismo da que da lo mismo; la suma cumple
conmutativa. Otra cosa es que la función no los manipule correctamente, como es el caso).

b) Dentro del caso no trivial, no existe operación auxiliar actual. La función combinación (+) tiene
como argumentos (sumandos) sendas llamadas recursivas.

c) Por mucho que el libro intente evitar esta terminología, la declaración local sea m=(i+j)div2 en,
no es otra cosa que una simple asignación a la variable local m (lo que en notación iterativa sería
m:=(i+j)div2. Así, en lo que sigue, uso m en vez de (i+j)div2.

Veamos ahora como sería una recursiva no lineal o múltiple, en la que es posible que en alguno de los
casos se realice más de una llamada. Para ello, vamos a usar la función suma de la página 61 sobre el
siguiente vector a de 5 elementos:
8 3 6 7 4
1 2 3 4 5
ÍNDICE
Como para este ejemplo no hay espacio suficiente para realizar cómodamente un dibujo similar al del
escolio anterior, uso entonces un diagrama arborescente, en el que deberá entenderse que en ambos
extremos de una flecha hay un código completo de la función y una copia del vector (aunque sólo
escriba la parte de código fundamental en cada paso). Además, conforme vayamos bajando de niveles
en el árbol, las copias de datos y código de los niveles superiores, permanecen en memoria.

54
25905053.doc

Donde ponga algo similar a: suma(a,1,5) [m=(1+5)div2=3] significa que la llamada actual es
suma(a,1,5) y que para las llamadas internas que esta llamada actual genere usará 3 como valor para
m.

Iniciamos la cadena descendente de llamadas recursivas:

si no se ven las flechas, picar 'ver / diseño página'


ÍNDICE

0) suma(a,1,5) [m=(1+5)div2=3]

1) suma(a,1,3) [m=2] + suma(a,4,5) [m=4]

2) suma(a,1,2)[m=1] + suma(a,3,3) suma(a,4,4) + suma(a,5,5)


=6 =7 =4

3) suma(a,1,1) + suma(a,2,2)
=8 =3

Los casos triviales devuelven directamente el valor destacado en negrilla y sombreado.


Mapa 3 recursión
ÍNDICE DE FIGURAS

El vector era:
8 3 6 7 4
1 2 3 4 5

Al llegar al tercer nivel (o profundidad 3 de recursión), hay en total 9 copias de código y de datos y
todas las cadenas descendentes de llamadas recursivas han finalizado, llegando a sendos casos
triviales, y se pueden iniciar las cadenas ascendentes de sumas hacia los niveles superiores, liberando
el espacio de memoria ya no necesario.

ÍNDICE

0) suma(a,1,5)

1) suma(a,1,3) + 11

2) 11 + 6

55
25905053.doc

(Obsérvese que aunque el 6 ya estaba disponible para la suma en el segundo nivel, hubo de esperar a
que en el tercero se pudieran sumar 8+3 para generar 11)
ÍNDICE

0) suma(a,1,5)

1) 17 + 11

Y finalmente:

0) suma(a,1,5)= 28

Este tipo de recursiones múltiples no las manejaremos en PeDos. Pero serán bastante frecuentes en
Estructura de Datos y Algoritmos, y pertenecerán a orden de complejidad nlogn, con log logaritmo en
base 2 casi siempre, pues dos serán normalmente las llamadas recursivas internas.

ÍNDICE

Pag 61. Figura 3.2. Terminología de funciones recursivas Corrigieron errores


iniciales María Jesús Vivas (MJV) y Jesús (JeRo).
Atención que en lo que sigue no escribo los guiones encima de x e y.(guiones que significan tupla o
vector de datos)

Discutiremos esta figura, usando nuestra función sum, usada en ejemplos anteriores, poniéndola en
paralelo con el esquema de Peña 61:

{Q(x)} {Q≡ 1 ≤ i ≤ n}
fun f (x:T1) dev (y:T2) = fun sum (a:vector; i: entero) dev (s:entero)=
caso Bt(x) → Triv(x) caso i=1 → a[i]
[] Bnt(x) → c(f(s(x)),x) [] i>1 → a[i]+sum(a, i-1)
fcaso fcaso
ffun ffun
{R(x,y)} {R ≡ s= Σα∈{1..i}.a[α]}

Aunque analizaremos después con más detalles cada uno de los términos del esquema, hay ya una
cosa que llama la atención: en general (x) denota la tupla de datos de entrada (así, los argumentos
formales de sum: a, i), pero también puede significar una restricción de este significado general:
alguno(s) de los elementos de esa tupla o incluso un valor concreto de alguno o todos los elementos
de la tupla de entrada. En un escolio posterior (Pag 64 y ss. Preórdenes) veremos que todavía puede
tener un cuarto significado: tamaño del problema.

Cuando hablemos de la 'llamada en curso' o 'llamada actual', nos referiremos siempre a la llamada
recursiva que estamos ejecutando o estudiando en un momento dado con unos datos de entrada
concretos. En los mapas de memoria de los escolios pag 60. Introducción recursividad. Una función
lineal las diferentes llamadas actuales serían las que en cada momento estaban resaltadas en gris claro.

Vamos por fin con la traducción, de la que es muy útil memorizar los nombres subrayados. Al final de
cada apartado, entre corchetes y resaltado en color, pondré la parte de la función sum que corresponde
a la definición dada:
ÍNDICE

56
25905053.doc

{Q(x)}: Precondición que se ha de cumplir para todos, parte o ninguno (lo que sería Q ≡ cierto)
de los datos de entrada x (no olvidemos que no estoy pintando el guión que debería ir encima de
la x)
[{Q≡ 1 ≤ i ≤ n}]

f nombre de la función [sum].

ÍNDICE
x:T1 Tupla x de datos de entrada (también llamada: vector de datos de entrada, argumentos,
parámetros formales…) compuesto de uno o varios elementos, cada uno con su tipo
correctamente declarado. x:T1 es una manera abreviada de escribir 'cada uno de los argumentos
con su tipo'. Aunque creo que sería más correcto escribir xi : Ti , para dejar bien patente que cada
variable de posición i debe ir declarada con su tipo correspondiente. Otra notación para esto
mismo, y creo que más correcta y gráfica, es 〈 xi : Ti 〉 , que equivale a 〈 x1:T1 ; x2: T2 .... xi : Ti 〉 .
[a:vector; i: entero]

y:T2 tupla y de datos de salida (normalmente uno) con su tipo correspondiente. Mismo
comentario a la notación y tipos que en el caso anterior.
[s: entero]

Bt (x) protección del caso trivial. Será una expresión booleana (de ahí la B), conteniendo un(os)
valor(es) concreto(s) para uno o más de los de x, de modo que cuando se haga cierta la expresión
booleana entremos en el consecuente del condicional. En una misma función es posible que
exista más de un caso trivial, cada uno con su devolución, como en la función suma de Peña 61.
[i = 1]

→ implicación matemática o lógica. A pesar de estar escrita con la flecha usada habitualmente
para el condicional ('implicación' material), deberemos leerlo como 'si estamos en este caso,
necesariamente ejecutamos lo siguiente'.

Triv(x) devolución del caso trivial. Cuando se cumple la condición booleana Bt, la función
entrega Triv(x), que será un valor concreto (incluso en el supuesto que el consecuente del caso
trivial contenga una llamada a función externa, en cuyo caso la devolución del caso trivial será
directamente la salida de esa función externa o bien esa salida operada con algún valor
determinado Ver pregunta 2 de ExP2_1997_1Sem_C)
[a[i], en este ejemplo coinciden esta devolución y el posterior cálculo actual. No siempre
ocurrirá así]

Bnt (x) protección del caso no trivial. Como Bt (x), pero que nos lleva al caso no trivial. Puede
haber más de un caso no trivial. Por ejemplo, la función maxcd en Peña 63. Para fun sum:
[i > 0]

s(x) función sucesor de la entrada actual. Modifica alguno de los datos de entrada que se pasarán
como argumento en la llamada recursiva interna. Es fundamental que esta modificación obligue
a la nueva llamada a trabajar sobre un tamaño de problema menor.
Aunque creo que no se usa, no habría ningún inconveniente en extender el convenio que veremos
en el párrafo siguiente y escribir x' exactamente con el mismo significado que s(x)
[i - 1].
ÍNDICE
(f(s(x)) devolución de la llamada recursiva interna. Valor devuelto por la llamada recursiva
interna (con alguno de los argumentos que se le pasaron modificados por s la función sucesor).
Con frecuencia se usa y' en vez de (f(s(x)).
[sum(a, i -1)]

57
25905053.doc

x (la última de c(f(s(x)), x)) Operación actual, operación auxiliar u operación auxiliar actual.
Dentro del caso no trivial, aquella que se realiza directamente por manipulación algebraica,
lógica o de asignación con alguno(s) de los datos de entrada, para usarlo como operador de la
función combinación (en las funciones no lineales, puede no existir esta operación actual).
[a[i]; esto es, simplemente extraer el dato de posición i del vector a para presentárselo a la
suma]

c función combinación; una simple operación binaria, algebraica o booleana (+, -, *, /, div, mod,
∧, ∨, NOR, EXOR...) que tiene como operadores al cálculo actual y la llamada recursiva interna.
[+]

Uniendo los tres últimos, tenemos el monstruoso consecuente del caso no trivial: c(f(s(x)), x):
Combinación del resultado devuelto por la llamada recursiva interna - f(s(x)) con tamaño del
problema reducido por la función sucesor s(x)- con la operación auxiliar actual (la de la llamada
en curso). O bien: en la devolución del caso no trivial se produce una combinación de la
operación actual con la devolución de la llamada recursiva interna. Una manera más cómoda de
escribir lo mismo es c(y', x)
[a[i]+sum(a, i-1) ver penúltimo párrafo de este escolio].

{R(x, y)} postcondición que ha de cumplir el dato, o datos, de salida y, habiendo tenido x como
datos de entrada.
[{R ≡ s= Σα∈ {1..i}.a[α]}]

Volver a Pag 71. Tabla 3.2. Verificación de funciones recursivas lineales

En ésta y siguientes figuras del libro, siempre que veamos una expresión como (a,b) se ha de entender
como un par ordenado, donde el primer elemento representa una entrada y el segundo una salida. El
mejor ejemplo lo tenemos en la expresión de la postcondición {R(x, y)}.

Harina de otro costal parece la expresión c(f(s(x)), x) (o la equivalente c(y', x)), donde para la llamada
actual, ambos elementos son datos de entrada (operandos) para la función combinación.
Para discutir un poco sobre este tema, mejor usemos la función combinación de nuestra función sum,
(consecuente del caso no trivial):
c(f(s(x)), x) ≡ c(y', x) = a[i]+sum(a, i-1)
ÍNDICE
Bien, sum(a, i-1) = y', puesto que será el valor devuelto (... cuando consiga devolverlo;-) por la
llamada recursiva interna. Pero, para la llamada actual, la que recibió como argumento el índice i, ¿es
esa y' un dato de entrada o uno de salida?. (Ojo que no pregunto qué es para la función combinación,
que trivialmente será un operando). Entrada sí es, implícita, no declarada en la especificación; pero
entrada a fin de cuentas, ya que es un dato recibido desde fuera. Y una salida no puede ser, puesto que
la llamada actual no la produce directamente mediante manipulación algebraica, lógica o de
asignación de alguno(s) de los datos de entrada.

Veamos ahora la operación auxiliar actual: a[i]. Resulta que es un valor de salida generado por el
consecuente del caso no trivial por simple manipulación directa de un dato de entrada (extracción de
un elemento del vector de entrada). ¿Valor de salida?, entonces ¿por qué no aparece declarada como
tal por ninguna parte?. Porque no hace falta. Que sea un dato de salida no implica que la función lo
entregue como tal dato de salida al llamante (usuario u otro programa); puede ser, como lo es en este
caso, que sea un dato de salida para consumo inmediato interno de la función. La aparentemente
incongruente notación del libro para la combinación resulta que es completamente correcta.

ÍNDICE

58
25905053.doc

Dicho de otra manera tal vez más clara (¿?). En la combinación se operan un dato de entrada (que es
suministrado por la salida de la llamada recursiva interna función) y un valor calculado por la
operación auxiliar actual. Este dato calculado es una salida para 'consumo inmediato' que no se
entrega al usuario llamante.

Otra observación de suma importancia sobre esta misma función combinación. Al escribir c(y',x)
sólo estamos haciendo referencia a lo comentado en la nota anterior, par ordenado de (entrada,
salida), y en ningún caso hemos de considerarlo como una imposición en el orden que se
escriban los operandos de la función combinación. Cuando ésta sea conmutativa (mucho cuidado
cuando no lo sea), podemos hacerlo en el orden que nos dé la gana. Algo más arriba puse que en el
caso de la función sum, la combinación c(y',x) era a[i]+sum(a, i-1), donde el primer sumando es la
salida de la operación auxiliar, x, y el segundo, la entrada y' suministrada por la llamada interna.

Atención a los posibles despistes si aparecen vírgulas (leídas 'prima') en alguna tupla de entrada o
salida; porque en caso de llevarla algún elemento, lo convierte en dato de entrada o salida de la
llamada recursiva interna. Por ejemplo, son completamente equivalentes las siguientes expresiones:
c(f(s(x)), x) ≡ c(f(x' ), x) ≡ c(y', x)
ÍNDICE

Pag 62. Líneas 14 y ss. Finales más eficientes que no finales


Dice literalmente:
Las funciones recursivas finales son, por lo general, más eficientes (en la constante multiplicativa en
cuanto a tiempo y en orden de complejidad en cuanto al espacio de memoria) que las recursivas no
finales.

Párrafo que puede llevar a conclusiones erróneas... como me ocurrió a mí.


Por ejemplo: ya que en las finales no existe cadena ascendente de combinaciones, siendo la
devolución del caso trivial la solución final del problema, podemos ahorrar un montón de memoria si
cada vez que hacemos una nueva llamada, eliminamos de memoria la llamada anterior. Pero las
finales no funcionan así. Existe una cadena ascendente de devoluciones, desde el caso trivial hasta la
llamada externa, que necesita que todas las llamadas permanezcan en memoria hasta que cada una
devuelva su salida a la anterior (ver escolio Pag 60. Introducción recursividad. Función sumF,
recursiva lineal final)

Entonces, el párrafo de Peña 62 no cuadra, muy especialmente lo del orden de complejidad de la


ocupación de memoria, con el comportamiento real de las recursivas.
Una posible explicación de este apartado, es la siguiente (copio y pego la respuesta que me envió el
profesor Anselmo Peñas Padilla, del equipo docente).
'A mi entender, continuando la lectura del párrafo que señalas, Peña hace la afirmación
considerando la transformación a iterativo de las funciones recursivas.'
ÍNDICE
De esta manera, el mencionado párrafo tiene cierto sentido. Lo que no deja de plantear nuevos
interrogantes:
La afirmación original de Peña se refiere claramente a la comparación entre final y no final, no entre
sus transformaciones.

En las versiones iterativas (cuando es posible realizar la de la no final, que no siempre se podrá, al
menos de manera automática), al pie de la letra sí se produce una mejora en la ocupación de memoria
por lo que respecta al código. Pero esta mejora sólo lo será en una constante multiplicativa (nunca
complejidades diferentes), pues donde en una tenemos un bucle, en otra tendremos dos. De estos dos
bucles, también se sigue una mejora en constante multiplicativa (nada más) en los tiempos de
ejecución, siendo más eficiente la versión iterativa por transformación de final.

59
25905053.doc

Parece por tanto seguirse (y esto es apreciación sumamente subjetiva) que la mejora que menciona
Peña en los órdenes se refiere a la comparación entre el iterativo por transformación de recursiva final
y la recursiva final sin transformar a iterativa.

Veamos otro enfoque sobre el mismo tema:


Sigue ahora una figura con los tiempos de ejecución, en segundos, de tres versiones diferentes de un
mismo programa que suma los elementos de un vector: una versión no final (azul), final (marrón) e
iterativa (fucsia). Corresponden a los tiempos obtenidos con la práctica del curso 2k2. Como no era
posible manipular vectores con tamaños lo suficientemente grandes para realizar tomas de tiempo
significativas, se iteraron 50.000 veces diferentes tamaños sí manipulables; de modo que los tres
tiempos correspondientes a un vector de un millón de elementos, en realidad son los tiempos de iterar
50.000 veces las sumas de un vector de 20 elementos. Por tanto los tiempos resultan algo
distorsionados al alza por el aumento de instrucciones derivados del bucle FOR.
ÍNDICE

segundos

2'5

1'5

0'5

0 1 2 3 4 5 6 7 8 9 10
millones de elementos

Comparativa tiempos: no final, final e iterativo


ÍNDICE DE FIGURAS

Se observa que en efecto es algo más eficiente en tiempo la versión final que la no final, siendo ambas
claramente de complejidad lineal y ambas mucho menos eficientes que la iterativa, también lineal.

¿Por qué esta diferencia entre recursivas?. Supongo que por la manipulación de la pila. Las diferentes
llamadas recursivas se van apilando en ella, de modo que la última llamada se mete siempre en la
cima. Pero en las no finales, hay que hacer dos 'meter cima': uno para la operación auxiliar actual y
otro para la llamada interna. En las finales, no se apila la operación auxiliar actual: va incluida como
un argumento más en la llamada apilada. Por tanto, por cada llamada en las finales se ahorra como
mínimo un meter y un sacar cima.

De lo que se sigue que en las finales la ocupación de la pila es menor que en las no finales, pero
menor según una constante multiplicativa, no de un orden de complejidad menor. No encuentro
justificación para la supuesta mejora en el orden de ocupación de memoria.

60
25905053.doc

La versión iterativa es mucho más eficiente por dos razones principales (que vienen a ser una única):
existe una única llamada: sólo una vez se consume tiempo poniendo código y datos en memoria y
porque todas las asignaciones se realizan sobre posiciones ya reservadas de memoria, sin usar la pila.
ÍNDICE

Pag 63. Punto 3.2. ∀x∈DT1.Q(x)→R(x,f(x))


El predicado ∀x∈DT1.Q(x) → R(x,f(x)) significa:
para toda tupla de datos de entrada (cada elemento perteneciente al dominio de su tipo) que cumplan
la precondición, entonces, las tuplas de salida cumplen la postcondición (normalmente la de salida
será una monotupla).

Otra manera formal, pero para mi más clara, de escribir lo mismo es:
∀x∈DT1 .Q(x) → ∃ y∈ DT2 . R(x,y)
ÍNDICE

Pag 64 y ss. Preórdenes; pbf


Estas tres páginas aparentan una mayor dificultad de la que tienen. Para poder entrar en el tema de
manera racional y ordenada, habría que ordenar los órdenes, comenzando por el orden tal como lo
entendemos en el lenguaje ordinario: Un conjunto está ordenado si todos sus elementos los podemos
poner en secuencia: primer elemento, segundo elemento, tercero, cuarto, ... y así hasta el último....
(si el conjunto es infinito, la secuencia es infinita y no tiene elemento ultimo).

Diremos entonces que primero precede al segundo (y precede a todos sus siguientes), segundo
precede al tercero (y a todos sus siguientes)..., siendo la relación 'precede a' una relación de
preorden estricto que denotamos con el símbolo . Así escribiremos 1º 2º 3º 4º.... (decimos
que es estricto pues no decimos que tercero precede a tercero).

A partir de esta definición tan elemental (en realidad existe todavía un orden mucho más restrictivo:
precede inmediatamente, que no cumple transitiva) se irían extendiendo las demás definiciones de
órdenes. Porque ocurre que no todos los conjuntos son secuenciables de esta manera y sin embargo
poseen algún tipo de 'orden' más relajado.

El ejemplo más conocido es el conjunto de los R de los reales, donde dados dos números cualesquiera
se puede determinar si son iguales, y en caso de no serlo, podemos decir quién es el menor de los dos
(se dice entonces que el orden es total). Pero dado un intervalo de reales, es imposible poner todos los
números del intervalo en una secuencia ordenada . Por eso se dice que R es un conjunto no ordinal o
no numerable. Incluso, si el intervalo es abierto, no existen elementos que sean etiquetables como
primero y último del intervalo.

ÍNDICE
Aquí no vamos a desarrollar todos los tipos de órdenes existentes (para las preguntas tipo test, será
imprescindible memorizar las definiciones de la página 64). Nos centraremos directa e informalmente
en los pbf aplicados al tipo de conjuntos que nos interesan, sin usar ni mencionar las distintas
propiedades que la relación de orden pueda tener.

Un apunte protesta. ¿por qué todos los libros de matemáticas que conozco comienzan la casa por el tejado?.
Directamente por el orden más laxo y abstracto. Pero para llegar a esa abstracción primero se ha de comenzar
por el orden de los naturales ordinales, (qué propiedades cumplen), después por los naturales cardinales (qué
nuevas propiedades cumplen y/o dejan de cumplir respecto del anterior conjunto), para pasar a los enteros (qué
propiedad dejan de cumplir) etc, etc. No se llega al lenguaje descriptivo a partir de la abstracción. Es el
lenguaje descriptivo el que permite alcanzar la abstracción.

61
25905053.doc

Empecemos por los conjuntos ordinales (u 'ordinables', o numerables o secuenciables) más sencillos,
cuyo paradigma es el conjunto N de los naturales. Son todos aquellos que cumplen lo dicho al
principio, a cada uno de sus elementos le podemos poner una etiqueta que expresa su posición en la
secuencia: primero, segundo.... Todo conjunto de este tipo (ordenable, 'ordinable', numerable o
secuenciable) es un preorden bien fundado (pbf).

Bien, muy bonito. ¿Pero quién es el conjunto que nos interesa?. El conjunto de los distintos
tamaños de problema que se van generando en las sucesivas llamadas recursivas.

Volvamos sobre nuestra función sum:


fun sum (a:vector[1..n] de ent; i: entero) dev (s:entero)=
caso i=1 → a[i]
[] i>1 → a[i]+sum(a, i-1)
ÍNDICE

que usamos para sumar el vector de cuatro elementos a ≡ [8, 6, 7, 9]. Hemos visto en el escolio Pag
60. Introducción recursividad. Función sum, recursiva lineal no final que en cada nueva llamada
trabajábamos con un subconjunto del vector. Veámoslo ahora desde la siguiente perspectiva:

Llamada nº Subvector tamaño del posición


problema
0 [8, 6, 7, 9] 4 cuarto
1 [8, 6, 7, 9] 3 tercero
2 [8, 6, 7, 9] 2 segundo
3 [8, 6, 7, 9] 1 primero (minimal)
Mapa 1 tamaños pbf
ÍNDICE DE FIGURAS

Donde:
• subvector es la parte del vector a sobre el que trabajará una llamada dada (determinado por el
índice i).
• tamaño del problema es el número de elementos que tiene cada subvector
• posición es la posición ordinal de los diferentes tamaños contando a partir del tamaño mínimo
(minimal del pbf).

Aquí, las que nos interesan, son las columnas sombreadas. Observamos que los diferentes tamaños del
problema forman un conjunto ordinal (con las mismas características, en cuanto al orden, que N).
Todos los tamaños son etiquetables según un criterio de secuencia ordenada (diremos también que el
conjunto de los tamaños del problema es contable o numerable). Por tanto, el conjunto de los tamaños
del problema forma un pbf.

ÍNDICE
En este ejemplo, el cardinal que expresa un determinado tamaño se corresponde con el ordinal que
expresa la posición de ese tamaño en la secuencia ordenada de tamaños. Pero es una coincidencia
completamente intrascendente (que se producirá con frecuencia cuando se recorre el vector
descendentemente con reducción del tamaño b=1). Veamos un ejemplo donde no se produce esta
coincidencia:

Sea el siguiente fragmento de código (que no nos importa de dónde procede ni qué hace... si es que
hace algo útil):

fun ejem (a:vector[1..n] de ent; i: entero) dev (s:entero)=


caso i < 1 → 0
[] i ≥ 1 → a[i]+sum(a, i-2)

62
25905053.doc

y realizamos una primera llamada ejem(a, n), siendo a ≡ [4, 8, 6, 7, 9, 3, 5, 4, 7].


Construyamos una tabla como la anterior:

Llamada nº Subvector tamaño del posición


problema
0 [4, 8, 6, 7, 9, 3, 5, 4, 7] 9 sexto
1 [4, 8, 6, 7, 9, 3, 5, 4, 7] 7 quinto
2 [4, 8, 6, 7, 9, 3, 5, 4, 7] 5 cuarto
3 [4, 8, 6, 7, 9, 3, 5, 4, 7] 3 tercero
4 [4, 8, 6, 7, 9, 3, 5, 4, 7] 1 segundo
5 [4, 8, 6, 7, 9, 3, 5, 4, 7] 0 primero (minimal)
Mapa 2 tamaños pbf
ÍNDICE DE FIGURAS

Vemos que los diferentes tamaños (¡ojo! no confundir los tamaños con posibles elementos del vector)
son 0, 1, 3, 5, 7 y 9. ¿Forman estos tamaños un conjunto ordinal o numerable?. La pregunta no está de
más; ya no es tan trivial como antes.

ÍNDICE
Pero veamos; con la función, la llamada inicial y el vector dados ¿existe, por ejemplo, la posibilidad
de un tamaño intermedio entre los tamaños 3 y 5? o ¿entre los tamaños 0 y 1?.
Claramente no. En consecuencia, estos tamaños forman un conjunto ordinal, porque podemos poner a
todos sus elementos en una secuencia ordenada; cada elemento etiquetable con su posición ordinal.
Otra vez un pbf.
O lo que es lo mismo (fundamental para la definición Peña 3.4): los tamaños del problema forman una
sucesión. 0 1 3 5 7 9.

Como en todos nuestros pbf de tamaños, esta sucesión es una sucesión de números naturales,
podemos leer como menor que, en vez de precede a. O incluso sustituir por el símbolo <.

• Si la observamos a partir del minimal, es sucesión estrictamente creciente:


• Si la observamos a partir del tamaño inicial (el correspondiente a la primera llamada, la externa),
forma una sucesión estrictamente decreciente.

Aunque aquí sólo estamos hablando de preórdenes (relación 'precede a'); para verlo más claro,
podemos reescribir la sucesión del siguiente modo (donde se lee 'sigue a', o bien en nuestro caso
como 'mayor que') 9 7 5 3 1 0.

En algunos test de exámenes (por ejemplo ExP2_1998_SepO_B, pregunta 9), se usa 'cadena' en vez de
sucesión.

¿Y por qué en Peña 3.4 habla de la no-existencia de sucesiones infinitas?. En los pbf que acabamos de
ver (tamaños de los problemas con las funciones sum y ejem) no tenemos problema alguno,
recorramos como recorramos la sucesión, ésta siempre será finita.
ÍNDICE
Vayamos a N, que como sabemos también es un pbf. Si lo recorremos ascendentemente a partir del
minimal: 1 2 3 4 ... es claramente una sucesión infinita estrictamente creciente.
Pero recorrámosla descendentemente. A pesar de ser N un conjunto infinito, necesariamente habremos
de elegir un natural cualquiera para comenzar el recorrido. No podemos decir que comenzamos en
infinito, pues ∞ no es un número (es un límite). Una vez escogido un n determinado: n n-1 ... 2
1. Da lo mismo que n elijamos. La sucesión no puede ser infinita, pues antes o después llegaremos

63
25905053.doc

al minimal que carece de predecesor. Entonces, en un pbf no existen sucesiones infinitas estrictamente
decrecientes.

ÍNDICE
Pero todos nuestros problemas serán con tamaños iniciales finitos (nunca usaremos vectores
infinitos... espero), entonces ¿Es necesaria esta definición de la 'no-existencia sucesiones infinitas
estrictamente decrecientes'?. Sí, por tres razones: una teórica que esquemáticamente acabamos de ver
al hablar del pbf de los naturales, una segunda que veremos después al hablar de funciones no lineales
y una tercera de tipo práctico no declarado en esta definición 3.4 (y sí implícitamente en el punto 6 de
la tabla 3.2):

En nuestros problemas, el tamaño minimal del problema corresponderá siempre al caso trivial. Por
tanto, es necesario que al recorrer la sucesión de tamaños del problema lo hagamos partiendo
del tamaño inicial en dirección al minimal determinado por el caso trivial, y que la sucesión así
recorrida sea estrictamente decreciente, que necesariamente será finita al tropezar con el minimal.

Para aclararlo, veamos un ejemplo de una función mal diseñada, en la que la primera llamada es
ejem2(a,n):
ÍNDICE
fun ejem2 (a:vector[1..n] de ent; i: entero) dev (s:entero)=
caso i < 1 → 0
[] i ≥ 1 → a[i]+sum(a, i+2)

Bien, la sucesión de tamaños será n, n+2, n+3, n+4..... en principio podemos construir una sucesión
finita estrictamente decreciente. Basta arrancar, por ejemplo, en n+4: n+4 n+3 n+2 n+1 n.
Pero hay dos problemas: hemos dejado fuera algunos (infinitos) tamaños del problema. Y dos: esa
sucesión tiene un minimal, pero no es el tamaño minimal del problema. No se corresponde con el
tamaño esperado en el caso trivial.
Dicho de otro modo. No hemos podido recorrer una sucesión finita estrictamente decreciente
partiendo del tamaño inicial y terminando en el tamaño minimal (i=0)

Resumiendo. En nuestro problemas tenemos que conseguir que los tamaños del problema formen una
sucesión del tipo:
tamaño_inicial ... tamaños_sucesivamente_menores ... minimal.

Vamos ahora a complicar un poco el asunto. Todo lo que hemos visto hasta ahora se limita a
funciones lineales, esto es, con una única llamada recursiva interna en el caso no trivial. En todos
estos casos, la relación precede podemos simbolizarla con el signo . Así diremos que en el caso de la
función ejem, el tamaño de problema 3 precede (o es menor) al tamaño 5 en el preorden de los
tamaños del problema. Simbólicamente 3 5 (además, 3 precede estrictamente a 5).
ÍNDICE

Pero Peña usa el símbolo que podemos leer como precede o coincide en el pbf. ¿Por qué?.
Creo que es simplemente por respeto a la definición general de pbf de interés para otro tipo de
problemas, donde puede ser útil (necesaria) la propiedad reflexiva.

Es cierto que un determinado tamaño de problema es menor o igual que él mismo, pero por los
ejemplos vistos en Peña, CD y ExPeDos (y un poco de ¿sentido común?), no creo que nos
encontraremos una función que en niveles diferentes de la recursión genere sendos subproblema del
mismo tamaño.

Y tampoco creo que el uso/admisión de reflexiva tenga que ver con las funciones no lineales.
ÍNDICE

64
25905053.doc

Veámoslo con el ejemplo visto en el escolio Pag 61. Introducción recursividad. Una función no lineal

fun suma (a:vector[1..n] de ent; i,j: entero) dev (s:entero)=


caso i >j → 0
[] i = j → a[i]
[] i < j → suma(a, i, (i+j)div2) + suma(a, 1+(i+j)div2, j)

función que usaremos para sumar el vector [8,3,6,7,4]. Dibujo ahora un diagrama arborescente muy
similar al usado en aquel escolio, pero ahora en los nodos del árbol pongo solamente el subvector
sobre el que va a actuar la llamada correspondiente.

0) [8,3,6,7,4]

1) [8,3,6,7,4] [8,3,6,7,4]

2) [8,3,6,7,4] [8,3,6,7,4] [8,3,6,7,4] [8,3,6,7,4]

3) [8,3,6,7,4] [8,3,6,7,4]

Mapa 3 tamaños pbf


ÍNDICE DE FIGURAS

Para estudiar el árbol de los tamaños del problema, nos es particularmente útil la definición 3.4.
Partiendo del tamaño inicial (raíz del árbol), tomemos el camino que tomemos, la sucesión formada
por los tamaños del problema siempre será estrictamente decreciente y finita por terminar en un
tamaño minimal (los subrayados). Además, se pueden observar dos particularidades:

ÍNDICE
• El tamaño [8,3,6,7,4] es el segundo según su rama derecha, y el tercero según su rama izquierda.
• ¿Ahora podemos/debemos usar el símbolo ? . Pues cualquier primero precede a su segundo
(ojo, a su segundo), pero además un primero puede tener un hermano primero. Esto es, un
segundo puede tener más de un predecesor primero. ¿Podemos escribir [8,3,6,7,4] [8,3,6,7,4]
del mismo modo que [8,3,6,7,4] [8,3,6,7,4]?.
ÍNDICE
Tengo mis dudas. Veamos un caso extremo:

Hemos visto que [8,3,6,7,4] es un segundo, pero sólo con respecto a su primero [8,3,6,7,4]. Podemos
escribir [8,3,6,7,4] precede o coincide con [8,3,6,7,4].

65
25905053.doc

Pero argumentando que tanto [8,3,6,7,4] como [8,3,6,7,4] son tamaños segundos (cada uno de su
primero), en absoluto podemos decir [8,3,6,7,4] precede o coincide con [8,3,6,7,4], porque son
segundos respecto a minimales que están a diferentes profundidades, por lo que las sucesiones
decrecientes que terminen en ambos minimales tendrán necesariamente distinta longitud.

Bien. Por otra parte, estamos hablando de PREórdenes bien fundados, en los que la relación, sea ésta
cual sea, entre dos elementos a, b se puede leer genéricamente a precede o coincide con b, lo que
invita a no considerar relacionados a todos aquellos elementos que no estén en una misma sucesión
decreciente. En el árbol diseñado, significaría que cualquiera de los caminos que parte de la raíz a un
nodo hoja es un pbf, pero que no lo es si introducimos en ese camino un elemento ajeno.

Consecuencias:

1) A no ser que realmente existan (?) funciones que en la cadena de recursiones realicen, en
diferentes niveles de la recursión, dos llamadas (o más) con el mismo tamaño, a nuestros efectos
podemos ignorar la propiedad reflexiva (¡¡¡sólo en los problemas o preguntas que tengan que ver
con tamaños de problemas, no en las preguntas teóricas tipo test!!!)

2) La definición de pbf no implica que la relación que induce el orden sea una relación de orden
total (sería total si dados dos elementos a, b cualesquiera, entonces se cumple a b ∨ b a)

Una vez realizada una introducción informal a los pbf desde el punto de vista que nos interesa,
volvamos un poco al formalismo.

ÍNDICE

Pregunta. Dado un conjunto D cualquiera, ¿es posible determinar si bajo una determinada relación
los elementos de D forman un pbf?.
Tenemos cuatro caminos distintos para averiguarlo:

• Demostrar, en todas las ramas, la secuenciabilidad o numerabilidad de todos los elementos de D


ordenados según .
• Demostrar que en ninguna de las posibles ramas existen sucesiones infinitas estrictamente
decrecientes.
• Demostrar que cualquier subconjunto no vacío de D posee al menos un minimal (Peña 65,
ejercicio 3.5. Puede que sea una manera cómoda de verificación. Yo siempre me olvido de esta
posibilidad)
• Encontrar una aplicación biyectiva desde el conjunto candidato a pbf hacia otro conjunto que
sepamos que es pbf (típicamente el conjunto imagen será un subconjunto de N)

Y además un quinto camino, que sirve para descartar pbf.


• Demostrar que no se cumple alguna de las siguientes condiciones necesarias (pero no
suficientes):
° Dado un elemento x del conjunto, ocurre que x x (reflexiva)
° Dados tres elementos x, y, z del conjunto, si x y e y z , entonces x z (transitiva)
° Existe al menos un minimal.

Pensando en conjuntos de tamaños de problemas, centrémonos en el cuarto procedimiento, porque


con dos ligeras modificaciones, eso será precisamente lo que hagamos en el punto 5 de la tabla 3.2 de
verificación de la corrección de una función recursiva. Las dos modificaciones son:

66
25905053.doc

a) En vez de aplicación biyectiva, nos conformamos con encontrar una función desde el conjunto
candidato a pbf hacia N. Lo que es perfectamente válido y no entra en contradicción con todo lo
anterior (porque no necesitamos establecer el tamaño del candidato a pbf).

b) Por razones exclusivamente prácticas, admitimos que el conjunto imagen de esa función sea
N+{0}. De hecho, no existe ninguna restricción lógica para que el conjunto imagen sea un
subconjunto (k, +∞) de Z, con k un entero cualquiera, incluso negativo... puesto que ese
subconjunto es biyectable con N, siendo k el minimal del pbf.

ÍNDICE
Y ya puestos en esta tesitura de escribir largo y tendido, vamos con el punto 6 de la mencionada tabla
3.2 de verificación de la corrección de recursivas.

Con la demostración del punto 5, si logramos hacerlo, lo que habremos hecho será en efecto decir que
el conjunto formado por los diferentes tamaños del problema que nuestra algoritmo va originando, es
un pbf; y que por tanto es un conjunto con un minimal (acotado por abajo)... pero no sabemos si lo
está por arriba, que puede perfectamente no estarlo (un vector tiene un tamaño mínimo infranqueable:
0 elementos; pero para máximo, no hay más limitación que la física que imponga la máquina para
tamaños monstruosos, teóricamente infinitos). Por lo que si la función la hemos diseñado mal (como
en ejem2), en cada nueva recursión podemos estar ampliando el tamaño del problema en vez de
reducirlo. Justamente aquí interviene el punto 6. Se trata ahí de demostrar que recorremos el pbf del
conjunto de los tamaños del problema en la dirección correcta. Hacia abajo, hacia el minimal.

(Como veremos en el próximo escolio, la condición de pbf no sólo la usaremos para la verificación de
los puntos 5 y 6 de la tabla 3.2. El 'buen fundamento' es fundamental para el buen inducir.)

Para terminar con el tema, veamos algunos de los ejemplos que plantea de Peña 65 y que pueden ser
útiles para las preguntas de los test.

ÍNDICE
Ejemplo 1 de pbf
Si D es el conjunto de las cadenas finitas de caracteres, demostrar que la relación long definida por:
c1 long c2 ≡ longitud (c1) ≤ longitud (c2) es un pbf. (léase ≡ como definición)

Dicho de otra manera: dos cadenas cualesquiera una será 'predecesora o coincidente en longitud' de la
otra si y sólo si la primera tiene una longitud menor o igual a la segunda.
ÍNDICE
Para simplificar, las cadenas sólo contendrán caracteres alfanuméricos (37 en total). Bajo este
preorden, podremos decir que la cadena <r> es un predecesor o coincidente con la cadena <p8>; así
como también que <zu9> es predecesor o coincidente con <iia> (en breve: zu9 long iia).

Primero vamos de manera indirecta, comprobando que sí cumple las condiciones necesarias: ¿Es
realmente un preorden? sí, es trivial que cumple reflexiva y transitiva. ¿Tiene minimal? sí, a falta de
uno, 37 (Aunque el enunciado no lo dice, supongo que no se admiten cadenas vacías. Si se admiten, el
razonamiento no varía, sólo que habría un único minimal en vez de 37)

¿Existe alguna función que desde el conjunto de las cadenas se aplique en N según la variable 'tamaño
de cadena'? Por supuesto: según esta función, cada cadena se aplica en el natural que describe la
longitud de la cadena. Entonces seguro que el preorden propuesto es un pbf.

Otra manera sería comprobando si cumple la definición 3.4. Supongamos que las cadenas son de
longitud máxima n. Habrá en total 37 + 372 + 373+.... +37n-1 + 372 cadenas diferentes. Una vez

67
25905053.doc

ordenadas las cadenas no existen sucesiones infinitas estrictamente decrecientes, pues todas tendrán
como máximo n elementos.

ÍNDICE

Ejemplo 2 de pbf
¿Es pbf las cadenas de caracteres con la relación lex que establece el orden lexicográfico?
Dicho de otra manera: con el orden lexicográfico (ordenación alfabética de los diccionarios) ¿forma
un pbf el conjunto de las palabras contenido en el diccionario de la Real Academia?. (Para simplificar,
no consideramos las cifras).

Claramente:
libra lex libro, libra lex libramiento, desoxirribonucleico lex s, hambre lex hambre
Claramente se puede aplicar un criterio de secuenciabilidad (¡¡ya está hecho en el propio
diccionario!!).
Claramente se puede encontrar una función adecuada. Basta numerar cada palabra del diccionario.
Claramente es un preorden. Y tiene un único minimal: <a>.
Claramente empecemos por la cadena que empecemos, toda sucesión estrictamente decreciente hacia
el minimal es finita. (Vamos a ver semejante trivialidad. El diccionario de la RAE es un pbf si
conseguimos demostrar que eligiendo cualquier palabra del diccionario, podemos recorrer en sentido
inverso y en un número finito de pasos todas las palabras que median entre la elegida y la entrada
minimal 'a'...).

ÍNDICE
Ejemplo 3 de pbf
(P(A), ⊆), partes finitas del conjunto A con la relación de inclusión, siendo A cualquier conjunto ¿Es
un pbf?.
Conozco el conjunto Partes de A (conjunto formado por todos los subconjuntos de conjunto A,
incluidos el vacío y el propio A), pero ignoro si al decir Partes Finitas de A se está introduciendo
algún matiz nuevo ¿tal vez que A sea finito o que sólo se admitan subconjuntos propios?. No encontré
nada al respecto en las referencias bibliográficas de las que dispongo (pocas) ni en Internet.

Supondré que significa lo mismo que partes de A. Se trata exactamente del mismo problema que el
primero con la relación long , con la única diferencia que aquí el número de minimales es único, el
conjunto vacío.
ÍNDICE

Estos tres ejemplos eran todos pbf. Veamos algunos que no lo son.

Ejemplo 1 de no_pbf
(Z, ≤ ). El conjunto de los enteros con la relación de preorden 'menor o igual a'.

No es desde luego pbf No posee minimal alguno. Aunque como ya queda dicho, en el momento que
establezcamos un punto de corte y tengamos dos intervalos de la forma (-∞, k) [k,∞) tendremos
sendos pbf; el primero con la particularidad que el orden es pbf sólo si recorremos el conjunto en
sentido inverso: el minimal es k (sea positivo o negativo); o lo que es lo mismo, es un postorden con k
como maximal (no posee sucesores estrictos).

Ejemplo 2 de no_pbf
([0,1] ⊂ R , ≤ ). Esto es: intervalo cerrado de reales entre 0 y 1 con la relación de orden 'menor o
igual'.

Vamos primero con el preorden: desde luego lo es: cumple reflexiva y transitiva.

68
25905053.doc

¿Tiene minimal(es)?. Al haber definido el intervalo como intervalo cerrado, el 0 forma parte de él y
no posee predecesores estrictos. Por tanto, podría ser pbf... pero recordemos que estas son condiciones
necesarias pero no suficientes...

¿cumple la definición 3.4?. Ciertamente no. Dado un real cualquiera entre 0 y 1 existen infinitos
reales entre el dado y su supuesto predecesor... por muy próximo que sea el supuesto predecesor.
Vemos que no basta con demostrar que el orden propuesto es un 'preorden con minimales'; es
necesario además que no existan sucesiones infinitas estrictamente decrecientes. O lo que es lo
mismo: es necesario que el conjunto sea numerable, que es lo mismo que decir que es secuenciable.
Que de todos los elementos del conjunto se pueda decir qué posición ocupan en la secuencia
ordenada.

ÍNDICE
Ejemplo 3 de no_pbf
(N x N, ), donde (a', b') (a, b) ≡ (b'-a') ≤ Z (b-a) (léase ≡ como definición)

A pesar del aspecto, es más sencillo de lo que parece. Aquí se esta diciendo que en el conjunto de
pares ordenados de naturales, los pares están relacionados según la relación propuesta (denotada ) si
y sólo si se cumple que la primera diferencia es menor o igual a la otra diferencia, admitiendo que esa
diferencia puede ser negativa, por eso se usa la comparación entre enteros.

Pero claro, si esa comparación es entre enteros, no puede ser un pbf, pues ya sabemos que Z no lo es.
Esto es, no podemos encontrar un par (a, b) tal que b-a sea el minimal de Z por la simple razón que Z
no tiene minimales.

ÍNDICE

Pag 67. Teorema 3.2 Principio de inducción noetheriana


Empecemos por lo menos importante. Este tipo de inducción recibe su calificativo de la matemática
Amalie (Emmy) Noether (n. 23 mar. 1882 en Erlangen, Alemania; m. 14 abr. 1935 en New Jersey,
EE.UU.), una de las grandes figuras matemáticas del siglo pasado.
ÍNDICE
Bien, volvamos a la inducción; pero empezando por lo más elemental.
Inducción matemática (a secas). Es una poderosa herramienta de demostración (o falsación) que se
aplica a problemas cuyo datos se pueden colocar en una secuencia ordenada (primero, segundo,
tercero...) y se supone que cumplen una determinada propiedad. O sea, la inducción matemática sirve
para demostrar si los elementos de un determinado pbf (al que llamaremos pbf de referencia) cumplen
una determinada propiedad que se enuncia como hipótesis.

Históricamente, la inducción matemática nació como método de prueba de propiedades sólo para un pbf muy
determinado: el conjunto de los naturales N.

El procedimiento es sencillo y consta de tres pasos:

• base) Base de inducción. Demostrar que el primer elemento del pbf de referencia (minimal del
pbf) cumple la propiedad enunciada

• hipo) Hipótesis de inducción. Conjeturar que la propiedad enunciada es cierta para i, un elemento
cualquiera del pbf de referencia.

• paso) Paso de inducción. Intentar demostrar, mediante operaciones algebraicas, que la propiedad
se sigue cumpliendo para el elemento del pbf de referencia inmediatamente siguiente a i.

69
25905053.doc

Si dije que es sencillo, ahora parece complejo y confuso, pero lo discutiremos con más detalle con el
siguiente ejemplo:
ÍNDICE
Conjeturo que todo natural múltiplo de cuatro es par. Esto es, afirmo de entrada que para todo natural
i de la forma i = 4·k (k ∈ N –sin el cero-) existe alguna j∈N, tal que se verifica i=2·j. Es una
trivialidad, pero ¡hay que demostrarla!. Lo haremos por inducción matemática:

base) 4 = 4·1 = 2·2 Es cierto que 4 siendo múltiplo de 4, es par. (i=4, k=1, j=2)

hipo) i = 4·k = 2·j Que lo suponemos cierto por hipótesis.

Con estos mimbres damos el siguiente paso: suponemos que la hipótesis se sigue verificando para el
múltiplo de 4 siguiente a i, que es i+4, e intentamos demostrar que este i+4 es también de la forma 2·j'
(evidentemente no tiene por qué cumplirse j' = j, pero sí que j'∈N)

paso) i+4 = 4·k + 4 = 2·j +2·2 = 2(j+1) = 2·j'.

ÍNDICE
La parte fundamental en este paso de inducción es que, transformando lo que está sombreado en gris
en lo que lo está en celeste (sustituyendo i por lo que dice la hipótesis de inducción y 4 por lo que dice
la base de la inducción), hemos llegado a una expresión que nos garantiza que i+4 también es de la
forma 2·j como conjetura la hipótesis.

En este ejemplo, el 'pbf de referencia' es el conjunto de todos los naturales múltiplos de 4, que es sólo
un subconjunto (infinito pero subconjunto) de N. Pero por eso mismo, es por definición un pbf :
podemos 'ordinar' el (sub)conjunto, diciendo que el 4 es el primero; el 8, el segundo; 12, tercero; 16,
cuarto;... de los múltiplos de 4. Entonces, el 'siguiente' natural se refiere al siguiente en el pbf con el
que estemos tratando. Por eso, el siguiente a i, que digamos ocupa la posición n-ésima, es i+4 que
ocupa la posición (n+1)-ésima en el pbf de los naturales múltiplos del cuatro.
ÍNDICE
Es imprescindible conseguir transformar, mediante manipulaciones legales, la expresión del paso de
inducción (en el ejemplo i+4 = 4·k + 4) en otra en la que aparezca la hipótesis de inducción; de lo
contrario, no demostramos absolutamente nada. En esta transformación puede o no aparecer la base de
la inducción.

¿Es necesario comprobar que se cumple el paso de inducción para i=4+4?, esto es ¿es necesario
verificar que el siguiente a la base (minimal) es también de la forma 2·j?. Claramente no. La hipótesis
se establece para cualquier i∈N que sea múltiplo de cuatro; por tanto, si el paso de inducción
demuestra la corrección de la hipótesis, ésta será cierta en particular para el segundo elemento, i =
4+4, que tiene como hipótesis a la base de la inducción (que para colmo sabemos que es cierta
seguro... si se demostró la base, claro).

¿Y qué ocurre si la hipótesis es falsa?. Una de dos:


a) Llegamos a un punto muerto en el que no somos capaces de seguir, lo que no demuestra nada;
habrá que buscar otra vía diferente a la inducción (o repasar, a ver si hemos pasado por alto
alguna transformación legal que conduzca a solución concluyente...cosa bastante probable)
b) Llegamos a flagrante contradicción con la hipótesis, lo que la falsa.

ÍNDICE
Por ejemplo: conjeturo que todo múltiplo de 3 (i=3·k), i ≥ 6, es de la forma i = 2·j (Si el 6 múltiplo de
3 y además par, ¿por qué no los siguientes?).
base) 6 = 3·2 = 2·3
hipo) i =3·k = 2·j
paso) i+3= 3·k +3 =2·j +3. Pero esto contradice la hipótesis, porque par + impar = impar.

70
25905053.doc

Por tanto, la conjetura era falsa, ¡lo que no quiere decir que todos los múltiplos de 3 son impares!.

Siguiente paso antes de llegar a la inducción noetheriana:

Lo que acabamos de ver, es válido para subconjuntos infinitos de N, que por definición son pbf (sin
esto sería imposible la inducción), lo que puede es un inconveniente. ¿Inconveniente?. Para nosotros
sí.

Nuestros problemas tendrán siempre un número finito de datos de entrada (menos mal). Para nuestro
subconjunto finito, podremos establecer fácilmente la hipótesis de inducción; todavía con mayor
facilidad estableceremos la base de la inducción. Pero tendremos un problema teórico serio (en la
práctica es indiferente...) con el paso de inducción, pues al suponer que i+k (el siguiente elemento a i,
con k ≥ 1), también verifica la hipótesis, acabaremos por salirnos infinitamente de rango, al sobrepasar
i la cota superior que delimita el tamaño del subconjunto finito, pbf finito, con el que estemos
trabajando.

También podría ocurrir, y esto ya es en la práctica un problema bastante más serio, que nuestra
hipótesis se demuestre falsa para un subconjunto infinito de N, pero cierta para el particular
subconjunto finito que nos interese.

ÍNDICE
Pensemos por ejemplo en la siguiente conjetura, que nos servirá para determinar el tamaño máximo
reservable para la definición de un vector (cuyos índices son naturales): Todo natural tiene a lo sumo
5 cifras. Claramente es falsa para cualquier subconjunto infinito de N, y por inducción llegaríamos, es
seguro, a una contradicción con la hipótesis. En cambio para casi todos los vectores que usemos en
nuestros problemas reales (o que admita el compilador...) esta conjetura puede ser cierta al no manejar
nunca vectores de más de 99.999 elementos.

Tenemos entonces que modificar ligeramente el procedimiento de inducción para que sea válido para
subconjuntos finitos de N.
ÍNDICE
Bien. Apliquemos un pequeño truco:

• Base de la inducción. Igual que en la inducción matemática 'normal. Comprobamos que el


minimal cumple la propiedad enunciada.

• Hipótesis de inducción. La única diferencia con la inducción 'normal'. En vez de suponer que la
propiedad enunciada la cumple el elemento i del pbf, suponemos que la cumple i-k, el elemento
predecesor de i en el pbf.

• Paso de inducción. Muy parecido a la inducción 'normal'. Intentamos demostrar, mediante


operaciones algebraicas, que si la propiedad es cierta para el elemento precedente, lo sigue
siendo para el elemento i.

Con este simple truco, tenemos garantizado que no nos salimos del pbf con el que estemos trabajando.

Por otra parte, la inducción noetheriana se puede aplicar en pbf infinitos. La base demuestra la validez
de la hipótesis para el minimal, que es predecesor de su siguiente, que también cumplirá la propiedad
porque así lo demuestra el paso de inducción. Este siguiente es predecesor de su siguiente.... y así
hasta el elemento i que nos de la gana.

71
25905053.doc

Esa es la diferencia con la inducción matemática 'normal', porque ahí tendríamos que decir ... y así
hasta el elemento i+k que nos de la gana, que bien podría ser un elemento que precisamente no nos de
la gana.

ÍNDICE
Veamos rápidamente el mismo ejemplo de antes: todo múltiplo de cuatro es par.

base) 4 = 4·1 = 2·2 Es cierto que 4 siendo múltiplo de 4, es par. (i=4, k=1, j=2)

hipo) i - 4 = 4·k = 2·j Que lo suponemos cierto por hipótesis.

paso) i = 4·k - 4 = 2·j -2·2 = 2(j-2) = 2·j'.

ÍNDICE

Ejemplos más complejos y con más detalle los veremos en la verificación del punto 4 de la tabla 3.2.

Volver ahora a Pag 71. Tabla 3.2. Verificación de funciones recursivas lineales
ÍNDICE

Pag 69. Análisis por casos y composición.


Este 'composición' me trajo al principio de cabeza. No terminaba de aclararme con combinación
(función combinación entre la operación actual y la llamada recursiva interna del caso no trivial) y
composición, entendiendo composición como composición de funciones. Pero, no. En el libro,
composición sólo significa realizar la codificación adecuada del análisis por casos realizado. Esto es,
escribir las protecciones de los casos y con las acciones correspondientes.
En algunos exámenes a esta composición se la denomina composición algorítmica.
ÍNDICE

Pag 71. Tabla 3.2. Verificación de funciones recursivas lineales


Además del que se usará en este escolio, se puede ver otro ejemplo completo de verificación de función no
final en el apartado 1 del problema de ExP2_1997_SepR.

Un ejemplo de verificación de función final, al final (en qué otro sitio, si no) de este escolio. Apéndice a tabla
3.2. Ejemplo de verificación de recursiva lineal final.

Esta tabla de verificación sólo es correcta para programas recursivos lineales (en ningún caso se
realiza más de una llamada recursiva interna simultáneamente). Por su extraordinaria importancia,
hay que aprendérsela de memoria, cosa más fácil de lo que parece una vez comprendida
correctamente. En ese momento, bastará (?) realizar el proceso inverso al que veremos aquí: traducir a
símbolos el significado de cada uno de los apartados de la tabla.

ÍNDICE
Importante.
Esta tabla comprueba la corrección lógica de la derivación realizada, pero en absoluto verifica todos
las posibles fuentes de error en el diseño. Por ejemplo: supongamos que nuestra función sum fuera:

{Q≡ 1 ≤ i ≤ n-1}
fun sum_primero (a:vector[1..n] de ent; i: entero) dev (s:entero)=
caso i=0 → 0
[] i>0 → a[i+1]+sum(a, i-1)
fcaso
ffun
{R ≡ s= Σα∈{1..i}.a[α]}

72
25905053.doc

donde aparecen sombreadas las modificaciones introducidas en la versión ya conocida. Si la


sometemos a verificación según la tabla 3.2, pasaría limpiamente todas las pruebas y podríamos
concluir erróneamente que la función es correcta. Pero no, la función no suma el elemento de posición
i=1.
Este fallo no lo detecta la tabla 3.2 pues ahí sólo se verifica si el conjunto de los datos de entrada
forma un pbf y distintas relaciones lógicas: de los casos entre sí, de las entradas respecto a la
precondición, y de las salidas con respecto a la postcondición. Pero no se verifica la correcta decisión
de diseño en el análisis por casos (especialmente el consecuente del caso no trivial) (Peña 69). Esto es,
no se verifica (¿no se puede formalmente?) si el diseño elegido recorre todos los elementos necesarios
del vector, y sólo esos, para el correcto funcionamiento de la función (puse elementos del vector, pero
habría que decir 'los datos de entrada necesarios'; no siempre serán vectores).
ÍNDICE
Entonces, y como corolario, no estaría de más añadir a la tabla una verificación 0, que consistiría en
comprobar informalmente qué ocurre con la función en los valores extremos y 'peri-extremos' del
dominio de definición de las variables que determinan el tamaño del problema; en la función
sum_primero del ejemplo, sería i:
• i = n-1. Se entra en caso no trivial (a no ser que el vector sea vacío) y a[n-1+1]+sum(a, n-1). Se
suma el último elemento a lo que devuelva la llamada interna. Bien
• i = n-2. Caso no trivial: se suma el penúltimo elemento. Bien.
• i = 1. Caso no trivial. a[1+1]+sum(a, 1-1). Se suma el segundo elemento. Bien
• i = 0. Caso trivial. Se devuelve 0 para sumar a a[2]. Bien, pero problema: no hemos sumado el
elemento de posición 1.

Aunque pueda parecerlo, esta comprobación no es la misma que se realiza en el punto 1 de la tabla.

ÍNDICE
La inclusión en la tabla 3.2 de esta 'verificación 0' tampoco es garantía absoluta de corrección, pero al
menos descubre este tipo de errores en los lugares donde es más fácil que aparezcan.

Volvamos a los 6 puntos 'oficiales' de la tabla.


Como prerrequisito, es imprescindible haber comprendido correctamente los conceptos de pbf e
inducción noetheriana, pues, excepto los dos primeros puntos, toda la verificación se basa en que los
diferentes tamaños generados por las sucesivas llamadas recursivas constituyen un conjunto finito con
un preorden bien fundado, del que es minimal el tamaño del problema que lleva al caso trivial.

Para la discusión, usaremos el ejemplo ya conocido, que figurará en color púrpura:

{Q≡ 1 ≤ i ≤ n}
fun sum (a:vector[1..n] de ent; i: entero) dev (s:entero)=
caso i=1 → a[i]
[] i>1 → a[i]+sum(a, i-1)
fcaso
ffun
{R ≡ s= Σα∈{1..i}.a[α]}

En lo que sigue, donde diga caso trivial o caso no trivial habrá de entenderse como casos triviales o no
triviales en el supuesto de que existan varios. (Ejemplos de verificación de algunos puntos de la tabla
3.2 con funciones de este tipo que tienen varios casos triviales y/o no triviales, en los problemas de
los exámenes ExP2_1999_SepO_B y ExP2_1999_SepR).
ÍNDICE
Tras el enunciado simbólico de cada punto a demostrar, pongo subrayado algunos de los nombres que
puede recibir, para a continuación realizar dos o tres enunciados verbales equivalentes entre sí. El

73
25905053.doc

primero de ellos será una traducción casi literal del simbólico, mientras que los siguientes, serán unos
más cómodos y expresivos.

Al final de cada punto, dos de las posibles verificaciones para la fun sum (una completa y
excesivamente formal, paso a paso, rotulada F, y otra breve, pero igualmente correcta, rotulada B...
supongo que en examen, en ocasiones será preferible la primera... o una intermedia entre las dos).

Si es necesario, volver al escolio Pag 61. Figura 3.2. Terminología de funciones recursivas.
Atención que igual que allí, en lo que sigue no escribo los guiones encima de x e y, que significan
tupla o vector de datos

1. Q(x) ⇒ Bt (x) ∨ Bnt (x) Exhaustividad de los casos o completitud de la alternativa

a) Si la tupla de datos de entrada cumple la precondición, entonces necesariamente se verifica la


protección del caso trivial o la del no trivial. O bien:
b) Si se cumple la precondición, entraremos en caso trivial o no trivial.

F:Q(x) ≡ (1 ≤ i ≤ n) ⇒ ((1 = i) ∨(1 < i )) ≡ (B (x) ∨B


t nt (x))

B:(1 ≤ i) ⇒ (i=1) ∨(i >1)

ÍNDICE
Tanto el libro como el equipo docente, en consulta telefónica, consideran suficiente esta
comprobación, sin necesidad de comprobar la autoexclusión de los casos, puesto que, argumentan,
estamos usando una programación determinista en la que no son posibles dos acciones diferentes y
simultáneas sobre el mismo argumento (de lo contrario, protestaría el compilador).
De acuerdo. Pero nosotros aquí estamos programando con lápiz y papel, donde es fácil realiza
solapamientos y no hay compilador que vigile. Por ejemplo: después de haber usado varias veces la
función sum, fue al llegar a este punto donde me di cuenta que por error había establecido como
protecciones de los casos (i =1) e (i ≥ 1), lo que haría ciertas a ambas cuando i=1. Aplicando al pie de
la letra el punto 1 de la tabla, el error habría pasado limpiamente la comprobación y no lo habría
descubierto.

Creo entonces que es más lógico y útil demostrar Q(x) ⇒ Bt(x) ⊕ Bnt (x), donde comprobamos la
exhaustividad y la autoexclusión de los casos (⊕ es 'o exclusiva', EXOR, 'o...o...')... aunque a la hora
de un examen usemos solamente la conectiva ∨.

Veamos además la pregunta 9 de ExP2_1998_2sem_C. La respuesta que aparece marcada como


correcta, además de ser correcta (son necesarias autoexclusión y exhaustividad; a no ser que la
pregunta signifique otra cosa) es la que el equipo docente consideró como tal.

9.- Al efectuar el análisis por casos:


A. Es suficiente que las opciones sean exhaustivas.
B. Ninguna de las otras es correcta.
C. Basta que las opciones sean autoexcluyentes.
D. Basta que el número de opciones sea finito.

ÍNDICE

2. Q(x) ∧ Bnt (x) ⇒ Q(s(x)) Conservación de la precondición, llamada recursiva interna cumple la
precondición, sucesor pertenece al dominio, operación de sucesora es interna ...

74
25905053.doc

a) Si se cumple la precondición y la protección del caso no trivial, necesariamente la tupla de datos


entrada sucesora de la llamada actual cumple la precondición (trivialmente no se exige esto del
caso trivial).
b) Si estamos en caso no trivial, entonces la entrada de la llamada recursiva interna, verifica la
precondición.

F:Q(x) ∧Bnt (x) ≡ ((1 ≤ i ≤ n) ∧(1 < i)) ≡ (i > 1)


(i > 1) → (a[i]+sum(a, i-1))
(a[i]+sum(a, i-1)) ⇒ {Q ≡ (1 ≤ i-1 ≤ n)} ≡ Q(i-1) ≡ Q(s(x))

La primera línea, no son más que equivalencias a Q(x) ∧Bnt(x) en este problema concreto.
Segunda línea: ya que estamos en caso no trivial, ejecutamos el consecuente.
Y este consecuente implica (tercera línea) que tras la reducción i-i se siga cumpliendo la
precondición, ya que si por hipótesis i > 1, es claro que i-1 ≥ 1... que es la precondición.

B: [1 < i → s(i) = i-1] ⇒ 1 ≤ i-1 ≤ n

ÍNDICE

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

a) Si se cumple la precondición y estamos en el caso trivial, entonces necesariamente la devolución


del caso trivial cumple la postcondición (base de la inducción).
b) La devolución del caso trivial verifica la postcondición (base de la inducción).
c) Con tamaño minimal, la función cumple la postcondición (base de la inducción).

F:Q(x) ∧Bt (x) ≡ ((1 ≤ i ≤ n) ∧(1 = i)) ≡ (i = 1)


(i = 1) → a[1]
a[1] ⇒ {R ≡ (s= Σα∈{1..1}.a[α]})} ≡ R(1, a[1]) ≡ R(x, triv(x)).

Primera línea: equivalencias de Q(x) ∧Bt (x)


Segunda línea: como estamos en caso trivial, ejecutamos su consecuente.
Tercera línea: y este consecuente implica que se cumple la postcondición.

B: i =1 → a[1] ≡ R(1, triv(1))

Los dos siguientes puntos de la tabla 3.2 es donde precisamos que los tamaños del problema formen
un pbf, con el tamaño minimal en el caso trivial, para poder aplicar inducción noetheriana.

4. Q(x) ∧ Bnt (x) ∧ R(s(x), y’) ⇒ R(x, c(y’,x)). Paso de inducción, donde R(s(x), y’) representa la
hipótesis de inducción).

a) Si se cumple la precondición en el caso no trivial y suponiendo cierto que el valor y' devuelto
por la función, con el sucesor de la entrada actual como entrada propia s(x), verifica la
postcondición (hipótesis de inducción), entonces dicho valor (y’) combinado con el cálculo
auxiliar actual, verifica la postcondición.
b) Estando en caso no trivial y suponiendo que la devolución de la llamada recursiva interna
verifica la postcondición, entonces, también la verifica la devolución de la llamada actual.

75
25905053.doc

c) En caso no trivial, suponiendo que la función con el tamaño predecesor cumple la hipótesis de
verificar la postcondición, entonces también la cumple la función con el tamaño actual.
ÍNDICE

F: Q(x) ∧Bnt (x) ∧R(s(x), y’) ≡ (1 ≤ i ≤ n) ∧(1 < i) ∧(s' = Σα∈{1..i-1}.a[α])≡


≡ (i > 1) ∧(s'= Σα∈{1..i-1}.a[α])

(i>1)→ (a[i]+sum(a, i-1))

(a[i]+sum(a, i-1))⇒a[i] + (s'= Σα∈{1..i-1}.a[α]) ≡


≡ (s = Σα∈{1..i-1}. a[i]+a[α]) ≡ R(x, c(y’,x)) ≡
≡ (s = Σα∈{1..i}. a[α]) ≡ R(x, y)

Donde el primer grupo de dos líneas son distintas equivalencias de la primera de ellas para este
problema particular. El predicado subrayado representa la hipótesis de inducción.
ÍNDICE
La línea del centro, representa la protección y ejecución del caso no trivial. Subrayada, la
llamada que se supone verifica la hipótesis de inducción.

Las tres últimas, son el consecuente del caso no trivial, sustituyendo sum(a, i-1) por el valor que
devuelve, s', que constituye la hipótesis de la inducción. Por supuesto que R(x, c(y’,x)) ≡ R(x,
y). Si puse el primero en la penúltima línea fue simplemente para destacar el significado de las
transformaciones.

En las tres últimas líneas, podríamos haber operado de otra manera para comprobar si la
implicación es cierta. Partir de la postcondición, e intentar convertirla en una combinación de la
entrada actual y la hipótesis de inducción:

F' {s= Σα∈{1..i}.a[α]} ≡ {s = a[i] + [s'= (Σα∈{1..i-1}.a[α])]}≡


≡ {a[i] + y'} (porque s' cumple R por hipótesis) ≡ R(x, c(y’,x)).

B: (i > 1) ∧(y'= Σα∈{1..i-1}.a[α] ) ⇒ (s= Σα∈{1..i}.a[α]) ≡ (s= a[i]+y) ≡ R(x, c(y’,x))

ÍNDICE
Se siga el procedimiento que se siga, obsérvese que seguimos la mecánica vista en el escolio Pag 67.
Teorema 3.2 Principio de inducción noetheriana . Escribimos el consecuente del caso no trivial (o la
postcondición) y realizamos transformaciones algebraicas intentando que aparezca por alguna parte la
hipótesis de inducción. Si no aparece, la 'verificación' no verifica.

Ya que en la función sum estamos recorriendo el vector descendentemente, con i tomando valores
desde n hasta 1, puede parecer contradictorio que en la hipótesis de inducción (en la postcondición) se
diga precisamente que i toma valores entre 1 e i-1. Esto es: si estamos en la llamada i, hemos
realizado todas las llamadas comprendidas entre n e i. Sin embargo, la hipótesis habla justamente del
intervalo no llamado.
Pero no hay nada contradictorio. Porque si la hipótesis de inducción se cumple, no es en la cadena
descendente de llamadas, es en la cadena ascendente de combinaciones. Para más detalles, ver el
mapa de memoria de la función sum en la página 62 de este documento, donde queda claro que la
verificación de los puntos 4 y 5 se refiere siempre a la cadena ascendente de combinaciones.
ÍNDICE

76
25905053.doc

Es importante fijarse que en los diseños en los que se recorre el vector descendentemente, el índice i
decrece conforme se incrementa el número de llamadas recursivas. Si se desea que índice y número de
llamada se comporten igual, basta modificar el diseño para recorrer el vector ascendentemente. Claro
que entonces la postcondición se escribiría R ≡ s= Σnα=ia[α] (desde i hasta n, en vez de 1 hasta i).

La hipótesis de inducción puede que se vea más clara (?) si se escribe R(s(x), f(s(x))), manera
completamente equivalente a R(s(x), y’).
ÍNDICE

Lo que sí que se vería más claro es el paso de inducción en su conjunto, si en vez de R(x,c(y’, x)),
usamos la equivalente R(x,y), que se lee 'la devolución de la llamada actual, cumple la postcondición'.
Tenemos entonces que el punto 5 de la tabla 3.2, podemos escribirlo de cuatro maneras diferentes:
Q(x) ∧Bnt(x) ∧R(s(x), f(s(x))) ⇒ R(x, c(y’,x))
Q(x) ∧Bnt(x) ∧R(s(x), f(s(x))) ⇒ R(x, y)
Q(x) ∧Bnt(x) ∧R(s(x), y’) ⇒ R(x, c(y’, x))
Q(x) ∧Bnt(x) ∧R(s(x), y’) ⇒ R(x, y)

Personalmente, me resulta más cómoda e intuitiva la última expresión.

5. Encontrar t : DT1 → Z | Q(x) ⇒ t(x) ≥ 0. Definición de la estructura de pbf sobre los datos de
entrada, el dominio es un pbf, los tamaños del problema son ordenables, los tamaños forman
pbf

a) Comprobar si los diferentes tamaños del problema forman un pbf.


Para ello, encontrar una aplicación t (a la que llamaremos función limitadora) desde el dominio
de los datos de entrada hacia el conjunto de los enteros, tal que si se cumple la precondición,
entonces la función limitadora, para cualesquiera datos de entrada (tamaño del problema), es
siempre mayor o igual que cero.
b) Establecer si los diferentes tamaños del problema son ordenables, formando por tanto un pbf (se
pueden poner en secuencia ordenada).

F: t : DT 1 → Z | Q(x) ⇒ t(x) ≥ 0
¿∃ t: 〈a, {1.. n}〉 → Z ? | i∈{1.. n} ⇒ t(i) ≥ 0
Propuesta: t(i) = i
t(n) = n ≥ 0
t(1) = 1 ≥ 0
ÍNDICE

En la segunda línea únicamente se enuncia la pregunta de si existe tal función cota.


〈a,{1.. n}〉 es la tupla ordenada del dominio de lo datos de entrada, representada en la primera línea
como DT 1.
En la tercera se propone una. Como es una función lineal, basta comprobar los valores extremos, cosa
que se hace en las dos siguientes y últimas líneas

B) t(i) = i ⇒ (i ≥ 0 ⇒ t(i) ≥ 0)

Cuidado. No confundir los conceptos de función limitadora t con el de función tiempo de ejecución
T. La primera, t, sólo acota los tamaños del problema. La segunda, T, acota los tiempos de ejecución.

77
25905053.doc

Ambos conceptos son completamente independientes (aunque en ocasiones tengan expresiones


matemáticas muy parecidas).
ÍNDICE
Por ejemplo: Recuadro 1.3 de Peña 18, tercera línea. Ahí, la función limitadora será alguna de la
forma ndivb (b tamaño de la reducción; puede existir alguna otra constante), por lo que limitadora
será una función lineal. En cambio, la función tiempo de ejecución T será alguna de tipo exponencial.

Desafortunadamente, a ambas funciones se les puede llamar función cota; será el contexto quien
decida de qué función se está hablando. Incrementa la posible confusión el no respetar siempre la
notación t para la limitadora y T para tiempo. Podemos encontrarlas etiquetadas al revés.

Veamos con un poco de detenimiento la segunda definición b)... aunque sea repetición de lo ya dicho
en el escolio correspondiente.

Los naturales y los enteros no negativos forman sendos pbf, puesto que fácilmente se pueden poner en
secuencia ordenada: primero, segundo, tercero…

En los naturales es evidente quién es quién.


En enteros no negativos, primero será el 0, segundo el 1… enésimo el n-1.
Será también pbf cualquier subconjunto finito de Z, pues todos sus elementos son trivialmente
ordenables según el criterio de ordinalidad (o secuenciabilidad). Por ejemplo el conjunto {-3, 0, 4,2,-
7} una vez secuenciado se convierte en la tupla 〈-7,-3,0,2,4〉 , con la muy interesante peculiaridad
que el –7 es minimal si recorremos la tupla de izquierda a derecha, mientras que si lo recorremos al
revés, el minimal será el 4, pues 4 es el primer elemento, 2 el segundo, 0 tercero, -3 cuarto y –7
ÍNDICE
Es también pbf el conjunto de las entradas del diccionario de la RAE, la primera entrada es 'a', la
décima sexta 'abacero', etc, etc. (edición vigésima primera… lo que ya indica un pbf en el conjunto de
las ediciones :-)

En resumidas cuentas. Lo que se trata de demostrar en este punto es que existe una primer tamaño
minimal del problema (el correspondiente al caso trivial), un segundo tamaño, un tercero... (tamaños
determinados normalmente por la variable i). Y se hace por el procedimiento expeditivo de demostrar
que existe al menos una aplicación entre el conjunto de los tamaños del problema y un pbf conocido.
Por comodidad, usaremos como tal pbf conocido el conjunto de los enteros no negativos.

Pero nada impide usar otro cualquiera por incómodo o disparatado que sea (por supuesto, siempre que
sea un pbf). Por ejemplo, podríamos usar los siguientes intervalos de enteros:
[-103,+∞), con -103 como minimal (primer elemento del pbf)
(-∞, 248]... con el 248 como minimal (primero: 248; segundo: 247; tercero: 246...).
O el pbf formado por las entradas del diccionario... empezando por el final. primero 'guzpatarra',
segundo 'guzpátaro', tercero 'guzmán', cuarto 'guzla'.
ÍNDICE
Atención al detalle. La demostración de este punto 5, sólo demuestra que los diferentes tamaños del
problema son ordenables (primer, menor o minimal tamaño del problema; segundo tamaño;
tercero...), pero nada más. En particular no demuestra si las sucesivas llamadas reducen o no dicho
tamaño. De este extremo se encarga el punto 6

6. Q(x) ∧ Bnt (x) ⇒ t(s(x)) < t(x) Decrecimiento estricto del tamaño de los subproblemas generados o
decrecimiento del problema o correcto recorrido del pbf

a) Si se cumple la precondición y estamos en el caso no trivial, entonces necesariamente el valor de


la función limitadora para los datos de entrada siguientes (con el tamaño del problema reducido)
es estrictamente menor que el valor de la función limitadora para los datos de entrada actuales.

78
25905053.doc

b) Recorremos el pbf en el sentido correcto (hacia el minimal).


ÍNDICE

F: Q(x) ∧Bnt (x) ≡ ((1 ≤ i ≤ n) ∧(1 < i)) ≡ (i > 1)


(i > 1) ⇒ (i-1< i) ≡ (t(i-1) < t(i)) ≡ (t(s(x)) < t(x))
B) (i > 1) ⇒ (i-1< i)

En este punto 6 hay que tener particular cuidado con quien es s(x) (tamaño siguiente, casi siempre será
s(i), leído, 'sucesor de i ').
Si recorremos descendentemente con reducción 1 por sustracción (p. e. la función sum), s(i) = i-1.
Si recorremos descendentemente con reducción k, s(i) = i - k.
Si recorremos ascendentemente con reducción 1, s(i) = i+1.
Si recorremos ascendentemente con reducción k, s(i) = i + k.
fin de la tabla 3.2 de verificación de recursivas lineales.

ÍNDICE
En la Guía Didáctica de Programación II, incluida en el CD de la Guía del Curso, en el apartado 2.2
(punto tercero de las Cuestiones Propuestas) se define verificación de la corrección parcial de un
algoritmo como la demostración de la verificación de los cuatro primeros puntos de esta tabla.
Entonces, un algoritmo del que sólo se puede demostrar la corrección parcial, es un algoritmo
formalmente correcto, pero del que no podemos demostrar (o todavía no lo hemos hecho) que la
ejecución del mismo termine.

Más brevemente, en la pregunta 5 de ExP2_1999_2sem_D se define como algoritmo parcialmente


correcto a aquel que cumple su especificación sin tener en cuenta su terminación.

Ésta es la razón por la que se dejan para el final de la tabla 3.2 la verificación de la existencia
del pbf y su recorrido correcto. Porque lo lógico habría sido comprobar estos dos últimos
puntos (o al menos el 5) antes de los puntos 3 y 4 (base y paso de inducción), pues si los
tamaños del problema no forman un pbf no tiene sentido realizar una demostración por
inducción ¡que tiene como soporte el pbf de los tamaños del problema!.
ÍNDICE

Apéndice a tabla 3.2. Ejemplo de verificación de recursiva lineal final.


Pasemos por fin a la verificación de los cuatro primeros puntos de la función final sumF ya conocida
(los dos últimos son idénticos a los de la versión no final). Realizo la verificación por el
procedimiento breve:

{Q sumF ≡ (1 ≤ i ≤ n) ∧(w = Σα∈ {i+1..n}.a[α])}


fun sumF (a:vector; i, w: entero) dev (s:entero)=
caso i = 1 → a[i] + w
[] i > 1 → sumF(a, i-1, a[i]+w)
fcaso
ffun
{RsumF ≡ s= Σα∈{1..n}.a[α]}

1. Q(x) ⇒ Bt (x) ∨ Bnt (x) Exhaustividad de los casos o completitud de la alternativa


1 ≤ i ≤ n ⇒ (i = 1) ∨(i > 1)
ÍNDICE

79
25905053.doc

2. Q(x) ∧ Bnt (x) ⇒ Q(s(x)) Sucesor pertenece al dominio, operación de sucesora es interna o
llamada recursiva interna cumple la precondición

Este será normalmente el paso más complicado de verificar en las recursivas finales.

Al entrar en la llamada con tamaño i tenemos:


(i > 1) ∧(w = Σα∈{i+1..n}.a[α]) que lleva al caso no trivial donde se realiza la llamada interna:
sumF(a, i-1, a[i]+w), que tendrá por precondición:

(1 ≤ i-1 ≤ n) ∧(w = Σα∈{i..n}.a[α])

La parte subrayada es correcta, ya que i > 1. Y la de doble subrayado también, ya que:


(w = Σα∈{i..n}.a[α]) = [a[i] + (w = Σα∈{i+1..n}.a[α])]. Verificado lo que se quería.

Podemos hacerlo también de manera más compacta:


(i > 1) ∧(w = Σα∈{i+1..n}.a[α]) ⇒ sumF(a, i-1, a[i]+w) ⇒
⇒ (1 ≤ i-1 ≤ n) ∧[a[i] + (w = Σα∈{i+1..n}.a[α])] ≡ (1 ≤ i-1 ≤ n) ∧(w= Σα∈{i..n}.a[α])

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


i=1 ⇒ a[1]+w ≡ a[1]+(w = Σα∈{2..n}.a[α]) ≡ w = Σα∈{1..n}.a[α] ≡ {RsumF}

4. Q(x) ∧ Bnt (x) ∧ R(s(x), y’) ⇒ R(x, c(y’,x)). Paso de inducción, donde R(s(x), y’) representa la
hipótesis de inducción).
Antes de entrar a discutir este punto, conviene repasar convenientemente el Mapa 2 recursión en la
página 53. Veíamos ahí que en las no finales existe una cadena ascendente de devoluciones, en la que
a partir de la devolución del caso trivial, se va pasando el resultado a cada llamada anterior hasta
llegar a la inicial externa, sin modificar nada por el camino. Por tanto, si por hipótesis la devolución
de una llamada interna cumple R, trivialmente también la cumple la llamada actual.
ÍNDICE

Pero, una duda: en el enunciado de este punto a verificar tenemos en el consecuente R(x, c(y’,x)),
donde claramente aparece una función combinación. ¡Pero en las finales no existe esa función
combinación (al menos, tal como la entendemos en las no finales)!.
Sin problemas: para no complicarnos la vida, imaginemos que sí existe esa función combinación, que
se reduce a operar con el elemento neutro. Por tanto, en nuestra función sumF, todo queda reducido a:
(i>1) ∧R(i-1, y’=s=Σα∈{1..n}.a[α]) ⇒ R(i, 0+(s=Σα∈{1..n}.a[α])) = R(i, s=Σα∈{1..n}.a[α])

Que he escrito de un modo compacto; hay que acostumbrarse a escribir y leer las mismas expresiones
de diferentes maneras.
ÍNDICE

Si en vez de c(y’,x), usamos la equivalente y (sin la vírgula, tal como se vio en la página 77), podemos
escribir este punto 4:
Q(x) ∧Bnt(x) ∧R(s(x), y’) ⇒ R(x, y)
que resulta más cómodo para las finales:
(i>1) ∧R(i-1, y’=s=Σα∈{1..n}.a[α]) ⇒ R(i, y=s=Σα∈{1..n}.a[α])).

Una última cosa. Aunque lo veremos más tarde, en el escolio (Apéndice a pag 94. Desplegado y
plegado. Postcondición no constante), podemos encontrarnos con finales que poseen una

80
25905053.doc

postcondición no constante, dependiente de los parámetros de inmersión (aunque en los casos que
veremos, sería inmediato convertir la postcondición en constante). En ese caso, la postcondición de
sumF, sería:
{RsumF ≡ s =(Σα∈{1..i}.a[α]) +(Σα∈{i+1..n}.a[α])}

Bien, puesta de esta manera, la hipótesis de inducción nos dice que


y' = s' = (Σα∈{1..i-1}.a[α]) +(Σα∈{i..n}.a[α]), cumple la postcondición.

Como estamos en finales, es cierto que y' = s' = y = s (¡la cadena ascendente es de sólo devoluciones!)
y es inmediato que (Σα∈{1..i-1}.a[α]) +(Σα∈{i..n}.a[α]) = (Σα∈{1..i}.a[α]) +(Σα∈{i+1..n}.a[α]).

ÍNDICE

Pag 81. Técnicas de inmersión


En el primer párrafo de 3.4 dice '... una técnica que puede solucionar el diseño consiste en definir una
función g, más general que f, con más parámetros o/y más resultados ...'.

Lo que puede sonar a que la especificación de la función inmersora contempla casos no contemplados
en la sumergida (admita tamaños mayores, entregue más resultados, contemple más excepciones... en
resumidas cuentas: que sea más polivalente). Esto puede ser cierto en algunos tipos de inmersiones,
pero en los problemas que veremos en los exámenes, no es exactamente así.

Por ejemplo, Supongamos que la especificación inicial de sum hubiera sido:

{Q≡ cierto}
fun sum_inicial (a:vector[1..n] de ent) dev (s:entero)=
{R ≡ s= Σα∈{1..n}.a[α]}

Define perfectamente qué se pide a la función, aunque no permite realizar una derivación recursiva (sí
una iterativa). Esta especificación sirve para una función que sume todos los elementos de un vector
de n elementos. Todos los elementos.
ÍNDICE

Aquí hemos usado directamente desde el principio una especificación inmersora:


{Q≡ 1 ≤ i ≤ n}
fun sum (a:vector[1..n] de ent; i: entero) dev (s:entero)=
{R ≡ s= Σα∈{1..i}.a[α]}

que además de permitirnos realizar una derivación recursiva, especifica la suma de cualquier
subintervalo [1..i] del vector (con 1 ≤ i ≤ n). Es entonces una especificación más general que la
inicial.

En cambio, la inmersión que realizamos para llegar de la no final sum a la final sumF restaura el uso
inicial:
{Q ≡ (1 ≤ i ≤ n) ∧(w = Σα∈{i+1..n}.a[α])}
fun sumF (a:vector; w, i: entero) dev (s:entero)=
{R ≡ s= Σα∈{1..n}.a[α]}

sólo sirve para sumar todo el vector; a pesar de haber realizado una 'generalización' introduciendo una
nueva variable inmersora acumuladora w. Eso sí, si ignoramos en la precondición el predicado que ha
de cumplir w, tendremos que sumF sí es más general, pues permite realizar la suma de todo el vector
más una constante 'acumulada' en la variable w si se llama a sumF con un valor de w diferente al

81
25905053.doc

neutro de la suma. En este supuesto, tenemos que sumF = sum + w, con lo que la primera es más
polivalente que la segunda.
ÍNDICE

Pag 85. Inmersión final


No confundir con desplegado y plegado. Aquí ahora de lo que se trata es pasar directamente de una
especificación no derivable (como la mencionada en el escolio anterior) a una que sí lo sea y que
conduzca directamente a una versión final (sin pasar por una intermedia no final, como en el caso del
desplegado y plegado).

La idea básica consiste partir de la especificación no derivable:


{Q≡ cierto}
fun sum_inicial (a:vector[1..n] de ent) dev (s:entero)=
{R ≡ s= Σα∈{1..n}.a[α]}

y sin tocar la postcondición, reforzar la precondición de modo que contemple qué han de hacer las
nuevas variables inmersoras a introducir. En vez de seguir un planteamiento formal como el de Peña,
si el fortalecimiento a realizar es simple, se puede seguir un planteamiento verbal que posteriormente
traduciremos a notación simbólica: 'quiero sumar todos los elementos del vector; necesitaré un índice
i para recorrerlo y como quiero que sea una versión final, necesitaré una variable acumuladora w
donde ir sumando los diferentes elementos del vector. Además, voy a recorrer el vector
descendentemente, por lo que w contendrá las sumas de lo ya recorrido –índices comprendidos entre
i+1 y n-'. Traduciendo todo esto, tenemos la especificación ya conocida:

{Q ≡ (1 ≤ i ≤ n) ∧(w = Σα∈{i+1..n}.a[α])}
fun sumF (a:vector; w, i: entero) dev (s:entero)=
{R ≡ s= Σα∈{1..n}.a[α]}

ÍNDICE

Pag 94. Desplegado y plegado


Además del ejemplo que veremos aquí, se puede encontrar un desarrollo muy parecido con una función un
poco más compleja en el apartado 2 del problema de ExP2_1997_1sem_C.

Según Peña 96, tercera línea, para poder aplicar este método es necesario que la función combinación,
c, tenga elemento neutro (puede bastar un elemento neutro 'lateral', sólo por la derecha o la izquierda)
y que cumpla asociativa. En el ejemplo veremos el porqué de esta necesidad.

Apliquemos la técnica a la función sum, (copiada aquí otra vez) para obtener la versión final sumF

{Q≡ 1 ≤ i ≤ n}
fun sum (a:vector[1..n] de ent; i: entero) dev (s:entero)=
caso i=1 → a[i]
[] i>1 → a[i]+sum(a, i-1)
fcaso
ffun
{R ≡ s= Σα∈{1..i}.a[α]}

ÍNDICE

Partimos del consecuente del caso no trivial de sum,(a[i] + sum(a, i-1)) y dibujamos su árbol
sintáctico.
si no se ve el árbol, picar en la barra de herramientas 'Ver', 'Diseño de página'

82
25905053.doc

a[i] sum

a i-1

En la raíz del árbol, figurará siempre la función combinación.

Aplicamos la heurística de Kodratoff. Para ello hay que pensar que para la nueva función inmersora
(en realidad también para sum) el valor variable a[i] se comporta, para una llamada determinada,
como una constante (es un valor a operar), por lo que la renombramos como variable w (variable a lo
largo de la ejecución, contante a operar en cada llamada). La variable índice i-1 seguimos
considerándola variable índice, pero renombrándola como i. El vector a, también será entrada para la
función inmersora y lo dejamos tal cual.

a[i] →w sum

a→ a i-1→ i

En resumidas cuentas, la idea es la siguiente. Del árbol original, no modificamos el nodo raíz (+) ni el
nodo de la función (sum) ni a ningún elemento que hubiera (aquí no los hay) en el camino directo
entre la raíz y el nodo de la función. Todos los demás nodos los renombramos como las variables a
usar en la nueva función inmersora: si antes era una variable 'operada' (como i-1) directamente
renombramos al nombre simple i; si era una expresión como a[i], renombramos por un nuevo nombre
de variable (la nueva variable a introducir w).

'Desdibujando' el árbol sintáctico, ya con las variables de inmersión, tenemos: w + sum(a,i).


Pues bien, a partir de aquí construiremos una versión final sumF que tendrá los mismos tres
parámetros y que con los correctos valores iniciales verificará: sumF(w,a,i) = w + sum(a,i).
ÍNDICE

Los valores iniciales correctos para que estas funciones se comporten igual, lógicamente han de ser
i=n y w=0. El primero es trivial. Queremos sumar todo el vector. El segundo, no tanto; pero veamos:
la nueva variable w se usará para llevar acumuladas sumas parciales. La función deberá comenzar a
trabajar con este acumulador con el neutro de la combinación, de lo contrario el resultado final estaría
falseado por el contenido inicial.
Por eso la necesidad de que la función combinación tenga elemento neutro (suma en este caso).
Si no pudiéramos realizar una primera llamada con el elemento neutro, ambas funciones, inmersora y
sumergida (sean cuales sean), no se comportarían igual ante los mismos datos de entrada.

83
25905053.doc

Bien, antes de seguir, un pequeño detalle estético. Por Kodratoff, hemos obtenido la igualdad
sumF(w,a,i) = w + sum(a,i), que es claramente antiestética. El nuevo parámetro de inmersión aparece
antes que el propio vector. Entonces si queremos, antes de hacer nada y si la función combinación es
conmutativa, podemos retocar el caso no trivial para que la llamada recursiva interna esté a la
izquierda de la función combinación. En nuestro caso sería: i>1 → sum(a, i-1) + a[i], que una vez
aplicado Kodratoff, daría la igualdad sumF(a, i, w) = sum(a, i) + w (*)

Con esta igualdad trabajaremos (para este ajuste no es necesario realizar Kodratoff otra vez, se puede
realizar directamente a partir de la igualdad inicialmente obtenida).
ÍNDICE

Desplegado de la función sum

sumF(a,i,w) = sum(a,i) + w

Decimos que desplegamos pues en la igualdad anterior, marcada como (*), procedemos a desplegar la
definición de sum; o sea, donde pone sum(a,i) escribimos la función completa:

sumF(a,i,w) = (fun sum (a:vector; i: entero) dev (s:entero)=


caso i = 1 → a[i]
[] i > 1 → sum(a, i-1) + a[i]) + w

Omitimos en el desplegado las partes de la función sum que no nos interesan, que lo único que
conseguirían sería complicar visualmente la transformación
ÍNDICE
El siguiente paso consiste en suprimir el paréntesis externo (color siena), aplicando distributiva de +
con respecto a los casos. Por evidente, no creo que sea necesario demostrar que esta transformación es
legal:

sumF(a,i,w) = fun sum (a:vector; i: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → (sum(a, i-1) + a[i]) + w

Pasamos ahora a reordenar el caso no trivial. No tenemos problema, pues + es asociativa: (a+b)+w ≡
a+(b+w). Por eso en Peña 96 se exige que función combinación cumpla asociativa. De lo contrario no
podríamos realizar esta ordenación, imprescindible para el posterior plegado

sumF(a,i,w) = fun sum (a:vector; i: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sum(a, i-1) + (a[i] + w)
ÍNDICE

A la expresión (a[i] + w), que es una nueva operación auxiliar actual, llamémosla w’ (**)

sumF(a,i,w) = fun sum (a:vector; i: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sum(a, i-1) + w'

Pero por lo que hemos visto en (*), resulta que lo subrayado equivale a sumF(a, i-1, w')
Podemos pasar por fin al plegado, donde lo que está expresado como sum más algo lo replegamos
expresándolo en función de sumF.

84
25905053.doc

Plegado
Añado un paso más, que a veces se omite, y que creo que permite entender mejor el proceso.

sumF(a,i,w) = fun sumF (a:vector; i, w: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sumF(a,i-1,w')

Y ya finalmente sólo nos falta sustituir w' por su expresión completa vista en (**):

sumF(a,i,w) = fun sumF (a:vector; i, w: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sumF(a, i-1, a[i]+w)

Resta ahora eliminar la redundante parte subrayada, completar las palabras reservadas omitidas y, lo
más importante, Q y R.
ÍNDICE

Postcondición
En el siguiente escolio veremos otro camino para escribir las pre y postcondición de una final
derivada a partir de una no final, pero ahora vayamos con el más ¿lógico?.

Para R no hay problema. Será constante, ya que no depende de las variables introducidas en las
sucesivas inmersiones. Lógico por otra parte, pues tal como vimos en los mapas de memoria del
escolio Pag 60. Introducción recursividad. Función sumF, recursiva lineal final el valor devuelto por
el caso trivial se va pasando a todas las llamadas anteriores, verificándose en todas ellas la misma
postcondición.

De la postcondición de sum podemos obtener directamente la de sumF sin más que sustituir i por n:

{Rsum ≡ s= Σα∈{1..i}.a[α]}
{RsumF ≡ s= Σα∈{1..n}.a[α]}

ÍNDICE

Precondición
Vamos a por Q, que será algo más compleja. Podemos usar tres métodos.

Primero.
Usando la cuenta de la vieja; porque Q de sumF será la misma que la de sum {Q ≡ 1 ≤ i ≤ n} y un
predicado conjuntado que exija que la nueva variable w lleva acumuladas las sumas ya realizadas.
Recordemos que recorremos el vector descendentemente desde i=n hasta i=1. Si vamos a entrar en la
llamada i de sumF es que vamos a sumar, a lo ya acumulado en w, el elemento a[i]; lo que significa
que w lleva acumuladas las sumas de los elementos a[i+1] hasta a[n]. Por tanto, el predicado que
buscamos es:
w = Σα∈{i+1..n}.a[α]. Predicado que deberemos añadir a lo ya conocemos:

{Q sumF ≡ (1 ≤ i ≤ n) ∧(w = Σα∈{i+1..n}.a[α])}


ÍNDICE

Segundo
procedimiento bastante más formal. Desdoblando R.

85
25905053.doc

{s= Σα∈{1..n}.a[α]} ≡ {s' = Σα∈{1..i}.a[α]}+{s'' = Σα∈{i+1..n}.a[α]}.


Aquí estamos buscando un predicado para incorporar a la precondición. Y por ésta sólo se pasa en la
cadena descendente de llamadas. Si vamos a entrar en la llamada i, cualquier condición sobre la
acumulación habrá de referirse al intervalo ya recorrido... {i+1..n}. Tenemos entonces que el
predicado subrayado especifica la condición 'llevar acumuladas las sumas realizadas'. Basta sólo
cambiar la s'' por el nombre que le hemos dado a ese acumulador: w.

El peligro de la aplicación mecánica de este desdoblamiento es la elección incorrecta de los


subintervalos. Por ejemplo, en este problema hubiera sido erróneo elegir: {1..i-1} y {i..n}.

Tercero
procedimiento, que básicamente es el anterior pero veo más claro y sobre todo minimiza el riesgo de
la elección de los subintervalos.

Conocemos ya la siguiente igualdad, vista en (*) sumF(a,i,w) = sum(a,i) + w

Y si esta igualdad es cierta, también será cierta la siguiente:


RsumF = (Rsum + w).

Desarrollando:
{R sumF ≡ s= Σα∈{1..n}.a[α]} = {(Rsum ≡ s= Σα∈{1..i}.a[α]) + w}

Jugando con los índices del primer predicado, podemos desdoblarlo en dos (omito ya las R):
{(s' = Σα∈{1..i}.a[α]) + (s'' = Σα∈{i+1..n}.a[α])} = {(s = Σα∈{1..i}.a[α]) + w}
ÍNDICE

El conjuntando subrayado del primer término de la igualdad es idéntico al primer conjuntando,


también subrayado, del segundo término. En esquema tenemos que a+b =a+c. Por tanto b=c. O lo que
es lo mismo:
(Σα∈{i+1..n}.a[α]) = w

Hemos conseguido expresar RsumF como la conjunción de lo que queda por comprobar y lo ya
comprobado. Esto último, en buena lógica, exigiremos que además se cumpla en la precondición:
{Q sumF ≡ (1 ≤ i ≤ n) ∧(w = Σα∈{i+1..n}.a[α])}

ÍNDICE

Recopilando todo lo averiguado:

{Q ≡ (1 ≤ i ≤ n) ∧(w = Σα∈{i+1..n}.a[α])}
sumF

fun sumF (a:vector; i, w: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sumF(a, i-1, a[i]+w)
fcaso
ffun
{R sumF ≡ s= Σα∈{1..n}.a[α]}

86
25905053.doc

Si ni en el desplegado y plegado ni en la derivación de Q y R hubo errores, esta función final es


correcta (porque lo era la no final de la que se derivó) y funcionará correctamente... además, es la que
hemos usado para la verificación de recursiva final.

Pero (siempre tiene que haber un pero) si posteriormente se va a realizar una transformación
automática a iterativo, puede ocurrir (y ocurrirá) un pequeño problema, que discutiremos al final del
escolio Pag 101. Figura 3.14. Transformación de recursiva final a iterativa
ÍNDICE

Al procedimiento descrito en Peña 95, se le denomina en CD 'heurística de Kodratoff', donde


heurística significa precisamente 'arte de inventar'. En nuestro caso, arte de inventar inmersiones
finales... aunque en los ejemplos que manejamos, más tiene de técnica que de arte.

Si tenemos la especificación de una función fun previa a inmersión por razones de diseño (esto es,
aquella que no se puede derivar por carecer de variable(s) como la que normalmente nombramos con
una i), su postcondición R será idéntica a la de la función doblemente sumergida iifun (la que
obtengamos por desplegado y plegado)

Tal como se discutió, pero sin desarrollar por completo, en el apartado 2 del problema de
ExP2_1997_1sem_C la técnica de desplegado y plegado necesita que la función combinación posea
elemento neutro (aunque sólo sea lateral), sólo para poder usar ese elemento neutro en la llamada
inicial a la función inmersora. Nada tiene que ver, con la supuesta necesidad de usar ese elemento
neutro en el caso trivial de la función inmersora, o que el caso trivial devuelva directamente
elemento neutro; ni mucho menos que el caso trivial sólo pueda devolver la variable de
inmersión. Ignoro el mecanismo psicológico que a más de uno nos llevó a sacar semejante
conclusión. Como acabamos de ver en el ejemplo de sumF, queda claro que en la devolución del caso
trivial puede ir perfectamente una expresión compleja: una combinación de Triv(x) de la no final con
el nuevo parámetro inmersor.

ÍNDICE

Apéndice a pag 94. Desplegado y plegado. Postcondición no constante


Una vez realizado el desplegado y plegado, tenemos otra manera de obtener la nueva especificación de
la inmersora (pre y postcondición): dejar la misma precondición que en la sumergida no final y hacer
la postcondición dependiente de los parámetros de inmersión; para ello bastará realizar la misma
manipulación algebraica que acabamos de ver, quedándonos con lo subrayado:

{R sumF ≡ s= Σα∈{1..n}.a[α]} = {RsumF ≡ (Σα∈{1..i}.a[α]) + (w = Σα∈{i+1..n}.a[α])}

¡Pero si esto es lo mismo que antes!. Sí, pero ahora no pasamos uno de los sumandos a la
precondición. Lo dejamos tal cual en la postcondición, donde de manera implícita sí se establece la
precondición para la llamada siguiente.

Incluso, si interesa (o porque se vea mejor), simplemente escribimos

{R sumF ≡ (Σα∈{1..i}.a[α]) + w}

Claro que de esta manera no se está haciendo restricción alguna sobre el valor inicial (el de la llamada
externa), por lo que se podrían hacer llamadas con un determinado valor de w inicial, de modo que el
resultado devuelto por la función será la suma de todo el vector más w.

87
25905053.doc

Personalmente, prefiero con mucho la postcondición constante. La postcondición no constante


entiendo que en el fondo no es más que una constante enmascarada; basta realizar una simple
operación algebraica o lógica (dependerá de cual sea la función combinación usada en la no final) para
hacerla constante. De todos modos, es posible que alguna vez nos encontremos con una post no
constante, como el siguiente ejemplo, modificado a partir de uno del profesor Artacho:
{Q ≡ (1 ≤ i ≤ n)}
sumF

fun sumF (a:vector; i, w: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sumF(a, i-1, a[i]+w)
fcaso
ffun
{R sumF ≡ s= (Σα∈{1..i}.a[α]) + w}

ÍNDICE

Pag 101. Figura 3.14. Transformación de recursiva final a iterativa .


Como aquí se usan conceptos estudiados en los posteriores epígrafes 4.1 a 4.3, puede ser interesante
estudiar estos primero, antes de meterse con la transformación a iterativo. No tiene mucho sentido
hablar aquí de invariantes, sin entender correctamente qué son (en resumidas cuentas, un invariante no
es más que la precondición del bucle).
ÍNDICE

Pretender memorizar en primera instancia esta tabla sería una locura..., aunque siempre queda el
recurso a la chuleta. Pero realizar esta transformación es bastante más sencillo y mecánico de lo que
parece y una vez dominada y comprendida, es muy útil memorizarla (no como foto fija, sino como
traducción simbólica de la cantinela memorizada). O posiblemente no haga falta memorizarla: la
transformación sale sola. En un algoritmo recursivo final, cada vez que entramos en caso no trivial se
produce una 'iteración virtual': llamamos a la misma función con parte de los argumentos (los de
inmersión) modificados por una 'asignación virtual' y la salida del caso trivial es ya la solución
completa a nuestro problema. Si pudiésemos leer directamente dicha salida del caso trivial, no
necesitaríamos esperar a que se complete la cadena ascendente de devoluciones.

Resumiendo y para entendernos: si el consecuente del caso no trivial es sumF(a, i-1, a[i]+w),
podemos escribir el barbarismo (¡sólo como imagen gráfica!) sumF(a, i := i-1, w := a[i]+w),

Transcribo la figura 3.14

{Q(x)} {Q(xini)}
fun f(x:T1) dev (y:T2) fun f(xini:T1) dev (y:T2)
= caso Bt (x) → triv(x) var x:T1 fvar
[] Bnt(x) → f(s(x)) x := xini
fcaso {P(x,xini)}
ffun mientras Bnt(x) hacer
{R(x,y)} x := s(x)
fmientras
dev triv(x)
ffun
{R(xini, y)}
ÍNDICE

Y esta es la transformación completamente automática de sumF en su versión iterativa sumIt

{Q≡ (1 ≤ i ≤ n)∧(w= Σα∈{i+1..n}.a[α])} {Q≡ (1 ≤ iini ≤ n)∧(w= Σα∈{iini+1..n}.a[α])}

88
25905053.doc

fun sumF (a:vec; i, w: ent) dev (s:ent) fun sumIt (aini:vec; iini, wini: ent) dev (s:ent)
caso i = 1 → a[i] + w var a:vec; i, w: ent fvar
[] i > 1 → sumF(a, i-1, a[i]+w) < a, i, w> ← < aini , iini , wini>
fcaso {P≡ (1 ≤ i ≤ n)∧(w= Σα∈{i+1..n}.a[α])}
ffun mientras i > 1 hacer
{R ≡ s= Σα∈{1..n}.a[α]} < i, w> ← < i-1, a[i]+w >
fmientras
dev a[1] + w
ffun
{R ≡ s= Σα∈{1..n}.a[α]}

Nota: iini = n, wini = 0

Analicemos la transformación comentando línea por línea el algoritmo iterativo (parte derecha de la
figura). En lo que sigue, llamaremos iterativo directo al algoritmo iterativo que obtendríamos por
derivación directa a partir de la especificación, no por transformación a partir de recursiva:
ÍNDICE
{Q(xini)}
Si la función recursiva final está especificada como fun f(x:T1) dev (y:T2), cuando invocamos a la
función f en la primera llamada lo hacemos como: f(xini). Este xini es el argumento inicial que hace
que la función recursiva f(x:T1) funcione correctamente, definiendo el tamaño original del
problema; es por tanto el mismo argumento que necesitamos para la versión iterativa. Claro que
en la especificación recursiva no podemos escribir xini, pues necesitamos dejar abierta la
posibilidad de que la función se llame a si misma pero con argumentos que reduzcan el tamaño
del problema.
{Q≡ (1 ≤ iini ≤ n)∧(w = Σα∈{iini+1..n}.a[α])} que una vez sustituidas las variables por los
valores iniciales, nos queda: {Q≡ (1 ≤ n ≤ n)∧(0 = Σα∈{n+1..n}.a[α])} que constituye una
tautología: La precondición de la versión iterativa queda reducida a cierto. Lógico por otra parte.
En la precondición de las recursivas aparecen sólo variables, usadas en la validación de las
llamadas recursivas. Llamadas que en la versión iterativa no existen.

fun f(xini : T1) dev (y: T2)


La especificación es idéntica a la recursiva, con la diferencia de xini, por las razones ya
comentadas. Eso sí, surge aquí una pequeña diferencia entre la iterativa por transformación de
recursiva y la iterativa directa. Todos los parámetros iniciales que en la recursiva son variables de
inmersión, en la versión iterativa directa, no aparecerán en la especificación de fun y las
declararemos directamente como variables locales en var inicializándolas a los valores correctos
en x := xini .
fun sumIt (aini:vec; iini, wini: ent) dev (s:ent) ≡ fun sumIt (aini:vec; n, 0) dev (s:ent)
que en la versión directa sería:
fun sumIt (a:vec) dev (s:ent) habiendo desaparecido incluso el subíndice del vector. En realidad
a esta variable nunca sería necesario ponerle tal subíndice. Lo hacemos simplemente por
generalidad: para hacer la transformación, trabajaremos de manera completamente automática
ÍNDICE

var x:T1 fvar


Los algoritmos iterativos necesitan una declaración de variables locales. En las recursivas dichas
variables ya van declaradas en la propia declaración de función.
var a:vec; i, w: ent fvar. Directamente y a lo bruto: declaramos tantas variables, con los mismos
nombres (sin el subíndice ini), tipos y orden como las que aparecen en la declaración de

89
25905053.doc

argumentos de la función. Como no precisaremos manipular la variable de devolución (s en


nuestro ejemplo), no declaramos una variable local para ella.

x := xini
< a, i, w> ← < aini , iini , wini> aquí por fin realizamos las inicializaciones previas al bucle. En
cuanto a la variable a tipo vector, ocurre lo mismo que antes: no es necesario inicializarla (en la
iterativa directa no lo haríamos). Vuelvo a ponerla sólo por automática generalidad.
Resumiendo el proceso de la inicialización: Al transformar de final a iterativo, no nos
planteamos cuáles han de ser las inicializaciones correctas; simplemente las 'importamos'
indirectamente a través de la declaración de variables de la función, que reciben los valores
iniciales de la versión recursiva.
La notación < a, i, w> ← < aini , iini , wini> es una asignación múltiple, equivalente a
< a, i, w> := < aini , iini , wini>. Por lo de pronto podemos pensar en ella como una manera
abreviada de escribir:
a ← aini
i ← iini
w ← wini

ÍNDICE

{P(x,xini)}
Escribir el invariante.
Puede resultar confuso el segundo elemento del par ordenado (tupla de salida) xini. Pero el
predicado {P(x,xini)} podemos leerlo muy libremente como 'escribir el invariante, que ha de ser
tal que sea cierto con todas las entradas (que lleguen al bucle) y en particular con los valores de
inicialización'.
Normalmente este invariante será inmediato: directamente la precondición de la versión recursiva
final.
{P≡ (1 ≤ i ≤ n)∧(w = Σα∈{i+1..n}.a[α])} que ya hemos visto que es tautológico para los
valores de inicialización: {P≡ (1 ≤ n ≤ n)∧(0 = Σα∈{n+1..n}.a[α])}
En cualquier caso, si hay dudas y/o ganas, se puede derivar P a partir de R (hay varios ejemplos
en ExPeDos).
ÍNDICE

mientras Bnt(x) hacer.


mientras i > 1 hacer Si en la recursiva la razón para 'iterar' diferentes llamadas internas es que
no se ha alcanzado el caso trivial, lógico que en la versión iterativa ese sea el mismo criterio para
iterar de verdad

x := s(x)
Cuidado con s(x). Si hemos entrado en caso no trivial por una llamada a la que se le pasaron
como argumentos (a, i, w), en la llamada recursiva interna que se genera, los parámetros que se
pasan son (a, i-1, a[i]+w). Por tanto s(x) no es sólo i-1, es toda la lista de argumentos de la
llamada recursiva interna.
< a, i, w> ← <a, i-1, a[i]+w> otra vez aquí incluimos sólo por generalidad el vector a, pues en la
versión iterativa directa, este argumento es una variable global, no local. Para simplificar un
poco la notación, en lo que resta, no incluyo esta asignación del vector de entrada.

El resto de la figura 3.14 es trivial y no lo discutimos.

IMPORTANTE

90
25905053.doc

En el último punto que acabamos de ver, surge un problema grave ya anunciado en anteriores
escolios. No podemos considerar la notación < i, w> ← < i-1, a[i]+w> usada en Peña para la
asignación múltiple como una manera abreviada de escribir:
i := i-1
w := a[i]+w

porque veamos que ocurriría en la primera iteración con i=n y w=0:


i := n-1
w := a[n-1]+w
ÍNDICE
A pesar de haber pasado limpiamente las condiciones del invariante, en la primera iteración del bucle
¡queda sin sumar el elemento del vector de posición n, que sí debería sumarse!.

Entonces, la notación <i, w> ← < i-1, a[i]+w> ha de considerarse como un todo, una asignación en
bloque; no como una secuencial; esto es: i toma el valor i-1 y, en paralelo, w toma a[i] +w con el
valor de i, no con i-1: se suma la posición i, no la i-1.

Creo entonces que esta notación es muy peligrosa por dos razones: por lo visto en ProgI y ETCI los
compiladores no funcionan así, y, lo más importante, si la usamos con frecuencia, es posible que a la
hora de implementar la versión iterativa, no nos demos cuenta y convirtamos la asignación múltiple
<i,w> ← < i-1, a[i]+w> en una secuencia de asignaciones, con lo que el programa nos quedaría:

fun sumIt (aini:vec; iini = n, wini = 0) dev (s:ent)


var a:vec; i, w: ent fvar
i := n
w := 0
mientras i > 1 hacer
i := i-1
w := a[i]+w
fmientras
dev a[1] + w
ffun
ÍNDICE

claramente erróneo, pues en lo destacado en negrilla tenemos la instrucción avanzar antes que la
instrucción restablecer (sobre estas dos instrucciones, se discutirá en los escolios Pag 121. Definición
4.1. Invariante y Pag 132. Derivación de bucles a partir de invariantes )
ÍNDICE

Para evitar este problema a la hora de la implementación de la versión iterativa, podemos optar por
tres soluciones diferentes:

1) obvia: tener mucho cuidado y la asignación múltiple <i,w> ← < i-1, a[i]+w>, la convertimos en
una secuencia de asignaciones colocadas en orden correcto dentro del bucle:

mientras i > 1 hacer


w := a[i]+w
i := i-1
fmientras
ÍNDICE
De esta manera, se sigue verificando el invariante y recorremos todo el vector (excepto el
elemento a[1], que en este ejemplo se suma fuera del bucle)

91
25905053.doc

2) Auténtica chapuza: modificar el valor inicial de i: iini = n+1. No sólo es chapuza: huele mal:

fun sumIt (aini:vec; iini = n+1, wini = 0) dev (s:ent)


var a:vec; i, w: ent fvar
i := n+1
w := 0
mientras i > 1 hacer
i := i-1
w := a[i]+w
fmientras
dev a[1] + w
ffun

3) óptima: procurar en la fase de desplegado y plegado hacia la función recursiva final, introducir
todas las nuevas variables inmersoras que actúan como acumuladores ANTES de las variables
que determinan el tamaño del problema (normalmente i). Esto nos permite, ahora sí, trabajar
mecánicamente y desentendernos por completo de estos ajustes que tan fácil resulta olvidar y que
conducen a una implementación equivocada.

Veamos con un poco más de detalle la última solución:


En el escolio correspondiente, la última fase del desplegado para derivar sumF era:

sumF(a,i,w) = fun sum (a:vector; i: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sum(a, i-1) + w'
ÍNDICE

Y la primera del plegado:

sumF(a,i,w) = fun sumF (a:vector; i, w: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sumF(a,i-1,w')

Pues bien, en el plegado, introduzcamos la variable inmersora antes de la variable que se usará en la
instrucción avanzar:

sumF(a,i,w) = fun sumF (a:vector; w, i: entero) dev (s:entero)=


caso i = 1 → a[i] + w
[] i > 1 → sumF(a,w',i-1)

Y completando el plegado, nos queda:


sumF(a,i,w) = fun sumF (a:vector; w, i: entero) dev (s:entero)=
caso i = 1 → a[i] + w
[] i > 1 → sumF(a, a[i]+w, i-1)

ÍNDICE
De esta manera, nos queda la versión final:
{Q sumF ≡ (1 ≤ i ≤ n) ∧(w = Σα∈{i+1..n}.a[α])}

fun sumF (a:vector; w, i: entero) dev (s:entero)=


caso i = 1 → a[i] + w.
[] i > 1 → sumF(a, a[i]+w, i-1)

92
25905053.doc

fcaso
ffun
{RsumF ≡ s= Σα∈{1..n}.a[α]}

Que podemos convertirla directamente en iterativa, despreocupándonos por completo del orden de las
instrucciones restablecer y avanzar. Aparecerán en la asignación múltiple de tal manera que podemos
traducirla como si fuera equivalente a una secuencia de asignaciones: (a la izquierda, la
transformación usando asignación múltiple, y a la derecha, traduciendo ésta en una secuencia de
asignaciones en el mismo orden):

{Q≡ (1 ≤ iini ≤ n)∧(w= Σα∈{iini+1..n}.a[α])} {Q≡ (1 ≤ iini ≤ n)∧(w= Σα∈{iini+1..n}.a[α])}


fun sumIt (aini:vec; wini, iini: ent) dev (s:ent) fun sumIt (aini:vec; wini, iini: ent) dev (s:ent)
var a:vec; w, i: ent fvar var a:vec; w, i: ent fvar
<w, i> := < wini , iini> w := wini
i := iini
{P≡ (1 ≤ i ≤ n)∧(w= Σα∈{i+1..n}.a[α])} {P≡ (1 ≤ i ≤ n)∧(w= Σα∈{i+1..n}.a[α])}
mientras i > 1 hacer mientras i > 1 hacer
<w, i> ← < a[i]+w, i-1> w := a[i]+w
i := i-1
fmientras fmientras
dev a[i] + w dev a[i] + w
ffun ffun
{R ≡ s= Σα∈{1..n}.a[α]} {R ≡ s= Σα∈{1..n}.a[α]}
ÍNDICE
Para evitar errores y poder realizar la transformación a iterativo de manera completamente automática,
dejemos pues los retoques manuales para esta fase de desplegado y plegado. Por algo se la califica de
heurística.
De todos modos, y por si las moscas, nunca estará de más comprobar rápida e informalmente el
comportamiento del bucle para datos extremos del dominio.

ÍNDICE

Pag 101. Expresión 3.19


Para evitar errores de interpretación, es preferible parentizar esta expresión del invariante:
P(x,xini) ≡ Q(x) ∧(f(xini) = f(x))

Pag 102. Figura 3.15. Transformación de recursiva no final a iterativa.


Respecto a la memoria, mismo comentario que en la transformación a partir de final. Mejor
comprender el proceso que memorizarlo.

Veamos cual es la mecánica. En las recursivas no finales, existe una primera cadena descendente de
llamadas, que lo único operativo que hace es buscar la devolución del caso trivial. Una vez alcanzada
esta devolución, se inicia la cadena ascendente de combinaciones de valores devueltos y operaciones
auxiliares actuales. Entonces la idea para la transformación a iterativo de una no final es la siguiente:

1. A partir de la precondición y de los valores iniciales de la llamada externa de la recursiva,


declarar variables locales e inicializarlas

2. Establecer un primer bucle de descenso, que nos lleve del valor iniciales a aquel que conduce al
caso trivial. Por tanto la protección de este bucle será la misma que la del caso no trivial.

93
25905053.doc

3. Inicializar la variable de devolución con el valor devuelto por el caso trivial recursivo.

4. Establecer un segundo bucle, que hará funciones de cadena ascendente de combinaciones. Dentro
de él, la instrucción avanzar será la inversa de la usada en el anterior bucle. La instrucción
restablecer realizará la combinación que se enuncia en el caso no trivial de la versión recursiva.
Como en este bucle recorremos la estructura de datos al revés, las instrucciones restablecer y
avanzar estarán en orden inverso al habitual.

5. A la salida del segundo bucle, devolver la variable acumuladora


ÍNDICE
Una vez entendida la transformación desde final, ésta no plantea mayor problema. Sólo destacar dos
cosas:
Por el paso 3 ahora sí será necesario declarar una variable local que haga de acumulador, cuyo valor
será el que finalmente se devuelva en 5.

La existencia en el segundo bucle de la función inversa de la función sucesora (s-1(x)), obliga a que
esta última tenga función inversa. Si no es así, es necesario implementar la función sucesora s(x)
como una función que coloca elementos en la cima de una pila, y la función s-1(x) como una
función que los retira, como en la figura 3.16 del libro. En nuestro ejemplo, no habrá problema: si
s(x) = i-1, entonces s-1(x)= i+1
ÍNDICE

Transcribo la figura 3.15

{Q(x)} {Q(xini)}
fun f(x:T1) dev (y:T2) fun f(xini:T1) dev (y:T2)
= caso Bt (x) → triv(x) var x:T1 fvar
[] Bnt(x) → c(f(s(x)),x) x := xini
fcaso {P1(x,xini)}
ffun mientras Bnt(x) hacer
{R(x,y)} x := s(x)
fmientras
y := triv(x)
{P2(x,xini,y)}
mientras x ≠ xini hacer
x := s-1(x)
y := c(y,x)
fmientras
dev y
ffun
{R(xini, y)}

y ahora, la transformación a iterativo de la recursiva no final sum (hago una transformación no tan
mecánica como la del ejemplo a partir de final, eliminando ya alguna cosilla redundante)
ÍNDICE
Salto de página.../...

94
25905053.doc

{Q≡ 1 ≤ i ≤ n} {Q≡ cierto}


fun sum (a:vec; i: ent) dev (s:ent)= fun sumIt2 (a:vec; iini: ent) dev (s:ent)=
caso i=1 → a[i] var i, S: ent fvar
[] i>1 → a[i]+sum(a, i-1) i:= iini (* iini = n *)
fcaso {P1 ≡ i ≥ 1} (* ver nota *)
ffun mientras i>1 hacer
{R ≡ s= Σα∈{1..i}.a[α]} i:= i-1
fmientras
S := a[i] (* i = 1*)
{P2 ≡ S = Σα∈{1..i}.a[α]}
mientras i ≠ iini hacer (* iini = n *)
i := i+1
S := S + a[i]
fmientras
dev S
ffun
{R ≡ s= Σα∈{1..n}.a[α]}

ÍNDICE
Nota: P1 ≡ i ≥ 1. Veamos por qué. En este bucle sólo se modifica la variable i, siendo la protección
del bucle ≡ i > 1, y estos estados también han de verificar el invariante; invariante que también ha de
verificar aquel estado concreto que no verifica la protección del bucle: i=1.

Una vez realizada la anterior transformación de manera automática (pensemos que es tarea
encomendable al compilador), se puede realizar alguna mejora manual:

Eliminar la variable índice de la lista de argumentos de la función, e inicializarla directamente al


tamaño del vector (esta mejora también se puede realizar en las transformadas a partir de final)
ÍNDICE

Eliminar el bucle de descenso, inicializando la variable índice no al tamaño del vector, sino al
primer elemento del vector, a aquel que en la recursiva lleva a caso trivial (si en la recursiva el caso
trivial está establecido como una salida de rango, sería, por ejemplo i:=0. En nuestro ejemplo i:=1).
Esta modificación sólo se podrá hacer si el bucle de descenso es sencillo (manipula pocas
variables).

Esta última modificación es la justificación de la pregunta 10 de ExP2_1998_2sem_C, que tiene por


respuesta correcta la opción D.
10.- Cuando se realiza la transformación directa de una función recursiva no final a un programa
iterativo:
A. No es posible realizar dicha transformación directamente.
B. En ocasiones podemos prescindir del bucle de ascenso de las llamadas recursivas.
C. Siempre obtenemos un único bucle.
D. En ocasiones podemos prescindir del bucle de descenso hacia el caso trivial.

Nota final sobre los diferentes ejemplos usados


A proposito, en todas las versiones que escribí de la función sum, usé como protección del caso trivial
la comparación i=1, lo que obligaba a que la devolución de dicho caso trivial incluyera el valor del
elemento a[1], decidiendo no usar como protección del caso trivial la mucho más elegante y cómoda
comprobación de dominio vacío (o salida de rango del índice), que en los ejemplos propuestos
hubiera sido i=0 y que tendría como salida del caso trivial el neutro de la función combinación (en las

95
25905053.doc

no finales) o simplemente la variable inmersora acumuladora (en las finales). Además, esta alternativa
elegante produce un código un poco más cómodo de derivar, leer, verificar y modificar a iterativo.

Seguí aquel criterio, primero porque me parece más simple en una introducción a la recursividad y
segundo porque en los ExPeDos figuran varios ejemplos en los que usé el criterio de dominio vacío.

ÍNDICE

Salto de página.../...

96
25905053.doc

ÍNDICE

TEMA 4. Diseño iterativo


Pag 112 y siguientes. Derivación de iterativas
Como ejemplo de derivación completa, el problema de ExP2_1997_2sem_D

Resumen:
Para derivar directamente algoritmos iterativos, lo haremos siempre desde R.
Entre cada dos instrucciones de asignación, siempre se puede escribir un predicado que defina los
estados del algoritmo en ese punto. A ese predicado le llamaremos aserto, que se convierte en la
postcondición de la instrucción de asignación anterior y la precondición de la siguiente.

Para indicar que de unos predicados dados E1…En se infieren o deducen otros predicados E’1…E’m ,
usaremos la siguiente notación de lógica matemática, donde la línea horizontal se lee ‘de lo anterior,
se infiere lo siguiente’:
E1…Em
E’1…E’m
En resumidas cuentas, de unos estados dados se infieren otros. Estudiar por Peña 113 a 120 la
semántica de las diferentes instrucciones, en particular la de asignación (pag 115), que se lee:
Partiendo de cualquier estado, se infiere que la sustitución literal en una postcondición de una
variable x por una expresión E hace que x tome el valor expresado por R tras la ejecución de E. Por
su parte, la nueva precondición de la instrucción de asignación será la conjunción del dominio de
los estados de E, Dom(E) y la antigua postcondición con la sustitución realizada.

Resumiendo todavía más: esto es lo que nos permite realizar problemas como el visto en el escolio
sobre la Pag 45. Definición 2.14. Sustitución textual .
ÍNDICE

Pag 121. Definición 4.1. Invariante


Sobre esta definición o fragmentos de ella, se han realizado diversas preguntas en diferentes
exámenes. Ojo que la frase ‘que describe todos los estados por los que atraviesa el cómputo realizado
por el bucle’, sacada fuera de contexto llama a engaño. En absoluto se refiere a los estados dentro del
bucle.

Cuando aparece en Peña, CD o exámenes 'ejecución del bucle', supongo que se refieren a su ejecución
completa hasta la condición de terminación (¬B) y no a la ejecución de una sola vuelta (iteración).

* * * * * * *

Veamos sobre la marcha si sale un buen ejemplo. Esto no es más que un primer borrador; es posible
que algunas cosas no queden muy claras.

Supongamos que tenemos el siguiente algoritmo iterativo que suma los elementos de un vector.

ÍNDICE

{Q ≡ cierto}

97
25905053.doc

fun sum_it(a:vector[1..n] de ent) dev s:ent


var i: ent; fvar
i := 0
s := 0
{P ≡ s= Σ a[α]. α∈{1..i}} (*lo veremos con más detalle más tarde*)
mientras i < n hacer
s := s + a[i+1]
i := i +1
fmientras
dev s
ffun
{R ≡ s= Σ a[α]. α∈{1..n}}

Antes de seguir, veamos unas cuantas cosas sueltas.

Por simplicidad, uso directamente la variable de devolución s para operar con ella como acumulador
de las sumas parciales. Una estrategia más elegante obligaría a usar s como variable local,
renombrando la variable de devolución, por ejemplo, como S, y a realizar un par de pequeñas
modificaciones más:

fun sum_it(a:vector[1..n] de ent) dev S:ent


var i, s: ent; fvar
i := 0
s := 0
mientras i < n hacer
s := s + a[i+1]
i := i +1
fmientras
S := s
dev S
ffun

Estos algoritmos lo acabo de escribir directamente (aunque en lo que sigue usaré el primero), sin
realizar una derivación formal a partir de R, pero procurando que quedara con las instrucciones tal
como las obtendríamos si hubiéramos seguido ese procedimiento. Lo más curioso, tal vez, es que se
inicializa el índice i a 0, siguiendo una estrategia que a la larga es muy útil: usar valores iniciales que
hagan vacío el dominio de definición, con lo que automáticamente el invariante (o la precondición si
hablamos de una recursiva final) se hace cierto y permite realizar una derivación formal más cómoda.
Esto obliga a que la instrucción fundamental del bucle que acumula los resultados parciales, sume el
elemento de posición i+1 en vez del más cómodo i, porque si fuera de esta manera sumaríamos en la
primera iteración el elemento inexistente (por tanto error) a[0].
ÍNDICE
En la expresión del invariante escribí: s=Σ a[α]. α∈{1..i}, que se lee 'sumatorio de todos los elementos
alfa, tales que alfa esté comprendido entre 1 e i', manera que me parece más elegante y expresiva que
la absolutamente equivalente pero más difícil de leer (y que parece preferir el equipo docente) s=
Σα∈{1..i}. a[α].
ÍNDICE
Bien, veamos cuál es la dinámica de las diferentes iteraciones cuando usamos esta función sum_it para
sumar los elementos del siguiente vector: a ≡ [8, 6, 7, 9]. Aprovecharemos la tabla para ver qué es el
invariante y alguna cosita más.

Antes de entrar con la tabla, recordar que el proceso es el siguiente: se comprueba la protección del
bucle. Si es cierto, se entra en él y se realiza una iteración completa. Una vez terminada se vuelve

98
25905053.doc

necesariamente a comprobar la protección (esto es fundamental). Si la protección se cumple, se


vuelve a entrar, en caso contrario, se salta directamente a la instrucción inmediatamente siguiente al
bucle; en nuestro ejemplo: S := s. (Seguro que suena a gilipollada, pero procuro siempre realizar estos
preámbulos; más de una vez metí el zueco por olvidar trivialidades palmarias :-(

Valores y comprobaciones antes de entrar iteraciones del bucle fin de


en bucle iteración
variab invariante B restablecer avanzar
l
i s s=Σ a[α ].α∈{1..i} i<4 s:= s+a[i+1] i := i+1 R P ∧R
0 0 (0=Σa[α].α∈{1..0}) ≡ V V s:= 0+8 = 8 i := 0+1= 1 F F
1 8 (8=Σa[α].α∈{1..1}) ≡ V V s:= 8+6 = 14 i := 1+1= 2 F F
2 14 (14=Σa[α].α∈{1..2})≡ V V s:= 14+7 = 21 i := 2+1= 3 F F
3 21 (21=Σa[α].α∈{1..3})≡ V V s:= 21+9 =30 i := 3+1= 4 V V
4 30 (30=Σa[α].α∈{1..4})≡ V F No se entra en bucle V V

Tabla evolución de un bucle y su invariante

ÍNDICE DE FIGURAS

Bien, si estudiamos la expresión del invariante, vemos que en él aparecen las dos variables que sufren
modificaciones dentro del bucle. Pero esta expresión es tal que refleja perfectamente cuál es la
relación que han de cumplir entre sí estas variables antes de comprobar la protección del bucle (o
bien al salir de él, que es absolutamente equivalente). Por eso destaqué en gris dos verdadero de las
últimas columnas. Porque en ese momento es cierto lo que ahí se enuncia, pero hemos de esperar a
comprobar la protección del bucle para ver si seguimos hacia R.

En nuestro caso lo que el invariante dice es: que antes o después (pero no durante) la ejecución de
una iteración cualquiera, la variable s ha de llevar acumuladas las sumas parciales de la parte ya
recorrida del vector; recorrido que viene determinado por la variable i.

Creo que enunciado de esta manera, queda bastante claro qué es el invariante… a fin de cuentas no es
otra cosa que el equivalente (en realidad ¡idéntico!) al predicado que añadimos en la precondición de
las finales para especificar qué condición ha de cumplir la nueva variable inmersora… que
normalmente no será otra cosa que una vulgar variable acumuladora (escondida como argumento de la
función, pero variable a fin de cuentas)
ÍNDICE
Importa destacar una cosa ya dicha: en el invariante han de aparecer forzosamente las variables que
sufrirán asignaciones dentro del bucle, pues de lo contrario P no conseguiría expresar correctamente
las relaciones entre esas variables que deben mantenerse a lo largo de todas las iteraciones.

Otro detalle, éste realmente importante y fuente de muchas confusiones. Las instrucciones restablecer
y avanzar, reciben este nombre por el orden en que las derivamos (ver un ejemplo de derivación en el
problema de ExP2_1997_2sem_D):

Primero comenzamos especificando R. A partir de ahí, por debilitamiento y teniendo en cuanta la


protección que queramos establecer derivamos P. En ese momento, en esquema, tenemos lo siguiente
(en negrilla lo que conocemos)

{P}
mientras B hacer
restablecer

99
25905053.doc

{S} (*aserto entre las instrucciones restablecer y avanzar *)


avanzar
fmientras
{P}
*****
***** (*posibles instrucciones entre el bucle y la postcondición*)
{R}

ÍNDICE
Como {P} se verifica también justo al terminar cada iteración, directamente derivamos de ahí la
instrucción avanzar. ¿Pero que ocurre así?. Que esta instrucción avanzar rompe la invarianza,
estableciendo la existencia de un aserto {S} que se convierte en la precondición de avanzar. Para
averiguar este aserto, procedemos a realizar una sustitución textual sobre {P}. Bien, una vez
averiguado {S}, este aserto, además de ser precondición de avanzar, será también postcondición de
restablecer. Lo que hemos de hacer ahora es derivar una instrucción restablecer tal que permita
restablecer la invarianza.

Vemos así que los nombres de estas instrucciones describen muy bien qué hacen en el orden que las
derivamos, recorriendo el bucle al revés. Pero claro, ocurre que en el orden natural de ejecución, se
comportan justamente al revés.

Aunque se puede ver también en la tabla de más arriba, veámoslo con nuestra función con una
ejecución desde el principio, con el vector ya conocido a ≡ [8, 6, 7, 9]. El fragmento de código que
nos interesa de la función sum_it es:

i := 0
s := 0
{P ≡ s= Σ a[α]. α∈{1..i}}
mientras i < n hacer
s := s + a[i+1]
i := i +1

ÍNDICE

Comenzamos la ejecución:
i = s = 0.
Comprobamos P: 0= Σ a[α]. α∈{1..0} Cierto, al ser vacío el dominio.
Comprobamos protección: 0 < 4. Cierto, entramos en bucle.
Ejecutamos restablecer: s := 0 + 8
¿Se verifica el invariante?: 8= Σ a[α]. α∈{1..0}. FALSO, pues 8≠ 0
Ejecutamos avanzar: i := 0 +1
fin de iteración
Comprobamos P: 8= Σ a[α]. α∈{1..1} Cierto, 8=8.
Comprobamos protección…. etc, etc

Mucho, mucho cuidado pues. Con este orden de derivación (son posibles otros que no producen
confusión), los nombres restablecer y avanzar son sumamente traidores. Describen qué hacen en
el orden de la derivación. En el de ejecución, quien rompe la invarianza es restablecer y quien la
restablece es avanzar
ÍNDICE

Pag 125. Figura 4.1. Esquema de programa iterativo

100
25905053.doc

{Q}
Inic;
mientras B hacer {P}
S
fmientras
{R}

B es la protección del bucle. Cumple la función equivalente a la protección Bnt en recursivas.

{P} es el invariante. Se admite por convenio escribirlo ahí; pero no es un lugar lógico. Puesto así,
puede parecer que lo que se pide al invariante es que se cumpla sólo si se cumple B, cosa que no
es cierta. El invariante se ha de cumplir siempre inmediatamente antes de la comprobación de la
protección del bucle, incluido si es cierto ¬ B. Al terminar la última iteración, la ejecución del
algoritmo comprobará si se cumple B. En ese momento, por supuesto, se sigue cumpliendo P a
la vez que se cumple ¬B, por lo que no se entra en el bucle y salta la ejecución a inmediatamente
después de fmientras. Como desde aquí hasta R no hay ninguna instrucción de asignación, se
tiene que R ≡ P ∧¬B.
Al realizar diferentes ejercicios, nos encontramos que entre el final del bucle y R, tenemos una
instrucción del tipo dev y; pero ésta no es una instrucción de asignación que realice ningún
cambio en los estados de P, por lo que a estos efectos, la ignoramos.

Si al final del bucle (pasado el fmientras) sí hubiera una instrucción de asignación, no hay
problema; mediante sustituciones textuales, a partir de R derivaríamos el aserto anterior a dicha
asignación. Aserto que sería P. Si hubiera más de una asignación, se repetiría el proceso de
sustituciones textuales hasta llegar al aserto inmediatamente posterior al bucle (anterior a la
primera asignación posterior al bucle).

S secuencia de instrucciones. Entre cada dos de ellas se puede derivar el aserto correspondiente. Si
una (o más) de estas instrucciones (s’) es condicional o iterativa, a efectos de bucle externo,
considerarla como una instrucción simple, definida por los asertos que la flanquean por arriba
{Q’} y abajo {R’}. Para derivar correctamente la instrucción compleja s’, considerarla como el
fragmento {Q’}s’{R’}.
ÍNDICE

Pag 126. Figura 4.2. Verificación de funciones iterativas


En los diseños recursivos, primero realizamos la derivación completa y posteriormente verificamos.
En iterativos, es posible ir realizando la derivación y verificación a la par. En cualquier caso, el orden
de verificación de los diferentes puntos es intrascendente... siempre y cuando se realicen todos.

Los puntos a verificar en esta tabla son únicamente para programas como el de la figura 4.1. Si son
más complejos, habrá que considerar como Q y R a los asertos que delimitan un bucle así definido.

0. Inventar un invariante P y una función limitadora t. Verificar que P ⇒ Dom (B).

Proponer un P con alguno de los procedimientos de la página 133. Comprobar que los estados
del invariante están incluidos en el dominio de la protección. OJO, esto no quiere decir que P sea
más fuerte que B, es justo al revés. Si B es un predicado bien definido en todos los estados
posibles (ninguna variable toma valor indefinido) una manera más clara de ver este punto es P ⇒
B ∨¬B

1. P ∧¬B ⇒ R

101
25905053.doc

Si se cumple el invariante y no se cumple la protección del bucle, entonces se cumple la


postcondición.

2. {Q}Inic{P}
Si se cumple la precondición, una vez ejecutadas todas las instrucciones de inicialización, se
cumple el invariante. O bien, {Q} se fortalece tras las instrucciones de inicialización
convirtiéndose en {P}

3. {P ∧B}S{P}
Tras cada iteración del bucle (para lo que se tuvo que cumplir el invariante y la protección del
bucle), se sigue cumpliendo el invariante.
Otra manera de indicarlo simbólicamente: P ∧B ∧ejecución ⇒ P

4. P ∧B ⇒ t ≥ 0
Siempre que se ejecute una iteración del bucle, la función limitadora toma valores no negativos.

5. {P ∧B ∧t = T}S{t < T}.


Tras cada iteración del bucle, el valor de la función limitadora decrece estrictamente.

ÍNDICE

Pag 132. Derivación de bucles a partir de invariantes


Orden de derivación partiendo de R.
1. Conseguir como sea un invariante.
2. Obtener la protección del bucle a partir de P ∧¬B ⇒ R. La protección del bucle será B.
3. Determinar las inicializaciones adecuadas para que se cumpla{Q}Inic{P}. Si necesario, retocar
Q.
4. A partir de lo ya conocido, conjeturar la instrucción avanzar. Este es el momento oportuno para
proponer una función limitadora t.
5. A partir de {P} y avanzar derivar por sustitución textual (o a ojo) el aserto {T} que define el
estado entre las instrucciones restablecer y avanzar. El aserto {T} ha de ser tal que se cumpla:
{P ∧ B}restablecer{T}avanzar{P}. Traducido: cumpliéndose la protección del bucle y el
invariante, tras la ejecución de la instrucción restablecer se cumple el aserto intermedio {T}, al
ejecutarse ahora la instrucción avanzar se cumple otra vez el invariante. Si se cumple
directamente {P ∧ B}⇒{T}, la instrucción restablecer sería 'hacer nada'. En este caso, el
invariante no se rompe dentro del bucle.

ÍNDICE

Pag 133. Derivación del invariante a partir de R


Ver ejemplo en el problema de ExP2_1997_2sem_D.
ÍNDICE

Roberto Alonso Menlle


Curso 2001/2002
Alumno del C.A de la UNED en Ceuta.
r.factorial@terra.es

Salto de página.../...

102
25905053.doc

ÍNDICE

ADDENDA
Resumo aquí todas las expresiones, tablas y figuras que conviene llevar al examen perfectamente
comprendidas y memorizadas… o tatuadas como chuleta en el velo del paladar.

Cadenas de inclusiones de órdenes de complejidad:


ϑ (1) ⊂ ϑ(log2+k n) ⊆ ϑ(log2 n)⊂ ϑ(n) ⊂ ϑ(nlog n) ⊂ ϑ(n2) ⊂ ϑ(n2+k) ⊂ ϑ(2n) ⊆ ϑ((2+k)n) ⊂ ϑ(n!) ⊂
⊂ ϑ(nn), con k real no negativo.

Ω (1) ⊃ Ω(log2+k n) ⊇ Ω(log2 n) ⊃ Ω(n) ⊃ Ω(nlog n) ⊃ Ω(n2) ⊃ Ω(n2+k) ⊃ Ω(2n) ⊇ Ω((2+k)n) ⊃ Ω(n!) ⊃
⊃ ϑ(nn), con k real no negativo.

Pag 7. Ejercicio 1.2


g(n) ∈ ϑ (f(n)) 
lim [g(n)/f(n)] = 0 ⇒ g(n) ∉ Ω(f(n))  g(n) crece más lentamente que f(n)
 g(n) ∉ Θ (f(n)) 

 g(n) ∉ ϑ(f(n)) 
lim [g(n)/f(n)] = ∞ ⇒  g(n) ∈ Ω (f(n))  g(n) crece más rápido que f(n)
 g(n) ∉ Θ(f(n)) 

 g(n) ∈ ϑ(f(n)) 
lim [g(n)/f(n)] = k ⇒  g(n) ∈ Ω(f(n))  g(n) crece a la misma velocidad que f(n)
(k >0)  g(n) ∈ Θ (f(n)) 

Uniendo primero y segundo obtenemos:


lim[g(n)/f(n)] = finito ⇒ g(n) ∈ ϑ(f(n))

Pag 9. Ejercicio 1.6 (Regla de la suma)


ϑ(f(n)) + ϑ(g(n)) = ϑ(f(n) + g(n)) = ϑ(max(f(n),g(n)). Igual para los otros tipos de órdenes.

Pag 10. Ejercicio 1.7 (Regla del producto)


ϑ(f(n))*ϑ(g(n)) = ϑ(f(n)*g(n)). Igual para los otros tipos de órdenes.

Pag 17. Recuadro (1.2)


Cálculo recursivo de la función de coste de una función recursiva f(n) con reducción del problema
por sustracción.
T(n) = cnk ,si 0 ≤ n < b. En resumidas cuentas, si es caso trivial.
T(n) = aT(n-b) + cnk ,si n ≥ b. En resumidas cuentas, si es caso no trivial

Pag 18. Recuadro (1.3)


Fórmulas para el cálculo de órdenes de complejidad de una función recursiva f(n) con reducción del
tamaño del problema mediante sustracción.
T(n) ∈ Θ(nk+1) ,si a = 1
T(n) ∈ Θ(an div b) ,si a > 1

103
25905053.doc

Pag 19. Recuadro (1.4)


Cálculo recursivo de la función de coste de una función recursiva f(n) con reducción del problema
por división.
T(n) = cnk si 1 ≤ n < b.En resumidas cuentas, si es caso trivial.
T(n) = aT(n/b) + cnk si n ≥ b. En resumidas cuentas, si es caso no trivial

Pag 20. Recuadro (1.5)


Fórmulas para el cálculo de órdenes de complejidad de una función recursiva f(n) con reducción del
tamaño del problema mediante división.
T(n) ∈ θ (nk log b n) ,si a = bk
T(n) ∈ θ (n ^ log b a) ,si a > bk

Pag 71. Tabla 3.2


1. Q(x) ⇒ Bt(x) ∨Bnt(x) (Exhaustividad de los casos)
2. Q(x) ∧Bnt(x) ⇒ Q(s(x)) (Operación sucesor es interna)
3. Q(x)∧Bt(x) ⇒ R(x,triv(x)) (Base de la inducción).
4. Q(x)∧ Bnt(x) ∧ R(s(x),y’) ⇒ R(x,c(y’,x)) (Paso de inducción, donde R(s(x),y’) representa la
hipótesis de inducción).
5. Encontrar t: DT1 → Z | Q(x) ⇒ t(x) ≥ 0 (Definición de la estructura de pbf sobre los datos de
entrada).
6. Q(x) ∧Bnt(x) ⇒ t(s(x)) < t(x) (Decrecimiento estricto del tamaño de los subproblemas generados)

Pag 101. Figura 3.14.


Transformación directa de recursiva final a iterativo.

{Q(x)} {Q(xini)}
fun f(x:T1) dev (y:T2) fun f(xini:T1) dev (y:T2)
= caso Bt (x) → triv(x) var x:T1 fvar
[] Bnt(x) → f(s(x)) x := xini
fcaso {P(x,xini)}
ffun mientras Bnt(x) hacer
{R(x,y)} x := s(x)
fmientras
dev triv(x)
ffun
{R(xini, y)}

Pag 102. Figura 3.15.


Transformación directa de recursiva no final a iterativo.

{Q(x)} {Q(xini)}
fun f(x:T1) dev (y:T2) fun f(xini:T1) dev (y:T2)
= caso Bt (x) → triv(x) var x:T1 fvar
[] Bnt(x) → c(f(s(x)),x) x := xini
fcaso {P1(x,xini)}
ffun mientras Bnt(x) hacer

104
25905053.doc

{R(x,y)} x := s(x)
fmientras
y := triv(x)
{P2(x,xini,y)}
mientras x ≠ xini hacer
x := s-1(x)
y := c(y,x)
fmientras
dev y
ffun
{R(xini, y)}

Pag 126. Figura 4.2.


0. Inventar un invariante P y una función limitadora t. Verificar que P ⇒ Dom (B).
1. P ∧¬B ⇒ R
2. {Q}Inic{P}
3. {P ∧B}S{P}
4. P ∧B ⇒ t ≥ 0
5. {P ∧B ∧t = T}S{t < T}

ÍNDICE

105

Вам также может понравиться