From f31b72c2f783f4815b04044f640b5129fd76209d Mon Sep 17 00:00:00 2001 From: Suyono Date: Mon, 7 Aug 2023 13:03:35 +1000 Subject: [PATCH] avl insertion --- .gitignore | 1 + .tool-versions | 1 + LICENSE | 2 +- avl.go | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ avl_test.go | 88 ++++++++++++++++++++++++++++++++++++++ go.mod | 3 ++ tree.go | 33 ++++++++++++++ 7 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 avl.go create mode 100644 avl_test.go create mode 100644 go.mod create mode 100644 tree.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a09c56d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..bf7f6cc --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.20.6 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/avl.go b/avl.go new file mode 100644 index 0000000..9c3c37d --- /dev/null +++ b/avl.go @@ -0,0 +1,114 @@ +package avl + +type Comparable interface { + Compare(c Comparable) int +} + +type Node struct { + key Comparable + height int + left, right *Node +} + +func NewNode(key Comparable) *Node { + return &Node{ + key: key, + height: 1, + } +} + +func Insert(node *Node, key Comparable) *Node { + if node == nil { + return NewNode(key) + } + + if key.Compare(node.key) < 0 { + node.left = Insert(node.left, key) + } else if key.Compare(node.key) > 0 { + node.right = Insert(node.right, key) + } else { + panic("duplicate key") // duplicate keys not allowed + } + + node.height = 1 + max(height(node.left), height(node.right)) + + balance := getBalance(node) + + // left-left case + if balance > 1 && key.Compare(node.left.key) < 0 { + return rightRotate(node) + } + + // right-right case + if balance < -1 && key.Compare(node.right.key) > 0 { + return leftRotate(node) + } + + // left-right case + if balance > 1 && key.Compare(node.left.key) > 0 { + node.left = leftRotate(node.left) + return rightRotate(node) + } + + // right-left case + if balance < -1 && key.Compare(node.right.key) < 0 { + node.right = rightRotate(node.right) + return leftRotate(node) + } + + return node +} + +func max(a, b int) int { + if a > b { + return a + } else { + return b + } +} + +func height(node *Node) int { + if node == nil { + return 0 + } + + return node.height +} + +func getBalance(node *Node) int { + if node == nil { + return 0 + } + + return height(node.left) - height(node.right) +} + +func rightRotate(y *Node) *Node { + var ( + x *Node = y.left + T2 *Node = x.right + ) + + x.right = y + y.left = T2 + + y.height = max(height(y.left), height(y.right)) + 1 + x.height = max(height(x.left), height(x.right)) + 1 + + return x +} + +func leftRotate(x *Node) *Node { + var ( + y *Node = x.right + T2 *Node = y.left + ) + + y.left = x + x.right = T2 + + x.height = max(height(x.left), height(x.right)) + 1 + y.height = max(height(y.left), height(y.right)) + 1 + + return y +} diff --git a/avl_test.go b/avl_test.go new file mode 100644 index 0000000..b5e035e --- /dev/null +++ b/avl_test.go @@ -0,0 +1,88 @@ +package avl + +import ( + "bytes" + "fmt" + "testing" +) + +type testKey int + +func (k testKey) Compare(c Comparable) int { + var ( + ck testKey + ok bool + ) + + if ck, ok = c.(testKey); !ok { + panic("unexpected type") + } + + return int(k - ck) +} + +func (k testKey) String() string { + return fmt.Sprintf("%d", int(k)) +} + +func preOrder(b *bytes.Buffer, node *Node) { + if node != nil { + _, _ = fmt.Fprint(b, node.key, " ") + preOrder(b, node.left) + preOrder(b, node.right) + } +} + +func TestInsert(t *testing.T) { + var ( + root *Node + b *bytes.Buffer + ) + + root = Insert(root, testKey(10)) + root = Insert(root, testKey(20)) + root = Insert(root, testKey(30)) + root = Insert(root, testKey(40)) + root = Insert(root, testKey(50)) + root = Insert(root, testKey(25)) + + b = bytes.NewBuffer([]byte{}) + preOrder(b, root) + + //The constructed AVL Tree would be + // 30 + // / \ + // 20 40 + // / \ \ + // 10 25 50 + + t.Log("preorder traversal of the tree") + t.Log(b) +} + +func TestInsert2(t *testing.T) { + var ( + tree *Tree + keys = []testKey{10, 20, 25, 30, 40, 50} + out []Comparable + ) + + tree = new(Tree) + tree.Insert(keys[0]) + tree.Insert(keys[1]) + tree.Insert(keys[3]) + tree.Insert(keys[4]) + tree.Insert(keys[5]) + tree.Insert(keys[2]) + + out = tree.Slice() + for i, k := range out { + if ck, ok := k.(testKey); ok { + if ck != keys[i] { + t.Fatal("mismatch data") + } + } else { + t.Fatal("invalid data type") + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4fce3be --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitea.suyono.dev/suyono/avl + +go 1.20 diff --git a/tree.go b/tree.go new file mode 100644 index 0000000..aede8de --- /dev/null +++ b/tree.go @@ -0,0 +1,33 @@ +package avl + +type Tree struct { + root *Node + num int +} + +func (t *Tree) Insert(key Comparable) { + t.root = Insert(t.root, key) + t.num++ +} + +func (t *Tree) Slice() []Comparable { + retval := make([]Comparable, t.num) + t.slice(retval, 0, t.root) + + return retval +} + +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 +}