.. | ||
README.md |
PEC 6
Volver a la página principal de "Fundamentos de la Programación"
Índice
- PEC 6
- 18. Tipos abstractos de datos
- 19. Navegación de TAD
18. Tipos abstractos de datos
Vamos a ver los tipos abstractos de datos (TAD) y cómo se pueden implementar para representar secuencias de elementos como Listas, Colas y Pilas. Estos TADs emulan conceptos que encontramos fuera del mundo de la programación.
Unos ejemplos son:
- De listas: una lista de la compra, una lista de tareas pendientes, una lista de reproducción de música.
- De colas: una cola de espera en un supermercado, una cola de espera en una llamada telefónica.
- De pilas: una pila de platos, una pila de libros, una pila de cartas.
Todos los TADs tienen una serie de operaciones que se pueden realizar sobre ellos. Por ejemplo, una pila tiene operaciones como push
y pop
, una cola tiene operaciones como enqueue
y dequeue
, y una lista tiene operaciones como insert
y delete
.
18.1. Tipo abstracto de datos (TAD)
Un TAD consiste en un conjunto de valores (dominio) y el conjunto de operaciones que se pueden aplicar a este conjunto de valores.
El concepto de TAD ya existe en los lenguajes de programación bajo la forma de tipos predefinidos como int
, float
, char
, string
, etc. Pero también podemos definir nuestros propios TADs. Por ejemplo, en C, el tipo de datos int
tiene como dominio todos los enteros en el rango [MININT, MAXINT]
y las operaciones que se pueden aplicar: suma, resta, producto, cociente y módulo.
Implementar una TAD significa elegir una representación para el conjunto de valores del tipo de datos, así como codificar sus operaciones utilizando esa representación en un lenguaje de programación. Es posible tener varias implementaciones para un mismo TAD, pero dada la definición de un TAD, el comportamiento ha de ser siempre el mismo para cualquier implementación.
La utilidad de definir un TAD surge al diseñar nuevos tipos de datos. La idea es que el nuevo TAD solo puede ser manipulado a través de sus operaciones.
18.1.1. Ejemplos de especificación e implementación
18.1.1.1. Números naturales
El conjunto de valores que engloba este TAD son todos los números enteros mayores o iguales a 0. Las operaciones que definiremos para aplicar a este conjunto son la suma
y la división
.
type
natural = integer;
end type
function suma(x: natural, y: natural): natural
return x + y;
end function
function division(x: natural, y: natural): natural
return x / y;
end function
#include <stdio.h>
typedef unsigned int natural;
natural suma(natural x, natural y) {
return x + y;
}
natural division(natural x, natural y) {
return x / y;
}
En esta definición no se han excluído los números negativos. La responsabilidad del correcto comportamiento del tipo yace en la definición de las operaciones.
18.1.1.2. Números binarios
El conjunto de valores que engloba este TAD son todos los números en su representación binaria, es decir, expresados en base 2. Las operaciones que definiremos para aplicar a este conjunto son desplazamientoIzq
y complemento
.
const
MAXBITS: integer:= 64;
end const
type
binario: vector[MAXBITS] of boolean;
end type
action desplazamientoIzq(inout b:binario, in n integer)
var
i: integer;
end var
for i:= n+1 to MAXBITS do
b[i-n]:= b[i];
end for
for i:= MAXBITS-n to MAXBITS do
b[i]:= 0;
end for
end action
action complemento(inout b:binario)
var
i: integer;
end var
for i:= 1 to MAXBITS do
b[i]:= not b[i];
end for
end action
#include <stdio.h>
#define MAXBITS 64
typedef int binario[MAXBITS];
void desplazamientoIzq(binario b, int n) {
int i;
for (i = n; i < MAXBITS; i++) {
b[i-n] = b[i];
}
for (i = MAXBITS-n; i < MAXBITS; i++) {
b[i] = 0;
}
}
void complemento(binario b) {
int i;
for (i = 0; i < MAXBITS; i++) {
b[i] = !b[i];
}
}
18.2. TAD para representar secuencias de elementos
Una secuencia lineal es un conjunto de tamaño arbitrario de elementos del mismo tipo. Exceptuando el primero y el último, cada elemento tiene un único predecesor y un único sucesor.
En general, un TAD que representa secuencias de elementos posee las siguientes operaciones:
- Inicializar la secuencia.
- Insertar un elemento en la secuencia.
- Eliminar un elemento de la secuencia.
- Consultar el valor de un elemento en la secuencia.
- Consultar el número de elementos de la secuencia.
¿Dónde insertar un elemento? ¿Qué elemento eliminar? ¿Qué elemento se puede consultar? ¿Cuántos elementos hay? Las respuestas a las anteriores preguntas son las que definen el comportamiento de las operaciones y, por lo tanto, el tipo de secuencia.
Tabla comparativa de las diferencias entre Pila, Cola y Lista:
Característica | Pila (Stack) | Cola (Queue) | Lista (List) |
---|---|---|---|
Estructura de Datos | Implementada típicamente con un arreglo estático o una lista enlazada. | Puede ser implementada con un arreglo estático o una lista enlazada. | Generalmente implementada con una lista enlazada o estructura dinámica. |
Principio de Acceso | LIFO (Last In, First Out): Último en entrar, primero en salir. | FIFO (First In, First Out): Primero en entrar, primero en salir. | Acceso Aleatorio: Acceso directo a cualquier elemento. |
Operaciones Básicas | initStack() : Inicializa la pila vacía push(elemento) : Añade un elemento al tope de la pila. pop() : Elimina y retorna el elemento del tope de la pila. isEmptyStack() : Verifica si la pila está vacía. isFullStack() : Verifica si la pila está llena (en caso de implementación estática). heightStack() : Devuelve número de elementos. |
initQueue() : Inicializa cola enqueue(elemento) : Añade un elemento al final de la cola. dequeue() : Elimina y retorna el elemento al frente de la cola. head() : Devuelve el elemento situado en el inicio. isEmptyQueue() : Verifica si la cola está vacía. isFullQueue() : Verifica si la cola está llena (en caso de implementación estática). lengthQueue : Devuelve el número de elementos. |
initList() : Inicia la lista vacía. insert(elemento) : Inserta un elemento en la lista en una posición específica o al final. delete(elemento) : Elimina un elemento de la lista. get(posición) : Busca un elemento específico en una posición. isEnd(posición) : Devuelve verdadero si la posición dada es posterior a la última. isEmptyList : Verifica si la lista está vacía. isFullList : Verifica si la lista está llena. lengthList : Devuelve el número de elementos. |
Implementación Común en C | Puede ser implementada usando un arreglo estático con un índice que indica el tope, o con una lista enlazada donde cada nodo contiene el dato y un puntero al siguiente nodo. | Similar a la pila, puede usar un arreglo estático o una lista enlazada. En la implementación de arreglo, se usa un índice para controlar el frente y el final de la cola. | Implementada típicamente usando una lista enlazada donde cada nodo contiene el dato y un puntero al siguiente nodo, permitiendo inserciones y eliminaciones eficientes en cualquier posición. |
Eficiencia | - Operaciones de push() y pop() son O(1) en una implementación con arreglo o lista enlazada. - Espacio requerido puede ser menos flexible que una lista enlazada si se usa un arreglo estático. |
- Operaciones de enqueue() y dequeue() son O(1) en una implementación con arreglo o lista enlazada. - Requiere más espacio que una pila debido al almacenamiento de referencias de frente y final. |
- Inserciones y eliminaciones pueden ser O(1) o O(n), dependiendo de si se inserta o elimina al principio o final de la lista. - Búsqueda es O(n) en una lista simple enlazada sin optimizaciones adicionales. |
Uso Típico | - Manejo de historial de llamadas (undo/redo). - Evaluación de expresiones matemáticas (notación polaca inversa). |
- Gestión de tareas en una cola (por ejemplo, impresión en una impresora). - Algoritmos de búsqueda en grafos (BFS utiliza una cola para gestionar los nodos a visitar). |
- Almacenamiento de datos que requieren acceso y modificación frecuente, pero no necesariamente en orden específico. - Implementación de estructuras de datos más complejas como listas enlazadas dobles o listas circulares. |
Explicación de términos comunes:
- Arreglo estático: Un arreglo con tamaño fijo determinado en tiempo de compilación.
- Lista enlazada: Una estructura de datos dinámica donde cada elemento (nodo) contiene el dato y un puntero al siguiente nodo.
- Acceso Aleatorio: Capacidad de acceder a cualquier elemento de la estructura de datos en tiempo constante (O(1)).
18.2.1. El TAD Pila (tStack)
- Una pila es una secuencia lineal de elementos.
- Los elementos se insertan únicamente por un extremo de la secuencia. Por eso se dice que es una estructura LIFO (Last In, First Out).
- La manipulación y acceso a los elementos de la pila se permite solo en un extremo de la secuencia.
Se puede pensar en una pila de libros dentro de una caja.
18.2.1.1. Lista de operaciones sobre tStack
Operación | Descripción |
---|---|
initStack |
Inicializa la pila. |
push |
Empila un elemento en la pila. |
pop |
Desempila un elemento de la pila. |
top |
Devuelve copia del valor del elemento en la cima de la pila. |
isEmptyStack |
Consulta si la pila está vacía (true). |
isFullStack |
Consulta si la pila está llena (true). |
heightStack |
Consulta el número de elementos de la pila. |
18.2.1.2. Implementación
Vamos a crear una implementación en un vector, donde tenemos un número máximo de elementos (MAX). Para conocer el espacio disponible de la pila en cada momento se necesita un atributo que indique el número total de elementos y que llamaremos nelem.
Implementación del tipo:
type
tStack = record
A: vector[MAX] of elem; {elem represents the type of the elements in th tStack}
nelem: integer;
end record
end type
typedef struct {
elem A[MAX];
int nelem;
} tStack;
Implementación de las operaciones del tipo:
Tanto la operación de apilar push
como la de desapilar pop
, la manipulación de elementos se realiza por el extremo final de la secuencia que es el tope de la pila.
action initStack(out s: tStack)
s.nelem:= 0;
end action
action push(inout s: tStack, in e: elem)
if s.nelem = MAX then
{error full tStack}
else
s.nelem:= s.nelem + 1;
s.A[s.nelem]:= e;
end if
end action
action pop(inout s: tStack)
if s.nelem = 0 then
{error empty tStack}
else
s.nelem:= s.nelem - 1;
end if
end action
action top(in s: tStack, out e: elem)
if s.nelem = 0 then
{error empty tStack}
else
e:= s.A[s.nelem];
end if
end action
function isEmptyStack(s: tStack): boolean
return s.nelem = 0;
end function
function isFullStack(s: tStack): boolean
return s.nelem = MAX;
end function
function heightStack(s: tStack): integer
return s.nelem;
end function
#include <stdio.h>
#include <stdbool.h>
void initStack(tStack *s) {
s->nelem = 0;
}
void push(tStack *s, elem e) {
if (s->nelem == MAX) {
printf("\n Full Stack \n");
} else {
s->A[s->nelem] = e; /* First position in C is 0 */
s->nelem++;
}
}
void pop(tStack *s) {
if (s->nelem == 0) {
printf("\n Empty Stack \n");
} else {
s->nelem--;
}
}
void top(tStack s, elem *e) {
if (s.nelem == 0) {
printf("\n Empty Stack \n");
} else {
*e = s.A[s.nelem-1];
}
}
bool isEmptyStack(tStack s) {
return s.nelem == 0;
}
bool isFullStack(tStack s) {
return s.nelem == MAX;
}
int heightStack(tStack s) {
return (s.nelem);
}
18.2.1.3. Ejemplo de uso
Vamos a suponer un ejemplo de una pila de libros. Cada libro dispone de un código identificador y un nombre. Definimos las estructuras de datos necesarias para modelar el ejemplo:
type
tBook = record
name: string;
id: integer;
end record
tBox = record
A: vector[MAX] of tBook;
nelem: integer;
end record
end type
#include <stdio.h>
#define MAX_NAME_LEN = 25
typedef struct {
char[MAX_NAME_LEN] name;
int id;
} tBook;
typedef struct {
tBook A[MAX];
int nelem;
} tBox;
Ok. Ahora vamos a implementar una acción que dada una pila de libros y el código de un libro, encuentre este libro en la pila. En caso de encontrarlo, lo retiramos de la pila; en caso contrario, dejamos la pila como estaba.
action findBook(inout s: tBox, in id: integer)
var
b: tBook;
aux: tBox;
found: boolean;
end var
found:= false;
initStack(aux);
while not isEmptyStack(s) and not found do
top(s, b);
if b.id = id then
push(aux, b);
else
found:= true;
end if
pop(s);
end while
while not isEmptyStack(aux) do
top(aux, b);
push(s, b);
pop(aux);
end while
end action
#include <stdio.h>
#include <stdbool.h>
void findBook(tBox *s, int id) {
tBook b;
tBox aux;
bool found;
found = false;
initStack(&aux);
while (!isEmptyStack(*s) && !found) {
top(*s, &b);
if (b.id == id) {
push(&aux, b);
} else {
found = true;
}
pop(s);
}
while (!isEmptyStack(aux)) {
top(aux, &b);
push(s, b);
pop(&aux);
}
}
18.2.2. El TAD Cola (tQueue)
- Una cola es una secuencia lineal de elementos.
- Los elementos se insertan por el final de la cola y se extraen por el principio. Por eso se dice que es una estructura FIFO (First In, First Out).
- La manipulación y el acceso a los elementos de la cola solo se permite en los extremos.
Un ejemplo de una cola es una cola de personas esperando para comprar una entrada en la taquilla de un teatro.
18.2.2.1. Lista de operaciones sobre tQueue
Operación | Descripción |
---|---|
initQueue |
Inicializa la cola. |
enqueue |
Encola un elemento en la cola. |
dequeue |
Desencola un elemento de la cola. |
head |
Devuelve copia del valor del elemento en la cabeza de la cola. |
isEmptyQueue |
Consulta si la cola está vacía (true). |
isFullQueue |
Consulta si la cola está llena (true). |
lengthQueue |
Consulta el número de elementos de la cola. |
18.2.2.2. Implementación
Vamos a crear una implementación en un vector, donde tenemos un número máximo de elementos (MAX). Para conocer el espacio disponible de la cola en cada momento se necesita un atributo que indique el número total de elementos y que llamaremos nelem.
Implementación del tipo:
type
tQueue = record
A: vector[MAX] of elem;
nelem: integer;
end record
end type
typedef struct {
elem A[MAX];
int nelem;
} tQueue;
Implementación de las operaciones:
Los elementos se encolan por el final de la cola y se desencolan por el principio. Tened en cuenta que cada vez que se elemina un elemento se han de desplazar todos los elementos de la cola una posición a la izquierda.
action initQueue(out q: tQueue)
q.nelem:= 0;
end action
action enqueue(inout q: tQueue, in e: elem)
if q.nelem = MAX then
{error full tQueue}
else
q.nelem:= q.nelem + 1;
q.A[q.nelem]:= e;
end if
end action
action dequeue(inout q: tQueue)
var
i: integer;
end var
if q.nelem = 0 then
{error empty tQueue}
else
for i:= 1 to q.nelem-1 do
q.A[i]:= q.A[i+1];
end for
q.nelem:= q.nelem - 1;
end if
end action
action head(in q: tQueue, out e: elem)
if q.nelem = 0 then
{error empty tQueue}
else
e:= q.A[1];
end if
end action
function isEmptyQueue(q: tQueue): boolean
return q.nelem = 0;
end function
function isFullQueue(q: tQueue): boolean
return q.nelem = MAX;
end function
function lengthQueue(q: tQueue): integer
return q.nelem;
end function
#include <stdio.h>
#include <stdbool.h>
void initQueue(tQueue *q) {
q->nelem = 0;
}
void enqueue(tQueue *q, elem e) {
if (q->nelem == MAX) {
printf("\n Full Queue \n");
} else {
q->A[q->nelem] = e; /* first position in C is 0 */
q->nelem++;
}
}
void dequeue(tQueue *q) {
int i;
if (q->nelem == 0) {
printf("\n Empty Queue \n");
} else {
for (i = 0; i < q->nelem-1; i++) {
q->A[i] = q->A[i+1];
}
q->nelem--;
}
}
void head(tQueue q, elem *e) {
if (q.nelem == 0) {
printf("\n Empty Queue \n");
} else {
*e = q.A[0];
}
}
bool isEmptyQueue(tQueue q) {
return (q.nelem == 0);
}
bool isFullQueue(tQueue q) {
return (q.nelem == MAX);
}
int lengthQueue(tQueue q) {
return (q.nelem);
}
18.2.2.3. Ejemplo de uso
Siguiendo el ejemplo de la cola de una taquilla. Cada cliente de la cola tiene asociado el ordinal en la cola desde que abrió la taquilla y la cantidad de entradas que desea adquirir. Definimos las estructuras de datos necesarias para modelar el ejemplo:
type
tClient = record
num: integer;
quantity: integer;
end record
tTicketOffice = record
A: vector[MAX] of tClient;
nelem: integer;
end record
end type
typedef struct {
int num;
int quantity;
} tClient;
typedef struct {
tClient A[MAX];
int nelem;
} tTicketOffice;
Ahora necesitamos implementar una acción que atienda el primer cliente de la cola en la taquilla. La acción recibe como parámetros la cola q
de clientes y un número available
que indica la disponibilidad de entradas. En caso de haber suficientes entradas disponibles, se actualiza la cantidad de entradas disponibles y se devuelve el valor verdadero en el parámetro sold. Una vez el cliente es atendido, se elimina de la cola.
action serverCliente(inout q: tTicketOffice, in available: integer, out sold: boolean)
var
c: tClient;
end var
sold:= false;
if not isEmptyQueue(q) then
head(q, c);
if c.quantity <= available then
dequeue(q);
available:= available - c.quantity;
sold:= true;
end if
dequeue(q);
end if
end action
#include <stdio.h>
#include <stdbool.h>
void serverClient(tTicketOffice *q, int *available, bool *sold) {
tClient c;
*sold = false;
if (!isEmptyQueue(*q)) {
head(*q, &c);
if (c.quantity <= *available) {
*available -= c.quantity;
*sold = true;
}
dequeue(q);
}
}
18.2.3. El TAD Lista (tList)
- Una lista es una secuencia lineal de elementos.
- Los elementos se insertan, se eliminan y se consultan en cualquier posición de la lista.
Para entender mejor el concepto de lista, podemos pensar en una lista de la compra del supermercado.
18.2.3.1. Lista de operaciones sobre tList
Operación | Descripción |
---|---|
initList |
Inicializa la lista. |
insert |
Inserta un elemento en la lista. |
delete |
Elimina un elemento de la lista. |
get |
Devuelve copia del valor del elemento en la posición i . |
isEnd |
Consulta si la posición i es el final de la lista (true). |
isEmptyList |
Consulta si la lista está vacía (true). |
isFullList |
Consulta si la lista está llena (true). |
lengthList |
Consulta el número de elementos de la lista. |
18.2.3.2. Implementación
Vamos a crear una implementación en un vector, donde tenemos un número máximo de elementos (MAX). Para conocer el espacio disponible de la lista en cada momento se necesita un atributo que indique el número total de elementos y que llamaremos nelem.
Implementación del tipo:
type
tList = record
A: vector[MAX] of elem;
nelem: integer;
end record
end type
typedef struct {
elem A[MAX];
int nelem;
} tList;
Implementación de las operaciones:
Los elementos se insertan en cualquier posición de la lista y se eliminan de la misma forma.
Cada vez que se inserta un elemento en una posición i
, se han de desplazar todos los elementos de la lista una posición a la derecha. Cada vez que se elimina un elemento de una posición i
, se han de desplazar todos los elementos de la lista una posición a la izquierda.
action initList(out l: tList)
l.nelem:= 0;
end action
action insert(inout l: tList, in e: elem, in index: integer)
var
i: integer;
end var
if l.nelem = MAX then
{error full tList}
else
for i:= l.nelem to index step -1 do
l.A[i+1]:= l.A[i];
end for
l.nelem:= l.nelem + 1;
l.A[index]:= e;
end if
end action
action delete(inout l: tList, in index: integer)
var
i: integer;
end var
if l.nelem = 0 then
{error empty tList}
else
for i:= index to l.nelem-1 do
l.A[i]:= l.A[i+1];
end for
l.nelem:= l.nelem - 1;
end if
end action
action get(in l: tList, in index: integer, out e: elem)
if l.nelem = 0 then
{error empty tList}
else
e:= l.A[index];
end if
end action
function isEnd(l: tList, pos: integer): boolean
return pos = l.nelem;
end function
function isEmptyList(l: tList): boolean
return l.nelem = 0;
end function
function isFullList(l: tList): boolean
return l.nelem = MAX;
end function
function lengthList(l: tList): integer
return l.nelem;
end function
#include <stdio.h>
#include <stdbool.h>
void initList(tList *l) {
l->nelem = 0;
}
void insert(tList *l, elem e, int index) {
int i;
if (l->nelem == MAX) {
printf("\n Full List \n");
} else {
for (i = l->nelem; i >= index; i--) {
l->A[i+1] = l->A[i];
}
l->nelem++;
l->A[index] = e;
}
}
void delete(tList *l, int index) {
int i;
if (l->nelem == 0) {
printf("\n Empty List \n");
} else {
for (i = index; i < l->nelem-1; i++) {
l->A[i] = l->A[i+1];
}
l->nelem--;
}
}
void get(tList l, int index, elem *e) {
if (l.nelem == 0) {
printf("\n Empty List \n");
} else {
*e = l.A[index];
}
}
bool isEnd(tList l, int pos) {
return pos == l.nelem;
}
bool isEmptyList(tList l) {
return l.nelem == 0;
}
bool isFullList(tList l) {
return l.nelem == MAX;
}
int lengthList(tList l) {
return l.nelem;
}
18.2.3.3. Ejemplo de uso
Siguiendo el ejemplo de la lista de la compra del supermercado. Cada artículo tiene asociado un nombre, el tipo de artículo clasificado según sea de panadería, frescos, bebidas, congelados, belleza o desayuno, y la cantidad de este artículo. Definimos las estructuras de datos necesarias para modelar el ejemplo:
type
tArticleType = {BAKERY, FRESH, DRINKS, FROZEN, BEAUTY, BREAKFAST}
tArticle = record
type: tArticleType;
quantity: real;
end record
tBuyList = record
A: vector[MAX] of tArticle;
nelem: integer;
end record
end type
#include <stdio.h>
typedef enum {BAKERY, FRESH, DRINKS, FROZEN, BEAUTY, BREAKFAST} tArticleType;
typedef struct {
tArticleType type;
float quantity;
} tArticle;
typedef struct {
tArticle A[MAX];
int nelem;
} tBuyList;
Necesitamos implementar una acción que elimine de la lista de la compra l
, todos los artículos de un tipo dado (filter) que se recibe como parámetro.
action filterArticleType(inout l: tBuyList, in filter: tArticleType)
var
a: tArticle;
pos: integer;
end var
pos := 1;
while not isEnd(l, pos) do
get(l, pos, a);
if a.type = filter then
delete(l, pos);
else
pos := pos + 1;
end if
end while
end action
#include <stdio.h>
void filterArticleType(tBuyList *l, tArticleType filter) {
elem a;
int pos;
pos = 0;
while (!isEnd(*l, pos)) {
get(*l, pos, &a);
if (a.type == filter) {
delete(l, pos);
} else {
pos = pos + 1;
}
}
}
18.2.4. Sintaxis para la declaración (Definición de un TAD de tipo pila, cola o lista)
Para declarar un tipo pila dentro de un algoritmo, acción o función, se utiliza la siguiente sintaxis:
nombreTipoPila = tStack(tipoElemento)
por ejemplo, tBinaryStack = tStack(tBit)
permite declarar el tipo tBinaryStack como un tipo de pila de elementos de tipo tBit. A partir de aquí, se le pueden aplicar todas las operaciones que se han definido para las pilas.
Como véis, en lenguaje algorítmico, la declaración queda como un tipo abstracto (su implementación es "oculta") pero en cambio, en lenguaje C, sí que se "ve" su implementación interna:
typedef struct{
tBit A[MAXBITS];
int nelem;
} tBinaryStack;
A partir de aquí, solo se debe utilizar con las funciones y acciones predefinidas para este tipo. De este modo, realmente lo estamos utilizando como un tipo abstracto pila.
De forma similar se puede declarar una cola o una lista:
nombreTipoCola = tQueue(tipoElemento)
nombreTipoLista = tList(tipolemento)
19. Navegación de TAD
19.1. Ejemplos sobre el TAD pila
19.1.1 Ejemplo 1
Se pide el diseño de una acción que muestre la representación binaria de un número recibido como parámetro. Se debe usar una pila que almacene los resultados de las divisiones parciales por la base (que es 2). Por ejemplo, si la entrada es 77, la salida debe ser 1001101.
type
tBinaryStack = tStack(tBit)
end type
action dev2bin(in n: integer)
var
b:tBinaryStack;
res, bit: integer;
end var
initStack(b);
res := n;
while res ≠ 0 do
bit := res mod 2;
res := res / 2;
push(b, bit);
end while
while not isEmptyStack(b) do
top(b, bit);
write(bit);
pop(b);
end while
end action
#include <stdio.h>
typedef struct {
tBit A[MAXBITS];
int nelem;
} tBinaryStack;
void dev2bin(int n) {
tBinaryStack b;
int res, bit;
initStack(&b);
res = n;
while (res != 0) {
bit = res % 2;
res = res / 2;
push(&b, bit);
}
printf("\n");
while (!isEmptyStack(b)) {
top(b, &bit);
printf("%d", bit);
pop(&b);
}
printf("\n");
}
19.1.2 Ejemplo 2
Se modela el historial de accesos a un conjunto de páginas web. La última página accedida está en la cima de la pila y si nos movemos hacia atrás en el historial iremos accediendo en orden desde el acceso más reciente al más antiguo.
En el historial cada página tiene la siguiente info:
- Identificador único de tipo entero.
- Una dirección de memoria donde se encuentra el código de la página
- La fecha de último acceso en formato aaaammdd.
Se pide una acción clearRecentHistory
que elimina todas las páginas web del historial que tienen último acceso posterior a una fecha dada.
type
tWebPage = record
id: integer;
address: integer;
lastAccess: integer;
end record
tHistory = tStack(tWebPage);
end type
action clearRecentHistory(inout s: tHistory, in date: integer)
var
end: boolean;
p: tWebPage;
end var
end:= false;
while not isEmptyStack(s) and not end do
top(s, p);
if p.lastAccess > date then
pop(s);
else
end:= true;
end if
end while
end action
#include <stdio.h>
#include <stdbool.h>
typedef struct {
int id;
int address;
int lastAccess;
} tWebPage;
typedef struct {
tWebPage A[MAX];
int nelem;
} tHistory;
void clearRecentHistory(tHistory *s, int date) {
bool end;
tWebPage p;
end = false;
while (!isEmptyStack(*s) && !end) {
top(*s, &p);
if (p.lastAccess > date) {
pop(s);
} else {
end = true;
}
}
}
19.1.3 Ejemplo 3
Continuando con el ejercicio anterior del historial de accesos de páginas web, ahora vamos a diseñar una acción pushupWebPage
que recarga el acceso a una página dada. Esto significa que sube su posición en la pila hasta la cima, para pasar a ser la de acceso más reciente.
action pushupWebPage(inout s: tHistory, in id: integer);
var
aux: tHistory;
found: boolean;
tmp, p: tWebPage;
end var
found:= false;
initStack(aux);
while not isEmptyStack(s) and not found do
top(s, p);
if p.id = id then
pop(s);
found:= true;
else
pop(s);
push(aux, p);
end if
end while
while not isEmptyStack(aux) do
top(aux, tmp);
push(s, tmp);
pop(aux);
end while
if found = true then
push(s, p);
end if
end action
#include <stdio.h>
#include <stdbool.h>
void pushupWebPage(tHistory *s, int id) {
tStack aux;
bool found;
tWebPage tmp, p;
found = false;
initStack(&aux);
while (!isEmptyStack(*s) && !found) {
top(*s, &p);
if (p.id == id) {
pop(s);
found = true;
} else {
pop(s);
push(&aux, p);
}
}
while (!isEmptyStack(aux)) {
top(aux, &tmp);
push(s, tmp);
pop(&aux);
}
if (found) {
push(s, p);
}
}
19.2. Ejemplos sobre el TAD cola
19.2.1 Ejemplo 1
Se modela la cola de trabajos de una impresora. Los trabajos nuevos se insertan al final de la secuencia de trabajos y el primer trabajo de la secuencia es el primero en enviarse a imprimir.
Cada trabajo tiene la siguiente información:
- Un identificador único de tipo entero.
- La dirección donde se encuentra almacenado el trabajo a imprimir.
- el tamaño en bytes.
- El tipo de impresión (a doble o a simple cara)
Se debe diseñar una función countBig
que devuelva el número de trabajos de la cola de impresión cuyo tamaño es superior a un límite dado en bytes.
type
tWork = record
id: integer;
address: integer;
size: integer;
doubleSide: boolean;
end record
tPrintQueue = tQueue(tWork);
end type
function countBig(q: tPrintQueue, limit: integer): integer
var
w: tWork;
ini, count: integer;
end: boolean;
end var
count := 0;
if not is EmptyQueue(q) then
head(q, w);
ini := w.id;
if w.size > limit then
count := count + 1;
end if
dequeue(q);
enqueue(q, w);
end:= false;
while not end do
head(q, w);
if w.id = ini then
end:= true;
else
if w.size > limit then
count := count + 1;
end if
dequeue(q);
enqueue(q, w);
end if
end while
end if
return count;
end function
#include <stdio.h>
#include <stdbool.h>
// Define la estructura de un trabajo de impresión
typedef struct {
int id; // Identificador del trabajo
int address; // Dirección del trabajo
int size; // Tamaño del trabajo
bool doubleSide; // Indica si el trabajo es a doble cara
} tWork;
// Define la estructura de una cola de impresión
typedef struct {
tWork A[MAX]; // Array de trabajos
int nelem; // Número de elementos en la cola
} tPrintQueue;
typedef tWork elem;
typedef tPrintQueue tQueue;
// Función para contar cuántos trabajos en la cola son más grandes que un cierto límite
int countBig(tQueue q, int limit){
elem w; // Elemento actual
int ini; // Identificador del primer elemento
int count; // Contador de trabajos grandes
bool end; // Indicador de fin de la cola
count=0;
// Si la cola no está vacía
if(!isEmptyQueue(q)){
head(q, &w); // Obtiene el primer elemento
ini=w.id; // Guarda el identificador del primer elemento
// Si el tamaño del trabajo es mayor que el límite
if(w.size>limit){
count++; // Incrementa el contador
}
dequeue(&q); // Elimina el primer elemento de la cola
enqueue(&q, w); // Añade el primer elemento al final de la cola
end=false;
// Mientras no se haya vuelto al primer elemento
while(!end){
head(q, &w); // Obtiene el primer elemento
// Si se ha vuelto al primer elemento
if(w.id==ini){
end=true; // Indica el fin de la cola
}else{
// Si el tamaño del trabajo es mayor que el límite
if(w.size>limit){
count++; // Incrementa el contador
}
dequeue(&q); // Elimina el primer elemento de la cola
enqueue(&q, w); // Añade el primer elemento al final de la cola
}
}
}
return count; // Devuelve el contador
}
19.2.2 Ejemplo 2
Siguiendo con el ejercicio anterior. Se debe diseñar una acción concatQueues
que, dadas dos colas de trabajos, concatena la segunda cola con la primer y vacía la segunda cola de trabajos. Si la primera cola de trabajos está llena, los trabajos que no se hayan concatenado se quedarán en la segunda cola y el parámetro de salida completed
será falso. Si se puede realizar la concatenación, el parámetro de salida completed
será verdadero.
action concatQueues(inout q1, q2: tPrintQueue, out completed: boolean)
var
w: tWork;
end var
while not isEmptyQueue(q2) and not isFullQueue(q1) do
head(q2, w);
enqueue(q1, w);
dequeue(q2);
end while
completed := isEmptyQueue(q2);
end action
#include <stdio.h>
#include <stdbool.h>
void concatQueues(tQueue *q1, tQueue *q2, bool *completed){
tWork w; // Elemento actual
while (!isEmptyQueue(*q2) && !isFullQueue(*q1)){
head(*q2, &w); // Obtiene el primer elemento
enqueue(q1, w); // Añade el primer elemento a la cola 1
dequeue(q2); // Elimina el primer elemento de la cola 2
}
*completed = isEmptyQueue(*q2); // Indica si se han añadido todos los elementos
}
19.2.3 Ejemplo 3
Siguiendo con el ejercicio anterior. Se debe diseñar una acción serveDoubleSide
, que envía a imprimir todos los trabajos cuyo tipo de impresión es doble cara. Se devuelve en el parámetro qsingle, una cola creada con todos los trabajos no enviados a imprimir, es decir, los que son de tipo simple cara.
Para enviar un trabajo a imprimir se dispone de la acción printWork
, que recibe el parámetro el trabajo a imprimir con todos sus datos (identificador, dirección, tamaño y tipo de impresión).
action serveDoubleSide(inout q: tPrintQeueu, inout qsingle: tPrintQueue)
var
w: tWork;
end var
initQueue(qsingle);
while not isEmptyQueue(q) do
head(q, w);
if w.doubleSide then
printWork(w);
else
enqueue(qsingle, w);
end if
dequeue(q);
end while
end action
#include <stdio.h>
#include <stdbool.h>
void serveDoubleSide (tQueue *q, tQueue *qSingle){
elem w;
initQueue(qSingle);
while (!isEmptyQueue(*q)){
head(*q, &w);
if (w.doubleSide){
printWork(w);
} else {
enqueue(qSingle, w);
}
dequeue(q);
}
}
19.3. Ejemplos sobre el TAD lista
19.3.1 Ejemplo 1
Se modela la lista de asientos disponibles de un vuelo concreto mediante una lista de asientos. Cada asiento tiene la siguiente información:
- Número entero que identifica
- Tipo de asiento:
- Regular
- Puerta de emergencia - No se asignan a menores de 12 años.
De cada pasajero se conoce:
- Identificador de tipo entero
- Número de maletas que ha facturado
- Edad
Se debe diseñar una acción seatAssign
que, dada una lista de asientos disponibles y la información de un pasajero, devuelve el asiento que se le ha asignado y el parámetro found
con valor verdadero. Si no ha sido posible asignarle un asiento, el parámetro found
será falso.
type
tPerson = record
id: integer;
bags: integer;
age: integer;
end record
tSeatType = {REGULAR, EMERGENCY_DOOR}
tSeat = record
num: integer;
type: tSeatType;
end record
tListSeat = tList(tSeat);
end type
action seatAssing(inout l: tListSeat, in p: tPerson, inout s: tSeat, inout found: boolean)
var
pos: integer;
found: boolean;
end var
pos := 1;
found := false;
while pos ≤ lengthList(l) and not found do
get(l, pos, s);
if p.age > 12 or (p.age ≤ 12 and s.type ≠ EMERGENCY_DOOR) then
delete(l, pos);
found := true;
else
pos := pos + 1;
end if
end while
end action
#include <stdio.h>
#include <stdbool.h>
typedef struct {
int id;
int bags;
int age;
} tPerson;
typedef enum {REGULAR, EMERGENCY_DOOR} tSeatType;
typedef struct {
int num;
tSeatType type;
} tSeat;
typedef struct {
tSeat A[MAX];
int nelem;
} tListSeat;
typedef tSeat elem;
typedef tListSeat tList;
void seatAssign(tListSeats *l, tPerson p, tSeat *s, bool *found){
int pos;
pos = 0
*found = false;
while (pos < lengthList(*l) && !*found){
get(*l, pos, s);
if (p.age > 12 || (p.age <= 12 && s.type != EMERGENCY_DOOR)){
delete(l, pos);
*found = true;
} else {
pos++;
}
}
}
19.3.2 Ejemplo 2
Se modela un conjunto de facturas de una empresa mediante una lista. Las facturas no siguen un orden especial en el alta del conjunto y es posible dar de baja cualquier factura del conjunto.
De cada factura se conoce:
- Un identificador de tipo entero
- Cantidad de dinero de tipo real
- Fecha de la factura en formato aaaammdd
Se debe diseñar una función countSmallAmounts
que devuelva el número de facturas con cantidad de dinero menor a una cantidad dada.
type
tInvoice = record
id: integer;
amount: real;
date: integer;
end record
tInvoiceList = tList(tInvoice);
end type
function countSmallAmounts(l: tInvoiceList, a: real): integer
var
inv: tInvoice;
total: integer;
pos: integer;
end var
pos := 1;
total := 0;
while pos ≤ lengthList(l) do
get(l, pos, inv);
if inv.amount < a then
total := total + 1;
end if
pos := pos + 1;
end while
return total
end function
#include <stdio.h>
typedef struct {
int id;
float amount;
int date; /* aaaammdd */
} tInvoice;
typedef struct {
tInvoice A[MAX];
int nelem;
} tInvoiceList;
typedef tInvoice elem;
typedef tInvoiceList tList;
int countSmallAmounts(tList l, float a){
tInvoce inv;
int total;
int pos;
pos = 0;
total = 0;
while (pos < lengthList(l)){
get(l, pos, &inv);
if (inv.amount < a){
total++;
}
pos++;
}
return total;
}
19.3.3 Ejemplo 3
Siguiendo el ejercicio anterior, vamos a diseñar la acción findInvoice
que, dado el identificador de una factura, devuelve el parámetro found
verdadero si la factura está en el conjunto de facturas así como su posición en la lista. En caso contrario, la acción devuelve el parámetro found con valor falso.
action findInvoice(in l: tInvoice, in d: integer, out found: boolean, out pos:integer)
var
inv: tInvoice;
end var
found := false;
pos := 1;
while pos ≤ lengthList(l) and not found do
get(l, pos, inv);
if inv.id = d then
found := true;
else
pos := pos + 1;
end if
end while
end action
#include <stdio.h>
#include <stdbool.h>
void findInvoice(tInvoices l, int id, bool *found, int *pos){
tInvoice inv;
*found = false;
*pos = 0;
while (*pos < lengthList(l) && ! (*found)){
get(l, *pos, &inv);
if (inv.id == id){
*found = true;
} else {
*pos++;
}
}
}