You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

513 lines
14 KiB
Go

// 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{}
}