Está en la página 1de 109

UNIVERSIDAD ESTATAL A DISTANCIA

VICERRECTORÍA ACADÉMICA
ESCUELA DE CIENCIAS EXACTAS Y NATURALES
CÁTEDRA DE TECNOLOGÍA DE SISTEMAS
CARRERA DE INGENIERÍA EN INFORMÁTICA

Material Complementario
para el curso

Estructura de datos
Código 825

Elaborado por
Enrique Gómez Jiménez

San José, Costa Rica


2008
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Créditos:

Producción académica y
asesoría metodológica:
Ana Láscaris-Comneno Slepuhin

Encargada de Cátedra:
Karol Castro Cháves

Revisión filológica:
Óscar Alvarado Vega

2
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Tabla de Contenidos
PRESENTACIÓN ................................................................................................................................ 4

ALGORITMOS Y FUNDAMENTOS DE PROGRAMACIÓN ................................................................... 5

ALGORITMOS ...............................................................................................................................................................5
DISEÑO Y ANÁLISIS DE ALGORITMOS...........................................................................................................................5
¿QUÉ ES UNA ESTRUCTURA DE DATOS? ....................................................................................................................6
ABSTRACCIÓN DE DATOS ............................................................................................................................................6
TIPOS DE DATOS ABSTRACTOS (TDA)........................................................................................................................6
TIPOS DE ESTRUCTURAS DE DATOS ESENCIALES:......................................................................................................6
MERGESORT ................................................................................................................................................................8
QUICKSORT .................................................................................................................................................................9
SELECCIÓN RÁPIDA ...................................................................................................................................................12
REPRESENTACIÓN .....................................................................................................................................................25
Matriz de Adyacencia.........................................................................................................................................25
Lista de adyacencias..........................................................................................................................................26
PROBLEMA DEL CAMINO MÍNIMO SIN PESOS .............................................................................................................27
PROBLEMA DEL CAMINO MÍNIMO CON PESOS POSITIVOS (DIJKSTRA) ......................................................................28
PROBLEMA DEL CAMINO MÍNIMO CON COSTES NEGATIVOS (DIJKSTRA)...................................................................33
PILAS .........................................................................................................................................................................36
ARREGLOS .................................................................................................................................................................36
Arreglos unidimensionales: ...............................................................................................................................37
COLAS .......................................................................................................................................................................40
Árboles generales:..............................................................................................................................................46
ÁrbolesbBinarios:................................................................................................................................................46
Árboles y recursividad:.......................................................................................................................................48
RECORRIDO DE ÁRBOLES ..........................................................................................................................................50
Recorrido en postorden .....................................................................................................................................50
Recorrido en orden simétrico............................................................................................................................51
Recorrido en preorden .......................................................................................................................................51
Operaciones ........................................................................................................................................................63
AVL .......................................................................................................................................................................67
Árboles Rojinegros .............................................................................................................................................81
AA-Árboles...........................................................................................................................................................91
B-Árboles ...........................................................................................................................................................100
TABLAS HASH ............................................................................................................................. 101

3
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Presentación

Este material está diseñado para reforzar el curso de Estructura de datos (código
825). Está creado para los estudiantes de la carrera Informática Administrativa. Su
propósito es brindar a los estudiantes una guía práctica que les permita reforzar la
temática que se revisa en las tutorías presenciales de laboratorio.
El material es un complemento práctico del libro de texto del curso, de la Orientación del
curso y de la Guía de estudio respectiva.
Este será útil a todos los estudiantes como medio de preparación para la tutoría
presencial. En especial, este documento le ayudará al estudiante cuando no pueda
asistir a la tutoría, ya que aquí podrá encontrar una visión general práctica de los temas
de estudio del curso.
El curso adopta un enfoque novedoso separando la presentación de cada estructura en
su especificación (a través de una interfaz Java) e implementación. Este enfoque
proporciona varios beneficios, entre ellos la promoción del pensamiento abstracto. Antes
de conocer la implementación se presenta la interfaz de la clase, motivando así al
estudiante a pensar desde el principio sobre la funcionalidad y la eficiencia potencial de
las distintas estructuras de datos.
Los estudiantes que utilizarán el libro de texto que brinda el curso de Estructura de
datos deben poseer previamente conocimientos básicos sobre algún lenguaje de
programación modular u orientado a objetos. El curso presenta el material utilizando el
lenguaje de programación Java. Este es un lenguaje relativamente reciente, que a
menudo se compara con C++. Java ofrece diversas ventajas; muchos programadores lo
consideran un lenguaje más seguro, portable y fácil de usar que C++.

4
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Algoritmos y fundamentos de programación


Un computador se utiliza para procesar gran cantidad de información. Asimismo, para
realizar cálculos muy precisos y de mucha complejidad, los cuales llevarían mucho
tiempo a los seres humanos realizar. No escapan tampoco de su uso aquellas tareas
transaccionales rutinarias en las cuales el ser humano no utilizaría su intelecto ni su
capacidad motora de la mejor manera.
Toda actividad que realiza el ser humano está conformada por una serie de pasos
sucesivos (a veces concurrentes, como conversar y caminar al mismo tiempo), pero que
están concebidos dentro de una lógica habitual. Por ejemplo, no se puede almorzar,
estar durmiendo y bañarse al mismo tiempo. Todo tiene una lógica de ejecución. El
hecho de hacer las cosas de una forma lógica es lo que podríamos llamar algoritmo.

Algoritmos
Computacionalmente, podríamos definir algoritmo como un conjunto de instrucciones
claramente especificadas que el ordenador debe seguir para resolver un problema. Una
vez que tenemos la idea práctica de cómo llevar a cabo esos pasos, sea secuencial o
concurrentemente, lo podríamos implementar en un lenguaje de programación.

Diseño y análisis de algoritmos


Una vez diseñado un algoritmo y probada su funcionalidad, es necesario analizar su
rendimiento y comportamiento. Para analizar un algoritmo, existen diversas técnicas
orientadas a estimar el tiempo requerido para su ejecución, el espacio de memoria
utilizado, la cantidad de datos procesados, etc. También debe considerarse la
arquitectura de la computadora sobre la cual se ejecuta el algoritmo, el compilador
utilizado, etc. A esto se le denomina análisis de algoritmos.
Para el análisis de algoritmos existen funciones, como lineal O(N Log N), cuadrática y
cúbica.
Las entradas de N, en el primer caso, varían de 1 a N cantidad de elementos, y los
tiempos de ejecución asociados varían de 0 a N milisegundos. Por ejemplo, si para
comprimir un archivo de 3000 Kb un programa compresor requiere 3 segundos, su
tiempo de ejecución viene dado por T(N)=3000/3, lo cual indica que se comprime 1000
Kb por segundo. Lógicamente, se tiene que agregar este análisis a las previsiones de la
arquitectura del computador (velocidad del procesador, memoria disponible, etc.). En
este caso, este análisis es de tipo lineal.
Cabe destacar que si comprimiéramos un archivo de unos 6000 Kb, probablemente el
tiempo requerido sería el doble. Es una característica del análisis lineal, dado que el
tiempo de ejecución es proporcional al tamaño de la entrada.

5
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

El análisis mediante una función cuadrática tiene como término dominante N2 por alguna
constante.
En el caso del análisis que utiliza una función cúbica, se determina mediante N3
multiplicado por alguna constante. Por ejemplo, 10 N3 + N2 + 40N + 80 es una función
cúbica.

Estructura de datos
Para representar adecuadamente un algoritmo, se requiere la existencia de estructuras
de datos también adecuadas. Estas estructuras, además, deben contar con operaciones
que le permitan gestionar las estructuras que contienen. Esa representación de datos y
las operaciones permisibles sobre ellas es lo que se conoce como estructura de datos.

¿Qué es una estructura de datos?


Una estructura de datos es un conjunto de tipos, un tipo diseñado partiendo de ese
conjunto de tipos, un conjunto de funciones y un conjunto de axiomas. También suele
definirse como conjunto de variables, quizás de tipos distintos, conectadas entre sí de
diversas formas. Debe considerarse, entonces, que cualquier grupo de datos
organizados de alguna forma, y que asocie un conjunto de operaciones para su
manipulación, conforma una estructura de datos.

Abstracción de datos
La abstracción es un proceso mental que permite representar un modelo de la vida real
a través de sus rasgos esenciales. En la estructura de datos, la abstracción representa
una técnica o metodología que permite diseñar estructuras de datos, extrayendo sus
características particulares, y representando su funcionalidad a través de un modelo
fácilmente entendible.

Tipos de datos abstractos (TDA)


Un tipo de datos abstracto es un modelo matemático, junto con varias operaciones
definidas sobre ese modelo. Un ejemplo de TDA son los conjuntos de números enteros
con las operaciones de unión, intersección y diferencia.
Los TDA se pueden implementar como generalizaciones de los tipos de datos primitivos
(enteros, reales, etc.), creando tipos de datos complejos tales como listas o grafos.

Tipos de estructuras de datos esenciales:


Para efectos de estudio, se considerarán siete estructuras de datos, ampliamente
utilizados en las estructuras de datos, siendo:

6
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

9 Pilas
9 Listas
9 Listas enlazadas
9 Árboles
9 Árboles binarios de búsqueda
9 Tablas Hash
9 Colas de prioridad
A continuación, se presentará brevemente cada una de estas estructuras:

Algoritmos de ordenación
Los métodos de ordenamiento Shellsort constituyen el algoritmo más largo y
complejo de la ordenación por inserción, pero el más simple de los algoritmos más
rápidos. Shellsort tiene la mecánica de comparar primero los elementos que están muy
separados, para después hacerlo con los más cercanos, reduciéndose gradualmente al
método de inserción. En resumen, la idea principal consiste en disminuir movimientos
de datos, considerando:

a) Comparar primero los datos más alejados (los que distan entre sí).
b) Disminuir los intervalos para ir comparando datos más cercanos.
c) Reducirse hasta utilizar el método de inserción en el que la distancia entre los
datos que comparamos es uno.

A continuación, se presenta el programa en Java que permite ordenar un arreglo de 8


elementos, utilizando el método Shellsort:
1. public class ShellSort {
2. public static void main(String args[]){
3. int arrayEntrada[]={321, 123, 213, 234, 1, 4, 5, 6};
4. //El anterior es el array de elementos que vamos a ordenar
5. shellSort(arrayEntrada); //llamada al metodo shellSort
6. for (int i=0;i < arrayEntrada.length;i++){ //Este bucle imprime el contenido
del array
7. System.out.print(arrayEntrada[i]+" ");
8. }//fin del for
9. }//fin del main

7
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

10. //
11. public static void shellSort( int a[ ]){
12. for( int gap = a.length / 2; gap > 0; gap = gap == 2 ? 1 : (int) ( gap / 2.2 ) ){
13. for( int i = gap; i < a.length; i++ )
14. {
15. int tmp = a[ i ];
16. int j;
17. for(j = i; j >= gap && tmp < a[ j - gap ] ; j -= gap ){
18. a[ j ] = a[ j - gap ];
19. }
20. a[ j ] = tmp;
21. }
22. }
23. }
24. }//class ShellSort
En resumen, el ordenamiento Shellsort presentado en el programa anterior consiste en:
• Suponer un arreglo[]
• Elegir un intervalo gap
• Ordenar por inserción los elementos que distan entre sí ese intervalo
• Elegir un nuevo intervalo (de tamaño menor)
• Volver a ordenar por inserción
• Repetir el proceso hasta que el tamaño del intervalo sea 1

Mergesort
El algoritmo de ordenación Mergesort utiliza la mecánica divide y vencerás.
Básicamente consiste en lo siguiente:
a) Suponer un arreglo[]
b) Si el número de elementos es 0 o 1 termina
c) Si no se divide el arreglo por la mitad
d) Se ordenan recursivamente los dos subvectores resultantes
e) Se mezclan las dos mitades ordenadas en un arreglo ordenado

8
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

La idea principal de este método es particionar un vector de elementos en dos


subvectores cuasi iguales y generar un vector de salida ordenado.
1. /**
2. *shellsort, utilizando la secuencia sugerida por Gonnet.
3. *@param a un vector de elementos Comparable
4. */
5. public static void shellsort (Comparable [ ] a )
6. {
7. for ( int intervalo = a.length / 2; intervalo > 0;
8. intervalo = intervalo == 2 ? 1 : (int) (intervalo / 2.2)
9. for (int i = intervalo; i < a.length; i++)
10. {
11. Comparable temp = a [ i ];
12. int j = i;
13.
14. for ( ; j >= intervalo &&
15. temp.menorQue( a [ j – intervalo ]);
16. j -= intervalo )
17. a [ j ] = a [ j – intervalo ];
18. a [ j ] = temp;
19. }
20. }

Quicksort
El método de ordenamiento Quicksort es el más eficiente para la clasificación interna
de los elementos de una estructura de datos, como lo es un vector. La funcionalidad de
este método consiste en clasificar un arreglo o vector A[1], …, A[n] tomando del arreglo
un valor clave denominado pivote. Sobre este pivote se reorganizan los demás
elementos del arreglo. Una idea sobre cómo trabaja es la siguiente:
• Se escoge un elemento clave del arreglo y se le denomina pivote. Se espera que
una mitad de los elementos del arreglo precedan a este pivote y la otra mitad lo
sucedan. Por ende, el pivote debe ser lo más cercano al valor medio de la clave
del arreglo.

9
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

• Se permutan los elementos del arreglo con el fin de que para algún elemento j
todos los elementos que signifiquen claves menores que el pivote aparezcan en
A[1], …, A[j] y todos aquellos con claves (valores) iguales al pivote o mayores que
éste aparezcan en A[j+1], …, A[n]
• Se aplica recursivamente Quicksort a A[1], …, A[j] y A[j-1], …, A[n] para ordenar
cada grupo de elementos.
En la figura 3 se presentan las secuencias recursivas que se utiliza para la clasificación
rápida (Quicksort), en este caso para ordenar la secuencia de enteros 3,1,4,1,5,9,2,6,5.
En cada secuencia se ha seleccionado como elemento pivote (v) al mayor de los dos
valores distintos que se encuentran más a la izquierda. La recursión termina cuando la
porción del arreglo posee valores similares.
En el caso de las secuencias del ordenamiento rápido, indicados en la figura 3, se
muestran los diferentes niveles de ejecución, los cuales constan de 2 pasos: uno antes
de dividir cada subarreglo y el segundo después. Todo concluye cuando se desea
particionar el subarreglo, y sus elementos están conformados por elementos similares o
se encuentran ordenados.

33 11 44 11 55 99 22 66 55 33
Partición v=3

22 11 11 44 55 99 22 66 55 33
Nivel 1

22 11 11 44 55 99 22 66 55 33
Partición v=2 Partición v=5

11 11 22 44 33 33 99 66 55 55 Nivel 2

11 11 22 44 33 33 99 66 55 55
Concluido Concluido Partición v=4 Partición v=9

33 33 44 55 66 55 99 Nivel 3

33 33 44 55 66 55 99
Concluido Concluido Partición v=6 Concluido

55 55 66 Nivel 4

Nivel 5 Concluido 55 55 66 Concluido

Figura No. 1: Ordenamiento mediante Quicksort

10
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

A continuación, se presenta la implementación del algoritmo Quicksort en Java:


1. //Algoritmo Quicksort que utiliza partición con
2. //la mediana de tres y un limite para detectar vectores pequeños
3.
4. private static void
5. quicksort (Comparable [ ] a, int ini, int fin )
6. {
7. if ( ini + LIMITE > fin )
8. ordenacionPorInsercion( a, ini, fin );
9. else
10. {
11. // ordenar ini, medio, fin
12. int medio = ( ini + fin ) / 2;
13. if a ( [ medio].menorQue( a[ ini ] ))
14. intercambiarReferencias ( a, ini, medio);
15. if a ( [ fin ].menorQue( a[ ini ] ))
16. intercambiarReferencias ( a, ini, fin);
17. if a ( [ fin ].menorQue( a[ medio ] ))
18. intercambiarReferencias ( a, medio, fin );
19.
20. //colocar el pivote en la posición fin – 1
21. intercambiarReferencias ( a, medio, fin - 1 );
22. Comparable pivote = a[ fin – 1 ];
23.
24. //empezar la partición
25. int i, j;
26. for (i = ini, j = fin – 1; ; )
27. {
28. while( a [ ++i ].menorQue( pivote))
29. ;
30. while( pivote.menorQue( a [ --j ] ))

11
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

31. ;
32. if ( i < j )
33. intercambiarReferencias( a, i, j );
34. else
35. break;
36. }
37. //colocar el pivote
38. intercambiarReferencias( a, i, fin -1 );
39.
40. quicksort( a, ini, i – 1 ); //ordenar elementos pequeños
41. quicksort( a, i + 1, fin ); //ordenar elementos grandes
42. }
43. }
44. public static void Quicksort ( Comparable [ ] a )
45. Quicksort ( a, 0, a.length – 1 );
46. }

Selección rápida
El ordenamiento por selección rápida se asemeja al algoritmo Quicksort,
diferenciándose en que solamente realiza una llamada recursiva en comparación con las
dos que este hace. Básicamente, consiste en encontrar el k-ésimo elemento menor de
una colección de N elementos. La implementación en Java de este algoritmo es la
siguiente:
1. //Método de selección interno que hace las llamadas recursivas
2. //coloca el k-ésimo elemento en a[k-1]
3. //la llamada inicial es seleccionRapida(a,0,a.length,k)
4.
5. private static void
6. SeleccionRapida(Comparable [] a, int ini, int fin, int k)
7. {
8. If (ini + LIMITE > fin)
9. OrdenacionPorInsercion(a,ini,fin)

12
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

10. else
11. //ordenar ini, medio, fin
12. int medio = (ini + fin) / 2:
13. if (a[medio].menorQue(a[ini]))
14. intercambiarReferencias(a,ini,medio);
15. if (a[fin].menorQue(a[ini]))
16. intercambiarReferencias(a,ini,fin);
17. if (a[fin].menorQue(a[medio]))
18. intercambiarReferencias(a,medio,fin);
19. //colocar el pivote en la posición fin-1
20. intercambiarReferencias(a,medio, fin-1);
21. Comparable pivote = a[fin-1];
22. //Iniciar la partición
23. int i, j;
24. for (i=ini, j=fin-1; ;)
25. while(a[++i].menorQue(pivote))
26. ;
27. while(pivote.menorQue(a[--j]))
28. ;
29. if(i<j)
30. intercambiarReferencias(a,i,j);
31. else
32. break;
33. }
34. //colocar el pivote
35. intercambiarReferencias(a,i, fin-1);
36. //recursión; solo cambia esta parte
37. if (k-1 < i)
38. seleccionRapida(a,ini,i-1,k);
39. else if(k-1>i)
40. seleccionRapida(a,i+1,fin,k);

13
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

41. }
42. }

A continuación, se presenta la implementación de las rutinas de ordenación presentadas


anteriormente, y que fueron implementadas por Mark Allen Weiss:
/**
* Una clase que implementa las rutinas de ordenamiento
* implementadas como métodos estáticos
* usa la implementación compareTo.
* @author Mark Allen Weiss
*/
public final class Sort
{
/**
* Ordenamiento mediante inserción simple.
*/
public static <AnyType extends Comparable<? super AnyType>>
void insertionSort( AnyType [ ] a )
{
int j;
for( int p = 1; p < a.length; p++ )
{
AnyType tmp = a[ p ];
for( j = p; j > 0 && tmp.compareTo( a[ j - 1 ] ) < 0; j-- )
a[ j ] = a[ j - 1 ];
a[ j ] = tmp;
}
}

/**
* Utilizando Shellsort

14
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

*/
public static <AnyType extends Comparable<? super AnyType>>
void shellsort( AnyType [ ] a )
{
int j;
for( int gap = a.length / 2; gap > 0; gap /= 2 )
for( int i = gap; i < a.length; i++ )
{
AnyType tmp = a[ i ];
for( j = i; j >= gap &&
tmp.compareTo( a[ j - gap ] ) < 0; j -= gap )
a[ j ] = a[ j - gap ];
a[ j ] = tmp;
}
}

/**
* Utilizando Heapsort Estandard
*/
public static <AnyType extends Comparable<? super AnyType>>
void heapsort( AnyType [ ] a )
{
for( int i = a.length / 2; i >= 0; i-- ) /* buildHeap */
percDown( a, i, a.length );
for( int i = a.length - 1; i > 0; i-- )
{
swapReferences( a, 0, i ); /* deleteMax */
percDown( a, 0, i );
}
}
/**

15
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

* Método interno para heapsort.


*/
private static int leftChild( int i )
{
return 2 * i + 1;
}

/**
* Método interno de heapsort el cual es utilizado en
* deleteMax y buildHeap.
*/
private static <AnyType extends Comparable<? super AnyType>>
void percDown( AnyType [ ] a, int i, int n )
{
int child;
AnyType tmp;

for( tmp = a[ i ]; leftChild( i ) < n; i = child )


{
child = leftChild( i );
if( child != n - 1 && a[ child ].compareTo( a[ child + 1 ] ) < 0 )
child++;
if( tmp.compareTo( a[ child ] ) < 0 )
a[ i ] = a[ child ];
else
break;
}
a[ i ] = tmp;
}

/**

16
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

* Algoritmo para Mergesort


*/
public static <AnyType extends Comparable<? super AnyType>>
void mergeSort( AnyType [ ] a )
{
AnyType [ ] tmpArray = (AnyType[]) new Comparable[ a.length ];
mergeSort( a, tmpArray, 0, a.length - 1 );
}

/**
* Método interno que realiza llamadas recursivas
*/
private static <AnyType extends Comparable<? super AnyType>>
void mergeSort( AnyType [ ] a, AnyType [ ] tmpArray,
int left, int right )
{
if( left < right )
{
int center = ( left + right ) / 2;
mergeSort( a, tmpArray, left, center );
mergeSort( a, tmpArray, center + 1, right );
merge( a, tmpArray, left, center + 1, right );
}
}

/**
* Método interno que mezcla dos arreglos ordenados
*/
private static <AnyType extends Comparable<? super AnyType>>
void merge( AnyType [ ] a, AnyType [ ] tmpArray, int leftPos, int rightPos, int rightEnd )
{

17
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

int leftEnd = rightPos - 1;


int tmpPos = leftPos;
int numElements = rightEnd - leftPos + 1;

// Main loop
while( leftPos <= leftEnd && rightPos <= rightEnd )
if( a[ leftPos ].compareTo( a[ rightPos ] ) <= 0 )
tmpArray[ tmpPos++ ] = a[ leftPos++ ];
else
tmpArray[ tmpPos++ ] = a[ rightPos++ ];

while( leftPos <= leftEnd ) // copia el resto en la primera mitad


tmpArray[ tmpPos++ ] = a[ leftPos++ ];

while( rightPos <= rightEnd ) // copia el resto en la mita de la derecha


tmpArray[ tmpPos++ ] = a[ rightPos++ ];

// Copia en tmpArray
for( int i = 0; i < numElements; i++, rightEnd-- )
a[ rightEnd ] = tmpArray[ rightEnd ];
}

/**
* Algoritmo Quicksort
*/
public static <AnyType extends Comparable<? super AnyType>>
void quicksort( AnyType [ ] a )
{
quicksort( a, 0, a.length - 1 );
}

18
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

private static final int CUTOFF = 3;

/**
* Método que intercambia los elementos en un arreglo
*/
public static <AnyType> void swapReferences( AnyType [ ] a, int index1, int index2 )
{
AnyType tmp = a[ index1 ];
a[ index1 ] = a[ index2 ];
a[ index2 ] = tmp;
}

/**
* Retorna mediana de la izquierda, centro y derecha.
* Ordena estos y oculta el pivote.
*/
private static <AnyType extends Comparable<? super AnyType>>
AnyType median3( AnyType [ ] a, int left, int right )
{
int center = ( left + right ) / 2;
if( a[ center ].compareTo( a[ left ] ) < 0 )
swapReferences( a, left, center );
if( a[ right ].compareTo( a[ left ] ) < 0 )
swapReferences( a, left, right );
if( a[ right ].compareTo( a[ center ] ) < 0 )
swapReferences( a, center, right );
// Pone el pivote en la posición derecha - 1
swapReferences( a, center, right - 1 );
return a[ right - 1 ];
}

19
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

/**
* Método quicksort mediante llamadas recursivas.
*/
private static <AnyType extends Comparable<? super AnyType>>
void quicksort( AnyType [ ] a, int left, int right )
{
if( left + CUTOFF <= right )
{
AnyType pivot = median3( a, left, right );
// Inicio de la partición
int i = left, j = right - 1;
for( ; ; )
{
while( a[ ++i ].compareTo( pivot ) < 0 ) { }
while( a[ --j ].compareTo( pivot ) > 0 ) { }
if( i < j )
swapReferences( a, i, j );
else
break;
}
swapReferences( a, i, right - 1 ); // Restaura pivote
quicksort( a, left, i - 1 ); // Ordena elementos pequeños
quicksort( a, i + 1, right ); // Ordena elementos grandes
}
else // Realizar una inserción ordenada en en sub arreglo
insertionSort( a, left, right );
}

/**
* Ordenamiento por inserción interna en sub arreglos
*/

20
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

private static <AnyType extends Comparable<? super AnyType>>


void insertionSort( AnyType [ ] a, int left, int right )
{
for( int p = left + 1; p <= right; p++ )
{
AnyType tmp = a[ p ];
int j;
for( j = p; j > left && tmp.compareTo( a[ j - 1 ] ) < 0; j-- )
a[ j ] = a[ j - 1 ];
a[ j ] = tmp;
}
}

/**
* Algoritmo de selección rápida
*/
public static <AnyType extends Comparable<? super AnyType>>
void quickSelect( AnyType [ ] a, int k )
{
quickSelect( a, 0, a.length - 1, k );
}
/**
* Método selección interna mediante llamadas recursivas.
*/
private static <AnyType extends Comparable<? super AnyType>>
void quickSelect( AnyType [ ] a, int left, int right, int k )
{
if( left + CUTOFF <= right )
{
AnyType pivot = median3( a, left, right );
// Inicio del particionamiento

21
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

int i = left, j = right - 1;


for( ; ; )
{
while( a[ ++i ].compareTo( pivot ) < 0 ) { }
while( a[ --j ].compareTo( pivot ) > 0 ) { }
if( i < j )
swapReferences( a, i, j );
else
break;
}
swapReferences( a, i, right - 1 ); // Restaura el pivote
if( k <= i )
quickSelect( a, left, i - 1, k );
else if( k > i + 1 )
quickSelect( a, i + 1, right, k );
}
else // Se realizar una inserción ordenada en un sub arreglo
insertionSort( a, left, right );
}
private static final int NUM_ITEMS = 1000;
private static int theSeed = 1;
private static void checkSort( Integer [ ] a )
{
for( int i = 0; i < a.length; i++ )
if( a[ i ] != i )
System.out.println( "Error en " + i );
System.out.println( "Checksort finalizado " );
}
public static void main( String [ ] args )
{
Integer [ ] a = new Integer[ NUM_ITEMS ];

22
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

for( int i = 0; i < a.length; i++ )


a[ i ] = i;
for( theSeed = 0; theSeed < 20; theSeed++ )
{
Random.permute( a );
Sort.insertionSort( a );
checkSort( a );

Random.permute( a );
Sort.heapsort( a );
checkSort( a );

Random.permute( a );
Sort.shellsort( a );
checkSort( a );

Random.permute( a );
Sort.mergeSort( a );
checkSort( a );
Random.permute( a );
Sort.quicksort( a );
checkSort( a );

Random.permute( a );
Sort.quickSelect( a, NUM_ITEMS / 2 );
System.out.println( a[ NUM_ITEMS / 2 - 1 ] + " " + NUM_ITEMS / 2 );
}
}
}

23
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Grafos y caminos
Un grafo es una red de elementos conectados entre sí. Actualmente se considera una
herramienta muy utilizada para modelar problemas reales en los cuales sus
componentes podrían representarse mediante un grafo. Por ejemplo, mediante un grafo
podrían representarse las carreteras de Costa Rica y las ciudades que conecta. En
tales circunstancias, las ciudades poseen varios caminos o carreteras por los cuales se
puede llegar a ella. Este tipo de problemas se resuelven mediante la implementación de
grafos.
En la teoría de grafos, debe considerarse la siguiente terminología:
• Grafo: Conjunto de nodos y arcos o de vértices y aristas
• Nodo: Elemento básico de información de un grafo
• Arco: Enlace que une dos nodos del grafo
• Nodos adyacentes: Dos nodos que son unidos por un arco
• Camino: Secuencia de nodos en los que cada par de nodos son adyacentes
• Camino simple: Camino en el que todos los nodos contenidos son diferentes,
con la posible excepción del primero y el último que podrían ser el mismo
• Grafo no dirigido: Los arcos en el grafo no tienen una dirección particular, es
decir, son bidireccionales. Por ejemplo, si los nodos A y B son adyacentes, es
igual ir de A a B que de B a A.
• Grafo dirigido: Es llamado dígrafo. Es donde los arcos del grafo tienen una
dirección asociada. El primer elemento del arco es el origen y el segundo es
considerado el destino. Por ejemplo: el arco A B es diferente del arco B A.
• Grafo ponderado: Cada arco del grafo tiene asociado un peso o valor.
Generalmente se asocia con costos y distancias, entre otros.
• Ciclo: Es un grafo dirigido. El ciclo es un camino donde el nodo de inicio y el
nodo final son el mismo.
• Grafo conectado: Un grafo no dirigido es conectado si hay un camino de
cualquier nodo hacia cualquier otro en el grafo.
• Dígrafo fuertemente conectado: Un grafo dirigido se considera fuertemente
conectado si hay un camino desde cualquier nodo hacia cualquier otro.
• Dígrafo débilmente conectado: Un grafo dirigido se considera débilmente
conectado si no hay caminos para llegar de cualquier nodo hacia cualquier otro.

Un grafo se puede mostrar como en la figura siguiente. Se visualizan los caminos


existentes entre las diferentes provincias de Costa Rica, y cómo se implementaría
mediante una estructura grafo:

24
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Guanacaste
Guanacaste

Puntarenas
Puntarenas

Alajuela
Alajuela

San
San José
José
José

Heredia
Heredia

Cartago
Cartago

Limó
Limón
Limón

Figura No. 2: Grafo representando los caminos entre las provincias de Costa Rica

Representación
Un grafo puede ser representado de muchas formas. Aquí se citan algunas de estas
formas:

Matriz de Adyacencia
Un grafo se puede representar fácilmente mediante una matriz. Inclusive, es la forma
más sencilla de representar un grafo. A esta matriz se le denomina matriz de
adyacencia. Esta matriz consiste básicamente en un arreglo bidimensional de tamaño n
x n, donde n es la máxima cantidad de nodos en el grafo. Cada casilla de la matriz se
carga con valores verdadero (v) o falso (f) en caso de que posea un camino de un nodo
(fila) con otro (columna). En caso de los grafos no dirigidos la matriz será simétrica. Sin
embargo, esto no ocurre en los dígrafos, donde se considera la dirección de cada uno
de los arcos. Para el caso de los grafos ponderados, la matriz podrá ser cargada con el
peso asociado a cada uno de los arcos.
La ventaja principal de la representación de grafos mediante una matriz de adyacencia
es su simplicidad, dado que facilita las operaciones que puedan realizarse sobre el
grafo.

25
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Su desventaja es que se limita a un número máximo de nodos en el grafo, lo que


provoca que, en cierto momento, sea imposible almacenar más información en la matriz.
Por el contrario, si la matriz es muy grande para representar un grafo pequeño, se
tendrá un importante desperdicio de espacio de almacenamiento de la matriz (y de
memoria). En el caso de un grafo no dirigido, el desperdicio sería aún mayor, ya que en
la matriz, al ser simétrica, la información estaría duplicada.
En la figura siguiente se muestra un grafo representado mediante una matriz de
adyacencia. Debe observarse que cada camino une dos nodos. Por ejemplo, de A hacia
C existe un camino por lo que el valor de la casilla es V (verdadero); de igual forma, de
A hacia B no existe un camino, por lo que el valor de la casilla de la matriz es F (falso).

A B C D E
A
A D
D A F F V V V
B F F V V F
C V V F F F
E C B
B D V V F F V
E V F F V V

Figura No. 3: Grafo representado por matriz de adyacencia

Lista de adyacencias
Mediante una lista de adyacencias, la representación de un grafo gana en dinamismo.
Esto por cuanto no se tiene que pensar en un tamaño máximo de representación para
los nodos y sus arcos de un grafo, sino que más bien se van creando los nodos y sus
arcos a medida que se van necesitando. Básicamente, consiste en definir una lista
encadenada de nodos y, para cada uno de ellos, encadenar una lista con los nodos
adyacentes. Si se utiliza una lista de adyacencias para representar un grafo no dirigido
se desperdicia mucho espacio de memoria, dado que la información de cada arco se
encuentra duplicada, puesto que existe una vinculación de un nodo a otro y viceversa.

26
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Este tipo de representación se muestra en la figura siguiente:

A
A D
D

E
E C B
B

Figura No. 4: Grafo representado por lista de adyacencias

Problema del camino mínimo sin pesos


El objetivo de medir la longitud del camino sin pesos es determinar el número de
aristas en el camino entre un nodo y otro (vértices). Para ello, es necesario realizar un
recorrido por el grafo. Para encontrar el camino mínimo sin pesos, se utiliza el algoritmo
denominado recorrido primero en anchura (breadth first) el cual utiliza una fila (cola)
para su implementación. La búsqueda en anchura procesa los vértices por niveles: los
vértices más cercanos al origen se calculan primero. La figura siguiente muestra el
recorrido mínimo sin pesos, considerando solamente el número mínimo de aristas entre
vértices o nodos de un grafo.

27
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

V
V00 V
V11
00
Se escoge el vértice V2 como el nodo origen
V
V22 V
V33 V
V44 para la búsqueda de los caminos mínimos

V
V55 V
V66

11
V
V00 V
V11
00
Se busca la mínima distancia de V2 que es 1
V
V22 V
V33 V
V44 y se determina que son V0 y V5
11 El camino es V2ÆV0 y V2ÆV5
V
V55 V
V66

11 22
V
V00 V
V11
00 22
Se busca la siguiente distancia de V2 que es 2
V
V22 V
V33 V
V44 partiendo desde V0 y V5 se determina que son
V1 y V3.
11
El camino es V2ÆV0ÆV1 y V2ÆV0ÆV3
V
V55 V
V66

11 22
V
V00 V
V11
00 22 33 Se busca la siguiente distancia de V2 que es 3
partiendo desde V1 y V3 se determina que son
V
V22 V
V33 V
V44 V4 y V6. El camino es V2ÆV0ÆV1ÆV4 y
11 33 V2ÆV0ÆV3ÆV6

V
V55 V
V66

Figura No. 5: Buscando caminos mínimos sin pesos en un grafo dirigido

Problema del camino mínimo con pesos positivos (Dijkstra)


Según Mark Allen, la longitud de un camino con pesos es la suma del coste de las
aristas del camino. Mediante el algoritmo de Dijkstra se resuelve el problema del
camino mínimo con pesos. En este caso, las aristas que unen los vértices o nodos de un
grafo deben tener pesos positivos y no negativos. En resumen, el algoritmo de Dijkstra

28
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

pretende encontrar el camino más corto, según su costo total, desde el vértice de origen
al resto de los vértices.
Básicamente, para construir el algoritmo del AEM desarrollado por Dijkstra, deben
seguirse los siguientes pasos:
• Crear el grafo vacío AEM (Árbol de Extensión Mínima).
• Insertar cualquier nodo vértice x del grafo en el AEM.
• Insertar todos los nodos vértices vecinos del nodo vértice x en una cola
priorizada. El valor del arco que une al nodo vértice x con su vecino es su
prioridad; la mayor prioridad es el valor menor
• Mientras la cola priorizada no esté vacia y no se hayan agregado todos los nodos
del AEM:
o Sacar un nodo vértice de la cola priorizada.
o Si el nodo vértice no está en el AEM, agregarlo con su correspondiente
arco y meter todos sus vecinos, que no estén en el AEM, en la cola
priorizada.
En la figura siguiente se muestra un ejemplo de cómo implementar el AEM de un grafo,
mediante una cola priorizada.

Si se supone el grafo siguiente, el AEM que le corresponde,


independientemente del nodo que se elija inicialmente, sería

2 2
A
A B
B A
A B
B
12 8 8
AEM
AEM
12
6 C
C 6 C
C 3
3

D
D E
E D
D E
E
7

B C D C E F D D Cola
Cola priorizada
priorizada
2 12 10 8 9 3 6 7

Figura No. 6: Definición de AEM de un grafo mediante una cola priorizada

29
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Para utilizar la cola de prioridad indicada en la figura anterior existen dos formas:
1. Almacenar cada vértice en la cola de prioridad y
2. Utilizar la distancia (obtenida consultando la tabla del grafo) como función de
ordenación.
“La cola de prioridad es una estructura apropiada. El método más sencillo es insertar en
la cola de prioridad una nueva entrada formada por un vértice y una distancia, cada vez
que se reduce la distancia de un vértice. Podemos encontrar el vértice adonde
movernos eliminando repetidamente el vértice de mínima distancia hasta que salga un
vértice no visitado” Mark Allen.
A continuación, se presenta la implementación en Java del algoritmo de Dijkstra,
mediante una cola de prioridad.
1. /**
2. *Objeto almacenado en la cola de prioridad
3. *en el algoritmo de Dijkstra
4. */
5. class Camino implements Comparable
6. {
7. int dest; //w
8. int coste; //d(w)
9.
10. static Camino infNeg = new Camino(); //centinela
11.
12. Camino()
13. { this( 0 ); }
14.
15. Camino( int d )
16. { this( d, 0 );}
17.
18. Camino( int d, int c )
19. { dest = d; coste = c;}
20.
21. public bolean menorQue( Comparable lder )
22. { return coste < ( (Camino) lder ).coste;}

30
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

23.
24. public int compara( Comparable lder)
25. { return coste < ( (Camino) lder ).coste ? -1 :
26. coste > ( (Camino) lder ).coste ? 1 : 0;}
27. }

El código anterior pretende la colocación de un objeto en la cola de prioridad. Está


