Tratamiento Secuencial M3
Tratamiento Secuencial M3
Tratamiento Secuencial M3
secuencial
Esquemas de recorrido y bsqueda
Jordi lvarez Canal
Xavier Burgus Illa
PID_00149891
FUOC PID_00149891
Tratamiento secuencial
ndice
Introduccin........................................................................................... 5
Objetivos .................................................................................................. 6
1. Algoritmos y secuencias .................................................................
12
12
13
14
2.4. Metodologa...................................................................................
15
16
16
18
21
21
22
23
3.4. Metodologa...................................................................................
24
24
24
28
30
30
30
4.1.2. Especificacin.....................................................................
31
31
33
33
4.2.2. Especificacin.....................................................................
34
35
36
36
37
38
5. Combinacin de esquemas.............................................................
40
5.1. Planteamiento................................................................................
40
40
40
42
FUOC PID_00149891
Resumen................................................................................................... 44
Ejercicios de autoevaluacin .............................................................. 49
Solucionario............................................................................................ 50
Glosario .................................................................................................... 52
Bibliografa ............................................................................................ 53
Tratamiento secuencial
FUOC PID_00149891
Tratamiento secuencial
Introduccin
En el mdulo anterior hemos aprendido a especificar un problema, hemos introducido la nocin de algoritmo que lo resuelve y hemos introducido el lenguaje algortmico, que nosotros utilizaremos para formular algoritmos. Todava
no hemos hablado de cmo llegar desde la especificacin de un problema hasta
el algoritmo que lo resuelve. En este mdulo nos ocuparemos de este tema. No
introduciremos aqu ningn elemento nuevo del lenguaje algortmico, sino
que, utilizando los elementos que ya conocemos, proporcionaremos una
metodologa que nos permita disear algoritmos de la forma ms sistemtica
posible.
Ciertamente, podramos intentar disear algoritmos de un nivel de complejidad similar a los ejemplos que aparecen en el mdulo 1. Sin embargo, las nicas
herramientas de las que disponemos hasta el momento son nuestra capacidad
creativa para combinar adecuadamente las construcciones del lenguaje algortmico y nuestra experiencia (que, por el momento, es poca), como ayuda inestimable para la primera.
El estudio de un conjunto de casos concretos que incremente nuestra experiencia podra ser una solucin, pero el aprendizaje por esta va puede ser bastante
costoso y, a un tiempo, incompleto. Por el contrario, en este curso proponemos
una metodologa lo ms sistemtica posible para disear algoritmos. A pesar de
todo, igualmente seguiremos necesitando nuestra capacidad creativa; aunque
una vez asimilada esta metodologa, el agujero que debemos llenar con nuestra creatividad ser bastante menor.
FUOC PID_00149891
Objetivos
Despus de haber estudiado este mdulo, tendris que haber alcanzado los siguientes objetivos:
1. Entender la nocin de secuencia como elemento bsico para el diseo de
algoritmos.
2. Conocer los esquemas bsicos de tratamiento secuencial y ser capaz de identificar los problemas a los que se pueden aplicar.
3. Dado un problema al que podemos aplicar los esquemas, saber averiguar la
secuencia subyacente. En ocasiones, esto resulta bastante evidente, como
veremos por ejemplo en el apartado correspondiente a las secuencias de
entrada y salida. Sin embargo, otras veces no lo es tanto.
4. Saber elegir adecuadamente el esquema que hay que aplicar para resolver
un problema concreto.
5. Obtener prctica refinando esquemas para problemas concretos. De este
modo, una vez hayis averiguado la secuencia y decidido el esquema que
hay que aplicar, el refinamiento debe ser una tarea bastante mecnica.
6. Identificar los problemas en los que hay que hacer varios tratamientos secuenciales para obtener un algoritmo que los solucione. Ser capaces de combinar los esquemas adecuadamente para hacerlo con xito.
Tratamiento secuencial
FUOC PID_00149891
1. Algoritmos y secuencias
De este modo, por ejemplo, un posible algoritmo del brazo robot podra ser el
siguiente:
Al principio de la jornada de trabajo hace falta que algn operario lo ponga
en marcha, lo cargue de pintura si no tiene la suficiente y compruebe que
el brazo funciona correctamente.
Una vez hecho esto, el brazo robot se pone a funcionar. Su trabajo es muy
simple. Espera a que llegue un automvil y lo pinta. Entonces se detiene y
espera a que llegue el siguiente automvil.
Fijmonos en que el brazo robot repite las mismas operaciones para cada automvil. La cinta mecanizada se encarga de ir ponindole los automviles delante para que el brazo robot acte. Entonces, la dificultad del algoritmo del
brazo robot se limita a pintar un automvil, mientras que la cinta mecanizada
se encarga de gestionar la secuencia de automviles.
Notad que esta organizacin supone dos grandes ventajas: en primer lugar, el
algoritmo del brazo robot es ms sencillo que si l mismo tuviese que gestionar
la secuencia de automviles (seguro que el brazo robot tendra que ser ms
complejo; por ejemplo, debera tener capacidad de movimiento). En segundo
lugar, podemos volver a utilizar la cinta mecanizada para otras cosas distintas.
Por ejemplo, junto al robot pintor podemos situar otro que ponga los intermitentes del lado izquierdo. La estructura secuencial de algoritmo es la misma, y
Tratamiento secuencial
FUOC PID_00149891
Tratamiento secuencial
lo nico que cambia es el tratamiento que le damos a cada automvil (un robot pinta mientras el otro pone intermitentes).
Lo que pretendemos en este mdulo es trasladar todo esto al mundo de la algortmica. En los siguientes temas proporcionaremos algoritmos genricos
que nos permitirn trabajar con secuencias. Estos algoritmos genricos se denominan esquemas, y llevarn a cabo un trabajo equivalente al de la cinta mecanizada.
Entonces, dado un problema que queremos solucionar, si somos capaces de plantear un algoritmo que lo solucione en trminos de tratamiento de una secuencia,
slo tendremos que aplicar el esquema adecuado a partir de este planteamiento.
Esto nos permitir centrar nuestro trabajo creativo nica y exclusivamente en dos
puntos (de forma similar al problema de pintar automviles):
a) El planteamiento inicial del algoritmo como un algoritmo de tratamiento
secuencial.
b) El tratamiento de un elemento de la secuencia.
Este hecho simplificar de modo importante nuestro trabajo. Adems, el hecho de partir de unos algoritmos genricos que nos sirven para muchas situaciones y que ya sabemos que son correctos har que equivocarnos sea bastante
ms difcil.
Antes de plantear los esquemas, veamos cmo podemos modelizar un algoritmo de tratamiento de una secuencia. Partamos de un ejemplo que ya conocemos: el clculo del factorial. Recordemos su algoritmo:
algoritmo factorial
var
i, n, fact := entero;
fvar
n := leerEntero();
{ Pre: n = N y N 0 }
fact := 1;
i := 1;
mientras i n hacer
fact := fact * i;
i := i + 1;
fmientras
{ Post: n = N, N > 0, fact es el factorial de i 1 e i = n + 1, por lo tanto, fact es el factorial de N }
escribirEntero(fact);
falgoritmo
Cul es aqu la secuencia de elementos tratados? La que est formada por los
nmeros enteros entre 1 y n. Notad, sin embargo, que mientras el algoritmo del
brazo robot ya se encuentra con una secuencia predeterminada de automviles
(aquellos que estn en la cinta mecanizada), el algoritmo del factorial genera por
s mismo la secuencia. Para hacerlo, hemos utilizado la variable i, que ir visitando cada uno de los elementos de la secuencia, de forma que:
Al principio nos situamos en el primer elemento de la secuencia (i := 1).
FUOC PID_00149891
Tratamiento secuencial
Al final del cuerpo del mientras, la instruccin i := i + 1 nos sirve para pasar
del elemento de la secuencia que ya hemos tratado al siguiente elemento
(que trataremos en la siguiente iteracin si no hemos llegado ya al final de
la secuencia). As pues, con esta instruccin pasamos del primer elemento
(la variable i tiene por valor 1) al segundo elemento (i pasa a valer 2). Posteriormente pasaremos del segundo elemento al tercero (i vale 3). Y as hasta llegar al final de la secuencia.
Finalmente, la condicin del mientras (i n) ser verdadera mientras la variable i se corresponda a un elemento de la secuencia que hay que tratar.
La condicin ser verdadera para todos los valores de i entre 1 y n; es decir,
para todos los elementos de la secuencia.
Y cul es el tratamiento que le damos a los elementos de la secuencia? En este
caso consiste en calcular el producto de todos ellos. Por eso nos hace falta una
variable (fact) en la que vayamos acumulando todos los productos intermedios.
Podemos ver que realizamos dos operaciones diferentes con fact:
En primer lugar, inicializamos fact a 1. Esto es necesario ya que, como nos
suceder en la inmensa mayora de los casos, el tratamiento es acumulativo
y necesitamos partir de unos valores iniciales (en este caso, damos a fact el
valor del elemento neutro del producto, que nos permitir ir acumulando los elementos que hay que tratar). Esta parte se corresponde a lo que
llamaremos inicializar el tratamiento.
En segundo lugar, ya dentro del cuerpo del mientras, multiplicamos fact por
i; es decir: tratamos el elemento actual (correspondiente al valor de la i). Finalmente, cuando los hayamos tratado todos, fact contendr el factorial de n,
que est definido como el producto de los nmeros enteros entre 1 y n (siendo n mayor que 0).
Fijaos en que la forma de trabajar es la misma que habamos visto al principio
con el brazo robot:
1) Hay un conjunto de acciones correspondientes al inicio del tratamiento y
la preparacin de la secuencia.
2) Hay otro grupo de acciones (en el caso del factorial, es una nica accin)
que nos sirve para tratar un elemento de la secuencia.
3) Una vez tratado un elemento, se pasa al siguiente elemento de la secuencia
(o bien llegamos al final de la secuencia si no quedan elementos por tratar).
Debemos tener en cuenta, por un lado, que los elementos se tratan de forma
ordenada (en primer lugar, se trata el primer elemento de la secuencia, despus
el segundo, el tercero, etc. hasta llegar al ltimo). Por otro lado, y esto es bas-
Notad que...
... i llega a valer n + 1. De todos
modos, la condicin del mientras evala a falso para este
valor, ya que no es un elemento de la secuencia que hay que
tratar. Por lo tanto, el ltimo
elemento tratado se corresponde a n.
FUOC PID_00149891
10
Tratamiento secuencial
Se trata justamente...
... de la misma idea de los cabezales de los reproductores de
cintas magnticas (video, casete,
etc.); el cabezal est situado en el
fragmento de cinta que estamos
viendo, escuchando, etc. en un
momento determinado.
11
FUOC PID_00149891
Algoritmo
i :=1;
fact :=1;
mientras i n hacer
Estado
Despus de las
inicializaciones
(el cabezal apunta
al primer elemento)
Despus de tratar
al primer elemento
(el cabezal apunta al
segundo elemento)
mientras i n hacer
fact := fact i;
i := i + 1
fmientras
Despus de tratar al
segundo elemento
(el cabezal apunta
al tercer elemento)
Despus de tratar
al tercer elemento
(el cabezal apunta
al cuarto elemento)
...
fmientras
escribirEntero(fact);
Secuencia y cabezal
Tratamiento secuencial
Variables del
tratamiento
fact = 1
fact = 1
fact = 2
fact = 6
Habiendo tratado
toda la secuencia
(el cabezal est
en la posicin extra
de final de la sec.)
fact = 24
FUOC PID_00149891
12
Tratamiento secuencial
algoritmo EsquemaRecorrido
prepararSecuencia
inicioTratamiento
{ El cabezal est al principio de la secuencia. No hemos tratado ningn elemento de sta }
Posteriormente,...
... en el apartado de secuencias
de entrada y salida veris
ejemplos de tratamiento de
secuencias donde al principio
no conoceremos su nmero
de elementos.
FUOC PID_00149891
13
2.2. Refinamiento
Las partes del esquema que deben ser sustituidas cuando lo refinamos para resolver un problema concreto son las siguientes:
Tratamiento secuencial
FUOC PID_00149891
14
Tratamiento secuencial
condicin del mientras es no finSecuencia), que es lo que aparece en el algoritmo. Fijaos en que cuando n es 0 (la secuencia no tiene elementos) finSecuencia ya es cierto la primera vez que se evala.
tratarElemento: corresponde al conjunto de acciones que llevan a cabo el tratamiento de un elemento de la secuencia. En el caso del factorial, esto consiste en guardar en fact el producto del mismo fact por el elemento tratado.
avanzarSecuencia: corresponde a un conjunto de acciones que avanzan el cabezal una posicin. Es decir, o bien pasan al siguiente elemento o, en el caso
en que el cabezal apunte al ltimo elemento de la secuencia, sitan el cabezal al final de la secuencia (recordad que hay una posicin especial ms all
del ltimo elemento). En el caso del factorial, dado que la secuencia tratada
es una secuencia de enteros donde los elementos corresponden a los diferentes valores que va tomando la variable i, se incrementa el valor de i en 1.
tratamientoFinal: corresponde a un conjunto de acciones que nos permite obtener el resultado del algoritmo a partir del tratamiento realizado. En algunas
ocasiones, este conjunto de acciones ser vaco; es decir, el tratamiento realizado ya nos dar directamente el resultado obtenido y, por lo tanto, no ser
necesario efectuar tratamiento final. En el caso del factorial, no tenemos tratamiento final.
2.3. Especificacin
Reflexionemos un poco ahora sobre la correccin del esquema que acabamos
de presentar estudiando su especificacin. Este apartado queda fuera del mbito de la asignatura, pero os puede ayudar bastante en la asimilacin del esquema de recorrido.
El invariante del mientras (y tambin del recorrido) corresponde a: hemos
tratado la parte de la secuencia que est a la izquierda del cabezal. El caso concreto del invariante antes de empezar a iterar es: el cabezal est al principio
de la secuencia; y despus de hacer todo el tratamiento: hemos tratado toda
la secuencia y el cabezal est al final, algo que nos debe permitir, junto con
el tratamiento final, asegurar que la postcondicin se cumple.
Al igual que las diferentes partes del esquema, el invariante tambin se debe
refinar para cada problema concreto al que apliquemos el esquema de recorrido. Es decir, el invariante general que aparece en el esquema se debe concretar
y debe describir el tratamiento realizado sobre la parte de la secuencia que est
a la izquierda del cabezal.
De este modo, por ejemplo, en el caso del factorial, el invariante se concretara
como: fact contiene el producto de los nmeros tratados hasta el momento.
Si quisiramos concretar ms an, aadiendo el hecho de que i se corresponde
Como ejemplo...
... de algoritmo con tratamiento final, pensad por ejemplo
en un algoritmo que calcula
la media de una secuencia de
nmeros. Lo ms directo
sera calcular la suma de los
nmeros (al mismo tiempo
que los contamos); y una
vez hecho esto (que sera el
tratamiento), dividir la suma
por la cantidad de nmeros
(tratamiento final).
FUOC PID_00149891
15
Tratamiento secuencial
ste es el motivo...
... por el que no es necesario
saber cuntos elementos tiene
la secuencia para garantizar la
finalizacin del algoritmo. En
cambio, s es necesario asegurar que la secuencia es finita.
2.4. Metodologa
La metodologa que utilizaremos para disear algoritmos empleando esquemas
de tratamiento secuencial es la siguiente:
1) Especificar el problema que queremos resolver.
2) Modelizar el problema como un problema de tratamiento secuencial. Descubrir cul es la secuencia que hay que tratar para resolver el problema.
3) Elegir el esquema adecuado que debemos aplicar. En este momento, slo
conocemos uno, pero en el siguiente apartado estudiaremos el esquema de
bsqueda. A veces este paso y el anterior pueden estar bastante relacionados.
4) Decidir el conjunto de variables que nos debe permitir solucionar el pro-
De entrada...
... nos podra parecer que
aplicar esta metodologa para
resolver problemas como el del
factorial puede ser como matar
moscas a caonazos. La utilidad del uso de los esquemas se
va haciendo ms evidente a
medida que la complejidad
de los problemas que hay que
resolver va aumentando.
blema (es fcil que, aparte de las variables de entrada y de salida, nos hagan
falta algunas ms para efectuar los clculos necesarios y para visitar los elementos de la secuencia). Este paso se relaciona con el siguiente, refinamiento
del esquema. Algunas de las variables dependern de cul sea el tratamiento
que debamos dar a los elementos de la secuencia.
5) Refinar el esquema. Consiste en sustituir cada una de las partes generales del
esquema por conjuntos de acciones y expresiones del lenguaje algortmico, de
modo que obtengamos un algoritmo que solucione el problema concreto especificado en el primer paso.
Como ya habis
estudiado...
... en el mdulo Introduccin
a la programacin, puede
haber muchas formas de
resolver un problema. Del
mismo modo, tambin pueden existir muchas formas de
modelizar un problema como
tratamiento de una secuencia
de elementos. Algunas de estas
formas pueden complicar los
pasos que van del 3 al 5.
FUOC PID_00149891
16
Tratamiento secuencial
Siguiendo este mtodo para disear algoritmos, el paso creativo se reduce a descubrir la secuencia a tratar. Posteriormente, si la secuencia es la adecuada, refinar
el esquema debe resultar algo bastante directo. Si no es as, es altamente probable
que no hayamos elegido la secuencia adecuada en el paso nmero 2, y entonces
el resto de pasos propuestos pueden resultar bastante ms complicados (podis
ver un caso de este tipo en el ejemplo de los divisores, a continuacin).
2.5. Ejemplos
2.5.1. Divisores de un nmero
Nos piden disear un algoritmo que, dado un nmero entero positivo (que
leeremos del dispositivo de entrada), escriba en el dispositivo de salida sus divisores (sin contarlo ni a ste ni al 1).
De este modo, por ejemplo, si el nmero que leemos del dispositivo de entrada
es el 28, el algoritmo deber escribir en el dispositivo de salida los nmeros 2,
4, 7 y 14. Si el nmero ledo es el 23, el algoritmo no deber escribir ningn
nmero (el 23 es un nmero primo).
Veamos cmo utilizamos la metodologa antes presentada para disear el algoritmo. En primer lugar, especificamos el algoritmo:
n: entero
{ Pre: n = N y N 0 }
divisores
{ Post: en el dispositivo de salida se ha escrito la secuencia de divisores de N }
FUOC PID_00149891
17
Tratamiento secuencial
y directa con una o varias operaciones (tal y como hacamos con el factorial
para pasar de i a i + 1).
De hecho, para encontrar el siguiente divisor de un nmero dado otro divisor
(correspondiente al elemento actual, el apuntado por el cabezal) deberamos
aplicar un esquema de tratamiento secuencial. En este caso, lo ms adecuado sera el esquema de bsqueda, que veremos en el siguiente apartado, refinado para
encontrar el prximo divisor. Notad que finalmente estaramos aplicando dos
esquemas diferentes, uno dentro del otro.
Podramos solucionar el problema con este modelo de secuencia. Pero tambin
podemos modelizarlo como el tratamiento de una secuencia diferente que haga
que el refinamiento del esquema sea bastante ms sencillo y no nos haga falta
el esquema de bsqueda.
Cul es, entonces, esta secuencia? Simplemente, la secuencia de nmeros enteros entre 2 y N 1. Ahora, por ejemplo, para el nmero 14 la secuencia que
hay que tratar ser <2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 >.
prepararSecuencia: al igual que en el caso del factorial, inicializamos la variable i en el primer elemento de la secuencia, que en este caso es 2.
inicioTratamiento: no es necesario hacer nada (notad que, a diferencia del algoritmo del factorial, aqu no hacemos ningn clculo acumulativo. En todo caso, la acumulacin est en el dispositivo de salida. Pero eso es otra historia).
finSecuencia: el cabezal estar en la posicin extra cuando se cumpla i = n
(el ltimo elemento de la secuencia es n 1). Notad, sin embargo, que en
los casos n = 0, n = 1 y n = 2, se trata de una secuencia vaca. Tambin debemos tener en cuenta estos casos en la expresin finSecuencia. Para tener
todos los casos en cuenta, i se debe iniciar en 2 y la condicin finsecuencia
debe ser i n.
18
FUOC PID_00149891
Tratamiento secuencial
19
FUOC PID_00149891
ro. Tenemos dos opciones: recorrer los dgitos de un nmero de izquierda a derecha o hacerlo de derecha a izquierda (para sumar los dgitos no importa cmo
lo hagamos, ya que la suma es conmutativa y asociativa). Elegimos la segunda
opcin porque nos resultar un poco ms sencillo recorrer los dgitos de menos
significativo a ms significativo (como ejercicio podis intentar resolver el problema de la otra forma). As, por ejemplo, la secuencia correspondiente al nmero 7.463 ser: <3, 6, 4, 7>.
Para ir generando la secuencia de dgitos slo tenemos que empezar con el nmero original e ir dividindolo por diez. Entonces, cada vez que queramos acceder a un dgito (que se corresponder al dgito menos significativo) slo tenemos
que calcular el nmero mdulo 10.
Utilizaremos una variable para guardar el dgito en cada iteracin (por ejemplo, d) y otra para guardar la suma de los dgitos que ya hemos tratado (suma),
aparte de la variable que leeremos de la entrada (utilizaremos n), y que iremos
dividiendo por 10 a medida que vayamos avanzando el cabezal por la secuencia de dgitos.
Pasando ya al refinamiento, tenemos que:
prepararSecuencia: el nmero n se corresponde en realidad a la secuencia de dgitos. El cabezal debe ubicarse en su dgito menos significativo. Esto lo podemos hacer as:
d := n mod 10;
avanzarSecuencia: debemos pasar al siguiente dgito. Esto lo hacemos dividiendo n por 10. De esta forma eliminamos el dgito menos significativo de n de
modo que el nuevo dgito menos significativo pasa a ser el segundo dgito menos significativo. Despus, debemos encontrar el nuevo dgito menos significativo:
n := n div 10;
d := n mod 10;
finSecuencia: habremos recorrido (y tratado) todos los dgitos del nmero inicial cuando el nmero n (que hemos ido dividiendo por 10) sea 0.
tratamientoFinal: el algoritmo debe escribir la suma de los dgitos por el dispositivo de salida.
Tratamiento secuencial
20
FUOC PID_00149891
Tratamiento secuencial
La condicin
del mientras,...
... en lugar de ser no (n = 0),
podra ser perfectamente n 0
(que es lo mismo); o bien n > 0.
Algoritmo
d := n mod 10;
suma := 0;
mientras no(n = 0) hacer
Estado
Secuencia y cabezal
Variables
del
tratamiento
Despus de
prepararSecuencia
e inicioTratamiento
suma = 0
Despus de tratar
el primer elemento
suma = 3
Como vemos,...
Despus de tratar
el segundo
elemento
suma = 9
Despus de tratar
el tercer elemento
suma = 13
Habiendo tratado
toda la secuencia
suma = 20
fmientras
...
fmientras
escribirEntero(suma);
... en este ejemplo hay dos variables que intervienen en la generacin de la secuencia. En este
caso nos podramos haber ahorrado fcilmente la variable d.
Sin embargo, a medida que
vayamos trabajando con algoritmos ms complejos, es probable que nos encontremos
frecuentemente con casos en
los que nos haga falta ms de
una variable para generar
la secuencia.
FUOC PID_00149891
21
Introducimos en este apartado el segundo esquema de tratamiento secuencial de los que veremos en este curso: el esquema de bsqueda. Este esquema nos permitir buscar un elemento en una secuencia. A diferencia del
esquema de recorrido, donde recorremos y tratamos todos los elementos de
la secuencia, con este nuevo esquema recorreremos la secuencia nicamente
hasta que encontremos un elemento que cumpla una condicin determinada. Esta condicin no tiene por qu referirse a un elemento con un valor
concreto (como por ejemplo, cuando queremos encontrar la primera letra a
en una frase), sino que puede ser una condicin ms general (encontrar el
primer nmero par de una secuencia de enteros, la primera palabra capica
de un texto, etc.).
El esquema de bsqueda es como sigue:
algoritmo EsquemaBusqueda
var
encontrado: booleano;
fvar
prepararSecuencia
encontrado := falso;
inicioTratamiento
{ El cabezal est al principio de la secuencia. No hemos tratado ningn elemento de sta }
mientras no finSecuencia y no encontrado hacer
{ Hemos tratado la parte de la secuencia que est a la izquierda del
cabezal. El elemento buscado no est en esta parte de la secuencia.
Adems, el cabezal no est al final y encontrado es falso. }
actualizarEncontrado
si no encontrado entonces
tratarElemento
avanzarSecuencia
fsi
{ Hemos tratado la parte de la secuencia que est a la izquierda del cabezal. El elemento buscado no est en esta parte de
la secuencia. Si encontrado es cierto, el elemento actual es el
elemento buscado. }
fmientras
Tratamiento secuencial
FUOC PID_00149891
22
El esquema est preparado para el caso en que la secuencia no contenga el elemento buscado. En este caso, se llega al final de la secuencia y se acaba la ejecucin del bloque mientras con la variable encontrado a falso.
En el caso de que la secuencia s contenga el elemento buscado, el condicional
interno del bloque mientras hace que el cabezal se site sobre el elemento buscado (que no se trata). Se sale del bucle mientras con la variable encontrado con
valor cierto.
Posteriormente, podemos utilizar el valor de encontrado en tratamientoFinal
para determinar si hemos encontrado o no el elemento y emprender las acciones pertinentes en cada caso (como por ejemplo, tratar el elemento encontrado,
si es necesario darle tratamiento).
Tal y como hemos hecho en el esquema de recorrido, en el esquema de bsqueda hemos aadido de forma orientativa la descripcin de los estados en que se
encuentra el algoritmo en diferentes momentos del proceso.
3.2. Refinamiento
Pasemos a comentar cada una de las partes del esquema, que igual que en el
esquema de recorrido deben ser sustituidas al refinarlo:
prepararSecuencia: del mismo modo que en el esquema de recorrido, si la secuencia tiene al menos un elemento (no est vaca), se procede a encontrar
el primer elemento de la secuencia. Y si la secuencia est vaca, situamos el
cabezal en la posicin extra del final de la secuencia.
inicioTratamiento: equivale a inicializar las variables correspondientes al tratamiento. Slo es necesario en caso de que sea una bsqueda con tratamiento (muchas bsquedas tienen como nico objetivo encontrar un elemento
determinado, sin hacer ningn tratamiento a los elementos que se recorren).
finSecuencia: corresponde a una expresin que evaluar a cierto cuando el cabezal est al final de la secuencia.
actualizarEncontrar: corresponde a un conjunto de acciones que tienen como
objetivo determinar si el elemento actual se corresponde con el elemento bus-
Tratamiento secuencial
FUOC PID_00149891
23
cado o no. En caso de serlo, la variable encontrado debe tener como valor cierto
despus de ejecutar este conjunto de acciones. Y, en caso de no serlo, la variable encontrado debe tener como valor falso.
tratarElemento: corresponde al conjunto de acciones que llevan a cabo el tra-
Tratamiento secuencial
Es habitual que...
... en las bsquedas no nos
haga falta tratamiento, a pesar
de que, evidentemente, no
siempre ser as.
3.3. Especificacin
Volvamos otra vez a reflexionar sobre el comportamiento del esquema haciendo un estudio de su especificacin.
Las descripciones de los estados que aparecen en el esquema estn totalmente
marcadas por el invariante del mientras y de la bsqueda, que corresponde a:
Hemos tratado la parte de la secuencia que est a la izquierda del cabezal. El
elemento buscado no est en esta parte de la secuencia. Si encontrado es cierto,
el elemento actual es el elemento buscado.
El invariante, junto con la negacin de la condicin del mientras, nos debe permitir, despus de ejecutar el tratamiento final, asegurar que la postcondicin del
algoritmo se cumple.
El invariante proporcionado es bastante general y se debe concretar para cada
aplicacin del esquema (igual que concretamos o refinamos los fragmentos del esquema en conjuntos de acciones o bien, expresiones del lenguaje algortmico).
En cuanto a la funcin de cota, la podemos hacer corresponder con el nmero
de elementos que quedan por tratar. Sin embargo, a diferencia del esquema de
recorrido, este nmero no corresponde al nmero de elementos de la secuencia. Es posible que encontremos el elemento buscado, y en este caso el nmero
de elementos tratados ser inferior al nmero de elementos de la secuencia.
Entonces, lo nico que hay que asegurar es que el nmero de elementos tratados es finito; para ello tenemos suficiente con asegurar que, o bien la secuencia
es finita, o bien siempre encontraremos el elemento buscado. Es decir, podemos
aplicar el esquema de bsqueda sobre secuencias infinitas siempre que estemos seguros de que encontraremos el elemento buscado. De este modo, por ejemplo,
podramos aplicar el esquema de bsqueda para encontrar un nmero primo que
sea capica, siempre que el propio enunciado nos asegure su existencia (o bien
nosotros lo hayamos deducido de otra forma).
Como antes,...
... este apartado queda fuera
del mbito de esta asignatura,
pero os puede resultar bastante til.
FUOC PID_00149891
24
Tratamiento secuencial
3.4. Metodologa
La metodologa que debemos utilizar para aplicar el esquema de bsqueda es
la misma que empleamos para el esquema de recorrido (ya ha sido introducida
en el punto 2.4). Evidentemente, se da una pequea diferencia en el paso nmero 5, donde los fragmentos de esquema que hay que refinar son diferentes
en cada caso.
3.5. Ejemplos
3.5.1. Nmeros de Fibonacci
Nos piden disear un algoritmo que determine si entre los 1.000 primeros nmeros de Fibonacci hay algn nmero que acabe en 9. Tambin nos piden que
en caso de que el nmero exista, lo escribamos por el dispositivo de salida; y
si no existe, que escribamos el entero negativo 1.
Los nmeros de Fibonacci estn definidos de la siguiente forma:
fib1 = 0
fib2 = 1
fibn = fibn1 + fibn2, para n > 2
Vamos a solucionar el problema. En primer lugar, como siempre, especificamos el algoritmo:
{ Pre: cierto }
buscarFibonacci9
{ Post: en el caso de que este nmero exista, por el dispositivo de salida se habr escrito
uno de los primeros 1.000 nmeros de Fibonacci, que, adems acaba en 9. Si este nmero
no existe, se habr escrito un 1 }
Notad que...
... este algoritmo no tiene ningn
dato de entrada. Por lo tanto, en
la precondicin no es necesario
establecer ninguna restriccin sobre los datos de entrada. Por este
motivo, la precondicin siempre
se cumplir y, por lo tanto, corresponde a cierto.
25
FUOC PID_00149891
Tratamiento secuencial
Construir la secuencia de los nmeros de Fibonacci no resulta nada complicado: los dos primeros los conocemos, el tercero lo podemos construir con la
suma de los dos primeros, el cuarto como la suma del segundo y el tercero, etc.,
as hasta llegar a fib1.000.
El enunciado nos pide que escribamos uno de estos nmeros que acabe en 9.
No nos pide ninguno en concreto, ni el primero, ni el ltimo, ni ningn otro.
As pues, elegiremos el menor que podamos encontrar, ya que, si de forma natural recorreremos la secuencia de los nmeros de Fibonacci de menor a mayor, ser el primero que encontremos (si es que existe, claro).
As pues, nuestro problema consistir en buscar en la secuencia constituida por
los 1.000 primeros nmeros de Fibonacci el primero que acabe en 9. Debemos
tener en cuenta que, de entrada, no sabemos si este nmero existe o no.
Recordad que...
... al dar un algoritmo para
resolver un problema, se trata,
como siempre, de satisfacer la
especificacin. Si sta nos ofrece varias posibilidades, nosotros somos libres de elegir la
que ms nos convenga (normalmente eligiremos aquella
que consuma menos recursos,
tanto en tiempo como en espacio de memoria).
Observad que...
... la secuencia es un poco ms
compleja que las que encontrbamos en los ejemplos que
habamos visto hasta ahora.
Un elemento de la secuencia
est asociado a los valores de
dos variables: actual (que es el
nmero de Fibonacci en cuestin) y el ndice i (que es el subndice correspondiente).
Adems, como para calcular
un nmero de Fibonacci nos
hacen falta los dos anteriores,
debemos tener siempre calculados dos nmeros de Fibonacci consecutivos a la vez (actual
y siguiente).
26
FUOC PID_00149891
avanzarSecuencia: aqu avanzar es un poco ms complicado que en los ejemplos que hemos visto hasta ahora. El motivo es que intervienen distintas variables a causa de que para calcular el siguiente nmero de Fibonacci nos
hacen falta los anteriores. La forma de proceder ser la siguiente: necesitamos
hacer correr actual y siguiente una posicin en la secuencia. Para hacerlo, debemos conocer el nmero de Fibonacci que sigue a siguiente. Lo calculamos y
lo guardamos en siguiente2. Una vez hecho esto, ya podemos hacer correr actual y siguiente una posicin; y al mismo tiempo, tambin debemos incrementar el ndice. Vemoslo:
si encontrado entonces
escribirEntero(actual)
sino
escribirEntero(1)
fsi
Tratamiento secuencial
Notad que,...
... en realidad, el condicional
no es equivalente a la asignacin sin condicional. Mientras
en el primero slo se establece
el valor de encontrado cuando
el nmero acaba en 9, en el segundo se establece el valor de
encontrado siempre (bien a
cierto si el nmero acaba en 9,
o bien a falso en caso contrario). Sin embargo, dado que
cuando encontrado se hace
cierto la bsqueda termina
en ese preciso momento, las
dos versiones nos sirven.
Tened en cuenta tambin que
actual mod 10 = 9 es una expresin booleana que tiene
como resultado cierto si actual
mod 10 es 9, y falso en caso
contrario.
27
FUOC PID_00149891
Tratamiento secuencial
si encontrado entonces
escribirEntero(actual)
sino
escribirEntero(1)
fsi
{ Post: Por el dispositivo de salida se ha escrito uno de los primeros 1.000 nmeros de
Fibonacci que, adems, acaba en 9, en el caso de que este nmero exista. Si este nmero
no existe, se ha escrito un 1 }
falgoritmo
Estado
Secuencia y cabezal
Como en este caso...
encontrado := falso;
actual := 0;
siguiente := 1;
i := 1;
mientras i 1.000 y no encontrado
hacer
...
Despus de
prepararSecuencia
e inicioTratamiento
Despus de descartar
el primer elemento
Despus de descartar
el segundo elemento
...
siguiente := siguiente2;
i := i + 1
fsi
fmientras
Despus de descartar
el decimosptimo
elemento
Despus de descartar
el decimoctavo
elemento
(actualizarEcontrado
pone encontrado
a cierto)
...
fmientras
si encontrado entonces
escribirEntero(actual)
sino
escribirEntero(1)
fsi
Habiendo
encontrado el
elemento buscado
................
FUOC PID_00149891
28
Tratamiento secuencial
En realidad,...
... tenemos suficiente con verificar esto para los elementos
de la secuencia <2, 3, 4, ..., K>,
donde K es el mayor nmero
tal que K2 N. Siempre que
exista un divisor entre K y N,
seguro que habr otro entre
2 y K (de forma que, multiplicndolos los dos, nos d N).
De la misma manera, si N no es
divisible por 2, tampoco lo ser
por 4, 6, ...
29
FUOC PID_00149891
Tratamiento secuencial
O bien as:
encontrado := n mod i = 0;
Fcilmente nos podramos haber ahorrado esta variable retornando directamente como resultado de la funcin la expresin no encontrado. No lo hemos hecho as con la nica intencin de mostrar explcitamente que existe
un tratamiento final que consiste en negar el booleano encontrado.
Veamos, entonces, cmo queda la funcin esPrimo:
funcion esPrimo(n: entero): booleano
{ Pre: n = N y N > 0 }
var
encontrado, primo: booleano;
i : entero;
fvar
i := 2;
encontrado := falso;
mientras i < n y no encontrado hacer
si n mod i = 0 entonces
encontrado := cierto
fsi
si no encontrado entonces
i := i + 1
fsi
fmientras
primo := no encontrado;
retorna primo;
{ Post: primo es cierto si N es primo y falso en caso contrario }
ffuncion
Notad que...
... muchas veces podremos
fusionar construcciones algortmicas adjuntas. Aqu, por ejemplo, podemos fusionar los dos si
en:
si n mod i = 0 entonces
encontrado := cierto
sino
i := i + 1
fsi
Este proceso de fusin siempre
debe ser posterior al refinamiento del esquema y nunca
debe ser una excusa para no
aplicarlo correctamente.
30
FUOC PID_00149891
Como ha quedado dicho, las secuencias sirven para modelizar muchos de los problemas que podemos resolver con un algoritmo. Como caso particular muy habitual, encontramos el procesamiento secuencial de la entrada de un algoritmo
(datos que lee del teclado, un fichero, etc.). Esto ampla el tipo de problemas que
podremos resolver: ahora ya no nos tendremos que limitar a tratar secuencias generadas por el propio algoritmo, sino que seremos capaces de procesar secuencias
que nos vienen dadas. Teniendo en cuenta la frecuencia con que se dan estas situaciones, refinaremos los esquemas introducidos en las secciones anteriores para
obtener de los mismos una adaptacin al tratamiento de la entrada.
Utilizaremos las operaciones de lectura que se presentaron en el mdulo anterior (las funciones leerEntero, leerReal y leerCaracter). Eso s: en cada tratamiento
secuencial slo se podr utilizar una de las tres funciones, teniendo en cuenta
que todos los elementos de una secuencia deben ser del mismo tipo.
En todo tratamiento secuencial es indispensable un mecanismo de deteccin del
final de la secuencia. En el caso de la entrada, para hacer esta deteccin se suele
aadir un elemento adicional detrs del ltimo elemento relevante. Este elemento no se debe tratar, slo hace el papel de aviso de final de secuencia. Denominamos este elemento marca, y las secuencias que tienen marca las denominamos,
como es natural, secuencias marcadas. De este modo, la entrada del programa ser
una secuencia marcada. Esto quiere decir que no tendremos nunca la secuencia
vaca: deberemos leer aunque no la tratemos, como mnimo, la marca.
algoritmo RecorridoEntrada
var elem: T;
fvar
Tratamiento secuencial
31
FUOC PID_00149891
mientras no (elem = marca) hacer { Hemos tratado todos los elementos ledos a excepcin del ltimo, que se encuentra en elem y
no es la marca }
tratarElemento(elem)
elem := leer()
fmientras
{ Hemos tratado todos los elementos ledos a excepcin del ltimo, que es la marca }
tratamientoFinal
falgoritmo
4.1.2. Especificacin
De forma similar a lo que hemos hecho en las secciones anteriores, y a pesar
de que no se incluya en los objetivos de la asignatura, reflexionemos un poco
sobre la correccin del algoritmo que acabamos de presentar.
En el esquema hemos mantenido la descripcin de los estados en los que se encuentra el algoritmo en diferentes momentos del proceso, adaptndola teniendo en cuenta que la secuencia es el dispositivo de entrada. El invariante del
mientras hemos tratado la parte de la secuencia que est antes del cabezal se
ha refinado como hemos tratado todos los elementos ledos a excepcin del ltimo. Por otro lado, sabemos que el ltimo ledo (el cabezal) est siempre guardado en la variable elem. Adems, dentro del bucle, elem no es la marca, porque
de otro modo no hubisemos entrado. El caso concreto del invariante antes de
empezar a iterar (el cabezal est al principio de la secuencia en el original) es
elem tiene el primer valor de la entrada, y despus de hacer todo el tratamiento
(hemos tratado toda la secuencia y el cabezal est al final en el original) es hemos tratado toda la entrada anterior a la marca y elem contiene la marca.
Recordad que, al igual que habis hecho con las diferentes partes del esquema,
la invariante tambin se debe refinar para cada problema concreto al que apliquemos el esquema de recorrido.
Finalmente, la nocin de cota se convierte ahora en el nmero de elementos
de la entrada que quedan por leer.
4.1.3. Ejemplo
Como ejemplo de aplicacin de este esquema, disearemos un algoritmo que
cuente las veces que la letra a aparece en una frase acabada con un punto e
introducida por el teclado. La especificacin es la siguiente:
{ Pre: en la entrada leeremos una secuencia de caracteres que no contiene ningn punto
seguida de un punto }
cuentaLetraA
{ Post: escribe el nmero de veces que aparece la letra a en la entrada }
Tratamiento secuencial
Notad que...
... no hemos hecho ms
que copiar el esquema de
recorrido cambiando prepararSecuencia por una lectura,
finSecuencia por una comparacin con la marca y avanzarSecuencia por una lectura. En este
refinamiento, el cabezal corresponde a la variable elem,
donde se encuentra el ltimo
elemento ledo, y la posicin
final de la secuencia corresponde a la marca.
32
FUOC PID_00149891
Tratamiento secuencial
Podemos asimilar este algoritmo a un recorrido de la entrada en el que el tratamiento de cada elemento consiste en incrementar o no un contador segn
el elemento sea o no sea una letra a. El punto final de la frase acta como marca de la secuencia. No nos es preciso ningn tratamiento final, y el tratamiento inicial se refina al inicializar el contador a 0, mientras que el tratamiento de
un elemento se convierte en una instruccin alternativa (si ... fsi).
Con estos refinamientos obtenemos este algoritmo:
algoritmo cuentaLetraA
var car: caracter; n: entero;
fvar
car := leerCaracter();
n := 0;
mientras no car = . hacer
{ n es el nmero de letras a que haba entre los caracteres ledos excepto el ltimo, que se
encuentra en car y no es el punto final }
si car = a entonces n := n + 1
fsi
car := LeerCaracter()
fmientras
{ n es el nmero de letras a que haba entre los caracteres ledos excepto el ltimo, que es el
punto final }
escribirEntero(n)
falgoritmo
Para facilitar la identificacin del algoritmo con el esquema de recorrido original, veamos una representacin grfica de la evolucin de su ejecucin en la
tabla siguiente. Supongamos que en la entrada encontraremos los caracteres
l, a, c, a y .. La segunda columna representa la secuencia del esquema y
la tercera presenta el valor de las variables:
La variable car refleja el estado de la secuencia y contiene el ltimo elemento ledo que, como hemos dicho, corresponde al cabezal de la secuencia.
La variable n, tal como indican los predicados del algoritmo, contiene el nmero de letras a que hay antes del cabezal.
La primera fila representa el estado despus de ejecutar las inicializaciones (preparar la secuencia e inicializar el tratamiento). Las filas siguientes representan el
estado al final de cada iteracin del bucle. Una flecha recuerda a qu punto concreto del algoritmo corresponde el valor de las variables que se muestra.
Algoritmo
car := leerCaracter();
n := 0;
Secuencia
Variables
car = l
n=0
33
FUOC PID_00149891
Algoritmo
Secuencia
Tratamiento secuencial
Variables
car = a
n=0
car = c
n=1
car = a
n=1
car = .
n=2
34
FUOC PID_00149891
algoritmo BsquedaEntrada
var
encontrado: booleano;
elem: T; { Tipos de datos de los elementos de la secuencia }
fvar
elem := leer(); { Funcin de lectura del tipo de datos de los elementos de la secuencia. Depender del tipo T que no
es relevante para la formulacin de este esquema }
encontrado := falso;
inicioTratamiento
mientras no (elem = marca) y no encontrado hacer
{ Hemos tratado todos los elementos ledos a excepcin del ltimo,
que se encuentra en elem y no es la marca. Ninguno de los elementos tratados es el que buscamos }
actualizarEncontrado
si no encontrado entonces
tratarElemento
elem := leer() { La misma funcin de lectura que antes }
fsi
fmientras
{ Hemos tratado todos los elementos ledos a excepcin del ltimo,
que se encuentra en elem. Ninguno de los elementos tratados es el que
buscamos. Si encontrado es cierto, elem es el elemento buscado; si es
falso, elem es la marca }
tratamientoFinal
falgoritmo
4.2.2. Especificacin
De forma similar a lo que hemos hecho en las secciones anteriores, reflexionemos un poco sobre la correccin del esquema que acabamos de presentar. Tal
como hemos hecho en la seccin anterior, examinemos la conexin que se establece entre el esquema de bsqueda general y el esquema adaptado para procesar el dispositivo de entrada.
Tratamiento secuencial
FUOC PID_00149891
35
Tratamiento secuencial
El predicado del inicio del mientras hemos tratado la parte de la secuencia que
est antes del cabezal. El elemento buscado no est en esta parte de la secuencia.
Adems, el cabezal no est al final se ha reformulado en hemos tratado todos
los elementos ledos a excepcin del ltimo, que se encuentra en elem y no es la
marca. Ninguno de los elementos tratados es el que buscamos.
Una vez ms, el invariante proporcionado es bastante general y se debe concretar
para cada aplicacin del esquema (igual que concretamos o refinamos los fragmentos del esquema en conjuntos de acciones o en expresiones del lenguaje algortmico).
En lo que respecta a la funcin de cota, vuelve a corresponder al nmero de elementos que quedan por leer.
4.2.3. Ejemplo
Un ejemplo sencillo de aplicacin de la bsqueda a la entrada consiste en averiguar si en una frase acabada en punto aparece alguna letra a.
{ Pre: en la entrada leeremos una secuencia de caracteres que no contiene ningn punto seguida de un punto }
saleLetraA
{ Post: por el dispositivo de salida se escribe S si en la entrada hay alguna letra a y N en
caso contrario }
Es una bsqueda...
... sin tratamiento, de forma
que no hay ni InicioTratamiento
ni TratarElemento ni TratamientoFinal. El refinamiento de
ActualizarEncontrado es, sencillamente:
encontrado := car = a.
36
FUOC PID_00149891
Tratamiento secuencial
Veamos, como antes, la evolucin de la ejecucin con una tabla para intentar
identificar el algoritmo con el esquema de bsqueda original. Supongamos
ahora que en la entrada encontraremos los caracteres t, o, c, a y .. De
nuevo, la segunda columna representa la secuencia del esquema y la tercera
presenta el valor de las variables:
car refleja el estado de la secuencia y contiene el ltimo elemento ledo
que, como hemos dicho, corresponde al cabezal de la secuencia lgica.
encontrado se asignar a cierto si el elemento del cabezal es decir, car es
una a.
Recordemos que la primera fila representa el estado despus de ejecutar las inicializaciones (preparar la secuencia e inicializar encontrado) y que cada una de
las filas siguientes representa el estado al final de cada iteracin del bucle.
Algoritmo
Secuencia
Variables
car := leerCaracter();
car = t
encontrado := falso;
encontrado = falso
car = o
encontrado = falso
mientras no car = . y no
encontrado hacer
encontrado := car= a;
si no encontrado
entonces
car := leerCaracter()
car = c
encontrado = falso
fsi
fmientras
car = a
encontrado = cierto
FUOC PID_00149891
37
Este problema se resuelve claramente con un recorrido de la entrada. Queremos que el bucle cuente y sume los elementos de la entrada. Por eso, antes de
cada iteracin queremos que un contador n diga cuntos elementos hemos
tratado (los que hemos ledo excepto el ltimo) y que un acumulador sum sea
la suma de estos n nmeros. De este modo, al final de la entrada, cuando hayamos ledo la marca, n ser el nmero de elementos relevantes que haba y
sum la suma de estos elementos. Para cumplir la condicin al principio, cuando hemos ledo un elemento es necesario que n y sum equivalgan a cero.
Tratamiento secuencial
Hemos refinado...
... el esquema de recorrido de
la entrada de forma que la
marca es el 0.0, el tratamiento
inicial es la inicializacin a cero
del nmero de elementos y la
suma y el tratamiento de un
elemento consiste en sumarlo
con la suma anterior e incrementar el contador de elementos El tratamiento final realiza
la divisin para obtener el resultado pedido.
algoritmo media
var
x, sum: real;
n: entero
fvar
x := leerReal();
n := 0;
sum := 0.0;
mientras no (x = 0.0) hacer
{ sum = suma de todos los elementos ledos a excepcin del ltimo, que se encuentra
en x y no es la marca y n = nmero de elementos sumados }
sum := sum + x;
n := n + 1;
x := leerReal()
fmientras
{ sum = suma de todos los elementos ledos a excepcin del ltimo, que es la marca, y
n = nmero de elementos sumados }
escribirReal(sum/enteroAReal(n)).
falgoritmo
Es una bsqueda...
... sin tratamiento, de forma
que no hay ni InicioTratamiento ni TratarElemento ni
TratamientoFinal. El refinamiento de ActualizarEncontrado es, sencillamente,
encontrado := (car = c) o
(car = D);
FUOC PID_00149891
38
Tratamiento secuencial
{ Pre: en la entrada leeremos un entero y una tira de caracteres del conjunto {A, B, C,
c, D seguida por una Z }
cotaAprobados
{ Post: por el dispositivo de salida se escribe N si el nmero de aprobados no es inferior a la
cota y S en caso contrario }
Este problema puede replantearse como la bsqueda de una nota que nos permite obtener la siguiente respuesta: un suspenso antes de cota notas o un apobado
despus de cota notas. Necesitamos, pus, hacer un tratamiento de recuento de
los elementos que se van leyendo.
algoritmo cotaAprobados
var
encontrado: booleano;
car: caracter;
cota, n: entero;
fvar
cota := leerEntero();
car := leerCaracter();
encontrado := falso;
n := 0;
mientras no (car = Z) y no encontrado hacer
{ Ninguna de las notas ledas anteriores a la ltima es un suspenso.
La ltima est en car, n s el nmero de notas ledas menos 1 }
encontrado := (car = c) o (car = D) o (n cota);
si no encontrado entonces
n := n + 1;
car := leerCaracter()
fsi
fmientras
{ Niguna de las notas ledas anteriores a la ltima es un suspenso.
La ltima est en car. Si encontrado es cierto, car es un suspenso; si es falso, car es Z.
n es el nmero de notas ledas menos 1 }
Es una bsqueda...
... con estos refinamientos:
InicioTratamiento es la inicializacin de n.
TratarElemento es el incremento de n.
No hay TratamientoFinal.
El refinamiento de ActualizarEncontrado es:
encontrado := (car = c) o
(car = D) o (n cota);
FUOC PID_00149891
si n cota entonces
escribirCaracter(N)
sino
escribirCaracter(S)
fsi
falgoritmo
39
Tratamiento secuencial
FUOC PID_00149891
40
5. Combinacin de esquemas
5.1. Planteamiento
Hemos estudiado en este mdulo dos esquemas de diseo de algoritmos recorrido y bsqueda muy tiles. Sin embargo, todava podemos aumentar su utilidad
si tenemos en cuenta la posibilidad de combinarlos, de forma que un algoritmo
de proceso de una secuencia no sea refinamiento de un esquema sino de distintos
esquemas dispuestos uno tras otro. De hecho, si lo observamos con mayor detenimiento, veremos que este tipo de combinacin deber ser de una de estas
dos formas:
a) bsqueda1; bsqueda2,..., bsquedan
b) bsqueda1; bsqueda2,..., bsquedan; recorrido
La razn es que, despus de un recorrido, ya hemos ledo toda la secuencia, de
modo que ya no tiene sentido plantear el hecho de continuar haciendo en ella
ningn proceso. En cambio, despus de una bsqueda, es posible que la secuencia haya sido parcialmente procesada, por lo que puede ser til continuar
el proceso del resto de la secuencia con otra bsqueda o un recorrido. Los
apartados siguientes presentan ejemplos de aplicacin de la combinacin de
esquemas.
5.2. Ejemplos
5.2.1. Media de los suspensos
Queremos disear un algoritmo que lea una serie de notas entre 0.0 y 10.0 ordenada de forma descendente y marcada con un 1.0; el algoritmo nos debe
dar la nota media de los suspensos (o 0.0, si no hay suspensos). Un suspenso
es cualquier nota inferior a 5.0.
{ Pre: en la entrada leeremos una secuencia de reales entre 0.0 y 10.0 ordenada descendientemente seguida por un 1.0 }
mediaSuspensos
{ Post: escribe la media de los nmeros x tales que 0 x < 5.0 o bien 0.0 si no haba ningn
nmero que cumpliese esta condicin }
Tratamiento secuencial
41
FUOC PID_00149891
Tratamiento secuencial
dente); a continuacin, todas las notas que quedan son suspensos, de modo
que las podemos procesar haciendo un recorrido que las acumule y las cuente
todas (ya no es necesario comprobar si es suspenso o no). Despus, como antes, efectuamos la divisin:
algoritmo mediaSuspensos
var
x, sum, result: real;
n: entero;
encontrado: booleano;
fvar
x := leerReal() ;
encontrado := falso;
mientras no (x = 1.0) y no encontrado hacer
{ Ninguna de las notas ledas anteriores a la ltima es un suspenso
La ltima est en x }
encontrado := x < 5.0;
si no encontrado entonces
x := leerReal()
fsi
fmientras
{ Ninguna de las notas ledas anteriores a la ltima es un suspenso.
La ltima est en x. Si encontrado es cierto, x es un suspenso; si es falso, x es 1.0 }
n := 0; sum := 0.0;
mientras no (x = 1.0) hacer
{ sum = suma de todos los valores ledos a partir del primer suspenso a excepcin del
ltimo, que se encuentra en x y no es la marca n = nmero de elementos sumados }
sum := sum + x; n := n + 1;
x := leerReal()
fmientras;
{ sum = suma de todos los valores ledos a partir del primer suspenso a excepcin del ltimo, que es la marca n = nmero de elementos sumados }
si n > 0 entonces
result := sum/enteroAReal(n)
sino
result := 0.0
fsi
escribirReal(result)
falgoritmo
Algoritmo
x := leerReal(); encontrado :=
falso;
Secuencia
Variables
x = 8,5
encontrado = falso
42
FUOC PID_00149891
Algoritmo
Secuencia
mientras no x := 1.0 y no
encontrado hacer
Tratamiento secuencial
Variables
x = 7,0
encontrado = falso
n := 0;
sum := 0.0;
x = 3,5
encontrado = cierto
x = 3,5
n=0
sum = 0,0
x = 3,0
n=1
mientras no x = 1.0 hacer
sum = 3,5
sum := sum + x;
n := n + 1;
x := leerReal();
fmientras
x = 1,0
n=2
sum = 6,5
Podemos obtener la solucin combinando dos bsquedas: la primera lee caracteres separadores hasta encontrar la primera letra de la primera palabra; la segunda bsqueda lee caracteres hasta encontrar el primer separador posterior a la
primera palabra y va contando las letras ledas. El valor de este contador es la respuesta pedida (incluso en el caso de que no haya ninguna palabra, porque, en
esta situacin, la primera bsqueda fracasa y queda al final de la secuencia; a continuacin, la segunda bsqueda acaba inmediatamente sin entrar en el bucle, dejando el contador a 0).
FUOC PID_00149891
43
Tratamiento secuencial
algoritmo longPrimeraPal
var
car: caracter;
n: entero;
encontrado: booleano;
fvar
car := leerCaracter();
encontrado := falso;
mientras no (car = .) y no encontrado hacer
{ Ninguno de los caracteres ledos anteriores al ltimo es una letra. El ltimo est
en car }
encontrado := (car Z) y (car A);
si no encontrado entonces
car := leerCaracter()
fsi
fmientras
{ Ninguno de los caracteres ledos anteriores al ltimo es una letra.
El ltimo est en car.
Si encontrado es cierto, car es una letra; si es falso, car es el punto final }
encontrado := falso;
n := 0;
mientras no (car = .) y no encontrado hacer
{ Ninguno de los caracteres ledos anteriores al ltimo despus de la primera letra es
un separador. El ltimo est en x, n es el nmero de letras ledas }
encontrado := (car) > Z o (car < A);
si no encontrado entonces
n := n + 1;
car := leerCaracter()
fsi
fmientras
{ Ninguno de los caracteres ledos anteriores al ltimo despus de la primera letra es un
separador. El ltimo est en car. Si encontrado es cierto, car es un separador; si es falso,
car es el punto final, n es el nmero de letras ledas }
escribirEntero(n)
falgoritmo
FUOC PID_00149891
44
Resumen
Tratamiento secuencial
FUOC PID_00149891
45
Tratamiento secuencial
3. Formular la solucin
FUOC PID_00149891
46
4. Evaluar la solucin
Los esquemas son algoritmos genricos bien construidos y correctos. Su uso,
por lo tanto, nos libera de una gran cantidad de errores que podramos cometer si tuvisemos que construir el algoritmo partiendo de cero cada vez (aparte
de la mayor dificultad de hacer esto). Sin embargo, no nos liberaremos de cualquier error. Podemos cometer errores:
En el planteamiento: cuando decidimos si el problema es solucionable mediante uno o ms tratamientos, en el momento de elegir el esquema, o incluso cuando seleccionamos la secuencia que hay que tratar.
En la formulacin de la solucin: cuando refinamos el esquema.
Los errores en el planteamiento son ms difciles de solucionar. Tambin son
costosos, pues muy probablemente implican que la formulacin de la solucin
para el planteamiento equivocado se deba repetir. Por este motivo, no nos debe
importar pasar un buen rato para plantear correctamente el problema.
Los errores de la formulacin se deben a que hemos refinado alguna de las partes
del esquema de forma incorrecta. Para detectar estos errores, debemos ser capaces de clasificar mentalmente las posibles secuencias que nuestro algoritmo
debe poder tratar en diferentes tipos. Normalmente nos encontraremos con un
caso general en el que tendremos una secuencia con unos cuantos elementos,
pero tambin con casos especiales, como por ejemplo la secuencia vaca (siempre
que la precondicin admita estos casos especiales, evidentemente).
Debemos prestar atencin a estos casos especiales y observar si la solucin que
hemos obtenido los trata correctamente. Tal vez hayamos hecho el refinamiento pensando nicamente en el caso general; entonces, en alguno de estos
casos especiales, es probable que el algoritmo no se comporte correctamente.
Una parte especialmente delicada es el finSecuencia. Por ejemplo, prestad atencin al comentario correspondiente a esta parte en el ejemplo de los nmeros
de Fibonacci del tema del esquema de bsqueda. Sin embargo, podis haber
introducido problemas sin daros cuenta en cualquiera de las partes del esquema que deben refinarse.
Para hacer todo esto tendris que razonar sobre el comportamiento del algoritmo resultante, utilizando el significado de las construcciones del lenguaje algortmico que aparecen en el mismo (y que ya conocis).
Por otro lado, se debe resaltar que el uso de los esquemas de tratamiento secuencial y de la metodologa introducida en este mdulo minimiza el esfuerzo
del desarrollador para obtener una solucin correcta a problemas que se puedan
modelar mediante el tratamiento secuencial (que, como ya hemos indicado,
son casi todos).
Tratamiento secuencial
FUOC PID_00149891
47
Por este motivo, el uso de estos esquemas y la experiencia planteando problemas como problemas de tratamiento secuencial son puntos clave para mejorar
de una forma importante la eficacia y la eficiencia de cualquier programador
en el momento de desempear su tarea.
Tratamiento secuencial
FUOC PID_00149891
49
Ejercicios de autoevaluacin
1. Disead una accin que descomponga un nmero natural en cifras. Es necesario escribir
el nmero dado dgito a dgito, siendo cada dgito un entero entre 0 y 9. Suponed que tenis
disponibles las funciones:
funcion cuantasCifras(n: entero): entero
{ Pre: n 0 }
{ Post: restituye el nmero de cifras significativas de n }
funcion potencia(n: entero): entero
{ Pre n 0 }
{ Post: retorna el valor 10 elevado a n }
Por ejemplo, 1234 se escribira 1, 2, 3, 4.
2. Disead un algoritmo que nos muestre el valor mximo de una tira de reales marcada con 0.0
disponible para ser leda.
3. Disead una accin que indique si una secuencia de reales marcada con 0.0 leda de la entrada est ordenada de forma creciente.
4. Decimos que un nmero es perfecto si la suma de sus divisores excepto l mismo es igual
al propio nmero.
Por ejemplo, 6 es perfecto porque los divisores de 6 son 1, 2, 3 y 6: 1 + 2 + 3 = 6.
Disead un algoritmo que indique si un nmero es perfecto.
5. Encontrad una accin que encuentre la parte entera de la raz cuadrada de un entero positivo
sin que tengamos ninguna funcin que calcule races cuadradas (sqr, raz cuadrada...).
Tratamiento secuencial
FUOC PID_00149891
50
Solucionario
1. Este problema no consiste en procesar una secuencia sino en generarla. Sin embargo, podemos construir la accin basndonos en el esquema de recorrido, siendo la secuencia recorrida la que vamos generando. Empecemos especificando el problema para asegurarnos de su
correcta comprensin.
accion cifras(ent n: entero)
{ Pre: n 0 }
{ Post: escribe por el canal de salida el entero n dgito a dgito }
Ahora intentamos plantear la solucin sobre la base de algn esquema. Lo que nos conviene
es hacer un recorrido de la secuencia, que en el caso que nos ocupa es la secuencia de dgitos
del nmero. Para obtener la solucin tenemos que refinar el esquema que hemos elegido; si
conocemos inicialmente la longitud de la secuencia, es decir, el nmero de cifras, podremos
obtener los elementos dividiendo n por potencias de 10, empezando precisamente por 10 elevado al nmero de cifras menos 1. En cada iteracin generamos una nueva cifra dividiendo
el nmero por una potencia de 10 inferior a la anterior. Por ejemplo, si tenemos la cifra 9728,
cuatroCifras (9728) = 4, potencia (41) = 1000, y por tanto, la primera cifra es (9728 div 1000)
mod 10) = 9 mod 10 = 9
accion cifras(n: entero)
{Pre: n 0 }
var
nx, pot10: entero
fvar
nx := cuantasCifras (n);
pot10 := Potencia(nx 1);
mientras nx > 0 hacer
{ Quedan nx cifras por escribir }
escribirEntero((n div pot10) mod 10);
nx := nx 1;
pot10 := pot10 div 10
fmientras
{ Post: escribe por el canal de salida el entero n dgito a dgito }
faccion
2. Primero especificamos el algoritmo (con una precondicin que requiere por lo menos un
elemento, porque no se puede definir el mximo de un conjunto vaco; para evitar una repeticin, nos limitamos a anotar la precondicin y la postcondicin en el propio algoritmo).
Ahora podemos solucionar el problema examinando toda la secuencia con un recorrido que,
en cada momento, recuerda el valor ms alto de los que se han ledo. Refinando el esquema
de la forma adecuada obtenemos este algoritmo:
algoritmo valMax
{ En la entrada hay una secuencia no vaca de reales que no contiene ningn 0.0 seguida
de 0.0 }
var
x, max: real;
fvar
x := leerReal();
inicioTratamiento
mientras x 0.0 hacer
{ max es el mayor de los que se han ledo antes de x }
si x > max entonces max := x
fsi
x := leerReal()
fmientras
{max es el mayor real de los que se han ledo antes de x y x es la marca}
escribirReal(max)
falgoritmo
Hemos refinado el esquema de recorrido de la entrada excepto la parte inicioTratamiento, porque requiere pensarla un poco ms que las otras. Debemos llevar a cabo las inicializaciones que
sea necesario para que { max es el mayor real de los que se han ledo antes de x }. Sin embargo,
dado que antes de x no se ha ledo nada, no est definido cul debe ser el valor de max. Lo
que debemos hacer es tratar el primer elemento (la precondicin nos garantiza que existe)
antes del bucle y el resto de los elementos en el bucle. Entonces tenemos:
algoritmo valMax
{ En la entrada hay una tira no vaca de reales que no contiene ningn 0.0 seguida de 0.0 }
var
Tratamiento secuencial
FUOC PID_00149891
51
x, max: real;
fvar
x := leerReal(); max := x; x := leerReal();
mientras x 0.0 hacer
{ max es el mayor real de los que se han ledo antes de x }
si x > max entonces
max := x
fsi
x := leerReal()
fmientras
{ max es el mayor real de los que se han ledo antes de x y x es la marca }
escribirReal(max)
falgoritmo
3. La especificacin es sencilla, y la podis ver reflejada con predicados en la propia accin.
Por lo que respecta a la estrategia de solucin, la podemos enfocar como una bsqueda: buscamos si existe un elemento que es menor que el anterior. Si esta bsqueda tiene xito, la
secuencia no es creciente. Si la bsqueda fracasa, la secuencia es creciente.
Notad que la bsqueda se debe realizar a partir del segundo elemento, porque el primer elemento no tiene anterior. Notad tambin que si la secuencia es vaca, no habr primer elemento (consideraremos que una secuencia de 0 1 elementos es creciente).
accion esCrec ()
{ En la entrada hay una secuencia no vaca de reales que no contiene ningn 0.0 seguida
de 0.0 }
var
x, ant: real;
encontrado: booleano;
fvar
x := leerReal(); encontrado := falso;
si x 0.0 entonces
ant := x; x := leerReal();
mientras no encontrado y x 0.0 hacer
{ Los elementos ledos excepto el ltimo, que se encuentra en x y no es la marca,
forman una serie creciente, y en ant est el elemento anterior a x }
encontrado := x < ant;
si no encontrado entonces
ant := x;
x := leerReal()
fsi
fmientras
fsi
{ encontrado indica si la secuencia no es creciente }
si encontrado entonces escribirCaracter(N)
sino escribirCaracter(S)
fsi
faccion
4. Resolveremos el problema calculando primero la suma de los divisores y, a continuacin,
la compararemos con el nmero. Para calcular la suma de los divisores de N, podemos hacer
un recorrido de una secuencia imaginaria de todos los nmeros mayores que 0 y menores
que N; el tratamiento consiste en acumular el nmero actual si es divisor de N.
Utilizaremos una variable que ir tomando los valores de la secuencia. De este modo, prepararSecuencia es asignar 1 a i; el final de la secuencia lo detectamos cuando i = N y avanzarSecuencia
se refina como un incremento de i.
algoritmo esPerfecto
{ En la entrada hay un nmero entero mayor que 0 }
var
x, i, sum: entero;
fvar
x := leerEntero();
i := 1; sum := 0;
mientras i < x hacer
{ sum es la suma de los divisores de x menores que i }
si x mod i = 0 entonces
sum := sum + i
fsi
i := i + 1
Tratamiento secuencial
FUOC PID_00149891
52
fmientras
{ sum es la suma de los divisores de x menores que x }
si sum = x entonces escribirCaracter(S)
sino escribirCaracter(N)
fsi
falgoritmo
5. La parte entera de la raz cuadrada de un entero positivo n la podemos encontrar mediante
una bsqueda en la secuencia de todos los naturales. Buscamos el primer elemento x que
cumple la propiedad x al cuadrado > n; el valor que nos piden es x 1. Por ejemplo:
La raz cuadrada de 0 es 0, que es igual a 1 1, siendo 1 el primer valor que, elevado al cuadrado, da un resultado mayor que 0.
La parte entera de la raz cuadrada de 5 es 2, que es igual a 3 1, siendo 3 el primer valor que,
elevado al cuadrado, da un resultado mayor que 5.
Refinamos prepararSecuencia con i := 0 y avanzarSecuencia con i := i + 1. tratamientoFinal no lo
refinamos porque estamos haciendo una bsqueda en una secuencia infinita. Sin embargo,
tenemos la garanta de que la bsqueda acabar porque siempre encontraremos un elemento
que cumpla la propiedad buscada.
accion raizCuadr(ent x: entero)
{x0}
var
i: entero; encontrado: booleano;
fvar
encontrado := falso; i := 0;
mientras no encontrado hacer
{ Ninguno de los nmeros menores que i elevado al cuadrado da un resultado mayor
que x }
encontrado := i * i > x;
si no encontrado entonces
i := i + 1
fsi
fmientras
{ Ninguno de los nmeros menores que i elevado al cuadrado da un resultado mayor que
x, pero i al cuadrado es mayor que x }
escribirEntero(i 1)
faccion
Glosario
bsqueda
Proceso que tiene como objetivo localizar un elemento que cumple una propiedad determinada.
cabezal
Nocin abstracta que permite determinar en cada momento el elemento actual de la secuencia
(aquel al que tenemos acceso en un momento dado). El nombre de cabezal se debe a la analoga
con el cabezal de los aparatos reproductores de casete, vdeo u otros.
esquema algortmico
Algoritmo genrico que sirve para solucionar un conjunto de problemas de un tipo determinado. No est expresado totalmente en lenguaje algortmico, sino que es un tipo de plantilla
que contiene unas partes que deben ser sustituidas por acciones y expresiones en lenguaje
algortmico. Estas acciones y expresiones dependern del problema concreto que estemos solucionando en aquel momento.
marca
Elemento especial que nos determina el final de la secuencia. Este elemento, normalmente,
no se debe tratar. Las marcas son especialmente tiles en las secuencias que leemos del dispositivo de entrada, donde representan una forma bastante cmoda de determinar el final
de la secuencia.
recorrido
Proceso que tiene como objetivo hacer un tratamiento a todos los elementos de una secuencia.
refinar (un esquema)
Sustituir las partes del esquema algortmico correspondiente por conjuntos de acciones o expresiones (segn lo que convenga). De esta forma convertimos un esquema algortimico en
un algoritmo que soluciona un problema concreto.
Tratamiento secuencial
FUOC PID_00149891
53
secuencia
Conjunto de elementos del mismo tipo que estn en un orden determinado (hay un primero,
un segundo, un tercero, etc.) Slo podemos acceder a sus elementos uno a uno, empezando
por el primero y siguiendo en el orden en que estn dispuestos los elementos en la secuencia.
secuencia vaca
Secuencia que no tiene ningn elemento.
tratamiento secuencial
Proceso que consiste en recorrer una secuencia (o bien una de sus partes) con el objetivo de
realizar algn clculo sobre sus elementos.
Bibliografa
Burgus, X.; Franch, X. (1998). Tratamiento secuencial. En: P. Botella; M. Bofill; X. Burgus;
R. Lagonigro; J. Vancells (1998). Fundamentos de Programacin I (mdulo 3). Barcelona: Ediuoc.
Son los apuntes anteriores de esta asignatura. Utilizan una especificacin formal y un modelo
fsico de secuencias que aqu no tenemos.
Castro, J.; Cucker, F.; Messeguer, X.; Rubio, A.; Solano, L.; Valles, B. (1992). Curs de
programaci. Madrid, etc.: McGraw-Hill.
Scholl, P. C.; Peurin, J.P. (1991). Esquemas algortmicos fundamentales. Secuencias e iteracin.
Manuales de informtica Masson. Masson.
Vancells, J.; Lpez E. (1992). Programaci: introducci a lalgorsmica. Vic: Eumo Editorial
(Tecno-Cincia).
Tratamiento secuencial