Programacion 2 - Erratas y Escolios

164
document.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. [email protected] 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' 1

Transcript of Programacion 2 - Erratas y Escolios

Page 1: Programacion 2 - Erratas y Escolios

document.doc

Erratas y Escolios PeDos

Recensión de los cuatro primeros capítulos de

Diseño de ProgramasFormalismo y Abstracción

de Ricardo Peña Marí

Roberto Alonso MenlleAlumno del C.A de la UNED en Ceuta.

[email protected]

Está expresamente permitida la reproducción total o parcial de esta obra y su tratamiento o transmi-sió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 li-bre 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 / Confi -gurar 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 suprimir -lo.

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

1

Page 2: Programacion 2 - Erratas y Escolios

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

Page 3: Programacion 2 - Erratas y Escolios

document.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ón1.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 32 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 43 Pag 62. Líneas 14 y ss. Finales más eficientes que no finales 61

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 19 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ón1.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 28 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 32 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 42 Pag 34. Definición 2.5. Cambio de variable en 42 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) 43 Pag 42. Debilitamiento predicado por sustitución de constante por variable 43 Pag 43. Segundo párrafo 43 Pag 43. Dominio vacío de un cuantificador 44 Pag 45. Definición 2.14. Sustitución textual 45

3

Page 4: Programacion 2 - Erratas y Escolios

document.doc

ÍNDICE TEMA 3. Diseño recursivo 48 Pag 55. Introducción recursividad. Un ejemplo hablado 48 Pag 60. Introducción recursividad. Función sum, recursiva lineal no final 49 Pag 60. Introducción recursividad. Función sumF, recursiva lineal final 52 Pag 61. Introducción recursividad. Una función no lineal 55 Pag 61. Figura 3.2. Terminología de funciones recursivas 57 Pag 62. Líneas 14 y ss. Finales más eficientes que no finales 61 Pag 63. Punto 3.2. xDT1.Q(x)R(x,f(x)) 62 Pag 64 y ss. Preórdenes; pbf 63 Pag 67. Teorema 3.2 Principio de inducción noetheriana 71 Pag 69. Análisis por casos y composición. 74 Pag 71. Tabla 3.2. Verificación de funciones recursivas lineales 74 Apéndice a tabla 3.2. Ejemplo de verificación de recursiva lineal final. 82 Pag 81. Técnicas de inmersión 83 Pag 85. Inmersión final 84 Pag 94. Desplegado y plegado 85 Apéndice a pag 94. Desplegado y plegado. Postcondición no constante 90 Pag 101. Figura 3.14. Transformación de recursiva final a iterativa 90 Pag 101. Expresión 3.19 96 Pag 102. Figura 3.15. Transformación de recursiva no final a iterativa. 96 Nota final sobre los diferentes ejemplos usados 98

ÍNDICE TEMA 4. Diseño iterativo 100 Pag 112 y siguientes. Derivación de iterativas 100 Pag 121. Definición 4.1. Invariante 100 Pag 125. Figura 4.1. Esquema de programa iterativo 104 Pag 126. Figura 4.2. Verificación de funciones iterativas 104 Pag 132. Derivación de bucles a partir de invariantes 105 Pag 133. Derivación del invariante a partir de R 105

ADDENDA 107

Í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 Figura10

21

Comparación órdenes logarítmicos / No asimetría entre (f(n)) y (f(n)) Figura11

25

Escasa influencia de la base en problemas intratables Figura 12 31Tema 3 Ejemplo de recursión lineal. Función sum Mapa 1 recursión 51 Ejemplo de recursión lineal final. Función sumF Mapa 2 recursión 54 Ejemplo de recursión multiple. Función suma Mapa 3 recursión 57 Comparativa tiempos: no final, final e iterativo 62 Ejemplo pbf tamaños problema función lineal reducción 1 Mapa 1 tamaños pbf 64 Ejemplo pbf tamaños problema función lineal reducción 2 Mapa 2 tamaños pbf 65 Ejemplo pbf tamaños problema función no lineal Mapa 3 tamaños pbf 67Tema 4 Tabla evolución de un bucle y su invariante 102

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

4

Page 5: Programacion 2 - Erratas y Escolios

document.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, es-te 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 do-cente 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 compreta-mente 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 di -fuminar responsabilidades o aparentar un sólido respaldo teórico o académico. Todas las posibles erra-tas, errores u horrores que aparezcan serán únicamente imputables a mi descuido, temeridad o igno-rancia. 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

Page 6: Programacion 2 - Erratas y Escolios

document.doc

ÍNDICE

ESCOLIOS GENERALES

1. Prerrequisitos.Según CD, se supone que el libro es autosuficiente para el estudio de la asignatura. Se supone... aun-que 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áti-ca' (J. Aranda et alii. ed. Sanz y Torres), y en concreto, de ese epígrafe 2.2, las páginas 36 a 40, ni mi -rarlas.

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í co-mo algunos epígrafes sueltos que resulten demasiado difíciles (¡sin descartar todos!). La inmensa ma-yorí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.52.1 a 2.3Tema 1Y ¿finalmente? una tercera, ya en el orden que cada cual estime más oportuno, de los epígrafes que re-sultaran 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 cuan-do 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 in -cluyen al 0). El 0 entonces NO pertenece a N, lo que por otra parte es lógico: N es el conjunto por an-tonomasia 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) co-mo 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.

6

Page 7: Programacion 2 - Erratas y Escolios

document.doc

Cualquiera de ellas es perfectamente válida, siempre que se especifique cuál se usa.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 cuan-do aparece en los predicados de una especificación o en un aserto. Pero en otras ocasiones puede sig-nificar 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 per-mite, 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 =1

i y sí la forma más fea {1..i} (claro que esto por imposición del Señor de las Venta-nas, 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 anti-guos, 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 produ-ce en un examen.También en examen se debe tener muy presente (… y yo casi siempre lo olvido) que nos podemos en-contrar preguntas similares a '¿Es X una solución para el problema Y?'. Si a pesar de existir más solu-ciones (o una más genérica, o más simple), X es una solución particular, por innecesariamente com-pleja que sea, la respuesta a la pregunta es afirmativa.

ÍNDICE

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

7

Page 8: Programacion 2 - Erratas y Escolios

document.doc

ÍNDICE

ESCOLIOS PARTICULARES

TEMA 1. La eficiencia de los algoritmos

Pag 3. Tamaño de los datos de entradaA 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, pe-ro que tomadas literalmente pueden inducir a error. En ningún momento significa el tamaño de la tu-pla (conjunto ordenado) que forman la lista de parámetros formales.

Por ejemplo. Supongamos que mediante una función de nombre ordena queremos ordenar ascenden-temente todos los elementos desordenados de un vector denominado arreglo con un millón de posi-ciones.

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 ta-mañ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 as-cendentemente 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á se-miordenado de un modo aleatorio y finalmente con el vector absolutamente desordenado (esto es, or -denado 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, mu-cho más...) en la tercera. Estos serán respectivamente los casos mejor, promedio y peor. (Existen algo-ritmos 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 ba-se 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óticasOjo a los nombres en éste y siguiente epígrafe. El estudio que se realiza de medidas asintóticas y órde-nes 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

8

Page 9: Programacion 2 - Erratas y Escolios

document.doc

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, serán conceptos ínti-mamente 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 ÓrdenesNada 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 impor-tancia de la que parece.).

Bien, dice el libro:

Posteriormente veremos un manera más clara de realizar esta definición simbólica. Ahora, simplemen-te 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 va-

riables (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 predica-do 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 to-

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

ÍNDICE

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

9

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

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

Page 10: Programacion 2 - Erratas y Escolios

document.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 co-lor 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 domi-nio

... pero bueno, aunque después veremos otra más precisa y clara, realicemos una primera aproxima-ció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 co-mo 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 defini -ció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 lla -mo 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 + 1704celeste h(n) = 48n2 – 816n verde j(n) = (n2)/3 +81n –1998siena k(n) = 162n2 + 2268rojo l(n) = n3

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

10

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

Page 11: Programacion 2 - Erratas y Escolios

document.doc

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

0

2000

4000

6000

8000

10000

20 40 60 80 100 0

2e+06

4e+06

6e+06

8e+06

1e+07

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 fa-milias, 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 par-tida.

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 curvatu-ra 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 con -jeturar 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.

11

Page 12: Programacion 2 - Erratas y Escolios

document.doc

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

0

2e+06

4e+06

6e+06

8e+06

1e+07

200 400 600 800 1000 0

5000

10000

15000

20000

25000

30000

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 cual-quier cuadrática. Antes o después se acaba demostrando que posee una aceleración de crecimiento ma-yor 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 funcio-nes 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 mo-delo 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 ) = cn 2 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á diferen-te.

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

12

Page 13: Programacion 2 - Erratas y Escolios

document.doc

Si demostramos que con cf(n) podemos acotar una función g(n) a partir de un determinado punto críti-co n0 ¿Podremos seguir acotando a g(n) con otra función c'f(n) con c' distinta de c?, y si es así ¿el pun-to 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)?.

