Docsity
Docsity

Prepara tus exámenes
Prepara tus exámenes

Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity


Consigue puntos base para descargar
Consigue puntos base para descargar

Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium


Orientación Universidad
Orientación Universidad


Tipos Abstractos de Datos , Apuntes de Algoritmos y Programación

Asignatura: Programacion, Profesor: Jesus Albert, Carrera: Enginyeria Informàtica, Universidad: UV

Tipo: Apuntes

2011/2012

Subido el 11/07/2012

usuario desconocido
usuario desconocido 🇪🇸

1 / 19

Toggle sidebar

Esta página no es visible en la vista previa

¡No te pierdas las partes importantes!

bg1
P
Pr
ro
og
gr
ra
am
ma
ac
ci
ió
ón
n
2
2
Tipos Abstractos de Datos
Universitat de València
Jesus Albert, Ricardo Ferrís
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13

Vista previa parcial del texto

¡Descarga Tipos Abstractos de Datos y más Apuntes en PDF de Algoritmos y Programación solo en Docsity!

PrProoggrraammaacciióónn

Tipos Abstractos de Datos

Universitat de València

Jesus Albert, Ricardo Ferrís

CONTENIDO

2.1. PROGRAMACIÓN Y ABSTRACCIÓN ................................................................................................................... 2

2.2. TIPOS DE DATOS Y ABSTRACCIÓN ..................................................................................................................... 3

2.3. CLASES EN C++ ........................................................................................................................................... 6

2.4. TIPOS ABSTRACTOS DE DATOS........................................................................................................................ 8

2.5. EJEMPLO DE CONSTRUCCIÓN DE TIPOS CON CLASES EN C++................................................................................ 10

2.1. PROGRAMACIÓN Y ABSTRACCIÓN

La abstracción es un mecanismo fundamental para la comprensión de fenómenos o

situaciones que implican gran cantidad de detalles. La idea de abstracción es uno de los

conceptos más potentes en el proceso de resolución de problemas. Se entiende por

abstracción la capacidad de manejar un objeto (tema o idea) como un concepto general, sin

considerar la enorme cantidad de detalles que pueden estar asociados con dicho objeto. Sin

abstracción no sería posible manejar, ni siquiera entender, la gran complejidad de ciertos

problemas. Por ejemplo, es muy difícil entender la organización y funcionamiento de una gran

empresa multinacional si se piensa en ella en términos de cada trabajador individual

(posiblemente miles, distribuidos por todo el mundo) o de cada uno de los productos que

fabrica, sin embargo, es más sencilla su comprensión si se ve, simplemente, como una

agrupación de departamentos especializados en una tarea concreta.

En todo proceso de abstracción aparecen dos aspectos complementarios:

i. destacar los aspectos relevantes del objeto.

ii. ignorar aspectos irrelevantes del mismo.

La relevancia de los detalles (información) depende del nivel de abstracción considerado, ya

que si se pasa a niveles más concretos, es posible que ciertos aspectos pasen a ser relevantes.

Se puede decir que la abstracción permite estudiar los fenómenos complejos siguiendo un

método jerárquico, es decir, por sucesivos niveles de detalle.

La abstracción implica reducción de información y, por tanto, simplificación del problema

tratado.

En el aspecto concreto que nos interesa, la programación, hay que tener en cuenta que los

programas son entidades complejas que pueden estar compuestos por miles de instrucciones,

cada una de las cuales puede dar lugar a un error del programa y que, por lo tanto, necesitan

mecanismos de definición que eviten, en la medida de lo posible, que el programador cometa

errores. Así, por ejemplo, los lenguajes de programación de alto nivel permiten al

programador abstraerse de la gran cantidad de detalles que es necesario controlar al

así como las operaciones básicas sobre dicho conjunto, es decir, definen cómo se representa la

información y cómo se interpreta.

Los tipos de datos pueden variar de un lenguaje de programación a otro, tanto los tipos

simples como los mecanismos para crear tipos compuestos. Los tipos de datos constituyen un

