Suomi

Tutustu binääristen hakupuiden (BST) perusteisiin ja opi toteuttamaan ne tehokkaasti JavaScriptillä. Tämä opas kattaa BST-rakenteen, operaatiot ja käytännön esimerkkejä kehittäjille maailmanlaajuisesti.

Binääriset hakupuut: Kattava toteutusopas JavaScriptillä

Binääriset hakupuut (BST) ovat perustavanlaatuinen tietorakenne tietojenkäsittelytieteessä, jota käytetään laajasti tehokkaaseen datan hakuun, lajitteluun ja noutoon. Niiden hierarkkinen rakenne mahdollistaa logaritmisen aikakompleksisuuden monissa operaatioissa, mikä tekee niistä tehokkaan työkalun suurten tietomäärien hallintaan. Tämä opas tarjoaa kattavan yleiskatsauksen binäärisistä hakupuista ja esittelee niiden toteutuksen JavaScriptillä, palvellen kehittäjiä maailmanlaajuisesti.

Binääristen hakupuiden ymmärtäminen

Mikä on binäärinen hakupuu?

Binäärinen hakupuu on puupohjainen tietorakenne, jossa jokaisella solmulla on enintään kaksi lasta, joita kutsutaan vasemmaksi ja oikeaksi lapseksi. Binäärisen hakupuun keskeinen ominaisuus on, että mille tahansa solmulle pätee:

Tämä ominaisuus varmistaa, että binäärisen hakupuun alkiot ovat aina järjestyksessä, mikä mahdollistaa tehokkaan haun ja noudon.

Keskeiset käsitteet

Binäärisen hakupuun toteuttaminen JavaScriptillä

Node-luokan määrittely

Ensin määrittelemme `Node`-luokan edustamaan jokaista solmua binäärisessä hakupuussa. Jokainen solmu sisältää `key`-avaimen tiedon tallentamiseen sekä `left`- ja `right`-osoittimet lapsiinsa.


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

BinarySearchTree-luokan määrittely

Seuraavaksi määrittelemme `BinarySearchTree`-luokan. Tämä luokka sisältää juurisolmun ja metodit puun alkioiden lisäämiseen, hakuun, poistamiseen ja läpikäyntiin.


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

  // Metodit lisätään tähän
}

Lisäys

`insert`-metodi lisää uuden solmun annetulla avaimella binääriseen hakupuuhun. Lisäysprosessi ylläpitää binäärisen hakupuun ominaisuutta sijoittamalla uuden solmun sopivaan paikkaan suhteessa olemassa oleviin solmuihin.


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

Esimerkki: Arvojen lisääminen binääriseen hakupuuhun


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

Haku

`search`-metodi tarkistaa, onko puussa solmua annetulla avaimella. Se käy puun läpi vertaamalla avainta nykyisen solmun avaimeen ja siirtyen vasempaan tai oikeaan alipuuhun sen mukaisesti.


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

Esimerkki: Arvon etsiminen binäärisestä hakupuusta


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

Poisto

`remove`-metodi poistaa solmun annetulla avaimella binäärisestä hakupuusta. Tämä on monimutkaisin operaatio, koska sen on ylläpidettävä binäärisen hakupuun ominaisuutta solmun poiston yhteydessä. Huomioon otettavia tapauksia on kolme:


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 {
    // avain on sama kuin node.key

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

    // tapa 2 - solmulla on vain 1 lapsi
    if (node.left === null) {
      node = node.right;
      return node;
    } else if (node.right === null) {
      node = node.left;
      return node;
    }

    // tapa 3 - solmulla on 2 lasta
    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;
}

Esimerkki: Arvon poistaminen binäärisestä hakupuusta


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

Puun läpikäynti

Puun läpikäynti tarkoittaa jokaisen solmun vierailemista tietyssä järjestyksessä. On olemassa useita yleisiä läpikäyntimenetelmiä:


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

Esimerkki: Binäärisen hakupuun läpikäynti


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

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

Minimi- ja maksimiarvot

Minimi- ja maksimiarvojen löytäminen binäärisestä hakupuusta on suoraviivaista sen järjestetyn luonteen ansiosta.


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

Esimerkki: Minimi- ja maksimiarvojen löytäminen


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

Binääristen hakupuiden käytännön sovellukset

Binäärisiä hakupuita käytetään monissa eri sovelluksissa, kuten:

