Slovenščina

Raziščite osnove binarnih iskalnih dreves (BST) in se naučite, kako jih učinkovito implementirati v JavaScriptu. Ta vodnik pokriva strukturo, operacije in praktične primere za razvijalce po vsem svetu.

Binarna iskalna drevesa: Celovit vodnik za implementacijo v JavaScriptu

Binarna iskalna drevesa (BST) so temeljna podatkovna struktura v računalništvu, ki se pogosto uporablja za učinkovito iskanje, razvrščanje in pridobivanje podatkov. Njihova hierarhična struktura omogoča logaritemsko časovno zahtevnost pri mnogih operacijah, zaradi česar so močno orodje za upravljanje velikih zbirk podatkov. Ta vodnik ponuja celovit pregled BST-jev in prikazuje njihovo implementacijo v JavaScriptu, namenjeno razvijalcem po vsem svetu.

Razumevanje binarnih iskalnih dreves

Kaj je binarno iskalno drevo?

Binarno iskalno drevo je drevesna podatkovna struktura, kjer ima vsako vozlišče največ dva otroka, imenovana levi otrok in desni otrok. Ključna lastnost BST-ja je, da za vsako dano vozlišče velja:

Ta lastnost zagotavlja, da so elementi v BST vedno urejeni, kar omogoča učinkovito iskanje in pridobivanje.

Ključni pojmi

Implementacija binarnega iskalnega drevesa v JavaScriptu

Definiranje razreda Node

Najprej definiramo razred `Node`, ki predstavlja vsako vozlišče v BST. Vsako vozlišče bo vsebovalo `key` za shranjevanje podatka ter `left` in `right` kazalca na svoja otroka.


class Node {
  constructor(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}

Definiranje razreda BinarySearchTree

Nato definiramo razred `BinarySearchTree`. Ta razred bo vseboval korensko vozlišče in metode za vstavljanje, iskanje, brisanje in prehajanje po drevesu.


class BinarySearchTree {
  constructor() {
    this.root = null;
  }

  // Metode bodo dodane tukaj
}

Vstavljanje

Metoda `insert` doda novo vozlišče z danim ključem v BST. Postopek vstavljanja ohranja lastnost BST z umestitvijo novega vozlišča na ustrezno mesto glede na obstoječa vozlišča.


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);
    }
  }
}

Primer: Vstavljanje vrednosti v 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);

Iskanje

Metoda `search` preveri, ali v BST obstaja vozlišče z danim ključem. Prehaja po drevesu, primerja ključ s ključem trenutnega vozlišča in se ustrezno premika v levo ali desno poddrevo.


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;
  }
}

Primer: Iskanje vrednosti v BST


console.log(bst.search(9));  // Output: true
console.log(bst.search(2));  // Output: false

Brisanje

Metoda `remove` izbriše vozlišče z danim ključem iz BST. To je najbolj zapletena operacija, saj mora med odstranjevanjem vozlišča ohraniti lastnost BST. Upoštevati je treba tri primere:


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 enak ključu vozlišča

    // primer 1 - list
    if (node.left === null && node.right === null) {
      node = null;
      return node;
    }

    // primer 2 - vozlišče ima samo 1 otroka
    if (node.left === null) {
      node = node.right;
      return node;
    } else if (node.right === null) {
      node = node.left;
      return node;
    }

    // primer 3 - vozlišče ima 2 otroka
    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;
}

Primer: Odstranjevanje vrednosti iz BST


bst.remove(7);
console.log(bst.search(7)); // Output: false

Prehajanje drevesa

Prehajanje drevesa vključuje obisk vsakega vozlišča v drevesu v določenem vrstnem redu. Obstaja več običajnih metod prehajanja:


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);
  }
}

Primer: Prehajanje po BST


const printNode = (value) => console.log(value);

bst.inOrderTraverse(printNode);   // Output: 3 5 8 9 10 11 12 13 14 15 18 20 25
bst.preOrderTraverse(printNode);  // Output: 11 5 3 9 8 10 15 13 12 14 20 18 25
bst.postOrderTraverse(printNode); // Output: 3 8 10 9 12 14 13 18 25 20 15 11

Minimalne in maksimalne vrednosti

Iskanje minimalne in maksimalne vrednosti v BST je preprosto zaradi njegove urejene narave.


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;
}

Primer: Iskanje minimalne in maksimalne vrednosti


