binary_search_tree


Data Structure: Binary Search Tree

A Binary Search Tree (BST) is a binary tree data structure with the key property that for each node, all elements in its left subtree are less than the node's key, and all elements in its right subtree are greater than the node's key. This property makes BSTs efficient for searching, inserting, and deleting elements.

Key Characteristics

Example 1: Basic Binary Search Tree Implementation

This example demonstrates a basic implementation of a Binary Search Tree with integer values.

#include <iostream>
#include <algorithm>

class BinarySearchTree {
private:
    struct Node {
        int data;
        Node* left;
        Node* right;
        Node(int val) : data(val), left(nullptr), right(nullptr) {}
    };

    Node* root;

    Node* insert(Node* node, int value) {
        if (node == nullptr) {
            return new Node(value);
        }

        if (value < node->data) {
            node->left = insert(node->left, value);
        } else if (value > node->data) {
            node->right = insert(node->right, value);
        }

        return node;
    }

    Node* search(Node* node, int value) const {
        if (node == nullptr || node->data == value) {
            return node;
        }

        if (value < node->data) {
            return search(node->left, value);
        }
        return search(node->right, value);
    }

    void inorderTraversal(Node* node) const {
        if (node) {
            inorderTraversal(node->left);
            std::cout << node->data << " ";
            inorderTraversal(node->right);
        }
    }

    Node* findMin(Node* node) const {
        while (node->left != nullptr) {
            node = node->left;
        }
        return node;
    }

    Node* remove(Node* node, int value) {
        if (node == nullptr) {
            return node;
        }

        if (value < node->data) {
            node->left = remove(node->left, value);
        } else if (value > node->data) {
            node->right = remove(node->right, value);
        } else {
            // Node with only one child or no child
            if (node->left == nullptr) {
                Node* temp = node->right;
                delete node;
                return temp;
            } else if (node->right == nullptr) {
                Node* temp = node->left;
                delete node;
                return temp;
            }

            // Node with two children
            Node* temp = findMin(node->right);
            node->data = temp->data;
            node->right = remove(node->right, temp->data);
        }
        return node;
    }

    void destroyTree(Node* node) {
        if (node) {
            destroyTree(node->left);
            destroyTree(node->right);
            delete node;
        }
    }

    int getHeight(Node* node) const {
        if (node == nullptr) {
            return 0;
        }
        return 1 + std::max(getHeight(node->left), getHeight(node->right));
    }

public:
    BinarySearchTree() : root(nullptr) {}
    ~BinarySearchTree() { destroyTree(root); }

    void insert(int value) {
        root = insert(root, value);
    }

    bool search(int value) const {
        return search(root, value) != nullptr;
    }

    void inorder() const {
        inorderTraversal(root);
        std::cout << std::endl;
    }

    void remove(int value) {
        root = remove(root, value);
    }

    int height() const {
        return getHeight(root);
    }
};

int main() {
    BinarySearchTree bst;

    bst.insert(50);
    bst.insert(30);
    bst.insert(70);
    bst.insert(20);
    bst.insert(40);
    bst.insert(60);
    bst.insert(80);

    std::cout << "Inorder traversal: ";
    bst.inorder();

    std::cout << "Height of the tree: " << bst.height() << std::endl;

    int searchValue = 40;
    std::cout << "Searching for " << searchValue << ": " 
              << (bst.search(searchValue) ? "Found" : "Not Found") << std::endl;

    int removeValue = 30;
    std::cout << "Removing " << removeValue << std::endl;
    bst.remove(removeValue);

    std::cout << "Inorder traversal after removal: ";
    bst.inorder();

    return 0;
}

Explanation

Example 2: BST for String Keys and Associated Values

This example shows how to implement a BST with string keys and associated integer values, demonstrating its use as a simple key-value store.

#include <iostream>
#include <string>
#include <algorithm>

class BST_KeyValue {
private:
    struct Node {
        std::string key;
        int value;
        Node* left;
        Node* right;
        Node(const std::string& k, int v) : key(k), value(v), left(nullptr), right(nullptr) {}
    };

