Bahasa Indonesia

Pelajari dasar-dasar Pohon Pencarian Biner (BST) dan implementasinya yang efisien dalam JavaScript. Panduan ini mencakup struktur, operasi, dan contoh praktis untuk pengembang global.

Pohon Pencarian Biner: Panduan Implementasi Komprehensif dalam JavaScript

Pohon Pencarian Biner (Binary Search Trees atau BST) adalah struktur data fundamental dalam ilmu komputer, yang banyak digunakan untuk pencarian, pengurutan, dan pengambilan data yang efisien. Struktur hierarkisnya memungkinkan kompleksitas waktu logaritmik dalam banyak operasi, menjadikannya alat yang ampuh untuk mengelola kumpulan data besar. Panduan ini memberikan gambaran komprehensif tentang BST dan menunjukkan implementasinya dalam JavaScript, yang ditujukan untuk para pengembang di seluruh dunia.

Memahami Pohon Pencarian Biner

Apa itu Pohon Pencarian Biner?

Pohon Pencarian Biner adalah struktur data berbasis pohon di mana setiap node memiliki paling banyak dua anak, yang disebut sebagai anak kiri dan anak kanan. Properti utama dari BST adalah bahwa untuk setiap node tertentu:

Properti ini memastikan bahwa elemen-elemen dalam BST selalu terurut, memungkinkan pencarian dan pengambilan yang efisien.

Konsep Kunci

Mengimplementasikan Pohon Pencarian Biner di JavaScript

Mendefinisikan Kelas Node

Pertama, kita mendefinisikan kelas Node untuk mewakili setiap node dalam BST. Setiap node akan berisi sebuah key untuk menyimpan data dan penunjuk left dan right ke anak-anaknya.


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

Mendefinisikan Kelas Pohon Pencarian Biner

Selanjutnya, kita mendefinisikan kelas BinarySearchTree. Kelas ini akan berisi node root dan metode untuk menyisipkan, mencari, menghapus, dan menelusuri pohon.


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

  // Metode akan ditambahkan di sini
}

Penyisipan

Metode insert menambahkan node baru dengan kunci yang diberikan ke BST. Proses penyisipan mempertahankan properti BST dengan menempatkan node baru di posisi yang sesuai relatif terhadap node yang ada.


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

Contoh: Menyisipkan nilai ke dalam 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);

Pencarian

Metode search memeriksa apakah sebuah node dengan kunci yang diberikan ada di dalam BST. Metode ini menelusuri pohon, membandingkan kunci dengan kunci node saat ini dan bergerak ke sub-pohon kiri atau kanan yang sesuai.


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

Contoh: Mencari nilai di dalam BST


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

Penghapusan

Metode remove menghapus sebuah node dengan kunci yang diberikan dari BST. Ini adalah operasi yang paling kompleks karena perlu mempertahankan properti BST saat menghapus node. Ada tiga kasus yang perlu dipertimbangkan:


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 {
    // kunci sama dengan node.key

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

    // kasus 2 - node hanya memiliki 1 anak
    if (node.left === null) {
      node = node.right;
      return node;
    } else if (node.right === null) {
      node = node.left;
      return node;
    }

    // kasus 3 - node memiliki 2 anak
    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;
}

Contoh: Menghapus nilai dari BST


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

Penelusuran Pohon

Penelusuran pohon melibatkan kunjungan ke setiap node di pohon dalam urutan tertentu. Ada beberapa metode penelusuran umum:


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

Contoh: Menelusuri 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

Nilai Minimum dan Maksimum

Menemukan nilai minimum dan maksimum dalam BST sangat mudah, berkat sifatnya yang terurut.


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

Contoh: Menemukan nilai minimum dan maksimum


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

Aplikasi Praktis Pohon Pencarian Biner

Pohon Pencarian Biner digunakan dalam berbagai aplikasi, termasuk:

Pertimbangan Kinerja

