TEMA 1.- INTRODUCCIÓN A LA INGENIERÍA DEL
                  SOFTWARE.
* Programación modular:
Se pretende hacer que los pr...
programación. Es la etapa más larga en el desarrollo (sin tener en cuenta el mantenimiento).
- prueba: testeo del producto...
* Construcción de prototipos:
Los prototipos tratan de caricaturizar lo que hace el sistema, sin saber más de lo que pasar...
* Técnicas de programación:
Característica de calidad de un sistema modular:
Se busca una calidad mínima del sistema, se m...
TEMA 2.- PUNTEROS.
Los punteros no son un tipo de datos usual puesto que no se hace ninguna operación con ellos
excepto la...
Puedo hacer:
       p := ADR(var);
       p^ := <alguna operación>;
       p := ADR(var2);
       p^ := <alguna operación>...
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 variabl...
Después de DISPOSE(p) es recomendable poner p := NIL, aunque no es imprescindible en todos
los Modula-2. Es una manera de ...
NEW(t);
               WITH t^ DO              (* lo mismo que: *)
                       info := n;      (* t^.info := n ...
END;
                     tt^.next := t;

              ELSE
                     p := t;
              END;

Ejemplo: bor...
END;
              IF l^.sigui = NIL THEN RETURN END;
              l2 := l^.sigui;
              l^.sigui := l2^.sigui;
 ...
PROCEDURE InsertOrd(VAR: lista: LISTA; nombre: STR32);

VAR l, l2: LISTA;

BEGIN
      NEW(l);
      l^.nombre := nombre;
...
TEMA 3.- RECURSIVIDAD.
Técnica que permite desarrollar algoritmos en una indefinición de estos.
Un algoritmo es recursivo ...
Recursión indirecta: cuando la recursión no se produce sobre el primer algoritmo sino sobre una
llamada a un segundo algor...
Recursión de cabeza: la recursión se produce al principio.
Recursión de cola: la recursión se produce al final.
Recursión ...
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 ...
Ejemplo: hallar el número total de números iguales cercanos en una malla (práctica del 15-03-
99)[orientación del problema...
- Ordenación QuickSort (o método de Hoare):
Este método divide el array en dos partes basándose en un elemento pivote que ...
REPEAT INC(i) UNTIL a[i] >= v;
                      REPEAT DEC(j) UNTIL (j = -1) OR (a[j] < v);
                      IF ...
TEMA 4.- TIPOS ABSTRACTOS DE DATOS (TAD's).
Un TAD es alguna forma de almacenar información y una serie de procedimientos ...
Borrar quita un elemento concreto.
LeerLista me devuelve la información de una posición.
Longitud me devuelve la longitud ...
Array
                      Acotada
                                   Cursores
Implementació                             ...
END;
                     tmp^.next := tt^.next;
                     tt^.next := tmp;
               END InsertaLista;

S...
PROCEDURE LongitudLista(l: LISTA): CARDINAL;

       VAR tmp: LISTA;

       BEGIN
            tmp :=l;
            i := 0...
* Implementación con cabecera:
La cabecera es un bloque fijo de información que está siempre aunque no haya elementos en l...
END Inserta;

La cabecera facilita enormemente el calcular la longitud de la lista:
       PROCEDURE LongitudLista(l: LIST...
El indicador que se guarda se usa para atajar, es como partir la lista en dos (1 .. pos; pos .. long).
Habría que hacer al...
* TA lista con cabecera y doble enlace:
Me permite tanto avanzar como retroceder en la lista.
Los dobles enlaces no tienen...
END;
                INC(l^tamanyo);
      END;
END Insertar;

Habría que introducir en la cabecera un puntero al último e...
| BACK:
                  FOR j :=1 TO steps DO
                         jlink := jlnik^.prev;
                  END;
    ...
ListErr := NOERR;
     WITH l^ DO
             firstCell := 0;
             firstEmpty := 1;
             size := 0;
     ...
PROCEDURE Destruye(VAR l: LISTA);

BEGIN
     DISPOSE(l);
END Destruye;

- Procedimientos internos a la implementación:
PR...
PROCEDURE LeeMono(p: POLI; grado: CARDINAL): REAL;
PROCEDURE Grado(p: POLI): CARDINAL; (* devuelve el mayor grado *)

El r...
PROCEDURE DestruirPila(VAR p: PILA);

VAR tmp, t2: PILA;

BEGIN
     WHILE tmp^.sig # NIL DO
             t2 := tmp^.sig;
...
$ Acotada:
TYPE PILA = POINTER TO CABECERA;
      CABECERA = RECORD
                    primero: CARDINAL;
               ...
* Control de errores(y 2):
Para el control de errores en las funciones, para cortarlas con RETURN podemos hacer que
devuel...
|'+': Pilas.Apilar(s, Pilas.Despilar(s)+Pilas.Desapilar(s));
                        |'-':                 !
             ...
c := Desapilar(orig);
               Apilar(aux, c);
               Apilar(dest, c);
     END;
     WHILE NOT(EsPilaVacia(...
* TA Cola (Queue):
Estructura FIFO.
El interfaz es:
        CreaCola(VAR q: COLA);
        DestruirCola(VAR q: COLA);
    ...
BEGIN
     x := q^.colas[q^.primero];
     q^.primero := (q^.primero MOD MAXTAM) + 1;
     DEC(q^.longitud);
     RETURN x...
VAR nuevo: COLA;
BEGIN
     NEW(nuevo);
     nuevo^.info := x;
     IF q = NIL THEN
             q := nuevo;
             ...
END UltimoCola;
                         q^.sig^.info es el primer elemento de la cola
                         q^.info es...
tmp2^.sigui := tmp1;
       ELSE
               tmp1^.sigui := q^.sigui;
               q^.sigui := tmp1;
               q...
HacerBVacio     •> T
EsBVacio      T •> B
Raíz          T •> I c E
Izda          T •> T c E
Drcha         T •> T c E
Hacer...
PROCEDURE Raiz(b: BARBOL): BASE;

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

PROCEDURE Izda(b: BARBOL): BARBOL;

BEGIN
      RE...
Se simplifica si construimos un procedimiento para crear hojas.
- Aplicaciones con árboles:
PROCEDURE Altura(b: BARBOL): C...
EscribePreorden(Izda(b));
       EscribePreorden(Drcha(b));
END EscribePreorden;
Los otros dos son análogos.

Podemos para...
- Á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 no...
ELSE
              RETURN       MenorBASE(MenorBASE(Raiz(b),           MinBArbol(Izda(b)),
              MinBArbol(Drcha(b...
PROCEDURE CopiarBArbol(b: BARBOL): BARBOL;

BEGIN
     IF EsBVacio THEN
           RETURN CrearBArbol( );
     ELSE
      ...
AnadirBBArbol(b: BBARBOL; x: BASE): BBARBOL;
       BorrarBBArbol(b: BBARBOL): BBARBOL;
       EstaEnBBArbol(b: BBARBOL): ...
PROCEDURE BorrarBBAbol(b: BARBOL; x: BASE): BBARBOL;
     PROCEDURE MenorBBArbol(b: BBARBOL): BASE;

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

PROCEDURE Raiz(b: BBARBOL): BBARBOL;

BEGIN
     RETURN ARBOL.Raiz(b);
END R...
TEMA 5.- FICHEROS.
Son dispositivos de almacenamiento externo controlados por el sistema operativo mediante
rutinas primit...
Ejemplo:
PROCEDURE Menu( ): CARDINAL;

BEGIN
     IO.WrStr(quot;1.- Abrir/Crear para añadir/meter datosquot;); IO.WrLn( );...
Para los ficheros ASCII:
        si quiero solo leer el fichero: f := OpenRead(nomfich); (* no permite escribir *)
       ...
La búsqueda en ficheros binarios sería:
       f := Open(nomfich);
       LOOP
              IF RdBin(f, datos, SIZE(datos...
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...
TEMA 6.- VERIFICACIÓN Y COMPLEJIDAD.
* Complejidad:
Medida de la eficiencia en el espacio y en el tiempo de un algoritmo. ...
* Tiempo de ejecución:
      1 •> es un tiempo imposible o casi imposible, no depende de la entrada.
      log n •> es un ...
* Complejidad en algoritmos recursivos:
Es el mismo problema pero cambia la técnica.

PROCEDURE Fact(n: CARDINAL): CARDINA...
$ Hay que hacer una pasada lineal antes, durante o después de dividir en dos partes:
                  T (n ) = n + 2T (n ...
T(n) = 1 + m $ T(n/m) = 1 + m(1 + T(1/m)) = 1 + m(1 + n(T(n/m2))) =
                                          = 1 + m + m2...
La verificación es más difícil, lenta y con una pretensión exagerada (que sea matemáticamente
correcto), solo es aplicable...
Programacion Modular
Programacion Modular
Programacion Modular
Programacion Modular
Programacion Modular
Programacion Modular
Programacion Modular
Programacion Modular
Programacion Modular
Programacion Modular
Upcoming SlideShare
Loading in...5
×

Programacion Modular

1,759

Published on

Published in: Travel, Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
1,759
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
52
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Transcript of "Programacion Modular"

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

×