1 of 100

pilas

ESTRUCTURA DE DATOS

2 of 100

Una pila es una estructura de datos a la cual se puede acceder solo por un extremo de la misma. Las operaciones de inserción y extracción se realizan a través del tope, por lo cual no se puede acceder a cualquier elemento de la pila. Se le suele llamar estructura L.I.F.O. como acrónimo de las palabras inglesas "last in, first out" (último en entrar, primero en salir). La pila se considera un grupo ordenado de elementos, teniendo en cuenta que el orden de los mismos depende del tiempo que lleven "dentro" de la estructura.

3 of 100

La pila debe pensarse como una resma de hojas sobre un escritorio, con sólo dos posibles operaciones básicas: apilar (agregar o push) y desapilar (sacar o pop). Se agregan hojas una sobre la otra, y se retiran desde arriba hacia abajo. Esto significa que en todo momento, sólo se puede acceder directamente al último elemento agregado (el que está más arriba en la pila, que es llamado TOS).

4 of 100

5 of 100

Pila de llamadas�

  • La Pila de llamadas es un segmento de memoria que utiliza esta estructura de datos para almacenar información sobre las llamadas a subrutinas actualmente en ejecución en un programa en proceso.
  • Cada vez que una nueva subrutina es llamada, se apila una nueva entrada con información sobre ésta tal como sus variables locales. En especial, se almacena aquí el punto de retorno al que regresar cuando esta subrutina termine (para volver a la subrutina anterior y continuar su ejecución después de esta llamada).

6 of 100

Pila como tipo abstracto de datos�

  • La pila es un contenedor de nodos y tiene dos operaciones básicas: push (o apilar) y pop (o desapilar). 'Push' añade un nodo a la parte superior de la pila, dejando por debajo el resto de los nodos. 'Pop' elimina y devuelve el actual nodo superior de la pila.

7 of 100

Operaciones�

  • Crear: se crea la pila vacía.
  • Apilar: se añade un elemento a la pila.(push)
  • Desapilar: se elimina el elemento frontal de la pila.(pop)
  • Cima: devuelve el elemento que esta en la cima de la pila. (top o peek)
  • Vacía: devuelve cierto si la pila está vacía o falso en caso contrario.

8 of 100

Implementación�

Un requisito típico de almacenamiento de una pila de n elementos es O(n). El requisito típico de tiempo de O(1) las operaciones también son fáciles de satisfacer con un array o con listas enlazadas simples.

La biblioteca de plantillas de C++ estándar proporciona una "pila" clase templated que se limita a sólo apilar/desapilar operaciones. Java contiene una biblioteca de la clase Pila que es una especialización de Vector. Esto podría ser considerado como un defecto, porque el diseño heredado get () de Vector método LIFO ignora la limitación de la Pila.

Una pila acotada es una pila limitada a un tamaño máximo impuesto en su especificación.

9 of 100

Expresión de evaluación y análisis sintáctico�

Se calcula empleando la notación polaca inversa utilizando una estructura de pila para los posibles valores. Las expresiones pueden ser representadas en prefijo, infijo, postfijo. La conversión de una forma de la expresión a otra forma necesita de una pila. Muchos compiladores utilizan una pila para analizar la sintaxis de las expresiones, bloques de programa, etc. Antes de traducir el código de bajo nivel. La mayoría de los lenguajes de programación son de contexto libre de los idiomas que les permite ser analizados con máquinas basadas en la pila.

10 of 100

Por ejemplo, el cálculo: ((1 + 2) * 4) + 3, puede ser anotado como en notación postfija con la ventaja de no prevalecer las normas y los paréntesis necesario:

1 2 + 4 * 3 +

La expresión es evaluada de izquierda a derecha utilizando una pila:

  • Apilar cuando se enfrentan a un operando y
  • Desafilar dos operandos y evaluar el valor cuando se enfrentan a una operación.
  • Apilar el resultado.

11 of 100

De la siguiente manera (la Pila se muestra después de que la operación se haya llevado a cabo):

ENTRADA OPERACION PILA

1 Apilar operando 1

2 Apilar operando 1, 2

+ Añadir 3

4 Apilar operando 3, 4

* Multiplicar 12

3 Apilar operando 12, 3

+ Añadir 15

El resultado final, 15, se encuentra en la parte superior de la pila al final del cálculo.

12 of 100

Ejemplo de pila en C�

Supongamos que queremos construir una pila para almacenar números enteros. Haremos pruebas intercalando varios "push" y "pop", y comprobando el resultado.

Algoritmo de la función "push"

  • Creamos un nodo para el valor que colocaremos en la pila.
  • Hacemos que nodo->siguiente apunte a Pila.
  • Hacemos que Pila apunte a nodo.

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;

}

13 of 100

Algoritmo de la función "pop"

  • Hacemos que nodo apunte al primer elemento de la pila, es decir a Pila.
  • Asignamos a Pila la dirección del segundo nodo de la pila: Pila->siguiente.
  • Guardamos el contenido del nodo para devolverlo como retorno, recuerda que la operación pop equivale a leer y borrar.
  • Liberamos la memoria asignada al primer nodo, el que queremos eliminar.

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 */

