lunes, 11 de diciembre de 2017

Algoritmos Insertar y Heapify

Algoritmo Insertar
Cuando se llega a un árbol vacío se crea el nodo en el puntero que se pasa como parámetro por referencia, de esta manera los nuevos enlaces mantienen la coherencia. Si el elemento a insertar ya existe entonces no se hace nada.
void insertar(tarbol **a, int elem)
{
  if (*a == NULL) {
    *a = (arbol *) malloc(sizeof(arbol));
    (*a)->clave = elem;
    (*a)->izq = (*a)->der = NULL;
  }
  else if ((*a)->clave < elem) insertar(&(*a)->der, elem);
  else if ((*a)->clave > elem) insertar(&(*a)->izq, elem);
}
Algoritmo Heapify

Llamada recursiva a Heapify para sus i’s subárboles, si no cumplen con la propiedad del heap 



       Heap-size[A] à 10 
       A[2], i = 2 no cumple con la propiedad de un heap (MAX-HEAP) 



       Tiempo de ejecución de heapify

       En un subárbol de tamaño n, con raíz en el nodo i
       Θ(1), relación fija entre A[i], A[LEFT(i)], A[RIGHT(i)] más
       Tiempo de ejecución de HEAPIFY en un subárbol con raíz en uno de los hijos de i 
       Tamaño de los subárboles de los hijos es a lo más 2n/3 
       T(n) ≤ T(2n/3) Θ(1) 
       Por el caso 2 del método maestro 
       T(n) = O(lgn) 
       Alternativa 
       Caracterizar tiempo de ejecución de HEAPIFY sobre un nodo de altura h como: O(h)

       Uso de heapify para construir el heap 

       Los elementos A[(floor(n/2) + 1) … n] son hojas del árbol 
       Orden en que se procesan los nodos garantiza que los subárboles con raíz en los hijos del nodo i son heaps antes de que HEAPIFY se ejecute en dicho nodo 
       Algoritmo BuildHeap





Teoremas I Y II Para Árboles Binarios

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. Existen tipos de árboles binarios que suelen usarse para fines específicos, como:
  • Árbol binario de búsqueda
  • Árbol de Fibonacci


     Árbol binario de búsqueda

     Sea A un árbol binario de raíz R e hijos izquierdo y derecho (posiblemente nulos) HI y HD , respectivamente. Se dice que A es un árbol binario de búsqueda (ABB) si y solo si se satisfacen las dos condiciones al mismo tiempo:
·         "HI es vacío" {\displaystyle \lor }V ("R es mayor que todo elemento de HI{\displaystyle \land }۸ "HI es un ABB").
·         "HD es vacío" {\displaystyle \lor }V ("R es menor que todo elemento de HD{\displaystyle \land }۸ "HD es un ABB").
Donde "۸{\displaystyle \land } " es la conjunción lógica "y", y "{\displaystyle \lor } v" es la disyunción lógica "o".
       Para una fácil comprensión queda resumido en que es un árbol binario que cumple que el subárbol izquierdo de cualquier nodo (si no está vacío) contiene valores menores que el que contiene dicho nodo, y el subárbol derecho (si no está vacío) contiene valores mayores. Para estas definiciones se considera que hay una relación de orden establecida entre los elementos de los nodos. Que cierta relación esté definida, o no, depende de cada lenguaje de programación. De aquí se deduce que puede haber distintos árboles binarios de búsqueda para un mismo conjunto de elementos. La altura h en el peor de los casos es siempre el mismo tamaño que el número de elementos disponibles. Y en el mejor de los casos viene dada por la expresión h=[log2 (c+1)*]. Un árbol binario de búsqueda no deja de ser un caso particular de árbol binario, así usando la siguiente especificación de árbol binario en maude:

fmod ARBOL-BINARIO {X :: TRIV}is
   sorts ArbolBinNV{X} ArbolBin{X} .
   subsort ArbolBinNV{X} < ArbolBin{X} .
   *** generadores
   op crear : -> ArbolBin{X} [ctor] .
   op arbolBin : X$Elt ArbolBin{X} ArbolBin{X} -> ArbolBinNV{X} [ctor] .
   endfm

       Se puede hacer la siguiente definición para un árbol binario de búsqueda (también en maude):

fmod ARBOL-BINARIO-BUSQUEDA {X :: ORDEN} is
       protecting ARBOL-BINARIO{VOrden}{X} .
       sorts ABB{X} ABBNV{X} .
       subsort ABBNV{X} < ABB{X} .
       subsort ABB{X} < ArbolBin{VOrden}{X} .
       subsort ABBNV{X} < ArbolBinNV{VOrden}{X} .
   *** generadores
   op crear : -> ArbolBin{X} [ctor] .
   op arbolBin : X$Elt ArbolBin{X} ArbolBin{X} -> ArbolBinNV{X} [ctor] .
   endfm

       Con la siguiente teoría de orden:

fth ORDEN is
    protecting BOOL .
    sort Elt .
    *** operaciones
    op _<_ : Elt Elt -> Bool .
    endfth

       Para que un árbol binario pertenezca al tipo árbol binario de búsqueda debe cumplir la condición de ordenación siguiente que iría junto al módulo ARBOL-BINARIO-BUSQUEDA:

var  R : X$Elt .
   vars INV DNV : ABBNV{X} .
   vars I D : ABB{X} .
   mb crear : ABB{X} .
   mb arbolBin(R, crear, crear) : ABBNV{X} .
   cmb arbolBin(R, INV, crear) : ABBNV{X} if R > max(INV) .
   cmb arbolBin(R, crear, DNV) : ABBNV{X} if R < min(DNV) .
   cmb arbolBin(R, INV, DNV) : ABBNV{X} if (R > max(INV)) and (R < min(DNV)) .
   ops min max : ABBNV{X} -> X$Elt .
   eq min(arbolBin(R, crear, D)) = R .
   eq min(arbolBin(R, INV, D)) = min(INV) .
   eq max(arbolBin(R, I, crear)) = R .
   eq max(arbolBin(R, I, DNV)) = max(DNV) .
class nodo:
    izq , der, dato = None, None, 0
   
    def __init__(self, dato):
        # crea un nodo
        self.izq = None
        self.der = None
        self.dato = dato
class arbolBinario:
    def __init__(self):
        # inicializa la raiz
        self.raiz = None
   
    def agregarNodo(self, dato):
        # crea un nuevo nodo y lo devuelve
        return nodo(dato)

    def insertar(self, raiz, dato):
        # inserta un dato nuevo en el árbol
        if raiz == None:
            # si no hay nodos en el árbol lo agrega
            return self.agregarNodo(dato)
        else:
            # si hay nodos en el árbol lo recorre
            if dato <= raiz.dato:
                # si el dato ingresado es  menor que el dato guardado va al subárbol izquierdo
                raiz.izq = self.insertar(raiz.izq, dato)
            else:
                # si no, procesa el subárbol derecho
                raiz.der = self.insertar(raiz.der, dato)
            return raiz

       Árbol de Fibonacci

       Árboles de Fibonacci de orden 1, 2, 3 y 4.
     Se llama árbol de Fibonacci a una variante de árbol binario con la propiedad que el orden de un nodo se calcula como la sucesión de Fibonacci. El árbol de Fibonacci se define de la siguiente manera:
  • El árbol nulo (no contiene ningún nodo) es de orden 0.
  •  El árbol que consta de un único nodo es de orden 1.

       Para n > 1, el árbol de Fibonacci de orden n consta de un nodo raíz con el árbol de Fibonacci de orden n-1 como hijo izquierdo y el árbol de Fibonacci de orden n-2 como hijo derecho. Dado que este tipo de árbol es un caso particular de un árbol AVL, ya que el factor de equilibrio de todo nodo es -1, un árbol de Fibonacci es balanceado con altura O(log n). 


TArbinDinamico<int> arbolFibonacci (int n){
TArbinDinamico<int> res;
if (n==0)
 res = TArbinDinamico(0, n, 0);
else if (n==1)
 res = TArbinDinamico(1, n, 0);
else
 res = TArbinDinamico(arbolFibonacci(n-1), n, arbolFibonacci(n-2));
return res;
}

Tipos De Árboles (Binarios y Heap)

Árboles Binarios

     Un árbol binario 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.






Los Montículos binarios

     Un caso particular y sencillo de la estructura de datos Montículo, y está basada en un árbol binario balanceado, que puede verse como un árbol binario con dos restricciones adicionales:

· Propiedad de montículo

     Cada nodo contiene un valor superior al de sus hijos (para un montículo por máximos) o más pequeño que el de sus hijos (para un montículo por mínimos).

· Árbol semi completo

     El árbol está balanceado y en un mismo nivel las inserciones se realizan de izquierda a derecha. Los montículos por mínimos se utilizan frecuentemente para representar colas de prioridad. A continuación se muestran dos montículos: el primero por mínimos y el segundo por máximos, que representan el mismo conjunto de valores y además son semi completos.



jueves, 7 de diciembre de 2017

Recursividad

De forma recursiva, un árbol como un tipo de datos se define como un valor (de un cierto tipo de datos, posiblemente vacía), junto con una lista de los árboles (posiblemente una lista vacía), los subárboles de sus hijos:

t: v [t[1], ..., t[k]]

(Un árbol t se compone de un valor v y una lista de otros árboles.)

Más elegantemente, a través de la recursión mutua, de los cuales un árbol es uno de los ejemplos más básicos, un árbol se puede definir en términos de un bosque (una lista de árboles), donde un árbol consta de un valor y un bosque (los subárboles de sus hijos):

f: [t[1], ..., t[k]]

t: v f

Tenga en cuenta que esta definición es en términos de valores, y es apropiada en lenguaje funcional (se asume la transparencia referencial); diferentes árboles no tienen conexión, ya que son simplemente listas de valores.

Como una estructura de datos, un árbol se define como un nodo (la raíz), que a su vez consta de un valor (de algún tipo de datos, posiblemente vacío), junto con una lista de referencias a otros nodos (lista posiblemente vacía y referencias posiblemente nulas):

n: v [&n[1], ..., &n[k]]

(Un nodo n se compone de un valor v y una lista de referencias a otros nodos.)

Esta estructura de datos define un grafo dirigido,1​ y para que sea un árbol hay que añadir una condición en su estructura global (en su topología), y esto es que a lo sumo una referencia puede apuntar a cualquier nodo dado (un nodo tiene como máximo un solo padre), y ningún nodo del árbol puede apuntar a la raíz. De hecho, todos los nodos (que no sean la raíz) deben tener exactamente un padre, y la raíz no debe tener ningún padre.

De hecho, dada una lista de nodos, y para cada nodo de una lista de referencias a sus hijos, no se puede saber si esta estructura es un árbol o no sin analizar su estructura global, como se define a continuación.

- Tipos de Arboles (Binarios y HEAP).

-  Teoremas I y II para árboles Binarios.


-  Algoritmos INSERTAR y HEAPIFY

Tipo de datos frente a la estructura de datos

Hay una distinción entre un árbol como un tipo de datos abstracto y como una estructura concreta de datos, de forma análoga a la distinción entre una lista y lista enlazada. Como tipo de dato, un árbol tiene un valor e hijos, y los hijos son a su vez subárboles; el valor y los hijos de un árbol se interpreta como el valor del nodo raíz y los subárboles de los hijos del nodo raíz. Para permitir que los árboles finitos, hay que, o bien permitir que la lista de los hijos pueda estar vacía, o permitir que los árboles puedan estar vacíos, en cuyo caso la lista de los hijos pueden ser de tamaño fijo (factor de ramificación, especialmente 2 o binario), si se desea.

   Como una estructura de datos, un árbol vinculado es un grupo de nodos, donde cada nodo tiene un valor y una lista de referencias a otros nodos (sus hijos). Esta estructura de datos realmente define a un grafo dirigido,1​ porque puede tener bucles o varias referencias al mismo nodo, del mismo modo que una lista enlazada. Luego también existe el requisito de que no hay dos referencias que apuntan al mismo nodo (que cada nodo tiene como máximo un solo padre, y de hecho todos lo tienen, a excepción de la raíz), y un árbol que viola esto es árbol corrupto.

   Debido al uso de referencias a los árboles en la estructura de datos de un árbol enlazado, a menudo se habla de árboles en los que implícitamente están siendo representados por referencias al nodo raíz, ya que esto es, normalmente, la forma en la que se aplican realmente. Por ejemplo, en lugar de un árbol vacío, uno puede tener una referencia nula: un árbol nunca está vacío, sino que una referencia a un árbol puede ser nula.