Sei sulla pagina 1di 71

APUNTES DE

ESTRUCTURAS DE DATOS
Un enfoque orientado a objetos

RODOLFO E. ARANA GONZALES

SASCI
2009
APUNTES DE
ESTRUCTURAS DE DATOS
Un enfoque orientado a objetos

RODOLFO E. ARANA GONZALES


Prefacio

Este material ha sido escrito en forma paralela al desenvolvimiento de cursos de Estructuras de


datos dictados en las universidades de Santa Cruz de la Sierra, con el afán de proporcionar al
estudiante una guía para aprender estructuras de datos en forma práctica. Asimismo, presentar
y entender ciertos conceptos, que a menudo no se abordan con la claridad necesaria, como ser
el manejo de punteros. También se mira la temática de las estructuras de datos desde un
enfoque orientado a objetos, explicando que esta metodología es una especie de “evolución
natural” de los tipos abstractos de datos en cuanto a su implementación.

Estos apuntes están dedicados a todos aquellos que están dispuestos a sacrificar horas de
descanso o diversión, con el fin de superarse permanentemente, hacerse mejores y hacer un
mundo mejor.

El autor
Tabla de contenido

PREFACIO ............................................................................................................................................................ 3
1. ESTRUCTURAS Y ABSTRACCIÓN DE DATOS ...................................................................................................... 1
1.1. INTRODUCCIÓN A LAS ESTRUCTURAS DE DATOS ........................................................................................... 1
1.2. ABSTRACCIÓN DE DATOS .............................................................................................................................. 1
1.3. CLASIFICACIÓN DE LAS ESTRUCTURAS DE DATOS ......................................................................................................... 2
1.4. TIPOS ABSTRACTOS DE DATOS............................................................................................................................... 2
Operaciones con arreglos ................................................................................................................................. 4
1.6 BÚSQUEDA Y CLASIFICACIÓN DE ARREGLOS ................................................................................................................ 4
Búsqueda secuencial ........................................................................................................................................ 5
Búsqueda binaria ............................................................................................................................................. 5
Clasificación ..................................................................................................................................................... 6
Método de intercambio o de Burbuja ............................................................................................................... 7
Método de la Baraja ........................................................................................................................................ 8
Metodo QuickSort ............................................................................................................................................ 8
2. ESTRUCTURA DE LENGUAJES DE PROGRAMACIÓN ORIENTADOS A OBJETO..................................................... 10
2.1 . ASPECTOS GENERALES DE LOS LENGUAJES .......................................................................................... 10
2.2 LENGUAJES ORIENTADOS A OBJETO ........................................................................................................................ 12
2.2. ESTRUCTURAS DE DATOS ORIENTADAS A OBJETOS ..................................................................................................... 14
2.4. RECURSIVIDAD Y LENGUAJES DE PROGRAMACIÓN OO................................................................................................ 15
Concepto de Recursividad............................................................................................................................... 15
1. Cálculo de la potencia................................................................................................................................. 17
2. La suma de forma recursiva ........................................................................................................................ 18
3. Búsqueda lineal recursiva (con dos casos base) ........................................................................................... 18
4. Búsqueda Binaria recursiva ........................................................................................................................ 18
3. ALMACENAMIENTO ESTÁTICO EN SECUENCIA – LISTAS .................................................................................... 20
3.1 ASPECTOS GENERALES DE LISTAS ........................................................................................................................... 20
3.2. PILAS ............................................................................................................................................................ 20
Aplicación de pila: Evaluación de expresiones ................................................................................................. 21
Transformación de infija a postfija ................................................................................................................. 22
3.3. COLAS ........................................................................................................................................................... 24
Operaciones Básicas....................................................................................................................................... 25
Aplicaciones ................................................................................................................................................... 25
IMPLEMENTACIÓN DE COLAS EN C++ ........................................................................................................................... 26
Tipos de colas ................................................................................................................................................ 27
Aplicación de cola: Colas de atención al público en un banco .......................................................................... 28
4.1 MANEJO DINÁMICO DE LA MEMORIA – LOS PUNTEROS ............................................................................................... 29
¿Que es una variable? .................................................................................................................................... 29
Punteros ........................................................................................................................................................ 31
4.2. LISTAS ENCADENADAS ....................................................................................................................................... 33
4.3 DISEÑO E IMPLEMENTACIÓN DINÁMICA DE LISTAS ENCADENADAS .................................................................................. 34
Estructura de Nodo ........................................................................................................................................ 34
Estructura de Lista encadenada...................................................................................................................... 35
Esquema y diseño de una lista enlazada ......................................................................................................... 35
Operación de Recorrido .................................................................................................................................. 37
Operación de Inserción ................................................................................................................................... 38
Operación de Borrado .................................................................................................................................... 39
Operación de Búsqueda ................................................................................................................................. 39
4.4. IMPLEMENTACIÓN DINÁMICA DE ESTRUCTURAS DE DATOS .......................................................................................... 39
Implementación de pilas con punteros............................................................................................................ 39
5. ARBOLES Y GRAFOS........................................................................................................................................... 40
5.1 ARBOLES BINARIOS ............................................................................................................................................ 40
Definición de árbol ......................................................................................................................................... 41
Formas de representación .............................................................................................................................. 41
Declaración de árbol binario........................................................................................................................... 42
Recorridos sobre árboles binarios ................................................................................................................... 42
Construcción de un árbol binario .................................................................................................................... 44
5.2 ÁRBOL BINARIO DE BÚSQUEDA ............................................................................................................................. 46
Operaciones básicas sobre árboles binarios de búsqueda................................................................................ 47
Ejercicio resuelto ............................................................................................................................................ 50
Aplicación práctica de un árbol binario de búsqueda ...................................................................................... 50
Ejercicios propuestos...................................................................................................................................... 51
5.3. ÁRBOLES B ..................................................................................................................................................... 53
Utilización de los Arboles B............................................................................................................................. 53
Funcionamiento ............................................................................................................................................. 54
¿Qué es un Arbol B? ....................................................................................................................................... 56
Costos............................................................................................................................................................ 60
Casos especiales............................................................................................................................................. 61
Conclusión ..................................................................................................................................................... 62
TRABAJOS PRACTICOS............................................................................................................................... 63
BIBLIOGRAFÍA .................................................................................................................................................... 66
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

1. Estructuras y abstracción de datos


1.1. Introducción a las estructuras de datos

Los programas están constituidos fundamentalmente por algoritmos y estructuras de datos. Esto
significa que los algoritmos se ejecutan sobre estas estructuras de datos. Esto permite ver que las
estructuras de datos son una parte fundamental de la programación de computadoras, en
particular; y del procesamiento de información, en general

Una estructura de datos es una colección de datos que pueden ser caracterizados por su
organización y las operaciones que se definen sobre ella.

Se trata de un conjunto de variables de un determinado tipo agrupadas y organizadas de alguna


manera para representar un comportamiento. Lo que se pretende con las estructuras de datos es
facilitar un esquema lógico para manipular los datos en función del problema que haya que tratar
y el algoritmo para resolverlo. En algunos casos la dificultad para resolver un problema radica en
escoger la estructura de datos adecuada. Y, en general, la elección del algoritmo y de las
estructuras de datos que manipulará estará muy relacionada.

1.2. Abstracción de datos

El procesamiento de información en una computadora requiere hacer una tarea llamada


abstracción de datos, que consiste en “ver” las cosas del mundo real en una forma integral, en el
sentido de que se ignoran algunas “propiedades irrelevantes” de los objetos reales, es decir, se
simplifican. De este modo se hace una selección de los datos más representativos de la realidad a
partir de los cuales se los pueda obtener un “modelo del objeto” para trabajar el computador y
obtener determinados resultados.

Así los lenguajes de programación proporcionan una serie de tipos de datos simples, como son
los números enteros, caracteres, números reales. En realidad los lenguajes suministran un modelo
de datos que son un subconjunto finito de éstos, pues la memoria del ordenador es finita. Los
punteros o apuntadores (si los tiene) son también un tipo de datos. El tamaño de cada tipo de
datos depende de la máquina y del compilador sobre los que se trabaja.

En principio, conocer la representación interna de estos tipos de datos no es necesaria para


realizar un programa, pero sí puede afectar en algunos casos al rendimiento. Sin embargo, cuando
se trata con problemas donde el tiempo o el espacio de almacenamiento es primordial, habrá que
entender en detalle no solo los alcances de los tipos de datos sino también de su forma de
almacenamiento.

1
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

1.3. Clasificación de las Estructuras de datos

Según su comportamiento durante la ejecución del programa distinguimos dos tipos de


estructuras: estáticas y dinámicas.

Las estructuras 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. Cuando el número de datos es grande, las operaciones de inserción y eliminación de
elementos es una tarea relativamente pesada; es decir los algoritmo deben realizar movimiento de
grandes cantidades de datos de una posición a otra. En general ocupan un tamaño fijo de
memoria, aunque hay algunos lenguajes que permiten redefinir su tamaño en pena ejecución. Las
estructuras estáticas se clasifican en arreglos, registros, archivos, conjuntos y strings.

Por otro lado, las estructuras dinámicas no tienen limitaciones en el tamaño de memoria ocupada
que son propias de las estructuras estáticas. El tamaño de las estructuras dinámicas se amplía y
contrae durante la ejecución del programa. Las estructuras dinámicas se clasifican en lineales y
no lineales. Las lineales son las pilas, colas y listas enlazadas. Las no lineales son los árboles y
los grafos.

Las estructuras dinámicas se diferencian de las estáticas, fundamentalmente, en la rapidez con


que realizan las inserciones y borrados de elementos.

1.4. Tipos Abstractos de Datos

Los Tipos Abstractos de Datos (TAD) permiten describir una estructura de datos en función de
las operaciones que pueden efectuar, dejando de lado su implementación. Esto se denomina
abstracción de datos o tipo abstracto de datos. En realidad los propios tipos de datos provistos por
los lenguajes de programación son Tipos Abstractos de Datos. Otros ejemplos son tipos muy
específicos de datos como ser TAD fecha, TAD hora, TAD Polinomio, TAD Racional, TAD
Array, etc.

Los TAD combinan estructuras de datos junto a una serie de operaciones de manipulación.
Incluyen una especificación sobre lo que verá el usuario, y una implementación (algoritmos de
operaciones sobre las estructuras de datos y su representación en un lenguaje de programación),
que el usuario no tiene necesariamente que conocer para manipular correctamente los tipos
abstractos de datos.

Se caracterizan por el encapsulamiento. Es como una caja negra que funciona simplemente
conectándole unos cables. Esto permite aumentar la complejidad de los programas pero
manteniendo una claridad suficiente que no desborde a los desarrolladores. Además, en caso de
que algo falle será más fácil determinar si lo que falla es la caja negra o son los cables.

2
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Por último, indicar que un TAD puede ser construido a partir de otro TAD ya definido. Por
ejemplo se pueden definir pilas, colas y árboles a partir de arrays o listas enlazadas. De hecho, las
listas enlazadas también pueden construirse a partir de arrays y viceversa.

Los tipos de datos abstractos (TAD) describen un conjunto de objetos con la misma
representación y comportamiento. Los tipos abstractos de datos representan una separación clara
entre la interfaz externa de un tipo de dato (nivel lógico) y su implementación interna (nivel
físico). La implementación de un tipo abstracto de datos está oculta, por consiguiente se pueden
utilizar implementaciones alternativas para un mismo tipo abstracto de datos sin cambiar su
interfaz favoreciendo así las etapas de corrección y depuración del software pues se puede
cambiar o mejorar un algoritmo interno sin cambiar su interfaz de modo tal que no afecta a los
demás módulos del software.

Hoy en día con los nuevos paradigmas de programación orientada a objetos, la aplicación de los
conceptos de abstracción se aplican mejor en paquetes cerrados llamados (clases) los cuales
presentan una interfaz al usuario de estas clases permitiendo solo el acceso a los métodos y
operaciones definidas para este T.A.D., ocultando los detalles de la implementación de tales
métodos, aplicando así el concepto de abstracción y facilitando al diseñador del software pues
este utilizara dicha interfaz para construir aplicaciones sin la necesidad de preocuparse por
detalles de la implementación del (T.A.D) Gracias a este concepto de abstracción y la aplicación
de la programación orientada a objetos hoy por hoy se agiliza grandemente la realización de
proyectos de desarrollo de software desde proyectos que se terminaban en aproximadamente un
año, a un mes; causando un gran impacto en el mercado del software. Por tanto las compañías de
desarrollo de software que no apliquen estos conceptos tienden a quedar en una gran desventaja
con sus competidores.

En términos formales, a nivel lógico una Estructura de Datos d se define como una tripleta
compuesta por con conjunto de dominios D, un conjunto de funciones u operaciones F y un
conjunto de axiomas A.

d = (D, F, A)

donde:

D El conjunto de dominios consiste en los tipos de datos primitivos que utiliza la estructura
F Es el conjunto de funciones que manipulan los datos
A Describen el significado de las funciones

1.5 Estructura de datos Arreglo

Un arreglo es una estructura de datos que consiste en un conjunto de pares índice-valor, sobre la
que se definen las operaciones de almacenamiento y recuperación. Todos los valores o elementos

3
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

de una rreglo son del mismo tipo, es decir es una estructura homogénea. El almacenamiento se
hace asociando un valor con cada índice y la recuperación significa que a partir de un índice se
puede obtener el valor.

Aquí en conjunto dominios serían los enteros (para los índices) y un tipo de dato (para los
valores), el conjunto de operaciones serían añadir un valor y recuperar un valor. Los axiomas
sería el significado de añadir un valor y el significado de recuperar un valor.

Por ejemplo, el siguiente arreglo es de números enteros

Valores
(elementos)

242 666 45 3 -6 321 31 12


[1] [2] [3] [4] [5] [6] [7] [8]

Indices
(posiciones)

Este es un arreglo de los meses del año

Enero Febrero Marzo Abril Mayo Junio Julio Agosto Septiembre Octubre Noviembre Diciembre

[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12]

Operaciones con arreglos

Para realizar operaciones con arreglos, es necesario hacer operaciones a nivel de cada elemento;
es decir, por ejemplo cargar con datos un arreglo (leer) significara cargar cada elemento mediante
una instrucción apropiada del lenguaje de programación.

Las principales operaciones con arreglos son: leer, imprimir, buscar máximo y mínimo, hallar
totales y subtotales, buscar un elemento, clasificar u ordenar un arreglo.

1.6 Búsqueda y clasificación de arreglos

La búsqueda de arreglos es un proceso de recuperación de datos. Consiste en localizar o ubicar la


posición de un valor dentro del arreglo, dando como resultado el índice correspondiente. Existen
varios métodos para realizar este proceso, los más comunes son la búsqueda secuencial y la
búsqueda binaria.

4
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Búsqueda secuencial

También llamada búsqueda lineal. Este proceso consiste en que dado el valor que se busca, se lo
debe comparar con cada valor del arreglo hasta dar con el que se busca; en ese momento se
captura la posición o índice. En caso de haber revisado todos los valores y no encontrar el valor,
se dirá que el valor no se encuentra. Un algoritmo para este proceso es el siguiente:

entero BúsquedaSecuencial( V arreglo, n tamaño, x valor, i índice) // devuelve el indice


Inicio
encontrado = falso
i = -1 // posición ficticia para valor no encontrado
k=1
mientras (no encontrado ) y (k <= n)
si V[k] = x entonces
encontrado = verdadero
i=k // guarda posición de valor encontrado
fin_si
k=k+1
fin_mientras
BusquedaSecuencial = i
Fin

El algoritmo de búsqueda secuencial no es optimo para cuando el tamaño del vector es grande,
puesto que el número de comprobaciones (si-entonces) es, en promedio, (n+1)/2, es decir
aproximadamente igual a la mitad de los elementos del vector.

Búsqueda binaria

Si el número de elementos del vector es grande, el algoritmo de búsqueda secuiencial se ralentiza


de un modo considerable. Por ejemplo si tuviéramos que consultar el número telefónico de de
una persona en la guía telefónica de una gran ciudad con mas de un millón de habitantes, el
tiempo de búsqueda sería muy largo. Por supuesto que las personas que viven en estas ciudades
nunca utilizan este método (de buscar secuencialmente), sino en un método más “natural” como
es el de dividir en mitades el espacio de búsqueda.

Si los datos que se buscan están clasificados en un determinado órden, el método de las mitades
será más adecuado. Este método se denomina búsqueda binaria.

La búsqueda binaria utiliza un método llamado “divide y vencerás” para localizar el valor
deseado. Con este método se examina primero el elemento central de la lista; si este es el
elemento buscado, entonces la búsqueda ha terminado. En caso contrario, se determina si el
elemento buscado está en la primera o segunda mitad de la lista y, a continuación se repite el
proceso, utilizando el elemento central de esta sublista.

5
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Un algoritmo para el proceso de búsqueda binaria es el siguiente:

entero BúsquedaBinaria( V arreglo, n tamaño, x valor, i índice) // devuelve el indice


Inicio
bajo = 1
alto = n
encontrado = falso
i = -1 // posición ficticia para valor no encontrado

mientras (no encontrado) y bajo <= alto) hacer


central = entero( (bajo + alto)/2 )
si V[central] = x entonces
encontrado = verdad
i = central // guarda posición de valor encontrado
sino
si V[central] > x entonces
alto = central -1
sino
bajo = central + 1
fin_si
fin_si
fin_mientras
BusquedaBinaria = i
Fin

No olvidemos que la búsqueda binaria requiere que el vector esté ordenado, y en la práctica esta
condición puede ser difícil de mantener, sin embargo, en contrapartida, la búsqueda binaria es un
método muy rápido ya que el número de comparaciones es pequeño en relación al tamaño del
vector. Se ha determinado que el número máximo de comparaciones esta dado por log2 n.

Esto significará que si se tienen100 elementos, requerirán solo 7 comparaciones como máximo.
Si son 100000 elementos, el número de comparaciones será de 20.

Clasificación

La clasificación u ordenación es el proceso de organizar datos en algún orden o secuencia


específica, tal como creciente o decreciente para datos numéricos o alfabéticamente para datos de
caracteres.
La elección de algún método de ordenación depende de tres parámetros: el número de datos a
clasificar, el tipo de datos y la cantidad de memoria disponible.

Los métodos de ordenación se dividen en dos categorías:

6
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

 Ordenación de arreglos
 Ordenación de archivos (ordenación externa)

La ordenación de arreglos se denomina también ordenación interna, ya que se realiza y almacena


en la memoria de una computadora de gran velocidad y acceso aleatorio. La ordenación de
archivos se suele hacer casi siempre sobre soportes de almacenamiento externo, discos, cintas,
etc. Por ello se denomina también ordenación externa. Estos dispositivos son más lentos en las
operaciones de E/S, pero por el contrario, pueden contener mayor cantidad de información.

En este curso veremos solo la ordenación interna. Los principales métodos de ordenación de
arreglos son:

 Inserción
o Inserción directa (baraja)
o Inserción binaria
 Intercambio
o Burbuja
o Pares no adyacentes
 Selección
 Shell
 Ordenación rápida (quick-sort)

Método de intercambio o de Burbuja

Este método se basa en el principio de comparar pares de elementos adyacentes e intercambiarlos


entre si hasta que estén ordenados. El algoritmo burbuja es el siguiente

Burbuja(V vector, n tamaño)


Inicio
bandera = Falso
mientras bandera = Falso hacer
bandera = Verdadero
para k = 1 hasta n-1
si V[k] > V[k+1] entonces
Intercambiar(V[k], V[k+1])
bandera = Falso
fin_si
fin_para
fin_mientras
Fin

Intercambiar (x, valor, y valor)

7
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Inicio
temporal = x
x=y
y = temporal
Fin

Método de la Baraja

Es un método de inserción, consiste en insertar un elemento en una parte del vector ya ordenada y
comenzar de nuevo con los elementos restantes. Por ser utilizado generalmente por los jugadores
de cartas se le conoce con el nombre de Baraja.

Baraja(V vector, n tamaño)


Inicio
para i = 2 hasta n
aux = V[i]
k=i-1
u = i-1
bandera = Falso
mientras bandera < > Falso y k >= 1 hacer
si aux < V[k] entonces
V[k+1] = V[k]
k=k–1
sino
bandera = Verdad
fin_si
fin_para
fin_mientras
V[k+1] = aux
Fin

Metodo QuickSort

El ordenamiento rápido (quicksort en inglés) es un algoritmo basado en la técnica de divide y


vencerás, que permite, en promedio, ordenar n elementos en un tiempo proporcional a n log n.
Esta es la técnica de ordenamiento más rápida conocida. Fue desarrollada por Carl Antony R.
Hoare en 1960.
El algoritmo fundamental es el siguiente:

 Elegir un elemento de la lista de elementos a ordenar, al que llamaremos pivote.


 Reubicar los demás elementos de la lista a cada lado del pivote, de manera que a un lado
queden todos los menores que él, y al otro los mayores. En este momento, el pivote ocupa
exactamente el lugar que le corresponderá en la lista ordenada.

8
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

 La lista queda separada en dos sublistas, una formada por los elementos a la izquierda del
pivote, y otra por los elementos a su derecha.
 Repetir este proceso de forma recursiva para cada sublista mientras éstas contengan más de
un elemento. Una vez terminado este proceso todos los elementos estarán ordenados.

Como se puede suponer, la eficiencia del algoritmo depende de la posición en la que termine el
pivote elegido.
 En el mejor caso, el pivote termina en el centro de la lista, dividiéndola en dos sublistas de
igual tamaño. En este caso, el orden de complejidad del algoritmo es O(n·log n).
 En el peor caso, el pivote termina en un extremo de la lista. El orden de complejidad del
algoritmo es entonces de O(n²). El peor caso dependerá de la implementación del algoritmo,
aunque habitualmente ocurre en listas que se encuentran ordenadas, o casi ordenadas.
 En el caso promedio, el orden es O(n·log n).

No es extraño, pues, que la mayoría de optimizaciones que se aplican al algoritmo se centren en


la elección del pivote. Una versión en C++ que ordena un vector de tipo numérico es la siguiente
es la siguiente:

void QuickSort(float V[], int m, int N)


{
int i, j, p;
if (m < N)
{
i = m; j = (N) + 1; p = V[m];
do
{
do i++; while(V[i]<p);
do j--; while(V[j]>p);
if (i<j)
intercambiar(V[i], V[j]);
else
break;
}
while (1);
intercambiar(V[m], V[j]);
QuickSort(V, m, j-1);
QuickSort(V, j+1, N);
}
}
void intercambiar(float &x, float &y)
{
float aux = x; x = y; y = aux;
}

9
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

2. Estructura de lenguajes de programación orientados a


objeto.
2.1 . Aspectos generales de los lenguajes

Si se echa un vistazo a la historia de la ingeniería del software, prestando particular atención a


cómo ha evolucionado, se puede observar que los progresos más significativos se han obtenido
gracias a la aplicación de uno de los principios fundamentales a la hora de resolver cualquier
problema, incluso de la vida cotidiana, la descomposición de un sistema complejo en partes que
sean más fáciles de manejar, es decir, gracias a la aplicación del dicho popular conocido como
‟divide y vencerás‟.

En los primeros estadios de desarrollo de los lenguajes de programación (Basic, Fortran) se tenía
un código en el que no había separación de conceptos, datos y funcionalidad se mezclaban sin
una línea divisoria clara. A esta etapa se la conoce como la del código spaghetti, ya que se tenía
una maraña entre datos y funcionalidad que recuerda a la que se forma del plato de esta pasta
italiana. En la siguiente etapa (Algol, Pascal, C) se pasó a aplicar la llamada descomposición
funcional, (da origen a la llamada programación modular) que pone en práctica el principio de
„divide y vencerás‟ identificando las partes más manejables como funciones que se definen en el
dominio del problema. La principal ventaja que proporciona esta descomposición es la facilidad
de integración de nuevas funciones, aunque también tiene grandes inconvenientes, como son el
hecho de que las funciones quedan algunas veces poco claras debido a la utilización de datos
compartidos, y el que los datos quedan esparcidos por todo el código, con lo cual, normalmente el
integrar un nuevo tipo de datos implica que se tengan que modificar varias funciones.

Intentando solventar estas desventajas con respecto a los datos, se dio otro paso en el desarrollo
de los sistemas software (lenguajes Smalltalk, Eiffel, C++, Java, Delphi, Phyton,). La
programación orientada a objetos (POO) ha supuesto uno de los avances más importantes de los
últimos años en la ingeniería del software para construir sistemas complejos utilizando el
principio de descomposición, ya que el modelo de objetos subyacente se ajusta mejor a los
problemas del dominio real que la descomposición funcional. La ventaja que tiene es que es fácil
la integración de nuevos datos, aunque también quedan las funciones esparcidas por todo el
código, y tiene los inconvenientes de que, con frecuencia, para realizar la integración de nuevas
funciones hay que modificar varios objetos, y de que se produce un enmarañamiento de los
objetos en funciones de alto nivel que involucran a varias clases.

En la Figura 1 se representa mediante un esquema los distintos estadios en la evolución de los


sistemas software. En este esquema se refleja de forma clara la mezcla de conceptos que se
produce en cada una de las etapas de la evolución (etapas comúnmente conocidas como
“generaciones”). Si cada una de las distintas formas que aparecen dibujadas (triángulo, cuadrado,
trapecio, elipse) representa a un tipo de datos distinto, y cada color o tonalidad representa a una

10
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

función distinta, se tiene que en la primera generación de los sistemas software funciones y datos
se entremezclaban.

En la segunda y tercera generación se tienen conjuntos de elementos agrupados por tonalidades, y


en cada conjunto conviven distintas formas, lo que nos indica que la descomposición del sistema
se hace en base a las funciones (tonalidades), y que los datos quedan esparcidos por todos los
conjuntos.

En la cuarta generación, sin embargo, esta agrupación se realiza en base a las formas (datos), pero
como se puede observar, en cada conjunto tenemos representadas distintas funcionalidades
(tonalidades), con lo cual éstas también se nos dispersan por todo el sistema.

Uno de los principales inconvenientes con el que nos encontramos al aplicar estas
descomposiciones ya tradicionales es que muchas veces se tienen ejecuciones ineficientes debido
a que las unidades de descomposición no siempre van acompañadas de un buen tratamiento de
aspectos tales como la gestión de memoria, la coordinación, la distribución, las restricciones de
tiempo real, ...

En el desarrollo de un sistema software además del diseño y la implementación de la


funcionalidad básica, se recogen otros aspectos tales como la sincronización, la distribución, el
manejo de errores, la optimización de la memoria, la gestión de seguridad, etc. Mientras que las
descomposiciones funcional y orientada a objetos no nos plantean ningún problema con respecto
al diseño y la implementación de la funcionalidad básica, estas técnicas no se comportan bien con
los otros aspectos. Es decir, que nos encontramos con problemas de programación en los cuales

11
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

ni las técnicas funcionales, ni las orientadas a objeto son suficientes para capturar todas las
decisiones de diseño que el programa debe implementar.

Con las descomposiciones tradicionales no se aislan bien estos otros aspectos, sino que quedan
diseminados por todo el sistema enmarañando el código que implementa la funcionalidad básica,
y yendo en contra de la claridad del mismo. Se puede afirmar entonces que las técnicas
tradicionales no soportan bien la separación de competencias para aspectos distintos de la
funcionalidad básica de un sistema, y que esta situación claramente tiene un impacto negativo en
la calidad del software.

La programación orientada a aspectos (POA) es una nueva metodología de programación que aspira
a soportar la separación de competencias para los aspectos antes mencionados. Es decir, que
intenta separar los componentes y los aspectos unos de otros, proporcionando mecanismos que
hagan posible abstraerlos y componerlos para formar todo el sistema. En definitiva, lo que se
persigue es implementar una aplicación de forma eficiente y fácil de entender.

2.2 Lenguajes orientados a objeto

Los lenguajes orientados a objeto se basan en un concepto fundamental llamado objeto y en toda
la tecnología desarrollada en torno a él. Muchos lenguajes se declaran Orientados a Objetos. Pero
la definición exacta del término depende de muchos rasgos y conceptos que se deben cumplir en
su totalidad; estos rasgos y características son:

1. Ocultamiento y encapsulamiento de la información


2. Herencia
3. Polimorfismo
4. Todos los tipos de datos predefinidos son objetos
5. Todas las operaciones realizan envío de mensajes a objetos
6. Todos los tipos de datos definidos por los usuarios son objetos

