Está en la página 1de 65

Tecnológico de

Estudios
ESTRUCTURA DE DATOS
Superiores de
Elaboró:
Ecatepec LIC. Dora Araceli Cruz Huerta

2011

Departamento
de
Licenciatura en Informática
Av. Valle del Mayo Esq. Av. Hank González Col. Valle de Anáhuac C.P. 55210 Ecatepec de Morelos, Edo. de
México

Tecnológico de Estudios Superiores de Ecatepec


Dirección Académica
Subdirección de Estudios Profesionales
Licenciatura en Informática
APUNTES DE LA ASIGNATURA
ESTRUCTURA DE DATOS

Por:

Lic. Cruz Huerta Dora Araceli.

OBJETIVO:

El alumno aprenderá las principales estructuras de datos desde un punto de


vista abstracto y las operaciones que se puedan realizar sobre ellas, aplicando
en forma práctica los conceptos adquiridos mediante resolución de problemas.

TEMARIO

1.- TIPOS DE DATOS

1.1 Tipos de datos.

2
1.1.1 Tipos de datos simples.
1.1.1.1 Definición de bit, byte, carácter y palabra.
1.1.1.2 Manipulación de bits.
1.1.1.3 Representación de datos simples.
1.1.2 Tipos de datos abstractos.
1.2 Estructuras de datos.
1.2.1 Definición.
1.2.2 Clasificación.
1.2.2.1 Lineales y no lineales.
1.2.2.2 Dinámicas y estáticas.

2.- ESTRUCTURAS LINEALES

2.1 Arreglos.
2.1.1 Definición.
2.1.2 Unidimensionales.
2.1.3 Bidimensionales.
2.1.4 Multidimensionales.
2.1.5 Resolución de problemas con arreglos.
2.1.6 Clases para la implementación de arreglos.
2.2 Pilas.
2.2.1 Definición.
2.2.2 Operaciones.
2.2.3 Clases para la implementación de pilas.
2.3 Colas.
2.3.1 Definición.
2.3.2 Tipos.
2.3.2.1 Colas simples.
2.3.2.2 Colas circulares.
2.3.2.3 Colas dobles.
2.3.3 Operaciones.
2.3.4 Clases para la implementación de colas.

3.- LISTAS ENLAZADAS

3.1 Listas enlazadas.


3.1.1 Simples.
3.1.2 Dobles.
3.1.3 Circulares.
3.1.4 Multilistas.
3.1.5 Clases para la implementación de listas.

4.- ESTRUCTURAS NO LINEALES

3
4.1 Árboles.
4.1.1 Definición.
4.1.2 Representación en memoria de árboles.
4.1.2.1 Árboles generales.
4.1.2.2 Árboles binarios.
4.1.3 Recorridos en un árbol binario.
4.1.3.1 Preorden.
4.1.3.2 Inorden.
4.1.3.3 Posorden.
4.1.4 Balanceo de árboles binarios.
4.1.5 Clases para la implementación de árboles.
4.2 Grafos.
4.2.1 Definición.
4.2.2 Tipos de grafos.
4.2.3 Representación de grafos en memoria.
4.2.4 Clases para la implementación de grafos.

UNIDAD1 TIPOS DE DATOS

1.1 Tipos de datos.

El manejo de la información en cualquier lenguaje de programación se realiza


mediante diferentes clases de datos.

Entero
Números enteros sin parte decimal.
(Integer)

4
Carácter (Char) Caracteres del código ASCII
Boleano
Pueden contener los valores de falso o verdadero
(Boolean)
Real Números que pueden incluir una parte decimal
En una secuencia de caracteres que se trata como un solo
Cadena (String)
dato.

Un programa debe ser capaz de manejar diferentes tipos de datos, como


pueden ser números enteros, reales, caracteres, cadenas de caracteres, etc.
Para lograr el manejo de toda esta información. Algunos de los más
importantes se citan en seguida:

Tipos enteros

En esta categoría generalmente cuenta con 5 tipos diferentes, cada uno abarca
un rango específico de valores y utilizan una diferente cantidad de memoria
dependiendo de ese rango. Naturalmente el trabajar con rangos menores nos
ofrece una mayor velocidad y menor espacio en memoria, pero si se utilizan
enteros largos se cuenta con mayor presición. Los tipos de enteros en son:

Tipo Rango de valores que acepta


Integer (Entero) -32,768 a 32,767
Word (Palabra) 0 a 65535
ShortInt (Entero corto) -128 a 127
Byte 0 a 255
LongInt (Entero largo) -2,147,483,648 a 2,147,483,648

Al utilizar los tipos enteros es posible representar en el programa un número en


formato hexadecimal, para hacer esto solo se le antepone el símbolo "$" al
valor hexadecimal, al momento de visualizar dicho valor, o utilizarlo en alguna
operación será como decimal casi siempre en todos los casos que se utilice.

Tipos reales

Los números reales son aquellos que cuentan con una parte decimal. En
algunos lenguajes de programación se tienen varios tipos de datos reales, pero
no se puede utilizar, más que el tipo real, en máquinas que no cuenten con un
coprocesador matemático. Los tipos de datos reales son:

Tipo Rango de valores que acepta


Real ) 2.9E-39 a 1.7E38
Single 1.5E-45 a 3.4E38

5
Double 5.0E-324 a 1.7E308
Extended 1.9E-4851 a 1.1E4932
Comp -9.2E18 a 9.2E18

Los números reales deben llevar por fuerza al menos un dígito de cada lado del
punto decimal así sea éste un cero. Como ejemplo, el número 5 debe
representarse como: 5.0, el .5 como 0.5 , etc.

En este tipo de datos se utiliza la notación científica, que es igual a la de las


calculadoras, el dígito que se encuentra a continuación de la E representa la
potencia a la que se elevará el número 10 para multiplicarlo por la cantidad a la
izquierda de dicha E:

3.0E5 = 3.0 * 10^5 = 3.0 * 100000 = 300000


1.5E-4 = 1.5 * 10^-4 = 1.5 * 0.0001 = 0.00015

Tipos carácter

Los caracteres son cada uno de los símbolos que forman el código ASCII.. Los
caracteres se especifican entre apostrofes:

'a' </TD 'B' </TD '2' '#'

El tipo Char es un tipo ordinal en algunos lenguajes de programación, esto


quiere decir que sus elementos válidos siguen una secuencia ordenada de
valores individuales. La secuencia de caracteres para este tipo corresponde al
número del código ASCII, del 0 al 255.

Es posible accesar a cada uno de los caracteres utilizando un signo # antes de


su valor correspondiente, por ejemplo, la letra A puede ser representada como
#65, el retorno de carro, o enter, se representa como #13, y así cualquier
carácter.

Tipo cadena

6
Las cadenas son secuencias de caracteres o arreglos que tienen una longitud
máxima de 255 caracteres. Se definen entre apostrofes.

Nombre : Cadena;

Nombre = 'Ernesto Chávez';

La cadena 'Ernesto Chávez' es almacenada en la variable nombre definida


como tipo cadena.

El tamaño por defecto para un tipo string es de 255 caracteres, pero es posible
definir uno mas pequeño utilizando el siguiente formato:

Variable : Cadena[Tamaño];

Donde Variable es la variable a definir y Tamaño es el número máximo de


caracteres que podrá contener esa variable (naturalmente mayor a 0 y menor a
256).

Es posible acceder a un solo carácter de una cadena utilizando


inmediatamente después del nombre de la misma la posición del carácter
encerrada entre corchetes. Por ejemplo:

Nombre : String[30];
{Permite un máximo de 30 caracteres en la variable}

Nombre := 'Ernesto Chávez';

Escribir (Nombre[5]);
{Visualiza el 5to carácter de la cadena}

Tipos lógicos

Este tipo de datos tienen la peculiaridad de que solo pueden tomar dos tipos de
datos: verdadero o falso, el verdadero puede ser representado por su nombre
en inglés: True y el falso por False; también se representan por 1 y por 0
respectivamente.

El tipo está definido como Boolean.

7
Los datos lógicos tienen una enorme aplicación en la evaluación de ciertos
procesos, así como en el control de flujo de los programas.

1.1.1 Tipos de datos simples.

Los datos a procesar por una computadora se pueden clasificar en:

• Simples
• Estructurados

La principal característica de los datos simples es que ocupan solo una casilla
de memoria, por lo tanto una variable simple hace referencia a un único valor a
la vez. Dentro de este grupo de datos se encuentran: enteros, reales, carácter,
boleanos, enumerados y subrrango (los dos últimos no existen en algunos
lenguajes de programación).

Los datos estructurados se caracterizan por el hecho de que con un nombre


(Identificador de variable) se hace referencia a un grupo de casillas de
memoria). Es decir, un dato estructurado tiene varios componentes. Cada uno
de los componentes puede ser un dato simple o estructurado. Sin embargo los
componentes básicos de cualquier dato estructurado son datos simples.

Todas las estructuras de datos constituyen un aspecto muy importante en la


computación, estas son:

• Arreglos.
• Registros.
• conjuntos.

1.1.1.1 Definición de bit, byte, carácter y palabra.

Bit: es una síntesis de dos términos en inglés: Binary digit, que en español
significan dígito binario, o lo que es lo mismo, número (dígito) con dos posibles
valores (binario). El término surge de usar las dos primeras letras de Binary con
la última de digit.: bit. Es la unidad de información más sencilla posible en el
sistema binario.

8
Byte: Unidad de información que consta de 8 bits equivalente a un único
carácter, como una letra, número o signo de puntuación.

Carácter: Es un elemento tomado de un conjunto de símbolos. Un ejemplo de


un conjunto de símbolos es {0,1,2,3,4,5,6,7,8,9,A,B,C....Y,z,¡,-,+,*} en el cual se
incluyen dígitos, los caracteres del alfabeto y algunos caracteres especiales.
Un compilador de lenguaje reconoce un conjunto particular de caracteres.

Palabra: Conjunto de bits que, como unidad elemental, puede manipular una
computadora. La longitud en bits de una palabra en una computadora puede
ser de 8, 16, 32, etc., y depende del microprocesador de su unidad central de
proceso.

1.1.1.2 Manipulación de bits.

Ahora el ser humano digitaliza su entorno. Pero, ¿qué significa digitalizar?


Digitalizar es traducir información como textos, imágenes o sonidos, a un
formato que puedan entender los microprocesadores, y éstos sólo están
capacitados para manejar los valores unos y ceros. En efecto, para tu
microprocesador todo lo que ves en estos momentos en la pantalla se maneja
con unos o ceros. Esto es porque la computadora maneja un sistema binario,
que se llama así porque sólo acepta dos valores (0 y 1).

Tal sencillez tiene su razón de ser: los microprocesadores son circuitos


electrónicos plasmados en un material llamado silicio (algo parecido al vidrio)
que procesan diminutos impulsos eléctricos, el más pequeño de los cuales es
conocido por el nombre de bit.

Como impulso eléctrico, el microprocesador sólo puede detectar cuando un bit


tiene carga eléctrica --su valor sería, en este caso, 1-- o cuando no la tienen
--su valor sería 0 - En este ejemplo manejamos los valores unos y ceros de
manera un tanto arbitraria, ya que la presencia o ausencia de carga eléctrica en
un bit puede ser interpretada como una gran diversidad de valores: cierto y
falso, hombre o mujer, T o J, etc.

La eficacia de las computadoras no se basa en la complejidad de su


fundamento lógico, que como vimos se reduce a manejar dos posibles valores,
sino de la velocidad con la que se aplica dicha lógica: los microprocesadores
actuales pueden procesar varios millones de bits en un sólo segundo.

Un bit puede representar solamente dos valores. Dos bits, cuatro posibles
valores y ocho bits 256 posibles combinaciones de unos y ceros. Una unidad
de medida muy utilizada en la informática es el byte, que consiste en la
agrupación de ocho bits.

Ejemplo de combinaciones posibles por número de bits

Posibles combinaciones de unos y ceros usando dos bits 4:

9
00, 01, 11, 10

Posibles combinaciones de unos y ceros usando ocho bits 256:

00000000, 00000001, 00000011, 00000111 […] 11111111

Usando grupos de 8 bits (es decir, bytes) es posible representar a todos los
caracteres que conforman el abecedario, incluyendo las mayúsculas y los
signos especiales, como el de moneda o los acentos, de tal suerte que cuando
se oprime la "e" en el teclado, el microprocesador recibe un paquete de 8 bits
con la siguiente combinación de valores:

Valor de la letra "e" minúscula en bits:

0 1100101

Pero si en cambio se presiona la misma tecla en mayúsculas, el paquete de


bits que se estará mandando al microprocesador sería el siguiente:
Valor de la letra "E" mayúscula en bits:

0 1000101

Mediante combinaciones de bits y bytes es posible representar una cantidad


infinita de cosas: desde bibliotecas completas hasta juegos y películas, todo un
universo de información que puede estar en diversas formas; textos, imágenes
y sonidos.

Se dice que algunas computadoras tienen aritmética decimal en lugar de


binaria. Cuatro bits proporcionan 16 combinaciones, utilizadas para los 10
dígitos de 0 a 9, con 6 combinaciones sin utilizar. El número 1944 se muestra
abajo codificado en decimal y en binario, utilizando 16 bits en cada ejemplo:

• Decimal: 0001 1001 0100 0100

• binario: 0000011110011000

1.1.1.3 Representación de datos simples.

Los datos que utilizan los programas se pueden clasificar en base a diferentes
criterios. Uno de los más significativos es aquel que dice que todos los datos
que utilizan los programas son simples o compuestos.

Un dato simple es indivisible (atómico), es decir, no se puede descomponer.

10
• Ejemplo 1: Un año es un dato simple.

• Año...: 2006

Un año se expresa con un número entero, el cual no se puede descomponer.


Sin embargo, un dato compuesto está formado por otros datos.

• Ejemplo 2: Una fecha es un dato compuesto por tres datos simples (día,
mes, año).

• Fecha:
• Día...: 30
• Mes...: 11
• Año...: 2006

• Ejemplo 3: Otro ejemplo de dato simple es una letra.

• Letra...: t

Una letra se representa con un carácter del alfabeto. Pero, cuando varias letras
se agrupan, entonces se obtiene un dato compuesto por varios caracteres.

• Ejemplo 4: Para formar un nombre de persona se utilizan varios


caracteres.

• Nombre...: Ana (dato compuesto por tres caracteres)

Clasificación de los tipos de datos simples

Los tipos de datos simples se clasifican en predefinidos y definidos por el


programador. La clasificación completa es:

11
Figura. Clasificación de los tipos de datos simples en pseudocódigo.

Los tipos de datos simples predefinidos (estándares) son aquellos


proporcionados por los lenguajes de programación. Pero, el programador
también puede definir sus propios tipos de datos simples (subrangos y
enumerados), los cuales se estudiarán más adelante.

Todos los datos simples son ordinales, excepto el dato de tipo real. Un dato
ordinal es aquel que puede tomar por valor un elemento perteneciente a un
conjunto en el que todo elemento tiene un predecesor y un sucesor, excepto el
primero y el último. Por ejemplo, el valor 5, perteneciente al conjunto de los
números enteros, tiene como predecesor al 4, y como sucesor al 6. Sin
embargo, entre dos números reales siempre hay un número infinito de
números.

1.1.2 Tipos de datos abstractos.

Un tipo de dato abstracto o TDA (también Tipo abstracto de datos o TAD)


es un modelo matemático compuesto por una colección de operaciones
definidas sobre un conjunto de datos para el modelo.

> Introducción

En el mundo de la programación existen diversos lenguajes que se han ido


creando con el paso del tiempo y que se han perfeccionado debido a las
necesidades de los programadores de la época a la que pertenecen. Los
primeros lenguajes de programación eran de tipo lineales, ya que un programa

12
se recorría desde un punto marcado como Inicio hasta llegar a un punto Fin.
Con el tiempo se fueron creando nuevos lenguajes y en nuestros días los más
utilizados son los llamados “Orientados a Objetos”.

Los Lenguajes Orientados a Objetos (LOO) tienen la característica de que no


son lenguajes lineales, sino que se forman de diversas funciones, las cuales
son llamadas en el orden en que el programa mismo las pide o el usuario
determina. Para entender mejor cómo funcionan los Lenguajes Orientados a
Objetos, vamos a introducir un concepto fundamental en las Estructuras de
Datos denominado Abstracción de Datos y que es parte importante de estos
Lenguajes y de la manera en que funciona la mayoría del software comercial
de nuestros días.

Historia

El concepto de tipo de dato abstracto (TDA, Abstract Data Types ), fue


propuesto por primera vez hacia 1974 por John Guttag y otros, pero no fue
hasta 1975 que por primera vez Liskov lo propuso para el lenguaje CLU.llina

Definición

Con mucha frecuencia se utilizan los términos “TDA” y “Abstracción de Datos”


de manera equivalente, y esto es debido a la similitud e interdependencia de
ambos. Sin embargo, es importante definir por separado los dos conceptos.

Como ya se mencionó, los Lenguajes de Programación Orientados a Objetos


son lenguajes formados por diferentes métodos o funciones y que son llamados
en el orden en que el programa lo requiere, o el usuario lo desea. La
abstracción de datos consiste en ocultar las características de un objeto y
obviarlas, de manera que solamente utilizamos el nombre del objeto en nuestro
programa. Esto es similar a una situación de la vida cotidiana. Cuando yo digo
la palabra “perro”, usted no necesita que yo le diga lo que hace el perro. Usted
ya sabe la forma que tiene un perro y también sabe que los perros ladran. De
manera que yo abstraigo todas las características de todos los perros en un
solo término, al cual llamo “perro”. A esto se le llama ‘Abstracción’ y es un
concepto muy útil en la programación, ya que un usuario no necesita
mencionar todas las características y funciones de un objeto cada vez que éste
se utiliza, sino que son declaradas por separado en el programa y simplemente
se utiliza el término abstracto (“perro”) para mencionarlo.

En el ejemplo anterior, “perro” es un Tipo de Dato Abstracto y todo el proceso


de definirlo, implementarlo y mencionarlo es a lo que llamamos Abstracción de
Datos.
Vamos a poner un ejemplo real de la programación. Supongamos que en algún
Lenguaje de Programación Orientado a Objetos un pequeño programa saca el
área de un rectángulo de las dimensiones que un usuario decida. Pensemos
también que el usuario probablemente quiera saber el área de varios
rectángulos. Sería muy tedioso para el programador definir la multiplicación de
‘base’ por ‘altura’ varias veces en el programa, además que limitaría al usuario
a sacar un número determinado de áreas.

13
Por ello, el programador puede crear una función denominada ‘Área’, la cual va
a ser llamada el número de veces que sean necesitadas por el usuario y así el
programador se evita mucho trabajo, el programa resulta más rápido, más
eficiente y de menor longitud. Para lograr esto, se crea el método Área de una
manera separada de la interfaz gráfica presentada al usuario y se estipula ahí
la operación a realizar, devolviendo el valor de la multiplicación. En el método
principal solamente se llama a la función Área y el programa hace el resto.

Al hecho de guardar todas las características y habilidades de un objeto por


separado se le llama Encapsulamiento y es también un concepto importante
para entender la estructuración de datos.

Caracterización

Un TDA está caracterizado por un conjunto de operaciones (funciones) al cual


le denominaron usualmente como su interfaz pública y representan el
comportamiento del TDA; mientras que la implementación como la parte
privada del TDA está oculta al programa cliente que lo usa. Todos los lenguajes
de alto nivel tienen predefinidos TDA; que son los tipos denominados simples y
las estructuras predefinidas, y estos tienen sus interfaces públicas que incluyen
las operaciones como la +, -, *, etc.

En un TDA no se necesita conocer como actúan tales operadores sobre la


representación interna de los tipos definidos, que además, suele ser una
implementación bastante dependiente de la máquina sobre la que trabaje el
compilador. Lo interesante es que los lenguajes actuales nos van a permitir
ampliar los TDA predefinidos con otros que serán definidos por el propio
programador para adecuar así los tipos de datos a las necesidades de los
programas.

Los TDA que nos van a interesar de ahora en adelante son aquellos que
reflejen cierto comportamiento organizando cierta variedad de datos
estructuradamente. A esta forma estructurada de almacenar los datos será a la
que nos refiramos para caracterizar cada TDA.

Los TDA que tienen informaciones simples pero dependientes de un


comportamiento estructural serán llamados polilíticos y aquellos TDA simples,
como son los tipos predefinidos donde la información no es relacionada
mediante ninguna estructura y no admiten más que un valor en cada momento
será denominados TDA monolíticos.

Nótese que cuando hablemos de un TDA no haremos ninguna alusión al tipo


de los elementos sino tan sólo a la forma en que están dispuestos estos
elementos. Sólo nos interesa la estructura que soporta la información y sus
operaciones. Para determinar el comportamiento estructural basta con observar
la conducta que seguirán los datos.

Caractericemos entonces los TDA. Un TDA tendrá una parte que será invisible
al usuario la cual hay que proteger y que se puede decir que es irrelevante para

14
el uso del usuario y está constituida tanto por la maquinaria algorítmica que
implemente la semántica de las operaciones como por los datos que sirvan de
enlace entre los elementos del TDA, es decir, información interna necesaria
para la implementación que se esté haciendo para ese comportamiento del
TDA. Resumiendo podemos decir, que tanto la implementación de las
operaciones como los elementos internos del TDA serán privados al acceso
externo y ocultos a cualquier otro nivel.

Un TDA representa una abstracción:

• Se destacan los detalles (normalmente pocos) de la especificación (el


qué).
• Se ocultan los detalles (casi siempre numerosos) de la implementación
(el cómo).

La abstracción

La abstracción, una de las herramientas que más nos ayuda a la hora de


solucionar un problema, es un mecanismo fundamental para la comprensión de
problemas y fenómenos que poseen una gran cantidad de detalles, su idea
principal consiste en manejar un problema, fenómeno, objeto, tema o idea
como un concepto general, sin considerar la gran cantidad de detalles que
estos puedan tener. El proceso de abstracción presenta dos aspectos
complementarios.

1.- Destacar los aspectos relevantes del objeto.


2.- Ignorar los aspectos irrelevantes del mismo (la irrelevancia depende del
nivel de abstracción, ya que si se pasa a niveles más concretos, es posible que
ciertos aspectos pasen a ser relevantes).

De modo general podemos decir que la abstracción permite establecer un nivel


jerárquico en el estudio de los fenómenos, el cual se establece por niveles
sucesivos de detalles. Generalmente, se sigue un sentido descendente de
detalles, desde los niveles más generales a los niveles más concretos.

Por ejemplo: los lenguajes de programación de alto nivel permiten al


programador abstraerse del sin fin de detalles de los lenguajes ensambladores.
Otro ejemplo, la memoria de la computadora es una estructura unidimensional
formada por celdas y sin embargo trabajamos como si fuera única.

La abstracción nos brinda la posibilidad de ir definiendo una serie de


refinamientos sucesivos a nuestro TDA y entiéndase bien que cuando decimos
refinamientos sucesivos nos estamos refiriendo a la estrategia que se utiliza
para descomponer un problema en subproblemas. Conforme evoluciona el
diseño de software a cada nivel de módulos se representa un refinamiento en
el nivel de abstracción. Esto es, incluir detalles que fueron obviados en un nivel
superior, en un nivel más bajo de la jerarquía.

15
Veamos los diferentes tipos de abstracción que podemos encontrar en un
programa:

1. Abstracción funcional: crear procedimientos y funciones e invocarlos


mediante un nombre donde se destaca qué hace la función y se ignora cómo lo
hace. El usuario sólo necesita conocer la especificación de la abstracción (el
qué) y puede ignorar el resto de los detalles (el cómo).

2. Abstracción de datos:

• Tipo de datos: proporcionado por los leguajes de alto nivel. La


representación usada es invisible al programador, al cual solo se le
permite ver las operaciones predefinidas para cada tipo.
• Tipos definidos: por el programador que posibilitan la definición de
valores de datos más cercanos al problema que se pretende resolver.
• TDA: para la definición y representación de tipos de datos (valores +
operaciones), junto con sus propiedades.
• Objetos: Son TDA a los que se añade propiedades de reutilización y de
compartición de código.

Si profundizamos más al mundo de la programación y sus conceptos, existen


dos de estos conceptos que no se deben confundir, ellos son: tipo de datos y
estructura de datos.

Un tipo de dato, en un lenguaje de programación, define un conjunto de valores


que una determinada variable puede tomar, así como las operaciones básicas
sobre dicho conjunto. Ahora veamos como se van relacionando estos
conceptos. Los tipos de datos constituyen un primer nivel de abstracción, ya
que no se tiene en cuenta cómo se implementan o se representan realmente la
información sobre la memoria de la máquina. Para el usuario, el proceso de
implementación o representación es invisible.

Veamos entonces que son las estructuras de datos. Las estructuras de datos
son colecciones de variables, no necesariamente del mismo tipo, relacionadas
entre sí de alguna forma. Las estructuras de datos están caracterizadas por el
tipo de dato de los elementos guardados en la estructura y por la relación
definida sobre estos elementos.

Al nivel de las estructuras de datos son totalmente irrelevantes las operaciones


sobre un elemento en particular, solamente tienen carácter relevante las
operaciones que envuelvan la estructura de forma global.
Ejemplos de utilización de TDAs
Algunos ejemplos de utilización de TDAs en programación son:

• Conjuntos: Implementación de conjuntos con sus operaciones básicas


(unión, intersección y diferencia), operaciones de inserción, borrado,
búsqueda...
• Árboles: Implementación de árboles de elementos, utilizados para la
representación interna de datos complejos.
• Pilas y Colas: Implementación de los algoritmos FIFO y LIFO.

16
1.2 Estructuras de datos.

Como ya sabemos, las computadoras fueron diseñadas o ideadas como una


herramienta mediante la cual podemos realizar operaciones de cálculo
complicadas en un lapso de mínimo tiempo. Pero la mayoría de las
aplicaciones de este fantástico invento del hombre, son las de almacenamiento
y acceso de grandes cantidades de información.

La información que se procesa en la computadora es un conjunto de datos, que


pueden ser simples o estructurados. Los datos simples son aquellos que
ocupan sólo una localidad de memoria, mientras que los estructurados son un
conjunto de casillas de memoria a las cuales hacemos referencia mediante un
identificador único.

Debido a que por lo general tenemos que tratar con conjuntos de datos y no
con datos simples (enteros, reales, booleanos, etc.) que por sí solos no nos
dicen nada, ni nos sirven de mucho, es necesario tratar con estructuras de
datos adecuadas a cada necesidad.

Las estructuras de datos son una colección de datos cuya organización se


caracteriza por las funciones de acceso que se usan para almacenar y acceder
a elementos individuales de datos.

Una estructura de datos se caracteriza por lo siguiente:

• Pueden descomponerse en los elementos que la forman.

• La manera en que se colocan los elementos dentro de la estructura


afectará la forma en que se realicen los accesos a cada elemento.

• La colocación de los elementos y la manera en que se accede a ellos


puede ser encapsulada.

1.2.1 Definición.

Una estructura de datos es una forma de organizar un conjunto de datos


elementales (un dato elemental es la mínima información que se tiene en el
sistema) con el objetivo de facilitar la manipulación de estos datos como un
todo o individualmente.

17
Una estructura de datos define la organización e interrelacionamiento de estos,
y un conjunto de operaciones que se pueden realizar sobre él. Las operaciones
básicas son:

• Alta, adicionar un nuevo valor a la estructura.


• Baja, borrar un valor de la estructura.
• Búsqueda, encontrar un determinado valor en la estructura para realizar
una operación con este valor, en forma SECUENCIAL o BINARIO
(siempre y cuando los datos estén ordenados)...

Otras operaciones que se pueden realizar son:

• Ordenamiento, de los elementos pertenecientes a la estructura.


• Apareo, dadas dos estructuras originar una nueva ordenada y que
contenga a las apareadas.

Cada estructura ofrece ventajas y desventajas en relación a la simplicidad y


eficiencia para la realización de cada operación. De esta forma, la elección de
la estructura de datos apropiada para cada problema depende de factores
como la frecuencia y el orden en que se realiza cada operación sobre los datos.

1.2.2 Clasificación.

Datos simples • Registro


• Tipo de datos algebraico
• Binarios • Listas Enlazadas
• Bit • Listas Simples
• Byte • Listas Dobles
• Numéricos • Listas Circulares
• Entero • Listas por saltos (Skip lists)
• Real • Pilas (stack)
• Coma fija • Colas (queue)
• Coma flotante • Colas de Prioridad
• Alfanuméricos • Árboles
• Carácter • Árboles Binarios
• Cadena • Árbol binario de búsqueda
• Árbol binario de búsqueda
Estructuras de datos autoajustable
• Árboles Rojo-Negro
• Arrays (Arreglos) • Árboles AVL
• Vectores
• Matrices
1.2.2.1 Lineales y no lineales.

Las estructuras de datos simples se pueden combinar de varias maneras para


formar estructuras más complejas. Las dos cases principales de estructuras de
datos son las lineales y las no lineales, dependiendo de la complejidad de las
relaciones lógicas que representan. Las estructuras de datos lineales incluyen

18
pilas, colas y listas ligadas lineales. Las estructuras de datos no lineales
incluyen grafos y árboles.

1.2.2.2 Dinámicas y estáticas.

Las estructuras de datos dinámicas:

No tienen las limitaciones o restricciones en el tamaño de memoria ocupada


que son propias de las estructuras estáticas.

Mediante el uso de un tipo de datos especifico, denominado puntero, es posible


construir estructuras de datos dinámicas que no son soportadas por la mayoría
de los lenguajes, pero que en aquellos que si tienen estas características
ofrecen soluciones eficaces y efectivas en la solución de problemas complejos.

Se caracteriza por el hecho de que con un nombre se hace referencia a un


grupo de casillas de memoria. Es decir un dato estructurado tiene varios
componentes.

Las estructuras de datos estáticas:

Son aquellas en las que el tamaño ocupado en memoria se define antes de que
el programa se ejecute y no puede modificarse dicho tamaño durante la
ejecución del programa.

Estas estructuras están implementadas en casi todos los lenguajes.

Su principal característica es que ocupan solo una casilla de memoria, por lo


tanto una variable simple hace referencia a un único valor a la vez, dentro de
este grupo de datos se encuentra: enteros, reales, caracteres, boléanos,
enumerados y subrangos (los últimos no existen en algunos lenguajes de
programación).

UNIDAD 2 ESTRUCTURAS LINEALES

2.1 Arreglos.

Los arreglos se clasifican de acuerdo con el número de dimensiones que


tienen. Así se tienen los:

19
- Unidimensionales (vectores)
- Bidimensionales (tablas o matrices)
- Multidimensionales (tres o más dimensiones)

2.1.1 Definición.

Un arreglo (array) es una colección de datos del mismo tipo, que se almacenan
en posiciones consecutivas de memoria y reciben un nombre común. Para
referirse a un determinado elemento de un array se deberá utilizar un índice,
que especifique su posición relativa en el array. Un arreglo es una colección
finita, homogénea y ordenada de elementos. Finita:Todo arreglo tiene un límite;

20
es decir, debe determinarse cuál será el número máximo de elementos que
podrán formar parte del arreglo. Homogénea: Todos los elementos del arreglo
deben ser del mismo tipo. Ordenada: Se puede determinar cuál es el primer
elemento, el segundo, el tercero,.... y el n-ésimo elemento.

2.1.2 Unidimensionales.

Están formados por un conjunto de elementos de un mismo tipo de datos que


se almacenan bajo un mismo nombre, y se diferencian por la posición que tiene
cada elemento dentro del arreglo de datos. Al declarar un arreglo, se debe
inicializar sus elementos antes de utilizarlos. Para declarar un arreglo tiene que
indicar su tipo, un nombre único y la cantidad de elementos que va a contener.
Por ejemplo, las siguientes instrucciones declaran tres arreglos distintos:
Float costo_partes[50];

Para acceder a valores específicos del arreglo, use un valor de índice que
apunte al elemento deseado. Por ejemplo, para acceder al primer elemento del
arreglo calificaciones debe utilizar el valor de índice 0 (calificaciones[0]). Los
programas en C++ siempre indizan el primer elemento de un arreglo con 0 y el
último con un valor menor en una unidad al tamaño del arreglo.