v = nodo->valor;

/* Borrar el nodo */

free(nodo);

return v;

}

14 of 100

COLAS

15 of 100

DEFINICION

Son aquellas que solo tiene 2 operaciones, Push(Inserción) y Pop(Eliminación). Push solo se puede efectuar por un extremo llamado Frente y Pop por el extremo Llamado Final. Sin Embargo se le pueden aplicar todas las operaciónes al igual que a las listas.

16 of 100

RECORRIDO

Ya que las colas son FIFO(First in - First Out) el Recorrido se hace sacando el primer dato que se inserto hasta que llegue al extremo llamado Final.

COMO FUNCIONA:

En un principio se compara para saber si tiene algún dato en la Cola, si no es así desplegara “Cola Vacía…”. De otra forma compara si Frente es mayor o igual a Final, de esta forma simplemente hace un Recorrido lineal como los anteriores. De otra forma usar Max como bandera para saber cuando empezar a contar de 0 a Final (Ya que sabemos que el Frente después del nodo Final).

17 of 100

Algoritmo

Recorrido(Cola, Frente, Final, Max)

Si Frente ≠ Nulo

Si Frente ≤ Final, entonces:

Apuntador <-- Frente

Repetir mientras Apuntador ≤ Final

Imprimir Cola[Apuntador]

Apuntador <-- Apuntador + 1

Fin del ciclo

Si no, si Frente > Final, entonces:

Apuntador <-- Frente

Repetir mientras Apuntador ≠ Final

Si Apuntador > Max, entonces:

Apuntador <-- 0

Imprimir Cola[Apuntador]

Apuntador <-- Apuntador + 1

Fin del ciclo

Si no:

Imprimir "Cola Vacía"

Salir 

18 of 100

DIAGRAMA

19 of 100

PUSH

Definición:

Push es simplemente el método por el cual va agregando un Dato nuevo a la Cola tomando en cuenta el Tamaño Máximo de Capacidad (Max), el Frente y el Final de la Cola.

Primero nos aseguramos que la Cola no este Llena, para que de esta manera sea capaz de insertar un Elemento nuevo. Si no desplegara Cola Llena. Después compara para determinar las posiciones de Frente y Final y de esta manera poder moverlo con libertad. Ya que determina los valores de Frente y Final, nos Indica que Cola[Final] tomara el valor de Elemento.

20 of 100

Algoritmo

Push(Cola, Frente, Final, Max, Elemento)

Si Frente = 0 y Final =9, o si Frente = (Final + 1)

Imprimir "Cola Llena" y Salir

Si Frente = Nulo

Frente <-- 0

Final <-- 0

Si no, si Final = Max 

Final <-- 0 

Si no: 

Final <-- Final + 1

Cola[Final] = Elemento 

Salir

21 of 100

DIAGRAMA

22 of 100

POP

Definición:

Pop es simplemente el método por el cual va sacando el primer Dato de la Cola (esto se comprueba ya que las Colas son FIFO), para esto toma en cuenta el Frente.

Compara para determinar si la cola esta vacía, de otra forma lo que hace es Imprimir “Eliminando el Dato…”. Después se hacen una series de comparaciones para determinar la nueva posición de Frente, de esa forma el Dato que existía en Frente es Eliminado.

23 of 100

Algoritmo

Pop(Cola, Frente, Final, Max)

Si Frente ≠ Nulo

Imprimir "Eliminado el Dato..."

Si Frente = Final

Frente = Nulo

Final = Nulo

Si no, si Frente = Max

Frente = 0

Si no:

Frente <-- Frente + 1

Si no:

Imprimir "Cola Vacía" 

Salir

24 of 100

DIAGRAMA

25 of 100

Busqueda

Definición:

Este método usa el recorrido para encontrar un Elemento y desplegar un mensaje si la búsqueda es exitosa.

El algoritmo usa básicamente la misma estructura del Recorrido, la única diferencia es que compara cada uno de los Datos con Elemento, de esta forma se da cuenta si este Dato existe en la Cola.

26 of 100

Algoritmo

Busqueda(Cola, Frente, Fin, Max, Elemento)

Si Frente ≠ Nulo

Si Frente ≤ Final, entonces:

Apuntador <-- Frente

Repetir mientras Apuntador ≤ Final

Si Elemento = Cola[Apuntador]

Imprimir "Dato encontrado..." y Salir

Apuntador <-- Apuntador + 1

Fin del ciclo

Si no, si Frente > Final, entonces:

Apuntador <-- Frente

Repetir mientras Apuntador ≠ Final

Si Apuntador > Max, entonces:

Apuntador <-- 0

Si Elemento = Cola[Apuntador]

Imprimir "Dato encontrado..." y Salir

Apuntador <-- Apuntador + 1

Fin del ciclo

Imprimir "Dato no encontrado..."

Si no:

Imprimir "Cola Vacía"

Salir

27 of 100

Diagrama

28 of 100

