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.
Learning Objectives¶
By the end of this lab you will be able to:
- Implement a BST node class and a BST class in Java
- Insert values and maintain the BST ordering property
- Perform in-order, pre-order, and post-order traversals
- Search for a value using the BST property
- Find the minimum value and delete a node (including the tricky two-children case)
- 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:
Step 1.2 โ Pull the Java Docker image¶
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¶
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 positionleftโ 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¶
Windows PowerShell users
Replace $(pwd) with ${PWD}:
Expected: No output, exit code 0, and a Node.class file appears in the folder.
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 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:
๐ธ Screenshot checkpoint E โ challenge output
Cleanup¶
Stop and remove any running containers:
Remove the Docker image (optional โ saves ~200 MB):
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 --versionoutput - [ ] 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):
-
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.
-
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?
-
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?
-
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