2.1.3 Bidimensionales.

21
Este tipo de arreglos al igual que los anteriores es un tipo de dato estructurado,
finito ordenado y homogéneo. El acceso a ellos también es en forma directa por
medio de un par de índices.

Los arreglos bidimensionales se usan para representar datos que pueden verse
como una tabla con filas y columnas. La primera dimensión del arreglo
representa las columnas, cada elemento contiene un valor y cada dimensión
representa una relación.

La representación en memoria se realiza de dos formas: almacenamiento por


columnas o por renglones.

Para determinar el número total de elementos en un arreglo bidimensional


usaremos las siguientes fórmulas:

RANGO DE RENGLONES (R1) = Ls1 - (Li1+1)


RANGO DE COLUMNAS (R2) = Ls2 - (Li2+1)
No. TOTAL DE COMPONENTES = R1 * R2

REPRESENTACION EN MEMORIA POR COLUMNAS

x : array [1..5,1..7] of integer

Para calcular la dirección de memoria de un elemento se usan la siguiente


formula:

A[i,j] = base (A) + [((j - li2) R1 + (i + li1))*w]

REPRESENTACION EN MEMORIA POR RENGLONES

22
x : array [1..5,1..7] of integer

Para calcular la dirección de memoria de un elemento se usan la siguiente


formula:

A[i,j] = base (A) + [((i - li1) R2 + (j + li2))*w]

Donde:

• i = Índice del renglón a calcular


• j = Índice de la columna a calcular
• li1 = Límite inferior de renglones
• li2 = Límite inferior de columnas
• w = Número de bytes tipo componente

2.1.4 Multidimensionales.

Este también es un tipo de dato estructurado, que está compuesto por n


dimensiones. Para hacer referencia a cada componente del arreglo es
necesario utilizar n índices, uno para cada dimensión.

Para determinar el número de elementos en este tipo de arreglos se usan las


siguientes fórmulas:

RANGO (Ri) = lsi - (lii + 1)


No. TOTAL DE ELEMENTOS = R1 * R2* R3 * ...* Rn

Donde:

• i = 1 ... n
• n = No. total de dimensiones

Para determinar la dirección de memoria se usa la siguiente formula:

•LOC A[i1,i2,i3,...,in] = base(A) + [(i1-li1)*R3*R4*Rn + (i2-


li2)*R3*R2*... (in - lin)*Rn]*w
2.1.5 Resolución de problemas con arreglos.

23
Las operaciones para la resolución de problemas en arreglos pueden
clasificarse de la siguiente forma:

• Lectura
• Escritura
• Asignación
• Actualización
• Ordenación
• Búsqueda

a) LECTURA

Este proceso consiste en leer un dato de un arreglo y asignar un valor a cada


uno de sus componentes.

La lectura se realiza de la siguiente manera:

para i desde 1 hasta N haz


x<--arreglo[i]

b) ESCRITURA

Consiste en asignarle un valor a cada elemento del arreglo.

La escritura se realiza de la siguiente manera:

para i desde 1 hasta N haz


arreglo[i]<--x

c) ASIGNACION

No es posible asignar directamente un valor a todo el arreglo, por lo que se


realiza de la manera siguiente:

para i desde 1 hasta N haz


arreglo[i]<--algún_valor

d) ACTUALIZACION

Dentro de esta operación se encuentran las operaciones de eliminar, insertar y


modificar datos. Para realizar este tipo de operaciones se debe tomar en
cuenta si el arreglo está o no ordenado.
Para arreglos ordenados los algoritmos de inserción, borrado y modificación
son los siguientes:

24
1.- Insertar.