Eliminacion

Definición:

Este método busca un Dato dentro de la cola y lo elimina.

Este Método es la mezcla de todos en uno, Recorrido, Búsqueda, Pop y Push. Debido que a busca el Dato haciendo un Recorrido, y en el proceso copia todos los Datos que no son en un Arreglo Temp, para después meterlos a la Cola original, esto lo hace hasta encontrar el dato deseado que posteriormente lo Elimina.

29 of 100

Diagrama

30 of 100

listas

Tomás Gerónimo Morales

31 of 100

Definición�

  • Una lista es una estructura de datos secuencial.

32 of 100

  • una lista enlazada es una de las estructuras de datos fundamentales, y puede ser usada para implementar otras estructuras de datos. Consiste en una secuencia de nodos, en los que se guardan campos de datos arbitrarios y una o dos referencias, enlaces o punteros (punteros) al nodo anterior o posterior. El principal beneficio de las listas enlazadas respecto a los vectores convencionales es que el orden de los elementos enlazados puede ser diferente al orden de almacenamiento en la memoria o el disco, permitiendo que el orden de recorrido de la lista sea diferente al de almacenamiento

33 of 100

caracteristicas

  • La cantidad reservada de almacenamiento para la pila o la cola es fija. Vimos pilas y colas alojadas en arreglos.
  • Problemas al insertar o eliminar elementos.
  • Una representación secuencial, refleja el orden lógico de los elementos físicamente almacenados en la lista; el orden físico y lógico son los mismos.

34 of 100

  • Solución a los problemas de movimiento de los datos que se ha encontrado al utilizar representaciones secuenciales.
  • Con la representación no secuencial el orden lógico y el orden físico de los elementos no es necesario que sea el mismo.
  • El orden lógico se representa de tal forma que cada elemento apunta al siguiente elemento, es decir, se encuentran ligados

35 of 100

clasificación

  • - lista densa: la propia estructura determina cuál es el siguiente elemento de la lista. Ejemplo: un array.
  • lista enlazada: la posición del siguiente elemento de la estructura la determina el elemento actual. Es necesario almacenar al menos la posición de memoria del primer elemento. Además es dinámica, es decir, su tamaño cambia durante la ejecución del programa.

36 of 100

Una lista enlazada se puede definir recursivamente de la siguiente manera:�

  • una lista enlazada es una estructura vacía o�- un elemento de información y un enlace hacia una lista (un nodo).

37 of 100

Implementación

  • Para representar en lenguaje C esta estructura de datos se utilizarán punteros, un tipo de datos que suministra el lenguaje. Se representará una lista vacía con la constante NULL. Se puede definir la lista enlazada de la siguiente manera:

struct lista

{

int clave;

struct lista *sig;

};

38 of 100

Listas ordenadas�

  • Las listas ordenadas son aquellas en las que la posición de cada elemento depende de su contenido. Por ejemplo, podemos tener una lista enlazada que contenga el nombre y apellidos de un alumno y queremos que los elementos -los alumnos- estén en la lista en orden alfabético.

struct lista *L;

L = NULL;

39 of 100

Cabecera ficticia y centinela�

Como se ha observado anteriormente, a la hora de insertar o actualizar elementos en una lista ordenada o reorganizable es fundamental actualizar el primer elemento de la lista cuando sea necesario. Esto lleva un coste de tiempo, aunque sea pequeño salvo en el caso de numerosas inserciones y borrados. Para subsanar este problema se utiliza la cabecera ficticia.

40 of 100

Se declara una lista vacía con cabecera, reservando memoria para la cabecera, de la siguiente manera:

struct lista {

int clave;

struct lista *sig;

}

...

struct lista *L;

L = (struct lista *) malloc(sizeof(struct lista));

L->sig = NULL;

41 of 100

Listas doblemente enlazadas�

  • Son listas que tienen un enlace con el elemento siguiente y con el anterior. Una ventaja que tienen es que pueden recorrerse en ambos sentidos, ya sea para efectuar una operación con cada elemento o para insertar/actualizar y borrar. La otra ventaja es que las búsquedas son algo más rápidas puesto que no hace falta hacer referencia al elemento anterior. Su inconveniente es que ocupan más memoria por nodo que una lista simple.

42 of 100

43 of 100

Listas circulares�

Las listas circulares son aquellas en las que el último elemento tiene un enlace con el primero. Su uso suele estar relacionado con las colas, y por tanto su desarrollo se realizará en el tema de colas. Por supuesto, se invita al lector a desarrollarlo por su cuenta.

44 of 100

45 of 100

ÁRBOLES

46 of 100

DEFINICIÓN

Un árbol es una estructura no lineal formada por un conjunto de nodos y un conjunto de ramas.

En un árbol existe un nodo especial denominado raíz. Así mismo, un nodo del que sale alguna rama, recibe el nombre de Nodo de bifurcación o nodo rama y un nodo que no tiene ramas recibe el nombre nodo terminal o Nodo hoja.