Supongamos que queremos comprobar si la función g(n) = 3n2+2n + 183 pertenece a la anterior fa-milia 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

0

200

400

600

800

1000

5 10 15 20 0

2e+06

4e+06

6e+06

8e+06

1e+07

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 supe-riores 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 fun-ción (o ligadura de n0 respecto de c) podemos indicarla de manera clara como n0(c) (leído 'n0 es fun-ció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 supe-riormente 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

13

Page 14: Programacion 2 - Erratas y Escolios

document.doc

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

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

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 su-ficiente 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 Compleji-dad 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 endiabladamen-te 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 pri-meras se caracterizan por tener velocidad de crecimiento 0 mientras que las segundas tienen velo-cidad 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 pode-mos decir que el Orden logarítmico es un subconjunto del orden lineal, que a su vez lo es del cuadráti -co... 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 pun -tualizaciones: 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 programado-

14

Page 15: Programacion 2 - Erratas y Escolios

document.doc

res y usuarios, lo que nos preocupará en general (habrá excepciones) es cómo se comporta temporal-mente un determinado algoritmo en el peor de los casos. Por eso usaremos el orden casi en exclusi-va 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 superior-mente por f(n) (¡sin la c!). Da lo mismo. Nos entendemos perfectamente... siempre que hayamos com-prendido el concepto de Orden de Complejidad.

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 com-plejidad 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 normal-mente, y en rigor, denotaremos como T(n).

Refiriéndose a una función de tiempo dada T(n), con frecuencia nos encontramos con expresiones si-milares a 'realizar un estudio asintótico de la función T(n)'. No se nos está pidiendo otra cosa que de-terminar a qué orden de complejidad pertenece la función T(n). Porque una vez establecido que perte-nece 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 concretamen-te 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 Com-plejidad 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 si-guiente que deja varios cabos sin atar:

Una función g(n) pertenecerá al conjunto infinito Orden de Complejidad de f(n), si podemos en-contrar algún real c que haga que se verifique g(n) cf(n) para todo n mayor que un determina-do 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).

15

Page 16: Programacion 2 - Erratas y Escolios

document.doc

Simbólicamente:

