Tema 3. TADs. Acerca del tema de los TADs (TAD = estructura de

Anuncio
Tema 3. TADs.
Acerca del tema de los TADs (TAD = estructura de datos + operaciones), hay algunas operaciones de los
tipos pila, cola y lista que en los años anteriores no siempre se han utilizado de la manera correcta. Para la
especificación de cada operación, véanse los anexos del tema 3 de teoría.
La operación creap(p) NO ‘crea’ la pila (por ejemplo, si p se pasa como parámetro a una subrutina, ya
existe...): simplemente la inicializa en función de cómo el TAD ha sido implementado (en caso de
representación vectorial inicializa el campo tope a cero y en caso de representación dinámica inicializa la
pila a la dirección nil). Lo mismo ocurre con creaq(q) y creal(l) que no crean sino inicializan
respectivamente una cola q y una lista l (véanse las implementaciones). Un error frecuente, en el caso de
diseño recursivo, ha sido inicializar el TAD cada vez que se llamaba recursivamente la subrutina. De esta
manera, en cada llamada se vuelve a inicializar el TAD perdiendo lo que había (es como si cada vez que
quisiéramos grabar un fichero en un disquete volviéramos a formatearlo: así borraríamos los datos
contenidos en otros ficheros del disquete...). El problema es más grave, si la representación del TAD es
dinámica (es decir, con punteros) ya que no devolveríamos a la zona heap de memoria el espacio ocupado
por los nodos: simplemente perdemos su referencia y ellos se quedan perdidos en la memoria (ocupando
memoria!). En función del problema que tenemos que resolver, la inicialización del TAD (es decir, la
llamada al procedimiento crea) puede que tenga que hacerse ANTES de la invocación a la subrutina
recursiva o en el mismo caso base. Se sugiere mirar los ejercicios y exámenes resueltos.
En el caso del TAD pila, una operación que a veces no ha sido utilizada correctamente es la que permite
desapilar el elemento que está en el tope de la pila. Este procedimiento desapila el tope SIN enseñar el
elemento, como se comenta en el tercer corto del vídeo anexo. Si esta operación no sólo despilara sino
que también permitiese conocer el elemento, el procedimiento tendría otra especificación y se declararía
con dos parámetros (véase la pizarra del cuarto corto del vídeo) o como función que devuelve el elemento
y lo desapila. En el corto se simulan las siguientes operaciones sobre una pila de CDs:
a:=topep(p)
desapilar(p)
apilar(p,e)
{y no desapilar(p,e) como si enseñase lo que hay en la pila y lo desapilara}
En el caso del TAD cola, la operación que no siempre se ha utilizado correctamente es desencolar ya que
(al igual que desapilar en la pila) desencola el primer elemento pero sin enseñarlo. La operación
primero(q), al contrario, enseña lo que hay en la primera posición pero sin desencolar. En el segundo
corto del vídeo se simulan tres de las operaciones sobre una cola de... estudiantes del grupo de FI del
primer cuatrimestre:
encolar(q,e)
desencolar(q)
e:=primero(q)
El último TAD es la lista con punto de interés. En este TAD nuestro interés en dónde hacer una
determinada operación puede cambiar (de una posición de la lista a otra) mientras que en la pila todas las
operaciones se hacían por un extremo del TAD (política LIFO) y en una cola (política FIFO) las
operaciones primero y desencolar por un extremo y la operación encolar por el otro. El punto de interés (o
posición distinguida) en una lista l puede estar inicialmente al principio (invocando el procedimiento
principio(l) ) o al final (invocando el procedimiento fin(l) ). Si nos hemos posicionado al principio,
podemos movernos a la posición siguiente (procedimiento siguiente(l) ) sin la necesidad de eliminar
ningún elemento (como teníamos que hacer en una pila o una cola).
Las operaciones de este TAD que parece no han quedado del todo claras son: suprime(l) y sobretodo
inserta(l,e). Como está explicado en la especificación de las operaciones de la lista con punto de interés
(véanse los anexos del tema 3 de teoría) cuando invocamos el procedimiento suprime suprimimos el
elemento que está en la posición distinguida y después, el punto de interés no cambia. Es como si
estuviésemos viendo un partido de tenis y una persona nos quitase la visual. Después de ‘suprimir’ la
persona (pidiéndole educadamente quitarse), nuestro interés queda en el mismo punto ya que seguiríamos
pidiendo que se quitase a la persona que se pusiese en la misma posición (distinguida) del que se quitó.
En el tercer corto del vídeo se simulan algunas operaciones sobre una lista (de estudiantes del grupo de FI
del primer cuatrimestre):
principio(l)
siguiente(l)
e:=recupera(l)
suprime(l)
Como se puede apreciar, después de suprimir el segundo elemento (es decir, la alumna), nuestro interés se
queda en la segunda posición.
Acerca de la operación inserta(l,e) que nos permite insertar un cierto elemento en una cierta posición
distinguida de la lista, es como si tuviéramos manía al elemento (persona) que está en la posición y
siempre quisiéramos insertar ANTES de él. Es decir, el punto de interés después de insertar un nuevo
elemento cambia en una posición (de i a i+1, véase la especificación de la operación) aunque seguimos
teniéndole manía al elemento delante del cual hemos insertado ...
En el quinto corto del vídeo se simulan algunas operaciones sobre una lista (siempre de estudiantes del
grupo de FI del primer cuatrimestre):
principio(l)
siguiente(l)
inserta(l,e)
fin(l)
inserta(l,e)
Después de insertar el elemento (es decir, la alumna) en la segunda posición, es decir, delante del alumno
que ESTABA en la segunda posición, nuestro interés se queda en ... fastidiar al pobre alumno (nada
personal!) y, si decidiéramos insertar otra persona (otra invocación a la operación inserta) seguiría siendo
antes de él. Afortunadamente (por él...) decidimos cambiar nuestro interés por el final, dónde insertamos
otro elemento (alumna).
Por último, hay que mencionar los punteros, utilizados para la representación dinámica de los TADs. El
error más común ha sido por cada variable auxiliar aux puntero a un nodo (o a un entero, a un real, etc.)
pedir dinámicamente espacio en memoria invocando SIEMPRE la primitiva new.
Ejemplo.
Var aux, aux2:^integer;
new(aux2) ;
aux2^ :=2 ;
aux :=aux2 ;
Si queremos, por ejemplo, utilizar la variable aux para hacer una copia de la dirección de memoria del
entero (para el cual se ha pedido espacio en memoria invocando la primitiva new: new(aux2) pide espacio
para un entero y escribe la dirección de memoria en aux2) no debemos pedir espacio para otro entero (es
decir, no debemos invocar new(aux) , simplemente hacemos la asignación aux:= aux2 ).
Cuidado también cuando en una guarda tenemos algo como (aux^. sig<>nil) con aux puntero a un nodo.
Hay que estar seguros que aux<>nil, es decir, que existe el nodo del cual queremos mirar el campo sig
(¿cómo podríamos mirar un campo de un nodo que no existe?)
Agradecimientos: a los alumnos del grupo de FI del 1er cuatrimestre / actores de los cortos!
Descargar