Added graph search adlgorithms and Heap and PriorityQueue data structures

main
oabrivard 2 years ago
parent 726d2b923f
commit e33b5b790f

@ -0,0 +1,103 @@
package container
import (
"golang.org/x/exp/constraints"
)
type HeapItem[T any, V constraints.Integer] struct {
Value T
Priority V
}
// Heap represents a generic heap data structure.
type Heap[T any, V constraints.Integer] struct {
elements []HeapItem[T, V]
isMinHeap bool
}
// NewHeap creates a new heap with the specified ordering.
// The isMinHeap parameter determines whether the heap is a min heap or a max heap.
// The heap is initially empty.
func NewHeap[T any, V constraints.Integer](isMinHeap bool) *Heap[T, V] {
return &Heap[T, V]{elements: []HeapItem[T, V]{}, isMinHeap: isMinHeap}
}
// Push adds an element to the heap.
func (h *Heap[T, V]) Push(element T, priority V) {
h.elements = append(h.elements, HeapItem[T, V]{element, priority})
h.heapifyUp()
}
// Pop removes and returns the root element from the heap.
func (h *Heap[T, V]) Pop() T {
if len(h.elements) == 0 {
var zero T // Return zero value of T
return zero
}
root := h.elements[0]
lastIndex := len(h.elements) - 1
h.elements[0] = h.elements[lastIndex]
h.elements = h.elements[:lastIndex]
h.heapifyDown()
return root.Value
}
// Peek returns the root element without removing it.
func (h *Heap[T, V]) Peek() T {
if len(h.elements) == 0 {
var zero T // Return zero value of T
return zero
}
return h.elements[0].Value
}
// heapifyUp adjusts the heap after adding a new element.
func (h *Heap[T, V]) heapifyUp() {
index := len(h.elements) - 1
for index > 0 {
parentIndex := (index - 1) / 2
if h.compare(h.elements[index].Priority, h.elements[parentIndex].Priority) {
h.elements[index], h.elements[parentIndex] = h.elements[parentIndex], h.elements[index]
index = parentIndex
} else {
break
}
}
}
// heapifyDown adjusts the heap after removing the root element.
func (h *Heap[T, V]) heapifyDown() {
index := 0
lastIndex := len(h.elements) - 1
for index < lastIndex {
leftChildIndex := 2*index + 1
rightChildIndex := 2*index + 2
var childIndex int
if leftChildIndex <= lastIndex {
childIndex = leftChildIndex
if rightChildIndex <= lastIndex && h.compare(h.elements[rightChildIndex].Priority, h.elements[leftChildIndex].Priority) {
childIndex = rightChildIndex
}
if h.compare(h.elements[childIndex].Priority, h.elements[index].Priority) {
h.elements[index], h.elements[childIndex] = h.elements[childIndex], h.elements[index]
index = childIndex
} else {
break
}
} else {
break
}
}
}
// compare compares two elements based on the heap type.
func (h *Heap[T, V]) compare(x, y V) bool {
if h.isMinHeap {
return x < y
}
return x > y
}