Para propósito de esta discusión, un lenguaje será considerado Orientado a Objetos Puro si
satisface todos los seis ítems nombrados. Será híbrido si sólo cumple con algunos de esos 6
lineamientos (por lo generar son híbridos cumpliendo los tres primeros).

Programación Orientada a Objeto

Los objetos son entidades que combinan estado, comportamiento e identidad:

El estado está compuesto de datos, serán uno o varios atributos a los que se habrán
asignado unos valores concretos (datos).
El comportamiento está definido por los procedimientos o métodos con que puede operar
dicho objeto, es decir, qué operaciones se pueden realizar con él.

12
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

La identidad es una propiedad de un objeto que lo diferencia del resto, dicho con otras
palabras, es su identificador (concepto análogo al de identificador de una variable o una
constante).

La programación orientada a objetos expresa un programa como un conjunto de estos objetos,


que colaboran entre ellos para realizar tareas. Esto permite hacer los programas y módulos más
fáciles de escribir, mantener y reutilizar.

De esta forma, un objeto contiene toda la información que permite definirlo e identificarlo frente
a otros objetos pertenecientes a otras clases e incluso frente a objetos de una misma clase, al
poder tener valores bien diferenciados en sus atributos. A su vez, los objetos disponen de
mecanismos de interacción llamados métodos que favorecen la comunicación entre ellos. Esta
comunicación favorece a su vez el cambio de estado en los propios objetos. Esta característica
lleva a tratarlos como unidades indivisibles, en las que no se separan ni deben separarse el estado
y el comportamiento.

Los métodos (comportamiento) y atributos (estado) están estrechamente relacionados por la


propiedad de conjunto. Esta propiedad destaca que una clase requiere de métodos para poder
tratar los atributos con los que cuenta. El programador debe pensar indistintamente en ambos
conceptos, sin separar ni darle mayor importancia a ninguno de ellos, hacerlo podría producir el
hábito erróneo de crear clases contenedoras de información por un lado y clases con métodos que
manejen a las primeras por el otro.

Entre los lenguajes orientados a objetos se destacan los siguientes:

Ada
C++
C#
Lenguaje de programación D
Object Pascal (Delphi)
Eiffel
Java
JavaScript (la herencia se realiza por medio de la programación basada en prototipos)
Lexico (en castellano)
Objective-C
Ocaml
Oz
Lenguaje de programación R
Perl
PHP (en su versión 5)
Python
Ruby
Smalltalk

13
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Magik (SmallWorld)
VB.NET
Visual FoxPro (en su versión 6)
Visual Basic
XBase++

Muchos de estos lenguajes de programación no son puramente orientados a objetos, sino que son
híbridos que combinan la POO con otros paradigmas

2.2. Estructuras de datos orientadas a objetos

Las Estructuras de Datos Orientadas a Objeto EDOO, pueden ser obtenidas directamente a partir
de los Tipos Abstractos de Datos, con solo añadirles, si es necesario, la funcionalidad, a través de
la implementación de sus operaciones. De este modo los TDA se convierten en clases, por lo
tanto, toda la tecnología y la terminología de la POO se aplica a las estructuras de datos
Orientadas a Objeto.

Los conceptos fundamentales y la terminología a ser utilizados en la definición de las EDOO son
los siguientes:

Clase: definiciones de las propiedades y comportamiento de un tipo de objeto concreto.


La instanciación es la lectura de estas definiciones y la creación de un objeto a partir de
ellas.
Herencia: (por ejemplo, herencia de la clase D a la clase C) Es la facilidad mediante la
cual la clase D hereda en ella cada uno de los atributos y operaciones de C, como si esos
atributos y operaciones hubiesen sido definidos por la misma D. Por lo tanto, puede usar
los mismos métodos y variables publicas declaradas en C. Los componentes registrados
como "privados" (private) también se heredan, pero como no pertenecen a la clase, se
mantienen escondidos al programador y solo pueden ser accesado a través de otros
métodos publicos. Esto es así para mantener hegemónico el ideal de OOP.
Objeto: entidad provista de un conjunto de propiedades o atributos (datos) y de
comportamiento o funcionalidad (métodos). Se corresponde con los objetos reales del
mundo que nos rodea, o a objetos internos del sistema (del programa). Es una instancia a
una clase.
Método: Algoritmo asociado a un objeto (o a una clase de objetos), cuya ejecución se
desencadena tras la recepción de un "mensaje". Desde el punto de vista del
comportamiento, es lo que el objeto puede hacer. Un método puede producir un cambio
en las propiedades del objeto, o la generación de un "evento" con un nuevo mensaje para
otro objeto del sistema.
Evento: un suceso en el sistema (tal como una interacción del usuario con la máquina, o
un mensaje enviado por un objeto). El sistema maneja el evento enviando el mensaje

14
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

adecuado al objeto pertinente. También se puede definir como evento, a la reacción que
puede desencadenar un objeto, es decir la acción que genera.
Mensaje: una comunicación dirigida a un objeto, que le ordena que ejecute uno de sus
métodos con ciertos parámetros asociados al evento que lo generó.
Propiedad o atributo: contenedor de un tipo de datos asociados a un objeto (o a una
clase de objetos), que hace los datos visibles desde fuera del objeto y esto se define como
sus características predeterminadas, y cuyo valor puede ser alterado por la ejecución de
algún método.
Estado interno: es una variable que se declara privada, que puede ser únicamente
accedida y alterada por un método del objeto, y que se utiliza para indicar distintas
situaciones posibles para el objeto (o clase de objetos). No es visible al programador que
maneja una instancia de la clase.
Componentes de un objeto: atributos, identidad, relaciones y métodos.
Representación de un objeto: un objeto se representa por medio de una tabla o entidad
que esté compuesta por sus atributos y funciones correspondientes.

2.4. Recursividad y lenguajes de programación OO

Concepto de Recursividad

La recursividad constituye una de las herramientas más potentes en programación. Es un


concepto matemático conocido. Esencialmente, un proceso es recursivo cuando en su
procesamiento se utiliza a si mismo. Dentro de la recursividad se distinguen dos situaciones
llamadas caso base (final) y caso general (recursivo).

Caso Base

Son hechos verdadero que no requieren demostración. En esta parte del algoritmo no existen
llamadas recursivas, son condiciones de terminación de ciclos o llamadas recursivas. Un
algoritmo recursivo puede tener varios casos base.

Caso General

Son hechos que se definen en función al cumplimiento de una definición previa. Estas
definiciones previas las define el mismo algoritmo en llamadas a si mismo con parámetros que
aseguren el cumplimiento de uno de los casos base en cada llamada recursiva.

La recursividad se expresa, generalmente, mediante una función recursiva. Una función que se
llama así misma se denomina recursiva. Podemos usar recursividad si la solución de un problema
está expresada en función de si misma, aunque de menor tamaño y conocemos la solución no-
recursiva para un determinado caso.

Ventajas: No es necesario definir la secuencia de pasos exacta para resolver el problema.


Desventajas: Podría ser menos eficiente.

15
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Para que una definición recursiva esté completamente identificada es necesario tener un caso base
que no se calcule utilizando casos anteriores y que la división del problema converja a ese caso
base.

Ejemplo: La potencia recursiva

1 si n = 0 (caso base)
n
x =
x · xn−1 si n > 0 (caso general)

Otra función recursiva es el factorial. Como se puede ver a continuación la definición recursiva
es clara.

Definición recursiva de factorial

1 si n = 0
n! =
n . (n − 1)! si n > 0

Por ejemplo calculamos el factorial con n=3. Este proceso se muestra paso a paso a continuación:

1) 3! = 3 * 2!
2) 3! = 3 * 2!
2! = 2 * 1!
3) 3! = 3 * 2!
2! = 2 * 1!
1! = 1 * 0!
4) 3! = 3 * 2!
2! = 2 * 1!
1! = 1 * 0!
0! = 1 (caso base)
5) 3! = 3 * 2!
2! = 2 * 1!
1! = 1 * 1
1
6) 3! = 3 * 2!
2! = 2 * 1
1! = 1 * 1 = 1
7) 3! = 3 * 2
2! = 2 * 1 = 2
8) 3! = 3 * 2 = 6

16
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

El algoritmo del factorial recursivo es el siguiente:

int factorial (int n) {


if (n==0) //Caso base
return 1;
else //Caso general
return n*factorial(n-1);
}

Otros ejemplos de funciones recursivas:

1. Cálculo de la potencia

1 si n = 0
xn =
x · xn−1 si n > 0

int potencia(int base, int expo){


if (expo==0)
return 1;
else
return base * potencia(base,expo-1);

17
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

2. La suma de forma recursiva

a si b = 0
suma(a, b) =
1 + suma(a, b − 1) si b > 0

int suma(int a, int b){


if (b==0)
return a;
else
return 1+suma(a,b-1);
}

3. Búsqueda lineal recursiva (con dos casos base)

Verdad si V [n] = b
Falso si V [0] 6= b
BusquedaLineal(V, n, b) =
(V [n] == b) ó (b 2 {V [0], . . . , V [n − 1]}) en otro caso

int BusquedaLineal(int *V, int n, int b)


{
if (n<0)
return 0;
else
if (V[n]==b)
return 1;
else
return BusquedaLineal(V,n-1,b);
}

4. Búsqueda Binaria recursiva

Líneas básicas (BusBinRec (v, i, j, x)):


1. Calcular la posición del elemento central t = (i + j)/2 entre las posiciones i y j, luego c = v[t].
2. Comparar c con x.
a) Si c = x, el elemento buscado está en la posición t. El proceso termina (éxito).
b) Si c < x, el elemento buscado está en una posición mayor que t entonces
BusBinRec(v, t+1, j, x)
c) Si c > x, el elemento buscado está en una posición menor que t, entonces

18
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

BusBinRec(v, i, t-1, x)
3. Al modificar los extremos puede darse que i > d, en cuyo caso el proceso termina (Fracaso).

int BusBinRec (int v[], int i, int d, int x) // v arreglo, d tamaño, x valor
{
int centro;
if (i<=d) {
centro = (i+d)/2;
if (v[centro]==x) // Caso base 1
return centro;
else
if (v[centro]>x) // Buscar izda.
return BusBinRec (v,i,centro-1,x);
else // Buscar dcha.
return BusBinRec (v,centro+1,d,x);
}
else // i > d

return -1; // Caso base 2


}

19
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

3. Almacenamiento estático en secuencia – Listas

3.1 Aspectos generales de listas

Una lista es un conjunto lineal de elementos o valores del mismo tipo que se encuentran
ordenados y pueden variar en número.

Los elementos de una lista se almacenan normalmente en forma contigua, es decir un elemento
después de otro, en posiciones consecutivas de memoria. Una lista lineal se almacena en la
memoria principal de una computadora en posiciones contiguas sucesivas de memoria. Esta
asignación de memoria se denomina almacenamiento secuencial o estático.

Las líneas así definidas se llaman contiguas. Las operaciones que se pueden realizar con listas
lineales son las siguientes:

1. Insertar, eliminar o localizar un elemento


2. Determinar el tamaño de la lista – número de elementos
3. Recorrer la lista para localizar un determinado elemento
4. Clasificar los elementos de la lista en orden ascendente o descendente
5. Unir dos o más listas en una sola
6. Dividir una lista en varias sublistas
7. Copiar un a lista
8. Borrar una lista

Una lista lineal se almacena en la memoria de la computadora en posiciones sucesivas o


adyacentes y se procesa como un arreglo unidimensional. En este caso el acceso a cualquier
elemento de la lista y la adición de nuevos elementos es fácil; sin embargo, la inserción o borrado
requieren desplazamiento de lugar de los elementos que le siguen y, en consecuencia, el diseño
de algoritmos específicos.

Las operaciones de añadir y eliminar se efectúan únicamente en los extremos de la lista. Esta
limitación es una de las razones por las que esta estructura de datos es poco utilizada como tal;
sin embargo, es útil en casos donde la cantidad de inserciones y/o eliminaciones es reducida, es
decir la estructura es estática. Asimismo, esta estructura permite diseñar otras estructuras más
complejas, implementándolas de manera sencilla; en especial las estructuras dinámicas de pilas y
colas.

3.2. Pilas

Una pila (o snack) es un tipo especial de lista lineal en la que la insrción y el borrado de nuevos
elementos se realiza sólo por un extremo que se denomina cima o tope (top).

20
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

La pila es una estructura con numerosas analogías en la vida real: una pila de platos, una pila de
monedas, una pila de cajas de zapatos, una pila de bandeja, un a pila de libros, etc.

Dado que la operación de insertar solo puede hacerse por un extremo, -el superior- los elementos
solo pueden eliminarse en orden inverso al que se insertaron en la pilas. El último elemento que
se pone en la pila es el primero que se puede sacar; por ello, a estas estructuras se les conoce por
el nombre LIFO (Last In, First Out) o UEPS (Ultimo en Entrar, Primero en Salir).

Las pilas se pueden representar como en la figura de la izquierda y se pueden implementar en un


arreglo con un determinado tamaño, como el la figura de la derecha.

Arreglo que implementa una pila

1 2 P-1 P n-1 n
Cima 
Cima Longitud máxima de la pila

Otra manera de implementar pilas es de manera dinámica a través de punteros; sin embargo,
cualquiera que sea su implementación, las operaciones sobre las pilas son las mismas y deben ser
transparentes para su utilización

Las operaciones básicas sobre una pila son: poner o meter un nuevo elemento y sacar o quitar un
elemento. En la siguiente lista se muestran estas y otras operaciones y su significado:

P = Cima Apuntador a la cima de la pila


Vacía Función lógica que indica si la pila está vacía
Push Subprograma para añadir elementos
Pop Subprograma para quitar elementos
LongMax Longitud máxima que puede tener la pila

Entre las muchas aplicaciones de las pilas están las que se usan en ciencias de la computación
como el rastreo de llamados a subprogramas, el control de bucles, la evaluación de expresiones
aritméticas, etc.

Aplicación de pila: Evaluación de expresiones

21
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Esta aplicación consiste en calcular el valor de una expresión aritmética dada; es decir, siguiendo las
reglas de la aritmética se debe obtener un valor final. Las expresiones aritméticas, normalmente se
escriben en una notación denominad infija, en la que el operador se encuentra en medio de los operandos.

Proceso para evaluación de una expresión aritmética

Una expresión aritmética escrita normalmente en notación infija, se evalúa según los siguientes
pasos:

1. Transformar la expresión de infija o postfija


2. Evaluar la expresión en postfija