Kinerja BST tergantung pada strukturnya. Dalam skenario kasus terbaik, BST yang seimbang memungkinkan kompleksitas waktu logaritmik untuk operasi penyisipan, pencarian, dan penghapusan. Namun, dalam skenario kasus terburuk (misalnya, pohon yang miring), kompleksitas waktu dapat menurun menjadi waktu linear.

Pohon Seimbang vs. Tidak Seimbang

BST yang seimbang adalah pohon di mana tinggi sub-pohon kiri dan kanan dari setiap node berbeda paling banyak satu. Algoritma penyeimbangan diri, seperti pohon AVL dan pohon Merah-Hitam, memastikan bahwa pohon tetap seimbang, memberikan kinerja yang konsisten. Wilayah yang berbeda mungkin memerlukan tingkat optimisasi yang berbeda berdasarkan beban pada server; penyeimbangan membantu mempertahankan kinerja di bawah penggunaan global yang tinggi.

Kompleksitas Waktu

Konsep Lanjutan BST

Pohon yang Menyeimbangkan Diri

Pohon yang menyeimbangkan diri adalah BST yang secara otomatis menyesuaikan strukturnya untuk menjaga keseimbangan. Hal ini memastikan bahwa tinggi pohon tetap logaritmik, memberikan kinerja yang konsisten untuk semua operasi. Pohon penyeimbang diri yang umum termasuk pohon AVL dan pohon Merah-Hitam.

Pohon AVL

Pohon AVL menjaga keseimbangan dengan memastikan bahwa perbedaan tinggi antara sub-pohon kiri dan kanan dari setiap node paling banyak satu. Ketika keseimbangan ini terganggu, rotasi dilakukan untuk mengembalikan keseimbangan.

Pohon Merah-Hitam

Pohon Merah-Hitam menggunakan properti warna (merah atau hitam) untuk menjaga keseimbangan. Mereka lebih kompleks daripada pohon AVL tetapi menawarkan kinerja yang lebih baik dalam skenario tertentu.

Contoh Kode JavaScript: Implementasi Lengkap Pohon Pencarian Biner


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 {
      // kunci sama dengan node.key

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

      // kasus 2 - node hanya memiliki 1 anak
      if (node.left === null) {
        node = node.right;
        return node;
      } else if (node.right === null) {
        node = node.left;
        return node;
      }

      // kasus 3 - node memiliki 2 anak
      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);
    }
  }
}

// Contoh Penggunaan
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("Penelusuran in-order:");
bst.inOrderTraverse(printNode);

console.log("Penelusuran pre-order:");
bst.preOrderTraverse(printNode);

console.log("Penelusuran post-order:");
bst.postOrderTraverse(printNode);

console.log("Nilai minimum:", bst.min().key);
console.log("Nilai maksimum:", bst.max().key);

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

bst.remove(7);
console.log("Cari angka 7 setelah dihapus:", bst.search(7));

Kesimpulan

Pohon Pencarian Biner adalah struktur data yang kuat dan serbaguna dengan banyak aplikasi. Panduan ini telah memberikan gambaran komprehensif tentang BST, yang mencakup struktur, operasi, dan implementasinya dalam JavaScript. Dengan memahami prinsip dan teknik yang dibahas dalam panduan ini, pengembang di seluruh dunia dapat secara efektif menggunakan BST untuk menyelesaikan berbagai masalah dalam pengembangan perangkat lunak. Dari mengelola basis data global hingga mengoptimalkan algoritma pencarian, pengetahuan tentang BST adalah aset yang tak ternilai bagi setiap programmer.

Saat Anda melanjutkan perjalanan Anda dalam ilmu komputer, menjelajahi konsep-konsep lanjutan seperti pohon yang menyeimbangkan diri dan berbagai implementasinya akan lebih meningkatkan pemahaman dan kemampuan Anda. Teruslah berlatih dan bereksperimen dengan berbagai skenario untuk menguasai seni menggunakan Pohon Pencarian Biner secara efektif.