Construir un árbol binario de búsqueda en JavaScript es más sencillo cuando dominas la regla central: en un binary search tree, los valores menores van siempre al lado izquierdo y los mayores al lado derecho. Aquí verás cómo detectar errores lógicos, modelar nodos y programar el método de insert con validaciones claras.
¿Qué corrige la lógica de un binary search tree?
La confusión inicial surge cuando alguna rama rompe la regla de orden. Si un nodo que debería aumentar aparece a la izquierda o uno que debería disminuir aparece a la derecha, el árbol queda mal formado. La lógica debe aplicarse a todas las ramas, no solo a nivel inmediato del root.
Si los nodos a la derecha no incrementan, la estructura es incorrecta.
Si los nodos a la izquierda no disminuyen, la estructura es incorrecta.
La validación debe repetirse en cada nivel del árbol.
¿Qué árbol de ejemplo vamos a formar?
Se construye un árbol con root 10; hijos 4 y 20; y, en niveles siguientes, 2, 8, 17 y 170. La forma correcta respeta el orden:
Izquierda del 10: 4, y a su vez 2 y 8.
Derecha del 10: 20, y a su vez 17 y 170.
Así, los valores a la derecha siempre aumentan y a la izquierda siempre disminuyen.
¿Cómo se modela el nodo y el árbol en JavaScript?
Cada nodo tiene tres propiedades: left, right y value. El árbol mantiene una referencia al root para iniciar recorridos e inserciones.
// Nodo: left, right, valueclassNode{constructor(value){this.left=null;this.right=null;this.value= value;}}// Árbol: referencia a root e inserción de valoresclassBinarySearchTree{constructor(){this.root=null;// inicia sin root}insert(value){const newNode =newNode(value);// Caso 1: árbol vacío → newNode es rootif(this.root===null){this.root= newNode;returnthis;// importante para romper el flujo}// Caso 2: árbol con elementos → recorrerlet currentNode =this.root;// *current node*while(true){// *loop* iterativoif(value < currentNode.value){// Ir a la izquierdaif(!currentNode.left){ currentNode.left= newNode;returnthis;} currentNode = currentNode.left;}else{// Ir a la derechaif(!currentNode.right){ currentNode.right= newNode;returnthis;} currentNode = currentNode.right;}}}}
Se usa una class para el árbol y otra para el nodo.
El constructor del árbol inicia con this.root = null.
El insert crea un new Node(value) y valida si el árbol está vacío.
El recorrido usa un while (true) y rompe con return this al insertar.
La decisión izquierda/derecha depende de value < currentNode.value.
¿Cómo funciona el método insert y el recorrido con while?
El método de insert cubre dos escenarios: crear el root y, si ya existe, recorrer la estructura hasta encontrar el hueco correcto. El recorrido es totalmente guiado por comparaciones menor/mayor.
Menor que el nodo actual: ir a left. Si left es null, insertar ahí.
Mayor o igual que el nodo actual: ir a right. Si right es null, insertar ahí.
En ambos casos, si existe un hijo, mover el current node y continuar el loop.
¿Cómo probar la inserción y validar la estructura?
Prueba rápida en JS creando una instancia del árbol y agregando valores en orden.
const tree =newBinarySearchTree();tree.insert(10);// roottree.insert(4);tree.insert(20);tree.insert(2);tree.insert(8);tree.insert(17);tree.insert(170);
Resultados esperados al inspeccionar:
root.value es 10.
root.left.value es 4 y sus hijos son 2 y 8.
root.right.value es 20 y sus hijos son 17 y 170.
¿Qué habilidades y conceptos refuerzas con este ejercicio?
Invariantes de estructura en un binary search tree.
Modelado con class y constructor en JavaScript.
Recorrido iterativo con while y control de flujo con return.
Comparaciones y asignación condicional para izquierda/derecha.
Manejo de null y uso de variables temporales como current node.
¿Qué sigue con el método search?
El siguiente paso es programar search: recibe un número y devuelve el nodo completo donde se encuentra (incluyendo sus hijos). La lógica de recorrido es muy similar a la de insert, solo que aquí no insertas: comparas y avanzas hasta encontrar o no el valor.
¿Te animas a implementar search? Compártelo en comentarios para que todos veamos tu enfoque y podamos aprender juntos.
Yo aprendí a hacer las búsquedas de árboles binarios de forma recursiva (además que se ve más fancy 👀) Aquí dejo mi código:
search(value, tree =this.root){if(tree ==null){return"El elemento no se encuentra.";}elseif(value > tree.value){returnthis.search(value, tree.right);}elseif(value < tree.value){returnthis.search(value, tree.left);}else{return"¡El elemento ha sido encontrado!";}}
No cumple con lo que pidió el profesor, porque no retorna un nodo.
El método search por lo general para cualquier lenguaje de programación suele regresar un valor booleano, este método es más para saber si existe algo o no, no tiene sentido regresar el mismo valor que estás buscando.
.
Para este caso, como se plantea regresar un nodo si aplica, simplemente al return le pondrías el nodo a retornar:
// return "¡El elemento ha sido encontrado!";return tree;
Arbol Binario
La siguiente imagen ayuda a explicar lo que se esta haciendo en el código fuente.
Una característica que me encanta de los árboles binarios es que su implementación es muy fácil de realizar haciendo uso de arreglos.
¿Cuáles son las ganancias de realizarlo usando simplemente arreglos? Simple, el espacio utilizado es muchísimo menor al utilizado por objetos y cuando tenemos un árbol binario completo el desperdicio de espacio es 0.
¿Qué hay que tener en cuenta? Unos simples métodos que podemos obtener con fórmulas matemática super básicas:
Obtener el indice padre del nodo actual = Math.floor((index - 1) / 2)
Obtener el indice del hijo de la izquierda del nodo actual = 2 * index + 1
Obtener el indice del hijo de la derecha del nodo actual = 2 * index + 2
Aquí paso mi código con el ejercicio implementado haciendo uso de un array como estructura de datos para reflejar un árbol binario.
Lo que está raro en ese árbol es que el "10" NO debería ir ahí, esto porque cualquier cosa que esté del lado del descendiente derecho del 33 debería ser mayor a 33 ^^
Tienes razón
Entiendo. Pero ¿entonces a dónde debería ir el "10"?
¿Sería abajo-derecha del "9"?
Mi código quedo así:
search(value){let current =this.root;while( current && current.value!= value ){if( value < current.value){ current = current.left;}else{ current = current.right;}}return current;}
me encanto tu solución yo llegue a una similar pero mucho mas verbosa, la tuya se entiende mucho mejor así que te la voy a robar :p
import'tree.dart';voidmain(List<String> arguments){final tree =BinarySearchTree(); tree.insert(10);print(tree.root);}
Alguien sabe como hacer el método delete?
Hola! este es El operador delete que elimina una propiedad de un objeto que seria usado del siguiente modo.
delete expresión
Donde la expresión debe evaluar una referencia de la propiedad, por ejemplo
delete objeto.propiedaddelete objeto['propiedad']
Los parámetros que utilizamos son:
*Objeto: El nombre de un objeto, o una expresión que evalua a un objeto.
*Propiedad: La propiedad a eliminar.
Al ejecutar este debe retornarte, en modo estricto arroja una excepción si la propiedad no es configurable (retorna false en modo no estricto). Retorna true en cualquier otro caso.
Espero ser de ayuda y ten buen día!
Hola, gracias por la información pero me refería al método delete del BinarySearchTree el cual lo puso como reto el profesor para que nosotros lo hiciéramos pero lo único que logre fue hacer el método search.
Les comparto mi códigoImplementé una verificación para impedir que se ingresen valores repetidos ya que no tendría sentido alguno
Este seria mi aporte sobre el método delete, el cual sigue casi la misma lógica que el search, solo que identifica el valor que quiero buscar antes de asignarlo al currentNode haciendo que pueda eliminar este de forma facil
Le hice unos pequeños cambios y le agregue algunos comentarios de como yo entendí la lógica.
remove(value){//Comprobar que el arbol no este vacioif(!this.root)returnconsole.error("The Binary Search Tree is empty");//Si la raiz del arbol contiene el valor que se busca eliminarif(value ===this.root.value){deletethis.root;//Se elimina la raiz, se elimina todo el arbolreturnthis;//Retorna un objeto vacio}//Usaremos el pointer para movernos por el arbol, empezando desde la raizlet pointer =this.root;//Mientras el pointer no sea null, se hara este ciclowhile(pointer){//Si es menor,esta en el lado izq del nodoif(value < pointer.value){//Comprobamos que el nodo isq no este vacio//Y despues, si el valor se encuentra en el nodo isqif(pointer.left&& value === pointer.left.value){ pointer.left=null;//Eliminamos el nodo izq que contiene el valorreturnthis;//Retornamos el arbol} pointer = pointer.left;//Cambiamos de nodo, pasando al nodo izq}else{//Si es mayor, esta en el lado der del nodo//Comprobamos que el nodo der no este vacio//Y despues, si el valor se encuentra en el nodo derif(pointer.rigth&& value === pointer.rigth.value){ pointer.rigth=null;//Eliminamos el nodo der que contiene el valorreturnthis;//Retornamos el arbol} pointer = pointer.rigth;//Cambiamos de nodo, pasando al nodo izq}}//Si el nodo no se encuentra en el arbol se retorna el errorif(!pointer)returnconsole.error("The node is not in the tree");}
Mi método Search haciendo las validaciones desde el principio y usando continue y break.
Mi codigo para el reto quedo de la siguiente manera:
Siguiendo la logica de la clase, se recorre todo el arbol dependiendo si el valor es mayor o menor (accede al nodo de la izquierda o derecha) al valor del root y devuelve el nodo completo.
Veo en los ejemplos muchas funciones resueltas con recursividad lo que las hace más sencillas de entender pero tienen un gran problema de rendimiento por consumo de memoria y pueden pueden llenar el call stack de JavaScript. Por ello en producción no suelen usarse funciones recursivas.
Dejo un ejemplo que he desarrollado del delete y el search sin recursividad para que no tenga problemas de rendimiento.
classNode<T>{publicleft:Node<T>|null=null;publicright:Node<T>|null=null;constructor(publicvalue:T,){}}exportclassBinarySearchTree<T>{privateroot:Node<T>|null=null;constructor(){}insert(value:T):void{const newNode =newNode(value);if(this.root===null){this.root= newNode;return;}let currentNode =this.root;while(true){if(value < currentNode.value){if(currentNode.left===null){ currentNode.left= newNode;return;} currentNode = currentNode.left;}else{if(currentNode.right===null){ currentNode.right= newNode;return;} currentNode = currentNode.right;}}}search(value:T):Node<T>|null{if(this.root===null){returnnull;}let currentNode =this.root;while(currentNode !==null){if(value < currentNode.value){if(currentNode.left===null){returnnull;} currentNode = currentNode.left;}elseif(value > currentNode.value){if(currentNode.right===null){returnnull;} currentNode = currentNode.right;}else{return currentNode;}}returnnull;}delete(value:T):Node<T>|null{if(this.root===null){// tree is emptyreturnnull;}let currentNode =this.root;letcurrentNodePosition:'right'|'left';let parentNode =null;while(currentNode !==null){//Empezamos a buscar el nodo y su padreif(value < currentNode.value){if(currentNode.left===null){// No encontramos retornamos nullreturnnull;} parentNode = currentNode; currentNode = currentNode.left; currentNodePosition ='left';}elseif(value > currentNode.value){if(currentNode.right===null){// No encontramos retornamos nullreturnnull;} parentNode = currentNode; currentNode = currentNode.right; currentNodePosition ='right';}else{//A partir de aquí hemos encontrado el nodo a eliminarif(parentNode ===null){// El nodo a eliminar es el rootif(currentNode.left===null&& currentNode.right===null){//No tiene hijosthis.root=null;return currentNode;}elseif(currentNode.left&& currentNode.right){//Tiene ambos hijosconst nodeToChange =this.searchLeafToChange(); nodeToChange.right= currentNode.right; nodeToChange.left= currentNode.left;this.root= nodeToChange;return currentNode;}elseif(currentNode.left!==null){//Solo tiene hijo a la izquierdathis.root= currentNode.left;return currentNode;}else{//Solo tiene hijo a la derechathis.root= currentNode.right;return currentNode;}}elseif(currentNode.left===null&& currentNode.right===null){//El nodo a eliminar es una hoja parentNode[currentNodePosition!]=null;return currentNode;}elseif(currentNode.left&& currentNode.right){//El nodo a eliminar es intermedio y tiene ambos hijosconst nodeToChange =this.searchLeafToChange(); nodeToChange.left= currentNode.left; nodeToChange.right= currentNode.right; parentNode[currentNodePosition!]= nodeToChange;return currentNode;}elseif(currentNode.left!==null){//El nodo a eliminar es intermedio y solo tiene hijo a la izquierda parentNode[currentNodePosition!]= currentNode.left;return currentNode;}else{//El nodo a eliminar es intermedio y solo tiene hijo a la derecha parentNode[currentNodePosition!]= currentNode.right;return currentNode;}}}return currentNode;}//Busca el nodo más pequeño de la parte derecha del root, lo elimina y lo devuelve.privatesearchLeafToChange():Node<T>{if(!this.root?.right){returnthis.root!;}let currentNode =this.root.right;let parentNode =this.root;while(true){if(currentNode.left!==null){//Buscamos el nodo más pequeño parentNode = currentNode; currentNode = currentNode.left;}elseif(currentNode.right===null){//No tiene hijos estamos en una hoja parentNode.left=null;return currentNode;}else{//Tiene hijo a la derecha parentNode.left= currentNode.right;return currentNode;}}}}const tree =newBinarySearchTree<number>();tree.insert(10);// 10tree.insert(4);// 10// /// 4tree.insert(20);// 10// / \// 4 20tree.insert(2);// 10// / \// 4 20// /// 2tree.insert(8);// 10// / \// 4 20// / \// 2 8tree.insert(17);// 10// / \// 4 20// / \ /// 2 8 17tree.insert(170);// 10// / \// 4 20// / \ / \// 2 8 17 170// console.log(tree);//Probando a borrar una hoja el número 8// console.log('search value 8: ', tree.search(8));// console.log('search value 4: ', tree.search(4));// console.log('delete 8: ', tree.delete(8));// console.log('search value 4: ', tree.search(4));// 10// / \// 4 20// / / \// 2 17 170//Probando a borrar el root// console.log(tree);// console.log('delete root 10: ', tree.delete(10));// console.log(tree);// 17// / \// 4 20// / \ \// 2 8 170//Probando a eliminar un nodo intermedio con ambos hijos el 20// console.log('search root 10: ', tree.search(10));// console.log('search value 20: ', tree.search(20));// console.log('delete 20: ', tree.delete(20));// console.log('search root 10: ', tree.search(10));// 10// / \// 4 17// / \ \// 2 8 170
Se suele tardar un poco más en implementar pero aumenta mucho el rendimiento.
Hola! Les comparto mi solución del reto 😁👨💻
classNode{constructor(value){this.value= value
this.left=nullthis.right=null}}classBinaryTree{constructor(){this.root=null}insert(value){const newNode =newNode(value)if(!this.root){this.root= newNode
}else{let currentNode =this.rootwhile(true){if(currentNode.value< value){if(!currentNode.right){ currentNode.right= newNode
returnthis}else{ currentNode = currentNode.right}}else{if(!currentNode.left){ currentNode.left= newNode
returnthis}else{ currentNode = currentNode.left}}}}}search(value){let currentNode =this.rootif(!currentNode.right&&!currentNode.left){return currentNode.root}while(true){if(currentNode.value< value){if(!currentNode.right){return'No se ha encontrado el valor 😥'}else{ currentNode = currentNode.right}}elseif(currentNode.value> value){if(!currentNode.left){return'No se ha encontrado el valor 😥'}else{ currentNode = currentNode.left}}elseif(currentNode.value=== value){return'Se ha encontrado el valor 😃 '+ currentNode.value}}}}const myTree =newBinaryTree()myTree.insert(101)myTree.insert(33)myTree.insert(105)myTree.insert(9)myTree.insert(39)myTree.insert(104)myTree.insert(144)console.log(myTree.search(108))
Hola compañeros!. Les comparto mi solución al reto 🤗
Como haces esa captura del código?
Reto completado!!! así hice el método de buscar:
search(valor){if(this.root===null){return"No hay datos para poder buscar";}let currentNode =this.root;while(true){// Lo encontraste?if(currentNode.value=== valor){return currentNode;}// Es mayor al actual?if(currentNode.value< valor){if(currentNode.right===null){return"Este dato no existe";}else{// Desplazo a la derecha currentNode = currentNode.right;}}else{if(currentNode.left===null){return"Este dato no existe";}else{// Desplazo a la izquierda currentNode = currentNode.left;}}}}}// InstanciarconstTree=newTreeSearch();// Insertar datosTree.insert(10)Tree.insert(4)Tree.insert(2)Tree.insert(20)Tree.insert(15)Tree.insert(30)/** RESULTADO:
*
* 10
* / \
* 4 20
* / / \
* 2 15 30
*
*/// Búsqueda - FuncionaTree.search(10)Tree.search(4)Tree.search(20)Tree.search(15)// Búsqueda - ErrorTree.search(33)// Resultado: "Este dato no existe"
Listo! está realizado el método search, también estoy evitando que se repitan números en insert, además cree un reorganizador de árbol por si le quieren dar una vista, es el que mencionó que no veríamos en esta clase, lo realice para aprender a reorganizar un árbol y darle peso a los dos lados correctamente, es una función que cuando la llamas optimiza el arbol reinsertando nuevamente los valores con un nuevo root como centro.
classNode{constructor(value){this.leftdown=null;this.rightdown=null;this.value= value;}}classBinarySearchTree{constructor(){this.root=null;this.container={};}reorder(){const arrayArray =Object.entries(this.container);let arrayClean =[];for(let i =0; arrayArray.length> i; i++){ arrayClean[i]= arrayArray[i][1];}let middle = arrayArray.length middle =parseInt(middle/2)this.root=newNode(arrayClean[middle])for(let i =0; arrayClean.length> i; i++){this.insert(arrayClean[i]);}return arrayClean;}insert(value){if(value ===Number){const newNode =newNode(value);let currentNode =this.root;if(!this.root){this.root= newNode;this.container[this.root.value]=this.root.valuereturnthis;}else{while(true){if(value == currentNode.value){return"If you want to update the node, please use 'update'"}if(value < currentNode.value){if(!currentNode.leftdown){ currentNode.leftdown= newNode;this.container[currentNode.leftdown.value]= currentNode.leftdown.valuereturnthis;}else{ currentNode = currentNode.leftdown;}}else{if(!currentNode.rightdown){ currentNode.rightdown= newNode;this.container[currentNode.rightdown.value]= currentNode.rightdown.valuereturnthis;}else{ currentNode = currentNode.rightdown;}}}}}else{return`You need to enter data type Number, ${value} it's not valid`}}update(value){if(value ===Number){const newNode =newNode(value);let currentNode =this.root;if(!this.root){this.root= newNode;returnthis;}else{while(true){if(value == currentNode.value){const beforeitem = currentNode; currentNode = newNode;return`Was update the before node ${beforeitem.value} to ${currentNode.value}`}elseif(value < currentNode.value){if(currentNode.leftdown){ currentNode = currentNode.leftdown;}}else{if(currentNode.rightdown){ currentNode = currentNode.rightdown;}}}}}else{return`You need to enter data type Number, ${value} it's not valid`}}search(value){if(value ===Number){}else{return`You need to enter data type Number, ${value} it's not valid`}try{let currentNode =this.root;if(value ===Number){if(this.root){while(true){if(value == currentNode.value){return currentNode
}elseif(value < currentNode.value){if(currentNode.leftdown){ currentNode = currentNode.leftdown;}else{return`Node with value ${value} not was found`;}}else{if(currentNode.rightdown){ currentNode = currentNode.rightdown;}else{return`Node with value ${value} not was found`;}}}}else{return"There're not data, use instance-->> .insert(value) to enter data"}}else{return`You need to enter data type Number, ${value} it's not valid`}}catch(err){logMyErrors(err.prototype.message)}}}const tree =newBinarySearchTree();tree.insert(2);tree.insert(3);tree.insert(12);tree.insert(87);tree.insert(7);tree.insert(42);tree.insert(54);tree.insert(67);tree.insert(2);tree.insert(1);tree.insert(6);tree.insert(10);tree.insert(3);tree.search(b);//CÓDIGO A PROBARtree.update(3);tree.search(2);tree.containertree.reorder()