formado por w y Dw y una función de comparación definida en la base Dw. A
continuación se muestra la rutina Dijkstra que calcula los caminos mínimos.
1. /**
2. *Algoritmo de Dijkstra utilizando un montículo binario
3. *Devuelve false si se detectan aristas negativas
4. */
5. private bolean dijkstra ( int nodoOrig )
6. {
7. int v, w;
8. ColaPrioridad cp = new MonticuloBinario( Camino.infNeg );
9. Camino vrec;
10.
11. limpiarDatos();
12. tabla [ nodoOrig ].dist = 0;
13. cp.insertar ( new Camino ( nodoOrig, 0 ));
14.
15. try
16. {
17. for ( int nodosVistos = 0; nodosVistos < numVertices;
18. nodosVistos++;
19. {
20. do
21. {
22. if ( cp.esVacia( ))
23. return trae;

31
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

24. vrec=(Camino) cp.eliminarMin( );


25. } while ( tabla[vrec.dest].extra != 0 );
26.
27. v=vrec.dest;
28. tabla[v].extra = 1;
29.
30. ListaIter p = new ListaEnlazadaIter (tabla[v].ady );
31. for (; p.estaDentro();p.avanzar())
32. {
33. w = ((Arista) p.recuperar( ) ).dest;
34. int cvw = ((Arista) p.recuperar()).coste;
35.
36. if (cvw<0)
37. return false;
38.
39. if ( tabla[w].dist > tabla [v].dist + cvw)
40. {
41. tabla[w].dist = tabla[v].dist+cvw;
42. tabla[w].ant = v;
43. cp.insertar (new Camino(w,tabla[w].dist));
44. }
45. }
46. }
47. }
48. catch(DesbordamientoInferior e) {} //No puede ocurrir
49.
50. return trae;
51. }
Se pueden señalar algunos puntos importantes del algoritmo anterior:
1. En la línea 8 se declara la cola de prioridad cp.
2. En la línea 9 se declara vrec, el cual almacenará el resultado de eliminarMin.

32
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

3. El ciclo For más externo inicia a partir de la línea No. 17. Aquí se centra la
atención sobre el vértice v, procesándolo y examinando los vértices adyacentes,
indicados por w.
4. v se elige repetidamente mientras se eliminan elementos de la cola de prioridad
(línea 24) hasta encontrar un vértice aún no procesado.
5. El atributo extra se utiliza para ir almacenando la información del punto anterior.
Inicialmente vale 0. Cuando el vértice se procesa pasa a valer 1 (línea 28)

Problema del camino mínimo con costes negativos (Dijkstra)


Ya se había mencionado que el algoritmo de Dijkstra necesita que los costes de las
aristas fueran positivos. Sin embargo, en algunos casos este aspecto es restrictivo. En
el caso de que los costes de las aristas fueran negativos, el algoritmo de Dijkstra no
funciona adecuadamente. Mark Allen señala que un ciclo de coste negativo hace que la
mayoría de los caminos mínimos, si no todos, estén indefinidos porque podemos dar
vueltas al ciclo arbitrariamente, para asi obtener una longitud arbitrariamente negativa.
Por ello, es necesario contar con un algoritmo alternativo.

A continuación, se muestra un ejemplo de grafo con una arista de coste negativo.

22 El camino V1ÆV4 es negativo


44 V
V00 V
V11 -10
-10
11 33
22 22
V
V22 V
V33 V
V44
44 66
55 88
V
V55 V
V66
11
Figura No. 7: Árbol con peso negativo

En el ejemplo anterior obsérvese que de V3 a V4 tiene un coste de 2, pero un camino


más corto sería siguiendo el ciclo V3, V4, V1, V3, V4 , que tiene un coste -3 (2 + -10 + 3 +
2 = - 3). Esto, lógicamente, es inconcebible. Una combinación de los algoritmos con
pesos y sin pesos resolverá el problema, pero a costa de un drástico incremento
potencial en el tiempo de ejecución.

33
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

A continuación se presenta el algoritmo, implementado en Java.


1. /**
2. *Se ejecuta el algoritmo de caminos mínimos.
3. *Se permiten aristas con coste negativo.
4. *Devuelve false si se detectan ciclos negativos
5. */
6. private bolean negativos ( int nodoOrig )
7. {
8. int v, w;
9. Cola q = new ColaVec();
10. int cvw;
11.
12. limpiarDatos();
13. tabla[ nodoOrig].dist = 0;
14. q.insertar(new Integer( nodoOrig ));
15. tabla[nodoOrig].extra;
16.
17. //Incrementa extra cuando el vértice v entra o sale
18. //de la cola. Si el vértice está a punto de entrar, pero ya
19. //está en la cola, cuenta como si entrara y saliera
20. try
21. {
22. while ( !q.esVacia())
23. {
24. v = ( (Integer) q.quitarPrimero()).intValue();
25.
26. if(tabla[v].extra++ > 2 * numVertices)
27. return false; //existe un ciclo.
28.
29. ListaIter p = new ListaEnlazadaIter(tabla[v].ady);

34
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

30. for(; p.estaDentro(); p.avanzar())


31. {
32. w = ((Arista) p.recuerar()).dest;
33. cvw = ((Arista).p.recuperar()).coste;
34. if ( tabla[w].dist > tabla[v].dist + cvw)
35. {
36. tabla[w].dist = tabla[v].dist + cvw;
37. tabla[w].ant = v;
38.
39. //inserter solo si no esta en la cola
40. if (tabla[w].extra++ % 2 == 0)
41. q.insertar(new Integer (w));
42. else
43. tabla[w].extra++;
44. }
45. }
46. }
47. }
48. catch ( DesbordamientoInferior e) {} //No puede ocurrir
49. return trae;
50. }

Pilas y colas
Las pilas y colas se utilizan para administrar el orden de entrada de un conjunto de
datos. Una pila será útil cuando la aplicación requiera de un orden inverso al orden de
entrada original de los datos. Un ejemplo típico es el manejo de las llamadas a
subrutinas en un programa de cómputo o el proceso de conversión de expresiones a
diferentes formatos (situación que se requiere en la implantación de un compilador). Una
cola será útil cuando la aplicación requiera del orden estricto de entrada de los datos.
Los ejemplos característicos de esta estructura son los buffer de memoria en los que se
almacenan temporalmente los datos (como el de impresión o de entrada de datos por
teclado).
A continuación se describen ambas estructuras.

35
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Pilas
Una pila es una estructura de datos en la cual el acceso está limitado al elemento más
recientemente insertado. Una pila se implementa en un TDA vector o arreglo
unidimensional, marcándose su índice N como el primer elemento en el fondo del TDA.
El efecto de insertar elementos en la pila se conoce como apilar, mientras que el sacar
un elemento se conoce como desapilar. Lógicamente, en este TDA el elemento a
desapilar es el último elemento apilado. Es como tener una pila de libros: inicialmente
vamos apilando uno sobre el otro, quedando el primer libro en el fondo y el último como
el más próximo a ser desapilado. Esta acción de apilar/desapilar se denomina LIFO
(Last Input, Last Output), es decir, el último en entrar es el primero en salir. En
castellano sería UEPS.

En la figura siguiente se muestra una representación del TDA Pila:

Apilamiento
Desapilamiento

Ultimo elemento
insertado

Primer elemento
insertado

Figura No. 8: TDA Pila

Como se muestra en la figura anterior, las pilas se construyen en memoria. A


continuación, se detalla la implementación abstracta del TDA Pila. La entrada a la pila
sería A, B, C y D, y la salida D, C, B y A.
Para tener un panorama claro del manejo de pilas y colas en Java, vamos a desarrollar
rápidamente el tema de los arreglos, donde generalmente se almacenan estos TDA.
Posteriormente, abordaremos el tema específico de los TDA Pila y Cola,
respectivamente.

Arreglos
Los arreglos son estructuras de datos ampliamente utilizados, y muy fáciles de
entender y derivar en otras estructuras complejas. En realidad, un arreglo es una

36
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

secuencia de elementos, donde cada elemento (grupo de bytes en memoria) se asocia


con al menos un índice (en el caso de un arreglo de una sola dimensión).

Existen arreglos unidimensionales (solo filas o columnas) y bidimensionales (filas y


columnas). Son los más utilizados aunque se pueden implementar con más
dimensiones. Sin embargo, hay que considerar lo siguiente:
9 Cada elemento ocupa el mismo número de bytes (dependiendo del tipo de datos:
entero, real, carácter, cadena, etc.).
9 Todos los elementos son del mismo tipo (enteros, reales, carácter, cadena, etc.).
9 Generalmente los elementos del arreglo se ubican en localizaciones de memoria
consecutivas, aunque en implementaciones de arreglos bidimensionales puede
variar.
9 El número de índices asociados con cada elemento constituye la dimensión del
arreglo.
Arreglos unidimensionales:
Java proporciona tres técnicas para crear un arreglo de una dimensión
(unidimensional): usando un solo inicializador, usar solo la palabra new y utilizar la
palabra new con un inicializador.
El siguiente fragmento de código crea un arreglo unidimensional en Java:
int [] notas = new int [5]
La línea anterior declara un arreglo unidimensional (notas) indicando su tipo de variable
int (entero) de cinco celdas de memoria.
Se pueden realizar implementaciones en Java, como las siguiente:
String[] dias = new string[] {“Lunes”, “Martes”, “Miércoles”, “Jueves”, “Viernes”};
En el caso anterior, dias es un arreglo unidimensional de tipo cadena (string) que se
inicializa con los valores de sus 7 celdas. Como el índice es un valor entero que va de 0
hasta n-1, entonces cada índice ubicará su contenido en el arreglo. Así, por ejemplo, la
posición 0 será “Lunes”, mientras que n-1 será “Viernes”. En Java podríamos desplegar
el contenido de la siguiente manera:
System.out.println(dias[0]); y System.out.println(dias[n-1]);
Un arreglo de dos dimensiones (bidimensional) se conoce también como matriz o tabla.
Cada elemento del arreglo se asocia con una pareja de índices que determinan su
posición en el mismo. En este caso, uno de los índices representará la fila del arreglo, y
el otro la columna.
El siguiente fragmento de código crea un arreglo bidimensional en Java:
double [][] temperaturas = new double[2][3];

37
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

En el fragmento anterior se declara un arreglo de dos filas y 3 columnas.


Podríamos llenar el arreglo anterior de la siguiente manera, utilizando Java:
Temperaturas[0][0] = 15.5; //Se llena la columna 0 de la fila 0
Temperaturas[0][1] = 5.7; //Se llena la columna 1 de la fila 0
Temperaturas[0][2] = 1.35; //Se llena la columna 2 de la fila 0
También se puede implementar en Java de la siguiente manera:
double [][] temperaturas = new double[][] {{15.5,5.7,1.35}, …}

Ya que hemos repasado el concepto de los arreglos en Java, ahora vamos a continuar
con el tema de las pilas y colas.
Como se había indicado, una pila es un caso especial de una lista lineal, en el cual la
inserción y supresión son operaciones que solo pueden ocurrir en un extremo de la pila,
el cual se denomina como tope de la pila.
Un ejemplo de pila es el apilamiento de una bandeja de libros en una biblioteca, o de los
platos en un restaurante. Podemos agregar más platos, pero es el último plato apilado
el que podemos desapilar primero. Igual ocurre con los libros.
Las operaciones básicas de una pila son las siguientes:
9 Crear(pila)
9 EsVacia(pila)
9 Apilar(elemento, pila)
9 Desapilar(elemento, pila)
El siguiente código Java implementa el TDA Pila.
1. package EstructuraDatos;
2.
3. import Excepciones.*;
4.
5. // Interfaz Pila: tomado de Estructura de Datos en Java, de Mark Allen.
6. //
7. // **************** OPERACIONES PÚBLICAS ******************
8. // void apilar (Object x); Æ Inserta x
9. // void desapilar() Æ Elimina el último elemento insertado.
10. // Object cima(); Æ Devuelve el último elemento insertado.
11. // Object cimaYDesapilar(); Æ Devuelve y elimina el último elemento.

38
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

12. // boolean esVacia(); Æ Devuelve Trae si la pila está vacia.


13. // void vaciar(); Æ Elimina todos los elementos
14. // **************** ERRORES ************************************
15. // cima, desapilar, o cimaYDesapilar sobre la pila vacia.
16.
17. public interface Pila
18. {
19. void apilar (Object x);
20. void desapilar() throws DesbordamientoInferior;
21. Object cima() throws DesbordamientoInferior;
22. Object cimaYDesapilar() throws DesbordamientoInferior;
23. boolean esVacia();
24. void vaciar();
25. }

La implementación anterior pretende la interfaz requerida para la construcción del


TDA pila. A continuación se presenta el programa requerido para su operatividad.
1. import EstructuraDatos.*;
2. import Excepciones.*;
3.
4. // Programa simple para probar las pilas. tomado de Estructura de Datos en Java,
de Mark A.
5.
6. public final class TestPila
7. {
8. public static void main( String [ ] args)
9. {
10. Pila p = new PilaVec();
11.
12. for (int i = 0, i < 5; i++)
13. p.apilar( new Integer ( i ));
14.

39
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

15. System.out.print(“Contenido : “);


16. Try
17. {
18. for ( ; ; )
19. System.out.print(“ “ + p.cimaYDesapilar());
20. }
21. Catch(DesbordamientoInferior e) {}
22. System.out.println();
23. }
24. }

Colas
Las colas se definen como una estructura de datos cuyos elementos se manejan bajo la
filosofía PEPS (Primero en Entrar Primero en Salir) o FIFO (First Input, First Output).
Una cola es una estructura lineal y finita, donde los elementos pueden ser de cualquier
tipo que se requiera. Su dominio lo forman aquellos elementos que puedan llegar a
almacenarse en la estructura, y se obtienen por dos partes de estructura: una llamada
frente que señala donde se encuentra el siguiente elemento por atender en la cola, y
otra llamada final que significa el lugar donde se encuentra el último elemento.
Las operaciones básicas de una cola son:
9 Insertar: insertar al final de la cola.
9 QuitarPrimero: quitar el elemento que se encuentra al frente de la cola.
9 Primero: acceso al primer elemento de la cola.

En la figura siguiente se muestra una representación del TDA Cola:

Entrada Salida

Fondo Frente

Figura No. 9: TDA Cola

40
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Obsérvese que los elementos entran al TDA Cola por el fondo y salen por el frente. En
este caso, el ingreso sería A, B, C y D y la salida sería A, B, C y D.
A continuación, se implementa en Java el TDA Cola:
1. package EstructuraDatos;
2.
3. import Excepciones.*;
4.
5. // Interfaz Cola: tomado de Estructura de Datos en Java, de Mark Allen.
6. //
7. // **************** OPERACIONES PÚBLICAS ******************
8. // void Insertar (x); Æ Inserta x
9. // Object primero() Æ Devuelve el primer elemento.
10. // Object quitarPrimero; Æ Devuelve el último elemento insertado.
11. // boolean esVacia(); Æ Devuelve Trae si la cola está vacia.
12. // void vaciar(); Æ Elimina todos los elementos
13. // **************** ERRORES ********************************
14. // primero y quitarPrimero sobre una cola vacia
15.
16. public interface Cola
17. {
18. void insertar (Object x);
19. Object primero() throws DesbordamientoInferior;
20. Object quitarPrimero() throws DesbordamientoInferior;
21. boolean esVacia();
22. void vaciar();
23. }

La implementación anterior pretende la interfaz requerida para la construcción del


TDA Cola. A continuación, se presenta el programa requerido para su operatividad.
1. import EstructuraDatos.*;
2. import Excepciones.*;
3.

41
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

4. // Programa simple para probar las colas. tomado de Estructura de Datos en


Java, de
5. // Mark Allen.
6.
7. public final class TestCola
8. {
9. public static void main( String [ ] args)
10. {
11. Cola c = new ColaVec();
12.
13. for (int i = 0, i < 5; i++)
14. c.insertar( new Integer ( i ));
15.
16. System.out.print(“Contenido : “);
17. Try
18. {
19. for ( ; ; )
20. System.out.print(“ “ + c.quitarPrimero());
21. }
22. Catch(DesbordamientoInferior e) {}
23.
24. System.out.println();
25. }
26. }

Antes de iniciar el tema siguiente, que trata sobre las listas, es importante detenerse a
estudiar un poco el concepto de referencias. Esto por cuanto los TDA anteriores, Pilas y
Colas, trabajan generalmente sobre vectores (arreglos unidimensionales). Los arreglos
constituyen TDA estáticos como sabemos y las listas, árboles y grafos se representan
generalmente pormedio de eslabones conectados mediante variables denominadas
referencias. Debe indicarse que los grafos, aunque se representen mediante referencias
que le aportan dinamismo pueden ser representados también a través de arreglos
bidimensionales.

42
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Una referencia en Java es una variable que guarda la dirección de memoria en la que se
almacena un objeto. Una referencia siempre contiene la dirección de memoria en la que
un objeto está guardado, a menos que no referencie a ningún objeto. En este caso,
almacena la referencia nula (Null). Java no permite referencias a variables primitivas.

Listas enlazadas
Las listas enlazadas suelen denominarse también listas encadenadas. Este tipo de
TDA puede definirse como una estructura de datos que tiene una organización lineal, y
se caracteriza porque cada uno de sus elementos tiene que indicar dónde se encuentra
el siguiente elemento de la lista. Una lista enlazada se gestiona a través de memoria
dinámica (utilizando nodos de datos y punteros), pero también mediante memoria
estática (en vectores bidimensionales). Sin embargo, esta segunda opción representa
un derroche importante de memoria.
En la figura siguiente se representa una lista enlazada, mediante punteros.

Figura No. 10: TDA Lista enlazada, mediante punteros (o referencias)

En la representación del TDA lista enlazada anterior podemos observar que existen
cuatro nodos que contienen dos campos esenciales: el primero, donde se guarda el dato
(A, B, C y D) y, el segundo, sus correspondientes campos de apuntador. Así, A tiene el
campo donde guarda el valor A y otro campo donde almacena la dirección al siguiente
nodo (B).

Posición siguiente
Posición Elemento
elemento

0 A 1

1 B 2

2 C 3

3 D Nulo

Tabla No. 1: TDA Lista enlazada, mediante arreglo bidimensional

43
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Para crear un nodo (datos y puntero) en Java, se utiliza la siguiente declaración:


Class NodoLista
{
Object dato; //Algún elemento
NodoLista siguiente; //Referencia al próximo nodo de la lista
}
Para agregar un nodo, utilizando Java, podemos utilizar el siguiente fragmento de
programa:

Ultimo.siguiente = new NodoLista(); // adjunta un nuevo nodo a la lista


Ultimo = ultimo.siguiente; //ajusta el último, apuntando al nodo apuntado por
ultimo.
Ultimo.dato = x; //coloca x en el nodo (el dato)
Ultimo.siguiente = null; //pone null al último nodo.

Las pilas y colas se pueden implementar en listas enlazadas. A este tipo de listas se les
denomina simple porque solamente se pueden ingresar datos por el fondo (en caso de
las colas) y por el tope (en el caso de las pilas). La eliminación se realiza por el frente
(en caso de las colas) y por el tope (en el caso de las pilas). Sin embargo, esos TDA se
manejan adecuadamente en arreglos unidimensionales y es funcionalmente adecuado.
Sin embargo, cuando los datos deben insertarse en cualquier parte, o eliminarse de
cualquier posición, el concepto de lista enlazada cambia su funcionalidad. En este caso,
las listas amplían el concepto de nodos enlazados a listas enlazadas, doblemente
enlazadas o listas circulares.
Las pilas y colas también pueden ser implementadas mediante listas enlazadas
mediante referencias. La ventaja de alojar estas estructuras en listas enlazadas es que
el exceso de gasto de memoria en que se incurre se reduce a una referencia por
elemento, mientras que si se almacenara en arreglos se exigiría un espacio mayor,
incluyendo los espacios vacíos. En Java, esta desventaja no es tan relevante, dado que
los espacios vacíos del vector representan referencias nulas, las cuales ocupan muy
poco espacio.
Una pila puede implementarse en Java donde la cima (tope) de la misma está
representada por el primer elemento.
Las siguientes condiciones deben cumplirse para gestionar la pila mediante listas
enlazadas:

44
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

9 Para apilar un nuevo elemento en la lista enlazada se debe utilizar la instrucción


new.
9 Para desapilar un elemento de la lista enlazada (si la pila no está vacia) se
deberá desplazar la cima de la pila al segundo elemento de la lista.
9 La pila vacia se representa mediante una lista vacia.

Debe recordarse que cada nodo tiene un único campo de enlace, una variable de
referencia contiene una dirección al primer nodo; cada nodo (excepto el último) enlaza
con el nodo siguiente, y el enlace del último nodo es nulo (null) para indicar que es el
final de la lista.
Cuando se trata el tema de punteros, en realidad se está hablando de variables de
referencia. Una variable de referencia en Java guarda la dirección de memoria en la
que se almacena un objeto.

Árboles
Un árbol es un conjunto de nodos y aristas. Las aristas son las referencias que
conectan pares de nodos. Un árbol organiza los datos en una estructura en forma
jerárquica o de niveles. Su característica principal es que mantiene una relación de uno
a muchos (1:n) entre sus elementos. Por ejemplo, mediante árboles podemos
representar un árbol genealógico, un organigrama, los directorios de un sistema
operativo, entre otros.

En la figura siguiente se muestra un árbol y sus componentes:

Nodo
Nodo
Arista
Arista

Figura No 11: TDA Árbol

45
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Existen conceptos que deben considerarse en la estructura árbol. Las mismas son:
1. Profundidad de un nodo: Es la longitud del camino que va desde la raíz hasta el
nodo.
2. Altura de un nodo: Es la longitud del camino que va desde el nodo hasta la hoja
más profunda bajo él.
3. Tamaño de un nodo: Es igual al número de descendientes que tiene
(incluyéndose).

Árboles generales:
Un árbol general está constituido por nodos y aristas que se correlacionan. Un árbol
general, como los demás tipos de árboles, inicia con un nodo raíz, y de ahí se despliega
hacia los demás nodos. En la figura No. 4 puede observarse un árbol general,
constituido por nodos y aristas que unen estos nodos.

Árboles binarios:
Un árbol binario es aquel en el cual cada nodo no puede tener más de dos hijos. En
este caso, un nodo padre tiene dos nodos hijos: el hijo izquierdo y el hijo derecho. Estos
hijos pueden, a su vez, ser vacíos o tener otro par de nodos o solo un hijo (izquierdo o
derecho). En la figura siguiente se muestra un árbol binario.

Figura No. 12: TDA Árbol Binario

46
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Los árboles binarios son muy utilizados en el diseño de compiladores, dado que
mediante estos se pueden construir expresiones, las cuales se almacenan en sus
nodos; la ruta de ejecución se representa mediante las aristas que los une. A este uso
se le denomina árbol de expresiones. Otro uso es para implementar un algoritmo
simple pero relativamente eficiente de compresión de datos. Se le denomina árbol de
codificación de Huffman.

La terminología básica en esta estructura es la siguiente:


• Nodo raíz: Es el primer elemento de un árbol binario; un árbol binario solo tiene
un nodo raiz.
• Nodo padre: Son los nodos que tienen al menos un hijo (derecho o izquierdo).
• Hijo derecho: Nodo que se representa al lado derecho de otro nodo.
• Hijo izquierdo: Nodo que se representa al lado izquierdo de otro nodo.
• Nodo hoja: Nodos que no tienen hijos. En un árbol binario puede tener uno, dos o
ninguno.
• Nodo hermano: Nodos que tienen un mismo nodo padre.
• Ancestros: Nodo padre de un nodo o el padre de algún nodo ancestro. El nodo
raíz es un ancestro de todos los nodos del árbol.
• Nodo descendiente: El hijo de un nodo o el hijo de otro descendiente de ese
nodo. Todos los nodos de un árbol son descendientes del nodo raíz.
• Subárbol izquierdo: Todos los descendientes por la izquierda de un nodo forman
un subárbol izquierdo, cuya raíz es el hijo izquierdo de ese nodo.
• Subárbol derecho: Todos los descendientes por la derecha de un nodo forman un
subárbol derecho, cuya raíz es el hijo derecho de ese nodo.
• Nivel de un nodo: Distancia desde la raíz. La raíz esta en el nivel cero. Cantidad
de nodos por los que se tiene que pasar para llegar a un nodo.

47
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

A continuación, se presenta un ejemplo de lo anterior:

El nodo raiz es el 5.
El hijo derecho de 5 es 8. El hijo derecho de 6 es 7.
El hijo izquierdo de 5 es 3. El hijo izquierdo de 8 es 6.
55
El nodo padre de 2 y 4 es 3. El nodo padre de 7 es 6.
Los nodos hojas son 1,4,7 y 10.
33 88
Los hermanos son (2 y 4), (6 y 9).
Los ancestros de 7 son 6,8 y 5.
22 44 66 99
Los descendientes de 8 son 6, 7, 9 y 10.
El subárbol derecho de 5 es el que tiene la raiz 8.
11 77 10
10 El subárbol izquierdo de 5 es el que tiene raiz 3.
El nodo del nivel cero es 5.
Los nodos del nivel 1 son 3 y 8.
Los nodos del nivel 2 son 2, 4, 6 y 9.
Los nodos del nivel 3 son 1, 7 y 10.

Figura No. 13: Ejemplo de los elementos de un árbol binario

Árboles y recursividad:
Dado que la definición de un árbol se realiza mediante recursión, así también las demás
rutinas que los gestionan también. El hecho de que un método se llame repetidamente
a sí mismo se conoce como recursión.
La recursión trabaja dividiendo un problema en subproblemas. Un programa llama a un
método con uno o más parámetros que describen un problema. Si el método detecta
que los valores no representan la forma más simple del problema, se llama a sí mismo
con valores de parámetros modificados que describen un subproblema cercano a esa
forma.
Para entender la recursión, consideremos un método que suma todos los enteros desde
1 hasta algún límite superior.

48
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

static int suma (nt limite)


{
int total = 0;
for (int i=1; i<=limite;i++)
total+=i;
return total;
}

El programa anterior es correcto dado que logra determinar el total de todos los números
desde 1 hasta límite. Sin embargo, mediante la recursión es posible realizar esta misma
suma de la siguiente forma:

static int suma (nt limite)


{
if (limite ==1)
return 1;
else
return limite + suma(limite – 1);
}
Un ejemplo de aplicación de la recursividad en árboles es una rutina para calcular el
tamaño de un nodo (número de descendientes que tiene, incluyéndose). El siguiente
algoritmo representa esta idea:
1. /**
2. Devuelve el tamaño del árbol binario con raíz en a.
3. */
4. static int tamanno (NodoBinario a)
5. {
6. if (a == null)
7. return 0;
8. else
9. return 1 + tamanno(a.izquierdo) + tamanno(a.derecho);
10. }

49
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

También se puede calcular la altura de un nodo (longitud del camino que va desde el
nodo hasta la hoja más profunda bajo él) utilizando recursividad. El siguiente fragmento
de programa representa este caso:
1. /**
2. Devuelve altura del árbol binario con raíz en a.
3. */
4. static int altura (NodoBinario a)
5. {
6. if (a == null)
7. return -1;
8. else
9. return 1 + Math.max(altura(a.izquierdo), altura(a.derecho));
10. }

Recorrido de árboles
Cuando se utiliza la recursión o recursividad para calcular el tamaño o la altura de un
árbol, estamos recorriendo todos los nodos que lo componen. En este caso, se está
recorriendo el árbol. Existen varios algoritmos que se utilizan para realizar los recorridos
en un árbol. A continuación se presentan varios algoritmos conocidos:

Recorrido en postorden
En el recorrido postorden, el nodo se procesa después de haber procesado
recursivamente sus hijos. Dos ejemplos son los fragmentos de programa de tamaño y
altura, que se utilizaron para determinar el tamaño y la altura de un árbol binario.
El recorrido postorden mantiene una pila que almacena los nodos ya visitados. La cima
de la pila indica el nodo que actualmente está siendo visitado. En este caso, se pueden
realizar cualesquiera de las siguientes operaciones: hacer una llamada recursiva al hijo
izquierdo, hacer una llamada recursiva al hijo derecho o procesar el nodo actual. Por
consecuencia, cada nodo se colocará en la pila tres veces, y se marca como el nodo
actual que ha de ser visitado una vez que se desapila por tercera vez.
Este recorrido es útil para implementar el destructor de un árbol binario (eliminación de
todos los nodos).

50
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Los pasos del recorrido son los siguientes:


1. Recorrer en postorden el subárbol izquierdo del nodo raíz.
2. Recorrer en postorden el subárbol derecho del nodo raíz.
3. Visitar el nodo raíz del árbol.

Recorrido en orden simétrico


El recorrido simétrico primero procesa recursivamente el hijo izquierdo, luego se procesa
el nodo actual y finalmente se procesa recursivamente el hijo derecho. Este recorrido se
utiliza para generar una expresión algebraica correspondiente a un árbol de expresión.
Por ejemplo, para construir la expresión (a+((b-c)*d)), se construye el siguiente árbol:

++

aa *

-- d
d

b
b cc

Figura No. 14: Árbol de expresión de (a+((b-c)*d))

Recorrido en preorden
En el recorrido preorden, primero se procesa el nodo y después se procesan sus hijos.
Es útil para reconstruir un ABB (Árbol Binario de Búsqueda). Las secuencias lógicas
para este recorrido son:
1. Visitar el nodo raíz del árbol.
2. Recorrer en preorden el subárbol izquierdo del nodo raíz.
3. Recorrer en preorden el subárbol derecho del nodo raíz.

La implementación recursiva, en Java, de los recorridos PreOrden, PostOrden y orden


Simétrico, es la siguiente:

51
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

1. void imprimirPreorden()
2. {
3. System.out.println( dato ); //nodo
4. if ( izquierdo ¡= null )
5. izquierdo.imprimirPreorden( ) // izquierdo
6. if ( derecho ¡= null )
7. derecho.imprimirPreorden( ) // derecho
8. }
9.
10. void imprimirPostorden()
11. {
12. if ( izquierdo ¡= null )
13. izquierdo.imprimirPostorden( ) // izquierdo
14. if ( derecho ¡= null )
15. derecho.imprimirPostorden( ) // derecho
16. System.out.println( dato ); //nodo
17. }
18. void imprimirOrdenSim()
19. {
20. if ( izquierdo ¡= null )
21. izquierdo.imprimirOrdenSim( ) // izquierdo
22. System.out.println( dato ); //nodo
23. if ( derecho ¡= null )
24. derecho.imprimirOrdenSim( ) // derecho
25. }

Árboles Binarios de Búsqueda (ABB)


Un árbol binario de búsqueda se define como una estructura de datos que guarda
información no repetida para administrar eficientemente la búsqueda de los propios
datos. Pertenece al grupo de estructuras jerárquicas, restringiendo las relaciones de
uno a dos como máximo, y cumpliendo con un ordenamiento de tal forma que, para

52
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

cada elemento del ABB, los elementos menores estarán a su izquierda, y los mayores a
su derecha.
A continuación, se muestra una figura de un árbol binario de búsqueda (ABB):

Árboles Binarios de Búsqueda (ABB) y no ABB (en circulo rojo)

88
55 44
33
33 99

11 88
33

66 99 11 22

55 77

Figura No. 15: Árboles Binarios de Búsqueda (ABB)

La idea básica del algoritmo de búsqueda consiste en comparar la información del nodo
con la del valor buscado. Si no son iguales, el apuntador de búsqueda se mueve a la
izquierda o a la derecha del valor buscado, según sea menor o mayor, comenzando por
la raíz y hasta que se encuentre o no el valor.
El algoritmo se puede describir de la siguiente manera:
1. Coloque un apuntador auxiliar en la raíz del árbol.
2. Mientras no se haya encontrado el valor que se busca, y el apuntador auxiliar no
este vacío (fuera del árbol):

• Verifique si la información del nodo señalado por el apuntador auxiliar es


mayor, menor o igual al nodo buscado.
• Si es mayor, mueva el apuntador auxiliar al nodo hijo derecho. Si es menor,
mueva el apuntador auxiliar al nodo izquierdo. Si son iguales, ha encontrado
el nodo, y el apuntador auxiliar lo señala.

53
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

La siguiente figura muestra la búsqueda en un ABB:

13
13

Buscar
Buscar el
el 13
13
12
12

77 21
21

44 99 16
16 25
25

22 88 13
13 19
19

La línea punteada señala la ruta


de búsqueda del valor 13,
siempre evaluando los valores
de los nodos visitados

Figura No. 16: Inserción en un ABB

En la figura anterior se muestra cómo se inicia la búsqueda del valor 13 en el árbol


binario. Inicialmente, el valor 13 es mayor que el nodo raíz (que 12), por lo que la
búsqueda se va por el subárbol derecho. Ahí se pregunta si es mayor, menor o igual
que el valor que tiene el nodo (vértice), y se evalúa que no (no es mayor ni igual que 21,
es menor), por lo que la búsqueda continúa por el subárbol izquierdo. Ahí se vuelve a
evaluar el valor, y se determina que es menor que el valor que posee el nodo visitado,
por lo que se va por su subárbol izquierdo. Ahí se vuelve a evaluar el valor, y se
determina que es igual al valor buscado, por lo que la búsqueda termina con éxito.
El siguiente algoritmo, implementado en Java, muestra la búsqueda en un árbol binario:
1. // Clase BinarySearchTree
2. //
3. // CONSTRUCTION: Sin inicializadores
4. //
5. // ******************Operaciones Públicas*********************
6. // void insert( x ) --> Inserta x
7. // void remove( x ) --> Remueve x

54
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

8. // boolean contains( x ) --> Retorna true si x es encontrada


9. // Comparable findMin( ) --> Retorna el elemento más pequeño
10. // Comparable findMax( ) --> Retorna el elemento más grande
11. // boolean isEmpty( ) --> Retorna true si está vacío; sino false
12. // void makeEmpty( ) --> Remueve todos los elementos
13. // void printTree( ) --> Imprime el árbol en forma ordenada
14. // ******************ERRORES********************************
15. // Throws UnderflowException as appropriate
16. /**
17. Implementa un árbol de búsqueda binaria desbalanceado.
18. Note que todas las "búsquedas" están basadas en compareTo method.
19. @author Mark Allen Weiss
20. */
21. public class BinarySearchTree<AnyType extends Comparable<? super
AnyType>>
22. {
23. /**
24. Construye el árbol.
25. */
26. public BinarySearchTree( )
27. {
28. root = null;
29. }
30. /**
31. Inserción en el árbol; los duplicados son ignorados
32. @param x es el elemento a insertar
33. */
34. public void insert( AnyType x )
35. {
36. root = insert( x, root );
37. }

55
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

38. /**
39. Remueve desde el árbol. No se hace nada si x no es encontrado
40. @param x es el item a remover.
41. */
42. public void remove( AnyType x )
43. {
44. root = remove( x, root );
45. }
46. /**
47. Encuentra el elemento más pequeño en el árbol.
48. @return el elemento más pequeño o nulo si no lo encuentra.
49. */
50. public AnyType findMin( )
51. {
52. if( isEmpty( ) )
53. throw new UnderflowException( );
54. return findMin( root ).element;
55. }
56. /**
57. Encuentra el elemento más grande en el árbol.
58. @return el elemento más grande o nulo si no lo encuentra.
59. */
60. public AnyType findMax( )
61. {
62. if( isEmpty( ) )
63. throw new UnderflowException( );
64. return findMax( root ).element;
65. }
66. /**
67. Encuentra un elemento en el árbol.
68. @param x es el elemento a buscar

56
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

69. @return true si no lo encuentra


70. */
71. public boolean contains( AnyType x )
72. {
73. return contains( x, root );
74. }
75. /**
76. Hace que el árbol esté lógicamente vacío.
77. */
78. public void makeEmpty( )
79. {
80. root = null;
81. }
82. /**
83. Comprueba si el árbol está lógicamente vacío.
84. @return true si está vacío, false en otro caso.
85. */
86. public boolean isEmpty( )
87. {
88. return root == null;
89. }
90. /**
91. Imprime el contenido del árbol en forma ordenada.
92. */
93. public void printTree( )
94. {
95. if( isEmpty( ) )
96. System.out.println( "Empty tree" );
97. else
98. printTree( root );
99. }

57
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

100. /**
101. Método interno para insertar en un sub árbol.
102. @param x es el elemento a insertar
103. @param t es el node raíz en el sub árbol.
104. @return la nueva raíz del sub árbol
105. */
106. private BinaryNode<AnyType> insert( AnyType x, BinaryNode<AnyType> t )
107. {
108. if( t == null )
109. return new BinaryNode<AnyType>( x, null, null );
110. int compareResult = x.compareTo( t.element );
111. if( compareResult < 0 )
112. t.left = insert( x, t.left );
113. else if( compareResult > 0 )
114. t.right = insert( x, t.right );
115. else; // Duplicado; Nada que hacer
116. return t;
117. }
118. /**
119. Método interno para eliminar de un sub árbol
120. @param x es el elemento a eliminar
121. @param t es el nodo raíz del sub árbol
122. @return la nueva raíz del sub árbol
123. */
124. private BinaryNode<AnyType> remove( AnyType x, BinaryNode<AnyType> t )
125. {
126. if( t == null )
127. return t; // Elemento no encontrado; Nada que hacer
128. int compareResult = x.compareTo( t.element );
129. if( compareResult < 0 )
130. t.left = remove( x, t.left );

58
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

131. else if( compareResult > 0 )


132. t.right = remove( x, t.right );
133. else if( t.left != null && t.right != null ) // Dos hijos
134. {
135. t.element = findMin( t.right ).element;
136. t.right = remove( t.element, t.right );
137. }
138. else
139. t = ( t.left != null ) ? t.left : t.right;
140. return t;
141. }
142. /**
143. Método interno para encontrar el elemento más pequeño en el sub árbol
144. @param t es el nodo raíz del sub árbol
145. @return el nodo con el elemento más pequeño del sub árbol
146. */
147. private BinaryNode<AnyType> findMin( BinaryNode<AnyType> t )
148. {
149. if( t == null )
150. return null;
151. else if( t.left == null )
152. return t;
153. return findMin( t.left );
154. }
155. /**
156. Método interno para encontrar el elemento más grande del sub árbol
157. @param t es el nodo raíz del sub árbol
158. @return el nodo con el elemento más grande del sub árbol
159. */
160. private BinaryNode<AnyType> findMax( BinaryNode<AnyType> t )
161. {

59
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

162. if( t != null )


163. while( t.right != null )
164. t = t.right;
165. return t;
166. }
167. /**
168. Método interno para encontrar un elemento en el sub árbol
169. @param x es el elemento a buscar
170. @param t es el nodo raíz del sub árbol
171. @return el nodo del elemento buscado
172. */
173. private boolean contains( AnyType x, BinaryNode<AnyType> t )
174. {
175. if( t == null )
176. return false;
177. int compareResult = x.compareTo( t.element );
178. if( compareResult < 0 )
179. return contains( x, t.left );
180. else if( compareResult > 0 )
181. return contains( x, t.right );
182. else
183. return true; // encontrado
184. }
185. /**
186. Método interno para imprimir ordenadamente el sub árbol
187. @param t es el nodo raíz del sub árbol
188. */
189. private void printTree( BinaryNode<AnyType> t )
190. {
191. if( t != null )
192. {

60
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

193. printTree( t.left );


194. System.out.println( t.element );
195. printTree( t.right );
196. }
197. }
198. /**
199. Método interno para calcular el tamaño del sub árbol
200. @param t es el nodo raíz del sub árbol
201. */
202. private int height( BinaryNode<AnyType> t )
203. {
204. if( t == null )
205. return -1;
206. else
207. return 1 + Math.max( height( t.left ), height( t.right ) );
208. }
209. // Nodo básico almacenado en árbol de búsqueda binaria desbalanceado
210. private static class BinaryNode<AnyType>
211. {
212. // Constructors
213. BinaryNode( AnyType theElement )
214. {
215. this( theElement, null, null );
216. }
217. BinaryNode( AnyType theElement, BinaryNode<AnyType> lt,
BinaryNode<AnyType> rt )
218. {
219. element = theElement;
220. left = lt;
221. right = rt;
222. }

61
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

223. AnyType element; // Los datos en el nodo


224. BinaryNode<AnyType> left; // Hijo izquierdo
225. BinaryNode<AnyType> right; // Hijo derecho
226. }
227. /** La raíz del árbol. */
228. private BinaryNode<AnyType> root;
229. // Test program
230. public static void main( String [ ] args )
231. {
232. BinarySearchTree<Integer> t = new BinarySearchTree<Integer>( );
233. final int NUMS = 4000;
234. final int GAP = 37;
235. System.out.println( "Checking... (no more output means success)" );
236. for( int i = GAP; i != 0; i = ( i + GAP ) % NUMS )
237. t.insert( i );
238. for( int i = 1; i < NUMS; i+= 2 )
239. t.remove( i );
240. if( NUMS < 40 )
241. t.printTree( );
242. if( t.findMin( ) != 2 || t.findMax( ) != NUMS - 2 )
243. System.out.println( "FindMin or FindMax error!" );
244. for( int i = 2; i < NUMS; i+=2 )
245. if( !t.contains( i ) )
246. System.out.println( "Find error1!" );
247. for( int i = 1; i < NUMS; i+=2 )
248. {
249. if( t.contains( i ) )
250. System.out.println( "Find error2!" );
251. }
252. }
253. }

62
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Operaciones
Las operaciones que se pueden realizar sobre un ABB son las de inserción y
eliminación. Todo nuevo nodo se insertará como nodo hoja en el ABB, en el lugar que le
corresponda según el proceso de búsqueda.
En el caso de la inserción, se realizará una búsqueda binaria con la idea de colocar el
valor en una posición adecuada. Observe el ejemplo presentado en la figura siguiente:

Buscar
Buscar el
15
15
el 15

12
12 12
12
15

12
12

77 21
21 77 21
21
77 21
21

44 99 25
25 44 99 25
25
44 99 25
25

22 88 22
16
16 88 16
16
22 88 16
16

13
13 19
19 13
13 19
19
13
13 19
19

Puesto que no existe se detecta


el nodo padre del 15 en la 15
15
inserción

Figura No. 17: Inserción en un ABB

La lógica de esta inserción es la siguiente:


1. Se crea un nodo por medio de un apuntador auxiliar 1. Se completa con la
información que se va a insertar en el árbol y se colocan sus apuntadores como
nodo hoja.
2. Se coloca un apuntador auxiliar 2 en la raíz del árbol y un apuntador auxiliar 3 en
vacío. El apuntador auxiliar 3 siempre señalará al nodo padre del nodo al que
señala el apuntador auxiliar 2.
3. Mientras el apuntador auxiliar 2 no sea vacío (fuera del árbol) se realiza lo
siguiente:

63
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

• Se coloca el apuntador auxiliar 3 en el nodo que marca el apuntador auxiliar 2.


• Mover el apuntador auxiliar 2 al nodo hijo izquierdo si la información que se va
a insertar es menor a la información del nodo que señala el apuntador auxiliar
2. En caso contrario, se debe mover a la derecha (puesto que la información
por insertar es mayor).
Al salir del ciclo el apuntador auxiliar 2 señalará vacío, pero el apuntador
auxiliar 3 estará en el nodo que será el padre del nuevo.
4. Verificar si el apuntador 3 es vacío, en cuyo caso el nuevo nodo será el primero
en el árbol y el apuntador raíz tendrá que señalarlo.
Si el apuntador auxiliar 3 no es vacío, entonces estará señalando al padre del
nuevo nodo. Se debe verificar si la información del nuevo nodo es menor a la del
marcado por el apuntador auxiliar 3, en cuyo caso deberá encadenarse el nuevo
nodo como hijo izquierdo del señalado por el apuntador auxiliar 3. Si la
información no es menor, entonces será mayor, y tendrá que encadenarse como
hijo derecho.
Para efectos de eliminación de un nodo en un ABB, se muestra el siguiente escenario:
1. El nodo que se va a borrar es una hoja. Puesto que no tiene hijos, su nodo padre
apuntará ahora a vacío. Véase la siguiente figura como representación:
Eliminar
Eliminar el
el 88

12
12 12
12

77 21
21 77 21
21

44 99 16
16 44 99 16
16

22 88 13
13 19
19 22 13
13 19
19

Paso 2) El padre del nodo


Paso 1) Buscar el elemento 8 borrado cambia su enlace a
NULL

Figura No. 18: Eliminación en un ABB cuando el nodo por borrar es una hoja

64
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

2. El nodo por borrar tiene sólo un hijo. En este caso, el padre del que se va a
borrar puede apuntar directamente al nodo hijo del que se eliminará. Obsérvese
la siguiente figura que demuestra este caso:

Eliminar
Eliminar el
Paso 1) Buscar el elemento 21

el 21
21
12
12 12
12

77 21
21 77 16
16

44 99 16
16 44 99 13 19
13 19

22 88 13
13 19
19 22 88

Paso 2) El padre del nodo


borrado cambia su apuntador al
hijo del nodo borrado

Figura No. 19: Eliminación en un ABB cuando el nodo por borrar tiene un hijo

3. El nodo por borrar tiene dos hijos. Puesto que el padre del que se va a borrar no
puede heredar dos apuntadores, se busca un valor sustituto del valor por borrar y
el nodo no se borra físicamente. Se puede escoger como sustituto al predecesor
del que se eliminará (el valor mayor de todos los valores menores); o bien, de los
nodos del subárbol izquierdo el de más a la derecha), y se elimina físicamente el
nodo donde se encuentre. Se puede asegurar que la baja física del nodo
sustituto cae en alguno de los dos primeros casos. De igual manera, se puede
considerar al sucesor como valor sustituto.

