Preskúmajte základy binárnych vyhľadávacích stromov (BST) a naučte sa ich efektívnu implementáciu v JavaScripte. Sprievodca pokrýva štruktúru, operácie a príklady.
Binárne vyhľadávacie stromy: Komplexný sprievodca implementáciou v JavaScripte
Binárne vyhľadávacie stromy (BST) sú základnou dátovou štruktúrou v informatike, široko používanou na efektívne vyhľadávanie, triedenie a získavanie údajov. Ich hierarchická štruktúra umožňuje logaritmickú časovú zložitosť pri mnohých operáciách, čo z nich robí výkonný nástroj na správu veľkých súborov údajov. Tento sprievodca poskytuje komplexný prehľad BST a demonštruje ich implementáciu v JavaScripte, určenú pre vývojárov z celého sveta.
Pochopenie binárnych vyhľadávacích stromov
Čo je binárny vyhľadávací strom?
Binárny vyhľadávací strom je stromová dátová štruktúra, kde každý uzol má najviac dve deti, označované ako ľavé dieťa a pravé dieťa. Kľúčovou vlastnosťou BST je, že pre akýkoľvek daný uzol:
- Všetky uzly v ľavom podstrome majú kľúče menšie ako kľúč uzla.
- Všetky uzly v pravom podstrome majú kľúče väčšie ako kľúč uzla.
Táto vlastnosť zaisťuje, že prvky v BST sú vždy usporiadané, čo umožňuje efektívne vyhľadávanie a získavanie dát.
Kľúčové pojmy
- Uzol (Node): Základná jednotka v strome, obsahujúca kľúč (dáta) a ukazovatele na ľavé a pravé dieťa.
- Koreň (Root): Najvyšší uzol v strome.
- List (Leaf): Uzol bez detí.
- Podstrom (Subtree): Časť stromu zakorenená v konkrétnom uzle.
- Výška (Height): Dĺžka najdlhšej cesty od koreňa k listu.
- Hĺbka (Depth): Dĺžka cesty od koreňa ku konkrétnemu uzlu.
Implementácia binárneho vyhľadávacieho stromu v JavaScripte
Definovanie triedy Node
Najprv definujeme triedu `Node`, ktorá bude reprezentovať každý uzol v BST. Každý uzol bude obsahovať `key` na uloženie dát a ukazovatele `left` a `right` na svoje deti.
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
Definovanie triedy BinarySearchTree
Ďalej definujeme triedu `BinarySearchTree`. Táto trieda bude obsahovať koreňový uzol a metódy na vkladanie, vyhľadávanie, mazanie a prechádzanie stromom.
class BinarySearchTree {
constructor() {
this.root = null;
}
// Tu budú pridané metódy
}
Vkladanie
Metóda `insert` pridáva nový uzol s daným kľúčom do BST. Proces vkladania udržiava vlastnosť BST umiestnením nového uzla na príslušnú pozíciu vzhľadom na existujúce uzly.
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);
}
}
}
Príklad: Vkladanie hodnôt do 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);
Vyhľadávanie
Metóda `search` kontroluje, či v BST existuje uzol s daným kľúčom. Prechádza stromom, porovnáva kľúč s kľúčom aktuálneho uzla a podľa toho sa posúva do ľavého alebo pravého podstromu.
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;
}
}
Príklad: Vyhľadávanie hodnoty v BST
console.log(bst.search(9)); // Výstup: true
console.log(bst.search(2)); // Výstup: false
Mazanie
Metóda `remove` maže uzol s daným kľúčom z BST. Ide o najzložitejšiu operáciu, pretože pri odstraňovaní uzla je potrebné zachovať vlastnosť BST. Je potrebné zvážiť tri prípady:
- Prípad 1: Uzol, ktorý sa má vymazať, je listový uzol. Jednoducho ho odstráňte.
- Prípad 2: Uzol, ktorý sa má vymazať, má jedno dieťa. Nahraďte uzol jeho dieťaťom.
- Prípad 3: Uzol, ktorý sa má vymazať, má dve deti. Nájdite následníka v poradí in-order (najmenší uzol v pravom podstrome), nahraďte uzol následníkom a potom vymažte následníka.
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 {
// kľúč sa rovná kľúču uzla
// prípad 1 - listový uzol
if (node.left === null && node.right === null) {
node = null;
return node;
}
// prípad 2 - uzol má len 1 dieťa
if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
// prípad 3 - uzol má 2 deti
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;
}
Príklad: Odstránenie hodnoty z BST
bst.remove(7);
console.log(bst.search(7)); // Výstup: false
Prechádzanie stromom
Prechádzanie stromom zahŕňa návštevu každého uzla v strome v určitom poradí. Existuje niekoľko bežných metód prechádzania:
- In-order: Navštívi ľavý podstrom, potom uzol, potom pravý podstrom. Výsledkom je návšteva uzlov vo vzostupnom poradí.
- Pre-order: Navštívi uzol, potom ľavý podstrom, potom pravý podstrom.
- Post-order: Navštívi ľavý podstrom, potom pravý podstrom, potom uzol.
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);
}
}
Príklad: Prechádzanie BST
const printNode = (value) => console.log(value);
bst.inOrderTraverse(printNode); // Výstup: 3 5 8 9 10 11 12 13 14 15 18 20 25
bst.preOrderTraverse(printNode); // Výstup: 11 5 3 9 8 10 15 13 12 14 20 18 25
bst.postOrderTraverse(printNode); // Výstup: 3 8 10 9 12 14 13 18 25 20 15 11
Minimálne a maximálne hodnoty
Nájdenie minimálnej a maximálnej hodnoty v BST je vďaka jeho usporiadanej povahe jednoduché.
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;
}
Príklad: Hľadanie minimálnej a maximálnej hodnoty
console.log(bst.min().key); // Výstup: 3
console.log(bst.max().key); // Výstup: 25
Praktické aplikácie binárnych vyhľadávacích stromov
Binárne vyhľadávacie stromy sa používajú v rôznych aplikáciách, vrátane:
- Databázy: Indexovanie a vyhľadávanie údajov. Napríklad mnohé databázové systémy používajú variácie BST, ako sú B-stromy, na efektívne vyhľadávanie záznamov. Zoberme si globálny rozsah databáz používaných nadnárodnými korporáciami; efektívne získavanie údajov je prvoradé.
- Kompilátory: Tabuľky symbolov, ktoré ukladajú informácie o premenných a funkciách.
- Operačné systémy: Plánovanie procesov a správa pamäte.
- Vyhľadávače: Indexovanie webových stránok a hodnotenie výsledkov vyhľadávania.
- Súborové systémy: Organizácia a prístup k súborom. Predstavte si súborový systém na serveri používanom globálne na hosťovanie webových stránok; dobre organizovaná štruktúra založená na BST pomáha pri rýchlom poskytovaní obsahu.
Úvahy o výkone
Výkon BST závisí od jeho štruktúry. V najlepšom prípade, vyvážený BST umožňuje logaritmickú časovú zložitosť pre operácie vkladania, vyhľadávania a mazania. Avšak v najhoršom prípade (napr. degenerovaný strom), sa časová zložitosť môže zhoršiť na lineárny čas.
Vyvážené vs. nevyvážené stromy
Vyvážený BST je taký, kde sa výška ľavého a pravého podstromu každého uzla líši najviac o jedna. Samovyvažovacie algoritmy, ako sú AVL stromy a červeno-čierne stromy, zaisťujú, že strom zostane vyvážený, čo poskytuje konzistentný výkon. Rôzne regióny môžu vyžadovať rôzne úrovne optimalizácie na základe zaťaženia servera; vyvažovanie pomáha udržiavať výkon pri vysokom globálnom využití.
Časová zložitosť
- Vkladanie: O(log n) v priemere, O(n) v najhoršom prípade.
- Vyhľadávanie: O(log n) v priemere, O(n) v najhoršom prípade.
- Mazanie: O(log n) v priemere, O(n) v najhoršom prípade.
- Prechádzanie: O(n), kde n je počet uzlov v strome.
Pokročilé koncepty BST
Samovyvažovacie stromy
Samovyvažovacie stromy sú BST, ktoré automaticky upravujú svoju štruktúru, aby si udržali rovnováhu. To zaisťuje, že výška stromu zostane logaritmická, čo poskytuje konzistentný výkon pre všetky operácie. Bežné samovyvažovacie stromy zahŕňajú AVL stromy a červeno-čierne stromy.
AVL stromy
AVL stromy udržiavajú rovnováhu tým, že zaisťujú, že rozdiel výšok medzi ľavým a pravým podstromom akéhokoľvek uzla je najviac jedna. Keď je táto rovnováha narušená, vykonávajú sa rotácie na jej obnovenie.
Červeno-čierne stromy
Červeno-čierne stromy používajú vlastnosti farieb (červená alebo čierna) na udržanie rovnováhy. Sú zložitejšie ako AVL stromy, ale v určitých scenároch ponúkajú lepší výkon.
Príklad kódu v JavaScripte: Kompletná implementácia binárneho vyhľadávacieho stromu
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 {
// kľúč sa rovná kľúču uzla
// prípad 1 - listový uzol
if (node.left === null && node.right === null) {
node = null;
return node;
}
// prípad 2 - uzol má len 1 dieťa
if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
// prípad 3 - uzol má 2 deti
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);
}
}
}
// Príklad použitia
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("Prechádzanie in-order:");
bst.inOrderTraverse(printNode);
console.log("Prechádzanie pre-order:");
bst.preOrderTraverse(printNode);
console.log("Prechádzanie post-order:");
bst.postOrderTraverse(printNode);
console.log("Minimálna hodnota:", bst.min().key);
console.log("Maximálna hodnota:", bst.max().key);
console.log("Vyhľadávanie 9:", bst.search(9));
console.log("Vyhľadávanie 2:", bst.search(2));
bst.remove(7);
console.log("Vyhľadávanie 7 po odstránení:", bst.search(7));
Záver
Binárne vyhľadávacie stromy sú výkonnou a všestrannou dátovou štruktúrou s mnohými aplikáciami. Tento sprievodca poskytol komplexný prehľad BST, pokrývajúci ich štruktúru, operácie a implementáciu v JavaScripte. Porozumením princípom a technikám diskutovaným v tomto sprievodcovi môžu vývojári z celého sveta efektívne využívať BST na riešenie širokej škály problémov pri vývoji softvéru. Od správy globálnych databáz po optimalizáciu vyhľadávacích algoritmov je znalosť BST neoceniteľným prínosom pre každého programátora.
Ako budete pokračovať vo svojej ceste v informatike, skúmanie pokročilých konceptov, ako sú samovyvažovacie stromy a ich rôzne implementácie, ďalej prehĺbi vaše porozumenie a schopnosti. Pokračujte v precvičovaní a experimentovaní s rôznymi scenármi, aby ste si osvojili umenie efektívneho používania binárnych vyhľadávacích stromov.