El nivel de un nodo respecto al nodo al nodo raíz se define diciendo que la raíz tiene nivel 0 y cualquier otro nodo tiene un nivel igual a la distancia de ese nodo al nodo raíz. El máximo de los niveles se denomina profundidad o altura del árbol.

47 of 100

Arboles binarios.

Un árbol binario es un conjunto finito de nodos que consta de un nodo raíz que tiene dos subárboles binarios denominados subárbol izquierdo y subárbol derecho.

Nodo hijo: cualquiera de los nodos apuntados por uno de los nodos del árbol. En el ejemplo, 'L' y 'M' son hijos de 'G'.

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

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

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

Nodo hoja: nodo que no tiene hijos. En el ejemplo hay varios: 'F', 'H', 'I', 'K', 'L', 'M', 'N' y 'O'.

Nodo rama: aunque esta definición apenas la usaremos, estos son los nodos que no pertenecen a ninguna de las dos categorías anteriores. En el ejemplo: 'B', 'C', 'D', 'E', 'G' y 'J'.

Un árbol en el que en cada nodo o bien todos o ninguno de los hijos existe, se llama árbol completo.

48 of 100

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, diremos 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, etc.

Grado: el número de hijos que tiene el elemento con más hijos dentro del árbol. En el árbol del ejemplo, el grado es tres, ya que tanto 'A' como 'D' tienen tres hijos, y no existen elementos con más de tres 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 es cero y el de sus hijos uno. Así sucesivamente. En el ejemplo, el nodo 'D' tiene nivel 1, el nodo 'G' tiene nivel 2, y el nodo 'N', nivel 3.

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 podemos hablar de altura de ramas. El árbol del ejemplo tiene altura 3, la rama 'B' tiene altura 2, la rama 'G' tiene altura 1, la 'H' cero, etc.

49 of 100

El árbol binario es una estructura de datos muy útil cuando:

  • El tamaño de la estructura no se conoce.
  • Se necesita acceder a sus elementos ordenadamente.
  • La velocidad de búsqueda es importante.
  • El orden en el que se insertan los elementos es casi aleatorio.

50 of 100

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.

Todos los nodos contengan el mismo número de punteros, es decir, usaremos la misma estructura para todos los nodos del árbol. Esto hace que la estructura sea más sencilla, y por lo tanto también los programas para trabajar con ellos.

CARACTERÍSTICAS

51 of 100

TIPOS DE ÁRBOLES

52 of 100

Es una estructura de datos en la cual cada nodo siempre tiene un hijo izquierdo y un hijo derecho. No pueden tener más de dos hijos (de ahí el nombre "binario"). Si algún hijo tiene como referencia a null, es decir que no almacena ningún dato, entonces este es llamado un nodo externo. En el caso contrario el hijo es llamado un nodo interno. Usos comunes de los árboles binarios son los árboles binarios de búsqueda, los montículos binarios y Codificación de Huffman.

53 of 100

Un árbol binario es un árbol con raíz en el que cada nodo tiene como máximo dos hijos.

Un árbol binario lleno es un árbol en el que cada nodo tiene cero o dos hijos.

Un árbol binario perfecto es un árbol binario lleno en el que todas las hojas (vértices con cero hijos) están a la misma profundidad (distancia desde la raíz, también llamada altura).

A veces un árbol binario perfecto es denominado árbol binario completo. Otros definen un árbol binario completo como un árbol binario lleno en el que todas las hojas están a profundidad n o n-1, para alguna n.

Un árbol binario es un árbol en el que ningún nodo puede tener más de dos subárboles. En un árbol binario cada nodo puede tener cero, uno o dos hijos (subárboles). Se conoce el nodo de la izquierda como hijo izquierdo y el nodo de la derecha como hijo derecho.

54 of 100

ÁRBOLES BINARIOS

55 of 100

Un árbol binario puede declararse de varias maneras. Algunas de ellas son:

Estructura con manejo de memoria dinámica, siendo puntA el puntero que apunta al árbol de tipo tArbol:

typedef struct nodo {

int clave;

struct nodo *izdo, *dcho;

}Nodo;

Estructura con arreglo indexado

typedef struct tArbol {

int clave;

tArbol hIzquierdo, hDerecho;

} tArbol;

tArbol árbol[NUMERO_DE_NODOS];

56 of 100

En el caso de un árbol binario casi-completo (o un árbol completo), puede utilizarse un sencillo arreglo de enteros con tantas posiciones como nodos deba tener el árbol. La información de la ubicación del nodo en el árbol es implícita a cada posición del arreglo. Así, si un nodo está en la posición i, sus hijos se encuentran en las posiciones 2i+1 y 2i+2, mientras que su padre (si tiene), se encuentra en la posición truncamiento((i-1)/2) (suponiendo que la raíz está en la posición cero). Este método se beneficia de un almacenamiento más compacto y una mejor localidad de referencia, particularmente durante un recorrido en pre-orden.

IMPLEMENTACIÓN EN C

57 of 100

ÁRBOL BINARIO DE BÚSQUEDA AUTO-BALANCEABLE

Es un árbol binario de búsqueda que intenta mantener su altura, o el número de niveles de nodos bajo la raíz, tan pequeños como sea posible en todo momento, automáticamente. Esto es importante, ya que muchas operaciones en un árbol de búsqueda binaria tardan un tiempo proporcional a la altura del árbol, y los árboles binarios de búsqueda ordinarios pueden tomar alturas muy grandes en situaciones normales, como cuando las claves son insertadas en orden. Mantener baja la altura se consigue habitualmente realizando transformaciones en el árbol, como la rotación de árboles, en momentos clave.

58 of 100

ÁRBOL MULTICAMINO

Un árbol multicamino posee un grado g mayor a dos, donde cada nodo de información del árbol tiene un máximo de g hijos.

Sea un árbol de m-caminos A, es un árbol m-caminos si y solo si:

  • A está vacío
  • Cada nodo de A muestra la siguiente estructura: [nClaves,Enlace0,Clave1,...,ClavenClaves,EnlacenClaves]
  • nClaves es el número de valores de clave de un nodo, pudiendo ser: 0 <= nClaves <= g-1Enlacei, son los enlaces a los subárboles de A, pudiendo ser: 0 <= i <= nClavesClavei, son los valores de clave, pudiendo ser: 1 <= i <= nClavesClavei < Clavei+1
  • Cada valor de clave en el subárbol Enlacei es menor que el valor de Clavei+1
  • Los subárboles Enlacei, donde 0 <= i <= nClaves, son también árboles m-caminos.

59 of 100

EJEMPLOS

Ejemplos en C…

//Nodo de un árbol binario

Ejemplo#1

Para C, y basándonos en la declaración de nodo que hemos visto más arriba, trabajaremos con los siguientes tipos:

typedef struct _nodo \{

int dato;

struct _nodo *rama[ORDEN];

} tipoNodo;

typedef tipoNodo *pNodo;

typedef tipoNodo *Arbol;

 

Declaramos un tipo tipoNodo para declarar nodos, y un tipo pNodo para es el tipo para declarar punteros a un nodo.

Árbol es el tipo para declarar árboles de orden ORDEN.

60 of 100

Ejemplo #2

/* Arbol Binario de Búsqueda en C */

/* (C) Abril 2002, Salvador Pozo */

/* C con Clase: http://c.conclase.net */

 

#include <stdlib.h>

#include <stdio.h>

 

#define TRUE 1

#define FALSE 0

 

/* Estructuras y tipos */

typedef struct _nodo {

int dato;

struct _nodo *derecho;

struct _nodo *izquierdo;

} tipoNodo;

typedef tipoNodo *pNodo;

typedef tipoNodo *Arbol;

61 of 100

/* Funciones con árboles: */

/* Insertar en árbol ordenado: */

void Insertar(Arbol *a, int dat);

/* Borrar un elemento: */

void Borrar(Arbol *a, int dat);

/* Función de búsqueda: */

int Buscar(Arbol a, int dat);

/* Comprobar si el árbol está vacío: */

int Vacio(Arbol r);

/* Comprobar si es un nodo hoja: */

int EsHoja(pNodo r);

/* Contar número de nodos: */

int NumeroNodos(Arbol a, int* c);

/* Calcular la altura de un árbol: */

int AlturaArbol(Arbol a, int* altura);

/* Calcular altura de un dato: */

int Altura(Arbol a, int dat);

/* Aplicar una función a cada elemento del árbol: */

void InOrden(Arbol, void (*func)(int*));

void PreOrden(Arbol, void (*func)(int*));

void PostOrden(Arbol, void (*func)(int*));

62 of 100

/* Funciones auxiliares: */

void Podar(Arbol *a);

void auxContador(Arbol a, int*);

void auxAltura(Arbol a, int, int*);

 

void Mostrar(int *d);

 

/* Programa de ejemplo */

int main()