Suorituskykyyn liittyviä huomioita

Binäärisen hakupuun suorituskyky riippuu sen rakenteesta. Parhaassa tapauksessa tasapainotettu binäärinen hakupuu mahdollistaa logaritmisen aikakompleksisuuden lisäys-, haku- ja poisto-operaatioille. Pahimmassa tapauksessa (esim. vinoutunut puu) aikakompleksisuus voi kuitenkin heiketä lineaariseen aikaan.

Tasapainotetut vs. tasapainottamattomat puut

Tasapainotettu binäärinen hakupuu on sellainen, jossa jokaisen solmun vasemman ja oikean alipuun korkeuden ero on enintään yksi. Itsetasapainottavat algoritmit, kuten AVL-puut ja punamustat puut, varmistavat, että puu pysyy tasapainossa, tarjoten johdonmukaisen suorituskyvyn. Eri alueet saattavat vaatia erilaisia optimointitasoja palvelimen kuormituksen mukaan; tasapainotus auttaa ylläpitämään suorituskykyä korkean maailmanlaajuisen käytön aikana.

Aikakompleksisuus

Edistyneet BST-käsitteet

Itsetasapainottuvat puut

Itsetasapainottuvat puut ovat binäärisiä hakupuita, jotka automaattisesti säätävät rakennettaan tasapainon ylläpitämiseksi. Tämä varmistaa, että puun korkeus pysyy logaritmisena, tarjoten johdonmukaisen suorituskyvyn kaikille operaatioille. Yleisiä itsetasapainottuvia puita ovat AVL-puut ja punamustat puut.

AVL-puut

AVL-puut ylläpitävät tasapainoa varmistamalla, että minkä tahansa solmun vasemman ja oikean alipuun korkeusero on enintään yksi. Kun tämä tasapaino häiriintyy, suoritetaan rotaatioita tasapainon palauttamiseksi.

Punamustat puut

Punamustat puut käyttävät väriominaisuuksia (punainen tai musta) tasapainon ylläpitämiseen. Ne ovat monimutkaisempia kuin AVL-puut, mutta tarjoavat paremman suorituskyvyn tietyissä tilanteissa.

JavaScript-koodiesimerkki: Täydellinen binäärisen hakupuun toteutus


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 {
      // avain on sama kuin node.key

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

      // tapa 2 - solmulla on vain 1 lapsi
      if (node.left === null) {
        node = node.right;
        return node;
      } else if (node.right === null) {
        node = node.left;
        return node;
      }

      // tapa 3 - solmulla on 2 lasta
      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);
    }
  }
}

// Esimerkkikäyttö
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("Sisäjärjestysläpikäynti:");
bst.inOrderTraverse(printNode);

console.log("Esijärjestysläpikäynti:");
bst.preOrderTraverse(printNode);

console.log("Jälkijärjestysläpikäynti:");
bst.postOrderTraverse(printNode);

console.log("Minimiarvo:", bst.min().key);
console.log("Maksimiarvo:", bst.max().key);

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

bst.remove(7);
console.log("Haku arvolle 7 poiston jälkeen:", bst.search(7));

Yhteenveto

Binääriset hakupuut ovat tehokas ja monipuolinen tietorakenne, jolla on lukuisia sovelluksia. Tämä opas on tarjonnut kattavan yleiskatsauksen binäärisistä hakupuista, kattaen niiden rakenteen, operaatiot ja toteutuksen JavaScriptillä. Ymmärtämällä tässä oppaassa käsitellyt periaatteet ja tekniikat, kehittäjät maailmanlaajuisesti voivat tehokkaasti hyödyntää binäärisiä hakupuita ratkaistakseen laajan valikoiman ohjelmistokehityksen ongelmia. Maailmanlaajuisten tietokantojen hallinnasta hakualgoritmien optimointiin, binääristen hakupuiden tuntemus on korvaamaton etu jokaiselle ohjelmoijalle.

Kun jatkat matkaasi tietojenkäsittelytieteen parissa, edistyneiden käsitteiden, kuten itsetasapainottuvien puiden ja niiden erilaisten toteutusten, tutkiminen parantaa entisestään ymmärrystäsi ja kykyjäsi. Jatka harjoittelua ja kokeilemista eri skenaarioilla oppiaksesi hallitsemaan binääristen hakupuiden tehokkaan käytön taidon.