Curso-lenguaje-C/fundamentos-programacion/PEC6/README.md
2024-06-23 20:26:43 +02:00

48 KiB

PEC 6

Volver a la página principal de "Fundamentos de la Programación"

Índice

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++;
    }
  }
}

Volver arriba

Volver a la PR4 | Seguir a la PEC 7