Se entiende que la notación postfija es aquella cuyos operados aparecen primero, luego aparece el
operador.

Para resolver cada paso es fundamental que los algoritmos utilicen pilas.

Transformación de infija a postfija

Se parte de una expresión en notación infija que tiene operandos, operadores y puede tener
paréntesis. Los operandos vienen representados por letras, los operadores van a ser:

Operadores: ^, *, / , +, -

La transformación se realiza utilizando una pila en la que se almacenan los operadores y los
paréntesis izquierdos. La expresión se va leyendo carácter a carácter, los operandos pasan
directamente a formar parte de la expresión en postfija.
Los operadores se meten en la pila siempre que esta esté vacía, o bien siempre que tengan
mayor prioridad que el operador cima de la pila (o bien igual si es la máxima prioridad). Si la
prioridad es menor o igual se saca el elemento cima de la pila y se vuelve a hacer la
comparación con el nuevo elemento cima.

Los paréntesis izquierdos siempre se meten en la pila, asignándoles la mínima prioridad. Cuando
se lee un paréntesis derecho, hay que sacar todos los operadores de la pila pasando a formar parte
de la expresión postfija, hasta llegar a un paréntesis izquierdo, el cual se elimina de la pila, ya que
los paréntesis no forman parte de la expresión postfija.

El algoritmo termina cuando no hay más ítems en la expresión infija, y la pila esta vacía.

Sea por ejemplo la expresión infija

A+B*C/(D–E)-F

22
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Items en Expresión infija


Estado de la PILA Comentario
Proceso Resultante
El operando A va a expresión postfija.
A A Pila vacia

El operador + va a la pila, ya que está vacía.


+ A +

El operando B va a expresión postfija y el


B* AB +* operador * tiene mayor prioridad que la cima de
la pila, en consecuencia va a la pila
El operando C va a expresión postfija y el
operador / tiene igual prioridad que la cima de la
C/ ABC* +/
pila, por tanto, salen ítems de la pila hasta un
item de menor prioridad. Luego / entra a la pila.
El paréntesis izquierdo ( va siempre a la pila
( ABC* +/(

El operando D va a expresión postfija y el


D- ABC*D +/(- operador – va a la pila, ya que tiene mayor
prioridad que la cima.
El operando E va a expresión postfija y el
E) ABC*DE- +/ paréntesis derecho ) obliga a sacar ítems de la
pila hasta un paréntesis izquierdo.
El operador – tiene menor prioridad que la cima,
en consecuencia salen de la pila hasta un
- ABC*DE-/+ -
operador de menor prioridad que él. Luego el
entra a la pila.
El operando F va a la expresión postfija
F ABC*DE-/+F -

Como ya no hay más ítems en la expresión, la


No hay ABC*DE-/+F- Pila vacia
pila se debe vaciar.

En consecuencia, la expresión postfija resultante es

ABC*DE-/+F-

Con el fin de que el algoritmo de conversión de infija a postfija se pueda manejar mejor, es
necesario definir prioridades para los operadores y el paréntesis izquierdo.

Operador Prioridad
^ 3
*, / 2
+, - 1
( 0

23
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Ejercicio.

1. Hallar el equivalente postfijo de la siguiente expresión infija: A*(B+C–(D/E^F)+G)-H


Respuesta ABC+DEF^/-G+*H-
2. Escribir el algoritmo para convertir una expresión infija en postfija.

3.3. Colas

Al igual que las pilas, las colas son estructuras de listas lineales pero con una disciplina diferente
de aquellas. Las colas insertan elemento por un extremo y sacan elementos por el otro. Esto se
conoce como FIFO (First In, First Out) o PEPS (Primero en Entrar, Primero en Salir).

Las eliminaciones se realizan por el comienzo de la lista (front) llamado frente, y las inserciones
se realizan por el otro extremo (rear) o final

En la vida real se tienen numerosos ejemplos de colas: la cola de un cine, la cola de atención en
un banco, una cola de vehículos en una gasolinera, etc. En todas ellas, el primero en llegar es el
primero en ser atendido.

En el campo de informática también se encuentran muchas aplicaciones de colas. Por ejemplo en


un sistema informático suele haber una impresora que atiende las solicitudes de impresión en el
orden de una cola. Existe otra aplicación en un sistema de tiempo compartido, donde un
procesador central atiende la petición de proceso según el orden de llegada, suponiendo que
existen varias terminales, y otros periféricos que demandan tiempo de procesador.

Un ejemplo bastante actual de aplicación de colas lo constituye el proceso de atención al público


por parte de bancos y otras entidades. En este caso se pueden presentar no solo una sino varias
colas, en función de determinados criterios de atención o prioridad.

Eliminación Inserción
1 2 n-1 n

Frente Final

Las colas se pueden implementar en forma estática mediante arreglos, como se ve en la figura, o
bien con el uso de punteros, en forma dinámica.

Cuando se implementa en forma estática en un arreglo se presenta el problema de que luego de


varias inserciones y eliminaciones, la cola parece “viajar” por el arreglo, hasta que llega a su

24
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

límite final, en cuyo caso el estado de la cola será aparentemente de cola llena, sin embargo, en el
frente de la cola pueden haber espacios vacíos. Una forma de superar esta limitación es utilizando
el arreglo como un espacio circular que se recicla con el primer elemento, cada vez que la cola
alcanza el final del arreglo. Este problema se puede abordar de diferentes maneras.

La implementación más adecuada para colas es mediante las llamadas listas encadenadas,
mediante el uso de punteros. Esta implementación se estudiará más adelante.

Operaciones Básicas

Crear: se crea la cola vacía.


Añadir (entrar, push): se
añade un elemento a la cola.
Se añade al final de esta.
Sacar (salir, pop): se elimina
el elemento frontal de la
cola, es decir, el primer
elemento que entró.
Frente (consultar, front): se
devuelve el elemento frontal
de la cola, es decir, el
primero elemento que entró.

Implementación de la operaciones

Las operaciones básicas sobre una cola son: poner o meter un nuevo elemento al final y sacar o
quitar un elemento del frente. En la siguiente lista se muestran estas y otras operaciones y su
significado:

F = Frente Apuntador hacia el frente de la cola


R = Final Apuntador hacia el final de la cola
Vacía Función lógica que indica si la cola está vacía
Poner Subprograma para añadir elementos por el frente
Sacar Subprograma para quitar elementos por el final
LongMax Longitud máxima que puede tener la cola

Aplicaciones

Ejemplos de colas en la vida real serían: personas comprando en un supermercado, esperando


para entrar a ver un partido de futbol, esperando en el cine para ver una película, una pequeña
peluquería, las colas en los bancos, etc. La idea esencial es que son todos líneas de espera.

En estos casos, el primer elemento de la lista realiza su función (pagar comida, pagar entrada para
el partido o para el cine) y deja la cola. Este movimiento está representado en la cola por la

25
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

función pop o desencolar. Cada vez que otro elemento se añade a la lista de espera se añaden al
final de la cola representando la función push o encolar. Hay otras funciones auxiliares para ver
el tamaño de la cola (size), para ver si está vacía en el caso de que no haya nadie esperando
(empty) o para ver el primer elemento de la cola (front).

Las Colas también se utilizan en muchas maneras en los sistemas operativos para planificar el uso
de los distintos recursos de la computadora. Uno de estos recursos es la propia CPU (Unidad
Central de Procesamiento).

Si esta trabajando en una sistema multiusuario, cuando le dice a la computadora que ejecute un
programa concreto, el sistema operativo añade su petición a su “cola de trabajo”.

Cuando su petición llega al frente de la cola, el programa solicitado pasa a ejecutarse.


Igualmente, las colas se utilizan para asignar tiempo a los distintos usuarios de los dispositivos
de entrada/salida (E/S), impresoras, discos, cintas y demás. El sistema operativo mantiene colas
para peticiones de imprimir, leer o escribir en cada uno de estos dispositivos.

Implementación de colas en C++

#ifndef COLA
#define COLA // define la cola

template <class T>


class Cola {

private:
struct Nodo {
T elemento;
Nodo* siguiente; // coloca el nodo en la segunda posicion
}* primero;
Nodo* ultimo;
unsigned int elementos;

public:
Cola() {
elementos = 0;
}

~Cola() {
while (elementos != 0) pop();
}

void push(const T& elem) {


Nodo* aux = new Nodo;
aux->elemento = elem;
if (elementos == 0) primero = aux;
else ultimo->siguiente = aux;
ultimo = aux;
++elementos;

26
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

void pop() {
Nodo* aux = primero;
primero = primero->siguiente;
delete aux;
--elementos;
}

T consultar() const {
return primero->elemento;
}

bool vacia() const {


return elementos == 0;
}

unsigned int size() const {


return elementos;
}

};

#endif

Tipos de colas

Colas circulares (anillos): en las que el último elemento y el primero están unidos.

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 Inicio 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 inicio ó al final.

Bicolas de salida restringida: Son aquellas donde sólo se elimina por el final, aunque se
puede insertar al inicio y al final.

27
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Aplicación de cola: Colas de atención al público en un banco

Una cola de atención al público, en realidad es un conjunto de colas que son atendidas en forma
cíclica según determinada prioridad. Esta prioridad puede ser administrada inclusive sobre la
periodicidad de atención a cada cola; es decir las colas con mayor prioridad pueden atenderse en
forma secuencial un cierto número de veces (según el número de elementos que tengan en ese
momento).

Por otra parte, la cantidad de puntos de atención puede ser diseñada según los requerimientos. EL
programa de ser tan flexible que permita configurar todos estos detalles.Esta aplicación tiene
como componente principal la interfaz del usuario.

28
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

4. Almacenamiento dinámico

4.1 Manejo dinámico de la memoria – Los punteros

¿Que es una variable?

Una computadora opera manipulando direcciones de memoria y los valores almacenados en


dichas direcciones. Un lenguaje de programación es una herramienta que permite al programador
codificar operaciones binarias en un lenguaje más parecido a nuestro lenguaje natural. Un
programa que realiza la traducción de instrucciones desde un lenguaje de programación dado al
lenguaje de maquina se llama compilador.

Una variable es un recurso, entre otros, para manipular un dato binario de modo más legible. Una
variable es un identificador de dato, al igual que el nombre de una función, este NOMBRE
representa para la maquina una localidad de memoria donde el programa puede almacenar y
manipular un dato.

Una declaración de variable como:

char xx=”B”;
int nn=25;

produce una asociación entre los nombre 'xx', “nn” y sus correspondientes espacios de
almacenamiento en memoria. Por lo tanto hay dos elementos relacionados con el nombre de una
variable: un valor que se puede almacenar allí y una dirección de memoria para la variable.

Existe una correspondencia entre el nombre de cada variable y una dirección de memoria a quien
representa; es decir, el nombre de la variable propiamente dicha (por ejemplo xx) es solo un
“símbolo gráfico” que realmente significa una dirección. Esta asociación podemos representarla
como una tabla virtual de variables. En consecuencia, esta tabla, luego de las anteriores
declaraciones y asignaciones, podría tener el siguiente estado:

Nombre de la variable Dirección de memoria

xx 0012FF8B
nn 0012FF84

Realmente las direcciones de la tabla virtual existen en lo que podríamos llamar el espacio real
de datos, el cual se puede representar como una tabla que podría tener el siguiente estado:

Dirección de memoria Contenido

0012FF8B 00000042 B
0012FF84 00000019 25

29
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Además del identificador "xx", tenemos la palabra "char" que nos indica el tipo (type) de la
variable. El tipo nos indica:

 Cuantas celdas de memoria se reservan para ese nombre de variable; es decir cuantos bytes.
 Como se interpretarán los bits contenidos en ese espacio o dirección de memoria

Para el manejo de direcciones y contenidos de variables existen dos operadores: & de dirección y
* de indirección.

Operador de direccion &. El operador (ampersand) & aplicado como prefijo a una variable,
devuelve la dirección de memoria en la que se encuentra la variable (sea variable normal o
variable puntero).

Operador de indireccion *. El asterisco escrito como prefijo de una variable puntero o de una
dirección de memoria, permite acceder o referirse al contenido de dicho puntero o dirección de
memoria.

Por ejemplo:

&xx : devuelve la dirección en la que se encuentra la variable xx.


*xx: permite acceder al contenido de la variable xx.

Entonces, si escribimos: printf("%p \n", &xx); // p formato para punteros

obtendremos una dirección de memoria como 0012FF8B

si escribimos: *(&xx) = ’C’;

estamos almacenando el carácter „C‟ en la variable xx. Esta última expresión es equivalente a:

xx = ’C’;

Por otra parte se sabe que un byte es la menor unidad de información que pueden direccionar la
mayoría de las computadoras. En la mayoría de las arquitecturas el tipo char ocupa un solo byte.
Un bool admite solo dos valores diferentes, pero es almacenado como un byte. El tipo int de C++
ocupa generalmente 2 bytes, un long 4, double 8, y así con el resto de los tipos.

El otro punto es la relación entre lo que hay en una celda de memoria y como es interpretado. Lo
que hay en un celda cuya extensión es un byte es simplemente un conjunto de ocho estados
posibles (8 bits) que a nivel hardware admiten dos estados diferenciables, estados que pueden ser
simbolizados como 'verdadero/falso', 0/1, o cualquier otro par de valores.

Así, la variable xx del ejemplo anterior en el espacio de datos, tiene como contenido lo siguiente:

30
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

00000042

¿Pero que significa este contenido?, Depende en gran medida del tipo (type) que hayamos
asociado a esa celda (y suponiendo que exista tal asociación). Ese contenido interpretado como
un hexadecimal es 0x42, en decimal es 66, y si fue asociado al tipo char (este es el caso del
ejemplo) representara la letra 'B', cuyo codigo ASCII es igual a 66. En ninguna localidad de
memoria hay algo como la letra 'B', lo que encontramos son valores binarios que en caso de estar
asociados a char y en caso de que lo saquemos en pantalla como char hará que veamos
encendidos ciertos pixeles de pantalla, en los cuales reconoceremos una representación de la letra
'B'.

Puesto que la representación binaria de datos ocupa demasiado espacio, es preferible utilizar el
sistema hexadecimal, que además de ser muy fácil de traducir a binario es más práctico.

Punteros

Un puntero es un tipo especial de variable, cuyo contenido es el valor de una dirección de


memoria, esta dirección puede ser la de una variable individual, pero más frecuentemente será la
de un elemento de un arreglo, o una estructura u objeto de una clase. Los punteros, al igual que
una variable común, pertenecen a un tipo (type), se dice que un puntero 'apunta a' ese tipo al que
pertenece.

Continuando con el ejemplo anterior, declaramos los siguientes punteros


char *x; // Puntero a char
int *n; // Puntero a entero

x = &xx; // asignamos la dirección de la variable xx al puntero x


n = &nn; // asignamos la dirección de la variable nn al puntero n

Luego de las anteriores declaraciones, la tabla virtual de variables podría tener el siguiente
estado:
Nombre de la variable Dirección de memoria

xx 0012FF8B

nn 0012FF84

*x 0012FF80

*n 0012FF7C

31
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

También podemos suponer que el espacio real de datos podría tener el siguiente estado:

Dirección de memoria Contenido

0012FF8B 00000042 B
0012FF84 00000019 25
0012FF80 0012FF8B Apunta a xx
0012FF7C 0012FF84 Apunta a nn

Como se puede ver, un puntero es, en esencia, una variable cuyo contenido es una dirección de
memoria, que apunta a un tipo dado.

El siguiente programa en C++, prueba lo explicado en este apartado

#include <iostream.h>
#include <stdio.h>

main()
{
char xx='B';
int nn=25;
char *x;
int *n;

x = &xx;
n = &nn;

printf("TABLA DE VARIABLES\n\n");
printf(" Variable Direccion \n\n");
printf("%15s ", "xx"); printf("%15p \n", &xx);
printf("%15s ", "nn"); printf("%15p \n", &nn);

printf("%15s ", "*x"); printf("%15p \n", &x);


printf("%15s ", "*n"); printf("%15p \n", &n);

printf("\n\n\nESPACIO DE DATOS\n\n");

printf(" Direccion Contenido Comentario\n\n");


printf("%15p ", &xx); printf("%15p ", xx); printf(" %-15c \n", xx);
printf("%15p ", &nn); printf("%15p ", nn); printf(" %-15i \n", nn);

printf("%15p ", &x); printf("%15p ", x); printf(" %-15s \n", "apunta a xx");
printf("%15p ", &n); printf("%15p ", n); printf(" %-15s \n", "apunta a nn");

getchar();
}

32
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Independientemente del tamaño (sizeof en C++) del objeto apuntado, el valor almacenado por el
puntero será el de una única dirección de memoria. En sentido estricto un puntero no puede
almacenar la dirección de memoria de un arreglo (completo), sino la de un elemento del arreglo,
y por este motivo no existen diferencias sintácticas entre punteros a elementos individuales y
punteros a arrays. La declaración de un puntero a un tipo char y otro a un arreglo de char es
igual.

Al definir variables o arreglos hemos visto que el tipo (type) modifica la cantidad de bytes que se
usaran para almacenar tales elementos, así un elemento de tipo 'char' utiliza 1 byte, y un entero 2
o 4. No ocurre lo mismo con los punteros, el tipo no influye en la cantidad de bytes asociados al
puntero, pues todas las direcciones de memoria se pueden expresar con solo 2 bytes (o 4 si es una
dirección de otro segmento)

La forma general de declarar un puntero es la siguiente:

TipoDeDato *NombrePuntero

Ejemplos:

int * pe // pe es un puntero a un entero


float *f // f es un puntero a float
TMatriz *m // m es un puntero a un objeto de la clase TMatriz.
int **t // t es un puntero a un puntero que apunta a entero
TRacional *r // r es un puntero a objetos de la clase TRacional

4.2. Listas encadenadas

Una estructura de datos muy utilizada en programación es la lista encadena (editores de texto,
compiladote, interfaces de bases de datos, etc.). En C++, es posible crear clases que representen a
listas encadenadas y que facilitarán la gestión de estas listas.

Una lista encadenada consta de nodos que se pueden almacenar en cualquier parte de la memoria
del sistema. Cada nodo contiene los datos asociados con el nodo y la dirección o puntero del
siguiente nodo. La siguiente figura muestra la estructura lógica de la lista encadenada.

33
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Donde Head representa el comienzo de la lista

Una lista encadenada se puede implementar mediante arreglos, esta manera de implementación
de listas basadas en arreglos funcionan bien para listas estáticas, sin embargo tiene algunas
desventajas importantes:

 Alta volatilidad de datos. No resultan eficientes para listas dinámicas en las que
continuamente se están añadiendo y eliminando elementos.
 Procesos lentos. La razón es que, en general, para insertar o eliminar un elemento de la lista
tenemos que desplazar otros elementos.
 Cantidad limitada de datos. Existen problemas de programación en los cuales no se conocen
con anticipación la cantidad máxima de elementos a insertar a la lista.
 Los procesos de inserción eliminación en las listas estáticas requieren recorrer gran parte de
los datos y modificar su posición de todos los elementos causando un retraso en el
procesamiento

La mayoría de estas desventajas pueden ser superadas utilizando listas implementadas con
punteros; es decir manejando dinámicamente la memoria.

4.3 Diseño e implementación dinámica de listas encadenadas

Para implementar una lista encadena mediante punteros, es necesario definir el concepto de nodo,
en base al cual se definirá la lista encadenada.

Estructura de Nodo

Un nodo es una estructura en memoria dividida en al menos dos campos:

Un campo para los NODO Un campo para


datos (de cualquier almacenar la
tipo) que contienen dirección de
la información o m emoria del
elementos que siguiente nodo de
conforman la lista Dato Link información si este
existe

Dato Null

Dato Link Dato

El Campo de enlace
link proporciona la
dirección o referencia
En caso de que un nodo sea
del siguiente nodo de 34 nodo terminal, se
la lista
representa como Null, cero o
un símbolo de tierra
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Una posible implementación en C++ de un nodo es la siguiente:

class nodo
{
public:
char dato;
nodo *link;

nodo() {
dato = ' ';
link = NULL;
}

void imprimir();
// void poner(char valor);
// char obtener();
};

El programa completo se puede ver en clase lista)

Estructura de Lista encadenada

Una lista encadenada o lista enlazada es una colección de elementos ó nodos, en donde cada uno
contiene datos y un enlace o link al siguiente nodo de la lista
L

Dato Link Dato Link Dato Link

Para acceder a los elementos de la lista encadenada basta conocer la dirección de memoria del
primer nodo.

Esquema y diseño de una lista enlazada

Dependiendo de donde se aplique la lista enlazada se pueden optar por diversos diseños de
acuerdo a las necesidades del problema en particular a continuación mostraremos algunos de los
diseños mas utilizados

a) Con un solo puntero

35
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Para agregar un elemento o llegar al último elemento de la lista se tiene que navegar toda la lista
desde el inicio, este hecho se considera como una desventaja ya que representa una tardanza en el
hecho de navegar secuencialmente de nodo a nodo
Primero

b) Con dos punteros


Dato Link Dato Link Dato Link
Mejora el inconveniente anterior pero ocupa más espacio para almacenar el puntero al último
nodo. Si se requiere conocer la cantidad de elementos se tendría que navegar y contar los
elementos (Mucho proceso para algo que puede ser más simple) este hecho se considera como
una desventaja.

Primero Ultimo
L F

Dato Link Dato Link Dato Link

class lista {
private :
nodo *L; // apunta al comienzo de la lista
nodo *F; // apunta al final de la lista
int tam;

public:
lista() {
L = NULL;
tam = 0;
}
void tamano();
void anadirFin(char c);
void anadirIni(char c);
void recorrer();

void insertar(int u, char c);


// inserta c despues de la posición u.
// Si excede el tamaño actual inserta al final
void eliminar(int u); // elimina de la posición u
};

El programa completo se puede ver en clase lista)

