Türkçe

İkili Arama Ağaçlarının (BST) temellerini keşfedin ve JavaScript'te nasıl verimli bir şekilde uygulayacağınızı öğrenin. Bu rehber BST yapısını, işlemlerini ve dünya çapındaki geliştiriciler için pratik örnekleri kapsar.

İkili Arama Ağaçları: JavaScript'te Kapsamlı Bir Uygulama Rehberi

İkili Arama Ağaçları (BST'ler), bilgisayar biliminde verilerin verimli bir şekilde aranması, sıralanması ve alınması için yaygın olarak kullanılan temel bir veri yapısıdır. Hiyerarşik yapıları, birçok işlemde logaritmik zaman karmaşıklığına olanak tanır ve bu da onları büyük veri kümelerini yönetmek için güçlü bir araç haline getirir. Bu rehber, BST'lere kapsamlı bir genel bakış sunar ve dünya çapındaki geliştiricilere yönelik olarak JavaScript'te uygulanmasını gösterir.

İkili Arama Ağaçlarını Anlamak

İkili Arama Ağacı Nedir?

İkili Arama Ağacı, her düğümün en fazla iki çocuğa sahip olduğu, sol çocuk ve sağ çocuk olarak adlandırılan, ağaç tabanlı bir veri yapısıdır. Bir BST'nin temel özelliği, herhangi bir düğüm için:

Bu özellik, bir BST'deki öğelerin her zaman sıralı olmasını sağlayarak verimli arama ve almayı mümkün kılar.

Temel Kavramlar

JavaScript'te Bir İkili Arama Ağacı Uygulamak

Düğüm (Node) Sınıfını Tanımlama

İlk olarak, BST'deki her bir düğümü temsil etmek için bir `Node` sınıfı tanımlarız. Her düğüm, veriyi saklamak için bir `key` ve çocuklarına `left` ve `right` işaretçileri içerecektir.


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

İkili Arama Ağacı (Binary Search Tree) Sınıfını Tanımlama

Ardından, `BinarySearchTree` sınıfını tanımlarız. Bu sınıf, kök düğümü ve ağaca ekleme, arama, silme ve ağacı dolaşma yöntemlerini içerecektir.


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

  // Metotlar buraya eklenecek
}

Ekleme

`insert` metodu, verilen anahtarla yeni bir düğümü BST'ye ekler. Ekleme işlemi, yeni düğümü mevcut düğümlere göre uygun konuma yerleştirerek BST özelliğini korur.


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

Örnek: BST'ye değer ekleme


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

Arama

`search` metodu, verilen anahtara sahip bir düğümün BST'de olup olmadığını kontrol eder. Anahtarı mevcut düğümün anahtarıyla karşılaştırarak ve buna göre sol veya sağ alt ağaca hareket ederek ağacı dolaşır.


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

Örnek: BST'de bir değer arama


console.log(bst.search(9));  // Çıktı: true
console.log(bst.search(2));  // Çıktı: false

Silme

`remove` metodu, verilen anahtara sahip bir düğümü BST'den siler. Bu, düğümü kaldırırken BST özelliğini koruması gerektiğinden en karmaşık işlemdir. Dikkate alınması gereken üç durum vardır:


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 {
    // anahtar, düğümün anahtarına eşit

    // durum 1 - bir yaprak düğüm
    if (node.left === null && node.right === null) {
      node = null;
      return node;
    }

    // durum 2 - düğümün sadece 1 çocuğu var
    if (node.left === null) {
      node = node.right;
      return node;
    } else if (node.right === null) {
      node = node.left;
      return node;
    }

    // durum 3 - düğümün 2 çocuğu var
    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;
}

Örnek: BST'den bir değer silme


bst.remove(7);
console.log(bst.search(7)); // Çıktı: false

Ağaç Dolaşımı

Ağaç dolaşımı, ağaçtaki her düğümü belirli bir sırayla ziyaret etmeyi içerir. Birkaç yaygın dolaşım yöntemi vardır:


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

Örnek: BST'yi dolaşma


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

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

Minimum ve Maksimum Değerler

Sıralı yapısı sayesinde bir BST'de minimum ve maksimum değerleri bulmak oldukça basittir.


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

Örnek: Minimum ve maksimum değerleri bulma


console.log(bst.min().key); // Çıktı: 3
console.log(bst.max().key); // Çıktı: 25

İkili Arama Ağaçlarının Pratik Uygulamaları

