Está en la página 1de 143

UNIVERSIDAD MARIANO GÁLVEZ

FACULTAD DE INGENIERIA EN SISTEMAS


DE LA INFORMACION Y CIENCIAS DE LA COMPUTACION.
CURSO: PROGRAMACION III
CATEDRÁTICO: EDGAR RAUL MOLINA REY

MANUAL DE ESTRUCTURA DE DATOS

SAÚL ERNESTO COY POP 4090-18-21786


INDICE

INTRODUCCIÓN ....................................................................................................................1
OBJETIVOS .............................................................................................................................2
TABLA DE ILUASTRACIONES ............................................................................................3
1. Listas ..................................................................................................................................4
1.1. Listas enlazadas. ..........................................................................................................4
Operaciones:..............................................................................................................4
Declaraciones de tipos para manejar listas .................................................................9
Recursos de Aprendizaje ......................................................................................... 18
1.2. Listas enlazadas Dobles.............................................................................................. 19
Listas dobles lineales. .............................................................................................. 19
Listas dobles circulares. ........................................................................................... 19
Operaciones básicas con listas doblemente enlazadas .............................................. 20
Recursos de Aprendizaje ......................................................................................... 31
1.3. Circulares. .................................................................................................................. 32
Añadir un elemento ................................................................................................. 33
Buscar un elemento en una lista circular .................................................................. 33
Borrar un elemento de una lista circular ................................................................... 34
Recursos de Aprendizaje ......................................................................................... 41
2. Pilas .................................................................................................................................. 42
2.1. Clases para la implementación de pilas...................................................................... 43
Recursos de Aprendizaje ......................................................................................... 52
3. Colas ................................................................................................................................. 53
3.1. Tipos. .......................................................................................................................... 53
3.2. Colas simples. ............................................................................................................. 54
3.3. Colas circulares. ......................................................................................................... 54
3.4. Colas dobles. .............................................................................................................. 55
Recursos de Aprendizaje ......................................................................................... 61
4. Árboles ............................................................................................................................. 62
4.1. Árboles Generales ...................................................................................................... 64
4.2. Árboles Binarios......................................................................................................... 65
Árbol binario de búsqueda (ABB)............................................................................ 71
4.3. Árboles AVL ............................................................................................................... 72
Altura logarítmica.................................................................................................... 73
Operaciones en Árbol AVL ..................................................................................... 73
Cambios en altura .................................................................................................... 74
Rotaciones ............................................................................................................... 74
Rotaciones en AVL ..................................................................................................... 75
4.4. Arboles B+ ................................................................................................................. 76
Recursos de Aprendizaje ....................................................................................... 103
5. Tablas Hash ................................................................................................................... 104
5.1. Funcionamiento ....................................................................................................... 104
Inserción................................................................................................................ 104
Búsqueda ............................................................................................................... 104
Prácticas recomendadas para las funciones hash .................................................... 105
Funciones Hash más usadas: .................................................................................. 106
5.2. Resolución de colisiones........................................................................................... 106
Direccionamiento Cerrado, Encadenamiento separado o Hashing abierto .............. 107
Direccionamiento abierto o Hashing cerrado.......................................................... 107
5.3. Ventajas e inconvenientes de las tablas hash ........................................................... 108
5.4. Funciones de Hash .................................................................................................. 110
Restas Sucesivas .................................................................................................... 110
Aritmética Modular ............................................................................................... 110
Mitad del Cuadrado ............................................................................................... 111
Truncamiento ........................................................................................................ 111
Plegamiento........................................................................................................... 112
Tratamiento de Colisiones ......................................................................................... 112
Prueba Lineal ........................................................................................................ 112
5.5. Hash Dinámico ........................................................................................................ 114
Métodos Totales .................................................................................................... 114
Métodos Parciales.................................................................................................. 116
Recursos de Aprendizaje ....................................................................................... 120
6. Grafos ............................................................................................................................ 121
Grafo no dirigido: .................................................................................................. 121
Grafo Dirigido ....................................................................................................... 122
6.1. Tipos de grafos. ........................................................................................................ 124
Grafo bipartito ....................................................................................................... 125
Grafo completo...................................................................................................... 125
Un grafo bipartito regular ...................................................................................... 125
Grafo nulo ............................................................................................................. 125
Grafos Isomorfos ................................................................................................... 125
Grafos Platónicos ...................................................................................................... 126
Grafos Eulerianos. ................................................................................................. 126
Grafos Conexos. .................................................................................................... 127
6.2. Representación de los Grafos ................................................................................... 128
Matriz de adyacencia................................................................................................. 128
Listas de adyacencia.................................................................................................. 129
6.3. Algoritmo de Dijkstra ............................................................................................... 130
Recursos de Aprendizaje ....................................................................................... 138
BIBLIOGRAFIA ................................................................................................................... 139
1

INTRODUCCIÓN

La programación es una herramienta fundamental en el mundo en el que vivimos,


convirtiéndose en una salida laboral importante. Además, su aprendizaje constituye una
oportunidad al mejorar el razonamiento lógico formal. Siendo un proceso en el cual una persona
es capaz de desarrollar un programa utilizando un determinado lenguaje de programación que le
permita escribir el código.
El saber programar es tener el privilegio de poder crear aplicaciones que puedan ayudar a otras
personas a resolver problemas o automatizar muchas de las tareas cotidianas.
Cuando se piensa en un informático, lo primero que se nos viene a la mente son computadoras
y programas, pero un informático no solo se basa en eso, se puede especializar en
otras ramas como bases de datos, conexión de redes, sistemas operativos, desarrollo de páginas
web, seguridad, auditoría informática, etc.
Para empezar a entender lo que es la programación, primero tenemos que tener ciertos
conocimientos sobre todo lo que la rodea, en el siguiente manual encontraremos diversos
conceptos que permiten la realización de programaciones, esto supone que podamos expresar
formalmente, mediante un conjunto de reglas, las relaciones y operaciones posibles. Por ello
parece razonable desarrollar la idea de la agrupación de datos, que tengan un cierto tipo de
estructura y organización interna. La selección de una estructura de datos frente a otra, a la hora
de programar es una decisión importante, ya que ello influye decisivamente en el algoritmo que
vaya a usarse para resolver un determinado problema. El objetivo no es sólo la descripción de las
distintas estructuras, sino también es muy importante saber programar ya que gracias a eso se tiene
la oportunidad de poder crear algo útil para los demás y ponerlo a su disposición en cualquier
momento.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


2

OBJETIVOS
Objetivo general

Proporcionar información de los conceptos de estructura de datos y su realización, con el fin


de impulsar las iniciativas y potenciar las habilidades para aprender a programar.

Objetivos específicos

Introducir los conceptos importantes sobre la programación.


Desarrollar la capacidad para analizar, diseñar e implementar soluciones computacionales de
baja y media complejidad.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


3

TABLA DE ILUASTRACIONES
ESTRUCTURA DE DATOS - 1-1 LISTA SIMPLE 4
ESTRUCTURA DE DATOS - 1-2 LISTA DOBLEMENTE ENLAZADA 19
ESTRUCTURA DE DATOS - 1-3 LISTA DOBLEMENTE ENLAZADA CIRCULAR 19
ESTRUCTURA DE DATOS - 1-4 LISTA CIRCULAR 32
ESTRUCTURA DE DATOS - 2-1 REPRESENTACIÓN DE UNA PILA 42
ESTRUCTURA DE DATOS - 3-1 COLA CIRCULAR 54
ESTRUCTURA DE DATOS - 3-2 COLA DOBLE 55
ESTRUCTURA DE DATOS - 4-1 ÁRBOL 62
ESTRUCTURA DE DATOS - 4-2 ÁRBOL BINARIO 65
ESTRUCTURA DE DATOS - 4-3 PREORDEN 68
ESTRUCTURA DE DATOS - 4-4 INORDEN 69
ESTRUCTURA DE DATOS - 4-5 POSTORDEN 70
ESTRUCTURA DE DATOS - 4-6 ABB 71
ESTRUCTURA DE DATOS - 4-7 ÁRBOL AVL 72
ESTRUCTURA DE DATOS - 6-1 GRAFO NO DIRIGIDO 121
ESTRUCTURA DE DATOS - 6-2 GRAFO DIRIGIDO 122
ESTRUCTURA DE DATOS - 6-3 GRAFO NULO 125
ESTRUCTURA DE DATOS - 6-4 GRAFOS ISOMORFOS 126
ESTRUCTURA DE DATOS - 6-5 GRAFOS PLATÓNICOS 126
ESTRUCTURA DE DATOS - 6-6 GRAFOS CONEXOS 127
ESTRUCTURA DE DATOS - 6-7 MATRIZ DE ADYACENCIA 128
ESTRUCTURA DE DATOS - 6-8 LISTA DE ADYACENCIA 129

Manual de Estructura de Datos Saúl Ernesto Coy Pop


4

1. Listas

1.1. Listas enlazadas.

Una lista enlazada o encadenada es una colección de elementos ó nodos, en donde cada uno
contiene datos y un enlace o liga. Un nodo es una secuencia de caracteres en memoria dividida en
campos (de cualquier tipo). Un nodo siempre contiene la dirección de memoria del siguiente nodo
de información si este existe.

Un apuntador es la dirección de memoria de un nodo La figura siguiente muestra la estructura


de un nodo:

Dato Enlace

El campo enlace, que es de tipo puntero, es el que se usa para establecer la liga con el
siguiente nodo de la lista. Si el nodo fuera el último, este campo recibe como valor NULL (vacío).
A continuación, se muestra el esquema de una lista:

Estructura de Datos - 1-1 Lista simple

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

I. Recorrido.
Esta operación consiste en visitar cada uno de los nodos que forman la lista. Para recorrer
todos los nodos de la lista, se comienza con el primero, se toma el valor del campo enlace para

Manual de Estructura de Datos Saúl Ernesto Coy Pop


5

avanzar al segundo nodo, el campo enlace de este nodo nos dará la dirección del tercer nodo, y así
sucesivamente.

II. Inserción.
Esta operación consiste en agregar un nuevo nodo a la lista. Para esta operación se pueden
considerar 4 casos:

 Insertar un nodo en una lista vacía


Este es uno de los casos más sencillo. Partiremos de que ya tenemos el nodo a insertar y, por
supuesto un puntero que apunte a él, además el puntero a la lista valdrá NULO:

El proceso es muy simple, bastará con que:

1. nodo.siguiente apunte a NULO.


2. Lista apunte a nodo.

 Insertar un nodo al inicio.

De nuevo partiremos de un nodo a insertar, con un puntero que apunte a él, y de una lista, en este
caso no vacía:

Manual de Estructura de Datos Saúl Ernesto Coy Pop


6

El proceso sigue siendo muy sencillo:

1. Hacemos que nodo.siguiente apunte a Lista.


2. Hacemos que Lista apunte a nodo.
 Insertar un nodo antes o después de cierto nodo.
Para este caso partiremos de una lista no vacía:

El proceso en este caso tampoco es excesivamente complicado:

1. Necesitamos un puntero que señale al último elemento de la lista. La manera de conseguirlo


es empezar por el primero y avanzar hasta que el nodo que tenga como siguiente el valor NULO.

2. Hacer que nodo.siguiente sea NULO.

3. Hacer que ultimo.siguiente sea nodo.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


7

 Insertar un nodo al final.

Suponemos que ya disponemos del nuevo nodo a insertar, apuntado por nodo, y un puntero al nodo
a continuación del que lo insertaremos.

El proceso a seguir será:

1. Hacer que nodo.siguiente señale a anterior.siguiente.

2. Hacer que anterior.siguiente señale a nodo.


3.

Es muy importante que el programa nunca pierda el valor del puntero al primer elemento, ya
que si no existe ninguna copia de ese valor, y se pierde, será imposible acceder al nodo y no
podremos liberar el espacio de memoria que ocupa.
III. Borrado.
La operación de borrado consiste en quitar un nodo de la lista, redefiniendo los enlaces que
correspondan. Se pueden presentar cuatro casos:

 Eliminar el primer nodo.

Es el caso más simple. Partiremos de una lista con uno o más nodos, y usaremos un puntero
auxiliar, nodo:

1. Hacemos que nodo apunte al primer elemento de la lista, es decir a Lista.

2. Asignamos a Lista la dirección del segundo nodo de la lista: Lista.siguiente.

3. Liberamos la memoria asignada al primer nodo, el que queremos eliminar.

Si no guardamos el puntero al primer nodo antes de actualizar Lista, después nos resultaría
imposible liberar la memoria que ocupa. Si liberamos la memoria antes de actualizar Lista,
perderemos el puntero al segundo nodo.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


8

Si la lista sólo tiene un nodo, el proceso es también válido, ya que el valor de Lista.siguiente
es NULO, y después de eliminar el primer nodo la lista quedará vacía, y el valor de Lista será
NULO.

Una operación que se suele usar para borrar listas completas es eliminar el primer nodo hasta
que la lista esté vacía.

 Eliminar un nodo con cierta información.

En todos los demás casos, eliminar un nodo se puede hacer siempre del mismo modo. Supongamos
que tenemos una lista con al menos dos elementos, y un puntero al nodo anterior al que queremos
eliminar. Y un puntero auxiliar nodo.

El proceso es parecido al del caso anterior:

1. Hacemos que nodo apunte al nodo que queremos borrar.

2. Ahora, asignamos como nodo siguiente del nodo anterior, el siguiente al que queremos
eliminar: anterior.sig = nodo.sig

3. Eliminamos la memoria asociada al nodo que queremos eliminar.

Si el nodo a eliminar es el último, es procedimiento es igualmente válido, ya que anterior pasará


a ser el último, y anterior.siguiente valdrá NULO.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


9

 Eliminar el nodo anterior o posterior al nodo cierta con información.

 Eliminar el último nodo.

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

Declaraciones de tipos para manejar listas

Normalmente se definen varios tipos que facilitan el manejo de las listas, la declaración de tipos
puede tener una forma parecida a esta:

struct nodo {
int dato;
nodo *siguiente;
}
Lista es el tipo para declarar listas, como puede verse, un puntero a un nodo y una lista son la
misma cosa. En realidad, cualquier puntero a un nodo es una lista, cuyo primer elemento es el nodo
apuntado.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


10

Ejemplos
Lista enlazada con inserción al inicio en C++
// Lista enlazada con un puntero al primer nodo e insercion al inicio de la lista

// Archivos de Cabecera
#include <iostream.h>
#include <conio.h>

// Definicion de la Estructura tipo Nodo


struct nodo {
int nro;
struct nodo *sgte;
}

