You are on page 1of 10

Estos apuntes son una recopilación y condensación de los apuntes de Jose Manuel

Carrillo, de algunos ejemplos del centro asociado de Vitoria y de algunos problemas


mas resueltos de los facilitados por el equipo docente.

No pretenden tener la profundidad de los creados por ejemplo por Jose Manuel Carillo
( sin los cuales yo no habría conseguido ni medio entender esta asignatura ) y mas son
un breve resumen, tocando solo las partes de creación de funciones recursivas e
iterativas, dejando en el tintero la conversión de recursivo a iterativo y sobre todo el
tema de coste de algoritmos, del que si consigo entender algo también haré un
resumencillo...

Lo que si he intentado es poner ejemplos de las demostraciones de los análisis por casos
y sobre todo de la verificación de funciones, que es lo que –aun sabiendo hacerlo de
manera mecánica – mas me costaba razonar y explicar, siendo esto parte fundamental en
el examen.

Probablemente estén llenos de erratas ( no soy ni mucho menos una autoridad en la


materia ), pero espero que al menos las demostraciones os sirvan de tanto como me ha
servido a mi realizarlas para aprender que además de poder desarrollar
Q(x)^Bnt(x)=>Q(s(x)) también puedes encontrarle un significado...

Como son bastante limitados ( no tenia intención ni de colgarlos, pero creo que pueden
ayudar para resolver alguna duda ) no tengo intención de ir ampliándolos ni nada así, así
que utilizar lo que os valga y lo que no pues ya sabéis que esta mal

1
ESQUEMA DE UNA FUNCION RECURSIVA LINEAL: Final y No Final

{Q≡(x)}
Fun f(x:T1) dev (y:T2)
caso Bt(x)->triv(x)
| Bnt(x)->c(f(s(x)),x)
Fcaso
Ffun
{R≡(x,y)}