{

Arbol ArbolInt=NULL;

int altura;

int nnodos;

 

/* Inserción de nodos en árbol: */

Insertar(&ArbolInt, 10);

Insertar(&ArbolInt, 5);

Insertar(&ArbolInt, 12);

Insertar(&ArbolInt, 4);

Insertar(&ArbolInt, 7);

Insertar(&ArbolInt, 3);

Insertar(&ArbolInt, 6);

63 of 100

Insertar(&ArbolInt, 9);

Insertar(&ArbolInt, 8);

Insertar(&ArbolInt, 11);

Insertar(&ArbolInt, 14);

Insertar(&ArbolInt, 13);

Insertar(&ArbolInt, 2);

Insertar(&ArbolInt, 1);

Insertar(&ArbolInt, 15);

Insertar(&ArbolInt, 10);

Insertar(&ArbolInt, 17);

Insertar(&ArbolInt, 18);

Insertar(&ArbolInt, 16);

 

printf("Altura de arbol %d\n", AlturaArbol(ArbolInt, &altura));

 

//Mostrar el árbol en tres ordenes distintos:

printf("InOrden: ");

InOrden(ArbolInt, Mostrar);

printf("\n");

printf("PreOrden: ");

PreOrden(ArbolInt, Mostrar);

printf("\n");

64 of 100

printf("PostOrden: ");

PostOrden(ArbolInt, Mostrar);

printf("\n");

/* Borraremos algunos elementos: */

printf("N nodos: %d\n", NumeroNodos(ArbolInt, &nnodos));

Borrar(&ArbolInt, 5);

printf("Borrar 5: ");

InOrden(ArbolInt, Mostrar);

printf("\n");

Borrar(&ArbolInt, 8);

printf("Borrar 8: ");

InOrden(ArbolInt, Mostrar);

printf("\n");

Borrar(&ArbolInt, 15);

printf("Borrar 15: ");

InOrden(ArbolInt, Mostrar);

printf("\n");

Borrar(&ArbolInt, 245);

printf("Borrar 245: ");

InOrden(ArbolInt, Mostrar);

printf("\n");

65 of 100

Borrar(&ArbolInt, 4);

printf("Borrar 4: ");

InOrden(ArbolInt, Mostrar);

printf("\n");

Borrar(&ArbolInt, 17);

printf("Borrar 17: ");

InOrden(ArbolInt, Mostrar);

printf("\n");

 

/* Veamos algunos parámetros */

printf("N nodos: %d\n", NumeroNodos(ArbolInt, &nnodos));

printf("Altura de 1 %d\n", Altura(ArbolInt, 1));

printf("Altura de 10 %d\n", Altura(ArbolInt, 10));

printf("Altura de arbol %d\n", AlturaArbol(ArbolInt, &altura));

 

/* Liberar memoria asociada al árbol: */

Podar(&ArbolInt);

system("PAUSE");

return 0;}

66 of 100

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

void Podar(Arbol *a)

{

/* Algoritmo recursivo, recorrido en postorden */

if(*a) {

Podar(&(*a)->izquierdo); /* Podar izquierdo */

Podar(&(*a)->derecho); /* Podar derecho */

free(*a); /* Eliminar nodo */

*a = NULL;

}

}

 

/* Insertar un dato en el árbol ABB */

void Insertar(Arbol *a, int dat)

{

pNodo padre = NULL;

pNodo actual = *a;

67 of 100

/* 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->izquierdo;

else if(dat > actual->dato) actual = actual->derecho;

}

 

/* 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)) {

*a = (Arbol)malloc(sizeof(tipoNodo));

(*a)->dato = dat;

(*a)->izquierdo = (*a)->derecho = NULL;

}

68 of 100

/* Si el dato es menor que el que contiene el nodo padre, lo insertamos

en la rama izquierda */

else if(dat < padre->dato) {

actual = (Arbol)malloc(sizeof(tipoNodo));

padre->izquierdo = actual;

actual->dato = dat;

actual->izquierdo = actual->derecho = NULL;

}

/* Si el dato es mayor que el que contiene el nodo padre, lo insertamos

en la rama derecha */

else if(dat > padre->dato) {

actual = (Arbol)malloc(sizeof(tipoNodo));

padre->derecho = actual;

actual->dato = dat;

actual->izquierdo = actual->derecho = NULL;

}}

69 of 100

/* Eliminar un elemento de un árbol ABB */

void Borrar(Arbol *a, int dat)

{

pNodo padre = NULL;

pNodo actual;

pNodo nodo;

int aux;

 

actual = *a;

/* 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)}

70 of 100

padre->izquierdo = NULL;

free(actual); /* Borrar el nodo */

actual = NULL;

return;

}

else { /* Si el valor está en el nodo actual, pero no es hoja */

padre = actual;

/* Buscar nodo más izquierdo de rama derecha */

if(actual->derecho) {

nodo = actual->derecho;

while(nodo->izquierdo) {

padre = nodo;

nodo = nodo->izquierdo;

}

}

71 of 100

/* 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;

actual->dato = nodo->dato;

nodo->dato = aux;

actual = nodo;

}

}

72 of 100

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*);

*/

void InOrden(Arbol a, void (*func)(int*))

73 of 100

{

if(a->izquierdo) InOrden(a->izquierdo, func);

func(&(a->dato));

if(a->derecho) InOrden(a->derecho, func);

}

 

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

el prototipo:

void func(int*);

*/

void PreOrden(Arbol a, void (*func)(int*))

{

func(&a->dato);

if(a->izquierdo) PreOrden(a->izquierdo, func);

if(a->derecho) PreOrden(a->derecho, func);

}

74 of 100

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

el prototipo:

void func(int*);

*/

void PostOrden(Arbol a, void (*func)(int*))

{

if(a->izquierdo) PostOrden(a->izquierdo, func);

if(a->derecho) PostOrden(a->derecho, func);

func(&a->dato);

}

 

/* Buscar un valor en el árbol */

int Buscar(Arbol a, int dat)

{

pNodo actual = a;

/* 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->izquierdo; /* Seguir */

else if(dat > actual->dato) actual = actual->derecho;

}

return FALSE; /* No está en árbol */

}

75 of 100

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

int Altura(Arbol a, int dat)

{

int altura = 0;

pNodo actual = a;

 

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

while(!Vacio(actual)) {

if(dat == actual->dato) return altura; /* encontrado: devolver altura */

else {

altura++; /* Incrementamos la altura, seguimos buscando */

if(dat < actual->dato) actual = actual->izquierdo;

else if(dat > actual->dato) actual = actual->derecho;

}

}

return -1; /* No está en árbol, devolver -1 */

}

 

/* Contar el número de nodos */

int NumeroNodos(Arbol a, int *contador)

76 of 100

{

*contador = 0;

 

auxContador(a, contador); /* 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 auxContador(Arbol nodo, int *c)

{

(*c)++; /* Otro nodo */

/* Continuar recorrido */

if(nodo->izquierdo) auxContador(nodo->izquierdo, c);

if(nodo->derecho) auxContador(nodo->derecho, c);

}

 

/* Calcular la altura del árbol, que es la altura del nodo de mayor altura. */

int AlturaArbol(Arbol a, int *altura)

{

*altura = 0;

auxAltura(a, 0, altura); /* Función auxiliar */

return *altura;}

77 of 100

/* 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 auxAltura(pNodo nodo, int a, int *altura)

{

/* Recorrido postorden */

if(nodo->izquierdo) auxAltura(nodo->izquierdo, a+1, altura);

if(nodo->derecho) auxAltura(nodo->derecho, a+1, altura);

/* 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;

}

 

/* Comprobar si un árbol es vacío */

int Vacio(Arbol r)

{

return r==NULL;

}

 

/* Comprobar si un nodo es hoja */

int EsHoja(pNodo r)

78 of 100

{

return !r->derecho && !r->izquierdo;

}

 

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

void Mostrar(int *d)

{

printf("%d, ", *d);

}

79 of 100

GRAFOS

ESTRUCTURA DE DATOS

80 of 100

Conceptos matemáticos :

Grafo. Informalmente, un grafo es un conjunto de objetos llamados vértices o nodos unidos por enlaces llamados aristas o arcos, que permiten representar relaciones binarias entre elementos de un conjunto.

81 of 100

  • Los grafos son estructuras de datos
  • Representan relaciones entre objetos
    • Relaciones arbitrarias, es decir
    • No jerárquicas
  • Son aplicables en
    • Química
    • Geografía
    • Ing. Eléctrica e Industrial, etc.
    • Modelado de Redes
      • Eléctricas
      • Etc.

Impresora

Modem

PC2

Servidor

PC1

Dado un escenario donde ciertos objetos se relacionan, se puede “modela el grafo” y luego aplicar algoritmos para resolver diversos problemas

82 of 100

DEFINICION

  • Un grafo G = (V,A)
  • V, el conjunto de vértices o nodos
    • Representan los objetos
  • A, el conjunto de arcos
    • Representan las relaciones

V = {1, 4, 5, 7, 9}

A= {(1,4), (5,1), (7,9), (7,5), (4,9), (4,1), (1,5), (9,7), (5, 7), (9,4)}

1

4

5

7

9

83 of 100

Etiquetado. Distinción que se hace a los vértices y/o aristas mediante una marca que los hace unívocamente distinguibles del resto, es decir, asignarle a cada vértice o arista un nombre.

Terminología:

Adyacencia. Se dice que dos vértices son adyacentes si hay una arista que los conecte entre ellos.

Grado de un vértice. El grado de un vértice es un número natural de 0 al infinito que designa el número de aristas le conectan con otros vértices.

84 of 100

Incidencia. Una arista es incidente a un vértice si ésta lo une a otro.

Ponderación. Corresponde a una función que a cada arista le asocia un valor (costo, peso, longitud, etc.), para aumentar la expresividad del modelo.

Camino. Un camino es una secuencia de aristas que comienzan en un vértice del grafo y recorren parte o la totalidad del grafo conectando vértices adyacentes.

85 of 100

Circuito. Cuando existe un camino que empieza y acaba en el mismo vértice.

Isomorfismo. Si dos grafos son isomorfos sólo varía la apariencia, es decir, que se mantienen las adyacencias, estructura, caminos, ciclos, número de vértices y número de aristas.

Conexo. Un grafo es conexo si tiene una única componente conexa, es decir, todos los vértices del grafo están relacionados.

86 of 100

Grafo regular:

Grafo completo:

Grafo complementario:

Grafo bipartito:

Grafo bipartito completo:

Grafo original

Grafo complementario

Familias de grafos simples:

87 of 100

TIPOS DE GRAFOS

  • Grafos dirigidos
    • Si los pares de nodos que forman arcos
    • Son ordenados. Ej.: (u->v)
  • Grafos no dirigidos
    • Si los pares de nodos de los arcos
    • No son ordenados Ej.: u-v

C

E

D

F

H

V = {C, D, E, F, H}

A= {(C,D), (D,F), (E,H), (H,E), (E,C)}

1

4

5

7

9

Grafo del ejemplo anterior

88 of 100

OTROS CONCEPTOS

  • Arista
    • Es un arco de un grafo no dirigido
  • Vertices adyacente
    • Vertices unidos por un arco
  • Factor de Peso
    • Valor que se puede asociar con un arco
    • Depende de lo que el grafo represente
    • Si los arcos de un grafo tienen F.P.
      • Grafo valorado

Guayaquil

Quito

Cuenca

Ambato

Riobamba

5

5

7

9

8

7

89 of 100

GRADOS DE UN NODO

  • En Grafo No Dirigido
    • Grado(V)
      • Numero de aristas que contiene a V
  • En Grafo Dirigido
    • Grado de entrada, Graden(V)
      • Numero de arcos que llegan a V
    • Grado de Salida, Gradsal(V)
      • Numero de arcos que salen de V

C

E

D

F

H

Guayaquil

Quito

Cuenca

Ambato

Riobamba

5

5

7

9

8

7

Gradoent(D) = 1 y Gradsal(D) = 1

Grado(Guayaquil) = 3

90 of 100

CAMINOS

  • Definicion
    • Un camino P en un grafo G, desde V0 a Vn
    • Es la secuencia de n+1 vertices
    • Tal que (Vi, Vi+1)  A para 0 i  n

  • Longitud de camino
    • El numero de arcos que lo forman
  • Camino Simple
    • Todos los nodos que lo forman son distintos
  • Ciclo
    • Camino simple cerrado de long. >= 2
    • Donde V0 = Vn

4

7

9

6

10

11

Camino entre 4 y 7

P = {4, 6, 9, 7}

Longitud: 3

A

B

C

D

E

F

Camino A y A

P = {A, E, B, F, A}

Longitud: 4 – 4ciclo

91 of 100

CONECTIVIDAD

  • Grafo No Dirigido
    • Conexo
      • Existe un camino entre cualquier par de nodos
  • Grafo Dirigido
    • Fuertemente Conexo
      • Existe un camino entre cualquier par de nodos
    • Conexo
      • Existe una cadena entre cualquier par de nodos

3

9

5

7

2

4

5

6

8

H

A

B

D

92 of 100

REPRESENTACION

  • Dos posibles representaciones
    • Estatica: Matriz de Adyacencia
      • Los vertices se representan por indices(0…n)
      • Las relaciones de los vertices se almacenan en una Matriz
    • Dinamica: Lista de Adyacencia
      • Los vertices forman una lista
      • Cada vertice tiene una lista para representar sus relaciones(arcos)

93 of 100

MATRIZ DE ADYACENCIA

  • Dado un Grafo G = (V, A)
  • Sean los Vertices V = {V0, V1, … Vn}
    • Se pueden representar por ordinales 0,1,..n
  • Como representar los Arcos?
    • Estos son enlaces entre vertices
  • Puede usarse una matriz

Si el grafo fuese valorado, en vez de 1, se coloca el factor de peso

4

7

9

6

10

11

V0

V1

V2

V3

V4

V5

94 of 100

EL TIPO DE DATO

  • Los Vertices
    • Se definen en un Arreglo
  • Los Arcos
    • Se definen en una Matriz

95 of 100

LISTA DE ADYACENCIA

  • Si una matriz
    • Tiene muchos vertices y
    • Pocos arcos
    • La Matriz de Adyacencia
      • Tendra demasiados ceros
      • Ocupara mucho espacio
  • Los vertices
    • Pueden formar una lista, no un vector
  • Los arcos
    • Son relaciones entre vertices
    • Se pueden representar con una lista x cada vertice

4

7

9

6

10

11

4

6

9

7

10

11

6

4

6

9

11

10

9

7

96 of 100

EL TIPO DE DATO

  • Cada vertice tiene
    • Contenido
    • Siguiente
    • Una lista de adyacencia
  • Cada nodo en la lista de adyacencia
    • Peso del arco
    • Siguiente
    • Una referencia al vertice(arco)

97 of 100

RECORRIDOS DEL GRAFO

  • Se busca
    • Visitar todos los nodos posibles
    • Desde un vertice de partida D
    • Cualquiera
  • Existe dos posibles recorridos
    • En Anchura y
    • En Profundidad

98 of 100

RECORRIDO EN ANCHURA

  • Encolar vertice de partida
  • Marcarlo como “visitado”
  • Mientras la cola no este vacia
    • Desencolar vertice W
    • Mostrarlo
    • Marcar como visitados
      • Los vertices adyacentes de W
      • Que no hayan sido ya visitados
    • Encolarlos

99 of 100

EJEMPLO

A

T

H

B

D

C

R

D

Cola

Se Muestra:

D

B

C

B

C

C

H

H

R

H

R

A

R

A

T

T

A

T

T

100 of 100

RECORRIDO EN PROFUNDIDAD

  • Marcar vertice origen V como visitado
  • Recorrer en Profundidad
    • Cada vertice adyacente de V
    • Que no haya sido visitado
  • Ejemplo

A

T

H

B

D

C

R

Se Muestra:

Pila

D

D

B

C

C

R

R

H

H

A

T

T

A

B