Added undirected graph management
parent
9b5259c192
commit
726d2b923f
@ -0,0 +1,512 @@
|
|||||||
|
// Package graph is an undirected graph management library.
|
||||||
|
// Do not support multithreaded updates.
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.paas.celticinfo.fr/oabrivard/abrolgo/algo"
|
||||||
|
"gitea.paas.celticinfo.fr/oabrivard/abrolgo/container"
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Edge[T comparable, V constraints.Integer] struct {
|
||||||
|
ID int
|
||||||
|
src T
|
||||||
|
dest T
|
||||||
|
weight V
|
||||||
|
}
|
||||||
|
|
||||||
|
type Graph[T comparable, V constraints.Integer] struct {
|
||||||
|
edgeID int
|
||||||
|
v, e int // number of edges and vertices
|
||||||
|
adjacencyList map[T][]Edge[T, V]
|
||||||
|
edges []Edge[T, V]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGraph creates a new instance of the undirected Graph data structure.
|
||||||
|
// It initializes the graph with zero vertices and zero edges.
|
||||||
|
// The graph uses a map to store the adjacency list and a slice to store the edges.
|
||||||
|
// The type parameters T and V represent the types of the vertices and edge values, respectively.
|
||||||
|
// The vertices must be comparable, and the edge values must be integers.
|
||||||
|
// Returns a pointer to the newly created Graph.
|
||||||
|
func NewGraph[T comparable, V constraints.Integer]() *Graph[T, V] {
|
||||||
|
return &Graph[T, V]{
|
||||||
|
edgeID: 0,
|
||||||
|
v: 0,
|
||||||
|
e: 0,
|
||||||
|
adjacencyList: map[T][]Edge[T, V]{},
|
||||||
|
edges: []Edge[T, V]{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddVertex adds a new vertex to the graph.
|
||||||
|
// This function initializes an empty adjacency list for the new vertex.
|
||||||
|
func (g *Graph[T, V]) AddVertex(vertex T) {
|
||||||
|
g.adjacencyList[vertex] = []Edge[T, V]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddEdge adds an edge to the graph between the source node (src) and the destination node (dest),
|
||||||
|
// with the given distance (dist).
|
||||||
|
// 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) {
|
||||||
|
g.edgeID++
|
||||||
|
srcToDest := Edge[T, V]{g.edgeID, src, dest, dist}
|
||||||
|
destToSrc := Edge[T, V]{g.edgeID, dest, src, dist}
|
||||||
|
g.adjacencyList[src] = append(g.adjacencyList[src], srcToDest)
|
||||||
|
g.adjacencyList[dest] = append(g.adjacencyList[dest], destToSrc)
|
||||||
|
g.edges = append(g.edges, srcToDest)
|
||||||
|
g.e++
|
||||||
|
g.v = len(g.adjacencyList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveEdge removes the first edge between the source node (src) and the destination node (dest).
|
||||||
|
// It updates the adjacency list, the list of edges, and the number of edges in the graph.
|
||||||
|
// If the edge does not exist, it does nothing.
|
||||||
|
func (g *Graph[T, V]) RemoveEdge(src, dest T) {
|
||||||
|
var ID int
|
||||||
|
edgeFound := false
|
||||||
|
|
||||||
|
// Check if source vertex exists
|
||||||
|
if _, exists := g.adjacencyList[src]; !exists {
|
||||||
|
// Optionally handle the case where the source vertex does not exist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the edge from the adjacency list
|
||||||
|
for i, edge := range g.adjacencyList[src] {
|
||||||
|
if edge.dest == dest {
|
||||||
|
g.adjacencyList[src] = append(g.adjacencyList[src][:i], g.adjacencyList[src][i+1:]...)
|
||||||
|
ID = edge.ID
|
||||||
|
edgeFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the reciprocal edge
|
||||||
|
for i, edge := range g.adjacencyList[dest] {
|
||||||
|
if edge.ID == ID {
|
||||||
|
g.adjacencyList[dest] = append(g.adjacencyList[dest][:i], g.adjacencyList[dest][i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no edge was removed from the adjacency list, return to avoid the uneceesary loop
|
||||||
|
if !edgeFound {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the edge from the list of edges
|
||||||
|
for i, edge := range g.edges {
|
||||||
|
if edge.ID == ID {
|
||||||
|
g.edges = append(g.edges[:i], g.edges[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.e--
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveEdge removes the first edge between the source node (src) and the destination node (dest).
|
||||||
|
// It updates the adjacency list, the list of edges, and the number of edges in the graph.
|
||||||
|
// If the edge does not exist, it does nothing.
|
||||||
|
func (g *Graph[T, V]) RemoveEdgeWithID(ID int) {
|
||||||
|
edgeFound := false
|
||||||
|
var edgeToRemove Edge[T, V]
|
||||||
|
|
||||||
|
// Remove the edge from the list of edges
|
||||||
|
for i, edge := range g.edges {
|
||||||
|
if edge.ID == ID {
|
||||||
|
edgeFound = true
|
||||||
|
edgeToRemove = edge
|
||||||
|
g.edges = append(g.edges[:i], g.edges[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if no edge found
|
||||||
|
if !edgeFound {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
src := edgeToRemove.src
|
||||||
|
dest := edgeToRemove.dest
|
||||||
|
|
||||||
|
// Remove the edge from the adjacency list
|
||||||
|
for i, edge := range g.adjacencyList[src] {
|
||||||
|
if edge.ID == ID {
|
||||||
|
g.adjacencyList[src] = append(g.adjacencyList[src][:i], g.adjacencyList[src][i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the reciprocal edge
|
||||||
|
for i, edge := range g.adjacencyList[dest] {
|
||||||
|
if edge.ID == ID {
|
||||||
|
g.adjacencyList[dest] = append(g.adjacencyList[dest][:i], g.adjacencyList[dest][i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.e--
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveVertex removes the specified vertex from the graph.
|
||||||
|
// It updates the adjacency list, the list of edges, and the number of edges and vertices in the graph.
|
||||||
|
// If the vertex does not exist, it does nothing.
|
||||||
|
func (g *Graph[T, V]) RemoveVertex(vertex T) {
|
||||||
|
// Remove the vertex from the adjacency list
|
||||||
|
delete(g.adjacencyList, vertex)
|
||||||
|
|
||||||
|
// Remove all edges in the adjacency list that have 'vertex' as their destination
|
||||||
|
for src, edges := range g.adjacencyList {
|
||||||
|
for i := 0; i < len(edges); {
|
||||||
|
if edges[i].dest == vertex {
|
||||||
|
// Remove edge from the slice
|
||||||
|
edges = append(edges[:i], edges[i+1:]...)
|
||||||
|
g.e-- // Decrement the edge count
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.adjacencyList[src] = edges
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all edges from the edges slice that involve 'vertex'
|
||||||
|
for i := 0; i < len(g.edges); {
|
||||||
|
if g.edges[i].src == vertex || g.edges[i].dest == vertex {
|
||||||
|
// Remove edge from the slice
|
||||||
|
g.edges = append(g.edges[:i], g.edges[i+1:]...)
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.v = len(g.adjacencyList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVertices returns the list of vertices in the graph.
|
||||||
|
func (g *Graph[T, V]) GetVertices() []T {
|
||||||
|
vertices := make([]T, len(g.adjacencyList))
|
||||||
|
i := 0
|
||||||
|
for vertex := range g.adjacencyList {
|
||||||
|
vertices[i] = vertex
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return vertices
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEdges returns the list of edges in the graph.
|
||||||
|
func (g *Graph[T, V]) GetEdges() []Edge[T, V] {
|
||||||
|
return g.edges
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNeighbors returns the list of edges connected to the specified vertex in the graph.
|
||||||
|
func (g *Graph[T, V]) GetNeighbors(vertex T) []Edge[T, V] {
|
||||||
|
return g.adjacencyList[vertex]
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNeighbor checks if a given source node has a neighbor node in the graph.
|
||||||
|
func (g *Graph[T, V]) HasNeighbor(src, neighbor T) bool {
|
||||||
|
return slices.ContainsFunc(g.adjacencyList[src], func(edge Edge[T, V]) bool {
|
||||||
|
return edge.dest == neighbor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEdge returns the first edge between the source node (src) and the destination node (dest).
|
||||||
|
// If the edge does not exist, it returns an empty Edge.
|
||||||
|
func (g *Graph[T, V]) GetEdge(src, dest T) Edge[T, V] {
|
||||||
|
for _, edge := range g.adjacencyList[src] {
|
||||||
|
if edge.dest == dest {
|
||||||
|
return edge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Edge[T, V]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the graph.
|
||||||
|
func (g *Graph[T, V]) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.Grow(512) // Provide an initial capacity estimate
|
||||||
|
|
||||||
|
for vertex, edges := range g.adjacencyList {
|
||||||
|
sb.WriteString(fmt.Sprint(vertex))
|
||||||
|
sb.WriteString(": ")
|
||||||
|
|
||||||
|
edgeStrings := make([]string, len(edges))
|
||||||
|
for i, e := range edges {
|
||||||
|
edgeStrings[i] = fmt.Sprintf("(%v, %v, %v, %v)", e.ID, e.src, e.dest, e.weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(strings.Join(edgeStrings, " "))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountComponents returns the number of connected components in the graph.
|
||||||
|
func (g *Graph[T, V]) CountComponents() int {
|
||||||
|
visited := map[T]bool{}
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
// We perform a breadth-first search starting from each unvisited vertex
|
||||||
|
// and increment the count for each new component found.
|
||||||
|
for vertex := range g.adjacencyList {
|
||||||
|
if !visited[vertex] {
|
||||||
|
count++
|
||||||
|
queue := container.NewQueue[T]()
|
||||||
|
queue.Enqueue(vertex)
|
||||||
|
|
||||||
|
for queue.HasElement() {
|
||||||
|
curVert := queue.Dequeue()
|
||||||
|
visited[curVert] = true
|
||||||
|
|
||||||
|
for _, linkedNode := range g.adjacencyList[curVert] {
|
||||||
|
if !visited[linkedNode.dest] {
|
||||||
|
queue.Enqueue(linkedNode.dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsConnected returns true if the graph is connected, false otherwise.
|
||||||
|
func (g *Graph[T, V]) IsConnected() bool {
|
||||||
|
return g.CountComponents() == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCyclic returns true if the graph contains a cycle, false otherwise.
|
||||||
|
func (g *Graph[T, V]) IsCyclic() bool {
|
||||||
|
visited := map[T]bool{}
|
||||||
|
recStack := map[T]bool{}
|
||||||
|
edgeIDs := map[int]bool{}
|
||||||
|
|
||||||
|
for vertex := range g.adjacencyList {
|
||||||
|
if g.isCyclicUtil(vertex, visited, recStack, edgeIDs) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isCyclicUtil is a helper function for IsCyclic.
|
||||||
|
// It performs a depth-first search on the graph starting from the specified vertex.
|
||||||
|
// It returns true if the graph contains a cycle, false otherwise.
|
||||||
|
func (g *Graph[T, V]) isCyclicUtil(vertex T, visited, recStack map[T]bool, edgeIDs map[int]bool) bool {
|
||||||
|
visited[vertex] = true
|
||||||
|
recStack[vertex] = true
|
||||||
|
|
||||||
|
for _, neighbor := range g.adjacencyList[vertex] {
|
||||||
|
// If the edge has already been visited via a reciprocal link, skip it
|
||||||
|
if edgeIDs[neighbor.ID] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
edgeIDs[neighbor.ID] = true
|
||||||
|
|
||||||
|
if !visited[neighbor.dest] && g.isCyclicUtil(neighbor.dest, visited, recStack, edgeIDs) {
|
||||||
|
return true
|
||||||
|
} else if recStack[neighbor.dest] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recStack[vertex] = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTree returns true if the graph is a tree, false otherwise.
|
||||||
|
func (g *Graph[T, V]) IsTree() bool {
|
||||||
|
return g.IsConnected() && !g.IsCyclic()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBipartite returns true if the graph is bipartite, false otherwise.
|
||||||
|
func (g *Graph[T, V]) IsBipartite() bool {
|
||||||
|
visited := map[T]bool{}
|
||||||
|
colors := map[T]bool{}
|
||||||
|
edgeIDs := map[int]bool{}
|
||||||
|
|
||||||
|
for vertex := range g.adjacencyList {
|
||||||
|
if !visited[vertex] {
|
||||||
|
if !g.isBipartiteUtil(vertex, visited, colors, edgeIDs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isBipartiteUtil is a helper function for IsBipartite.
|
||||||
|
// It performs a depth-first search on the graph starting from the specified vertex.
|
||||||
|
// It returns true if the graph is bipartite, false otherwise.
|
||||||
|
func (g *Graph[T, V]) isBipartiteUtil(vertex T, visited, colors map[T]bool, edgeIDs map[int]bool) bool {
|
||||||
|
visited[vertex] = true
|
||||||
|
|
||||||
|
for _, neighbor := range g.adjacencyList[vertex] {
|
||||||
|
// If the edge has already been visited via a reciprocal link, skip it
|
||||||
|
if edgeIDs[neighbor.ID] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
edgeIDs[neighbor.ID] = true
|
||||||
|
|
||||||
|
if !visited[neighbor.dest] {
|
||||||
|
colors[neighbor.dest] = !colors[vertex]
|
||||||
|
if !g.isBipartiteUtil(neighbor.dest, visited, colors, edgeIDs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if colors[vertex] == colors[neighbor.dest] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// kargerMinCutUF performs the Karger's algorithm to find the minimum cut in the graph using the Union-Find data structure.
|
||||||
|
// It returns the number of cut edges and the subsets of vertices after the contraction.
|
||||||
|
// Being a monte-carlo algorithm, it can return a wrong result with a very small probability.
|
||||||
|
// Adapted from https://www.geeksforgeeks.org/introduction-and-implementation-of-kargers-algorithm-for-minimum-cut/?ref=lbp
|
||||||
|
func (g *Graph[T, V]) kargerMinCutUF(loops int) (int, [][]T) {
|
||||||
|
var subsets map[T]*algo.Subset[T]
|
||||||
|
minCut := math.MaxInt64
|
||||||
|
|
||||||
|
for loops > 0 {
|
||||||
|
loops--
|
||||||
|
|
||||||
|
// Allocate memory for creating V subsets.
|
||||||
|
subsets = map[T]*algo.Subset[T]{}
|
||||||
|
|
||||||
|
// Get data of given graph
|
||||||
|
edges := g.edges
|
||||||
|
|
||||||
|
// Create V subsets with single elements
|
||||||
|
for k := range g.adjacencyList {
|
||||||
|
subsets[k] = &algo.Subset[T]{Parent: k, Rank: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially there are V vertices in
|
||||||
|
// contracted graph
|
||||||
|
vertices := g.v
|
||||||
|
|
||||||
|
// Keep contracting vertices until there are
|
||||||
|
// 2 vertices.
|
||||||
|
for vertices > 2 {
|
||||||
|
// Pick a random edge
|
||||||
|
i := rand.Int() % g.e
|
||||||
|
|
||||||
|
// Find vertices (or sets) of two corners
|
||||||
|
// of current edge
|
||||||
|
subset1 := algo.Find(subsets, edges[i].src)
|
||||||
|
subset2 := algo.Find(subsets, edges[i].dest)
|
||||||
|
|
||||||
|
// If two corners belong to same subset,
|
||||||
|
// then no point considering this edge
|
||||||
|
if subset1 == subset2 {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// Else contract the edge (or combine the
|
||||||
|
// corners of edge into one vertex)
|
||||||
|
vertices--
|
||||||
|
algo.Union(subsets, subset1, subset2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have two vertices (or subsets) left in
|
||||||
|
// the contracted graph, so count the edges between
|
||||||
|
// two components and return the count.
|
||||||
|
cutedges := 0
|
||||||
|
for i := 0; i < g.e; i++ {
|
||||||
|
subset1 := algo.Find(subsets, edges[i].src)
|
||||||
|
subset2 := algo.Find(subsets, edges[i].dest)
|
||||||
|
if subset1 != subset2 {
|
||||||
|
cutedges++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cutedges < minCut {
|
||||||
|
minCut = cutedges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consolidate the subsets
|
||||||
|
consolidatedSubsets := map[T][]T{}
|
||||||
|
for k, v := range subsets {
|
||||||
|
_, found := consolidatedSubsets[v.Parent]
|
||||||
|
if !found {
|
||||||
|
consolidatedSubsets[v.Parent] = []T{}
|
||||||
|
}
|
||||||
|
consolidatedSubsets[v.Parent] = append(consolidatedSubsets[v.Parent], k)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultSubsets := [][]T{}
|
||||||
|
for _, s := range consolidatedSubsets {
|
||||||
|
resultSubsets = append(resultSubsets, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return minCut, resultSubsets
|
||||||
|
}
|
||||||
|
|
||||||
|
// DFS performs a depth-first search starting from the specified vertex until a goal vertex is reached.
|
||||||
|
// It returns a slice of vertices visited during the search.
|
||||||
|
// If no goal vertex is found, an empty slice is returned.
|
||||||
|
func (g *Graph[T, V]) DFS(start T, isGoal func(vertex T) bool) []T {
|
||||||
|
visited := map[T]bool{}
|
||||||
|
result := []T{}
|
||||||
|
stack := container.NewStack[T]()
|
||||||
|
|
||||||
|
stack.Push(start)
|
||||||
|
|
||||||
|
for stack.HasElement() {
|
||||||
|
curVert := stack.Pop()
|
||||||
|
result = append(result, curVert)
|
||||||
|
|
||||||
|
if isGoal(curVert) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if !visited[curVert] {
|
||||||
|
visited[curVert] = true
|
||||||
|
|
||||||
|
for _, linkedNode := range g.adjacencyList[curVert] {
|
||||||
|
stack.Push(linkedNode.dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []T{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BFS performs a breadth-first search starting from the specified vertex until a goal vertex is reached.
|
||||||
|
// It returns a slice of vertices visited during the search.
|
||||||
|
// If no goal vertex is found, an empty slice is returned.
|
||||||
|
func (g *Graph[T, V]) BFS(start T, isGoal func(vertex T) bool) []T {
|
||||||
|
visited := map[T]bool{}
|
||||||
|
result := []T{}
|
||||||
|
queue := container.NewQueue[T]()
|
||||||
|
|
||||||
|
visited[start] = true
|
||||||
|
queue.Enqueue(start)
|
||||||
|
|
||||||
|
for queue.HasElement() {
|
||||||
|
curVert := queue.Dequeue()
|
||||||
|
result = append(result, curVert)
|
||||||
|
|
||||||
|
if isGoal(curVert) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, linkedNode := range g.adjacencyList[curVert] {
|
||||||
|
if !visited[linkedNode.dest] {
|
||||||
|
visited[linkedNode.dest] = true
|
||||||
|
queue.Enqueue(linkedNode.dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []T{}
|
||||||
|
}
|
||||||
@ -0,0 +1,724 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewGraph(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Check if the graph is initialized correctly
|
||||||
|
if g.v != 0 {
|
||||||
|
t.Errorf("NewGraph() failed: expected v = 0, got v = %d", g.v)
|
||||||
|
}
|
||||||
|
if g.e != 0 {
|
||||||
|
t.Errorf("NewGraph() failed: expected e = 0, got e = %d", g.e)
|
||||||
|
}
|
||||||
|
if len(g.adjacencyList) != 0 {
|
||||||
|
t.Errorf("NewGraph() failed: expected empty adjacency list, got %v", g.adjacencyList)
|
||||||
|
}
|
||||||
|
if len(g.edges) != 0 {
|
||||||
|
t.Errorf("NewGraph() failed: expected empty edges list, got %v", g.edges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddEdge(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Add edges to the graph
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
// Check if the edges are added correctly
|
||||||
|
expectedV := 4
|
||||||
|
if g.v != expectedV {
|
||||||
|
t.Errorf("AddEdge() failed: expected v = %d, got v = %d", expectedV, g.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedE := 3
|
||||||
|
if g.e != expectedE {
|
||||||
|
t.Errorf("AddEdge() failed: expected e = %d, got e = %d", expectedE, g.e)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedAdjacencyList := map[int][]Edge[int, int]{
|
||||||
|
1: {{1, 1, 2, 10}},
|
||||||
|
2: {{1, 2, 1, 10}, {2, 2, 3, 20}},
|
||||||
|
3: {{2, 3, 2, 20}, {3, 3, 4, 30}},
|
||||||
|
4: {{3, 4, 3, 30}},
|
||||||
|
}
|
||||||
|
if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) {
|
||||||
|
t.Errorf("AddEdge() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedEdges := []Edge[int, int]{
|
||||||
|
{1, 1, 2, 10},
|
||||||
|
{2, 2, 3, 20},
|
||||||
|
{3, 3, 4, 30},
|
||||||
|
}
|
||||||
|
if !compareEdges(g.edges, expectedEdges) {
|
||||||
|
t.Errorf("AddEdge() failed: expected edges list = %v, got %v", expectedEdges, g.edges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareAdjacencyLists(a, b map[int][]Edge[int, int]) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, valueA := range a {
|
||||||
|
valueB, ok := b[key]
|
||||||
|
if !ok || !compareEdges(valueA, valueB) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareEdges(a, b []Edge[int, int]) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(a); i++ {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNeighbors(t *testing.T) {
|
||||||
|
// Create a new graph
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Test GetNeighbors for vertex 1
|
||||||
|
neighbors := g.GetNeighbors(1)
|
||||||
|
expectedNeighbors := []Edge[int, int]{{ID: 1, src: 1, dest: 2, weight: 10}, {ID: 2, src: 1, dest: 3, weight: 20}}
|
||||||
|
if !equalEdges(neighbors, expectedNeighbors) {
|
||||||
|
t.Errorf("GetNeighbors() failed for vertex 1: expected %v, got %v", expectedNeighbors, neighbors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetNeighbors for vertex 2
|
||||||
|
neighbors = g.GetNeighbors(2)
|
||||||
|
expectedNeighbors = []Edge[int, int]{{ID: 1, src: 2, dest: 1, weight: 10}, {ID: 3, src: 2, dest: 3, weight: 30}}
|
||||||
|
if !equalEdges(neighbors, expectedNeighbors) {
|
||||||
|
t.Errorf("GetNeighbors() failed for vertex 2: expected %v, got %v", expectedNeighbors, neighbors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetNeighbors for vertex 3
|
||||||
|
neighbors = g.GetNeighbors(3)
|
||||||
|
expectedNeighbors = []Edge[int, int]{{ID: 2, src: 3, dest: 1, weight: 20}, {ID: 3, src: 3, dest: 2, weight: 30}}
|
||||||
|
if !equalEdges(neighbors, expectedNeighbors) {
|
||||||
|
t.Errorf("GetNeighbors() failed for vertex 3: expected %v, got %v", expectedNeighbors, neighbors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if two slices of edges are equal
|
||||||
|
func equalEdges(edges1, edges2 []Edge[int, int]) bool {
|
||||||
|
if len(edges1) != len(edges2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(edges1); i++ {
|
||||||
|
if edges1[i] != edges2[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasNeighbor(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Add edges to the graph
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(1, 3, 20)
|
||||||
|
g.AddEdge(2, 3, 30)
|
||||||
|
|
||||||
|
// Test HasNeighbor for existing neighbors
|
||||||
|
if !g.HasNeighbor(1, 2) {
|
||||||
|
t.Errorf("HasNeighbor() failed: expected true, got false")
|
||||||
|
}
|
||||||
|
if !g.HasNeighbor(1, 3) {
|
||||||
|
t.Errorf("HasNeighbor() failed: expected true, got false")
|
||||||
|
}
|
||||||
|
if !g.HasNeighbor(2, 3) {
|
||||||
|
t.Errorf("HasNeighbor() failed: expected true, got false")
|
||||||
|
}
|
||||||
|
if !g.HasNeighbor(3, 1) {
|
||||||
|
t.Errorf("HasNeighbor() failed: expected true, got false")
|
||||||
|
}
|
||||||
|
if !g.HasNeighbor(2, 1) {
|
||||||
|
t.Errorf("HasNeighbor() failed: expected true, got false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test HasNeighbor for non-existing neighbors
|
||||||
|
if g.HasNeighbor(1, 4) {
|
||||||
|
t.Errorf("HasNeighbor() failed: expected false, got true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEdge(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
// Test existing edge
|
||||||
|
expectedEdge := Edge[int, int]{ID: 1, src: 1, dest: 2, weight: 10}
|
||||||
|
edge := g.GetEdge(1, 2)
|
||||||
|
if edge != expectedEdge {
|
||||||
|
t.Errorf("GetEdge() failed for existing edge: expected %v, got %v", expectedEdge, edge)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test non-existing edge
|
||||||
|
edge = g.GetEdge(1, 3)
|
||||||
|
if edge != (Edge[int, int]{}) {
|
||||||
|
t.Errorf("GetEdge() failed for non-existing edge: expected empty edge, got %v", edge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestString(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)
|
||||||
|
|
||||||
|
result := g.String()
|
||||||
|
|
||||||
|
if !strings.Contains(result, "1: (1, 1, 2, 10) (2, 1, 3, 20)\n") || !strings.Contains(result, "2: (1, 2, 1, 10) (3, 2, 3, 30)\n") || !strings.Contains(result, "3: (2, 3, 1, 20) (3, 3, 2, 30)\n") {
|
||||||
|
t.Errorf("String() failed and got %q", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCountComponents(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Add vertices and edges to the graph
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(4, 5, 30)
|
||||||
|
|
||||||
|
expectedCount := 2
|
||||||
|
actualCount := g.CountComponents()
|
||||||
|
if actualCount != expectedCount {
|
||||||
|
t.Errorf("CountComponents() failed: expected count = %d, got count = %d", expectedCount, actualCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add another component to the graph
|
||||||
|
g.AddEdge(6, 7, 40)
|
||||||
|
g.AddEdge(7, 8, 50)
|
||||||
|
|
||||||
|
expectedCount = 3
|
||||||
|
actualCount = g.CountComponents()
|
||||||
|
if actualCount != expectedCount {
|
||||||
|
t.Errorf("CountComponents() failed: expected count = %d, got count = %d", expectedCount, actualCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsConnected(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Add vertices and edges to the graph
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
// Test IsConnected for a connected graph
|
||||||
|
if !g.IsConnected() {
|
||||||
|
t.Errorf("IsConnected() failed: expected true, got false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test IsConnected for a disconnected graph
|
||||||
|
g.AddVertex(5)
|
||||||
|
if g.IsConnected() {
|
||||||
|
t.Errorf("IsConnected() failed: expected false, got true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsCyclic(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Add edges to create a cyclic graph
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 1, 30)
|
||||||
|
|
||||||
|
// Check if the graph is cyclic
|
||||||
|
if !g.IsCyclic() {
|
||||||
|
t.Errorf("IsCyclic() failed: expected true, got false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add edges to create an acyclic graph
|
||||||
|
g = NewGraph[int, int]()
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
// Check if the graph is acyclic
|
||||||
|
if g.IsCyclic() {
|
||||||
|
t.Errorf("IsCyclic() failed: expected false, got true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsCyclicUtil(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 1, 30)
|
||||||
|
|
||||||
|
visited := make(map[int]bool)
|
||||||
|
recStack := make(map[int]bool)
|
||||||
|
edgeIDs := make(map[int]bool)
|
||||||
|
|
||||||
|
// Test for cyclic graph
|
||||||
|
if !g.isCyclicUtil(1, visited, recStack, edgeIDs) {
|
||||||
|
t.Errorf("isCyclicUtil() failed: expected true, got false")
|
||||||
|
}
|
||||||
|
|
||||||
|
g = NewGraph[int, int]()
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
visited = make(map[int]bool)
|
||||||
|
recStack = make(map[int]bool)
|
||||||
|
edgeIDs = make(map[int]bool)
|
||||||
|
|
||||||
|
// Test for acyclic graph
|
||||||
|
if g.isCyclicUtil(1, visited, recStack, edgeIDs) {
|
||||||
|
t.Errorf("isCyclicUtil() failed: expected false, got true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsTree(t *testing.T) {
|
||||||
|
// Create a new graph
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Add vertices and edges to the graph
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
// Test IsTree for a connected acyclic graph
|
||||||
|
if !g.IsTree() {
|
||||||
|
t.Errorf("IsTree() failed for a connected acyclic graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an edge to create a cycle
|
||||||
|
g.AddEdge(4, 1, 40)
|
||||||
|
|
||||||
|
// Test IsTree for a graph with a cycle
|
||||||
|
if g.IsTree() {
|
||||||
|
t.Errorf("IsTree() failed for a graph with a cycle")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the edge to make the graph acyclic again
|
||||||
|
g.RemoveEdge(4, 1)
|
||||||
|
|
||||||
|
// Remove a vertex to make the graph disconnected
|
||||||
|
g.RemoveVertex(3)
|
||||||
|
|
||||||
|
// Test IsTree for a disconnected graph
|
||||||
|
if g.IsTree() {
|
||||||
|
t.Errorf("IsTree() failed for a disconnected graph")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsBipartite(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(3, 4, 40)
|
||||||
|
|
||||||
|
// Test for a bipartite graph
|
||||||
|
if !g.IsBipartite() {
|
||||||
|
t.Error("IsBipartite() failed for a bipartite graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an edge to create a cycle
|
||||||
|
g.AddEdge(4, 1, 50)
|
||||||
|
|
||||||
|
// Test for a non-bipartite graph with a cycle
|
||||||
|
if g.IsBipartite() {
|
||||||
|
t.Error("IsBipartite() failed for a non-bipartite graph with a cycle")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the edge to break the cycle
|
||||||
|
g.RemoveEdge(4, 1)
|
||||||
|
|
||||||
|
// Test for a non-bipartite graph without a cycle
|
||||||
|
if !g.IsBipartite() {
|
||||||
|
t.Error("IsBipartite() failed for a non-bipartite graph without a cycle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsBipartiteUtil(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Add vertices and edges to the graph
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
visited := make(map[int]bool)
|
||||||
|
colors := make(map[int]bool)
|
||||||
|
edgeIDs := map[int]bool{}
|
||||||
|
|
||||||
|
// Test isBipartiteUtil for a bipartite graph
|
||||||
|
result := g.isBipartiteUtil(1, visited, colors, edgeIDs)
|
||||||
|
expectedResult := true
|
||||||
|
if result != expectedResult {
|
||||||
|
t.Errorf("isBipartiteUtil() failed for bipartite graph: expected %v, got %v", expectedResult, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
visited = make(map[int]bool)
|
||||||
|
colors = make(map[int]bool)
|
||||||
|
edgeIDs = map[int]bool{}
|
||||||
|
|
||||||
|
// Test isBipartiteUtil for a non-bipartite graph
|
||||||
|
g.AddEdge(4, 2, 40)
|
||||||
|
result = g.isBipartiteUtil(1, visited, colors, edgeIDs)
|
||||||
|
expectedResult = false
|
||||||
|
if result != expectedResult {
|
||||||
|
t.Errorf("isBipartiteUtil() failed for non-bipartite graph: expected %v, got %v", expectedResult, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddVertex(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Add a vertex to the graph
|
||||||
|
g.AddVertex(1)
|
||||||
|
|
||||||
|
// Check if the vertex is added correctly
|
||||||
|
expectedAdjacencyList := map[int][]Edge[int, int]{
|
||||||
|
1: {},
|
||||||
|
}
|
||||||
|
if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) {
|
||||||
|
t.Errorf("AddVertex() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestGetVertices(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
|
||||||
|
// Add vertices to the graph
|
||||||
|
g.AddVertex(1)
|
||||||
|
g.AddVertex(2)
|
||||||
|
g.AddVertex(3)
|
||||||
|
|
||||||
|
// Get the vertices from the graph
|
||||||
|
vertices := g.GetVertices()
|
||||||
|
|
||||||
|
// Check if the vertices are returned correctly
|
||||||
|
expectedVertices := []int{1, 2, 3}
|
||||||
|
if !equalSlices(vertices, expectedVertices) {
|
||||||
|
t.Errorf("GetVertices() failed: expected %v, got %v", expectedVertices, vertices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if two slices are equal
|
||||||
|
func equalSlices(a, b []int) bool {
|
||||||
|
sort.Ints(a)
|
||||||
|
sort.Ints(b)
|
||||||
|
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(a); i++ {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveEdge(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
// Test removing an existing edge
|
||||||
|
g.RemoveEdge(1, 2)
|
||||||
|
if edges := g.GetEdges(); len(edges) != 2 {
|
||||||
|
t.Errorf("RemoveEdge() failed: expected 2 edges, got %d", len(edges))
|
||||||
|
}
|
||||||
|
if neighbors := g.GetNeighbors(1); len(neighbors) != 0 {
|
||||||
|
t.Errorf("RemoveEdge() failed: expected 0 neighbors for vertex 1, got %d", len(neighbors))
|
||||||
|
}
|
||||||
|
if neighbors := g.GetNeighbors(2); len(neighbors) != 1 {
|
||||||
|
t.Errorf("RemoveEdge() failed: expected 1 neighbor for vertex 2, got %d", len(neighbors))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing a non-existing edge
|
||||||
|
g.RemoveEdge(1, 3)
|
||||||
|
if edges := g.GetEdges(); len(edges) != 2 {
|
||||||
|
t.Errorf("RemoveEdge() failed: expected 2 edges, got %d", len(edges))
|
||||||
|
}
|
||||||
|
if neighbors := g.GetNeighbors(1); len(neighbors) != 0 {
|
||||||
|
t.Errorf("RemoveEdge() failed: expected 0 neighbors for vertex 1, got %d", len(neighbors))
|
||||||
|
}
|
||||||
|
if neighbors := g.GetNeighbors(3); len(neighbors) != 2 {
|
||||||
|
t.Errorf("RemoveEdge() failed: expected 1 neighbor for vertex 3, got %d", len(neighbors))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveVertex(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(3, 4, 40)
|
||||||
|
|
||||||
|
// Remove vertex 2
|
||||||
|
g.RemoveVertex(2)
|
||||||
|
|
||||||
|
// Check if the vertex is removed correctly
|
||||||
|
expectedAdjacencyList := map[int][]Edge[int, int]{
|
||||||
|
1: {{2, 1, 3, 20}},
|
||||||
|
3: {{2, 3, 1, 20}, {4, 3, 4, 40}},
|
||||||
|
4: {{4, 4, 3, 40}},
|
||||||
|
}
|
||||||
|
if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) {
|
||||||
|
t.Errorf("RemoveVertex() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedEdges := []Edge[int, int]{
|
||||||
|
{2, 1, 3, 20},
|
||||||
|
{4, 3, 4, 40},
|
||||||
|
}
|
||||||
|
if !compareEdges(g.edges, expectedEdges) {
|
||||||
|
t.Errorf("RemoveVertex() failed: expected edges list = %v, got %v", expectedEdges, g.edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedV := 3
|
||||||
|
if g.v != expectedV {
|
||||||
|
t.Errorf("RemoveVertex() failed: expected v = %d, got v = %d", expectedV, g.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedE := 2
|
||||||
|
if g.e != expectedE {
|
||||||
|
t.Errorf("RemoveVertex() failed: expected e = %d, got e = %d", expectedE, g.e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEdges(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
// Test GetEdges
|
||||||
|
expectedEdges := []Edge[int, int]{
|
||||||
|
{ID: 1, src: 1, dest: 2, weight: 10},
|
||||||
|
{ID: 2, src: 2, dest: 3, weight: 20},
|
||||||
|
{ID: 3, src: 3, dest: 4, weight: 30},
|
||||||
|
}
|
||||||
|
edges := g.GetEdges()
|
||||||
|
if !compareEdges(edges, expectedEdges) {
|
||||||
|
t.Errorf("GetEdges() failed: expected edges = %v, got edges = %v", expectedEdges, edges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveEdgeWithID(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
|
||||||
|
// Test removing an existing edge
|
||||||
|
g.RemoveEdgeWithID(1)
|
||||||
|
|
||||||
|
// Check if the edge is removed from the list of edges
|
||||||
|
expectedEdges := []Edge[int, int]{
|
||||||
|
{ID: 2, src: 2, dest: 3, weight: 20},
|
||||||
|
{ID: 3, src: 3, dest: 4, weight: 30},
|
||||||
|
}
|
||||||
|
if !compareEdges(g.edges, expectedEdges) {
|
||||||
|
t.Errorf("RemoveEdgeWithID() failed: expected edges list = %v, got %v", expectedEdges, g.edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the edge is removed from the adjacency list
|
||||||
|
expectedAdjacencyList := map[int][]Edge[int, int]{
|
||||||
|
1: {},
|
||||||
|
2: {{2, 2, 3, 20}},
|
||||||
|
3: {{2, 3, 2, 20}, {3, 3, 4, 30}},
|
||||||
|
4: {{3, 4, 3, 30}},
|
||||||
|
}
|
||||||
|
if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) {
|
||||||
|
t.Errorf("RemoveEdgeWithID() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the edge count is updated
|
||||||
|
expectedE := 2
|
||||||
|
if g.e != expectedE {
|
||||||
|
t.Errorf("RemoveEdgeWithID() failed: expected e = %d, got e = %d", expectedE, g.e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing a non-existing edge
|
||||||
|
g.RemoveEdgeWithID(5)
|
||||||
|
|
||||||
|
// Check if the graph remains unchanged
|
||||||
|
if !compareEdges(g.edges, expectedEdges) {
|
||||||
|
t.Errorf("RemoveEdgeWithID() failed: expected edges list = %v, got %v", expectedEdges, g.edges)
|
||||||
|
}
|
||||||
|
if !compareAdjacencyLists(g.adjacencyList, expectedAdjacencyList) {
|
||||||
|
t.Errorf("RemoveEdgeWithID() failed: expected adjacency list = %v, got %v", expectedAdjacencyList, g.adjacencyList)
|
||||||
|
}
|
||||||
|
if g.e != expectedE {
|
||||||
|
t.Errorf("RemoveEdgeWithID() failed: expected e = %d, got e = %d", expectedE, g.e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestKargerMinCutUF(t *testing.T) {
|
||||||
|
// Create a new graph
|
||||||
|
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(3, 4, 40)
|
||||||
|
g.AddEdge(4, 5, 50)
|
||||||
|
g.AddEdge(4, 6, 60)
|
||||||
|
g.AddEdge(5, 6, 70)
|
||||||
|
|
||||||
|
// Run Karger's algorithm to find the minimum cut
|
||||||
|
cutEdges, resultSubsets := g.kargerMinCutUF(10000)
|
||||||
|
|
||||||
|
// Check if the number of cut edges is correct
|
||||||
|
expectedCutEdges := 1
|
||||||
|
if cutEdges != expectedCutEdges {
|
||||||
|
t.Errorf("kargerMinCutUF() failed: expected cut edges = %d, got cut edges = %d", expectedCutEdges, cutEdges)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the result subsets are correct
|
||||||
|
expectedResultSubsets := [][]int{{1, 2, 3}, {4, 5, 6}}
|
||||||
|
if !equalSubsets(resultSubsets, expectedResultSubsets) {
|
||||||
|
t.Errorf("kargerMinCutUF() failed: expected result subsets = %v, got result subsets = %v", expectedResultSubsets, resultSubsets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if two subsets are equal
|
||||||
|
func equalSubsets(a, b [][]int) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(a); i++ {
|
||||||
|
if !equalSlices(a[i], b[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBFS(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)
|
||||||
|
|
||||||
|
// Define the isGoal function
|
||||||
|
isGoal := func(vertex int) bool {
|
||||||
|
return vertex == 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform BFS starting from vertex 1
|
||||||
|
result := g.BFS(1, isGoal)
|
||||||
|
|
||||||
|
// Check if the result is correct
|
||||||
|
expectedResult := []int{1, 2, 3, 4}
|
||||||
|
if !equalSlices(result, expectedResult) {
|
||||||
|
t.Errorf("BFS() failed: expected %v, got %v", expectedResult, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform BFS starting from vertex 2
|
||||||
|
result = g.BFS(2, isGoal)
|
||||||
|
|
||||||
|
// Check if the result is correct
|
||||||
|
expectedResult = []int{2, 1, 3, 4}
|
||||||
|
if !equalSlices(result, expectedResult) {
|
||||||
|
t.Errorf("BFS() failed: expected %v, got %v", expectedResult, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform BFS starting from vertex 3
|
||||||
|
result = g.BFS(3, isGoal)
|
||||||
|
|
||||||
|
// Check if the result is correct
|
||||||
|
expectedResult = []int{3, 1, 2, 4}
|
||||||
|
if !equalSlices(result, expectedResult) {
|
||||||
|
t.Errorf("BFS() failed: expected %v, got %v", expectedResult, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform BFS starting from vertex 4
|
||||||
|
result = g.BFS(4, isGoal)
|
||||||
|
|
||||||
|
// Check if the result is correct
|
||||||
|
expectedResult = []int{4}
|
||||||
|
if !equalSlices(result, expectedResult) {
|
||||||
|
t.Errorf("BFS() failed: expected %v, got %v", expectedResult, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDFS(t *testing.T) {
|
||||||
|
g := NewGraph[int, int]()
|
||||||
|
g.AddEdge(1, 2, 10)
|
||||||
|
g.AddEdge(2, 3, 20)
|
||||||
|
g.AddEdge(3, 4, 30)
|
||||||
|
g.AddEdge(4, 5, 40)
|
||||||
|
g.AddEdge(5, 6, 50)
|
||||||
|
|
||||||
|
// Test DFS with a goal function that returns true for vertex 6
|
||||||
|
isGoal := func(vertex int) bool {
|
||||||
|
return vertex == 6
|
||||||
|
}
|
||||||
|
|
||||||
|
result := g.DFS(1, isGoal)
|
||||||
|
expectedResult := []int{1, 2, 3, 4, 5, 6}
|
||||||
|
if !equalSlices(result, expectedResult) {
|
||||||
|
t.Errorf("DFS() failed with goal function: expected %v, got %v", expectedResult, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test DFS with a goal function that returns true for vertex 3
|
||||||
|
isGoal = func(vertex int) bool {
|
||||||
|
return vertex == 3
|
||||||
|
}
|
||||||
|
|
||||||
|
result = g.DFS(1, isGoal)
|
||||||
|
expectedResult = []int{1, 2, 3}
|
||||||
|
if !equalSlices(result, expectedResult) {
|
||||||
|
t.Errorf("DFS() failed with goal function: expected %v, got %v", expectedResult, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test DFS with a goal function that returns true for vertex 7 (non-existent vertex)
|
||||||
|
isGoal = func(vertex int) bool {
|
||||||
|
return vertex == 7
|
||||||
|
}
|
||||||
|
|
||||||
|
result = g.DFS(1, isGoal)
|
||||||
|
expectedResult = []int{}
|
||||||
|
if !equalSlices(result, expectedResult) {
|
||||||
|
t.Errorf("DFS() failed with goal function: expected %v, got %v", expectedResult, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue