74
J. Jaime A. Pepe'00 PM 1 TEMA 1.- INTRODUCCIÓN A LA INGENIERÍA DEL SOFTWARE. * Programación modular: Se pretende hacer que los programas sean reutilizables. Haremos programas modulares, a partir de una definición y una implementación construimos una librería. En la definición (fichero DEF) expongo el interfaz y los procedimientos que necesito, todo DEF tiene asociado un MOD que contiene el motor necesario para que el DEF funcione. El programa únicamente ve lo que le deja ver el DEF. * Introducción a la ingeniería del software: La evolución de la informática sucede de la siguiente manera: hardware: cada vez más rápido, pequeño y barato. software: cada vez más lento, grande y costoso. Se hace cada vez más complicado desarrollar buen software, se llega a hablar de una crisis del software. Son necesarios grandes análisis y grupos de desarrolladores muy bien estructurados y compenetrados y en gran número para desarrollar buen software. * Ciclo de vida del software: Es la sucesión de etapas por las que pasa el software desde que un proyecto es concebido hasta que se llega a utilizar. Un error será más fácil y económico corregirlo durante las primeras etapas que en etapas más avanzadas. Existen varios paradigmas de la vida del software: en cascada, contractual, técnicas de 40 generación, construcción de prototipos y modelo en espiral. * En cascada: Está basado en delimitar una serie de etapas consecutivas a realizar en secuencia una detrás de otra suponiendo que cada una de las etapas anteriores se ha completado correctamente (gran problema). Estas etapas son: -análisis - diseño - codificación - prueba - utilización - mantenimiento. - análisis: recopilación del máximo de información del dominio donde va a ser usado el diseño, el resultado del análisis es la especificación de requisitos. El análisis lo hacen los analistas. Un fallo en el análisis implica un gran fallo en el resultado. - diseño: a partir de las especificaciones procedentes del análisis se estudian los datos, teorías, librerías, etc, necesario para completar el diseño. Se traducen los requisitos a una representación del software de tal manera que pueda conocerse la arquitectura, funcionalidad y calidad del mismo antes de que se empiece la codificación. En el diseño no se concreta ni ordenador ni lenguajes ni algoritmos, etc. - codificación: los programadores convierten las especificaciones de diseño a un lenguaje de

Programacion Modular

Embed Size (px)

Citation preview

Page 1: Programacion Modular

J. Jaime A. Pepe'00 PM 1

TEMA 1.- INTRODUCCIÓN A LA INGENIERÍA DELSOFTWARE.

* Programación modular:Se pretende hacer que los programas sean reutilizables. Haremos programas modulares, a partirde una definición y una implementación construimos una librería. En la definición (fichero DEF)expongo el interfaz y los procedimientos que necesito, todo DEF tiene asociado un MOD quecontiene el motor necesario para que el DEF funcione. El programa únicamente ve lo que le dejaver el DEF.

* Introducción a la ingeniería del software:La evolución de la informática sucede de la siguiente manera:

hardware: cada vez más rápido, pequeño y barato.software: cada vez más lento, grande y costoso.

Se hace cada vez más complicado desarrollar buen software, se llega a hablar de una crisis delsoftware.Son necesarios grandes análisis y grupos de desarrolladores muy bien estructurados ycompenetrados y en gran número para desarrollar buen software.

* Ciclo de vida del software:Es la sucesión de etapas por las que pasa el software desde que un proyecto es concebido hastaque se llega a utilizar.Un error será más fácil y económico corregirlo durante las primeras etapas que en etapas másavanzadas.Existen varios paradigmas de la vida del software: en cascada, contractual, técnicas de 40generación, construcción de prototipos y modelo en espiral.

* En cascada:Está basado en delimitar una serie de etapas consecutivas a realizar en secuencia una detrás deotra suponiendo que cada una de las etapas anteriores se ha completado correctamente (granproblema). Estas etapas son:-análisis

- diseño- codificación

- prueba- utilización

- mantenimiento.

- análisis: recopilación del máximo de información del dominio donde va a ser usado el diseño,el resultado del análisis es la especificación de requisitos. El análisis lo hacen los analistas. Unfallo en el análisis implica un gran fallo en el resultado.- diseño: a partir de las especificaciones procedentes del análisis se estudian los datos, teorías,librerías, etc, necesario para completar el diseño. Se traducen los requisitos a una representacióndel software de tal manera que pueda conocerse la arquitectura, funcionalidad y calidad delmismo antes de que se empiece la codificación. En el diseño no se concreta ni ordenador nilenguajes ni algoritmos, etc.- codificación: los programadores convierten las especificaciones de diseño a un lenguaje de

Page 2: Programacion Modular

J. Jaime A. Pepe'00 PM 2

programación. Es la etapa más larga en el desarrollo (sin tener en cuenta el mantenimiento).- prueba: testeo del producto.- utilización: el cliente paga y se queda el producto.- mantenimiento: es un proceso muy largo en el que se corrigen errores en distintas versiones yse van a quitar o añadir cosas al producto (lo que el cliente pide y lo que se le da no tiene nadaque ver). El mantenimiento consiste en volver a empezar todo el ciclo. Antiguamente no habíamantenimiento, el ciclo del software terminaba en la utilización.

Todos los demás ciclos son variaciones de este.

El problema de este ciclo es la lentitud. Lo ideal es que el cliente observe el proceso, hacer unproceso más transparente al cliente. El analista distorsiona mucho el proyecto, lo ideal sería queel cliente fuese el propio analista. Para ello están los otros modelos de ciclo.

* Modelo contractual:Se van a cerrar cada una de las etapas de una forma exitosa y rápida mediante contratos deservicios de corto plazo en los que le voy a dar al cliente productos intermedios. A partir de esosresultados se pueden desarrollar más resultados.

cliente proveedor

análisis usuario analista

diseño analista diseñador

codificación diseñador programador

Con este método no ocurren grandes catástrofes si se detectan errores, solo pequeñas catástrofeslocalizadas.

* Técnicas de 40 generación:Se utilizan métodos informáticos para el desarrollo. Modifican un poco el ciclo de vida en cuantoa que la generación del código puede ser automatizable, a partir de las especificaciones delanalista que será el propio usuario en este caso; el usuario sería capaz de especificar gráficamentequé es lo que quiere, y el sistema especificaría el diseño y generaría el código final compilabley el ejecutable.Todos los errores serían corregidos por el propio usuario.Esta generación de código automática es muy ineficiente por lo que suele ser necesario que unprogramador lo revise.Se usan entornos gráficos en la programación, esta técnica es uno de los grandes retos de laingeniería del software.

Page 3: Programacion Modular

J. Jaime A. Pepe'00 PM 3

recogida derequisitos

evolución delprototipo

diseñorápido

construccióndel prototipo producto

final

* Construcción de prototipos:Los prototipos tratan de caricaturizar lo que hace el sistema, sin saber más de lo que pasaría (nosabes como va a actuar el sistema).Este método lo que busca es acelerar la construcción del programa final aunque sea una cáscarahueca en su aspecto, sin ninguna funcionalidad, el cliente solo vería el interfaz del usuario.

Se puede diferenciar:

Un prototipo suele tener que funcionar en una semana.Este método ha empezado a ser usado cuando se le ha dado importancia al interfaz del usuario.Las herramientas de prototipado suelen ser lenguajes de muy alto nivel (Delphi, Visual Basic,Power Builder, Clipper.....).

* Modelo en espiral (o evolutivo):Es un modelo que racionaliza el ciclo de vida, hasta ahora solo hemos visto pequeñas mejoras.En él se plantea el ciclo de vida en cascada pero de una forma evolutiva con una característicafundamental: hace un análisis de riesgo constantemente y una vuelta al inicio del ciclo de unaforma periódica.

planificación análisis

evoluciónprototipos desarrollo

Page 4: Programacion Modular

J. Jaime A. Pepe'00 PM 4

* Técnicas de programación:Característica de calidad de un sistema modular:Se busca una calidad mínima del sistema, se mide por la cohesión, es la dependencia conceptualque cada uno de los elementos de módulo va a tener. Buscar cohesión es buscar módulos fácilesde describir simplificando suocalización dentro de un proyecto, la cohesión también va a permitirla compatibilidad.Un módulo es una agrupación de objetos del sistema, por lo que sería coherente si todo el módulova definido a una misma utilidad. Coherente significa que todo lo que escribo en un paquetetiene que ver una cosa con otra.Es más coherente el MATHLIB que el IO.Un módulo es más fácilmente coherente mientras más pequeño sea (menos funciones tenga).

Otra característica es el acoplamiento: grado de dependencia entre los componentes. Existentambién acoplamientos temporales, dependiendo del momento en el que se hagan las cosas.Si los sistemas están muy interrelacionados es más difícil modularizar (descomponer) el sistema.El acoplamiento no es agradable en la descomposición, es inverso a la cohesión. Lo ideal sonelementos tan sueltos que no dependan de nada (ideal un poco absurdo), normalmente se buscael mínimo de acoplamiento y el máximo de cohesión.El acoplamiento también se mide como el Fan-In (abanico de entrada) o como el Fan-Out(abanico de salida).

La dependencia funcional es el grado de división de funciones que pueda existir dentro de unsistema, cada uno de los componentes tiene funciones independientes si cada uno trata de casosdistintos.

Se utiliza también técnicas de modulación y abstracción. Modular consiste en reunir elementoscoherentes. Abstraer es aislar elementos del resto (no hacer muy acoplado).Si un módulo consigo hacerlo abstracto (elimino lo innecesario) se consigue menoracoplamiento.La abstracción lleva a la ocultación de datos, solo se dejan ver los datos necesarios, no todos.

Es mucho más fácil documentar módulos poco acoplados, coherentes y con una interfaz opacay sencilla, ayuda mucho en los trabajos en grupos (se mantiene la idea).Una programación que se apoya en todo esto es la programación orientada a objetos. Los rasgosde la orientación a objetos permiten diseñar el sistema enfocado no a módulo, sino directamentea conjuntos de las características del sistema (objetos), cada objeto presenta un interfaz y unaserie de rutinas y mediante esto intenta describir cada una de las partes del sistema (un objeto noes lo mismo que un módulo).La programación orientada a objeto da programas muy coherentes y poco acoplados.La otra forma de programar (la de toda la vida) es la programación funcional.

* Bibliografía:Ian Somerville, Ingeniería del Software, Addison-Wesley Iberoamericana, 1988.Roge S. Pressmanm, Ingenieríca del Software, Mc Graw Hill, 1993.

Page 5: Programacion Modular

J. Jaime A. Pepe'00 PM 5

TEMA 2.- PUNTEROS.Los punteros no son un tipo de datos usual puesto que no se hace ninguna operación con ellosexcepto la de referenciar la memoria del ordenador. Mediante un objeto de estos guardo ladirección de una zona de memoria (índice).Para asignar el valor al puntero usamos las instrucciones propias del lenguaje, igualmente parala lectura de la dirección. No tenemos que preocuparnos de la información que tenga el puntero.El propio puntero usa una dirección de memoria.

En resumen, un puntero es un índice de memoria RAM (una referencia a la RAM).

El puntero nos va a permitir desarrollar estructuras de datos dinámicas que estén libres de tamañoprefijados durante la compilación y que puedan tener una estructura (interconexión) definida pornuestro propio algoritmo.También sirven para implementar técnicas de ejecución imposibles de recoger en un lenguaje.

Un puntero en sí es una variable con un tamaño fijo, que se carga y utiliza durante la ejecucióndel programa.Una variable de datos dinámica es una variable que puede cambiar de tamaño durante laejecución del programa (sin tamaño fijo).El puntero NNOO es una estructura de datos dinámica, solo me sirve para montarlas.

La declaración de un puntero en Modula-2 es:VAR p: POINTER TO INTEGER•> a lo que apunta

Un puntero puede apuntar a cualquier tipo que anteriormente se haya definido.

j, i: INTEGER;

BEGINi := 1;p? (* no se sabe lo que vale *)

un puntero sin inicializar no se sabe lo que contiene, se les suelen llamar punteros locos.p := ADR(i);

ADR( ) nos devuelve la dirección de memoria que ocupa el objeto referido. Esto se denominareferenciar, pero me hace falta dirigirme a la dirección de memoria, esto se denominaderreferenciar. Para derreferenciar se usa el operador ^:

p^ significa "lo apuntado por p"

p^ := i + 3;

Al final la i valdrá 4 porque la p está apuntando a i (30 línea del BEGIN).

Page 6: Programacion Modular

J. Jaime A. Pepe'00 PM 6

Puedo hacer:p := ADR(var);p^ := <alguna operación>;p := ADR(var2);p^ := <alguna operación>;

y cada variable permanece sin variar (cada una en su dirección de memoria).

* Generación dinámica de memoria:P. ej.: puedo pasar una variable por copia y modificarla, solo necesito un puntero:

PROCEDURE P(p :POINTER);

BEGINp^ := 33;

END P;

BEGINp := ADR(i);P(p);

END;aunque este método no es muy recomendable.

Si modificamos el ejemplo anterior:PROCEDURE P(p :POINTER): POINTER;

VAR j: INTEGER;

BEGINp^ := 33;RETURN ADR(j);

END P;

VAR p, PP: POINTER;

BEGINp := ADR(i);PP := P(p);PP^ := 34;

END;pero la variable PP no existe, estoy escribiendo en una RAM no válida, es muy peligroso.

Page 7: Programacion Modular

J. Jaime A. Pepe'00 PM 7

Se podría escribir p^^ y el sistema interpretaría que la dirección a la que apunta p contiene ladirección de otra variable.

TYPE POINTEGER: POINTER TO INTEGER;PP : POINTEGER;p : POINTER TO POINTEGER;

sería necesario esta estructura de datos para hacer lo del p^^.

VAR i : INTEGER;

BEGINPP := ADR(i); (* el orden de estas dos líneas *)p := ADR(PP); (* es indiferente *)p^^ := 3; (* (p^)^ := 3 *)p^ := ADR(j); (*cambiaría la dirección de lo apuntado por p, sería lo mismo *)

(* que cambiar PP *)

Desde el momento de la compilación un programa tiene reservada una cantidad de memoriaestática para sus variables: hay una zona de RAM reservada para constantes, otra zona paravariables globales y otra para las variables locales a los procedimientos. Esta última zona no estan estática. Además de estas tres zonas el sistema tiene el Heap.Partición de la RAM:

Variablesprocedimientos

(dinámicas)Heap