(f(n))={(g : N R + {0})|((cR + 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 aumen-tar la legibilidad y eliminando un paréntesis no necesario (eliminar más puede ser contraproducente):

(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 órdenesSe 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 ejecu-ció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 tem-poral (que normalmente no se usan en memoria principal, pero pertenecen al orden nlogbn, ¡con la ba-se 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 mis -mo, 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 mu-cho más lógico hablar de Ordenes Temporales... o para no inventar nombres nuevos, Ordenes de Infi -nitud, 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 debe-ríamos saber, lo que era; incluso aunque no lo conociéramos por tal nombre y lo usáramos intuitiva -mente 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 dife-rente. Básicamente, complejidad se usará para estudiar cotas y se definen según un criterio de cota; in-finitud 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.

16

Page 17: Programacion 2 - Erratas y Escolios

document.doc

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 co-rolario!.

ÍNDICE

Apéndice 2Volviendo 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 consecuente-mente 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, aun-que 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 3Tomada 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 de-terminados problemas con indeterminación en el cálculo de límites. Estos nuevos Órdenes estarían in-cluidos en el Orden Constante y pertenecerían a dos grandes categorías, en las que no se incluye nin -guna 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 funcio-

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

17

Page 18: Programacion 2 - Erratas y Escolios

document.doc

negro f(n) = 1 / n3

verde g(n) = 1 / n2

marrón h(n) = 1 / nlognazul j(n) = 1 / nmagenta k(n) = 1 / (log1,3n)

ÍNDICE

0

0.1

0.2

0.3

0.4

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 desi-gualdad 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 res-pecto 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ógi -ca de tipo condicional. Afinando más, el primero de los problemas es un condicional puro o implica-ció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 afir-mar 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.

18

Page 19: Programacion 2 - Erratas y Escolios

document.doc

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 Lo ya comentado en un apéndice al escolio Pag 6. Definición 1.1. (f(n)) e introducción a los Órde-nes. 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 especial -mente en análisis matemático (en realidad, insisto, no es que sean útiles: constituyen el criterio deci-sor de adscripción a un determinado orden). En cambio, su utilidad práctica en PeDos queda casi limi-tada a la resolución de algunas preguntas tipo test de los exámenes, ya que en los problemas a desarro-llar, 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 modi-ficaciones:

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

Page 20: Programacion 2 - Erratas y Escolios

document.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 numera-dor 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 di -rectamente 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 loga-rí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 ca-dena? o ¿se consideran del mismo orden que 2n?. En principio, aplicando límites según el escolio an-terior, 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 encon-trar 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 si-guiente ejemplo. Supongamos que tenemos cuatro algoritmos, f(n), g(n), h(n) y k(n) cuyas funciones de tiempo son las siguientes:

20

Page 21: Programacion 2 - Erratas y Escolios

document.doc

negro Tf(n) = 2n unidades de tiempoazul Tg(n) = 4n unidades de tiempomagenta Th(n) = n! unidades de tiemporojo 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

0

5

10

15

20

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 algorit-mos 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 compro-barlo, imaginemos que los cuatro algoritmos realizan un cálculo que consideramos realmente impor-tante y que podemos esperar lo necesario para obtener la solución. Digamos que nuestra paciencia al -canza hasta... los 10 años (3'1536*1011 milisegundos); que ya es paciencia. Superada esa cota de tiem-po, consideraremos que los problemas son intratables; al menos para nuestra monacal capacidad de es-pera (figura 9). Pero si resulta que los cálculos son realmente necesarios para el futuro de la humanidad y que pode-mos/queremos dejar funcionando las máquinas durante siglos, para que al menos un ignorado descen-diente 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*1016 ms. Todo sea en aras de la supervivencia de la especie (figura 10). Salto de página.../...

21

Page 22: Programacion 2 - Erratas y Escolios

document.doc

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

0

5e+10

1e+11

1.5e+11

2e+11

2.5e+11

3e+11

10 20 30 40 50 0

5e+15

1e+16

1.5e+16

2e+16

2.5e+16

3e+16

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 me-jor pudo aprovechar el tiempo (negra), no fue capaz de tratar más que 40 miserables datos. Eso sí, co -mo 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 me-nos 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/algorit -mo durante toda una Edad de Universo (unos 15.000 millones de años, 4'7304*1020 ms. El tamaño de problema que pongo, es el máximo posible que necesita un tiempo que no sobrepasa ese límite absolutamente infran -queable... 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 68azul Tg(n) = 4n 34magenta Th(n) = n! 21rojo 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 conve-nio (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

Page 23: Programacion 2 - Erratas y Escolios

document.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 poten-cial-exponencial también cruza a la factorial). Admitir aquí el mismo convenio de inclusión o identi -dad 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 intrata-ble, ya que son todas de una eficiencia absolutamente extraordinariamente (sólo superada por las fun-ciones constantes). Tan extraordinaria, exactamente en los mismos órdenes de magnitud, como extra-ordinaria 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 log 2n (gráfica negra) en 4 segundos procesa unos 17 datos (Figura 8), resultará que en aproximadamente 38 segun-dos 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 es-tamos 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 habi-lidad 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 7n! 2!=2 3!=6 4!=24 5!=120 6!=720 7!=50402n 22=4 23=8 24=16 25=32 26=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 equi-vocarnos.

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 pro-blema 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 eleva-do, pero que para problemas de tamaño igual o menor a nuestro máximo, muestra un comportamiento

23

Page 24: Programacion 2 - Erratas y Escolios

document.doc

no asintótico (mejor en ese dominio), que el de otro algoritmo que haga lo mismo y que pertenezca a un orden inferior. Dicho más claro: nos quedamos con el algoritmo de orden superior si en ese domi-nio 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 modifi-caciones 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})|((cR + 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 le-gibilidad, 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 permi-ta 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 dis - tingo 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 incorrec-ció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.

24

Page 25: Programacion 2 - Erratas y Escolios

document.doc

Supongamos que tenemos el algoritmo/función ordena mencionado en los escolios a la página 3, cu-ya función de tiempo es Tordena(n) [función de tiempo a la que en estas primeras páginas del libro se la hubie-

ra llamado con nombre genérico de función f(n), o incluso t(n)].

Por procedimientos que ahora no nos interesan, realizamos tres estudios detallados de cómo se com-porta 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 logarit -mos:

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

0

20

40

60

80

100

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 co-mo 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 cual-quier 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 absolutamen-te 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. Co-mo es cota inferior, 5log3 25n pertenece a (log3 n). Cometemos el mismo abuso de lenguaje y dire-mos 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.

25

Page 26: Programacion 2 - Erratas y Escolios

document.doc

E inferiormente, está acotada de manera absoluta por (log10 n) en todos los casos. En particular, el mejor.

ÍNDICE

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 aco-tan 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, al-go 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 obte-nemos 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 determi-nada (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áfi-ca 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 mane-ra 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:

26

Page 27: Programacion 2 - Erratas y Escolios

document.doc

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} (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 va -cía... de lo que se sigue, que según la definición 1.3 de Peña, tendríamos que cualquier función cua -drá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ínAl final del recuadroDice: 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 di -cen 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 con-funden.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 de-termina el tamaño de un vector (un arreglo), que por definición tiene un número natural finito de ele-mentos. 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 do-minio de definición, o en parte de él), en esta asignatura sólo nos interesan los puntos (n, f(n)) tales que nN.Por eso en el libro se usa f(n) en vez de f(x)

ÍNDICE

Pag 11. Figura 1.1Atenció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 crecimien-to 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 im-presionante 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.

27

Page 28: Programacion 2 - Erratas y Escolios

document.doc

Pag 16. Punto 1.5. Resolución recurrencias costeComo puede tener un poco de guasa realizar un cálculo recursivo de costes antes de conocer correcta-mente 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) re-presenta 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 mu-cho 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 cal-cula el coste de f(n). Ambas estarán íntimamente ligadas, pero en absoluto son la misma cosa. Las ad-miraciones 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_booleanaBnt 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) + cnk si n b (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 resolu-ció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 or-den lineal (n), tendremos que para que nk (n), k = 1.

28

Page 29: Programacion 2 - Erratas y Escolios

document.doc

Si son de coste constante, sólo k = 0 hace cierto nk (1). En todos nuestros problemas, las par-tes 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 recur-sió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 pro-blema 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 me-nor 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 reali-dad todas las recursiones sí tienen una bifurcación; en la última llamada cuando se entra en el caso tri -vial. Pero al contrario que las bifurcaciones iterativas, ésta es siempre de un coste fijo conocido a prio-ri. 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 = 1T(n) (an 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_booleanaBnt 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 pertene-ce a (nk ). Si en total se realizan n llamadas recursivas, el coste total de la primera llamada ini-cial será n(nk ) = (n*nk) = (nk + 1). [La primera igualdad, ¿necesita demostración?: n (n)].

29

Page 30: Programacion 2 - Erratas y Escolios

document.doc

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.

f(n) Bt expresión_aritmética_o_booleanaBnt 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 re-ducen el tamaño en b unidades?. Veamos primero el siguiente ejemplo: partimos de n = 9, b=3 y a=2. Esquemáticamente, tendre-mos 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.

97 7

4 4 4 4 4 41 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 ha-brá 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, ha-rá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 importan-cia. 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ác-tico de problemas informáticos; ya habíamos visto en el apéndice 3 del escolio a la Pag 6 'Defini-ció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é reduc-ció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 reduc-

30

Page 31: Programacion 2 - Erratas y Escolios

document.doc

ción 5 lleguen al caso trivial, ya podrían iniciar la cadena ascendente de combinaciones; combinacio-nes 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 descenden-tes, 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)).Aunque creo que es notacionalmente válido, es incorrecto como lo es T(f(n)). i es una variable de in-mersió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 pro -blema 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 ndi-vb. 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 algo-ritmo 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 excesi-vas 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. Obten-dríamos un coste temporal de 500.0002 = 250 millardos de unidades de tiempo; si esa u.t. es la milési-ma 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 tiempoazul Tg(n) = 10n unidades de tiempomagenta Th(n) = 1000n unidades de tiemporojo Tk(n) = 100000n unidades de tiempo

ÍNDICE

0

5e+10

1e+11

1.5e+11

2e+11

2.5e+11

3e+11

2 4 6 8 10 12 14 16

Figura 12ÍNDICE DE FIGURAS

ÍNDICE

31

Page 32: Programacion 2 - Erratas y Escolios

document.doc

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)

Bt expresión_aritmética_o_booleanaBnt 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 divi -sió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

32

Page 33: Programacion 2 - Erratas y Escolios

document.doc

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: (nk ) = (n3) ; 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

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 eleva-da al orden de lo no recursivo. Mnemónicamente: si la complejidad de a es mayor que la de lo no re-cursivo. 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 tri-vial) 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 po-demos 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 conside-raba 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 ente -ra, logbn o la parte entera con redondeo por exceso logbn.

ÍNDICE

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

33

Page 34: Programacion 2 - Erratas y Escolios

document.doc

ÍNDICE

TEMA 2. Especificación de problemas

Pag 28. Ejemplo de especificaciónPor culpa de la n que figura en la precondición y en la lista de parámetros formales, el ejemplo es po -co 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 de-clarado:

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 itera-tivos) 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

{Q1 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 posi-ciones, 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 pa-san perfectamente la precondición. Sólo admitiendo el segundo convenio, la segunda llamada sería in-correcta.

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

34

Page 35: Programacion 2 - Erratas y Escolios

document.doc

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.

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 do -minio. 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 ol-vidas de algo tan trivial en momentos críticos. Por eso es mucho más práctico aplicar el sentido co-mú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 PAl 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 al -gú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 en-tendemos perfectamente como la negación simple 'ninguno', pero que puede ocasionar serios proble-mas si el predicado a su vez contiene negaciones. Problemas que se agravan si el dominio se hace va-cío.

ÍNDICE

Aunque esto ya entre en el terreno de la lógica de descriptores o de predicados con identidad, convie-ne tener cuidado con las siguientes expresiones que aparecen con bastante frecuencia en definiciones matemáticas:xy.Pxy : existe algún x (sea o no uno único), común para todo y, tales que verifican P. Por ejem-plo, existe (hay) pastel para todos los niños que son alumnos de esta clase. No estamos diciendo cuan -tos pasteles hay, sólo que al menos existe uno, y que sean los que sean, son para todosyx.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-).

35

Page 36: Programacion 2 - Erratas y Escolios

document.doc

Nos dan los siguientes predicados (inmediatamente después de cada predicado su clausulación):P3 xy(Rxy Sxy) xy(¬Rxy Sxy) x(¬Rxfx Sxfx) ¬Rxfx Sxfx.P4 xy(RxyQx) xy(¬Rxy Qx) ¬Rxfx Qx.P5 xy(Qx¬Sxy) xy(¬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

Al presentarnos así la interpretación, quiere decir que tenemos un conjunto Universal de sólo tres ele-mentos (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 termi -nologí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, am-bas 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 xU, ¬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 resolverloP4 ¬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 vere-mos 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 xy(RxyQx) xy(¬Rxy Qx) [(¬R00Q0) (¬R01Q0) (¬R02Q0)] [(¬R10Q1) (¬R11Q1) (¬R12Q1)] [(¬R20Q2) (¬R21Q2) (¬R22Q2)] que es una tautología, pues tenemos [VVV][VVV][VVF].Como ejercicio: ¿por qué no es válida la siguiente transformación?:P4 xy(RxyQx) xy(¬Rxy Qx) [(¬R00Q0) (¬R01Q0) (¬R02Q0)] [(¬R10Q1) (¬R11Q1) (¬R12Q1)]

36

Page 37: Programacion 2 - Erratas y Escolios

document.doc

[(¬R20Q2) (¬R21Q2) (¬R22Q2)]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 xy(¬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)}

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

¿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 pre-gunta fuese ¿es ¬P4 satisfacible en la interpretación M?, la respuesta seguiría siendo afirmativa, a pe-sar 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 xy, xy xy.En el momento que predicamos algo (P) de dos o más sujetos (xy) y lo ponemos en un único predica -do, 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 forma-da 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á conveniente-mente explicado. Siguen ahora unos ejemplos que usan dicho convenio, donde g y f serán funciones y

37

Page 38: Programacion 2 - Erratas y Escolios

document.doc

a una constante de Skolem. Para mayor claridad, pongo el punto que normalmente omitimos y que se lee ‘tal que’.xyzt.Rxyzt (xy)^(Azt).Rxyzt Rxf(x)zg(z) y está ligada a x, t está ligada SÓLO a z. xy.Pxy (f)x.Pxf(x)y.Rxy xy.Rxy Rxf(x) xy.Rxy Ray

Y otros cuatro ejemplos que no son convenio notacional, pero es conveniente destacarlos y que son fáciles de demostrar convirtiendo PQ en ¬PQx(PxyQy) xy(PxQy) Px Qax(yPy Qx) xy(PyQx) PaQxx(yPyQx) Py Qx xy(Pxy Qy) Pxf(x) Qf(x).

Pag 32. Variables ligadas. Primer párrafo bajo la definición 2.3Si tenemos dos cuantificadores no anidados, aunque no es recomendable en la práctica, no existe pro-blema 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.4Faltan 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 en-tre y , aunque también es posible que su sitio sea en el extremo derecho. Será entonces el contex-to quien decida la precedencia de la implicación.

La no existencia del convenio completo puede acarrear serios problemas en la interpretación de algu-nos 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 con-venio 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óni-mos 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

38

Page 39: Programacion 2 - Erratas y Escolios

document.doc

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)]}{ 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 paren-tizarla 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 ST={1,2,3,4}. La unión disjunta, en cambio, considera a los dos conjuntos como disjuntos (de inter-secció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 ST={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

39

Page 40: Programacion 2 - Erratas y Escolios

document.doc

indice_a = {1..3}indice_v = {2..4}

a = vector[indice_a] de entv = vector[indice_v] de ent VAR

i : indice_aj : indice_va[i] : entv[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.

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 or-denadas, 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 apli-car el identificador i en el valor v[4]. Resultaría que el identificador de un índice, usado para referen-ciar 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 pue-de 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 pro-blemas iterativos exactamente con este significado. No logro entender por qué aquí se sigue otro crite-rio.

Lo que sigue ahora es una estudio más en profundidad sobre esta definición y sus implicaciones... res-petando la que creo notación errónea del libro. A fin de cuentas, será con ella con la que nos examine-mos. 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.

40

Page 41: Programacion 2 - Erratas y Escolios

document.doc

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 subcon-juntos 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 to-dos 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 apli-caciones diferentes como elementos tenga el producto cartesiano de los diferentes dominios: # = #D1

* #D2 *… * #Dn.

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 varia-ble. 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 ma-nera: 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 (in-cluido 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 defini-do 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 pre -dicados 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 denotare-mos ( xi). En el anterior estado = (=2,=3), tendremos que () = 3.

41

Page 42: Programacion 2 - Erratas y Escolios

document.doc

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 claramen-te indefinido: 11 mod . En este caso, tenemos que DC contiene un valor indefinido concreto perfecta-mente identificable

Pag 34. Definición 2.5. Cambio de variable en 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 defi-

nido en el caso del predicado R. (Aunque aquí, se incurre en definición circular, pero bueno, ad-mitámosla como correcta).

De esta definición se sigue, por otra parte algo trivial, que es posible que un predicado está bien defi -nido en un estado, pero no en otro ’. Y tan trivial, lo vimos continuamente en Análisis Matemáti-co, donde la función f(x)= 1/x (predicado f xR. 1/x) está bien definida en todo el dominio, ex-cepto 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 esta-do 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.

42

Page 43: Programacion 2 - Erratas y Escolios

document.doc

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 es-tados 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, aun -que añadimos una conjunción, estamos debilitando y no fortaleciendo como dice la norma general. Segundo, porque no podemos admitir, como se dice en Peña 42 (final de página), la comparación en-tre 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 es-tá 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árrafoDestacar 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'.

43

Page 44: Programacion 2 - Erratas y Escolios

document.doc

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 en el dominio considerado), no existe ninguno que pueda falsar el predicado. Por tanto el predi -cado es cierto.

ÍNDICE

Destacar que {1..n}.P es una manera abreviada de escribir P(1) P(2) ... P(n), y que jus-tamente el valor veritativo de un predicado cuantificado universalmente cuando el dominio se ha-ce 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 satisfa-cerlo {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 conse-cuencia 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 predi-cado, 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}.PSumatorio. 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:

44

Page 45: Programacion 2 - Erratas y Escolios

document.doc

{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 con-venio admitimos en este caso que la suma es cero, elemento neutro de la suma. Del mismo mo-do, por convenio admitimos que la suma de un sólo elemento es precisamente el valor de dicho elemento.

ÍNDICE

{1..n}.PMultiplicatorio. 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 ele-mento es el valor de dicho elemento.

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

Vemos con frecuencia que para nosotros una suma es una operación de asignación del tipo s:=s+nue-vo_sumando. Esto es, tenemos una posición de memoria de nombre s donde vamos acumulando el re-sultado de la suma de lo que ya existe en s más un nuevo_sumando. Antes de empezar a acumular na-da 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 par-tiendo solamente de la postcondición o del aserto existente inmediatamente después de dichas instruc-ciones. 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 alcan-zados después de ellas (o postcondición), averiguar el aserto correspondiente (o precondición) existen-te 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, convir-tiéndose en el aserto que define el estado del algoritmo inmediatamente después de dicha instrucción ({1}, {2} y {R}).

45

Page 46: Programacion 2 - Erratas y Escolios

document.doc

Para averiguar la Q pedida, hacemos el camino al revés. Partimos de R, sustituyendo todas las apari -ciones 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 }x

2x + y}yx - y

Primera sustitución:{{{x = P y = T}x

-x +y}x2x + y}y

x - y {{-x + y = P y = T}x2x + y}y

x - y {{-x + T = P y = T}x2x + y}y

x - y

{{x = T - P y = T}x

2x + y}yx - y {{2}x

2x + 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}x

2x + y}yx - y {{2x + y = T - P y = T}y

x - y {{2x + T = T - P y = T}yx - y

{{x = - P/2 y = T}yx - y {1}y

x - y

Tercera sustitución:{{x = - P/2 y = T}y

x - 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.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 corres-pondientes 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ónx := -x + y = P – T + T = P =(x = P y = T)

46

Page 47: Programacion 2 - Erratas y Escolios

document.doc

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

47

Page 48: Programacion 2 - Erratas y Escolios

document.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 ver-bal.Supongamos que leemos u oímos la frase 'él sabe que ella cree que él coquetea con María '. No tene-mos ningún problema para comprenderla correctamente, ¿dónde está entonces la recursión?. Precisa-mente 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 has-ta 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 favora-bles, 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 (le -xicó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 ac-tiva 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 entra-da 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 neurona-les 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

48

Page 49: Programacion 2 - Erratas y Escolios

document.doc

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

(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 ar-gumento (llamada2).

2) Por fin de mensaje, MAS detecta fin de oración con estructura sintáctica correcta. El último obje-to 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 sali-da a llamada1) quien la recibe como objeto directo:

1') con los datos devueltos MAS ya puede construir la estructura completa del primer nivel recursi-vo:S'-V'-OD'{S''-V''-CC''} . La frase ella cree que él coquetea con María, tiene un objeto directo con sig-nificado 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ági -nas...), 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 finalVolviendo a la informática; para comprender correctamente qué es esto de la recursión y cómo funcio-na, 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.

{Q1 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)

fcasoffun {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):

49

Page 50: Programacion 2 - Erratas y Escolios

document.doc

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), 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 91 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 memo-ria 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 91 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)

50

Page 51: Programacion 2 - Erratas y Escolios

document.doc

8 6 7 9 8 6 7 9 8 6 7 91 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) 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 91 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=8s= 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 li -bera 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 91 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=14s= 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 91 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)

51

Page 52: Programacion 2 - Erratas y Escolios

document.doc

s= ? s= 7+14=21s= 4

=1a[]= s= 3=1a[]=

= =8+6+7=21

0')sum(a, 4)

8 6 7 91 2 3 4

a[4]+sum(a,4-1)

9+sum(a,3)

s= 9+21=30s= 4

=1a[]= =8+6+7+9=30

ÍNDICE

Y este último valor, 30, es el que devuelve la función inicialmente llamada por nosotros (llamada ex-terna, 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 as -cendente (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 obser-var la evolución de las distintas llamadas) , pueden derivarse aparentes paradojas en el diseño recursi-vo 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 comproba-ción de si se cumple la postcondición se realiza en la cadena ascendente de combinaciones de las de-voluciones anteriores con los respectivos cálculos actuales.

Pag 60. Introducción recursividad. Función sumF, recursiva lineal finalGracias al profesor Quesada, del C.A. de Victoria-Gasteiz, por haber señalado un error grave... di -ciéndolo suavemente. Porque en realidad era una burrada. No tenía en cuenta la cadena de devolu-ciones, por lo que llegaba a conclusiones tan pintorescas como que carecía de sentido el paso de in-ducció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 ver-sión final, llamada sumF, que aquí usaremos como ejemplo de la dinámica del funcionamiento de una recursiva final.

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

fcasoffun{RsumF s= {1..n}.a[]}

ÍNDICE

52

Page 53: Programacion 2 - Erratas y Escolios

document.doc

Vemos dos características: la postcondición es constante (no dependiente de los parámetros de inmer-sió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 esta-ría distorsionado por el valor que hubiéramos introducido en w. Bueno, también nos importa saber que ahora la precondición es más compleja, ya que hemos de espe-cificar qué es w. Obsérvese que tal como está enunciado el conjuntando que se refiere a w, precisa-mente especifica implícitamente que la primera llamada a de ser con w=0. (Mismo comentario que an-tes 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 91 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 vec-tor 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

53

Page 54: Programacion 2 - Erratas y Escolios

document.doc

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 91 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= ¿?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 91 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]+22sumF(a, 3, 9+0) sumF(a, 2, 7+9) sumF(a, 1, 6+16) 8+22 =30sumF(a, 3, 9) sumF(a, 2, 16) sumF(a, 1, 22)

s= ¿? s= ¿? s= ¿? s= 8+22 =30s= 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 91 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)

54

Page 55: Programacion 2 - Erratas y Escolios

document.doc

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

s= ¿? s= ¿? s= 30s= 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 91 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= 30s= 4

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

=8+6+7+9=30

0')

sumF(a,4, 0)

8 6 7 91 2 3 4

sumF(a, 4-1, a[4]+0) sumF(a, 3, 9+0)sumF(a, 3, 9)

s= 30s= 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 ca-da caso se realiza una o ninguna llamada recursiva (que por tanto que serán siempre de complejidad li -neal, 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 sumaLa 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)

fcasoffun

55

Page 56: Programacion 2 - Erratas y Escolios

document.doc

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 exter -nas erróneas (no muy lógica de cara al usuario; si llamamos con los índices invertidos, no debe-rí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 41 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 es-criba 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.

Donde ponga algo similar a: suma(a,1,5) [m=(1+5)div2=3] significa que la llamada actual es su-ma(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)

56

Page 57: Programacion 2 - Erratas y Escolios

document.doc

= 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 41 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 trivia-les, y se pueden iniciar las cadenas ascendentes de sumas hacia los niveles superiores, liberando el es-pacio de memoria ya no necesario.

ÍNDICE

0) suma(a,1,5)

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

2) 11 + 6

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

57

Page 58: Programacion 2 - Erratas y Escolios

document.doc

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)} {Q1 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 fcasoffun 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 co-sa que llama la atención: en general (x) denota la tupla de datos de entrada (así, los argumentos forma-les 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 re-cursiva que estamos ejecutando o estudiando en un momento dado con unos datos de entrada concre-tos. 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

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

[{Q1 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 comenta-rio 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

58

Page 59: Programacion 2 - Erratas y Escolios

document.doc

booleana entremos en el consecuente del condicional. En una misma función es posible que exis-ta 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, ne-cesariamente ejecutamos lo siguiente'.

Triv(x) devolución del caso trivial. Cuando se cumple la condición booleana Bt, la función en-trega Triv(x), que será un valor concreto (incluso en el supuesto que el consecuente del caso tri-vial contenga una llamada a función externa, en cuyo caso la devolución del caso trivial será di-rectamente la salida de esa función externa o bien esa salida operada con algún valor determina-do 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 ocu-rrirá así]

Bnt (x) protección del caso no trivial. Como Bt (x), pero que nos lleva al caso no trivial. Puede ha-ber 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 inter-na (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)]

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ógi-ca o de asignación con alguno(s) de los datos de entrada, para usarlo como operador de la fun-ció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 su-ma]

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 pro-blema 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].

59

Page 60: Programacion 2 - Erratas y Escolios

document.doc

{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 llama-da 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 tri-vialmente 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 llama-da actual no la produce directamente mediante manipulación algebraica, lógica o de asignación de al -guno(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 con-secuente 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 entre -gue 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 incon-gruente notación del libro para la combinación resulta que es completamente correcta.

ÍNDICE

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 opera-ció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, sali-da), 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 sali -da; 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

60

Page 61: Programacion 2 - Erratas y Escolios

document.doc

Pag 62. Líneas 14 y ss. Finales más eficientes que no finalesDice 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 fi -nales.

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 devolu-ció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 ex-terna, 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 fi-nal)

Entonces, el párrafo de Peña 62 no cuadra, muy especialmente lo del orden de complejidad de la ocu -pació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 consideran-do 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 interro-gantes: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 ejecu-ción, siendo más eficiente la versión iterativa por transformación de final.

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 sig-nificativas, 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 al -za por el aumento de instrucciones derivados del bucle FOR.

ÍNDICE

61

Page 62: Programacion 2 - Erratas y Escolios

document.doc

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 ci -ma. 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 ar-gumento 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 me -nor según una constante multiplicativa, no de un orden de complejidad menor. No encuentro justifica-ción para la supuesta mejora en el orden de ocupación de memoria.

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. xDT1.Q(x)R(x,f(x)) El predicado xDT1.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 se-rá una monotupla). Otra manera formal, pero para mi más clara, de escribir lo mismo es:xDT1 .Q(x) y DT2 . R(x,y)