primer nivel de abstracción, ya que no se tiene en cuenta cómo se representa realmente la

información sobre la memoria de la máquina, ni cómo se manipula. Para el usuario el proceso

de representación es invisible. El programador no manipula directamente las cadenas de bits

que constituyen los datos, sino que hace uso de las operaciones previstas para cada tipo de

datos. Por ejemplo, el tipo simple ENTERO define un conjunto de valores enteros

comprendidos en un determinado intervalo, para los que están definidas las operaciones

suma, resta, multiplicación, división entera, asignación, etc. Para el programador es imposible

manipular un dato entero si no es a través de las operaciones definidas para ese tipo de datos,

cualquier otro proceso de manipulación está prohibido por el lenguaje. De esta manera, al

escribirse los programas independientemente de la representación última de los datos en la

memoria, si cambiase, por ejemplo, la forma de representar la información en los

ordenadores, los programas escritos en lenguajes de alto nivel sólo necesitarían ser

recompilados para ejecutarse correctamente en las nuevas máquinas.

La memoria del ordenador es una estructura unidimensional (secuencia de elementos)

formada por celdas iguales que pueden almacenar números binarios con un número fijo de

cifras (bits). Cada tipo de datos tiene asociada una función de transformación que permite

pasar los datos del formato en que se manejan en un programa al formato de la memoria y

viceversa. De manera que cambiar la representación en memoria de los datos sólo implica

modificar la función de transformación, el programador no ve afectado para nada su trabajo,

ya que se encuentra en un nivel superior de abstracción.

Los tipos de datos que un programador utiliza en un lenguaje de alto nivel suelen ser de dos

tipos: predefinidos en el lenguaje y definidos por el usuario. Esta última posibilidad contribuye

a elevar el nivel del lenguaje, pues permite definir tipos de datos más próximos al problema

que se desea resolver. Para ello, el lenguaje suministra constructores genéricos de tipos

mediante los cuales el programador puede definir tipos concretos. Sin embargo, en los

lenguajes de alto nivel más tradicionales (procedimentales y no orientados a objetos), al

programador no se le permite definir cuáles son las operaciones permitidas para los nuevos

tipos de datos. En general, los lenguajes suministran unas operaciones predefinidas muy

genéricas que, en la mayoría de los casos, no resultan las más apropiadas para el nuevo tipo.

Supóngase, por ejemplo, la definición de un tipo de datos para manipular fechas del

calendario, una posible definición en C/C++ sería:

struct fecha { int dia; int mes; int año; };

Con esta declaración se intenta indicar que una fecha es un dato compuesto, formado por tres

elementos: día, mes y año, y todos ellos son números enteros. Sin embargo, esto último no es

del todo cierto, ya que el rango de variación de, al menos, dos de los tres elementos es mucho

más restringido. El concepto día sólo puede variar en el rango 1..31 y el concepto mes sólo lo

puede hacer en el rango 1..12.

Una vez definido el tipo fecha, ya se pueden declara en el programa variables de ese nuevo

tipo:

fecha f1, f2;

También se pueden definir subprogramas que operen sobre valores de este tipo y que, de

alguna manera, representarían los operadores válidos sobre esos datos. Por ejemplo, en una

aplicación se puede considerar adecuado manejar datos del tipo fecha mediante las siguientes

operaciones (procedimientos o funciones):

Asignar un valor (día, mes y año) válido a una fecha:

void asignarFecha (fecha f, int d, int m, int a);

Incrementar en un día una fecha:

void incrementarFecha (fecha f);

Decrementar en un día una fecha:

void decrementarFecha (fecha f);

Comprobar si una fecha es menor (anterior) que otra:

bool menorFecha (fecha f, fecha g);

Visualizar los datos de una fecha:

void visualizarFecha (fecha f);

Todas estas operaciones pueden incluir las comprobaciones pertinentes para verificar que

todas las fechas manejadas sean válidas (meses de 30 o 31 días, años bisiestos, etc). Sin

embargo, no se puede impedir que se generen, mediante otros operadores, valores que no