65
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

EEl
12 Paso 2) Buscar el sustituto

liimm
Paso 1) Buscar el elemento 12 12

iinn
aar
r ee
ll 11
21

22
77 21
12
12

44 99 16
16
77 21
21

22 88 13
13 19
19
44 99 16
16

Predecesor Sucesor
99
22 88 13
13 19
19

77 21
21

Paso 3) Bajar utilizando el


44 88 16
16 predecesor

22 13
13 19
19

Figura No. 20: Eliminación en un ABB cuando el nodo a borrar tiene dos hijos

El algoritmo para llevar a cabo las operaciones anteriores está compuesto de dos
partes. La primera se encarga de localizar el nodo por borrar y la segunda borra el nodo
encontrado.

Primera Parte: (localizar el nodo por borrar)


1. Se coloca un apuntador auxiliar 1 en la raíz del árbol y un apuntador auxiliar 2 en
vacío. El apuntador auxiliar 2 siempre señalará al nodo padre del que señala el
apuntador auxiliar 1.
2. Mientras no se haya encontrado el nodo por borrar:
• Se verifica que la información del nodo señalado por el apuntador auxiliar 1 es
la que se desea borrar. En caso de que no sea, a) se coloca el apuntador
auxiliar 2 en el nodo que señala el apuntador auxiliar 1, y b) se mueve el
apuntador auxiliar 1 al nodo izquierdo si la información que se va a borrar es
menor a la información del nodo que señala el apuntador auxiliar 1. En caso
contrario, debe moverse a la derecha.
• Al salir del ciclo, el apuntador auxiliar 1 estará señalando al nodo por borrar y
el apuntador auxiliar 2 al nodo padre.

66
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Segunda Parte: (eliminar el nodo correspondiente)


1. Se coloca un apuntador temporal en el nodo que se desea borrar.
2. Se verifica si el nodo por borrar es hoja o tiene solo un hijo, en cuyo caso se
desencadenará del árbol para borrarlo.
Nodo hoja: Si el nodo apuntado por el auxiliar 1 no tiene hijo izquierdo ni
derecho, debe modificarse el apuntador que lo conecta con su padre (a través del
auxiliar 2) de tal forma que apunte hacia vacío.
Nodo con un hijo derecho: Si el nodo apuntado por el auxiliar 1 no tiene hijo
izquierdo pero si el derecho, se modifica el apuntador que lo conecta con su
padre (a través del auxiliar 2) de tal forma que señale al hijo derecho.
Nodo con un hijo izquierdo: Si el nodo apuntado por el auxiliar 1 no tiene hijo
derecho, pero si hijo izquierdo, debe modificarse el apuntador que lo conecta con
su padre (a través del auxiliar 2), de tal forma que señale al nodo izquierdo.
Nodo con dos hijos: Si el nodo tiene dos hijos, se procederá a localizar su
sustituto buscando su predecesor de la siguiente forma:
• Se coloca el apuntador temporal en el hijo izquierdo del nodo señalado por
el apuntador auxiliar 1.
• Se mueve el apuntador temporal hacia la derecha lo más posible, es decir,
justo en el nodo antes de que el movimiento a la derecha lo saque del
árbol. El nodo al que se llegue será el nodo sustituto y se puede asegurar
que es nodo hoja o que tiene solo un hijo izquierdo.
• Se copia la información del nodo sustituto en el marcado por el apuntador
auxiliar 1.
• Se desencadena el nodo señalado por el apuntador temporal de la misma
forma en que se hace para un nodo hoja o uno con hijo izquierdo. En este
caso, para el movimiento de apuntadores, se tendrá que evaluar si el nodo
marcado por el apuntador temporal es la raíz del subárbol izquierdo del
nodo señalado por el apuntador auxiliar 1 o es de un nivel inferior.
3. Se libera el nodo señalado por el apuntador temporal.

AVL
Un árbol AVL es un árbol de búsqueda que trata de mantenerse lo más balanceado
posible, conforme se realizan las operaciones de inserción y eliminación. En los árboles
AVL se debe cumplir el hecho que para cualquier nodo del árbol, la diferencia en las
alturas de sus subárboles no exceda una unidad.
A continuación, la figura muestra los árboles que son AVL y aquellos que no lo son,
considerando la diferencia en las alturas de sus subárboles.

67
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Árboles AVL Árboles que no son AVL

Figura No. 21: Ejemplos de Árboles AVL y no Árboles AVL

Los nodos de un árbol AVL guardan un valor entre 1 y -1, lo que se conoce como Factor
de Balanceo (FB). Representa la diferencia entre las alturas de sus árboles. Un FB
igual a cero en un nodo significa que las alturas de sus subárboles son iguales. Un FB
positivo significa que el subárbol derecho es más grande que el izquierdo. Un FB
negativo, representa que el subárbol izquierdo es más grande que el derecho.
Para mantener un árbol balanceado se debe aplicar uno de los cuatro esquemas de
balanceos denominados simple o doble. El balanceo simple (izquierda o RSI) y derecha
(RSD) implica el movimiento de tres apuntadores. Véase la siguiente figura que
demuestra este tipo de rotación:

Resto
Resto del
del Árbol
Árbol Resto
Resto del
del Árbol
Árbol

Rotació
Rotación Simple

+ Nodo Pivote 0
B
B A
A

0 A
A 0 Ajustar FB en ruta
B
B
de búsqueda
1 3
0
2 3 1 2 Nuevo Nodo

Nuevo Nodo

Figura No. 22: Balanceo de un AVL por Rotación Simple

68
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

En la figura anterior se tiene una rotación simple izquierda de un árbol AVL. Los
triángulos representan subárboles de cualquier altura (la misma para todos). La rotación
hace un balanceo a partir del nodo pivote y el resto del árbol no se afecta. Observe que
el valor A es mayor que B y hay congruencia después de la rotación. La imagen de la
RSD se obtiene viendo este esquema a través de un espejo.

Para representar la rotación doble se tiene la siguiente figura:

Resto
Resto del
del Árbol
Árbol Resto
Resto del
del Árbol
Árbol

Rotació
Rotación Doble

+ B
B Nodo Pivote
0
C
C

0
0 A
A
B
B 0+ A
A

0
C
1 C

2 3

2 3 4 1 0 0 4
Nuevo Nodo
Nuevo Nodo

Figura No. 23: Balanceo de un AVL por Rotación Doble

En la figura anterior, se tiene una rotación doble izquierda de un árbol AVL. Los
triángulos 1 y 4 representan subárboles de cualquier altura; los triángulos 2 y 3 son
subárboles con una altura menor en un nivel, con respecto a 1 y 4. La rotación hace un
balanceo a partir del nodo pivote. El resto del árbol no se ve afectado. Observe que el
valor C es mayor que B y menor que A, por lo que hay congruencia después de la
rotación. El esquema de RDD se obtiene viendo este esquema a través de un espejo.
El algoritmo general para la inserción de un nodo en un árbol AVL es el siguiente:
1. Insertar el nodo como en un ABB (rutina de inserción).
2. Buscar el nodo pivote. Colocar los apuntadores P1, P2, P3 y P4, donde:
• P1 = Apuntador al nodo padre del nodo pivote
• P2 = Apuntador al nodo pivote

69
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

• P3 = Apuntador al nodo hijo del nodo pivote, que es la raíz del subárbol más
grande.
• P4 = Apuntador al nodo hijo del nodo apuntado por P3, que sigue en la ruta de
búsqueda del nuevo nodo.
3. Si no existe nodo pivote (P2 apunta a vacío), entonces modificar los FB desde la
raíz hasta el nuevo nodo (rutina que modifica los FB).
Si el nuevo nodo se insertó en el subárbol más pequeño del nodo pivote,
entonces modificar los FB desde el nodo pivote hasta el nuevo (rutina que
modifica los FB).
Si no es así, verificar el tipo de rotación:
Si es rotación simple, entonces se deben modificar los apuntadores y FB
(rutina que realiza la rotación simple).
Si no, entonces deben modificarse los apuntadores y FB (rutina que realiza
la rotación doble).

Algoritmo para la rotación simple:


Se suponen los apuntadores P1, P2, P3 y P4 colocados según los esquemas:
1. Si P1 no apunta a vacío:
Si la información del nuevo nodo es menor que la información apuntada por P1:
Hijo Izquierdo de P1 = P3
Si no
Hijo Derecho de P1 = P3
Si no
P3 es la nueva raíz del árbol.

2. Si la información del nuevo nodo es menor que la información apuntada por P2:
Hijo Izquierdo de P2 = Hijo Derecho de P3
Hijo Derecho de P3 = P2
Modificar el FB desde el hijo izquierdo de P3 hasta el padre del nuevo
nodo
Si no
Hijo Derecho de P2 = Hijo Izquierdo de P3
Hijo Izquierdo de P3 = P2

70
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Modificar FB desde el hijo derecho de P3 hasta el padre del nuevo nodo

3. El FB del nodo señalado por P2 = 0

Algoritmo para la rotación doble:


1. Si P1 no apunta a vacío
Si la información del nuevo nodo es menor que la información apuntada por P1:
Hijo Izquierdo de P1 = P4
Si no
Hijo Derecho de P1 = P4
Si no
P4 es la nueva raíz del árbol.
2. Si la información del nuevo nodo es menor que la información apuntada por P2:
Hijo Derecho de P3 = Hijo Izquierdo de P4
Hijo Izquierdo de P2 = Hijo Derecho de P4
Hijo Izquierdo de P4 = P3
Hijo Derecho de P4 = P2
Ir a Punto 3
Si no
Hijo Izquierdo de P3 = Hijo Derecho de P4
Hijo Derecho de P2 = Hijo Izquierdo de P4
Hijo Derecho de P4 = P3
Hijo Izquierdo de P4 = P2
Ir a Punto 4
3. Si la información del nuevo nodo es menor que la información de de P4:
Modificar FB desde el hijo derecho de P3 hasta el padre del nuevo nodo
Modificar FB del nodo señalado por P2 (ahora vale +1)
Si no si la información del nuevo nodo es mayor a la información de P4:
Modificar FB desde el hijo izquierdo de P2 hasta el padre del nuevo nodo
Modificar FB del nodo señalado por P3 (ahora vale -1)
Modificar FB del nodo señalado por P2 (ahora vale 0)

71
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

4. Si la Información del nuevo nodo es mayor que la información de P4:


Modificar FB desde el hijo izquierdo de P3 hasta el padre del nuevo nodo
Modificar FB desde el nodo señalado por P3 (ahora vale -1)
Si no si la información del nuevo nodo es menor que la información de P4:
Modificar FB desde el hijo derecho de P2 hasta el padre del nuevo nodo
Modificar FB del nodo señalado por P3 (ahora vale +1)
Modificar FB del nodo señalado por P2 (ahora vale 0)
A continuación se muestra el código para la implementación de un árbol AVL:
1. // Clase AvlTree
2. //
3. // CONSTRUCCIÓN: sin inicializador
4. //
5. // ******************OPERACIONES PÚBLICAS*********************
6. // void insert( x ) --> Inserta x
7. // void remove( x ) --> Remueve x (no implementado)
8. // boolean contains( x ) --> Retorna true si x es encontrado
9. // Comparable findMin( ) --> Retorna el elemento más pequeño
10. // Comparable findMax( ) --> Retorna el elemento más grande
11. // boolean isEmpty( ) --> Retorna true si está vacío; sino false
12. // void makeEmpty( ) --> Remueve todos los elementos
13. // void printTree( ) --> Imprime el árbol ordenadamente
14. // ******************ERRORES********************************
15. /**
16. Implementando un árbol AVL.
17. Note que todas las búsquedas están basadas en el método compareTo method.
18. @author Mark Allen Weiss
19. */
20. public class AvlTree<AnyType extends Comparable<? super AnyType>>
21. {
22. /**
23. Construyendo el árbol

72
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

24. */
25. public AvlTree( )
26. {
27. root = null;
28. }
29. /**
30. Inserta en el árbol; los duplicados son ignorados
31. @param x es el elemento a insertar
32. */
33. public void insert( AnyType x )
34. {
35. root = insert( x, root );
36. }
37. /**
38. Elimina en el árbol. No se hace nada si x no es encontrado
39. @param x es el elemento a borrar
40. */
41. public void remove( AnyType x )
42. {
43. System.out.println( "Lo siento, remove no implementado! " );
44. }
45. /**
46. Buscando el elemento más pequeño en el árbol.
47. @return el elemento más pequeño o nulo si está vacío.
48. */
49. public AnyType findMin( )
50. {
51. if( isEmpty( ) )
52. throw new UnderflowException( );
53. return findMin( root ).element;
54. }

73
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

55. /**
56. Buscando el elemento más grande en el árbol.
57. @retorna el elemento más grande o nulo si está vacío.
58. */
59. public AnyType findMax( )
60. {
61. if( isEmpty( ) )
62. throw new UnderflowException( );
63. return findMax( root ).element;
64. }
65. /**
66. Busca un elemento en el árbol
67. @param x es el elemento a buscar
68. @return true si x es encontrado
69. */
70. public boolean contains( AnyType x )
71. {
72. return contains( x, root );
73. }
74. /**
75. Logicamente vacia el árbol
76. */
77. public void makeEmpty( )
78. {
79. root = null;
80. }
81. /**
82. Comprueba si el árbol está vacío.
83. @return true si está vacío, false en otro caso.
84. */
85. public boolean isEmpty( )

74
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

86. {
87. return root == null;
88. }
89. /**
90. Imprime el contenido del árbol en forma ordenada
91. */
92. public void printTree( )
93. {
94. if( isEmpty( ) )
95. System.out.println( "Arbol Vacío" );
96. else
97. printTree( root );
98. }
99. /**
100. Método interno para insertar un elemento en el árbol
101. @param x es el elemento a insertar
102. @param t es el nodo raíz del árbol
103. @return la nueva raíz del árbol
104. */
105. private AvlNode<AnyType> insert( AnyType x, AvlNode<AnyType> t )
106. {
107. if( t == null )
108. return new AvlNode<AnyType>( x, null, null );
109. int compareResult = x.compareTo( t.element );
110. if( compareResult < 0 )
111. {
112. t.left = insert( x, t.left );
113. if( height( t.left ) - height( t.right ) == 2 )
114. if( x.compareTo( t.left.element ) < 0 )
115. t = rotateWithLeftChild( t );
116. else

75
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

117. t = doubleWithLeftChild( t );
118. }
119. else if( compareResult > 0 )
120. {
121. t.right = insert( x, t.right );
122. if( height( t.right ) - height( t.left ) == 2 )
123. if( x.compareTo( t.right.element ) > 0 )
124. t = rotateWithRightChild( t );
125. else
126. t = doubleWithRightChild( t );
127. }
128. else
129. ; // Duplicado; nada que hacer.
130. t.height = Math.max( height( t.left ), height( t.right ) ) + 1;
131. return t;
132. }
133. /**
134. Método interno para encontrar el elemento más pequeño en el árbol.
135. @param t es el nodo raíz en el árbol
136. @return el nodo con el elemento más pequeño del árbol
137. */
138. private AvlNode<AnyType> findMin( AvlNode<AnyType> t )
139. {
140. if( t == null )
141. return t;
142. while( t.left != null )
143. t = t.left;
144. return t;
145. }
146. /**
147. Método interno para encontrar el elemento más grande del árbol

76
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

148. @param t es el nodo raíz en el árbol


149. @return el nodo conteniendo el elemento más grande del árbol
150. */
151. private AvlNode<AnyType> findMax( AvlNode<AnyType> t )
152. {
153. if( t == null )
154. return t;
155. while( t.right != null )
156. t = t.right;
157. return t;
158. }
159. /**
160. Método interno para encontrar el elemento en un árbol
161. @param x es el elemento a buscar
162. @param t es el nodo raíz del árbol
163. @return true si x es encontrado en el árbol
164. */
165. private boolean contains( AnyType x, AvlNode<AnyType> t )
166. {
167. while( t != null )
168. {
169. int compareResult = x.compareTo( t.element );
170. if( compareResult < 0 )
171. t = t.left;
172. else if( compareResult > 0 )
173. t = t.right;
174. else
175. return true; // Encontrado
176. }
177. return false; // Encontrado
178. }

77
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

179. /**
180. Método interno para imprimir el árbol en forma ordenada
181. @param t es el nodo raíz del árbol
182. */
183. private void printTree( AvlNode<AnyType> t )
184. {
185. if( t != null )
186. {
187. printTree( t.left );
188. System.out.println( t.element );
189. printTree( t.right );
190. }
191. }
192. /**
193. Retorna el tamaño del nodo t, o -1 si es nulo
194. */
195. private int height( AvlNode<AnyType> t )
196. {
197. return t == null ? -1 : t.height;
198. }
199. /**
200.
201. Rota el nodo del ábol binario con el hijo izquierdo
202. Para las árboles AVL, esto es una rotación simple para el caso 1.
203. Actualiza y retorna la nueva raíz
204. */
205. private AvlNode<AnyType> rotateWithLeftChild( AvlNode<AnyType> k2 )
206. {
207. AvlNode<AnyType> k1 = k2.left;
208. k2.left = k1.right;
209. k1.right = k2;

78
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

210. k2.height = Math.max( height( k2.left ), height( k2.right ) ) + 1;


211. k1.height = Math.max( height( k1.left ), k2.height ) + 1;
212. return k1;
213. }
214. /**
215. Rota el nodo del árbol binario con el hijo derecho
216. Para las árboles AVL, esto es una rotación simple para el caso 4
217. Actualiza y retorna la nueva raíz
218. */
219. private AvlNode<AnyType> rotateWithRightChild( AvlNode<AnyType> k1 )
220. {
221. AvlNode<AnyType> k2 = k1.right;
222. k1.right = k2.left;
223. k2.left = k1;
224. k1.height = Math.max( height( k1.left ), height( k1.right ) ) + 1;
225. k2.height = Math.max( height( k2.right ), k1.height ) + 1;
226. return k2;
227. }
228. /**
229. El nodo del árbol binario es rotado en forma doble: primero por el hijo izquierdo
230. con este hijo derecho el nodo k3 es el nuevo hijo izquierdo
231. with its right child; then node k3 with new left child.
232. Para las árboles AVL, esto es una rotación simple para el caso 2
233. Actualiza y retorna la nueva raíz
234. */
235. private AvlNode<AnyType> doubleWithLeftChild( AvlNode<AnyType> k3 )
236. {
237. k3.left = rotateWithRightChild( k3.left );
238. return rotateWithLeftChild( k3 );
239. }
240. /**

79
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

241. node de un árbol binario con rotación doble: primero el hijo derecho
242. cuando este es el hijo izquierdo entonces el nodo k1 es el nuevo hijo derecho
243. Para las árboles AVL, esto es una rotación simple para el caso 3
244. Actualiza y retorna la nueva raíz
245. */
246. private AvlNode<AnyType> doubleWithRightChild( AvlNode<AnyType> k1 )
247. {
248. k1.right = rotateWithLeftChild( k1.right );
249. return rotateWithRightChild( k1 );
250. }
251. private static class AvlNode<AnyType>
252. {
253. // Constructores
254. AvlNode( AnyType theElement )
255. {
256. this( theElement, null, null );
257. }
258. AvlNode( AnyType theElement, AvlNode<AnyType> lt, AvlNode<AnyType> rt )
259. {
260. element = theElement;
261. left = lt;
262. right = rt;
263. height = 0;
264. }
265. AnyType element; // El dato en el nodo
266. AvlNode<AnyType> left; // hijo izquierdo
267. AvlNode<AnyType> right; // hijo derecho
268. int height; // tamaño
269. }
270. /** La raíz del árbol. */
271. private AvlNode<AnyType> root;

80
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

272. // Programa de prueba


273. public static void main( String [ ] args )
274. {
275. AvlTree<Integer> t = new AvlTree<Integer>( );
276. final int NUMS = 4000;
277. final int GAP = 37;
278. System.out.println( "Checando... (no más salidas…)" );
279. for( int i = GAP; i != 0; i = ( i + GAP ) % NUMS )
280. t.insert( i );
281. if( NUMS < 40 )
282. t.printTree( );
283. if( t.findMin( ) != 1 || t.findMax( ) != NUMS - 1 )
284. System.out.println( "Error FindMin o FindMax!" );
285. for( int i = 1; i < NUMS; i++ )
286. if( !t.contains( i ) )
287. System.out.println( "Encontrado error1!" );
288. }
289. }

Árboles Rojinegros
“Los árboles rojinegros son una buena alternativa a los árboles AVL. Los detalles de
la implementación producen un código más eficiente, ya que las rutinas de inserción y
eliminación emplean un solo recorrido descendente. No se permite que haya nodos
consecutivos de color rojo, y todos los caminos deben tener el mismo número de nodos
negros.” Mark Allen [pp. 492]

Un árbol rojinegro debe verificar siempre las siguientes propiedades de orden:


1. Que cada nodo esté coloreado con los colores rojo o negro.
2. La raíz debe ser negra.
3. Si un nodo es rojo, sus hijos deben ser negros.
4. Todos los caminos desde un nodo a una referencia Null deben contener el mismo
número de nodos negros.

81
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

A continuación, se muestra la inserción ascendente en un árbol rojinegro:

30
30

70
70
15
15

60
60 85
85
10
10 20
20

50
50 65
65 80
80 90
90
55

40
40 55
55

La secuencia de inserciones es:


10, 85, 15, 70, 20, 60, 30, 50, 65, 80, 90, 40, 5 y 55

Figura No. 24: Inserción Ascendente en un árbol rojinegro

Debe considerarse que los elementos que se inserten en el árbol deben ser de color
rojo. Si el padre también es rojo debemos colorear de nuevo y/o realizar rotaciones para
eliminar los nodos rojos consecutivos. Asimismo, si el hermano del padre es negro, la
situación se arregla con una rotación simple o doble, al igual que en los árboles AVL.
A continuación, se muestra la implementación de árboles rojinegros en Java:
1. // Clase RedBlackTree (árbol rojinegro)
2. //
3. // CONSTRUCCION: sin parámetros
4. //
5. // *****************OPERACIONES PUBLICAS*******************
6. // void insert( x ) --> Inserta x
7. // void remove( x ) --> Remueve x (sin implementar)
8. // boolean contains( x ) --> Retorna true si x es encontrado
9. // Comparable findMin( ) --> Retorna el elemento más pequeño
10. // Comparable findMax( ) --> Retorna el elemento más grande

82
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

11. // boolean isEmpty( ) --> Retorna true si el árbol está vacío, sin false
12. // void makeEmpty( ) --> Remueve todos los elementos del árbol
13. // void printTree( ) --> Imprime todos los elementos
14. // ******************ERRORES*******************************
15. /**
16. Implementa un árbol rojinegro
17. Note que todas las búsquedas están basadas en el método compareTo
18. @author Mark Allen Weiss
19. */
20. public class RedBlackTree<AnyType extends Comparable<? super AnyType>>
21. {
22. /**
23. Construyendo el árbol
24. */
25. public RedBlackTree( )
26. {
27. nullNode = new RedBlackNode<AnyType>( null );
28. nullNode.left = nullNode.right = nullNode;
29. header = new RedBlackNode<AnyType>( null );
30. header.left = header.right = nullNode;
31. }
32. /**
33. Compare los elementos y t.element1, usando el método compareTo
34. Esta rutina debe ser llamada, desde una cabecera
35. Si no es posible para t ser la cabecera, use el método compareTo directamente
36. */
37. private final int compare( AnyType item, RedBlackNode<AnyType> t )
38. {
39. if( t == header )
a. return 1;
40. else

83
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

a. return item.compareTo( t.element );


41. }
42. /**
43. Inserta en el árbol .
44. @param es el elemento para insertar en el árbol.
45. @throws DuplicateItemException si se encuentra el elemento.
46. */
47. public void insert( AnyType item )
48. {
49. current = parent = grand = header;
50. nullNode.element = item;
51. while( compare( item, current ) != 0 )
52. {
a. great = grand; grand = parent; parent = current;
b. current = compare( item, current ) < 0 ?
i. current.left : current.right;
c. // Verificar si dos hijos son rojos; fijar si ése es el caso
d. if( current.left.color == RED && current.right.color == RED )
e. handleReorient( item );
53. }

a. // La inserción falla si el elemento existe.


54. if( current != nullNode )
a. return;
55. current = new RedBlackNode<AnyType>( item, nullNode, nullNode );
a. // enlaza…
56. if( compare( item, parent ) < 0 )
a. parent.left = current;
57. else
a. parent.right = current;
58. handleReorient( item );

84
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

59. }
60. /**
61. Remoción desde el árbol
62. @param x the item to remove.
63. */
64. public void remove( AnyType x )
65. {
66. throw new UnsupportedOperationException( );
67. }
68. /**
69. Encontrar el elemento más pequeño del árbol
70. @return el elemento más pequeño o null si el árbol está vacío
71. */
72. public AnyType findMin( )
73. {
74. if( isEmpty( ) )
a. throw new UnderflowException( );
75. RedBlackNode<AnyType> itr = header.right;
76. while( itr.left != nullNode )
a. itr = itr.left;
77. return itr.element;
78. }
79. /**
80. Encontrar el elemento más grande en el árbol
81. @return el elemento más grande o null si el árbol está vacío
82. */
83. public AnyType findMax( )
84. {
85. if( isEmpty( ) )
a. throw new UnderflowException( );
86. RedBlackNode<AnyType> itr = header.right;

85
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

87. while( itr.right != nullNode )


a. itr = itr.right;
88. return itr.element;
89. }
90. /**
91. Encontrar un elemento en el árbol
92. @param x es el elemento a buscar
93. @return true si x es encontrado sino false
94. */
95. public boolean contains( AnyType x )
96. {
97. nullNode.element = x;
98. current = header.right;
99. for( ; ; )
100. {
a. if( x.compareTo( current.element ) < 0 )
b. current = current.left;
c. else if( x.compareTo( current.element ) > 0 )
d. current = current.right;
e. else if( current != nullNode )
f. return true;
g. else
h. return false;
101. }
102. }
103. /**
104. Pone el árbol vacío
105. */
106. public void makeEmpty( )
107. {
108. header.right = nullNode;

86
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

109. }
110. /**
111. Imprime los elementos del árbol en forma ordenada
112. */
113. public void printTree( )
114. {
115. printTree( header.right );
116. }
117. /**
118. Método interno para imprimir un sub árbol en orden
119. @param t es el nodo raíz del árbol.
120. */
121. private void printTree( RedBlackNode<AnyType> t )
122. {
123. if( t != nullNode )
124. {
a. printTree( t.left );
b. System.out.println( t.element );
c. printTree( t.right );
125. }
126. }
127. /**
128. Examina si el árbol está lógicamente vacío
129. @return true si está vacío sino false
130. */
131. public boolean isEmpty( )
132. {
133. return header.right == nullNode;
134. }
135. /**
136. Rutina interna que es llamada durante una inserción

87
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

137. Si un nodo tiene dos hijos rojos, Realiza las rotaciones.


138. Si un nodo tiene dos hijos rojos. @param es el elemento que se va a insertar
139. */
140. private void handleReorient( AnyType item )
141. {
a. //Haga de color rojo el nodo actual, el izquierdo y derecho en rojo
142. current.color = RED;
143. current.left.color = BLACK;
144. current.right.color = BLACK;
145. if( parent.color == RED ) // Tiene que rotar
146. {
a. grand.color = RED;
b. if( ( compare( item, grand ) < 0 ) !=
c. ( compare( item, parent ) < 0 ) )
d. parent = rotate( item, grand ); // inicia doble rotación
e. current = rotate( item, great );
f. current.color = BLACK;
147. }
148. header.right.color = BLACK; // Pone la raíz en negro
149. }
150. /**
151. Rutina interna que optimiza una rotación simple o doble
152. Por que los resultados es enlazado al padre, esto para el caso 4
153. llamado desde handleReorient.
154. @param es el elemento en handleReorient.
155. @param es el padre en la raíz del sub árbol rotado
156. @retorna la raíz del sub árbol rotado
157. */
158. private RedBlackNode<AnyType> rotate( AnyType item,
RedBlackNode<AnyType> parent )
159. {

88
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

160. if( compare( item, parent ) < 0 )


a. return parent.left = compare( item, parent.left ) < 0 ?
b. rotateWithLeftChild( parent.left ) : // LL
c. rotateWithRightChild( parent.left ) ; // LR
161. else
a. return parent.right = compare( item, parent.right ) < 0 ?
b. rotateWithLeftChild( parent.right ) : // RL
c. rotateWithRightChild( parent.right ); // RR
162. }
163. /**
164. Rota el node del árbol binario con el hijo izquierdo
165. */
166. private static <AnyType> RedBlackNode<AnyType> rotateWithLeftChild(
RedBlackNode<AnyType> k2 )
167. {
168. RedBlackNode<AnyType> k1 = k2.left;
169. k2.left = k1.right;
170. k1.right = k2;
171. return k1;
172. }
173. /**
174. Rota el nodo del árbol binario con el hijo derecho
175. */
176. private static <AnyType> RedBlackNode<AnyType> rotateWithRightChild(
RedBlackNode<AnyType> k1 )
177. {
178. RedBlackNode<AnyType> k2 = k1.right;
179. k1.right = k2.left;
180. k2.left = k1;
181. return k2;
182. }
183. private static class RedBlackNode<AnyType>

89
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

184. {
a. // Constructores
185. RedBlackNode( AnyType theElement )
186. {
a. this( theElement, null, null );
187. }

188. RedBlackNode( AnyType theElement, RedBlackNode<AnyType> lt,


RedBlackNode<AnyType> rt )
189. {
a. element = theElement;
b. left = lt;
c. right = rt;
d. color = RedBlackTree.BLACK;
190. }
191. AnyType element; // The data in the node
192. RedBlackNode<AnyType> left; // Left child
193. RedBlackNode<AnyType> right; // Right child
194. int color; // Color
195. }
196. private RedBlackNode<AnyType> header;
197. private RedBlackNode<AnyType> nullNode;
198. private static final int BLACK = 1; // BLACK must be 1
199. private static final int RED = 0;
200. // Usada en la rutina de inserción
201. private RedBlackNode<AnyType> current;
202. private RedBlackNode<AnyType> parent;
203. private RedBlackNode<AnyType> grand;
204. private RedBlackNode<AnyType> great;
205. // Programa de prueba
206. public static void main( String [ ] args )

90
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

207. {
208. RedBlackTree<Integer> t = new RedBlackTree<Integer>( );
209. final int NUMS = 400000;
210. final int GAP = 35461;
211. System.out.println( "Comprobando (no más salidas!)" );
212. for( int i = GAP; i != 0; i = ( i + GAP ) % NUMS )
a. t.insert( i );
213. if( t.findMin( ) != 1 || t.findMax( ) != NUMS - 1 )
a. System.out.println( "Error en FindMin o FindMax!" );
214. for( int i = 1; i < NUMS; i++ )
a. if( !t.contains( i ) )
b. System.out.println( "Error1 Encontrado !" );
215. }
216. }

