19
Juan Eduardo Licona Guevara | Estructura de Datos | 09 de enero de 2015 Introducción a las Estructuras de Datos

Introduccion a Las Estructuras de Datos

  • Upload
    eduardo

  • View
    225

  • Download
    2

Embed Size (px)

DESCRIPTION

introduccion a las estructuras de datos

Citation preview

Page 1: Introduccion a Las Estructuras de Datos

 |   | 

Introducción a las Estructuras de Datos

Page 2: Introduccion a Las Estructuras de Datos

Índice

1.1. Tipos de Datos Abstractos (TDA)……………………………………… 2

1.2. Modularidad (enfoque OO)……………………………………………….4

1.3. Uso d TDAs………………………………………………………………………..6

1.4. Manejo de Memoria Estática……………………………………………..9

1.5. Manejo de Memoria Dinámica………………………………………….11

Bibliografía…………………………………………………………………………….14

PÁGINA 1

Page 3: Introduccion a Las Estructuras de Datos

1.1. Tipos de Datos Abstractos (TDA)

En un lenguaje de programación, el tipo de datos de una variable está determinado por el conjunto de valores que dicha variable puede tomar y el conjunto de operaciones que puede realizar con variables del mencionado tipo (como argumentos y/o como resultado).

Una estructura de datos es una colección de variables (del mismo tipo o no), organizadas de alguna manera determinada. Se considera a la célula como la unidad básica de una estructura de datos.

Un tipo de dato abstracto queda determinado por el modelo matemático que lo sustenta y por un conjunto de operaciones que se definen sobre el mencionado modelo. En relación con un TDA se puede hablar de definirlo o de implementarlo. Aunque frecuentemente se confunden ambas expresiones, estas tienen significados perfectamente diferenciados.

Definir un TDA es dar el modelo y el conjunto de operaciones correspondientes, expresando con claridad y sin ambigüedad las características de cada una de ellas. Por ejemplo, podría definirse el TDA Numero Complejo de la siguiente manera:

- Modelo: par ordenado de números reales.- Conjunto de operaciones: suma, diferencia, producto, modulo, argumento,

leer_número, imprimir_número.- Descripción precisa de cada una de las operaciones.

La definición de un TDA debe ser clara y precisa, ya que por un lado está el implementador del TDA quien tomara dicha descripción como base y la seguirá fielmente, y por otro lado está el usuario del TDA que lo usara teniendo en cuenta también lo que indica la definición. Obviamente, para que luego el programa funcione correctamente, ambas cosas deben concordar.

Para implementar un TDA se debe encontrar una estructura de datos adecuada (o un TDA estructural) para representar el modelo subyacente del TDA, y se deben escribir los procedimientos (o funciones) que cumplirán al ejecutarse con las tareas propuestas por las operaciones.

Tipos básicos de operaciones con un TDA Constructores: Crean una nueva instancia del tipo. Transformación: Cambian el valor de uno o más elementos de una instancia

del tipo.

PÁGINA 2

Page 4: Introduccion a Las Estructuras de Datos

Observación: Nos permiten observar el valor de uno o más elementos de una instancia sin modificarlos.

Iteradores: Nos permiten procesar todos los componentes de un TDA de forma secuencial.

Tipos de Datos Abstractos

PÁGINA 3

Page 5: Introduccion a Las Estructuras de Datos

1.2. Modularidad (Enfoque OO)Modularidad en Ciencias de la computación es la característica por la cual un programa de computador está compuesto de porciones que se conocen como

PÁGINA 4

Page 6: Introduccion a Las Estructuras de Datos

módulos. El diseño estructurado es la técnica de diseño de algoritmos en que se basa la programación modular, paradigma de programación que persigue desarrollar programas modulares.

La modularidad se basa en la descomposición de un problema en una serie de sub problemas; dividiéndolo en módulos que resultan de segmentar el problema en funciones lógicas que son perfectamente diferenciadas, que tengan una cierta cohesión y que sean más tratables que el problema original. Esta división exige la presencia de un módulo denominado módulo de base o principal a objeto de que controle y se relacione con los demás.

Al conjunto de subprogramas que resuelve un subproblema se le llama módulo funcional.

Es una técnica de programación que todavía se utiliza tanto para la construcción de algoritmos computacionales básicos así como apoyo al desarrollo de sistemas de gestión (en el diseño de diagramas modulares).

La salida del módulo debe ser función de la entrada, pero no de ningún estado interno. En la creación de los módulos deben cumplirse tres aspectos básicos: descripción, rendimiento y diseño.

