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
|
||||
- [ ] Deque (segmented array), ⛔ Not possible in Go
|
||||
|
||||
> Documentation
|
||||
|
||||
```bash
|
||||
go doc -all ./linear | bat -l go
|
||||
```
|
||||
|
||||
## Tree — hierarchical, parent/child relationships
|
||||
|
||||
- [x] Binary Search Tree
|
||||
- [x] AVL Tree
|
||||
- [x] Heap (min/max)
|
||||
- [ ] Trie
|
||||
- [ ] LSM Tree
|
||||
|
||||
> Documentation
|
||||
|
||||
```bash
|
||||
for p in ./trees/ ./trees/avl ./trees/heap; do
|
||||
go doc -all "$p"; done | bat -l go
|
||||
```
|
||||
|
||||
## Graph
|
||||
|
||||
@@ -31,6 +45,12 @@
|
||||
|
||||
- [x] Union Find
|
||||
|
||||
> Documentation
|
||||
|
||||
```bash
|
||||
go doc -all ./sets/ | bat -l go
|
||||
```
|
||||
|
||||
## Probabilistic
|
||||
|
||||
- [ ] Bloom filter
|
||||
@@ -47,6 +67,12 @@
|
||||
- [ ] Modular Arithmetic
|
||||
- [ ] Sieve of Eratosthenes
|
||||
|
||||
> Documentation
|
||||
|
||||
```bash
|
||||
go doc -all ./algo | bat -l go
|
||||
```
|
||||
|
||||
## Heaps & Trees
|
||||
|
||||
- [x] Priority Queue
|
||||
@@ -59,33 +85,7 @@
|
||||
- [x] DFS & BFS
|
||||
- [ ] Topological Sort with Kahn's algorithm
|
||||
|
||||
# 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
|
||||
> Documentation
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
|
||||
type numericTypes interface {
|
||||
int | int8 | int16 | int32 | int64 |
|
||||
uint | uint8 | uint16 | uint32 | uint64 |
|
||||
float32 | float64
|
||||
}
|
||||
"datastructures/linear"
|
||||
)
|
||||
|
||||
type AvlNode[T numericTypes] struct {
|
||||
type AvlNode[T cmp.Ordered] struct {
|
||||
Left *AvlNode[T]
|
||||
Right *AvlNode[T]
|
||||
Data T
|
||||
Height int
|
||||
}
|
||||
|
||||
type AvlTree[T numericTypes] struct {
|
||||
type AvlTree[T cmp.Ordered] struct {
|
||||
Root *AvlNode[T]
|
||||
}
|
||||
|
||||
// NewAvlTree() -> creates an AVL tree
|
||||
func NewAvlTree[T numericTypes]() *AvlTree[T] {
|
||||
func NewAvlTree[T cmp.Ordered]() *AvlTree[T] {
|
||||
return &AvlTree[T]{}
|
||||
}
|
||||
|
||||
// height() -> 0 if root
|
||||
func height[T numericTypes](n *AvlNode[T]) int {
|
||||
func height[T cmp.Ordered](n *AvlNode[T]) int {
|
||||
if n == nil {
|
||||
return 0
|
||||
}
|
||||
@@ -33,12 +32,12 @@ func height[T numericTypes](n *AvlNode[T]) int {
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
func rotateRight[T numericTypes](y *AvlNode[T]) *AvlNode[T] {
|
||||
func rotateRight[T cmp.Ordered](y *AvlNode[T]) *AvlNode[T] {
|
||||
x := y.Left
|
||||
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
|
||||
func rotateLeft[T numericTypes](x *AvlNode[T]) *AvlNode[T] {
|
||||
func rotateLeft[T cmp.Ordered](x *AvlNode[T]) *AvlNode[T] {
|
||||
y := x.Right
|
||||
b := y.Left
|
||||
|
||||
@@ -79,7 +78,7 @@ func (tree *AvlTree[T]) Insert(data T) {
|
||||
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
|
||||
if node == nil {
|
||||
return &AvlNode[T]{Data: data, Height: 1}
|
||||
@@ -136,6 +135,72 @@ func (t AvlTree[T]) Find(val T) (*AvlNode[T], bool) {
|
||||
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,
|
||||
// left subtree on the bottom, connected with box-drawing characters.
|
||||
// Iterative , no recursion.
|
||||
|
||||
Reference in New Issue
Block a user