added test fot AvlTree and implemented inorder, postorder traversal
Go / build (push) Successful in 34s
Go / build (push) Successful in 34s
This commit is contained in:
@@ -13,12 +13,26 @@
|
|||||||
- [x] Circular Buffer
|
- [x] Circular Buffer
|
||||||
- [ ] Deque (segmented array), ⛔ Not possible in Go
|
- [ ] Deque (segmented array), ⛔ Not possible in Go
|
||||||
|
|
||||||
|
> Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go doc -all ./linear | bat -l go
|
||||||
|
```
|
||||||
|
|
||||||
## Tree — hierarchical, parent/child relationships
|
## Tree — hierarchical, parent/child relationships
|
||||||
|
|
||||||
- [x] Binary Search Tree
|
- [x] Binary Search Tree
|
||||||
- [x] AVL Tree
|
- [x] AVL Tree
|
||||||
- [x] Heap (min/max)
|
- [x] Heap (min/max)
|
||||||
- [ ] Trie
|
- [ ] Trie
|
||||||
|
- [ ] LSM Tree
|
||||||
|
|
||||||
|
> Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for p in ./trees/ ./trees/avl ./trees/heap; do
|
||||||
|
go doc -all "$p"; done | bat -l go
|
||||||
|
```
|
||||||
|
|
||||||
## Graph
|
## Graph
|
||||||
|
|
||||||
@@ -31,6 +45,12 @@
|
|||||||
|
|
||||||
- [x] Union Find
|
- [x] Union Find
|
||||||
|
|
||||||
|
> Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go doc -all ./sets/ | bat -l go
|
||||||
|
```
|
||||||
|
|
||||||
## Probabilistic
|
## Probabilistic
|
||||||
|
|
||||||
- [ ] Bloom filter
|
- [ ] Bloom filter
|
||||||
@@ -47,6 +67,12 @@
|
|||||||
- [ ] Modular Arithmetic
|
- [ ] Modular Arithmetic
|
||||||
- [ ] Sieve of Eratosthenes
|
- [ ] Sieve of Eratosthenes
|
||||||
|
|
||||||
|
> Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go doc -all ./algo | bat -l go
|
||||||
|
```
|
||||||
|
|
||||||
## Heaps & Trees
|
## Heaps & Trees
|
||||||
|
|
||||||
- [x] Priority Queue
|
- [x] Priority Queue
|
||||||
@@ -59,33 +85,7 @@
|
|||||||
- [x] DFS & BFS
|
- [x] DFS & BFS
|
||||||
- [ ] Topological Sort with Kahn's algorithm
|
- [ ] Topological Sort with Kahn's algorithm
|
||||||
|
|
||||||
# Documentation
|
> Documentation
|
||||||
|
|
||||||
> Linear Structures
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go doc -all ./linear | bat -l go
|
|
||||||
```
|
|
||||||
|
|
||||||
> Algorithms
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go doc -all ./algo | bat -l go
|
|
||||||
```
|
|
||||||
|
|
||||||
> Sets
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go doc -all ./sets/ | bat -l go
|
|
||||||
```
|
|
||||||
|
|
||||||
> Trees
|
|
||||||
|
|
||||||
```bash
|
|
||||||
for p in ./trees/ ./trees/avl ./trees/heap; do go doc -all "$p"; done | bat -l go
|
|
||||||
```
|
|
||||||
|
|
||||||
> Graphs
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go doc -all ./graphs | bat -l go
|
go doc -all ./graphs | bat -l go
|
||||||
|
|||||||
@@ -0,0 +1,617 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"datastructures/trees/avl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AvlTree exposes Root and AvlNode.Data/Height, so these tests drive the tree
|
||||||
|
// through Insert and observe it through the two traversals. Inorder of a BST
|
||||||
|
// must come back sorted, which makes it a cheap oracle for the whole structure:
|
||||||
|
// if a rotation dropped or duplicated a node, the sorted comparison catches it.
|
||||||
|
//
|
||||||
|
// Helpers uniqueSorted, equalInts and validBSTPreorder live in
|
||||||
|
// binarySearchTree_test.go, same package.
|
||||||
|
|
||||||
|
func equalStrings(a, b []string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlTraversalsOnEmptyTree(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
|
||||||
|
if got := tree.TraverseInorder(); len(got) != 0 {
|
||||||
|
t.Errorf("TraverseInorder() on empty tree = %v, want empty", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := tree.TraversePreorder(); len(got) != 0 {
|
||||||
|
t.Errorf("TraversePreorder() on empty tree = %v, want empty", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlTraversalsOnSingleNode(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
tree.Insert(42)
|
||||||
|
|
||||||
|
if got := tree.TraverseInorder(); !equalInts(got, []int{42}) {
|
||||||
|
t.Errorf("TraverseInorder() = %v, want [42]", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := tree.TraversePreorder(); !equalInts(got, []int{42}) {
|
||||||
|
t.Errorf("TraversePreorder() = %v, want [42]", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlInorderIsSorted(t *testing.T) {
|
||||||
|
inputs := [][]int{
|
||||||
|
{5, 3, 8, 1, 4, 7, 9},
|
||||||
|
{1, 2, 3, 4, 5, 6, 7}, // ascending, forces left rotations
|
||||||
|
{7, 6, 5, 4, 3, 2, 1}, // descending, forces right rotations
|
||||||
|
{10, 20, 30, 40, 50, 25}, // mixes both, hits the double rotations
|
||||||
|
{-5, 0, -10, 3, -3, 8}, // negatives
|
||||||
|
{42}, // single node
|
||||||
|
{1, 1, 1, 1}, // all duplicates
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, in := range inputs {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range in {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := tree.TraverseInorder()
|
||||||
|
want := uniqueSorted(in)
|
||||||
|
|
||||||
|
if !equalInts(got, want) {
|
||||||
|
t.Errorf("insert %v: TraverseInorder() = %v, want %v", in, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The insert sequence below is worked out by hand: it ends as
|
||||||
|
// 30(20(10,25), 40(_,50)) after a left-right double rotation at the root, so
|
||||||
|
// the exact preorder pins down the tree shape, not just its contents.
|
||||||
|
func TestAvlPreorderMatchesKnownShape(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range []int{10, 20, 30, 40, 50, 25} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []int{30, 20, 10, 25, 40, 50}
|
||||||
|
if got := tree.TraversePreorder(); !equalInts(got, want) {
|
||||||
|
t.Errorf("TraversePreorder() = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantInorder := []int{10, 20, 25, 30, 40, 50}
|
||||||
|
if got := tree.TraverseInorder(); !equalInts(got, wantInorder) {
|
||||||
|
t.Errorf("TraverseInorder() = %v, want %v", got, wantInorder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlPreorderStartsAtRoot(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range []int{50, 25, 75, 10, 30, 60, 90} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := tree.TraversePreorder()
|
||||||
|
if len(got) == 0 {
|
||||||
|
t.Fatal("TraversePreorder() returned nothing")
|
||||||
|
}
|
||||||
|
|
||||||
|
if got[0] != tree.Root.Data {
|
||||||
|
t.Errorf("TraversePreorder()[0] = %d, want root %d", got[0], tree.Root.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlPreorderIsValidBSTPreorder(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range []int{15, 3, 27, 1, 9, 20, 40, 6, 12, 35, 50} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
pre := tree.TraversePreorder()
|
||||||
|
if !validBSTPreorder(pre) {
|
||||||
|
t.Errorf("TraversePreorder() = %v is not a valid BST pre-order", pre)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both traversals must visit every node exactly once, so they agree on length
|
||||||
|
// and on contents once sorted.
|
||||||
|
func TestAvlTraversalsVisitSameNodes(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range []int{8, 2, 19, 4, 11, 25, 1, 6, 30, 15} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
inorder := tree.TraverseInorder()
|
||||||
|
preorder := tree.TraversePreorder()
|
||||||
|
|
||||||
|
if len(inorder) != len(preorder) {
|
||||||
|
t.Fatalf("length mismatch: inorder %d, preorder %d", len(inorder), len(preorder))
|
||||||
|
}
|
||||||
|
|
||||||
|
sortedPre := append([]int(nil), preorder...)
|
||||||
|
sort.Ints(sortedPre)
|
||||||
|
|
||||||
|
if !equalInts(inorder, sortedPre) {
|
||||||
|
t.Errorf("traversals disagree: inorder %v, sorted preorder %v", inorder, sortedPre)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlTraversalsDoNotMutateTree(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range []int{5, 2, 9, 1, 7, 12} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootBefore := tree.Root
|
||||||
|
heightBefore := tree.Root.Height
|
||||||
|
|
||||||
|
first := tree.TraverseInorder()
|
||||||
|
second := tree.TraverseInorder()
|
||||||
|
firstPre := tree.TraversePreorder()
|
||||||
|
secondPre := tree.TraversePreorder()
|
||||||
|
|
||||||
|
if !equalInts(first, second) {
|
||||||
|
t.Errorf("repeated TraverseInorder() differ: %v then %v", first, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !equalInts(firstPre, secondPre) {
|
||||||
|
t.Errorf("repeated TraversePreorder() differ: %v then %v", firstPre, secondPre)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.Root != rootBefore || tree.Root.Height != heightBefore {
|
||||||
|
t.Error("traversal changed the tree root")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The constraint moved from a numeric-only interface to cmp.Ordered, so strings
|
||||||
|
// have to work end to end.
|
||||||
|
func TestAvlStringKeys(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[string]()
|
||||||
|
for _, w := range []string{"pear", "apple", "fig", "date", "banana", "cherry"} {
|
||||||
|
tree.Insert(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []string{"apple", "banana", "cherry", "date", "fig", "pear"}
|
||||||
|
if got := tree.TraverseInorder(); !equalStrings(got, want) {
|
||||||
|
t.Errorf("TraverseInorder() = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := tree.TraversePreorder(); len(got) != len(want) {
|
||||||
|
t.Errorf("TraversePreorder() returned %d values, want %d", len(got), len(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlFloatKeys(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[float64]()
|
||||||
|
for _, v := range []float64{3.5, -1.25, 0, 9.75, 2.5} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []float64{-1.25, 0, 2.5, 3.5, 9.75}
|
||||||
|
got := tree.TraverseInorder()
|
||||||
|
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf("TraverseInorder() = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range want {
|
||||||
|
if got[i] != want[i] {
|
||||||
|
t.Fatalf("TraverseInorder() = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Degenerate insert orders are what the rotations exist for: 1..n ascending
|
||||||
|
// would be a linked list in a plain BST. Height must stay within the AVL bound
|
||||||
|
// of 1.44*log2(n+2).
|
||||||
|
func TestAvlStaysBalancedOnSortedInput(t *testing.T) {
|
||||||
|
const count = 1000
|
||||||
|
|
||||||
|
for _, name := range []string{"ascending", "descending"} {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if name == "ascending" {
|
||||||
|
tree.Insert(i)
|
||||||
|
} else {
|
||||||
|
tree.Insert(count - i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
got := tree.TraverseInorder()
|
||||||
|
if len(got) != count {
|
||||||
|
t.Fatalf("%s: TraverseInorder() returned %d values, want %d", name, len(got), count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sort.IntsAreSorted(got) {
|
||||||
|
t.Fatalf("%s: TraverseInorder() is not sorted", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxHeight := int(1.44 * math.Log2(float64(count+2)))
|
||||||
|
if tree.Root.Height > maxHeight {
|
||||||
|
t.Errorf("%s: height %d exceeds AVL bound %d", name, tree.Root.Height, maxHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlTraversalsRandomized(t *testing.T) {
|
||||||
|
const inserts = 3000
|
||||||
|
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
var raw []int
|
||||||
|
|
||||||
|
for i := 0; i < inserts; i++ {
|
||||||
|
v := rand.Intn(750)
|
||||||
|
tree.Insert(v)
|
||||||
|
raw = append(raw, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := uniqueSorted(raw)
|
||||||
|
|
||||||
|
if got := tree.TraverseInorder(); !equalInts(got, want) {
|
||||||
|
t.Errorf("TraverseInorder() has %d values, want %d unique", len(got), len(want))
|
||||||
|
}
|
||||||
|
|
||||||
|
pre := tree.TraversePreorder()
|
||||||
|
if len(pre) != len(want) {
|
||||||
|
t.Errorf("TraversePreorder() returned %d values, want %d", len(pre), len(want))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validBSTPreorder(pre) {
|
||||||
|
t.Error("TraversePreorder() is not a valid BST pre-order")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Insert ---
|
||||||
|
|
||||||
|
// checkAvlInvariants walks the tree recomputing height from the leaves up and
|
||||||
|
// returns the true height. It fails the test if any node's stored Height is
|
||||||
|
// stale, if any balance factor leaves [-1,1], or if the BST ordering is broken.
|
||||||
|
func checkAvlInvariants(t *testing.T, node *avl.AvlNode[int], low, high int) int {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if node == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.Data <= low || node.Data >= high {
|
||||||
|
t.Errorf("node %d violates BST ordering, must be in (%d, %d)", node.Data, low, high)
|
||||||
|
}
|
||||||
|
|
||||||
|
leftHeight := checkAvlInvariants(t, node.Left, low, node.Data)
|
||||||
|
rightHeight := checkAvlInvariants(t, node.Right, node.Data, high)
|
||||||
|
|
||||||
|
realHeight := 1 + max(leftHeight, rightHeight)
|
||||||
|
if node.Height != realHeight {
|
||||||
|
t.Errorf("node %d has stored Height %d, recomputed %d", node.Data, node.Height, realHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
if balance := leftHeight - rightHeight; balance < -1 || balance > 1 {
|
||||||
|
t.Errorf("node %d has balance factor %d, want within [-1,1]", node.Data, balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
return realHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each sequence below triggers exactly one of the four rebalancing cases, and
|
||||||
|
// all four settle into the same 2(1,3) shape.
|
||||||
|
func TestAvlInsertRotationCases(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
insert []int
|
||||||
|
}{
|
||||||
|
{"left-left", []int{3, 2, 1}},
|
||||||
|
{"right-right", []int{1, 2, 3}},
|
||||||
|
{"left-right", []int{3, 1, 2}},
|
||||||
|
{"right-left", []int{1, 3, 2}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range c.insert {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := tree.TraversePreorder(); !equalInts(got, []int{2, 1, 3}) {
|
||||||
|
t.Errorf("%s: inserting %v gave preorder %v, want [2 1 3]", c.name, c.insert, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.Root.Height != 2 {
|
||||||
|
t.Errorf("%s: root height = %d, want 2", c.name, tree.Root.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAvlInvariants(t, tree.Root, math.MinInt, math.MaxInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlInsertIgnoresDuplicates(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
|
||||||
|
for _, v := range []int{5, 3, 8, 5, 3, 8, 5} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []int{3, 5, 8}
|
||||||
|
if got := tree.TraverseInorder(); !equalInts(got, want) {
|
||||||
|
t.Errorf("TraverseInorder() = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.Root.Height != 2 {
|
||||||
|
t.Errorf("root height = %d, want 2 (duplicates must not deepen the tree)", tree.Root.Height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlInsertSetsRootAndLeafHeights(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
|
||||||
|
if tree.Root != nil {
|
||||||
|
t.Fatal("NewAvlTree() should start with a nil Root")
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.Insert(1)
|
||||||
|
|
||||||
|
if tree.Root == nil {
|
||||||
|
t.Fatal("Insert() did not set Root")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.Root.Data != 1 {
|
||||||
|
t.Errorf("Root.Data = %d, want 1", tree.Root.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.Root.Height != 1 {
|
||||||
|
t.Errorf("leaf Height = %d, want 1", tree.Root.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.Root.Left != nil || tree.Root.Right != nil {
|
||||||
|
t.Error("a single-node tree should have no children")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert order must not matter: the same set of keys always produces the same
|
||||||
|
// AVL tree only if the rotations are right, but at minimum every order has to
|
||||||
|
// yield the same contents and hold the invariants.
|
||||||
|
func TestAvlInsertOrderIndependence(t *testing.T) {
|
||||||
|
orders := [][]int{
|
||||||
|
{1, 2, 3, 4, 5, 6, 7},
|
||||||
|
{7, 6, 5, 4, 3, 2, 1},
|
||||||
|
{4, 2, 6, 1, 3, 5, 7},
|
||||||
|
{1, 7, 2, 6, 3, 5, 4},
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []int{1, 2, 3, 4, 5, 6, 7}
|
||||||
|
|
||||||
|
for _, order := range orders {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range order {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := tree.TraverseInorder(); !equalInts(got, want) {
|
||||||
|
t.Errorf("insert %v: TraverseInorder() = %v, want %v", order, got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAvlInvariants(t, tree.Root, math.MinInt, math.MaxInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlInsertKeepsInvariantsRandomized(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
|
||||||
|
for i := 0; i < 2000; i++ {
|
||||||
|
tree.Insert(rand.Intn(5000))
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAvlInvariants(t, tree.Root, math.MinInt, math.MaxInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Find ---
|
||||||
|
|
||||||
|
func TestAvlFindOnEmptyTree(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
|
||||||
|
node, ok := tree.Find(1)
|
||||||
|
if ok {
|
||||||
|
t.Error("Find() on an empty tree returned true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if node == nil {
|
||||||
|
t.Fatal("Find() returned a nil node; the contract is a zero node and false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.Data != 0 {
|
||||||
|
t.Errorf("Find() miss returned Data %d, want the zero value", node.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlFindLocatesEveryInsertedValue(t *testing.T) {
|
||||||
|
values := []int{50, 25, 75, 10, 30, 60, 90, 5, 15, 27, 40}
|
||||||
|
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range values {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range values {
|
||||||
|
node, ok := tree.Find(v)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Find(%d) = false, want true", v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.Data != v {
|
||||||
|
t.Errorf("Find(%d) returned node holding %d", v, node.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlFindMissingValues(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range []int{50, 25, 75, 10, 30} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, missing := range []int{-1, 0, 11, 26, 49, 51, 74, 76, 1000} {
|
||||||
|
if _, ok := tree.Find(missing); ok {
|
||||||
|
t.Errorf("Find(%d) = true, want false", missing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find walks the tree by reassigning t.Root on a value receiver. That is only
|
||||||
|
// safe because the receiver is a copy; this test would catch a switch to a
|
||||||
|
// pointer receiver, which would leave the tree truncated after one lookup.
|
||||||
|
func TestAvlFindDoesNotMutateTree(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range []int{50, 25, 75, 10, 30, 60, 90} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
before := tree.TraverseInorder()
|
||||||
|
rootBefore := tree.Root
|
||||||
|
|
||||||
|
tree.Find(90)
|
||||||
|
tree.Find(10)
|
||||||
|
tree.Find(-1) // miss, walks all the way to a nil child
|
||||||
|
|
||||||
|
if tree.Root != rootBefore {
|
||||||
|
t.Error("Find() moved the tree Root")
|
||||||
|
}
|
||||||
|
|
||||||
|
if after := tree.TraverseInorder(); !equalInts(before, after) {
|
||||||
|
t.Errorf("tree changed after Find(): %v then %v", before, after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlFindStringKeys(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[string]()
|
||||||
|
for _, w := range []string{"pear", "apple", "fig"} {
|
||||||
|
tree.Insert(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
if node, ok := tree.Find("apple"); !ok || node.Data != "apple" {
|
||||||
|
t.Errorf(`Find("apple") = %v, %v; want the apple node and true`, node, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := tree.Find("kiwi"); ok {
|
||||||
|
t.Error(`Find("kiwi") = true, want false`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Display ---
|
||||||
|
|
||||||
|
// captureAvlDisplay runs tree.Display() with stdout redirected and returns the
|
||||||
|
// printed lines. captureDisplay in binarySearchTree_test.go is for BSTree.
|
||||||
|
func captureAvlDisplay(tree *avl.AvlTree[int]) []string {
|
||||||
|
old := os.Stdout
|
||||||
|
r, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
tree.Display()
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = old
|
||||||
|
|
||||||
|
var lines []string
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
lines = append(lines, scanner.Text())
|
||||||
|
}
|
||||||
|
_, _ = io.Copy(io.Discard, r)
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlDisplayEmptyTree(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
|
||||||
|
lines := captureAvlDisplay(tree)
|
||||||
|
if len(lines) != 1 || lines[0] != "<empty>" {
|
||||||
|
t.Errorf("Display() on empty tree printed %q, want [<empty>]", lines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAvlDisplaySingleNode(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
tree.Insert(7)
|
||||||
|
|
||||||
|
lines := captureAvlDisplay(tree)
|
||||||
|
if len(lines) != 1 || lines[0] != "7" {
|
||||||
|
t.Errorf("Display() printed %q, want [7] with no connector", lines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Golden output for the hand-checked 30(20(10,25), 40(_,50)) tree. Display
|
||||||
|
// draws it rotated 90° left, so the right subtree sits on top.
|
||||||
|
func TestAvlDisplayKnownTree(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range []int{10, 20, 30, 40, 50, 25} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []string{
|
||||||
|
" ┌── 50",
|
||||||
|
" ┌── 40",
|
||||||
|
"30",
|
||||||
|
" │ ┌── 25",
|
||||||
|
" └── 20",
|
||||||
|
" └── 10",
|
||||||
|
}
|
||||||
|
|
||||||
|
got := captureAvlDisplay(tree)
|
||||||
|
if !equalStrings(got, want) {
|
||||||
|
t.Errorf("Display() printed:\n%s\nwant:\n%s",
|
||||||
|
strings.Join(got, "\n"), strings.Join(want, "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotated 90° left means reading the lines top to bottom yields a reverse
|
||||||
|
// in-order walk, which ties Display back to the traversals.
|
||||||
|
func TestAvlDisplayReadsAsReverseInorder(t *testing.T) {
|
||||||
|
tree := avl.NewAvlTree[int]()
|
||||||
|
for _, v := range []int{15, 3, 27, 1, 9, 20, 40, 6, 12, 35, 50} {
|
||||||
|
tree.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := captureAvlDisplay(tree)
|
||||||
|
|
||||||
|
var printed []int
|
||||||
|
for _, line := range lines {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
value, err := strconv.Atoi(fields[len(fields)-1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not read a value from Display() line %q: %v", line, err)
|
||||||
|
}
|
||||||
|
printed = append(printed, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
inorder := tree.TraverseInorder()
|
||||||
|
|
||||||
|
want := make([]int, 0, len(inorder))
|
||||||
|
for i := len(inorder) - 1; i >= 0; i-- {
|
||||||
|
want = append(want, inorder[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !equalInts(printed, want) {
|
||||||
|
t.Errorf("Display() values top to bottom = %v, want reverse inorder %v", printed, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
+80
-15
@@ -1,31 +1,30 @@
|
|||||||
package avl
|
package avl
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"cmp"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
type numericTypes interface {
|
"datastructures/linear"
|
||||||
int | int8 | int16 | int32 | int64 |
|
)
|
||||||
uint | uint8 | uint16 | uint32 | uint64 |
|
|
||||||
float32 | float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type AvlNode[T numericTypes] struct {
|
type AvlNode[T cmp.Ordered] struct {
|
||||||
Left *AvlNode[T]
|
Left *AvlNode[T]
|
||||||
Right *AvlNode[T]
|
Right *AvlNode[T]
|
||||||
Data T
|
Data T
|
||||||
Height int
|
Height int
|
||||||
}
|
}
|
||||||
|
|
||||||
type AvlTree[T numericTypes] struct {
|
type AvlTree[T cmp.Ordered] struct {
|
||||||
Root *AvlNode[T]
|
Root *AvlNode[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAvlTree() -> creates an AVL tree
|
// NewAvlTree() -> creates an AVL tree
|
||||||
func NewAvlTree[T numericTypes]() *AvlTree[T] {
|
func NewAvlTree[T cmp.Ordered]() *AvlTree[T] {
|
||||||
return &AvlTree[T]{}
|
return &AvlTree[T]{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// height() -> 0 if root
|
// height() -> 0 if root
|
||||||
func height[T numericTypes](n *AvlNode[T]) int {
|
func height[T cmp.Ordered](n *AvlNode[T]) int {
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -33,12 +32,12 @@ func height[T numericTypes](n *AvlNode[T]) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// updateHeight()
|
// updateHeight()
|
||||||
func updateHeight[T numericTypes](node *AvlNode[T]) {
|
func updateHeight[T cmp.Ordered](node *AvlNode[T]) {
|
||||||
node.Height = 1 + max(height(node.Left), height(node.Right))
|
node.Height = 1 + max(height(node.Left), height(node.Right))
|
||||||
}
|
}
|
||||||
|
|
||||||
// balanceFactor() -> only 0 , -1 ,-2 ok
|
// balanceFactor() -> only 0 , -1 ,-2 ok
|
||||||
func balanceFactor[T numericTypes](n *AvlNode[T]) int {
|
func balanceFactor[T cmp.Ordered](n *AvlNode[T]) int {
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -47,7 +46,7 @@ func balanceFactor[T numericTypes](n *AvlNode[T]) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rotateRight() -> fixes a left heavy subtree, returns the new subtree root
|
// rotateRight() -> fixes a left heavy subtree, returns the new subtree root
|
||||||
func rotateRight[T numericTypes](y *AvlNode[T]) *AvlNode[T] {
|
func rotateRight[T cmp.Ordered](y *AvlNode[T]) *AvlNode[T] {
|
||||||
x := y.Left
|
x := y.Left
|
||||||
b := x.Right
|
b := x.Right
|
||||||
|
|
||||||
@@ -61,7 +60,7 @@ func rotateRight[T numericTypes](y *AvlNode[T]) *AvlNode[T] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rotateLeft() -> fixes a right heavy subtree, returns the new subtree root
|
// rotateLeft() -> fixes a right heavy subtree, returns the new subtree root
|
||||||
func rotateLeft[T numericTypes](x *AvlNode[T]) *AvlNode[T] {
|
func rotateLeft[T cmp.Ordered](x *AvlNode[T]) *AvlNode[T] {
|
||||||
y := x.Right
|
y := x.Right
|
||||||
b := y.Left
|
b := y.Left
|
||||||
|
|
||||||
@@ -79,7 +78,7 @@ func (tree *AvlTree[T]) Insert(data T) {
|
|||||||
tree.Root = insertHelper(tree.Root, data)
|
tree.Root = insertHelper(tree.Root, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertHelper[T numericTypes](node *AvlNode[T], data T) *AvlNode[T] {
|
func insertHelper[T cmp.Ordered](node *AvlNode[T], data T) *AvlNode[T] {
|
||||||
// normal BST insert
|
// normal BST insert
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return &AvlNode[T]{Data: data, Height: 1}
|
return &AvlNode[T]{Data: data, Height: 1}
|
||||||
@@ -136,6 +135,72 @@ func (t AvlTree[T]) Find(val T) (*AvlNode[T], bool) {
|
|||||||
return zero, false
|
return zero, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Traversals
|
||||||
|
|
||||||
|
// TraverseInorder() -> in order traversal of avl tree starting at root, returns a slice of
|
||||||
|
// AvlNode.Data. Iterative, no recursion.
|
||||||
|
func (tree *AvlTree[T]) TraverseInorder() []T {
|
||||||
|
var list []T
|
||||||
|
stack := linear.Stack[*AvlNode[T]]{}
|
||||||
|
|
||||||
|
current := tree.Root
|
||||||
|
|
||||||
|
// every node on the stack has been walked past but not emitted yet
|
||||||
|
for current != nil || stack.Size() > 0 {
|
||||||
|
|
||||||
|
// dive left
|
||||||
|
for current != nil {
|
||||||
|
stack.Push(current)
|
||||||
|
current = current.Left
|
||||||
|
}
|
||||||
|
|
||||||
|
node, err := stack.Pop()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, node.Data)
|
||||||
|
|
||||||
|
current = node.Right
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraversePreorder() -> pre order traversal of avl tree starting at root, returns a slice of
|
||||||
|
// AvlNode.Data
|
||||||
|
func (tree *AvlTree[T]) TraversePreorder() []T {
|
||||||
|
var list []T
|
||||||
|
stack := linear.Stack[*AvlNode[T]]{}
|
||||||
|
|
||||||
|
if tree.Root == nil {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.Push(tree.Root)
|
||||||
|
|
||||||
|
for stack.Size() > 0 {
|
||||||
|
|
||||||
|
root, err := stack.Pop()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, root.Data)
|
||||||
|
|
||||||
|
if root.Right != nil {
|
||||||
|
stack.Push(root.Right)
|
||||||
|
}
|
||||||
|
|
||||||
|
if root.Left != nil {
|
||||||
|
stack.Push(root.Left)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
// Display() -> draws the tree rotated 90° left: right subtree on top,
|
// Display() -> draws the tree rotated 90° left: right subtree on top,
|
||||||
// left subtree on the bottom, connected with box-drawing characters.
|
// left subtree on the bottom, connected with box-drawing characters.
|
||||||
// Iterative , no recursion.
|
// Iterative , no recursion.
|
||||||
|
|||||||
Reference in New Issue
Block a user