36
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

c) Con dos punteros y un campo tamaño

Mejora el diseño anterior manteniendo un campo (tamaño) actualizado en todos los procesos de
inserción y eliminación que permita almacenar la cantidad de nodos de la lista, de modo que el
hecho de conocer la cantidad de nodos no sería más que una simple devolución de esta variable
actualizada en todas las operaciones que modifiquen la cantidad de nodos de la lista.

Primero Ultimo
Tamaño = 3

23 Link 60 Link 90 Link

Definición de operaciones

Las principales operaciones que podemos realizar sobre la lista encadenada simple son:

 Recorrido
 Inserció n
 Borrado
 Búsqueda

Operación de Recorrido

Consiste en visitar cada uno de los nodos que forman la lista. Se comienza con el primero, se
toma el valor del campo Link para avanzar al segundo nodo, así sucesivamente.
Cuando se encuentre un link = Null se habrá terminado de recorrer la lista

L F
x
5 2 9 7
0

5 2 9 7

37
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Utilizando una variable auxiliar P que se inicia en L y desde ahí se comienza a navegar de nodo
en nodo utilizando el Link de cada uno de ellos. El hecho de utilizar un puntero auxiliar es para
no alterar el valor del puntero L ya que si se altera se puede perder la dirección de toda la lista
enlazada.

Operación de Inserción

Consiste en insertar un nodo a cualquier parte de la lista, esto se realiza mediante asignación
dinámica de memoria es decir que se reserva memoria para un nuevo nodo y luego se almacena
en él el dato para enlazarlo a la lista en el lugar deseado ya sea al principio de la lista, al final o
entre medio de la lista. En cualquiera de los casos se deben actualizar los punteros de los nodos
adyacentes para mantener la lista enlazada.

El proceso se realiza en cuatro pasos, dada una lista como la que muestra a continuación:

5 2 9 7

1) Se crea un nuevo nodo, al cual se le asigna un dato:

2) Se recorre la lista hasta el lugar de inserción, mediante un puntero P:

L P

5 2 9 7

3) Se copia el campo link del nodo apuntado por P, al campo link del nuevo nodo N:

L P
4

5 2 9 7

38
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

4) Por último, se copia en el campo link del nodo apuntado por P, el puntero N del nuevo nodo:

L P 4
L P

5 2 9 7

5 2 9 7

Operación de Borrado

Consiste en eliminar un nodo de lista enlazada, en este caso se debe actualizar las direcciones de
los punteros necesarios y liberar la memoria ocupada por el nodo al sistema operativo para que
pueda ser utilizado en otras operaciones.

Operación de Búsqueda

Consiste en buscar un elemento cualquiera en la lista enlazada, este hecho se realiza recorriendo
la lista y comparando el dato de cada uno de los nodos visitados con el elemento que se está
buscando hasta que se lo encuentre. Es similar a la operación de recorrido.

4.4. Implementación dinámica de estructuras de datos

Las estructuras de datos dinámicas como son las pilas y las colas son mejor implementadas
mediante el manejo dinámico de memoria, a través de punteros.

Implementación de pilas con punteros

Utilizando la estructura nodal de una lista encadena, es posible implementar una pila como una
lista restringida por la disciplina de inserción y eliminación que impone una pila. Esto se puede
realizar utilizando el concepto de herencia de la POO, que permite diseñar una nueva estructura a
partir de una ya existente. En este caso, diseñar una clase pila a partir de la clase lista.

39
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

5. Arboles y Grafos
5.1 Arboles binarios

Un árbol es una estructura no lineal constituida por un conjunto de elementos arreglados en forma
jerárquica, en la que cada elemento tiene un antecesor (padre) y puede tener de cero a dos
sucesores (hijos). Los elementos se denominan nodos.
Existe un único nodo especial llamado raíz que no tiene antecesor.

Si el número máximo de sucesores es dos, se dice que el árbol es binario.

Antes de seguir entrando en materia, será conveniente dar unas cuantas definiciones:

El primer nodo del árbol recibe el nombre de raíz, los enlaces reciben el nombre de aristas.
Se dice que un nodo B es hijo de un nodo A, si existe alguna arista que va desde A hasta B.
Por ejemplo, en la figura, 7 es hijo de 2, 4 es hijo de 9, 11 es hijo de 6, etc.
Al mismo tiempo se dice que un nodo A es padre de un nodo B si existe una arista que va
desde A hasta B. Ej. 9 es padre de 4, 6 es padre de 5 y de 11, etc.
Se dice que un nodo es hoja, si no tiene hijos. Ej. 11 y 4 son hojas, pero 6, 7, y 9 no lo son.
La rama izquierda de un nodo es el árbol que tiene como raíz el hijo izquierdo de tal nodo,
por ejemplo {7, 2, 6, 5, 11} son los nodos de la rama izquierda de 2.
La rama derecha de un nodo es el árbol que tiene como raíz el hijo derecho de tal nodo.
Los nodos tambien pueden ser llamados vertices.
Nivel: número de ramas que hay que recorrer para llegar de la raíz a un nodo. Ejemplo: el
nivel del nodo 2 es 1 (es un convenio), el nivel del nodo 9 es 3.
Altura: el nivel más alto del árbol. En el ejemplo de la figura la altura es 4.
Amplitud: es el mayor valor del número de nodos que hay en un nivel. En la figura, la
amplitud es 3.
Grado de un nodo: el número de descendientes directos que tiene. Ejemplo: 5 tiene grado 2,
2 tiene grado 0, 6 tiene grado 2.
Grado de un árbol. es el máximo grado de entre todos los nodos.

40
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Definición de árbol

Un árbol es una estructura de datos, que puede definirse de forma recursiva como:

- Una estructura vacía o


- Un nodo (elemento o clave de información) más un número finito de estructuras tipo
árbol, disjuntos, llamados subárboles. Si dicho número de estructuras es inferior o igual a
2, se tiene un árbol binario.

Es, por tanto, una estructura no secuencial.

Otra definición nos da el árbol como un tipo de grafo (ver grafos): un árbol es un grafo acíclico,
conexo y no dirigido. Es decir, es un grafo no dirigido en el que existe exactamente un camino
entre todo par de nodos. Esta definición permite implementar un árbol y sus operaciones
empleando las representaciones que se utilizan para los grafos. Sin embargo, en esta sección no
se tratará esta implementación.

Formas de representación

- Mediante un grafo:

Figura 1

Mediante un diagrama encolumnado:

a
b
d
c
e
f

En computación se utiliza mucho una estructura de datos llamada árbol binario. Estos árboles
tienen 0, 1 ó 2 descendientes como máximo. El árbol de la figura anterior es un ejemplo válido de
árbol binario.

41
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Declaración de árbol binario

Se definirá el árbol con una clave de tipo entero (puede ser cualquier otra tipo de datos) y dos
hijos: izquierdo (izq) y derecho (der). Para representar los enlaces con los hijos se utilizan
punteros. El árbol vacío se representará con un puntero nulo.