ÍNDICE

62

Page 63: Programacion 2 - Erratas y Escolios

document.doc

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 en-tendemos en el lenguaje ordinario: Un conjunto está ordenado si todos sus elementos los podemos po-ner 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 prece-de al tercero (y a todos sus siguientes)..., siendo la relación 'precede a' una relación de preorden es-

tricto 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 ór-denes. Porque ocurre que no todos los conjuntos son secuenciables de esta manera y sin embargo po -seen 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 pri-mero 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 propie-dades 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 len -guaje descriptivo el que permite alcanzar la abstracción.

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 princi-pio, a cada uno de sus elementos le podemos poner una etiqueta que expresa su posición en la secuen-cia: 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)

63

Page 64: Programacion 2 - Erratas y Escolios

document.doc

ÍNDICE

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

Llamada nº Subvector tamaño del proble-ma

posición

0 [8, 6, 7, 9] 4 cuarto1 [8, 6, 7, 9] 3 tercero2 [8, 6, 7, 9] 2 segundo3 [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 ín-

dice 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). To-dos 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 ex-presa la posición de ese tamaño en la secuencia ordenada de tamaños. Pero es una coincidencia com -pletamente intrascendente (que se producirá con frecuencia cuando se recorre el vector descendente-mente 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)

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 proble-ma