En la descripción se definen las funciones y objetivos del programa. Para obtener el máximo rendimiento se ha de comprobar que el programa realice el proceso aprovechando al máximo todos los recursos de los que dispone. En cuanto al diseño, se debe comprobar la estructura que sigue el módulo, así como la estructura de los datos y la forma de comunicaciones entre los diversos y diferentes módulos. 

Veamos como ejemplo el cálculo de la frecuencia de aparición de cada letra minúscula en un texto dado:

acción frecuencia (sal f: tabla_frec, ent s: sec de caracter)

{ Pre: La secuencia s está cerrada }

{ Post: f contiene la frecuencia de las letras minúsculas de s }

El módulo tendría adicionalmente una función auxiliar que determinaría si una letra es minúscula

• La estructura de datos tabla_frec estaría definida en otro módulo con las siguientes operaciones en el interfaz:

accion inicializar_cero(sal f: tabla_frec)

{Pre: Cierto }

{Post: Inicializamos la tabla de frecuencia a cero }

PÁGINA 5

Page 7: Introduccion a Las Estructuras de Datos

En este caso la representación es muy sencilla mediante una tabla de caracteres con rango 1..27

Si cambiamos levemente el problema con el fin de determinar la frecuencia de las palabras la estructura de datos se complica si queremos obtener una solución eficiente. El módulo principal se simplifica pero ahora tenemos dos estructuras de datos: una para las palabras y otra para almacenar la frecuencia de las palabras.

Una posible representación de la estructura para almacenar la frecuencia de las palabras sería mediante una lista ordenada de pares (palabra, frecuencia). Esta solución hace que la operación incrementar tenga un coste lineal.

Sintaxis abstracta del lenguaje de módulosVeamos ahora su sintaxis abstracta:

Modulo < nombre >

Usa < lista_modulos >

Ops < operaciones >

Implementación

< representacion >

< subprogramas >

fmodulo

El nombre del módulo coincide con el nuevo tipo que estamos definiendo;

usa < modulos >

Al incluir el nombre de un módulo o tad en esta lista nos permite utilizar cualquier elemento de su interfaz en el módulo que estamos definiendo.

< operaciones > Aquí definimos un conjunto de operaciones.

Toda operación que no aparezca en la cláusula ops pero esté definida dentro del módulo, no podrá ser importada por otro módulo.

< representacion > La representación consiste en una declaración de constantes y una declaración de tipos con la misma sintaxis que en notación algorítmica.

Uno de los tipos ha de tener el nombre del nombre del módulo.

< subprogramas > Aquí se definen las operaciones del módulo utilizando como parámetros formales la representación del nuevo tipo definido en el módulo.

Para cada operación, tenemos que definir su cabecera, a continuación su especificación indicando su precondición y pos condición y finalmente el código.

PÁGINA 6

Page 8: Introduccion a Las Estructuras de Datos

1.3 Uso de TDAUsar el TDA permite aprovechar el nivel de abstracción en el desarrollo de un problema.Por ejemplo: Resolver el problema de verificación si la suma y multiplicación de 2númeroscomplejos producen el mismo número complejo. Solución en pseudo lenguaje:INICIO // Programa principal X, Y COMPLEJOA BooleanoX = CREAR_COMPLEJO(3,-5) Y = CREAR_COMPLEJO(8,-3)A = VERIFICAR1(X,Y)Si A = verdadero entonces imprimir “Son iguales la suma y lamultiplicación”Sino imprimir “NO son iguales la suma y la multiplicación”FsiFINfunción VERIFICAR1 (X,Y: COMPLEJO): Booleano // Función Verificar1Z1,Z2 COMPLEJOZ1 = SUMAR (X,Y)Z2 = MULTIPLICAR (X,Y)RETORNAR IGUAL (Z1,Z2)f.funciónfunción VERIFICAR2 (X,Y: COMPLEJO): Booleano // Función Verificar2 RETORNAR IGUAL (SUMAR (X,Y), MULTIPLICAR (X,Y) )f.funciónSe provee al lector de otra versión función VERIFICAR2 que realiza la misma operación sobre los números complejos. Note que VERIFICAR1 no es una operación del TDA.

Ejemplo: TDA COMPLEJO cuyos objetos son los números complejos (parte real e imaginaria)

X = (X1, X2). Parte imaginaria es la raíz cuadra de un número negativo.

Especificación Sintáctica:

Tipos de operaciones:

• CONSTRUCTORAS: crear objetos del TDA, p.e : CREAR_COMPLEJO