Un árbol binario puede declararse de la siguiente manera:

class Tarbol
{
public:
int clave;
Tarbol *izq;
Tarbol *der;
};

Recorridos sobre árboles binarios

Se consideran dos tipos de recorrido: recorrido en profundidad y recorrido en amplitud a nivel.


Puesto que los árboles no son secuenciales como las listas, hay que buscar estrategias
alternativas para visitar todos los nodos.

Recorridos en profundidad

* Recorrido en preorden: consiste en visitar el nodo actual (visitar puede ser simplemente
mostrar la clave del nodo por pantalla), y después visitar el subárbol izquierdo y una vez visitado,
visitar el subárbol derecho. Es un proceso recursivo por naturaleza.
Si se hace el recorrido en preorden del árbol de la figura 1 las visitas serían en el orden siguiente:
a,b,d,c,e,f.

void preorden(Tarbol *a)


{
if (a != NULL) {
visitar(a);
preorden(a->izq);
preorden(a->der);
}
}

* Recorrido en inorden u orden central: se visita el subárbol izquierdo, el nodo actual, y después
se visita el subárbol derecho. En el ejemplo de la figura 1 las visitas serían en este orden:
b,d,a,e,c,f.

void inorden(Tarbol *a)


{
if (a != NULL) {
inorden(a->izq);
visitar(a);
inorden(a->der);
}
}

42
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

* Recorrido en postorden: se visitan primero el subárbol izquierdo, después el subárbol derecho,


y por último el nodo actual. En el ejemplo de la figura 1 el recorrido quedaría así: d,b,e,f,c,a.

void postorden(Tarbol *a)


{
if (a != NULL) {
postorden(a->izq);
postorden(a->der);
visitar(a);
}
}

La ventaja del recorrido en postorden es que permite borrar el árbol de forma consistente. Es
decir, si visitar se traduce por borrar el nodo actual, al ejecutar este recorrido se borrará el árbol o
subárbol que se pasa como parámetro. La razón para hacer esto es que no se debe borrar un nodo
y después sus subárboles, porque al borrarlo se pueden perder los enlaces, y aunque no se
perdieran se rompe con la regla de manipular una estructura de datos inexistente. Una alternativa
es utilizar una variable auxiliar, pero es innecesario aplicando este recorrido.

Recorrido en amplitud:

Consiste en ir visitando el árbol por niveles. Primero se visitan los nodos de nivel 1 (como mucho
hay uno, la raíz), después los nodos de nivel 2, así hasta que ya no queden más.
Si se hace el recorrido en amplitud del árbol de la figura una visitaría los nodos en este orden:
a,b,c,d,e,f
En este caso el recorrido no se realizará de forma recursiva sino iterativa, utilizando una cola (ver
Colas) como estructura de datos auxiliar. El procedimiento consiste en encolar (si no están
vacíos) los subárboles izquierdo y derecho del nodo extraido de la cola, y seguir desencolando y
encolando hasta que la cola esté vacía.
En la codificación que viene a continuación no se implementan las operaciones sobre colas.

void amplitud(Tarbol *a)


{
tCola cola; /* las claves de la cola serán de tipo árbol binario */
arbol *aux;

if (a != NULL) {
CrearCola(cola);
encolar(cola, a);
while (!colavacia(cola)) {
desencolar(cola, aux);
visitar(aux);
if (aux->izq != NULL) encolar(cola, aux->izq);
if (aux->der != NULL) encolar(cola, aux->der);
}
}
}

Por último, considérese la sustitución de la cola por una pila en el recorrido en amplitud. ¿Qué
tipo de recorrido se obtiene?

43
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Construcción de un árbol binario

Hasta el momento se ha visto la declaración y recorrido de un árbol binario. Sin embargo no se ha


estudiado ningún método para crearlos. A continuación se estudia un método para crear un árbol
binario que no tenga claves repetidas partiendo de su recorrido en preorden e inorden,
almacenados en sendos arrays.

Antes de explicarlo se recomienda al lector que lo intente hacer por su cuenta, es sencillo cuando
uno es capaz de construir el árbol viendo sus recorridos pero sin haber visto el árbol terminado.

Partiendo de los recorridos preorden e inorden del árbol de la figura 1 puede determinarse que la
raíz es el primer elemento del recorrido en preorden. Ese elemento se busca en el array inorden.
Los elementos en el array inorden entre izq y la raíz forman el subárbol izquierdo. Asimismo los
elementos entre der y la raíz forman el subárbol derecho. Por tanto se tiene este árbol:

A continuación comienza un proceso recursivo. Se procede a crear el subárbol izquierdo, cuyo


tamaño está limitado por los índices izq y der. La siguiente posición en el recorrido en preorden
es la raíz de este subárbol. Queda esto:

El subárbol b tiene un subárbol derecho, que no tiene ningún descendiente, tal y como indican los
índices izq y der. Se ha obtenido el subárbol izquierdo completo de la raíz a, puesto que b no
tiene subárbol izquierdo:

44
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Después seguirá construyéndose el subárbol derecho a partir de la raíz c.

Ejercicio

Realizar la implementación de la construcción de un árbol partiendo de los recorridos en


preorden y en inorden en C++.

45
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

5.2 Árbol binario de búsqueda

Un árbol binario de búsqueda es aquel que es:

- Una estructura vacía o


- Un elemento o clave de información (nodo) más un número finito -a lo sumo dos- de estructuras
tipo árbol, disjuntos, llamados subárboles y además cumplen lo siguiente:

* Todas las claves del subárbol izquierdo al nodo son menores que la clave del nodo.
* Todas las claves del subárbol derecho al nodo son mayores que la clave del nodo.
* Ambos subárboles son árboles binarios de búsqueda.

Un ejemplo de árbol binario de búsqueda:

Figura 5

Al definir el tipo de datos que representa la clave de un nodo dentro de un árbol binario de
búsqueda es necesario que en dicho tipo se pueda establecer una relación de orden. Por ejemplo,
suponer que el tipo de datos de la clave es un puntero (da igual a lo que apunte). Si se codifica el
árbol en Pascal no se puede establecer una relación de orden para las claves, puesto que Pascal no
admite determinar si un puntero es mayor o menor que otro.

En el ejemplo de la figura 5 las claves son números enteros. Dada la raíz 4, las claves del
subárbol izquierdo son menores que 4, y las claves del subárbol derecho son mayores que 4. Esto
se cumple también para todos los subárboles. Si se hace el recorrido de este árbol en orden
central (inorden) se obtiene una lista de los números ordenada de menor a mayor.
Cuestión: ¿Qué hay que hacer para obtener una lista de los números ordenada de mayor a menor?

Una ventaja fundamental de los árboles de búsqueda es que son en general mucho más rápidos
para localizar un elemento que una lista enlazada. Por tanto, son más rápidos para insertar y
borrar elementos. Si el árbol está perfectamente equilibrado -esto es, la diferencia entre el
número de nodos del subárbol izquierdo y el número de nodos del subárbol derecho es a lo sumo
1, para todos los nodos- entonces el número de comparaciones necesarias para localizar una clave
es aproximadamente de logN en el peor caso. Además, el algoritmo de inserción en un árbol

46
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

binario de búsqueda tiene la ventaja -sobre los arrays ordenados, donde se emplearía búsqueda
dicotómica para localizar un elemento- de que no necesita hacer una reubicación de los elementos
de la estructura para que esta siga ordenada después de la inserción. Dicho algoritmo funciona
avanzando por el árbol escogiendo la rama izquierda o derecha en función de la clave que se
inserta y la clave del nodo actual, hasta encontrar su ubicación; por ejemplo, insertar la clave 7 en
el árbol de la figura 5 requiere avanzar por el árbol hasta llegar a la clave 8, e introducir la nueva
clave en el subárbol izquierdo a 8.
El algoritmo de borrado en árboles es algo más complejo, pero más eficiente que el de borrado en
un array ordenado.

Ahora bien, suponer que se tiene un árbol vacío, que admite claves de tipo entero. Suponer que se
van a ir introduciendo las claves de forma ascendente. Ejemplo: 1, 2, 3, 4, 5, 6.
Se crea un árbol cuya raíz tiene la clave 1. Se inserta la clave 2 en el subárbol derecho de 1. A
continuación se inserta la clave 3 en el subárbol derecho de 2.
Continuando las inserciones se ve que el árbol degenera en una lista secuencial, reduciendo
drásticamente su eficacia para localizar un elemento. De todas formas es poco probable que se de
un caso de este tipo en la práctica. Si las claves a introducir llegan de forma más o menos
aleatoria entonces la implementación de operaciones sobre un árbol binario de búsqueda que
vienen a continuación son, en general, suficientes.

Existen variaciones sobre estos árboles, como los AVL o Red-Black (no se tratan aquí), que sin
llegar a cumplir al 100% el criterio de árbol perfectamente equilibrado, evitan problemas como el
de obtener una lista degenerada.

Operaciones básicas sobre árboles binarios de búsqueda

- Búsqueda

Si el árbol no es de búsqueda, es necesario emplear uno de los recorridos anteriores sobre el árbol
para localizarlo. El resultado es idéntico al de una búsqueda secuencial. Aprovechando las
propiedades del árbol de búsqueda se puede acelerar la localización. Simplemente hay que
descender a lo largo del árbol a izquierda o derecha dependiendo del elemento que se busca.

boolean buscar(tarbol *a, int elem)


{
if (a == NULL) return FALSE;
else if (a->clave < elem) return buscar(a->der, elem);
else if (a->clave > elem) return buscar(a->izq, elem);
else return TRUE;
}

- Inserción

La inserción tampoco es complicada. Es más, resulta prácticamente idéntica a la búsqueda.


Cuando se llega a un árbol vacío se crea el nodo en el puntero que se pasa como parámetro por
referencia, de esta manera los nuevos enlaces mantienen la coherencia. Si el elemento a insertar
ya existe entonces no se hace nada.

47
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

void insertar(tarbol **a, int elem)


{
if (*a == NULL) {
*a = (arbol *) malloc(sizeof(arbol));
(*a)->clave = elem;
(*a)->izq = (*a)->der = NULL;
}
else if ((*a)->clave < elem) insertar(&(*a)->der, elem);
else if ((*a)->clave > elem) insertar(&(*a)->izq, elem);
}

La versión orientada a objetos de este algoritmo es la siguiente:

- Borrado

La operación de borrado si resulta ser algo más complicada. Se recuerda que el árbol debe seguir
siendo de búsqueda tras el borrado. Pueden darse tres casos, una vez encontrado el nodo a borrar:
1) El nodo no tiene descendientes. Simplemente se borra.
2) El nodo tiene al menos un descendiente por una sola rama. Se borra dicho nodo, y su primer
descendiente se asigna como hijo del padre del nodo borrado. Ejemplo: en el árbol de la figura 5
se borra el nodo cuya clave es -1. El árbol resultante es:

3) El nodo tiene al menos un descendiente por cada rama. Al borrar dicho nodo es necesario
mantener la coherencia de los enlaces, además de seguir manteniendo la estructura como un árbol
binario de búsqueda. La solución consiste en sustituir la información del nodo que se borra por el
de una de las hojas, y borrar a continuación dicha hoja. ¿Puede ser cualquier hoja? No, debe ser la
que contenga una de estas dos claves:
· la mayor de las claves menores al nodo que se borra. Suponer que se quiere borrar el nodo 4
del árbol de la figura 5. Se sustituirá la clave 4 por la clave 2.
· la menor de las claves mayores al nodo que se borra. Suponer que se quiere borrar el nodo 4
del árbol de la figura 5. Se sustituirá la clave 4 por la clave 5.

48
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

El algoritmo de borrado que se implementa a continuación realiza la sustitución por la mayor de


las claves menores, (aunque se puede escoger la otra opción sin pérdida de generalidad). Para
lograr esto es necesario descender primero a la izquierda del nodo que se va a borrar, y después
avanzar siempre a la derecha hasta encontrar un nodo hoja. A continuación se muestra
gráficamente el proceso de borrar el nodo de clave 4:

Codificación: el procedimiento sustituir es el que desciende por el árbol cuando se da el caso del
nodo con descencientes por ambas ramas.

void borrar(tarbol **a, int elem)


{
void sustituir(tarbol **a, tarbol **aux);
tarbol *aux;

if (*a == NULL) /* no existe la clave */


return;

if ((*a)->clave < elem) borrar(&(*a)->der, elem);


else if ((*a)->clave > elem) borrar(&(*a)->izq, elem);
else if ((*a)->clave == elem) {
aux = *a;
if ((*a)->izq == NULL) *a = (*a)->der;
else if ((*a)->der == NULL) *a = (*a)->izq;
else sustituir(&(*a)->izq, &aux); /* se sustituye por
la mayor de las menores */

free(aux);
}
}

49
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Ficheros relacionados

Implementación de algunas de las operaciones sobre árboles binarios.

Ejercicio resuelto

Escribir una función que devuelva el numero de nodos de un árbol binario. Una solución
recursiva puede ser la siguiente:

funcion nodos(arbol : tipoArbol) : devuelve entero;


inicio
si arbol = vacio entonces devolver 0;
en otro caso devolver (1 + nodos(subarbol_izq) + nodos(subarbol_der));
fin

Adaptarlo para que detecte si un árbol es perfectamente equilibrado o no.

Aplicación práctica de un árbol binario de búsqueda

Se tiene un fichero de texto ASCII. Para este propósito puede servir cualquier libro electrónico de
la librería Gutenberg o Cervantes, que suelen tener varios cientos de miles de palabras. El
objetivo es clasificar todas las palabras, es decir, determinar que palabras aparecen, y cuantas
veces aparece cada una. Palabras como 'niño'-'niña', 'vengo'-'vienes' etc, se consideran diferentes
por simplificar el problema.

Escribir un programa, que recibiendo como entrada un texto, realice la clasificación descrita
anteriormente.
Ejemplo:
Texto: "a b a c. hola, adios, hola"

La salida que produce es la siguiente:


a2
adios 1
b1
c1
hola 2

Nótese que el empleo de una lista enlazada ordenada no es una buena solución. Si se obtienen
hasta 20.000 palabras diferentes, por decir un número, localizar una palabra cualquiera puede ser,
y en general lo será, muy costoso en tiempo. Se puede hacer una implementación por pura
curiosidad para evaluar el tiempo de ejecución, pero no merece la pena.

50
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