Si no es necesario la función combinación en el caso no trivial ( c(f(s(x),x)) entonces la


función es Recursiva Final

Si para calcular la función recursiva hemos debilitado la Poscondición R entonces


obtendremos una función recursiva no final. Si actuamos fortaleciendo la precondición
Q obtendremos una función recursiva final.

ANALISIS POR CASOS Y COMPOSICION:


Realizaremos los siguientes pasos para realizar nuestra función recursiva de manera
directa:
- Buscamos la descomposición recursiva que reduzca más drásticamente el
tamaño del problema.
- Se analizan los casos que puedan darse, identificando los casos triviales y no
triviales, bajo que condiciones se puede considerar un caso trivial y como hay
que tratarlos.
- Asegurarse de que la reducción aplicada en Bnt conduce a problemas más
pequeños que nos conducirán al caso trivial Bt.
- Asegurarse de que entre los triviales Bt y no triviales Bnt se cubren todos los
estados previstos por la poscondición.
- En la etapa de composición asegurarse de que los condicionantes son
mutuamente excluyentes.
- Introducir declaraciones locales para evitar calcular repetidas veces la misma
expresión.

TECNICAS DE INMERSION NO FINAL:


Si queremos obtener nuestra función mediante inmersión no final a partir de una
definición previa del problema actuaremos de la siguiente manera:
NOTA: En los siguientes ejemplos trabajamos con una poscondición:
R≡s=∑α €{1..N}v[α]

- Debilitaremos la poscondición mediante sustitución de constantes por variables


de manera que: R=>R1^R2. Como hemos incluido una nueva variable
(habitualmente i ) en la función, habrá que incluirla en la Precondición. Como
buscamos la precondición más débil, ampliaremos el rango de i, aprovechando
así para obtener el caso trivial.
Ejemplo: R≡s=∑α €{1..N}v[α] => R≡s=∑α €{1..i}v[α] ^ i=N
R1 R2

2
- En el análisis por casos, como el caso trivial ya lo tenemos ( desarrollar que
tiene que devolverse en el caso trivial Bt: anulación del cuantificador, elemento
neutro, etc... )
Ejemplo: Si i=0 entonces s=∑α €{1..0}v[α] en este caso al anularse el rango
del sumatorio ( al ser sumatorio de un conjunto vació ) obtenemos 0, siendo
este el elemento neutro del sumatorio.
- Para calcular el caso Bnt realizaremos los siguiente pasos:
- Realizaremos una llamada recursiva pero con el tamaño del problema reducido.
- El resultado debe de cumplir la poscondición R y es la combinación de f(x)
y f(s(x))
f(v,i-1)=f(s(x))=s`=∑α €{1..i-1}v[α]
f(v,i)=f(v,i-1)+v[i]
por lo tanto cuando Bnt -> c(f(s(x),x)=f(v,i-i)+v[i] o s=s`+v[i]
o mas exacto:
s`=∑α €{1..i-1}v[α] ( llamada recursiva )
s=∑α €{1..i}v[α] (poscondición R)
para que s`=s le falta el termino v[i] osea s=s`+v[i] lo que satisfacerla la
poscondición, siendo esta la solución en el caso no trivial. Demostración:
s=v[1]+v[2]...v[i-1]+v[i] s`=v[1]+v[2]...+v[i-1]
esta claro que a s` le falta v[i] para ser s y satisfacer la poscondición.
- Definiremos la llamada inicial y la relación entre la original y la sumergida
isuma(v,N)=suma(v)

TECNICAS DE INMERSION FINAL:


Básicamente en la precondición pediremos que parte del trabajo se de “de hecho”
mediante un parámetro acumulador y seguiremos con los siguientes pasos:
- Actuamos como antes, pero R1 lo pasamos a la precondición como parámetro
acumulador y mantenemos R como poscondición Ej:
{Q≡ (0<=i<=N ) ^ (r=∑α €{1..i}v[α]) }
Fun iisuma (v:vector [1..N] de enteros;i:natural;r:entero) dev (s:entero)
{R≡ s=∑α €{1..N}v[α])}
- Análisis por casos:
Caso trivial: Pues sencillo, en Bt devolveremos r ya que en dicho caso r será el
total de todas las operaciones anteriores.
Demostración:
Si i=N r= ∑α €{1..i=N}v[α]) ya que r= v[1]+v[2]+..v[N-1]+v[N]
Caso no trivial: En el caso no trivial lógicamente tendremos que acercarnos
progresivamente al caso trivial, por lo que realizaremos llamadas recursivas
reduciendo el problema. En este caso por ejemplo mientras i<N entonces
devolveríamos i<N -> iisuma(v,i`,r`) donde si i`=i+1 ( acercándonos al caso
trivial ) lógicamente r`=r+v[i+1] ( acumulado anterior mas el de la ejecución
actual con lo que podemos verificar la Poscondición ) quedando la llamada en el
caso no recursivo i<N -> iisuma(v,i+1,r+v[i+1])
Demostración:
Al llamar a iisuma (v,i+1,...) tenemos que r en esta llamada es
∑α €{1..i+1}v[α]
En la ejecución anterior por lo tanto habría que devolver
∑α €{1..i+1}v[α]= ∑α €{1..i}v[α]+v[i+1]. Como ∑α €{1..i}v[α] =r entonces
r`=r+v[i+1]
- Definiremos la llamada inicial como isuma2(v,0,0)=suma(v)

3
PLEGADO Y DESPLEGADO:
Nos permite convertir una función recursiva no final en recursiva final:

PASOS:
GENERALIZACION: Definimos una función generalizadora g(x,w) como
expresión dependiente de f(x) y en la que fijamos un valor de (w) para los cuales
g(x,w) se comporta como f(x)

DESARROLLAMOS EL ARBOL SINTACTICO: Desarrollamos el árbol sintáctico


a partir del caso no trivial. Ej: caso i>0 -> isuma (v,i-1)+v[i]

PASO 1 PASO 2 PASO 3


+ + +

Isuma v[i] Isuma IIsuma r

v i-1 v i

Donde en el paso 3: iisuma(v,i,r)=isuma(v,i)+r


Y donde la llamada inicial en este caso siendo una suma, el elemento neutro seria el
0 por lo que iisuma(v,i,0)=isuma(v,i)

DESPLEGAMOS: Una vez desarrollado el árbol sintáctico realizaríamos el º


desplegado, donde:

Sustituimos isuma por su desarrollo:


Iisuma(v,i,r)= caso i=0 -> 0
| i>0-> isuma(v,i-1)+v[i] +r

Desplegamos:
Iisuma(v,i,r)= caso i=0 -> 0 +r
| i>0-> isuma(v,i-1)+v[i] +r

Reducimos:
Iisuma(v,i,r)= caso i=0 -> r
| i>0-> isuma(v,i-1)+(v[i]+r)

Plegamos:
Iisuma(v,i,r)= caso i=0 -> r
| i>0-> iisuma(v,i-1,v[i]+r)

Y como habíamos dicho anteriormente la llamada inicial será:


Iisuma(v,N,0)=isuma(v,N) ya que 0 es el elemento neutro del sumatorio

4
CORRECCION DE PROGRAMAS RECURSIVOS:

Hay que verificar formalmente la corrección de la función desarrollada.


Para ello hay que seguir los siguientes 6 pasos:

1) COMPLETITUD DE LA ALTERNATIVA: Q(x)=>Bt(x) OR Bnt(x)