Si i< mensaje(arreglo contrario caso En arreglo[i]<--valor i<--i+1


entonces>

2.- Borrar.

Si N>=1 entonces
inicio
i<--1
encontrado<--falso
mientras i<=n y encontrado=falso
inicio
si arreglo[i]=valor_a_borrar entonces
inicio
encontrado<--verdadero
N<--N-1
para k desde i hasta N haz
arreglo[k]<--arreglo[k-1]
fin
en caso contrario
i<--i+1
fin
fin
Si encontrado=falso entonces
mensaje (valor no encontrado)

3.- Modificar.

Si N>=1 entonces
inicio
i<--1
encontrado<--falso
mientras i<=N y encontrado=false haz
inicio
Si arreglo[i]=valor entonces
arreglo[i]<--valor_nuevo
encontrado<--verdadero
En caso contrario
i<--i+1
fin
fin

2.1.6 Clases para la implementación de arreglos.

25
Las clases de implementación de arreglos son un conjunto de caracteres
incluido el espacio en blanco, que se almacena en un área contigua de la
memoria central. La longitud de una cadena es el número de caracteres que
contiene. Una cadena vacía es la que no tiene ningún carácter. Una constante
de tipo cadena es un conjunto de caracteres válidos encerrados entre comillas.
Una variable de cadena es aquella cuyo contenido es una cadena de
caracteres. El último carácter de la cadena marca el fin de la cadena.

Las variables de cadena se dividen en:

• · Estáticas. Su longitud se define antes de ejecutar el programa y no


puede cambiarse a lo largo de este.

• · Semiestáticas. Su longitud puede variar durante la ejecución del


programa, pero sin sobrepasar un límite máximo declarado al principio.

• · Dinámicas. Su longitud puede variar sin limitación dentro del programa.

Operaciones básicas con cadenas:

• · Asignación.

Nombre ¬ "Luis Humberto"

• · Entrada/ Salida

Leer(nombre, estado_civil)
Escribir(nombre, apellido)
Escribir(nombre, apellido)

2.2 Pilas.

Las pilas son otro tipo de estructura de datos lineales, las cuales presentan
restricciones en cuanto a la posición en la cual pueden realizarse las
inserciones y las extracciones de elementos. Una pila es una lista de elementos
en la que se pueden insertar y eliminar elementos sólo por uno de los
extremos. Como consecuencia, los elementos de una pila serán eliminados en
orden inverso al que se insertaron. Es decir, el último elemento que se metió a
la pila será el primero en salir de ella.

En la vida cotidiana existen muchos ejemplos de pilas, una pila de platos en


una alacena, una pila de latas en un supermercado, una pila de papeles sobre
un escritorio, etc.

Debido al orden en que se insertan y eliminan los elementos en una pila,


también se le conoce como estructura LIFO (Last In, First Out: último en entrar,
primero en salir).
2.2.1 Definición.

26
Una pila (stack en inglés) es una estructura de datos de tipo LIFO (del inglés
Last In First Out, último en entrar, primero en salir) que permite almacenar y
recuperar datos. Se aplica en multitud de ocasiones en informática debido a su
simplicidad y ordenación implícita en la propia estructura.

2.2.2 Operaciones.

Para el manejo de los datos se cuenta con dos operaciones básicas: apilar
(push), que coloca un objeto en la pila, y su operación inversa, retirar (o
desapilar, pop), que retira el último elemento apilado.

En cada momento sólo se tiene acceso a la parte superior de la pila, es decir,


al último objeto apliado (denominado TOS, top of stack en inglés). La operación
retirar permite la obtención de este elemento, que es retirado de la pila
permitiendo el acceso al siguiente (apilado con anterioridad), que pasa a ser el
nuevo TOS.

Por analogía con objetos cotidianos, una operación apilar equivaldría a colocar
un plato sobre una pila de platos, y una operación retirar a retirarlo.

Las pilas suelen emplearse en los siguientes contextos:

• Evaluación de expresiones en notación postfija (notación polaca


inversa).
• Reconocedores sintácticos de lenguajes independientes del contexto
• Implementación de recursividad.

2.2.3 Clases para la implementación de pilas.

El concepto de recursión es difícil de precisar, pero existen ejemplos de la vida


cotidiana que nos pueden servir para darnos una mejor idea acerca de lo que
es recursividad. Un ejemplo de esto es cuando se toma una fotografía de una
fotografía, o cuando en un programa de televisión un periodista transfiere el
control a otro periodista que se encuentra en otra ciudad, y este a su vez le
transfiere el control a otro.
Casos típicos de estructuras de datos definidas de manera recursiva son los
árboles binarios y las listas enlazadas.

27
La recursión se puede dar de dos formas:

• DIRECTA. Este tipo de recursión se da cuando un subprograma se


llama directamente a sí mismo.

• INDIRECTA Sucede cuando un subprograma llama a un segundo


subprograma, y este a su vez llama al primero, es decir el subproceso A
llama al B, y el B invoca al subproceso A.

Otra de las aplicaciones en las que podemos utilizar las pilas es en la


implementación de la recursividad. A continuación se mostrarán algunos
ejemplos.

|
| 1, N=0
Factorial <
| N*(n-1)!, N&gt0
|

sp <--0
mientras n <> 1 haz
push(pila,n)
n<--n-1
mientras sp <> 0 haz
factorial<--factorial*pop(pila)

|
| 0 , si a < b
Q <
| Q(a-b,b)+1, si a<=b
|

sp<--0
Q<--0
lee(a), lee(b)
mientras a>=b haz
push(pila,1)
a<--a-b
mientras sp< > 0 haz
Q<-- Q + pop(pila)

2.3 Colas.

28
Una cola es una estructura de almacenamiento, donde la podemos considerar
como una lista de elementos, en la que éstos van a ser insertados por un
extremo y serán extraídos por otro.

Las colas son estructuras de tipo FIFO (first-in, first-out), ya que el primer
elemento en entrar a la cola será el primero en salir de ella.

Existen muchísimos ejemplos de colas en la vida real, como por ejemplo:


personas esperando en un teléfono público, niños esperando para subir a un
juego mecánico, estudiantes esperando para subir a un camión escolar, etc.

2.3.1 Definición.

Una cola es una estructura de datos, caracterizada por ser una secuencia de
elementos en la que la operación de inserción push se realiza por un extremo y
la operación de extracción pop por el otro. También se le llama estructura FIFO
(del inglés First In First Out), debido a que el primer elemento en entrar será
también el primero en salir.

2.3.2 Tipos.

Colas de prioridad: En ellas, los elementos se atienden en el orden indicado


por una prioridad asociada a cada uno. Si varios elementos tienen la misma
prioridad, se atenderán de modo convencional según la posición que ocupen.

Hay 2 formas de implementación:

1 Añadir un campo a cada nodo con su prioridad. Resulta conveniente


mantener la cola ordenada por orden de prioridad.
2 Crear tantas colas como prioridades haya, y almacenar cada elemento en
su cola.

• Bicolas: son colas en donde los nodos se pueden añadir y quitar por
ambos extremos; se les llama DEQUE (Double Ended QUEue). Para
representar las bicolas lo podemos hacer con un array circular con Ini y
Fin que apunten a cada uno de los extremos. Hay variantes:

• Bicolas de entrada restringida: Son aquellas donde la inserción sólo


se hace por el final, aunque podemos eliminar al principio ó al final.

• Bicolas de salida restringida: Son aquellas donde sólo se elimina por


el final, aunque se puede insertar al principio y al final.

Colas de prioridad: son las que cumplen dos reglas:

29
1 De dos elementos siempre se atenderá antes al que tenga mayor
prioridad.
2 Si dos elementos tienen la misma prioridad se atiende primero el que
llego antes.

Realización Se ponen todos los nodos en la misma cola. Su particularidad es


que cada nodo tiene un campo adicional con la prioridad del dato; de tal forma
que cuando insertamos nuevos datos, el nuevo nodo, se inserta al final de la
cola de los que tengan su misma prioridad.

2.3.2.1 Colas simples.

Esta significa el primero en llegar es el primero en salir. La estructura de este


es como cualquier tipo de fila como: la cola que tienes que hacer en un lugar
esperando a ser atendido y como todos sabemos, el primero en llegar estará
en frente de la cola y será el primero en salir.

In- Sirve para ingresar los valores que se van agregando, este valor será el
primero en la cola y se quedara con el apuntador externo "front", los demás
serán parte de la cola y el ultimo que este en la cola será apuntado por el
apuntador externo "end".

Algoritmo:

1. inicio manda a llamar in


2. estado de cola?

Vacía =3

sino =8

3. crear nodo

4. asignación de valor

5. los apuntadores externos "end" y "front" apuntan al nuevo nodo

6. el apuntador interno "next" del nuevo nodo apunta a nulo

7. fin

30
8. crear nodo

9. asignación de valor

10. el apuntador externo "nuevo" asigna al apuntador interno "next" del nodo del nuevo
valor apuntar al nodo que esta apuntando el apuntador externo "end".

11. el apuntador externo "end" se actualiza moviéndose al nuevo nodo

12. el apuntador externo "end" asigna al apuntador interno "next" apuntar a nulo

13. fin

Para el algoritmo #7 el diseño de la estructura seria:

Para el algoritmo #13 el diseño de la estructura seria:

Out- Sirve para sacar el primer valor que fue ingresado. Si hay solo un valor en
la estructura, simplemente ya no hay mas valores en la pila. Si se intenta sacar
un valor cuando no hay valores se avisara que no hay valores.

31
Algoritmo:

1. inicio manda a llamar a out


2. estado de cola?

Vacía =3

Solo 1 valor =5

Sino =7

3. mensaje "no hay valores en la pila

4. fin
5. se muestra el valor por borrar y los apuntadores externos "end" y "front" serán nulos

6. fin
7. se muestra el valor por borrar y se inicializa un ciclo para desconectar el primer valor y
hacer el segundo como primero.

8. y para eso el apuntador externo "front" se actualiza con al ayuda del apuntador externo
"aux" y asigna al apuntador interno "next" apuntar a nulo

9. fin

Para el algoritmo #6 el diseño de la estructura seria:

32
Para el algoritmo #7 el diseño de la estructura seria:

Para el algoritmo #9 el diseño de la estructura seria:

El segundo que este se queda con el apuntador externo "front" y el primer


valor, antes de que se hiciera el pop, se desconecta de la estructura.

Todo esto es un proceso de programación muy delicada ya que con cualquier


mal asignación de apuntadores, tanto internos como externos, puede arruinar
la estructura de los valores y hasta perder la información.

33
2.3.2.2 Colas circulares.

Las colas lineales tienen un grave problema, como las extracciones sólo
pueden realizarse por un extremo, puede llegar un momento en que el
apuntador A sea igual al máximo número de elementos en la cola, siendo que
al frente de la misma existan lugares vacíos, y al insertar un nuevo elemento
nos mandará un error de overflow (cola llena).

Para solucionar el problema de desperdicio de memoria se implementaron las


colas circulares, en las cuales existe un apuntador desde el último elemento al
primero de la cola.

La representación gráfica de esta estructura es la siguiente:

La condición de vacío en este tipo de cola es que el apuntador F sea igual a


cero.

Las condiciones que debemos tener presentes al trabajar con este tipo de
estructura son las siguientes:

• Over flow, cuando se realice una inserción.


• Under flow, cuando se requiera de una extracción en la cola.
• Vacio

ALGORITMO DE INICIALIZACIÓN

F < -- 0
A<-- 0

ALGORITMO PARA INSERTAR

Si (F+1=A) ó (F=1 y A=máximo) entonces


mensaje (overflow)
en caso contrario
inicio
si A=máximo entonces
A<--1
cola[A]<-- valor
en caso contrario
A <--A+1
cola[A]<-- valor
si F=0 entonces
F <-- 1
fin

34
ALGORITMO PARA EXTRAER

Si F=0 entonces
mensaje (underflow)
en caso contrario
x <-- cola[F]
si F=A entonces
F <-- 0
A<-- 0
en caso contrario
si F=máximo entonces
F <--1 en caso contrario F <-- F+1

2.3.2.3 Colas dobles.

Esta estructura es una cola bidimensional en que las inserciones y


eliminaciones se pueden realizar en cualquiera de los dos extremos de la
bicola. Gráficamente representamos una bicola de la siguiente manera:

Existen dos variantes de la doble cola:

• Doble cola de entrada restringida.


• Doble cola de salida restringida.

La primer variante sólo acepta inserciones al final de la cola, y la segunda


acepta eliminaciones sólo al frente de la cola

ALGORITMOS DE ENTRADA RESTRINGIDA

Algoritmo de Inicialización

F < -- 1
A <-- 0

Algoritmo para Insertar

Si A=máximo entonces
mensaje (overflow)
en caso contrario
A <--A+1
cola[A]<-- valor

35
Algoritmo para Extraer

Si F&gtA entonces
mensaje (underflow)
en caso contrario
mensaje (frente/atrás)
si frente entonces
x <-- cola[F]
F <-- F+1
en caso contrario
x <-- cola[A]
A <-- A-1

ALGORITMOS DE SALIDA RESTRINGIDA

Algoritmo de Inicialización

F <--1
A <-- 0

Algoritmo para Insertar

Si F&gtA entonces
mensaje (overflow)
en caso contrario
mensaje (Frente/Atrás)
si Frente entonces
cola[F] <--valor
en caso contrario
A <-- A+1
cola[A] <--valor

Algoritmo para Extraer

Si F=0 entonces
mensaje (underflow)
en caso contrario
x <--cola[F]
F <-- F+1

2.3.3 Operaciones.

Las operaciones que nosotros podemos realizar sobre una cola son las
siguientes:

• Inserción.

36
• Extracción.
Las inserciones en la cola se llevarán a cabo por atrás de la cola, mientras que
las eliminaciones se realizarán por el frente de la cola (hay que recordar que el
primero en entrar es el primero en salir).

2.3.4 Clases para la implementación de colas.

Esta implementación es estática, es decir, da un tamaño máximo fijo a la cola.


No se incluye comprobación de errores dentro del encolado y el desencolado,
pero se implementan como funciones aparte.

¿Por qué un array circular? ¿Qué es eso? Como se aprecia en la


implementación de las pilas, los elementos se quitan y se ponen sobre la cima,
pero en este caso se introducen por un sitio y se quitan por otro.

Podría hacerse con un array secuencial, como se muestra en las siguientes


figuras. 'Entrada' es la posición de entrada a la cola, y 'Salida' por donde salen.

En esta primera figura se observa que se han introducido tres elementos: 3, 1 y


4 (en ese orden):

se desencola, obteniendo un 3:

se encola un 7:

Enseguida se aprecia que esto tiene un grave defecto, y es que llega un


momento en el que se desborda la capacidad del array. Una solución nada
efectiva es incrementar su tamaño. Esta implementación es sencilla pero
totalmente ineficaz.

37
Como alternativa se usa el array circular. Esta estructura nos permite volver al
comienzo del array cuando se llegue al final, ya sea el índice de entrada o el
índice de salida.
UNIDAD 3 LISTAS ENLAZADAS

3.1 Listas enlazadas.

Una lista enlazada o encadenada es una colección de elementos ó nodos, en


donde cada uno contiene datos y un enlace o liga.

Un nodo es una secuencia de caracteres en memoria dividida en campos (de


cualquier tipo). Un nodo siempre contiene la dirección de memoria del siguiente
nodo de información si este existe.

Un apuntador es la dirección de memoria de un nodo

La figura siguiente muestra la estructura de un nodo:

El campo liga, que es de tipo puntero, es el que se usa para establecer la liga
con el siguiente nodo de la lista. Si el nodo fuera el último, este campo recibe
como valor NIL (vacío).

A continuación se muestra el esquema de una lista:

Las operaciones que podemos realizar sobre una lista enlazada son las
siguientes:

Recorrido.

Esta operación consiste en visitar cada uno de los nodos que forman la lista .
Para recorrer todos los nodos de la lista, se comienza con el primero, se toma
el valor del campo liga para avanzar al segundo nodo, el campo liga de este
nodo nos dará la dirección del tercer nodo, y así sucesivamente.

Inserción.

Esta operación consiste en agregar un nuevo nodo a la lista. Para esta


operación se pueden considerar tres casos:
Insertar un nodo al inicio.
Insertar un nodo antes o después de cierto nodo.

38
Insertar un nodo al final.

Borrado.

La operación de borrado consiste en quitar un nodo de la lista, redefiniendo las


ligas que correspondan. Se pueden presentar cuatro casos:

• Eliminar el primer nodo.


• Eliminar el último nodo.
• Eliminar un nodo con cierta información.
• Eliminar el nodo anterior o posterior al nodo cierta con información.

Búsqueda.

Esta operación consiste en visitar cada uno de los nodos, tomando al campo
liga como puntero al siguiente nodo a visitar.

3.1.1 Simples.

En esta sección se mostrarán algunos algoritmos sobre listas simples sin nodo
de cabecera y con nodo de cabecera.

Una lista con nodo de cabecera es aquella en la que el primer nodo de la lista
contendrá en su campo dato algún valor que lo diferencíe de los demás nodos
(como : *, -, +, etc). Un ejemplo de lista con nodo de cabecera es el siguiente:

En el caso de utilizar listas con nodo de cabecera, usaremos el apuntador CAB para
hacer referencia a la cabeza de la lista.

Para el caso de las listas sin nodo de cabecera, se usará la expresión TOP para
referenciar al primer nodo de la lista, y TOP(dato), TOP(liga) para hacer referencia al
dato almacenado y a la liga al siguiente nodo respectivamente.

Algoritmo de Creación

top<--NIL
repite
new(p)
leer(p(dato))
si top=NIL entonces
top<--p
en caso contrario
q(liga)<--p
p(liga)<--NIL
q<--p
mensaje('otro nodo?')
leer(respuesta)
hasta respuesta=no

39
Algoritmo para Recorrido

p<--top
mientras p<>NIL haz
escribe(p(dato))
p<--p(liga:)

Algoritmo para insertar al final

p<--top
mientras p(liga)<>NIL haz
p<--p(liga)
new(q)
p(liga)<--q
q(liga)<--NIL

Algoritmo para insertar antes/después de 'X' información

p<--top
mensaje(antes/despues)
lee(respuesta)
si antes entonces
mientras p<>NIL haz
si p(dato)='x' entonces
new(q)
leer(q(dato))
q(liga)<--p
si p=top entonces
top<--q
en caso contrario
r(liga)<--q
p<--nil
en caso contrario
r<--p
p<--p(link)
si despues entonces
p<--top
mientras p<>NIL haz
si p(dato)='x' entonces
new(q)
leer(q(dato))
q(liga)<--p(liga)
p(liga)<--q
p<--NIL
en caso contrario
p<--p(liga)
p<--top
mientras p(liga)<>NIL haz

40
p<--p(liga)
new(q)
p(liga)<--q
q(liga)<--NIL

Algoritmo para borrar un nodo

p<--top
leer(valor_a_borrar)
mientras p<>NIL haz
si p(dato)=valor_a_borrar entonces
si p=top entonces
si p(liga)=NIL entonces
top<--NIL
en caso contrario
top(liga)<--top(liga)
en caso contrario
q(liga)<--p(liga)
dispose(p)
p<--NIL
en caso contrario
q<--p
p<--p(liga)

Algoritmo de creación de una lista con nodo de cabecera

new(cab)
cab(dato)<--'*'
cab(liga)<--NIL
q<--cab
repite
new(p)
leer(p(dato))
p(liga)<--NIL
q<--p
mensaje(otro nodo?)
leer(respuesta)
hasta respuesta=no

Algoritmo de extracción en una lista con nodo de cabecera

leer(valor_a_borrar)
p<--cab
q<--cab(liga)
mientras q<>NIL haz
si q(dato)=valor_a_borrar entonces
p<--q(liga)
dispose(q)
q<--NIL
en caso contrario
p<--q

41
q<--q(liga)

3.1.2 Dobles.

Una lista doble , ó doblemente ligada es una colección de nodos en la cual


cada nodo tiene dos punteros, uno de ellos apuntando a su predecesor (li) y
otro a su sucesor(ld). Por medio de estos punteros se podrá avanzar o
retroceder a través de la lista, según se tomen las direcciones de uno u otro
puntero.

La estructura de un nodo en una lista doble es la siguiente:

Existen dos tipos de listas doblemente ligadas:

Listas dobles lineales. En este tipo de lista doble, tanto el puntero izquierdo
del primer nodo como el derecho del último nodo apuntan a NIL.

Listas dobles circulares. En este tipo de lista doble, el puntero izquierdo del
primer nodo apunta al último nodo de la lista, y el puntero derecho del último
nodo apunta al primer nodo de la lista.

Debido a que las listas dobles circulares son más eficientes, los algoritmos que
en esta sección se traten serán sobre listas dobles circulares.

En la figura siguiente se muestra un ejemplo de una lista doblemente ligada


lineal que almacena números:

En la figura siguiente se muestra un ejemplo de una lista doblemente ligada


circular que almacena números:

A continuación mostraremos algunos algoritmos sobre listas enlazadas. Como


ya se mencionó, llamaremos li al puntero izquierdo y ld al puntero derecho,
también usaremos el apuntador top para hacer referencia al primer nodo en la
lista, y p para referenciar al nodo presente.

42
Algoritmo de creación

top<--NIL
repite
si top=NIL entonces
new(p)
lee(p(dato))
p(ld)<--p
p(li)<--p
top<--p
en caso contrario
new(p)
lee(p(dato))
p(ld)<--top
p(li)<--p
p(ld(li))<--p
mensaje(otro nodo?)
lee (respuesta)
hasta respuesta=no

Algoritmo para recorrer la lista

--RECORRIDO A LA DERECHA.

p<--top
repite
escribe(p(dato))
p<--p(ld)
hasta p=top

--RECORRIDO A LA IZQUIERDA.

p<--top
repite
escribe(p(dato))
p<--p(li)
hasta p=top(li)

43
Algoritmo para insertar antes de 'X' información

p<--top
mensaje (antes de ?)
lee(x)
repite
si p(dato)=x entonces
new(q)
leer(q(dato))
si p=top entonces
top<--q
q(ld)<--p
q(li)<--p(li)
p(ld(li))<--q
p(li)<--q
p<--top
en caso contrario
p<--p(ld)
hasta p=top

Algoritmo para insertar despues de 'X' información

p<--top
mensaje(despues de ?)
lee(x)
repite
si p(dato)=x entonces
new(q)
lee(q(dato))
q(ld)<--p(ld)
q(li)<--p
p(li(ld))<--q
p(ld)<--q
p<--top
en caso contrario
p<--p(ld)
hasta p=top

Algoritmo para borrar un nodo

p<--top
mensaje(Valor a borrar)
lee(valor_a_borrar)
repite
si p(dato)=valor_a_borrar entonces
p(ld(li))<--p(ld)
p(li(ld))<--p(li)
si p=top entonces
si p(ld)=p(li) entonces
top<--nil
en caso contrario
top<--top(ld)
dispose(p)
p<--top

44
en caso contrario
p<--p(ld)
hasta p=top

3.1.3 Circulares.

Las listas circulares tienen la característica de que el último elemento de la


misma apunta al primero

La siguiente figura es una representación gráfica de una lista circular.

Enseguida se mostrarán los algoritmos más comunes en listas circulares. Al


igual que en las secciones anteriores, utilizaremos el apuntador top para hacer
referencia al primer nodo en la lista.

Algoritmo de creación

repite
new(p)
lee(p(dato))
si top=nil entonces
top<--p
q<--p
en caso contrario
q(liga)<--p
q<--p
p(liga)<--top
mensaje (otro nodo ?)
lee(respuesta)
hasta respuesta=no

Algoritmo para recorrer la lista

p<--top
repite
escribe(p(dato))
p<--p(liga)
hasta p=top

45
Algoritmo para insertar antes de 'X' información

new(p)
lee(p(dato))
si top=nil entonces
top<--p
p(liga)<--top
en caso contrario
mensaje(antes de ?)
lee(x)
q<--top
r<--top(liga)
repite
si q(dato)=x entonces
p(liga)<--q
r(liga)<--p
si p(liga)=top entonces
top<--p
q<--q(liga)
r<--r(liga)
hasta q=top

Algoritmo para insertar después de 'X' información

new(p)
lee(p(dato))
mensaje(después de ?)
lee(x)
q<--top
r<--top(liga)
repite
si q(dato)=x entonces
q(liga)<--p
p(liga)<--r
q<--q(liga)
r<--r(liga)
hasta q=top

46
Algoritmo para borrar

mensaje(valor a borrar )
lee(valor_a_borrar)
q<--top
r<--top
p<--top
mientras q(liga)<>top haz
q<--q(liga)
repite
si p(dato)=valor_a_borrar entonces
si p=top entonces
si top(liga)=top entonces
top<--NIL
en caso contrario
top<--top(liga)
q(liga)<--top
en caso contrario
r(liga)<--p(liga)
dispose(p)
p<--top
en caso contrario
r<--p
p<--p(liga)
hasta p=top

3.1.4 Multilistas.

Este método de búsqueda permite accesar la información de manera ordenada


a través de campos claves. Las multilistas permiten llegar a un registro por
diferentes caminos. El camino lo determina el campo clave sobre el cual se
haga la búsqueda. A continuación se presenta un ejemplo de multilistas.

Supóngase que se tiene un archivo en el cual cada registro almacena la


siguiente información:

Nombre Profesión Categoría

Se tienen los datos de seis personas:

Juan Matemático 1
Daniel Físico 2
José Matemático 2
Pascual Ingeniero 3
Miguel Ingeniero 1

47
Felipe Abogado 2

En este caso, la información de cada individuo puede accesarse por medio de


su profesión y por medio de su categoría, que son los atributos que permiten
realizar búsqueda directa en el archivo. Como puede verse en la figura
siguiente, se tiene una lista por profesión y una por categoría.

En general las multilistas son recomendables cuando la búsqueda se hace


sobre un solo atributo. En caso de necesitarse una combinación de atributos es
preferible usar listas invertidas.

3.1.5 Clases para la implementación de listas.

Declaración de listas enlazadas

En C++, los nodos de una lista enlazada se representan mediante registros de


dos campos: un campo para los datos y otro para el enlace con el siguiente
nodo.

Ejemplo Las declaraciones para una lista enlazada de nombres son las
siguientes:

TYPE

TipoElementosLista = ARRAY[1..5] OF char;


PteroLista = ^NodoLista;
NodoLista = RECORD
Datos : TipoElementosLista;
Sig : PteroLista
END;
TipoListaEnlazada = PteroLista;

VAR

Lista : TipoListaEnlazada;

En este ejemplo, la variable Lista será un puntero al primer nodo de una lista o
la constante puntero NIL. Nótese, además, que la definición del tipo puntero
PteroLista precede a la definición del tipo registro NodoLista. Esta es la única
situación en la que se permite utilizar un identificador (en este caso, NodoLista)
antes de definirlo.

Crear una lista enlazada vacía

Para construir una lista enlazada debemos ser capaces de crear una lista
vacía.

PROCEDURE CrearLista( VAR Lista : TipoListaEnlazada );

48
BEGIN
Lista := NIL;
END
Comprobar si una lista enlazada está vacía

Dado que una lista enlazada vacía se representa mediante un puntero nulo,
resulta sencillo determinar si una lista enlazada está vacía o no.

FUNCTION ListaVacia( Lista : TipoListaEnlazad ) : boolean;


BEGIN
ListaVacia := Lista = NIL
END

Añadir un elemento al principio de una lista enlazada

Uno de los métodos para construir una lista enlazada consiste en añadir
repetidamente elementos al principio de la lista, comenzando con una lista
vacía.

PROCEDURE AnnadirPrincLista( VAR Lista : TipoListaEnlazada;


Elem : TipoElementosLista );
VAR
PteroTemp : PteroLista;

BEGIN
new( PteroTemp );
PteroTerm^.Datos := Elem;
PteroTemp^.Sig := Lista;
Lista := PteroTerm
END

Recorrer una lista enlazada

• Una vez que se ha construido una lista enlazada podemos querer


recorrerla desde el principio hasta el final, realizando algún tipo de
procesamiento sobre cada uno de los elementos (por ejemplo,
visualizarlo).

• Para movernos por una lista enlazada, la idea es variar una variable
puntero dentro de una estructura de repetición, utilizando los campos de
enlaces para saltar de un nodo al siguiente.

Ejemplo El siguiente procedimiento permite visualizar los valores de una lista.

PROCEDURE VisualizarEnlazada( Lista : TipoListaEnlaza );


VAR
PteroAct : PteroLista;

BEGIN
PteroAct := Lista;
WHILE PteroAct <> NIL DO

49
BEGIN
write( PteroAct^.Datos );
PteroAct := PteroAct^.Sig
END END
UNIDAD 4 ESTRUCTURAS NO LINEALES

4.1 Árboles.

A los árboles ordenados de grado dos se les conocen como árboles binarios ya
que cada nodo del árbol no tendrá más de dos descendientes directos. Las
aplicaciones de los árboles binarios son muy variadas ya que se les puede
utilizar para representar una estructura en la cual es posible tomar decisiones
con dos opciones en distintos puntos.

La representación gráfica de un árbol binario es la siguiente:

4.1.1 Definición.

En ciencias de la computación, un árbol es una estructura de datos


ampliamente usada que emula la forma de un árbol (un conjunto de nodos
conectados). Un nodo es la unidad sobre la que se construye el árbol y puede
tener cero o más nodos hijos conectados a él. Se dice que un nodo a es padre
de un nodo b si existe un enlace desde a hasta b (en ese caso, también
decimos que b es hijo de a). Sólo puede haber un único nodo sin padres, que
llamaremos raíz. Un nodo que no tiene hijos se conoce como hoja.

Formalmente, podemos definir un árbol de la siguiente forma recursiva:

• Caso base: un árbol con sólo un nodo (es a la vez raíz del árbol y hoja).

• Un nuevo árbol a partir de un nodo nr y k árboles de raíces con


elementos cada uno, puede construirse estableciendo una relación
padre-hijo entre nr y cada una de las raíces de los k árboles. El árbol
resultante de nodos tiene como raíz el nodo nr, los nodos son los hijos
de nr y el conjunto de nodos hoja está formado por la unión de los k
conjuntos hojas iniciales. A cada uno de los árboles Ai se les denota
ahora subárboles de la raíz.

Una sucesión de nodos del árbol, de forma que entre cada dos nodos
consecutivos de la sucesión haya una relación de parentesco, decimos que es
un recorrido árbol.

50
Existen dos recorridos típicos para listar los nodos de un árbol: primero en
profundidad y primero en anchura. En el primer caso, se listan los nodos
expandiendo el hijo actual de cada nodo hasta llegar a una hoja, donde se
vuelve al nodo anterior probando por el siguiente hijo y así sucesivamente.
En el segundo, por su parte, antes de listar los nodos de nivel n + 1 (a distancia
n + 1 aristas de la raíz), se deben haber listado todos los de nivel n. Otros
recorridos típicos del árbol son preorden, postorden e inorden:

• El recorrido en preorden, también llamado orden previo consiste en


recorrer en primer lugar la raíz y luego cada uno de los hijos en orden
previo.

• El recorrido en inorden, también llamado orden simétrico (aunque este


nombre sólo cobra significado en los árboles binarios) consiste en
recorrer en primer lugar A1, luego la raíz y luego cada uno de los hijos
en orden simétrico.

• El recorrido en postorden, también llamado orden posterior consiste en


recorrer en primer cada uno de los hijos en orden posterior y por último
la raíz.

4.1.2 Representación en memoria de árboles.

Hay dos formas tradicionales de representar un árbol binario en memoria:

• Por medio de datos tipo punteros también conocidos como variables


dinámicas o listas.
• Por medio de arreglos.

Sin embargo la más utilizada es la primera, puesto que es la más natural para
tratar este tipo de estructuras.

Los nodos del árbol binario serán representados como registros que
contendrán como mínimo tres campos. En un campo se almacenará la
información del nodo. Los dos restantes se utilizarán para apuntar al subárbol
izquierdo y derecho del subárbol en cuestión.

Cada nodo se representa gráficamente de la siguiente manera:

51
El algoritmo de creación de un árbol binario es el siguiente:

Procedimiento crear(q:nodo)
inicio
mensaje("Rama izquierda?")
lee(respuesta)
si respuesta = "si" entonces
new(p)
q(li) <-- nil
crear(p)
en caso contrario
q(li) <-- nil
mensaje("Rama derecha?")
lee(respuesta)
si respuesta="si" entonces
new(p)
q(ld)<--p
crear(p)
en caso contrario
q(ld) <--nil
fin
INICIO
new(p)
raiz<--p
crear(p)
FIN

4.1.2.1 Árboles generales.

Los árboles representan las estructuras no lineales y dinámicas de datos más


importantes en computación. Dinámicas porque las estructuras de árbol
pueden cambiar durante la ejecución de un programa. No lineales, puesto que
a cada elemento del árbol pueden seguirle varios elementos.

Los árboles pueden ser construidos con estructuras estáticas y dinámicas. Las
estáticas son arreglos, registros y conjuntos, mientras que las dinámicas están
representadas por listas.

La definición de árbol es la siguiente: es una estructura jerárquica aplicada


sobre una colección de elementos u objetos llamados nodos; uno de los cuales
es conocido como raíz. Además se crea una relación o parentesco entre los
nodos dando lugar a términos como padre, hijo, hermano, antecesor, sucesor,

52
ancestro, etc.. Formalmente se define un árbol de tipo T como una estructura
homogénea que es la concatenación de un elemento de tipo T junto con un
número finito de árboles disjuntos, llamados subárboles. Una forma particular
de árbol puede ser la estructura vacía.

La figura siguiente representa a un árbol general.

Se utiliza la recursión para definir un árbol porque representa la forma más


apropiada y porque además es una característica inherente de los mismos.

Los árboles tienen una gran variedad de aplicaciones. Por ejemplo, se pueden
utilizar para representar fórmulas matemáticas, para organizar adecuadamente
la información, para construir un árbol genealógico, para el análisis de circuitos
eléctricos y para numerar los capítulos y secciones de un libro.

4.1.2.2 Árboles binarios.

Existen cuatro tipos de árbol binario:.

A. B. Distinto.
A. B. Similares.
A. B. Equivalentes.
A. B. Completos.

A continuación se hará una breve descripción de los diferentes tipos de árbol


binario así como un ejemplo de cada uno de ellos.

A. B. DISTINTO

Se dice que dos árboles binarios son distintos cuando sus estructuras son
diferentes. Ejemplo:

53
A. B. SIMILARES

Dos árboles binarios son similares cuando sus estructuras son idénticas, pero
la información que contienen sus nodos es diferente. Ejemplo:

A. B. EQUIVALENTES

Son aquellos árboles que son similares y que además los nodos contienen la
misma información. Ejemplo:

A. B. COMPLETOS

Son aquellos árboles en los que todos sus nodos excepto los del ultimo nivel,
tiene dos hijos; el subárbol izquierdo y el subárbol derecho.

4.1.3 Recorridos en un árbol binario.

Hay tres maneras de recorrer un árbol: en inorden, preorden y postorden. Cada


una de ellas tiene una secuencia distinta para analizar el árbol.

4.1.3.1 Preorden.

PREORDEN

• Examinar la raíz.
• Recorrer el subárbol izquierdo en preorden.
• recorrer el subárbol derecho en preorden.

54
4.1.3.2 Inorden.

INORDEN

• Recorrer el subárbol izquierdo en inorden.


• Examinar la raíz.
• Recorrer el subárbol derecho en inorden.

4.1.3.3 Posorden.

POSTORDEN

• Recorrer el subárbol izquierdo en postorden.


• Recorrer el subárbol derecho en postorden.
• Examinar la raíz.

4.1.4 Balanceo de árboles binarios.

Mantener un árbol perfectamente balanceado es muy costoso, entonces se


adopta un concepto menos estricto de balanceo: árboles balanceados
(equilibrados).

Un árbol está balanceado si para cada uno de sus nodos se tiene que las
alturas de sus dos subárboles difieren a lo más en 1.

Se tiene que un árbol perfectamente balanceado es también balanceado; pero


no es cierto lo contrario.

Para estos árboles el mantenimiento del balanceo no es muy costoso, y las


operaciones de búsqueda, inserción y borrado mantienen su orden O(log2n).
Los árboles pueden estar balanceados por altura o por peso.

• Arbol balanceado por altura: en dónde todos los hijos o nodos hoja se
intentan mantener a la misma distancia de la raíz.

• Arbol balanceado por peso: en dónde los nodos más visitados o


utilizados se mantienen a poca distancia de la raíz

Un árbol es un grafo conexo y sin ciclos.

Propiedades:

Si G = (V,A) es un árbol de n vértices, entonces:

1) Para todo par de vértices x e y existe un único camino de x a y.