// Funcion Principal
int main()
{

// Declaracion de Variables y Estructuras


struct nodo *lista; // puntero al inicio de la lista
struct nodo *q; // variable de trabajo
int i, n;

// Inicializacion del puntero a la lista


lista = NULL;

// Ingreso del numero de elementos de la lista


cout << "Ingrese el número de elementos de la lista: ";
cin >> n;

// Ingreso de los datos a los nodos


for (i=0; i<n; i++) {
cout << "Ingrese el elemento: " << i+1 << ": ";
q = new(struct nodo); // Asignar espacio para el nodo q
cin >> q->nro; // Lectura del Numero
q->sgte = lista; // Actualizacion del Enlace al Siguiente
lista = q; // Actualizacion del nodo lista al primer
elemento
}

// Despliegue de la Lista
cout << "\n\nElementos de la Lista: \n";
q = lista;

while (q != NULL) {
cout << q->nro << "\t"; // muestra el elemento de la lista

Manual de Estructura de Datos Saúl Ernesto Coy Pop


11

q = q->sgte;
}

while (!kbhit());

Manual de Estructura de Datos Saúl Ernesto Coy Pop


12

Lista enlazada con inserción al final en C++


// Lista enlazada con un puntero al primer nodo e insercion al final de la lista

// Archivos de Cabecera
#include <iostream.h>
#include <conio.h>
#include <ctype.h>
#include <stdlib.h>

// Definicion de la Estructura tipo Nodo


struct nodo {
int nro;
struct nodo *sgte;
};

// Funcion Principal
void main(void)
{

// Declaracion de Variables y Estructuras


struct nodo *lista; // puntero al inicio de
la lista
struct nodo *nuevo, *anterior; // punteros al nuevo nodo y al anterior
char cont = 'S'; // Variable
bandera para el control del ciclo

// Inicializacion de la lista
lista = NULL;

do {

// Ingreso de Elemento al Nodo


cout << "Ingrese un elemento: ";
nuevo = new(struct nodo);
cin >> nuevo->nro;
nuevo->sgte = NULL;

// Ordenamiento de los punteros


if (lista == NULL) {
lista = nuevo;
anterior = nuevo;
}
else {
anterior->sgte = nuevo;
anterior = nuevo;
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


13

cout << "\nDesea continuar: -S/N- ";


cin >> cont;

} while (toupper(cont)=='S');

// Despliega la Lista
cout << "\n\nListado: \n";
nuevo = lista;

while (nuevo!=NULL)
{
cout << nuevo->nro << "\t";
nuevo = nuevo->sgte;
}

system ("pause");
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


14

Lista enlazada con inserción en una posición especifica en C++


// Lista Enlazada con puntero al primer nodo e insercion en una posicion dada

#include <iostream.h>
#include <conio.h>

// Definicion de la estructura
struct nodo {
int nro;
struct nodo *sgte;
};

// Definicion de Tipos de Datos y Prototipos de Funciones


typedef struct nodo* TLista;
void imprimir(TLista lista);
void insertaAlInicio(TLista &lista, int valor);
void insertaAlFinal(TLista &lista, int valor);
void insertaEnPosicion(TLista &lista, int valor, int pos);
void buscaValor(TLista &lista, int valor);

int main()
{
// Declaracion de Variables
TLista lista=NULL;
int opcion, valor, pos;

// Menu principal
do {

clrscr();
cout << "*** Menu Principal ***";
cout << "\n1. Insertar al Inicio";
cout << "\n2. Insertar al Final";
cout << "\n3. Insertar en una Posición";
cout << "\n4. Desplegar la Lista";
cout << "\n5. Buscar Valor";
cout << "\n6. Salir del Programa";
cout << "\n\n Ingrese su opción: ";
cin >> opcion;

switch(opcion)
{
case 1: cout << "Valor a insertar al inicio: ";
cin >> valor;
insertaAlInicio(lista,valor);
break;

Manual de Estructura de Datos Saúl Ernesto Coy Pop


15

case 2: cout << "Valor a insertar al final: ";


cin >> valor;
insertaAlFinal(lista,valor);
break;

case 3: cout << "Valor a insertar: ";


cin >> valor;
cout << "Ingrese la posicion: ";
cin >> pos;
insertaEnPosicion(lista, valor, pos);
break;
case 4: imprimir(lista);
break;
case 5: cout << "Ingrese el valor a buscar: ";
cin >> valor;
buscaValor(lista, valor);
break;
} // Fin del Switch

} while (opcion != 6);

return 0;
} // Fin del Main
// Funcion que imprime la Lista
void imprimir(TLista lista)
{
cout << "\n\nLista:\n";
while (lista !=NULL)
{
cout << lista->nro << "\t";
lista = lista->sgte;
}

while (!kbhit());
}

// Funcion para buscar valor


void buscaValor(TLista &lista, int valor)
{
TLista t = lista;
int i=1;

if (t==NULL)
{
cout << "El valor ingresado no existe";

Manual de Estructura de Datos Saúl Ernesto Coy Pop


16

while (!kbhit());
return;
}
else
while (t->nro != valor)
if (t->sgte == NULL) {
cout << "El valor ingresado no existe";
while (!kbhit());
return; }
else {
t = t->sgte;
i++;
}
cout << "El valor esta en la posicion: " << i;
while (!kbhit());
return;
}

// Funcion para insertar al inicio de la Lista


void insertaAlInicio(TLista &lista, int valor)
{
TLista q;
q=new(struct nodo);
q->nro = valor;
q->sgte = lista;
lista = q;
}

// Funcion para insertar al final de la Lista


void insertaAlFinal(TLista &lista, int valor)
{
TLista t, q;
q=new(struct nodo);
q->nro = valor;
q->sgte = NULL;

if (lista==NULL)
lista=q;
else
{
t = lista;
while (t->sgte!=NULL)
t=t->sgte;
t->sgte=q;
}
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


17

// Funcion para insertar en una Posicion Especifica


void insertaEnPosicion (TLista &lista, int valor, int pos)
{
TLista q, t;
int i;
q=new(struct nodo);
q->nro =valor;

// Verificar posicion
if (pos==1){
q->sgte = lista;
lista=q;
}
else {
t=lista;
for (i=1; t!=NULL; i++)
{
if (i==pos-1)
{
q->sgte = t->sgte;
t->sgte = q;
return;
}
t=t->sgte;
} // Fin del For
} // Fin del Else

cout << "La posición ingresada no existe!!!";


while (!kbhit());
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


18

Recursos de Aprendizaje
Podemos encontrar los siguientes recursos, enlaces de videos y/o páginas en donde
encontraremos más información y ejemplos sobre los temas.
Listas enlazadas: Teoría
https://youtu.be/0NzAFk1CwaQ?list=PLTd5ehIj0goMTSK7RRAPBF4wP-Nj5DRvT

Listas enlazadas práctica.


https://youtu.be/vldM-3PYAmo?list=PLTd5ehIj0goMTSK7RRAPBF4wP-Nj5DRvT

Lista enlazada - Insertar lista y desplegar lista


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20Lista%20Simple%20-
%20Parte%201%20-%20Insertar%20Nodo%20%20y%20Desplegar%20Lista.cpp

Lista enlazada - Insertar Buscar


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20Lista%20Simple%20-
%20Parte%202%20-%20Insertar%20Buscar.cpp

Lista enlazada - Modificar Nodo


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20Lista%20Simple%20-
%20Parte%203%20-%20Modificar%20Nodo.cpp

Lista enlazada - Eliminar Nodo


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20Lista%20Simple%20-
%20Parte%204%20-%20Eliminar%20Nodo.cpp

Manual de Estructura de Datos Saúl Ernesto Coy Pop


19

1.2. Listas enlazadas Dobles


Una lista doble, o doblemente enlazada es una colección de nodos en la cual cada nodo tiene
dos punteros, uno de ellos apuntando a su predecesor (li) y otro a su sucesor(ld). Por medio de
estos punteros se podrá avanzar o retroceder a través de la lista, según se tomen las direcciones de
uno u otro puntero.
La estructura de un nodo en una lista doble es la siguiente:

Li Dato Ld

Existen dos tipos de listas doblemente enlazadas:

Listas dobles lineales.


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

Listas dobles circulares.


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

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

En la figura siguiente se muestra un ejemplo de una lista doblemente enlazada lineal que
almacena números:

Estructura de Datos - 1-2 Lista Doblemente Enlazada

En la figura siguiente se muestra un ejemplo de una lista doblemente enlazada circular que
almacena números:

Estructura de Datos - 1-3 Lista doblemente enlazada circular

Manual de Estructura de Datos Saúl Ernesto Coy Pop


20

Operaciones básicas con listas doblemente enlazadas


Las operaciones con listas doblemente enlazas son muy similares a las de listas simples, por lo
tanto, solo se indicará los pasos a seguir en cada una de ellas.

 Insertar un elemento en una lista doble


Antes de cualquier otra operación sobre la lista, se inicializa el puntero inicio y el puntero fin con
el valor NULO. Para agregar un elemento a la lista hay varias situaciones:
1. Inserción en una lista vacía
2. Inserción al inicio de la lista
3. Inserción al final de la lista
4. Inserción antes de un elemento
5. Inserción después de un element

 Inserción en una lista vacía


Pasos:
Asignación de memoria para el nuevo elemento
Rellenar el campo de datos del nuevo elemento
El puntero anterior del nuevo elemento apuntará hacia NULO (ya que la inserción es hecha
en una lista vacía utilizamos la dirección del puntero inicio que vale NULO)
El puntero siguiente del nuevo elemento apuntará hacia NULO (ya que la inserción es
hecha en una lista vacía se utiliza la dirección del puntero fin que vale NULO)
Los punteros inicio y fin apuntaran hacia el nuevo elemento
El tamaño es actualizado

 Inserción al inicio de la lista


Pasos:
Asignación de memoria al nuevo elemento
Rellenar el campo de datos del nuevo elemento
El puntero anterior del nuevo elemento apunta hacia NULO

Manual de Estructura de Datos Saúl Ernesto Coy Pop


21

El puntero siguiente del nuevo elemento apunta hacia el 1er elemento


El puntero anterior del 1er elemento apunta hacia el nuevo elemento
El puntero inicio apunta hacia el nuevo elemento
El puntero fin no cambia
El tamaño es incrementado

 Inserción al final de la lista


Pasos:
Asignación de memoria al nuevo elemento
Rellenar el campo de datos del nuevo elemento
El puntero siguiente del nuevo elemento apunta hacia NULO
El puntero anterior del nuevo elemento apunta hacia el último elemento (es el puntero fin
que contiene por ahora su dirección)
El puntero siguiente del último elemento apuntará hacia el nuevo elemento
El puntero fin apunta hacia el nuevo elemento
El puntero inicio no cambia

 Inserción antes de un elemento de la lista

La inserción se efectuara antes de cierta posición pasado como argumento a la función. La posición
indicada no debe ser ni el primer ni el último elemento. En ese caso hay que utilizar las funciones
de inserción al inicio y/o al final de la lista.

Pasos:
Asignación de memoria al nuevo elemento
Rellenar el campo de datos del nuevo elemento
Elegir una posición en la lista (la inserción se hará después de la posición elegida)
El puntero siguiente del nuevo elemento apunta hacia el elemento actual.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


22

El puntero anterior del nuevo elemento apunta hacia la dirección hacia la que apunta el puntero
anterior del elemento actual.
El puntero siguiente del elemento que precede al elemento actual apuntará hacia el nuevo elemento
El puntero anterior del elemento actual apunta hacia el nuevo elemento
El puntero fin no cambia
El puntero inicio cambia si la posición elegida es la posición 1

 Inserción después de un elemento de la lista


La posición indicada no debe ser el último elemento. En ese caso hay que utilizar la función de
inserción al final de la lista.
Pasos:
Asignación de memoria al nuevo elemento
Rellenar el campo de datos del nuevo elemento
Elegir una posición en la lista (la inserción se hará después de la posición elegida)
El puntero siguiente del nuevo elemento apunta hacia la dirección hacia la que apunta el puntero
siguiente del elemento actual
El puntero anterior del nuevo elemento apunta hacia el elemento actual.
El puntero anterior del elemento que va después del elemento actual apuntará hacia el nuevo
elemento.
El puntero siguiente del elemento actual apunta hacia el nuevo elemento
El puntero inicio no cambia
El puntero fin cambia si la posición elegida es la posición del último elemento (el tamaño de la
lista)

 Eliminación de un elemento de la lista


En comparación a la lista enlazada simple en el que la eliminación solo puede ser hecha después
que un elemento ha sido designado, las listas doblemente enlazadas son más flexibles gracias a los
2 punteros que permiten guardar la ubicación de la lista, tanto hacia atrás como hacia delante.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


23

Para eliminar un elemento de la lista hay varias situaciones:


1. Eliminación al inicio de la lista
2. Eliminación al final de la lista
3. Eliminación antes de un elemento
4. Eliminación después de un elemento
5 Eliminación de un elemento

Pasos:
Asignación de memoria al nuevo elemento
Rellenar el campo de datos del nuevo elemento
Elegir una posición en la lista (la inserción se hará después de la posición elegida)
El puntero siguiente del nuevo elemento apunta hacia el elemento actual.
El puntero anterior del nuevo elemento apunta hacia la dirección hacia la que apunta el puntero
anterior del elemento actual.
El puntero siguiente del elemento que precede al elemento actual apuntará hacia el nuevo elemento
El puntero anterior del elemento actual apunta hacia el nuevo elemento
El puntero fin no cambia
El puntero inicio cambia si la posición elegida es la posición 1

 Inserción después de un elemento de la lista


La posición indicada no debe ser el último elemento. En ese caso hay que utilizar la función de
inserción al final de la lista.
Pasos:
Asignación de memoria al nuevo elemento
Rellenar el campo de datos del nuevo elemento
Elegir una posición en la lista (la inserción se hará después de la posición elegida)
El puntero siguiente del nuevo elemento apunta hacia la dirección hacia la que apunta el puntero
siguiente del elemento actual
El puntero anterior del nuevo elemento apunta hacia el elemento actual.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


24

El puntero anterior del elemento que va después del elemento actual apuntará hacia el nuevo
elemento.
El puntero siguiente del elemento actual apunta hacia el nuevo elemento
El puntero inicio no cambia
El puntero fin cambia si la posición elegida es la posición del último elemento (el tamaño de la
lista)

 Eliminación de un elemento de la lista


En comparación a la lista enlazada simple en el que la eliminación solo puede ser hecha después
que un elemento ha sido designado, las listas doblemente enlazadas son más flexibles gracias a los
2 punteros que permiten guardar la ubicación de la lista, tanto hacia atrás como hacia delante.
Para eliminar un elemento de la lista hay varias situaciones:
1. Eliminación al inicio de la lista
2. Eliminación al final de la lista
3. Eliminación antes de un elemento
4. Eliminación después de un elemento
5. Eliminación de un elemento

Manual de Estructura de Datos Saúl Ernesto Coy Pop


25

Ejemplos
Lista enlazada doble - Insertar Nodo y Desplegar lista
#include <iostream>

struct nodo{
int dato;
nodo* siguiente;
nodo* atras;
} *primero, *ultimo;

void insertarNodo();

void desplegarListaPU();
void desplegarListaUP();

int main(){

insertarNodo();
insertarNodo();
insertarNodo();
insertarNodo();
insertarNodo();
cout << "\n Lista Primero al ultimo\n";
desplegarListaPU();
cout << "\n Lista ultimo al Primero\n";
desplegarListaUP();

return 0;
}
void insertarNodo(){
nodo* nuevo = new nodo();
cout << " Ingrese el dato que contendra el nuevo Nodo: ";
cin >> nuevo->dato;

if(primero==NULL){
primero = nuevo;
primero->siguiente = NULL;
primero->atras = NULL;
ultimo = primero;
}else{
ultimo->siguiente = nuevo;
nuevo->siguiente = NULL;
nuevo->atras = ultimo;
ultimo = nuevo;
}
cout << "\n Nodo Ingresado\n\n";

Manual de Estructura de Datos Saúl Ernesto Coy Pop


26

void desplegarListaPU(){
nodo* actual = new nodo();
actual = primero;
if(primero!=NULL){

while(actual!=NULL){
cout << "\n " << actual->dato;
actual = actual->siguiente;
}

}else{
cout << "\n La listas se encuentra Vacia\n\n";
}
}

void desplegarListaUP(){
nodo* actual = new nodo();
actual = ultimo;
if(primero!=NULL){

while(actual!=NULL){
cout << "\n " << actual->dato;
actual = actual->atras;
}

}else{
cout << "\n La listas se encuentra Vacia\n\n";
}
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


27

Lista enlazada Doble – Búsqueda


#include <iostream>

using namespace::std;

struct nodo{
int dato;
nodo* siguiente;
nodo* atras;
} *primero, *ultimo;

void insertarNodo();
void buscarNodo();
void desplegarListaPU();
void desplegarListaUP();

int main(){

int opcion_menu=0;
do
{
cout << "\n|-------------------------------------|";
cout << "\n| ° LISTA DOBLE ° |";
cout << "\n|------------------|------------------|";
cout << "\n| 1. Insertar | 5. Desplegar P.U |";
cout << "\n| 2. Buscar | 6. Desplegar U.P |";
cout << "\n| 3. Modificar | 7. Salir |";
cout << "\n| 4. Eliminar | |";
cout << "\n|------------------|------------------|";
cout << "\n\n Escoja una Opcion: ";
cin >> opcion_menu;
switch(opcion_menu){
case 1:
cout << "\n\n INSERTA NODO EN LA LISTA \n\n";
insertarNodo();
break;
case 2:
cout << "\n\n BUSCAR UN NODO EN LA LISTA \n\n";
buscarNodo();
break;
case 3:
cout << "\n\n MODIFICAR UN NODO DE LA LISTA \n\n";
break;
case 4:
cout << "\n\n ELIMINAR UN NODO DE LA LISTA \n\n";
break;

Manual de Estructura de Datos Saúl Ernesto Coy Pop


28

case 5:
cout << "\n\n DESPLEGAR LISTA DE NODOS DEL PRIMERO
AL UTLIMO\n\n";
desplegarListaPU();
break;
case 6:
cout << "\n\n DESPLEGAR LISTA DE NODOS DEL UTLIMO AL
PRIMERO\n\n";
desplegarListaUP();
break;
case 7:
cout << "\n\n Programa finalizado...";
break;
default:
cout << "\n\n Opcion No Valida \n\n";
}
} while (opcion_menu != 7);
return 0;
}

// primero = 45 ultimo = 12 actual = 12 encontrado = true nodoBuscado = 78;

// lista doble NULL <- 45 -> <- 67 -> <- 78 -> <- 12 -> NULL

void insertarNodo(){
nodo* nuevo = new nodo();
cout << " Ingrese el dato que contendra el nuevo Nodo: ";
cin >> nuevo->dato;

if(primero==NULL){
primero = nuevo;
primero->siguiente = NULL;
primero->atras = NULL;
ultimo = primero;
}else{
ultimo->siguiente = nuevo;
nuevo->siguiente = NULL;
nuevo->atras = ultimo;
ultimo = nuevo;
}
cout << "\n Nodo Ingresado\n\n";
}

void buscarNodo(){
nodo* actual = new nodo();

Manual de Estructura de Datos Saúl Ernesto Coy Pop


29

actual = primero;
bool encontrado = false;
int nodoBuscado = 0;
cout << " Ingrese el dato del Nodo a Buscar: ";
cin >> nodoBuscado;
if(primero!=NULL){

while(actual!=NULL && encontrado!=true){

if(actual->dato == nodoBuscado){
cout << "\n Nodo con el dato ( " << nodoBuscado << " )
Encontrado\n\n";
encontrado = true;
}

actual = actual->siguiente;
}

if(!encontrado){
cout << "\n Nodo no Encontrado\n\n";
}

}else{
cout << "\n La listas se encuentra Vacia\n\n";
}
}

void desplegarListaPU(){
nodo* actual = new nodo();
actual = primero;
if(primero!=NULL){

while(actual!=NULL){
cout << "\n " << actual->dato;
actual = actual->siguiente;
}

}else{
cout << "\n La listas se encuentra Vacia\n\n";
}
}

void desplegarListaUP(){
nodo* actual = new nodo();
actual = ultimo;
if(primero!=NULL){

Manual de Estructura de Datos Saúl Ernesto Coy Pop


30

while(actual!=NULL){
cout << "\n " << actual->dato;
actual = actual->atras;
}

}else{
cout << "\n La listas se encuentra Vacia\n\n";
}
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


31

Recursos de Aprendizaje
Podemos encontrar los siguientes recursos, enlaces de videos y páginas en donde encontraremos
más información y ejemplos sobre los temas.
Lista Doblemente Enlazadas- Creación e inserción (java)
https://youtu.be/8oCjWIJJI9c?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Lista Doblemente Enlazadas- Eliminación del inicio y final (java)


https://youtu.be/auxlBjH7XuQ?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Lista Doblemente Enlazada – Insertar nodo y Desplegar lista


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20Lista%20Doble%20-
%20Parte%201%20-%20Insertar%20Nodo%20%20y%20Desplegar%20Lista.cpp

Lista Doblemente Enlazada – Buscar nodo


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20Lista%20Doble%20-
%20Parte%202%20-%20Buscar%20Nodo.cpp

Lista Doblemente Enlazada – Modificar nodo


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20Lista%20Doble%20-
%20Parte%203%20-%20Modificar%20Nodo.cpp

Lista Doblemente Enlazada – Eliminar nodo


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20Lista%20Simple%20-
%20Parte%204%20-%20Eliminar%20Nodo.cpp

Manual de Estructura de Datos Saúl Ernesto Coy Pop


32

1.3. Circulares.
Las listas circulares tienen la característica de que el último elemento de la misma apunta al
primero.

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

Estructura de Datos - 1-4 Lista Circular

Una lista circular es una lista lineal en la que el último nodo a punta al primero. Las listas circulares
evitan excepciones en las operaciones que se realicen sobre ellas. No existen casos especiales, cada
nodo siempre tiene uno anterior y uno siguiente. En algunas listas circulares se añade un nodo
especial de cabecera, de ese modo se evita la única excepción posible, la de que la lista esté vacía.
Los tipos que definiremos normalmente para manejar listas cerradas son los mismos que para para
manejar listas abiertas:

tipoNodo es el tipo para declarar nodos, evidentemente. pNodo es el tipo para declarar punteros a
un nodo. Lista es el tipo para declarar listas, tanto abiertas como circulares. En el caso de las
circulares, apuntará a un nodo cualquiera de la lista. A pesar de que las listas circulares
simplifiquen las operaciones sobre ellas, también introducen algunas complicaciones. Por ejemplo,
en un proceso de búsqueda, no es tan sencillo dar por terminada la búsqueda cuando el elemento
buscado no existe. Por ese motivo se suele resaltar un nodo en particular, que no tiene por qué ser
siempre el mismo. Cualquier nodo puede cumplir ese propósito, y puede variar durante la
ejecución del programa. Otra alternativa que se usa a menudo, y que simplifica en cierto modo el
uso de listas circulares es crear un nodo especial de hará la función de nodo cabecera. De este
modo, la lista nunca estará vacía, y se eliminan casi todos los casos especiales.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


33

Operaciones básicas con listas circulares: A todos los efectos, las listas circulares son como las
listas abiertas en cuanto a las operaciones que se pueden realizar sobre ellas:

a) Añadir o insertar elementos.

b) Buscar o localizar elementos.

c) Borrar elementos.

d) Moverse a través de la lista, siguiente.

Añadir un elemento
El único caso especial a la hora de insertar nodos en listas circulares es cuando la lista esté vacía.

I. Añadir elemento en una lista circular vacía:


El proceso es muy simple, bastará con que:

lista apunta a nodo.


lista->siguiente apunte a nodo.

II. Añadir elemento en una lista circular no vacía:


De nuevo partiremos de un nodo a insertar, con un puntero que apunte a él, y de una lista, en
este caso, el puntero no será nulo:

El proceso sigue siendo muy sencillo:


Hacemos que nodo->siguiente apunte a lista->siguiente.
Después que lista->siguiente apunte a nodo.

III. Añadir elemento en una lista circular, caso general:


Para generalizar los dos casos anteriores, sólo necesitamos añadir una operación:
Si lista está vacía hacemos que lista apunte a nodo.
Si lista no está vacía, hacemos que nodo->siguiente apunte a lista->siguiente. Después que lista-
>siguiente apunte a nodo.

Buscar un elemento en una lista circular


A la hora de buscar elementos en una lista circular sólo hay que tener una precaución, es
necesario almacenar el puntero del nodo en que se empezó la búsqueda, para poder detectar
el caso en que no exista el valor que se busca. Por lo demás, la búsqueda es igual que en el caso
de las listas abiertas, salvo que podemos empezar en cualquier punto de la lista.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


34

Borrar un elemento de una lista circular


Para ésta operación podemos encontrar tres casos diferentes:
Eliminar un nodo cualquiera, que no sea el apuntado por lista.
Eliminar el nodo apuntado por lista, y que no sea el único nodo.
Eliminar el único nodo de la lista.

En el primer caso necesitamos localizar el nodo anterior al que queremos borrar. Como el
principio de la lista puede ser cualquier nodo, haremos que sea precisamente lista quien apunte al
nodo anterior al que queremos eliminar.
Esto elimina la excepción del segundo caso, ya que lista nunca será el nodo a eliminar, salvo
que sea el único nodo de la lista.

Una vez localizado el nodo anterior y apuntado por lista, hacemos que lista->siguiente apunte
a nodo->siguiente. Y a continuación borramos nodo.

En el caso de que sólo exista un nodo, será imposible localizar el nodo anterior, así que
simplemente eliminaremos el nodo, y haremos que lista valga NULL.

I. Eliminar un nodo en una lista circular con más de un elemento

El primer paso es conseguir que lista apunte al nodo anterior al que queremos eliminar. Esto
se consigue haciendo que lista valga lista->siguiente mientras lista->siguiente sea distinto de nodo.
Hacemos que lista->siguiente apunte a nodo->siguiente.
Eliminamos el nodo.

II. Eliminar el único nodo en una lista circular

Este caso es mucho más sencillo. Si lista es el único nodo de una lista circular: Borramos el nodo
apuntado por lista.
Hacemos que lista = NULL

Otro algoritmo para borrar nodos: Existe un modo alternativo de eliminar un nodo en una
lista circular con más de un nodo.
Supongamos que queremos eliminar un nodo apuntado por nodo:
Copiamos el contenido del nodo->siguiente sobre el contenido de nodo.
Hacemos que nodo->siguiente apunte a nodo->siguiente->siguiente.
Eliminamos nodo->siguiente.
Si lista es el nodo->siguiente, hacemos lista = nodo.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


35

Este método también funciona con listas circulares de un sólo elemento, salvo que el nodo a
borrar es el único nodo que existe, y hay que hacer que lista = NULL.

Ejemplos

Algoritmo de la función "Insertar":


void Insertar(Lista *lista, int v) {
pNodo nodo;

// Creamos un nodo para el nuvo valor a insertar


nodo = (pNodo)malloc(sizeof(tipoNodo));
nodo->valor = v;

// Si la lista está vacía, la lista será el nuevo nodo


// Si no lo está, insertamos el nuevo nodo a continuación del apuntado
// por lista
if(*lista == NULL) *lista = nodo;
else nodo->siguiente = (*lista)->siguiente;
// En cualquier caso, cerramos la lista circular
(*lista)->siguiente = nodo;
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


36

Algoritmo de la función "Borrar":


void Borrar(Lista *lista, int v) {
pNodo nodo;

nodo = *lista;

// Hacer que lista apunte al nodo anterior al de valor v


do {
if((*lista)->siguiente->valor != v) *lista = (*lista)->siguiente;
} while((*lista)->siguiente->valor != v && *lista != nodo);
// Si existe un nodo con el valor v:
if((*lista)->siguiente->valor == v) {
// Y si la lista sólo tiene un nodo
if(*lista == (*lista)->siguiente) {
// Borrar toda la lista
free(*lista);
*lista = NULL;
}
else {
// Si la lista tiene más de un nodo, borrar el nodo de valor v
nodo = (*lista)->siguiente;
(*lista)->siguiente = nodo->siguiente;
free(nodo);
}
}
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


37

Lista Circular Simple Completa


#include <stdio.h>

typedef struct _nodo {


int valor;
struct _nodo *siguiente;
} tipoNodo;

typedef tipoNodo *pNodo;


typedef tipoNodo *Lista;

// Funciones con listas:


void Insertar(Lista *l, int v);
void Borrar(Lista *l, int v);
void BorrarLista(Lista *);
void MostrarLista(Lista l);

int main() {
Lista lista = NULL;
pNodo p;

Insertar(&lista, 10);
Insertar(&lista, 40);
Insertar(&lista, 30);
Insertar(&lista, 20);
Insertar(&lista, 50);

Manual de Estructura de Datos Saúl Ernesto Coy Pop


38

MostrarLista(lista);

Borrar(&lista, 30);
Borrar(&lista, 50);

MostrarLista(lista);

BorrarLista(&lista);
return 0;
}

void Insertar(Lista *lista, int v) {


pNodo nodo;

// Creamos un nodo para el nuvo valor a insertar


nodo = (pNodo)malloc(sizeof(tipoNodo));
nodo->valor = v;

// Si la lista está vacía, la lista será el nuevo nodo


// Si no lo está, insertamos el nuevo nodo a continuación del apuntado
// por lista
if(*lista == NULL) *lista = nodo;
else nodo->siguiente = (*lista)->siguiente;
// En cualquier caso, cerramos la lista circular
(*lista)->siguiente = nodo;
}

void Borrar(Lista *lista, int v) {

Manual de Estructura de Datos Saúl Ernesto Coy Pop


39

pNodo nodo;

nodo = *lista;

// Hacer que lista apunte al nodo anterior al de valor v


do {
if((*lista)->siguiente->valor != v) *lista = (*lista)->siguiente;
} while((*lista)->siguiente->valor != v && *lista != nodo);
// Si existe un nodo con el valor v:
if((*lista)->siguiente->valor == v) {
// Y si la lista sólo tiene un nodo
if(*lista == (*lista)->siguiente) {
// Borrar toda la lista
free(*lista);
*lista = NULL;
}
else {
// Si la lista tiene más de un nodo, borrar el nodo de valor v
nodo = (*lista)->siguiente;
(*lista)->siguiente = nodo->siguiente;
free(nodo);
}
}
}

void BorrarLista(Lista *lista) {


pNodo nodo;

Manual de Estructura de Datos Saúl Ernesto Coy Pop


40

// Mientras la lista tenga más de un nodo


while((*lista)->siguiente != *lista) {
// Borrar el nodo siguiente al apuntado por lista
nodo = (*lista)->siguiente;
(*lista)->siguiente = nodo->siguiente;
free(nodo);
}
// Y borrar el último nodo
free(*lista);
*lista = NULL;
}

void MostrarLista(Lista lista) {


pNodo nodo = lista;

do {
printf("%d -> ", nodo->valor);
nodo = nodo->siguiente;
} while(nodo != lista);
printf("\n");
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


41

Recursos de Aprendizaje
Podemos encontrar los siguientes recursos, enlaces de videos y páginas en donde
encontraremos más información y ejemplos sobre los temas.

Listas Circulares, Creación e Inserción (Java)


https://youtu.be/m2ellSLIc5E?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Listas Circulares, Eliminando Nodos (Java)


https://youtu.be/tzLadW2Yg48?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9
Lista Circular Simple, inserter nodo y desplegar lista
https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-
%20Lista%20Circular%20Simple%20-%20Parte%201%20-
%20Insertar%20Nodo%20%20y%20Desplegar%20Lista.cpp

Lista Circular, Buscar nodo.


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-
%20Lista%20Circular%20Simple%20-%20Parte%202%20-%20Buscar%20Nodo.cpp

Lista Circular, Modificar nodo.


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-
%20Lista%20Circular%20Simple%20-%20Parte%203%20-%20Modificar%20Nodo.cpp

Lista Circular, Eliminar nodo.


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-
%20Lista%20Circular%20Simple%20-%20Parte%204%20-%20Eliminar%20Nodo.cpp

Manual de Estructura de Datos Saúl Ernesto Coy Pop


42

2. Pilas
Las pilas son otro tipo de estructura de datos lineales, las cuales presentan restricciones
en cuanto a la posición en la cual pueden realizarse las inserciones y las extracciones
de elementos. Una pila es una lista de elementos en la que se pueden insertar y eliminar
elementos sólo por uno de los extremos.
Como consecuencia, los elementos de una pila serán eliminados en orden inverso al que
se insertaron. Es decir, el último elemento que se metió a la pila será el primero en salir
de ella.
En la vida cotidiana existen muchos ejemplos de pilas, una pila de platos en una alacena,
una pila de latas en un supermercado, una pila de papeles sobre un escritorio, etc.
Debido al orden en que se insertan y eliminan los elementos en una pila, también se le
conoce como estructura LIFO (Last In, First Out: último en entrar, primero en salir).

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

Estructura de Datos - 2-1 Representación de una Pila

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

En cada momento sólo se tiene acceso a la parte superior de la pila, es decir, al último
objeto apliado (denominado TOS, top of stack en inglés). La operación retirar permite la

Manual de Estructura de Datos Saúl Ernesto Coy Pop


43

obtención de este elemento, que es retirado de la pila permitiendo el acceso al siguiente


(apilado con anterioridad), que pasa a ser el nuevo TOS.

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

Las pilas suelen emplearse en los siguientes contextos:


 Evaluación de expresiones en notación postfija (notación polaca inversa).
 Reconocedores sintácticos de lenguajes independientes del contexto.
 Implementación de recursividad.

2.1. Clases para la implementación de pilas.

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


que nos pueden servir para darnos una mejor idea acerca de lo que es recursividad. Un
ejemplo de esto es cuando se toma una fotografía de una fotografía, o cuando en un
programa de televisión un periodista transfiere el control a otro periodista que se
encuentra en otra ciudad, y este a su vez le transfiere el control a otro.
Casos típicos de estructuras de datos definidas de manera recursiva son los árboles
binarios y las listas enlazadas.
La recursión se puede dar de dos formas:
 DIRECTA. Este tipo de recursión se da cuando un subprograma se llama
directamente a sí mismo.

 INDIRECTA Sucede cuando un subprograma llama a un segundo subprograma,


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

Manual de Estructura de Datos Saúl Ernesto Coy Pop


44

Ejemplos
Algoritmo de la función "push"

void Push(Pila *pila, int v) {


pNodo nuevo;

/* Crear un nodo nuevo */


nuevo = (pNodo)malloc(sizeof(tipoNodo));
nuevo->valor = v;

/* Añadimos la pila a continuación del nuevo nodo */


nuevo->siguiente = *pila;
/* Ahora, el comienzo de nuestra pila es en nuevo nodo */
*pila = nuevo;
}

Algoritmo de la función “pop”

int Pop(Pila *pila) {


pNodo nodo; /* variable auxiliar para manipular nodo */
int v; /* variable auxiliar para retorno */

/* Nodo apunta al primer elemento de la pila */


nodo = *pila;
if(!nodo) return 0; /* Si no hay nodos en la pila retornamos 0 */
/* Asignamos a pila toda la pila menos el primer elemento */
*pila = nodo->siguiente;
/* Guardamos el valor de retorno */

Manual de Estructura de Datos Saúl Ernesto Coy Pop


45

v = nodo->valor;
/* Borrar el nodo */
free(nodo);
return v;
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


46

Algoritmo de una Pila

#include <stdlib.h>
#include <stdio.h>

typedef struct _nodo {


int valor;
struct _nodo *siguiente;
} tipoNodo;

typedef tipoNodo *pNodo;


typedef tipoNodo *Pila;

/* Funciones con pilas: */


void Push(Pila *l, int v);
int Pop(Pila *l);

int main() {
Pila pila = NULL;

Push(&pila, 20);
Push(&pila, 10);
printf("%d, ", Pop(&pila));
Push(&pila, 40);
Push(&pila, 30);

printf("%d, ", Pop(&pila));


printf("%d, ", Pop(&pila));

Manual de Estructura de Datos Saúl Ernesto Coy Pop


47

Push(&pila, 90);
printf("%d, ", Pop(&pila));
printf("%d\n", Pop(&pila));

getchar();
return 0;
}

void Push(Pila *pila, int v) {


pNodo nuevo;

/* Crear un nodo nuevo */


nuevo = (pNodo)malloc(sizeof(tipoNodo));
nuevo->valor = v;

/* Añadimos la pila a continuación del nuevo nodo */


nuevo->siguiente = *pila;
/* Ahora, el comienzo de nuestra pila es en nuevo nodo */
*pila = nuevo;
}

int Pop(Pila *pila) {


pNodo nodo; /* variable auxiliar para manipular nodo */
int v; /* variable auxiliar para retorno */

/* Nodo apunta al primer elemento de la pila */


nodo = *pila;
if(!nodo) return 0; /* Si no hay nodos en la pila retornamos 0 */

Manual de Estructura de Datos Saúl Ernesto Coy Pop


48

/* Asignamos a pila toda la pila menos el primer elemento */


*pila = nodo->siguiente;
/* Guardamos el valor de retorno */
v = nodo->valor;
/* Borrar el nodo */
free(nodo);
return v;
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


49

Algoritmo de una Pila (2do ejemplo)


#include <iostream.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

struct pilas
{
int d;
pilas *a;
}*c,*e;

void menu(void);
void ingresar(void);
void sacar (void);
void actualizar_pila(void);

main()
{
menu();
}
void menu(void)
{
int y,opc;
for(;;)
{
cout<<"\n1. Ingresar datos";
cout<<"\t2. Sacar datos";
cout<<"\t0. Terminar";
cout<<"\n Ingrese opcion: ";cin>>opc;
switch(opc)
{
case 1:
ingresar();
break;
case 2: sacar();
break;
case 0: exit(1);
default: cout<<"\n Opcion no valida!!"; break;
}
actualizar_pila();
cout<<"\n\nOprima una tecla para continuar";
getch();
}
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


50

void ingresar (void)


{
if(!c)
{
c=new(pilas);
cout<<"Ingrese elemento: ";
cin>>c->d;
c->a=NULL;
return;
}

e=new(pilas);
cout<<"\nIngrese elemento: ";
cin>>e->d;
e->a=c;
c=e;
}

void sacar(void)
{
if(!c)
{
cout<<"\n\nNo hay elementos!!";
return;
}

e=new(pilas);
e=c;
cout<<"\n\nElemento eliminado: " <<e->d;
c=e->a;
delete(e);

}
void actualizar_pila(void)
{
int y=2,i,ca=0;
e=c;
while(e)
{
ca++;
e=e->a;
}

for(i=0;i<=ca;i++)
{

Manual de Estructura de Datos Saúl Ernesto Coy Pop


51

cout<<" ";
}
//muestro lo que tiene la pila!!
i=0;
e=c;
while(e)
{
cout<<"\n";
cout<<++i<<" - "<<e->d;
e=e->a;
}
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


52

Recursos de Aprendizaje
Podemos encontrar los siguientes recursos, enlaces de videos y/o páginas en donde
encontraremos más información y ejemplos sobre los temas.

Pilas-Teoría
https://youtu.be/JDlhpEuLUrQ?list=PLTd5ehIj0goMTSK7RRAPBF4wP-Nj5DRvT

Pilas en C++
https://youtu.be/KMbf3UHaAs4?list=PLTd5ehIj0goMTSK7RRAPBF4wP-Nj5DRvT
Pilas (Java)
https://youtu.be/kRp_g3ywOjY?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Pila, insertar nodo y desplegar pila


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20PILA%20-
%20Parte%201%20-%20Insertar%20Nodo%20%20y%20Desplegar%20Pila.cpp

Pila, buscar pila


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20PILA%20-
%20Parte%202%20-%20Buscar%20Nodo.cpp

Pila, modificar pila


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20PILA%20-
%20Parte%203%20-%20Modificar%20Nodo.cpp

Pila, eliminar pila


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20PILA%20-
%20Parte%204%20-%20Eliminar%20Nodo.cpp

Manual de Estructura de Datos Saúl Ernesto Coy Pop


53

3. Colas

Una cola es una estructura de almacenamiento, donde la podemos considerar como una lista de
elementos, en la que éstos van a ser insertados por un extremo y serán extraídos por otro.
Las colas son estructuras de tipo FIFO (first-in, first-out), ya que el primer elemento en entrar a la
cola será el primero en salir de ella.
Existen muchísimos ejemplos de colas en la vida real, como, por ejemplo: personas esperando en
un teléfono público, niños esperando para subir a un juego mecánico, estudiantes esperando para
subir a un camión escolar, etc.

Definición.

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

3.1. Tipos.

Colas de prioridad:
En ellas, los elementos se atienden en el orden indicado por una prioridad asociada a cada
uno. Si varios elementos tienen la misma prioridad, se atenderán de modo convencional según la
posición que ocupen.

Hay 2 formas de implementación:


1 Añadir un campo a cada nodo con su prioridad. Resulta conveniente mantener la cola
ordenada por orden de prioridad.
2 Crear tantas colas como prioridades haya, y almacenar cada elemento en su cola.

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

· Bicolas de entrada restringida: Son aquellas donde la inserción sólo se hace por el final, aunque
podemos eliminar al principio ó al final.

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

Manual de Estructura de Datos Saúl Ernesto Coy Pop


54

Colas de prioridad:
son las que cumplen dos reglas:
1 De dos elementos siempre se atenderá antes al que tenga mayor prioridad.
2 Si dos elementos tienen la misma prioridad se atiende primero el que llego antes.

Realización Se ponen todos los nodos en la misma cola. Su particularidad es que cada nodo tiene
un campo adicional con la prioridad del dato; de tal forma que cuando insertamos nuevos datos, el
nuevo nodo, se inserta al final de la cola de los que tengan su misma prioridad.

3.2. Colas simples.

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

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

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

3.3. Colas circulares.


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

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

Estructura de Datos - 3-1 Cola Circular

Manual de Estructura de Datos Saúl Ernesto Coy Pop


55

La condición de vacío en este tipo de cola es que el apuntador F sea igual a cero.
Las condiciones que debemos tener presentes al trabajar con este tipo de estructura son las
siguientes:
· Over flow, cuando se realice una inserción.
· Under flow, cuando se requiera de una extracción en la cola.
· Vacio

3.4. Colas dobles.


Esta estructura es una cola bidimensional en que las inserciones y eliminaciones se pueden realizar
en cualquiera de los dos extremos de la bicola.
Gráficamente representamos una bicola de la siguiente manera:

Estructura de Datos - 3-2 Cola doble

Existen dos variantes de la doble cola:


· Doble cola de entrada restringida.
· Doble cola de salida restringida.

La primera variante sólo acepta inserciones al final de la cola, y la segunda acepta eliminaciones
sólo al frente de la cola.

I. Operaciones.
Las operaciones que nosotros podemos realizar sobre una cola son las
siguientes:
· Inserción.
· Extracción.
Las inserciones en la cola se llevarán a cabo por atrás de la cola, mientras que las eliminaciones se
realizarán por el frente de la cola (hay que recordar que el primero en entrar es el primero en salir).

II. Clases para la implementación de colas.


Esta implementación es estática, es decir, da un tamaño máximo fijo a la cola. No se incluye
comprobación de errores dentro del encolado y el desencolado, pero se implementan como
funciones aparte. ¿Por qué un array circular? ¿Qué es eso? Como se aprecia en la implementación
de las pilas, los elementos se quitan y se ponen sobre la cima, pero en este caso se introducen por
un sitio y se quitan por otro.

Podría hacerse con un array secuencial, como se muestra en las siguientes figuras. 'Entrada' es la
posición de entrada a la cola, y 'Salida' por donde salen.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


56

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


4 (en ese orden):

se desencola, obteniendo un 3:

se encola un 7:

Enseguida se aprecia que esto tiene un grave defecto, y es que llega un momento en el que se
desborda la capacidad del array. Una solución nada efectiva es incrementar su tamaño. Esta
implementación es sencilla pero totalmente ineficaz.

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

Manual de Estructura de Datos Saúl Ernesto Coy Pop


57

Ejemplos
Cola en C++

#include <iostream>
using namespace std;

class nodo {
public:
nodo(int v, nodo *sig = NULL) {
valor = v;
siguiente = sig;
}

private:
int valor;
nodo *siguiente;

friend class cola;


};

typedef nodo *pnodo;

class cola {
public:
cola() : ultimo(NULL), primero(NULL) {}
~cola();

void Push(int v);

Manual de Estructura de Datos Saúl Ernesto Coy Pop


58

int Pop();

private:
pnodo ultimo;
};

cola::~cola() {
while(primero) Leer();
}

void cola::Anadir(int v) {
pnodo nuevo;

/* Crear un nodo nuevo */


nuevo = new nodo(v);
/* Si la cola no estaba vacía, añadimos el nuevo a continuación de ultimo */
if(ultimo) ultimo->siguiente = nuevo;
/* Ahora, el último elemento de la cola es el nuevo nodo */
ultimo = nuevo;
/* Si primero es NULL, la cola estaba vacía, ahora primero apuntará también al nuevo nodo */
if(!primero) primero = nuevo;
}

int cola::Leer() {

pnodo nodo; /* variable auxiliar para manipular nodo */


int v; /* variable auxiliar para retorno */

Manual de Estructura de Datos Saúl Ernesto Coy Pop


59

/* Nodo apunta al primer elemento de la pila */


nodo = primero;
if(!nodo) return 0; /* Si no hay nodos en la pila retornamos 0 */
/* Asignamos a primero la dirección del segundo nodo */
primero = nodo->siguiente;
/* Guardamos el valor de retorno */
v = nodo->valor;
/* Borrar el nodo */
delete nodo;
/* Si la cola quedó vacía, ultimo debe ser NULL también*/
if(!primero) ultimo = NULL;
return v;
}
int main() {
cola Cola;

Cola.Anadir(20);
cout << "Añadir(20)" << endl;
Cola.Anadir(10);
cout << "Añadir(10)" << endl;
cout << "Leer: " << Cola.Leer() << endl;
Cola.Anadir(40);
cout << "Añadir(40)" << endl;
Cola.Anadir(30);
cout << "Añadir(30)" << endl;
cout << "Leer: " << Cola.Leer() << endl;
cout << "Leer: " << Cola.Leer() << endl;
Cola.Anadir(90);

Manual de Estructura de Datos Saúl Ernesto Coy Pop


60

cout << "Añadir(90)" << endl;


cout << "Leer: " << Cola.Leer() << endl;
cout << "Leer: " << Cola.Leer() << endl;

return 0;

Manual de Estructura de Datos Saúl Ernesto Coy Pop


61

Recursos de Aprendizaje
Podemos encontrar los siguientes recursos, enlaces de videos y/o páginas en donde
encontraremos más información y ejemplos sobre los temas.
Colas: Teoría
https://youtu.be/6i2f6k5PPjs?list=PLTd5ehIj0goMTSK7RRAPBF4wP-Nj5DRvT

Colas en C
https://youtu.be/nicbZo7sI_w?list=PLTd5ehIj0goMTSK7RRAPBF4wP-Nj5DRvT

Colas, Implementación (Java)


https://youtu.be/Y1mnS8NmTbA?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Colas – Insertar nodo y desplegar cola


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20COLA%20-
%20Parte%201%20-%20Insertar%20Nodo%20%20y%20Desplegar%20Cola.cpp

Colas - Buscar nodo


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20COLA%20-
%20Parte%202%20-%20Buscar%20Nodo.cpp

Colas – Modificar Nodo


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20COLA%20-
%20Parte%203%20-%20Modificar%20Nodo.cpp

Colas – Eliminar Nodo


https://github.com/ProgramacionExplciada/Estructura-de-datos-en-C-
/blob/master/Estructura%20de%20datos%20en%20C%2B%2B%20-%20COLA%20-
%20Parte%204%20-%20Eliminar%20Nodo.cpp

Manual de Estructura de Datos Saúl Ernesto Coy Pop


62

4. Árboles

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

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

Estructura de Datos - 4-1 Árbol

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

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


• Caso base: un árbol con sólo un nodo (es a la vez raíz del árbol y hoja).
• Un nuevo árbol a partir de un nodo nr y k árboles de raíces con elementos cada uno, puede
construirse estableciendo una relación padre-hijo entre nr y cada una de las raíces de los k
árboles. El árbol resultante de nodos tiene como raíz el nodo nr, los nodos son los hijos de nr
y el conjunto de nodos hoja está formado por la unión de los k conjuntos hojas iniciales. A
cada uno de los árboles Ai se les denota ahora subárboles de la raíz.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


63

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

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

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


lugar la raíz y luego cada uno de los hijos en orden previo.
• El recorrido en inorden, también llamado orden simétrico (aunque este nombre sólo
cobra significado en los árboles binarios) consiste en recorrer en primer lugar A1, luego la raíz
y luego cada uno de los hijos en orden simétrico.
• El recorrido en postorden, también llamado orden posterior consiste en recorrer en primer
cada uno de los hijos en orden posterior y por último la raíz.

II. Representación en memoria de árboles.


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

• Por medio de datos tipo punteros también conocidos como variables dinámicas o listas.
• Por medio de arreglos.

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

Manual de Estructura de Datos Saúl Ernesto Coy Pop


64

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

Izq. Dato Der.

4.1. Árboles Generales


Los árboles representan las estructuras no lineales y dinámicas de datos más importantes en
computación. Dinámicas porque las estructuras de árbol pueden cambiar durante la ejecución de
un programa. No lineales, puesto que a cada elemento del árbol pueden seguirle varios elementos.

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

La definición de árbol es la siguiente: es una estructura jerárquica aplicada sobre una colección
de elementos u objetos llamados nodos; uno de los cuales es conocido como raíz. Además se crea
una relación o parentesco entre los nodos dando lugar a términos como padre, hijo, hermano,
antecesor, sucesor, ancestro, etc.. Formalmente se define un árbol de tipo T como una estructura
homogénea que es la concatenación de un elemento de tipo T junto con un número finito de árboles
disjuntos, llamados subárboles. Una forma particular de árbol puede ser la estructura vacía.

La figura siguiente representa a un árbol general.

Se utiliza la recursión para definir un árbol porque representa la forma más apropiada y porque
además es una característica inherente de los mismos.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


65

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

4.2. Árboles Binarios

Los árboles binarios son estructuras de datos muy similares a las listas doblemente enlazadas,
en el sentido que tienen dos punteros que apuntan a otros elementos, pero no tienen una estructura
lógica de tipo lineal o secuencial como aquellas, sino ramificada. Tienen aspecto de árbol, de ahí
su nombre.

Un árbol binario es una estructura de datos no lineal en la que cada nodo puede apuntar a uno
o máximo a dos nodos. También se suele dar una definición recursiva que indica que es una
estructura compuesta por un dato y dos árboles. Esto son definiciones simples. Este tipo de árbol
se caracteriza porque tienen un vértice principal y de él se desprende dos ramas. La rama izquierda
y la rama derecha a las que también se les conoce como subárboles.

Una representación gráfica de la estructura general de un árbol binario se puede visualizar en


la siguiente imagen.

Estructura de Datos - 4-2 Árbol Binario

Manual de Estructura de Datos Saúl Ernesto Coy Pop


66

La rama izquierda y la derecha, también son dos árboles binarios. El Vértice principal se
denomina raíz y cada una de las ramas se puede denominar como subárbol izquierdo y subárbol
derecho.

Teniendo en cuenta la gráfica del árbol binario de la imagen anterior podemos identificar
algunas generalidades y partes del árbol.

Nodo: Un árbol binario es un conjunto de elementos cada uno de los cuales se denomina nodo.
Un árbol Binario puede tener cero nodos y este caso se dice que está vacío. Puede tener un sólo
nodo, y en este caso solamente existe la raíz del árbol o puede tener un número finito de nodos.
Cada nodo puede estar ramificado por la izquierda o por la derecha o puede no tener ninguna
ramificación.

Con relación al tipo de nodos que hacen parte de los árboles, se identifican algunos nodos:

Nodo hijo: cualquiera de los nodos apuntados por uno de los nodos del árbol. En la gráfica de
la imagen, se tiene, ‘D’ y ‘M’ son hijos de ‘A’.

Nodo padre: nodo que contiene un puntero al nodo actual. En el ejemplo, el nodo ‘A’ es padre
de ‘D’ y ‘D’.

Los árboles con los que trabajará tienen otra característica importante: cada nodo sólo puede
ser apuntado por otro nodo, es decir, cada nodo sólo tendrá un padre. Esto hace que estos árboles
estén fuertemente jerarquizados, y es lo que en realidad les da la apariencia de árboles.

En cuanto a la posición dentro del árbol se tiene:

Nodo raíz: nodo que no tiene padre. Este es el nodo que usaremos para referirnos al árbol. En
el ejemplo anterior, es el nodo ‘A’.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


67

Nodo hoja: nodo que no tiene hijos. En el ejemplo hay varios: ‘L’, ‘K’, ‘I’, ‘E’.

Existen otros conceptos que definen las características del árbol, en relación a su tamaño:

Orden: es el número potencial de hijos que puede tener cada elemento de árbol. De este modo,
se dice que un árbol en el que cada nodo puede apuntar a otros dos es de orden dos, si puede
apuntar a tres será de orden tres y así sucesivamente.

Grado: el número de hijos que tiene el elemento con más hijos dentro del árbol. En el árbol
del ejemplo en la imagen 2, el grado es dos, ya que tanto ‘A’ como ‘D’ y ‘M’ tienen dos hijos, y
no existen elementos con más de dos hijos.

Nivel: se define para cada elemento del árbol como la distancia a la raíz, medida en nodos. El
nivel de la raíz siempre será cero y el de sus hijos uno. Así sucesivamente. En el ejemplo de la
imagen 2, el nodo ‘D’ tiene nivel 1, el nodo ‘L’ tiene nivel 2.

Altura: la altura de un árbol se define como el nivel del nodo de mayor nivel. Como cada nodo
de un árbol puede considerarse a su vez como la raíz de un árbol, también se puede hablar de altura
de ramas.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


68

Formas de Recorrer un árbol binario

Los árboles binarios, son estructuras de datos no lineales, son considerados como estructuras
jerárquicas y como tal su forma de recorrerlos difiere sustancialmente en comparación con las
listas enlazadas que son estructuras de datos de tipo lineal. En ese orden de ideas, el recorrido
de un árbol binario se lleva a cabo en tres sentidos: Preorden, Inorden y Postorden.

A continuación, se detalla cada caso.

I. Recorrido en Preorden:
• Examinar la raíz.

• Recorrer el subárbol izquierdo en preorden.

• recorrer el subárbol derecho en preorden.

Representación gráfica del árbol binario y su recorrido en preorden

Estructura de Datos - 4-3 Preorden

El recorrido inicia con el subárbol izquierdo, el primer nodo a visitar es la raíz que es el nodo
10, luego se visita el subárbol izquierdo con el nodo 5, posteriormente el 3, luego el nodo 1, sigue
con el nodo 4, pasamos al nodo 7 y luego el 9.

Continuamos con el recorrido del subárbol derecho en preorden, con la visita del nodo 15,
luego el 14, se continúa con el 17, se visita el 16 y se finaliza con la visita del nodo 20.

El resultado completo del recorrido en preorden para el árbol de la imagen es: 10 – 5 – 3 – 1


– 4 – 7 – 9 – 15 – 14 – 17 -16 – 20.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


69

II. Recorrido en Inorden


• Recorrer el subárbol izquierdo en inorden.

• Examinar la raíz.

• Recorrer el subárbol derecho en inorden.

Representación gráfica del árbol binario y su recorrido en Inorden

Estructura de Datos - 4-4 Inorden

El recorrido inicia con el subárbol izquierdo, el primer nodo a visitar es el 3 luego se visita el
5 y posteriormente el 7, con esto se garantiza que el recorrido del subárbol izquierdo se hizo en
Inorden.

Finalizado el recorrido del subárbol izquierdo se visita el nodo de la raíz, que para este caso
es el número 10.

Solo queda recorrer el subárbol derecho en Inorden, es decir se visita el 11 luego el 12 y se


finaliza con la visita del nodo 15

El resultado completo del recorrido en Inorden para el árbol de la imagen es:3 – 5 – 7


– 10 – 11 – 12 – 15.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


70

III. Recorrido en Postorden:


• Recorrer el subárbol izquierdo en postorden.

• Recorrer el subárbol derecho en postorden.

• Examinar la raíz.

Representación gráfica del árbol binario y su recorrido en postorden

Estructura de Datos - 4-5 Postorden

El recorrido inicia con el subárbol izquierdo, el primer nodo a visitar es el 3 luego se visita el
7 y posteriormente el 5 que es la raíz, con esto se garantiza que el recorrido del subárbol izquierdo
se hizo en Postorden.

Finalizado el recorrido del subárbol izquierdo se inicia la visita al subárbol derecho en


Postorden, es decir, se visita el 11 luego el 15 y se finaliza con la visita del nodo 12 que sería la
raíz de este subárbol.

Solo queda recorrer la raíz del árbol que para este caso es el número 10.

El resultado completo del recorrido en Postorden para el árbol de la imagen es:

3 – 7 – 5 – 11 – 15 – 12 – 10.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


71

Árbol binario de búsqueda (ABB)


Los árboles binarios de búsqueda, son un tipo especial de árbol binario cuya característica
radica en la forma ordenada de insertar sus elementos, facilitando así la búsqueda de un nodo en
particular. Para puntualizar aún más, se tratarán los árboles binarios de búsqueda, en los que se
tiene preestablecido un cierto orden, que seguramente ayudará a encontrar un cierto dato dentro de
un árbol con mucha rapidez.

La pregunta sería; ¿cómo es este orden prefijado o preestablecido? La respuesta es sencilla y


entenderlo es aún más, Solo se debe cumplir la condición que para cada nodo se tiene que:

la rama de la izquierda contendrá elementos menores.

la rama de la derecha contendrá elementos mayores.

Un ejemplo sería la forma más sencilla de explicarlo y comprenderlo. Parimos de la siguiente


gráfica del árbol binario de Búsqueda.

Gráfica de un árbol binario de búsqueda

Estructura de Datos - 4-6 ABB

Partiendo de la gráfica del árbol de la imagen 6 realizaremos los tres recorridos, conservando
el orden correspondiente para cada uno.

Recorrido en preorden: 20 – 13 – 9 – 6 – 10 – 18 – 14 – 17 – 32 – 26 – 24 – 29 – 36 – 34 – 40

Recorrido en inorden: 6 – 9 – 10 – 13 – 14 – 18 – 17 – 20 – 24 – 26 – 29 – 32 – 34 – 36 – 40

Recorrido en postorden: 6 – 10 – 9 – 14 – 17 – 18 – 13 – 24 – 29 – 26 – 34 – 40 – 36 – 32 –
20

Manual de Estructura de Datos Saúl Ernesto Coy Pop


72

4.3. Árboles AVL


Los árboles AVL son árboles BB donde todo nodo cumple la propiedad de equilibrado AVL:
La altura del subárbol izquierdo y del derecho no se diferencian en más de uno.
Se define factor de equilibrio de un nodo como:
Fe(nodo) = altura(derecho) – altura(izquierdo)
En un árbol AVL el factor de equilibrio de todo nodo es -1, 0 ó +1.
Tras la inserción o borrado de un elemento, sólo los ascendientes del nodo pueden sufrir un
cambio en su factor de equilibrio, y en todo caso sólo en una unidad.
Se añade una etapa donde se recorren los ascendientes. Si alguno está desequilibrado (+2 o -
2) se vuelve a equilibrar mediante operaciones denominadas rotaciones.

Estructura de Datos - 4-7 Árbol AVL

Manual de Estructura de Datos Saúl Ernesto Coy Pop


73

Altura logarítmica
Todo árbol binario con equilibrado AVL tiene altura logarítmica
Se define árbol de Fibonacci ( Fh ) como:
F-1 es el árbol vacío.
F0 es el árbol con un único nodo.
Fh es el árbol con subárbol izquierdo Fh-2 y derecho Fh-1
El árbol Fh tiene altura h y número de elementos:

Operaciones en Árbol AVL


Un árbol AVL es un árbol binario de búsqueda (ABB), ampliado con un campo que indica el
factor de equilibrio de cada nodo.
Las operaciones de acceso son idénticas a las de un ABB.
Las operaciones de inserción y borrado se realizan igual que en un ABB, salvo que se añade
una etapa posterior de reequilibrado.
El reequilibrado recorre los ascendientes del nodo que ha sufrido modificación, recalculando
sus factores de equilibrio y aplicando las rotaciones adecuadas cuando es necesario.
El recorrido se detiene al llegar al nodo raíz o cuando el subárbol del nodo actual no haya
sufrido cambios en altura respecto a la situación anterior a la operación.
Es necesario controlar el cambio de altura de los subárboles, dH, a lo largo del recorrido.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


74

Cambios en altura
En inserción (dH > 0), si un hijo ( y) incrementa su altura, el padre ( x) también la incrementa
si su factor de equilibrio era -1 o 0 (hijo izquierdo) o bien 0 o +1 (hijo derecho)
En borrado (dH < 0), si un hijo ( y) decrementa su altura, el padre ( x) también la decrementa
si su factor de equilibrio era -1 (hijo izquierdo) o +1 (hijo derecho)

Rotaciones
Una rotación es una reestructuración local de un subárbol BB que mantiene la propiedad de
ordenación.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


75

Rotaciones en AVL
Tras una operación de inserción o borrado, se recorren los ascendientes, recalculando
sus factores de equilibrio y teniendo en cuenta el cambio en altura del subárbol.
Es posible que en el recorrido el factor de equilibrio de algún nodo pasa a valer +2 ó -2
(desequilibrado).
En ese caso se aplica una determinada rotación que restablece el equilibrio del nodo (aunque
es posible que cambie la altura del nodo).
En un árbol AVL se necesitan 2 tipos de rotaciones (simples y dobles), en un sentido u otro
(izquierdas y derechas).
Teniendo en cuenta los distintos ajustes de factores de equilibrio y posibles resultados respecto
al cambio de altura, existen seis casos a considerar.

Rotación 2|1 (Simple derecha).


Rotación 2|0 (Simple derecha).
Rotación 2|-1 (Doble derecha).
Rotación -2|-1 (Simple izquierda).
Rotación -2|0 (Simple izquierda).
Rotación -2|1 (Doble izquierda).

Manual de Estructura de Datos Saúl Ernesto Coy Pop


76

4.4. Arboles B+
Los arboles B+ son una variante de los arboles B, se diferencian en que los arboles B+ toda
la información se encuentra almacenada en las hojas. En la raíz y en las páginas internas se
encuentran almacenado índices o claves para llegar a un dato.

Principales características de los arboles B+ de orden m son:


-La raíz almacena como mínimo un dato y como máximo m-1 datos.
-La página raíz tiene como mínimo dos descendientes.
-Las paginas intermedias tienen como mínimo (m-1) /2(Parte entera) datos.
-Las paginas intermedias tienen como máximo m-1 datos.
-Todas las paginas hojas tienen la misma altura
-La información se encuentra ordenada.
-Toda la información se encuentra almacenada en las páginas hoja, por lo que en las páginas
internas se puede duplicar la claves.

Ejemplo de un árbol B+ de orden 5:

Árbol B+ 4-1

Manual de Estructura de Datos Saúl Ernesto Coy Pop


77

I. Insercción en un árbol B+:

La Insercción en un árbol B+ es similar a la del árbol B se diferencia en el momento que una


página deja de cumplir la condición del número de datos almacenados. Para realizarla se debe
subir una copia de la clave mediana de los datos del nodo a la página padre, solo se duplica la
información cuando la clave que sube es de una página hoja.

Los pasos a seguir para una inserción son los siguientes:


1.Se ubica en la página raíz.
2.Se evalúa si es una página hoja
2.1. Si la respuesta es afirmativa, se evalúa si no sobrepasa los límites de datos.
2.1.1. Si la respuesta es afirmativa, entonces se procede a insertar el nuevo valor en lugar
del correspondiente.
2.1.2. Si la respuesta es negativa, se divide la página en dos, se sube una copia de la
mediana a la página padre, si la
página padre se encuentra llena se debe de partir igual y así el mismo proceso hasta
donde sea necesario, si este proceso llega hasta la raíz la altura del árbol aumenta en uno.
2.2. si no es hoja, se compara el elemento a insertar con cada uno de los valores almacenados
para encontrar la página descendiente donde proseguir la búsqueda. Se regresa al paso 1.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


78

Ejemplo de inserción:
-Insertar las siguientes claves a un árbol de orden 5: 10-27-29-17-25-21-15-31-13-51-20-24-48-
19-60-35-66

Manual de Estructura de Datos Saúl Ernesto Coy Pop


79

II. Eliminación:
La operación de eliminación en árboles-B+ es más simple que en árboles-B. Esto ocurre porque
las claves a eliminar siempre se encuentran en las páginas hojas. En general deben distinguirse
los siguientes casos:
Si al eliminar una clave, la cantidad de llaves queda mayor o igual que [m/2] entonces termina la
operación. Las claves de los nodos raíz o internos no se modifican por más que sean una copia de
la clave eliminada en las hojas.
Si al eliminar una clave, la cantidad de llaves queda menor que [m/2] entonces debe realizarse
una redistribución de claves, tanto en el índice como en las páginas hojas.
Ejemplo del caso 1:

Ejemplo del caso 2:

Manual de Estructura de Datos Saúl Ernesto Coy Pop


80

En resumen:
El árbol B+ mantiene varios órdenes. Dentro de cada nodo existe orden. Entre las hojas existe
orden. En los punteros dentro de un nodo existe orden.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


81

Ejemplos
1 Árbol Binario de Búsqueda (ABB) en C++

class ArbolABB {
private:
//// Clase local de Lista para Nodo de ArbolBinario:
class Nodo {
public:
// Constructor:
Nodo(const int dat, Nodo *izq=NULL, Nodo *der=NULL) :
dato(dat), izquierdo(izq), derecho(der) {}
// Miembros:
int dato;
Nodo *izquierdo;
Nodo *derecho;
};

// Punteros de la lista, para cabeza y nodo actual:


Nodo *raíz;
Nodo *actual;
int contador;
int altura;

public:
// Constructor y destructor básicos:
ArbolABB() : raíz(NULL), actual(NULL) {}
~ArbolABB() { Podar(raíz); }
// Insertar en árbol ordenado:

Manual de Estructura de Datos Saúl Ernesto Coy Pop


82

void Insertar(const int dat);


// Borrar un elemento del árbol:
void Borrar(const int dat);
// Función de búsqueda:
bool Buscar(const int dat);
// Comprobar si el árbol está vacío:
bool Vacio(Nodo *r) { return r==NULL; }
// Comprobar si es un nodo hoja:
bool EsHoja(Nodo *r) { return !r->derecho && !r->izquierdo; }
// Contar número de nodos:
const int NumeroNodos();
const int AlturaArbol();
// Calcular altura de un int:
int Altura(const int dat);
// Devolver referencia al int del nodo actual:
int &ValorActual() { return actual->dato; }
// Moverse al nodo raíz:
void Raiz() { actual = raíz; }
// Aplicar una función a cada elemento del árbol:
void InOrden(void (*func)(int&) , Nodo *nodo=NULL, bool r=true);
void PreOrden(void (*func)(int&) , Nodo *nodo=NULL, bool r=true);
void PostOrden(void (*func)(int&) , Nodo *nodo=NULL, bool r=true);
private:
// Funciones auxiliares
void Podar(Nodo* &);
void auxContador(Nodo*);
void auxAltura(Nodo*, int);
};

Manual de Estructura de Datos Saúl Ernesto Coy Pop


83

2 Árbol Binario de Búsqueda (ABB) en C++


#include <iostream.h>
struct nodo{ int valor;
struct nodo *izq;
struct nodo *der;
};
typedef struct nodo * ABB;

void inserta(ABB &, int);


void verArbol(ABB, int);
int busquedaRec(ABB, int);
int busquedaIter(ABB, int);

void main(void)
{ ABB arbol=NULL; // inicializa árbol
int opc, n;
do
{ cout<<endl<<"*** Menu ***"<<endl;
cout<<"1. Insertar"<<endl;
cout<<"2. Búsqueda recursiva"<<endl;
cout<<"3. Búsqueda iterativa"<<endl;
cout<<"4. Ver árbol"<<endl;
cout<<"5. Salir"<<endl;
cout <<"Ingrese opción:";
cin>>opc;
switch(opc)
{ case 1: cout <<"Valor a insertar: "; cin>>n;
inserta(arbol, n);
break;
case 2: cout <<"Valor a buscar recursivamente: "; cin>>n;
if(busquedaRec(arbol, n)==1)
cout<<"Elemento encontrado";
else cout<<"Elemento no encontrado";
break;
case 3: cout <<"Valor a buscar iterativamente: "; cin>>n;
if(busquedaIter(arbol, n)==1)
cout<<"Elemento encontrado";
else cout<<"Elemento no encontrado";
break;
case 4: verArbol(arbol, 0); // imprime arbol
break;
} // fin de switch
}while(opc!=5);
} // fin de main( )
void inserta(ABB &arbol, int n)
{ if (arbol==NULL)
{ arbol = new (struct nodo);
arbol->valor = n;
arbol->izq = NULL;
arbol->der = NULL;
}
else if (n<arbol->valor) inserta(arbol->izq, n);
else if (n>arbol->valor) inserta(arbol->der, n);
}
void verArbol(ABB arbol, int nro)
{ int i;

Manual de Estructura de Datos Saúl Ernesto Coy Pop


84

if (arbol==NULL) return;
verArbol(arbol->der, nro+1);
for (i=0; i<nro; i++) cout <<" ";
cout <<arbol->valor<<endl;
verArbol(arbol->izq, nro+1);
}

int busquedaRec(ABB arbol, int dato)


{ int r=0; // 0 indica no lo encontré
if (arbol==NULL) return r; // no lo encontré
if (dato < arbol->valor )
r=busquedaRec(arbol->izq, dato);
else if (dato > arbol->valor )
r=busquedaRec(arbol->der, dato);
else r=1; //son iguales lo encontré
return r;
}

int busquedaIter(ABB arbol, int dato)


{ while(arbol!=NULL)
{ if (dato<arbol->valor) arbol=arbol->izq;
else if (dato>arbol->valor) arbol=arbol->der;
else return 1; // lo encontré
}
return 0; // no lo encontré
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


85

3 Árbol AVL – Equilibrar partiendo de un nodo


/* Equilibrar árbol AVL partiendo de un nodo*/
void Equilibrar(Arbol *a, pNodo nodo, int rama, int nuevo) {
int salir = FALSE;

/* Recorrer camino inverso actualizando valores de FE: */


while(nodo && !salir) {
if(nuevo)
if(rama == IZQUIERDO) nodo->FE--; /* Depende de si añadimos ... */
else nodo->FE++;
else
if(rama == IZQUIERDO) nodo->FE++; /* ... o borramos */
else nodo->FE--;
if(nodo->FE == 0) salir = TRUE; /* La altura de las rama que
empieza en nodo no ha variado,
salir de Equilibrar */
else if(nodo->FE == -2) { /* Rotar a derechas y salir: */
if(nodo->izquierdo->FE == 1) RDD(a, nodo); /* Rotación doble */
else RSD(a, nodo); /* Rotación simple */
salir = TRUE;
}
else if(nodo->FE == 2) { /* Rotar a izquierdas y salir: */
if(nodo->derecho->FE == -1) RDI(a, nodo); /* Rotación doble */
else RSI(a, nodo); /* Rotación simple */
salir = TRUE;
}
if(nodo->padre)
if(nodo->padre->derecho == nodo) rama = DERECHO; else rama = IZQUIERDO;

Manual de Estructura de Datos Saúl Ernesto Coy Pop


86

nodo = nodo->padre; /* Calcular FE, siguiente nodo del camino. */


}
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


87

4 Árbol AVL – Rotación Simple a la derecha


/* Rotación simple a derecha */
void RSD(Arbol *a, pNodo nodo) {
pNodo Padre = nodo->padre;
pNodo P = nodo;
pNodo Q = P->izquierdo;
pNodo B = Q->derecho;

if(Padre)
if(Padre->derecho == P) Padre->derecho = Q;
else Padre->izquierdo = Q;
else *a = Q;

/* Reconstruir árbol: */
P->izquierdo = B;
Q->derecho = P;

/* Reasignar padres: */
P->padre = Q;
if(B) B->padre = P;
Q->padre = Padre;

/* Ajustar valores de FE: */


P->FE = 0;
Q->FE = 0;
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


88

5 Árbol AVL – Rotación doble a la derecha


/* Rotación doble a derechas */
void RDD(Arbol *raíz, Arbol nodo) {
pNodo Padre = nodo->padre;
pNodo P = nodo;
pNodo Q = P->izquierdo;
pNodo R = Q->derecho;
pNodo B = R->izquierdo;
pNodo C = R->derecho;

if(Padre)
if(Padre->derecho == nodo) Padre->derecho = R;
else Padre->izquierdo = R;
else *raíz = R;

/* Reconstruir árbol: */
Q->derecho = B;
P->izquierdo = C;
R->izquierdo = Q;
R->derecho = P;

/* Reasignar padres: */
R->padre = Padre;
P->padre = Q->padre = R;
if(B) B->padre = Q;
if(C) C->padre = P;

/* Ajustar valores de FE: */

Manual de Estructura de Datos Saúl Ernesto Coy Pop


89

switch(R->FE) {
case -1: Q->FE = 0; P->FE = 1; break;
case 0: Q->FE = 0; P->FE = 0; break;
case 1: Q->FE = -1; P->FE = 0; break;
}
R->FE = 0;
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


90

6 Árbol y Recorridos en C++

#include <iostream.h>
#include <stdlib.h>

struct nodo{ int valor;


struct nodo *izq;
struct nodo *der;
};
typedef struct nodo * ABB;

void inserta(ABB &, int);


void preorden (ABB);
void postorden(ABB);
void enorden (ABB);
void verArbol(ABB, int);
void main(void)

{ ABB arbol=NULL;
int n, x;
cout<<"Cantidad de elementos del arbol: ";
cin>>n;
for (int i=0; i<n; i++)
{ cout<<"Ingrese nodo número "<<i<<": ";
cin>>x;
inserta(arbol,x);
}
verArbol(arbol, 0);
cout<< endl<<"Preorden : "; preorden(arbol);
cout<< endl<<"Postorden : "; postorden(arbol);
cout<< endl<<"Enorden : "; enorden(arbol);

system("pause");
}

void inserta(ABB &arbol, int x)


{ if (arbol==NULL)
{ arbol=new (struct nodo);
arbol->valor = x;
arbol->izq = NULL;
arbol->der = NULL;
}
else if (x<arbol->valor) inserta(arbol->izq,x);
else if (x>arbol->valor) inserta(arbol->der,x);
} // si es igual no se inserta

void preorden(ABB arbol)


{ if(arbol!=NULL)
{ cout<<arbol->valor<<" ";
preorden(arbol->izq);
preorden(arbol->der);
}
}

void enorden(ABB arbol)

Manual de Estructura de Datos Saúl Ernesto Coy Pop


91

{ if (arbol!=NULL)
{ enorden(arbol->izq);
cout<<arbol->valor<<" ";
enorden(arbol->der);
}
}
void postorden(ABB arbol)
{ if (arbol!=NULL)
{ postorden(arbol->izq);
postorden(arbol->der);
cout<<arbol->valor<<" ";
}
}
void verArbol(ABB arbol, int nro)
{ int i;
if (arbol==NULL) return;
verArbol(arbol->der, nro+1);
for (i=0; i<nro; i++) cout <<" ";
cout <<arbol->valor<<endl;
verArbol(arbol->izq, nro+1);
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


92

7 Arbol AVL en C++


// Arbol AVL en C++
// (C) Mayo 2002, Salvador Pozo
// C con Clase: http://c.conclase.net

#include <iostream>
using namespace std;

class AVL;

// Clase Nodo de Arbol AVL:


class Nodo {
public:
// Constructor:
Nodo(const int dat, Nodo *pad=NULL, Nodo *izq=NULL, Nodo *der=NULL) :
dato(dat), padre(pad), izquierdo(izq), derecho(der), FE(0) {}
// Miembros:
int dato;
int FE;
Nodo *izquierdo;
Nodo *derecho;
Nodo *padre;
friend class AVL;
};

class AVL {
private:
enum {IZQUIERDO, DERECHO};
// Punteros de la lista, para cabeza y nodo actual:
Nodo *raiz;
Nodo *actual;
int contador;
int altura;

public:
// Constructor y destructor básicos:
AVL() : raiz(NULL), actual(NULL) {}
~AVL() { Podar(raiz); }
// Insertar en árbol ordenado:
void Insertar(const int dat);
// Borrar un elemento del árbol:
void Borrar(const int dat);
// Función de búsqueda:
bool Buscar(const int dat);
// Comprobar si el árbol está vacío:
bool Vacio(Nodo *r) { return r==NULL; }
// Comprobar si es un nodo hoja:
bool EsHoja(Nodo *r) { return !r->derecho && !r->izquierdo; }
// Contar número de nodos:
const int NumeroNodos();
const int AlturaArbol();
// Calcular altura de un dato:
int Altura(const int dat);
// Devolver referencia al dato del nodo actual:
int &ValorActual() { return actual->dato; }
// Moverse al nodo raiz:

Manual de Estructura de Datos Saúl Ernesto Coy Pop


93

void Raiz() { actual = raiz; }


// Aplicar una función a cada elemento del árbol:
void InOrden(void (*func)(int&, int) , Nodo *nodo=NULL, bool r=true);
void PreOrden(void (*func)(int&, int) , Nodo *nodo=NULL, bool r=true);
void PostOrden(void (*func)(int&, int) , Nodo *nodo=NULL, bool r=true);
private:
// Funciones de equilibrado:
void Equilibrar(Nodo *nodo, int, bool);
void RSI(Nodo* nodo);
void RSD(Nodo* nodo);
void RDI(Nodo* nodo);
void RDD(Nodo* nodo);
// Funciones auxiliares
void Podar(Nodo* &);
void auxContador(Nodo*);
void auxAltura(Nodo*, int);
};

// Poda: borrar todos los nodos a partir de uno, incluido


void AVL::Podar(Nodo* &nodo)
{
// Algoritmo recursivo, recorrido en postorden
if(nodo) {
Podar(nodo->izquierdo); // Podar izquierdo
Podar(nodo->derecho); // Podar derecho
delete nodo; // Eliminar nodo
nodo = NULL;
}
}

// Insertar un dato en el árbol AVL


void AVL::Insertar(const int dat)
{
Nodo *padre = NULL;

cout << "Insertar: " << dat << endl;


actual = raiz;
// Buscar el dato en el árbol, manteniendo un puntero al nodo padre
while(!Vacio(actual) && dat != actual->dato) {
padre = actual;
if(dat > actual->dato) actual = actual->derecho;
else if(dat < actual->dato) actual = actual->izquierdo;
}

// Si se ha encontrado el elemento, regresar sin insertar


if(!Vacio(actual)) return;
// Si padre es NULL, entonces el árbol estaba vacío, el nuevo nodo será
// el nodo raiz
if(Vacio(padre)) raiz = new Nodo(dat);
// Si el dato es menor que el que contiene el nodo padre, lo insertamos
// en la rama izquierda
else if(dat < padre->dato) {
padre->izquierdo = new Nodo(dat, padre);
Equilibrar(padre, IZQUIERDO, true);
}
// Si el dato es mayor que el que contiene el nodo padre, lo insertamos
// en la rama derecha

Manual de Estructura de Datos Saúl Ernesto Coy Pop


94

else if(dat > padre->dato) {


padre->derecho = new Nodo(dat, padre);
Equilibrar(padre, DERECHO, true);
}
}

// Equilibrar árbol AVL partiendo del nodo nuevo


void AVL::Equilibrar(Nodo *nodo, int rama, bool nuevo)
{
bool salir = false;

// Recorrer camino inverso actualizando valores de FE:


while(nodo && !salir) {
if(nuevo)
if(rama == IZQUIERDO) nodo->FE--; // Depende de si añadimos ...
else nodo->FE++;
else
if(rama == IZQUIERDO) nodo->FE++; // ... o borramos
else nodo->FE--;
if(nodo->FE == 0) salir = true; // La altura de las rama que
// empieza en nodo no ha variado,
// salir de Equilibrar
else if(nodo->FE == -2) { // Rotar a derechas y salir:
if(nodo->izquierdo->FE == 1) RDD(nodo); // Rotación doble
else RSD(nodo); // Rotación simple
salir = true;
}
else if(nodo->FE == 2) { // Rotar a izquierdas y salir:
if(nodo->derecho->FE == -1) RDI(nodo); // Rotación doble
else RSI(nodo); // Rotación simple
salir = true;
}
if(nodo->padre)
if(nodo->padre->derecho == nodo) rama = DERECHO; else rama =
IZQUIERDO;
nodo = nodo->padre; // Calcular FE, siguiente nodo del camino.
}
}

// Rotación doble a derechas


void AVL::RDD(Nodo* nodo)
{
cout << "RDD" << endl;
Nodo *Padre = nodo->padre;
Nodo *P = nodo;
Nodo *Q = P->izquierdo;
Nodo *R = Q->derecho;
Nodo *B = R->izquierdo;
Nodo *C = R->derecho;

if(Padre)
if(Padre->derecho == nodo) Padre->derecho = R;
else Padre->izquierdo = R;
else raiz = R;

// Reconstruir árbol:
Q->derecho = B;

Manual de Estructura de Datos Saúl Ernesto Coy Pop


95

P->izquierdo = C;
R->izquierdo = Q;
R->derecho = P;

// Reasignar padres:
R->padre = Padre;
P->padre = Q->padre = R;
if(B) B->padre = Q;
if(C) C->padre = P;

// Ajustar valores de FE:


switch(R->FE) {
case -1: Q->FE = 0; P->FE = 1; break;
case 0: Q->FE = 0; P->FE = 0; break;
case 1: Q->FE = -1; P->FE = 0; break;
}
R->FE = 0;
}

// Rotación doble a izquierdas


void AVL::RDI(Nodo* nodo)
{
cout << "RDI" << endl;
Nodo *Padre = nodo->padre;
Nodo *P = nodo;
Nodo *Q = P->derecho;
Nodo *R = Q->izquierdo;
Nodo *B = R->izquierdo;
Nodo *C = R->derecho;

if(Padre)
if(Padre->derecho == nodo) Padre->derecho = R;
else Padre->izquierdo = R;
else raiz = R;

// Reconstruir árbol:
P->derecho = B;
Q->izquierdo = C;
R->izquierdo = P;
R->derecho = Q;

// Reasignar padres:
R->padre = Padre;
P->padre = Q->padre = R;
if(B) B->padre = P;
if(C) C->padre = Q;

// Ajustar valores de FE:


switch(R->FE) {
case -1: P->FE = 0; Q->FE = 1; break;
case 0: P->FE = 0; Q->FE = 0; break;
case 1: P->FE = -1; Q->FE = 0; break;
}
R->FE = 0;
}

// Rotación simple a derechas

Manual de Estructura de Datos Saúl Ernesto Coy Pop


96

void AVL::RSD(Nodo* nodo)


{
cout << "RSD" << endl;
Nodo *Padre = nodo->padre;
Nodo *P = nodo;
Nodo *Q = P->izquierdo;
Nodo *B = Q->derecho;

if(Padre)
if(Padre->derecho == P) Padre->derecho = Q;
else Padre->izquierdo = Q;
else raiz = Q;

// Reconstruir árbol:
P->izquierdo = B;
Q->derecho = P;

// Reasignar padres:
P->padre = Q;
if(B) B->padre = P;
Q->padre = Padre;

// Ajustar valores de FE:


P->FE = 0;
Q->FE = 0;
}

// Rotación simple a izquierdas


void AVL::RSI(Nodo* nodo)
{
cout << "RSI" << endl;
Nodo *Padre = nodo->padre;
Nodo *P = nodo;
Nodo *Q = P->derecho;
Nodo *B = Q->izquierdo;

if(Padre)
if(Padre->derecho == P) Padre->derecho = Q;
else Padre->izquierdo = Q;
else raiz = Q;

// Reconstruir árbol:
P->derecho = B;
Q->izquierdo = P;

// Reasignar padres:
P->padre = Q;
if(B) B->padre = P;
Q->padre = Padre;

// Ajustar valores de FE:


P->FE = 0;
Q->FE = 0;
}

// Eliminar un elemento de un árbol AVL


void AVL::Borrar(const int dat)

Manual de Estructura de Datos Saúl Ernesto Coy Pop


97

{
Nodo *padre = NULL;
Nodo *nodo;
int aux;

actual = raiz;
// Mientras sea posible que el valor esté en el árbol
while(!Vacio(actual)) {
if(dat == actual->dato) { // Si el valor está en el nodo actual
if(EsHoja(actual)) { // Y si además es un nodo hoja: lo borramos
if(padre) // Si tiene padre (no es el nodo raiz)
// Anulamos el puntero que le hace referencia
if(padre->derecho == actual) padre->derecho = NULL;
else if(padre->izquierdo == actual) padre->izquierdo = NULL;
delete actual; // Borrar el nodo
actual = NULL;
// El nodo padre del actual puede ser ahora un nodo hoja, por lo
tanto su
// FE es cero, pero debemos seguir el camino a partir de su
padre, si existe.
if((padre->derecho == actual && padre->FE == 1) ||
(padre->izquierdo == actual && padre->FE == -1)) {
padre->FE = 0;
actual = padre;
padre = actual->padre;
}
if(padre)
if(padre->derecho == actual) Equilibrar(padre, DERECHO,
false);
else Equilibrar(padre, IZQUIERDO,
false);
return;
}
else { // Si el valor está en el nodo actual, pero no es hoja
// Buscar nodo
padre = actual;
// Buscar nodo más izquierdo de rama derecha
if(actual->derecho) {
nodo = actual->derecho;
while(nodo->izquierdo) {
padre = nodo;
nodo = nodo->izquierdo;
}
}
// O buscar nodo más derecho de rama izquierda
else {
nodo = actual->izquierdo;
while(nodo->derecho) {
padre = nodo;
nodo = nodo->derecho;
}
}
// Intercambiar valores de no a borrar u nodo encontrado
// y continuar, cerrando el bucle. El nodo encontrado no tiene
// por qué ser un nodo hoja, cerrando el bucle nos aseguramos
// de que sólo se eliminan nodos hoja.
aux = actual->dato;

Manual de Estructura de Datos Saúl Ernesto Coy Pop


98

actual->dato = nodo->dato;
nodo->dato = aux;
actual = nodo;
}
}
else { // Todavía no hemos encontrado el valor, seguir buscándolo
padre = actual;
if(dat > actual->dato) actual = actual->derecho;
else if(dat < actual->dato) actual = actual->izquierdo;
}
}
}

// Recorrido de árbol en inorden, aplicamos la función func, que tiene


// el prototipo:
// void func(int&, int);
void AVL::InOrden(void (*func)(int&, int) , Nodo *nodo, bool r)
{
if(r) nodo = raiz;
if(nodo->izquierdo) InOrden(func, nodo->izquierdo, false);
func(nodo->dato, nodo->FE);
if(nodo->derecho) InOrden(func, nodo->derecho, false);
}

// Recorrido de árbol en preorden, aplicamos la función func, que tiene


// el prototipo:
// void func(int&, int);
void AVL::PreOrden(void (*func)(int&, int), Nodo *nodo, bool r)
{
if(r) nodo = raiz;
func(nodo->dato, nodo->FE);
if(nodo->izquierdo) PreOrden(func, nodo->izquierdo, false);
if(nodo->derecho) PreOrden(func, nodo->derecho, false);
}

// Recorrido de árbol en postorden, aplicamos la función func, que tiene


// el prototipo:
// void func(int&, int);
void AVL::PostOrden(void (*func)(int&, int), Nodo *nodo, bool r)
{
if(r) nodo = raiz;
if(nodo->izquierdo) PostOrden(func, nodo->izquierdo, false);
if(nodo->derecho) PostOrden(func, nodo->derecho, false);
func(nodo->dato, nodo->FE);
}

// Buscar un valor en el árbol


bool AVL::Buscar(const int dat)
{
actual = raiz;

// Todavía puede aparecer, ya que quedan nodos por mirar


while(!Vacio(actual)) {
if(dat == actual->dato) return true; // dato encontrado
else if(dat > actual->dato) actual = actual->derecho; // Seguir
else if(dat < actual->dato) actual = actual->izquierdo;
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


99

return false; // No está en árbol


}

// Calcular la altura del nodo que contiene el dato dat


int AVL::Altura(const int dat)
{
int altura = 0;
actual = raiz;

// Todavía puede aparecer, ya que quedan nodos por mirar


while(!Vacio(actual)) {
if(dat == actual->dato) return altura; // dato encontrado
else {
altura++; // Incrementamos la altura, seguimos buscando
if(dat > actual->dato) actual = actual->derecho;
else if(dat < actual->dato) actual = actual->izquierdo;
}
}
return -1; // No está en árbol
}

// Contar el número de nodos


const int AVL::NumeroNodos()
{
contador = 0;

auxContador(raiz); // FUnción auxiliar


return contador;
}

// Función auxiliar para contar nodos. Función recursiva de recorrido en


// preorden, el proceso es aumentar el contador
void AVL::auxContador(Nodo *nodo)
{
contador++; // Otro nodo
// Continuar recorrido
if(nodo->izquierdo) auxContador(nodo->izquierdo);
if(nodo->derecho) auxContador(nodo->derecho);
}

// Calcular la altura del árbol, que es la altura del nodo de mayor altura.
const int AVL::AlturaArbol()
{
altura = 0;

auxAltura(raiz, 0); // Función auxiliar


return altura;
}

// Función auxiliar para calcular altura. Función recursiva de recorrido en


// postorden, el proceso es actualizar la altura sólo en nodos hojas de mayor
// altura de la máxima actual
void AVL::auxAltura(Nodo *nodo, int a)
{
// Recorrido postorden
if(nodo->izquierdo) auxAltura(nodo->izquierdo, a+1);
if(nodo->derecho) auxAltura(nodo->derecho, a+1);

Manual de Estructura de Datos Saúl Ernesto Coy Pop


100

// Proceso, si es un nodo hoja, y su altura es mayor que la actual del


// árbol, actualizamos la altura actual del árbol
if(EsHoja(nodo) && a > altura) altura = a;
}

// Función de prueba para recorridos del árbol


void Mostrar(int &d, int FE)
{
cout << d << "(" << FE << "),";
}

int main()
{
// Un árbol de enteros
AVL ArbolInt;

// Inserción de nodos en árbol:


/* ArbolInt.Insertar(15);
ArbolInt.Insertar(4);
ArbolInt.Insertar(20);
ArbolInt.Insertar(3);
ArbolInt.Insertar(25);
ArbolInt.Insertar(6);
ArbolInt.Insertar(8);*/
ArbolInt.Insertar(1);
ArbolInt.Insertar(2);
ArbolInt.Insertar(3);
ArbolInt.Insertar(4);
ArbolInt.Insertar(5);
ArbolInt.Insertar(6);
ArbolInt.Insertar(7);
ArbolInt.Insertar(8);
ArbolInt.Insertar(9);
ArbolInt.Insertar(10);
ArbolInt.Insertar(11);
ArbolInt.Insertar(12);
ArbolInt.Insertar(13);
ArbolInt.Insertar(14);
ArbolInt.Insertar(15);
ArbolInt.Insertar(16);

cout << "Altura de arbol " << ArbolInt.AlturaArbol() << endl;

// Mostrar el árbol en tres ordenes distintos:


cout << "InOrden: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
cout << "PreOrden: ";
ArbolInt.PreOrden(Mostrar);
cout << endl;
cout << "PostOrden: ";
ArbolInt.PostOrden(Mostrar);
cout << endl;

ArbolInt.Borrar(8);
ArbolInt.Borrar(11);

Manual de Estructura de Datos Saúl Ernesto Coy Pop


101

cout << "Altura de arbol " << ArbolInt.AlturaArbol() << endl;

// Mostrar el árbol en tres ordenes distintos:


cout << "InOrden: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
cout << "PreOrden: ";
ArbolInt.PreOrden(Mostrar);
cout << endl;
cout << "PostOrden: ";
ArbolInt.PostOrden(Mostrar);
cout << endl;

/* // Borraremos algunos elementos:


cout << "N nodos: " << ArbolInt.NumeroNodos() << endl;
ArbolInt.Borrar(5);
cout << "Borrar 5: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
ArbolInt.Borrar(8);
cout << "Borrar 8: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
ArbolInt.Borrar(15);
cout << "Borrar 15: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
ArbolInt.Borrar(245);
cout << "Borrar 245: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
ArbolInt.Borrar(4);
cout << "Borrar 4: ";
ArbolInt.InOrden(Mostrar);
ArbolInt.Borrar(17);
cout << endl;
cout << "Borrar 17: ";
ArbolInt.InOrden(Mostrar);
cout << endl;

// Veamos algunos parámetros


cout << "N nodos: " << ArbolInt.NumeroNodos() << endl;
cout << "Altura de 1 " << ArbolInt.Altura(1) << endl;
cout << "Altura de 10 " << ArbolInt.Altura(10) << endl;
cout << "Altura de arbol " << ArbolInt.AlturaArbol() << endl;

cin.get();

// Arbol de cadenas:
AVL<Cadena> ArbolCad;

// Inserción de valores:
ArbolCad.Insertar("Hola");
ArbolCad.Insertar(",");
ArbolCad.Insertar("somos");
ArbolCad.Insertar("buenos");
ArbolCad.Insertar("programadores");

Manual de Estructura de Datos Saúl Ernesto Coy Pop


102

// Mostrar en diferentes ordenes:


cout << "InOrden: ";
ArbolCad.InOrden(Mostrar);
cout << endl;
cout << "PreOrden: ";
ArbolCad.PreOrden(Mostrar);
cout << endl;
cout << "PostOrden: ";
ArbolCad.PostOrden(Mostrar);
cout << endl;

// Borrar "buenos":
ArbolCad.Borrar("buenos");
cout << "Borrar 'buenos': ";
ArbolCad.InOrden(Mostrar);
cout << endl; */
cin.get();
return 0;
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


103

Recursos de Aprendizaje
Podemos encontrar los siguientes recursos, enlaces de videos y/o páginas en donde
encontraremos más información y ejemplos sobre los temas.

Árbol Teoría (Java)


https://youtu.be/w4aYNHmkOQ4?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Árbol Binario (Java)


https://youtu.be/JXckd1_XdAU?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Árbol Binario de Búsqueda, Creación e inserción (Java)


https://youtu.be/ZKnwBJ8q2TE?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Árbol Binario de Búsqueda, Recorrido Inorden(Java)


https://youtu.be/l8XPkY_q4Qs?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Árbol Binario de Búsqueda, Recorrido Preorden(Java)


https://youtu.be/Nz-9ZQrhgO0?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Árbol Binario de Búsqueda, Recorrido Postorden(Java)


https://youtu.be/52mLzH97gYA?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Árbol Binario de Búsqueda, Buscar un nodo(Java)


https://youtu.be/SNU7J3kkrFY?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Árbol Binario de Búsqueda, Eliminar un nodo(Java)


https://youtu.be/Av1IWdWMDlY?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Manual de Estructura de Datos Saúl Ernesto Coy Pop


104

5. Tablas Hash
5.1. Funcionamiento
Las operaciones básicas implementadas en las tablas hash son: inserción (llave, valor),
búsqueda(llave) que devuelve valor, la mayoría de las implementaciones también incluyen
borrar(llave).

También se pueden ofrecer funciones como iteración en la tabla, crecimiento y vaciado. Algunas
tablas hash permiten almacenar múltiples valores bajo la misma clave.

Para usar una tabla hash se necesita:

Una estructura de acceso directo (normalmente un array).

Una estructura de datos con una clave

Una función resumen (hash) cuyo dominio sea el espacio de claves y su imagen (o rango) los
números naturales.

Inserción
Para almacenar un elemento en la tabla hash se ha de convertir su clave a un número. Esto se
consigue aplicando la función resumen (hash) a la clave del elemento.

El resultado de la función resumen ha de mapearse al espacio de direcciones del vector que se


emplea como soporte, lo cual se consigue con la función módulo. Tras este paso se obtiene un
índice válido para la tabla.

El elemento se almacena en la posición de la tabla obtenido en el paso anterior.

Si en la posición de la tabla ya había otro elemento, se ha producido una colisión. Este problema
se puede solucionar asociando una lista a cada posición de la tabla, aplicando otra función o
buscando el siguiente elemento libre. Estas posibilidades han de considerarse a la hora de recuperar
los datos.

Búsqueda
Para recuperar los datos, es necesario únicamente conocer la clave del elemento, a la cual se le
aplica la función resumen.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


105

El valor obtenido se mapea al espacio de direcciones de la tabla.

Si el elemento existente en la posición indicada en el paso anterior tiene la misma clave que la
empleada en la búsqueda, entonces es el deseado. Si la clave es distinta, se ha de buscar el elemento
según la técnica empleada para resolver el problema de las colisiones al almacenar el elemento.

Prácticas recomendadas para las funciones hash


Una buena función hash es esencial para el buen rendimiento de una tabla hash. Las colisiones son
generalmente resueltas por algún tipo de búsqueda lineal, así que, si la función tiende a generar
valores similares, las búsquedas resultantes se vuelven lentas.

En una función hash ideal, el cambio de un simple bit en la llave (incluyendo el hacer la llave más
larga o más corta) debería cambiar la mitad de los bits del hash, y este cambio debería ser
independiente de los cambios provocados por otros bits de la llave. Como una función hash puede
ser difícil de diseñar, o computacionalmente cara de ejecución, se han invertido muchos esfuerzos
en el desarrollo de estrategias para la resolución de colisiones que mitiguen el mal rendimiento del
hasheo. Sin embargo, ninguna de estas estrategias es tan efectiva como el desarrollo de una buena
función hash de principio.

Es deseable utilizar la misma función hash para arrays de cualquier tamaño concebible. Para esto,
el índice de su ubicación en el array de la tabla hash se calcula generalmente en dos pasos:

1. Un valor hash genérico es calculado, llenando un entero natural de máquina.

2. Este valor es reducido a un índice válido en el vector encontrando su módulo con respecto al
tamaño del array.

El tamaño del vector de las tablas hash es con frecuencia un número primo. Esto se hace con el
objetivo de evitar la tendencia de que los hash de enteros grandes tengan divisores comunes con
el tamaño de la tabla hash, lo que provocaría colisiones tras el cálculo del módulo. Sin embargo,
el uso de una tabla de tamaño primo no es un sustituto a una buena función hash.

Un problema bastante común que ocurre con las funciones hash es el aglomeramiento. El
aglomeramiento ocurre cuando la estructura de la función hash provoca que llaves usadas
comúnmente tiendan a caer muy cerca unas de otras o incluso consecutivamente en la tabla hash.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


106

Esto puede degradar el rendimiento de manera significativa, cuando la tabla se llena usando ciertas
estrategias de resolución de colisiones, como el sondeo lineal.

Cuando se depura el manejo de las colisiones en una tabla hash, suele ser útil usar una función
hash que devuelva siempre un valor constante, como 1, que cause colisión en cada inserción.

Funciones Hash más usadas:


1. Hash de División:

Dado un diccionario D, se fija un número m >= |D| (m mayor o igual al tamaño del diccionario) y
que sea primo no cercano a potencia de 2 o de 10. Siendo k la clave a buscar y h(k) la función
hash, se tiene h(k)=k % m (Resto de la división k/m).

2. Hash de Multiplicación

Si por alguna razón, se necesita una tabla hash con tantos elementos o punteros como una potencia
de 2 o de 10, será mejor usar una función hash de multiplicación, independiente del tamaño de la
tabla. Se escoge un tamaño de tabla m >= |D| (m mayor o igual al tamaño del diccionario) y un
cierto número irracional φ (normalmente se usa 1+5^(1/2)/2 o 1-5^(1/2)/2). De este modo se define
h(k)= Suelo (m*Parte fraccionaria(k*φ))

5.2. Resolución de colisiones


Si dos llaves generan un hash apuntando al mismo índice, los registros correspondientes no pueden
ser almacenados en la misma posición. En estos casos, cuando una casilla ya está ocupada,
debemos encontrar otra ubicación donde almacenar el nuevo registro, y hacerlo de tal manera que
podamos encontrarlo cuando se requiera.

Para dar una idea de la importancia de una buena estrategia de resolución de colisiones, considérese
el siguiente resultado, derivado de la paradoja de las fechas de nacimiento. Aun cuando
supongamos que el resultado de nuestra función hash genera índices aleatorios distribuidos
uniformemente en todo el vector, e incluso para vectores de 1 millón de entradas, hay un 95% de
posibilidades de que al menos una colisión ocurra antes de alcanzar los 2.500 registros.

Hay varias técnicas de resolución de colisiones, pero las más populares


son encadenamiento y direccionamiento abierto.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


107

Direccionamiento Cerrado, Encadenamiento separado o Hashing abierto


En la técnica más simple de encadenamiento, cada casilla en el array referencia una lista de los
registros insertados que colisionan en la misma casilla. La inserción consiste en encontrar la casilla
correcta y agregar al final de la lista correspondiente. El borrado consiste en buscar y quitar de la
lista.

La técnica de encadenamiento tiene ventajas sobre direccionamiento abierto. Primero el borrado


es simple y segundo el crecimiento de la tabla puede ser pospuesto durante mucho más tiempo
dado que el rendimiento disminuye mucho más lentamente incluso cuando todas las casillas ya
están ocupadas. De hecho, muchas tablas hash encadenadas pueden no requerir crecimiento nunca,
dado que la degradación de rendimiento es lineal en la medida que se va llenando la tabla. Por
ejemplo, una tabla hash encadenada con dos veces el número de elementos recomendados, será
dos veces más lenta en promedio que la misma tabla a su capacidad recomendada.

Las tablas hash encadenadas heredan las desventajas de las listas ligadas. Cuando se almacenan
cantidades de información pequeñas, el gasto extra de las listas ligadas puede ser significativo.
También los viajes a través de las listas tienen un rendimiento de caché muy pobre.

Otras estructuras de datos pueden ser utilizadas para el encadenamiento en lugar de las listas
ligadas. Al usarárboles auto-balanceables, por ejemplo, el tiempo teórico del peor de los casos
disminuye de O(n) a O(log n). Sin embargo, dado que se supone que cada lista debe ser pequeña,
esta estrategia es normalmente ineficiente a menos que la tabla hash sea diseñada para correr a
máxima capacidad o existan índices de colisión particularmente grandes. También se pueden
utilizar vectores dinámicos para disminuir el espacio extra requerido y mejorar el rendimiento del
caché cuando los registros son pequeños.

Direccionamiento abierto o Hashing cerrado


Las tablas hash de direccionamiento abierto pueden almacenar los registros directamente en el
array. Las colisiones se resuelven mediante un sondeo del array, en el que se buscan diferentes
localidades del array (secuencia de sondeo) hasta que el registro es encontrado o se llega a una
casilla vacía, indicando que no existe esa llave en la tabla.

Las secuencias de sondeo más socorridas incluyen:

Manual de Estructura de Datos Saúl Ernesto Coy Pop


108

sondeo lineal

en el que el intervalo entre cada intento es constante (frecuentemente 1).

sondeo cuadrático

en el que el intervalo entre los intentos aumenta linealmente (por lo que los índices son
descritos por una función cuadrática), y

doble hasheo

en el que el intervalo entre intentos es constante para cada registro pero es calculado por
otra función hash.

El sondeo lineal ofrece el mejor rendimiento del caché, pero es más sensible al aglomeramiento,
en tanto que el doble hasheo tiene pobre rendimiento en el caché pero elimina el problema de
aglomeramiento. El sondeo cuadrático se sitúa en medio. El doble hasheo también puede requerir
más cálculos que las otras formas de sondeo.

Una influencia crítica en el rendimiento de una tabla hash de direccionamiento abierto es el


porcentaje de casillas usadas en el array. Conforme el array se acerca al 100% de su capacidad, el
número de saltos requeridos por el sondeo puede aumentar considerablemente. Una vez que se
llena la tabla, los algoritmos de sondeo pueden incluso caer en un círculo sin fin. Incluso utilizando
buenas funciones hash, el límite aceptable de capacidad es normalmente 80%. Con funciones hash
pobremente diseñadas el rendimiento puede degradarse incluso con poca información, al provocar
aglomeramiento significativo. No se sabe a ciencia cierta qué provoca que las funciones hash
generen aglomeramiento, y es muy fácil escribir una función hash que, sin querer, provoque un
nivel muy elevado de aglomeramiento.

5.3. Ventajas e inconvenientes de las tablas hash


Una tabla hash tiene como principal ventaja que el acceso a los datos suele ser muy rápido si se
cumplen las siguientes condiciones:

 Una razón de ocupación no muy elevada (a partir del 75% de ocupación se producen
demasiadas colisiones y la tabla se vuelve ineficiente).

Manual de Estructura de Datos Saúl Ernesto Coy Pop


109

 Una función resumen que distribuya uniformemente las claves. Si la función está mal
diseñada, se producirán muchas colisiones.

Los inconvenientes de las tablas hash son:

 Necesidad de ampliar el espacio de la tabla si el volumen de datos almacenados crece. Se


trata de una operación costosa.
 Dificultad para recorrer todos los elementos. Se suelen emplear listas para procesar la
totalidad de los elementos.
 Desaprovechamiento de la memoria. Si se reserva espacio para todos los posibles
elementos, se consume más memoria de la necesaria; se suele resolver reservando espacio
únicamente para punteros a los elementos.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


110

5.4. Funciones de Hash


Hay diferentes funciones para transformar el elemento y el número obtenido es el índice del
elemento.

La principal forma de transformar el elemento es asignarlo directamente, es decir al 0 le


corresponde el índice 0, al 1 el 1, y así sucesivamente pero cuando los elementos son muy grandes
se desperdicia mucho espacio ya que necesitamos arreglo grandes para almacenarlos y estos
quedan con muchos espacios libres, para utilizar mejor el espacio se utilizan funciones más
complejas.

La función de hash ideal debería ser biyectiva, esto es, que a cada elemento le corresponda un
índice, y que a cada índice le corresponda un elemento, pero no siempre es fácil encontrar esa
función, e incluso a veces es inútil, ya que puedes no saber el número de elementos a almacenar.
La función de hash depende de cada problema y de cada finalidad, y se pueden utilizar con números
o cadenas, pero las más utilizadas son:

Restas Sucesivas
Esta función se emplea con claves numéricas entre las que existen huecos de tamaño conocido,
obteniéndose direcciones consecutivas. Un ejemplo serían los alumnos de ingeniería en sistemas
que entraron en el año 2005 sus números de control son consecutivos y está definido el número de
alumnos.

05210800 -05210800 → 0

05210801 -05210800 → 1

05210802 -05210800 → 2

05210899 -05210800 → 99

Aritmética Modular
El índice de un número es resto de la división de ese número entre un número N prefijado,
preferentemente primo. Los números se guardarán en las direcciones de memoria de 0 a N-1. Este
método tiene el problema de que dos o más elementos pueden producir el mismo residuo y un

Manual de Estructura de Datos Saúl Ernesto Coy Pop


111

índice puede ser señalado por varios elementos. A este fenómeno se le llama colisión. Si el número
N es el 7, los números siguientes quedan transformados en:

1679 → 6

4567 → 3

8471 → 1

0435 → 1

5033 → 0

Mientras más grande sea número de elementos es mejor escoger un número primo mayor para
seccionar el arreglo en más partes. El número elegido da el número de partes en que se secciona
el arreglo, y las cada sección está compuesta por todos los elementos que arrojen el mismo residuo,
y mientras más pequeñas sean las secciones la búsqueda se agilizara más que es lo que nos interesa.

Mitad del Cuadrado


Consiste en elevar al cuadrado la clave y coger las cifras centrales. Este método también presenta
problemas de colisión.

709^2=502681 → 26

456^2=207936 → 79

105^2=011025 → 10

879^2=772641 → 26

619^2=383161 → 31

Nota: en caso de que la cifra resultante tenga un número de dígitos impar, se toma el valor número
y el anterior.

Truncamiento
Consiste en ignorar parte del número y utilizar los elementos restantes como índice. También se
produce colisión. Por ejemplo, si un número de 7 cifras se debe ordenar en un arreglo de elementos,
se pueden tomar el segundo, el cuarto y el sexto para formar un nuevo número:

Manual de Estructura de Datos Saúl Ernesto Coy Pop


112

5700931 → 703

3498610 → 481

0056241 → 064

9134720 → 142

5174829 → 142

Plegamiento
Consiste en dividir el número en diferentes partes, y operar con ellas (normalmente con suma o
multiplicación). También se produce colisión. Por ejemplo, si dividimos el número de 7 cifras en
2, 2 y 3 cifras y se suman, dará otro número de tres cifras (y si no, se toman las tres últimas cifras):

5700931»> 57 + 00 + 931 = 988

3498610 → 34 + 98 + 610 = 742

0056241 → 00 + 56 + 241 = 297

9134720 → 91 + 34 + 720 = 845

5174929 → 51 + 74 + 929 = 1054

Nota: Estas solo son sugerencias y que con cada problema se pude implementar una nueva función
hash que incluso tu puedes inventar o formular.

Tratamiento de Colisiones
Hay diferentes maneras de solucionarlas, pero lo más efectivo es en vez de crear un arreglo de
número, crear un arreglo de punteros, donde cada puntero señala el principio de una lista enlazada.
Así, cada elemento que llega a un determinado índice se pone en el último lugar de la lista de ese
índice. El tiempo de búsqueda se reduce considerablemente, y no hace falta poner restricciones al
tamaño del arreglo, ya que se pueden añadir nodos dinámicamente a la lista.

Prueba Lineal
Consiste en que una vez detectada la colisión se debe recorrer el arreglo secuencialmente a partir
del punto de colisión, buscando al elemento. El proceso de búsqueda concluye cuando el elemento

Manual de Estructura de Datos Saúl Ernesto Coy Pop


113

es hallado, o bien cuando se encuentra una posición vacía. Se trata al arreglo como a una estructura
circular: el siguiente elemento después del último es el primero. La función de rehashing es, por
tanto, de la forma: R(H(X)) = (H(X) + 1) % m (siendo m el tamaño del arreglo) Ejemplo: Si la
posición 397 ya estaba ocupada, el registro con clave 0596397 es colocado en la posición 398, la
cual se encuentra disponible. Una vez que el registro ha sido insertado en esta posición, otro
registro que genere la posición 397 o la 398 es insertado en la posición siguiente disponible.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


114

5.5. Hash Dinámico


Las tablas hash se presentaron como una alternativa hacia las estructuras tipo árbol ya que
permitían el almacenamiento de grandes volúmenes de información y algoritmos eficientes para
la administración sobre estas estructuras (inserción, eliminación y búsqueda).

Sin embargo, presentan 2 grandes problemas:

1. No existen funciones hash perfectas que permitan asegurar que por cada transformación de un
elemento habrá una única correspondencia en la clave que contiene este elemento.

2. Son estructuras estáticas que no pueden crecer ya que necesitan un tamaño fijo para el
funcionamiento de la estructura.

Para solucionar el segundo problema se implementa la utilización de métodos totales y métodos


parciales. Convirtiendo la tabla hash en una estructura dinámica capaz de almacenar un flujo de
información y no una cantidad fija de datos.

Métodos Totales
I. Método de las expansiones totales
El método de las expansiones totales consiste en realizar una duplicación del tamaño del arreglo
establecido para realizar la tabla hash, esta expansión se ejecuta cuando se supera la densidad de
ocupación.2 Así si se tiene una tabla hash de tamaño N, al realizar la expansión total se obtendrá
una tabla hash de 2N, al realizar una segunda expansión se obtendrá una tabla hash de 4N, al
realizar una tercera expansión se obtendrá una tabla hash de 8N y en general el tamaño de la tabla
para una i-ésima expansión se define como aparece a continuación:

Dónde:

N: Tamaño de la Tabla. i: Número de expansiones que se quieren realizar. T: Nuevo tamaño de la


Tabla.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


115

La densidad de ocupación se define como el cociente entre el número de registros ocupados y el


número de registros disponibles; así se tiene que:

Dónde:

ro: Registros Ocupados. rd: Registros Disponibles. ρo: Densidad de Ocupación.

Cada vez que se pretende insertar un elemento es necesario calcular la densidad de ocupación, si
se supera esta densidad se procede a implementar la expansión. Al realizar cada de una de las
expansiones es necesario volver a implementar la función hash para cada uno de los registros
almacenados en la tabla y volver a insertarlos de nuevo en la tabla.

II. Método de las reducciones totales


Este método surge como una consecuencia del método de expansiones totales presentado
anteriormente. En este método la densidad de ocupación disminuye de tal manera que acepta una
reducción del tamaño de la tabla hash a la mitad. Así si se tiene una tabla hash de N, la primera
reducción dará como resultado la N/2, la segunda reducción dará como resultado N/4, la tercera
reducción dará N/8 y la i-ésima reducción dará como resultado:

Dónde:

N: Tamaño de la Tabla. i: Número de expansiones que se quieren realizar. T: Nuevo tamaño de la


Tabla.

Para realizar una reducción la densidad de ocupación se debe disminuir a un valor menor al rango
establecido y los registros se deben eliminar de tal manera que los registros resultantes se puedan
ingresar en una tabla hash que posea la mitad del tamaño de la tabla original. Cada vez que se
implementa una reducción es necesario volver a utilizar la función hash con cada uno de los
registros almacenados.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


116

Métodos Parciales
I. Método de las expansiones parciales
El método de las expansiones parciales consiste en incrementar en un 50% el tamaño del arreglo
establecido para realizar la tabla hash, esta expansión se ejecuta cuando se supera la densidad de
ocupación. Así si se tiene una tabla hash de tamaño N, al realizar la expansión parcial se obtendrá
una tabla hash de 1.5 N, al realizar una segunda expansión se obtendrá una tabla hash de 2.25 N,
al realizar una tercera expansión se obtendrá una tabla hash de 3.375 N y en general el tamaño de
la tabla para una i-ésima expansión se define como:

T = ↓( (1.5) ^ i * N )

Dónde:

N: Tamaño de la Tabla. i: Número de expansiones que se quieren realizar. T: Nuevo tamaño de la


Tabla.

Cada vez que se pretende insertar un elemento es necesario calcular la densidad de ocupación, si
se supera esta densidad se procede a implementar la expansión. Al realizar cada de una de las
expansiones es necesario volver a implementar la función hash para cada uno de los registros
almacenados en la tabla hash y volver a insertarlos de nuevo en la tabla.

II. Método de las reducciones parciales


Este método surge como una consecuencia del método de expansiones parciales presentado en la
sección 2.1 de este documento. En este método la densidad de ocupación disminuye de tal manera
que acepta una reducción del tamaño de la tabla hash al 50%. Así si se tiene una tabla hash de N,
la primera reducción dará como resultado la 0.5 N, la segunda reducción dará como resultado 0.25
N, la tercera reducción dará 0.125 N y la i-ésima reducción dará como resultado:

T = ↑((0.5)^i*N)

Dónde:

N: Tamaño de la Tabla. i: Número de reducciones que se quieren realizar. T: Nuevo tamaño de la


Tabla.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


117

Para realizar una reducción la densidad de ocupación debe disminuir a un valor menor al rango
establecido y los registros se deben eliminar de tal manera que los registros resultantes se puedan
ingresar en una tabla hash que posea la mitad del tamaño de la tabla original. Cada vez que se
implementa una reducción es necesario volver a utilizar la función hash con cada uno de los
registros almacenados.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


118

Ejemplos

Funciones Hash

public class Hash {


int dato;
int estado; //0 = Vacío, 1 = Eliminado, 2 = Ocupado

static int funcion(int n, int m) {


return ((n + 1) % m);
}

static void insertaHash(Hash[] h, int m, int n) {

int j = funcion(n, m);


do {
if (h[j].estado == 0 || h[j].estado == 1) {
h[j].dato = n;
h[j].estado = 2;

} else {
j++;
}
} while (j < m && !i);

static int buscaHash(Hash[] h, int m, int n) {


int j = funcion(n, m);
while (j < m) {
if (h[j].estado == 0) {

Manual de Estructura de Datos Saúl Ernesto Coy Pop


119

return -1;
} else if (h[j].dato == n) {
if (h[j].estado == 1) {
return -1;
} else {
return j;
}
} else {
j++;
}
}
return -1;
}

static int eliminaHash(Hash[] h, int m, int n) {


int i = buscaHash(h, m, n);
if (i == -1) {
return -1;
} else {
h[i].estado = 1;
return 1;
}
}
}

Manual de Estructura de Datos Saúl Ernesto Coy Pop


120

Recursos de Aprendizaje
Podemos encontrar los siguientes recursos, enlaces de videos y/o páginas en donde
encontraremos más información y ejemplos sobre los temas.
Que son las Tablas Hash?
https://youtu.be/LluB6jU-SwY
Métodos de Búsqueda, Funciones Hash, Tablas Hash, Teoría (Java)
https://youtu.be/gYBnEDIn054?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Métodos de Búsqueda, Funciones Hash, Tablas Hash, Implementación (Java)


https://youtu.be/yUKGvDxleKI?list=PLCLpAU8VN0j4RGemFfybZrWoSX57NbEq9

Tabla Hash, implementación en C++


https://youtu.be/ulbKBb2R0cM

Manual de Estructura de Datos Saúl Ernesto Coy Pop


121

6. Grafos

Los grafos son nodos (o vértices) unidos por líneas (o aristas). Según las aristas podemos
encontrarnos grafos no dirigidos o grafos dirigidos, según si las aristas tienen dirección o no.

Grafo no dirigido:

Estructura de Datos - 6-1 Grafo no dirigido

La forma de representar el grafo en modo de ecuación es G = (N, A), siendo N(G) el conjunto de
nodos (vértices) y A(G) el conjunto de aristas del grafo. La representación del grafo de ejemplo
sería el siguiente:

N(G) = { 1, 2, 3, 4, 5, 6 }

A(G) = { (1,2), (1,6), (6,4), (4,5), (2,3), (2,4) }

Una arista que se enlaza un nodo consigo mismo se la denomina bucle o lazo.

Dos nodos son adyacentes si siendo distintos existe una arista que los une.

El grado de un vértice (o nodo) es el número de aristas que entran o salen de él.

Un camino es una secuencia de aristas consecutivas. La longitud del camino corresponde al


número de aristas que contiene.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


122

Hay dos tipos de caminos:

 camino simple, es un camino que no emplea más de una vez la misma arista;
 camino compuesto, cuando el camino emplea dos veces la misma arista (aunque no de forma
continua).

Un ciclo (o circuito) es un camino simple que empieza y termina en el mismo vértice.

Grafo Dirigido

Estructura de Datos - 6-2 Grafo Dirigido

La forma de representar el grafo en modo de ecuación es G = <N, A>, siendo N el conjunto de


nodos (vértices) y A el conjunto de aristas del grafo. La representación del grafo de ejemplo sería
el siguiente:

N(G) = { 1, 2, 3, 4, 5, 6 }

A(G) = { <2,1>, <1,6>, <6,4>, <4,5>, <3,2>, <2,4>, <4,4> }

El grado de un vértice (o nodo) en el grafo dirigido, puede ser de:

 grado de entrada (o entrante) de un vértice que es el número de aristas que llegan a él (con
dirección hacia él); y el
 grado de salida (o saliente) de un vértice que es el número de aristas que salen de él (con
dirección hacia otros vértices).

Manual de Estructura de Datos Saúl Ernesto Coy Pop


123

Un camino, en un grafo dirigido, es una secuencia finita de aristas entre dos vértices, siendo el
extremo final de cada arista el extremo inicial de la siguiente.

Un grafo dirigido es fuertemente conexo si para cada par de vértices distintos n y m hay un
camino de n a m y viceversa. El grafo dirigido expuesto como ejemplo tiene esta propiedad, ya
que se puede acceder a cualquier nodo desde cualquier otro.

Dos vértices o nodos están conectados si hay un camino que los une.

Las aristas, al igual que los vértices, también pueden contener información, este es el caso de
los grafos etiquetados (o valorados). Se representan de la siguiente forma:

G = (N,A,P)

N(G) = { 1, 2, 3, 4, 5, 6 }

A(G) = { <2,1>, <1,4>, <4,3>, <3,2>, <2,4>, <4,5>, <6,4> }

P(G) = { 5, 8, 1, 10, 2, 6, 7 }

La representación gráfica:

Manual de Estructura de Datos Saúl Ernesto Coy Pop


124

6.1. Tipos de grafos.

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


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

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

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


manera:

Algunos de los principales tipos de grafos son los que se muestran a continuación:

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

Manual de Estructura de Datos Saúl Ernesto Coy Pop


125

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


tercero no es regular.

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

Grafo completo
Aquel con una arista entre cada par de vértices. Un grafo completo con n vértices se denota Kn.
A continuación, pueden verse los dibujos de K3, K4, K5 y K6.

Un grafo bipartito regular


Se denota Km, n donde m, n es el grado de cada conjunto disjunto de vértices.
A continuación, ponemos los dibujos de K1,2, K3,3, y K2,5

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

Estructura de Datos - 6-3 Grafo nulo

Grafos Isomorfos
Dos grafos son isomorfos cuando existe una correspondencia biunívoca (uno a uno), entre sus
vértices de tal forma que dos de estos quedan unidos por una arista en común.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


126

Estructura de Datos - 6-4 Grafos Isomorfos

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

Estructura de Datos - 6-5 Grafos Platónicos

Grafos Eulerianos.

Para definir un camino euleriano es importante definir un camino euleriano primero. Un


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

Manual de Estructura de Datos Saúl Ernesto Coy Pop


127

Teniendo esto definido podemos hablar de los grafos eulerianos describiéndolos


simplemente como aquel grafo que contiene un camino euleriano. Como ejemplos tenemos las
siguientes imágenes:
El primer grafo de ellos no contiene caminos eulerianos mientras el segundo contiene al
menos uno.

Grafos Conexos.

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


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

Estructura de Datos - 6-6 Grafos Conexos

Manual de Estructura de Datos Saúl Ernesto Coy Pop


128

6.2. Representación de los Grafos


Los grafos se pueden representar de varias formas, pero las dos más comunes son: la matriz de
adyacencias y la lista de adyacencias.

Matriz de adyacencia
Se crea una matriz (la llamaremos MA por Matriz de Adyacencias) asociada a G, de modo que
tenga n x n elementos. Indicaremos sus contenidos con 0 y 1, siendo 1 que en la
posición MA[i,j] existe una arista y 0 lo contrario.

Un ejemplo, viendo este grafo dirigido:

Su matriz de adyacencia sería la siguiente (teniendo en cuenta las filas como el origen y las
columnas como el destino):

Estructura de Datos - 6-7 Matriz de Adyacencia

Manual de Estructura de Datos Saúl Ernesto Coy Pop


129

En los grafos no dirigidos cada arista es simétrica. En el caso de los grafos valorados, los valores
que se representan en la tabla ya no serían 0 y 1, sino el valor del peso de cada arista.

Determinar cuántas aristas hay en un grafo tiene un coste de O(n2), y requiere de un espacio de
memoria de θ(n2). Por lo que no es aconsejado si hay pocas aristas.

Listas de adyacencia
Se representa por un vector de n listas, siendo n el número de nodos, por lo que contiene tantas
listas como nodos hay en el grafo. Por lo que el grafo que se muestra a continuación:

Tendrá esta representación en formato de listas de adyacencia:

Estructura de Datos - 6-8 Lista de Adyacencia

Manual de Estructura de Datos Saúl Ernesto Coy Pop


130

En el caso de los grafos etiquetados, las listas deberían incluir un campo adicional para
almacenar la etiqueta asociada a cada arista.

Determinar si existe una arista entre el nodo i y el nodo j puede llevar un tiempo de O(n), ya que
puede haber n vértices en la lista asociada de ese vértice en concreto. El coste en espacio de esta
representación está en θ(n+a), siendo n el número de nodos y a el número de aristas.

6.3. Algoritmo de Dijkstra

El algoritmo de Dijkstra, también llamado algoritmo de caminos mínimos, es un algoritmo para la


determinación del camino más corto dado un vértice origen al resto de los vértices en un grafo con
pesos en cada arista.

I. Aplicaciones

En múltiples aplicaciones donde se aplican los grafos, es necesario conocer el camino de menor
costo entre dos vértices dados:

 Distribución de productos a una red de establecimientos comerciales.


 Distribución de correos postales.
 Sea G = (V, A) un grafo dirigido ponderado.

El problema del camino más corto de un vértice a otro consiste en determinar el camino de
menor costo, desde un vértice u a otro vértice v. El costo de un camino es la suma de los costos
(pesos) de los arcos que lo conforman.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


131

II. Características del algoritmo

 Es un algoritmo greddy.
 Trabaja por etapas, y toma en cada etapa la mejor solución sin considerar consecuencias
futuras.
 El óptimo encontrado en una etapa puede modificarse posteriormente si surge una solución
mejor.

III. Pasos del algoritmo

Algoritmo de Dijkstra. Inicialización.

 Sea V un conjunto de vértices de un grafo.


 Sea C una matriz de costos de las aristas del grafo, donde en C[u,v] se almacena el costo de
la arista entre u y v.
 Sea S un conjunto que contendrá los vértices para los cuales ya se tiene determinado el
camino mínimo.
 Sea D un arreglo unidimensional tal que D[v] es el costo del camino mínimo del vértice
origen al vértice v.
 Sea P un arreglo unidimensional tal que P[v] es el vértice predecesor de v en el camino
mínimo que se tiene construido.
 Sea v inicial el vértice origen. Recordar que el Algoritmo Dijkstra determina los caminos
mínimos que existen partiendo de un vértice origen al resto de los vértices.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


132

Ejemplos
Algoritmo de Dijkstra – Ejemplo

Teniendo en cuenta la siguiente red, que tiene como origen el nodo 1:

Procedemos con las iteraciones, partimos de la iteración 0, es decir, asignar la etiqueta para el
nodo origen:
Iteración 0
En este paso, asignamos una etiqueta permanente para el nodo origen. Recordemos que la
etiqueta se compone por un valor acumulado, que en este caso debe ser 0, ya que es el punto base
y que no existe procedencia:

Etiqueta nodo 1 = [valor acumulado, procedencia] iteración


Etiqueta nodo 1 = [0, -] 0
Iteración 1
En este paso, evaluamos a cuáles nodos se puede llegar desde el nodo 1. En este caso se puede
llegar a los nodos 2 y 3. De manera que debemos asignar las etiquetas para cada nodo:

Etiqueta nodo 2 = [valor acumulado, procedencia] iteración


Etiqueta nodo 2 = [0 + 100, 1] 1
0 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso el valor acumulado
para el nodo 1. 100 es el valor del arco que une el nodo procedencia (1) y el nodo destino
(2). 1 es el número de la iteración.
Etiqueta nodo 3 = [valor acumulado, procedencia] iteración
Etiqueta nodo 3 = [0 + 30, 1] 1

Manual de Estructura de Datos Saúl Ernesto Coy Pop


133

En este caso, podemos observar que la etiqueta del nodo 3, ya contiene el menor valor
acumulado posible para llegar a este. Ya que 30 es la mínima distancia posible, toda vez que para
llegar al nodo 3 por medio del nodo 2, tendrá que recorrer como mínimo el valor absoluto del
arco que une el origen con el nodo 2 = 100. Así entonces, la etiqueta del nodo 3 pasa a ser
permanente.
Tabulamos la iteración 1:

Iteración 2:
En este paso, evaluamos las posibles salidas desde el nodo 3, es decir los nodos 4 y 5. De manera
que debemos asignar las etiquetas para cada nodo:

Etiqueta nodo 4 = [valor acumulado, procedencia] iteración


Etiqueta nodo 4 = [30 + 10, 3] 2
Etiqueta nodo 4 = [40, 3] 2

30 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso el valor acumulado
para el nodo 3. 10 es el valor del arco que une el nodo procedencia (3) y el nodo destino (4). 2 es
el número de la iteración.

Etiqueta nodo 5 = [valor acumulado, procedencia] iteración

Manual de Estructura de Datos Saúl Ernesto Coy Pop


134

Etiqueta nodo 5 = [30 + 60, 3] 2


Etiqueta nodo 4 = [90, 3] 2
30 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso el valor acumulado
para el nodo 3. 60 es el valor del arco que une el nodo procedencia (3) y el nodo destino (5). 2 es
el número de la iteración.

En este caso, podemos observar que la etiqueta del nodo 4, contiene el menor valor acumulado
posible para llegar a este. Así entonces, la etiqueta del nodo 4 pasa a ser permanente.

Tabulamos la iteración 2:

Iteración 3:
En este paso, evaluamos las posibles salidas desde el nodo 4, es decir los nodos 2 y 5. De manera
que debemos asignar las etiquetas para cada nodo:

Etiqueta nodo 2 = [valor acumulado, procedencia] iteración


Etiqueta nodo 2 = [40 + 15, 4] 3
Etiqueta nodo 2 = [55, 4] 3

Manual de Estructura de Datos Saúl Ernesto Coy Pop


135

40 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso el valor acumulado
para el nodo 4. 15 es el valor del arco que une el nodo procedencia (4) y el nodo destino (2). 3 es
el número de la iteración.

Etiqueta nodo 5 = [valor acumulado, procedencia] iteración


Etiqueta nodo 5 = [40 + 50, 4] 3
Etiqueta nodo 5 = [90, 4] 3
40 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso el valor acumulado
para el nodo 3. 50 es el valor del arco que une el nodo procedencia (4) y el nodo destino (5). 3 es
el número de la iteración.

En este caso, podemos observar que el nodo 2 ahora cuenta con 2 etiquetas temporales, y
definitivas, ya que no existe otra ruta hacia dicho nodo. De manera que se elige la etiqueta que
tenga el menor valor acumulado. Así entonces, la etiqueta del nodo 2 con procedencia del nodo
4, pasa a ser permanente.

Tabulamos la iteración 3:

Manual de Estructura de Datos Saúl Ernesto Coy Pop


136

Iteración 4:
En este paso, evaluamos las posibles salidas desde el nodo 2 y el nodo 5. Sin embargo, el nodo 2
solo tiene un posible destino, el nodo 3, el cual ya tiene una etiqueta permanente, de manera que
no puede ser reetiquetado. Ahora, evaluamos el nodo 5 y es un nodo que no tiene destinos. Así
entonces, su etiqueta temporal pasa a ser permanente, en este caso cuenta con 2 etiquetas que
tienen el mismo valor, es decir, alternativas óptimas. De esta manera concluye el algortimo de
Dijkstra.

¿Cuál es la ruta más corta? La ruta más corta entre el nodo 1 (origen) y cualquier otro nodo de
la red (destino), se determina partiendo desde el nodo destino y recorriendo las procedencias de
sus etiquetas. Por ejemplo:

Manual de Estructura de Datos Saúl Ernesto Coy Pop


137

¿Cuál es la ruta más corta entre el nodo 1 y el nodo 4?


En este caso, debemos partir desde la etiqueta del nodo 4.

La ruta más corta entre el nodo 1 y el nodo 4 tiene un valor acumulado de: 40
Es necesario considerar, que los valores utilizados en los arcos de la red objeto de estudio del
algoritmo de Dijkstra, no necesariamente representan distancias. Si bien, es un modelo que
aborda el denominado «problema de la ruta más corta»; en la práctica, puede utilizarse para
optimizar: distancia, costos, tiempo.

Manual de Estructura de Datos Saúl Ernesto Coy Pop


138

Recursos de Aprendizaje
Podemos encontrar los siguientes recursos, enlaces de videos y/o páginas en donde
encontraremos más información y ejemplos sobre los temas.

1. Algoritmo de Dijkstra
https://youtu.be/eLFEIxDEphA
2. Algoritmo de Dijkstra
https://youtu.be/skF82H7dt1s

Manual de Estructura de Datos Saúl Ernesto Coy Pop


139

BIBLIOGRAFIA

Estructura de Datos. Licda. Dora Araceli Cruz Huerta. 2011


EGRAFIA
https://cprog.cubava.cu/2018/03/25/tablas-hash/
http://conclase.net/

Manual de Estructura de Datos Saúl Ernesto Coy Pop

También podría gustarte