SlideShare une entreprise Scribd logo
1  sur  74
Télécharger pour lire hors ligne
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

J. Jaime A.    Pepe'00                         PM 1
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 y
se van a quitar o añadir cosas al producto (lo que el cliente pide y lo que se le da no tiene nada
que ver). El mantenimiento consiste en volver a empezar todo el ciclo. Antiguamente no había
mantenimiento, 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 un
proceso más transparente al cliente. El analista distorsiona mucho el proyecto, lo ideal sería que
el 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 de
servicios de corto plazo en los que le voy a dar al cliente productos intermedios. A partir de esos
resultados 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ástrofes
localizadas.

* Técnicas de 40 generación:
Se utilizan métodos informáticos para el desarrollo. Modifican un poco el ciclo de vida en cuanto
a que la generación del código puede ser automatizable, a partir de las especificaciones del
analista que será el propio usuario en este caso; el usuario sería capaz de especificar gráficamente
qué es lo que quiere, y el sistema especificaría el diseño y generaría el código final compilable
y 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 un
programador lo revise.
Se usan entornos gráficos en la programación, esta técnica es uno de los grandes retos de la
ingeniería del software.



J. Jaime A.    Pepe'00                         PM 2
* Construcción de prototipos:
Los prototipos tratan de caricaturizar lo que hace el sistema, sin saber más de lo que pasaría (no
sabes como va a actuar el sistema).
Este método lo que busca es acelerar la construcción del programa final aunque sea una cáscara
hueca en su aspecto, sin ninguna funcionalidad, el cliente solo vería el interfaz del usuario.

Se puede diferenciar:

                               recogida de
                                requisitos


        evolución del                                 diseño
          prototipo                                   rápido


                               construcción
                               del prototipo                            producto
                                                                          final


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ística
fundamental: hace un análisis de riesgo constantemente y una vuelta al inicio del ciclo de una
forma periódica.

               planificación                                                 análisis




               evolución
               prototipos                                                  desarrollo




J. Jaime A.    Pepe'00                         PM 3
* 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 conceptual
que cada uno de los elementos de módulo va a tener. Buscar cohesión es buscar módulos fáciles
de describir simplificando suocalización dentro de un proyecto, la cohesión también va a permitir
la compatibilidad.
Un módulo es una agrupación de objetos del sistema, por lo que sería coherente si todo el módulo
va definido a una misma utilidad. Coherente significa que todo lo que escribo en un paquete
tiene 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. Existen
tambié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 son
elementos tan sueltos que no dependan de nada (ideal un poco absurdo), normalmente se busca
el 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 un
sistema, cada uno de los componentes tiene funciones independientes si cada uno trata de casos
distintos.

Se utiliza también técnicas de modulación y abstracción. Modular consiste en reunir elementos
coherentes. Abstraer es aislar elementos del resto (no hacer muy acoplado).
Si un módulo consigo hacerlo abstracto (elimino lo innecesario) se consigue menor
acoplamiento.
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 opaca
y 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 rasgos
de la orientación a objetos permiten diseñar el sistema enfocado no a módulo, sino directamente
a conjuntos de las características del sistema (objetos), cada objeto presenta un interfaz y una
serie de rutinas y mediante esto intenta describir cada una de las partes del sistema (un objeto no
es 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.




J. Jaime A.    Pepe'00                        PM 4
TEMA 2.- PUNTEROS.
Los punteros no son un tipo de datos usual puesto que no se hace ninguna operación con ellos
excepto la de referenciar la memoria del ordenador. Mediante un objeto de estos guardo la
dirección de una zona de memoria (índice).
Para asignar el valor al puntero usamos las instrucciones propias del lenguaje, igualmente para
la 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ño
prefijados durante la compilación y que puedan tener una estructura (interconexión) definida por
nuestro 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ón
del programa.
Una variable de datos dinámica es una variable que puede cambiar de tamaño durante la
ejecución del programa (sin tamaño fijo).
El puntero NO 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;

       BEGIN
              i := 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 denomina
referenciar, pero me hace falta dirigirme a la dirección de memoria, esto se denomina
derreferenciar. Para derreferenciar se usa el operador ^:
       p^ significa quot;lo apuntado por pquot;

               p^ := i + 3;

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




J. Jaime A.    Pepe'00                        PM 5
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);

       BEGIN
             p^ := 33;
       END P;

      BEGIN
              p := 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;

       BEGIN
             p^ := 33;
             RETURN ADR(j);
       END P;

       VAR p, PP: POINTER;

        BEGIN
                p := 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.




J. Jaime A.    Pepe'00                       PM 6
Se podría escribir p^^ y el sistema interpretaría que la dirección a la que apunta p contiene la
direcció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;

       BEGIN
            PP := 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 memoria
estática para sus variables: hay una zona de RAM reservada para constantes, otra zona para
variables globales y otra para las variables locales a los procedimientos. Esta última zona no es
tan estática. Además de estas tres zonas el sistema tiene el Heap.
Partición de la RAM:
    Variables                               variables
 procedimientos           Heap              globales          constantes           código
   (dinámicas)

El sistema permite reservar y utilizar esa zona libre de RAM (Heap). Hay procedimientos que
reservan 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 un
parche). NEW devuelve un puntero al lugar reservado por él (11 reserva RAM, 21 indica cual
es 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én
es 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) que
indica un lugar absurdo en la RAM (comúnmente la dirección 0), es un valor de puntero que
indica que no tiene valor. Es una manera de inicializar el puntero.

J. Jaime A.    Pepe'00                        PM 7
Después de DISPOSE(p) es recomendable poner p := NIL, aunque no es imprescindible en todos
los 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 declare
alguna vez.

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

TYPE LISTA = POINTER TO NODO
     NODO = RECORD
                 info: CARDINAL;
                 next: LISTA;
             END;

VAR n: CARDINAL;
    t, p: LISTA;

BEGIN
     t := NIL; p := NIL;
     LOOP
             n := RdCard( );
             IF n = 0 THEN EXIT END;

J. Jaime A.   Pepe'00                         PM 8
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 quot;de atrás a adelantequot; 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 una
lista 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;

BEGIN
     t := 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;

J. Jaime A.    Pepe'00                        PM 9
END;
                     tt^.next := t;

              ELSE
                     p := t;
              END;

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

VAR l, l2: PTRFICHA;
    n: CARDINAL;

BEGIN
     l := lista;
     IF pos = 1 THEN
              l2 := lista;
              lista := lista^.sigui;
     ELSE
              n := 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;