55
2) Todas las aristas de G son puentes.

3) ½ A½ = n - 1.

4) Todo árbol tiene al menos dos hojas (vértices de grado uno).

Caracterizaciones: Un grafo G=(V,A) es un árbol Û Para todo par de vértices x


e y existe un único camino de x a y Û G es conexo y todas las aristas son
puentes Û G es acíclico y maximal (la adición de una arista nueva origina un
ciclo) Û G es conexo y ½ A½ = n - 1 Û G es acíclico y ½ A½ = n - 1

Los árboles forman una de las subclases de las gráficas de uso más amplio. En
el terreno de la computación los árboles sirven para organizar y relacionar los
datos de una base de datos.

4.1.5 Clases para la implementación de árboles.

Este tipo de estructura es usual incluso fuera del campo de la informática. El


lector seguramente conoce casos como los árboles gramaticales para analizar
oraciones, los árboles genealógicos ,representación de jerarquías, etc...La
estructuración en árbol de los elementos es fundamental dentro del campo de
la informática aplicándose en una amplia variedad de problemas como veremos
más adelante.

En principio podemos considerar la estructura de árbol de manera intuitiva


como una estructura jerárquica. Por tanto, para estructurar un conjunto de
elementos ei en árbol, deberemos escoger uno de ellos e1 al que llamaremos
raíz del árbol. Del resto de los elementos se selecciona un subconjunto e2,...,ek
estableciendo una relación padre-hijo entre la raíz y cada uno de dichos
elementos de manera que e1 es llamado el padre de e2,de e3,...ek y cada uno
de ellos es llamado un hijo de e1. Iterativamente podemos realizar la misma
operación para cada uno de estos elementos asignando a cada uno de ellos un
número de 0 o más hijos hasta que no tengamos más elementos que insertar.
El único elemento que no tiene padre es e1,la raíz del árbol. Por otro lado hay
un conjunto de elementos que no tienen hijos aunque sí padre que son
llamados hojas .Como hemos visto la relación de paternidad es una relación
uno a muchos.

