Skip to content

Lab โ€” Binary Search Tree in Java

Course: SCIA-120 ยท Introduction to Secure Computing
Frostburg State University โ€” Department of Computer Science & Information Technology


Overview

A Binary Search Tree (BST) is a fundamental data structure in which each node stores a value and has at most two children. The key property that makes a BST efficient is the ordering invariant:

Left subtree values < Node value < Right subtree values

This ordering lets us search, insert, and delete in O(log n) time on average โ€” far better than scanning a plain list. BSTs underlie databases, compilers, and many security tools (e.g., fast IP-lookup tables, certificate stores).

In this lab you will build a complete BST from scratch in Java, compile and run every step inside a Docker container, and observe exactly how the data structure behaves.

        50          โ† root
       /  \
     30    70
    /  \  /  \
   20  40 60  80

Learning Objectives

By the end of this lab you will be able to:

  1. Implement a BST node class and a BST class in Java
  2. Insert values and maintain the BST ordering property
  3. Perform in-order, pre-order, and post-order traversals
  4. Search for a value using the BST property
  5. Find the minimum value and delete a node (including the tricky two-children case)
  6. Run and verify Java programs inside a Docker container without installing Java on your machine

Prerequisites

Requirement Details
Docker Desktop Download โ€” installed and running
Terminal PowerShell (Windows) ยท Terminal (macOS/Linux)
A text editor VS Code, Notepad, nano โ€” any editor works
Basic Java familiarity Variables, methods, and classes

No Java Installation Needed

Everything runs inside the eclipse-temurin:21-jdk-alpine Docker image.
Java 21 LTS is pulled automatically โ€” nothing to install on your machine.


Part 1 โ€” Environment Setup

Step 1.1 โ€” Create a working directory

Open a terminal and create a folder for this lab:

mkdir bst-lab
cd bst-lab

Step 1.2 โ€” Pull the Java Docker image

docker pull eclipse-temurin:21-jdk-alpine

Expected output:

Status: Downloaded newer image for eclipse-temurin:21-jdk-alpine
docker.io/library/eclipse-temurin:21-jdk-alpine

Step 1.3 โ€” Verify Java is available

docker run --rm eclipse-temurin:21-jdk-alpine java --version

Expected output:

openjdk 21.0.10 2026-01-20 LTS
OpenJDK Runtime Environment Temurin-21.0.10+7 (build 21.0.10+7-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.10+7 (build 21.0.10+7-LTS, mixed mode, sharing)

๐Ÿ“ธ Screenshot checkpoint A โ€” terminal showing Java version output


Part 2 โ€” The Node Class

Every BST is made of nodes. Each node holds:

  • data โ€” the integer value stored at this position
  • left โ€” a reference to the left child node (smaller values)
  • right โ€” a reference to the right child node (larger values)

Step 2.1 โ€” Create Node.java

Inside your bst-lab folder, create a file named Node.java with this content:

public class Node {
    int data;
    Node left, right;

    public Node(int data) {
        this.data = data;
        this.left = null;
        this.right = null;
    }
}

What this does:

Line Meaning
int data Stores the integer value at this node
Node left, right Pointers to child nodes (null = no child)
Constructor Sets data, initializes both children to null

Step 2.2 โ€” Compile Node.java via Docker

docker run --rm -v "$(pwd)":/lab -w /lab \
  eclipse-temurin:21-jdk-alpine \
  javac Node.java

Windows PowerShell users

Replace $(pwd) with ${PWD}:

docker run --rm -v "${PWD}:/lab" -w /lab eclipse-temurin:21-jdk-alpine javac Node.java

Expected: No output, exit code 0, and a Node.class file appears in the folder.

ls *.class
Node.class


Part 3 โ€” The BST Class: Insert & Traversals

Step 3.1 โ€” Create BST.java (Part A: Insert + Traversals)

Create BST.java with the following code:

public class BST {
    Node root;

    public BST() {
        this.root = null;
    }

    // โ”€โ”€ INSERT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    public Node insert(Node root, int data) {
        if (root == null) {
            return new Node(data);      // empty spot found โ†’ place node here
        }
        if (data < root.data) {
            root.left = insert(root.left, data);   // go left (smaller)
        } else if (data > root.data) {
            root.right = insert(root.right, data); // go right (larger)
        }
        // duplicate values are silently ignored
        return root;
    }

    // โ”€โ”€ TRAVERSALS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    // In-order: Left โ†’ Root โ†’ Right  โ†’ produces SORTED output
    public void inOrder(Node root) {
        if (root != null) {
            inOrder(root.left);
            System.out.print(root.data + " ");
            inOrder(root.right);
        }
    }

    // Pre-order: Root โ†’ Left โ†’ Right  โ†’ root always printed first
    public void preOrder(Node root) {
        if (root != null) {
            System.out.print(root.data + " ");
            preOrder(root.left);
            preOrder(root.right);
        }
    }

    // Post-order: Left โ†’ Right โ†’ Root  โ†’ root always printed last
    public void postOrder(Node root) {
        if (root != null) {
            postOrder(root.left);
            postOrder(root.right);
            System.out.print(root.data + " ");
        }
    }

    public static void main(String[] args) {
        BST tree = new BST();
        int[] values = {50, 30, 70, 20, 40, 60, 80};

        System.out.println("=== Binary Search Tree Lab ===\n");

        // Insert all values
        System.out.println("Inserting: 50, 30, 70, 20, 40, 60, 80");
        for (int v : values) {
            tree.root = tree.insert(tree.root, v);
        }
        System.out.println("Root node: " + tree.root.data);

        // Print traversals
        System.out.print("\nIn-Order   (sorted): ");
        tree.inOrder(tree.root);

        System.out.print("\nPre-Order  (root first): ");
        tree.preOrder(tree.root);

        System.out.print("\nPost-Order (root last):  ");
        tree.postOrder(tree.root);
        System.out.println();
    }
}