@ -0,0 +1,145 @@
package container
import (
"testing"
)
// TestPush tests the Push method of the Heap.
func TestPush(t *testing.T) {
heap := NewHeap[int, int](true) // Testing a min heap
heap.Push(3, 3)
heap.Push(1, 1)
heap.Push(2, 2)
expected := []int{1, 3, 2} // The expected min-heap state after pushing elements
for i, v := range heap.elements {
if v.Value != expected[i] {
t.Errorf("Push() test failed: expected %v at index %d, got %v", expected[i], i, v)
}
}
}
// TestPop tests the Pop method of the Heap.
func TestPop(t *testing.T) {
heap := NewHeap[int, int](true)
heap.Push(3, 3)
heap.Push(1, 1)
heap.Push(2, 2)
if val := heap.Pop(); val != 1 {
t.Errorf("Pop() test failed: expected 1, got %v", val)
}
if val := heap.Pop(); val != 2 {
t.Errorf("Pop() test failed: expected 2, got %v", val)
}
}
// TestPeek tests the Peek method of the Heap.
func TestPeek(t *testing.T) {
heap := NewHeap[int, int](true)
heap.Push(3, 3)
heap.Push(1, 1)
heap.Push(2, 2)
if val := heap.Peek(); val != 1 {
t.Errorf("Peek() test failed: expected 1, got %v", val)
}
// Check if Peek() doesn't remove the element
if val := heap.Pop(); val != 1 {
t.Errorf("Peek() test failed: Peek() should not remove the element")
}
}
// TestHeapProperty tests if the heap maintains its property after operations.
func TestHeapProperty(t *testing.T) {
heap := NewHeap[int, int](true) // Testing a min heap
heap.Push(5, 5)
heap.Push(3, 3)
heap.Push(8, 8)
heap.Push(1, 1)
heap.Push(7, 7)
// After every Pop(), the next smallest element should come out
expectedOrder := []int{1, 3, 5, 7, 8}
for _, expected := range expectedOrder {
if val := heap.Pop(); val != expected {
t.Errorf("Heap property test failed: expected %v, got %v", expected, val)
}
}
}
// TestNewHeap tests the NewHeap function.
func TestNewHeap(t *testing.T) {
// Test creating a min heap
minHeap := NewHeap[int, int](true)
if minHeap == nil {
t.Error("NewHeap() test failed: expected a non-nil heap")
}
if minHeap != nil && !minHeap.isMinHeap {
t.Error("NewHeap() test failed: expected a min heap")
}
// Test creating a max heap
maxHeap := NewHeap[int, int](false)
if maxHeap == nil {
t.Error("NewHeap() test failed: expected a non-nil heap")
}
if minHeap != nil && maxHeap.isMinHeap {
t.Error("NewHeap() test failed: expected a max heap")
}
}
// TestHeapifyUp tests the heapifyUp method of the Heap.
func TestHeapifyUp(t *testing.T) {
heap := NewHeap[int, int](true)
heap.elements = []HeapItem[int, int]{{2, 2}, {3, 3}, {1, 1}}
heap.heapifyUp()
expected := []int{1, 3, 2} // The expected min-heap state after heapifyUp
for i, v := range heap.elements {
if v.Value != expected[i] {
t.Errorf("heapifyUp() test failed: expected %v at index %d, got %v", expected[i], i, v)
}
}
}
// TestHeapifyUpEmptyHeap tests the heapifyUp method of an empty Heap.
func TestHeapifyUpEmptyHeap(t *testing.T) {
heap := NewHeap[int, int](true)
heap.heapifyUp()
if len(heap.elements) != 0 {
t.Errorf("heapifyUp() test failed: expected empty heap, got %v", heap.elements)
}
}
// TestHeapifyUpSingleElement tests the heapifyUp method of a Heap with a single element.
func TestHeapifyUpSingleElement(t *testing.T) {
heap := NewHeap[int, int](true)
heap.elements = []HeapItem[int, int]{{1, 1}}
heap.heapifyUp()
if len(heap.elements) != 1 || heap.elements[0].Value != 1 {
t.Errorf("heapifyUp() test failed: expected [1], got %v", heap.elements)
}
}
// TestHeapifyDown tests the heapifyDown method of the Heap.
func TestHeapifyDown(t *testing.T) {
heap := NewHeap[int, int](true)
heap.elements = []HeapItem[int, int]{{8, 8}, {1, 1}, {3, 3}, {5, 5}, {7, 7}}
heap.heapifyDown()
expected := []int{1, 5, 3, 8, 7} // The expected heap state after heapifyDown
for i, v := range heap.elements {
if v.Value != expected[i] {
t.Errorf("heapifyDown() test failed: expected %v at index %d, got %v", expected[i], i, v)
}
}
}

@ -0,0 +1,38 @@
package container
import (
"golang.org/x/exp/constraints"
)
// PriorityQueue represents a priority queue data structure.
type PriorityQueue[T any, V constraints.Integer] struct {
heap *Heap[T, V]
}
// NewPriorityQueue creates a new PriorityQueue instance.
// isMinQueue determines whether it is a min-priority queue (true) or a max-priority queue (false).
func NewPriorityQueue[T any, V constraints.Integer](isMinQueue bool) *PriorityQueue[T, V] {
return &PriorityQueue[T, V]{
heap: NewHeap[T, V](isMinQueue),
}
}
// Enqueue adds an element to the priority queue.
func (pq *PriorityQueue[T, V]) Enqueue(element T, priority V) {
pq.heap.Push(element, priority)
}
// Dequeue removes and returns the element with the highest priority from the queue.
func (pq *PriorityQueue[T, V]) Dequeue() T {
return pq.heap.Pop()
}
// Peek returns the element with the highest priority without removing it from the queue.
func (pq *PriorityQueue[T, V]) Peek() T {
return pq.heap.Peek()
}
// IsEmpty returns true if the priority queue is empty.
func (pq *PriorityQueue[T, V]) IsEmpty() bool {
return len(pq.heap.elements) == 0
}

