STRUTTURA DATI: ALBERO BINARIO DI RICERCA (BST)
Un Binary Search Tree (BST) è una struttura dati ad albero che organizza i valori in modo ordinato per consentire ricerca, inserimento e rimozione efficienti.
PROPRIETÀ DI RICERCA
- Ogni nodo contiene un valore univoco.
- Per ogni nodo
N:- Sottoalbero sinistro → valori <
N.value - Sottoalbero destro → valori >
N.value
- Sottoalbero sinistro → valori <
- Grazie a questa proprietà, è possibile sfruttare la ricerca binaria durante la navigazione.
TERMINOLOGIA
- Nodo → contiene un valore e i puntatori a sinistra e destra.
- Radice (Root) → nodo principale dell’albero.
- Foglia (Leaf) → nodo senza figli.
- Padre (Parent) → nodo con almeno un figlio.
- Figlio (Child) → nodo collegato a un padre.
- Sottoalbero (Subtree) → albero discendente da un nodo.
- Altezza dell’Albero → percorso più lungo dalla radice a una foglia.
OPERAZIONI PRINCIPALI
inserisci(valore)→ aggiunge un nodo rispettando la proprietà BST.cerca(valore)→ verifica la presenza di un valore.elimina(valore)→ rimuove un nodo:- Nessun figlio → eliminato direttamente.
- Un figlio → sostituito con il figlio.
- Due figli → sostituito dal successore in ordine (minimo del sottoalbero destro).
visitaInOrdine()→ restituisce valori ordinati crescenti.visitaPreOrdine()→ visita radice → sottoalbero sinistro → sottoalbero destro.visitaPostOrdine()→ visita sottoalberi → radice.altezza()→ calcola la profondità massima.contaNodi()→ numero totale di nodi.
OPERAZIONI AUSILIARIE
getMin()→ valore minimo (nodo più a sinistra).getMax()→ valore massimo (nodo più a destra).isBalanced()→ verifica che le altezze dei sottoalberi differiscano al massimo di 1.
EFFICIENZA
- Ricerca → O(log n) (medio), O(n) (peggiore caso).
- Inserimento → O(log n) (medio), O(n) (peggiore caso).
- Eliminazione → O(log n) (medio), O(n) (peggiore caso).
- Spazio → O(n).
APPLICAZIONI
- Strutture di database.
- Implementazioni di insiemi e mappe ordinate.
- Algoritmi di ordinamento.
- Gestione di intervalli e dati gerarchici.
NOTE
- Più semplice rispetto ad AVL.
- Non garantisce bilanciamento automatico.
- Può degradare in una lista se i dati sono inseriti in ordine.
CODICE
class Node<E> {
value: E;
left: Node<E> | null;
right: Node<E> | null;
constructor(value: E) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BinarySearchTree<E> {
root: Node<E> | null;
constructor() {
this.root = null;
}
/**
* Inserisce un valore nell'albero rispettando la proprieta di ricerca
* @param value valore da inserire nell'albero
* @returns nuovo nodo inserito
*/
insert(value: E) : Node<E> | null{
const newNode = new Node(value);
if (!this.root) {
this.root = newNode;
return this.root;
}
let current = this.root;
while (true) {
// valori duplicati non consentiti
if (value === current.value) return null;
if (value < current.value) {
if (!current.left) {
current.left = newNode;
return this.root;
}
current = current.left;
} else {
if (!current.right) {
current.right = newNode;
return this.root;
}
current = current.right;
}
}
}
/**
* Cerca un valore nell'albero sfruttando la proprietà di ricerca
* @param value valore da cercare nell'albero
* @returns true se esiste un nodo con il valore "value", false altrimenti
*/
find(value: E): boolean {
if (!this.root) return false;
let current: Node<E> | null = this.root;
while (current) {
if (value === current.value) return true;
if (value < current.value) {
current = current.left;
} else {
current = current.right;
}
}
return false;
}
/**
* Funzione ausiliaria che effettua l'eleminazione
* @param node nodo da cui deve partire l'eliminazione, valore di partenza la radice
* @param value valore da eliminare
* @returns nuova radice dell'albero
*/
private auxRemove(node: Node<E> | null, value: E): Node<E> | null {
if (!node) return null;
if (value < node.value) {
node.left = this.auxRemove(node.left, value);
return node;
} else if (value > node.value) {
node.right = this.auxRemove(node.right, value);
return node;
} else {
// Caso 1: Nessun figlio
if (!node.left && !node.right) return null;
// Caso 2: Un figlio
if (!node.left) return node.right;
if (!node.right) return node.left;
// Caso 3: Due figli
let temp = this.getMin(node.right);
if (temp) {
node.value = temp.value;
node.right = this.auxRemove(node.right, temp.value);
return node;
}
return null;
}
}
/**
* Rimuove un nodo dall'albero
* @param value valore del nodo da eliminare
*/
remove(value: E): void {
this.root = this.auxRemove(this.root, value);
}
/**
* Trova il valore minimo in un sottoalbero sfruttando la proprietà di ricerca
* @param node nodo da cui deve partire la ricerca, valore di partenza la radice
* @returns nodo con il valore minimo, null se l'albero è vuoto
*/
getMin(node: Node<E> | null = this.root): Node<E> | null {
while (node && node.left) {
node = node.left;
}
return node;
}
/**
* Trova il valore massimo in un sottoalbero sfruttando la proprietà di ricerca
* @param node nodo da cui deve partire la ricerca, valore di partenza la radice
* @returns nodo con il valore massimo, null se l'albero è vuoto
*/
getMax(node: Node<E> | null = this.root): Node<E> | null {
while (node && node.right) {
node = node.right;
}
return node;
}
/**
* Funzione ausiliara che detrmina se l'albero radicato su nodo in input è bilanciato o meno
* @param node nodo da verificare se è bilanciato
* @returns oggetto con contenuto se è bilanciato o meno e l'altezza
*/
private isBalancedAux(node: Node<E> | null): { balanced: boolean, height: number } {
if (!node) return { balanced: true, height: 0 };
const left = this.isBalancedAux(node.left);
const right = this.isBalancedAux(node.right);
const balanced = left.balanced && right.balanced && Math.abs(left.height - right.height) <= 1;
const height = Math.max(left.height, right.height) + 1;
return { balanced, height };
};
/**
* Verifica che l'albero è bilanciato
* @returns oggetto con contenuto se è bilanciato o meno e l'altezza
*/
isBalanced(): { balanced: boolean, height: number } {
return this.isBalancedAux(this.root);
}
/**
* Visita in ordine dell'albero
* @returns array con i
*/
inOrderTraversal(): E[] {
let result: E[] = [];
let node = this.root;
this.auxInOrderTraversal(node, result)
return result
}
/**
* Ritorna l'albero in ordine (in-order traversal)[sx - r - dx]
* @param node nodo da cui far partire la visita, di solito la radice
* @param result array contenente i valori in ordine in base alla visita
*/
auxInOrderTraversal(node: Node<E> | null, result: E[]) {
if (node) {
if (node.left)
this.auxInOrderTraversal(node.left, result);
result.push(node.value);
if (node.right)
this.auxInOrderTraversal(node.right, result);
}
}
};
// test della struttura dati
const bst = new BinarySearchTree();
bst.insert(10);
bst.insert(5);
bst.insert(15);
bst.insert(3);
bst.insert(7);
bst.insert(13);
bst.insert(18);
console.log("Attraversamento in preordine:", bst.inOrderTraversal());
console.log("Ricerca di valori:");
console.log("Cerca 7:", bst.find(7));
console.log("Cerca 20:", bst.find(20));
console.log("Eliminazione di un nodo (10)");
bst.remove(10);
console.log("Attraversamento in preordine dopo aver rimosso il 10:", bst.inOrderTraversal());