• ACCESO O ANALIZADORAS: permite obtener componentes del TDA como resultado,

p.e: PARTE_REAL.

• TRANSFORMACIÓN O SIMPLIFICADORAS: cambios al TDA y retornan un objeto del TDA

PÁGINA 7

Page 9: Introduccion a Las Estructuras de Datos

como resultado, p.e: SUMAR.

Especificación Semántica:

función SUMAR (X,Y: COMPLEJO): COMPLEJO

Pre-Cond: X,Y son del tipo complejo

Si X = (X1,X2) y Y = (Y1,Y2) entonces:

Z = (Z1,Z2) donde Z1 = X1 + Y1 y Z2 = X2 + Y2

Post-Cond: Z es del tipo complejo, Z = X + Y

finfunción

función MULTIPLICAR (X,Y: COMPLEJO): COMPLEJO

Pre-Cond: X,Y son del tipo complejo

Si X = (X1,X2) y Y =(Y1,Y2) entonces:

Z = (Z1,Z2) tal que Z1 = X1 * Y1 – X2 * Y2

Y Z2 = X1 * Y2 + X2 * Y1

Post-Cond: Z es complejo, Z = X * Y

finfunción

función IGUAL (X,Y: COMPLEJO): Booleano

Pre-Cond: X,Y son del tipo complejo

Si X=(X1,X2) y Y=(Y1,Y2) entonces

Si X1 = Y1 y X2 = Y2 entonces Resultado = Verdadero.

Sino Resultado = Falso

Post-Cond: Resultado de comparar (X = Y)

finfunción

función PARTE_REAL (X:COMPLEJO): Real Pre-Cond: X es del tipo complejo

Post-Cond: Si X = (X1,X2) entonces Resultado = X1

finfunción

función CREAR_COMPLEJO (X1,X2: Real): COMPLEJO

Pre-Cond: X1 y X2 son del tipo Real.

Post-Cond: X = (X1,X2) resultado X es COMPLEJO

Finfunción

PÁGINA 8

Page 10: Introduccion a Las Estructuras de Datos

Ejemplo de uso de un TDA Usar el TDA permite aprovechar el nivel de abstracción en el desarrollo de un problema.

Por ejemplo: Resolver el problema de verificación si la suma y multiplicación de 2 números complejos producen el mismo número complejo. Solución en pseudo lenguaje:

INICIO // Programa principal

X, Y COMPLEJO

A Booleano

X = CREAR_COMPLEJO(3,-5)

Y = CREAR_COMPLEJO(8,-3)

A = VERIFICAR1(X,Y)

Si A = verdadero entonces imprimir “Son iguales la suma y la multiplicación”

Sino imprimir “NO son iguales la suma y la multiplicación”

Fsi

FIN

función VERIFICAR1 (X,Y: COMPLEJO): Booleano // Función Verificar1

Z1,Z2 COMPLEJO

Z1 = SUMAR (X,Y)

Z2 = MULTIPLICAR (X,Y)

RETORNAR IGUAL (Z1,Z2)

f.función

función VERIFICAR2 (X,Y: COMPLEJO): Booleano // Función Verificar2

RETORNAR IGUAL (SUMAR (X,Y), MULTIPLICAR (X,Y) )

f.función

Se provee al lector de otra versión función VERIFICAR2 que realiza la misma operación sobre los números complejos. Note que VERIFICAR1 no es una operación del TDA, por qué?

Ventajas de uso de un TDA• Herramienta para resolver problemas (nivel de abstracción)

• Independencia entre la forma de representar la información y la solución del problema Æ portabilidad de la solución. • Favorece la especificación, verificación y depuración de programas.

PÁGINA 9

Page 11: Introduccion a Las Estructuras de Datos

• Contribuye a la flexibilidad de los cambios.

PÁGINA 10

Page 12: Introduccion a Las Estructuras de Datos

1.4 Manejo de Memoria EstáticaLa administración de memoria de una computadora es una tarea fundamental debido a que la cantidad de memoria es limitada. El sistema operativo es el encargado de administrar la memoria del sistema y compartirla entre distintos usuarios y/o aplicaciones.

El RTS (Run Time System) de un lenguaje de programación administra la memoria

para cada programa en ejecución.

La ejecución de un programa requiere que diversos elementos se almacenen en la memoria:

o Código del programa (instrucciones)o Datoso Permanenteso Temporaleso Direcciones para controlar de flujo la ejecución del programa

A la asignación de memoria para algunos elementos fijos del programa que es controlada por el compilador se le llama asignación de memoria estática.