tengan sentido. Por ejemplo, hacer:

f1.dia = 30; f1.mes = 2; /* ¡¡¡día 30 de febrero!!! */

Como se puede observar, la declaración incluye tanto la especificación de los datos necesarios

para representar una fecha (día, mes y año), como las operaciones que permiten su

manipulación.

En la declaración de la clase hay que destacar el significado de las palabras reservadas public

y private. La finalidad de estos elementos del lenguaje es especificar el nivel de visibilidad de

los componentes definidos. La palabra public permite especificar aquellos componentes

(datos y/o operaciones) que está permitido usar “fuera” de la clase, es decir, por cualquier

programa o función que declare fechas y desee manipularlas de alguna manera. Por el

contrario, la palabra private especifica aquellos componentes (también datos y/o

operaciones) que se desea que estén protegidos por el lenguaje, de manera que se impide su

acceso desde “fuera” de la clase. Sólo las operaciones definidas en la clase pueden tener

acceso a estos elementos.

Por lo tanto, una declaración de clase no sólo permite definir conjuntamente datos y

operaciones de manipulación, sino también establecer el nivel de visibilidad (protección) de

estos elementos en un programa.

EJEMPLO:

El siguiente programa hace uso de la clase fecha anteriormente declarada:

int main () { fecha f; // f.dia = 3; //2, error f.mes = 5; //3, error f.anyo = 2100; //4, error f.visualizarFecha (); // f.asignarFecha(4,10,2020); // f.visualizarFecha (); // return 0; }

Lo primero que hay que destacar es que un dato de tipo fecha, f, se declara de igual forma

que una variable estándar (línea 1). No obstante, en lugar de variables, estos elementos

reciben el nombre de objetos ( objeto = dato perteneciente a una clase). Por su lado, el acceso

a los elementos contenidos en el interior de un objeto, que vienen dados por la declaración de

la clase a la que pertenece, se realiza mediante la sintaxis típica de una estructura en C++

(struct). En la forma: objeto.componente, como se puede ver en las líneas 2-7. Las

operaciones definidas en una clase siempre deben de ser invocadas a través de un objeto. Por

ejemplo, la sentencia:

visualizarFecha ();

no es válida. Puesto, que está declarada en el interior de la clase fecha, debe de ser invocada

asociada con un objeto de dicha clase (¿qué fecha se debe visualizar?):

f.visualizarFecha ();

En realidad, se le está pidiendo al objeto f que ejecute dicha operación. De ahí que los objetos

sean considerados algo más que variables. Las variables se limitan a almacenar información de

manera pasiva, sin embargo, los objetos son elementos capaces de realizar acciones y para

ello, adicionalmente almacenan información.

No todas las sentencias del programa de ejemplo son correctas. Así, las líneas 2, 3 y 4 darían

lugar a errores de compilación, puesto que se está intentando acceder a elementos que han

sido declarados como privados en la clase. Este es un mecanismo de protección frente al uso

incorrecto de la información. Por su parte, las líneas 5, 6 y 7 muestran cual debería ser la

forma correcta de acceder a las fechas, a través de los elementos públicos de la clase.

Como regla general, se establece que: (i) todos los datos (atributos) declarados en el interior

de una clase deben de ser privados a la misma, (ii) deben de ser declaradas como públicas

aquellas operaciones a través de las cuales se permita manipular (correctamente) los objetos

que se declaren de esta clase.

La descripción de las clases permite observar claramente los dos niveles que existen en la

manipulación de datos en un programa. Por un lado, el proceso de diseño y construcción de

los datos y por otro lado, la utilización de estos datos en los programas para describir

soluciones a problemas. Durante el primero de los procesos es necesario conocer y tener

acceso a todos los componentes declarados en la clase (tanto públicos como privados). Sin

embargo, para el uso de objetos de la clase sólo es necesario conocer la especificación de la

misma y la forma en que se puede manipular (elementos públicos). Lo mismo ocurre con los

objetos del mundo real. Un teléfono móvil, una vez construido sólo puede ser manipulado a

través de las operaciones que el fabricante ha previsto a tal efecto (botones, menús, etc).

    1. TIPOS ABSTRACTOS DE DATOS

Los tipos de datos se pueden presentar con distinto nivel de detalle (abstracción), desde la

especificación formal (tipos abstractos de datos) hasta el nivel de implementación en un

lenguaje de programación concreto. De igual manera que un algoritmo, que describe la

secuencia de operaciones a realizar para resolver un problema, se puede expresar desde un

nivel esquemático mediante diagramas de flujo antes de concretarlo en forma de función en

C++.

El nivel más alto de abstracción en la representación de los tipos de datos lo constituye la

especificación formal de los mismos, dando lugar a los denominados tipos abstractos de datos.

Éstos se pueden ver como modelos matemáticos sobre los que se definen una serie de

operaciones. El tipo abstracto de datos es independiente del lenguaje de programación, ya que

un tipo abstracto de datos (en adelante TAD) es una especificación que no incluye ningún

detalle sobre la forma de representación.

Existen múltiples formas de implantar un mismo TAD, pero todos los TAD se deben

implementar utilizando otros tipos de datos (predefinidos o no en el lenguaje de

programación).

Las estructuras de datos son tipos de datos orientados al almacenamiento de información

(contenedores) que son habitualmente utilizados como soporte para construir otros tipos de

datos y que, por tanto, son fundamentales en el proceso de programación. Son tipos de

aplicación generalizada a un amplio espectro de aplicaciones informáticas.

    1. EJEMPLO DE CONSTRUCCIÓN DE TIPOS CON CLASES EN C++

En esta sección se muestra un ejemplo completo de construcción de un tipo de datos

utilizando clases en el lenguaje de programación C++.

Se desea construir un tipo de datos fecha a partir de la siguiente especificación del tipo

abstracto de datos:

TAD FECHA

Dominio:

Conjunto de fechas válidas especificadas por tres valores enteros que identifican el día, mes y

año.

Operaciones:

asignarFecha (Entero, Entero, Entero )  Fecha incrementarFecha (Fecha)  Fecha decrementarFecha (Fecha)  Fecha menorFecha (Fecha, Fecha)  Lógico visualizarFecha (Fecha)

Axiomas:

La especificación de los axiomas de un tipo se suele hacer, por precisión, siguiendo una

notación algebraica. Sin embargo, por simplicidad, ahora se va a realizar una descripción en

lenguaje natural de las propiedades que debe cumplir cada operación.

Sea d,m,a Є Entero:

asignarFecha (d, m, a), debe generar una fecha válida cuyo día sea d, su mes

sea m y su año sea a.

Sea f Є Fecha:

incrementarFecha (f), debe generar la fecha correspondiente a un día posterior a

f.

Sea f Є Fecha:

decrementarFecha (f), debe generar la fecha correspondiente a un día anterior a f.

Sea f,g Є Fecha:

menorFecha (f, g), devolverá cierto si f corresponde a una fecha anterior a g y falso

en caso contrario.

Sea f Є Fecha:

visualizarFecha (f), debe imprimir en pantalla los datos de día, mes y año

correspondientes a la fecha f.

Como se puede observar esta descripción del TAD Fecha no incluye ningún detalle sobre la

forma en que se debe implementar el tipo (datos a utilizar o algoritmos a emplear para

implementar las operaciones). Una posible implementación vendría dada por la siguiente clase

en C++:

Archivo “fecha.h”: Interfaz de la clase (sólo declaración de prototipos)

class Fecha { public: //Operaciones descritas en el TAD void asignarFecha (int d, int m, int a); void incrementarFecha (); void decrementarFecha (); bool menorFecha (Fecha f); void visualizarFecha (); private: //Detalles de implementación //Datos a emplear para la representación int dia; int mes; int anyo; //Funciones auxiliares para implementar //las operaciones públicas bool fechaValida (int d, int m, int a); int diasMes (int m, int a); bool bisiesto (int a); };

bool Fecha::bisiesto (int a) //Método privado que indica si un año es bisiesto { bool b; if ((a % 400) == 0) b = true; else if ((a % 100) == 0) b = false; else if ((a % 4) == 0) b = true; else b = false; return (b); } void Fecha::asignarFecha (int d, int m, int a) //Asigna un día, mes y año a la fecha { //Como especifica el TAD, sólo asigna fechas válidas if (fechaValida (d, m, a)) { dia = d; mes = m; anyo = a; } else cerr << “Error: fecha no valida.” << ende; }

void Fecha::incrementarFecha () //Incrementa en un día la fecha { //En días intermedios del mes sólo es preciso //incrementar el valor de dia if (fechaValida (dia+1, mes, anyo)) dia = dia + 1; else //Si estamos en el último día del mes //es necesario pasar al día 1 del mes siguiente if (fechaValida (1, mes+1, anyo)) { dia = 1; mes = mes + 1; } else //Estamos en el 31 de diciembre y es preciso //pasar al 1 de enero del siguiente año { dia = 1; mes = 1; anyo = anyo + 1; } }

void Fecha::visualizarFecha () //Muestra por pantalla la fecha { string nomMes; switch (mes) { case 1: nomMes = “Enero”; break; case 2: nomMes = “Febrero”; break; case 3: nomMes = “Marzo”; break; case 4: nomMes = “Abril”; break; case 5: nomMes = “Mayo”; break; case 6: nomMes = “Junio”; break; case 7: nomMes = “Julio”; break; case 8: nomMes = “Agosto”; break; case 9: nomMes = “Septiembre”; break; case 10: nomMes = “Octubre”; break; case 11: nomMes = “Noviembre”; break; case 12: nomMes = “Diciembre”; } cout << dia << “ de “; cout << nomMes << “ de “; cout << anyo; } Modifica do: 18/ 02/

Cuestionario de Autoevaluación

1. Un tipo abstracto de datos especifica (marcar la respuesta correcta):

(a) La representación en memoria de la información

(b) La representación en memoria de la información y las operaciones asociadas

(c) Las operaciones válidas para un tipo de datos y sus propiedades

(d) La declaración de tipos en un lenguaje de programación

2. Señala cuál de los siguientes aspectos NO aparece en la especificación de un tipo abstracto de

datos (TAD) (marcar la respuesta correcta):

a) Cuáles son las operaciones válidas del tipo

b) Cuál debe de ser la implementación de las operaciones del tipo