console.log(bst.min().key); // Output: 3
console.log(bst.max().key); // Output: 25

Praktična uporaba binarnih iskalnih dreves

Binarna iskalna drevesa se uporabljajo v različnih aplikacijah, vključno z:

Učinkovitost delovanja

Učinkovitost delovanja BST je odvisna od njegove strukture. V najboljšem primeru uravnoteženo BST omogoča logaritemsko časovno zahtevnost za operacije vstavljanja, iskanja in brisanja. Vendar pa se lahko v najslabšem primeru (npr. pri poševnem drevesu) časovna zahtevnost poslabša na linearni čas.

Uravnotežena proti neuravnoteženim drevesom

Uravnoteženo BST je tisto, kjer se višina levega in desnega poddrevesa vsakega vozlišča razlikuje za največ ena. Samouravnoteževalni algoritmi, kot so AVL drevesa in rdeče-črna drevesa, zagotavljajo, da drevo ostane uravnoteženo, kar zagotavlja dosledno delovanje. Različne regije morda zahtevajo različne stopnje optimizacije glede na obremenitev strežnika; uravnoteženje pomaga ohranjati delovanje pri visoki globalni uporabi.

Časovna zahtevnost

Napredni koncepti BST

Samouravnoteževalna drevesa

Samouravnoteževalna drevesa so BST-ji, ki samodejno prilagajajo svojo strukturo za ohranjanje ravnotežja. To zagotavlja, da višina drevesa ostane logaritemska, kar zagotavlja dosledno delovanje za vse operacije. Pogosta samouravnoteževalna drevesa vključujejo AVL drevesa in rdeče-črna drevesa.

AVL drevesa

AVL drevesa ohranjajo ravnotežje tako, da zagotavljajo, da je razlika v višini med levim in desnim poddrevesom katerega koli vozlišča največ ena. Ko je to ravnotežje porušeno, se izvedejo rotacije za ponovno vzpostavitev ravnotežja.

Rdeče-črna drevesa

Rdeče-črna drevesa uporabljajo barvne lastnosti (rdeča ali črna) za ohranjanje ravnotežja. So bolj zapletena kot AVL drevesa, vendar v določenih primerih ponujajo boljšo zmogljivost.

Primer kode v JavaScriptu: Popolna implementacija binarnega iskalnega drevesa


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 enak ključu vozlišča

      // primer 1 - list
      if (node.left === null && node.right === null) {
        node = null;
        return node;
      }

      // primer 2 - vozlišče ima samo 1 otroka
      if (node.left === null) {
        node = node.right;
        return node;
      } else if (node.right === null) {
        node = node.left;
        return node;
      }

      // primer 3 - vozlišče ima 2 otroka
      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);
    }
  }
}

// Primer uporabe
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("Prečni obhod:");
bst.inOrderTraverse(printNode);

console.log("Predponski obhod:");
bst.preOrderTraverse(printNode);

console.log("Postponski obhod:");
bst.postOrderTraverse(printNode);

console.log("Minimalna vrednost:", bst.min().key);
console.log("Maksimalna vrednost:", bst.max().key);

console.log("Iskanje 9:", bst.search(9));
console.log("Iskanje 2:", bst.search(2));

bst.remove(7);
console.log("Iskanje 7 po odstranitvi:", bst.search(7));

Zaključek

Binarna iskalna drevesa so močna in vsestranska podatkovna struktura s številnimi aplikacijami. Ta vodnik je ponudil celovit pregled BST-jev, ki zajema njihovo strukturo, operacije in implementacijo v JavaScriptu. Z razumevanjem načel in tehnik, obravnavanih v tem vodniku, lahko razvijalci po vsem svetu učinkovito uporabljajo BST-je za reševanje širokega spektra problemov pri razvoju programske opreme. Od upravljanja globalnih podatkovnih baz do optimizacije iskalnih algoritmov je znanje o BST-jih neprecenljivo sredstvo za vsakega programerja.

Ko boste nadaljevali svojo pot v računalništvu, bo raziskovanje naprednih konceptov, kot so samouravnoteževalna drevesa in njihove različne implementacije, še dodatno izboljšalo vaše razumevanje in zmožnosti. Nadaljujte z vajo in eksperimentiranjem z različnimi scenariji, da boste obvladali umetnost učinkovite uporabe binarnih iskalnih dreves.