BEGIN
     l := lista;
     IF lista = NIL THEN RETURN END;
     IF Compare(l^.nombre, nombre) = -1 THEN
              l2 := lista;
              lista := lista^.sigui;
     ELSE
              l := lista;
              WHILE (l^.sigui # NIL) AND (Compare(l^.sigui^.nombre, nombre) <= 0) DO
                       l := l^.sigui;

J. Jaime A.   Pepe'00                   PM 10
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;

BEGIN
     IF 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;

BEGIN
     l := 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:

J. Jaime A.   Pepe'00                      PM 11
PROCEDURE InsertOrd(VAR: lista: LISTA; nombre: STR32);

VAR l, l2: LISTA;

BEGIN
      NEW(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;
      ELSE
               l2 := 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;




J. Jaime A.   Pepe'00                  PM 12
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 algoritmo
es 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 mayor
complejidad.
Todo algoritmo recursivo debe tener una forma de ser ejecutado en la que no haya recursión (la
llamada 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;

BEGIN
     IF (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;

        BEGIN
              IF 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;

       BEGIN
             n1 := 1; n2 := 1;
             WHILE i < n DO
                     sol := n1 + n2;
                     n1 := n2;
                     n2 := sol;
                     INC(i);
             END;
             RETURN sol;
       END Fibo;



J. Jaime A.    Pepe'00                      PM 13
Recursión indirecta: cuando la recursión no se produce sobre el primer algoritmo sino sobre una
llamada a un segundo algoritmo que a su vez llama al primero.

PROCEDURE F(x: REAL): REAL;

BEGIN
      IF 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;

BEGIN
     IF 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 se
usa 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úmero
factorial).
Recursión doble: el programa se llama a si mismo dos veces (en dos puntos del código), como
la suma de Fibonacci.



J. Jaime A.    Pepe'00                      PM 14
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 tratar
prácticamente de la misma manera.
Una recursión intermedia siempre se puede convertir a iteración aunque el resultado puede ser
totalmente distinto y el método no tan simple, se hace usando dos pilas: una para llamadas y otra
para 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 sistema
una zona para las variables (parámetros locales incluidos) de esa función; además se guarda en
esa pila la dirección de vuelta, de manera que al finalizar el procedimiento el sistema sea capaz
de 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 quot;limpiaquot; la pila leyéndose la dirección de
partida 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 la
recursión es peligrosa en cuanto a que puede agotar el espacio en la pila (que no es mucho); si
se pasa puede que escriba en zonas de memoria sin control, incluso que llegue a escribir la
memoria 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 cuanto
a pilas.

Ejercicio: función de Ackermann:
        A(0, s) = 2s s $ 0
        A(r, 0) = 0     r$0
        A(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 a
4 o 5, poco más; este algoritmo queda como una forma de probar la capacidad recursiva y el
control 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 tipos
de ineficiencias:
        11.- técnica: debido a la sobrecarga que le produce al sistema la operación de llamada
        a procedimientos.
        20.- complejidad: debida a la cantidad de redundancias que se producen en la llamada
        recursiva a los procedimientos.

J. Jaime A.    Pepe'00                        PM 15
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 = 0
      MCD(n, m) si n < m
      MCD(n, m MOD n)

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

BEGIN
     IF ( 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 procedimiento
de 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;

     BEGIN
          IF i >= i1 THEN RETURN END;
          m := (i0 + i1) DIV 2;
          IF a[m] = x THEN
                  pos := m;
                  RETURN TRUE;
          ELSIF a[m] < x THEN
                  RETURN bb(m + 1, i1);
          ELSE
                  RETURN bb(i0, m - 1);
          END;
     END bb;
BEGIN
     RETURN bb(0, HIGH(a));
END BB;

J. Jaime A.    Pepe'00                      PM 16
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;

BEGIN
     IF l = NIL THEN RETURN END;
     IF l^.info = x THEN
             tmp := l;
             l := l^.next;
             DISPOSE(tmp);
     ELSE
             Delete(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 la
                                               condición de no poner nunca un disco grande
                                               encima de otro pequeño, uno a uno.

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

BEGIN
     IF n > 0 THEN
             hanoi(n - 1, source, inter, target);
             (* mover disco de origen a destino *)
             hanoi(n - 1, inter, target source);
     END;
END Hanoi;

J. Jaime A.    Pepe'00                      PM 17
- 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 la
cadena. Se repite con cada una de las dos zonas y así sucesivamente hasta que tengo un array de
dos 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:
         aedcbf
         abdcef
         abcdef •> ordenado

PROCEDURE QuickSort(VAR a: ARRAY OF CHAR);

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

                   VAR t: CHAR;

                   BEGIN
                        t := 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;

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


J. Jaime A.        Pepe'00                      PM 18
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;

       BEGIN
            IF de > iz THEN
                    p := partition(a, iz, de);
                    quick(a, iz, p-1);
                    quick(a, p+1, de);
            END;
       END quick;

BEGIN
     Quick(a, 0, Length(a)-1);
END QuickSort;




J. Jaime A.   Pepe'00                      PM 19
TEMA 4.- TIPOS ABSTRACTOS DE DATOS (TAD's).
Un TAD es alguna forma de almacenar información y una serie de procedimientos de accesos
a 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 de
       construir cualquier instancia.
       - minimalismo, deben de ser los mínimos procedimientos para cumplir lo primero. La
       ventaja 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 las
       dependencias 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 procedimientos
de acceso:
        TYPE LISTA
        PROCEDURE 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 el
máximo de la colección numerando cada uno de los elementos). No hay un número fijo de
elementos. 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 selectores
muestran 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.


J. Jaime A.    Pepe'00                      PM 20
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 el
programador).
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);

       BEGIN
              NEW(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);

       BEGIN
            NEW(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);

       BEGIN
            l : = NIL;
       END Crearlista;

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


J. Jaime A.    Pepe'00                       PM 21
Array
                      Acotada
                                   Cursores
Implementació                                         Modos simples enlzados.
       n                             Sin cabecera
                                                      Modos dobles enlazados.
   de listas          No acotada
                                                      Modos simples enlzados.
                                     Con cabecera
                                                      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 el
       movimiento de la lista. Pueden utilizarse como bloques de memoria.
       Cada nodo tiene la información base y quot;nextquot; (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éndose
       al campo siguiente.
       Hay que mantener además de los enlaces entre nodos con infromación otra quot;cadenaquot; con
       los 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);

       BEGIN
            l := NIL;
       END Crearlista;

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

       VAR tmp, tt: LISTA;
           i: CARDINAL;

       BEGIN
            NEW (tmp);
            tmp^.inf := x;
            IF pos = 1 THEN
                   tmp^.next := l;
                   l := tmp;
            ELSE
                   FOR i := 1 TO pos-2 DO
                   (* pos-2 porque yas estoy en la que es y quiero avanzar una más *)
                           tt := tt^.next;

J. Jaime A.     Pepe'00                       PM 22
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 (la
posición está fuera de lista, etc...).

- Control de errores:
$ Mensaje y parada: abortamos (HALT).
$ Variable quot;errorquot; 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;

       BEGIN
            IF pos = 1 THEN
                    tmp := l;
                    l := l^.next;
                    DISPOSE(tmp);
            ELSE
                    tmp := l;
                    FOR i := 1 TO pos - 2 DO
                             tmp := tmp^.next;
                    END;
                    tt := tmp^.next;
                    tmp^.next := tt^.next;
                    DIPSOSE(tt);
            END;
       END BorraLista;


J. Jaime A.    Pepe'00                      PM 23
PROCEDURE LongitudLista(l: LISTA): CARDINAL;

       VAR tmp: LISTA;

       BEGIN
            tmp :=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;

       BEGIN
            tmp := 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;

       BEGIN
            FOR i := 1 TO Longitud(l) DO
                    BorraLista(l, 1);
            END;
       END DestruirLista;

otra forma de destruir la lista:
        BEGIN
               IF l# NIL THEN
                       DestruirLista(l^.sig);
                       DISPOSE(l);
               END;
        END; (* la recursión no es buena para esto *)




J. Jaime A.   Pepe'00                      PM 24
* Implementación con cabecera:
La cabecera es un bloque fijo de información que está siempre aunque no haya elementos en la
lista 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);

       BEGIN
            NEW(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;

BEGIN
     NEW(tmp); tmp^.info := x;
     IF pos = 1 THEN
            tmp^.sigui := l^.lista;
            l^.lista := tmp;
            INC(l^.longitud);
     ELSE
            tt := l^.lista;
            FOR i := 2 TO pos - 1 DO
                     tt := tt^.sigui;
            END;
            tmp^.sigui := tt^.sigui;
            tt^.sigui := tmp;
            INC(l^.longitud);

J. Jaime A.   Pepe'00                     PM 25
END Inserta;

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

       BEGIN
            RETURN l^.longitud;
       END LongitudLista;

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

       VAR i: CARDINAL;

       BEGIN
            FOR i := 1 TO Longitud(l) DO
                   IF LeeLista(l) = x THEN
                          RETURN i;
                   END;
            END;
            RETURN 0;
       END Buscar;

Todos los algoritmos desarrollados son poco eficientes, sobre todo en el acceso a elementos, para
mejorarlos usamos la cabecera. En la cabecera añado un campo de quot;última visitaquot; que se refiere
al 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 última
posición visitada.
El procedimiento Crear me pondría la última visita y la posición de la última visita a NIL y 0
respectivamente.

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

VAR tmp: LISTA;
    i: CARDINAL;

BEGIN
     tmp := l^.lista;
     FOR i := 2 TO pos DO
             tmp := tmp^.sigui;
     END;
     l^.ultimavisita := tmp;
     l^.nultimavisita := pos;
     RETURN tmp^.info;
END LeeLista




J. Jaime A.    Pepe'00                       PM 26
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 el
nodo 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;

BEGIN
     IF l^.posuv <= pos THEN
             tmp := l^.puntuv;
             n := pos-l^.posuv;
     ELSE
             tmp := 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 la
implementación.

- Cursores:
Son arrays con dos campos para cada elemento, uno con la información y otro con el elemento
siguiente.
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 quot;siguientequot; del anterior la posición en el
array 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

J. Jaime A.     Pepe'00                        PM 27
* 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....

BEGIN
     NEW(nuevo);
     nuevo^.info := x;
     tmp := l^.lista;
     IF l^.tamanyo = 0 THEN
             nuevo^.ante := nuevo;
             nuevo^.sig := nuevo;
             l^.lista := nuevo;

       ELSIF i = 1 THEN
             l^.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;
       ELSE
             FOR 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;

J. Jaime A.    Pepe'00                     PM 28
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ía
el 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 THEN
                   IF i-1 > nvisit THEN
                            jlink := pvisit;
                            dir := BACK;
                            steps := nvisit - i;
                   ELSE
                            jlink := firsst;
                            dir := FORW;
                            steps := i-1;
                   END;

                ELSE
                       IF i - nvisit < size-1 THEN
                                jlink := pvisit;
                                dir := FORW;
                                steps := i-nvisit;
                       ELSE
                                jlink := 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;

J. Jaime A.     Pepe'00                       PM 29
| 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 ........

BEGIN
     tmp := 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;

BEGIN
     ALLOCATE(l, SIZE(BLOCK));
     IF l = NIL THEN
             ListErr := MEMOVER;
             RETURN;
     END;

J. Jaime A.    Pepe'00                      PM 30
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;

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

BEGIN
      new := newEle(l);
      WITH l^ DO
             cells[new].item := x;
             IF i = 1 THEN
                     cells[new].next := firstCell;
                     firstCell := new;
             ELSE
                     prev := cursorIavo(l, i-1);
                     cells[new].next := cells[prev].next;
                     cells[prev].next := new;
             END;
             INC(size);
      END;
END Insert;




J. Jaime A.     Pepe'00                    PM 31
PROCEDURE Destruye(VAR l: LISTA);

BEGIN
     DISPOSE(l);
END Destruye;

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

VAR pos: CURSOR;

BEGIN
     WITH l^ DO
            pos := firstEmpty;
            firstEmpty := cells[pos].next;
     END;
     RETURN pos;
END newEle;

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

BEGIN
     l^.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 los
procedimientos:
       <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 no
funciones, 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);

J. Jaime A.    Pepe'00                      PM 32
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... se
harí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);

BEGIN
     CrearLista(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);

BEGIN
     NEW(p);
     p := NIL;
END CrearPila;




J. Jaime A.    Pepe'00                       PM 33
PROCEDURE DestruirPila(VAR p: PILA);

VAR tmp, t2: PILA;

BEGIN
     WHILE 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;

BEGIN
      NEW(nuevo);
      nuevo^.info := x;
      nuevo^.sig := p;
      p := nuevo;
END Apilar;

PROCEDURE Desapilar(VAR p: PILA): BASE;

VAR tmp: PILA;
    tinfo: BASE;

BEGIN
     tmp := p;
     p := p^.sig;
     tinfo := tmp^.info;
     DISPOSE(tmp);
     RETURN tinfo;
END Desapilar;

PROCEDURE Cima(p: PILA): BASE;

BEGIN
     RETURN p^.info;
END Cima;

PROCEDURE EsPilaVacia(p: PILA): BOOLEAN;

BEGIN
     RETURN p = NIL;
END EsPilaVacia;

J. Jaime A.   Pepe'00                 PM 34
$ Acotada:
TYPE PILA = POINTER TO CABECERA;
      CABECERA = RECORD
                    primero: CARDINAL;
                    lista: ARRAY[1..MAXTAM] OF BASE;
                  END;

PROCEDURE CreaPila(VAR p: PILA);

BEGIN
     NEW(p);
     p^.primero := 0;
END CreaPila;

PROCEDURE DestruirPila(VAR p: PILA);

BEGIN
     DISPOSE(p);
END DestruirPila;

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

BEGIN
     p^.primero := primero + 1;
     p^.lista[primero] := x;
END Apilar;

PROCEDURE Desapilar(VAR p: PILA): BASE;

BEGIN
     p^.primero := primero - 1;
     RETURN p^.lista[p^.primero+1];
END Desapilar;

PROCEDURE Cima(p: PILA): BASE;

BEGIN
     RETURN p^.lista[p^.primero];
END Cima;

PROCEDURE EsPilaVacia(p: PILA): BOOLEAN;

BEGIN
     RETURN p^.primero = 0;
END EsPilaVacia;




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

* Ejemplo de uso de pilas: notación polaca, análisis de expresiones aritméticas dentro del
parsing 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 +                     (8 + 2) * 3 + 5 * 6 => 8 2 + 3 * 5 6 * +
    3 * 2 + 4 * (3 + 5) => 3 2 * 4 3 5 + * +                     8 - 2 => 8 2 -
             1 + 2 + 3 => 1 2 3 + +                                 -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 = RECORD
            CASE 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;

BEGIN
     CrearPila(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


J. Jaime A.    Pepe'00                      PM 36
|'+': 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 introducir
los 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;

BEGIN
     IF 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;

BEGIN
     CreaPila(aux);
     WHILE NOT(EsPilaVacia(orig)) DO

J. Jaime A.    Pepe'00                      PM 37
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;

BEGIN
     CreaPila(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 (o
                                            derecho) al central, desde el central puedo pasar
                                            un elemento al derecho o quedármelo, hasta que
                                            aparezca el que busco y volver el resto por donde
                                            entró. Son realmente dos pilas: el raíl central y el
                                            raíl izquierdo.
                                            No todas las combinaciones son posibles (para
                                                                                           más
                                                                                           de
                                                                                           4
                                                                                           ele
                                                                                           me
                                                                                           nto
                                                                                           s).


J. Jaime A.    Pepe'00                  PM 38
* 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: CARDINAL
                     colas: ARRAY[1..MAXTAM] OF BASE;
                   END;

PROCEDURE CreaCola(VAR q: COLA);

BEGIN
     NEW(q);
     q^.primero := 1;
     q^.ultimo := 0;
     q^.longitud := 0;
END CreaCola;
PROCEDURE DestruirCola(VAR q: COLA);

BEGIN
     DISPOSE(q);
END DestruirCola;

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

BEGIN
     q^.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;


J. Jaime A.   Pepe'00               PM 39
BEGIN
     x := q^.colas[q^.primero];
     q^.primero := (q^.primero MOD MAXTAM) + 1;
     DEC(q^.longitud);
     RETURN x;
END SacarCola;

PROCEDURE PrimeroCola(q: COLA): BASE;

BEGIN
     RETURN q^.colas[q^.primero];
END PrimeroCola;

PROCEDURE UltimoCola(q: COLA): BASE;

BEGIN
     RETURN 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);

BEGIN
     q := NIL;
END CreaCola;

PROCEDURE DestruirCola(VAR q: COLA);

VAR tmp, t2: COLA;

BEGIN
     tmp := q;
     WHILE tmp^.sig # NIL DO
             t2 := tmp;
             tmp := tmp^.sig;
             DISPOSE(t2);
     END;
END DestruirCola;

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


J. Jaime A.   Pepe'00               PM 40
VAR nuevo: COLA;
BEGIN
     NEW(nuevo);
     nuevo^.info := x;
     IF q = NIL THEN
             q := nuevo;
             q^.sig := q;
     ELSE
             nuevo^.sig := q^.sig;
             q^.sig := nuevo;
             q := nuevo;
     END;
END EnColar;

PROCEDURE SacarCola(VAR q: COLA): BASE;

VAR x: BASE;
    tmp: COLA;

BEGIN
     tmp := 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;

BEGIN
     RETURN q = NIL;
END EsColaVacia;

PROCEDURE PrimeroCola(q: COLA): BASE;

BEGIN
     RETURN q^.sig^.info;
END PrimeroCola;

PROCEDURE UltimoCola(q: COLA): BASE;

BEGIN
     RETURN q^.info;

J. Jaime A.   Pepe'00                PM 41
END UltimoCola;
                         q^.sig^.info es el primer elemento de la cola
                         q^.info es el último elemento de la cola.

- Otros tipos de cola:
        $ Cola doble: permite insertar y extraer por los dos extremos, es interesante hacerla con
        doble 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 saltan
       directamente al principio (u otra posición avanzada, según la prioridad). Cambia el
       procedimiento:
                      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 directamente
       EnColar 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;

BEGIN
     NEW(tmp1);
     tmp1^.info := x;
     tmp1^.prioridad := pri;
     IF q = NIL THEN
            q := tmp1;
            tmp1^.sig := tmp1;
     ELSIF pri < q^.prioridad THEN
            tmp2 := q;
            WHILE tmp1^.prioridad =< tmp2^.sigui^.prioridad DO
                    tmp2 := tmp2^.sigui;
            END;
            tmp1^.sigui := tmp2^.sigui;

J. Jaime A.    Pepe'00                      PM 42
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 cada
nodo, 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 quot;padrequot; 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
      árbol
      Nodos imaginarios (especiales): nodo que se añade en un árbol n-ario para cerrar todas
      las 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.


J. Jaime A.    Pepe'00                       PM 43
HacerBVacio     •> T
EsBVacio      T •> B
Raíz          T •> I c E
Izda          T •> T c E
Drcha         T •> T c E
HacerBArbol   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úmero
de 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;

BEGIN
      RETURN NIL;
HacerBVacio;

PROCEDURE EsBVacio(b: BARBOL): BOOLEAN;

BEGIN
     RETURN b = NIL;
END EsBVacio;




J. Jaime A.   Pepe'00                   PM 44
PROCEDURE Raiz(b: BARBOL): BASE;

BEGIN
     RETURN b^.info;
END Raíz;

PROCEDURE Izda(b: BARBOL): BARBOL;

BEGIN
      RETURN b^.left;
END Izda;

PROCEDURE Drcha(b: BARBOL): BARBOL;

BEGIN
     RETURN b^.right;
END Drcha;


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

VAR tmp: BARBOL;

BEGIN
     NEW(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 gestionando
la basura (memoria dealocada).

Ejercicio: construir el árbol:
                        3

                8                6

                        2            1

BEGIN
     b     :=    HacerBArbol(HacerBArbol(HacerBVacio),      8,     HacerBVacio),
     HacerBArbol(HacerBArbol(HacerBArbol),        2,  HacerBVacio(      ),    6,
     HacerBArbol(HacerBVacio( ), 1, HacerBVacio( ))).
END;

J. Jaime A.     Pepe'00                  PM 45
Se simplifica si construimos un procedimiento para crear hojas.
- Aplicaciones con árboles:
PROCEDURE Altura(b: BARBOL): CARDINAL;

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

       BEGIN
            IF a >= b THEN
                   RETURN a;
            ELSE
                   RETURN b:
            END;
       END Mayor;

BEGIN
     IF 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;

BEGIN
     RETURN (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:
                              *
                      +               2
               3              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);

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

J. Jaime A.    Pepe'00                      PM 46
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);

       BEGIN
            IF 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 que
coincida con el tipo predefinido
Ejercicio: 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. Los
recorridos vienen como listas.


J. Jaime A.    Pepe'00                       PM 47
- Á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 nodos
izquierdos 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 de
búsqueda teniendo en cuenta las propiedades que tiene este tipo de árbol.

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

BEGIN
     IF 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;

BEGIN
     IF 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;

BEGIN
     IF 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));




J. Jaime A.    Pepe'00                      PM 48
ELSE
              RETURN       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;

       BEGIN
            IF EsBVacio(b) THEN
                  RETURN FALSE;
            ELSIF nv = 1 THEN
                  RETURN TRUE;


              ELSE
                     RETURN (EsNivelLleno(Izda(b), nv-1)) AND (EsNivelLleno(Drcha(b),
                     nv-1));
            END;
       END EsNivelLleno;

VAR mayor: BASE;

BEGIN
     IF 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);

