Árbol binario de búsqueda
Un árbol donde cada nodo tiene a la izquierda los valores menores y a la derecha los mayores: buscar, insertar y borrar en O(log n) si está equilibrado, y recorrerlo en orden da los datos ordenados.
Visualízalo paso a paso
Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.
Árbol binario de búsqueda
Escribe los valores que se insertan, en orden, y los que se buscan después.
insertar(50)
- nodo nuevo
Paso 1
El árbol está vacío: el 50 es la raíz.
1static Nodo insertar(Nodo n, int x) {
2 if (n == null) return new Nodo(x); // x = 50, nodos = 1
3 if (x < n.valor) n.izq = insertar(n.izq, x);
4 else if (x > n.valor) n.der = insertar(n.der, x);
5 return n;
6}
7
8static boolean buscar(Nodo n, int x) {
9 while (n != null) {
10 if (x == n.valor) return true;
11 n = x < n.valor ? n.izq : n.der;
12 }
13 return false;
14}Variables
- x
- 50
- nodos
- 1
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
Un árbol binario está hecho de nodos, y cada nodo tiene como mucho dos hijos: el izquierdo y el derecho. En un árbol binario de búsqueda (ABB) se cumple además una regla en todos los nodos: lo que hay en su subárbol izquierdo es menor que él, y lo que hay en el derecho, mayor.
Con esa regla, buscar es como la búsqueda binaria: se empieza en la raíz y en cada nodo se baja a la izquierda o a la derecha según el valor buscado sea menor o mayor. Insertar es buscar hasta encontrar un hueco vacío y colgar ahí el nodo nuevo.
Recorrer el árbol en inorden (subárbol izquierdo, nodo, subárbol derecho) visita los valores de menor a mayor: el árbol mantiene los datos ordenados aunque se inserten en cualquier orden.
El coste depende de la altura. Si el árbol está equilibrado (las ramas tienen alturas parecidas), la altura es unas log₂ n y todo es rapidísimo. Pero si se insertan los datos ya ordenados, cada nodo nuevo cuelga del anterior y el árbol degenera en una lista: altura n. Por eso los árboles de las bibliotecas se reequilibran solos (AVL, rojo-negro).
Cuándo usarlo
- Hay que buscar, insertar y borrar datos con frecuencia y además recorrerlos ordenados (un
HashSetes más rápido buscando, pero no mantiene el orden). - Hacen falta consultas por orden: el mínimo, el máximo, el siguiente mayor que x, todos los valores entre a y b.
- Para entender estructuras que se usan a diario:
TreeMap,TreeSety los índices de las bases de datos.
Cuándo no
- Si solo hace falta saber si un valor está: un
HashSetlo hace en O(1) de media. - Si los datos llegan ordenados y el árbol no se reequilibra: degenera en una lista. En Java, usa
TreeMapoTreeSet, que siempre están equilibrados.
Paso a paso
- Buscar. Desde la raíz: si el valor es el del nodo, encontrado; si es menor, se sigue por la izquierda; si es mayor, por la derecha; si se llega a
null, no está. - Insertar. Se busca el valor; donde la búsqueda acaba en
nullse crea el nodo nuevo como hijo del último nodo visitado. - Recorrer. Inorden (izquierda, nodo, derecha) da los valores ordenados; preorden (nodo primero) sirve para copiar el árbol; postorden (nodo al final), para borrarlo o calcular cosas que dependen de los hijos.
- Borrar. Tres casos: un nodo sin hijos se quita; con un hijo, el hijo ocupa su lugar; con dos, se sustituye su valor por el de su sucesor (el menor del subárbol derecho) y se borra el sucesor.
El código
Insertar, buscar, recorrer y altura
Un nodo es una clase con su valor y dos referencias. Los métodos recursivos reciben un subárbol y devuelven el resultado; insertar devuelve la raíz del subárbol para poder enganchar el nodo nuevo a su padre.
1import java.util.*;
2
3public class Main {
4 static class Nodo {
5 int valor;
6 Nodo izq, der;
7 Nodo(int valor) { this.valor = valor; }
8 }
9
10 static Nodo raiz;
11
12 /** Inserta x y devuelve la raíz del subárbol (así se engancha el nodo nuevo a su padre). */
13 static Nodo insertar(Nodo n, int x) {
14 if (n == null) return new Nodo(x); // hueco libre: aquí va
15 if (x < n.valor) n.izq = insertar(n.izq, x); // menores a la izquierda
16 else if (x > n.valor) n.der = insertar(n.der, x); // mayores a la derecha
17 return n; // (los repetidos no se insertan)
18 }
19
20 static boolean contiene(Nodo n, int x) {
21 while (n != null) {
22 if (x == n.valor) return true;
23 n = x < n.valor ? n.izq : n.der; // como en la búsqueda binaria
24 }
25 return false;
26 }
27
28 static void inorden(Nodo n, List<Integer> l) { // izquierda, nodo, derecha
29 if (n == null) return;
30 inorden(n.izq, l);
31 l.add(n.valor);
32 inorden(n.der, l);
33 }
34
35 static void preorden(Nodo n, List<Integer> l) { // nodo, izquierda, derecha
36 if (n == null) return;
37 l.add(n.valor);
38 preorden(n.izq, l);
39 preorden(n.der, l);
40 }
41
42 static int altura(Nodo n) {
43 return n == null ? 0 : 1 + Math.max(altura(n.izq), altura(n.der));
44 }
45
46 public static void main(String[] args) {
47 for (int x : new int[] {50, 30, 70, 20, 40, 60, 80, 45}) raiz = insertar(raiz, x);
48 List<Integer> in = new ArrayList<>(), pre = new ArrayList<>();
49 inorden(raiz, in);
50 preorden(raiz, pre);
51 System.out.println("Inorden (ordenado): " + in);
52 System.out.println("Preorden: " + pre);
53 System.out.println("Altura: " + altura(raiz));
54 System.out.println("¿Contiene 45? " + (contiene(raiz, 45) ? "sí" : "no") + " · ¿Contiene 65? " + (contiene(raiz, 65) ? "sí" : "no"));
55 }
56}class Nodo:
def __init__(self, valor):
self.valor = valor
self.izq = None
self.der = None
def insertar(n, x):
"""Inserta x y devuelve la raíz del subárbol (así se engancha el nodo nuevo a su padre)."""
if n is None:
return Nodo(x) # hueco libre: aquí va
if x < n.valor:
n.izq = insertar(n.izq, x) # menores a la izquierda
elif x > n.valor:
n.der = insertar(n.der, x) # mayores a la derecha
return n # (los repetidos no se insertan)
def contiene(n, x):
while n is not None:
if x == n.valor:
return True
n = n.izq if x < n.valor else n.der # como en la búsqueda binaria
return False
def inorden(n, l): # izquierda, nodo, derecha
if n is None:
return
inorden(n.izq, l)
l.append(n.valor)
inorden(n.der, l)
def preorden(n, l): # nodo, izquierda, derecha
if n is None:
return
l.append(n.valor)
preorden(n.izq, l)
preorden(n.der, l)
def altura(n):
return 0 if n is None else 1 + max(altura(n.izq), altura(n.der))
raiz = None
for x in [50, 30, 70, 20, 40, 60, 80, 45]:
raiz = insertar(raiz, x)
en_orden, pre = [], []
inorden(raiz, en_orden)
preorden(raiz, pre)
print("Inorden (ordenado):", en_orden)
print("Preorden:", pre)
print("Altura:", altura(raiz))
print("¿Contiene 45?", "sí" if contiene(raiz, 45) else "no", "· ¿Contiene 65?", "sí" if contiene(raiz, 65) else "no")class Nodo {
constructor(valor) {
this.valor = valor;
this.izq = null;
this.der = null;
}
}
/** Inserta x y devuelve la raíz del subárbol (así se engancha el nodo nuevo a su padre). */
function insertar(n, x) {
if (n === null) return new Nodo(x); // hueco libre: aquí va
if (x < n.valor) n.izq = insertar(n.izq, x); // menores a la izquierda
else if (x > n.valor) n.der = insertar(n.der, x); // mayores a la derecha
return n; // (los repetidos no se insertan)
}
function contiene(n, x) {
while (n !== null) {
if (x === n.valor) return true;
n = x < n.valor ? n.izq : n.der; // como en la búsqueda binaria
}
return false;
}
function inorden(n, l) { // izquierda, nodo, derecha
if (n === null) return;
inorden(n.izq, l);
l.push(n.valor);
inorden(n.der, l);
}
function preorden(n, l) { // nodo, izquierda, derecha
if (n === null) return;
l.push(n.valor);
preorden(n.izq, l);
preorden(n.der, l);
}
const altura = (n) => (n === null ? 0 : 1 + Math.max(altura(n.izq), altura(n.der)));
let raiz = null;
for (const x of [50, 30, 70, 20, 40, 60, 80, 45]) raiz = insertar(raiz, x);
const enOrden = [], pre = [];
inorden(raiz, enOrden);
preorden(raiz, pre);
const lista = (l) => "[" + l.join(", ") + "]";
console.log("Inorden (ordenado): " + lista(enOrden));
console.log("Preorden: " + lista(pre));
console.log("Altura: " + altura(raiz));
console.log(`¿Contiene 45? ${contiene(raiz, 45) ? "sí" : "no"} · ¿Contiene 65? ${contiene(raiz, 65) ? "sí" : "no"}`);using System;
using System.Collections.Generic;
class Nodo {
public int Valor;
public Nodo? Izq, Der;
public Nodo(int valor) { Valor = valor; }
}
class Program {
// Inserta x y devuelve la raíz del subárbol (así se engancha el nodo nuevo a su padre).
static Nodo Insertar(Nodo? n, int x) {
if (n == null) return new Nodo(x); // hueco libre: aquí va
if (x < n.Valor) n.Izq = Insertar(n.Izq, x); // menores a la izquierda
else if (x > n.Valor) n.Der = Insertar(n.Der, x); // mayores a la derecha
return n; // (los repetidos no se insertan)
}
static bool Contiene(Nodo? n, int x) {
while (n != null) {
if (x == n.Valor) return true;
n = x < n.Valor ? n.Izq : n.Der; // como en la búsqueda binaria
}
return false;
}
static void Inorden(Nodo? n, List<int> l) { // izquierda, nodo, derecha
if (n == null) return;
Inorden(n.Izq, l);
l.Add(n.Valor);
Inorden(n.Der, l);
}
static void Preorden(Nodo? n, List<int> l) { // nodo, izquierda, derecha
if (n == null) return;
l.Add(n.Valor);
Preorden(n.Izq, l);
Preorden(n.Der, l);
}
static int Altura(Nodo? n) => n == null ? 0 : 1 + Math.Max(Altura(n.Izq), Altura(n.Der));
static void Main() {
Nodo? raiz = null;
foreach (int x in new[] { 50, 30, 70, 20, 40, 60, 80, 45 }) raiz = Insertar(raiz, x);
var enOrden = new List<int>();
var pre = new List<int>();
Inorden(raiz, enOrden);
Preorden(raiz, pre);
Console.WriteLine(quot;Inorden (ordenado): [{string.Join(", ", enOrden)}]");
Console.WriteLine(quot;Preorden: [{string.Join(", ", pre)}]");
Console.WriteLine(quot;Altura: {Altura(raiz)}");
Console.WriteLine(quot;¿Contiene 45? {(Contiene(raiz, 45) ? "sí" : "no")} · ¿Contiene 65? {(Contiene(raiz, 65) ? "sí" : "no")}");
}
}<?php
class Nodo {
public ?Nodo $izq = null;
public ?Nodo $der = null;
public function __construct(public int $valor) { }
}
/** Inserta $x y devuelve la raíz del subárbol (así se engancha el nodo nuevo a su padre). */
function insertar(?Nodo $n, int $x): Nodo {
if ($n === null) return new Nodo($x); // hueco libre: aquí va
if ($x < $n->valor) $n->izq = insertar($n->izq, $x); // menores a la izquierda
elseif ($x > $n->valor) $n->der = insertar($n->der, $x); // mayores a la derecha
return $n; // (los repetidos no se insertan)
}
function contiene(?Nodo $n, int $x): bool {
while ($n !== null) {
if ($x === $n->valor) return true;
$n = $x < $n->valor ? $n->izq : $n->der; // como en la búsqueda binaria
}
return false;
}
function inorden(?Nodo $n, array &$l): void { // izquierda, nodo, derecha
if ($n === null) return;
inorden($n->izq, $l);
$l[] = $n->valor;
inorden($n->der, $l);
}
function preorden(?Nodo $n, array &$l): void { // nodo, izquierda, derecha
if ($n === null) return;
$l[] = $n->valor;
preorden($n->izq, $l);
preorden($n->der, $l);
}
function altura(?Nodo $n): int {
return $n === null ? 0 : 1 + max(altura($n->izq), altura($n->der));
}
$raiz = null;
foreach ([50, 30, 70, 20, 40, 60, 80, 45] as $x) $raiz = insertar($raiz, $x);
$enOrden = [];
$pre = [];
inorden($raiz, $enOrden);
preorden($raiz, $pre);
echo "Inorden (ordenado): [" . implode(", ", $enOrden) . "]\n";
echo "Preorden: [" . implode(", ", $pre) . "]\n";
echo "Altura: " . altura($raiz) . "\n";
echo "¿Contiene 45? " . (contiene($raiz, 45) ? "sí" : "no") . " · ¿Contiene 65? " . (contiene($raiz, 65) ? "sí" : "no") . "\n";Salida al ejecutarlo (la misma en los 5 lenguajes)
Inorden (ordenado): [20, 30, 40, 45, 50, 60, 70, 80] Preorden: [50, 30, 20, 40, 45, 70, 60, 80] Altura: 4 ¿Contiene 45? sí · ¿Contiene 65? no
El árbol del ejemplo
150
2 / \
3 30 70
4 / \ / \
5 20 40 60 80
6 \
7 45Borrar un nodo
El caso difícil es el nodo con dos hijos: no se puede quitar sin romper el árbol, así que se copia en él el valor de su sucesor (que tiene como mucho un hijo) y se borra el sucesor, que es un caso fácil.
1/** Borra x del subárbol n y devuelve la nueva raíz de ese subárbol. */
2static Nodo borrar(Nodo n, int x) {
3 if (n == null) return null; // no estaba
4 if (x < n.valor) n.izq = borrar(n.izq, x);
5 else if (x > n.valor) n.der = borrar(n.der, x);
6 else {
7 if (n.izq == null) return n.der; // casos 1 y 2: sin hijos o con uno,
8 if (n.der == null) return n.izq; // el hijo ocupa su lugar
9 Nodo sucesor = n.der; // caso 3: dos hijos
10 while (sucesor.izq != null) sucesor = sucesor.izq; // el menor de la derecha
11 n.valor = sucesor.valor; // copia su valor aquí
12 n.der = borrar(n.der, sucesor.valor); // y lo borra de la derecha
13 }
14 return n;
15}def borrar(n, x):
"""Borra x del subárbol n y devuelve la nueva raíz de ese subárbol."""
if n is None:
return None # no estaba
if x < n.valor:
n.izq = borrar(n.izq, x)
elif x > n.valor:
n.der = borrar(n.der, x)
else:
if n.izq is None: # casos 1 y 2: sin hijos o con uno,
return n.der # el hijo ocupa su lugar
if n.der is None:
return n.izq
sucesor = n.der # caso 3: dos hijos
while sucesor.izq is not None: # el menor de la derecha
sucesor = sucesor.izq
n.valor = sucesor.valor # copia su valor aquí
n.der = borrar(n.der, sucesor.valor) # y lo borra de la derecha
return n/** Borra x del subárbol n y devuelve la nueva raíz de ese subárbol. */
function borrar(n, x) {
if (n === null) return null; // no estaba
if (x < n.valor) n.izq = borrar(n.izq, x);
else if (x > n.valor) n.der = borrar(n.der, x);
else {
if (n.izq === null) return n.der; // casos 1 y 2: sin hijos o con uno,
if (n.der === null) return n.izq; // el hijo ocupa su lugar
let sucesor = n.der; // caso 3: dos hijos
while (sucesor.izq !== null) sucesor = sucesor.izq; // el menor de la derecha
n.valor = sucesor.valor; // copia su valor aquí
n.der = borrar(n.der, sucesor.valor); // y lo borra de la derecha
}
return n;
}// Borra x del subárbol n y devuelve la nueva raíz de ese subárbol.
static Nodo? Borrar(Nodo? n, int x) {
if (n == null) return null; // no estaba
if (x < n.Valor) n.Izq = Borrar(n.Izq, x);
else if (x > n.Valor) n.Der = Borrar(n.Der, x);
else {
if (n.Izq == null) return n.Der; // casos 1 y 2: sin hijos o con uno,
if (n.Der == null) return n.Izq; // el hijo ocupa su lugar
Nodo sucesor = n.Der; // caso 3: dos hijos
while (sucesor.Izq != null) sucesor = sucesor.Izq; // el menor de la derecha
n.Valor = sucesor.Valor; // copia su valor aquí
n.Der = Borrar(n.Der, sucesor.Valor); // y lo borra de la derecha
}
return n;
}/** Borra $x del subárbol $n y devuelve la nueva raíz de ese subárbol. */
function borrar(?Nodo $n, int $x): ?Nodo {
if ($n === null) return null; // no estaba
if ($x < $n->valor) $n->izq = borrar($n->izq, $x);
elseif ($x > $n->valor) $n->der = borrar($n->der, $x);
else {
if ($n->izq === null) return $n->der; // casos 1 y 2: sin hijos o con uno,
if ($n->der === null) return $n->izq; // el hijo ocupa su lugar
$sucesor = $n->der; // caso 3: dos hijos
while ($sucesor->izq !== null) $sucesor = $sucesor->izq; // el menor de la derecha
$n->valor = $sucesor->valor; // copia su valor aquí
$n->der = borrar($n->der, $sucesor->valor); // y lo borra de la derecha
}
return $n;
}Traza: insertar 45 en el árbol de 50, 30, 70, 20, 40, 60, 80
| Nodo visitado | Comparación | Decisión |
|---|---|---|
| 50 | 45 < 50 | bajar por la izquierda |
| 30 | 45 > 30 | bajar por la derecha |
| 40 | 45 > 40 | la derecha de 40 está vacía: 45 se cuelga ahí |
Tres comparaciones con siete nodos en el árbol: tantas como niveles hay que bajar.
Complejidad
| Operación | Árbol equilibrado | Árbol degenerado (datos insertados en orden) |
|---|---|---|
| Buscar | O(log n) | O(n) |
| Insertar | O(log n) | O(n) |
| Borrar | O(log n) | O(n) |
| Recorrer | O(n) | O(n) |
| Mínimo y máximo | O(log n) | O(n) |
La memoria es O(n), un nodo por valor. Las versiones recursivas usan además una pila tan profunda como la altura: en un árbol degenerado de 100.000 nodos, un StackOverflowError.
- Mejor caso: O(1)
- Caso medio: O(log n)
- Peor caso: O(n)
Buscar: log n si el árbol está equilibrado, n si ha degenerado en una lista. Las curvas grises son las demás clases, para comparar.
En la práctica
TreeMapyTreeSetson árboles rojo-negro: árboles binarios de búsqueda que se reequilibran al insertar y borrar, así que siempre son O(log n). DanfirstKey,ceilingKey,subMap…- Los índices de las bases de datos son árboles B+: la misma idea con cientos de hijos por nodo, para leer pocas páginas del disco.
- Los árboles sintácticos de un compilador o de una calculadora, y el DOM de una página web, son árboles (no de búsqueda) que se recorren con las mismas técnicas.
Errores típicos
- En la inserción recursiva, llamar a
insertar(n.izq, x)sin asignar el resultado (n.izq = insertar(n.izq, x)): el nodo nuevo se crea y se pierde. - Olvidar
raiz = insertar(raiz, x)en el primer nodo: el árbol se queda vacío. - No decidir qué hacer con los repetidos (ignorarlos, contarlos o ponerlos a un lado): acaban duplicados o perdidos.
- Borrar un nodo con dos hijos quitándolo sin más: se pierde uno de los dos subárboles.
- Dar por hecho que siempre es O(log n): insertando datos ordenados se obtiene una lista con forma de árbol.
Ejercicios
Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.
1. Un árbol con órdenes
Programa las operaciones básicas de un árbol binario de búsqueda de enteros y úsalas con las órdenes de la entrada. El main, que lee las órdenes y escribe los resultados, ya está: completa los métodos. Los valores repetidos no se insertan.
insertar x→insertado xox ya estaba.buscar x→x está (profundidad d)(la raíz es 1) ox no está.inorden,preorden,postorden→Inorden: 20 30 40((vacío)si no hay nodos).altura→Altura: h(0 vacío, 1 con solo la raíz).nodos→Nodos: n.minimoymaximo→Mínimo: x,Máximo: xoEl árbol está vacío. Otra orden:Orden no válida: línea.
Ejemplo
insertar 50 insertar 30 insertar 70 insertar 20 insertar 40 insertar 60 insertar 80 insertar 30 buscar 40 buscar 65 inorden preorden postorden altura nodos minimo maximo
insertado 50 insertado 30 insertado 70 insertado 20 insertado 40 insertado 60 insertado 80 30 ya estaba 40 está (profundidad 3) 65 no está Inorden: 20 30 40 50 60 70 80 Preorden: 50 30 20 40 70 60 80 Postorden: 20 40 30 60 80 70 50 Altura: 3 Nodos: 7 Mínimo: 20 Máximo: 80
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 static class Nodo {
5 int valor;
6 Nodo izq, der;
7 Nodo(int valor) { this.valor = valor; }
8 }
9
10 static Nodo raiz;
11
12 static String lista(List<Integer> l) {
13 return l.isEmpty() ? "(vacío)" : String.join(" ", l.stream().map(String::valueOf).toList());
14 }
15
16 static Nodo insertar(Nodo n, int x) {
17 if (n == null) return new Nodo(x);
18 if (x < n.valor) n.izq = insertar(n.izq, x);
19 else if (x > n.valor) n.der = insertar(n.der, x);
20 return n;
21 }
22
23 /** Profundidad del nodo con x (la raíz es 1), o 0 si no está. */
24 static int profundidad(int x) {
25 int d = 1;
26 for (Nodo n = raiz; n != null; d++) {
27 if (x == n.valor) return d;
28 n = x < n.valor ? n.izq : n.der;
29 }
30 return 0;
31 }
32
33 static void recorrer(Nodo n, String orden, List<Integer> l) {
34 if (n == null) return;
35 if (orden.equals("preorden")) l.add(n.valor);
36 recorrer(n.izq, orden, l);
37 if (orden.equals("inorden")) l.add(n.valor);
38 recorrer(n.der, orden, l);
39 if (orden.equals("postorden")) l.add(n.valor);
40 }
41
42 static int altura(Nodo n) {
43 return n == null ? 0 : 1 + Math.max(altura(n.izq), altura(n.der));
44 }
45
46 static int nodos(Nodo n) {
47 return n == null ? 0 : 1 + nodos(n.izq) + nodos(n.der);
48 }
49
50 static Integer extremo(boolean minimo) {
51 if (raiz == null) return null;
52 Nodo n = raiz;
53 while ((minimo ? n.izq : n.der) != null) n = minimo ? n.izq : n.der;
54 return n.valor;
55 }
56
57 public static void main(String[] args) {
58 Scanner sc = new Scanner(System.in);
59 while (sc.hasNextLine()) {
60 String linea = sc.nextLine().trim();
61 if (linea.isEmpty()) continue;
62 String[] p = linea.split("\\s+");
63 try {
64 switch (p[0]) {
65 case "insertar" -> {
66 int x = Integer.parseInt(p[1]);
67 boolean estaba = profundidad(x) > 0;
68 raiz = insertar(raiz, x);
69 System.out.println(estaba ? x + " ya estaba" : "insertado " + x);
70 }
71 case "buscar" -> {
72 int x = Integer.parseInt(p[1]);
73 int d = profundidad(x);
74 System.out.println(d > 0 ? x + " está (profundidad " + d + ")" : x + " no está");
75 }
76 case "inorden", "preorden", "postorden" -> {
77 List<Integer> l = new ArrayList<>();
78 recorrer(raiz, p[0], l);
79 System.out.println(Character.toUpperCase(p[0].charAt(0)) + p[0].substring(1) + ": " + lista(l));
80 }
81 case "altura" -> System.out.println("Altura: " + altura(raiz));
82 case "nodos" -> System.out.println("Nodos: " + nodos(raiz));
83 case "minimo", "maximo" -> {
84 Integer v = extremo(p[0].equals("minimo"));
85 System.out.println(v == null ? "El árbol está vacío" : (p[0].equals("minimo") ? "Mínimo: " : "Máximo: ") + v);
86 }
87 default -> System.out.println("Orden no válida: " + linea);
88 }
89 } catch (RuntimeException e) {
90 System.out.println("Orden no válida: " + linea);
91 }
92 }
93 }
94}Todas las operaciones siguen la regla del árbol: comparar y bajar por un lado. Por eso buscar, insertar, el mínimo y el máximo cuestan lo que la altura, y los recorridos y contar los nodos, lo que el número de nodos.
Un solo método recorrer con el orden como parámetro muestra que los tres recorridos se diferencian solo en dónde se visita el nodo.
2. Borrar, recorrer por niveles y comprobar el equilibrio
Completa el árbol con tres operaciones: borrar un valor (con los tres casos, y el sucesor inorden cuando el nodo tiene dos hijos), el recorrido por niveles (de arriba abajo y de izquierda a derecha, con una cola) y la comprobación de si está equilibrado (en todos sus nodos las alturas de los dos subárboles se diferencian en 1 como mucho). Insertar y la lectura de órdenes ya están hechos.
insertar x y z…inserta los valores en ese orden (sin escribir nada).borrar x→borrado xox no está.niveles→ una líneaNivel N: valorespor nivel, o(árbol vacío).equilibrado→Equilibrado: sí (altura h)oEquilibrado: no (altura h).- Otra orden o un número mal escrito:
Orden no válida: línea.
Ejemplo
insertar 50 30 70 20 40 60 80 45 niveles borrar 20 borrar 40 borrar 50 niveles borrar 99 equilibrado
Nivel 1: 50 Nivel 2: 30 70 Nivel 3: 20 40 60 80 Nivel 4: 45 borrado 20 borrado 40 borrado 50 Nivel 1: 60 Nivel 2: 30 70 Nivel 3: 45 80 99 no está Equilibrado: sí (altura 3)
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 static class Nodo {
5 int valor;
6 Nodo izq, der;
7 Nodo(int valor) { this.valor = valor; }
8 }
9
10 static Nodo raiz;
11
12 static Nodo insertar(Nodo n, int x) {
13 if (n == null) return new Nodo(x);
14 if (x < n.valor) n.izq = insertar(n.izq, x);
15 else if (x > n.valor) n.der = insertar(n.der, x);
16 return n;
17 }
18
19 static boolean contiene(Nodo n, int x) {
20 while (n != null && n.valor != x) n = x < n.valor ? n.izq : n.der;
21 return n != null;
22 }
23
24 static int altura(Nodo n) {
25 return n == null ? 0 : 1 + Math.max(altura(n.izq), altura(n.der));
26 }
27
28 /** Borra x del subárbol n; con dos hijos, lo sustituye por su sucesor (el menor de la derecha). */
29 static Nodo borrar(Nodo n, int x) {
30 if (n == null) return null;
31 if (x < n.valor) n.izq = borrar(n.izq, x);
32 else if (x > n.valor) n.der = borrar(n.der, x);
33 else {
34 if (n.izq == null) return n.der;
35 if (n.der == null) return n.izq;
36 Nodo s = n.der;
37 while (s.izq != null) s = s.izq;
38 n.valor = s.valor;
39 n.der = borrar(n.der, s.valor);
40 }
41 return n;
42 }
43
44 /** Un recorrido por niveles: una cola con los nodos del nivel actual. */
45 static List<List<Integer>> niveles() {
46 List<List<Integer>> r = new ArrayList<>();
47 Deque<Nodo> cola = new ArrayDeque<>();
48 if (raiz != null) cola.add(raiz);
49 while (!cola.isEmpty()) {
50 List<Integer> nivel = new ArrayList<>();
51 for (int k = cola.size(); k > 0; k--) {
52 Nodo n = cola.poll();
53 nivel.add(n.valor);
54 if (n.izq != null) cola.add(n.izq);
55 if (n.der != null) cola.add(n.der);
56 }
57 r.add(nivel);
58 }
59 return r;
60 }
61
62 /** En todos los nodos, las alturas de sus dos subárboles se diferencian en 1 como mucho. */
63 static boolean equilibrado(Nodo n) {
64 if (n == null) return true;
65 return Math.abs(altura(n.izq) - altura(n.der)) <= 1 && equilibrado(n.izq) && equilibrado(n.der);
66 }
67
68 public static void main(String[] args) {
69 Scanner sc = new Scanner(System.in);
70 while (sc.hasNextLine()) {
71 String linea = sc.nextLine().trim();
72 if (linea.isEmpty()) continue;
73 String[] p = linea.split("\\s+");
74 try {
75 switch (p[0]) {
76 case "insertar" -> {
77 if (p.length < 2) throw new IllegalArgumentException();
78 int[] xs = new int[p.length - 1];
79 for (int i = 1; i < p.length; i++) xs[i - 1] = Integer.parseInt(p[i]); // todos válidos antes de insertar
80 for (int x : xs) raiz = insertar(raiz, x);
81 }
82 case "borrar" -> {
83 int x = Integer.parseInt(p[1]);
84 if (!contiene(raiz, x)) System.out.println(x + " no está");
85 else {
86 raiz = borrar(raiz, x);
87 System.out.println("borrado " + x);
88 }
89 }
90 case "niveles" -> {
91 List<List<Integer>> n = niveles();
92 if (n.isEmpty()) System.out.println("(árbol vacío)");
93 for (int i = 0; i < n.size(); i++) System.out.println("Nivel " + (i + 1) + ": " + String.join(" ", n.get(i).stream().map(String::valueOf).toList()));
94 }
95 case "equilibrado" -> System.out.println("Equilibrado: " + (equilibrado(raiz) ? "sí" : "no") + " (altura " + altura(raiz) + ")");
96 default -> System.out.println("Orden no válida: " + linea);
97 }
98 } catch (RuntimeException e) {
99 System.out.println("Orden no válida: " + linea);
100 }
101 }
102 }
103}Borrar con el sucesor inorden mantiene la regla del árbol: el sucesor es mayor que todo el subárbol izquierdo y menor que el resto del derecho, así que puede ocupar el sitio del nodo borrado.
El recorrido por niveles usa una cola en lugar de recursividad: es una búsqueda en anchura. La comprobación de equilibrio, tal como está, recalcula alturas y es O(n²) en el peor caso; calculando la altura y el equilibrio en la misma pasada sería O(n).
Test
Test: Árbol binario de búsqueda
0/5 respondidas · 0 aciertosElige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.
1.En un árbol binario de búsqueda, ¿qué recorrido da los valores de menor a mayor?
2.Insertas 1, 2, 3, 4 y 5 en ese orden en un ABB sin reequilibrio. ¿Qué altura tiene?
3.Al borrar un nodo con dos hijos, ¿por qué valor se sustituye?
4.¿Qué estructura de la biblioteca de Java es un árbol binario de búsqueda equilibrado?
5.¿Cuánto cuesta buscar en un ABB equilibrado de n nodos?