    Node* root;

    Node* insert(Node* node, const std::string& key, int value) {
        if (node == nullptr) {
            return new Node(key, value);
        }

        if (key < node->key) {
            node->left = insert(node->left, key, value);
        } else if (key > node->key) {
            node->right = insert(node->right, key, value);
        } else {
            // Update value if key already exists
            node->value = value;
        }

        return node;
    }

    Node* search(Node* node, const std::string& key) const {
        if (node == nullptr || node->key == key) {
            return node;
        }

        if (key < node->key) {
            return search(node->left, key);
        }
        return search(node->right, key);
    }

    void inorderTraversal(Node* node) const {
        if (node) {
            inorderTraversal(node->left);
            std::cout << node->key << ": " << node->value << std::endl;
            inorderTraversal(node->right);
        }
    }

    void destroyTree(Node* node) {
        if (node) {
            destroyTree(node->left);
            destroyTree(node->right);
            delete node;
        }
    }

public:
    BST_KeyValue() : root(nullptr) {}
    ~BST_KeyValue() { destroyTree(root); }

    void insert(const std::string& key, int value) {
        root = insert(root, key, value);
    }

    bool search(const std::string& key, int& value) const {
        Node* result = search(root, key);
        if (result) {
            value = result->value;
            return true;
        }
        return false;
    }

    void printInOrder() const {
        inorderTraversal(root);
    }
};

int main() {
    BST_KeyValue bst;

    bst.insert("apple", 5);
    bst.insert("banana", 7);
    bst.insert("cherry", 3);
    bst.insert("date", 1);
    bst.insert("elderberry", 2);

    std::cout << "Key-Value pairs in order:" << std::endl;
    bst.printInOrder();

    std::string searchKey = "banana";
    int value;
    if (bst.search(searchKey, value)) {
        std::cout << "Value for key '" << searchKey << "': " << value << std::endl;
    } else {
        std::cout << "Key '" << searchKey << "' not found." << std::endl;
    }

    // Update a value
    bst.insert("banana", 10);

    std::cout << "\nAfter updating 'banana':" << std::endl;
    bst.printInOrder();

    return 0;
}

Explanation

Additional Considerations

  1. Balancing: Standard BSTs can become unbalanced, leading to worst-case O(n) time complexity for operations. Self-balancing variants like AVL trees or Red-Black trees address this issue.

  2. Duplicate Keys: The handling of duplicate keys should be clearly defined. In this implementation, we update the value if the key already exists.

  3. Traversal Orders: Besides inorder traversal, preorder and postorder traversals can be useful for different applications.

  4. Memory Management: Proper memory management is crucial, especially when implementing destructors and copy constructors.

  5. Applications: BSTs are widely used in many applications, including:

  6. Implementing associative arrays
  7. Sorting algorithms
  8. Priority queues

Summary

Binary Search Trees are efficient data structures for storing sorted data and performing quick search, insertion, and deletion operations. They are particularly useful in applications that require frequent lookups and maintenance of ordered data.

In this guide, we explored two implementations of Binary Search Trees:

  1. A basic BST for integer values, demonstrating the fundamental operations like insertion, deletion, and searching.
  2. A BST implementation for key-value pairs, showing how BSTs can be used as simple associative arrays or dictionaries.

These examples highlight the versatility of BSTs in handling different types of data and their efficiency in maintaining sorted information. The first example provides a foundation for understanding BST operations, while the second demonstrates a practical application in storing and retrieving key-value pairs, which is a common requirement in many software systems.

BSTs are particularly relevant to your interests in scientific computing and artificial intelligence. In scientific computing, they can be used for efficient data organization and quick lookups. In AI and machine learning, decision trees (which are a type of BST) are used in various algorithms for classification and regression tasks.

For further exploration, you might consider implementing more advanced tree structures like AVL trees or Red-Black trees, which maintain balance automatically. You could also explore using BSTs in specific applications related to your research or teaching in scientific computing or AI, such as implementing efficient database indexing or decision-making algorithms.

Previous Page | Course Schedule | Course Content