Eesti

Uurige binaarsete otsingupuude (BST) aluseid ja õppige neid JavaScriptis implementeerima. Juhend katab BST struktuuri, operatsioone ja praktilisi näiteid.

Binaarsed otsingupuud: põhjalik implementeerimisjuhend JavaScriptis

Binaarsed otsingupuud (BST-d) on informaatika fundamentaalne andmestruktuur, mida kasutatakse laialdaselt andmete tõhusaks otsimiseks, sortimiseks ja hankimiseks. Nende hierarhiline struktuur võimaldab paljudes operatsioonides logaritmilist ajakomplekssust, mis teeb neist võimsa tööriista suurte andmehulkade haldamiseks. See juhend annab põhjaliku ülevaate BST-dest ja demonstreerib nende implementeerimist JavaScriptis, olles suunatud arendajatele üle maailma.

Binaarsete otsingupuude mõistmine

Mis on binaarne otsingupuu?

Binaarne otsingupuu on puupõhine andmestruktuur, kus igal tipul on maksimaalselt kaks last, mida nimetatakse vasakuks ja paremaks lapseks. BST põhiomadus on see, et iga antud tipu puhul:

See omadus tagab, et BST elemendid on alati järjestatud, võimaldades tõhusat otsimist ja andmete leidmist.

Põhimõisted

Binaarse otsingupuu implementeerimine JavaScriptis

Klassi Node defineerimine

Esmalt defineerime klassi `Node`, et esindada iga tippu BST-s. Iga tipp sisaldab `key` (võtit) andmete hoidmiseks ning `left` ja `right` viiteid oma lastele.


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

Klassi BinarySearchTree defineerimine

Järgmisena defineerime klassi `BinarySearchTree`. See klass sisaldab juurtippu ning meetodeid puu lisamiseks, otsimiseks, kustutamiseks ja läbimiseks.


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

  // Meetodid lisatakse siia
}

Lisamine

Meetod `insert` lisab uue tipu antud võtmega BST-sse. Lisamisprotsess säilitab BST omaduse, paigutades uue tipu olemasolevate tippude suhtes sobivasse kohta.


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

Näide: Väärtuste lisamine BST-sse


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

Otsimine

Meetod `search` kontrollib, kas BST-s eksisteerib antud võtmega tipp. See läbib puu, võrreldes võtit praeguse tipu võtmega ja liikudes vastavalt vasakule või paremale alampuule.


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

Näide: Väärtuse otsimine BST-st


console.log(bst.search(9));  // Väljund: true
console.log(bst.search(2));  // Väljund: false

Kustutamine

Meetod `remove` kustutab antud võtmega tipu BST-st. See on kõige keerulisem operatsioon, kuna see peab tipu eemaldamisel säilitama BST omaduse. Arvesse tuleb võtta kolme juhtu:


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 {
    // võti on võrdne tipu võtmega

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

    // 2. juhtum - tipul on ainult 1 laps
    if (node.left === null) {
      node = node.right;
      return node;
    } else if (node.right === null) {
      node = node.left;
      return node;
    }

    // 3. juhtum - tipul on 2 last
    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;
}

Näide: Väärtuse eemaldamine BST-st


bst.remove(7);
console.log(bst.search(7)); // Väljund: false

Puu läbimine

Puu läbimine hõlmab iga tipu külastamist kindlas järjekorras. On mitu levinud läbimisviisi:


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

Näide: BST läbimine


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

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

Minimaalsed ja maksimaalsed väärtused

Minimaalse ja maksimaalse väärtuse leidmine BST-s on selle järjestatud olemuse tõttu lihtne.


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

Näide: Minimaalse ja maksimaalse väärtuse leidmine


console.log(bst.min().key); // Väljund: 3
console.log(bst.max().key); // Väljund: 25

Binaarsete otsingupuude praktilised rakendused

Binaarseid otsingupuid kasutatakse mitmesugustes rakendustes, sealhulgas:

Jõudlusega seotud kaalutlused

BST jõudlus sõltub selle struktuurist. Parimal juhul võimaldab tasakaalustatud BST logaritmilist ajakomplekssust lisamis-, otsimis- ja kustutamisoperatsioonide jaoks. Halvimal juhul (nt viltune puu) võib ajakomplekssus aga langeda lineaarsele ajale.

Tasakaalustatud vs tasakaalustamata puud

Tasakaalustatud BST on selline, kus iga tipu vasaku ja parema alampuu kõrguste erinevus on maksimaalselt üks. Isetasakaalustuvad algoritmid, nagu AVL-puud ja puna-mustad puud, tagavad, et puu jääb tasakaalustatuks, pakkudes stabiilset jõudlust. Erinevad piirkonnad võivad vajada erinevaid optimeerimistasemeid sõltuvalt serveri koormusest; tasakaalustamine aitab säilitada jõudlust suure globaalse kasutuse korral.

Ajakomplekssus

Täiustatud BST kontseptsioonid

Isetasakaalustuvad puud

Isetasakaalustuvad puud on BST-d, mis kohandavad automaatselt oma struktuuri tasakaalu säilitamiseks. See tagab, et puu kõrgus jääb logaritmiliseks, pakkudes stabiilset jõudlust kõigi operatsioonide jaoks. Levinud isetasakaalustuvad puud hõlmavad AVL-puid ja puna-musti puid.

AVL-puud

AVL-puud säilitavad tasakaalu, tagades, et iga tipu vasaku ja parema alampuu kõrguste erinevus on maksimaalselt üks. Kui see tasakaal on häiritud, tehakse tasakaalu taastamiseks pöördeid.

Puna-mustad puud

Puna-mustad puud kasutavad tasakaalu säilitamiseks värviomadusi (punane või must). Need on keerulisemad kui AVL-puud, kuid pakuvad teatud stsenaariumides paremat jõudlust.

JavaScripti koodinäide: täielik binaarse otsingupuu implementatsioon


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 {
      // võti on võrdne tipu võtmega

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

      // 2. juhtum - tipul on ainult 1 laps
      if (node.left === null) {
        node = node.right;
        return node;
      } else if (node.right === null) {
        node = node.left;
        return node;
      }

      // 3. juhtum - tipul on 2 last
      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);
    }
  }
}

// Kasutusnäide
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("Sisejärjestuslik läbimine:");
bst.inOrderTraverse(printNode);

console.log("Eesjärjestuslik läbimine:");
bst.preOrderTraverse(printNode);

console.log("Tagajärjestuslik läbimine:");
bst.postOrderTraverse(printNode);

console.log("Minimaalne väärtus:", bst.min().key);
console.log("Maksimaalne väärtus:", bst.max().key);

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

bst.remove(7);
console.log("Otsi 7 pärast eemaldamist:", bst.search(7));

Kokkuvõte

Binaarsed otsingupuud on võimas ja mitmekülgne andmestruktuur, millel on arvukalt rakendusi. See juhend on andnud põhjaliku ülevaate BST-dest, hõlmates nende struktuuri, operatsioone ja implementeerimist JavaScriptis. Mõistes selles juhendis käsitletud põhimõtteid ja tehnikaid, saavad arendajad üle maailma tõhusalt kasutada BST-sid tarkvaraarenduses laia probleemide ringi lahendamiseks. Alates globaalsete andmebaaside haldamisest kuni otsingualgoritmide optimeerimiseni on BST-de tundmine iga programmeerija jaoks hindamatu väärtus.

Jätkates oma teekonda informaatikas, aitab täiustatud kontseptsioonide, nagu isetasakaalustuvate puude ja nende erinevate implementatsioonide uurimine, teie arusaamist ja võimeid veelgi parandada. Jätkake harjutamist ja katsetamist erinevate stsenaariumidega, et omandada binaarsete otsingupuude tõhusa kasutamise kunst.