Hrvatski

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:

Ovo svojstvo osigurava da su elementi u BST-u uvijek poredani, što omogućuje efikasno pretraživanje i dohvaćanje.

Ključni pojmovi

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:


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:


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:

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

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.