Skip to main content

TreeMap

A self-balancing binary search tree implementation that stores key-value pairs in sorted order by keys.

Purpose

  • Maintains key-value pairs in sorted order
  • Provides efficient operations for insertion, deletion, and lookup
  • Guarantees O(log n) time complexity for most operations

Common Methods

const map = new TreeMap();

// Basic Operations
map.put(key, value) // Add or update key-value pair
map.get(key) // Get value by key
map.remove(key) // Remove key-value pair
map.has(key) // Check if key exists
map.clear() // Remove all entries
map.getSize() // Get number of entries
map.isEmpty() // Check if map is empty

// Navigation
map.firstKey() // Get minimum key
map.lastKey() // Get maximum key
map.ceilingKey(key) // Get smallest key ≥ given key
map.floorKey(key) // Get largest key ≤ given key

// Collection Views
map.keys() // Get all keys in sorted order
map.values() // Get all values in key order
map.entries() // Get all key-value pairs in key order

Example Usage

const map = new TreeMap();
map.put("A", 1);
map.put("C", 3);
map.put("B", 2);

console.log(map.get("B")); // 2
console.log(map.ceilingKey("B")); // "B"
console.log(map.floorKey("B")); // "B"
console.log(map.keys()); // ["A", "B", "C"]

TreeMap implementation

The TreeMap implementation provides a wide range of functionalities for storing and navigating key-value pairs.

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


class TreeMap {
constructor() {
this.root = null;
this.size = 0;
}

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

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

// Right rotation
#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;
}

// Left rotation
#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;
}

// Insert a key-value pair
put(key, value) {
this.root = this.#put(this.root, key, value);
return this;
}

#put(node, key, value) {
if (!node) {
this.size++;
return new TreeNode(key, value);
}

if (key < node.key) {
node.left = this.#put(node.left, key, value);
} else if (key > node.key) {
node.right = this.#put(node.right, key, value);
} else {
node.value = value; // Update value if key exists
return node;
}

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

const balance = this.#getBalance(node);

// Left Left Case
if (balance > 1 && key < node.left.key) {
return this.#rightRotate(node);
}

// Right Right Case
if (balance < -1 && key > node.right.key) {
return this.#leftRotate(node);
}

// Left Right Case
if (balance > 1 && key > node.left.key) {
node.left = this.#leftRotate(node.left);
return this.#rightRotate(node);
}

// Right Left Case
if (balance < -1 && key < node.right.key) {
node.right = this.#rightRotate(node.right);
return this.#leftRotate(node);
}

return node;
}

// Get value by key
get(key) {
const node = this.#get(this.root, key);
return node ? node.value : undefined;
}

#get(node, key) {
if (!node) return null;
if (key === node.key) return node;
return key < node.key ? this.#get(node.left, key) : this.#get(node.right, key);
}

// Remove key-value pair
remove(key) {
const hasKey = this.has(key);
if (hasKey) {
this.root = this.#remove(this.root, key);
this.size--;
}
return hasKey;
}

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

if (key < node.key) {
node.left = this.#remove(node.left, key);
} else if (key > node.key) {
node.right = this.#remove(node.right, key);
} else {
if (!node.left || !node.right) {
return node.left || node.right;
}

const successor = this.#getMin(node.right);
node.key = successor.key;
node.value = successor.value;
node.right = this.#remove(node.right, successor.key);
}

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

const balance = this.#getBalance(node);

// Rebalance after removal
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 key exists
has(key) {
return this.#get(this.root, key) !== null;
}

// Get minimum key
firstKey() {
if (!this.root) return undefined;
return this.#getMin(this.root).key;
}

// Get maximum key
lastKey() {
if (!this.root) return undefined;
return this.#getMax(this.root).key;
}

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

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

// Get first key greater than or equal to given key
ceilingKey(key) {
let node = this.root;
let ceiling = null;

while (node) {
if (key === node.key) {
return key;
}
if (key < node.key) {
ceiling = node.key;
node = node.left;
} else {
node = node.right;
}
}

return ceiling;
}

// Get last key less than or equal to given key
floorKey(key) {
let node = this.root;
let floor = null;

while (node) {
if (key === node.key) {
return key;
}
if (key < node.key) {
node = node.left;
} else {
floor = node.key;
node = node.right;
}
}

return floor;
}

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

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

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

// Get all keys in order
keys() {
const result = [];
this.#inorderTraversal(this.root, node => result.push(node.key));
return result;
}

// Get all values in order of keys
values() {
const result = [];
this.#inorderTraversal(this.root, node => result.push(node.value));
return result;
}

// Get all entries in order
entries() {
const result = [];
this.#inorderTraversal(this.root, node => result.push([node.key, node.value]));
return result;
}

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

const map = new TreeMap();

// Insert key-value pairs
map.put(5, "five");
map.put(3, "three");
map.put(7, "seven");

console.log(map.get(3)); // "three"
console.log(map.has(7)); // true
console.log(map.firstKey()); // 3
console.log(map.lastKey()); // 7
console.log(map.ceilingKey(4)); // 5
console.log(map.floorKey(6)); // 5
console.log(map.keys()); // [3, 5, 7]
console.log(map.getSize()); // 3

map.remove(5);
console.log(map.has(5)); // false

When to Use Each

TreeMap:

  • Need key-value associations in sorted order
  • Large datasets with frequent modifications
  • Need efficient range queries
  • 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)