Para tratar esta estructura cambiaremos la notación:

Las listas tienen posiciones. Los árboles tienen nodos.

Las listas tienen un elemento en cada posición. Los árboles tienen una etiqueta
en cada nodo (algunos autores distinguen entre árboles con y sin etiquetas. Un
árbol sin etiquetas tiene sentido aunque en la inmensa mayoría de los
problemas necesitaremos etiquetar los nodos. Es por ello por lo que a partir de
ahora sólo haremos referencia a árboles etiquetados).

56
4.2 Grafos

Hoy en día podemos ver muchas cosas que nos pueden parecer de lo mas
cotidianas, carreteras, líneas telefónicas, líneas de televisión por cable, el
transporte colectivo metro, circuitos eléctricos de nuestras casas, automóviles,
y tantas cosas mas; lo que no pensamos frecuentemente es que estos forman
parte de algo que en matemáticas se denomina como grafos.

En este trabajo se tratará de explicar lo que son los grafos, sus tipos, y algunas
derivaciones de ellos, así como su representación gráfica y en algunos casos,
su representación en algún programa informático, así como en la memoria.

En este trabajo, se explicando de manera muy sencilla los conceptos y algunas


metodologías con un lenguaje no tan rebuscado para su mayor entendimiento.

4.2.1 Definición.

Desafortunadamente no existe una terminología estandarizada en la teoría de


los grafos, por lo tanto es oportuno aclarar que las presentes definiciones
pueden variar ligeramente entre diferentes publicaciones de estructura de datos
y de teoría de grafos, pero en general se puede decir que un grafo como indica
su nombre lo indica es la representación (para nuestro caso) gráfica de los
datos de una situación particular, ejemplo:

Los datos contienen, en algunos casos, relaciones entre ellos que no es


necesariamente jerárquica. Por ejemplo, supongamos que unas líneas aéreas
realizan vuelos entre las ciudades conectadas por líneas como se ve en la
figura anterior (más adelante se presentaran grafos con estructuras de datos);
la estructura de datos que refleja esta relación recibe el nombre de grafo.

Se suelen usar muchos nombres al referirnos a los elementos de una


estructura de datos. Algunos de ellos son "elemento", "ítem", "asociación de
ítems", "registro", "nodo" y "objeto". El nombre que se utiliza depende del tipo
de estructura, el contexto en que usamos esa estructura y quien la utiliza.

En la mayoría de los textos de estructura de datos se utiliza el término


"registro" al hacer referencia a archivos y "nodo" cuando se usan listas
enlazadas, árboles y grafos.

También un grafo es una terna G = (V,A,j ), en donde V y A son conjuntos


finitos, y j es una aplicación que hace corresponder a cada elemento de A un
par de elementos de V. Los elementos de V y de A se llaman, respectivamente,
"vértices" y "aristas" de G, y j asocia entonces a cada arista con sus dos
vértices.

Esta definición da lugar a una representación gráfica, en donde cada vértice es


un punto del plano, y cada arista es una línea que une a sus dos vértices.

57
4.2.2 Tipos de grafos.

Podemos clasificar los grafos en dos grupos: dirigidos y no dirigidos. En un


grafo no dirigido el par de vértices que representa un arco no está ordenado.
Por lo tanto, los pares (v1, v2) y (v2, v1) representan el mismo arco. En un
grafo dirigido cada arco está representado por un par ordenado de vértices, de
forma que y representan dos arcos diferentes.

Ejemplos

G1 = (V1, A1)
V1 = {1, 2, 3, 4} A1 = {(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)}
G2 = (V2, A2)
V2 = {1, 2, 3, 4, 5, 6} A2 = {(1, 2), (1, 3), (2, 4), (2, 5), (3, 6)}
G3 = (V3, A3)
V3 = {1, 2, 3} A3 = { <1, 2>, <2, 1>, <2, 3> }

Gráficamente estas tres estructuras de vértices y arcos se pueden representar


de la siguiente manera:

Algunos de los principales tipos de grafos son los que se muestran a


continuación:

 Grafo regular: Aquel con el mismo grado en todos los vértices. Si ese
grado es k lo llamaremos k-regular.

Por ejemplo, el primero de los siguientes grafos es 3-regular, el segundo es 2-


regular y el tercero no es regular

 Grafo bipartito: Es aquel con cuyos vértices pueden formarse dos


conjuntos disjuntos de modo que no haya adyacencias entre vértices
pertenecientes al mismo conjunto Ejemplo.- de los dos grafos siguientes el
primero es bipartito y el segundo no lo es.

 Grafo completo: Aquel con una arista entre cada par de vértices. Un grafo
completo con n vértices se denota Kn.

A continuación pueden verse los dibujos de K3, K4, K5 y K6

 Un grafo bipartito regular: se denota Km,n donde m, n es el grado de


cada conjunto disjunto de vértices.

58
A continuación ponemos los dibujos de K1,2, K3,3, y K2,5

 Grafo nulo: Se dice que un grafo es nulo cuando los vértices que lo
componen no están conectados, esto es, que son vértices aislados.

 Grafos Isomorfos: Dos grafos son isomorfos cuando existe una


correspondencia biunívoca (uno a uno), entre sus vértices de tal forma que dos
de estos quedan unidos por una arista en común.

 Grafos Platónicos: Son los Grafos formados por los vértices y aristas de
los cinco sólidos regulares (Sólidos Platónicos), a saber, el tetraedro, el cubo,
el octaedro, el dodecaedro y el icosaedro.

59
GRAFOS EULERIANOS.

Para definir un camino euleriano es importante definir un camino euleriano


primero. Un camino euleriano se define de la manera más sencilla como un
camino que contiene todos los arcos del grafo.

Teniendo esto definido podemos hablar de los grafos eulerianos


describiéndolos simplemente como aquel grafo que contiene un camino
euleriano. Como ejemplos tenemos las siguientes imágenes:

El primer grafo de ellos no contiene caminos eulerianos mientras el segundo


contiene al menos uno.

GRAFOS CONEXOS.

Un grafo se puede definir como conexo si cualquier vértice V pertenece al


conjunto de vértices y es alcanzable por algún otro. Otra definición que dejaría
esto más claro sería: “un grafo conexo es un grafo no dirigido de modo que
para cualquier par de nodos existe al menos un camino que los une”.

4.2.3 Representación de grafos en memoria.

Los grafos se representan en memoria secuencial mediante matrices de


adyacencia.

Una matriz de adyacencia, es una matriz de dimensión n*n, en donde n es el


número de vértices que almacena valores booleanos, donde matriz M[i,j] es
verdadero si y solo si existe un arco que vaya del vértice y al vértice j.
Veamos el siguiente grafo dirigido:

60
La matriz de adyacencia, que se obtuvo a partir del grafo anterior es la
siguiente:

Los grafos se representan en memoria enlazada mediante listas de


adyacencia.

Una lista de adyacencia, se define de la siguiente manera: Para un vértice i es


una lista en cierto orden formada por todos los vértices adyacentes [a,i]. Se
puede representar un grafo por medio de un arreglo donde cabeza de i es un
apuntador a la lista de adyacencia al vértice i.

Veamos el siguiente grafo dirigido:

La lista de adyacencia, que se obtuvo a partir del grafo anterior es la siguiente:

4.2.4 Clases para la implementación de grafos.

En esta sección analizaremos algunas de las operaciones sobre grafos, como:

• Creación.
• Inserción.
• Búsqueda.
• Eliminación.

61
En esta sección, continuaremos utilizando los apuntadores que se usaron en
las secciones anteriores. TOP para hacer referencia al primer nodo, LD para
indicar liga derecha y LA para indicar liga abajo, por último usaremos los
apuntadores P y Q para hacer referencia a los nuevos nodos que vayan a ser
usados.

ALGORITMO DE CREACION.

repite
si top=NIL entonces
new(top)
top(la)<--NIL
top(ld)<--NIL
lee(top(dato))
q<--top
en caso contrario
new(p)
p(ld)<--NIL
p(la)<--NIL
q(la)<--p
lee(p(dato))
q<--p
mensaje(otro vertice ?)
lee(respuesta)
hasta repuesta=no
p<--top
mientras p<>NIL haz
mensaje(tiene vértices adyacentes p(dato) ?)
lee(respuesta)
si respueta=si entonces
repite
new(q)
lee(q(dato))
q(ld)<--p(ld)
p(ld)<--q
mensaje(otro vértice ?)
lee(respuesta2)
hasta respuesta2=no
p<--p(la)

ALGORITMO DE INSERCION

mensaje(valor a insertar ?)
lee(valor_a_insertar)
si top<>NIL entonces
p<--top
mientras p(la)<>NIL haz
p<--p(la)
new(q)

62
lee(q(dato))
p(la)<--q
q(la)<--NIL
mensaje(Hay vértices adyacentes?)
lee(respuesta)
si respuesta=si entonces
mensaje(Cuantos vértices?)
lee(número_vértices)
desde i=1 hasta número_vértices haz
new(p)
lee(p(dato))
q(ld)<--p
q<--q(ld)
en caso contrario
mensaje(no existe lista)
ALGORITMO DE BUSQUEDA
mensaje(vértice a buscar)
lee(vértice_a_buscar)
p<--top
repite
si p(dato)=vértice_a_buscar entonces
repite
p<--p(ld)
escribe(p(dato))
hasta p(ld)=NIL
en caso contrario
p<--(la)
hasta p=NIL

ALGORITMO DE BORRADO

mensaje(vértice a borrar ?)
lee(vértice_a_borrar)
p&Lt--top
r<--p
q<--p
sw<--falso
repite
si p(dato)=vértice_a_borrar entonces
si p=top entonces
top<--top(la)
r<--top
sw<--verdadero
en caso contrario
r(la)<--p(la)
repite
p<--p(ld)
dispose(q)
q<--p

63
hasta p=NIL
si sw=verdadero entonces
p<--r
q<--p
en caso contrario
p<--r(la)
q<--p
en caso contrario
r<--p
repite
q<--p(ld)
si q(dato)=vértice_a_borrar entonces
p(ld)<--q(ld)
dispose(q)
q<--p
en caso contrario
p<--p(ld)
hasta p=NIL

64
BIBLIOGRAFIAS

• Aho, A.V., J.E. Hopcroft, J.D. Ullman, Estructuras de datos y algoritmos,


Addison-Wesley, 1988.

• Arnold, K., J. Gosling, D. Holmes, El Lenguaje de Programación Java, 3ª


Ed., Addison-Wesley, 2001.

• Arnow, D., G. Weiss, Introducción a la Programación con Java. Un


enfoque Orientado a Objetos, Addison-Wesley, 2000.

• Eckel, B., Piensa en Java. 2ª Edición, Prentice-Hall, 2002.

• Horowitz, E., S. Sahni, Fundamentals of Data Structures in Pascal,


Computer Science, 1994.

• Joyanes, L., I. Zahonero, Estructuras de Datos. Algoritmos, abstracción


y objetos, McGraw-Hill, 1998.

• Peña, Ricardo, Diseño de programas. Formalismo y abstracción,


Prentice-Hall, 1998.

• Weiss, M.A., Estructuras de datos y algoritmos, Addison-Wesley, 1995.

• Wirth, N., Algoritmos y Estructuras de Datos, Prentice-Hall


Iberoamericana, 1987.

65

También podría gustarte