@ -0,0 +1,72 @@
package container
import (
"testing"
)
// TestEnqueue tests the Enqueue method of the PriorityQueue.
func TestEnqueue(t *testing.T) {
pq := NewPriorityQueue[int, int](true) // Min-priority queue
pq.Enqueue(3, 3)
pq.Enqueue(1, 1)
pq.Enqueue(2, 2)
expected := []int{1, 3, 2} // Expected min-heap state after enqueuing elements
for i, v := range pq.heap.elements {
if v.Value != expected[i] {
t.Errorf("Enqueue() test failed: expected %v at index %d, got %v", expected[i], i, v)
}
}
}
// TestDequeue tests the Dequeue method of the PriorityQueue.
func TestDequeue(t *testing.T) {
pq := NewPriorityQueue[int, int](true)
pq.Enqueue(3, 3)
pq.Enqueue(1, 1)
pq.Enqueue(2, 2)
if val := pq.Dequeue(); val != 1 {
t.Errorf("Dequeue() test failed: expected 1, got %v", val)
}
if val := pq.Dequeue(); val != 2 {
t.Errorf("Dequeue() test failed: expected 2, got %v", val)
}
}
// TestPeek tests the Peek method of the PriorityQueue.
func TestPQueuePeek(t *testing.T) {
pq := NewPriorityQueue[int, int](true)
pq.Enqueue(3, 3)
pq.Enqueue(1, 1)
pq.Enqueue(2, 2)
if val := pq.Peek(); val != 1 {
t.Errorf("Peek() test failed: expected 1, got %v", val)
}
// Check if Peek() doesn't remove the element
if val := pq.Dequeue(); val != 1 {
t.Errorf("Peek() test failed: Peek() should not remove the element")
}
}
// TestIsEmpty tests the IsEmpty method of the PriorityQueue.
func TestIsEmpty(t *testing.T) {
pq := NewPriorityQueue[int, int](true)
if !pq.IsEmpty() {
t.Errorf("IsEmpty() test failed: expected true, got false")
}
pq.Enqueue(1, 1)
if pq.IsEmpty() {
t.Errorf("IsEmpty() test failed: expected false, got true")
}
pq.Dequeue()
if !pq.IsEmpty() {
t.Errorf("IsEmpty() test failed: expected true, got false")
}
}