İkili Arama Ağaçları, aşağıdakiler de dahil olmak üzere çeşitli uygulamalarda kullanılır:

Performans Değerlendirmeleri

Bir BST'nin performansı yapısına bağlıdır. En iyi durumda, dengeli bir BST, ekleme, arama ve silme işlemleri için logaritmik zaman karmaşıklığına izin verir. Ancak, en kötü durumda (örneğin, eğik bir ağaç), zaman karmaşıklığı doğrusal zamana düşebilir.

Dengeli ve Dengesiz Ağaçlar

Dengeli bir BST, her düğümün sol ve sağ alt ağaçlarının yüksekliğinin en fazla bir fark ettiği bir ağaçtır. AVL ağaçları ve Kırmızı-Siyah ağaçlar gibi kendi kendini dengeleyen algoritmalar, ağacın dengeli kalmasını sağlayarak tutarlı performans sunar. Farklı bölgeler, sunucudaki yüke bağlı olarak farklı optimizasyon seviyeleri gerektirebilir; dengeleme, yüksek küresel kullanım altında performansı korumaya yardımcı olur.

Zaman Karmaşıklığı

İleri Düzey BST Kavramları

Kendi Kendini Dengeleyen Ağaçlar

Kendi kendini dengeleyen ağaçlar, dengeyi korumak için yapılarını otomatik olarak ayarlayan BST'lerdir. Bu, ağacın yüksekliğinin logaritmik kalmasını sağlayarak tüm işlemler için tutarlı performans sunar. Yaygın kendi kendini dengeleyen ağaçlar arasında AVL ağaçları ve Kırmızı-Siyah ağaçlar bulunur.

AVL Ağaçları

AVL ağaçları, herhangi bir düğümün sol ve sağ alt ağaçları arasındaki yükseklik farkının en fazla bir olmasını sağlayarak dengeyi korur. Bu denge bozulduğunda, dengeyi yeniden sağlamak için döndürmeler gerçekleştirilir.

Kırmızı-Siyah Ağaçlar

Kırmızı-Siyah ağaçlar dengeyi korumak için renk özelliklerini (kırmızı veya siyah) kullanır. AVL ağaçlarından daha karmaşıktırlar ancak belirli senaryolarda daha iyi performans sunarlar.

JavaScript Kod Örneği: Tam İkili Arama Ağacı Uygulaması


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 {
      // anahtar, düğümün anahtarına eşit

      // durum 1 - bir yaprak düğüm
      if (node.left === null && node.right === null) {
        node = null;
        return node;
      }

      // durum 2 - düğümün sadece 1 çocuğu var
      if (node.left === null) {
        node = node.right;
        return node;
      } else if (node.right === null) {
        node = node.left;
        return node;
      }

      // durum 3 - düğümün 2 çocuğu var
      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);
    }
  }
}

// Örnek Kullanım
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("Sıralı dolaşım:");
bst.inOrderTraverse(printNode);

console.log("Öncelikli dolaşım:");
bst.preOrderTraverse(printNode);

console.log("Sondalı dolaşım:");
bst.postOrderTraverse(printNode);

console.log("Minimum değer:", bst.min().key);
console.log("Maksimum değer:", bst.max().key);

console.log("9 için arama:", bst.search(9));
console.log("2 için arama:", bst.search(2));

bst.remove(7);
console.log("Silme işleminden sonra 7 için arama:", bst.search(7));

Sonuç

İkili Arama Ağaçları, sayısız uygulaması olan güçlü ve çok yönlü bir veri yapısıdır. Bu rehber, BST'lere yapıları, işlemleri ve JavaScript'te uygulanmaları dahil olmak üzere kapsamlı bir genel bakış sunmuştur. Bu rehberde tartışılan ilke ve teknikleri anlayarak, dünya çapındaki geliştiriciler, yazılım geliştirmede çok çeşitli sorunları çözmek için BST'leri etkili bir şekilde kullanabilirler. Küresel veritabanlarını yönetmekten arama algoritmalarını optimize etmeye kadar, BST bilgisi her programcı için paha biçilmez bir varlıktır.

Bilgisayar bilimi yolculuğunuza devam ederken, kendi kendini dengeleyen ağaçlar gibi ileri düzey kavramları ve bunların çeşitli uygulamalarını keşfetmek, anlayışınızı ve yeteneklerinizi daha da artıracaktır. İkili Arama Ağaçlarını etkili bir şekilde kullanma sanatında ustalaşmak için farklı senaryolarla pratik yapmaya ve denemeler yapmaya devam edin.