Compare commits
10 commits
69668e4b99
...
fcd2f9b99a
Author | SHA1 | Date | |
---|---|---|---|
fcd2f9b99a | |||
d4cbad40c3 | |||
ebe6a3ab37 | |||
b90f1218ce | |||
eb82d79371 | |||
1b3ca7b515 | |||
afc077c8a6 | |||
f85fd63c74 | |||
bee59b5d0b | |||
e161de1881 |
11 changed files with 1183 additions and 28 deletions
389
searching/binary_search_tree.go
Normal file
389
searching/binary_search_tree.go
Normal file
|
@ -0,0 +1,389 @@
|
|||
package searching
|
||||
|
||||
import "github.com/fotonmoton/algorithms/fundamentals/queue"
|
||||
|
||||
type bstNode[K any, V any] struct {
|
||||
left *bstNode[K, V]
|
||||
right *bstNode[K, V]
|
||||
key K
|
||||
val V
|
||||
n int64
|
||||
}
|
||||
|
||||
// TODO: maybe pass pointers for recursive funcs?
|
||||
type bst[K any, V any] struct {
|
||||
root *bstNode[K, V]
|
||||
cmp func(*K, *K) int
|
||||
}
|
||||
|
||||
func NewBST[K any, V any](cmp func(*K, *K) int) SymbolTable[K, V] {
|
||||
return &bst[K, V]{nil, cmp}
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) Put(key K, val V) {
|
||||
t.root = t.put(key, val, t.root)
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) put(key K, val V, node *bstNode[K, V]) *bstNode[K, V] {
|
||||
if node == nil {
|
||||
return &bstNode[K, V]{nil, nil, key, val, 1}
|
||||
}
|
||||
|
||||
cmp := t.cmp(&key, &node.key)
|
||||
|
||||
if cmp < 0 {
|
||||
node.left = t.put(key, val, node.left)
|
||||
}
|
||||
|
||||
if cmp == 0 {
|
||||
node.val = val
|
||||
}
|
||||
|
||||
if cmp > 0 {
|
||||
node.right = t.put(key, val, node.right)
|
||||
}
|
||||
|
||||
node.n = t.size(node.left) + t.size(node.right) + 1
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) Get(key K) *V {
|
||||
return t.get(key, t.root)
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) get(key K, node *bstNode[K, V]) *V {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmp := t.cmp(&key, &node.key)
|
||||
|
||||
if cmp < 0 {
|
||||
return t.get(key, node.left)
|
||||
}
|
||||
|
||||
if cmp > 0 {
|
||||
return t.get(key, node.right)
|
||||
}
|
||||
|
||||
return &node.val
|
||||
}
|
||||
|
||||
func (t *bst[_, __]) Size() int64 {
|
||||
return t.size(t.root)
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) size(node *bstNode[K, V]) int64 {
|
||||
if node == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return node.n
|
||||
}
|
||||
|
||||
func (t *bst[K, _]) Min() *K {
|
||||
if t.root == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &t.min(t.root).key
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) min(node *bstNode[K, V]) *bstNode[K, V] {
|
||||
if node.left == nil {
|
||||
return node
|
||||
}
|
||||
|
||||
return t.min(node.left)
|
||||
}
|
||||
|
||||
func (t *bst[K, _]) Max() *K {
|
||||
if t.root == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &t.max(t.root).key
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) max(node *bstNode[K, V]) *bstNode[K, V] {
|
||||
if node.right == nil {
|
||||
return node
|
||||
}
|
||||
|
||||
return t.max(node.right)
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) Floor(key K) *K {
|
||||
largest := t.floor(key, t.root)
|
||||
|
||||
if largest == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &largest.key
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) floor(key K, node *bstNode[K, V]) *bstNode[K, V] {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmp := t.cmp(&key, &node.key)
|
||||
|
||||
if cmp == 0 {
|
||||
return node
|
||||
}
|
||||
|
||||
if cmp < 0 {
|
||||
return t.floor(key, node.left)
|
||||
}
|
||||
|
||||
larger := t.floor(key, node.right)
|
||||
|
||||
if larger != nil {
|
||||
return larger
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) Ceiling(key K) *K {
|
||||
smallest := t.ceiling(key, t.root)
|
||||
|
||||
if smallest == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &smallest.key
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) ceiling(key K, node *bstNode[K, V]) *bstNode[K, V] {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmp := t.cmp(&key, &node.key)
|
||||
|
||||
if cmp == 0 {
|
||||
return node
|
||||
}
|
||||
|
||||
if cmp > 0 {
|
||||
return t.ceiling(key, node.right)
|
||||
}
|
||||
|
||||
smaller := t.ceiling(key, node.left)
|
||||
|
||||
if smaller != nil {
|
||||
return smaller
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) Rank(key K) int64 {
|
||||
return t.rank(key, t.root)
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) rank(key K, node *bstNode[K, V]) int64 {
|
||||
if node == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
cmp := t.cmp(&key, &node.key)
|
||||
|
||||
// If we found key in a tree then left subtree
|
||||
// will always contain keys less than current node key
|
||||
// and right subtree will always ontain greater keys (by BST definition).
|
||||
// So we simply return left subtree size
|
||||
if cmp == 0 {
|
||||
return t.size(node.left)
|
||||
}
|
||||
|
||||
// If current node key is bigger than key for which rank is searched
|
||||
// we should descend deeper in left subtree
|
||||
if cmp < 0 {
|
||||
return t.rank(key, node.left)
|
||||
}
|
||||
|
||||
// If we found node with key that is less than search key
|
||||
// we get the size of the left subtree, add 1 to count current node in
|
||||
// rank value and descend deeper in right subtree.
|
||||
return 1 + t.size(node.left) + t.rank(key, node.right)
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) KeyByRank(i int64) *K {
|
||||
node := t.keyByRank(i, t.root)
|
||||
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &node.key
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) keyByRank(rank int64, node *bstNode[K, V]) *bstNode[K, V] {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We need left subtree size to substract it from our index
|
||||
// when we descend deeper in right subtree
|
||||
leftSize := t.size(node.left)
|
||||
|
||||
if rank < leftSize {
|
||||
return t.keyByRank(rank, node.left)
|
||||
}
|
||||
|
||||
if rank > leftSize {
|
||||
// We subtract left size subtree
|
||||
return t.keyByRank(rank-leftSize-1, node.right)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) Contains(key K) bool {
|
||||
return t.Get(key) == nil
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) IsEmpty() bool {
|
||||
return t.Size() == 0
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) DeleteMin() {
|
||||
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.root = t.deleteMin(t.root)
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) deleteMin(node *bstNode[K, V]) *bstNode[K, V] {
|
||||
if node.left == nil {
|
||||
return node.right
|
||||
}
|
||||
|
||||
node.left = t.deleteMin(node.left)
|
||||
node.n = t.size(node.left) + t.size(node.right) + 1
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) DeleteMax() {
|
||||
|
||||
if t.root == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.root = t.deleteMax(t.root)
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) deleteMax(node *bstNode[K, V]) *bstNode[K, V] {
|
||||
if node.right == nil {
|
||||
return node.left
|
||||
}
|
||||
|
||||
node.right = t.deleteMax(node.right)
|
||||
node.n = t.size(node.left) + t.size(node.right) + 1
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) Delete(key K) {
|
||||
t.root = t.delete(key, t.root)
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) delete(key K, node *bstNode[K, V]) *bstNode[K, V] {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmp := t.cmp(&key, &node.key)
|
||||
|
||||
if cmp < 0 {
|
||||
node.left = t.delete(key, node.left)
|
||||
} else if cmp > 0 {
|
||||
node.right = t.delete(key, node.right)
|
||||
} else {
|
||||
|
||||
// Shortcut: we can return left or right subtree if we have only one of them
|
||||
// without size recalculation and pointers juggling
|
||||
if node.right == nil {
|
||||
return node.left
|
||||
}
|
||||
if node.left == nil {
|
||||
return node.right
|
||||
}
|
||||
|
||||
// Needed to delete "min" node in right subtree
|
||||
tmp := node
|
||||
// We substitute current node with "min" node from right subtree.
|
||||
// When "node" variable will be returned to the caller "tmp" node
|
||||
// will be erased by "node" value and be marked for garbage collection.
|
||||
// At least it should work as described
|
||||
node = t.min(tmp.right)
|
||||
// We prevent "node" duplication in the tree by deleting it from right subtree
|
||||
node.right = t.deleteMin(tmp.right)
|
||||
// Left subtree stays unchanged
|
||||
node.left = tmp.left
|
||||
}
|
||||
node.n = t.size(node.left) + t.size(node.right) + 1
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) KeysBetween(lo, hi K) []K {
|
||||
q := queue.NewQueue[K]()
|
||||
t.keysBetween(lo, hi, t.root, q)
|
||||
keys := make([]K, 0, q.Size())
|
||||
|
||||
for !q.IsEmpty() {
|
||||
keys = append(keys, q.Dequeue())
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) keysBetween(lo, hi K, node *bstNode[K, V], q queue.Queue[K]) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
cmplo := t.cmp(&lo, &node.key)
|
||||
cmphi := t.cmp(&hi, &node.key)
|
||||
|
||||
if cmplo < 0 {
|
||||
t.keysBetween(lo, hi, node.left, q)
|
||||
}
|
||||
|
||||
if cmplo <= 0 && cmphi >= 0 {
|
||||
q.Enqueue(node.key)
|
||||
}
|
||||
|
||||
if cmphi > 0 {
|
||||
t.keysBetween(lo, hi, node.right, q)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) Keys() []K {
|
||||
if t.IsEmpty() {
|
||||
return []K{}
|
||||
}
|
||||
|
||||
q := queue.NewQueue[K]()
|
||||
t.keysBetween(*t.Min(), *t.Max(), t.root, q)
|
||||
keys := make([]K, 0, q.Size())
|
||||
|
||||
for !q.IsEmpty() {
|
||||
keys = append(keys, q.Dequeue())
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func (t *bst[K, V]) SizeBetween(lo K, hi K) int64 {
|
||||
q := queue.NewQueue[K]()
|
||||
t.keysBetween(lo, hi, t.root, q)
|
||||
|
||||
return int64(q.Size())
|
||||
}
|
331
searching/binary_search_tree_test.go
Normal file
331
searching/binary_search_tree_test.go
Normal file
|
@ -0,0 +1,331 @@
|
|||
package searching
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func intCompare(a, b *int) int {
|
||||
if *a < *b {
|
||||
return -1
|
||||
}
|
||||
|
||||
if *a > *b {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestPut(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
table.Put(1, 10)
|
||||
table.Put(2, 20)
|
||||
|
||||
assert.Equal(t, 10, *table.Get(1))
|
||||
assert.Equal(t, 20, *table.Get(2))
|
||||
|
||||
// rewrite
|
||||
table.Put(1, 11)
|
||||
|
||||
assert.Equal(t, 11, *table.Get(1))
|
||||
assert.Equal(t, 20, *table.Get(2))
|
||||
assert.Equal(t, int64(2), table.Size())
|
||||
|
||||
}
|
||||
|
||||
// TODO: test with delete
|
||||
func TestGet(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.Nil(t, table.Get(0))
|
||||
|
||||
table.Put(1, 2)
|
||||
|
||||
assert.Equal(t, 2, *table.Get(1))
|
||||
}
|
||||
|
||||
// TODO: test with delete
|
||||
func TestSize(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.Equal(t, int64(0), table.Size())
|
||||
|
||||
table.Put(1, 1)
|
||||
|
||||
assert.Equal(t, int64(1), table.Size())
|
||||
|
||||
table.Put(2, 2)
|
||||
|
||||
assert.Equal(t, int64(2), table.Size())
|
||||
}
|
||||
|
||||
// TODO: test with delete
|
||||
func TestMin(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.Nil(t, table.Min())
|
||||
|
||||
table.Put(3, 3)
|
||||
|
||||
assert.Equal(t, 3, *table.Min())
|
||||
|
||||
table.Put(2, 2)
|
||||
|
||||
assert.Equal(t, 2, *table.Min())
|
||||
|
||||
table.Put(4, 4)
|
||||
|
||||
assert.Equal(t, 2, *table.Min())
|
||||
|
||||
table.Put(1, 1)
|
||||
|
||||
assert.Equal(t, 1, *table.Min())
|
||||
}
|
||||
|
||||
// TODO: test with delete
|
||||
func TestMax(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.Nil(t, table.Max())
|
||||
|
||||
table.Put(1, 1)
|
||||
assert.Equal(t, 1, *table.Max())
|
||||
|
||||
table.Put(2, 2)
|
||||
assert.Equal(t, 2, *table.Max())
|
||||
|
||||
table.Put(5, 5)
|
||||
assert.Equal(t, 5, *table.Max())
|
||||
|
||||
table.Put(4, 4)
|
||||
assert.Equal(t, 5, *table.Max())
|
||||
|
||||
table.Put(3, 3)
|
||||
assert.Equal(t, 5, *table.Max())
|
||||
|
||||
table.Put(5, 55)
|
||||
assert.Equal(t, 5, *table.Max())
|
||||
|
||||
table.Put(6, 6)
|
||||
assert.Equal(t, 6, *table.Max())
|
||||
}
|
||||
|
||||
// TODO: test with delete
|
||||
func TestFloor(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.Nil(t, table.Floor(0))
|
||||
|
||||
table.Put(1, 1)
|
||||
assert.Equal(t, 1, *table.Floor(1))
|
||||
|
||||
table.Put(5, 5)
|
||||
assert.Equal(t, 5, *table.Floor(5))
|
||||
assert.Equal(t, 1, *table.Floor(4))
|
||||
|
||||
table.Put(4, 4)
|
||||
assert.Equal(t, 5, *table.Floor(5))
|
||||
assert.Equal(t, 4, *table.Floor(4))
|
||||
assert.Equal(t, 1, *table.Floor(3))
|
||||
assert.Nil(t, table.Floor(0))
|
||||
}
|
||||
|
||||
// TODO: test with delete
|
||||
func TestCeiling(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.Nil(t, table.Ceiling(0))
|
||||
|
||||
table.Put(5, 5)
|
||||
assert.Equal(t, 5, *table.Ceiling(5))
|
||||
|
||||
table.Put(4, 4)
|
||||
assert.Equal(t, 4, *table.Ceiling(0))
|
||||
assert.Equal(t, 5, *table.Ceiling(5))
|
||||
|
||||
table.Put(3, 3)
|
||||
assert.Equal(t, 3, *table.Ceiling(0))
|
||||
assert.Equal(t, 3, *table.Ceiling(1))
|
||||
assert.Equal(t, 3, *table.Ceiling(3))
|
||||
assert.Equal(t, 4, *table.Ceiling(4))
|
||||
assert.Equal(t, 5, *table.Ceiling(5))
|
||||
assert.Nil(t, table.Ceiling(6))
|
||||
}
|
||||
|
||||
// TODO: test with delete
|
||||
func TestRank(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.Equal(t, int64(0), table.Rank(1))
|
||||
|
||||
table.Put(0, 0)
|
||||
assert.Equal(t, int64(1), table.Rank(1))
|
||||
|
||||
table.Put(1, 1)
|
||||
assert.Equal(t, int64(2), table.Rank(2))
|
||||
|
||||
table.Put(4, 4)
|
||||
assert.Equal(t, int64(2), table.Rank(2))
|
||||
assert.Equal(t, int64(2), table.Rank(3))
|
||||
assert.Equal(t, int64(3), table.Rank(5))
|
||||
|
||||
table.Put(2, 2)
|
||||
assert.Equal(t, int64(2), table.Rank(2))
|
||||
assert.Equal(t, int64(3), table.Rank(3))
|
||||
assert.Equal(t, int64(4), table.Rank(5))
|
||||
|
||||
table.Put(3, 3)
|
||||
assert.Equal(t, int64(2), table.Rank(2))
|
||||
assert.Equal(t, int64(3), table.Rank(3))
|
||||
assert.Equal(t, int64(4), table.Rank(4))
|
||||
assert.Equal(t, int64(5), table.Rank(5))
|
||||
}
|
||||
|
||||
// TODO: test with delete
|
||||
func TestKeyByRank(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.Nil(t, table.KeyByRank(1))
|
||||
|
||||
table.Put(0, 0)
|
||||
assert.Nil(t, table.KeyByRank(1))
|
||||
assert.Equal(t, 0, *table.KeyByRank(table.Rank(0)))
|
||||
|
||||
table.Put(5, 5)
|
||||
assert.Equal(t, 5, *table.KeyByRank(table.Rank(5)))
|
||||
assert.EqualValues(t, 1, table.Rank(*table.KeyByRank(1)))
|
||||
}
|
||||
|
||||
func TestDeleteMin(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
table.DeleteMin()
|
||||
|
||||
table.Put(0, 0)
|
||||
assert.EqualValues(t, 1, table.Size())
|
||||
|
||||
table.DeleteMin()
|
||||
assert.EqualValues(t, 0, table.Size())
|
||||
|
||||
table.Put(5, 5)
|
||||
table.Put(0, 0)
|
||||
table.Put(1, 1)
|
||||
table.Put(2, 2)
|
||||
|
||||
assert.Equal(t, 0, *table.Get(0))
|
||||
|
||||
table.DeleteMin()
|
||||
|
||||
assert.Nil(t, table.Get(0))
|
||||
assert.EqualValues(t, 3, table.Size())
|
||||
}
|
||||
|
||||
func TestDeleteMax(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
table.DeleteMin()
|
||||
|
||||
table.Put(0, 0)
|
||||
assert.EqualValues(t, 1, table.Size())
|
||||
|
||||
table.DeleteMax()
|
||||
assert.EqualValues(t, 0, table.Size())
|
||||
|
||||
table.Put(0, 0)
|
||||
table.Put(5, 5)
|
||||
table.Put(1, 1)
|
||||
table.Put(2, 2)
|
||||
|
||||
assert.Equal(t, 5, *table.Get(5))
|
||||
|
||||
table.DeleteMax()
|
||||
|
||||
assert.Nil(t, table.Get(5))
|
||||
assert.EqualValues(t, 3, table.Size())
|
||||
}
|
||||
|
||||
// TODO: add more cases
|
||||
func TestDelete(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
table.Delete(0)
|
||||
|
||||
table.Put(0, 0)
|
||||
|
||||
table.Delete(0)
|
||||
assert.EqualValues(t, 0, table.Size())
|
||||
assert.Nil(t, table.Get(0))
|
||||
|
||||
table.Put(0, 0)
|
||||
table.Put(5, 5)
|
||||
table.Put(1, 1)
|
||||
table.Put(2, 2)
|
||||
|
||||
assert.Equal(t, 1, *table.Get(1))
|
||||
|
||||
table.Delete(1)
|
||||
assert.Nil(t, table.Get(1))
|
||||
assert.EqualValues(t, 3, table.Size())
|
||||
|
||||
table.Delete(2)
|
||||
table.Delete(5)
|
||||
table.Delete(0)
|
||||
assert.EqualValues(t, 0, table.Size())
|
||||
}
|
||||
|
||||
func TestKeysBetween(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.EqualValues(t, []int{}, table.KeysBetween(0, 10))
|
||||
|
||||
table.Put(1, 1)
|
||||
|
||||
assert.EqualValues(t, []int{}, table.KeysBetween(2, 10))
|
||||
assert.EqualValues(t, []int{1}, table.KeysBetween(1, 1))
|
||||
|
||||
table.Put(2, 2)
|
||||
table.Put(5, 5)
|
||||
|
||||
assert.EqualValues(t, []int{5}, table.KeysBetween(3, 10))
|
||||
assert.EqualValues(t, []int{1, 2, 5}, table.KeysBetween(1, 5))
|
||||
}
|
||||
|
||||
func TestKeys(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.EqualValues(t, []int{}, table.Keys())
|
||||
|
||||
table.Put(1, 1)
|
||||
|
||||
assert.EqualValues(t, []int{1}, table.Keys())
|
||||
|
||||
table.Put(2, 2)
|
||||
table.Put(5, 5)
|
||||
|
||||
assert.EqualValues(t, []int{1, 2, 5}, table.Keys())
|
||||
|
||||
table.Delete(2)
|
||||
|
||||
assert.EqualValues(t, []int{1, 5}, table.Keys())
|
||||
|
||||
}
|
||||
|
||||
func TestSizeBetween(t *testing.T) {
|
||||
table := NewBST[int, int](intCompare)
|
||||
|
||||
assert.EqualValues(t, 0, table.SizeBetween(0, 10))
|
||||
|
||||
table.Put(1, 1)
|
||||
|
||||
assert.EqualValues(t, 0, table.SizeBetween(2, 10))
|
||||
assert.EqualValues(t, 1, table.SizeBetween(1, 1))
|
||||
|
||||
table.Put(2, 2)
|
||||
table.Put(5, 5)
|
||||
|
||||
assert.EqualValues(t, 1, table.SizeBetween(3, 10))
|
||||
assert.EqualValues(t, 3, table.SizeBetween(1, 5))
|
||||
}
|
23
searching/symbol_table.go
Normal file
23
searching/symbol_table.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package searching
|
||||
|
||||
// TODO: think about pointer semantics: where pointers should be used?
|
||||
// Does go compiler silently convert values to pointers when they are leave table?
|
||||
type SymbolTable[K any, V any] interface {
|
||||
Put(K, V) // add value V with associated key K to symbol table
|
||||
Get(K) *V // get value V with associated key K to symbol table, nil if value is absent
|
||||
Size() int64 // number of key-value pairs
|
||||
Min() *K // smallest key
|
||||
Max() *K // largest key
|
||||
Floor(K) *K // largest key less than or equal to K
|
||||
Ceiling(K) *K // smallest key greater or equal to K
|
||||
Rank(K) int64 // number of keys less than K. Rank(*Index(in)) = in
|
||||
KeyByRank(int64) *K // key of specified rank. *Index(Rank(K)) = K
|
||||
Contains(K) bool // check if key K exists in symbol table
|
||||
IsEmpty() bool // check if symbol table is empty
|
||||
DeleteMin() // delete value with smallest key
|
||||
DeleteMax() // delete value with largest key
|
||||
Delete(K) // delete value associated with key K.
|
||||
KeysBetween(K, K) []K // keys between two other keys in sorted order
|
||||
Keys() []K // all existing keys in sorted order
|
||||
SizeBetween(K, K) int64 // number of keys between two keys
|
||||
}
|
55
sorting/heap.go
Normal file
55
sorting/heap.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package sorting
|
||||
|
||||
func swap[T any](i, j int, items []T) {
|
||||
items[i], items[j] = items[j], items[i]
|
||||
}
|
||||
|
||||
func swim[T any](child int, depth int, items []T, less func(T, T) bool) {
|
||||
for {
|
||||
parent := (child - 1) / 2
|
||||
|
||||
if child <= 0 || less(items[child], items[parent]) {
|
||||
break
|
||||
}
|
||||
|
||||
swap(parent, child, items)
|
||||
|
||||
child = parent
|
||||
}
|
||||
}
|
||||
|
||||
func sink[T any](parent int, depth int, items []T, less func(T, T) bool) {
|
||||
for {
|
||||
child := parent*2 + 1
|
||||
|
||||
if child >= depth {
|
||||
break
|
||||
}
|
||||
|
||||
if child+1 < depth && less(items[child], items[child+1]) {
|
||||
child++
|
||||
}
|
||||
|
||||
if !less(items[parent], items[child]) {
|
||||
break
|
||||
}
|
||||
|
||||
swap(parent, child, items)
|
||||
|
||||
parent = child
|
||||
}
|
||||
}
|
||||
|
||||
func Heap[T any](items []T, less func(T, T) bool) {
|
||||
len := len(items)
|
||||
|
||||
for k := len / 2; k >= 0; k-- {
|
||||
sink(k, len, items, less)
|
||||
}
|
||||
|
||||
for len > 0 {
|
||||
len--
|
||||
swap(0, len, items)
|
||||
sink(0, len, items, less)
|
||||
}
|
||||
}
|
145
sorting/priority_queue/index_priority_queue.go
Normal file
145
sorting/priority_queue/index_priority_queue.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
package priority_queue
|
||||
|
||||
type IndexPriorityQueue[T any] interface {
|
||||
Top() T // get item with biggest priority
|
||||
TopIndex() int // get index of an item with biggest priority
|
||||
Remove() T // removes item with the biggest priority
|
||||
RemoveAtIndex(index int) T // removes item at specified index
|
||||
Insert(index int, item T) // adds item at specified index
|
||||
Change(index int, item T) // changes item at specified index and preserves ordering
|
||||
Contains(index int) bool // checks if item exists at specified index
|
||||
IsEmpty() bool
|
||||
Size() int
|
||||
}
|
||||
|
||||
type indexPriorityQueue[T any] struct {
|
||||
n int
|
||||
// unordered items.
|
||||
// items[index] = item.
|
||||
// we store pointers instead of values to prevent memory leaks
|
||||
// by setting nil for removed items
|
||||
items []*T
|
||||
// priority queue. Contains keys for items in priority order.
|
||||
// items[pq[1]] = item with biggest priority
|
||||
pq []int
|
||||
// "reverse" for pq. Maps item index to priority
|
||||
// qp[index] = priority index, qp[pq[index]] = pq[qp[index]] = index
|
||||
qp []int
|
||||
// "less" function. Depending on desired order first element
|
||||
// not necessary less than a second
|
||||
less func(T, T) bool
|
||||
}
|
||||
|
||||
// TODO: panic for illegal operations
|
||||
// TODO: can we construct queue without bounded index size?
|
||||
func NewIPQ[T any](less func(T, T) bool, indexSize int) IndexPriorityQueue[T] {
|
||||
n := 0
|
||||
items := make([]*T, indexSize)
|
||||
pq := make([]int, indexSize)
|
||||
qp := make([]int, indexSize)
|
||||
|
||||
for i := range qp {
|
||||
qp[i] = -1
|
||||
}
|
||||
|
||||
for i := range pq {
|
||||
pq[i] = -1
|
||||
}
|
||||
|
||||
return &indexPriorityQueue[T]{n, items, pq, qp, less}
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[T]) Top() T {
|
||||
return *q.items[q.pq[0]]
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[T]) TopIndex() int {
|
||||
return q.pq[0]
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[T]) Insert(index int, item T) {
|
||||
q.pq[q.n] = index
|
||||
q.qp[index] = q.n
|
||||
q.items[index] = &item
|
||||
q.swim(q.n)
|
||||
q.n++
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[T]) Remove() T {
|
||||
return q.RemoveAtIndex(q.TopIndex())
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[T]) RemoveAtIndex(index int) T {
|
||||
pivot := q.qp[index]
|
||||
removed := q.items[index]
|
||||
q.n--
|
||||
q.swap(pivot, q.n)
|
||||
q.swim(pivot)
|
||||
q.sink(pivot)
|
||||
q.items[index] = nil
|
||||
q.qp[index] = -1
|
||||
q.pq[q.n] = -1
|
||||
return *removed
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[T]) Change(index int, item T) {
|
||||
q.items[index] = &item
|
||||
q.swim(q.qp[index])
|
||||
q.sink(q.qp[index])
|
||||
}
|
||||
|
||||
func (pq *indexPriorityQueue[_]) Size() int {
|
||||
return pq.n
|
||||
}
|
||||
|
||||
func (pq *indexPriorityQueue[_]) IsEmpty() bool {
|
||||
return pq.n == 0
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[_]) Contains(index int) bool {
|
||||
return q.qp[index] != -1
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[T]) sink(parent int) {
|
||||
|
||||
for {
|
||||
child := 2*parent + 1
|
||||
|
||||
if child >= q.n {
|
||||
break
|
||||
}
|
||||
|
||||
if child+1 < q.n && q.less(*q.items[q.pq[child]], *q.items[q.pq[child+1]]) {
|
||||
child++
|
||||
}
|
||||
|
||||
if !q.less(*q.items[q.pq[parent]], *q.items[q.pq[child]]) {
|
||||
break
|
||||
}
|
||||
|
||||
q.swap(parent, child)
|
||||
|
||||
parent = child
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[T]) swim(child int) {
|
||||
for {
|
||||
parent := (child - 1) / 2
|
||||
|
||||
if child <= 0 || q.less(*q.items[q.pq[child]], *q.items[q.pq[parent]]) {
|
||||
break
|
||||
}
|
||||
|
||||
q.swap(parent, child)
|
||||
|
||||
child = parent
|
||||
}
|
||||
}
|
||||
|
||||
func (q *indexPriorityQueue[_]) swap(i, j int) {
|
||||
q.qp[q.pq[i]] = j
|
||||
q.qp[q.pq[j]] = i
|
||||
q.pq[i], q.pq[j] = q.pq[j], q.pq[i]
|
||||
}
|
193
sorting/priority_queue/index_priority_queue_test.go
Normal file
193
sorting/priority_queue/index_priority_queue_test.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
package priority_queue
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewIPQ(t *testing.T) {
|
||||
q := NewIPQ(intDescending, 1)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
assert.Equal(t, 0, q.Size())
|
||||
assert.Equal(t, true, q.IsEmpty())
|
||||
assert.Equal(t, -1, q.TopIndex())
|
||||
}
|
||||
|
||||
func TestIPQInsert(t *testing.T) {
|
||||
pq := NewIPQ(intDescending, 10)
|
||||
|
||||
pq.Insert(1, 3)
|
||||
assert.Equal(t, 3, pq.Top())
|
||||
|
||||
pq.Insert(2, 4)
|
||||
assert.Equal(t, 4, pq.Top())
|
||||
|
||||
pq.Insert(3, 1)
|
||||
assert.Equal(t, 4, pq.Top())
|
||||
|
||||
pq.Insert(4, 4)
|
||||
assert.Equal(t, 4, pq.Top())
|
||||
}
|
||||
|
||||
func TestMoreIPQInsert(t *testing.T) {
|
||||
pq := NewIPQ(intDescending, 10)
|
||||
|
||||
pq.Insert(1, 10)
|
||||
|
||||
assert.Equal(t, 1, pq.TopIndex())
|
||||
assert.Equal(t, 10, pq.Top())
|
||||
assert.Equal(t, 1, pq.Size())
|
||||
assert.Equal(t, true, pq.Contains(1))
|
||||
assert.Equal(t, false, pq.IsEmpty())
|
||||
|
||||
pq.Insert(2, 20)
|
||||
|
||||
assert.Equal(t, 2, pq.TopIndex())
|
||||
assert.Equal(t, 20, pq.Top())
|
||||
assert.Equal(t, 2, pq.Size())
|
||||
assert.Equal(t, true, pq.Contains(2))
|
||||
assert.Equal(t, false, pq.IsEmpty())
|
||||
}
|
||||
|
||||
func TestIPQRemove(t *testing.T) {
|
||||
pq := NewIPQ(intDescending, 10)
|
||||
|
||||
pq.Insert(1, 10)
|
||||
|
||||
assert.Equal(t, 1, pq.TopIndex())
|
||||
assert.Equal(t, 10, pq.Top())
|
||||
assert.Equal(t, 1, pq.Size())
|
||||
assert.Equal(t, true, pq.Contains(1))
|
||||
assert.Equal(t, false, pq.IsEmpty())
|
||||
|
||||
pq.Insert(2, 20)
|
||||
|
||||
assert.Equal(t, 2, pq.TopIndex())
|
||||
assert.Equal(t, 20, pq.Top())
|
||||
assert.Equal(t, 2, pq.Size())
|
||||
assert.Equal(t, true, pq.Contains(2))
|
||||
assert.Equal(t, false, pq.IsEmpty())
|
||||
|
||||
removed := pq.Remove()
|
||||
|
||||
assert.Equal(t, 20, removed)
|
||||
assert.Equal(t, 10, pq.Top())
|
||||
assert.Equal(t, 1, pq.Size())
|
||||
assert.Equal(t, false, pq.Contains(2))
|
||||
assert.Equal(t, true, pq.Contains(1))
|
||||
assert.Equal(t, false, pq.IsEmpty())
|
||||
|
||||
removed = pq.Remove()
|
||||
|
||||
assert.Equal(t, 10, removed)
|
||||
// TODO: should return nil?
|
||||
// assert.Equal(t, "", pq.Top())
|
||||
assert.Equal(t, 0, pq.Size())
|
||||
assert.Equal(t, false, pq.Contains(1))
|
||||
assert.Equal(t, true, pq.IsEmpty())
|
||||
}
|
||||
|
||||
func TestIPQRemoveAtIndex(t *testing.T) {
|
||||
pq := NewIPQ(intDescending, 10)
|
||||
|
||||
// Top -> 40 - 30 - 20 - 10
|
||||
pq.Insert(8, 10)
|
||||
pq.Insert(5, 20)
|
||||
pq.Insert(3, 30)
|
||||
pq.Insert(4, 40)
|
||||
|
||||
assert.Equal(t, 40, pq.Top())
|
||||
assert.Equal(t, 4, pq.TopIndex())
|
||||
|
||||
// Top -> 40 - 30 - 10
|
||||
removed := pq.RemoveAtIndex(5)
|
||||
|
||||
assert.Equal(t, 20, removed)
|
||||
assert.Equal(t, 40, pq.Top())
|
||||
assert.Equal(t, 4, pq.TopIndex())
|
||||
|
||||
// Top -> 30 - 10
|
||||
removed = pq.RemoveAtIndex(4)
|
||||
|
||||
assert.Equal(t, 40, removed)
|
||||
assert.Equal(t, 30, pq.Top())
|
||||
assert.Equal(t, 3, pq.TopIndex())
|
||||
|
||||
// Top -> 30 - 20 - 10
|
||||
pq.Insert(5, 20)
|
||||
|
||||
assert.Equal(t, 30, pq.Top())
|
||||
assert.Equal(t, 3, pq.TopIndex())
|
||||
|
||||
// Top -> 10
|
||||
removed = pq.RemoveAtIndex(3)
|
||||
assert.Equal(t, 30, removed)
|
||||
removed = pq.RemoveAtIndex(5)
|
||||
assert.Equal(t, 20, removed)
|
||||
|
||||
assert.Equal(t, 10, pq.Top())
|
||||
assert.Equal(t, 8, pq.TopIndex())
|
||||
assert.Equal(t, 1, pq.Size())
|
||||
assert.Equal(t, false, pq.Contains(5))
|
||||
assert.Equal(t, false, pq.Contains(4))
|
||||
assert.Equal(t, false, pq.Contains(3))
|
||||
assert.Equal(t, true, pq.Contains(8))
|
||||
assert.Equal(t, false, pq.IsEmpty())
|
||||
}
|
||||
|
||||
func TestIndexChange(t *testing.T) {
|
||||
pq := NewIPQ(intDescending, 10)
|
||||
|
||||
pq.Insert(1, 9)
|
||||
pq.Insert(2, 8)
|
||||
pq.Insert(3, 12)
|
||||
|
||||
assert.Equal(t, 12, pq.Top())
|
||||
|
||||
pq.Change(3, 7)
|
||||
|
||||
assert.Equal(t, 9, pq.Top())
|
||||
|
||||
pq.Change(2, 10)
|
||||
|
||||
assert.Equal(t, 10, pq.Top())
|
||||
}
|
||||
|
||||
func TestMultiwayMerge(t *testing.T) {
|
||||
|
||||
multiwayMerge := func(streams ...*strings.Reader) string {
|
||||
q := NewIPQ(func(t1, t2 rune) bool { return t1 > t2 }, len(streams))
|
||||
b := strings.Builder{}
|
||||
|
||||
for i, stream := range streams {
|
||||
rune, _, _ := stream.ReadRune()
|
||||
q.Insert(i, rune)
|
||||
}
|
||||
|
||||
for !q.IsEmpty() {
|
||||
b.WriteRune(q.Top())
|
||||
streamIndex := q.TopIndex()
|
||||
q.Remove()
|
||||
rune, _, err := streams[streamIndex].ReadRune()
|
||||
if err != io.EOF {
|
||||
q.Insert(streamIndex, rune)
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ordered "streams"
|
||||
firstStream := strings.NewReader("ABCFGIIZ")
|
||||
secondStream := strings.NewReader("BDHPQQ")
|
||||
thirdStream := strings.NewReader("ABEFJN")
|
||||
|
||||
expected := "AABBBCDEFFGHIIJNPQQZ"
|
||||
actual := multiwayMerge(firstStream, secondStream, thirdStream)
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package sorting
|
||||
package priority_queue
|
||||
|
||||
type PriorityQueue[T any] interface {
|
||||
top() T
|
||||
|
@ -34,7 +34,13 @@ func (pq *priorityQueue[T]) top() T {
|
|||
}
|
||||
|
||||
func (pq *priorityQueue[T]) insert(item T) {
|
||||
// TODO: increase by square when capacity is full
|
||||
// We can ignore "resize optimization":
|
||||
// append function will create new array for new slice
|
||||
// with doubled capacity when needed
|
||||
// https://github.com/golang/go/blob/master/src/runtime/slice.go#L188
|
||||
// https://go.dev/blog/slices-intro
|
||||
// https://go.dev/play/p/OKtCFskbp2t
|
||||
// https://stackoverflow.com/questions/23531737/how-the-slice-is-enlarged-by-append-is-the-capacity-always-doubled
|
||||
pq.heap = append(pq.heap, item)
|
||||
pq.n++
|
||||
pq.swim(pq.n)
|
||||
|
@ -60,24 +66,6 @@ func (pq *priorityQueue[_]) isEmpty() bool {
|
|||
return pq.n == 0
|
||||
}
|
||||
|
||||
func (pq *priorityQueue[_]) swap(i, j int) {
|
||||
pq.heap[i], pq.heap[j] = pq.heap[j], pq.heap[i]
|
||||
}
|
||||
|
||||
func (pq *priorityQueue[T]) swim(child int) {
|
||||
// Until we reach top of the heap
|
||||
// and parent node is less than current child
|
||||
for child > 1 && pq.less(pq.heap[child/2], pq.heap[child]) {
|
||||
|
||||
// We swap parent with the child
|
||||
pq.swap(child/2, child)
|
||||
|
||||
// Parent node becomes new child
|
||||
// for next iteration
|
||||
child = child / 2
|
||||
}
|
||||
}
|
||||
|
||||
func (pq *priorityQueue[T]) sink(parent int) {
|
||||
// While parent has some children
|
||||
for 2*parent <= pq.n {
|
||||
|
@ -105,3 +93,21 @@ func (pq *priorityQueue[T]) sink(parent int) {
|
|||
parent = child
|
||||
}
|
||||
}
|
||||
|
||||
func (pq *priorityQueue[T]) swim(child int) {
|
||||
// Until we reach top of the heap
|
||||
// and parent node is less than current child
|
||||
for child > 1 && pq.less(pq.heap[child/2], pq.heap[child]) {
|
||||
|
||||
// We swap parent with the child
|
||||
pq.swap(child/2, child)
|
||||
|
||||
// Parent node becomes new child
|
||||
// for next iteration
|
||||
child = child / 2
|
||||
}
|
||||
}
|
||||
|
||||
func (pq *priorityQueue[T]) swap(i, j int) {
|
||||
pq.heap[i], pq.heap[j] = pq.heap[j], pq.heap[i]
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package sorting
|
||||
package priority_queue
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
@ -6,15 +6,13 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var intCompare = func(t1, t2 int) bool { return t1 < t2 }
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
pq := NewPQ(intCompare)
|
||||
pq := NewPQ(intDescending)
|
||||
assert.NotNil(t, pq)
|
||||
}
|
||||
|
||||
func TestSize(t *testing.T) {
|
||||
pq := NewPQ(intCompare)
|
||||
pq := NewPQ(intDescending)
|
||||
assert.Equal(t, 0, pq.size())
|
||||
pq.insert(1)
|
||||
assert.Equal(t, 1, pq.size())
|
||||
|
@ -24,14 +22,14 @@ func TestSize(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
pq := NewPQ(intCompare)
|
||||
pq := NewPQ(intDescending)
|
||||
assert.Equal(t, true, pq.isEmpty())
|
||||
pq.insert(1)
|
||||
assert.Equal(t, false, pq.isEmpty())
|
||||
}
|
||||
|
||||
func TestInsert(t *testing.T) {
|
||||
pq := NewPQ(intCompare)
|
||||
pq := NewPQ(intDescending)
|
||||
pq.insert(1)
|
||||
assert.Equal(t, 1, pq.top())
|
||||
pq.insert(4)
|
||||
|
@ -43,7 +41,7 @@ func TestInsert(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
pq := NewPQ(intCompare)
|
||||
pq := NewPQ(intDescending)
|
||||
pq.insert(1)
|
||||
pq.insert(2)
|
||||
pq.insert(6)
|
3
sorting/priority_queue/testing.go
Normal file
3
sorting/priority_queue/testing.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package priority_queue
|
||||
|
||||
var intDescending = func(t1, t2 int) bool { return t1 < t2 }
|
|
@ -1,3 +1,5 @@
|
|||
package sorting
|
||||
|
||||
// TODO: compare function should receive pointers to slice elements
|
||||
// to prevent unnecessary coping
|
||||
type SliceSorter[T any] func([]T, func(T, T) bool)
|
||||
|
|
|
@ -73,3 +73,13 @@ func BenchmarkQuick(b *testing.B) {
|
|||
BenchmarkSort(10000, Quick[int])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeap(t *testing.T) {
|
||||
CheckSliceSorter(Heap[int])
|
||||
}
|
||||
|
||||
func BenchmarkHeap(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
BenchmarkSort(10000, Heap[int])
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue