Išnagrinėkite dvejetainių paieškos medžių (BST) pagrindus ir sužinokite, kaip juos efektyviai įdiegti naudojant JavaScript. Šis vadovas apima struktūrą, operacijas ir praktinius pavyzdžius.
Dvejetainiai paieškos medžiai: išsamus diegimo vadovas naudojant JavaScript
Dvejetainiai paieškos medžiai (angl. Binary Search Trees, BST) yra pagrindinė kompiuterių mokslo duomenų struktūra, plačiai naudojama efektyviai duomenų paieškai, rūšiavimui ir gavimui. Jų hierarchinė struktūra leidžia daugeliui operacijų pasiekti logaritminį laiko sudėtingumą, todėl jie yra galingas įrankis valdant didelius duomenų rinkinius. Šiame vadove pateikiama išsami BST apžvalga ir demonstruojamas jų diegimas naudojant JavaScript, skirtas programuotojams visame pasaulyje.
Dvejetainių paieškos medžių supratimas
Kas yra dvejetainis paieškos medis?
Dvejetainis paieškos medis yra medžio tipo duomenų struktūra, kurioje kiekviena viršūnė turi ne daugiau kaip du vaikus, vadinamus kairiuoju vaiku ir dešiniuoju vaiku. Pagrindinė BST savybė yra ta, kad bet kuriai duotai viršūnei:
- Visos kairiajame pomedyje esančios viršūnės turi raktus, mažesnius už viršūnės raktą.
- Visos dešiniajame pomedyje esančios viršūnės turi raktus, didesnius už viršūnės raktą.
Ši savybė užtikrina, kad elementai BST visada yra surūšiuoti, o tai leidžia efektyviai ieškoti ir gauti duomenis.
Pagrindinės sąvokos
- Viršūnė: Pagrindinis medžio vienetas, turintis raktą (duomenis) ir rodykles į kairįjį ir dešinįjį vaikus.
- Šaknis: Aukščiausia medžio viršūnė.
- Lapas: Viršūnė be vaikų.
- Pomedis: Medžio dalis, kurios šaknis yra tam tikra viršūnė.
- Aukštis: Ilgiausio kelio nuo šaknies iki lapo ilgis.
- Gylis: Kelio nuo šaknies iki konkrečios viršūnės ilgis.
Dvejetainio paieškos medžio diegimas JavaScript
Viršūnės klasės apibrėžimas
Pirmiausia apibrėžiame `Node` klasę, kuri atstovaus kiekvieną BST viršūnę. Kiekviena viršūnė turės `key` duomenims saugoti ir `left` bei `right` rodykles į savo vaikus.
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
Dvejetainio paieškos medžio klasės apibrėžimas
Toliau apibrėžiame `BinarySearchTree` klasę. Šioje klasėje bus šaknies viršūnė ir metodai, skirti medžiui įterpti, ieškoti, šalinti ir apeiti.
class BinarySearchTree {
constructor() {
this.root = null;
}
// Čia bus pridėti metodai
}
Įterpimas
`insert` metodas prideda naują viršūnę su duotu raktu į BST. Įterpimo procesas išlaiko BST savybę, įdėdamas naują viršūnę į tinkamą poziciją, palyginti su esamomis viršūnėmis.
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);
}
}
}
Pavyzdys: reikšmių įterpimas į 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);
Paieška
`search` metodas patikrina, ar BST egzistuoja viršūnė su duotu raktu. Jis apeina medį, lygindamas raktą su dabartinės viršūnės raktu ir atitinkamai pereidamas į kairįjį ar dešinįjį pomedį.
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;
}
}
Pavyzdys: reikšmės paieška BST
console.log(bst.search(9)); // Išvestis: true
console.log(bst.search(2)); // Išvestis: false
Šalinimas
`remove` metodas pašalina viršūnę su duotu raktu iš BST. Tai sudėtingiausia operacija, nes šalinant viršūnę reikia išlaikyti BST savybę. Reikia apsvarstyti tris atvejus:
- 1 atvejis: Šalinama viršūnė yra lapas. Tiesiog pašalinkite ją.
- 2 atvejis: Šalinama viršūnė turi vieną vaiką. Pakeiskite viršūnę jos vaiku.
- 3 atvejis: Šalinama viršūnė turi du vaikus. Raskite tvarkos įpėdinį (mažiausią viršūnę dešiniajame pomedyje), pakeiskite viršūnę įpėdiniu, o tada pašalinkite įpėdinį.
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 {
// raktas yra lygus viršūnės raktui
// 1 atvejis - lapo viršūnė
if (node.left === null && node.right === null) {
node = null;
return node;
}
// 2 atvejis - viršūnė turi tik 1 vaiką
if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
// 3 atvejis - viršūnė turi 2 vaikus
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;
}
Pavyzdys: reikšmės šalinimas iš BST
bst.remove(7);
console.log(bst.search(7)); // Išvestis: false
Medžio apėjimas
Medžio apėjimas apima kiekvienos medžio viršūnės aplankymą tam tikra tvarka. Yra keletas įprastų apėjimo metodų:
- Vidinis (In-order): Aplanko kairįjį pomedį, tada viršūnę, tada dešinįjį pomedį. Tai lemia viršūnių aplankymą didėjimo tvarka.
- Pirminis (Pre-order): Aplanko viršūnę, tada kairįjį pomedį, tada dešinįjį pomedį.
- Poordinis (Post-order): Aplanko kairįjį pomedį, tada dešinįjį pomedį, tada viršūnę.
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);
}
}
Pavyzdys: BST apėjimas
const printNode = (value) => console.log(value);
bst.inOrderTraverse(printNode); // Išvestis: 3 5 8 9 10 11 12 13 14 15 18 20 25
bst.preOrderTraverse(printNode); // Išvestis: 11 5 3 9 8 10 15 13 12 14 20 18 25
bst.postOrderTraverse(printNode); // Išvestis: 3 8 10 9 12 14 13 18 25 20 15 11
Mažiausios ir didžiausios reikšmės
Mažiausios ir didžiausios reikšmės radimas BST yra paprastas dėl jo tvarkingos prigimties.
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;
}
Pavyzdys: mažiausios ir didžiausios reikšmių radimas
console.log(bst.min().key); // Išvestis: 3
console.log(bst.max().key); // Išvestis: 25
Praktinis dvejetainių paieškos medžių taikymas
Dvejetainiai paieškos medžiai naudojami įvairiose srityse, įskaitant:
- Duomenų bazės: Duomenų indeksavimas ir paieška. Pavyzdžiui, daugelis duomenų bazių sistemų naudoja BST variantus, tokius kaip B-medžiai, kad efektyviai rastų įrašus. Pagalvokite apie tarptautinių korporacijų naudojamų duomenų bazių pasaulinį mastą; efektyvus duomenų gavimas yra itin svarbus.
- Kompiliatoriai: Simbolių lentelės, kuriose saugoma informacija apie kintamuosius ir funkcijas.
- Operacinės sistemos: Procesų planavimas ir atminties valdymas.
- Paieškos sistemos: Interneto puslapių indeksavimas ir paieškos rezultatų reitingavimas.
- Failų sistemos: Failų organizavimas ir prieiga prie jų. Įsivaizduokite failų sistemą serveryje, naudojamame visame pasaulyje svetainėms talpinti; gerai organizuota BST pagrįsta struktūra padeda greitai pateikti turinį.
Našumo aspektai
BST našumas priklauso nuo jo struktūros. Geriausiu atveju, subalansuotas BST leidžia pasiekti logaritminį laiko sudėtingumą įterpimo, paieškos ir šalinimo operacijoms. Tačiau blogiausiu atveju (pvz., iškrypęs medis), laiko sudėtingumas gali pablogėti iki tiesinio laiko.
Subalansuoti ir nesubalansuoti medžiai
Subalansuotas BST yra toks, kuriame bet kurios viršūnės kairiojo ir dešiniojo pomedžių aukščiai skiriasi ne daugiau kaip vienu. Savaime balansuojantys algoritmai, tokie kaip AVL medžiai ir raudonai-juodi medžiai, užtikrina, kad medis išliktų subalansuotas, suteikdamas pastovų našumą. Skirtingiems regionams gali prireikti skirtingų optimizavimo lygių, atsižvelgiant į serverio apkrovą; balansavimas padeda išlaikyti našumą esant dideliam pasauliniam naudojimui.
Laiko sudėtingumas
- Įterpimas: O(log n) vidutiniškai, O(n) blogiausiu atveju.
- Paieška: O(log n) vidutiniškai, O(n) blogiausiu atveju.
- Šalinimas: O(log n) vidutiniškai, O(n) blogiausiu atveju.
- Apėjimas: O(n), kur n yra medžio viršūnių skaičius.
Pažangios BST sąvokos
Savaime balansuojantys medžiai
Savaime balansuojantys medžiai yra BST, kurie automatiškai koreguoja savo struktūrą, kad išlaikytų pusiausvyrą. Tai užtikrina, kad medžio aukštis išliktų logaritminis, suteikdamas pastovų našumą visoms operacijoms. Įprasti savaime balansuojantys medžiai apima AVL medžius ir raudonai-juodus medžius.
AVL medžiai
AVL medžiai palaiko pusiausvyrą užtikrindami, kad aukščio skirtumas tarp bet kurios viršūnės kairiojo ir dešiniojo pomedžių būtų ne didesnis kaip vienas. Kai ši pusiausvyra sutrinka, atliekami pasukimai pusiausvyrai atkurti.
Raudonai-juodi medžiai
Raudonai-juodi medžiai naudoja spalvų savybes (raudona arba juoda) pusiausvyrai palaikyti. Jie yra sudėtingesni nei AVL medžiai, tačiau tam tikrais atvejais siūlo geresnį našumą.
JavaScript kodo pavyzdys: Pilnas dvejetainio paieškos medžio diegimas
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 {
// raktas yra lygus viršūnės raktui
// 1 atvejis - lapo viršūnė
if (node.left === null && node.right === null) {
node = null;
return node;
}
// 2 atvejis - viršūnė turi tik 1 vaiką
if (node.left === null) {
node = node.right;
return node;
} else if (node.right === null) {
node = node.left;
return node;
}
// 3 atvejis - viršūnė turi 2 vaikus
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);
}
}
}
// Naudojimo pavyzdys
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("Vidinis apėjimas:");
bst.inOrderTraverse(printNode);
console.log("Pirminis apėjimas:");
bst.preOrderTraverse(printNode);
console.log("Poordinis apėjimas:");
bst.postOrderTraverse(printNode);
console.log("Mažiausia reikšmė:", bst.min().key);
console.log("Didžiausia reikšmė:", bst.max().key);
console.log("Ieškoti 9:", bst.search(9));
console.log("Ieškoti 2:", bst.search(2));
bst.remove(7);
console.log("Ieškoti 7 po pašalinimo:", bst.search(7));
Išvada
Dvejetainiai paieškos medžiai yra galinga ir universali duomenų struktūra, turinti daugybę pritaikymų. Šiame vadove pateikta išsami BST apžvalga, apimanti jų struktūrą, operacijas ir diegimą naudojant JavaScript. Suprasdami šiame vadove aptartus principus ir metodus, programuotojai visame pasaulyje gali efektyviai naudoti BST sprendžiant įvairias programinės įrangos kūrimo problemas. Nuo pasaulinių duomenų bazių valdymo iki paieškos algoritmų optimizavimo, BST žinios yra neįkainojamas turtas bet kuriam programuotojui.
Tęsiant kelionę kompiuterių mokslo srityje, gilesnis susipažinimas su pažangiomis sąvokomis, tokiomis kaip savaime balansuojantys medžiai ir jų įvairūs diegimai, dar labiau sustiprins jūsų supratimą ir gebėjimus. Toliau praktikuokitės ir eksperimentuokite su skirtingais scenarijais, kad įvaldytumėte efektyvaus dvejetainių paieškos medžių naudojimo meną.