BEGIN
     IF EsBVacio(b) THEN RETURN END;
     IF n > 1 THEN
             EscNv(Izda(b), n-1, f);
             EscNv(Drcha(b), n-1, f);
     ELSE
             f(Raiz(b));
     END;
END EscNv;




J. Jaime A.   Pepe'00                  PM 49
PROCEDURE CopiarBArbol(b: BARBOL): BARBOL;

BEGIN
     IF 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);

       BEGIN
            linea[pos] := WrBASE(x);
       END EscribeRaiz;

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

       BEGIN
            IF 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;

BEGIN
     FOR i := 0 TO Altura(b) DO
            FOR j := 0 TO MAX DO
                   linea[i] := quot; quot;;
            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 para
obtenerla.
       CrearBBArbol( ): BBARBOL;

J. Jaime A.   Pepe'00                   PM 50
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;

BEGIN
      IF 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;

BEGIN
     IF 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;

BEGIN
     IF 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;


J. Jaime A.   Pepe'00                 PM 51
PROCEDURE BorrarBBAbol(b: BARBOL; x: BASE): BBARBOL;
     PROCEDURE MenorBBArbol(b: BBARBOL): BASE;

       BEGIN
            IF NOT (EsBVacio(Izda(b)) THEN
                  RETURN MenorBBArbol(Izda(b));
            END;
            RETURN Raiz(b);
       END MenorBBArbol;

VAR tmp: BASE;

BEGIN
     IF 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));

       ELSE
              IF 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;

BEGIN
     RETURN CrearBArbol( );
END CrearBBArbol;

PROCEDURE Izda(b: BBARBOL): BBARBOL;

BEGIN
      RETURN ARBOL.Izda(b);
END Izda;

PROCEDURE Drcha(b: BBARBOL): BBARBOL;


J. Jaime A.   Pepe'00                  PM 52
BEGIN
     RETURN ARBOL.Drcha(b);
END Drcha;

PROCEDURE Raiz(b: BBARBOL): BBARBOL;

BEGIN
     RETURN ARBOL.Raiz(b);
END Raiz;

Ejercicio: hacer un procedimiento que devuelva el sucesor de un nodo en un árbol binario de
búsqueda:
        Sucesor(b: BBARBOL; x: BASE): BASE;

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

BEGIN
     IF 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;




J. Jaime A.   Pepe'00                     PM 53
TEMA 5.- FICHEROS.
Son dispositivos de almacenamiento externo controlados por el sistema operativo mediante
rutinas primitivas en los cuales puedo hacer lecturas y escrituras de bytes mediante una serie de
elementos del S.O.: buffer, cursor y handle (manejador). El handle accede al buffer y al cursor
internamente.
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 un
fichero indicando donde se hará la lectura o escritura siguiente de ese fichero, a menos que yo
cambie 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 transacciones
de un número concreto de bytes sin interpretación del contenido de estos bytes durante la lectura
o 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 de
acceso 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 acceso
directo.
Para el indexado uso otro fichero además del que ya tengo con datos, este nuevo fichero contiene
una 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 que
IO 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 de
errores.




J. Jaime A.    Pepe'00                      PM 54
Ejemplo:
PROCEDURE Menu( ): CARDINAL;

BEGIN
     IO.WrStr(quot;1.- Abrir/Crear para añadir/meter datosquot;); IO.WrLn( );
     IO.WrStr(quot;2.- Visualizar datosquot;); IO.WrLn( );
     IO.WrStr(quot;3.- Salirquot;); IO.WrLn( );
     RETURN ORD(IO.RdKey( ) - ORD(quot;0quot;));
END Menu;

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

BEGIN (* principal *)
     LOOP
             CASE Menu OF
                 |1: IO.WrStr(quot;)Nombre del fichero a abrir/crear?quot;);
                      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;




J. Jaime A.   Pepe'00                     PM 55
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 un
fichero.
Después de usar el EOF habrá que ponerlo a FALSE, ya que no lo hace automáticamente:
        LOOP
               c := 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 •> command
line interpreter), en la librería Lib hay dos funciones para esto:
        ParamCount( ) •> cuenta el número de argumentos, el nombre del programa se cuenta
        como 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.




J. Jaime A.    Pepe'00                      PM 56
La búsqueda en ficheros binarios sería:
       f := Open(nomfich);
       LOOP
              IF RdBin(f, datos, SIZE(datos)) < SIZE(datos) THEN
                     EXIT; (* 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;

BEGIN
     FOR 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;

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


J. Jaime A.    Pepe'00                      PM 57
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 última
funció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); pos
va 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ándolo
en 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;

BEGIN
     sc := 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;




J. Jaime A.    Pepe'00                      PM 58
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 puede
depender de otros factores: el ordenador, el sistema operativo, etc...

Para poder medir la complejidad temporal no vamos a medir en segundos. Se trata de analizar
el 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 pueda
descomponer en otras operaciones menores, se toma como operación unidad. Se puede tomar
como 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 cuanta
informació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 muchas
aproximaciones.
Me interesa la función cuando x 6 4, comportamiento asintótico.
       f(x) = O(g(x))
           x64

       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.


J. Jaime A.      Pepe'00                      PM 59
* 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 ........

BEGIN
     FOR 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 cierto
puesto que el bucle FOR tiene una primera y una última vez distinta. La primera tiene 2 pasos
más (asignación, además de la iteración), cada iteración tiene 2 pasos y la última tiene 2 pasos
más (intenta incrementar y sale, además de la iteración).

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

El caso peor (pasando por todos los bucles) sería:
                          n
                     2 + ∑ 7 + (4 + 3)(i − 1) + 1 =
                                                      7 2 9
                                                        n + n−7
                         i =2                         2    2

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 que
el peor caso:
                                            n
                         7   7 13 7            7 13 7  33
                     10 + i − = + i = 2 + ∑ i + = n 2 + n − 8
                         2   2 2 2        i =2 2  2 4   2
J. Jaime A.    Pepe'00                            PM 60
* 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)) =
              k −1
       =   ∑         3i + 3 k T (n − k )
              i =0




       T(O) = 2 <• fin de la ecuación.
                                                 n −1
       en general:              T (n ) = 3 n ⋅ 2 + ∑ 3 n ⋅ 2
                                                 i =0

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

- Comportamientos recurrentes típicos:
$ En cada llamada elimino un ítem:
                                                n +1
                     T (n ) = n + T (n − 1) =        ⋅n
                                                  2
                     T (0) = 0
                             n +1
                     T (n ) =       ⋅n
                               2
                     O(T (n )) = n 2

$ Divido la entrada en dos partes y trato solo una de ellas:
                                                                                n 
           T (n ) = 1 + T (n 2) = 1 + 1 + T (n 4) = 1 + 1 + 1 + T (n 8) = k + T  ⋅ n 
                                                                                2 
           T (1) = 1
           T (n ) = 1 + log 2 n
$ Divido la entrada en dos partes y examino los n elementos al dividir:
       T(n) = n + T(n/2) = 2n




J. Jaime A.          Pepe'00                                   PM 61
$ Hay que hacer una pasada lineal antes, durante o después de dividir en dos partes:
                  T (n ) = n + 2T (n 2 ) tomando n = 2 k :
                  T (2 k )          T (2 k −1 )    T (2 k − 2 ) T (1)
                      k
                           = 1 + 2 ⋅ k −1 = 1 + 1 + k − 2 = k +
                    2                 2              2           1
                  T (1) = 1
                  T (2 k ) = k + 1 ⇒ T (n ) = n ⋅ (log 2 n ) + n


$ 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: ù;

BEGIN
      m := (l + r) DIV 2;                        3
      IF h > 0 THEN                              1
              pintaraya(m, h);                   1
              regla(l, m, h-a);                  1 + T(d/2)
              regla(m, r, a-1);                  1 + T(d/2)
      END;
END regla;

       Mejor caso:
       h#0
       4 op.
       T(d) = 7 + 2T(d/2) = 7 + 2(7 + 2T(d/22) = 7 + 2 $ 7 + 2 $ 22 $ 7 + ... + 2k $ T(d/2k) =
                               (        ) (      )
           k −1
       = 7 ∑ 2i + 2 k T d 2 k = 7 2 n +1 − 1
           i =0

              u

          ∑2
          i =0
                     i
                         = 2 n +1 − 1



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

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



J. Jaime A.          Pepe'00                             PM 62
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)) =
                                            k −1
                                                           n 
                                          = ∑ m i + m k  T  k   = {log m n = k }
                                                          m 
                                            i =0              
                                             n −1 
                                          =         + n(T1 )
                                             m − 1
                                                   

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

BEGIN
     WHILE 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 algoritmo
de 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: ù;

BEGIN
     FOR i := HIGH(a) TO 0 BY -1 DO
            FOR j := 1 TO i DO
                   IF 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 de
busca 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 totalmente
correcto.