posición

0 [4, 8, 6, 7, 9, 3, 5, 4, 7] 9 sexto1 [4, 8, 6, 7, 9, 3, 5, 4, 7] 7 quinto2 [4, 8, 6, 7, 9, 3, 5, 4, 7] 5 cuarto3 [4, 8, 6, 7, 9, 3, 5, 4, 7] 3 tercero4 [4, 8, 6, 7, 9, 3, 5, 4, 7] 1 segundo

64

Page 65: Programacion 2 - Erratas y Escolios

document.doc

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, pode-

mos 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 suce -sió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, recorra-mos 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 in-

finito, 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 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 infini-tos... espero), entonces ¿Es necesaria esta definición de la 'no-existencia sucesiones infinitas estricta-mente decrecientes'?. Sí, por tres razones: una teórica que esquemáticamente acabamos de ver al ha-blar 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):

65

Page 66: Programacion 2 - Erratas y Escolios

document.doc

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. Pe-ro hay dos problemas: hemos dejado fuera algunos (infinitos) tamaños del problema. Y dos: esa suce-sió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 partien-do 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 funcio-nes lineales, esto es, con una única llamada recursiva interna en el caso no trivial. En todos estos ca -

sos, 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 proble-mas, 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 ejem-plos 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

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)

66

Page 67: Programacion 2 - Erratas y Escolios