AA-Árboles
En los árboles rojinegros es bastante complicada la operación eliminar dada la gran
cantidad de rotaciones necesarias. Los árboles AA son el método elegido cuando se
necesitan árboles equilibrados. En ellos se permite una implementación más inmediata
y se requieren eliminaciones.
A continuación, se presenta el algoritmo para implementar un árbol AA en Java:
1. // AATree class
2. //
3. // CONSTRUCCION: sin inicializador
4. //
5. // ******************OPERACIONES PUBLICAS*********************
6. // void insert( x ) --> Inserta x
7. // void remove( x ) --> Remueve x
8. // boolean contains( x ) --> Retorna true si x es encontrado
9. // Comparable findMin( ) --> Retorna el elemento más pequeño
10. // Comparable findMax( ) --> Retorna el elemento más grande
11. // boolean isEmpty( ) --> Retorna true si el árbol está vacío, sino false

91
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

12. // void makeEmpty( ) --> Remueve todos los elementos


13. // ******************ERRORES********************************
14. /**
15. Implementación de un Arbol AA
16. Observe que todas las búsquedas están basadas en el método compareTo
method.
17. @author Mark Allen Weiss
18. */
19. public class AATree<AnyType extends Comparable<? super AnyType>>
20. {
21. /**
22. Construyendo el árbol
23. */
24. public AATree( )
25. {
26. nullNode = new AANode<AnyType>( null, null, null );
27. nullNode.left = nullNode.right = nullNode;
28. nullNode.level = 0;
29. root = nullNode;
30. }
31. /**
32. Insertar en el árbol
33. @param x es el elemento a insertar
34. */
35. public void insert( AnyType x )
36. {
37. root = insert( x, root );
38. }
39. /**
40. Remover desde el árbol
41. @param x es el elemento a remover

92
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

42. */
43. public void remove( AnyType x )
44. {
45. deletedNode = nullNode;
46. root = remove( x, root );
47. }
48. /**
49. Encontrar el elemento más pequeño en el árbol
50. returna el elementno más pequeño o throw UnderflowException si está vacío
51. */
52. public AnyType findMin( )
53. {
54. if( isEmpty( ) )
55. throw new UnderflowException( );
56. AANode<AnyType> ptr = root;
57. while( ptr.left != nullNode )
58. ptr = ptr.left;
59. return ptr.element;
60. }
61. /**
62. Encontrar el elemento más grande en el árbol
63. returna el elemento más grande o throw UnderflowException si está vacío
64. */
65. public AnyType findMax( )
66. {
67. if( isEmpty( ) )
68. throw new UnderflowException( );
69. AANode<AnyType> ptr = root;
70. while( ptr.right != nullNode )
71. ptr = ptr.right;
72. return ptr.element;

93
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

73. }
74. /**
75. Encontrar un elemento en el árbol
76. x es el elemento a buscar
77. retorna true si x es encontrado
78. */
79. public boolean contains( AnyType x )
80. {
81. AANode<AnyType> current = root;
82. nullNode.element = x;
83. for( ; ; )
84. {
a. int compareResult = x.compareTo( current.element );
85. if( compareResult < 0 )
a. current = current.left;
86. else if( compareResult > 0 )
a. current = current.right;
87. else if( current != nullNode )
a. return true;
88. else
a. return false;
89. }
90. }
91. /**
92. Pone el árbol lógicamente vacío
93. */
94. public void makeEmpty( )
95. {
96. root = nullNode;
97. }
98. /**

94
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

99. Comprube si el árbol está lógicamente vacío


100. retorna true si está vacío o de lo contrario false
101. */
102. public boolean isEmpty( )
103. {
104. return root == nullNode;
105. }
106. /**
107. métoro interno para inserter en un sub árbol
108. x es el elemento a insertar
109. t es el nodo raíz del sub árbol
110. retorna la nueva raíz del sub árbol
111. */
112. private AANode<AnyType> insert( AnyType x, AANode<AnyType> t )
113. {
114. if( t == nullNode )
115. return new AANode<AnyType>( x, nullNode, nullNode );
a. int compareResult = x.compareTo( current.element );
116. if( compareResult < 0 )
117. t.left = insert( x, t.left );
118. else if( compareResult > 0 )
119. t.right = insert( x, t.right );
120. else
121. return t;
122. t = skew( t );
123. t = split( t );
124. return t;
125. }
126. /**
127. método interno para remover un elemento del sub árbol
128. x es el elemento a remover

95
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

129. t es el nodo raíz del subárbol


130. retorna la nueva raíz del sub árbol
131. */
132. private AANode<AnyType> remove( AnyType x, AANode<AnyType> t )
133. {
134. if( t != nullNode )
135. {
136. // Paso 1: buscar hacia abajo del árbol y establecer el último nodo y los nodos
eliminados
137. lastNode = t;
138. if( x.compareTo( t.element ) < 0 )
a. t.left = remove( x, t.left );
139. else
140. {
a. deletedNode = t;
b. t.right = remove( x, t.right );
141. }
142. // Paso 2: Si es el final del árbol y se encuentra x, removerlo
143. if( t == lastNode )
144. {
a. if( deletedNode == nullNode || x.compareTo( deletedNode.element ) != 0 )
i. return t; //elemento no encontrado; nada que hacer
b. deletedNode.element = t.element;
c. t = t.right;
145. }
146. // Paso 3: En otro caso, si no se está al final, rebalancear
147. else if( t.left.level < t.level - 1 || t.right.level < t.level - 1 )
148. {
a. if( t.right.level > --t.level )
i. t.right.level = t.level;
b. t = skew( t );

96
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

c. t.right = skew( t.right );


d. t.right.right = skew( t.right.right );
e. t = split( t );
f. t.right = split( t.right );
149. }
150. }
151. return t;
152. }
153. /**
154. t es el nodo raíz del árbol
155. retorna la nueva raíz luego de la rotación
156. */
157. private AANode<AnyType> skew( AANode<AnyType> t )
158. {
159. if( t.left.level == t.level )
160. t = rotateWithLeftChild( t );
161. return t;
162. }
163. /**
164.
165. Lanza las primitivas para el árbol AA
166. @param es el nodo raíz del árbol
167. @return es la nueva raíz del árbol luego de la rotación
168. */
169. private AANode<AnyType> split( AANode<AnyType> t )
170. {
171. if( t.right.right.level == t.level )
172. {
173. t = rotateWithRightChild( t );
174. t.level++;
175. }

97
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

176. return t;
177. }
178. /**
179. Rotar el nodo del árbol binario con el hijo izquierdo
180. */
181. private AANode<AnyType> rotateWithLeftChild( AANode<AnyType> k2 )
182. {
183. AANode<AnyType> k1 = k2.left;
184. k2.left = k1.right;
185. k1.right = k2;
186. return k1;
187. }
188. /**
189. Rotar el nodo del árbol binario con el hijo derecho.
190. */
191. private AANode<AnyType> rotateWithRightChild( AANode<AnyType> k1 )
192. {
193. AANode<AnyType> k2 = k1.right;
194. k1.right = k2.left;
195. k2.left = k1;
196. return k2;
197. }
198. private static class AANode<AnyType>
199. {
200. // Constructor
201. AANode( AnyType theElement, AANode<AnyType> lt, AANode<AnyType> rt )
202. {
203. element = theElement;
204. left = lt;
a. right = rt;
205. level = 1;

98
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

206. }
207. AnyType element; // El dato en el nodo
208. AANode<AnyType> left; // hijo izquierdo
209. AANode<AnyType> right; // hijo derecho
210. int level; //nivel
211. }
212. private AANode<AnyType> root;
213. private AANode<AnyType> nullNode;
214. private AANode<AnyType> deletedNode;
215. private AANode<AnyType> lastNode;
216. //Programa de prueba:
217. public static void main( String [ ] args )
218. {
219. AATree<Integer> t = new AATree<Integer>( );
220. final int NUMS = 40000;
221. final int GAP = 307;
222. System.out.println( "Comprobando…)" );
223. t.insert( NUMS * 2 );
224. t.insert( NUMS * 3 );
225. for( int i = GAP; i != 0; i = ( i + GAP ) % NUMS )
226. t.insert( i );
227. System.out.println( "Inserción completada" );
228. t.remove( t.findMax( ) );
229. for( int i = 1; i < NUMS; i+= 2 )
230. t.remove( i );
231. t.remove( t.findMax( ) );
232. System.out.println( "Remoción completada" );
233. if( t.findMin( ) != 2 || t.findMax( ) != NUMS - 2 )
234. System.out.println( "Error en FindMin o FindMax!" );
235. for( int i = 2; i < NUMS; i+=2 )
236. if( !t.contains( i ) )

99
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

a. System.out.println( "Error: Falla en " + i );


237. for( int i = 1; i < NUMS; i+=2 )
238. if( t.contains( i ) )
a. System.out.println( "Error: Elementos borrados encontrados… " + i );
239. }
240. }

B-Árboles
Un árbol B es un tipo especial de árbol con determinadas propiedades que lo hacen útil
para guardar y acceder eficientemente grandes cantidades de información en memoria
secundaria. De igual forma que en un árbol ABB, la búsqueda de un elemento requiere
del recorrido de un camino, desde la raíz del árbol hacia alguna de las hojas. Estos
árboles están completamente balanceados, por lo que se garantiza eficiencia en los
algoritmos de búsqueda.
Una definición general del árbol B es la siguiente:
1. Todas las hojas en el árbol están bajo el mismo nivel.
2. Cada nodo contiene entre n y 2n elementos (excepto la raíz que tiene entre 1 y
2n).
3. Si un nodo tiene m elementos, el nodo contendrá 0 ó m + 1 hijos.
4. Los elementos de un nodo del árbol están ordenados linealmente en el nodo.
5. Los elementos del árbol B están organizados siguiendo las propiedades de un
ABB; es decir, los elementos menores a la izquierda y las mayores a la derecha
del nodo original.
La siguiente figura muestra un árbol b de orden 2. La cantidad máxima de elementos
que puede tener un nodo es 4, el número mínimo es 2 (excepto la raíz) y la cantidad de
hijos que tendría un nodo depende de la cantidad de elementos del nodo, pero podrá
variar entre 0, 2, 3, 4 ó 5 hijos.

32,
32, 38,
38, 43
43

10,
10, 20,
20, 25
25 32,
32, 34
34 40,
40, 42
42 44,
44, 50,
50, 56
56

Árbol B de orden 2
Cantidad máxima de elementos = 4, mínimo = 2

Figura No. 25: representación de un árbol B

100
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Tablas Hash
Un conjunto es una colección de objetos. Computacionalmente, un conjunto se define
como una agrupación finita de objetos, con características comunes, que no presentan
relación alguna entre sí. Una tabla hashing sirve para mapear todos los posibles
valores de llaves de un conjunto.
Para determinar la llave de un determinado valor en un conjunto podría utilizarse:
f (llave) = posición_tabla(direccion base)
Las funciones utilizadas para hacer la conversión reciben el nombre de funciones
hashing.
Cuando se realizan las conversiones de llaves, lógicamente se pueden presentar
colisiones. Estas colisiones se puden resolver mediante las metodologٌías de
direccionamiento abierto o encadenamiento. Entre las funciones de direccionamiento
abierto se pueden realizar pruebas como Prueba Lineal, Prueba Cuadrática, Prueba
Aleatoria, Prueba de doble hashing.
A continuación se muestra la implementación hashing en Java:
// Clase Hashing de Prueba Cuadrática
//
// CONSTRUCCIÓN: un tamaño o un defecto inicial aproximado de 101
//
// ******************OPERACIONES PÚBLICAS*********************
// bool insert( x ) --> Inserta x
// bool remove( x ) --> Remueve x
// bool contains( x ) --> Retorna true si x es encontrada
// void makeEmpty( ) --> Remueve todos los elementos
/**
* Implementación de pruebas de hashing
* Observe que todas las búsqueds están basados en un método similar
* @author Mark Allen Weiss
*/
public class QuadraticProbingHashTable<AnyType>
{
/**
* Construyendo la tabla hash
*/

101
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

public QuadraticProbingHashTable( )
{
this( DEFAULT_TABLE_SIZE );
}

/**
* Construyendo la tabla hash
*/
public QuadraticProbingHashTable( int size )
{
allocateArray( size );
makeEmpty( );
}

/**
* Incluyendo en la tabla hash. Si el elemento existe
* no hace nada
* x es el elemento a insertar
*/
public void insert( AnyType x )
{
// Insertar x como activo
int currentPos = findPos( x );
if( isActive( currentPos ) )
return;
array[ currentPos ] = new HashEntry<AnyType>( x, true );
if( ++currentSize > array.length / 2 )
rehash( );
}

/**

102
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

* Expandiendo la tabla hash


*/
private void rehash( )
{
HashEntry<AnyType> [ ] oldArray = array;
//Crear una tabla vacia
allocateArray( nextPrime( 2 * oldArray.length ) );
currentSize = 0;
// Copiando los elementos
for( int i = 0; i < oldArray.length; i++ )
if( oldArray[ i ] != null && oldArray[ i ].isActive )
insert( oldArray[ i ].element );
}
/**
* Metodo que prueba la solución cuadrática
* x es el elemento a buscar
* retorna la posición donde la búsqueda termina.
*/
private int findPos( AnyType x )
{
int offset = 1;
int currentPos = myhash( x );
while( array[ currentPos ] != null &&
!array[ currentPos ].element.equals( x ) )
{
currentPos += offset; // Compute ith probe
offset += 2;
if( currentPos >= array.length )
currentPos -= array.length;
}

103
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

return currentPos;
}

/**
* Remoción de la tabla hash
* x es el elemento a remover
*/
public void remove( AnyType x )
{
int currentPos = findPos( x );
if( isActive( currentPos ) )
array[ currentPos ].isActive = false;
}
/**
* Buscar el elemento en la tabla hash
* x es el elemento a buscar
* retorna el elemento buscado
*/
public boolean contains( AnyType x )
{
int currentPos = findPos( x );
return isActive( currentPos );
}

/**
* Retorna true sif currentPos existe y si está activa
* currentPos es el resultado de la llamada a findPos.
* retorna true si currentPos esta activa.
*/
private boolean isActive( int currentPos )
{

104
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

return array[ currentPos ] != null && array[ currentPos ].isActive;


}
/**
* Pone vacia la tabla hash
*/
public void makeEmpty( )
{
currentSize = 0;
for( int i = 0; i < array.length; i++ )
array[ i ] = null;
}

private int myhash( AnyType x )


{
int hashVal = x.hashCode( );
hashVal %= array.length;
if( hashVal < 0 )
hashVal += array.length;
return hashVal;
}

private static class HashEntry<AnyType>


{
public AnyType element; // the element
public boolean isActive; // false if marked deleted
public HashEntry( AnyType e )
{
this( e, true );
}

public HashEntry( AnyType e, boolean i )

105
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

{
element = e;
isActive = i;
}
}

private static final int DEFAULT_TABLE_SIZE = 11;


private HashEntry<AnyType> [ ] array; // el arreglo de elementos
private int currentSize; // El numero de celdas ocupadas

/**
* Método interno para asignar el arreglo
* arraySize es el tamaño del arreglo
*/
private void allocateArray( int arraySize )
{
array = new HashEntry[ arraySize ];
}

/**
* Método interno para encontrar un número primo tan grande como n
* n es el número que inicia (debe ser positivo).
* retorna un número primo tran grande como n.
*/
private static int nextPrime( int n )
{
if( n % 2 == 0 )
n++;
for( ; !isPrime( n ); n += 2 )
;
return n;

106
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

/**
* método interno para probar si un número es primo
* No es un algoritmo eficiente
* n es el número para probar
* retorna el resultado del test
*/
private static boolean isPrime( int n )
{
if( n == 2 || n == 3 )
return true;

if( n == 1 || n % 2 == 0 )
return false;
for( int i = 3; i * i <= n; i += 2 )
if( n % i == 0 )
return false;
return true;
}

// Menu simple
public static void main( String [ ] args )
{
QuadraticProbingHashTable<String> H = new
QuadraticProbingHashTable<String>( );
final int NUMS = 400000;
final int GAP = 37;
System.out.println( "Probando... (no más salidas…)" );
for( int i = GAP; i != 0; i = ( i + GAP ) % NUMS )
H.insert( ""+i );

107
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

for( int i = 1; i < NUMS; i+= 2 )


H.remove( ""+i );
for( int i = 2; i < NUMS; i+=2 )
if( !H.contains( ""+i ) )
System.out.println( "Falla encontrada " + i );
for( int i = 1; i < NUMS; i+=2 )
{
if( H.contains( ""+i ) )
System.out.println( "OOPS!!! " + i );
}
}
}

108
Universidad Estatal a Distancia (UNED) – Estructura de Datos
Material Complementario

Bibliografía

Referencia WEB:

Pozo, Salvador, 2001. Estructuras dinámicas de datos. 25 de octubre de 2007,


http://www.conclase.net/c/edd/index.php

109