Documentation
¶
Index ¶
- func ConnectedComponents[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]
- func FindCycle[T comparable](g gograph.Graph[T]) ([]*gograph.Vertex[T], bool)
- func Gabow[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]
- func HasCycle[T comparable](g gograph.Graph[T]) bool
- func Kosaraju[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]
- func Tarjan[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]
- type Condensation
- type CondenseOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ConnectedComponents ¶ added in v0.9.0
func ConnectedComponents[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]
ConnectedComponents returns the connected components of g. For a directed graph, edge directions are ignored, so it returns the weakly connected components. The vertices in the result are the vertices stored in g, not copies, and an empty graph returns an empty slice.
The components are in the order of their first vertex in GetAllVertices. In an undirected graph, the vertices of a component are in breadth-first order from that vertex, following the order the edges were added. In a directed graph, they are in GetAllVertices order.
A graph from gograph.New takes O(V+E) time, up to the inverse Ackermann factor of union-find for directed graphs, and uses O(V) extra space. Any other Graph is read through GetAllVertices and EdgesOf. It panics if g is nil.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/connectivity"
)
func main() {
g := gograph.New[string](gograph.Directed())
_, _ = g.AddEdge(gograph.NewVertex("api"), gograph.NewVertex("auth"))
_, _ = g.AddEdge(gograph.NewVertex("billing"), gograph.NewVertex("auth"))
_, _ = g.AddEdge(gograph.NewVertex("search"), gograph.NewVertex("index"))
// Edge directions are ignored, so billing joins api through auth.
for _, component := range connectivity.ConnectedComponents(g) {
var labels []string
for _, v := range component {
labels = append(labels, v.Label())
}
fmt.Println(labels)
}
}
Output: [api auth billing] [search index]
func FindCycle ¶ added in v0.10.0
FindCycle returns the vertices of one cycle of g in order, without repeating the first vertex at the end: [a b c] means a -> b -> c -> a. It returns nil and false when g has no cycle, so it reports a cycle exactly when HasCycle does.
A self-loop is a cycle of one vertex. In a directed graph, a cycle follows the edge directions. In an undirected graph, the two stored directions of an edge count as one edge, so any other cycle has at least three vertices.
The cycle is the first one that traverse.DFSEventsAll finds: the first back edge u -> v closes it, and it runs from v down the search path to u. That is one cycle of g, and a shorter one can exist.
A graph created with gograph.Acyclic returns false without a search. Any other graph takes O(V+E) time, and the search stops at the first back edge. It keeps O(V) state, plus the neighbors still to look at of each vertex on the search path, which is at most O(V+E). HasCycle gives the answer alone faster, since it skips the edge events. It panics if g is nil.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/connectivity"
)
func main() {
g := gograph.New[string](gograph.Directed())
for _, e := range [][2]string{{"api", "auth"}, {"auth", "db"}, {"db", "cache"}, {"cache", "auth"}} {
_, _ = g.AddEdge(gograph.NewVertex(e[0]), gograph.NewVertex(e[1]))
}
cycle, ok := connectivity.FindCycle(g)
if !ok {
fmt.Println("no cycle")
return
}
var path []string
for _, v := range cycle {
path = append(path, v.Label())
}
fmt.Println("import cycle:", strings.Join(path, " -> "), "->", path[0])
}
Output: import cycle: auth -> db -> cache -> auth
func Gabow ¶
func Gabow[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]
Gabow runs Gabow's path-based algorithm, and returns a list of strongly connected components, where each component is represented as an array of pointers to vertex structs.
It keeps the search path in a slice instead of recursing, so the depth of the graph doesn't grow the goroutine stack.
The order of the components and of the vertices in them is stable: it only depends on the order of the vertices and edges in the graph, so a graph built the same way gives the same result on every run.
func HasCycle ¶ added in v0.9.0
func HasCycle[T comparable](g gograph.Graph[T]) bool
HasCycle reports whether g contains a cycle. Directed graphs are checked using their edge directions. In undirected graphs, the two stored directions of an edge are treated as one edge. A self-loop is a cycle.
A graph created with gograph.Acyclic returns false without a search. A graph from gograph.New takes O(V+E) time and O(V) extra space, and the search stops at the first cycle it finds. Any other graph is read through its methods. It panics if g is nil.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/connectivity"
)
func main() {
g := gograph.New[int]()
_, _ = g.AddEdge(gograph.NewVertex(1), gograph.NewVertex(2))
_, _ = g.AddEdge(gograph.NewVertex(2), gograph.NewVertex(3))
fmt.Println(connectivity.HasCycle(g))
_, _ = g.AddEdge(g.GetVertexByID(3), g.GetVertexByID(1))
fmt.Println(connectivity.HasCycle(g))
}
Output: false true
func Kosaraju ¶
func Kosaraju[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]
Kosaraju implements Kosaraju's Algorithm. It performs a depth-first search of the graph to create a stack of vertices, and then performs a second depth-first search on the transposed graph to identify the strongly connected components.
The function returns a slice of slices, where each slice represents a strongly connected component and contains the vertices that belong to that component. The vertices are the ones stored in g, not copies.
The order of the components and of the vertices in them is stable: it only depends on the order of the vertices and edges in the graph, so a graph built the same way gives the same result on every run.
func Tarjan ¶
func Tarjan[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]
Tarjan is the entry point to the algorithm. It initializes the index, stack, and sccs variables and then loops through all the vertices in the graph. It returns a slice of vertices' slice, where each inner slice represents a strongly connected component of the graph.
The order of the components and of the vertices in them is stable: it only depends on the order of the vertices and edges in the graph, so a graph built the same way gives the same result on every run.
Types ¶
type Condensation ¶ added in v0.9.0
type Condensation[T comparable] struct { // Graph has one vertex per strongly connected component, labeled 0 to // n-1 so that every edge goes from a lower label to a higher one. It is // directed and has no cycles. Graph gograph.Graph[int] // Members has the vertices of each component, indexed by component // label, as the original graph's own vertex pointers. Members [][]*gograph.Vertex[T] // ComponentOf maps the label of each original vertex to the label of // its component. ComponentOf map[T]int }
Condensation is a graph with one vertex per strongly connected component of another graph. It never has a cycle, so the functions that need a DAG, such as gograph.TopologySort, work on it.
func Condense ¶ added in v0.9.0
func Condense[T comparable](g gograph.Graph[T], opts ...CondenseOption[T]) (*Condensation[T], error)
Condense replaces each strongly connected component of g with a single vertex. A component with more than one member is a group of vertices that depend on each other, for example modules that have to be upgraded in one step.
There is an edge from component a to component b if at least one edge of g goes from a member of a to a member of b. Edges inside a component, including self-loops, are dropped. Without options, Condensation.Graph is unweighted and its vertices have no weight.
The components are numbered in topological order, and the members of each component follow the order of GetAllVertices. The result is stable: a graph built the same way gives the same result on every run.
Condensation.Graph is created with gograph.Directed and not gograph.Acyclic, since an acyclic graph sorts itself on every AddEdge. It has no cycles all the same.
It runs Tarjan once and takes O(V + E) time. It returns gograph.ErrNotDirected for undirected graphs.
Example ¶
package main
import (
"fmt"
"github.com/hmdsefi/gograph"
"github.com/hmdsefi/gograph/connectivity"
)
func main() {
g := gograph.New[string](gograph.Directed())
for _, e := range [][2]string{
{"api", "auth"},
{"auth", "users"},
{"users", "auth"},
{"users", "db"},
} {
_, _ = g.AddEdge(gograph.NewVertex(e[0]), gograph.NewVertex(e[1]))
}
c, err := connectivity.Condense(g)
if err != nil {
fmt.Println(err)
return
}
// the condensation has no cycles, so it can be sorted
order, err := gograph.TopologySort(c.Graph)
if err != nil {
fmt.Println(err)
return
}
for _, comp := range order {
var members []string
for _, v := range c.Members[comp.Label()] {
members = append(members, v.Label())
}
fmt.Println(comp.Label(), members)
}
}
Output: 0 [api] 1 [auth users] 2 [db]
type CondenseOption ¶ added in v0.9.0
type CondenseOption[T comparable] func(*condenseOptions[T])
CondenseOption configures Condense.
func WithComponentWeight ¶ added in v0.9.0
func WithComponentWeight[T comparable](fn func(members []*gograph.Vertex[T]) float64) CondenseOption[T]
WithComponentWeight sets the weight of each component vertex, for example the sum of the weights of its members. fn gets the members in the same order as Condensation.Members.
func WithCrossingWeight ¶ added in v0.9.0
func WithCrossingWeight[T comparable](fn func(crossing []*gograph.Edge[T]) float64) CondenseOption[T]
WithCrossingWeight sets the weight of each edge between two components from the original edges that cross between them, for example their minimum or their sum. fn gets the crossing edges in the order of AllEdges. With this option, Condensation.Graph is weighted.