c) Cuál es el conjunto de valores sobre el que se define el tipo

d) Cuáles son las propiedades que cumplen las operaciones del tipo

3. Dada la siguiente la especificación del TAD imagen, donde se indica el Dominio, las Operaciones

y una descripción en lenguaje natural de los Axiomas. Se pide implementar la interfaz de una clase

en C++ para este TAD.

Dominio: Una imagen en niveles de gris es una matriz de números enteros en el rango 0..255, de

un determinado tamaño (especificado por los atributos, número de filas nfilas y número de

columnas ncols), en la que cada elemento almacena el nivel de gris de un punto.

Operaciones : Las operaciones que se podrán realizar sobre una imagen son:

Imagen(entero, entero, entero) → Imagen AsignarImagen (Imagen) → Imagen NFilas() → entero NCols() → entero ObtenGris(Imagen, entero, entero) → entero PonGris(Imagen, entero, entero, entero) → Imagen

Axiomas :

Sea n, m, g, altura y anchura valores enteros, sea I una Imagen, sea f un archivo:

5. Dada la clase declarada en el ejercicio 4 se pide:

a) Declarar un objeto v de la clase VectorEnteros.

b) Declarar un array lista de la 5 objetos de la clase VectorEnteros.

c) Poner a cero todos los elementos del objeto v.

d) Escribir en pantalla el número de elementos de los 3 objetos del array lista.

e) Guardar el valor 5 en la posición 10 del objeto v.

f) Buscar el valor 15 en el primer elemento del array lista, sino lo encuentra buscarlo en el

segundo y si tampoco lo encuentra buscarlo en el tercero.