La solución pasa por emplear un árbol binario de búsqueda para insertar las claves. El valor de
log(20.000) es aproximadamente de 14. Eso quiere decir que localizar una palabra entre 20.000
llevaría en el peor caso unos 14 accesos. El contraste con el empleo de una lista es simplemente
abismal. Por supuesto, como se ha comentado anteriormente el árbol no va a estar perfectamente
equilibrado, pero nadie escribe novelas manteniendo el orden lexicográfico (como un
diccionario) entre las palabras, asi que no se obtendrá nunca un árbol muy degenerado. Lo que
está claro es que cualquier evolución del árbol siempre será mejor que el empleo de una lista.

Por último, una vez realizada la lectura de los datos, sólo queda hacer un recorrido en orden
central del árbol y se obtendrá la solución pedida en cuestión de segundos.

Una posible definición de la estructura árbol es la siguiente:

typedef struct tarbol


{
char clave[MAXPALABRA];
int contador; /* numero de apariciones. Iniciar a 0 */
struct tarbol *izq,
*der;
} tarbol;

Ejercicios propuestos

1. Dado los siguientes recorridos:

In- Orden (30, 60, 210, 240, 250, 255, 280, 285)
Pre- Orden (60, 30, 210, 280, 250, 240, 255, 285)
Reconstruir el árbol binario.

2. Del árbol reconstruido anteriormente graficar como queda el árbol después de eliminar el
240, el 210, y el 60

In- Orden (30, 60, 210, 240, 250, 255, 280, 285)
Pre- Orden (60, 30, 210, 280, 250, 240, 255, 285)
Reconstruir el árbol binario.

3. Realizar un método para eliminar todos los nodos hojas existentes en un árbol binario.

4. Realizar un método para eliminar todos los nodos incompletos existentes en un árbol
binario.

5. Realizar un método para eliminar cualquier elemento de un árbol binario de búsqueda el


usuario indicará que elemento quiere eliminar.

6. Realizar un método para eliminar un sub-árbol cualquiera de un árbol el usuario determina


el sub-árbol a partir de un elemento denominado raíz.

51
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

7. Realizar un método para podar un árbol a partir de un elemento especificado se eliminan


todos los nodos del sub- árbol izquierdo y derecho que formen parte del elemento
especificado.

8. Realizar un método para el ADT árbol que permita obtener el elemento mayor de los
menores de un elemento dado es decir el menor más próximo a un elemento x.

9. Realizar un método para eliminar un elemento cualquiera del árbol utilizando en vez del
nodo sucesor in-orden el menor más próximo.

10. Implementar un método para eliminar todos los elementos menores a un elemento x

52
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

5.3. Árboles B

La administración de un gran conjunto de datos y su almacenamiento en ficheros es una tarea


vital para los sistemas informáticos. El ordenador debe recuperar un elemento del fichero y
cargarlo en memoria principal, antes de ser procesado. Una organización inteligente de los
datos en el fichero puede hacer que el sistema informático minimize el número de accesos al
dispositivo secundario. De tal manera de optimizar su funcionamiento.

Como una propuesta inicial para la ordenación lógica de datos se presenta la estructura de datos
Arbol Binario de Búsqueda (ABB). El cual mediante el uso de punteros permite establecer un
orden de relación entre los datos sin importar su orden físico.

Recordemos que en un ABB cada nodo contiene un dato, así como punteros a los subarboles
izquierdo y derecho. El primer nodo se denomina raíz del árbol y aquellos nodos que están al
final de las ramas se llaman hojas.

Para buscar un dato en el ABB se debía recorrer el árbol desde su raíz y comparar el dato buscado
con el dato existente en el nodo. Si era menor al dato en el nodo se buscaba en el subarbol
izquierdo y si era mayor en el subarbol derecho. Así recursivamente, hasta encontrarlo o
encontrarse con una rama vacía.

Estos árboles binarios tan simples, aunque fáciles de entender y de implementar, tienen algunas
desventajas en la práctica. Si los datos no están bien distribuidos o son añadidos de forma no
aleatoria, el árbol puede resultar bastante asimétrico, dando lugar a un aumento bastante amplio
en el tiempo total de recorrido.

Como solución ha este problema se presentó la estructura de datos Arbol Binario Balanceado
(AVL), el cual tenía las mismas propiedades que un ABB pero además debía satisfacer que las
alturas de los subarboles izquierdo y derecho no difirieran en más de uno. Esto se logra utilizando
herramientas de balanceo que se denominan rotaciones.

Ahora retomando nuestra idea inicial de tener una gran cantidad de datos almacenada en algún
dispositivo secundario (HDD, Diskette, CD, etc.). Los ABB y los AVL deben realizar un acceso
al disco cada vez que cargan en memoria un dato del fichero. Lo cual es bastante costoso si
consideramos que el tiempo de búsqueda en una cantidad N de datos es del orden de log 2N más el
tiempo de cada acceso al disco (Un acceso al diskette, por ejemplo, demora alrededor de 1 seg.)

Para optimizar esta situación usaremos la estructura de datos Arbol B y sus métodos de
mantenimiento. El Método de Trabajo de los Arboles B sobre los datos en memoria secundaria es
abordado en el apartado Funcionamiento de un Arbol B.

Utilización de los Arboles B


Los Arboles B se usan en una inmensa gama de sistemas informáticos. Y constituyen una herramienta muy eficiente
para administrar grandes volumenes de datos. Es por ésto, que los Arboles B constituyen el núcleo de los motores de
Búsqueda de las Bases de Datos.

53
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Veamos un ejemplo real. Si como Ingenieros en Computación se nos encarga administrar la Base
de Datos de todos los sufragantes de Chile (los cuales son cientos de miles), entonces deberíamos
usar una Estructura de Datos adecuada a nuestra problemática. Sabiendo que se nos pide Buscar
personas, Verificar domicilios, Inscribir un sufragante, Eliminar a un sufragante, etc. La manera
más idónea de hacerlo es usar una estructura dinámica y capaz de almacenar grandes cantidades
de datos. Es por eso que elegimos los Arboles B, porque nos entregan una eficaz administración
de los datos de muchas personas.

Otro ejemplo real y concreto, es el uso en los Sistemas Operativos con Administración de
Memoria del Tipo Virtual.

Los algoritmos de administración de memoria virtual y otros, deben intercambiar en memoria


ciertos procesos (programas en ejecución) que se eligieron por algún motivo específico. Sucede
que a veces, algunos procesos residen mucho tiempo en memoria principal. Y simultaneamente
existen otros proceoso en vía de ser cargados a memoria principal. Si ésto pasa, y además el
proceso en vías de ser cargado no tiene el espacio suficiente para ser cargado por completo en
memoria principal. Entonces nuestro algoritmo de administración de memoria decidirá cargar en
memoria secundaria (generalmente HDD) el estado del proceso menos importante o sin uso. Es
decir, guardar todos los valores de variables, registros y otra información importante del proceso
en un dispositivo secundario. ¿Y dónde aparecen los Arboles B?. Aquí, puesto que esta estructura
de datos permite cargar y recobrar la gran cantidad de datos almacenada en el dispositivo
secundario, debido al intercambio de procesos en la memoria de nuestro PC.

Los Arboles B son muy funcionales en todos estos tipos de problemas en los cuales se requiere
una cantidad de accesos mínimos al dispositivo secundario, para poder procesar mucha
información.

Funcionamiento

El Arbol B es un TDA de búsqueda equilibrado, diseñado para ser usado con grandes conjuntos
de datos en almacenamiento secundario.

Generalmente se considera a los Arboles B como el mejor método para implementar al TDA
dinámico en una unidad de disco.

A diferencia de los Arboles Binarios (que sólo podían almacenar un dato en cada nodo,
induciéndo así a realizar un acceso al disco cada vez que se carga un dato en el árbol antes de ser
procesado), el Arbol B accede al disco mediante bloques de datos, es decir, agrupa los datos en
paquetes para su lectura o escritura de así serlo.

Esta propuesta reduce bastante el número de accesos al dispositivo secundario, optimizando así el
rendimiento de nuestro sistema informático.

54
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Así por ejemplo, supongamos que tenemos un archivo con nueve mil registros de empleados
(RUT, nombre, apellido, dirección, cargo) de 100 Bytes cada registro, como se muestra en la
Figura 1. Además sabemos que el bloque del disco es de 512 Bytes y que cada puntero al bloque
de disco es de 2 Bytes.

Figura 1. Representación de un registro

Si hubiésemos usado un ABB para administrar los datos en este archivo, tendríamos que haber
accesido al disco unas 9000 veces para cargar el árbol completo en memoria principal (lo cual es
bastante lento).

Ahora si usamos un Arbol B con 5 registros (500 Bytes) y 6 punteros (12 Bytes) por cada nodo,
tendremos que acceder al disco unas 1800 veces. Ya que empaquetados los datos en bloques de 5
registros por nodo. Esto es mucho más rápido y eficiente que en el caso de los ABB.

Por lo tanto, ya debemos tener clara la idea que el Arbol B es el TDA óptimo para administrar
una gran cantidad de datos en memoria secundaria. Ahora que sabemos el uso de los Arboles B y
su forma de trabajar, les mostraremos su definición formal.

55
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

¿Qué es un Arbol B?

A fines de los años sesenta, R. Bayer y E. McCreight postularon un criterio muy razonable de
organizar datos en un fichero externo, lo llamaron Arbol B.

Se dice que un Arbol es B de orden n si:

1. Cada página contiene a lo sumo 2n elementos (llaves).


2. Cada página, excepto la de la raíz, contiene n elementos por lo menos.
3. Cada página es una página de hoja, o sea que no tiene, descendientes o tiene m+1
descendientes, donde m es el número de llaves en esta página.
4. Todas las páginas de hoja aparecen al mismo nivel.

La Figura 2 muestra un Arbol B de orden 2 con 3 niveles. Todas las páginas tienen 2, 3 o 4
elementos; la excepción es la raíz que puede contener un solo elemento únicamente. Todas las
páginas de hoja aparecen en el nivel 3.

Figura 2. Arbol B de orden 2

Búsqueda

Examinemos una página como la de la Figura 3 y un argumento de búsqueda x.

Figura 3. Página de Arbol B con m llaves

Suponiendo que hemos cargado en memoria primaria una página P del Arbol B, entonces
podemos aplicar los métodos ordinarios de búsqueda entre las llaves k1 .... km .

Nota: Si m es muy grande, se puede hacer una búsqueda del tipo "Dividir para reinar". Pero si
es pequeña, bastará con realizar una búsqueda Secuencial.

Si la búsqueda fracasa, nos encontraremos en una de las siguientes situaciones:

56
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

1. ki-1 < x < ki+1 para 1 <= i < m. Proseguimos la búsqueda en la página p i
2. km < x La búsqueda prosigue en la página pm
3. x < k1 La búsqueda prosigue en la página p0

Si en algún caso el apuntador desigando es NULL, esto es, si no hay página de hijo, entonces
tampoco existe un elemento con la llave x en el Arbol B y la búsqueda finaliza.

Inserción

Es intersante señalar que la inserción en un Arbol B de orden n es relativamente sencilla. Si hay


que insertar un elemento en una página con m<2n elementos, el proceso de inserción queda
limitado a esa página.

Para analizar esta situación, considérese la Figura 4(a) que muestra un Arbol B de orden 2.
Puesto que cada página en un Arbol B de orden n (excepto la raíz ) contiene entre n y 2n
elementos, cada página del ejemplo tiene entre 2 y 4 elementos. En cada página debe existir un
indicador (que no está reflejado en la figura) para informar sobre el número de elementos que
tiene la página. Primero se procede a Buscar desde la raíz hasta localizar la página apropiada para
la inserción. Entonces se realiza la inserción. Refiriéndonos a la Figura 4(a), uno puede ver que
cuando se inserta el elemento 24, la Búsqueda termina sin éxito en la segunda hoja. Puesto que la
hoja puede alojar otro elemento, se inserta el elemento nuevo simplemente, dando lugar al Arbol
que se muestra en la Figura 4(b).

Figura 4. (a) Un Arbol B de orden 2, y (b) el mismo árbol tras la inserción del elemento 24

La otra situación que se presenta y la más problemática, es cuando se inserta un elemento en una
página ya llena. Esto puede afectar la Estructura del Arbol y ocasionar asignación de páginas
nuevas.

Para comprender lo que sucede en este caso, analizemos la Figura 5 que ilustra la inserción de la
llave 22 en un Arbol B de orden 2. La acción se realiza en los siguientes pasos:

1. Se descubre que falta la llave 22; la inserción en la página C es imposible porque C ya está llena.
2. La página C se divide en dos páginas (esto es, se asigna una nueva página D).
3. Las 2n + 1 llaves se distribuyen uniformemente en C y D, y la llave de la mitad se sube un nivel hacia la
página madre A.

57
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Figura 5. Inserción de la llave 22 en un Arbol B de orden 2

Este plan tan elegante preserva todas las Propiedades típicas de los Arboles B. En particular, las
páginas divididas contienen exactamente n elementos. Desde luego, la inserción de un elemento
en la página madre puede hacer que ésta se desborde, con lo cual ocasiona que la división se
propague. En el caso extremo, puede propagarse hasta la raíz. Es decir, la única manera en que el
Arbol B pueda aumentar su altura. Tiene pues una manera singular de crecer: crece de las hojas
hacia la raíz.

Borrado

Se distinguen dos casos:

1. El elemento a borrar se encuentra en una página hoja.


2. El elemento a borrar no se encuentra en una página hoja, entonces debe ser sustituido por uno de los dos
elementos adyacentes (predecesor o sucesor), que resultan estar en páginas hojas y que pueden ser borrados
fácilmente.

En cualquier caso, después de la eliminación, debe seguir una comprobación del número de
elementos restantes en la página, pues si m < n se tiene se estaría violando las Propiedades para el
Arbol B de orden n y entonces, se requiere reorganizar el Arbol.

Para analizar el caso 1 (el más sencillo), considérse el Arbol B de orden 2 que se muestra en la
Figura 6(a).

Figura 6. (a) Un Arbol B de orden 2

Ahora bien, supongámos que deseamos eliminar del Arbol el elemento con valor 14. El primer
procedimiento es Buscar dicho elemento y determinar su posición en el Arbol (se encuentra en
una página hoja). Una vez encontrado, procedemos a eliminarlo del Arbol y mover los elementos
adyacentes a sus nuevas posiciones. Finalmente se debe realizar una verificación. Si la página en