document.doc

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 so-bre 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. Par-tiendo 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 se-

gundo 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].

Pero argumentando que tanto [8,3,6,7,4] como [8,3,6,7,4] son tamaños segundos (cada uno de su pri-mero), en absoluto podemos decir [8,3,6,7,4] precede o coincide con [8,3,6,7,4], porque son segun-dos 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.

67

Page 68: Programacion 2 - Erratas y Escolios

document.doc

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 invi-ta a no considerar relacionados a todos aquellos elementos que no estén en una misma sucesión decre -ciente. 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 diferen-tes niveles de la recursión, dos llamadas (o más) con el mismo tamaño, a nuestros efectos pode-mos 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, volva-mos 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 decre-

cientes. Demostrar que cualquier subconjunto no vacío de D posee al menos un minimal (Peña 65, ejerci-

cio 3.5. Puede que sea una manera cómoda de verificación. Yo siempre me olvido de esta posibi-lidad)

Encontrar una aplicación biyectiva desde el conjunto candidato a pbf hacia otro conjunto que se-pamos 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 suficien-

tes):

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:

68

Page 69: Programacion 2 - Erratas y Escolios

document.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 sub -conjunto (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 es-tá 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 reducir-lo. 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 preor-den, 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 real-mente 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 ra -zonamiento 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 longi-tud de la cadena. Entonces seguro que el preorden propuesto es un pbf.

69

Page 70: Programacion 2 - Erratas y Escolios

document.doc

Otra manera sería comprobando si cumple la definición 3.4. Supongamos que las cadenas son de lon-gitud máxima n. Habrá en total 37 + 372 + 373+.... +37n-1 + 372 cadenas diferentes. Una vez ordenadas las cadenas no existen sucesiones infinitas estrictamente decrecientes, pues todas tendrán como máxi -mo 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:

libralex libro, libralex libramiento, desoxirribonucleico lex s, hambre lex hambreClaramente 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 conse-guimos demostrar que eligiendo cualquier palabra del diccionario, podemos recorrer en sentido inver-so 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, inclui-dos el vacío y el propio A), pero ignoro si al decir Partes Finitas de A se está introduciendo algún ma-tiz 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 sen-dos 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

70

Page 71: Programacion 2 - Erratas y Escolios

document.doc

([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. ¿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 rea-les 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 necesa-rio 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 to -dos 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 pa-

res 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, terce-ro...) 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.

71

Page 72: Programacion 2 - Erratas y Escolios

document.doc

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.

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 jN, tal que se verifica i=2·j. Es una triviali-dad, 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 : pode-mos 'ordinar' el (sub)conjunto, diciendo que el 4 es el primero; el 8, el segundo; 12, tercero; 16, cuar-to;... 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 verifi-car que el siguiente a la base (minimal) es también de la forma 2· j?. Claramente no. La hipótesis se establece para cualquier iN 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 demos -tró 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 algu-na 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.

72

Page 73: Programacion 2 - Erratas y Escolios

document.doc

Í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·3hipo) i =3·k = 2·jpaso) i+3= 3·k +3 =2·j +3. Pero esto contradice la hipótesis, porque par + impar = impar.

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 faci-lidad 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 mini-mal 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 ope-raciones algebraicas, que si la propiedad es cierta para el elemento precedente, lo sigue siendo para el elemento i.

73

Page 74: Programacion 2 - Erratas y Escolios

document.doc

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í has -ta el elemento i que nos de la gana.

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 (fun-ción combinación entre la operación actual y la llamada recursiva interna del caso no trivial) y compo-sició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 fi-nal 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 tabla3.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 reali-za 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 signi -ficado de cada uno de los apartados de la tabla.

ÍNDICE

Importante.

74

Page 75: Programacion 2 - Erratas y Escolios

document.doc

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:

{Q1 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)

fcasoffun {R s= {1..i}.a[]}

donde aparecen sombreadas las modificaciones introducidas en la versión ya conocida. Si la somete-mos 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 for -ma un pbf y distintas relaciones lógicas: de los casos entre sí, de las entradas respecto a la precondi -ció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 ha-brí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 do-minio de definición de las variables que determinan el tamaño del problema; en la función sum_pri-mero 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 in-ducción noetheriana, pues, excepto los dos primeros puntos, toda la verificación se basa en que los di-ferentes 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:

{Q1 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)

75

Page 76: Programacion 2 - Erratas y Escolios