La memoria estática define la cantidad de memoria necesaria para un programa durante el tiempo de compilación. El tamaño no puede cambiar durante el tiempo de ejecución del programa. Algunos lenguajes de programación utilizan la palabra static para especificar elementos del programa que deben almacenarse en memoria estática.

Elementos que residen en memoria estática. Código del programa

PÁGINA 11

Page 13: Introduccion a Las Estructuras de Datos

Las variables definidas en la sección principal del programa, las cuales pueden solo cambiar su contenido no su tamaño.

Todas aquellas variables declaradas como estáticas en otras clases o módulos.

Estos elementos se almacenan en direcciones fijas que son relocalizadas dependiendo de la dirección en donde el cargador las coloque para su ejecución.

Método común de asignación de memoria.

Un mapa de memoria (del inglés memory map) es una estructura de datos (tablas) que indica cómo está distribuida la memoria. Contiene información sobre el tamaño total de memoria y las relaciones que existen entre direcciones lógicas y físicas, además de poder proveer otros detalles específicos sobre la arquitectura de la computadora.

El stack de ejecuciónCada subprograma (procedimiento, función, método, etc.) requiere una representación de si en tiempo de ejecución.

Estas representaciones se almacenan en el stack de ejecución con el fin de controlar el flujo de ejecución del programa.

PÁGINA 12

Page 14: Introduccion a Las Estructuras de Datos

1.5 Manejo de Memoria Dinámica Su tamaño y forma es variable (o puede serlo) a lo largo de un programa, por lo que se crean y destruyen en tiempo de ejecución. Esto permite dimensionar la estructura de datos de una forma precisa: se va asignando memoria en tiempo de ejecución según se va necesitando.

Cuando el sistema operativo carga un programa para ejecutarlo y lo convierte en proceso, le asigna cuatro partes lógicas en memoria principal: texto, datos (estáticos), pila y una zona libre. Esta zona libre (o heap) es la que va a contener los datos dinámicos, la cual, a su vez, en cada instante de la ejecución tendrá partes asignadas a los mismos y partes libres que

PÁGINA 13

Page 15: Introduccion a Las Estructuras de Datos

fragmentaran esta zona, siendo posible que se agote si no se liberan las partes utilizadas ya inservibles. (La pila también varía su tamaño dinámicamente, pero la gestiona el sistema operativo, no el programador):

Para trabajar con datos dinámicos necesitamos dos cosas:

1. Subprogramas predefinidos en el lenguaje que nos permitan gestionar la memoria de forma dinámica (asignación y liberación).

2. Algún tipo de dato con el que podamos acceder a esos datos dinámicos (ya que con los tipos vistos hasta ahora solo podemos acceder a datos con un tamaño y forma ya determinados).

Supongamos que nuestro programa debe manipular estructuras de datos de longitud desconocida. Un ejemplo simple podría ser el de un programa que lee las líneas de un archivo y las ordena. Por tanto, deberemos leer un número indeterminado de líneas, y tras leer la última, ordenarlas. Una manera de manejar ese ``número indeterminado’‘, sería declarar una constante MAX_LINEAS, darle un valor vergonzosamente grande, y declarar un array de tamaño MAX_LINEAS. Esto, obviamente, es muy ineficiente (y feo). Nuestro programa no sólo quedaría limitado por ese valor máximo, sino que además gastaría esa enorme cantidad de memoria para procesar hasta el más pequeño de los ficheros.

La solución consiste en utilizar memoria dinámica. La memoria dinámica es un espacio de almacenamiento que se solicita en tiempo de ejecución. De esa manera, a medida que el proceso va necesitando espacio para más líneas, va solicitando más memoria al sistema operativo para guardarlas. El medio para manejar la memoria que otorga el sistema operativo, es el puntero, puesto que no podemos saber en tiempo de compilación dónde nos dará huecos el sistema operativo (en la memoria de nuestro PC).

Sobre el tratamiento de memoria, G Lib dispone de una serie de instrucciones que sustituyen a las ya conocidas por todos malloc, free, etc. y, siguiendo con el modo de llamar a las funciones en G Lib, las funciones que sustituyen a las ya mencionadas son g_malloc y g_free.

Reserva de memoria.La función g_malloc posibilita la reserva de una zona de memoria, con un número de bytes que le pasemos como parámetro. Además, también existe una función similar llamada g_malloc0 que, no sólo reserva una zona de memoria, sino que, además, llena esa zona de memoria con ceros, lo cual nos puede beneficiar si se necesita una zona de memoria totalmente limpia.

gpointer g_malloc (gulong numero_de_bytes);

gpointer g_malloc0 (gulong numero_de_bytes );

Existe otro conjunto de funciones que nos permiten reservar memoria de una forma parecida a cómo se hace en los lenguajes orientados a objetos.

Liberación de memoria.

PÁGINA 14

Page 16: Introduccion a Las Estructuras de Datos

Cuando se hace una reserva de memoria con g_malloc y, en un momento dado, el uso de esa memoria no tiene sentido, es el momento de liberar esa memoria. Y el sustituto de free es g_free que, básicamente, funciona igual que la anteriormente mencionada.

void g_free (gpointer memoria_reservada );

Realojamiento de memoriaEn determinadas ocasiones, sobre todo cuando se utilizan estructuras de datos dinámicas, es necesario ajustar el tamaño de una zona de memoria (ya sea para hacerla más grande o más pequeña). Para eso, G Lib ofrece la función g_realloc, que recibe un puntero a memoria que apunta a una región que es la que será acomodada al nuevo tamaño y devuelve el puntero a la nueva zona de memoria. El anterior puntero es liberado y no se debería utilizar más:

gpointer g_realloc (gpointer memoria_reservada , gulong numero_de_bytes);

Asignación dinámicaEl proceso de compactación del punto anterior es una instancia particular del problema de asignación de memoria dinámica, el cual es el cómo satisfacer una necesidad de tamaño n con una lista de huecos libres. Existen muchas soluciones para el problema. El conjunto de huecos es analizado para determinar cuál hueco es el más indicado para asignarse. Las estrategias más comunes para asignar algún hueco de la tabla son:

Primer ajuste: Consiste en asignar el primer hueco con capacidad suficiente. La búsqueda puede iniciar ya sea al inicio o al final del conjunto de huecos o en donde terminó la última búsqueda. La búsqueda termina al encontrar un hueco lo suficientemente grande.

Mejor ajuste: Busca asignar el espacio más pequeño de los espacios con capacidad suficiente. La búsqueda se debe de realizar en toda la tabla, a menos que la tabla esté ordenada por tamaño. Esta estrategia produce el menor desperdicio de memoria posible.

Peor ajuste: Asigna el hueco más grande. Una vez más, se debe de buscar en toda la tabla de huecos a menos que esté organizada por tamaño.

Esta estrategia produce los huecos de sobra más grandes, los cuales pudieran ser de más uso si llegan procesos de tamaño mediano que quepan en ellos.

Se ha demostrado mediante simulacros que tanto el primer y el mejor ajuste son mejores que el peor ajuste en cuanto a minimizar tanto el tiempo del almacenamiento. Ni el primer o el mejor ajuste es claramente el mejor en términos de uso de espacio, pero por lo general el primer ajuste es más rápido.

PÁGINA 15

Page 17: Introduccion a Las Estructuras de Datos

Tipo punteroLas variables de tipo puntero son las que nos permiten referenciar datos dinámicos.

Tenemos que diferenciar claramente entre:

1. la variable referencia o apuntadora, de tipo puntero;

2. la variable anónima referenciada o apuntada, de cualquier tipo, tipo que estar´a asociado siempre al puntero.

Físicamente, un puntero no es más que una dirección de memoria. En el siguiente ejemplo se muestra el contenido de la memoria con un puntero que apunta a la dirección 78AC (16, la cual contiene 6677(16:

PÁGINA 16

Page 18: Introduccion a Las Estructuras de Datos

Bibliografía1.1

http://ldc.usb.ve/~gabro/teaching/CI2126/TADPilaLista.pdf

http://cs.uns.edu.ar/~ldm/data/eda/apuntes/01_td-ed-tda.pdf

1.2

http://www.cs.upc.edu/~pred/tads2.pdf

http://www.scoop.it/t/estructura-de-datos

1.3

http://webdelprofesor.ula.ve/ingenieria/hyelitza/materias/programacion2/tda/PR2-tema1-TDA.pdf

http://www.scoop.it/t/estructura-de-datos

1.4

https://ingenieriaticelaya.files.wordpress.com/2014/09/manejo-de-memoria.pdf

http://www.programacionfacil.com/estructura_de_datos:manejo_de_memoria

1.5

http://estructura-de-datos-itsav.blogspot.mx/2012/03/22-manejo-de-memoria-dinamica.html

http://www.lcc.uma.es/~lopez/lp2/apuntes/03-punteros/memoria_dinamica.pdf

PÁGINA 17