ARBOLES B
Los árboles B se utilizan para la creación de bases de datos. Otra aplicación dada a los árboles B es la gestión del sistema de archivos de los sistemas operativos, con el fin de aumentar la eficacia en la búsqueda de archivos por los subdirectorios. subdirectorios. También se conocen aplicaciones de los árboles B en sistemas de compresión de datos. Bastantes algoritmos de compresión utilizan árboles B para la búsqueda por clave de datos comprimidos. En un árbol B se suele denominar a los nodos como páginas. Cada nodo, cada página, es una unidad a la que se accede en bloque.
Como se puede observar en la figura 1,un B-árbol se dice que es de orden m si sus nodos pueden contener hasta un máximo de m hijos.En la literatura también aparece que si un árbol es de orden m significa que el mínimo número de hijos que puede tener es m+1(m claves). El conjunto de claves que se sitúan en un nodo cumplen la condición:
de forma que los elementos que cuelgan del primer hijo tienen una clave con valor menor que K 1,los que cuelgan del segundo tienen una clave con valor ma yor que K 1 y menor que K 2,etc...Obviamente,los ,etc...Obviamente,los que cuelgan del último hijo tienen una clave con valor mayor que la última últ ima clave(hay que tener en cuenta que el nodo puede tener menos de m hijos y por consiguiente menos de m-1 claves).
CARACTERISTICAS:
Un parámetro muy importante en los árboles-B es el ORDEN (m). El orden de un árbol-B es el número máximo de ramas que pueden partir de un nodo.
Si “n” es el número de ramas que parten de un nodo de un árbol -b, el nodo contendrá n-1 claves. El árbol está ordenado. Todos los nodos terminales, (nodos hoja), están en el mismo nivel. Todos los nodos intermedios, excepto el raíz, deben tener entre m/2 y m ramas no nulas. El máximo número de claves por nodo es m-1. El mínimo número de claves por nodo es (m/2)-1. La profundidad (h) es el número máximo de consultas para encontrar una clave.
La condición de que todas las hojas de un árbol B se encuentren en el mismo nivel impone el comportamiento característico de los árboles B: crecen “hacia arriba”, crecen en la raíz.
OPERACIONES BASICAS:
BÚSQUEDA EN UN B-ÁRBOL. Localizar una clave en un B-árbol es una operación simple pues consiste en situarse en el nodo raíz del árbol, si la clave se encuentra ahí hemos terminado y si no es así seleccionamos de entre los hijos el que se encuentra entre dos valores de clave que son menor y mayor que la buscada respectivamente y repetimos el proceso hasta que la encontremos. En caso de que se llegue a una hoja y no podamos proseguir la búsqueda la clave no se encuentra en el árbol. En definitiva, los pasos a seguir son los siguientes: 1. Seleccionar como nodo actual la raíz del árbol. 2. Comprobar si la clave se encuentra en el nodo actual: 1. Si la clave está, fin. 2. Si la clave no está: Si estamos en una hoja, no se encuentra la clave. Fin. Si no estamos en una hoja, hacer nodo actual igual al hijo que corresponde según el valor de la clave a buscar y los valores de las claves del nodo actual(i buscamos la clave K en un nodo con n claves: el hijo izquierdo si KK n y el hijo i-ésimo si K i
INSERCIÓN EN UN B-ÁRBOL. Para insertar una nueva clave usaremos un algoritmo que consiste en dos pasos recursivos:
1. Buscamos la hoja donde debiéramos encontrar el valor de la clave de una forma totalmente paralela a la búsqueda de ésta (si en esta búsqueda encontramos en algún lugar del árbol la clave a insertar, el algoritmo no debe hacer nada más).Si la clave no se encuentra en el árbol habremos llegado a una hoja que es justamente el lugar donde debemos realizar esa inserción. 2. Situados en un nodo donde realizar la inserción si no está completo, es decir, si el número de claves que existen es menor que el orden menos 1 del árbol, el elemento puede ser insertado y el algoritmo termina. En caso de que el nodo esté completo insertamos la clave en su posición y puesto que no caben en un único nodo dividimos en dos nuevos nodos conteniendo cada uno de ellos la mitad de las claves y tomando una de éstas para insertarla en el padre (se usará la mediana).Si el padre está también completo, habrá que repetir el proceso hasta llegar a la raíz. En caso de que la raíz esté completa, la altura del árbol aumenta en uno creando un nuevo nodo raíz con una única clave. En la figura 2 podemos observar el efecto de insertar una nueva clave en un nodo que está lleno.
Podemos realizar una modificación al algoritmo de forma que se retrase al máximo el momento de romper un nodo en dos. Con ello podríamos vernos beneficiados por dos razones fundamentalmente: 1. La razón más importante para modificar así el algoritmo es que los nodos en el árbol están más llenos con lo cual el gasto en memoria para mantener la estructura es mucho menor. 2. Retrasamos el momento en que la raíz llega a dividirse y por consiguiente retrasamos el momento en que la altura del árbol aumenta. La forma más sencilla de realizar esta modificación es que en el caso de que tengamos que realizar esa división, antes de llevarla a cabo, comprobemos si los hermanos adyacentes tienen espacio libre de forma que si alguno de ellos lo tiene se redistribuyen las claves que se encuentran en el nodo actual más las de ese hermano más la clave que los separa(que se encuentra en el padre)más la clave a insertar de forma que en el padre se queda la mediana y las demás quedan distribuidas entre los dos nodos. En la figura 3 podemos observar el efecto de insertar una nueva clave en un nodo que está lleno pero con redistribución.
BORRADO EN UN B-ÁRBOL. La idea para realizar el borrado de una clave es similar a la i nserción teniendo en cuenta que ahora,en lugar de divisiones,realizamos uniones.Existe un problema añadido,las claves a borrar pueden aparecer en cualquier lugar del árbol y por consiguiente no coincide con el caso de la inserción en la que siempre comenzamos desde una hoja y propagamos hacia arriba.La solución a esto es inmediata pues cuando borramos una clave que está en un nodo interior,lo primero que realizamos es un intercambio de este valor con el inmediato sucesor en el árbol,es decir,el hijo más a la izquierda del hijo derecho de esa clave. Las operaciones a realizar para poder llevar a cabo el borrado son por tanto: 1. Redistribución:la utilizaremos en el caso en que al borrar una clave el nodo se queda con un número menor que el mínimo y uno de los hermanos adyacentes tiene al menos uno más que ese mínimo,es decir,redistribuyendo podemos solucionar el problema. 2. Unión:la utilizaremos en el caso de que no sea posible la redistribución y por tanto sólo será posible unir los nodos junto con la clave que los separa y se encuentra en el padre. En definitiva,el algoritmo nos queda como sigue: 1. Localizar el nodo donde se encuentra la clave. . 2. Si el nodo localizado no es una hoja,intercambiar el valor de la clave localizada con el valor de la clave más a la izquierda del hijo a la derecha.En definitiva colocar la clave a borrar en una hoja.Hacemos nodo actual igual a esa hoja.
3. Borrar la clave. 4. Si el nodo actual contiene al menos el mínimo de claves como para seguir siendo un B-árbol,fin. 5. Si el nodo actual tiene un número menor que el mínimo: 1. Si un hermano tiene más del mínimo de claves,redistribución y fin. 2. Si ninguno de los hermanos tiene más del mínimo,unión de dos nodos junto con la clave del padre y vuelta al paso 4 para propagar el borrado de dicha clave(ahora en el padre).
IMPLEMENTACION:
1. public class NodoT 2. { 3. public NodoT NodoIzquierdo; 4. public int Informacion; 5. public NodoT NodoDerecho; 6. //Constructor 7. public NodoT() 8. { 9. this.NodoIzquierdo=null; 10. this.Informacion=0; 11. this.NodoDerecho=null; 12. } 13. } 14. /// 15. /// Summary description for Class1. 16. /// 17. class Class1 18. { 19. /// 20. /// The main entry point for the application. 21. /// 22. [STAThread] 23. static void Main(string[] args) 24. { 25. ClearConsole ClearMyConsole = new ClearConsole(); 26. int Opcion=0; 27. NodoT Raiz=null; 28. int Dato; 29. do 30. { 31. Opcion=Menu(); 32. switch(Opcion) 33. { 34. case 1: Console.Write("Valor del Nuevo Nodo: ");
35. Dato=int.Parse(Console.ReadLine()); 36. if(Raiz==null) 37. { 38. NodoT NuevoNodo=new NodoT(); 39. NuevoNodo.Informacion=Dato; 40. Raiz=NuevoNodo; 41. } 42. else 43. { 44. Insertar(Raiz, Dato); 45. } 46. ClearMyConsole.Clear(); 47. break; 48. //Recorrido en Pre Orden del Arbol 49. case 2: RecorridoPreorden(Raiz); 50. Console.WriteLine("Fin del Recorrido,..."); 51. Console.ReadLine(); 52. ClearMyConsole.Clear(); 53. break; 54. //Recorrido en Post Orden del Arbol 55. case 3: RecorridoPostorden(Raiz); 56. Console.WriteLine("Fin del Recorrido,..."); 57. Console.ReadLine(); 58. ClearMyConsole.Clear(); 59. break; 60. //Recorrido en In Orden del Arbol 61. case 4: RecorridoInorden(Raiz); 62. Console.WriteLine("Fin del Recorrido,..."); 63. Console.ReadLine(); 64. ClearMyConsole.Clear(); 65. break; 66. case 5: Console.Write("Teclee el Dato a Buscar: ");
67. Dato=int.Parse(Console.ReadLine()); 68. if(Raiz!=null) 69. { 70. BuscarNodo(Raiz, Dato); 71. } 72. else 73. { 74. Console.WriteLine("ERROR, Arbol Vacio...."); 75. } 76. ClearMyConsole.Clear(); 77. break; 78. case 6: Console.Write("Teclee el Dato a Eliminar: "); 79. Dato=int.Parse(Console.ReadLine()); 80. if(Raiz!=null) 81. { 82. EliminarNodo(ref Raiz, Dato); 83. } 84. else 85. { 86. Console.WriteLine("ERROR, Arbol Vacio...."); 87. } 88. ClearMyConsole.Clear(); 89. break; 90. case 7: Finalizar(); 91. break; 92. } 93. }while(Opcion!=7); 94. 95. } 96. static int Menu() 97. { 98. ClearConsole ClearMyConsole = new ClearConsole(); 99. int Resultado=0; 100. do 101. { 102. Console.WriteLine("MENU DE ARBOLES"); 103. Console.WriteLine(""); 104. Console.WriteLine("1.- Registrar un Nuevo Nodo");
105. Console.WriteLine("2.- Recorrido en Pre-orden"); 106. Console.WriteLine("3.- Recorrido en Post-orden"); 107. Console.WriteLine("4.- Recorrido en In-orden"); 108. Console.WriteLine("5.- Buscar un Nodo"); 109. Console.WriteLine("6.- Eliminar un Nodo"); 110. Console.WriteLine("7.- Finalizar el Programa"); 111. Console.WriteLine(""); 112. Console.Write("Teclee la Opcion Deseada: "); 113. Resultado=int.Parse(Console.ReadLine()); 114. Console.WriteLine(""); 115. if(Resultado<1||Resultado>7) 116. { 117. Console.WriteLine("ERROR, Opcion Invalida...."); 118. Console.ReadLine(); 119. Console.WriteLine(""); 120. } 121. ClearMyConsole.Clear(); 122. }while(Resultado<1||Resultado>7); 123. return Resultado; 124. } 125. //Insertar en un arbol binario 126. static void Insertar(NodoT Raiz, int Dato) 127. { 128. if(DatoRaiz.Informacion)
145. { 146. if(Raiz.NodoDerecho==null) 147. { 148. NodoT NuevoNodo=new NodoT(); 149. NuevoNodo.Informacion=Dato; 150. Raiz.NodoDerecho=NuevoNodo; 151. } 152. else 153. { 154. //Llamada recursiva por el lado derecho 155. Insertar(Raiz.NodoDerecho, Dato); 156. } 157. } 158. else 159. { 160. //El Nodo existe en el Arbol 161. Console.WriteLine("Nodo Existente, Imposible Insertar..."); 162. Console.ReadLine(); 163. } 164. } 165. } 166. //Metodo de recorrido en Pre-Orden 167. static void RecorridoPreorden(NodoT Raiz) 168. { 169. if(Raiz!=null) 170. { 171. Console.Write("{0}, ",Raiz.Informacion); 172. RecorridoPreorden(Raiz.NodoIzquierdo); 173. RecorridoPreorden(Raiz.NodoDerecho); 174. } 175. } 176. //Metodo de recorrido en In-Orden 177. static void RecorridoInorden(NodoT Raiz) 178. { 179. if(Raiz!=null) 180. { 181. RecorridoInorden(Raiz.NodoIzquierdo); 182. Console.Write("{0}, ",Raiz.Informacion); 183. RecorridoInorden(Raiz.NodoDerecho);
184. } 185. } 186. //Metodo de recorrido en Post-Orden 187. static void RecorridoPostorden(NodoT Raiz) 188. { 189. if(Raiz!=null) 190. { 191. RecorridoPostorden(Raiz.NodoIzquierdo); 192. RecorridoPostorden(Raiz.NodoDerecho); 193. Console.Write("{0}, ",Raiz.Informacion); 194. } 195. } 196. //Metodo de Buscar un nodo 197. static void BuscarNodo(NodoT Raiz, int Dato) 198. { 199. if(DatoRaiz.Informacion) 215. { 216. //Buscar por el Sub-Arbol derecho 217. if(Raiz.NodoDerecho==null) 218. { 219. Console.WriteLine("ERROR, No se encuentra el Nodo..."); 220. Console.ReadLine(); 221. } 222. else 223. {
224. BuscarNodo(Raiz.NodoDerecho, Dato); 225. } 226. } 227. else 228. { 229. //El nodo se encontro 230. Console.WriteLine("Nodo Localizado en el Arbol..."); 231. Console.ReadLine(); 232. } 233. } 234. } 235. //Metodo de Eliminar 236. static void EliminarNodo(ref NodoT Raiz, int Dato) 237. { 238. if(Raiz!=null) 239. { 240. if(DatoRaiz.Informacion) 247. { 248. EliminarNodo(ref Raiz.NodoDerecho, Dato); 249. } 250. else 251. { 252. //Si lo Encontro 253. NodoT NodoEliminar=Raiz; 254. if(NodoEliminar.NodoDerecho==null) 255. { 256. Raiz=NodoEliminar.NodoIzquierdo; 257. } 258. else 259. { 260. if(NodoEliminar.NodoIzquierdo==null) 261. { 262. Raiz=NodoEliminar.NodoDerecho; 263. } 264. else 265. {
266. NodoT AuxiliarNodo=null; 267. NodoT Auxiliar=Raiz.NodoIzquierdo; 268. bool Bandera=false; 269. while(Auxiliar.NodoDerecho!=null) 270. { 271. AuxiliarNodo=Auxiliar; 272. Auxiliar=Auxiliar.NodoDerecho; 273. Bandera=true; 274. } 275. Raiz.Informacion=Auxiliar.Informacion; 276. NodoEliminar=Auxiliar; 277. if(Bandera==true) 278. { 279. AuxiliarNodo.NodoDerecho=Auxiliar.NodoIzquierdo; 280. } 281. else 282. { 283. Raiz.NodoIzquierdo=Auxiliar.NodoIzquierdo; 284. } 285. } 286. } 287. } 288. } 289. } 290. else 291. { 292. Console.WriteLine("ERROR, EL Nodo no se Encuentra en el Arbol..."); 293. Console.ReadLine(); 294. } 295. } 296. //Metodo de Finalizacion 297. static void Finalizar() 298. { 299. Console.WriteLine("Fin del Programa, press any key to continue,..."); 300. Console.ReadLine(); 301. } 302. }