Step 3.2 โ€” Compile and run via Docker

docker run --rm -v "$(pwd)":/lab -w /lab \
  eclipse-temurin:21-jdk-alpine \
  sh -c "javac Node.java BST.java && java BST"

Expected output:

=== Binary Search Tree Lab ===

Inserting: 50, 30, 70, 20, 40, 60, 80
Root node: 50

In-Order   (sorted): 20 30 40 50 60 70 80 
Pre-Order  (root first): 50 30 20 40 70 60 80 
Post-Order (root last):  20 40 30 60 80 70 50 

Why In-Order gives sorted output

In-order traversal visits Left โ†’ Root โ†’ Right at every node. Because BSTs keep smaller values on the left, visiting left-first naturally produces ascending order. This is exactly how a BST can replace a sorted array for fast ordered access.

๐Ÿ“ธ Screenshot checkpoint B โ€” terminal showing all three traversal outputs


Part 4 โ€” Search and Min/Max

Step 4.1 โ€” Add search and findMin methods

Add these two methods to BST.java before the main method:

// โ”€โ”€ SEARCH โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// Returns true if target exists in the tree, false otherwise
public boolean search(Node root, int target) {
    if (root == null) return false;           // fell off the tree โ†’ not found
    if (root.data == target) return true;     // found it!
    if (target < root.data)
        return search(root.left, target);     // go left (target is smaller)
    return search(root.right, target);        // go right (target is larger)
}

// โ”€โ”€ FIND MINIMUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// The leftmost node always holds the smallest value in a BST
public Node findMin(Node root) {
    if (root == null) return null;
    while (root.left != null) root = root.left;
    return root;
}

Step 4.2 โ€” Update main to test search and min

Add these lines inside main, after the traversal block:

// Search
System.out.println("\n--- Search ---");
System.out.println("Search 40: " + tree.search(tree.root, 40));
System.out.println("Search 99: " + tree.search(tree.root, 99));

// Min
System.out.println("\n--- Min / Max ---");
System.out.println("Minimum: " + tree.findMin(tree.root).data);

Step 4.3 โ€” Run and verify

docker run --rm -v "$(pwd)":/lab -w /lab \
  eclipse-temurin:21-jdk-alpine \
  sh -c "javac Node.java BST.java && java BST"

Expected new output (appended to Part 3 output):

--- Search ---
Search 40: true
Search 99: false

--- Min / Max ---
Minimum: 20

Search complexity

Each comparison eliminates half the remaining nodes (like binary search). In a balanced BST, search is O(log n). In the worst case (a degenerate "stick" tree), it degrades to O(n).

๐Ÿ“ธ Screenshot checkpoint C โ€” search and minimum output


Part 5 โ€” Delete a Node

Deletion is the trickiest BST operation because there are three cases:

Case Situation Solution
1 Node has no children (leaf) Simply remove it
2 Node has one child Replace node with its child
3 Node has two children Replace value with the in-order successor (smallest value in right subtree), then delete that successor

Step 5.1 โ€” Add the delete method

Add this to BST.java before main:

// โ”€โ”€ DELETE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public Node delete(Node root, int data) {
    if (root == null) return null;

    if (data < root.data) {
        root.left = delete(root.left, data);       // target is in left subtree
    } else if (data > root.data) {
        root.right = delete(root.right, data);     // target is in right subtree
    } else {
        // โ”€โ”€ Node found โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        // Case 1 & 2: zero or one child
        if (root.left == null)  return root.right;
        if (root.right == null) return root.left;

        // Case 3: two children
        // Find in-order successor (smallest node in right subtree)
        Node minNode = findMin(root.right);
        root.data = minNode.data;                  // copy successor's value here
        root.right = delete(root.right, minNode.data); // remove the successor
    }
    return root;
}

Step 5.2 โ€” Update main to test deletion

Add at the end of main:

// Delete
System.out.println("\n--- Delete node 30 (two children) ---");
tree.root = tree.delete(tree.root, 30);
System.out.print("In-Order after delete: ");
tree.inOrder(tree.root);
System.out.println();

Step 5.3 โ€” Run and verify

docker run --rm -v "$(pwd)":/lab -w /lab \
  eclipse-temurin:21-jdk-alpine \
  sh -c "javac Node.java BST.java && java BST"

Expected complete output:

=== Binary Search Tree Lab ===

Inserting: 50, 30, 70, 20, 40, 60, 80
Root node: 50

In-Order   (sorted): 20 30 40 50 60 70 80 
Pre-Order  (root first): 50 30 20 40 70 60 80 
Post-Order (root last):  20 40 30 60 80 70 50 

--- Search ---
Search 40: true
Search 99: false

--- Min / Max ---
Minimum: 20

--- Delete node 30 (two children) ---
In-Order after delete: 20 40 50 60 70 80 

What happened to node 30?

Node 30 had two children (20 and 40). Its in-order successor is 40 (the smallest value in its right subtree). Java replaced 30's value with 40, then deleted the original 40 node. The tree remains valid โ€” notice 20 is still to the left of 40.

๐Ÿ“ธ Screenshot checkpoint D โ€” complete output including delete result


Part 6 โ€” Challenge Exercises

Create a new file BSTChallenge.java:

public class BSTChallenge {

    // Challenge 1: Count total nodes
    public static int countNodes(Node root) {
        if (root == null) return 0;
        return 1 + countNodes(root.left) + countNodes(root.right);
    }

    // Challenge 2: Height of the BST (longest path from root to leaf)
    public static int height(Node root) {
        if (root == null) return 0;
        return 1 + Math.max(height(root.left), height(root.right));
    }

    // Challenge 3: Validate whether a tree satisfies the BST property
    public static boolean isValidBST(Node root, Integer min, Integer max) {
        if (root == null) return true;
        if (min != null && root.data <= min) return false;
        if (max != null && root.data >= max) return false;
        return isValidBST(root.left, min, root.data)
            && isValidBST(root.right, root.data, max);
    }

    public static void main(String[] args) {
        BST tree = new BST();
        for (int v : new int[]{50, 30, 70, 20, 40, 60, 80}) {
            tree.root = tree.insert(tree.root, v);
        }

        System.out.println("=== BST Challenge Exercises ===\n");
        System.out.println("Total nodes : " + countNodes(tree.root));
        System.out.println("Tree height : " + height(tree.root));
        System.out.println("Valid BST?  : " + isValidBST(tree.root, null, null));
    }
}

Compile and run:

docker run --rm -v "$(pwd)":/lab -w /lab \
  eclipse-temurin:21-jdk-alpine \
  sh -c "javac Node.java BST.java BSTChallenge.java && java BSTChallenge"

Expected output:

=== BST Challenge Exercises ===

Total nodes : 7
Tree height : 3
Valid BST?  : true

๐Ÿ“ธ Screenshot checkpoint E โ€” challenge output


Cleanup

Stop and remove any running containers:

docker ps -a
docker rm $(docker ps -aq) 2>/dev/null || true

Remove the Docker image (optional โ€” saves ~200 MB):

docker rmi eclipse-temurin:21-jdk-alpine

BST Complexity Reference

Operation Average Case Worst Case (degenerate/stick tree)
Insert O(log n) O(n)
Search O(log n) O(n)
Delete O(log n) O(n)
Traversal O(n) O(n)
Space O(n) O(n)

Why worst case is O(n)

If you insert values in sorted order (1, 2, 3, 4 โ€ฆ), the BST degenerates into a linked list โ€” every node only has a right child. Self-balancing trees (AVL, Red-Black) fix this by automatically rebalancing after inserts and deletes.


Lab Assessment

Screenshot Submission Checklist

Submit five screenshots to Canvas:

  • [ ] Screenshot A โ€” docker pull + java --version output
  • [ ] Screenshot B โ€” All three traversal outputs (In-Order, Pre-Order, Post-Order)
  • [ ] Screenshot C โ€” Search and minimum output
  • [ ] Screenshot D โ€” Complete output including delete result
  • [ ] Screenshot E โ€” Challenge exercises output

Reflection Questions

Answer each question in 3โ€“5 sentences (40 points total):

  1. BST Property: Explain in your own words why in-order traversal of a BST always produces a sorted sequence. Use the tree from this lab to illustrate your explanation.

  2. Search Efficiency: A BST with 1,000,000 nodes can find any value in at most ~20 comparisons (logโ‚‚ 1,000,000 โ‰ˆ 20). Why is this so much faster than scanning an unsorted array? What property of the BST makes this possible?

  3. Delete Complexity: When deleting a node with two children, why must we replace it with the in-order successor (smallest value in right subtree) instead of just removing it? What would happen to the BST property if we removed it directly?

  4. Real-World Application: Identify one real-world software system (e.g., a database, file system, programming language runtime, or security tool) that uses a BST or a BST-variant internally. Describe what data it stores and why a BST is an appropriate choice for that use case.

Grading Rubric

Screenshots 40 pts (8 pts each ร— 5) + Challenge Output 20 pts + Reflection Questions 40 pts (10 pts each) = 100 pts