From caaf53b30db5dc40c7cb92934bdcd5d386dedabd Mon Sep 17 00:00:00 2001 From: Suyono Date: Mon, 7 Aug 2023 20:38:24 +1000 Subject: [PATCH] red-black insertion --- .gitignore | 1 + .tool-versions | 1 + LICENSE | 2 +- go.mod | 3 + redblack.go | 181 +++++++++++++++++++++++++++++++++++++++++++++++ redblack_test.go | 37 ++++++++++ 6 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 go.mod create mode 100644 redblack.go create mode 100644 redblack_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..757fee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..6d1d0fb --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.20.7 diff --git a/LICENSE b/LICENSE index 2071b23..1587a33 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2023 Suyono Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a161e4a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitea.suyono.dev/suyono/redblack + +go 1.20 diff --git a/redblack.go b/redblack.go new file mode 100644 index 0000000..9643f8b --- /dev/null +++ b/redblack.go @@ -0,0 +1,181 @@ +package redblack + +type Comparable interface { + Compare(c Comparable) int +} + +type Tree struct { + root *Node + num int +} + +type Node struct { + key Comparable + color int + parent *Node + left *Node + right *Node +} + +func (t *Tree) rightRotate(node *Node) { + var ( + left = node.left + ) + + node.left = node.right + if node.left != nil { + node.left.parent = node + } + left.parent = node.parent + + if node.parent == nil { + t.root = left + } else if node == node.parent.left { + node.parent.left = left + } else { + node.parent.right = left + } + + left.right = node + node.parent = left +} + +func (t *Tree) leftRotate(node *Node) { + var ( + right = node.right + ) + + node.right = right.left + if node.right != nil { + node.right.parent = node + } + right.parent = node.parent + + if node.parent == nil { + t.root = right + } else if node == node.parent.left { + node.parent.left = right + } else { + node.parent.right = right + } + + right.left = node + node.parent = right +} + +func (t *Tree) bst(trav, temp *Node) *Node { + if trav == nil { + return temp + } + + if temp.key.Compare(trav.key) < 0 { + trav.left = t.bst(trav.left, temp) + trav.left.parent = trav + } else if temp.key.Compare(trav.key) > 0 { + trav.right = t.bst(trav.right, temp) + trav.right.parent = trav + } else { + panic("duplicate key") // duplicate key not allowed + } + + return trav +} + +func (t *Tree) fixup(root, pt *Node) { + var ( + parentPt *Node + grandParentPt *Node + unclePt *Node + ) + + for pt != t.root && pt.color != 0 && pt.parent.color == 1 { + parentPt = pt.parent + grandParentPt = pt.parent.parent + + //case A: Parent of pt is left child of grand-parent of pt + if parentPt == grandParentPt.left { + unclePt = grandParentPt.right + + //case 1: the uncle of pt is also red; only recoloring required + if unclePt != nil && unclePt.color == 1 { + grandParentPt.color = 1 + parentPt.color = 0 + unclePt.color = 0 + pt = grandParentPt + } else { + //case 2: pt is right child of its parent; left-rotation required + if pt == parentPt.right { + t.leftRotate(parentPt) + pt = parentPt + parentPt = pt.parent + } + + //case 3: pt is left child of its parent; right-rotation required + t.rightRotate(grandParentPt) + t := parentPt.color + parentPt.color = grandParentPt.color + grandParentPt.color = t + pt = parentPt + } + } else { + //case B: parent of pt is right child of grand parent of pt + unclePt = grandParentPt.left + + //case 1: the unclePt is also red; only recoloring required + if unclePt != nil && unclePt.color == 1 { + grandParentPt.color = 1 + parentPt.color = 0 + unclePt.color = 0 + pt = grandParentPt + } else { + + //case 2: pt is left child of its parent; right-rotate required + if pt == parentPt.left { + t.rightRotate(parentPt) + pt = parentPt + parentPt = pt.parent + } + + //case 3: pt is right child of its parent; left-rotate required + t.leftRotate(grandParentPt) + t := parentPt.color + parentPt.color = grandParentPt.color + grandParentPt.color = t + pt = parentPt + } + } + } +} + +func (t *Tree) Insert(key Comparable) { + temp := &Node{ + key: key, + color: 1, + } + + t.root = t.bst(t.root, temp) + t.fixup(t.root, temp) + t.root.color = 0 + t.num++ +} + +func (t *Tree) Slice() []Comparable { + returnValue := make([]Comparable, t.num) + t.slice(returnValue, 0, t.root) + return returnValue +} + +func (t *Tree) slice(s []Comparable, index int, node *Node) int { + if node.left != nil { + index = t.slice(s, index, node.left) + } + + s[index] = node.key + index++ + + if node.right != nil { + index = t.slice(s, index, node.right) + } + + return index +} diff --git a/redblack_test.go b/redblack_test.go new file mode 100644 index 0000000..e7ee649 --- /dev/null +++ b/redblack_test.go @@ -0,0 +1,37 @@ +package redblack + +import "testing" + +type testKey int + +func (k testKey) Compare(c Comparable) int { + if ck, ok := c.(testKey); ok { + return int(k - ck) + } + + panic("unexpected type") +} + +func TestInsert(t *testing.T) { + var ( + tree *Tree + out []Comparable + keys = []testKey{7, 6, 5, 4, 3, 2, 1} + ) + + tree = new(Tree) + for _, k := range keys { + tree.Insert(k) + } + + out = tree.Slice() + for i, d := range out { + if ck, ok := d.(testKey); ok { + if ck != keys[len(keys)-1-i] { + t.Fatal("mismatch data") + } + } else { + t.Fatal("invalid data type") + } + } +}