Esto quiere decir que entre los casos triviales y no triviales se dan todos los casos
admitidos por la precondición.
EJEMPLO:
{Q≡ (0<=i<=N) } Bt=i=0 Bnt=i>0 sustituimos:
0<=i<=N => i=0 OR i>0 En este caso esta claro que todos los valores de i estan
contemplados.

2) SATISFACCION DE LA PRECONDICION EN LA LLAMADA INTERNA:


Q(x) ^ Bnt(x) => Q(s(x))
Esto quiere decir que verificamos que en el caso no trivial, la llamada recursiva se
realizara con parámetros que satisfagan la precondición.
EJEMPLO NO FINAL:
{Q≡ (0<=i<=N) } Bnt=i>0 {Q(s(x))= (0<=i-1<=N)}
(0<=i<=N} ^ i>0 =>(0<=i-1<=N)
Se puede afirmar que al ser i>0 i-1 será <=0 y también que i-1<N por lo tanto se
cumple la precondición en la llamada al estar i entre los valores admitidos.

EJEMPLO FINAL:
{Q≡ (0<=i<=N) ^(r=∑α €{i+1..N}v[α])} Bnt=i>0 {Q(s(x)) ≡ (0<=i-1<=N) ^(r=∑α €{i..N}v[α])}

Sustituimos:
(0<=i<=N) ^(r=∑α €{i+1..N}v[α]) ^ i>0 =>(0<=i-1<=N) ^(r=∑α €{i..N}v[α])

Por un lado comprobamos que se cumple la precondición en la siguiente


llamada a i siendo claro en este ejemplo que 0<=i-1 <=N

(0<=i<=N) ^(r=∑α €{i+1..N}v[α]) ^ i>0 =>(0<=i-1<=N) ^(r=∑α €{i..N}v[α])

Por otro lado comprobamos el acumulador donde podemos ver que:


r=∑α €{i+1..N}v[α]) si sumamos v[i] en ambos lados obtenemos que:
r+v[i]= ∑α €{i+1..N}v[α])+v[i] o r+v[i]= ∑α €{i..N}v[α]) siendo este lado Q(s(x))
quedando demostrado que:
r+v[i]= ∑α €{i..N}v[α])=v[i]+v[i+1]...v[N]=Q(s(x)) que es lo que queríamos
demostrar.

5
3) BASE DE INDUCCION: Q(x) ^ Bt(x) =>R(x,triv(x))
Se trata de comprobar que en el caso trivial el predicado R nos devolvera el valor
fijado como trivial y se cumple la poscondición

EJEMPLO NO FINAL:
{Q≡ (0<=i<=N) } Bt=i=0 {R≡s=∑α €{1..i}v[α] }
(0<=i<=N) ^ i=0 => s=∑α €{1..0}v[α]
Cuando i=0 -> 0
Cuando i=0 R≡s=∑α €{1..0}v[α]=0 ya que se anula el rango del cuantificador.

EJEMPLO FINAL:
{Q≡ (0<=i<=N) ^(r=∑α €{i+1..N}v[α]) Bt=i=0 {R≡s=∑α €{1..i}v[α] }
(0<=i<=N) ^(r=∑α €{i+1..N}v[α]) ^ i=0 => s=∑α €{1..i}v[α]
Esta claro que r=∑α €{0+1..N}v[α])=>s=∑α €{1..i}v[α]
Quedando que r=s devolviendo r en el caso trivial que es s

4) PASO DE INDUCCION: Q(x) ^ Bnt(x) ^ R(s(x),y)=>R(x,c(y,x))


Consiste en comprobar la poscondición para los casos recursivos a partir de la
suposición de que la poscondición se cumple para los casos de la llamada interna. A
la expresión R(s(x),y) se le llama HIPOTESIS DE INDUCCION.

EJEMPLO NO FINAL:
Q(x)≡ (0<=i<=N) 1
Bnt(x) ≡ (i<0) 2
R(s(x),y) ≡s`=∑α €{1..i-1}v[α] 3
c(y,x)=s`+v[i]
R≡s=∑α €{1..i}v[α]

Cumpliéndose 1, 2 y 3 verificamos que:


s=∑α €{1..i}v[α]= v[1]+v[2]+..v[i-1]+v[i] = v[1]+v[2]+..v[i-1]=s`+v[i]
demostrando que s=s`+v[i] o dicho también que s=c(y,x) que es lo que
queríamos demostrar.

