Slovenčina

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:

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

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:


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:


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:

Ú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ť

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.