diff --git a/README.md b/README.md index c9f19e8..05197d7 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ - [SegmentTree](#segment-tree) - [DisjointSet(UnionFind)](#disjoint-set) - [AVL Tree](#avl-tree) + - [RedBlack Tree](#redblack-tree) 4. [License](#license) ## [Installation](#installation) @@ -777,6 +778,115 @@ The AVL tree maintains the following invariants: - Spatial partitioning - Scene graph management --- +### [Red-Black Tree](#red-black-tree) +A Red-Black Tree is a self-balancing binary search tree where each node is colored either red or black, following specific rules that maintain balance. This ensures O(log n) time complexity for insertions, deletions, and lookups. + +#### Type `RedBlackTree[T any]` +- **Constructor:** +```go +func New[T any](compare func(a, b T) int) *RedBlackTree[T] +``` +- *`compare`*: A comparison function that returns: + - -1 if a < b + - 0 if a == b + - 1 if a > b + +- **Methods:** + - `Insert(value T)`: Adds a value to the tree while maintaining Red-Black properties + - `Delete(value T) bool`: Removes a value from the tree while maintaining Red-Black properties + - `Search(value T) bool`: Checks if a value exists in the tree + - `InOrderTraversal(fn func(T))`: Visits all nodes in ascending order + - `Clear()`: Removes all elements from the tree + - `Size() int`: Returns the number of nodes in the tree + - `IsEmpty() bool`: Checks if the tree is empty + - `Height() int`: Returns the height of the tree + +#### Example Usage: +```go +package main + +import ( + "fmt" + "github.com/idsulik/go-collections/v2/rbtree" +) + +// Compare function for integers +func compareInts(a, b int) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 +} + +func main() { + // Create a new Red-Black tree + tree := rbtree.New[int](compareInts) + + // Insert values + values := []int{50, 30, 70, 20, 40, 60, 80} + for _, v := range values { + tree.Insert(v) + } + + // Search for values + fmt.Println(tree.Search(30)) // true + fmt.Println(tree.Search(45)) // false + + // Traverse the tree in order + tree.InOrderTraversal(func(value int) { + fmt.Printf("%d ", value) + }) // Output: 20 30 40 50 60 70 80 + + // Delete a value + tree.Delete(30) + + // Check size and height + fmt.Printf("\nSize: %d, Height: %d\n", tree.Size(), tree.Height()) +} +``` + +#### Performance Characteristics: +| Operation | Average Case | Worst Case | +|-----------|--------------|------------| +| Space | O(n) | O(n) | +| Search | O(log n) | O(log n) | +| Insert | O(log n) | O(log n) | +| Delete | O(log n) | O(log n) | + +Where n is the number of nodes in the tree. + +#### Balance Property: +The Red-Black tree maintains the following invariants: +1. Every node is either red or black +2. The root is always black +3. All leaves (NIL nodes) are black +4. If a node is red, both its children are black +5. Every path from root to leaf contains the same number of black nodes + +#### Use Cases: +1. **Database Systems:** + - Index structures implementation + - Multi-version concurrency control + +2. **Memory Management:** + - Virtual memory segment trees + - Memory allocation tracking + +3. **File Systems:** + - Directory organization + - File system journaling + +4. **Process Scheduling:** + - Task prioritization + - Real-time scheduling + +5. **Programming Languages:** + - Symbol table implementation + - Garbage collection algorithms +--- ## Performance Comparison diff --git a/rbtree/rbtree.go b/rbtree/rbtree.go new file mode 100644 index 0000000..c88fdd3 --- /dev/null +++ b/rbtree/rbtree.go @@ -0,0 +1,386 @@ +// Package rbtree implements a Red-Black Tree data structure +package rbtree + +// color represents the color of a node in the Red-Black tree +type color bool + +const ( + Black color = true + Red color = false +) + +// Node represents a node in the Red-Black tree +type node[T any] struct { + value T + color color + left *node[T] + right *node[T] + parent *node[T] +} + +// RedBlackTree represents a Red-Black tree data structure +type RedBlackTree[T any] struct { + root *node[T] + size int + compare func(a, b T) int +} + +// New creates a new Red-Black tree +func New[T any](compare func(a, b T) int) *RedBlackTree[T] { + return &RedBlackTree[T]{ + compare: compare, + } +} + +// Size returns the number of nodes in the tree +func (t *RedBlackTree[T]) Size() int { + return t.size +} + +// IsEmpty returns true if the tree is empty +func (t *RedBlackTree[T]) IsEmpty() bool { + return t.size == 0 +} + +// Clear removes all nodes from the tree +func (t *RedBlackTree[T]) Clear() { + t.root = nil + t.size = 0 +} + +// Insert adds a value to the tree if it doesn't already exist +func (t *RedBlackTree[T]) Insert(value T) { + // First check if value already exists + if t.Search(value) { + return // Don't insert duplicates + } + + newNode := &node[T]{ + value: value, + color: Red, + } + + if t.root == nil { + t.root = newNode + t.size++ + t.insertFixup(newNode) + return + } + + current := t.root + var parent *node[T] + + for current != nil { + parent = current + cmp := t.compare(value, current.value) + if cmp == 0 { + return // Double-check for duplicates + } else if cmp < 0 { + current = current.left + } else { + current = current.right + } + } + + newNode.parent = parent + if t.compare(value, parent.value) < 0 { + parent.left = newNode + } else { + parent.right = newNode + } + + t.size++ + t.insertFixup(newNode) +} + +// insertFixup maintains Red-Black properties after insertion +func (t *RedBlackTree[T]) insertFixup(n *node[T]) { + if n.parent == nil { + n.color = Black + return + } + + for n.parent != nil && n.parent.color == Red { + if n.parent == n.parent.parent.left { + uncle := n.parent.parent.right + if uncle != nil && uncle.color == Red { + n.parent.color = Black + uncle.color = Black + n.parent.parent.color = Red + n = n.parent.parent + } else { + if n == n.parent.right { + n = n.parent + t.rotateLeft(n) + } + n.parent.color = Black + n.parent.parent.color = Red + t.rotateRight(n.parent.parent) + } + } else { + uncle := n.parent.parent.left + if uncle != nil && uncle.color == Red { + n.parent.color = Black + uncle.color = Black + n.parent.parent.color = Red + n = n.parent.parent + } else { + if n == n.parent.left { + n = n.parent + t.rotateRight(n) + } + n.parent.color = Black + n.parent.parent.color = Red + t.rotateLeft(n.parent.parent) + } + } + if n == t.root { + break + } + } + t.root.color = Black +} + +// rotateLeft performs a left rotation around the given node +func (t *RedBlackTree[T]) rotateLeft(x *node[T]) { + y := x.right + x.right = y.left + if y.left != nil { + y.left.parent = x + } + y.parent = x.parent + if x.parent == nil { + t.root = y + } else if x == x.parent.left { + x.parent.left = y + } else { + x.parent.right = y + } + y.left = x + x.parent = y +} + +// rotateRight performs a right rotation around the given node +func (t *RedBlackTree[T]) rotateRight(y *node[T]) { + x := y.left + y.left = x.right + if x.right != nil { + x.right.parent = y + } + x.parent = y.parent + if y.parent == nil { + t.root = x + } else if y == y.parent.right { + y.parent.right = x + } else { + y.parent.left = x + } + x.right = y + y.parent = x +} + +// Search checks if a value exists in the tree +func (t *RedBlackTree[T]) Search(value T) bool { + current := t.root + for current != nil { + cmp := t.compare(value, current.value) + if cmp == 0 { + return true + } else if cmp < 0 { + current = current.left + } else { + current = current.right + } + } + return false +} + +// InOrderTraversal visits all nodes in ascending order +func (t *RedBlackTree[T]) InOrderTraversal(fn func(T)) { + var inorder func(*node[T]) + inorder = func(n *node[T]) { + if n == nil { + return + } + inorder(n.left) + fn(n.value) + inorder(n.right) + } + inorder(t.root) +} + +// Delete removes a value from the tree +func (t *RedBlackTree[T]) Delete(value T) bool { + node := t.findNode(value) + if node == nil { + return false + } + + t.deleteNode(node) + t.size-- + return true +} + +// findNode finds the node containing the given value +func (t *RedBlackTree[T]) findNode(value T) *node[T] { + current := t.root + for current != nil { + cmp := t.compare(value, current.value) + if cmp == 0 { + return current + } else if cmp < 0 { + current = current.left + } else { + current = current.right + } + } + return nil +} + +// deleteNode removes the given node from the tree +func (t *RedBlackTree[T]) deleteNode(n *node[T]) { + var x, y *node[T] + + if n.left == nil || n.right == nil { + y = n + } else { + y = t.successor(n) + } + + if y.left != nil { + x = y.left + } else { + x = y.right + } + + if x != nil { + x.parent = y.parent + } + + if y.parent == nil { + t.root = x + } else if y == y.parent.left { + y.parent.left = x + } else { + y.parent.right = x + } + + if y != n { + n.value = y.value + } + + if y.color == Black { + t.deleteFixup(x, y.parent) + } +} + +// successor returns the next larger node +func (t *RedBlackTree[T]) successor(n *node[T]) *node[T] { + if n.right != nil { + return t.minimum(n.right) + } + y := n.parent + for y != nil && n == y.right { + n = y + y = y.parent + } + return y +} + +// minimum returns the node with the smallest value in the subtree +func (t *RedBlackTree[T]) minimum(n *node[T]) *node[T] { + current := n + for current.left != nil { + current = current.left + } + return current +} + +// deleteFixup maintains Red-Black properties after deletion +func (t *RedBlackTree[T]) deleteFixup(n *node[T], parent *node[T]) { + for n != t.root && (n == nil || n.color == Black) { + if n == parent.left { + w := parent.right + if w.color == Red { + w.color = Black + parent.color = Red + t.rotateLeft(parent) + w = parent.right + } + if (w.left == nil || w.left.color == Black) && + (w.right == nil || w.right.color == Black) { + w.color = Red + n = parent + parent = n.parent + } else { + if w.right == nil || w.right.color == Black { + if w.left != nil { + w.left.color = Black + } + w.color = Red + t.rotateRight(w) + w = parent.right + } + w.color = parent.color + parent.color = Black + if w.right != nil { + w.right.color = Black + } + t.rotateLeft(parent) + n = t.root + break + } + } else { + w := parent.left + if w.color == Red { + w.color = Black + parent.color = Red + t.rotateRight(parent) + w = parent.left + } + if (w.right == nil || w.right.color == Black) && + (w.left == nil || w.left.color == Black) { + w.color = Red + n = parent + parent = n.parent + } else { + if w.left == nil || w.left.color == Black { + if w.right != nil { + w.right.color = Black + } + w.color = Red + t.rotateLeft(w) + w = parent.left + } + w.color = parent.color + parent.color = Black + if w.left != nil { + w.left.color = Black + } + t.rotateRight(parent) + n = t.root + break + } + } + } + if n != nil { + n.color = Black + } +} + +// Height returns the height of the tree +func (t *RedBlackTree[T]) Height() int { + var height func(*node[T]) int + height = func(n *node[T]) int { + if n == nil { + return -1 + } + leftHeight := height(n.left) + rightHeight := height(n.right) + if leftHeight > rightHeight { + return leftHeight + 1 + } + return rightHeight + 1 + } + return height(t.root) +} diff --git a/rbtree/rbtree_test.go b/rbtree/rbtree_test.go new file mode 100644 index 0000000..526b299 --- /dev/null +++ b/rbtree/rbtree_test.go @@ -0,0 +1,415 @@ +package rbtree + +import ( + "fmt" + "math/rand" + "testing" + "time" +) + +// compareInts is a helper function for comparing integers +func compareInts(a, b int) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 +} + +// verifyRedBlackProperties checks if the tree maintains Red-Black properties +func verifyRedBlackProperties[T any](t *RedBlackTree[T]) bool { + if t.root == nil { + return true + } + + // Property 1: Root must be black + if t.root.color != Black { + return false + } + + // Check other properties recursively + blackHeight, valid := verifyNodeProperties(t.root, nil) + return valid && blackHeight >= 0 +} + +// verifyNodeProperties checks Red-Black properties for a node and its subtrees +func verifyNodeProperties[T any](n *node[T], parent *node[T]) (int, bool) { + if n == nil { + return 0, true // Nil nodes are considered black + } + + // Check parent pointer + if n.parent != parent { + return -1, false + } + + // Property 2: No red node has a red child + if n.color == Red && parent != nil && parent.color == Red { + return -1, false + } + + // Check left subtree + leftBlackHeight, leftValid := verifyNodeProperties(n.left, n) + if !leftValid { + return -1, false + } + + // Check right subtree + rightBlackHeight, rightValid := verifyNodeProperties(n.right, n) + if !rightValid { + return -1, false + } + + // Property 5: All paths must have same number of black nodes + if leftBlackHeight != rightBlackHeight { + return -1, false + } + + // Calculate black height + blackHeight := leftBlackHeight + if n.color == Black { + blackHeight++ + } + + return blackHeight, true +} + +func TestNewRedBlackTree(t *testing.T) { + tree := New[int](compareInts) + if tree == nil { + t.Error("Expected non-nil tree") + } + if !tree.IsEmpty() { + t.Error("Expected empty tree") + } + if tree.Size() != 0 { + t.Errorf("Expected size 0, got %d", tree.Size()) + } +} + +func TestRedBlackTree_Insert(t *testing.T) { + tests := []struct { + name string + values []int + }{ + {"Empty", []int{}}, + {"Single Value", []int{1}}, + {"Ascending Order", []int{1, 2, 3, 4, 5}}, + {"Descending Order", []int{5, 4, 3, 2, 1}}, + {"Random Order", []int{3, 1, 4, 5, 2}}, + {"Duplicates", []int{1, 2, 2, 3, 1}}, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + tree := New[int](compareInts) + uniqueValues := make(map[int]bool) + + for _, v := range tt.values { + tree.Insert(v) + uniqueValues[v] = true + + // Verify Red-Black properties after each insertion + if !verifyRedBlackProperties(tree) { + t.Error("Red-Black properties violated after insertion") + } + } + + // Check size + expectedSize := len(uniqueValues) + if tree.Size() != expectedSize { + t.Errorf("Expected size %d, got %d", expectedSize, tree.Size()) + } + + // Verify all values are present + for v := range uniqueValues { + if !tree.Search(v) { + t.Errorf("Value %d not found after insertion", v) + } + } + }, + ) + } +} + +func TestRedBlackTree_Delete(t *testing.T) { + tests := []struct { + name string + insertOrder []int + deleteOrder []int + expectedSize int + }{ + { + name: "Delete Root", + insertOrder: []int{1}, + deleteOrder: []int{1}, + expectedSize: 0, + }, + { + name: "Delete Leaf", + insertOrder: []int{2, 1, 3}, + deleteOrder: []int{1}, + expectedSize: 2, + }, + { + name: "Delete Internal Node", + insertOrder: []int{2, 1, 3, 4}, + deleteOrder: []int{3}, + expectedSize: 3, + }, + { + name: "Delete All", + insertOrder: []int{1, 2, 3, 4, 5}, + deleteOrder: []int{1, 2, 3, 4, 5}, + expectedSize: 0, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + tree := New[int](compareInts) + + // Insert values + for _, v := range tt.insertOrder { + tree.Insert(v) + } + + // Delete values + for _, v := range tt.deleteOrder { + if !tree.Delete(v) { + t.Errorf("Failed to delete value %d", v) + } + + // Verify Red-Black properties after each deletion + if !verifyRedBlackProperties(tree) { + t.Error("Red-Black properties violated after deletion") + } + } + + // Check final size + if tree.Size() != tt.expectedSize { + t.Errorf("Expected size %d, got %d", tt.expectedSize, tree.Size()) + } + + // Verify deleted values are gone + for _, v := range tt.deleteOrder { + if tree.Search(v) { + t.Errorf("Value %d still present after deletion", v) + } + } + }, + ) + } +} + +func TestRedBlackTree_InOrderTraversal(t *testing.T) { + tests := []struct { + name string + values []int + expected []int + }{ + { + name: "Empty Tree", + values: []int{}, + expected: []int{}, + }, + { + name: "Single Node", + values: []int{1}, + expected: []int{1}, + }, + { + name: "Multiple Nodes", + values: []int{5, 3, 7, 1, 9, 4, 6, 8, 2}, + expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + tree := New[int](compareInts) + for _, v := range tt.values { + tree.Insert(v) + } + + var result []int + tree.InOrderTraversal( + func(v int) { + result = append(result, v) + }, + ) + + if len(result) != len(tt.expected) { + t.Errorf("Expected length %d, got %d", len(tt.expected), len(result)) + } + + for i := range result { + if result[i] != tt.expected[i] { + t.Errorf("Expected %v at index %d, got %v", tt.expected[i], i, result[i]) + } + } + }, + ) + } +} + +func TestRedBlackTree_Height(t *testing.T) { + tests := []struct { + name string + values []int + expectedHeight int + }{ + {"Empty Tree", []int{}, -1}, + {"Single Node", []int{1}, 0}, + {"Two Nodes", []int{1, 2}, 1}, + {"Three Nodes", []int{2, 1, 3}, 1}, + {"Multiple Nodes", []int{5, 3, 7, 1, 9, 4, 6, 8, 2}, 3}, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + tree := New[int](compareInts) + for _, v := range tt.values { + tree.Insert(v) + } + + height := tree.Height() + if height != tt.expectedHeight { + t.Errorf("Expected height %d, got %d", tt.expectedHeight, height) + } + }, + ) + } +} + +func TestRedBlackTree_Clear(t *testing.T) { + tree := New[int](compareInts) + values := []int{5, 3, 7, 1, 9} + + for _, v := range values { + tree.Insert(v) + } + + tree.Clear() + + if !tree.IsEmpty() { + t.Error("Tree should be empty after Clear()") + } + if tree.Size() != 0 { + t.Errorf("Expected size 0 after Clear(), got %d", tree.Size()) + } + if tree.Height() != -1 { + t.Errorf("Expected height -1 after Clear(), got %d", tree.Height()) + } + + // Verify no values remain + for _, v := range values { + if tree.Search(v) { + t.Errorf("Value %d still present after Clear()", v) + } + } +} + +func TestRedBlackTree_RandomOperations(t *testing.T) { + tree := New[int](compareInts) + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + + operations := 1000 + maxValue := 100 + values := make(map[int]bool) + + for i := 0; i < operations; i++ { + value := rng.Intn(maxValue) + if rng.Float32() < 0.7 { // 70% insertions + tree.Insert(value) + values[value] = true + } else { // 30% deletions + tree.Delete(value) + delete(values, value) + } + + // Verify Red-Black properties + if !verifyRedBlackProperties(tree) { + t.Errorf("Red-Black properties violated after operation %d", i) + } + + // Verify size matches unique values + if tree.Size() != len(values) { + t.Errorf( + "Size mismatch at operation %d: expected %d, got %d", + i, len(values), tree.Size(), + ) + } + + // Verify all values are present + for v := range values { + if !tree.Search(v) { + t.Errorf("Value %d missing at operation %d", v, i) + } + } + } +} + +func BenchmarkRedBlackTree(b *testing.B) { + benchmarks := []struct { + name string + size int + }{ + {"Small", 100}, + {"Medium", 1000}, + {"Large", 10000}, + } + + for _, bm := range benchmarks { + b.Run( + fmt.Sprintf("Insert_%s", bm.name), func(b *testing.B) { + for i := 0; i < b.N; i++ { + tree := New[int](compareInts) + for j := 0; j < bm.size; j++ { + tree.Insert(j) + } + } + }, + ) + + b.Run( + fmt.Sprintf("Search_%s", bm.name), func(b *testing.B) { + tree := New[int](compareInts) + for i := 0; i < bm.size; i++ { + tree.Insert(i) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + tree.Search(rand.Intn(bm.size)) + } + }, + ) + + b.Run( + fmt.Sprintf("Delete_%s", bm.name), func(b *testing.B) { + values := make([]int, bm.size) + for i := range values { + values[i] = i + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + tree := New[int](compareInts) + for _, v := range values { + tree.Insert(v) + } + b.StartTimer() + for _, v := range values { + tree.Delete(v) + } + } + }, + ) + } +}