Skip to main content

TreeSet

A self-balancing binary search tree implementation that stores unique values in sorted order.

Purpose

  • Maintains unique elements in sorted order
  • Provides efficient operations for insertion, deletion, and lookup
  • Guarantees O(log n) time complexity for most operations
  • Automatically handles duplicates

Common Methods

const set = new TreeSet();

// Basic Operations
set.add(value) // Add value to set
set.remove(value) // Remove value from set
set.has(value) // Check if value exists
set.clear() // Remove all values
set.getSize() // Get set size
set.isEmpty() // Check if set is empty

// Navigation
set.first() // Get minimum value
set.last() // Get maximum value
set.ceiling(value) // Get smallest value ≥ given value
set.floor(value) // Get largest value ≤ given value

// Conversion
set.toArray() // Convert to sorted array

Example Usage

const set = new TreeSet();
set.add(3);
set.add(1);
set.add(2);

console.log(set.has(2)); // true
console.log(set.ceiling(1.5)); // 2
console.log(set.floor(2.5)); // 2
console.log(set.toArray()); // [1, 2, 3]

TreeSet Implementation

class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
this.height = 1;
}
}

class TreeSet {
constructor(comparator = (a, b) => a - b) {
this.root = null;
this.size = 0;
this.comparator = comparator;
}

#getHeight(node) {
return node ? node.height : 0;
}

#getBalance(node) {
return node ? this.#getHeight(node.left) - this.#getHeight(node.right) : 0;
}

#rightRotate(y) {
const x = y.left;
const T2 = x.right;

x.right = y;
y.left = T2;

y.height = Math.max(this.#getHeight(y.left), this.#getHeight(y.right)) + 1;
x.height = Math.max(this.#getHeight(x.left), this.#getHeight(x.right)) + 1;

return x;
}

#leftRotate(x) {
const y = x.right;
const T2 = y.left;

y.left = x;
x.right = T2;

x.height = Math.max(this.#getHeight(x.left), this.#getHeight(x.right)) + 1;
y.height = Math.max(this.#getHeight(y.left), this.#getHeight(y.right)) + 1;

return y;
}

// Add value to set
add(value) {
const result = !this.has(value);
if (result) {
this.root = this.#add(this.root, value);
this.size++;
}
return result;
}

#add(node, value) {
if (!node) return new TreeNode(value);

const cmp = this.comparator(value, node.value);
if (cmp < 0) {
node.left = this.#add(node.left, value);
} else if (cmp > 0) {
node.right = this.#add(node.right, value);
} else {
return node;
}

node.height = Math.max(this.#getHeight(node.left), this.#getHeight(node.right)) + 1;
const balance = this.#getBalance(node);

// Balance the tree
if (balance > 1 && this.comparator(value, node.left.value) < 0) {
return this.#rightRotate(node);
}
if (balance < -1 && this.comparator(value, node.right.value) > 0) {
return this.#leftRotate(node);
}
if (balance > 1 && this.comparator(value, node.left.value) > 0) {
node.left = this.#leftRotate(node.left);
return this.#rightRotate(node);
}
if (balance < -1 && this.comparator(value, node.right.value) < 0) {
node.right = this.#rightRotate(node.right);
return this.#leftRotate(node);
}

return node;
}

// Remove value from set
remove(value) {
const result = this.has(value);
if (result) {
this.root = this.#remove(this.root, value);
this.size--;
}
return result;
}

#remove(node, value) {
if (!node) return null;

const cmp = this.comparator(value, node.value);
if (cmp < 0) {
node.left = this.#remove(node.left, value);
} else if (cmp > 0) {
node.right = this.#remove(node.right, value);
} else {
if (!node.left || !node.right) {
return node.left || node.right;
}
const temp = this.#getMin(node.right);
node.value = temp.value;
node.right = this.#remove(node.right, temp.value);
}

node.height = Math.max(this.#getHeight(node.left), this.#getHeight(node.right)) + 1;
const balance = this.#getBalance(node);

if (balance > 1 && this.#getBalance(node.left) >= 0) {
return this.#rightRotate(node);
}
if (balance > 1 && this.#getBalance(node.left) < 0) {
node.left = this.#leftRotate(node.left);
return this.#rightRotate(node);
}
if (balance < -1 && this.#getBalance(node.right) <= 0) {
return this.#leftRotate(node);
}
if (balance < -1 && this.#getBalance(node.right) > 0) {
node.right = this.#rightRotate(node.right);
return this.#leftRotate(node);
}

return node;
}

// Check if value exists
has(value) {
return this.#find(this.root, value) !== null;
}

#find(node, value) {
if (!node) return null;
const cmp = this.comparator(value, node.value);
if (cmp === 0) return node;
return cmp < 0 ? this.#find(node.left, value) : this.#find(node.right, value);
}

#getMin(node) {
while (node.left) {
node = node.left;
}
return node;
}

#getMax(node) {
while (node.right) {
node = node.right;
}
return node;
}

// Get smallest value greater than or equal to given value
ceiling(value) {
let node = this.root;
let result = null;

while (node) {
const cmp = this.comparator(value, node.value);
if (cmp === 0) return node.value;
if (cmp < 0) {
result = node.value;
node = node.left;
} else {
node = node.right;
}
}

return result;
}

// Get largest value less than or equal to given value
floor(value) {
let node = this.root;
let result = null;

while (node) {
const cmp = this.comparator(value, node.value);
if (cmp === 0) return node.value;
if (cmp < 0) {
node = node.left;
} else {
result = node.value;
node = node.right;
}
}

return result;
}

// Get smallest value in set
first() {
return this.root ? this.#getMin(this.root).value : undefined;
}

// Get largest value in set
last() {
return this.root ? this.#getMax(this.root).value : undefined;
}

// Get set size
getSize() {
return this.size;
}

// Check if set is empty
isEmpty() {
return this.size === 0;
}

// Clear all values
clear() {
this.root = null;
this.size = 0;
}

// Convert to array (in-order traversal)
toArray() {
const result = [];
this.#inorderTraversal(this.root, value => result.push(value));
return result;
}

#inorderTraversal(node, callback) {
if (node) {
this.#inorderTraversal(node.left, callback);
callback(node.value);
this.#inorderTraversal(node.right, callback);
}
}

// Iterator
*[Symbol.iterator]() {
yield* this.#iterate(this.root);
}

*#iterate(node) {
if (node) {
yield* this.#iterate(node.left);
yield node.value;
yield* this.#iterate(node.right);
}
}
}

// TreeSet usage
const set = new TreeSet();
set.add(5);
set.add(3);
set.add(7);
console.log(set.toArray()); // [3, 5, 7]
console.log(set.ceiling(4)); // 5
console.log(set.floor(6)); // 5
set.remove(5);
console.log(set.toArray()); // [3, 7]

// Custom comparator example
const strSet = new TreeSet((a, b) => a.localeCompare(b));
strSet.add("banana");
strSet.add("apple");
strSet.add("cherry");
console.log(strSet.toArray()); // ["apple", "banana", "cherry"]

When to Use Each

TreeSet:

  • Need unique values in sorted order
  • Large datasets with frequent modifications
  • Need automatic duplicate handling
  • Memory usage is not a primary concern

Performance Comparison

OperationTreeMap/SetSortedList
InsertO(log n)O(n)
DeleteO(log n)O(n)
SearchO(log n)O(log n)
AccessO(log n)O(1)
Min/MaxO(log n)O(1)