document.doc

fcasoffun {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 pri -mero 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 excesiva-mente 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 tu - pla 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 )) (Bt (x) Bnt (x))

B:(1 i) (i=1) (i >1)

ÍNDICE

Tanto el libro como el equipo docente, en consulta telefónica, consideran suficiente esta comproba-ció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 solapa-mientos 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 co-rrecta, 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.

76

Page 77: Programacion 2 - Erratas y Escolios

document.doc

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

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 pre-condició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 precon-dició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))

77

Page 78: Programacion 2 - Erratas y Escolios

document.doc

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 au-xiliar actual, verifica la postcondición.

b)Estando en caso no trivial y suponiendo que la devolución de la llamada recursiva interna verifi -ca la postcondición, entonces, también la verifica la devolución de la llamada actual.

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 llama-da 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 implica-ció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

78

Page 79: Programacion 2 - Erratas y Escolios

document.doc

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 des-de n hasta 1, puede parecer contradictorio que en la hipótesis de inducción (en la postcondición) se di-ga 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 64 de este documento, donde queda claro que la ve-rificación de los puntos 4 y 5 se refiere siempre a la cadena ascendente de combinaciones.

ÍNDICE

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

pletamente 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, en-tonces la función limitadora, para cualesquiera datos de entrada (tamaño del problema), es siem-pre 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

79

Page 80: Programacion 2 - Erratas y Escolios

document.doc

¿ t: a, {1.. n} Z ? | i{1.. n} t(i) 0