J. Jaime A.    Pepe'00                     PM 63
La verificación es más difícil, lenta y con una pretensión exagerada (que sea matemáticamente
correcto), 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 (el
má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 un
algoritmo.
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 la
precondició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 valores
intermedios.

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




J. Jaime A.    Pepe'00                       PM 64
Introducción a la ingeniería del software
Introducción a la ingeniería del software
Introducción a la ingeniería del software
Introducción a la ingeniería del software
Introducción a la ingeniería del software
Introducción a la ingeniería del software
Introducción a la ingeniería del software
Introducción a la ingeniería del software
Introducción a la ingeniería del software
Introducción a la ingeniería del software

Contenu connexe

Tendances

Modelado clasico prototipo
Modelado clasico prototipoModelado clasico prototipo
Modelado clasico prototipoVictor Hugo
 
Modelos de software ventajas y desventajas
Modelos de software ventajas y desventajasModelos de software ventajas y desventajas
Modelos de software ventajas y desventajasEdith Carreño
 
Método cascada
Método cascadaMétodo cascada
Método cascadamariacebu
 
11. modelos según roger s
11.  modelos según roger s11.  modelos según roger s
11. modelos según roger sYvan Mayta
 
5 ciclos de vida del software(fixed)
5   ciclos de vida del software(fixed)5   ciclos de vida del software(fixed)
5 ciclos de vida del software(fixed)rockrlos
 
Modelos Prescriptivos del Desarrollo del Sistema de Información
Modelos Prescriptivos del Desarrollo del Sistema de InformaciónModelos Prescriptivos del Desarrollo del Sistema de Información
Modelos Prescriptivos del Desarrollo del Sistema de InformaciónIsaias Toledo
 
Prototipos
PrototiposPrototipos
PrototiposTensor
 
Modelos de Ciclo de Vida del Software [Ventajas y Desventajas]
Modelos de Ciclo de Vida del Software [Ventajas y Desventajas]Modelos de Ciclo de Vida del Software [Ventajas y Desventajas]
Modelos de Ciclo de Vida del Software [Ventajas y Desventajas]Cloud Rodriguez
 
Modelos en la ingeniería de software
Modelos en la ingeniería de softwareModelos en la ingeniería de software
Modelos en la ingeniería de softwareMarco Aurelio
 
Desarrollo de prototipos en Introduccion al analisis y diseño de sistemas
Desarrollo de prototipos en Introduccion al analisis y diseño de sistemasDesarrollo de prototipos en Introduccion al analisis y diseño de sistemas
Desarrollo de prototipos en Introduccion al analisis y diseño de sistemasCarlos Antonio Hernandez
 
Modelos de proceso evolutivo
Modelos de proceso evolutivoModelos de proceso evolutivo
Modelos de proceso evolutivoUriel Ramos
 
Cuadro comparativo modelos para el desarrollo de software
Cuadro comparativo modelos para el desarrollo de softwareCuadro comparativo modelos para el desarrollo de software
Cuadro comparativo modelos para el desarrollo de softwarepaoaboytes
 
Arquitectura software.taxonomias.construccion.002
Arquitectura software.taxonomias.construccion.002Arquitectura software.taxonomias.construccion.002
Arquitectura software.taxonomias.construccion.002Jose Emilio Labra Gayo
 
Cuadro comparativo de_modelos_de_procesos_de_software
Cuadro comparativo de_modelos_de_procesos_de_softwareCuadro comparativo de_modelos_de_procesos_de_software
Cuadro comparativo de_modelos_de_procesos_de_softwareShaman King
 
Arquitectura software.taxonomias.definiciones.001
Arquitectura software.taxonomias.definiciones.001Arquitectura software.taxonomias.definiciones.001
Arquitectura software.taxonomias.definiciones.001Jose Emilio Labra Gayo
 
Diapositivas De Ingenieria De Software
Diapositivas De Ingenieria De SoftwareDiapositivas De Ingenieria De Software
Diapositivas De Ingenieria De Softwarerapa69
 

Tendances (20)

Paradigmas
ParadigmasParadigmas
Paradigmas
 
Modelado clasico prototipo
Modelado clasico prototipoModelado clasico prototipo
Modelado clasico prototipo
 
Modelos de software ventajas y desventajas
Modelos de software ventajas y desventajasModelos de software ventajas y desventajas
Modelos de software ventajas y desventajas
 
Método cascada
Método cascadaMétodo cascada
Método cascada
 
11. modelos según roger s
11.  modelos según roger s11.  modelos según roger s
11. modelos según roger s
 
5 ciclos de vida del software(fixed)
5   ciclos de vida del software(fixed)5   ciclos de vida del software(fixed)
5 ciclos de vida del software(fixed)
 
Modelos Prescriptivos del Desarrollo del Sistema de Información
Modelos Prescriptivos del Desarrollo del Sistema de InformaciónModelos Prescriptivos del Desarrollo del Sistema de Información
Modelos Prescriptivos del Desarrollo del Sistema de Información
 
Prototipos
PrototiposPrototipos
Prototipos
 
2 modelos de la ingenieria de software
2  modelos de la ingenieria de software2  modelos de la ingenieria de software
2 modelos de la ingenieria de software
 
Modelos de Ciclo de Vida del Software [Ventajas y Desventajas]
Modelos de Ciclo de Vida del Software [Ventajas y Desventajas]Modelos de Ciclo de Vida del Software [Ventajas y Desventajas]
Modelos de Ciclo de Vida del Software [Ventajas y Desventajas]
 
Modelos en la ingeniería de software
Modelos en la ingeniería de softwareModelos en la ingeniería de software
Modelos en la ingeniería de software
 
Desarrollo de prototipos en Introduccion al analisis y diseño de sistemas
Desarrollo de prototipos en Introduccion al analisis y diseño de sistemasDesarrollo de prototipos en Introduccion al analisis y diseño de sistemas
Desarrollo de prototipos en Introduccion al analisis y diseño de sistemas
 
Modelos de proceso evolutivo
Modelos de proceso evolutivoModelos de proceso evolutivo
Modelos de proceso evolutivo
 
03 proceso de desarrollo de software
03 proceso de desarrollo de software03 proceso de desarrollo de software
03 proceso de desarrollo de software
 
Cuadro comparativo modelos para el desarrollo de software
Cuadro comparativo modelos para el desarrollo de softwareCuadro comparativo modelos para el desarrollo de software
Cuadro comparativo modelos para el desarrollo de software
 
Clase 01 agilidad
Clase 01 agilidadClase 01 agilidad
Clase 01 agilidad
 
Arquitectura software.taxonomias.construccion.002
Arquitectura software.taxonomias.construccion.002Arquitectura software.taxonomias.construccion.002
Arquitectura software.taxonomias.construccion.002
 
Cuadro comparativo de_modelos_de_procesos_de_software
Cuadro comparativo de_modelos_de_procesos_de_softwareCuadro comparativo de_modelos_de_procesos_de_software
Cuadro comparativo de_modelos_de_procesos_de_software
 
Arquitectura software.taxonomias.definiciones.001
Arquitectura software.taxonomias.definiciones.001Arquitectura software.taxonomias.definiciones.001
Arquitectura software.taxonomias.definiciones.001
 
Diapositivas De Ingenieria De Software
Diapositivas De Ingenieria De SoftwareDiapositivas De Ingenieria De Software
Diapositivas De Ingenieria De Software
 

En vedette

Convolucion Y Modulacion
Convolucion Y ModulacionConvolucion Y Modulacion
Convolucion Y ModulacionGrupo03senales
 
Prsentacion De Sistemas De Control
Prsentacion De Sistemas De ControlPrsentacion De Sistemas De Control
Prsentacion De Sistemas De ControlGrupo03senales
 
Presentación Convolucion Modulacion
Presentación Convolucion ModulacionPresentación Convolucion Modulacion
Presentación Convolucion ModulacionGrupo03senales
 
SeñAles Y Sistemas Presentacion
SeñAles Y Sistemas PresentacionSeñAles Y Sistemas Presentacion
SeñAles Y Sistemas PresentacionGrupo03senales
 
Grupo 3 Presentación Series Y Transformadas De Fourier Y Laplace
Grupo 3 Presentación Series Y Transformadas De  Fourier Y  LaplaceGrupo 3 Presentación Series Y Transformadas De  Fourier Y  Laplace
Grupo 3 Presentación Series Y Transformadas De Fourier Y LaplaceGrupo03senales
 
Aplicaciones de la transformada de fourier para deteccion de daños
Aplicaciones de la transformada de fourier para deteccion de dañosAplicaciones de la transformada de fourier para deteccion de daños
Aplicaciones de la transformada de fourier para deteccion de dañosJavier Gonzales
 
A L G U N O S C O M P I L A D O R E S
A L G U N O S  C O M P I L A D O R E SA L G U N O S  C O M P I L A D O R E S
A L G U N O S C O M P I L A D O R E SNahum Vera
 
FuncióN De Transferencia
FuncióN De TransferenciaFuncióN De Transferencia
FuncióN De TransferenciaGrupo03senales
 

En vedette (10)

Convolucion Y Modulacion
Convolucion Y ModulacionConvolucion Y Modulacion
Convolucion Y Modulacion
 
Tutorial De Matlab
Tutorial De MatlabTutorial De Matlab
Tutorial De Matlab
 
Ejercicios Maple
Ejercicios MapleEjercicios Maple
Ejercicios Maple
 
Prsentacion De Sistemas De Control
Prsentacion De Sistemas De ControlPrsentacion De Sistemas De Control
Prsentacion De Sistemas De Control
 
Presentación Convolucion Modulacion
Presentación Convolucion ModulacionPresentación Convolucion Modulacion
Presentación Convolucion Modulacion
 
SeñAles Y Sistemas Presentacion
SeñAles Y Sistemas PresentacionSeñAles Y Sistemas Presentacion
SeñAles Y Sistemas Presentacion
 
Grupo 3 Presentación Series Y Transformadas De Fourier Y Laplace
Grupo 3 Presentación Series Y Transformadas De  Fourier Y  LaplaceGrupo 3 Presentación Series Y Transformadas De  Fourier Y  Laplace
Grupo 3 Presentación Series Y Transformadas De Fourier Y Laplace
 
Aplicaciones de la transformada de fourier para deteccion de daños
Aplicaciones de la transformada de fourier para deteccion de dañosAplicaciones de la transformada de fourier para deteccion de daños
Aplicaciones de la transformada de fourier para deteccion de daños
 
A L G U N O S C O M P I L A D O R E S
A L G U N O S  C O M P I L A D O R E SA L G U N O S  C O M P I L A D O R E S
A L G U N O S C O M P I L A D O R E S
 
FuncióN De Transferencia
FuncióN De TransferenciaFuncióN De Transferencia
FuncióN De Transferencia
 

Similaire à Introducción a la ingeniería del software

MODELO DE DESARRROLLO DE SOFTWARE
MODELO DE DESARRROLLO DE SOFTWAREMODELO DE DESARRROLLO DE SOFTWARE
MODELO DE DESARRROLLO DE SOFTWAREJesus Yepez
 
Investigación de modelos
Investigación de modelos Investigación de modelos
Investigación de modelos bren1995
 
Modelo De Construccion De Prototipados
Modelo De Construccion De PrototipadosModelo De Construccion De Prototipados
Modelo De Construccion De Prototipadoslivia1988
 
Modelos de desarrollo de software
Modelos de desarrollo de softwareModelos de desarrollo de software
Modelos de desarrollo de softwareRadel Fuentes
 
1. ciclo de_vida_de_software
1. ciclo de_vida_de_software1. ciclo de_vida_de_software
1. ciclo de_vida_de_softwareMiguel Castro
 
Cuadro comparativo Modelos de Software.
Cuadro comparativo Modelos de Software.Cuadro comparativo Modelos de Software.
Cuadro comparativo Modelos de Software.templarioo
 
Metodología de desarrollo de software
Metodología de desarrollo de softwareMetodología de desarrollo de software
Metodología de desarrollo de softwareAbner Garcia
 
Modelos de Procesos de Software
Modelos de Procesos de SoftwareModelos de Procesos de Software
Modelos de Procesos de Softwaresebas montes
 
Comparativa Metodologias
Comparativa MetodologiasComparativa Metodologias
Comparativa MetodologiasAlipknot
 
Metodologias De Analisis Y Diseño De Sistemas
Metodologias De Analisis Y Diseño De SistemasMetodologias De Analisis Y Diseño De Sistemas
Metodologias De Analisis Y Diseño De Sistemasgrupo7inf162
 
Modelos de Ing de soft
Modelos de Ing de softModelos de Ing de soft
Modelos de Ing de softJazmin Cr
 
Modelo de cascadaa
Modelo de cascadaaModelo de cascadaa
Modelo de cascadaamendez45
 
Métodos de la ingeniería
Métodos de la ingenieríaMétodos de la ingeniería
Métodos de la ingenieríaSam Stgo
 
Unidad 3 los modelos de procesos de software
Unidad 3 los modelos de procesos de softwareUnidad 3 los modelos de procesos de software
Unidad 3 los modelos de procesos de softwareAndhy H Palma
 

Similaire à Introducción a la ingeniería del software (20)

Apuntes
ApuntesApuntes
Apuntes
 
MODELO DE DESARRROLLO DE SOFTWARE
MODELO DE DESARRROLLO DE SOFTWAREMODELO DE DESARRROLLO DE SOFTWARE
MODELO DE DESARRROLLO DE SOFTWARE
 
Investigación de modelos
Investigación de modelos Investigación de modelos
Investigación de modelos
 
Modelo De Construccion De Prototipados
Modelo De Construccion De PrototipadosModelo De Construccion De Prototipados
Modelo De Construccion De Prototipados
 
Modelos del software
Modelos del softwareModelos del software
Modelos del software
 
Modelos de desarrollo de software
Modelos de desarrollo de softwareModelos de desarrollo de software
Modelos de desarrollo de software
 
Presentacion grupo8
Presentacion grupo8Presentacion grupo8
Presentacion grupo8
 
1. ciclo de_vida_de_software
1. ciclo de_vida_de_software1. ciclo de_vida_de_software
1. ciclo de_vida_de_software
 
Cuadro comparativo Modelos de Software.
Cuadro comparativo Modelos de Software.Cuadro comparativo Modelos de Software.
Cuadro comparativo Modelos de Software.
 
Metodología de desarrollo de software
Metodología de desarrollo de softwareMetodología de desarrollo de software
Metodología de desarrollo de software
 
Modelos de Procesos de Software
Modelos de Procesos de SoftwareModelos de Procesos de Software
Modelos de Procesos de Software
 
Trabajo de desarrollo desoftware
Trabajo de desarrollo desoftwareTrabajo de desarrollo desoftware
Trabajo de desarrollo desoftware
 
Doc grupo2-webquest
Doc grupo2-webquestDoc grupo2-webquest
Doc grupo2-webquest
 
Comparativa Metodologias
Comparativa MetodologiasComparativa Metodologias
Comparativa Metodologias
 
Metodologias De Analisis Y Diseño De Sistemas
Metodologias De Analisis Y Diseño De SistemasMetodologias De Analisis Y Diseño De Sistemas
Metodologias De Analisis Y Diseño De Sistemas
 
Modelos de Ing de soft
Modelos de Ing de softModelos de Ing de soft
Modelos de Ing de soft
 
Modelo de cascadaa
Modelo de cascadaaModelo de cascadaa
Modelo de cascadaa
 
Métodos de la ingeniería
Métodos de la ingenieríaMétodos de la ingeniería
Métodos de la ingeniería
 
Ing
IngIng
Ing
 
Unidad 3 los modelos de procesos de software
Unidad 3 los modelos de procesos de softwareUnidad 3 los modelos de procesos de software
Unidad 3 los modelos de procesos de software
 

Introducción a la ingeniería del software

  • 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 J. Jaime A. Pepe'00 PM 1
  • 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 y se van a quitar o añadir cosas al producto (lo que el cliente pide y lo que se le da no tiene nada que ver). El mantenimiento consiste en volver a empezar todo el ciclo. Antiguamente no había mantenimiento, 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 un proceso más transparente al cliente. El analista distorsiona mucho el proyecto, lo ideal sería que el 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 de servicios de corto plazo en los que le voy a dar al cliente productos intermedios. A partir de esos resultados 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ástrofes localizadas. * Técnicas de 40 generación: Se utilizan métodos informáticos para el desarrollo. Modifican un poco el ciclo de vida en cuanto a que la generación del código puede ser automatizable, a partir de las especificaciones del analista que será el propio usuario en este caso; el usuario sería capaz de especificar gráficamente qué es lo que quiere, y el sistema especificaría el diseño y generaría el código final compilable y 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 un programador lo revise. Se usan entornos gráficos en la programación, esta técnica es uno de los grandes retos de la ingeniería del software. J. Jaime A. Pepe'00 PM 2
  • 3. * Construcción de prototipos: Los prototipos tratan de caricaturizar lo que hace el sistema, sin saber más de lo que pasaría (no sabes como va a actuar el sistema). Este método lo que busca es acelerar la construcción del programa final aunque sea una cáscara hueca en su aspecto, sin ninguna funcionalidad, el cliente solo vería el interfaz del usuario. Se puede diferenciar: recogida de requisitos evolución del diseño prototipo rápido construcción del prototipo producto final 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ística fundamental: hace un análisis de riesgo constantemente y una vuelta al inicio del ciclo de una forma periódica. planificación análisis evolución prototipos desarrollo J. Jaime A. Pepe'00 PM 3
  • 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 conceptual que cada uno de los elementos de módulo va a tener. Buscar cohesión es buscar módulos fáciles de describir simplificando suocalización dentro de un proyecto, la cohesión también va a permitir la compatibilidad. Un módulo es una agrupación de objetos del sistema, por lo que sería coherente si todo el módulo va definido a una misma utilidad. Coherente significa que todo lo que escribo en un paquete tiene 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. Existen tambié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 son elementos tan sueltos que no dependan de nada (ideal un poco absurdo), normalmente se busca el 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 un sistema, cada uno de los componentes tiene funciones independientes si cada uno trata de casos distintos. Se utiliza también técnicas de modulación y abstracción. Modular consiste en reunir elementos coherentes. Abstraer es aislar elementos del resto (no hacer muy acoplado). Si un módulo consigo hacerlo abstracto (elimino lo innecesario) se consigue menor acoplamiento. 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 opaca y 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 rasgos de la orientación a objetos permiten diseñar el sistema enfocado no a módulo, sino directamente a conjuntos de las características del sistema (objetos), cada objeto presenta un interfaz y una serie de rutinas y mediante esto intenta describir cada una de las partes del sistema (un objeto no es 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. J. Jaime A. Pepe'00 PM 4
  • 5. TEMA 2.- PUNTEROS. Los punteros no son un tipo de datos usual puesto que no se hace ninguna operación con ellos excepto la de referenciar la memoria del ordenador. Mediante un objeto de estos guardo la dirección de una zona de memoria (índice). Para asignar el valor al puntero usamos las instrucciones propias del lenguaje, igualmente para la 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ño prefijados durante la compilación y que puedan tener una estructura (interconexión) definida por nuestro 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ón del programa. Una variable de datos dinámica es una variable que puede cambiar de tamaño durante la ejecución del programa (sin tamaño fijo). El puntero NO 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; BEGIN i := 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 denomina referenciar, pero me hace falta dirigirme a la dirección de memoria, esto se denomina derreferenciar. Para derreferenciar se usa el operador ^: p^ significa quot;lo apuntado por pquot; p^ := i + 3; Al final la i valdrá 4 porque la p está apuntando a i (30 línea del BEGIN). J. Jaime A. Pepe'00 PM 5
  • 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); BEGIN p^ := 33; END P; BEGIN p := 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; BEGIN p^ := 33; RETURN ADR(j); END P; VAR p, PP: POINTER; BEGIN p := 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. J. Jaime A. Pepe'00 PM 6
  • 7. Se podría escribir p^^ y el sistema interpretaría que la dirección a la que apunta p contiene la direcció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; BEGIN PP := 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 memoria estática para sus variables: hay una zona de RAM reservada para constantes, otra zona para variables globales y otra para las variables locales a los procedimientos. Esta última zona no es tan estática. Además de estas tres zonas el sistema tiene el Heap. Partición de la RAM: Variables variables procedimientos Heap globales constantes código (dinámicas) El sistema permite reservar y utilizar esa zona libre de RAM (Heap). Hay procedimientos que reservan 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 un parche). NEW devuelve un puntero al lugar reservado por él (11 reserva RAM, 21 indica cual es 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én es 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) que indica un lugar absurdo en la RAM (comúnmente la dirección 0), es un valor de puntero que indica que no tiene valor. Es una manera de inicializar el puntero. J. Jaime A. Pepe'00 PM 7
  • 8. Después de DISPOSE(p) es recomendable poner p := NIL, aunque no es imprescindible en todos los 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 declare alguna vez. Ejemplo: hacer un programa que le pida al usuario un número tras otro ilimitadamente hasta que se introduzca el 0: TYPE LISTA = POINTER TO NODO NODO = RECORD info: CARDINAL; next: LISTA; END; VAR n: CARDINAL; t, p: LISTA; BEGIN t := NIL; p := NIL; LOOP n := RdCard( ); IF n = 0 THEN EXIT END; J. Jaime A. Pepe'00 PM 8
  • 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 quot;de atrás a adelantequot; 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 una lista 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; BEGIN t := 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; J. Jaime A. Pepe'00 PM 9
  • 10. END; tt^.next := t; ELSE p := t; END; Ejemplo: borrar por posición: PROCEDURE BorraPos(VAR lista: PTRFICHA; pos: CARDINAL); VAR l, l2: PTRFICHA; n: CARDINAL; BEGIN l := lista; IF pos = 1 THEN l2 := lista; lista := lista^.sigui; ELSE n := 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; BEGIN l := lista; IF lista = NIL THEN RETURN END; IF Compare(l^.nombre, nombre) = -1 THEN l2 := lista; lista := lista^.sigui; ELSE l := lista; WHILE (l^.sigui # NIL) AND (Compare(l^.sigui^.nombre, nombre) <= 0) DO l := l^.sigui; J. Jaime A. Pepe'00 PM 10
  • 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; BEGIN IF 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; BEGIN l := 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: J. Jaime A. Pepe'00 PM 11
  • 12. PROCEDURE InsertOrd(VAR: lista: LISTA; nombre: STR32); VAR l, l2: LISTA; BEGIN NEW(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; ELSE l2 := 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; J. Jaime A. Pepe'00 PM 12
  • 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 algoritmo es 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 mayor complejidad. Todo algoritmo recursivo debe tener una forma de ser ejecutado en la que no haya recursión (la llamada 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; BEGIN IF (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; BEGIN IF 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; BEGIN n1 := 1; n2 := 1; WHILE i < n DO sol := n1 + n2; n1 := n2; n2 := sol; INC(i); END; RETURN sol; END Fibo; J. Jaime A. Pepe'00 PM 13
  • 14. Recursión indirecta: cuando la recursión no se produce sobre el primer algoritmo sino sobre una llamada a un segundo algoritmo que a su vez llama al primero. PROCEDURE F(x: REAL): REAL; BEGIN IF 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; BEGIN IF 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 se usa 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úmero factorial). Recursión doble: el programa se llama a si mismo dos veces (en dos puntos del código), como la suma de Fibonacci. J. Jaime A. Pepe'00 PM 14
  • 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 tratar prácticamente de la misma manera. Una recursión intermedia siempre se puede convertir a iteración aunque el resultado puede ser totalmente distinto y el método no tan simple, se hace usando dos pilas: una para llamadas y otra para 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 sistema una zona para las variables (parámetros locales incluidos) de esa función; además se guarda en esa pila la dirección de vuelta, de manera que al finalizar el procedimiento el sistema sea capaz de 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 quot;limpiaquot; la pila leyéndose la dirección de partida 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 la recursión es peligrosa en cuanto a que puede agotar el espacio en la pila (que no es mucho); si se pasa puede que escriba en zonas de memoria sin control, incluso que llegue a escribir la memoria 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 cuanto a pilas. Ejercicio: función de Ackermann: A(0, s) = 2s s $ 0 A(r, 0) = 0 r$0 A(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 a 4 o 5, poco más; este algoritmo queda como una forma de probar la capacidad recursiva y el control 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 tipos de ineficiencias: 11.- técnica: debido a la sobrecarga que le produce al sistema la operación de llamada a procedimientos. 20.- complejidad: debida a la cantidad de redundancias que se producen en la llamada recursiva a los procedimientos. J. Jaime A. Pepe'00 PM 15
  • 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 = 0 MCD(n, m) si n < m MCD(n, m MOD n) PROCEDURE MCD (m, n: CARDINAL): CARDINAL; BEGIN IF ( 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 procedimiento de 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; BEGIN IF i >= i1 THEN RETURN END; m := (i0 + i1) DIV 2; IF a[m] = x THEN pos := m; RETURN TRUE; ELSIF a[m] < x THEN RETURN bb(m + 1, i1); ELSE RETURN bb(i0, m - 1); END; END bb; BEGIN RETURN bb(0, HIGH(a)); END BB; J. Jaime A. Pepe'00 PM 16
  • 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; BEGIN IF l = NIL THEN RETURN END; IF l^.info = x THEN tmp := l; l := l^.next; DISPOSE(tmp); ELSE Delete(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 la condición de no poner nunca un disco grande encima de otro pequeño, uno a uno. PROCEDURE hanoi(n: CARDINAL; source, target, inter: CHAR); BEGIN IF n > 0 THEN hanoi(n - 1, source, inter, target); (* mover disco de origen a destino *) hanoi(n - 1, inter, target source); END; END Hanoi; J. Jaime A. Pepe'00 PM 17
  • 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 la cadena. Se repite con cada una de las dos zonas y así sucesivamente hasta que tengo un array de dos 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: aedcbf abdcef abcdef •> ordenado PROCEDURE QuickSort(VAR a: ARRAY OF CHAR); PROCEDURE swap(VAR a: ARRAY OF CHAR; i, j: CARDINAL); VAR t: CHAR; BEGIN t := 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; BEGIN v := a[de]; i := iz - 1; j := de; REPEAT J. Jaime A. Pepe'00 PM 18
  • 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; BEGIN IF de > iz THEN p := partition(a, iz, de); quick(a, iz, p-1); quick(a, p+1, de); END; END quick; BEGIN Quick(a, 0, Length(a)-1); END QuickSort; J. Jaime A. Pepe'00 PM 19
  • 20. TEMA 4.- TIPOS ABSTRACTOS DE DATOS (TAD's). Un TAD es alguna forma de almacenar información y una serie de procedimientos de accesos a 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 de construir cualquier instancia. - minimalismo, deben de ser los mínimos procedimientos para cumplir lo primero. La ventaja 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 las dependencias 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 procedimientos de acceso: TYPE LISTA PROCEDURE 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 el máximo de la colección numerando cada uno de los elementos). No hay un número fijo de elementos. 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 selectores muestran 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. J. Jaime A. Pepe'00 PM 20
  • 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 el programador). 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); BEGIN NEW(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); BEGIN NEW(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); BEGIN l : = NIL; END Crearlista; l^.info := 3; (* no se puede hacer al ser opaco el tipo de la lista *) J. Jaime A. Pepe'00 PM 21
  • 22. Array Acotada Cursores Implementació Modos simples enlzados. n Sin cabecera Modos dobles enlazados. de listas No acotada Modos simples enlzados. Con cabecera 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 el movimiento de la lista. Pueden utilizarse como bloques de memoria. Cada nodo tiene la información base y quot;nextquot; (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éndose al campo siguiente. Hay que mantener además de los enlaces entre nodos con infromación otra quot;cadenaquot; con los 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); BEGIN l := NIL; END Crearlista; PROCEDURE InsertarLista(VAR l: LISTA; pos: CARDINAL; x: BASE); VAR tmp, tt: LISTA; i: CARDINAL; BEGIN NEW (tmp); tmp^.inf := x; IF pos = 1 THEN tmp^.next := l; l := tmp; ELSE FOR i := 1 TO pos-2 DO (* pos-2 porque yas estoy en la que es y quiero avanzar una más *) tt := tt^.next; J. Jaime A. Pepe'00 PM 22
  • 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 (la posición está fuera de lista, etc...). - Control de errores: $ Mensaje y parada: abortamos (HALT). $ Variable quot;errorquot; 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; BEGIN IF pos = 1 THEN tmp := l; l := l^.next; DISPOSE(tmp); ELSE tmp := l; FOR i := 1 TO pos - 2 DO tmp := tmp^.next; END; tt := tmp^.next; tmp^.next := tt^.next; DIPSOSE(tt); END; END BorraLista; J. Jaime A. Pepe'00 PM 23
  • 24. PROCEDURE LongitudLista(l: LISTA): CARDINAL; VAR tmp: LISTA; BEGIN tmp :=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; BEGIN tmp := 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; BEGIN FOR i := 1 TO Longitud(l) DO BorraLista(l, 1); END; END DestruirLista; otra forma de destruir la lista: BEGIN IF l# NIL THEN DestruirLista(l^.sig); DISPOSE(l); END; END; (* la recursión no es buena para esto *) J. Jaime A. Pepe'00 PM 24
  • 25. * Implementación con cabecera: La cabecera es un bloque fijo de información que está siempre aunque no haya elementos en la lista 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); BEGIN NEW(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; BEGIN NEW(tmp); tmp^.info := x; IF pos = 1 THEN tmp^.sigui := l^.lista; l^.lista := tmp; INC(l^.longitud); ELSE tt := l^.lista; FOR i := 2 TO pos - 1 DO tt := tt^.sigui; END; tmp^.sigui := tt^.sigui; tt^.sigui := tmp; INC(l^.longitud); J. Jaime A. Pepe'00 PM 25
  • 26. END Inserta; La cabecera facilita enormemente el calcular la longitud de la lista: PROCEDURE LongitudLista(l: LISTA): CARDINAL; BEGIN RETURN l^.longitud; END LongitudLista; Un posible algoritmo de búsqueda podría ser: PROCEDURE Buscar(l: LISTA; x: BASE): CARDINAL; VAR i: CARDINAL; BEGIN FOR i := 1 TO Longitud(l) DO IF LeeLista(l) = x THEN RETURN i; END; END; RETURN 0; END Buscar; Todos los algoritmos desarrollados son poco eficientes, sobre todo en el acceso a elementos, para mejorarlos usamos la cabecera. En la cabecera añado un campo de quot;última visitaquot; que se refiere al 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 última posición visitada. El procedimiento Crear me pondría la última visita y la posición de la última visita a NIL y 0 respectivamente. PROCEDURE LeeLista(l: LISTA; pos: CARDINAL): BASE; VAR tmp: LISTA; i: CARDINAL; BEGIN tmp := l^.lista; FOR i := 2 TO pos DO tmp := tmp^.sigui; END; l^.ultimavisita := tmp; l^.nultimavisita := pos; RETURN tmp^.info; END LeeLista J. Jaime A. Pepe'00 PM 26
  • 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 el nodo 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; BEGIN IF l^.posuv <= pos THEN tmp := l^.puntuv; n := pos-l^.posuv; ELSE tmp := 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 la implementación. - Cursores: Son arrays con dos campos para cada elemento, uno con la información y otro con el elemento siguiente. 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 quot;siguientequot; del anterior la posición en el array 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 J. Jaime A. Pepe'00 PM 27
  • 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.... BEGIN NEW(nuevo); nuevo^.info := x; tmp := l^.lista; IF l^.tamanyo = 0 THEN nuevo^.ante := nuevo; nuevo^.sig := nuevo; l^.lista := nuevo; ELSIF i = 1 THEN l^.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; ELSE FOR 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; J. Jaime A. Pepe'00 PM 28
  • 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ía el 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 THEN IF i-1 > nvisit THEN jlink := pvisit; dir := BACK; steps := nvisit - i; ELSE jlink := firsst; dir := FORW; steps := i-1; END; ELSE IF i - nvisit < size-1 THEN jlink := pvisit; dir := FORW; steps := i-nvisit; ELSE jlink := 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; J. Jaime A. Pepe'00 PM 29
  • 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 ........ BEGIN tmp := 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; BEGIN ALLOCATE(l, SIZE(BLOCK)); IF l = NIL THEN ListErr := MEMOVER; RETURN; END; J. Jaime A. Pepe'00 PM 30
  • 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; BEGIN WITH 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 ......... BEGIN new := newEle(l); WITH l^ DO cells[new].item := x; IF i = 1 THEN cells[new].next := firstCell; firstCell := new; ELSE prev := cursorIavo(l, i-1); cells[new].next := cells[prev].next; cells[prev].next := new; END; INC(size); END; END Insert; J. Jaime A. Pepe'00 PM 31
  • 32. PROCEDURE Destruye(VAR l: LISTA); BEGIN DISPOSE(l); END Destruye; - Procedimientos internos a la implementación: PROCEDURE newEle(VAR l: LISTA): CURSOR; VAR pos: CURSOR; BEGIN WITH l^ DO pos := firstEmpty; firstEmpty := cells[pos].next; END; RETURN pos; END newEle; PROCEDURE DisposeEle(VAR l: LISTA; i: CARDINAL); BEGIN l^.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 los procedimientos: <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 no funciones, 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); J. Jaime A. Pepe'00 PM 32
  • 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... se harí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); BEGIN CrearLista(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); BEGIN NEW(p); p := NIL; END CrearPila; J. Jaime A. Pepe'00 PM 33
  • 34. PROCEDURE DestruirPila(VAR p: PILA); VAR tmp, t2: PILA; BEGIN WHILE 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; BEGIN NEW(nuevo); nuevo^.info := x; nuevo^.sig := p; p := nuevo; END Apilar; PROCEDURE Desapilar(VAR p: PILA): BASE; VAR tmp: PILA; tinfo: BASE; BEGIN tmp := p; p := p^.sig; tinfo := tmp^.info; DISPOSE(tmp); RETURN tinfo; END Desapilar; PROCEDURE Cima(p: PILA): BASE; BEGIN RETURN p^.info; END Cima; PROCEDURE EsPilaVacia(p: PILA): BOOLEAN; BEGIN RETURN p = NIL; END EsPilaVacia; J. Jaime A. Pepe'00 PM 34
  • 35. $ Acotada: TYPE PILA = POINTER TO CABECERA; CABECERA = RECORD primero: CARDINAL; lista: ARRAY[1..MAXTAM] OF BASE; END; PROCEDURE CreaPila(VAR p: PILA); BEGIN NEW(p); p^.primero := 0; END CreaPila; PROCEDURE DestruirPila(VAR p: PILA); BEGIN DISPOSE(p); END DestruirPila; PROCEDURE Apilar(VAR p: PILA; x: BASE); BEGIN p^.primero := primero + 1; p^.lista[primero] := x; END Apilar; PROCEDURE Desapilar(VAR p: PILA): BASE; BEGIN p^.primero := primero - 1; RETURN p^.lista[p^.primero+1]; END Desapilar; PROCEDURE Cima(p: PILA): BASE; BEGIN RETURN p^.lista[p^.primero]; END Cima; PROCEDURE EsPilaVacia(p: PILA): BOOLEAN; BEGIN RETURN p^.primero = 0; END EsPilaVacia; J. Jaime A. Pepe'00 PM 35
  • 36. * Control de errores(y 2): Para el control de errores en las funciones, para cortarlas con RETURN podemos hacer que devuelva una variable local del mismo tipo de lo que se devuelve con el RETURN, esta variable estaría declarada pero no inicializada (no igualada a nada). Estamos devolviendo basura en caso de error. Evidentemente, en caso de error, el driver no miraría la información devuelta y se obviaría esta basura. * Ejemplo de uso de pilas: notación polaca, análisis de expresiones aritméticas dentro del parsing 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 + (8 + 2) * 3 + 5 * 6 => 8 2 + 3 * 5 6 * + 3 * 2 + 4 * (3 + 5) => 3 2 * 4 3 5 + * + 8 - 2 => 8 2 - 1 + 2 + 3 => 1 2 3 + + -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 = RECORD CASE 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; BEGIN CrearPila(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 J. Jaime A. Pepe'00 PM 36
  • 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 introducir los 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; BEGIN IF 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; BEGIN CreaPila(aux); WHILE NOT(EsPilaVacia(orig)) DO J. Jaime A. Pepe'00 PM 37
  • 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; BEGIN CreaPila(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 (o derecho) al central, desde el central puedo pasar un elemento al derecho o quedármelo, hasta que aparezca el que busco y volver el resto por donde entró. Son realmente dos pilas: el raíl central y el raíl izquierdo. No todas las combinaciones son posibles (para más de 4 ele me nto s). J. Jaime A. Pepe'00 PM 38
  • 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: CARDINAL colas: ARRAY[1..MAXTAM] OF BASE; END; PROCEDURE CreaCola(VAR q: COLA); BEGIN NEW(q); q^.primero := 1; q^.ultimo := 0; q^.longitud := 0; END CreaCola; PROCEDURE DestruirCola(VAR q: COLA); BEGIN DISPOSE(q); END DestruirCola; PROCEDURE EnColar(VAR q: COLA; x: BASE); BEGIN q^.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; J. Jaime A. Pepe'00 PM 39
  • 40. BEGIN x := q^.colas[q^.primero]; q^.primero := (q^.primero MOD MAXTAM) + 1; DEC(q^.longitud); RETURN x; END SacarCola; PROCEDURE PrimeroCola(q: COLA): BASE; BEGIN RETURN q^.colas[q^.primero]; END PrimeroCola; PROCEDURE UltimoCola(q: COLA): BASE; BEGIN RETURN 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); BEGIN q := NIL; END CreaCola; PROCEDURE DestruirCola(VAR q: COLA); VAR tmp, t2: COLA; BEGIN tmp := q; WHILE tmp^.sig # NIL DO t2 := tmp; tmp := tmp^.sig; DISPOSE(t2); END; END DestruirCola; PROCEDURE EnColar(VAR q: COLA; x: BASE); J. Jaime A. Pepe'00 PM 40
  • 41. VAR nuevo: COLA; BEGIN NEW(nuevo); nuevo^.info := x; IF q = NIL THEN q := nuevo; q^.sig := q; ELSE nuevo^.sig := q^.sig; q^.sig := nuevo; q := nuevo; END; END EnColar; PROCEDURE SacarCola(VAR q: COLA): BASE; VAR x: BASE; tmp: COLA; BEGIN tmp := 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; BEGIN RETURN q = NIL; END EsColaVacia; PROCEDURE PrimeroCola(q: COLA): BASE; BEGIN RETURN q^.sig^.info; END PrimeroCola; PROCEDURE UltimoCola(q: COLA): BASE; BEGIN RETURN q^.info; J. Jaime A. Pepe'00 PM 41
  • 42. END UltimoCola; q^.sig^.info es el primer elemento de la cola q^.info es el último elemento de la cola. - Otros tipos de cola: $ Cola doble: permite insertar y extraer por los dos extremos, es interesante hacerla con doble 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 saltan directamente al principio (u otra posición avanzada, según la prioridad). Cambia el procedimiento: 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 directamente EnColar 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; BEGIN NEW(tmp1); tmp1^.info := x; tmp1^.prioridad := pri; IF q = NIL THEN q := tmp1; tmp1^.sig := tmp1; ELSIF pri < q^.prioridad THEN tmp2 := q; WHILE tmp1^.prioridad =< tmp2^.sigui^.prioridad DO tmp2 := tmp2^.sigui; END; tmp1^.sigui := tmp2^.sigui; J. Jaime A. Pepe'00 PM 42
  • 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 cada nodo, 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 quot;padrequot; 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 árbol Nodos imaginarios (especiales): nodo que se añade en un árbol n-ario para cerrar todas las 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. J. Jaime A. Pepe'00 PM 43
  • 44. HacerBVacio •> T EsBVacio T •> B Raíz T •> I c E Izda T •> T c E Drcha T •> T c E HacerBArbol 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úmero de 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; BEGIN RETURN NIL; HacerBVacio; PROCEDURE EsBVacio(b: BARBOL): BOOLEAN; BEGIN RETURN b = NIL; END EsBVacio; J. Jaime A. Pepe'00 PM 44
  • 45. PROCEDURE Raiz(b: BARBOL): BASE; BEGIN RETURN b^.info; END Raíz; PROCEDURE Izda(b: BARBOL): BARBOL; BEGIN RETURN b^.left; END Izda; PROCEDURE Drcha(b: BARBOL): BARBOL; BEGIN RETURN b^.right; END Drcha; PROCEDURE HacerBArbol(l: ARBOL; x: BASE; r: BARBOL): BARBOL; VAR tmp: BARBOL; BEGIN NEW(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 gestionando la basura (memoria dealocada). Ejercicio: construir el árbol: 3 8 6 2 1 BEGIN b := HacerBArbol(HacerBArbol(HacerBVacio), 8, HacerBVacio), HacerBArbol(HacerBArbol(HacerBArbol), 2, HacerBVacio( ), 6, HacerBArbol(HacerBVacio( ), 1, HacerBVacio( ))). END; J. Jaime A. Pepe'00 PM 45
  • 46. Se simplifica si construimos un procedimiento para crear hojas. - Aplicaciones con árboles: PROCEDURE Altura(b: BARBOL): CARDINAL; PROCEDURE Mayor(a, b: CARDINAL): CARDINAL; BEGIN IF a >= b THEN RETURN a; ELSE RETURN b: END; END Mayor; BEGIN IF 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; BEGIN RETURN (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: * + 2 3 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); BEGIN IF EsBVacio(b) THEN RETURN END; WrCard(Raiz(b), 0); J. Jaime A. Pepe'00 PM 46
  • 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); BEGIN IF 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 que coincida con el tipo predefinido Ejercicio: 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. Los recorridos vienen como listas. J. Jaime A. Pepe'00 PM 47
  • 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 nodos izquierdos 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 de búsqueda teniendo en cuenta las propiedades que tiene este tipo de árbol. $ Corrección de ejercicios: PROCEDURE BuscaBArbol(b: BARBOL; x: BASE): BOOLEAN; BEGIN IF 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; BEGIN IF 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; BEGIN IF 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)); J. Jaime A. Pepe'00 PM 48
  • 49. ELSE RETURN 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; BEGIN IF EsBVacio(b) THEN RETURN FALSE; ELSIF nv = 1 THEN RETURN TRUE; ELSE RETURN (EsNivelLleno(Izda(b), nv-1)) AND (EsNivelLleno(Drcha(b), nv-1)); END; END EsNivelLleno; VAR mayor: BASE; BEGIN IF 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); BEGIN IF EsBVacio(b) THEN RETURN END; IF n > 1 THEN EscNv(Izda(b), n-1, f); EscNv(Drcha(b), n-1, f); ELSE f(Raiz(b)); END; END EscNv; J. Jaime A. Pepe'00 PM 49
  • 50. PROCEDURE CopiarBArbol(b: BARBOL): BARBOL; BEGIN IF 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); BEGIN linea[pos] := WrBASE(x); END EscribeRaiz; PROCEDURE ImprimirNivelBA(b: BARBOL; nv, pos, w: CARDINAL); BEGIN IF 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; BEGIN FOR i := 0 TO Altura(b) DO FOR j := 0 TO MAX DO linea[i] := quot; quot;; 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 para obtenerla. CrearBBArbol( ): BBARBOL; J. Jaime A. Pepe'00 PM 50
  • 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; BEGIN IF 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; BEGIN IF 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; BEGIN IF 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; J. Jaime A. Pepe'00 PM 51
  • 52. PROCEDURE BorrarBBAbol(b: BARBOL; x: BASE): BBARBOL; PROCEDURE MenorBBArbol(b: BBARBOL): BASE; BEGIN IF NOT (EsBVacio(Izda(b)) THEN RETURN MenorBBArbol(Izda(b)); END; RETURN Raiz(b); END MenorBBArbol; VAR tmp: BASE; BEGIN IF 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)); ELSE IF 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; BEGIN RETURN CrearBArbol( ); END CrearBBArbol; PROCEDURE Izda(b: BBARBOL): BBARBOL; BEGIN RETURN ARBOL.Izda(b); END Izda; PROCEDURE Drcha(b: BBARBOL): BBARBOL; J. Jaime A. Pepe'00 PM 52
  • 53. BEGIN RETURN ARBOL.Drcha(b); END Drcha; PROCEDURE Raiz(b: BBARBOL): BBARBOL; BEGIN RETURN ARBOL.Raiz(b); END Raiz; Ejercicio: hacer un procedimiento que devuelva el sucesor de un nodo en un árbol binario de búsqueda: Sucesor(b: BBARBOL; x: BASE): BASE; PROCEDURE Sucesor(b: BBARBOL; x: BASE): BASE; BEGIN IF 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; J. Jaime A. Pepe'00 PM 53
  • 54. TEMA 5.- FICHEROS. Son dispositivos de almacenamiento externo controlados por el sistema operativo mediante rutinas primitivas en los cuales puedo hacer lecturas y escrituras de bytes mediante una serie de elementos del S.O.: buffer, cursor y handle (manejador). El handle accede al buffer y al cursor internamente. 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 un fichero indicando donde se hará la lectura o escritura siguiente de ese fichero, a menos que yo cambie 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 transacciones de un número concreto de bytes sin interpretación del contenido de estos bytes durante la lectura o 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 de acceso 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 acceso directo. Para el indexado uso otro fichero además del que ya tengo con datos, este nuevo fichero contiene una 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 que IO 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 de errores. J. Jaime A. Pepe'00 PM 54
  • 55. Ejemplo: PROCEDURE Menu( ): CARDINAL; BEGIN IO.WrStr(quot;1.- Abrir/Crear para añadir/meter datosquot;); IO.WrLn( ); IO.WrStr(quot;2.- Visualizar datosquot;); IO.WrLn( ); IO.WrStr(quot;3.- Salirquot;); IO.WrLn( ); RETURN ORD(IO.RdKey( ) - ORD(quot;0quot;)); END Menu; VAR f: FIO.FILE; (* el handle *) BEGIN (* principal *) LOOP CASE Menu OF |1: IO.WrStr(quot;)Nombre del fichero a abrir/crear?quot;); 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; J. Jaime A. Pepe'00 PM 55
  • 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 un fichero. Después de usar el EOF habrá que ponerlo a FALSE, ya que no lo hace automáticamente: LOOP c := 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 •> command line interpreter), en la librería Lib hay dos funciones para esto: ParamCount( ) •> cuenta el número de argumentos, el nombre del programa se cuenta como 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. J. Jaime A. Pepe'00 PM 56
  • 57. La búsqueda en ficheros binarios sería: f := Open(nomfich); LOOP IF RdBin(f, datos, SIZE(datos)) < SIZE(datos) THEN EXIT; (* 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; BEGIN FOR 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; BEGIN LOOP 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. J. Jaime A. Pepe'00 PM 57
  • 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 última funció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); pos va 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ándolo en 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; BEGIN sc := 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; J. Jaime A. Pepe'00 PM 58
  • 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 puede depender de otros factores: el ordenador, el sistema operativo, etc... Para poder medir la complejidad temporal no vamos a medir en segundos. Se trata de analizar el 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 pueda descomponer en otras operaciones menores, se toma como operación unidad. Se puede tomar como 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 cuanta informació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 muchas aproximaciones. Me interesa la función cuando x 6 4, comportamiento asintótico. f(x) = O(g(x)) x64 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. J. Jaime A. Pepe'00 PM 59
  • 60. * 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 ........ BEGIN FOR 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 cierto puesto que el bucle FOR tiene una primera y una última vez distinta. La primera tiene 2 pasos más (asignación, además de la iteración), cada iteración tiene 2 pasos y la última tiene 2 pasos más (intenta incrementar y sale, además de la iteración). Finalmente resulta: (2+8)(n-1) +2 O(temp(n)) = n El caso peor (pasando por todos los bucles) sería: n 2 + ∑ 7 + (4 + 3)(i − 1) + 1 = 7 2 9 n + n−7 i =2 2 2 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 que el peor caso: n 7 7 13 7 7 13 7 33 10 + i − = + i = 2 + ∑ i + = n 2 + n − 8 2 2 2 2 i =2 2 2 4 2 J. Jaime A. Pepe'00 PM 60
  • 61. * 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)) = k −1 = ∑ 3i + 3 k T (n − k ) i =0 T(O) = 2 <• fin de la ecuación. n −1 en general: T (n ) = 3 n ⋅ 2 + ∑ 3 n ⋅ 2 i =0 Si fuese un algoritmo con dos llamadas recursivas aparecerían dos términos recursivos. - Comportamientos recurrentes típicos: $ En cada llamada elimino un ítem: n +1 T (n ) = n + T (n − 1) = ⋅n 2 T (0) = 0 n +1 T (n ) = ⋅n 2 O(T (n )) = n 2 $ Divido la entrada en dos partes y trato solo una de ellas: n  T (n ) = 1 + T (n 2) = 1 + 1 + T (n 4) = 1 + 1 + 1 + T (n 8) = k + T  ⋅ n  2  T (1) = 1 T (n ) = 1 + log 2 n $ Divido la entrada en dos partes y examino los n elementos al dividir: T(n) = n + T(n/2) = 2n J. Jaime A. Pepe'00 PM 61
  • 62. $ Hay que hacer una pasada lineal antes, durante o después de dividir en dos partes: T (n ) = n + 2T (n 2 ) tomando n = 2 k : T (2 k ) T (2 k −1 ) T (2 k − 2 ) T (1) k = 1 + 2 ⋅ k −1 = 1 + 1 + k − 2 = k + 2 2 2 1 T (1) = 1 T (2 k ) = k + 1 ⇒ T (n ) = n ⋅ (log 2 n ) + n $ 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: ù; BEGIN m := (l + r) DIV 2; 3 IF h > 0 THEN 1 pintaraya(m, h); 1 regla(l, m, h-a); 1 + T(d/2) regla(m, r, a-1); 1 + T(d/2) END; END regla; Mejor caso: h#0 4 op. T(d) = 7 + 2T(d/2) = 7 + 2(7 + 2T(d/22) = 7 + 2 $ 7 + 2 $ 22 $ 7 + ... + 2k $ T(d/2k) = ( ) ( ) k −1 = 7 ∑ 2i + 2 k T d 2 k = 7 2 n +1 − 1 i =0 u ∑2 i =0 i = 2 n +1 − 1 T(1) = 4 k = log2 d ; d = 4 T(d) = 7(2d1) + d4 = 18d - 4 O(t(d)) = d Y complejidad lineal. T(n) = 1 + 2T(n/2) O(T(n)) = n J. Jaime A. Pepe'00 PM 62
  • 63. 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)) = k −1   n  = ∑ m i + m k  T  k   = {log m n = k }  m  i =0     n −1  = + n(T1 )  m − 1  Ejercicio: PROCEDURE Eu(m, n: ù): ù; BEGIN WHILE 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 algoritmo de 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: ù; BEGIN FOR i := HIGH(a) TO 0 BY -1 DO FOR j := 1 TO i DO IF 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 de busca 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 totalmente correcto. J. Jaime A. Pepe'00 PM 63
  • 64. La verificación es más difícil, lenta y con una pretensión exagerada (que sea matemáticamente correcto), 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 (el má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 un algoritmo. 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 la precondició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 valores intermedios. * 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)} J. Jaime A. Pepe'00 PM 64