Istražite osnove binarnih stabala pretraživanja (BST) i naučite kako ih efikasno implementirati u JavaScriptu. Vodič pokriva BST strukturu i operacije.
Binarna stabla pretraživanja: Sveobuhvatan vodič za implementaciju u JavaScriptu
Binarna stabla pretraživanja (BST) su temeljna struktura podataka u računarstvu, široko korištena za efikasno pretraživanje, sortiranje i dohvaćanje podataka. Njihova hijerarhijska struktura omogućuje logaritamsku vremensku složenost za mnoge operacije, što ih čini moćnim alatom za upravljanje velikim skupovima podataka. Ovaj vodič pruža sveobuhvatan pregled BST-ova i demonstrira njihovu implementaciju u JavaScriptu, namijenjen programerima diljem svijeta.
Razumijevanje binarnih stabala pretraživanja
Što je binarno stablo pretraživanja?
Binarno stablo pretraživanja je struktura podataka temeljena na stablu gdje svaki čvor ima najviše dvoje djece, koja se nazivaju lijevo dijete i desno dijete. Ključno svojstvo BST-a je da za bilo koji zadani čvor:
- Svi čvorovi u lijevom podstablu imaju ključeve manje od ključa čvora.
- Svi čvorovi u desnom podstablu imaju ključeve veće od ključa čvora.
Ovo svojstvo osigurava da su elementi u BST-u uvijek poredani, što omogućuje efikasno pretraživanje i dohvaćanje.
Ključni pojmovi
- Čvor (Node): Osnovna jedinica u stablu, koja sadrži ključ (podatak) i pokazivače na svoje lijevo i desno dijete.
- Korijen (Root): Najviši čvor u stablu.
- List (Leaf): Čvor bez djece.
- Podstablo (Subtree): Dio stabla s korijenom u određenom čvoru.
- Visina (Height): Duljina najdužeg puta od korijena do lista.
- Dubina (Depth): Duljina puta od korijena do određenog čvora.
Implementacija binarnog stabla pretraživanja u JavaScriptu
Definiranje klase Node
Prvo, definiramo klasu `Node` koja će predstavljati svaki čvor u BST-u. Svaki čvor će sadržavati `key` za pohranu podataka te `left` i `right` pokazivače na svoju djecu.
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
Definiranje klase BinarySearchTree
Zatim, definiramo klasu `BinarySearchTree`. Ova klasa će sadržavati korijenski čvor i metode za umetanje, pretraživanje, brisanje i obilazak stabla.
class BinarySearchTree {
constructor() {
this.root = null;
}
// Metode će biti dodane ovdje
}
Umetanje
Metoda `insert` dodaje novi čvor s danim ključem u BST. Proces umetanja održava svojstvo BST-a postavljanjem novog čvora na odgovarajuće mjesto u odnosu na postojeće čvorove.
insert(key) {
const newNode = new Node(key);
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
insertNode(node, newNode) {
if (newNode.key < node.key) {
if (node.left === null) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode);
}
} else {
if (node.right === null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
}
}
Primjer: Umetanje vrijednosti u BST
const bst = new BinarySearchTree();
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);
Pretraživanje
Metoda `search` provjerava postoji li čvor s danim ključem u BST-u. Ona obilazi stablo, uspoređujući ključ s ključem trenutnog čvora i pomičući se u lijevo ili desno podstablo u skladu s tim.
search(key) {
return this.searchNode(this.root, key);
}
searchNode(node, key) {
if (node === null) {
return false;
}
if (key < node.key) {
return this.searchNode(node.left, key);
} else if (key > node.key) {
return this.searchNode(node.right, key);
} else {
return true;
}
}
Primjer: Pretraživanje vrijednosti u BST-u
console.log(bst.search(9)); // Izlaz: true
console.log(bst.search(2)); // Izlaz: false
Brisanje
Metoda `remove` briše čvor s danim ključem iz BST-a. Ovo je najsloženija operacija jer je potrebno održati svojstvo BST-a prilikom uklanjanja čvora. Postoje tri slučaja za razmatranje:
- Slučaj 1: Čvor koji se briše je list. Jednostavno ga uklonite.
- Slučaj 2: Čvor koji se briše ima jedno dijete. Zamijenite čvor s njegovim djetetom.
- Slučaj 3: Čvor koji se briše ima dvoje djece. Pronađite sljedbenika po in-order poretku (najmanji čvor u desnom podstablu), zamijenite čvor sa sljedbenikom, a zatim obrišite sljedbenika.
remove(key) {
this.root = this.removeNode(this.root, key);
}
removeNode(node, key) {
if (node === null) {
return null;
}
if (key < node.key) {
node.left = this.removeNode(node.left, key);
return node;
} else if (key > node.key) {
node.right = this.removeNode(node.right, key);
return node;
} else {
// ključ je jednak ključu čvora
// slučaj 1 - list
if (node.left === null && node.right === null) {
node = null;
return node;
}
// slučaj 2 - čvor ima samo 1 dijete
if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
// slučaj 3 - čvor ima 2 djeteta
const aux = this.findMinNode(node.right);
node.key = aux.key;
node.right = this.removeNode(node.right, aux.key);
return node;
}
}
findMinNode(node) {
let current = node;
while (current != null && current.left != null) {
current = current.left;
}
return current;
}
Primjer: Uklanjanje vrijednosti iz BST-a
bst.remove(7);
console.log(bst.search(7)); // Izlaz: false
Obilazak stabla
Obilazak stabla uključuje posjećivanje svakog čvora u stablu određenim redoslijedom. Postoji nekoliko uobičajenih metoda obilaska:
- In-order: Posjećuje lijevo podstablo, zatim čvor, pa desno podstablo. To rezultira posjećivanjem čvorova u rastućem redoslijedu.
- Pre-order: Posjećuje čvor, zatim lijevo podstablo, pa desno podstablo.
- Post-order: Posjećuje lijevo podstablo, zatim desno podstablo, pa čvor.
inOrderTraverse(callback) {
this.inOrderTraverseNode(this.root, callback);
}
inOrderTraverseNode(node, callback) {
if (node !== null) {
this.inOrderTraverseNode(node.left, callback);
callback(node.key);
this.inOrderTraverseNode(node.right, callback);
}
}
preOrderTraverse(callback) {
this.preOrderTraverseNode(this.root, callback);
}
preOrderTraverseNode(node, callback) {
if (node !== null) {
callback(node.key);
this.preOrderTraverseNode(node.left, callback);
this.preOrderTraverseNode(node.right, callback);
}
}
postOrderTraverse(callback) {
this.postOrderTraverseNode(this.root, callback);
}
postOrderTraverseNode(node, callback) {
if (node !== null) {
this.postOrderTraverseNode(node.left, callback);
this.postOrderTraverseNode(node.right, callback);
callback(node.key);
}
}
Primjer: Obilazak BST-a
const printNode = (value) => console.log(value);
bst.inOrderTraverse(printNode); // Izlaz: 3 5 8 9 10 11 12 13 14 15 18 20 25
bst.preOrderTraverse(printNode); // Izlaz: 11 5 3 9 8 10 15 13 12 14 20 18 25
bst.postOrderTraverse(printNode); // Izlaz: 3 8 10 9 12 14 13 18 25 20 15 11
Minimalne i maksimalne vrijednosti
Pronalaženje minimalne i maksimalne vrijednosti u BST-u je jednostavno, zahvaljujući njegovoj uređenoj prirodi.
min() {
return this.minNode(this.root);
}
minNode(node) {
let current = node;
while (current !== null && current.left !== null) {
current = current.left;
}
return current;
}
max() {
return this.maxNode(this.root);
}
maxNode(node) {
let current = node;
while (current !== null && current.right !== null) {
current = current.right;
}
return current;
}
Primjer: Pronalaženje minimalne i maksimalne vrijednosti
console.log(bst.min().key); // Izlaz: 3
console.log(bst.max().key); // Izlaz: 25
Praktične primjene binarnih stabala pretraživanja
Binarna stabla pretraživanja koriste se u raznim primjenama, uključujući:
- Baze podataka: Indeksiranje i pretraživanje podataka. Na primjer, mnogi sustavi baza podataka koriste varijacije BST-a, kao što su B-stabla, za efikasno lociranje zapisa. Razmotrite globalnu skalu baza podataka koje koriste multinacionalne korporacije; efikasno dohvaćanje podataka je od presudne važnosti.
- Kompajleri: Tablice simbola, koje pohranjuju informacije o varijablama i funkcijama.
- Operacijski sustavi: Raspoređivanje procesa i upravljanje memorijom.
- Tražilice: Indeksiranje web stranica i rangiranje rezultata pretraživanja.
- Datotečni sustavi: Organiziranje i pristup datotekama. Zamislite datotečni sustav na poslužitelju koji se koristi globalno za hosting web stranica; dobro organizirana struktura temeljena na BST-u pomaže u brzom posluživanju sadržaja.
Razmatranja o performansama
Performanse BST-a ovise o njegovoj strukturi. U najboljem slučaju, uravnoteženi BST omogućuje logaritamsku vremensku složenost za operacije umetanja, pretraživanja i brisanja. Međutim, u najgorem slučaju (npr. nagnuto stablo), vremenska složenost može se svesti na linearno vrijeme.
Uravnotežena vs. neuravnotežena stabla
Uravnoteženi BST je onaj kod kojeg se visina lijevog i desnog podstabla svakog čvora razlikuje za najviše jedan. Algoritmi za samouravnoteženje, kao što su AVL stabla i crveno-crna stabla, osiguravaju da stablo ostane uravnoteženo, pružajući dosljedne performanse. Različite regije mogu zahtijevati različite razine optimizacije ovisno o opterećenju poslužitelja; uravnoteženje pomaže u održavanju performansi pod visokom globalnom upotrebom.
Vremenska složenost
- Umetanje: O(log n) u prosjeku, O(n) u najgorem slučaju.
- Pretraga: O(log n) u prosjeku, O(n) u najgorem slučaju.
- Brisanje: O(log n) u prosjeku, O(n) u najgorem slučaju.
- Obilazak: O(n), gdje je n broj čvorova u stablu.
Napredni koncepti BST-a
Samouravnotežujuća stabla
Samouravnotežujuća stabla su BST-ovi koji automatski prilagođavaju svoju strukturu kako bi održali ravnotežu. To osigurava da visina stabla ostane logaritamska, pružajući dosljedne performanse za sve operacije. Uobičajena samouravnotežujuća stabla uključuju AVL stabla i crveno-crna stabla.
AVL stabla
AVL stabla održavaju ravnotežu osiguravajući da razlika u visini između lijevog i desnog podstabla bilo kojeg čvora bude najviše jedan. Kada se ta ravnoteža naruši, izvode se rotacije kako bi se ravnoteža ponovno uspostavila.
Crveno-crna stabla
Crveno-crna stabla koriste svojstva boja (crvena ili crna) za održavanje ravnoteže. Složenija su od AVL stabala, ali nude bolje performanse u određenim scenarijima.
Primjer JavaScript koda: Kompletna implementacija binarnog stabla pretraživanja
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
class BinarySearchTree {
constructor() {
this.root = null;
}
insert(key) {
const newNode = new Node(key);
if (this.root === null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
insertNode(node, newNode) {
if (newNode.key < node.key) {
if (node.left === null) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode);
}
} else {
if (node.right === null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
}
}
search(key) {
return this.searchNode(this.root, key);
}
searchNode(node, key) {
if (node === null) {
return false;
}
if (key < node.key) {
return this.searchNode(node.left, key);
} else if (key > node.key) {
return this.searchNode(node.right, key);
} else {
return true;
}
}
remove(key) {
this.root = this.removeNode(this.root, key);
}
removeNode(node, key) {
if (node === null) {
return null;
}
if (key < node.key) {
node.left = this.removeNode(node.left, key);
return node;
} else if (key > node.key) {
node.right = this.removeNode(node.right, key);
return node;
} else {
// ključ je jednak ključu čvora
// slučaj 1 - list
if (node.left === null && node.right === null) {
node = null;
return node;
}
// slučaj 2 - čvor ima samo 1 dijete
if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
// slučaj 3 - čvor ima 2 djeteta
const aux = this.findMinNode(node.right);
node.key = aux.key;
node.right = this.removeNode(node.right, aux.key);
return node;
}
}
findMinNode(node) {
let current = node;
while (current != null && current.left != null) {
current = current.left;
}
return current;
}
min() {
return this.minNode(this.root);
}
minNode(node) {
let current = node;
while (current !== null && current.left !== null) {
current = current.left;
}
return current;
}
max() {
return this.maxNode(this.root);
}
maxNode(node) {
let current = node;
while (current !== null && current.right !== null) {
current = current.right;
}
return current;
}
inOrderTraverse(callback) {
this.inOrderTraverseNode(this.root, callback);
}
inOrderTraverseNode(node, callback) {
if (node !== null) {
this.inOrderTraverseNode(node.left, callback);
callback(node.key);
this.inOrderTraverseNode(node.right, callback);
}
}
preOrderTraverse(callback) {
this.preOrderTraverseNode(this.root, callback);
}
preOrderTraverseNode(node, callback) {
if (node !== null) {
callback(node.key);
this.preOrderTraverseNode(node.left, callback);
this.preOrderTraverseNode(node.right, callback);
}
}
postOrderTraverse(callback) {
this.postOrderTraverseNode(this.root, callback);
}
postOrderTraverseNode(node, callback) {
if (node !== null) {
this.postOrderTraverseNode(node.left, callback);
this.postOrderTraverseNode(node.right, callback);
callback(node.key);
}
}
}
// Primjer korištenja
const bst = new BinarySearchTree();
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);
const printNode = (value) => console.log(value);
console.log("In-order obilazak:");
bst.inOrderTraverse(printNode);
console.log("Pre-order obilazak:");
bst.preOrderTraverse(printNode);
console.log("Post-order obilazak:");
bst.postOrderTraverse(printNode);
console.log("Minimalna vrijednost:", bst.min().key);
console.log("Maksimalna vrijednost:", bst.max().key);
console.log("Pretraga za 9:", bst.search(9));
console.log("Pretraga za 2:", bst.search(2));
bst.remove(7);
console.log("Pretraga za 7 nakon uklanjanja:", bst.search(7));
Zaključak
Binarna stabla pretraživanja su moćna i svestrana struktura podataka s brojnim primjenama. Ovaj vodič pružio je sveobuhvatan pregled BST-ova, pokrivajući njihovu strukturu, operacije i implementaciju u JavaScriptu. Razumijevanjem principa i tehnika o kojima se raspravljalo u ovom vodiču, programeri diljem svijeta mogu efikasno koristiti BST-ove za rješavanje širokog spektra problema u razvoju softvera. Od upravljanja globalnim bazama podataka do optimizacije algoritama za pretraživanje, znanje o BST-ovima je neprocjenjiva prednost za svakog programera.
Kako nastavljate svoje putovanje u računarstvu, istraživanje naprednih koncepata poput samouravnotežujućih stabala i njihovih različitih implementacija dodatno će poboljšati vaše razumijevanje i sposobnosti. Nastavite vježbati i eksperimentirati s različitim scenarijima kako biste savladali umijeće efikasnog korištenja binarnih stabala pretraživanja.