variablesglobales constantes código

El sistema permite reservar y utilizar esa zona libre de RAM (Heap). Hay procedimientos quereservan parte de esa memoria y nos dicen donde está esa reserva.

Para reservar el espacio se usa el procedimiento NEW( ) de la librería Storage (realmente es unparche). NEW devuelve un puntero al lugar reservado por él (11 reserva RAM, 21 indica cuales el lugar reservado).

NEW (p);

NEW es en realidad un máscara del procedimiento Allocate:p := Allocate (SIZE(p^));

Allocate y Deallocate hay que importarlos de la librería Storage.

Cuando se reserva una zona de memoria nadie más va a poder usarla.

Si quiero liberar ese bloque de memoria reservado con NEW dispongo de DISPOSE( ), tambiénes una máscara, en este caso de Deallocate. Deallocate pone el puntero p a NIL.

NIL es una constante compatible con todos los tipos de puntero (apunten a lo que apunten) queindica un lugar absurdo en la RAM (comúnmente la dirección 0), es un valor de puntero queindica que no tiene valor. Es una manera de inicializar el puntero.

Page 8: Programacion Modular

J. Jaime A. Pepe'00 PM 8

Después de DISPOSE(p) es recomendable poner p := NIL, aunque no es imprescindible en todoslos Modula-2. Es una manera de anular un puntero.Hay otra función en Storage que me devuelve la cantidad de bytes disponibles, es Avaible,devuelve un cardinal.

Una broma (pesada):NEW(p);PP := p;

< opero con p^ >

DISPOSE(p);p := NIL;PP^ :=3; (* a saber lo que pasará *)

Si libero dos veces un bloque de memoria no pasa nada.

* Creación de memoria de tamaño variable mediante una lista enlazada:Se va a enlazar una lista de bloques.

TYPE P = POINTER TO BLOQUE; BLOQUE = RECORD

info: CARDINAL;siguiente: P;

END;Cada vez que creo un bloque se prepara un enganche con otro.Este desorden de tipos se permite para la memoria dinámica.Los pointers a objetos no declarados están permitidos siempre y cuando el objeto se declarealguna vez.

Ejemplo: hacer un programa que le pida al usuario un número tras otro ilimitadamente hasta quese introduzca el 0:

TYPE LISTA = POINTER TO NODO NODO = RECORD

info: CARDINAL;next: LISTA;

END;

VAR n: CARDINAL; t, p: LISTA;

BEGINt := NIL; p := NIL;LOOP

n := RdCard( );IF n = 0 THEN EXIT END;

Page 9: Programacion Modular

J. Jaime A. Pepe'00 PM 9

NEW(t);WITH t^ DO (* lo mismo que: *)

info := n; (* t^.info := n *)next := p; (* t^.next := p *)

END;p := t;

END;END;

p info info info info

dir dir dir$$$$$$$$$$$

dir

NIL

Esta forma de crear la lista es "de atrás a adelante" y no es la mejor forma.Para recorrer cualquier lista enlazada:

t := p;WHILE t < > NIL DO

WrCard(t^.info, 0); (* o cualquier otra acción *)t := t^.next;

END;

Ejercicio: hacer un programa que pidiendo números al usuario los vaya enlazando al final de unalista de bloques dinámicos de memoria:

TYPE LISTA = POINTER TO NODO; NODO = RECORD

info : CARDINAL;next : LISTA;

END;

VAR n: CARDINAL; tt, p, t: LISTA;

BEGINt := NIL;LOOP

n := RdCard();IF n = 0 THEN EXIT END;NEW(t);IF t = NIL THEN HALT END;t^.info := n;t^.next := NIL;IF p # NIL THEN

tt := p;WHILE tt^.next # NIL DO

tt := tt^.next;

Page 10: Programacion Modular

J. Jaime A. Pepe'00 PM 10

END;tt^.next := t;

ELSEp := t;

END;

Ejemplo: borrar por posición:PROCEDURE BorraPos(VAR lista: PTRFICHA; pos: CARDINAL);

VAR l, l2: PTRFICHA; n: CARDINAL;

BEGINl := lista;IF pos = 1 THEN

l2 := lista;lista := lista^.sigui;

ELSEn := 1;l := lista;WHILE (l^.sigui # NIL) AND ( n < pos -1) DO

l := l^.sigui;INC(n);

END;IF l^.sigui = NIL THEN HALT END;l2 := l^.sigui; (* resumible en: *)l^.sigui := l2^.sigui; (* l^.sigui := l^.sigui^.sigui *)

END;DISPOSE(l2); (* ((((((( OJO OJO OJO OJO !!!!!!! *)

END BorraPos;

Ejemplo: borrar buscando un elemento:PROCEDURE Borranombre(VAR lista: LISTA; nombre: STR32);

VAR l, l2: LISTA;

BEGINl := lista;IF lista = NIL THEN RETURN END;IF Compare(l^.nombre, nombre) = -1 THEN

l2 := lista;lista := lista^.sigui;

ELSEl := lista;WHILE (l^.sigui # NIL) AND (Compare(l^.sigui^.nombre, nombre) <= 0) DO

l := l^.sigui;

Page 11: Programacion Modular

J. Jaime A. Pepe'00 PM 11

END;IF l^.sigui = NIL THEN RETURN END;l2 := l^.sigui;l^.sigui := l2^.sigui;

END;DISPOSE(l2);

END Borranombre;

Ejercicio: obtener el puntero a partir de la posición:PROCEDURE PuntFromPos(l: LISTA; pos: CARDINAL): LISTA;

VAR i: CARDINAL; t: LISTA;

BEGINIF pos = 1 THEN RETURN l END;t := l; i := 1;WHILE (t # NIL) AND (i < pos) DO

INC(i);t := t^.sigui;

END;RETURN t;

END PuntFromPos;

Ejemplo: buscar en una lista enlazada:PROCEDURE Buscar(lista: LISTA; nombre: STR32): CARDINAL;

VAR l: LISTA; n: CARDINAL;

BEGINl := lista;n := 1;WHILE (l # NIL) AND (Compare(l^.nombre, nombre) # 0) DO

l := l^.sigui;INC(n);

END;IF l = NIL THEN RETURN 0 END;RETURN n;

END Buscar;

Ejemplo: insertar de una forma ordenada:

Page 12: Programacion Modular

J. Jaime A. Pepe'00 PM 12

PROCEDURE InsertOrd(VAR: lista: LISTA; nombre: STR32);

VAR l, l2: LISTA;

BEGINNEW(l);l^.nombre := nombre;l^.sigui := NIL;IF lista := NIL THEN

lista := l;ELSIF Compare(lista^.nombre, nombre) < 0 THEN

l^.sigui := lista;lista := l;

ELSEl2 := l;WHILE (l2^.sigui # NIL) AND (Compare(nombre, l2^.sigui.nombre) <= 0) DO

l2 := l2^.sigui;END;l^.sigui := l2^.sigui;l2^.sigui := l;RETURN

END;END InsertOrd;

Page 13: Programacion Modular

J. Jaime A. Pepe'00 PM 13

TEMA 3.- RECURSIVIDAD.Técnica que permite desarrollar algoritmos en una indefinición de estos.Un algoritmo es recursivo si durante su ejecución vuelve a ser llamado desde el principio, estádefinido en término de si mismo. Si durante la ejecución de un algoritmo recursivo el algoritmoes llamado a si mismo y con mayor o igual complejidad, el algoritmo no terminarà nunca.Cada llamada interna a sí mismo debe de ser más simple que la anterior, nunca de igual o mayorcomplejidad.Todo algoritmo recursivo debe tener una forma de ser ejecutado en la que no haya recursión (lallamada más simple de todas).

Un ejemplo de recursión fácil es el cálculo del número factorial:PROCEDURE FactRecur(n: CARDINAL): CARDINAL;

BEGINIF (n = 0) OR (n = 1) THEN RETURN 1 END;RETURN n * FactRecur(n-1);

END FactRecur;

En general la recursión es menos efectiva pero más expresiva.

Ejemplo: suma de Fibonacci:en recursivo:

PROCEDURE Fibo(n: CARDINAL): CARDINAL;

BEGINIF n < 2 THEN RETURN 1;ELSE

RETURN Fibo(n-1) + Fibo(n-2);END;

END Fibo;en iterativo:

PROCEDURE Fibo(n: CARDINAL): CARDINAL;

VAR n1, n2, sol, i: CARDINAL;

BEGINn1 := 1; n2 := 1;WHILE i < n DO

sol := n1 + n2;n1 := n2;n2 := sol;INC(i);

END;RETURN sol;

END Fibo;

Page 14: Programacion Modular

J. Jaime A. Pepe'00 PM 14

Recursión indirecta: cuando la recursión no se produce sobre el primer algoritmo sino sobre unallamada a un segundo algoritmo que a su vez llama al primero.

PROCEDURE F(x: REAL): REAL;

BEGINIF x < 0.0 THEN

RETURN x * (H(3.0 * x - 1.0)) - 5.0;ELSE

RETURN 2.0;END;

END F;

PROCEDURE H(x: REAL): REAL;

BEGINIF x < 0.0 THEN

RETURN (5.0 * x - 2.0);ELSIF x < 20.0 THEN

RETURN x * H(x * x + 1.0) - F(x * x);ELSE

RETURN 2.0 - F(x);END;

END H;

Para arreglar el desorden en la posición de las funciones en llamadas dóblemente recursivas seusa FORWARD en el interfaz, indica que ese algoritmo se especifica más adelante:

PROCEDURE H(x: REAL): REAL; FORWARD;

PROCEDURE F::

END F;

PROCEDURE H(x: REAL): REAL;::

END H;

- Tipos de recursión:Recursión simple: el programa se llama a si mismo en un solo punto del código (númerofactorial).Recursión doble: el programa se llama a si mismo dos veces (en dos puntos del código), comola suma de Fibonacci.

Page 15: Programacion Modular

J. Jaime A. Pepe'00 PM 15

Recursión de cabeza: la recursión se produce al principio.Recursión de cola: la recursión se produce al final.Recursión intermedia: resto de casos (ni cabeza ni cola).

Las recursiones de cabeza y de cola son realmente iteraciones enmascaradas; p. ej.: el factorial,la recursión es realmente un bucle FOR. La recursión de cabeza y de cola se pueden tratarprácticamente de la misma manera.Una recursión intermedia siempre se puede convertir a iteración aunque el resultado puede sertotalmente distinto y el método no tan simple, se hace usando dos pilas: una para llamadas y otrapara datos.

* Ejecución de un algoritmo recursivo:Cada vez que se hace una llamada a una recursión se genera dinámicamente en la pila del sistemauna zona para las variables (parámetros locales incluidos) de esa función; además se guarda enesa pila la dirección de vuelta, de manera que al finalizar el procedimiento el sistema sea capazde volver al punto desde el cual se llamó.Cada vez que se produce una llamada, la pila se vuelve a generar.

Cuando termina una llamada a un procedimiento se "limpia" la pila leyéndose la dirección departida para regresar liberando la memoria.Conforme voy volviendo en las llamadas me encuentro los ámbitos de las llamadas anteriores.

Por todo esto, gracias a las pilas puede existir la recursión.

Si una llamada recursiva es muy profunda se genera mucho espacio en pila, por lo que larecursión es peligrosa en cuanto a que puede agotar el espacio en la pila (que no es mucho); sise pasa puede que escriba en zonas de memoria sin control, incluso que llegue a escribir lamemoria del programa y que sucedan cosas raras; o puede que simplemente se pare el programa(HALT, pero no un cuelgue), depende del sistema operativo y la seguridad que tenga en cuantoa pilas.

Ejercicio: función de Ackermann:A(0, s) = 2s s $ 0A(r, 0) = 0 r $ 0A(r, s) = A(r-1, A(r, s-1)) s > 1

Para valores muy bajos (2 ó 3) el sistema cuelga (en S.O.'s normales), en S.O.'s grandes llega a4 o 5, poco más; este algoritmo queda como una forma de probar la capacidad recursiva y elcontrol de pilas de un S.O.

* Recursión vs iteración:Los desarrollos recursivos suelen ser más legibles y más cortos de escribir pero generan dos tiposde ineficiencias:

11.- técnica: debido a la sobrecarga que le produce al sistema la operación de llamadaa procedimientos.20.- complejidad: debida a la cantidad de redundancias que se producen en la llamadarecursiva a los procedimientos.

Page 16: Programacion Modular

J. Jaime A. Pepe'00 PM 16

La complejidad técnica de una función iterativa es n, en una recursión simple (número factorial)también es n.La suma de Fibonacci, por ejemplo, tiene muchas redundancias internas.

Ejemplo: MCD(m, n):m si n # m y m MOD n = 0MCD(n, m) si n < mMCD(n, m MOD n)

PROCEDURE MCD (m, n: CARDINAL): CARDINAL;

BEGINIF ( n <= m) AND (m MOD n = 0) THEN (* sustituible por n = 0 *)

RETURN m;ELSIF m < n THEN

RETURN MCD(n, m);ELSE

RETURN MCD(n, m MOD n);END;

END MCD;

Ejemplo: tenemos un array de números ordenados decrecientemente; desarrollar el procedimientode búsqueda binaria de manera recursiva.

PROCEDURE BB(a: ARRAY OF CARDINAL; x: CARDINAL; VAR pos: CARDINAL):BOOLEAN;

PROCEDURE bb(i0, i1: CARDINAL): BOOLEAN;

VAR m: CARDINAL;

BEGINIF i >= i1 THEN RETURN END;m := (i0 + i1) DIV 2;IF a[m] = x THEN

pos := m;RETURN TRUE;

ELSIF a[m] < x THENRETURN bb(m + 1, i1);

ELSERETURN bb(i0, m - 1);

END;END bb;

BEGINRETURN bb(0, HIGH(a));

END BB;

Page 17: Programacion Modular

J. Jaime A. Pepe'00 PM 17

Ejemplo: hallar el número total de números iguales cercanos en una malla (práctica del 15-03-99)[orientación del problema]:PROCEDURE Malla(m: MATRIZ): CARDINAL;

PROCEDURE columna(j);

PROCEDURE fila(i);

END fila;END columna;

END Malla;

Ejemplo borrado de una lista, por recursión:PROCEDURE Delete(VAR l: LIST; x: BASE);

VAR tmp: LISTA;

BEGINIF l = NIL THEN RETURN END;IF l^.info = x THEN

tmp := l;l := l^.next;DISPOSE(tmp);

ELSEDelete(l^.next, x);

END;END Delete;

Ejercicio: hacer un algoritmo que borre todos los elementos de la lista:

- Las Torres de Hanoi: o a d

Colocar los discos de o en d ayudado con a, con lacondición de no poner nunca un disco grandeencima de otro pequeño, uno a uno.

PROCEDURE hanoi(n: CARDINAL; source, target, inter: CHAR);

BEGINIF n > 0 THEN

hanoi(n - 1, source, inter, target);(* mover disco de origen a destino *)hanoi(n - 1, inter, target source);

END;END Hanoi;

Page 18: Programacion Modular

J. Jaime A. Pepe'00 PM 18

- Ordenación QuickSort (o método de Hoare):Este método divide el array en dos partes basándose en un elemento pivote que equilibra lacadena. Se repite con cada una de las dos zonas y así sucesivamente hasta que tengo un array dedos elementos.

P' P < P' >P P <

Voy recorriendo la cadena así:•> a b <•

>P P <P

si a y b están desordenados => SWAP(a, b), hasta que los índices sean iguales.

El mejor pivote es la mediana, pero para ahorrar tiempo se coge el 1er elemento.

P. ej.: ordenar fedcba:aedcbfabdcefabcdef •> ordenado

PROCEDURE QuickSort(VAR a: ARRAY OF CHAR);

PROCEDURE swap(VAR a: ARRAY OF CHAR; i, j: CARDINAL);

VAR t: CHAR;

BEGINt := a[i]; a[i] := a[j]; a[j] := t;WrStr(a); WrLn( ); (* testeo *)

END swap

PROCEDURE partition(VAR a: ARRAY OF CHAR; iz, de: INTEGER): CARDINAL;

VAR v, t: CHAR; i, j: INTEGER;

BEGINv := a[de];i := iz - 1;j := de;REPEAT

Page 19: Programacion Modular

J. Jaime A. Pepe'00 PM 19

REPEAT INC(i) UNTIL a[i] >= v;REPEAT DEC(j) UNTIL (j = -1) OR (a[j] < v);IF i < j THEN Swap(a, i, j); END;

UNTIL i >= j;Swap(a, i, de);RETURN i;

END partition;

PROCEDURE quick(VAR a: ARRAY OF CHAR; iz, de: INTEGER);

VAR p: INTEGER;

BEGINIF de > iz THEN

p := partition(a, iz, de);quick(a, iz, p-1);quick(a, p+1, de);

END;END quick;

BEGINQuick(a, 0, Length(a)-1);

END QuickSort;

Page 20: Programacion Modular

J. Jaime A. Pepe'00 PM 20

TEMA 4.- TIPOS ABSTRACTOS DE DATOS (TAD's).Un TAD es alguna forma de almacenar información y una serie de procedimientos de accesosa esa información.Se van a estudiar desde dos puntos de vista: abstracto y concreto de la implementación.El TAD es aplicable a todo lo visto y es un concepto válido en matemáticas.

Los elementos que forman un TAD deben cumplir:- completitud (suficiencia), con los procedimientos del TAD debemos ser capaces deconstruir cualquier instancia.- minimalismo, deben de ser los mínimos procedimientos para cumplir lo primero. Laventaja del minimalismo es que aumenta la portabilidad y la reutilizabilidad de los TAD,puesto que sé en cada momento qué cantidad de procedimientos son indispensables y lasdependencias del TAD (acoplamiento con el TAD) se hacen más pequeñas.

* Expresión en Modula-2:En la definición (fichero DEF) aparecerán el tipo lista (forma de almacenar) y los procedimientosde acceso:

TYPE LISTAPROCEDURE Crealista(VAR l: LISTA);PROCEDURE Destruir(VAR l: LISTA);PROCEDURE Insertar(VAR l: LISTA; pos: CARDINAL; x: BASE);PROCEDURE Borrar(VAR l: LISTA; pos: CARDINAL);

Estos son los procedimientos constructores de un TA lista (tipo abstracto lista).Una lista es una colección de elementos que tienen un índice (orden que se mueve entre 1 y elmáximo de la colección numerando cada uno de los elementos). No hay un número fijo deelementos. Las listas las definen por antonomasia las propiedades de inserción y borrado.Necesito procedimientos que me devuelvan información de la lista (selectores). Los selectoresmuestran información mínima suficiente del TAD para trabajar con él:

PROCEDURE LeeElem(l: LISTA; pos: CARDINAL): BASE;PROCEDURE Longitud(l: LISTA): CARDINAL;

Los selectores nunca modifican la estructura (nunca usar VAR l: LISTA en selectores).

Estos 6 procedimientos son la estructura TA lista.

Para el tipo en sí se pondría:TYPE LISTA;

El Modula-2 permite esta excepción, es un tipo opaco.En el fichero de implementación hay que definir significativamente el tipo:

TYPE LISTA = POINTER TO ..................el puntero siempre hay que usarlo en los TAD's y tipos opacos.Todos los TAD's llevan tipo de datos opacos.

CrearLista crea una estructura vacía para una lista.DestruirLista libera toda la memoria asignada a la lista y destruye la cabeza (la pone a NIL).Insertar mete un elemento en la secuencia. Las posiciones son 1 hasta Longitud(lista) + 1.

Page 21: Programacion Modular

J. Jaime A. Pepe'00 PM 21

Borrar quita un elemento concreto.LeerLista me devuelve la información de una posición.Longitud me devuelve la longitud de la lista.

- Implementación de la lista:Puede ser acotada (capacidad predeterminada) o no acotada (tamaño no determinado por elprogramador).La forma más simple de implementar una lista acotada es mediante array.

TYPE LISTA = POINTER TO BLOQUE; BLOQUE = ARRAY[1..MAX] OF BASE;

PROCEDURE Crealista(VAR l: LISTA);

BEGINNEW(l);

END;Este método va a dar problemas.Es mejor así:

TYPE LISTA = POINTER TO BLOQUE; BLOQUE = RECORD

arr: ARRAY[1..MAX] OF BASE;length: CARDINAL;

END;PROCEDURE Crealista(VAR l: LISTA);

BEGINNEW(l);l^.length := 0;

END Crealista;

Aunque esta implementación es la peor posible.

La implementación no acotada sería:TYPE LISTA = POINTER TO NODO; NODO = RECORD

info: BASE;sig: LISTA;

END;

PROCEDURE Crearlista(VAR l: LISTA);

BEGINl : = NIL;

END Crearlista;

l^.info := 3; (* no se puede hacer al ser opaco el tipo de la lista *)

Page 22: Programacion Modular

J. Jaime A. Pepe'00 PM 22

ArrayCursores

Implementación

de listas Sin cabecera

Con cabecera

Acotada

Modos simples enlzados. Modos dobles enlazados.

No acotada Modos simples enlzados. Modos dobles enlazados.

- Lista acotada:Array:Es necesario tener una variable tope.

Cursor:Ventajas: no requieren punteros para enlzar los nodos. Para enlazar no requieren elmovimiento de la lista. Pueden utilizarse como bloques de memoria.Cada nodo tiene la información base y "next" (un cardinal).Es necesario conocer cuál es el primero de los elementos que contienen la información.El último elemento es un cero. La lista se lee llendo a la variable primera y moviéndoseal campo siguiente.Hay que mantener además de los enlaces entre nodos con infromación otra "cadena" conlos nodos libres.La ventaja es que mantiene la eficiencia del array, pero tiene una limitación de tamaño.

- Modo dinámico enlazado (no acotada):Simple enlace: tenemos una implementación poco eficiente.PROCEDURE CrearLista (VAR l: LISTA);

BEGINl := NIL;

END Crearlista;

PROCEDURE InsertarLista(VAR l: LISTA; pos: CARDINAL; x: BASE);

VAR tmp, tt: LISTA; i: CARDINAL;

BEGINNEW (tmp);tmp^.inf := x;IF pos = 1 THEN

tmp^.next := l;l := tmp;

ELSEFOR i := 1 TO pos-2 DO(* pos-2 porque yas estoy en la que es y quiero avanzar una más *)

tt := tt^.next;

Page 23: Programacion Modular

J. Jaime A. Pepe'00 PM 23

END;tmp^.next := tt^.next;tt^.next := tmp;

END InsertaLista;

Si hacemos:WHILE (tt # NIL) AND (i < pos-1) DO

tt := tt^.next;END;IF tt = NIL THEN ERROR END;

Este bucle se realiza para detectar los posibles errores con los que me puedo encontrar (laposición está fuera de lista, etc...).

- Control de errores:$ Mensaje y parada: abortamos (HALT).$ Variable "error" por referencia: minucioso con los errores.$ Variable Global me indicará siempre un estado.

VAR listerror: (INDERROR, REMOVER, INDEXRANGE);Este es el error anterior expresado utilizando variable global.

listerror := IDEXRANGE;DISPOSE(tmp);RETURN;

Las variables por referencia conviene ponerlas a cero en las entradas.

PROCEDURE BorrarLista(VAR l: LISTA; pos: CARDINAL);

VAR i: CARDINAL; tmp: LISTA;

BEGINIF pos = 1 THEN

tmp := l;l := l^.next;DISPOSE(tmp);

ELSEtmp := l;FOR i := 1 TO pos - 2 DO

tmp := tmp^.next;END;tt := tmp^.next;tmp^.next := tt^.next;DIPSOSE(tt);

END;END BorraLista;

Page 24: Programacion Modular

J. Jaime A. Pepe'00 PM 24

PROCEDURE LongitudLista(l: LISTA): CARDINAL;

VAR tmp: LISTA;

BEGINtmp :=l;i := 0;WHILE tmp # NIL DO

INC(i);tmp := tmp^.next;

END;RETURN i;

END LongitudLista;

Procedimiento de acceso al contenido de la lista: leer.PROCEDURE LeeLista(l: LISTA; pos: CARDINAL): BASE;

VAR tmp: LISTA;

BEGINtmp := l; (* faltan las verificaciones *)FOR i := 2 TO pos DO

tmp := tmp^.next;END;RETURN tmp^.inf;

END LeeLista;

Para destruir la lista:PROCEDURE DestruirLista(VAR l: LISTA);

VAR i: CARDINAL;

BEGINFOR i := 1 TO Longitud(l) DO

BorraLista(l, 1);END;

END DestruirLista;

otra forma de destruir la lista:BEGIN

IF l# NIL THENDestruirLista(l^.sig);DISPOSE(l);

END;END; (* la recursión no es buena para esto *)

Page 25: Programacion Modular

J. Jaime A. Pepe'00 PM 25

* Implementación con cabecera:La cabecera es un bloque fijo de información que está siempre aunque no haya elementos en lalista y contiene datos útiles acerca de la lista.Normalmente es el primer objeto o aquel al cual se refiere la variable lista.La implementación sería:

TYPE LISTA = POINTER TO CABECERA; LINK = POINTER TO NODO; CABECERA = RECORD

lista: LINK;longitud: CARDINAL;

END; NODO = RECORD

info: BASE;sig: LINK;

END;Esta sería una cabecera mínima, se pueden poner muchos más datos interesantes.

Habría que modificar algo los procedimientos:PROCEDURE CreaLista(VAR l: LISTA);

BEGINNEW(l); (* crea una cabecera, no una lista *)l^.longitud := 0;l^.lista := NIL;

END CreaLista;

Cuando se quiera destruir la lista habrá que tener cuidado con destruir también la cabecera(DISPOSE(l)).

PROCEDURE Inserta (VAR l: LISTA; pos: CARDINAL; x: BASE);

VAR tmp, tt: LINK;

BEGINNEW(tmp); tmp^.info := x;IF pos = 1 THEN

tmp^.sigui := l^.lista;l^.lista := tmp;INC(l^.longitud);

ELSEtt := l^.lista;FOR i := 2 TO pos - 1 DO

tt := tt^.sigui;END;tmp^.sigui := tt^.sigui;tt^.sigui := tmp;INC(l^.longitud);

Page 26: Programacion Modular

J. Jaime A. Pepe'00 PM 26

END Inserta;

La cabecera facilita enormemente el calcular la longitud de la lista:PROCEDURE LongitudLista(l: LISTA): CARDINAL;

BEGINRETURN l^.longitud;

END LongitudLista;

Un posible algoritmo de búsqueda podría ser:PROCEDURE Buscar(l: LISTA; x: BASE): CARDINAL;

VAR i: CARDINAL;

BEGINFOR i := 1 TO Longitud(l) DO

IF LeeLista(l) = x THENRETURN i;

END;END;RETURN 0;

END Buscar;

Todos los algoritmos desarrollados son poco eficientes, sobre todo en el acceso a elementos, paramejorarlos usamos la cabecera. En la cabecera añado un campo de "última visita" que se refiereal lugar último en el que he estado, se guarda como una variable del tipo LINK.Pero esta solución no es totalmente buena, es interesante guardar también el cardinal de la últimaposición visitada.El procedimiento Crear me pondría la última visita y la posición de la última visita a NIL y 0respectivamente.

PROCEDURE LeeLista(l: LISTA; pos: CARDINAL): BASE;

VAR tmp: LISTA; i: CARDINAL;

BEGINtmp := l^.lista;FOR i := 2 TO pos DO

tmp := tmp^.sigui;END;l^.ultimavisita := tmp;l^.nultimavisita := pos;RETURN tmp^.info;

END LeeLista

Page 27: Programacion Modular

J. Jaime A. Pepe'00 PM 27

El indicador que se guarda se usa para atajar, es como partir la lista en dos (1 .. pos; pos .. long).Habría que hacer alguna modificación al LeeLista anterior:IF pos >= l^.nultimavisita THEN

tmp := l^.ultimavisita;pos := nultimavisita - pos + 1;

END;< el código que ya había >

Esto también tiene interés en el borrado y la inserción.Sería interesante hacer un procedimiento que a partir de una lista y una posición me devuelva elnodo correspondiente.

PROCEDURE LinktoNodo(l: LISTA; pos: CARDINAL): LINK;También sería útil que se pudiera avanzar en los dos sentidos (dobles enlaces).

PROCEDURE LinkToNode(l: LISTA; pos: CARDINAL): LINK;

BEGINIF l^.posuv <= pos THEN

tmp := l^.puntuv;n := pos-l^.posuv;

ELSEtmp := l^.l;n := pos-1;

END;FOR i := 1 TO n DO

tmp := tmp^.sigui;END;RETURN tmp;

END LinkToNode;

Este procedimiento no habría que ponerlo en la definición de la librería de lista pero sí en laimplementación.

- Cursores:Son arrays con dos campos para cada elemento, uno con la información y otro con el elementosiguiente.En caso de una inserción, en lugar de mover todos los elementos que hay después del insertado,busco un lugar vacío en el array y pongo en el campo "siguiente" del anterior la posición en elarray de este nuevo elemento.P. ejemplo:

0 1 2 3 4 5 20 21 22 23 24

info info info info info info info info info info info

1 24 3 sig sig sig sig sig sig sig 2

elemento nuevo insertado <•He insertado el elemento nuevo (24) entre el 1 y el 2

Page 28: Programacion Modular

J. Jaime A. Pepe'00 PM 28

* TA lista con cabecera y doble enlace:Me permite tanto avanzar como retroceder en la lista.Los dobles enlaces no tienen ningún interés si no hay cabecera.

LINK = POINTER TO NODO;LISTA = POINTER TO CABECERA;CABECERA = RECORD

lista: LINK;tamanyo: CARDINAL;puntuv: LINK;posuv: CARDINAL;

END;NODO = RECORD

info: BASE;sig, ante: LINK;

END;

El CreaLista es idéntico al que teníamos en el enlace simple.

PROCEDURE Insertar(VAR l: LISTA; i: CARDINAL; x: BASE);

VAR....

BEGINNEW(nuevo);nuevo^.info := x;tmp := l^.lista;IF l^.tamanyo = 0 THEN

nuevo^.ante := nuevo;nuevo^.sig := nuevo;l^.lista := nuevo;

ELSIF i = 1 THENl^.lista^.ante^.sig := nuevo; (* el siguiente del último = nuevo 11 *)nuevo^.sig := l^.lista;nuevo^.ante := nuevo^.sig^.ante; (* lo enlaza al último *)l^.lista^.ante := nuevo;l^.lista := nuevo;

ELSEFOR n := 2 TO i-1 DO

tmp := tmp^.sig;END;nuevo^.sig := tmp^.sig;nuevo^.ante := tmp;nuevo^.sig^.ante := nuevo;nuevo^.ante^.sig := nuevo;IF i := l^.tamanyo + 1 THEN

l^.lista^.ante := nuevo;

Page 29: Programacion Modular

J. Jaime A. Pepe'00 PM 29

END;INC(l^tamanyo);

END;END Insertar;

Habría que introducir en la cabecera un puntero al último elemento de la lista lo que simplificaríael algoritmo.

- Aprovechando la última visita con el doble enlace:LIST Doble enlace última visita:PROCEDURE LinkToNode(l: LISTA; i: CARDINAL): LINK;

VAR j: CARDINAL; (* contador *) jlink: LINK; (* contador-puntero *) dir: (FORW, BACK); (* hacia adelante-atrás *) steps: CARDINAL (* n1 de pasos *)

BEGIN (* first -> cabecera *)WITH l^ DO

IF i < nvisit THENIF i-1 > nvisit THEN

jlink := pvisit;dir := BACK;steps := nvisit - i;

ELSEjlink := firsst;dir := FORW;steps := i-1;

END;

ELSEIF i - nvisit < size-1 THEN

jlink := pvisit;dir := FORW;steps := i-nvisit;

ELSEjlink := first^.prev;dir := BACK;steps := size - i;

END;END;

END; (* IF *)CASE dir OF

| FORW:FOR j := 1 TO steps DO

jlink := jlink^.next;END;

Page 30: Programacion Modular

J. Jaime A. Pepe'00 PM 30

| BACK:FOR j :=1 TO steps DO

jlink := jlnik^.prev;END;

END; (* CASE *)RETURN jlink;END (* While *)

END LinkToNode;

PROCEDURE LeeLista(l: LISTA; pos: CARDINAL): BASE;

VAR ........

BEGINtmp := LinkToNode(l, pos);l^.puntuv := tmp;l^.posuv := pos;RETURN tmp^.info;

END LeeLista;

Ejercicio: implementar cursores y listas de doble enlace.

* Implementación de cursores:CONST MAXTAM = 100;TYPE CURSOR = [1..MAXTAM]; LISTA = POINTER TO CABECERA; BLOQUE = RECORD

info: BASE;sig: CARDINAL;

END; CABECERA = RECORD

lista: ARRAY[1..MAXTAM] OF BLOQUE;ultlleno: CARDINAL;primero: CARDINAL;tamanyo: CARDINAL;

END;

PROCEDURE CreateLista(VAR l: LISTA);

VAR i: CURSOR;

BEGINALLOCATE(l, SIZE(BLOCK));IF l = NIL THEN

ListErr := MEMOVER;RETURN;

END;

Page 31: Programacion Modular

J. Jaime A. Pepe'00 PM 31

ListErr := NOERR;WITH l^ DO

firstCell := 0;firstEmpty := 1;size := 0;FOR i := 1 TO MAXTAM-1 DO

cells[i].next := i + 1;END;cells[MAXTAM].next := 0;

END;END CreateLista;

PROCEDURE cusorIavo(l: LIST; iavo: CARDINAL): CURSOR;

VAR pos, i: CURSOR;

BEGINWITH l^ DO

pos := firstCell;FOR i := 2 TO iavo DO

pos := cells[pos].next;END;RETURN pos;

END;END cursorIavo;

PROCEDURE Insert(VAR l: LIST; i: CARDINAL; x: BASE);

VAR .........

BEGINnew := newEle(l);WITH l^ DO

cells[new].item := x;IF i = 1 THEN

cells[new].next := firstCell;firstCell := new;

ELSEprev := cursorIavo(l, i-1);cells[new].next := cells[prev].next;cells[prev].next := new;

END;INC(size);

END;END Insert;

Page 32: Programacion Modular

J. Jaime A. Pepe'00 PM 32

PROCEDURE Destruye(VAR l: LISTA);

BEGINDISPOSE(l);

END Destruye;

- Procedimientos internos a la implementación:PROCEDURE newEle(VAR l: LISTA): CURSOR;

VAR pos: CURSOR;

BEGINWITH l^ DO

pos := firstEmpty;firstEmpty := cells[pos].next;

END;RETURN pos;

END newEle;

PROCEDURE DisposeEle(VAR l: LISTA; i: CARDINAL);

BEGINl^.cells[i].next := firstEmpty;l^.firstEmpty := i;

END DisposeEle;

Realmente hay dos listas: una con las celdas llenas y otra con las celdas vacías.

* Implementación de la variable de control de errores:La variable del control de errores de tipifica en el módulo de definición después de todos losprocedimientos:

<todo el módulo de definición>VAR ListErr: (NOERR, MEMOVER, .................);

El ListErr se importa desde la librería.Para facilitar el control de errores es conveniente usar procedimientos exclusivamente y nofunciones, para poder usar RETURN sin tener que devolver nada.

Ejemplo: definición de un polinomio:TYPE BASE = RECORD

coef: REAL;grado: CARDINAL;

END;

PROCEDURE CreaPoli(VAR p: POLI);PROCEDURE DestruyePoli(VAR p: POLI);PROCEDURE SumaMono(VAR p: POLI; grado: CARDINAL; coef: REAL);

Page 33: Programacion Modular

J. Jaime A. Pepe'00 PM 33

PROCEDURE LeeMono(p: POLI; grado: CARDINAL): REAL;PROCEDURE Grado(p: POLI): CARDINAL; (* devuelve el mayor grado *)

El resto de utilidades como restar, multiplicar, dividir, evaluar para una x, resolver, etc... seharían en otra librería.

En la implementación importaríamos una librería de listas:FROM LISTA IMPORT CrearLista, ......................;

TYPE BASE = POLI;

PROCEDURE CreaPoli(VAR p: POLI);

BEGINCrearLista(p);

END;

En un polinomio, la suma puede implicar crear o destruir un nodo.

* Estructura pila (LIFO):La definición:

CrearPila(VAR p: PILA);DestruirPila(VAR p: PILA);Apilar(VAR p: PILA; x: BASE);Desapilar(VAR p: PILA): BASE;Cima(p: PILA): BASE; (* devuelve el primer elemento *)

- Forma de implementación:Hay dos implementaciones posibles: acotada y no acotada.

$ No acotada:TYPE PILA = POINTER TO BLOQUE; BLOQUE = RECORD

info: BASE;sig: PILA;

END;

PROCEDURE CrearPila(VAR p: PILA);

BEGINNEW(p);p := NIL;

END CrearPila;

Page 34: Programacion Modular

J. Jaime A. Pepe'00 PM 34

PROCEDURE DestruirPila(VAR p: PILA);

VAR tmp, t2: PILA;

BEGINWHILE tmp^.sig # NIL DO

t2 := tmp^.sig;DISPOSE(tmp);tmp := t2^.sig;

END;DISPOSE(p);

END DestruirPila;

PROCEDURE Apilar(VAR p: PILA; x: BASE);

VAR nuevo: PILA;

BEGINNEW(nuevo);nuevo^.info := x;nuevo^.sig := p;p := nuevo;

END Apilar;

PROCEDURE Desapilar(VAR p: PILA): BASE;

VAR tmp: PILA; tinfo: BASE;

BEGINtmp := p;p := p^.sig;tinfo := tmp^.info;DISPOSE(tmp);RETURN tinfo;

END Desapilar;

PROCEDURE Cima(p: PILA): BASE;

BEGINRETURN p^.info;

END Cima;

PROCEDURE EsPilaVacia(p: PILA): BOOLEAN;

BEGINRETURN p = NIL;

END EsPilaVacia;

Page 35: Programacion Modular

J. Jaime A. Pepe'00 PM 35

$ Acotada:TYPE PILA = POINTER TO CABECERA; CABECERA = RECORD

primero: CARDINAL; lista: ARRAY[1..MAXTAM] OF BASE; END;

PROCEDURE CreaPila(VAR p: PILA);

BEGINNEW(p);p^.primero := 0;

END CreaPila;

PROCEDURE DestruirPila(VAR p: PILA);

BEGINDISPOSE(p);

END DestruirPila;

PROCEDURE Apilar(VAR p: PILA; x: BASE);

BEGINp^.primero := primero + 1;p^.lista[primero] := x;

END Apilar;

PROCEDURE Desapilar(VAR p: PILA): BASE;

BEGINp^.primero := primero - 1;RETURN p^.lista[p^.primero+1];

END Desapilar;

PROCEDURE Cima(p: PILA): BASE;

BEGINRETURN p^.lista[p^.primero];

END Cima;

PROCEDURE EsPilaVacia(p: PILA): BOOLEAN;

BEGINRETURN p^.primero = 0;

END EsPilaVacia;

Page 36: Programacion Modular

J. Jaime A. Pepe'00 PM 36

* Control de errores(y 2):Para el control de errores en las funciones, para cortarlas con RETURN podemos hacer quedevuelva una variable local del mismo tipo de lo que se devuelve con el RETURN, esta variableestaría declarada pero no inicializada (no igualada a nada). Estamos devolviendo basura en casode error.Evidentemente, en caso de error, el driver no miraría la información devuelta y se obviaría estabasura.

* Ejemplo de uso de pilas: notación polaca, análisis de expresiones aritméticas dentro delparsing del lenguaje.Tipos de operaciones:

infijas: operadores entre los operandos: 4 + 3.prefijas: operando después del operador: sen(x).postfijas: expresión polaca, primero los operandos y después los operadores: 4 8 +.

$ Paso de expresión infija a polaca (ejemplos):

3 + 2 => 3 2 +3 * 2 + 4 * (3 + 5) => 3 2 * 4 3 5 + * +

1 + 2 + 3 => 1 2 3 + +

(8 + 2) * 3 + 5 * 6 => 8 2 + 3 * 5 6 * +8 - 2 => 8 2 -

-4 => 4 -

La conversión de infija a polaca no es unívoca.

$ Evaluación de la notación polaca:Es más fácil mediante listas (suponiendo elementos multidígito).

BASE = RECORDCASE TIPO: (OPERADOR, OPERANDO); |OPERADOR: p: CHAR; |OPERANDO: x: CARDINAL;END;

END;

PROCEDURE EvaluaPolish(l: Listas.LISTA): Pilas.BASE;

VAR s: Pilas.PILA; x: Listas.LISTA; y: Pilas.BASE; i: CARDINAL;

BEGINCrearPila(s);FOR i := 1 TO Listas.Longitud(l) DO

x := Listas.LeeElem(l, i);IF x.tipo = OPERANDO THEN

Pilas.Apilar(s, x);ELSE

CASE x.p OF

Page 37: Programacion Modular

J. Jaime A. Pepe'00 PM 37

|'+': Pilas.Apilar(s, Pilas.Despilar(s)+Pilas.Desapilar(s)); |'-': ! |'*': <operaciones análogas> |'/': !END;

END;END;y := Pilas.Desapilar(s);Pilas.Destruir(s);RETURN y;

END EvaluaPolish;

Hay que tener cuidado con las operaciones como la resta o la división ya que hay que introducirlos operandos a la inversa, por ejemplo, la resta:

|'-': op1 := Pilas.Desapilar(s); op2 := Pilas.Desapilar(s); Pilas.Apilar(s, op2-op1);

De otra forman, teniendo en cuenta la estructura LIFO, no se podría realizar.

Ejercicio: hacer un programa que pida una cadena en notación polaca y que la evalúe.

- Algunas utilidades para las pilas:$ Invertir la pila:De forma recursiva:PROCEDURE SwapStack(VAR orig, dest: STACK);

VAR c: BASE;

BEGINIF NOT(EsPilaVacia(orig)) THEN

c := Desapilar(orig);Apilar(dest, c);SwapStack(orig, dest);Apilar(orig, c);

END;END SwapStack;

De forma iterativa:PROCEDURE SwapStack(VAR orig, dest: STACK);

VAR aux: STACK; c: BASE;

BEGINCreaPila(aux);WHILE NOT(EsPilaVacia(orig)) DO

Page 38: Programacion Modular

J. Jaime A. Pepe'00 PM 38

c := Desapilar(orig);Apilar(aux, c);Apilar(dest, c);

END;WHILE NOT(EsPilaVacia(aux)) DO

Apilar(orig, Desapilar(aux));END;Destruir(aux);

END SwapStack;

$ Copia una pila:PROCEDURE CopyStack(VAR orig, dest: STACK);

VAR tmp: STACK; c: BASE;

BEGINCreaPila(tmp);REPEAT

Apilar(tmp, Desapilar(orig));UNTIL EsPilaVacia(orig);REPEAT

c := Desapilar(tmp);Apilar(orig, c);Apilar(dest, c);

UNTIL EsPilaVacia(orig);DestruyePila(aux);

END CopyStack;

Ejercicio: ordenar una pila.

$ Red de trenes:Van cayendo los elementos del tramo izquierdo (oderecho) al central, desde el central puedo pasarun elemento al derecho o quedármelo, hasta queaparezca el que busco y volver el resto por dondeentró. Son realmente dos pilas: el raíl central y elraíl izquierdo.No todas las combinaciones son posibles (para

másde4elementos).

Page 39: Programacion Modular

J. Jaime A. Pepe'00 PM 39

* TA Cola (Queue):Estructura FIFO.El interfaz es:

CreaCola(VAR q: COLA);DestruirCola(VAR q: COLA);EnColar(VAR q: COLA; x: BASE);SacarCola(VAR q: COLA): BASE;PrimeroCola(q: COLA): BASE;UltimoCola(q: COLA): BASE;EsColaVacia(q: COLA): BOOLEAN;

- Implementación acotada:TYPE COLA = POINTER TO BLOQUE; BLOQUE = RECORD

primero: CARDINAL;ultimo: CARDINAL;longitud: CARDINALcolas: ARRAY[1..MAXTAM] OF BASE;

END;

PROCEDURE CreaCola(VAR q: COLA);

BEGINNEW(q);q^.primero := 1;q^.ultimo := 0;q^.longitud := 0;

END CreaCola;PROCEDURE DestruirCola(VAR q: COLA);

BEGINDISPOSE(q);

END DestruirCola;

PROCEDURE EnColar(VAR q: COLA; x: BASE);

BEGINq^.ultimo := (q^.ultimo MOD MAXTAM) + 1;q^.colas[q^.ultimo] := x;INC(q^.longitud);

END EnColar;

PROCEDURE SacarCola(VAR q: COLA): BASE;

VAR x: BASE;

Page 40: Programacion Modular

J. Jaime A. Pepe'00 PM 40

BEGINx := q^.colas[q^.primero];q^.primero := (q^.primero MOD MAXTAM) + 1;DEC(q^.longitud);RETURN x;

END SacarCola;

PROCEDURE PrimeroCola(q: COLA): BASE;

BEGINRETURN q^.colas[q^.primero];

END PrimeroCola;

PROCEDURE UltimoCola(q: COLA): BASE;

BEGINRETURN q^.colas[q^.ultimo];

END UltimoCola;

- Implementación no acotada:TYPE COLA = POINTER TO BLOQUE; BLOQUE = RECORD

info: BASE;sig: COLA;

END;

PROCEDURE CreaCola(VAR q: COLA);

BEGINq := NIL;

END CreaCola;

PROCEDURE DestruirCola(VAR q: COLA);

VAR tmp, t2: COLA;

BEGINtmp := q;WHILE tmp^.sig # NIL DO

t2 := tmp;tmp := tmp^.sig;DISPOSE(t2);

END;END DestruirCola;

PROCEDURE EnColar(VAR q: COLA; x: BASE);

Page 41: Programacion Modular

J. Jaime A. Pepe'00 PM 41

VAR nuevo: COLA;BEGIN

NEW(nuevo);nuevo^.info := x;IF q = NIL THEN

q := nuevo;q^.sig := q;

ELSEnuevo^.sig := q^.sig;q^.sig := nuevo;q := nuevo;

END;END EnColar;

PROCEDURE SacarCola(VAR q: COLA): BASE;

VAR x: BASE; tmp: COLA;

BEGINtmp := q^.sig;x := q^.sig^.info;IF q^.sig = q THEN

q := NIL;ELSE

q^.sig := tmp^.sig;END;DISPOSE(tmp);RETURN x;

END Desencolar;

PROCEDURE EsColaVacia(q: COLA): BASE;

BEGINRETURN q = NIL;

END EsColaVacia;

PROCEDURE PrimeroCola(q: COLA): BASE;

BEGINRETURN q^.sig^.info;

END PrimeroCola;

PROCEDURE UltimoCola(q: COLA): BASE;

BEGINRETURN q^.info;

Page 42: Programacion Modular

J. Jaime A. Pepe'00 PM 42

END UltimoCola;

- Otros tipos de cola:$ Cola doble: permite insertar y extraer por los dos extremos, es interesante hacerla condoble enlace. Cambian los procedimientos:

TYPE LADO = (PRINCIPIO, FINAL);SacarCola(VAR q: COLA; l: LADO): BASE;EnColar(VAR q: COLA; x: BASE; l: LADO);

El resto de procedimientos permanecen igual en el interfaz.

$ Cola de prioridades: no todos los elementos entran al último lugar, algunos saltandirectamente al principio (u otra posición avanzada, según la prioridad). Cambia elprocedimiento:

EnColar(VAR q: COLA; x: BASE; p: PRIORIDAD);El resto de procedimientos permanecen igual en el interfaz.

La prioridad suele ser un número de corto rango.SacarCola no tiene cambios, los cambios referentes a la prioridad los aplica directamenteEnColar al insertar los elementos.Si todos los elementos tienen la misma prioridad se ordenan por el orden de entrada.

TYPE COLA = POINTER TO NODO; NODO = RECORD

info: BASE;sigui: COLA;prioridad: CARDINAL;

END;

PROCEDURE Encolar(VAR q: COLA; pri: CARDINAL; x: BASE);

VAR tmp1, tmp2: COLA;

BEGINNEW(tmp1);tmp1^.info := x;tmp1^.prioridad := pri;IF q = NIL THEN

q := tmp1;tmp1^.sig := tmp1;

ELSIF pri < q^.prioridad THENtmp2 := q;WHILE tmp1^.prioridad =< tmp2^.sigui^.prioridad DO

tmp2 := tmp2^.sigui;END;tmp1^.sigui := tmp2^.sigui;

q^.sig^.info es el primer elemento de la colaq^.info es el último elemento de la cola.

Page 43: Programacion Modular

J. Jaime A. Pepe'00 PM 43

tmp2^.sigui := tmp1;ELSE

tmp1^.sigui := q^.sigui;q^.sigui := tmp1;q := tmp1;

END;END Encolar;Con mayor número, mayor prioridad. Los signos van al revés.

El tipo de cola doble sería:TYPE COLA = POINTER TO NODO NODO = RECORD

info: BASE;sigui: COLA;ante: COLA;

END;

* Árboles:Modula-2 no permite una buena implementación de árboles.Vamos a usar casi exclusivamente variables por referencia.Un árbol es o bien el vacío o bien un dato con el que esta relacionado otra serie disjunta de nárboles con n $ 0 junto con sus procedimientos de acceso, un árbol puede tener n enlaces en cadanodo, esto da lugar a tres tipos de árboles:

- árboles generales •> sin conocer la cantidad de descendientes.- árboles n-arios •> árboles con un número fijo de descendientes (n), dentro de esta clase aparecen los árboles binarios (n = 2), BARBOL.- árboles multivía •> no tiene por qué haber elemento de información en cada nodo.

Algunas definiciones sobre árboles:Mínimo ancestro común: el "padre" más cercano común a los dos nodos.El primer nivel (raíz) es el 1, el árbol vacío tiene nivel 0.Lista de nivel: lista de nodos de un nivel.Bosque: lista de árboles disjuntos.Camino interno: suma de las longitudes de caminos de todos los componentes de unárbolNodos imaginarios (especiales): nodo que se añade en un árbol n-ario para cerrar todaslas vacantes de enlace que hay (terminadores).Camino externo: longitud de los caminos de los nodos especiales o imaginarios.Árbol lleno o perfectamente balanceado: árbol que tiene todos sus niveles completos(no hay vacantes).

- Procedimientos de acceso:T •> conjunto de los árboles.I •> conjunto de los elementos de tipo BASE.B •> BOOLEAN.E •> posibles excepciones.

Page 44: Programacion Modular

J. Jaime A. Pepe'00 PM 44

HacerBVacio •> TEsBVacio T •> BRaíz T •> I c EIzda T •> T c EDrcha T •> T c EHacerBArbol T x I x T •> T

Los procedimientos:HacerBVacio( ): BARBOL;EsBVacio(b: BARBOL): BOOLEAN;Raíz(b: BARBOL): BASE;Izda(b: BARBOL): BARBOL;Drcha(b: BARBOL): BARBOL;HacerBArbol(b: BARBOL; x: BASE; r: BARBOL): BARBOL;

Ejercicio: hacer PROCEDURE NoNodos(b: BARBOL): CARDINAL que devuelva el númerode nodos.

BEGIN (* recursivamente *)IF EsBVacio(b) THEN RETURN 0 END;RETURN 1 + NoNodos(Izda(b)) + NoNodos(Drcha(b));

END;

- Implementación no acotada:La implementación acotada no merece la pena.

TYPE BARBOL = POINTER TO NODO; NODO = RECORD

info: BASE;left, right: BARBOL;

END;

PROCEDURE HacerBVacio( ): BARBOL;

BEGINRETURN NIL;

HacerBVacio;

PROCEDURE EsBVacio(b: BARBOL): BOOLEAN;

BEGINRETURN b = NIL;

END EsBVacio;

Page 45: Programacion Modular

J. Jaime A. Pepe'00 PM 45

PROCEDURE Raiz(b: BARBOL): BASE;

BEGINRETURN b^.info;

END Raíz;

PROCEDURE Izda(b: BARBOL): BARBOL;

BEGINRETURN b^.left;

END Izda;

PROCEDURE Drcha(b: BARBOL): BARBOL;

BEGINRETURN b^.right;

END Drcha;

PROCEDURE HacerBArbol(l: ARBOL; x: BASE; r: BARBOL): BARBOL;

VAR tmp: BARBOL;

BEGINNEW(tmp);tmp^.info := x;tmp^.left := l;tmp^.right := r;RETURN tmp;

END HacerBArbol;

No nos preocupamos mucho de liberar la memoria porque el Modula-2 es muy malo gestionandola basura (memoria dealocada).

Ejercicio: construir el árbol:3

8 6

2 1

BEGINb := HacerBArbol(HacerBArbol(HacerBVacio), 8, HacerBVacio),HacerBArbol(HacerBArbol(HacerBArbol), 2, HacerBVacio( ), 6,HacerBArbol(HacerBVacio( ), 1, HacerBVacio( ))).

END;

Page 46: Programacion Modular

J. Jaime A. Pepe'00 PM 46

Se simplifica si construimos un procedimiento para crear hojas.- Aplicaciones con árboles:PROCEDURE Altura(b: BARBOL): CARDINAL;

PROCEDURE Mayor(a, b: CARDINAL): CARDINAL;

BEGINIF a >= b THEN

RETURN a;ELSE

RETURN b:END;

END Mayor;

BEGINIF EsBVacio(b) THEN RETURN END;RETURN 1 + Mayor(Altura(Izda(b)), Altura(Drcha(b)));

END Altura;

Comprobar si un árbol es hoja:PROCEDURE EsHoja(b: BARBOL): BOOLEAN;

BEGINRETURN (NOT(EsBVacio(b))) AND (EsBVacio(Izda(b))) AND (EsBVacio(Drcha(b)));

END EsHoja;

El recorrido en profundidad puede ser de tres formas:preorden: visita raíz, después izquierda, por última derecha.inorden: primero izquierda, después raíz, por último derecha.postorden: primero izquierda, después derecha, por último raíz.

A partir del árbol:*

+ 23 2

recorrido en inorden: (3 + 2) * 2 [expresión infija].recorrido en preorden: * ( + (3, 2), 2) [expresión prefija].recorrido en postorden: 3 2 + 2 * [expresión postfija, Polaca inversa].

Ejercicio: escribir un árbol en pantalla en preorden, inorden y postorden.

PROCEDURE EscribePreorden(b: BARBOL);

BEGINIF EsBVacio(b) THEN RETURN END;WrCard(Raiz(b), 0);

Page 47: Programacion Modular

J. Jaime A. Pepe'00 PM 47

EscribePreorden(Izda(b));EscribePreorden(Drcha(b));

END EscribePreorden; Los otros dos son análogos.

Podemos parametrizar el procedimiento que quiero hacer al recorrer el árbol:TYPE OPER = PROCEDURE(BASE);

PROCEDURE EscPreorden(b: BARBOL; f: OPER);

BEGINIF EsBVacio(b) THEN RETURN END;f(Raiz(b));EscPreorden(Izda(b));EscPreorden(Drcha(b));

END EscPreorden;

El parámetro que paso como función (f) debe ser un procedimiento propio del usuario quecoincida con el tipo predefinidoEjercicio: descubre el elemento más pequeño de un árbol binario:

Ejercicio: hacer procedimiento busca:BuscaBArbol(b: BARBOL; x: BASE): BOOLEAN;

Ejercicio: procedimiento que devuelve la mínima hoja:MinHoja(b: BARBOL): BASE;

Ejercicio: mínimo en nivel:MinNivel(b: BARBOL; n: CARDINAL): BASE;

Ejercicio: mayor nivel lleno:MayNivLleno(b: BARBOL; n: CARDINAL): CARDINAL

[n, a partir del que buscamos]

Ejercicio: número de hojas:NoHojas(b: BARBOL): CARDINAL;

Ejercicio: escribe el nivel:EscNivel(b: BARBOL; n: CARDINAL);

Ejercicio: CopiaBArbol(b: BARBOL): BARBOL;

Ejercicio: escribir el árbol entero y respetando las posiciones.

Ejercicio: dados los recorridos en preorden e inorden de un árbol binario, construir el árbol. Losrecorridos vienen como listas.

Page 48: Programacion Modular

J. Jaime A. Pepe'00 PM 48

- Árbol de búsqueda binario:Es un árbol binario tal que todos sus nodos tienen un valor en la raíz mayor que todos los nodosizquierdos y menor que todos los nodos derechos.

8

6 10

2 7 9 50

Ejercicio: recibiendo un árbol devolver si es de búsqueda o no.

Ejercicio: hacer un procedimiento que diga si ha encontrado un elemento en el árbol binario debúsqueda teniendo en cuenta las propiedades que tiene este tipo de árbol.

$ Corrección de ejercicios:PROCEDURE BuscaBArbol(b: BARBOL; x: BASE): BOOLEAN;

BEGINIF EsBVacio(b) THEN RETURN FALSE END;IF Raiz(b) = x THEN RETURN TRUE END;RETURN (BuscaBArbol(Izda(b), x)) OR (BuscaBArbol(Drcha(b), x));

END BuscaBArbol;

PROCEDURE MinHoja(b: BARBOL): BASE;

BEGINIF EsHoja(b) THEN RETURN Raiz(b) END;IF EsBVacio(Izda(b)) THEN

RETURN MinHoja(Drcha(b));ELSIF EsBVacio(Drcha(b)) THEN

RETURN MinHoja(Izda(b));ELSE

RETURN MenorBASE(MinHoja(Izda(b)), MinHoja(Drcha(b)));END;

END MinHoja;

PROCEDURE MinBArbol(b: BARBOL): BASE;

BEGINIF EsHoja(b) THEN RETURN Raiz(b) END;IF EsBVacio(Izda(b)) THEN

RETURN MenorBASE(MinBArbol(Drcha(b)), Raiz(b));ELSIF EsBVacio(Drcha(b)) THEN

RETURN MenorBASE(MinBArbol(Izda(b)), Raiz(b));

Page 49: Programacion Modular

J. Jaime A. Pepe'00 PM 49

ELSERETURN MenorBASE(MenorBASE(Raiz(b), MinBArbol(Izda(b)),MinBArbol(Drcha(b));

END;END MinBArbol;

PROCEDURE MayNvLleno(b: BARBOL): CARDINAL; (* by Sonsoles *)

PROCEDURE EsNivelLleno(b: BARBOL; nv: CARDINAL): BOOLEAN;

BEGINIF EsBVacio(b) THEN

RETURN FALSE;ELSIF nv = 1 THEN

RETURN TRUE;

ELSERETURN (EsNivelLleno(Izda(b), nv-1)) AND (EsNivelLleno(Drcha(b),nv-1));

END;END EsNivelLleno;

VAR mayor: BASE;

BEGINIF EsBVacio(b) THEN

RETURN 0;ELSE

RETURN 1 + MinimoBASE(MayNvLleno(Izda(b)), MayNvLleno(Drcha(b)));END;

END MayNvLleno;

PROCEDURE EscNv(b: BARBOL; n: CARDINAL; f: OPER);

BEGINIF EsBVacio(b) THEN RETURN END;IF n > 1 THEN

EscNv(Izda(b), n-1, f);EscNv(Drcha(b), n-1, f);

ELSEf(Raiz(b));

END;END EscNv;

Page 50: Programacion Modular

J. Jaime A. Pepe'00 PM 50

PROCEDURE CopiarBArbol(b: BARBOL): BARBOL;

BEGINIF EsBVacio THEN

RETURN CrearBArbol( );ELSE

RETURN HacerBArbol(CopiarBArbol(Izda(b); Raiz(b);CopiarBArbol(Drcha(b))));

END;END CopiarBArbol;

PROCEDURE ImprimirBArbol(b: BARBOL; w: CARDINAL; WrBASE: OPER);

PROCEDURE EscribeRaiz(x: BASE; pos: CARDINAL);

BEGINlinea[pos] := WrBASE(x);

END EscribeRaiz;

PROCEDURE ImprimirNivelBA(b: BARBOL; nv, pos, w: CARDINAL);

BEGINIF EsBVacio(b) THEN RETURN END;IF nv = 1 THEN

EscribeRaiz(Raiz(b), pos);ELSE

ImprimirNivelBA(Izda(b), nv-1, pos - w DIV 4, w DIV 2);ImprimirNivelBA(Drcha(b), nv-1, pos + w DIV 4, w DIV 2);

END;END ImprimirNivelBA;

BEGINFOR i := 0 TO Altura(b) DO

FOR j := 0 TO MAX DOlinea[i] := " ";

END;ImprimirNivelBA(b, i, w DIV 2, w);WrStr(linea);WrLn( );

END;END ImprimirBArbol;

$ Árbol de búsqueda binario, implementación:Un árbol de búsqueda es un almacén, una tabla, que contiene información optimizada paraobtenerla.

CrearBBArbol( ): BBARBOL;

Page 51: Programacion Modular

J. Jaime A. Pepe'00 PM 51

AnadirBBArbol(b: BBARBOL; x: BASE): BBARBOL;BorrarBBArbol(b: BBARBOL): BBARBOL;EstaEnBBArbol(b: BBARBOL): BBARBOL;Izda(b: BBARBOL): BBARBOL;Drcha(b: BBARBOL): BBARBOL;Raiz(b: BBARBOL): BBARBOL;

PROCEDURE EsArbolBB(b: BARBOL): BOOLEAN;

BEGINIF EsBVacio(b) THEN

RETURN TRUE;ELSIF EsBVacio(Drcha(b)) THEN

RETURN Mayor(Izda(b)) < Raiz(b);ELSIF EsBVacio(Izda(b)) THEN

RETURN Menor(Izda(b)) > Raiz(b);ELSE

RETURN (Mayor(Izda(b)) < Raiz(b)) AND (Menor(Drcha(b)) > Raiz(b));END;

END EsArbolBB;Implementación:PROCEDURE EstaEnBBArbol(b: BBARBOL; x: BASE): BOOLEAN;

BEGINIF EsBVacio(b) THEN RETURN FALSE END;IF x = Raiz(b) THEN

RETURN FALSE;ELSIF x < Raiz(b) THEN

RETURN EstaEnBBArbol(Izda(b));ELSE

RETURN EstaEnBBArbol(Drcha(b));END;

END EstaEnBBArbol;

PROCEDURE AnadirBBArbol(b: BBARBOL; x: BASE): BBARBOL;

BEGINIF Raiz(b) = x THEN RETURN b END;IF EsBVacio(b) THEN

RETURN HacerBArbol(HacerBVacio( ), x, HacerBVacio( ));ELSIF x < Raiz(b) THEN

RETURN HacerBArbol(AnadirBBArbol(Izda(b), x), Raiz(b), Drcha(b));ELSIF x > Raiz(b) THEN

RETURN HacerBArbol(Izda(b), Raiz(b), AnadirBBArbol(Drcha(b), x));END;

END AnadirBBArbol;

Page 52: Programacion Modular

J. Jaime A. Pepe'00 PM 52

PROCEDURE BorrarBBAbol(b: BARBOL; x: BASE): BBARBOL;PROCEDURE MenorBBArbol(b: BBARBOL): BASE;

BEGINIF NOT (EsBVacio(Izda(b)) THEN

RETURN MenorBBArbol(Izda(b));END;RETURN Raiz(b);

END MenorBBArbol;

VAR tmp: BASE;

BEGINIF EsBVacio(b) THEN RETURN CrearBBArbol( ) END;IF x < Raiz(b) THEN

RETURN HacerBBArbol(BorrarBBArbol(Izda(b), x), Raiz(b), Drcha(b));ELSIF x > Raiz(b) THEN

RETURN HacerBBArbol(Izda(b), Raiz(x), BorrarBBArbol(Drcha(b));

ELSEIF EsHoja(b) THEN

RETURN CrearBBArbol( );ELSIF EsBVacio(Izda(b)) THEN

RETURN Drcha(b);ELSIF EsBVacio(Drcha(b)) THEN

RETURN Izda(b);ELSE

tmp := MenorBBArbol(Drcha(b));RETURN HacerBArbol(Izda(b), tmp, BorrarBBArbol(Drcha(b), tmp));

END;END;

END BorrarBBArbol;

PROCEDURE CrearBBArbol( ): BBARBOL;

BEGINRETURN CrearBArbol( );

END CrearBBArbol;

PROCEDURE Izda(b: BBARBOL): BBARBOL;

BEGINRETURN ARBOL.Izda(b);

END Izda;

PROCEDURE Drcha(b: BBARBOL): BBARBOL;

Page 53: Programacion Modular

J. Jaime A. Pepe'00 PM 53

BEGINRETURN ARBOL.Drcha(b);

END Drcha;

PROCEDURE Raiz(b: BBARBOL): BBARBOL;

BEGINRETURN ARBOL.Raiz(b);

END Raiz;

Ejercicio: hacer un procedimiento que devuelva el sucesor de un nodo en un árbol binario debúsqueda:

Sucesor(b: BBARBOL; x: BASE): BASE;

PROCEDURE Sucesor(b: BBARBOL; x: BASE): BASE;

BEGINIF x < Raiz(b) THEN

RETURN Sucesor(Izda(b), x);ELSIF x > Raiz(b) THEN

RETURN Sucesor(Drcha(b), x);ELSE

RETURN Menor(Drcha(b));END;

END Sucesor;

Page 54: Programacion Modular

J. Jaime A. Pepe'00 PM 54

TEMA 5.- FICHEROS.Son dispositivos de almacenamiento externo controlados por el sistema operativo medianterutinas primitivas en los cuales puedo hacer lecturas y escrituras de bytes mediante una serie deelementos del S.O.: buffer, cursor y handle (manejador). El handle accede al buffer y al cursorinternamente.Los ficheros son series de bytes escritos en un disco, tienen un acceso muy lento.Un buffer es un almacenamiento intermedio de información.El cursor es un indicador numérico que avanza cuando se hacen lecturas o escrituras en unfichero indicando donde se hará la lectura o escritura siguiente de ese fichero, a menos que yocambie deliberadamente el cursor.El handle es una referencia a todos los sistemas necesarios para el manejo del fichero.

* Tipos de ficheros:Ficheros de acceso por bloque o binarios: son ficheros a los que leo o escribo en transaccionesde un número concreto de bytes sin interpretación del contenido de estos bytes durante la lecturao escritura.Ficheros ASCII: ficheros en los cuales se interpretan los códigos ASCII al leer o escribir.

Los ficheros por bloques (tamaño de bloque fijo) son más rápidos de acceder que los ASCII(tamaño de bloque variable).

Los ficheros se pueden acceder de dos maneras:- acceso secuencial: cuando no voy a cambiar la posición del cursor durante todo el proceso deacceso al fichero, el cursor cambia automáticamente, se lee el fichero sin saltos de cursor.- acceso directo: yo voy cambiando la posición del cursor.

El acceso secuencial es interesante para analizar el ficherol.El acceso directo es típico cuando tienen una estructura que yo controlo desde el programa.

Cuando se tienen ficheros muy grandes se usan ficheros indexados, una variante del accesodirecto.Para el indexado uso otro fichero además del que ya tengo con datos, este nuevo fichero contieneuna relación entre el índice del fichero de datos y una clave.

* Manejo de ficheros en Modula-2:Las funciones de acceso a ficheros están en la librería FIO, contiene las mismas funciones queIO y algunas más, pero en FIO hay un parámetro más: el fichero con el que estamos trabajando(handle).Se podría usar FIO como IO siendo 0 el fichero pantalla, 1 el fichero teclado y 2 el control deerrores.

Page 55: Programacion Modular

J. Jaime A. Pepe'00 PM 55

Ejemplo:PROCEDURE Menu( ): CARDINAL;

BEGINIO.WrStr("1.- Abrir/Crear para añadir/meter datos"); IO.WrLn( );IO.WrStr("2.- Visualizar datos"); IO.WrLn( );IO.WrStr("3.- Salir"); IO.WrLn( );RETURN ORD(IO.RdKey( ) - ORD("0"));

END Menu;

VAR f: FIO.FILE; (* el handle *)

BEGIN (* principal *)LOOP

CASE Menu OF |1: IO.WrStr(")Nombre del fichero a abrir/crear?");

IO.RdStr(nomfich);IF FIO.Exists(nomfich) THEN

f := FIO.Append(nomfich);ELSE

f := FIO.Create(nomfich);END;IF f = NIL THEN HALT END; (* error de apertura *)LOOP (* meter datos *)

IO.RdStr(nombre);IF Str.Length(nombre) = 0 THEN EXIT END;IO.RdStr(telefono);FIO.WrStr(f, nombre); FIO.WrLn(f);FIO.WrStr(f, telefono); FIO.WrLn(f);

END;FIO.Close(f);

En los ficheros ASCII son importantes las separaciones de campos (tabuladores) y de registro(final de línea).

|2: <pedir nombre del fichero><comprobar que existe el fichero>f := FIO.OpenRead(nomfich);LOOP

FIO.RdStr(f, nombre);IF FIO.EOF THEN EXIT; END;FIO.RdStr(f, telefono);IO.WrStr(nombre); IO.WrLn();IO.WrStr(telefono); IO.WrLn();

END;

Page 56: Programacion Modular

J. Jaime A. Pepe'00 PM 56

Para los ficheros ASCII:si quiero solo leer el fichero: f := OpenRead(nomfich); (* no permite escribir *)si quiero reescribir todo el fichero: f := Create(nomfich); (* destruyo todo *)si quiero modificar (añadir) el fichero: f := Append(nomfich);

El EOF es una variable BOOLEAN de FIO que se pone a TRUE cuando se detecta el final de unfichero.Después de usar el EOF habrá que ponerlo a FALSE, ya que no lo hace automáticamente:

LOOPc := RdChar(f);IF EOF THEN EXIT END;

END;EOF := FALSE;Close(f);

Para copiar un fichero:fin := OpenRead(nomfichin);fout := Create(nomfichout);LOOP

c := RdChar(f);IF EOF THEN EXIT END;WrChar(f, c);

END;EOF := FALSE;Close(fin);Close(fout);

$ Programas con parámetros:Si queremos usar argumentos de comando al estilo del Shell CLI MS-DOS (CLI •> commandline interpreter), en la librería Lib hay dos funciones para esto:

ParamCount( ) •> cuenta el número de argumentos, el nombre del programa se cuentacomo otro argumento.ParamStr(s, número) •> me devuelve en s el parámetro de la posición que le indico.

* Ficheros binarios:Con bloques de tamaño conocido. Se usan dos funciones:RdBin(f: FILE; VAR Buf: ARRAY OF BYTE; Sz: CARDINAL): CARDINAL; (* para leer *)WrBin(f: FILE; Buf: ARRAY OF BYTES; Sz: CARDINAL): (* para escribir *)Usan el tipo Array of Byte, es en realidad un puntero a un bloque sin tipo.

n := RdBin(f, personas, SIZE(TIPO PERSONA));WrBin(f, personas, SIZE(TIPO PERSONA));n es el número de bytes efectivamente leídos.

Page 57: Programacion Modular

J. Jaime A. Pepe'00 PM 57

La búsqueda en ficheros binarios sería:f := Open(nomfich);LOOP

IF RdBin(f, datos, SIZE(datos)) < SIZE(datos) THENEXIT; (* se acabó el fichero *)

ELSE<proceso>

END;END;Close(f);EOF := FALSE;

Una librería de listas en ficheros (ListFIO):PROCEDURE WrList(l: LISTA; f: FILE): BOOLEAN;

VAR i: CARDINAL;

BEGINFOR i := 1 TO Longitud(l) DO

WrBin(f, LeeLista(l, i), SIZE(BASE));IF IOResult THEN RETURN FALSE END;

END;RETURN TRUE;

END WrList;

PROCEDURE RdList(l: LISTA; f: FILE): BOOLEAN;

VAR x: BASE;

BEGINLOOP

IF RdBin(f, SIZE(BASE)) < SIZE(BASE) THEN EXIT END;InsertaElem(l, i, x);

END;END RdList;

* Control de errores y variables de FIO:FIO contiene una serie de tipos y variables:

EOF (BOOLEAN) •> final de fichero.IOCheck (BOOLEAN) •> si es cierto el sistema aborta el programa en caso de error de

I/O, si es falso el sistema no actúa (en caso de error I/O).Separator (Str.CHARSET) •> contiene los caracteres usados por el sistema para separar

tokens. Se puede variar a mano.OK (BOOLEAN) •> indica si hay problema de conversión en la lectura.ChopOff (BOOLEAN) •> indica si se supera el ancho de campo.Eng (BOOLEAN) •> indica si se usan las expresiones en formato de de 3 en 3.

Page 58: Programacion Modular

J. Jaime A. Pepe'00 PM 58

La n que me devuelve RdBin si es 0 quiere decir que el fichero ha acabado, si n < SIZE(datos)se ha producido un error.En la escritura se usa la función IOResult que indica qué es lo que ha pasado en la últimafunción de E/S, es un BOOLEAN, si TRUE entonces hay error.Si IOCheck es TRUE el IOResult no sirve para nada porque el sistema abortaría antes.

* Acceso aleatorio:Para poder escribir en cualquier parte del fichero se usa Seek(f: FILE; pos: LONGCARD); posva desde 0 hasta el tamaño del fichero, esta función posiciona el fichero f en la posición pos(modifica el cursor).

La función Truncate(f: FILE); corta (destruye) el trozo de fichero que hay a partir del cursor.

Para posicionar el cursor al final del fichero sería:Seek(f, SIZE(f));

RdBin y WrBin aumentan por sí mismos el cursor en el SIZE que tengan indicado.

Ejercicio: tengo un fichero con números que quiero ordenar directamente en disco manipulándoloen forma binaria (supongo números cardinals): SortCard(VAR f: FILE);

PROCEDURE SortCard(VAR f: FILE);

VAR n1, n2: ARRAY OF BIN; cambio: BOOLEAN; i, sc: LONGCARD;

BEGINsc := SIZE(CARDINAL);REPEAT

cambio := FALSE;FOR i := 0 TO (SIZE(f)/SIZE(CARDINAL)) - 1 DO

Seek(f, i*sc); RdBin(f, n1, sc);Seek(f, (i+1)*sc); RdBin(f, n2, sc);IF n1 > n2 THEN

Seek(f, i*sc); WrBin(f, n2, sc);Seek(f, (i+1)*sc); WrBin(f, n1, sc);cambio := TRUE;

END;END; (* FOR *)

UNTIL NOT (cambio);END SortCard;

Page 59: Programacion Modular

J. Jaime A. Pepe'00 PM 59

TEMA 6.- VERIFICACIÓN Y COMPLEJIDAD.* Complejidad:Medida de la eficiencia en el espacio y en el tiempo de un algoritmo. Inmediatez de la ejecución,recursos de datos utilizados.

O(n) •> para medir la complejidad n.

Cuando queremos medir la complejidad:- debemos saber de cual complejidad hablamos (espacial o temporal).- acotamiento asintótico.

Vamos a considerar la complejidad temporal, lo que tarda el algoritmo en ejecutarse. Esto puededepender de otros factores: el ordenador, el sistema operativo, etc...

Para poder medir la complejidad temporal no vamos a medir en segundos. Se trata de analizarel comportamiento del algoritmo en función de las operaciones que tiene que utilizar.

Operación elemental: una operación suficientemente simple como para que no se puedadescomponer en otras operaciones menores, se toma como operación unidad. Se puede tomarcomo operación elemental cualquier operación, aunque se descomponga en otras.

Habrá que tener en consideración también la muestra de entrada, en qué forma viene y cuantainformación contiene. N: tamaño de la muestra de entrada.La disposición de los elementos de la muestra de entrada puede ser peor, medio o mejor.

La mejor disposición de elementos de entrada no implica el mejor comportamiento del algoritmo,puede aparecer un comportamiento innatural (p. ej.: en el Quick Sort).

f(x) / tiempo de ejecución.

f(x) es una función bastante mala puesto que para obtenerla se han hecho muchasaproximaciones.Me interesa la función cuando x 6 4, comportamiento asintótico.

f(x) = O(g(x)) x 6 4

si › C, x0 / |f(x)| # C $ g(x) œ x > x0 [C cte.]

Ejemplo:f(x) = 3x2 + 2x + 8

g(x) = x2 (se busca la g(x) más simple).

Ejemplos:sin(x) = O(x) [x0 = 1; C = 1]1/(1+x2) = O(1) [x0 = 0; C = 1]

Llamaremos a f tiempo de ejecución y a O complejidad.A O la situaremos en la función representativa de f que encaje con la del modelo.

Page 60: Programacion Modular

J. Jaime A. Pepe'00 PM 60

( )( )∑=

−+=+−+++n

i

nni2

2 72

9

2

7113472

∑=

−+=++=+=−+n

i

nniii2

2 82

33

4

7

2

13

2

72

2

7

2

13

2

7

2

710

* Tiempo de ejecución:1 •> es un tiempo imposible o casi imposible, no depende de la entrada.log n •> es un tiempo muy bueno, casi constante, como la búsqueda binaria.n •> es bastante lento ya, depende directamente de la cantidad de elementos.n log n •> comportamiento linearítmico, bastante malo también.2n •> comportamiento exponencial, muy malo.nn •> comportamiento exponencial, una aberración.

- Medida del tiempo de ejecución:El algoritmo de ordenación por inserción como ejemplo:PROCEDURE OI(VAR a[1..n] OF BASE);

VAR ........

BEGINFOR i := 2 TO n DO

x := a[i]; •> 1 paso (aunque tendrá más).WHILE (i > 0) AND (x < a[j]) DO •> 3 pasos (2 comparaciones y conjunción).

a[j-i] := a[j];j := j-1;

END;a[j+i] := x •> 2 pasos (suma y asignación de array).

END;END OI;

En el mejor caso no hacemos el cuerpo del WHILE. El caso mejor resulta 8 pasos por pasada,como se repite n-2 veces (bucle FOR) hay 8$(n-2) pasos en total, aunque no es totalmente ciertopuesto que el bucle FOR tiene una primera y una última vez distinta. La primera tiene 2 pasosmás (asignación, además de la iteración), cada iteración tiene 2 pasos y la última tiene 2 pasosmás (intenta incrementar y sale, además de la iteración).

Finalmente resulta: (2+8)(n-1) +2O(temp(n)) = n

El caso peor (pasando por todos los bucles) sería:

El +1 después del paréntesis es el último paso, no entra al WHILE.

El caso medio es con una entrada media de entre todas las posibles.

Las variaciones aquí se van a producir en el WHILE, solo va a llegar a la mitad de las veces queel peor caso:

Page 61: Programacion Modular

J. Jaime A. Pepe'00 PM 61

( )knTkik

i

−+∑−

=33

1

0

( ) 23231

0

⋅+⋅= ∑−

=

nn

i

nnT

( ) ( )( )( )

( )( ) 2

2

1

002

11

nnTO

nn

nT

T

nn

nTnnT

=

⋅+=

=

⋅+=−+=

( ) ( ) ( ) ( )

( )( ) nnT

T

nn

TknTnTnTnT

2log1

11

2811141121

+==

⋅+=+++=++=+=

* Complejidad en algoritmos recursivos:Es el mismo problema pero cambia la técnica.

PROCEDURE Fact(n: CARDINAL): CARDINAL;

BEGINÎ IF n <= 1 THEN RETURN n END;Ï RETURN n * Fact(n-1);END Fact;

Î •> 1 op. Ï •> 3 op.T(n) = 1 + 3T(n-1) •> ecuación recurrente.T(n) = 1 + 3((1+3)T(n-2)) = 1 + 3(1+3)(1+3)T(n-3) = ......... = 13 + 27((1+3)(T(n-4)) =

=

T(O) = 2 <• fin de la ecuación.

en general:

Si fuese un algoritmo con dos llamadas recursivas aparecerían dos términos recursivos.

- Comportamientos recurrentes típicos:$ En cada llamada elimino un ítem:

$ Divido la entrada en dos partes y trato solo una de ellas:

$ Divido la entrada en dos partes y examino los n elementos al dividir:T(n) = n + T(n/2) = 2n

Page 62: Programacion Modular

J. Jaime A. Pepe'00 PM 62

( ) ( )( ) ( ) ( ) ( )

( )( ) ( ) ( ) nnnnTkT

T

Tk

TTT

nnTnnT

k

k

k

k

k

k

k

k

+⋅=⇒+==

+=++=⋅+=

=+=

2

2

2

1

1

log12

111

1

2

211

2

221

2

2

:2 tomando22

( ) ( )1272227 11

0

−=+= +−

=∑ nk

i

kki dT

∑=

+ −=u

i

ni

0

1 122

$ Hay que hacer una pasada lineal antes, durante o después de dividir en dos partes:

$ Dividir la muestra en dos partes en un solo paso y trabajar cada una por separado:T(n) = 1 + 2T(n/2)

Ejemplo:PROCEDURE regla(l, r, h: ù);

VAR m: ù;

BEGINm := (l + r) DIV 2; 3IF h > 0 THEN 1

pintaraya(m, h); 1regla(l, m, h-a); 1 + T(d/2)regla(m, r, a-1); 1 + T(d/2)

END;END regla;

Mejor caso:h # 04 op.T(d) = 7 + 2T(d/2) = 7 + 2(7 + 2T(d/22) = 7 + 2 $ 7 + 2 $ 22 $ 7 + ... + 2k $ T(d/2k) =

T(1) = 4k = log2 d ; d = 4T(d) = 7(2d1) + d4 = 18d - 4O(t(d)) = d Y complejidad lineal.

T(n) = 1 + 2T(n/2)O(T(n)) = n

Page 63: Programacion Modular

J. Jaime A. Pepe'00 PM 63

{ }

( )1

1

0

1

1

log

Tnm

n

knm

nTmm mk

kik

i

+

−−=

==

+= ∑

=

T(n) = 1 + m $ T(n/m) = 1 + m(1 + T(1/m)) = 1 + m(1 + n(T(n/m2))) == 1 + m + m2 + ... + mk (T(n/m2)) =

Ejercicio:PROCEDURE Eu(m, n: ù): ù;

BEGINWHILE m > 0 DO

t := n MOD m;n := m;m := t;

END;RETURN n;

END Eu;

Ejercicio: demostrar que x62 se puede calcular con solo x operaciones. Encontrar un algoritmode multiplicación rápida (complejidad algorítmica respecto a la base).

Ejercicio: método de ordenación de la burbuja, estudiar la complejidad en el pero caso:PROCEDURE BB(VAR a: VECTOR);

VAR i, j: ù;

BEGINFOR i := HIGH(a) TO 0 BY -1 DO

FOR j := 1 TO i DOIF a[j+1] > a THEN

Swap(a[j+1], a[j]);END;

END;END;

END BB;

Ejercicio: estudiar la complejidad en función de la longitud de la muestra en un algoritmo debusca en la muestra según un patrón.

VERIFICACIÓN.Hay dos formas de verificación: testeo y verificación formal.

El testeo es la forma más rápida y más usada pero no asegura que el programa sea totalmentecorrecto.

Page 64: Programacion Modular

J. Jaime A. Pepe'00 PM 64

La verificación es más difícil, lenta y con una pretensión exagerada (que sea matemáticamentecorrecto), solo es aplicable a pequeños textos.

Notación de Hoare: se basa en los estados (valor de los objetos que los compone).Asertos: expresión booleana sobre los estados. Un aserto es más fuerte que otro si el primero (elmás fuerte) implica al segundo (menos fuerte).Precondición: aserto que se cumple o se supone que se cumple antes de la ejecución de unalgoritmo.Postcondición: expresión de resultado.Aserto intermedio: aserto que se cumple en el interior (no extremos) del código.

Los asertos se notan con llaves { }.Unas precondiciones a partir de un programa se convierten en unas postcondiciones: {P} S {Q}.En la verificación muchas veces se hace el proceso hacia atrás, desde la postcondición hasta laprecondición más débil (Wp).

{S} S1 {R} v {R} S2 {Q} Y {P} S1, S2 {Q}.Es interesante poder dividir las secuencias grandes en secuencias más cortas con valoresintermedios.

* Sentencias:- Asignación:

{Qve} v := e {Q}

•> se sustituyen todas las apariciones de v por e.

P / {(x = x0) v (y = y0)}t := x •> P2 / {(t = x0) v (y = y0) v (x = x0)}x := y •> P1 / {(x = y0) v (y = y0) v (t = x0)}y := t •> P0 / {(y = x0) v (x = y0) v (t = x0)} Y Q

Q / {(y = x0) v (x = y0)}

Ejemplo: {i = jk}

k := k + 1 •> P1 / {(k = k0 + 1) v (i = jk-1)}i := i * j •> P2 / {(i = jk-1 $ j) v (k = k0 + 1)} Y Q

{(i = jk) v (k0 = k0 + 1)}

Page 65: Programacion Modular

J. Jaime A. Pepe'00 PM 65

P1 / {(i = 1) v P}P2 / {(i = 1) v (j = 1)}P3 / {(i = 0) v P}P4 / {(i = 0) v (j = 1)}P5 / {P2 w P4} / {(i = 0 w i = 1) v (j = 1)} Y Q

P1 / {(i0 > 1) v (j = j0)}P2 / {i = i0} ?Q2 / {(j = j0 + 1) v (i = i0 + 1) v (i0 > 1)}P3 / {(P v 5B) w (P v B v S)} Y Q

- Selección:La verificación se hace separando cada rama y conjuntando los resultados de todas las ramas.

TRUE B FALSE

{P1} {P2}

S1 S2

{Q1} {Q2}

{Q}

P v B = P1

P v 5B = P2

Q1 w Q2 / QWp / (P v B) w (P v 5B)

TRUE {P}

B

S1 FALSE

{Q1

}

{Q}

igual que antes pero con P v 5B Y Q

Ejemplo:P / {(i = 0) w (i = 1)}

SI i = 1 entoncesj := i

enotrocasoj := i + 1

finsiQ / {j = 1}

Ejemplo:P?SI i > 1 entonces

i := i + 1;j := j + 1;

fin si;Q / {j # (i + 1)}

(P v 5B) Y {(j # (i + 1)} Y {(j # 2) v (i # 1)}(P v B v S) Y {(P v (i > 1) v S}

(i0 > j) v (j0 + 1 # (i0 + 1) + 1) v (i0 > 1) / (j0 # 2) v (i > 1)((i # 1) v (j # 2)) w ((i > 1) v (j # 2)) / (j # 2)

Page 66: Programacion Modular

J. Jaime A. Pepe'00 PM 66

Solución: P / {(j # (i + 1)}: - ?

Buscábamos las más débiles precondiciones y con más fuertes postcondiciones.Lo normal es que se haga de abajo a arriba.

Corrección parcial: se presupone que el programa siempre termina

Variable ligada es una que pertenece al programa, no es libre, es una variable local . Una noligada tiene sentido antes de la ejecución del programa. Un cuantificador es un iterador sobre lavariable ligada.

Ejemplo:3 sumaJ producto› existencia œ para todoœi, j a[i] < x

Hay situaciones en las que un cuantificador puede carecer de sentido: no se conoce el rango devalores.

{Aex} indica sustituir la expresión e por el valor x.

El aserto es el mismo, solo que x la puedo sustituir siempre por e.El aserto, tras cambiar x por e, sigue siendo el aserto.

Ejemplo:x := x-1 {x = 4}{x = 4x

x-1}x-1 = 4 •> precondición, a partir de la postcondición sé la precondición.x = 5

{x = 3} x := x-1 {x = 4}

Hay situaciones en las que se producen efectos laterales.a[a[i]] := i-1 <• se lía la cosa porque las aes cambian. El propio índice está cambiando.

- Bucle:{P}MIENTRAS B HACER

SFIN MIENTRAS{Q}

Hay dos formas: inducción y verificación formal.La inductiva pretende determinar un aserto para el estado intermedio y comprobar el siguiente.Se debe probar el estado inicial.

{P} v 5B => P0 P0 v B => PA => ... Pk v 5B => QSe busca el aserto que no cambia para todo, es el invariante en todo el bucle.

Page 67: Programacion Modular

J. Jaime A. Pepe'00 PM 67

22

1

1

⋅+== ∑=

ijs

i

j

Además Pi v 5B => Q.Lo difícil es encontrar Pi: el invariante.Ejemplo:

i := 0; s := 0 <• {P}MIENTRAS i # n HACER

i := i + 1s := s + i

FIN MIENTRAS{s = n(n+1)/2} = {Q}11.- Comprobar que termina:

i3 > i2 > iA > i0 , in > in n se supone $ 0i0 = 0 ... i0x $ n => 5B i creciente y n fijo.

con esto queda comprobada la terminación.21.- {i = 0, n = 0} n(i = n) => n = 0 s = 031.- Busco un aserto que me relaciones todo. Buscar algo de i, s, n:

i | s1 | 12 | 32 | 6

Pi {s= ((i+1)/2)$i} El Pi es l invariante.Falta la n, I/ {s = (i+1)$i/2 v i# n} este sí es el invariante.A I le añadimos 5B y tiene que salir {Q}I v 5B => Q {s = i$(i+1)/2 v i # n} v i = n => s = n(n+1)/2 /Q

No se contempla el que sea muy grande. Se puede pero no lo vamos a hacer.

Ejercicio:WHILE NOT ODD(x) DO

y := 2 * y;x := x DIV 2;

END;Q / {c = x *y}

B / {5 ODD(x)}P v 5 B / QP v ODD(x) / QCalculo I.

Otro más fácil:WHILE x # 0 DO

Wc := c - y;x := x -1

END;{c = x * y}

Page 68: Programacion Modular

J. Jaime A. Pepe'00 PM 68

TEMA 7.- TODOS LOS EJERCICIOS QUE TE PUEDAS IMAGINAR.

Ejercicio, examen )?: una matriz M dispersa: grandes dimensiones con unos pocos elementosp distintos de 0.

ARRAY[1..n][1..m] OF BASEREAL;(m x n)-p huecos.

Hacer la estructura apropiada con complejidad referida a p. Optimización en espacio, tamañoproporcional a p.

CrearMatrizNula(VAR ma: MATRIX; m, n: CARDINAL);EscribeCelda(VAR ma: MATRIX; i, j: CARDINAL; x: BASEREAL);DestruyeMatriz(ma: MATRIX);LeeDimMatriz(ma: MATRIX; VAR m, n: CARDINAL);

EscribeCelda(i, j, 0.0) haría desaparecer una celda de esta representación.

IMPORT LISTAS; (* BASE = RECORD i, j: CARDINAL; x: BASEREAL *)MATRIX = POINTER TO HEAD;HEAD = RECORD

m, n: CARDINAL;a: LISTAS.LISTA;

END;

PROCEDURE CrearMatrizNula(VAR ma: MATRIX, m, n: CARDINAL);

BEGINNEW(ma);ma^.n := n;ma^.m := m;LISTAS.CreaLista(ma^.a);

END CreaMatrizNula;

PROCEDURE EscribeCelda(VAR ma: MATRIX; i, j: CARDINAL; x: BASEREAL);

VAR ..........

BEGINn := BuscaElem(m, i, j);IF (x = BASEREAL(0)) THEN

IF n # 0 THENLISTAS.BorrarElem(m^.a, n);

END;ELSE

elem.x := x;elem.i := i;elem.j := j;IF n # 0 THEN

LISTAS.BorraPos(m^.a, n);

Page 69: Programacion Modular

J. Jaime A. Pepe'00 PM 69

END;LISTAS.Insertar(m^.a, Longitud(m^.a)+1, elem);

END;END EscribeCelda;

PROCEDURE BuscarElem(m: MATRIX; i, j: CARDINAL): BASEREAL;

VAR ..........

BEGINFOR k := 1 TO Longitud(m^.a) DO

elem := LISTAS.LeeElem(m^.a, k);IF (elem.i = i) AND (elem.j = j) THEN

RETURN k;END;

END;RETURN BASEREAL(0);

END BuscarElem;

PROCEDURE LeeCelda(m: MATRIX; i, j: CARDINAL): BASEREAL;

VAR ..........

BEGINn := BuscarElem(m, i, j);IF n = 0 THEN

RETURN BASEREAL(0);ELSE

elem := LISTAS.LeeElem(m^.a, n);RETURN elem;

END;END LeeElem;

PROCEDURE MultMatriz(VAR m: MATRIX; s, t: MATRIX);

VAR ..........

BEGINLeeDimMatriz(s, Mn, p);LeeDimMatriz(t, p, Mm);FOR i := 1 TO Mn DO

FOR j := 1 TO Mm DOEscribeCelda(m, i, j, 0);FOR k := 1 TO p DO

tmp := LeeCelda(s, i, k) * LeeCelda(t, k, j) + LeeCelda(m, i, j);END;EscribirCelda(m, i, j, tmp);

Page 70: Programacion Modular

J. Jaime A. Pepe'00 PM 70

En toda la aplicación se puede insertar en la posición 1 en lugar de en la posiciónLongitud( ... ) +1.

END;END;

END MultMatriz;

PROCEDURE Sumar(VAR m: MATRIX; s, t: MATRIX); (* dentro del TAD *)

VAR ..........

BEGINFOR i := 1 TO Longitud(s^.a) DO

LISTAS.InsertaElem(m^.a, i, LISTAS.LeeElem(m^.a, i));END;FOR i := 1 TO LISTAS.Longitud(t^.a) DO

elem := LISTAS.LeeElem(t^.a, i);n := BuscarElem(m, elem.i, elem.j);IF n = 0 THEN

LISTAS.Insertar(m^.a, LISTAS.Longitud(m^.a)+1, elem);ELSE

elem1 := LISTAS.LeeElem(m^.a, n);LISTAS.BorraPos(m^.a, n);IF (elem + elem1) # 0 THEN

LISTAS.InsertaElem(m^.a, n, elem + elem1);END;

END;END;

END SumaMatriz.

Ejercicio, examen )?: comprobar:x, a, b, u, v: INTEGER;{a $ 1, b $ 1, u $ 0, v $ 0, x = u$a - v$b}WHILE x # 0 DO

IF x > 0 THEN x := x-1; u = u-1;ELSE x := x+b; v := v-1;END;

END;{x = 0}

Ejercicio: copiar un árbol binario:IF EsBVacio(b) THEN

RETURN HacerBVacio( );ELSE

RETURN CrearArbol(Copia(Izda(b)), Raiz(b), Copia(Drcha(b));END;

Page 71: Programacion Modular

J. Jaime A. Pepe'00 PM 71

Para generar el mirror de un árbol simplemente se cambiaría en la 40 línea del procedimientoanterior:

RETURN CrearArbol(Copia(Drcha(b)), Raiz(b), Copia(Izda(b));

* Ventanas:Existen TAD's que además de la estructura ya conocida tienen 2 posiciones más: una delante delprimer elemento y otra detrás del último, se llaman ventanas y la manipulación se va a hacer porellas.

La definición:TYPE LISTA; VENTANA; BASE = ................;

PROCEDURE CrearVacio(VAR l: LISTA);PROCEDURE EstaVacio(l: LISTA): BOOLEAN;PROCEDURE AntesdelPrimero(l: LISTA): VENTANA;PROCEDURE DespuesdelUltimo(l: LISTA): VENTANA;PROCEDURE EstaAntesdelPrimero(l: LISTA; v: VENTANA): BOOLEAN;PROCEDURE EstaDespuesdelUltimo(l: LISTA; v: VENTANA): BOOLEAN;PROCEDURE Siguiente(l: LISTA; v: VENTANA): VENTANA;PROCEDURE Anterior(l: LISTA; v: VENTANA): VENTANA;PROCEDURE Examina(l: LISTA; v: VENTANA): BASE;PROCEDURE Reemplaza(VAR l: LISTA; v: VENTANA; l: BASE);PROCEDURE InsertaDespues(VAR l: LISTA; v: VENTANA; l: BASE);PROCEDURE InsertaAntes(VAR l: LISTA; v: VENTANA; l: BASE);PROCEDURE Borrar(VAR l: LISTA; VAR v: VENTANA);

Ejemplo de aplicación:PROCEDURE Copiar(l: LISTA; VAR nueva: LISTA);

BEGINv := AntesdelPrimero(l); n := DespuesdelUltimo(nuevo);LOOP

v := Siguiente(l, v);IF EstaDepuesdelUltimo(l, v) THEN EXIT END;x := Examina(l, v);InsertaAntes(nuevo, n, x);

END;END Copiar;

La implementación:TYPE VENTANA = POINTER TO NODO; LISTA = POINTER TO HEAD; HEAD = RECORD

ap, du: VENTANA; END;

NODO = RECORD

Page 72: Programacion Modular

J. Jaime A. Pepe'00 PM 72

x: BASE;sig, ant: VENTANA;

END;

PROCEDURE CrearVacio(VAR l: LISTA);

BEGINNEW(l);NEW(l^.ap);NEW(l^.du);l^.ap^.sig := l^.du;l^.du^.ant := l^.ap;

END CrearVacio;

PROCEDURE EstaAntesdelPrimero(l: LISTA; v: VENTANA): BASE;

BEGINRETURN v = l^.ap;

END; EstaAntesdelPrimero;

PROCEDURE Anterior(l: LISTA; v: VENTANA): VENTANA;

BEGINRETURN v^.ant;

END Anterior;

PROCEDURE InsertaAntes(VAR l: LISTA; v: VENTANA; e: BASE);

VAR nuevo: VENTANA;

BEGINNEW(nuevo);nuevo^.x := e;nuevo^.ant := v^.ant;v^.ant := nuevo;nuevo^.sig := v;nuevo^.ant^.sig := nuevo;

END InsertaAntes;

PROCEDURE Borrar(VAR l: LISTA; VAR v: VENTANA);

VAR tmp: VENTANA;

BEGINv^.ant^.sig := v^.sig;v^.sig^.ant := v^.ant;tmp := v;

Page 73: Programacion Modular

J. Jaime A. Pepe'00 PM 73

v := v^.sig;DISPOSE(t);

END Borrar;

Ejercicio: Sin utilizar recursión haces un procedimiento que devuelva el valor numérico de unaexpresión prefija compuesta por ['0' .. '9'], simples (no hay más de 2 dígitos) y los operadores +,-, * y /. Utilizar dos pilas: una para operadores y otra para operandos. Se pueden suponer yacreador los TAD's.

PROCEDURE Eval(s: ARRAY OF CHAR): CARDINAL;

BEGINCreaPila(num); CreaPila(sig);FOR i := 0 TO Length(s) -1 DO

x := s[i];IF x <= '9' AND x >= '0' THEN PUSH(num, x);ELSE PUSH(sig, x);END;

END;WHILE NOT(EsPilaVacia(sig)) DO

x := Pop(num); y := Pop(num); z := Pop(sig);Operar(num, x, y, z);

END;RETURN Pop(num);

END Eval;

Ejercicio: Dado un array de registros con una clave respecto a la que ordenamos, desarrollar unprocedimiento de complejidad lineal (no puede ser menores que linearítmicas) sabiendo que laclave es del tipo RANGO = [0..1].VAR k: CARDINAL;

BEGINk := 0;FOR i := 0 TO HIGH(s) DO

IF s[i] := 0 THENSwap(s, i, k);INC(k);

END;END;

Otras cosas:Una infija con todos paréntesis se para a postfija:(8 / ((2 * 3) + (5 / 4)))

Si es op: escribe.Si es sig: pila.Si es ): saca pila •> escribe.

Page 74: Programacion Modular

J. Jaime A. Pepe'00 PM 74

Dada una cadena de caracteres, comprobar que está bien parentisada:( ), [ ], { }, < >, ) ?, ( !, " ",

Si abre: pila.Si cierra: comprueba cierra (igual tipo) de la pila.