58
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

donde fué eliminado dicho elemento cumple con las Propiedades de Arbol B de orden 2, entonces
se queda como está. En caso contrario, se debería realizar una combinación de páginas. En
nuestro caso sólo debemos eliminarlo y volver a ordenar los elmentos de la página. El Arbol que
se obtiene después del borrado del elemento con valor 14 se muestra en la Figura 6(b).

Figura 6. (b) El mismo Arbol B de la Figura 6(a) tras el borrado del elemento 14

El caso 2 lo analizaremos através el ejemplo de la Figura 7(a), en el cual se muestra un Arbol B


de orden 2. El elemento que deseamos borrar es el 88, y se encuentra ubicado en una página que
no es hoja.

Figura 7. (a) Arbol B de orden 2

Posteriormente se elimina el elemento y se reordenan los elementos restantes de la página A.


Finalmente se procede a verificar el cumplimiento de las Propiedades del Arbol B de orden 2.
Debido a que la página K queda con un elemento, debe hacerse una combinación entre las
páginas J y L, y subir a K el elemento central, es decir el 91. Como se muestra en la Figura
7(b).

59
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Figura 7. (b) Arbol B de la Figura 7(a) tras el borrado del elemento 88

La reorganización se da tomando un elemeto del padre, quien, a su vez debe tomar una elemento
del hermano. Pero puede ocurrir que ya el hermano haya alcanzado su tamaño mínimo n, y no
tenga, por tanto, posibilidad de prestar un elemento; entonces las dos páginas hermanas se unen
en una sola, conteniendo las 2n - 1 elementos de las dos hermanas más un elemeto central
proveniente del padre. La combinación de páginas, causadas por cantidades de elementos en las
páginas, menores a las permitidas, puede prolongarse a los niveles superiores, y en el caso
extremo hasta la raíz, que cuando queda reducida a un tamaño nulo se borra, produciéndose
reducción de la altura del árbol.

Costos

Ya que visitar un nodo en el Arbol B conlleva a acceder a memoria secundaria, el número de


páginas que se visiten en cada operación sobre el Arbol es lo que nos da la medida de su costo.
Analizemos el caso de un archivo externo (ubicado en memoria secundaria) de N elementos.

Costo de Buscar

Como vimos en la sección Funcionamiento de un Arbol B y su Definición, podemos organizar


lógicamente los elementos del archivo mediante un Arbol B de orden n, en el cual cada página
contiene entre n y 2n elementos. De aquí que, la Búsqueda de un elemento requiere a lo más

lognN

accesos al dispositivo secundario. De este modo, el costo de procesar una operación de Búsqueda
crece de forma logarítmica en relación con el tamaño del archivo.

El peor caso de Búsqueda corresponde cuando se está buscando un elemento que está en la hoja
del Arbol y además está situado al final de la página hoja.

Costo de Inserción y Borrado

La Inserción o el Borrado de un elemento del Arbol B podría requerir, además de la operación de


Búsqueda, un costo adicional que es el acceso a memoria secundaria. Es decir el costo de
Insertar o Borrar un elemento es de:

lognN + Tiempo de acceso a memoria secundaria

Pero como el "Tiempo de acceso a memoria secundaria" es una constante (muchas veces bastante
significativa), podemos obviarla si usamos el criterio asintótico. Y decir que el costo de Inserción
y Borrado en un Arbol B de orden n que contiene las claves de un archivo externo de N
elementos es a lo más de:

lognN

60
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

La Tabla 1 muestra cómo puede ser de razonable el costo logarítmico, incluso para archivos de
gran tamaño. Por ejemplo, en un Arbol B de orden 50 que contenga las claves que indexan a un
fichero de un millón de registros, se puede realizar una operación de Búsqueda, Inserción o
Borrado con 4 accesos como mucho.

Tamaño del archivo (N)


Tamaño de la 103 104 105 106 107
página (n)
10 3 4 5 6 7
50 2 3 3 4 4
100 2 2 3 3 4
150 2 2 3 3 4

Casos especiales

Búsqueda

Caso peor

Esta situación se presenta cuando el elemento que se está buscando se encuentra al final de una
página hoja del Arbol B.

Caso mejor

El mejor caso es claramente evidente, y se presenta cuando el elemento buscado es el primer dato
de la raíz del Arbol.

Inserción

Caso peor

Ocurre cuando se inserta un elemento una página hoja que ya está completa. Y además su página
padre también está completa. Esto conlleva, irremediablemente, a que el Arbol aumente su altura.

Caso mejor

Esta situación se presenta cuando se está insertando un elemento en la raíz del Arbol, que aún no
está completa.

Borrado

Caso peor

Esta situación se presenta cuando el elemento que se desea borrar se encuentra al final de una
página hoja con 2n elementos.

61
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Caso mejor

El mejor caso es cuando el elemento borrado es el primer dato de la raíz del Arbol. que además
tiene un solo dato.

Conclusión

Después de realizar un exhaustivo estudio de la Estructura de Datos Arbol B. Su utilización, las


variantes, su mantenimiento y mucho más... Hemos podido concluir lo siguiente:

Que existe una inmensa gama de sistemas de información computacional, en los cuales se debe
administrar eficientemente grandes volúmenes de datos en archivos externos. Por ejemplo, en el
caso de las Bases de Datos, se deben realizar las operaciones de Actualización, Búsqueda,
Borrado e Incorporación de nuevos datos de la forma más eficiente que se pueda. Este ejemplo de
las BDs a su vez comprende una serie de subtemas, como por ejemplo la Administración de
Memoria, Los Diccionarios, etc. Todos ellos requieren la utilización de Tipos de Datos
Abstractos adecuados. Es decir, que permitan la manipulación eficiente de un gran cantidad de
información. Bajo ésta problemática es que nacen o se fueron creados, los Arboles B. (Ver Cómo
nacieron el Arbol B)

Los Arboles B constituyen la Estructura de Datos óptima para administrar grandes cantidades de
datos en ficheros externos. Gracias a su Estructura, el tiempo de Buscar, Insertar y Borrar datos
es mínimo.

Otra ventaja de los Arboles B es que dichas operaciones de mantenimiento son de fácil
entendimiento lógico. Sin embargo, al momento de ser implementadas comienzan a surgir
bastantes detalles, a veces son engorrosos (Ver Simulación)

Otra característica a tomar en cuenta cuando se trabaja con los Arboles B es: la versatilidad que
éstos tienen. (Véase Tipos de Arboles B)

Los Costos de Buscar, Insertar y Borrar son el punto fuerte de los Arboles B. La mayoría de los
Arboles Binarios requieren en promedio unos logN accesos al dispositivo de almacenamiento de
datos, para realizar operaciones de Búsqueda, Inserción y Borrado. Donde N es el número de
datos. En cambio los Arboles B, solamente necesitan log nN accesos. (Véase Costos). Donde n
es la cantidad máxima de datos que caben en una página (Véase Introducción). Esto reduce
significativamente el número de accesos, ya que si incrementamos n, entonces log nN decrece.

Tal vez, el único defecto que tiene el Arbol B que nosotros estudiamos es que a veces sus páginas
están solamente utilizadas al 50% de su capacidad. Lo que puede ser un desperdicio de memoria
considerable si se tiene una cantidad de datos demasiado grande.

En resumen, gracias a su estructura y comportamiento, los Arboles B costituyen el TDA más


óptimo para administrar datos en forma dinámica a gran escala.

62
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

TRABAJOS PRACTICOS

Trabajo práctico Nº 1

1. Identifique los Tipos Abstractos de Datos que hubiera utilizado o desarrollado en los semestres anteriores
2. Diseñar un T.A.D. para representar y manejar números quebrados
3. Crear un T.A.D que represente un tipo de dato Conjunto y todas sus operaciones Unión, Intersección, Diferencia
simétrica, etc.
4. Diseñar los T.A.D. necesarios para modelar el problema de una agenda en la cual cada registro de la agenda
puede tener n e-mails, identificar las siguientes operaciones
a) Adición de registros de personas.
b) Eliminación de registros
c) Búsqueda de registros
d) Listado de personas ordenadas por nombres.
e) Listado de personas ordenados por e-mail.

Trabajo práctico con recursividad Nº 4

1.- Realizar un algoritmo recursivo para verificar si un número tiene al menos un dígito cero.

2.- Realizar un algoritmo recursivo para contar cuantos dígitos tiene un número

3.- Realizar un algoritmo recursivo para fusionar dos números enteros positivos en un tercer número.

4.- Realizar un algoritmo recursivo para inicializar una lista con N elementos 0 y 1 intercalados. Ejemplo si N = 5, la
lista debe contener [0,1,0,1,0,1]

5.- Realizar un algoritmo recursivo para eliminar un elemento x de una lista

6.- Realizar el algoritmo de ordenación quick-sort para la lista enlazada simple.

7.- Realizar un algoritmo recursivo para obtener la suma de los elementos impares de una lista.

8.- Realizar un algoritmo recursivo para obtener la suma de los elementos de una sub-lista definida por las
posiciones (j,k) desde la posición j hasta k.

9.- Realizar un algoritmo recursivo para invertir los elementos de una lista doblemente enlazada

10.- Realizar un algoritmo recursivo fusionar dos listas en una tercera lista ejemplo L1 =(2,3,5) L2 = (5,4,7,2,3) L3 =
L1+ L2 = (2,3,5,5,4,7,2,3)

Trabajo práctico Nº 2

1. Realizar un programa que resuelva un ecuación de segundo grado con una incógnita utilizando solo
punteros (No debe utilizar variables que no sean punteros)

63
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

2. Realizar un programa para resolver un sistema de ecuaciones de 3 incógnitas con tres variables utilizando
solo punteros.
3. Realizar un programa para obtener el mayor elemento de N números introducidos por el usuario utilizando
solo punteros.
4. Realizar un programa para calcular cuántos dígitos impares tiene un numero entero positivo utilizando solo
punteros.
5. Realizar un modulo en c++ que permita almacenar números aleatorios a un StringGrid pasado como
parámetro en todo caso utilizar solo punteros.
6. Realizar un algoritmo obtener los resultados de la serie de fibonaci en un ListBox pasado como parámetro.
7. Realizar un programa que permita crear un edit en tiempo de ejecución utilizando solo punteros.
8. Realizar un programa que permita almacenar en un vector N números enteros, Debe asignarse memoria
dinámicamente según la cantidad exacta que el usuario necesita almacenar en el vector.
9. Para las estructuras de datos Listas estáticas realizar un procedimiento para buscar el número más repetido
del vector (Utilizar solo punteros) y el acceso a los elementos del vector no realizar de la forma V[i], buscar otra
alternativa considerando que V es un puntero.
10. Realizar un procedimiento para invertir una cadena utilizando punteros a char.

Trabajo práctico Nº 3

1. Escribir un procedimiento que añada un nuevo elemento en la lista enlazada a partir del elemento i-ésimo.
2. Escribir un procedimiento que elimine un elemento de la lista enlazada, indicado por un parámetro
(Posición).
3. Dada un lista enlazada de números enteros, escribir las rutinas necesarias,
para que la liste este ordenada en orden creciente la ordenación se debe hacer de al menos 3 métodos de
ordenación.
4. Se quiere representar un tipo abstracto de datos conjunto de tal forma que los elementos estén almacenados en
la lista enlazada, escribir una unidad para implementar el TAD conjunto mediante listas enlazadas.
5. Realizar un método para la clase Tconjunto que permita realizar la UNIÓN INTERSECCIÓN Y DIFERENCIA DE
CONJUNTOS sobre la estructura de datos.
6. Implementar un método para obtener el conjunto potencia de un conjunto.
7. Implementar un método para realizar la diferencia simétrica de dos conjuntos.
8. Implementar el método para encontrar el complemento de un conjunto.
9. Realizar una estructura para representar un juego de bingo sobre listas enlazadas.
10. Realizar un método que permite generar y almacenar N cartones para un bingo.

Trabajo práctico con pilas

1.- Convertir la siguiente expresión in-fija a postfija


a) 25 + 36 * 5 – 6 + 3.

Expresión postfija
_____________________________

b) 2 + (6 – 10)/( 5 – 6) ^ 3.

64
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

Expresión postfija
_____________________________

c) 6-5/(6 – 10*5/6) ^ 3.

Expresión postfija
_____________________________

d) ((10– (10*5/6) ^ 3)^2)*2 + 5.

Expresión postfija
_____________________________

e) (15/2*(8 + 10- 5 / 2) ^ 2)^3.

Expresión postfija
_____________________________

2.- Definir el ADT para implementar una aplicación que permita evaluar expresiones aritméticas introducidas por el
usuario en formato cadena.

3.- Definir un ADT para implementar una aplicación que permita graficar cualquier función en el plano de
coordenadas X,Y la función debe ser introducida por el usuario.

4.- Implementar el método para evaluar la expresión en formato cadena de la notación infija a la notación postfija
utilizando listas.

5.- Implementar el método para evaluar la expresión en formato postfija de de la lista en función a una valor par a la
variable X de la función f(x)

65
Apuntes de Estructuras de datos SASCI Rodolfo Arana Gonzales

BIBLIOGRAFÍA

Sisa Alberto Jaime (2002). “Estructuras de Datos y Algoritmos”, Pearson Educación de


Colombia LTDA.
Jorge A. Villalobos S. (1996). “Diseño y Manejo de Estructuras de Datos en C”. MacGraw Hill.
Jorge A. Villalobos S, Alejandro Quintero, “Estructuras de Datos- Un enfoque de Tipos
Abstractos de Datos”. MacGraw Hill
Galve, Gonzales, Sanches, (1998). Velásquez, “Algoritmia”, MacGraw – Hill.
Horowitz, Ellis y Sahni Sartaj (1981) “Data Structure” Mc Graw ´Hill
Joyanes, Aguilar, Luis: Estructura de datos, Ed. McGraw-Hill Interamericana de España, 1998.
(Signatura topográfica: 005.73 J84)
Joyanes, Aguilar, Luis, C++ a su alcance, Ed. McGraw-Hill Interamericana de España, 1994.
(Signatura topográfica: 005.1 J84)
Ceballos, Javier, Curso de programación C++, 1991. (Signatura topográfica: 005.1 C32)
Deitel, H.M. C++ como programar, 1999. (Signatura topográfica: 005.1 D36 c.2)

66

Potrebbero piacerti anche