Propuesta: t(i) = it(n) = n 0t(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. Ambos conceptos son completamente independientes (aunque en ocasiones tengan expresiones mate-má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 for-ma 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 deci-da 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 orde-nables 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 quinto.

Í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 mi-nimal del problema (el correspondiente al caso trivial), un segundo tamaño, un tercero... (tamaños de-terminados 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.

80

Page 81: Programacion 2 - Erratas y Escolios

document.doc

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', se-gundo '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; terce-ro...), 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.

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 ta-maños del problema no forman un pbf no tiene sentido realizar una demostración por induc - ción ¡que tiene como soporte el pbf de los tamaños del problema! .

81

Page 82: Programacion 2 - Erratas y Escolios

document.doc

Í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 procedimien-to breve:

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

fcasoffun{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

2. Q(x) Bnt (x) Q(s(x)) Sucesor pertenece al dominio, operación de sucesora es interna o lla-mada 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 inter-na: 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 54. 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

82

Page 83: Programacion 2 - Erratas y Escolios

document.doc

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 combi-nació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 79), 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 ple-gado. Postcondición no constante), podemos encontrarnos con finales que poseen una postcondición no constante, dependiente de los parámetros de inmersión (aunque en los casos que veremos, sería in -mediato 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:

{Qcierto}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.

83

Page 84: Programacion 2 - Erratas y Escolios

document.doc

ÍNDICE

Aquí hemos usado directamente desde el principio una especificación inmersora:{Q1 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 subinter-valo [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 neu-tro de la suma. En este supuesto, tenemos que sumF = sum + w, con lo que la primera es más poliva-lente que la segunda.

ÍNDICE

Pag 85. Inmersión finalNo 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 con-duzca directamente a una versión final (sin pasar por una intermedia no final, como en el caso del des-plegado y plegado).

La idea básica consiste partir de la especificación no derivable: {Qcierto}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 don-de ir sumando los diferentes elementos del vector. Además, voy a recorrer el vector descendentemen-te, por lo que w contendrá las sumas de lo ya recorrido –índices comprendidos entre i+1 y n-'. Tradu-ciendo 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

84

Page 85: Programacion 2 - Erratas y Escolios

document.doc

Pag 94. Desplegado y plegadoAdemás del ejemplo que veremos aquí, se puede encontrar un desarrollo muy parecido con una función un po-co 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

{Q1 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)

fcasoffun {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ácti-co.si no se ve el árbol, picar en la barra de herramientas 'Ver', 'Diseño de página'

+

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, co-mo 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án-dola variable índice, pero renombrándola como i. El vector a, también será entrada para la función in-mersora y lo dejamos tal cual.

+

a[i] w sum

85

Page 86: Programacion 2 - Erratas y Escolios

document.doc

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 en-tre 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 renombra-mos al nombre simple i; si era una expresión como a[i], renombramos por un nuevo nombre de varia-ble (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áme-tros 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.

Bien, antes de seguir, un pequeño detalle estético. Por Kodratoff, hemos obtenido la igualdad su-mF(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 izquier-da 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 conse-guirían sería complicar visualmente la transformación

ÍNDICE

86

Page 87: Programacion 2 - Erratas y Escolios

document.doc

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 ex-presándolo en función de sumF.

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 deriva-da a partir de una no final, pero ahora vayamos con el más ¿lógico?.

87

Page 88: Programacion 2 - Erratas y Escolios

document.doc

Para R no hay problema. Será constante, ya que no depende de las variables introducidas en las suce-sivas 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 postcondi-ció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ónVamos 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 bus-camos es:w = {i+1..n}.a[]. Predicado que deberemos añadir a lo ya conocemos:

{QsumF (1 i n) (w = {i+1..n}.a[])} ÍNDICE

Segundo procedimiento bastante más formal. Desdoblando R.

{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 acu-mulació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 subinter-valos. 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).

88

Page 89: Programacion 2 - Erratas y Escolios

document.doc

Desarrollando:

{RsumF 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, tam-bié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 compro-bado. Esto último, en buena lógica, exigiremos que además se cumpla en la precondición:

{QsumF (1 i n) (w = {i+1..n}.a[])}

ÍNDICE

Recopilando todo lo averiguado:

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

fcasoffun

{RsumF s= {1..n}.a[]}

Si ni en el desplegado y plegado ni en la derivación de Q y R hubo errores, esta función final es co-rrecta (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áti -ca 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ísti-ca 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 obtenga-mos por desplegado y plegado)

89

Page 90: Programacion 2 - Erratas y Escolios

document.doc

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 ini-cial 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 ele-mento 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 constanteUna 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 ma-nipulación algebraica que acabamos de ver, quedándonos con lo subrayado:

{RsumF s= {1..n}.a[]} = { R sumF ( {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 precondi-ción. Lo dejamos tal cual en la postcondición, donde de manera implícita sí se establece la precondi-ción para la llamada siguiente.

Incluso, si interesa (o porque se vea mejor), simplemente escribimos

{RsumF ({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.

Personalmente, prefiero con mucho la postcondición constante. La postcondición no constante entien-do que en el fondo no es más que una constante enmascarada; basta realizar una simple operación al-gebraica 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, co-mo el siguiente ejemplo, modificado a partir de uno del profesor Artacho:

{QsumF (1 i n)}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)

fcasoffun

{RsumF s= ({1..i}.a[]) + w}

ÍNDICE

Pag 101. Figura 3.14. Transformación de recursiva final a iterativa .

90

Page 91: Programacion 2 - Erratas y Escolios

document.doc

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 ha-blar 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 re -curso a la chuleta. Pero realizar esta transformación es bastante más sencillo y mecánico de lo que pa-rece y una vez dominada y comprendida, es muy útil memorizarla (no como foto fija, sino como tra -ducción simbólica de la cantinela memorizada). O posiblemente no haga falta memorizarla: la trans-formación sale sola. En un algoritmo recursivo final, cada vez que entramos en caso no trivial se pro-duce una 'iteración virtual': llamamos a la misma función con parte de los argumentos (los de inmer -sió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 es-perar 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), pode-mos 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)

fmientrasdev 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[])}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 >

fmientrasdev a[1] + w

ffun

{R s= {1..n}.a[]}

Nota: iini = n, wini = 0

91

Page 92: Programacion 2 - Erratas y Escolios

document.doc

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 deri -vació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 proble-ma; 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 va-

lores iniciales, nos queda: {Q(1 n n)(0 = {n+1..n}.a[])} que constituye una tau-tologí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 comenta-das. 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 direc-tamente 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 genera-lidad: 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 argu-mentos 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 plantea-mos 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 ver -sió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 abrevia-da de escribir:

92

Page 93: Programacion 2 - Erratas y Escolios

document.doc

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 predi-cado {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 iniciali-zació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 valo-

res 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 co-mo argumentos (a, i, w), en la llamada recursiva interna que se genera, los parámetros que se pa-san 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 po-co 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.

IMPORTANTEEn el último punto que acabamos de ver, surge un problema grave ya anunciado en anteriores esco-lios. 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-1w := a[i]+w

porque veamos que ocurriría en la primera iteración con i=n y w=0:i := n-1w := 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!.

93

Page 94: Programacion 2 - Erratas y Escolios

document.doc

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 va-lor 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 := nw := 0mientras i > 1 hacer

i := i-1w := a[i]+w

fmientrasdev a[1] + w

ffunÍNDICE

claramente erróneo, pues en lo destacado en negrilla tenemos la instrucción avanzar antes que la ins-trucción restablecer (sobre estas dos instrucciones, se discutirá en los escolios Pag 121. Definición4.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 hacerw := a[i]+wi := i-1

fmientrasÍNDICE

De esta manera, se sigue verificando el invariante y recorremos todo el vector (excepto el ele-mento a[1], que en este ejemplo se suma fuera del bucle)

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+1w := 0mientras i > 1 hacer

i := i-1w := a[i]+w

fmientras

94

Page 95: Programacion 2 - Erratas y Escolios

document.doc

dev a[1] + wffun

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 me-cá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:

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

fcasoffun{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 transforma-

95

Page 96: Programacion 2 - Erratas y Escolios

document.doc

ció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]+wi := i-1

fmientras fmientrasdev 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.19Para 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 compren-der 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, decla-rar 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.

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 resta-blecer realizará la combinación que se enuncia en el caso no trivial de la versión recursiva. Como

96

Page 97: Programacion 2 - Erratas y Escolios

document.doc

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 fun-ció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)

fmientrasy := triv(x){P2(x,xini,y)}

mientras x xini hacerx := s-1(x)y := c(y,x)

fmientrasdev 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.../...

97

Page 98: Programacion 2 - Erratas y Escolios

document.doc

{Q1 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

fmientrasS := a[i] (* i = 1*){P2 S = {1..i}.a[]}mientras i iini hacer (* iini = n *)

i := i+1S := S + a[i]

fmientrasdev 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 encomen-dable 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 ta-mañ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 pri-mer 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 varia-bles).

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 usadosA 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 hubie-

98

Page 99: Programacion 2 - Erratas y Escolios

document.doc

ra sido i=0 y que tendría como salida del caso trivial el neutro de la función combinación (en las 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 se-gundo porque en los ExPeDos figuran varios ejemplos en los que usé el criterio de dominio vacío.

ÍNDICE

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

99

Page 100: Programacion 2 - Erratas y Escolios

document.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 es-tados del algoritmo en ese punto. A ese predicado le llamaremos aserto, que se convierte en la pos-tcondició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án-tica 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 va-riable 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 so-bre 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áme-nes. 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

100

Page 101: Programacion 2 - Erratas y Escolios

document.doc

{Q cierto}fun sum_it(a:vector[1..n] de ent) dev s:entvar i: ent; fvar

i := 0s := 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 sffun{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, renombran-do 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:entvar i, s: ent; fvar

i := 0 s := 0 mientras i < n hacer

s := s + a[i+1]i := i +1

fmientras S := s dev Sffun

Estos algoritmos lo acabo de escribir directamente (aunque en lo que sigue usaré el primero), sin reali -zar 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 iniciali -za 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 habla -mos 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 ele-mento de posición i+1 en vez del más cómodo i, porque si fuera de esta manera sumaríamos en la pri-mera 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 elemen-tos 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.

101

Page 102: Programacion 2 - Erratas y Escolios

document.doc

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 ne-cesariamente 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ámbu-los; más de una vez metí el zueco por olvidar trivialidades palmarias :-(

Valores y comprobaciones antes de entrar en bucle

iteraciones del bucle fin de itera-ción

variabl invariante B restablecer avanzari s s=a[].{1..i} i<4 s:= s+a[i+1] i := i+1 R P R0 0 (0=a[].{1..0}) V V s:= 0+8 = 8 i := 0+1= 1 F F1 8 (8=a[].{1..1}) V V s:= 8+6 = 14 i := 1+1= 2 F F2 14 (14=a[].{1..2}) V V s:= 14+7 = 21 i := 2+1= 3 F F3 21 (21=a[].{1..3}) V V s:= 21+9 =30 i := 3+1= 4 V V4 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 rela -ció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 últi-mas columnas. Porque en ese momento es cierto lo que ahí se enuncia, pero hemos de esperar a com -probar 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 reco-rrida 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 normal-mente no será otra cosa que una vulgar variable acumuladora (escondida como argumento de la fun-ció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):

102

Page 103: Programacion 2 - Erratas y Escolios

document.doc

Primero comenzamos especificando R. A partir de ahí, por debilitamiento y teniendo en cuanta la pro-tección que queramos establecer derivamos P. En ese momento, en esquema, tenemos lo siguiente (en negrilla lo que conocemos)

{P}mientras B hacer restablecer {S} (*aserto entre las instrucciones restablecer y avanzar *) avanzarfmientras{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 ins-trucción avanzar. ¿Pero que ocurre así?. Que esta instrucción avanzar rompe la invarianza, estable-ciendo 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}, es-te 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 ejecu-ción desde el principio, con el vector ya conocido a [8, 6, 7, 9]. El fragmento de código que nos in-teresa de la función sum_it es:

i := 0s := 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 80

Ejecutamos avanzar: i := 0 +1fin de iteración

Comprobamos P: 8= a[]. {1..1} Cierto, 8=8.Comprobamos protección…. etc, etc

103

Page 104: Programacion 2 - Erratas y Escolios

document.doc

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 {Q}Inic;mientras B hacer {P}

Sfmientras{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 al-goritmo 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 cam-bio 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 pro-blema; mediante sustituciones textuales, a partir de R derivaríamos el aserto anterior a dicha asig-nación. Aserto que sería P. Si hubiera más de una asignación, se repetiría el proceso de sustitu-ciones 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, con-siderarla 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 frag-mento {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.

104

Page 105: Programacion 2 - Erratas y Escolios

document.doc

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 posi-bles (ninguna variable toma valor indefinido) una manera más clara de ver este punto es P B ¬B

1. P ¬B RSi se cumple el invariante y no se cumple la protección del bucle, entonces se cumple la postcon-dició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én-dose 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 0Siempre 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 es-

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

105

Page 106: Programacion 2 - Erratas y Escolios

document.doc

ÍNDICE

Roberto Alonso MenlleCurso 2001/2002

Alumno del C.A de la UNED en [email protected]

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

106

Page 107: Programacion 2 - Erratas y Escolios

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

107

Page 108: Programacion 2 - Erratas y Escolios

document.doc

T(n) (nk+1),si a = 1T(n) (an div b) ,si a > 1

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.21. 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óte-

sis de inducción). 5. Encontrar t: DT1 Z | Q(x) t(x) 0 (Definición de la estructura de pbf sobre los datos de en-

trada).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)

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

108

Page 109: Programacion 2 - Erratas y Escolios

document.doc

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

fmientrasy := triv(x){P2(x,xini,y)}

mientras x xini hacerx := s-1(x)y := c(y,x)

fmientrasdev 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 R2. {Q}Inic{P}3. {P B}S{P}4. P B t 05. {P B t = T}S{t < T}

ÍNDICE

109