@ -11,6 +11,7 @@ import (
"gitea.paas.celticinfo.fr/oabrivard/abrolgo/algo"
"gitea.paas.celticinfo.fr/oabrivard/abrolgo/container"
"gitea.paas.celticinfo.fr/oabrivard/abrolgo/maths"
"golang.org/x/exp/constraints"
)
@ -51,13 +52,13 @@ func (g *Graph[T, V]) AddVertex(vertex T) {
}
// AddEdge adds an edge to the graph between the source node (src) and the destination node (dest),
// with the given distance (dist).
// with the given weight (weight).
// It adds the edge in both directions (src -> dest and dest -> src).
// If vertices src and dest do not exist in the graph, they are added to the graph.
func (g *Graph[T, V]) AddEdge(src, dest T, dist V) {
func (g *Graph[T, V]) AddEdge(src, dest T, weight V) {
g.edgeID++
srcToDest := Edge[T, V]{g.edgeID, src, dest, dist}
destToSrc := Edge[T, V]{g.edgeID, dest, src, dist}
srcToDest := Edge[T, V]{g.edgeID, src, dest, weight}
destToSrc := Edge[T, V]{g.edgeID, dest, src, weight}
g.adjacencyList[src] = append(g.adjacencyList[src], srcToDest)
g.adjacencyList[dest] = append(g.adjacencyList[dest], destToSrc)
g.edges = append(g.edges, srcToDest)
@ -510,3 +511,97 @@ func (g *Graph[T, V]) BFS(start T, isGoal func(vertex T) bool) []T {
return []T{}
}
// AStar performs the A* search algorithm to find the shortest path between two vertices in the graph.
// It returns the shortest path as a slice of vertices.
// If no path is found, an empty slice is returned.
func (g *Graph[T, V]) AStar(start, goal T, heuristic func(vertex T) V) []T {
// Initialize the distance map
dist := map[T]V{}
for vertex := range g.adjacencyList {
dist[vertex] = maths.MaxInteger[V]()
}
dist[start] = V(0)
// Initialize the previous map
prev := map[T]T{}
// Initialize the priority queue
pq := container.NewPriorityQueue[T, V](true)
pq.Enqueue(start, 0)
for !pq.IsEmpty() {
curVert := pq.Dequeue()
if curVert == goal {
// Reconstruct the path
path := []T{goal}
for prevVertex, ok := prev[goal]; ok; prevVertex, ok = prev[prevVertex] {
path = append([]T{prevVertex}, path...)
}
return path
}
for _, linkedNode := range g.adjacencyList[curVert] {
alt := dist[curVert] + linkedNode.weight
if alt < dist[linkedNode.dest] {
dist[linkedNode.dest] = alt
prev[linkedNode.dest] = curVert
priority := alt + heuristic(linkedNode.dest)
pq.Enqueue(linkedNode.dest, priority)
}
}
}
return []T{}
}
// Dijkstra performs Dijkstra's algorithm to find the shortest path between two vertices in the graph.
// It returns the shortest path as a slice of vertices.
// If no path is found, an empty slice is returned.
func (g *Graph[T, V]) Dijkstra(start, goal T) []T {
return g.AStar(start, goal, func(vertex T) V {
return V(0)
})
/*
// Initialize the distance map
dist := map[T]V{}
for vertex := range g.adjacencyList {
dist[vertex] = maths.MaxInteger[V]()
}
dist[start] = V(0)
// Initialize the previous map
prev := map[T]T{}
// Initialize the priority queue
pq := container.NewPriorityQueue[T, V](true)
pq.Enqueue(start, 0)
for !pq.IsEmpty() {
curVert := pq.Dequeue()
if curVert == goal {
// Reconstruct the path
path := []T{goal}
for prevVertex, ok := prev[goal]; ok; prevVertex, ok = prev[prevVertex] {
path = append([]T{prevVertex}, path...)
}
return path
}
for _, linkedNode := range g.adjacencyList[curVert] {
alt := dist[curVert] + linkedNode.weight
if alt < dist[linkedNode.dest] {
dist[linkedNode.dest] = alt
prev[linkedNode.dest] = curVert
pq.Enqueue(linkedNode.dest, alt)
}
}
}
return []T{}
*/
}

@ -599,7 +599,7 @@ func TestKargerMinCutUF(t *testing.T) {
g.AddEdge(5, 6, 70)
// Run Karger's algorithm to find the minimum cut
cutEdges, resultSubsets := g.kargerMinCutUF(10000)
cutEdges, resultSubsets := g.kargerMinCutUF(100)
// Check if the number of cut edges is correct
expectedCutEdges := 1
@ -722,3 +722,59 @@ func TestDFS(t *testing.T) {
t.Errorf("DFS() failed with goal function: expected %v, got %v", expectedResult, result)
}
}
func TestDijkstra(t *testing.T) {
g := NewGraph[int, int]()
// Add vertices and edges to the graph
g.AddEdge(1, 2, 10)
g.AddEdge(1, 3, 20)
g.AddEdge(2, 3, 30)
g.AddEdge(2, 4, 40)
g.AddEdge(3, 4, 50)
// Test Dijkstra algorithm
start := 1
goal := 4
expectedPath := []int{1, 2, 4}
path := g.Dijkstra(start, goal)
if !equalSlices(path, expectedPath) {
t.Errorf("Dijkstra() failed: expected path = %v, got path = %v", expectedPath, path)
}
}
func TestAStar(t *testing.T) {
g := NewGraph[int, int]()
// Add vertices and edges to the graph
g.AddEdge(1, 2, 10)
g.AddEdge(1, 3, 20)
g.AddEdge(2, 3, 30)
g.AddEdge(2, 4, 40)
g.AddEdge(3, 4, 50)
g.AddEdge(3, 5, 60)
g.AddEdge(4, 5, 70)
// Define the heuristic function
heuristic := func(vertex int) int {
return 0 // equivalent of Disjkstra's algorithm
}
// Test AStar for a valid path
start := 1
goal := 5
expectedPath := []int{1, 3, 5}
path := g.AStar(start, goal, heuristic)
if !equalSlices(path, expectedPath) {
t.Errorf("AStar() failed for valid path: expected %v, got %v", expectedPath, path)
}
// Test AStar for an invalid path
start = 1
goal = 6
expectedPath = []int{}
path = g.AStar(start, goal, heuristic)
if !equalSlices(path, expectedPath) {
t.Errorf("AStar() failed for invalid path: expected %v, got %v", expectedPath, path)
}
}

@ -1,4 +1,4 @@
package math
package maths
import "math"

@ -1,4 +1,4 @@
package math
package maths
import "testing"

@ -1,4 +1,4 @@
package math
package maths
import (
"golang.org/x/exp/constraints"
@ -111,3 +111,7 @@ func gaussianElimination(coefficients Matrix[float64], rhs []float64) {
}
}
}
func MaxInteger[T constraints.Integer]() T {
return T(^uint(T(0)) >> 1)
}

@ -1,4 +1,4 @@
package math
package maths
import (
"testing"

@ -1,4 +1,4 @@
package math
package maths
type Matrix[T comparable] [][]T

@ -1,4 +1,4 @@
package math
package maths
import (
"reflect"
Loading…
Cancel
Save