EJEMPLO FINAL:
En este caso prácticamente no hay nada que comprobar debido a que llamada
interna devuelve directamente y`que cumple la poscondición R ya que no se
realiza la funcion “combinación” adicional. O la manera elegante de decirlo:
SI POR HIPOTESIS DE INDUCION SE CUMPLE R(s(x,y)) AL NO EXISTIR
OPERACIÓN ADICIONAL SE CUMPLE LA POSCONDICION

6
5) ELEGIR UNA EXTRUCTURA DE PBF: Q(x)=>h(x)>=0
Se trata de encontrar una función de los parámetros de entrada en los enteros no
negativos. Como el pbf mas conocido es el de los números naturales, muchas veces
es posible dotar a un conjunto de una relación de pbf estableciendo una aplicación
en el conjunto de los naturales N.

EJEMPLO en i decreciente:
Si Q(x)=0<=i<=N podemos elegir h(x)=i o sea h(v,i) estando claro que para
cualquiera de los i aceptados por Q se cumple que i>=0

EJEMPLO en i creciente:
Si Q(x)=0<=i<=N e i va en incremento podemos elegir h(x)=N-i, o sea
h(v,i,r)=N-i cumpliéndose N-i>=0

En ambos casos intentaremos ir disminuyendo h hacia 0, facilitándonos la


siguiente comprobación, demostración del decrecimiento de los datos.

6) DEMOSTRACION DEL DECRECIMIENTO DE LOS DATOS:


Q(x) ^Bnt(x)=>h(s(x))<h(x)
Hay que demostrar que en cada llamada recursiva la operación previa hace decrecer
estrictamente los datos en relación al pbf previamente definido. Esto garantiza que
la sucesión de las llamadas lleva al caso trivial.

EJEMPLO en i decreciente:
(0<=i<=N) ^i>0 =>h(v,i-1)<h(v,i)
Siendo h(x)=i y h(s(x))=i-1 esta claro que i-1<i

EJEMPLO en i creciente:
(0<=i<=N) ^ i<N=>h(v,i+1)<h(v,i)
Habiendo fijado en el punto anterior h=N-i y siendo h(s(x))=N-1-i esta claro
que N-1-i<N-i

7
DISEÑO ITERATIVO DE PROGRAMAS

La otra variante de diseño que nos encontramos es el diseño iterativo. Una función
iterativa puede tener instrucciones condicionales, de asignación, etc..., pero su esquema
básico en la mayoría de los ejemplos y exámenes consta de una asignación de variables,
un bucle, una secuencia “avanzar”, una secuencia “restablecer” y su esquema básico es
el siguiente:

ESQUEMA BASICO EJEMPLO

{Q} {Q≡cierto}
Inic; fun sumait(v:vector[1..N] de ent) dev (s:ent)
mientras B hacer {P} var i: nat,s:ent fvar
S <s,i>:=<0,0>
Fmientras mientras i≠N hacer {P≡s=∑α€{1..i}· v[α])}
{R} s:=s+v[i+1]; // restablecer
i:=i+1; // avanzar
fmientras
dev s
ffun
{R≡s=∑α€{1..N}· v[α]}

INSTRUCCIONES BASICAS

INSTRUCCIONES CONDICIONALES

Si B entonces S1 sino S2 fsi


Básicamente indica que si se cumple el predicado B entonces realiza la secuencia de
instrucciones S1 si no realizara S2. Estructura:
{Q}
Si x<0 entonces x:=x+1 sino x:=x-1 fsi
{R=x>=0}

INSTRUCCIONES ITERATIVAS: INVARIANTES

Mientras B hacer S fmientras


Esta estructura describe la estructura básica de bucle conocida en programación, lo
único es que define una condición de ejecución del bucle B y un predicado P que
describe todos los estados por los que atraviesa el computo realizado por el bucle justo
antes de evaluar la condición B de terminación. Este predicado se llama
INVARIANTE. El INVARIANTE se satisface antes de la primera iteración,
después de cada una de ellas y en especial a la finalización del bucle ( cuando
B=falso o mas utilizado ¬B ). Por lo tanto se puede definir: R≡P^¬B.

Para garantizar además que si termina lo haga en P^¬B, tenemos que encontrar una
Función Limitadora que asegure que efectivamente termina el bucle ( normalmente
nos basaremos en B para encontrarla ) y la usaremos luego en la verificación formal de
la función que hemos desarrollado. Debemos construir una función t que decrezca y
además que se mantenga no negativa mientras se den P^B

8
DERIVACION DE BUCLES:

Utilizaremos básicamente 2 técnicas para la derivación iterativa de bucles:

DERIVACION A PARTIR DEL INVARIANTE:


1) Conociendo el invariante P ( si no lo tenemos luego veremos como obtenerlo )
determinaremos B a partir de ¬B.
2) A partir del Invariante P se determinaran las instrucciones de inicialización
que hacen que {Q}Inic{P} siendo estas lo mas simple posibles. Aquí es donde
encajan la creación de variables y su asignación de valores.
3) Derivamos el cuerpo del bucle S incluyendo al final del mismo una instrucción
“avanzar” que progrese el bucle hasta su terminación, a su vez, “avanzar” ha
de decrecer la función limitadora t.
4) Como “avanzar” rompe la invarianza de P incluimos una función
“restablecer” para que esta se siga cumpliendo.

Ejemplo de restablecer:
Si avanzar es i:=i+1 y sabemos que P≡s=∑α€{1..i}· v[α] entonces
s `,i +1
sustituyendo valores:{P^B}=> P s ,i 1
Si s=∑α€{1..i}· v[α] entonces s`=∑α€{1..i+1}· v[α]
Despejando s` vemos: s`=∑α€{1..i+1}· v[α] => s`=∑α€{1..i}· v[α]+ v[α+1]

s
Por lo tanto vemos que s`=s+v[α+1] y por lo tanto sabemos que la
operación restablecer ha de ser s:= s+v[α+1] y con ello garantizamos la
invarianza en la siguiente ejecución del bucle o a la salida de el si se diera
¬B tal y como nos pide la poscondición.

DERIVACION DEL INVARIANTE A PARTIR DE LA POSCONDICION:


Esta parte es relativamente sencilla. Partiendo de la poscondición R eliminaremos una
conjunción utilizándola como B y el resto de la poscondición como P o si no se puede o
no existe, incorporaremos una variable a la poscondición R y así obtendremos la
conjunción que necesitamos para obtener P y B.

Ejemplo a partir de R≡s=∑α€{1..N}· v[α]


Introducimos una variable i con lo que nos quedaría R≡s=∑α€{1..i}· v[α] ^ i=N
Igual que hicimos con las funciones recursivas debilitando la poscondición
habríamos obtenido R1 y R2 que usaríamos como B y P en este caso ( y negando B
para que sea MIENTRAS i≠N en este caso ).

1
Este simbolito indica que el valor de abajo se sustituye por el de arriba, en el ejemplo s=s` e i=i+1.
Conociendo s y conociendo i y conociendo i+1, comprobamos, verificamos u obtenemos s`

9
VERIFICACION FORMAL DE FUNCIONES ITERATIVAS:

Igual que se realizo en el diseño recursivo, debemos demostrar que se cumplen ciertas
condiciones para garantizar el funcionamiento de nuestras funciones iterativas.
Realizaremos 5 pasos ( más uno previo de preparación ).

0) Fijamos la invariante ( obtenida en la etapa de diseño ) y la función limitadora


t(x) que yo recomiendo obtener de una forma sencilla. Si la instrucción
“avanzar” consiste en ir incrementando (partiendo habitualmente de 0 )
t(x)=N-i ( donde N seria el tope del bucle en caso de un vector por ejemplo e i
la variable que nos hemos fijado para recorrerlo ) o en caso de que la
instrucción “avanzar” sea decrementar entonces t(x)=i.
1) P^¬B=>R: Siempre será cierta por construcción, aunque podemos desarrollarlo
para demostrarlo:
∑α€{1..i}· v[α] ^ ¬( i≠N) =>∑α€{1..N}· v[α] desarrollando =>
∑α€{1..i}· v[α] ^ i=N =>∑α€{1..N}· v[α] => Esta claro que si i=N son
idénticos.
2) {Q}Inc.{P}: Sustituimos y vemos que en el caso anterior:
i=0=>s=∑α€{1..0}· v[α] razonando vemos que efectivamente cuando i=0 s=0
debido a que el sumatorio del conjunto vació es 0 ( INTENTAR MEJORAR
ESTA EXPLICACION )
3) {P^B}S{P}: Si hemos calculado restablecer a partir de esta propiedad, no seria
necesario demostrar nada, pero aun así intentamos demostrar:

1 {P^B}≡∑α€{1..i}· v[α] ^ 0<=i<N


2 si ejecutamos avanzar y restablecer, P` será: s`=∑α€{1..i+1}· v[α]+ v[α+1]
s `,i +1
por lo tanto sabemos que :{P^B}=> P s ,i
4) P^B=>t>=0: Como la función limitadora la hemos calculado en función de i de
manera que se te decremente durante la ejecución del bucle, otra vez mas es
correcta por diseño. De todos modos podemos explicar:
Si t=N-i puesto que el valor inicial de i=0 y va aumentando hasta llegar a N
esta claro que t>=0
5) {P^B^t=T}S{t<T}: Si hemos obtenido la función limitadora de la manera
indicada esta claro que se cumple, pudiendo añadir la explicación:
si t y T=N-i y tras el bucle t=N-i-1 ya que avanzar incrementa i en 1 (( y por
lo tanto reduce en 1 t ) obtenemos que t=n-i-1<T=n-1 por lo que
efectivamente se cumple t<T

10

You might also like