├── go.sum ├── go.mod ├── .github ├── scripts │ └── coverage.sh └── workflows │ └── build.yml ├── .gitignore ├── graph_types.go ├── .golangci.yml ├── Makefile ├── connectivity ├── gabow_test.go ├── tarjan_test.go ├── kosaraju_test.go ├── kosaraju.go ├── tarjan.go ├── gabow.go └── README.md ├── traverse ├── iterator.go ├── topological_iterator.go ├── depth_first_iterator.go ├── breadth_first_iterator.go ├── breadth_first_iterator_test.go ├── depth_first_iterator_test.go ├── topological_iterator_test.go ├── closest_first_iterator.go ├── closest_first_iterator_test.go ├── random_walk_iterator.go ├── random_walk_iterator_test.go └── README.md ├── acyclic.go ├── graph_test.go ├── path ├── transitive-reduction.md ├── floyd-warshall.md ├── bellman-ford.md ├── dijkstra_test.go ├── floyd_warshall_test.go ├── bellman_ford.go ├── floyd_warshall.go ├── dijkstra.md ├── dijkstra.go ├── bellman_ford_test.go ├── transitive_reduction.go └── transitive_reduction_test.go ├── acyclic_test.go ├── partition ├── bron_kerbosch_test.go ├── k_cut_randomized_test.go ├── girvan_newman_test.go ├── k-cut.md ├── k_cut_randomized.go ├── bron_kerbosch.md ├── girvan-newman.md ├── girvan_newman.go └── bron_kerbosch.go ├── properties.go ├── util ├── priority_queue_test.go └── priority_queue.go ├── README.md ├── graph.go └── LICENSE /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hmdsefi/gograph 2 | 3 | go 1.24.2 4 | -------------------------------------------------------------------------------- /.github/scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.out 5 | 6 | for d in $(go list ./... | grep -v vendor); do 7 | go test -race -coverprofile=profile.out -covermode=atomic $d 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.out 10 | rm profile.out 11 | fi 12 | done -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | 10 | # Compiled Java class files 11 | *.class 12 | 13 | # Compiled Python bytecode 14 | *.py[cod] 15 | 16 | # Log files 17 | *.log 18 | 19 | /bin 20 | /.build 21 | 22 | # Package files 23 | *.jar 24 | 25 | # Maven 26 | target/ 27 | dist/ 28 | 29 | # JetBrains IDE 30 | .idea/ 31 | 32 | # Unit test reports 33 | TEST*.xml 34 | 35 | # Generated by MacOS 36 | .DS_Store 37 | 38 | # Generated by Windows 39 | Thumbs.db 40 | 41 | # Applications 42 | *.app 43 | *.exe 44 | *.war 45 | 46 | # Large media files 47 | *.mp4 48 | *.tiff 49 | *.avi 50 | *.flv 51 | *.mov 52 | *.wmv -------------------------------------------------------------------------------- /graph_types.go: -------------------------------------------------------------------------------- 1 | package gograph 2 | 3 | // GraphType defines methods to determine the type of graph. 4 | // A graph can have multiple types. e.g., a directed graph 5 | // can be a weighted or acyclic. 6 | type GraphType interface { 7 | // IsDirected returns true if the graph is directed, false otherwise. 8 | IsDirected() bool 9 | 10 | // IsAcyclic returns true if the graph is acyclic, false otherwise. 11 | IsAcyclic() bool 12 | 13 | // IsWeighted returns true if the graph is weighted, false otherwise. 14 | IsWeighted() bool 15 | } 16 | 17 | // IsDirected returns true if the graph is directed, false otherwise. 18 | func (g *baseGraph[T]) IsDirected() bool { 19 | return g.properties.isDirected 20 | } 21 | 22 | // IsAcyclic returns true if the graph is acyclic, false otherwise. 23 | func (g *baseGraph[T]) IsAcyclic() bool { 24 | return g.properties.isAcyclic 25 | } 26 | 27 | // IsWeighted returns true if the graph is weighted, false otherwise. 28 | func (g *baseGraph[T]) IsWeighted() bool { 29 | return g.properties.isWeighted 30 | } 31 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | - bodyclose 5 | - dogsled 6 | - gochecknoinits 7 | - gocritic 8 | - gosec 9 | - nakedret 10 | - staticcheck 11 | - unconvert 12 | - unparam 13 | - whitespace 14 | disable: 15 | - dupl 16 | - gochecknoglobals 17 | - lll 18 | settings: 19 | errcheck: 20 | check-type-assertions: true 21 | goconst: 22 | min-len: 2 23 | min-occurrences: 2 24 | gocritic: 25 | disabled-checks: 26 | - ifElseChain 27 | gocyclo: 28 | min-complexity: 25 29 | nakedret: 30 | max-func-lines: 15 31 | exclusions: 32 | generated: lax 33 | presets: 34 | - comments 35 | - common-false-positives 36 | - legacy 37 | - std-error-handling 38 | paths: 39 | - third_party$ 40 | - builtin$ 41 | - examples$ 42 | formatters: 43 | enable: 44 | - goimports 45 | exclusions: 46 | generated: lax 47 | paths: 48 | - third_party$ 49 | - builtin$ 50 | - examples$ 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO = go 2 | M = $(shell printf "\033[34;1m>>\033[0m") 3 | 4 | # Check richgo does exist. 5 | ifeq (, $(shell which richgo)) 6 | $(warning "could not find richgo in $(PATH), run: go get github.com/kyoh86/richgo") 7 | endif 8 | 9 | .PHONY: test sync codecov test-app 10 | 11 | .PHONY: default 12 | default: all 13 | 14 | .PHONY: all 15 | all: build test 16 | 17 | .PHONY: test 18 | test: sync 19 | $(info $(M) running tests) 20 | go test -race ./... 21 | 22 | .PHONY: coverage 23 | coverage: sync 24 | $(info $(M) running tests coverage) 25 | go test -coverprofile=c.out ./...;\ 26 | go tool cover -func=c.out;\ 27 | rm c.out 28 | 29 | 30 | 31 | .PHONY: build 32 | build: 33 | $(info $(M) build and compile) 34 | go build ./... 35 | 36 | .PHONY: sync 37 | sync: 38 | $(info $(M) downloading dependencies) 39 | go get -v ./... 40 | 41 | .PHONY: fmt 42 | fmt: 43 | $(info $(M) format code) 44 | @ret=0 && for d in $$($(GO) list -f '{{.Dir}}' ./... | grep -v /vendor/); do \ 45 | $(GO) fmt $$d/*.go || ret=$$? ; \ 46 | done ; exit $$ret 47 | 48 | .PHONY: lint 49 | lint: ## Run linters 50 | $(info $(M) running golangci linter) 51 | golangci-lint run --timeout 5m0s ./... 52 | 53 | -------------------------------------------------------------------------------- /connectivity/gabow_test.go: -------------------------------------------------------------------------------- 1 | package connectivity 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "testing" 7 | 8 | "github.com/hmdsefi/gograph" 9 | ) 10 | 11 | func TestGabow(t *testing.T) { 12 | // Create a dag with 6 vertices and 6 edges 13 | g := gograph.New[int](gograph.Directed()) 14 | 15 | v1 := g.AddVertexByLabel(1) 16 | v2 := g.AddVertexByLabel(2) 17 | v3 := g.AddVertexByLabel(3) 18 | v4 := g.AddVertexByLabel(4) 19 | v5 := g.AddVertexByLabel(5) 20 | 21 | _, _ = g.AddEdge(v1, v2) 22 | _, _ = g.AddEdge(v2, v3) 23 | _, _ = g.AddEdge(v3, v1) 24 | _, _ = g.AddEdge(v3, v4) 25 | _, _ = g.AddEdge(v4, v5) 26 | _, _ = g.AddEdge(v5, v4) 27 | 28 | // call the Tarjan function 29 | sccs := Gabow(g) 30 | 31 | // check that the function returned the expected number of SCCs 32 | if len(sccs) != 2 { 33 | t.Errorf("Expected 2 SCCs, got %d", len(sccs)) 34 | } 35 | 36 | // check that the function returned the correct SCCs 37 | expectedSCCs := map[int][]int{3: {1, 2, 3}, 2: {4, 5}} 38 | 39 | for i, scc := range sccs { 40 | var labels []int 41 | for _, items := range scc { 42 | labels = append(labels, items.Label()) 43 | } 44 | 45 | sort.Ints(labels) 46 | 47 | if !reflect.DeepEqual(expectedSCCs[len(scc)], labels) { 48 | t.Errorf("SCC %d: expected %v, got %v", i+1, expectedSCCs[i], scc) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /connectivity/tarjan_test.go: -------------------------------------------------------------------------------- 1 | package connectivity 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "testing" 7 | 8 | "github.com/hmdsefi/gograph" 9 | ) 10 | 11 | func TestTarjan(t *testing.T) { 12 | // Create a dag with 6 vertices and 6 edges 13 | g := gograph.New[int](gograph.Directed()) 14 | 15 | v1 := g.AddVertexByLabel(1) 16 | v2 := g.AddVertexByLabel(2) 17 | v3 := g.AddVertexByLabel(3) 18 | v4 := g.AddVertexByLabel(4) 19 | v5 := g.AddVertexByLabel(5) 20 | 21 | _, _ = g.AddEdge(v1, v2) 22 | _, _ = g.AddEdge(v2, v3) 23 | _, _ = g.AddEdge(v3, v1) 24 | _, _ = g.AddEdge(v3, v4) 25 | _, _ = g.AddEdge(v4, v5) 26 | _, _ = g.AddEdge(v5, v4) 27 | 28 | // call the Tarjan function 29 | sccs := Tarjan(g) 30 | 31 | // check that the function returned the expected number of SCCs 32 | if len(sccs) != 2 { 33 | t.Errorf("Expected 2 SCCs, got %d", len(sccs)) 34 | } 35 | 36 | // check that the function returned the correct SCCs 37 | expectedSCCs := map[int][]int{3: {1, 2, 3}, 2: {4, 5}} 38 | 39 | for i, scc := range sccs { 40 | var labels []int 41 | for _, items := range scc { 42 | labels = append(labels, items.Label()) 43 | } 44 | 45 | sort.Ints(labels) 46 | 47 | if !reflect.DeepEqual(expectedSCCs[len(scc)], labels) { 48 | t.Errorf("SCC %d: expected %v, got %v", i+1, expectedSCCs[i], scc) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /connectivity/kosaraju_test.go: -------------------------------------------------------------------------------- 1 | package connectivity 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "testing" 7 | 8 | "github.com/hmdsefi/gograph" 9 | ) 10 | 11 | func TestKosaraju(t *testing.T) { 12 | // Create a dag with 6 vertices and 6 edges 13 | g := gograph.New[int](gograph.Directed()) 14 | 15 | v1 := g.AddVertexByLabel(1) 16 | v2 := g.AddVertexByLabel(2) 17 | v3 := g.AddVertexByLabel(3) 18 | v4 := g.AddVertexByLabel(4) 19 | v5 := g.AddVertexByLabel(5) 20 | 21 | _, _ = g.AddEdge(v1, v2) 22 | _, _ = g.AddEdge(v2, v3) 23 | _, _ = g.AddEdge(v3, v1) 24 | _, _ = g.AddEdge(v3, v4) 25 | _, _ = g.AddEdge(v4, v5) 26 | _, _ = g.AddEdge(v5, v4) 27 | 28 | // call the Tarjan function 29 | sccs := Kosaraju(g) 30 | 31 | // check that the function returned the expected number of SCCs 32 | if len(sccs) != 2 { 33 | t.Errorf("Expected 2 SCCs, got %d", len(sccs)) 34 | } 35 | 36 | // check that the function returned the correct SCCs 37 | expectedSCCs := map[int][]int{3: {1, 2, 3}, 2: {4, 5}} 38 | 39 | for i, scc := range sccs { 40 | var labels []int 41 | for _, items := range scc { 42 | labels = append(labels, items.Label()) 43 | } 44 | 45 | sort.Ints(labels) 46 | 47 | if !reflect.DeepEqual(expectedSCCs[len(scc)], labels) { 48 | t.Errorf("SCC %d: expected %v, got %v", i+1, expectedSCCs[i], scc) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /traverse/iterator.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "github.com/hmdsefi/gograph" 5 | ) 6 | 7 | // Iterator represents a general purpose iterator for iterating over 8 | // a sequence of graph's vertices. It provides methods for checking if 9 | // there are more elements to be iterated over, getting the next element, 10 | // iterating over all elements using a callback function, and resetting 11 | // the iterator to its initial state. 12 | type Iterator[T comparable] interface { 13 | // HasNext returns a boolean value indicating whether there are more 14 | // elements to be iterated over. It returns true if there are more 15 | // elements. Otherwise, returns false. 16 | HasNext() bool 17 | 18 | // Next returns the next element in the sequence being iterated over. 19 | // If there are no more elements, it returns nil. It also advances 20 | // the iterator to the next element. 21 | Next() *gograph.Vertex[T] 22 | 23 | // Iterate iterates over all elements in the sequence and calls the 24 | // provided callback function on each element. The callback function 25 | // takes a single argument of type *Vertex, representing the current 26 | // element being iterated over. It returns an error value, which is 27 | // returned by the Iterate method. If the callback function returns 28 | // an error, iteration is stopped and the error is returned. 29 | Iterate(func(v *gograph.Vertex[T]) error) error 30 | 31 | // Reset resets the iterator to its initial state, allowing the 32 | // sequence to be iterated over again from the beginning. 33 | Reset() 34 | } 35 | -------------------------------------------------------------------------------- /acyclic.go: -------------------------------------------------------------------------------- 1 | package gograph 2 | 3 | // TopologySort performs a topological sort of the graph using 4 | // Kahn's algorithm. If the sorted list of vertices does not contain 5 | // all vertices in the graph, it means there is a cycle in the graph. 6 | // 7 | // It returns error if it finds a cycle in the graph. 8 | func TopologySort[T comparable](g Graph[T]) ([]*Vertex[T], error) { 9 | // Initialize a map to store the inDegree of each vertex 10 | inDegrees := make(map[*Vertex[T]]int) 11 | vertices := g.GetAllVertices() 12 | for _, v := range vertices { 13 | inDegrees[v] = v.inDegree 14 | } 15 | 16 | // Initialize a queue with vertices of inDegrees zero 17 | queue := make([]*Vertex[T], 0) 18 | for v, inDegree := range inDegrees { 19 | if inDegree == 0 { 20 | queue = append(queue, v) 21 | } 22 | } 23 | 24 | // Initialize the sorted list of vertices 25 | sortedVertices := make([]*Vertex[T], 0) 26 | 27 | // Loop through the vertices with inDegree zero 28 | for len(queue) > 0 { 29 | // Get the next vertex with inDegree zero 30 | curr := queue[0] 31 | queue = queue[1:] 32 | 33 | // Add the vertex to the sorted list 34 | sortedVertices = append(sortedVertices, curr) 35 | 36 | // Decrement the inDegree of each of the vertex's neighbors 37 | for _, neighbor := range curr.neighbors { 38 | inDegrees[neighbor]-- 39 | if inDegrees[neighbor] == 0 { 40 | queue = append(queue, neighbor) 41 | } 42 | } 43 | } 44 | 45 | // If the sorted list does not contain all vertices, there is a cycle 46 | if len(sortedVertices) != len(vertices) { 47 | return nil, ErrDAGHasCycle 48 | } 49 | 50 | return sortedVertices, nil 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [ push, pull_request ] 3 | jobs: 4 | golangci: 5 | name: lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: actions/setup-go@v5 10 | with: 11 | go-version: '1.24.2' # Explicitly set Go version 12 | - name: Run golangci-lint 13 | uses: golangci/golangci-lint-action@v8 14 | with: 15 | version: v2.1.6 # Latest stable version as of the search results:cite[2]:cite[3] 16 | 17 | build: 18 | name: Go build 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | go-version: 1.24.2 24 | - name: Build 25 | run: | 26 | git clone --depth=1 https://github.com/${GITHUB_REPOSITORY} 27 | cd $(basename ${GITHUB_REPOSITORY}) 28 | go build -v -race 29 | 30 | test: 31 | name: Go test 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v3 35 | with: 36 | go-version: 1.24.2 37 | - name: go get & test 38 | run: | 39 | go get -v -t -d ./... 40 | go test -v ./... 41 | 42 | - name: Generate coverage report 43 | run: sh ./.github/scripts/coverage.sh 44 | shell: bash 45 | 46 | - name: Upload coverage to codecov 47 | uses: codecov/codecov-action@v3 48 | with: 49 | token: ${{ secrets.CODECOV_TOKEN }} 50 | files: ./coverage.out 51 | flags: unittests # optional 52 | name: codecov-umbrella # optional 53 | fail_ci_if_error: false # optional (default = false) 54 | verbose: true # optional (default = false) 55 | -------------------------------------------------------------------------------- /graph_test.go: -------------------------------------------------------------------------------- 1 | package gograph 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestVertex(t *testing.T) { 9 | g := New[string]() 10 | vA := g.AddVertexByLabel("A") 11 | vB := g.AddVertexByLabel("B") 12 | vC := g.AddVertexByLabel("C") 13 | _, err := g.AddEdge(vA, vB) 14 | if err != nil { 15 | t.Errorf(testErrMsgError, err) 16 | } 17 | 18 | _, err = g.AddEdge(vA, vC) 19 | if err != nil { 20 | t.Errorf(testErrMsgError, err) 21 | } 22 | 23 | v := vA.NeighborByLabel("B") 24 | if !reflect.DeepEqual(vB, v) { 25 | t.Errorf(testErrMsgNotEqual, vB, v) 26 | } 27 | 28 | if !vA.HasNeighbor(vC) { 29 | t.Error(testErrMsgNotTrue) 30 | } 31 | 32 | if vA.HasNeighbor(NewVertex("D")) { 33 | t.Error(testErrMsgNotFalse) 34 | } 35 | 36 | if vA.OutDegree() != 2 { 37 | t.Errorf(testErrMsgNotEqual, 2, vA.OutDegree()) 38 | } 39 | 40 | // test cloning neighbors 41 | neighbors := vA.Neighbors() 42 | if len(neighbors) != len(vA.neighbors) { 43 | t.Errorf(testErrMsgNotEqual, len(neighbors), len(vA.neighbors)) 44 | } 45 | 46 | neighbors[0].label = "D" 47 | if neighbors[0].Label() == vA.neighbors[0].Label() { 48 | t.Error(testErrMsgNotFalse) 49 | } 50 | } 51 | 52 | func TestEdge_OtherVertex(t *testing.T) { 53 | edge := NewEdge[int](NewVertex(1), NewVertex(2)) 54 | 55 | if edge.OtherVertex(3) != nil { 56 | t.Errorf("Expect OtherVertex return nil, but get %+v", edge.OtherVertex(3)) 57 | } 58 | 59 | if edge.OtherVertex(1).label != edge.Destination().Label() { 60 | t.Errorf("Expect OtherVertex return 2, but get %+v", edge.OtherVertex(1)) 61 | } 62 | 63 | if edge.OtherVertex(2).label != edge.Source().Label() { 64 | t.Errorf("Expect OtherVertex return 1, but get %+v", edge.OtherVertex(2)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /path/transitive-reduction.md: -------------------------------------------------------------------------------- 1 | # gograph 2 | 3 | ## Path 4 | 5 | ### Transitive Reduction 6 | 7 | "An algorithm for finding a minimal equivalent graph of a digraph." — Harry Hsu, Journal of the ACM, 22(1):11–16, Jan. 1975. 8 | 9 | Here's a step-by-step explanation of how the transitive reduction algorithm works: 10 | 11 | 1. **Verify the graph is a DAG:** Transitive reduction as implemented here applies only to directed acyclic 12 | graphs. The algorithm checks whether the graph is directed and acyclic before proceeding. 13 | 14 | 2. **Find descendants efficiently:** For each vertex in the graph, the algorithm identifies its neighbors and 15 | their descendants using depth-first search traversal. Instead of computing the full transitive closure, 16 | this approach lazily computes descendants only when needed. 17 | 18 | 3. **Identify redundant edges:** For each vertex u and its direct neighbor v, the algorithm checks if any of 19 | v's descendants are also direct neighbors of u. If so, the edge from u to those descendants is redundant 20 | and can be removed, since there's already a path through v. 21 | 22 | 4. **Create the reduced graph:** The algorithm constructs a new graph with all vertices from the original 23 | graph but only includes non-redundant edges. 24 | 25 | The time complexity of this transitive reduction algorithm is `O(V(V+E))`, where V is the number of vertices 26 | and E is the number of edges in the graph. This is more efficient than the O(V³) approach using Floyd-Warshall, 27 | especially for sparse graphs where E is much smaller than V². 28 | 29 | For a directed acyclic graph (DAG), the transitive reduction is unique. This implementation is specifically 30 | designed for DAGs and will return an error if applied to graphs with cycles or undirected graphs. 31 | -------------------------------------------------------------------------------- /acyclic_test.go: -------------------------------------------------------------------------------- 1 | package gograph 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestTopologySort(t *testing.T) { 9 | // Create a dag with 6 vertices and 6 edges 10 | g := New[int](Acyclic()) 11 | 12 | if !g.IsDirected() { 13 | t.Error(testErrMsgNotTrue) 14 | } 15 | 16 | if !g.IsAcyclic() { 17 | t.Error(testErrMsgNotTrue) 18 | } 19 | 20 | v1 := g.AddVertexByLabel(1) 21 | v2 := g.AddVertexByLabel(2) 22 | v3 := g.AddVertexByLabel(3) 23 | v4 := g.AddVertexByLabel(4) 24 | v5 := g.AddVertexByLabel(5) 25 | v6 := g.AddVertexByLabel(6) 26 | 27 | _, err := g.AddEdge(v1, v2) 28 | if err != nil { 29 | t.Errorf("unexpected error: %v", err) 30 | } 31 | 32 | _, err = g.AddEdge(v2, v3) 33 | if err != nil { 34 | t.Errorf("unexpected error: %v", err) 35 | } 36 | 37 | _, err = g.AddEdge(v2, v4) 38 | if err != nil { 39 | t.Errorf("unexpected error: %v", err) 40 | } 41 | 42 | _, err = g.AddEdge(v2, v5) 43 | if err != nil { 44 | t.Errorf("unexpected error: %v", err) 45 | } 46 | 47 | _, err = g.AddEdge(v3, v5) 48 | if err != nil { 49 | t.Errorf("unexpected error: %v", err) 50 | } 51 | 52 | _, err = g.AddEdge(v4, v6) 53 | if err != nil { 54 | t.Errorf("unexpected error: %v", err) 55 | } 56 | 57 | _, err = g.AddEdge(v5, v6) 58 | if err != nil { 59 | t.Errorf("unexpected error: %v", err) 60 | } 61 | 62 | // Perform a topological sort 63 | sortedVertices, err := TopologySort[int](g) 64 | 65 | // Check that there was no error 66 | if err != nil { 67 | t.Errorf("unexpected error: %v", err) 68 | } 69 | 70 | // Check that the sorted order is correct 71 | expectedOrder := []*Vertex[int]{v1, v2, v3, v4, v5, v6} 72 | if !reflect.DeepEqual(sortedVertices, expectedOrder) { 73 | t.Errorf("unexpected sort order. Got %v, expected %v", sortedVertices, expectedOrder) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /path/floyd-warshall.md: -------------------------------------------------------------------------------- 1 | # gograph 2 | 3 | ## Shortest Path 4 | 5 | ### Floyd-Warshall 6 | 7 | The Floyd-Warshall algorithm is a dynamic programming algorithm used to find the shortest paths between 8 | all pairs of vertices in a weighted graph, even in the presence of negative weight edges (as long as there 9 | are no negative weight cycles). It was proposed by Robert Floyd and Stephen Warshall. 10 | 11 | Here's a step-by-step explanation of how the Floyd-Warshall algorithm works: 12 | 13 | 1. **Initialization:** Create a distance matrix `D[][]` where `D[i][j]` represents the shortest distance between 14 | vertex i and vertex j. Initialize this matrix with the weights of the edges between vertices if there 15 | is an edge, otherwise set the value to infinity. Also, set the diagonal elements `D[i][i]` to 0. 16 | 17 | 2. **Shortest Path Calculation:** Iterate through all vertices as intermediate vertices. For each pair of 18 | vertices (i, j), check if going through the current intermediate vertex k leads to a shorter path than 19 | the current known distance from i to j. If so, update the distance matrix `D[i][j]` to the new shorter 20 | distance `D[i][k] + D[k][j]`. 21 | 22 | 3. **Detection of Negative Cycles:** After the iterations, if any diagonal element `D[i][i]` of the distance 23 | matrix is negative, it indicates the presence of a negative weight cycle in the graph. 24 | 25 | 4. **Output:** The resulting distance matrix `D[][]` will contain the shortest path distances between all 26 | pairs of vertices. If there is a negative weight cycle, it might not produce the correct shortest paths, 27 | but it can still detect the presence of such cycles. 28 | 29 | The time complexity of the Floyd-Warshall algorithm is `O(V^3)`, where V is the number of vertices in the graph. 30 | Despite its cubic time complexity, it is often preferred over other algorithms like Bellman-Ford for dense 31 | graphs or when the graph has negative weight edges and no negative weight cycles, as it calculates shortest 32 | paths between all pairs of vertices in one go. -------------------------------------------------------------------------------- /partition/bron_kerbosch_test.go: -------------------------------------------------------------------------------- 1 | package partition 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sort" 7 | "testing" 8 | 9 | "github.com/hmdsefi/gograph" 10 | ) 11 | 12 | func normalizeVertexCliques[T comparable](cliques [][]*gograph.Vertex[T]) [][]*gograph.Vertex[T] { 13 | for _, c := range cliques { 14 | sort.Slice( 15 | c, func(i, j int) bool { 16 | return fmt.Sprintf("%v", c[i].Label()) < fmt.Sprintf("%v", c[j].Label()) 17 | }, 18 | ) 19 | } 20 | 21 | sort.Slice( 22 | cliques, func(i, j int) bool { 23 | a, b := cliques[i], cliques[j] 24 | for k := 0; k < len(a) && k < len(b); k++ { 25 | if a[k].Label() != b[k].Label() { 26 | return fmt.Sprintf("%v", a[k].Label()) < fmt.Sprintf("%v", b[k].Label()) 27 | } 28 | } 29 | return len(a) < len(b) 30 | }, 31 | ) 32 | 33 | return cliques 34 | } 35 | 36 | func TestMaximalCliques_Triangle(t *testing.T) { 37 | g := gograph.New[string]() 38 | 39 | a := g.AddVertexByLabel("A") 40 | b := g.AddVertexByLabel("B") 41 | c := g.AddVertexByLabel("C") 42 | 43 | _, _ = g.AddEdge(a, b) 44 | _, _ = g.AddEdge(b, c) 45 | _, _ = g.AddEdge(c, a) 46 | 47 | cliques := MaximalCliques(g) 48 | 49 | cliques = normalizeVertexCliques(cliques) 50 | 51 | want := [][]*gograph.Vertex[string]{{a, b, c}} 52 | want = normalizeVertexCliques(want) 53 | 54 | if !reflect.DeepEqual(cliques, want) { 55 | t.Fatalf("expected %v, got %v", want, cliques) 56 | } 57 | } 58 | 59 | func TestMaximalCliques_SquareWithDiagonal(t *testing.T) { 60 | g := gograph.New[string]() 61 | 62 | a := g.AddVertexByLabel("A") 63 | b := g.AddVertexByLabel("B") 64 | c := g.AddVertexByLabel("C") 65 | d := g.AddVertexByLabel("D") 66 | 67 | _, _ = g.AddEdge(a, b) 68 | _, _ = g.AddEdge(b, c) 69 | _, _ = g.AddEdge(c, d) 70 | _, _ = g.AddEdge(d, a) 71 | _, _ = g.AddEdge(a, c) // diagonal 72 | 73 | cliques := MaximalCliques(g) 74 | cliques = normalizeVertexCliques(cliques) 75 | 76 | want := [][]*gograph.Vertex[string]{ 77 | {a, b, c}, 78 | {a, c, d}, 79 | } 80 | want = normalizeVertexCliques(want) 81 | 82 | if !reflect.DeepEqual(cliques, want) { 83 | t.Fatalf("expected %v, got %v", want, cliques) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /path/bellman-ford.md: -------------------------------------------------------------------------------- 1 | # gograph 2 | 3 | ## Shortest Path 4 | 5 | ### Bellman-Ford 6 | 7 | The Bellman-Ford algorithm is a graph algorithm used to find the shortest path from a source vertex to all other 8 | vertices in a weighted graph, even in the presence of negative weight edges (as long as there are no negative weight 9 | cycles). It was developed by Richard Bellman and Lester Ford Jr. 10 | 11 | Here's a step-by-step explanation of how the Bellman-Ford algorithm works: 12 | 13 | 1. **Initialization:** Start by setting the distance of the source vertex to itself as 0, 14 | and the distance of all other vertices to infinity. 15 | 16 | 2. **Relaxation:** Iterate through all edges in the graph `|V| - 1` times, where `|V|` is 17 | the number of vertices. In each iteration, attempt to improve the shortest path estimates 18 | for all vertices. This is done by relaxing each edge: if the distance to the destination 19 | vertex through the current edge is shorter than the current estimate, update the estimate with the shorter distance. 20 | 21 | 3. **Detection of Negative Cycles:** After the `|V| - 1` iterations, perform an additional iteration. 22 | If during this iteration, any of the distances are further reduced, it indicates the presence of a 23 | negative weight cycle in the graph. This is because if a vertex's distance can still be improved 24 | after `|V| - 1` iterations, it means there's a negative weight cycle that can be traversed indefinitely 25 | to reduce the distance further. 26 | 27 | 4. **Output:** If there is no negative weight cycle, the algorithm outputs the shortest path 28 | distances from the source vertex to all other vertices. If there is a negative weight cycle, 29 | the algorithm typically returns an indication of this fact. 30 | 31 | The time complexity of the Bellman-Ford algorithm is `O(V*E)`, where V is the number of vertices and E 32 | is the number of edges. This makes it less efficient than algorithms like Dijkstra's algorithm for 33 | graphs with non-negative edge weights, but its ability to handle negative weight edges (as long as 34 | there are no negative weight cycles) makes it useful in certain scenarios. 35 | 36 | 37 | -------------------------------------------------------------------------------- /traverse/topological_iterator.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "github.com/hmdsefi/gograph" 5 | ) 6 | 7 | // topologicalIterator is an implementation of the Iterator interface 8 | // for traversing a graph using a topological sort algorithm. 9 | type topologicalIterator[T comparable] struct { 10 | graph gograph.Graph[T] // the graph being traversed. 11 | queue []*gograph.Vertex[T] // a slice that represents the queue of vertices to visit in topological order. 12 | head int // the current head of the queue. 13 | } 14 | 15 | // NewTopologicalIterator creates a new instance of topologicalIterator 16 | // and returns it as the Iterator interface. 17 | func NewTopologicalIterator[T comparable](g gograph.Graph[T]) (Iterator[T], error) { 18 | return newTopologicalIterator[T](g) 19 | } 20 | 21 | func newTopologicalIterator[T comparable](g gograph.Graph[T]) (*topologicalIterator[T], error) { 22 | queue, err := gograph.TopologySort[T](g) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return &topologicalIterator[T]{ 28 | graph: g, 29 | queue: queue, 30 | head: -1, 31 | }, nil 32 | } 33 | 34 | // HasNext returns a boolean indicating whether there are more vertices 35 | // to be visited in the topological traversal. It returns true if the 36 | // head index is in the range of the queue indices. 37 | func (t *topologicalIterator[T]) HasNext() bool { 38 | return t.head < len(t.queue)-1 39 | } 40 | 41 | // Next returns the next vertex to be visited in the topological order. 42 | // If the HasNext is false, returns nil. 43 | func (t *topologicalIterator[T]) Next() *gograph.Vertex[T] { 44 | if !t.HasNext() { 45 | return nil 46 | } 47 | 48 | t.head++ 49 | return t.queue[t.head] 50 | } 51 | 52 | // Iterate iterates through all the vertices in the BFS traversal order 53 | // and applies the given function to each vertex. If the function returns 54 | // an error, the iteration stops and the error is returned. 55 | func (t *topologicalIterator[T]) Iterate(f func(v *gograph.Vertex[T]) error) error { 56 | for t.HasNext() { 57 | if err := f(t.Next()); err != nil { 58 | return err 59 | } 60 | } 61 | 62 | return nil 63 | } 64 | 65 | // Reset resets the iterator by setting the initial state of the iterator. 66 | // It calls the gograph.TopologySort again. If topology sort returns 67 | // error, it panics. 68 | func (t *topologicalIterator[T]) Reset() { 69 | t.head = -1 70 | 71 | var err error 72 | t.queue, err = gograph.TopologySort[T](t.graph) 73 | if err != nil { 74 | panic(err) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /properties.go: -------------------------------------------------------------------------------- 1 | package gograph 2 | 3 | // GraphOptionFunc represent an alias of function type that 4 | // modifies the specified graph properties. 5 | type GraphOptionFunc func(properties *GraphProperties) 6 | 7 | // GraphProperties represents the properties of a graph. 8 | type GraphProperties struct { 9 | isDirected bool 10 | isWeighted bool 11 | isAcyclic bool 12 | } 13 | 14 | func newProperties(options ...GraphOptionFunc) GraphProperties { 15 | var properties GraphProperties 16 | for _, option := range options { 17 | option(&properties) 18 | } 19 | 20 | return properties 21 | } 22 | 23 | // Acyclic returns a GraphOptionFunc that modifies the specified 24 | // graph properties. It sets the isAcyclic and isDirected to true. 25 | // Only direct graph can be acyclic. 26 | func Acyclic() GraphOptionFunc { 27 | return func(properties *GraphProperties) { 28 | properties.isAcyclic = true 29 | properties.isDirected = true 30 | } 31 | } 32 | 33 | // Directed returns a GraphOptionFunc that modifies the specified 34 | // graph properties. It sets the isDirected to true. 35 | func Directed() GraphOptionFunc { 36 | return func(properties *GraphProperties) { 37 | properties.isDirected = true 38 | } 39 | } 40 | 41 | // Weighted returns a GraphOptionFunc that modifies the specified 42 | // graph properties. It sets the isWeighted to true. 43 | func Weighted() GraphOptionFunc { 44 | return func(properties *GraphProperties) { 45 | properties.isWeighted = true 46 | } 47 | } 48 | 49 | // EdgeOptionFunc represent an alias of function type that 50 | // modifies the specified edge properties. 51 | type EdgeOptionFunc func(properties *EdgeProperties) 52 | 53 | // EdgeProperties represents the properties of an edge. 54 | type EdgeProperties struct { 55 | weight float64 56 | } 57 | 58 | // WithEdgeWeight sets the edge weight for the specified edge 59 | // properties in the returned EdgeOptionFunc. 60 | func WithEdgeWeight(weight float64) EdgeOptionFunc { 61 | return func(properties *EdgeProperties) { 62 | properties.weight = weight 63 | } 64 | } 65 | 66 | // VertexOptionFunc represent an alias of function type that 67 | // modifies the specified vertex properties. 68 | type VertexOptionFunc func(properties *VertexProperties) 69 | 70 | // VertexProperties represents the properties of an edge. 71 | type VertexProperties struct { 72 | weight float64 73 | } 74 | 75 | // WithVertexWeight sets the edge weight for the specified vertex 76 | // properties in the returned VertexOptionFunc. 77 | func WithVertexWeight(weight float64) VertexOptionFunc { 78 | return func(properties *VertexProperties) { 79 | properties.weight = weight 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /path/dijkstra_test.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hmdsefi/gograph" 7 | ) 8 | 9 | func TestDijkstraSimple(t *testing.T) { 10 | g := gograph.New[string](gograph.Weighted()) 11 | 12 | vA := g.AddVertexByLabel("A") 13 | vB := g.AddVertexByLabel("B") 14 | vC := g.AddVertexByLabel("C") 15 | vD := g.AddVertexByLabel("D") 16 | 17 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(4)) 18 | _, _ = g.AddEdge(vA, vC, gograph.WithEdgeWeight(3)) 19 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1)) 20 | _, _ = g.AddEdge(vB, vD, gograph.WithEdgeWeight(2)) 21 | _, _ = g.AddEdge(vC, vD, gograph.WithEdgeWeight(4)) 22 | 23 | // use not existing vertex 24 | dist := DijkstraSimple(g, "X") 25 | if len(dist) > 0 { 26 | t.Errorf("Expected dist map length be 0, got %d", len(dist)) 27 | } 28 | 29 | dist = DijkstraSimple(g, "A") 30 | 31 | if dist[vA.Label()] != 0 { 32 | t.Errorf("Expected distance from A to %s to be 0, got %f", vA.Label(), dist[vA.Label()]) 33 | } 34 | if dist[vB.Label()] != 4 { 35 | t.Errorf("Expected distance from A to %s to be 4, got %f", vB.Label(), dist[vB.Label()]) 36 | } 37 | if dist[vC.Label()] != 3 { 38 | t.Errorf("Expected distance from A to %s to be 3, got %f", vC.Label(), dist[vC.Label()]) 39 | } 40 | if dist[vD.Label()] != 6 { 41 | t.Errorf("Expected distance from A to %s to be 6, got %f", vD.Label(), dist[vD.Label()]) 42 | } 43 | } 44 | 45 | func TestDijkstra(t *testing.T) { 46 | g := gograph.New[int](gograph.Weighted()) 47 | 48 | v1 := g.AddVertexByLabel(1) 49 | v2 := g.AddVertexByLabel(2) 50 | v3 := g.AddVertexByLabel(3) 51 | v4 := g.AddVertexByLabel(4) 52 | 53 | _, _ = g.AddEdge(v1, v2, gograph.WithEdgeWeight(4)) 54 | _, _ = g.AddEdge(v1, v3, gograph.WithEdgeWeight(3)) 55 | _, _ = g.AddEdge(v2, v3, gograph.WithEdgeWeight(1)) 56 | _, _ = g.AddEdge(v2, v4, gograph.WithEdgeWeight(2)) 57 | _, _ = g.AddEdge(v3, v4, gograph.WithEdgeWeight(4)) 58 | 59 | dist := Dijkstra(g, 1) 60 | 61 | if dist[v1.Label()] != 0 { 62 | t.Errorf("Expected distance from 1 to %d to be 0, got %f", v1.Label(), dist[v1.Label()]) 63 | } 64 | if dist[v2.Label()] != 4 { 65 | t.Errorf("Expected distance from 1 to %d to be 4, got %f", v2.Label(), dist[v2.Label()]) 66 | } 67 | if dist[v3.Label()] != 3 { 68 | t.Errorf("Expected distance from 1 to %d to be 3, got %f", v3.Label(), dist[v3.Label()]) 69 | } 70 | if dist[v4.Label()] != 6 { 71 | t.Errorf("Expected distance from 1 to %d to be 6, got %f", v4.Label(), dist[v4.Label()]) 72 | } 73 | 74 | // use not existing vertex 75 | dist = Dijkstra(g, 0) 76 | if len(dist) > 0 { 77 | t.Errorf("Expected dist map length be 0, got %d", len(dist)) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /partition/k_cut_randomized_test.go: -------------------------------------------------------------------------------- 1 | package partition 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hmdsefi/gograph" 7 | ) 8 | 9 | func TestRandomizedKCut_SmallGraph(t *testing.T) { 10 | g := gograph.New[string]() 11 | a := g.AddVertexByLabel("A") 12 | b := g.AddVertexByLabel("B") 13 | c := g.AddVertexByLabel("C") 14 | d := g.AddVertexByLabel("D") 15 | 16 | _, _ = g.AddEdge(a, b) 17 | _, _ = g.AddEdge(a, c) 18 | _, _ = g.AddEdge(b, c) 19 | _, _ = g.AddEdge(b, d) 20 | _, _ = g.AddEdge(c, d) 21 | 22 | k := 2 23 | result, err := RandomizedKCut(g, k) 24 | if err != nil { 25 | t.Fatalf("unexpected error: %v", err) 26 | } 27 | 28 | if len(result.Supernodes) == 0 { 29 | t.Errorf("expected supernodes in k-cut, got none") 30 | } 31 | 32 | if len(result.CutEdges) == 0 { 33 | t.Errorf("expected edges in k-cut, got none") 34 | } 35 | 36 | var supernodes []string 37 | for _, supernode := range result.Supernodes { 38 | var str string 39 | for _, v := range supernode { 40 | str += v.Label() 41 | } 42 | supernodes = append(supernodes, str) 43 | } 44 | 45 | t.Log(supernodes) 46 | 47 | if len(supernodes[0])+len(supernodes[1]) != int(g.Order()) { 48 | t.Errorf("expected total number of nodes to be %d, got %d", g.Order(), len(supernodes[0])+len(supernodes[1])) 49 | } 50 | } 51 | 52 | func TestRandomizedKCut_KEqualsVertices(t *testing.T) { 53 | g := gograph.New[int]() 54 | v := []int{1, 2, 3} 55 | for _, val := range v { 56 | g.AddVertexByLabel(val) 57 | } 58 | 59 | _, _ = g.AddEdge(g.GetVertexByID(1), g.GetVertexByID(2)) 60 | _, _ = g.AddEdge(g.GetVertexByID(2), g.GetVertexByID(3)) 61 | _, _ = g.AddEdge(g.GetVertexByID(1), g.GetVertexByID(3)) 62 | 63 | result, err := RandomizedKCut(g, 3) 64 | if err != nil { 65 | t.Fatalf("unexpected error: %v", err) 66 | } 67 | 68 | if len(result.CutEdges) != 0 { 69 | t.Errorf("expected 0 edges when k == number of vertices, got %d", len(result.CutEdges)) 70 | } 71 | } 72 | 73 | func TestRandomizedKCut_InvalidK(t *testing.T) { 74 | g := gograph.New[int]() 75 | g.AddVertexByLabel(1) 76 | g.AddVertexByLabel(2) 77 | 78 | _, err := RandomizedKCut(g, 1) 79 | if err == nil { 80 | t.Errorf("expected error for k < 2") 81 | } 82 | } 83 | 84 | func TestRandomizedKCut_EmptyGraph(t *testing.T) { 85 | g := gograph.New[int]() 86 | _, err := RandomizedKCut(g, 2) 87 | if err == nil { 88 | t.Errorf("expected error for empty graph") 89 | } 90 | } 91 | 92 | func TestRandomizedKCut_DisconnectedGraph(t *testing.T) { 93 | g := gograph.New[int]() 94 | g.AddVertexByLabel(1) 95 | g.AddVertexByLabel(2) 96 | g.AddVertexByLabel(3) 97 | 98 | // No edges, fully disconnected 99 | result, err := RandomizedKCut(g, 2) 100 | if err != nil { 101 | t.Fatalf("unexpected error: %v", err) 102 | } 103 | if len(result.CutEdges) != 0 { 104 | t.Errorf("expected 0 edges for disconnected graph, got %d", len(result.CutEdges)) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /path/floyd_warshall_test.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "testing" 7 | 8 | "github.com/hmdsefi/gograph" 9 | ) 10 | 11 | func TestFloydWarshall(t *testing.T) { 12 | g := gograph.New[string](gograph.Weighted(), gograph.Directed()) 13 | 14 | vA := g.AddVertexByLabel("A") 15 | vB := g.AddVertexByLabel("B") 16 | vC := g.AddVertexByLabel("C") 17 | vD := g.AddVertexByLabel("D") 18 | vE := g.AddVertexByLabel("E") 19 | vF := g.AddVertexByLabel("F") 20 | 21 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5)) 22 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1)) 23 | _, _ = g.AddEdge(vB, vD, gograph.WithEdgeWeight(2)) 24 | _, _ = g.AddEdge(vC, vE, gograph.WithEdgeWeight(1)) 25 | _, _ = g.AddEdge(vE, vD, gograph.WithEdgeWeight(-1)) 26 | _, _ = g.AddEdge(vD, vF, gograph.WithEdgeWeight(2)) 27 | _, _ = g.AddEdge(vF, vE, gograph.WithEdgeWeight(3)) 28 | 29 | dist, err := FloydWarshall(g) 30 | if err != nil { 31 | t.Errorf("Expected no errors, but get an err: %s", err) 32 | } 33 | 34 | inf := math.Inf(1) 35 | 36 | expectedDist := map[string]map[string]float64{ 37 | "A": {"A": 0, "B": 5, "C": 6, "D": 6, "E": 7, "F": 8}, 38 | "B": {"A": inf, "B": 0, "C": 1, "D": 1, "E": 2, "F": 3}, 39 | "C": {"A": inf, "B": inf, "C": 0, "D": 0, "E": 1, "F": 2}, 40 | "D": {"A": inf, "B": inf, "C": inf, "D": 0, "E": 5, "F": 2}, 41 | "E": {"A": inf, "B": inf, "C": inf, "D": -1, "E": 0, "F": 1}, 42 | "F": {"A": inf, "B": inf, "C": inf, "D": 2, "E": 3, "F": 0}, 43 | } 44 | 45 | for source, destMap := range dist { 46 | for dest, value := range destMap { 47 | if expectedDist[source][dest] != value { 48 | t.Fatalf( 49 | "expected distance %f from %s to %s, but got %f", 50 | expectedDist[source][dest], 51 | source, 52 | dest, 53 | value, 54 | ) 55 | } 56 | } 57 | } 58 | } 59 | 60 | func TestFloydWarshall_NotWeighted(t *testing.T) { 61 | g := gograph.New[string](gograph.Directed()) 62 | 63 | vA := g.AddVertexByLabel("A") 64 | vB := g.AddVertexByLabel("B") 65 | vC := g.AddVertexByLabel("C") 66 | 67 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5)) 68 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1)) 69 | 70 | _, err := FloydWarshall(g) 71 | if err == nil { 72 | t.Errorf("Expected error, but got nil") 73 | } 74 | 75 | if !errors.Is(err, ErrNotWeighted) { 76 | t.Errorf("Expected error \"%s\", but got \"%s\"", ErrNotWeighted, err) 77 | } 78 | } 79 | 80 | func TestFloydWarshall_NotDirected(t *testing.T) { 81 | g := gograph.New[string](gograph.Weighted()) 82 | 83 | vA := g.AddVertexByLabel("A") 84 | vB := g.AddVertexByLabel("B") 85 | vC := g.AddVertexByLabel("C") 86 | 87 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5)) 88 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1)) 89 | 90 | _, err := FloydWarshall(g) 91 | if err == nil { 92 | t.Errorf("Expected error, but got nil") 93 | } 94 | 95 | if !errors.Is(err, ErrNotDirected) { 96 | t.Errorf("Expected error \"%s\", but got \"%s\"", ErrNotDirected, err) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /util/priority_queue_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "container/heap" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/hmdsefi/gograph" 9 | ) 10 | 11 | func TestPriorityQueue(t *testing.T) { 12 | // create a new priority queue 13 | pq := make(priorityQueue[string], 0) 14 | 15 | // push some items with different priorities to the queue 16 | heap.Push(&pq, NewVertexWithPriority(gograph.NewVertex("A"), 3)) 17 | heap.Push(&pq, NewVertexWithPriority(gograph.NewVertex("B"), 1)) 18 | heap.Push(&pq, NewVertexWithPriority(gograph.NewVertex("C"), 5)) 19 | heap.Push(&pq, NewVertexWithPriority(gograph.NewVertex("D"), 2)) 20 | heap.Push(&pq, NewVertexWithPriority(gograph.NewVertex("E"), 4)) 21 | 22 | // push different type 23 | heap.Push(&pq, 123) 24 | 25 | // check that the length of the priority queue is 5 26 | if len(pq) != 5 { 27 | t.Errorf("priorityQueue length = %d; want 5", len(pq)) 28 | } 29 | 30 | // pop items from the queue and check that they are in the correct order 31 | items := make([]string, 0) 32 | for pq.Len() > 0 { 33 | item := heap.Pop(&pq) 34 | vp, ok := item.(*VertexWithPriority[string]) 35 | if !ok { 36 | t.Errorf("Expected *VertexWithPriority[string] type, but got %+v", reflect.TypeOf(item)) 37 | } 38 | items = append(items, vp.Vertex().Label()) 39 | } 40 | expected := []string{"B", "D", "A", "E", "C"} 41 | if !reflect.DeepEqual(items, expected) { 42 | t.Errorf("PriorityQueue Pop() order = %v; want %v", items, expected) 43 | } 44 | } 45 | 46 | func TestVertexPriorityQueue(t *testing.T) { 47 | // create a new vertex priority queue 48 | vpq := NewVertexPriorityQueue[string]() 49 | 50 | // push some items with different priorities to the queue 51 | vpq.Push(NewVertexWithPriority(gograph.NewVertex("A"), 2)) 52 | vpq.Push(NewVertexWithPriority(gograph.NewVertex("B"), 1)) 53 | vpq.Push(NewVertexWithPriority(gograph.NewVertex("C"), 3)) 54 | 55 | if vpq.Peek().vertex.Label() != "B" { 56 | t.Errorf("Expected Peek returns B, but got %v", vpq.Peek().vertex.Label()) 57 | } 58 | 59 | // check that the length of the priority queue is 5 60 | if vpq.Len() != 3 { 61 | t.Errorf("VertexPriorityQueue length = %d; want 5", len(vpq.pq)) 62 | } 63 | 64 | // pop items from the queue and check that they are in the correct order 65 | items := make([]string, 0) 66 | priorities := make([]float64, 0) 67 | for vpq.pq.Len() > 0 { 68 | item := vpq.Pop() 69 | items = append(items, item.Vertex().Label()) 70 | priorities = append(priorities, item.Priority()) 71 | } 72 | expectedVertices := []string{"B", "A", "C"} 73 | if !reflect.DeepEqual(items, expectedVertices) { 74 | t.Errorf("VertexPriorityQueue Pop() order = %v; want %v", items, expectedVertices) 75 | } 76 | 77 | expectedPriorities := []float64{1, 2, 3} 78 | if !reflect.DeepEqual(priorities, expectedPriorities) { 79 | t.Errorf("VertexPriorityQueue Pop() order = %v; want %v", priorities, expectedPriorities) 80 | } 81 | 82 | if vpq.Peek() != nil { 83 | t.Errorf("Expected Peek returns nil, but got %v", vpq.Peek()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /traverse/depth_first_iterator.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "github.com/hmdsefi/gograph" 5 | ) 6 | 7 | // depthFirstIterator is an implementation of the Iterator interface 8 | // for traversing a graph using a depth-first search (DFS) algorithm. 9 | type depthFirstIterator[T comparable] struct { 10 | graph gograph.Graph[T] // the graph being traversed. 11 | start T // the label of the starting vertex for the DFS traversal. 12 | stack []T // a slice that represents the stack of vertices to visit in DFS traversal order. 13 | visited map[T]bool // a map that keeps track of whether a vertex has been visited or not. 14 | } 15 | 16 | // NewDepthFirstIterator creates a new instance of depthFirstIterator 17 | // and returns it as the Iterator interface. 18 | func NewDepthFirstIterator[T comparable](g gograph.Graph[T], start T) (Iterator[T], error) { 19 | v := g.GetVertexByID(start) 20 | if v == nil { 21 | return nil, gograph.ErrVertexDoesNotExist 22 | } 23 | 24 | return newDepthFirstIterator[T](g, start), nil 25 | } 26 | 27 | func newDepthFirstIterator[T comparable](g gograph.Graph[T], start T) *depthFirstIterator[T] { 28 | return &depthFirstIterator[T]{ 29 | graph: g, 30 | start: start, 31 | stack: []T{start}, 32 | visited: map[T]bool{start: true}, 33 | } 34 | } 35 | 36 | // HasNext returns a boolean indicating whether there are more vertices 37 | // to be visited in the DFS traversal. It returns true if the head index 38 | // is in the range of the queue indices. 39 | func (d *depthFirstIterator[T]) HasNext() bool { 40 | return len(d.stack) > 0 41 | } 42 | 43 | // Next returns the next vertex to be visited in the DFS traversal. It 44 | // pops the latest vertex that has been added to the stack. 45 | // If the HasNext is false, returns nil. 46 | func (d *depthFirstIterator[T]) Next() *gograph.Vertex[T] { 47 | if !d.HasNext() { 48 | return nil 49 | } 50 | 51 | // get the next vertex from the queue 52 | label := d.stack[len(d.stack)-1] 53 | d.stack = d.stack[:len(d.stack)-1] 54 | currentNode := d.graph.GetVertexByID(label) 55 | 56 | // add unvisited neighbors to the queue 57 | neighbors := currentNode.Neighbors() 58 | for _, neighbor := range neighbors { 59 | if !d.visited[neighbor.Label()] { 60 | d.stack = append(d.stack, neighbor.Label()) 61 | d.visited[neighbor.Label()] = true 62 | } 63 | } 64 | 65 | return currentNode 66 | } 67 | 68 | // Iterate iterates through all the vertices in the DFS traversal order 69 | // and applies the given function to each vertex. If the function returns 70 | // an error, the iteration stops and the error is returned. 71 | func (d *depthFirstIterator[T]) Iterate(f func(v *gograph.Vertex[T]) error) error { 72 | for d.HasNext() { 73 | if err := f(d.Next()); err != nil { 74 | return err 75 | } 76 | } 77 | 78 | return nil 79 | } 80 | 81 | // Reset resets the iterator by setting the initial state of the iterator. 82 | func (d *depthFirstIterator[T]) Reset() { 83 | d.stack = []T{d.start} 84 | d.visited = map[T]bool{d.start: true} 85 | } 86 | -------------------------------------------------------------------------------- /path/bellman_ford.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | 7 | "github.com/hmdsefi/gograph" 8 | ) 9 | 10 | var ( 11 | ErrNegativeWeightCycle = errors.New("graph contains negative weight cycle") 12 | ErrNotDirected = errors.New("graph is not directed") 13 | ErrNotWeighted = errors.New("graph is not weighted") 14 | ) 15 | 16 | // BellmanFord finds the shortest path from a source vertex to all other vertices 17 | // in a weighted graph, even in the presence of negative weight edges, as long as 18 | // there are no negative weight cycle. 19 | // 20 | // Steps: 21 | // 22 | // 1.Initialization: Start by setting the distance of the source vertex to itself 23 | // as 0, and the distance of all other vertices to infinity. 24 | // 25 | // 2. Relaxation: Iterate through all edges in the graph |V| - 1 times, where |V| 26 | // is the number of vertices. In each iteration, attempt to improve the shortest 27 | // path estimates for all vertices. This is done by relaxing each edge: if the distance 28 | // to the destination vertex through the current edge is shorter than the current 29 | // estimate, update the estimate with the shorter distance. 30 | // 31 | // 3.Detection of Negative Cycles: After the |V| - 1 iterations, perform an additional 32 | // iteration. If during this iteration, any of the distances are further reduced, it 33 | // indicates the presence of a negative weight cycle in the graph. This is because if a 34 | // vertex's distance can still be improved after |V| - 1 iterations, it means there's a 35 | // negative weight cycle that can be traversed indefinitely to reduce the distance further. 36 | // 37 | // 4.Output: If there is no negative weight cycle, the algorithm outputs the shortest path 38 | // distances from the source vertex to all other vertices. If there is a negative weight 39 | // cycle, the algorithm typically returns an indication of this fact. 40 | // 41 | // The time complexity of the Bellman-Ford algorithm is O(V*E), where V is the number of vertices 42 | // and E is the number of edges. 43 | func BellmanFord[T comparable](g gograph.Graph[T], start T) (map[T]float64, error) { 44 | if !g.IsWeighted() { 45 | return nil, ErrNotWeighted 46 | } 47 | 48 | if !g.IsDirected() { 49 | return nil, ErrNotDirected 50 | } 51 | 52 | vertices := g.GetAllVertices() 53 | edges := g.AllEdges() 54 | 55 | dist := make(map[T]float64) 56 | maxValue := math.Inf(1) 57 | for _, v := range vertices { 58 | dist[v.Label()] = maxValue 59 | } 60 | 61 | dist[start] = 0 62 | for i := 1; i < len(vertices); i++ { 63 | for _, edge := range edges { 64 | weight := edge.Weight() 65 | if dist[edge.Source().Label()] != maxValue && 66 | dist[edge.Source().Label()]+weight < dist[edge.Destination().Label()] { 67 | dist[edge.Destination().Label()] = dist[edge.Source().Label()] + weight 68 | } 69 | } 70 | } 71 | 72 | for _, edge := range edges { 73 | if dist[edge.Source().Label()] != maxValue && 74 | dist[edge.Source().Label()]+edge.Weight() < dist[edge.Destination().Label()] { 75 | return nil, ErrNegativeWeightCycle 76 | } 77 | } 78 | 79 | return dist, nil 80 | } 81 | -------------------------------------------------------------------------------- /traverse/breadth_first_iterator.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "github.com/hmdsefi/gograph" 5 | ) 6 | 7 | // breadthFirstIterator is an implementation of the Iterator interface 8 | // for traversing a graph using a breadth-first search (BFS) algorithm. 9 | type breadthFirstIterator[T comparable] struct { 10 | graph gograph.Graph[T] // the graph being traversed. 11 | start T // the label of the starting vertex for the BFS traversal. 12 | queue []T // a slice that represents the queue of vertices to visit in BFS traversal order. 13 | visited map[T]bool // a map that keeps track of whether a vertex has been visited or not. 14 | head int // the current head of the queue. 15 | } 16 | 17 | // NewBreadthFirstIterator creates a new instance of breadthFirstIterator 18 | // and returns it as the Iterator interface. 19 | func NewBreadthFirstIterator[T comparable](g gograph.Graph[T], start T) (Iterator[T], error) { 20 | v := g.GetVertexByID(start) 21 | if v == nil { 22 | return nil, gograph.ErrVertexDoesNotExist 23 | } 24 | 25 | return newBreadthFirstIterator[T](g, start), nil 26 | } 27 | 28 | func newBreadthFirstIterator[T comparable](g gograph.Graph[T], start T) *breadthFirstIterator[T] { 29 | return &breadthFirstIterator[T]{ 30 | graph: g, 31 | start: start, 32 | queue: []T{start}, 33 | visited: map[T]bool{start: true}, 34 | head: -1, 35 | } 36 | } 37 | 38 | // HasNext returns a boolean indicating whether there are more vertices 39 | // to be visited in the BFS traversal. It returns true if the head index 40 | // is in the range of the queue indices. 41 | func (d *breadthFirstIterator[T]) HasNext() bool { 42 | return d.head < len(d.queue)-1 43 | } 44 | 45 | // Next returns the next vertex to be visited in the BFS traversal. 46 | // It dequeues the next vertex from the queue and updates the head field. 47 | // If the HasNext is false, returns nil. 48 | func (d *breadthFirstIterator[T]) Next() *gograph.Vertex[T] { 49 | if !d.HasNext() { 50 | return nil 51 | } 52 | 53 | d.head++ 54 | 55 | // get the next vertex from the queue 56 | currentNode := d.graph.GetVertexByID(d.queue[d.head]) 57 | 58 | // add unvisited neighbors to the queue 59 | neighbors := currentNode.Neighbors() 60 | for _, neighbor := range neighbors { 61 | if !d.visited[neighbor.Label()] { 62 | d.visited[neighbor.Label()] = true 63 | d.queue = append(d.queue, neighbor.Label()) 64 | } 65 | } 66 | 67 | return currentNode 68 | } 69 | 70 | // Iterate iterates through all the vertices in the BFS traversal order 71 | // and applies the given function to each vertex. If the function returns 72 | // an error, the iteration stops and the error is returned. 73 | func (d *breadthFirstIterator[T]) Iterate(f func(v *gograph.Vertex[T]) error) error { 74 | for d.HasNext() { 75 | if err := f(d.Next()); err != nil { 76 | return err 77 | } 78 | } 79 | 80 | return nil 81 | } 82 | 83 | // Reset resets the iterator by setting the initial state of the iterator. 84 | func (d *breadthFirstIterator[T]) Reset() { 85 | d.queue = []T{d.start} 86 | d.head = -1 87 | d.visited = map[T]bool{d.start: true} 88 | } 89 | -------------------------------------------------------------------------------- /traverse/breadth_first_iterator_test.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/hmdsefi/gograph" 9 | ) 10 | 11 | func TestBreadthFirstIterator(t *testing.T) { 12 | // Create a new graph 13 | g := gograph.New[string]() 14 | 15 | // the example graph 16 | // A -> B -> C 17 | // | | | 18 | // v v v 19 | // D -> E -> F 20 | 21 | vertices := map[string]*gograph.Vertex[string]{ 22 | "A": g.AddVertexByLabel("A"), 23 | "B": g.AddVertexByLabel("B"), 24 | "C": g.AddVertexByLabel("C"), 25 | "D": g.AddVertexByLabel("D"), 26 | "E": g.AddVertexByLabel("E"), 27 | "F": g.AddVertexByLabel("F"), 28 | } 29 | 30 | // add some edges 31 | _, _ = g.AddEdge(vertices["A"], vertices["B"]) 32 | _, _ = g.AddEdge(vertices["A"], vertices["D"]) 33 | _, _ = g.AddEdge(vertices["B"], vertices["C"]) 34 | _, _ = g.AddEdge(vertices["B"], vertices["E"]) 35 | _, _ = g.AddEdge(vertices["C"], vertices["F"]) 36 | _, _ = g.AddEdge(vertices["D"], vertices["E"]) 37 | _, _ = g.AddEdge(vertices["E"], vertices["F"]) 38 | 39 | // create an iterator with a vertex that doesn't exist 40 | _, err := NewBreadthFirstIterator(g, "X") 41 | if err == nil { 42 | t.Error("Expect NewBreadthFirstIterator returns error, but got nil") 43 | } 44 | 45 | // test depth first iteration 46 | iter, err := NewBreadthFirstIterator(g, "A") 47 | if err != nil { 48 | t.Errorf("Expect NewBreadthFirstIterator doesn't return error, but got %s", err) 49 | } 50 | 51 | expected := []string{"A", "B", "D", "C", "E", "F"} 52 | 53 | for i, label := range expected { 54 | if !iter.HasNext() { 55 | t.Errorf("Expected iter.HasNext() to be true, but it was false for label %s", label) 56 | } 57 | 58 | v := iter.Next() 59 | if v.Label() != expected[i] { 60 | t.Errorf("Expected iter.Next().Label() to be %s, but got %s", expected[i], v.Label()) 61 | } 62 | } 63 | 64 | if iter.HasNext() { 65 | t.Error("Expected iter.HasNext() to be false, but it was true") 66 | } 67 | 68 | v := iter.Next() 69 | if v != nil { 70 | t.Errorf("Expected nil, but got %+v", v) 71 | } 72 | 73 | // test the Reset method 74 | iter.Reset() 75 | if !iter.HasNext() { 76 | t.Error("Expected iter.HasNext() to be true, but it was false after reset") 77 | } 78 | 79 | v = iter.Next() 80 | if v.Label() != "A" { 81 | t.Errorf("Expected iter.Next().Label() to be %s, but got %s", "A", v.Label()) 82 | } 83 | 84 | // test Iterate method 85 | iter.Reset() 86 | var ordered []string 87 | err = iter.Iterate( 88 | func(vertex *gograph.Vertex[string]) error { 89 | ordered = append(ordered, vertex.Label()) 90 | return nil 91 | }, 92 | ) 93 | if err != nil { 94 | t.Errorf("Expect iter.Iterate(func) returns no error, but got one %s", err) 95 | } 96 | 97 | if !reflect.DeepEqual(expected, ordered) { 98 | t.Errorf( 99 | "Expect same vertex order, but got different one expected: %v, actual: %v", 100 | expected, ordered, 101 | ) 102 | } 103 | 104 | iter.Reset() 105 | expectedErr := errors.New("something went wrong") 106 | err = iter.Iterate( 107 | func(vertex *gograph.Vertex[string]) error { 108 | return expectedErr 109 | }, 110 | ) 111 | if err == nil { 112 | t.Error("Expect iter.Iterate(func) returns error, but got nil") 113 | } 114 | 115 | if !errors.Is(err, expectedErr) { 116 | t.Errorf("Expect %+v error, but got %+v", expectedErr, err) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /traverse/depth_first_iterator_test.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/hmdsefi/gograph" 9 | ) 10 | 11 | func TestDepthFirstIterator(t *testing.T) { 12 | // Create a new graph 13 | g := gograph.New[string](gograph.Directed()) 14 | 15 | // the example graph 16 | // A -> B -> C 17 | // | | | 18 | // v v v 19 | // D -> E -> F 20 | 21 | vertices := map[string]*gograph.Vertex[string]{ 22 | "A": g.AddVertexByLabel("A"), 23 | "B": g.AddVertexByLabel("B"), 24 | "C": g.AddVertexByLabel("C"), 25 | "D": g.AddVertexByLabel("D"), 26 | "E": g.AddVertexByLabel("E"), 27 | "F": g.AddVertexByLabel("F"), 28 | } 29 | 30 | // add some edges 31 | _, _ = g.AddEdge(vertices["A"], vertices["B"]) 32 | _, _ = g.AddEdge(vertices["A"], vertices["D"]) 33 | _, _ = g.AddEdge(vertices["B"], vertices["C"]) 34 | _, _ = g.AddEdge(vertices["B"], vertices["E"]) 35 | _, _ = g.AddEdge(vertices["C"], vertices["F"]) 36 | _, _ = g.AddEdge(vertices["D"], vertices["E"]) 37 | _, _ = g.AddEdge(vertices["E"], vertices["F"]) 38 | 39 | // create an iterator with a vertex that doesn't exist 40 | _, err := NewDepthFirstIterator(g, "X") 41 | if err == nil { 42 | t.Error("Expect NewDepthFirstIterator returns error, but got nil") 43 | } 44 | 45 | // test depth first iteration 46 | iter, err := NewDepthFirstIterator[string](g, "A") 47 | if err != nil { 48 | t.Errorf("Expect NewDepthFirstIterator doesn't return error, but got %s", err) 49 | } 50 | 51 | expected := []string{"A", "D", "E", "F", "B", "C"} 52 | 53 | for i, label := range expected { 54 | if !iter.HasNext() { 55 | t.Errorf("Expected iter.HasNext() to be true, but it was false for label %s", label) 56 | } 57 | 58 | v := iter.Next() 59 | if v.Label() != expected[i] { 60 | t.Errorf("Expected iter.Next().Label() to be %s, but got %s", expected[i], v.Label()) 61 | } 62 | } 63 | 64 | if iter.HasNext() { 65 | t.Error("Expected iter.HasNext() to be false, but it was true") 66 | } 67 | 68 | v := iter.Next() 69 | if v != nil { 70 | t.Errorf("Expected nil, but got %+v", v) 71 | } 72 | 73 | // test the Reset method 74 | iter.Reset() 75 | if !iter.HasNext() { 76 | t.Error("Expected iter.HasNext() to be true, but it was false after reset") 77 | } 78 | 79 | v = iter.Next() 80 | if v.Label() != "A" { 81 | t.Errorf("Expected iter.Next().Label() to be %s, but got %s", "A", v.Label()) 82 | } 83 | 84 | // test Iterate method 85 | iter.Reset() 86 | var ordered []string 87 | err = iter.Iterate( 88 | func(vertex *gograph.Vertex[string]) error { 89 | ordered = append(ordered, vertex.Label()) 90 | return nil 91 | }, 92 | ) 93 | if err != nil { 94 | t.Errorf("Expect iter.Iterate(func) returns no error, but got one %s", err) 95 | } 96 | 97 | if !reflect.DeepEqual(expected, ordered) { 98 | t.Errorf( 99 | "Expect same vertex order, but got different one expected: %v, actual: %v", 100 | expected, ordered, 101 | ) 102 | } 103 | 104 | iter.Reset() 105 | expectedErr := errors.New("something went wrong") 106 | err = iter.Iterate( 107 | func(vertex *gograph.Vertex[string]) error { 108 | return expectedErr 109 | }, 110 | ) 111 | if err == nil { 112 | t.Error("Expect iter.Iterate(func) returns error, but got nil") 113 | } 114 | 115 | if !errors.Is(err, expectedErr) { 116 | t.Errorf("Expect %+v error, but got %+v", expectedErr, err) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /util/priority_queue.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "container/heap" 5 | 6 | "github.com/hmdsefi/gograph" 7 | ) 8 | 9 | // VertexPriorityQueue wraps the priorityQueue type to decrease the 10 | // exposes methods, and increase the type safety. 11 | type VertexPriorityQueue[T comparable] struct { 12 | pq priorityQueue[T] // a slice of VertexWithPriority that represents min heap. 13 | } 14 | 15 | func NewVertexPriorityQueue[T comparable]() *VertexPriorityQueue[T] { 16 | pq := make(priorityQueue[T], 0) 17 | heap.Init(&pq) 18 | return &VertexPriorityQueue[T]{ 19 | pq: pq, 20 | } 21 | } 22 | 23 | // Push adds new VertexWithPriority to the queue. 24 | func (v *VertexPriorityQueue[T]) Push(in *VertexWithPriority[T]) { 25 | heap.Push(&v.pq, in) 26 | } 27 | 28 | // Pop removes and returns the minimum element (according to Less) from 29 | // the underlying heap. 30 | func (v *VertexPriorityQueue[T]) Pop() *VertexWithPriority[T] { 31 | out, _ := heap.Pop(&v.pq).(*VertexWithPriority[T]) 32 | return out 33 | } 34 | 35 | // Peek is the number of elements in the underlying queue. 36 | func (v *VertexPriorityQueue[T]) Peek() *VertexWithPriority[T] { 37 | if v.Len() > 0 { 38 | return v.pq[0] 39 | } 40 | 41 | return nil 42 | } 43 | 44 | // Len is the number of elements in the underlying queue. 45 | func (v *VertexPriorityQueue[T]) Len() int { 46 | return len(v.pq) 47 | } 48 | 49 | // VertexWithPriority is a vertex priority queue item that stores 50 | // vertex along with its priority. 51 | type VertexWithPriority[T comparable] struct { 52 | vertex *gograph.Vertex[T] 53 | priority float64 54 | index int 55 | } 56 | 57 | func NewVertexWithPriority[T comparable](vertex *gograph.Vertex[T], priority float64) *VertexWithPriority[T] { 58 | return &VertexWithPriority[T]{vertex: vertex, priority: priority} 59 | } 60 | 61 | // Priority returns the priority of the vertex. 62 | func (v VertexWithPriority[T]) Priority() float64 { 63 | return v.priority 64 | } 65 | 66 | // Vertex returns the vertex. 67 | func (v VertexWithPriority[T]) Vertex() *gograph.Vertex[T] { 68 | return v.vertex 69 | } 70 | 71 | // priorityQueue is a priority queue that implements 72 | // heap and sort interfaces. It represents a min heap. 73 | type priorityQueue[T comparable] []*VertexWithPriority[T] 74 | 75 | // Len is the number of elements in the collection. 76 | func (pq priorityQueue[T]) Len() int { return len(pq) } 77 | 78 | // Less reports whether the element with index i 79 | // must sort before the element with index j. 80 | func (pq priorityQueue[T]) Less(i, j int) bool { 81 | return pq[i].priority < pq[j].priority 82 | } 83 | 84 | // Swap swaps the elements with indexes i and j. 85 | func (pq priorityQueue[T]) Swap(i, j int) { 86 | pq[i], pq[j] = pq[j], pq[i] 87 | pq[i].index = i 88 | pq[j].index = j 89 | } 90 | 91 | // Push adds new item to the collection. 92 | func (pq *priorityQueue[T]) Push(x interface{}) { 93 | item, ok := x.(*VertexWithPriority[T]) 94 | if !ok { 95 | return 96 | } 97 | 98 | n := len(*pq) 99 | item.index = n 100 | *pq = append(*pq, item) 101 | } 102 | 103 | // Pop removes and returns the minimum element (according to Less) from the heap. 104 | // The complexity is O(log n) where n = h.Len(). 105 | // Pop is equivalent to Remove(h, 0). 106 | func (pq *priorityQueue[T]) Pop() interface{} { 107 | old := *pq 108 | n := len(old) 109 | item := old[n-1] 110 | old[n-1] = nil // avoid memory leak 111 | item.index = -1 112 | *pq = old[0 : n-1] 113 | return item 114 | } 115 | -------------------------------------------------------------------------------- /path/floyd_warshall.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/hmdsefi/gograph" 7 | ) 8 | 9 | // FloydWarshall finds the shortest paths between all pairs of vertices in a 10 | // weighted graph, even in the presence of negative weight edges (as long as 11 | // there are no negative weight cycles). It was proposed by Robert Floyd and 12 | // Stephen Warshall. 13 | // 14 | // Steps: 15 | // 16 | // 1. Initialization: Create a distance matrix D[][] where D[i][j] represents the 17 | // shortest distance between vertex i and vertex j. Initialize this matrix with 18 | // the weights of the edges between vertices if there is an edge, otherwise set 19 | // the value to infinity. Also, set the diagonal elements D[i][i] to 0. 20 | // 21 | // 2. Shortest Path Calculation: Iterate through all vertices as intermediate vertices. 22 | // For each pair of vertices (i, j), check if going through the current intermediate 23 | // vertex k leads to a shorter path than the current known distance from i to j. If so, 24 | // update the distance matrix D[i][j] to the new shorter distance D[i][k] + D[k][j]. 25 | // 26 | // 3. Detection of Negative Cycles: After the iterations, if any diagonal element D[i][i] 27 | // of the distance matrix is negative, it indicates the presence of a negative weight cycle 28 | // in the graph. 29 | // 30 | // 4. Output: The resulting distance matrix D[][] will contain the shortest path distances 31 | // between all pairs of vertices. If there is a negative weight cycle, it might not produce 32 | // the correct shortest paths, but it can still detect the presence of such cycles. 33 | // 34 | // The time complexity of the Floyd-Warshall algorithm is O(V^3), where V is the 35 | // number of vertices in the graph. Despite its cubic time complexity, it is often 36 | // preferred over other algorithms like Bellman-Ford for dense graphs or when the 37 | // graph has negative weight edges and no negative weight cycles, as it calculates 38 | // shortest paths between all pairs of vertices in one go. 39 | func FloydWarshall[T comparable](g gograph.Graph[T]) (map[T]map[T]float64, error) { 40 | if !g.IsWeighted() { 41 | return nil, ErrNotWeighted 42 | } 43 | 44 | if !g.IsDirected() { 45 | return nil, ErrNotDirected 46 | } 47 | 48 | vertices := g.GetAllVertices() 49 | 50 | dist := make(map[T]map[T]float64) 51 | maxValue := math.Inf(1) 52 | for _, source := range vertices { 53 | for _, dest := range vertices { 54 | destMap, ok := dist[source.Label()] 55 | if !ok { 56 | destMap = make(map[T]float64) 57 | } 58 | 59 | destMap[dest.Label()] = maxValue 60 | if dest.Label() == source.Label() { 61 | destMap[dest.Label()] = 0 62 | } 63 | 64 | if edge := g.GetEdge(source, dest); edge != nil { 65 | destMap[dest.Label()] = edge.Weight() 66 | } 67 | 68 | dist[source.Label()] = destMap 69 | } 70 | } 71 | 72 | for _, intermediate := range vertices { 73 | for _, source := range vertices { 74 | for _, dest := range vertices { 75 | weight := dist[source.Label()][intermediate.Label()] + dist[intermediate.Label()][dest.Label()] 76 | if weight < dist[source.Label()][dest.Label()] { 77 | dist[source.Label()][dest.Label()] = weight 78 | } 79 | } 80 | } 81 | } 82 | 83 | edges := g.AllEdges() 84 | for _, v := range vertices { 85 | for _, edge := range edges { 86 | if dist[v.Label()][edge.Source().Label()] != maxValue && 87 | dist[v.Label()][edge.Source().Label()]+edge.Weight() < dist[v.Label()][edge.Destination().Label()] { 88 | return nil, ErrNegativeWeightCycle 89 | } 90 | } 91 | } 92 | 93 | return dist, nil 94 | } 95 | -------------------------------------------------------------------------------- /connectivity/kosaraju.go: -------------------------------------------------------------------------------- 1 | package connectivity 2 | 3 | import "github.com/hmdsefi/gograph" 4 | 5 | // Kosaraju's Algorithm: This algorithm is also based on depth-first search 6 | // and is used to find strongly connected components in a graph. The algorithm 7 | // has a time complexity of O(V+E) and is considered to be one of the most 8 | // efficient algorithms for finding strongly connected components. 9 | // 10 | // Note that this implementation assumes that the graph is connected, and 11 | // may not work correctly for disconnected graphs. It also does not handle 12 | // cases where the graph contains self-loops or parallel edges. 13 | 14 | // kosarajuDFS exposes two different depth-first search methods. 15 | type kosarajuDFS[T comparable] struct { 16 | visited map[T]bool 17 | } 18 | 19 | func newKosarajuSCCS[T comparable]() *kosarajuDFS[T] { 20 | return &kosarajuDFS[T]{ 21 | visited: make(map[T]bool), 22 | } 23 | } 24 | 25 | // Kosaraju implements Kosaraju's Algorithm. It performs a depth-first 26 | // search of the graph to create a stack of vertices, and then performs 27 | // a second depth-first search on the transposed graph to identify the 28 | // strongly connected components. 29 | // 30 | // The function returns a slice of slices, where each slice represents 31 | // a strongly connected component and contains the vertices that belong 32 | // to that component. 33 | func Kosaraju[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T] { 34 | vertices := g.GetAllVertices() 35 | 36 | // Step 1: Perform a depth-first search of the graph to create a stack of vertices 37 | kosar := newKosarajuSCCS[T]() 38 | stack := make([]T, 0, len(vertices)) 39 | for _, v := range vertices { 40 | if !kosar.visited[v.Label()] { 41 | kosar.dfs1(v, &stack) 42 | } 43 | } 44 | 45 | // Step 2: Perform a second depth-first search on the transposed graph 46 | transposed := kosar.reverse(g) 47 | kosar.visited = make(map[T]bool) 48 | sccs := make([][]*gograph.Vertex[T], 0) 49 | for len(stack) > 0 { 50 | v := transposed.GetVertexByID(stack[len(stack)-1]) 51 | stack = stack[:len(stack)-1] 52 | 53 | if !kosar.visited[v.Label()] { 54 | scc := make([]*gograph.Vertex[T], 0) 55 | kosar.dfs2(v, &scc) 56 | sccs = append(sccs, scc) 57 | } 58 | } 59 | 60 | return sccs 61 | } 62 | 63 | // dfs1 creates the stack of vertices. 64 | func (k *kosarajuDFS[T]) dfs1(v *gograph.Vertex[T], stack *[]T) { 65 | k.visited[v.Label()] = true 66 | neighbors := v.Neighbors() 67 | for _, neighbor := range neighbors { 68 | if !k.visited[neighbor.Label()] { 69 | k.dfs1(neighbor, stack) 70 | } 71 | } 72 | *stack = append(*stack, v.Label()) 73 | } 74 | 75 | // dfs2 explores the strongly connected components. 76 | func (k *kosarajuDFS[T]) dfs2(v *gograph.Vertex[T], scc *[]*gograph.Vertex[T]) { 77 | k.visited[v.Label()] = true 78 | *scc = append(*scc, v) 79 | neighbors := v.Neighbors() 80 | for _, neighbor := range neighbors { 81 | if !k.visited[neighbor.Label()] { 82 | k.dfs2(neighbor, scc) 83 | } 84 | } 85 | } 86 | 87 | func (k *kosarajuDFS[T]) reverse(g gograph.Graph[T]) gograph.Graph[T] { 88 | reversed := gograph.New[T](gograph.Directed()) 89 | vertices := g.GetAllVertices() 90 | 91 | for _, v := range vertices { 92 | reversed.AddVertexByLabel(v.Label()) 93 | } 94 | 95 | for i := range vertices { 96 | neighbors := vertices[i].Neighbors() 97 | for j := range neighbors { 98 | _, _ = reversed.AddEdge( 99 | reversed.GetVertexByID(neighbors[j].Label()), 100 | reversed.GetVertexByID(vertices[i].Label()), 101 | ) 102 | } 103 | } 104 | return reversed 105 | } 106 | -------------------------------------------------------------------------------- /partition/girvan_newman_test.go: -------------------------------------------------------------------------------- 1 | package partition 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hmdsefi/gograph" 7 | ) 8 | 9 | func TestGirvanNewman_StringGraph(t *testing.T) { 10 | g := gograph.New[string]() 11 | 12 | // Create a graph with 6 nodes and cycles 13 | a := g.AddVertexByLabel("A") 14 | b := g.AddVertexByLabel("B") 15 | c := g.AddVertexByLabel("C") 16 | d := g.AddVertexByLabel("D") 17 | e := g.AddVertexByLabel("E") 18 | f := g.AddVertexByLabel("F") 19 | 20 | _, _ = g.AddEdge(a, b) 21 | _, _ = g.AddEdge(a, c) 22 | _, _ = g.AddEdge(b, c) 23 | _, _ = g.AddEdge(c, d) 24 | _, _ = g.AddEdge(d, e) 25 | _, _ = g.AddEdge(e, f) 26 | _, _ = g.AddEdge(d, f) 27 | 28 | // k = 2 29 | components, err := GirvanNewman(g, 2) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | if len(components) != 2 { 35 | t.Fatalf("expected 2 components, got %d", len(components)) 36 | } 37 | 38 | // Check that all original vertices are present 39 | vertexCount := 0 40 | for _, comp := range components { 41 | vertexCount += int(comp.Order()) 42 | } 43 | if vertexCount != int(g.Order()) { 44 | t.Fatalf("vertex count mismatch after partitioning") 45 | } 46 | } 47 | 48 | func TestGirvanNewman_IntGraph(t *testing.T) { 49 | g := gograph.New[int]() 50 | 51 | // Simple triangle graph 52 | v1 := g.AddVertexByLabel(1) 53 | v2 := g.AddVertexByLabel(2) 54 | v3 := g.AddVertexByLabel(3) 55 | 56 | _, _ = g.AddEdge(v1, v2) 57 | _, _ = g.AddEdge(v2, v3) 58 | _, _ = g.AddEdge(v3, v1) 59 | 60 | components, err := GirvanNewman(g, 0) // remove all edges 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | // With all edges removed, each vertex is a separate component 66 | expected := 3 67 | if len(components) != expected { 68 | t.Fatalf("expected %d components, got %d", expected, len(components)) 69 | } 70 | 71 | // Check each component has exactly one vertex 72 | for _, comp := range components { 73 | if comp.Order() != 1 { 74 | t.Fatalf("component should have 1 vertex, got %d", comp.Order()) 75 | } 76 | } 77 | } 78 | 79 | func TestGirvanNewman_ComplexGraph(t *testing.T) { 80 | g := gograph.New[string]() 81 | 82 | // Complex graph with multiple cycles and bridges 83 | a := g.AddVertexByLabel("A") 84 | b := g.AddVertexByLabel("B") 85 | c := g.AddVertexByLabel("C") 86 | d := g.AddVertexByLabel("D") 87 | e := g.AddVertexByLabel("E") 88 | f := g.AddVertexByLabel("F") 89 | g1 := g.AddVertexByLabel("G") 90 | h := g.AddVertexByLabel("H") 91 | 92 | // Core cycle 93 | _, _ = g.AddEdge(a, b) 94 | _, _ = g.AddEdge(b, c) 95 | _, _ = g.AddEdge(c, a) 96 | 97 | // Bridge connections 98 | _, _ = g.AddEdge(c, d) 99 | _, _ = g.AddEdge(d, e) 100 | _, _ = g.AddEdge(e, f) 101 | _, _ = g.AddEdge(f, g1) 102 | _, _ = g.AddEdge(g1, h) 103 | 104 | components, err := GirvanNewman(g, 3) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | 109 | if len(components) != 3 { 110 | t.Fatalf("expected 3 components, got %d", len(components)) 111 | } 112 | 113 | // Verify vertices preserved 114 | vertexCount := 0 115 | for _, comp := range components { 116 | vertexCount += int(comp.Order()) 117 | } 118 | if vertexCount != int(g.Order()) { 119 | t.Fatalf("vertex count mismatch after partitioning") 120 | } 121 | } 122 | 123 | func TestGirvanNewman_EmptyGraph(t *testing.T) { 124 | g := gograph.New[string]() 125 | 126 | components, err := GirvanNewman(g, 0) 127 | if err != nil { 128 | t.Fatal(err) 129 | } 130 | 131 | if len(components) != 0 { 132 | t.Fatalf("expected 0 components for empty graph, got %d", len(components)) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /path/dijkstra.md: -------------------------------------------------------------------------------- 1 | # gograph 2 | 3 | ## Shortest Path 4 | 5 | ### Dijkstra 6 | 7 | Dijkstra's algorithm is a graph algorithm used to find the shortest path from a single source vertex to all 8 | other vertices in a weighted graph with non-negative edge weights. It was developed by Dutch computer scientist 9 | Edsger W. Dijkstra in 1956. 10 | 11 | Here's a step-by-step explanation of how Dijkstra's algorithm works: 12 | 13 | 1. **Initialization:** Start by selecting a source vertex. Set the distance of the source vertex to itself as 0, and the 14 | distances of all other vertices to infinity. Maintain a priority queue (or a min-heap) to keep track of vertices 15 | based on their tentative distances from the source vertex. 16 | 17 | 2. **Selection of Vertex:** At each step, select the vertex with the smallest tentative distance from the priority 18 | queue. 19 | Initially, this will be the source vertex. 20 | 21 | 3. **Relaxation:** For the selected vertex, iterate through all its neighboring vertices. For each neighboring vertex, 22 | update its tentative distance if going through the current vertex results in a shorter path than the current 23 | known distance. If the tentative distance is updated, update the priority queue accordingly. 24 | 25 | 4. **Repeating Steps:** Repeat steps 2 and 3 until all vertices have been visited or until the priority queue is empty. 26 | 27 | 5. **Output:** After the algorithm terminates, the distances from the source vertex to all other vertices will be 28 | finalized. 29 | 30 | Dijkstra's algorithm guarantees the shortest path from the source vertex to all other vertices in the graph, 31 | as long as the graph does not contain negative weight edges. It works efficiently for sparse graphs with 32 | non-negative edge weights. 33 | 34 | The time complexity of Dijkstra's algorithm is `O((V + E) log V)`, where V is the number of vertices and E is 35 | the number of edges in the graph. This complexity arises from the use of a priority queue to maintain the tentative 36 | distances efficiently. If a simple array-based implementation is used to select the minimum distance vertex in each 37 | step, the time complexity becomes O(V^2), which is more suitable for dense graphs. 38 | 39 | #### Implementation with Slices 40 | 41 | Steps: 42 | 43 | 1. Initialize distances with maximum float/integer values except for the source vertex distance, which is set to 0. 44 | 2. Iterate numVertices - 1 times (where numVertices is the number of vertices in the graph). 45 | 3. Select the vertex with the minimum distance among the unvisited vertices. 46 | 4. Relax the distances of its neighboring vertices if a shorter path is found. 47 | 5. Mark the selected vertex as visited. 48 | 49 | **Time Complexity:** `O(V^2)`, where V is the number of vertices in the graph. This is because finding the 50 | minimum distance vertex in each iteration takes `O(V)` time, and we perform this process V times. 51 | 52 | **Space Complexity:** `O(V^2)` for storing the graph and distances. 53 | 54 | #### Implementation with Heap 55 | 56 | Steps: 57 | 58 | * Similar to the slice implementation but instead of linearly searching for the vertex with the minimum distance, 59 | we use a heap to maintain the priority queue. 60 | * The priority queue ensures that the vertex with the smallest tentative distance is efficiently selected 61 | in each iteration. 62 | 63 | **Time Complexity:** `O((V + E) log V)`, where V is the number of vertices and E is the number of edges in 64 | the graph. This is because each vertex is pushed and popped from the priority queue once, and each 65 | edge is relaxed once. 66 | 67 | **Space Complexity:** `O(V)` for storing the priority queue and distances. 68 | -------------------------------------------------------------------------------- /traverse/topological_iterator_test.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/hmdsefi/gograph" 8 | ) 9 | 10 | func TestTopologyOrderIterator(t *testing.T) { 11 | // create the graph 12 | g := gograph.New[int](gograph.Acyclic()) 13 | 14 | // add vertices to the graph 15 | vertices := make(map[int]*gograph.Vertex[int]) 16 | for i := 1; i <= 6; i++ { 17 | vertices[i] = g.AddVertexByLabel(i) 18 | } 19 | 20 | // add edges to the graph 21 | _, _ = g.AddEdge(vertices[1], vertices[2]) 22 | _, _ = g.AddEdge(vertices[1], vertices[3]) 23 | _, _ = g.AddEdge(vertices[2], vertices[4]) 24 | _, _ = g.AddEdge(vertices[2], vertices[5]) 25 | _, _ = g.AddEdge(vertices[3], vertices[5]) 26 | _, _ = g.AddEdge(vertices[4], vertices[6]) 27 | _, _ = g.AddEdge(vertices[5], vertices[6]) 28 | 29 | // create the topology order iterator 30 | iterator, err := NewTopologicalIterator[int](g) 31 | if err != nil { 32 | t.Errorf("Expect no error by calling NewTopologicalIterator, but got one, %s", err) 33 | } 34 | 35 | // test the Next method 36 | expectedOrder := []int{1, 2, 3, 4, 5, 6} 37 | for i := 0; i < 6; i++ { 38 | if !iterator.HasNext() { 39 | t.Errorf("Expected iterator.HasNext() to be true, but it was false for index %d", i) 40 | } 41 | 42 | v := iterator.Next() 43 | if v.Label() != expectedOrder[i] { 44 | t.Errorf("Expected iterator.Next().Label() to be %d, but got %d", expectedOrder[i], v.Label()) 45 | } 46 | } 47 | 48 | if iterator.HasNext() { 49 | t.Error("Expected iterator.HasNext() to be false, but it was true") 50 | } 51 | 52 | v := iterator.Next() 53 | if v != nil { 54 | t.Errorf("Expected nil, but got %+v", v) 55 | } 56 | 57 | // test the Reset method 58 | iterator.Reset() 59 | if !iterator.HasNext() { 60 | t.Error("Expected iterator.HasNext() to be true, but it was false after reset") 61 | } 62 | 63 | v = iterator.Next() 64 | if v.Label() != 1 { 65 | t.Errorf("Expected iterator.Next().Label() to be %d, but got %d", 1, v.Label()) 66 | } 67 | 68 | // test Iterate method 69 | iterator.Reset() 70 | var j int 71 | err = iterator.Iterate( 72 | func(vertex *gograph.Vertex[int]) error { 73 | if vertex.Label() != expectedOrder[j] { 74 | t.Errorf("Expected vertex.Label() to be %+v, but got %+v", expectedOrder[j], vertex.Label()) 75 | } 76 | 77 | j++ 78 | return nil 79 | }, 80 | ) 81 | if err != nil { 82 | t.Errorf("Expect iterator.Iterate(func) returns no error, but got one %s", err) 83 | } 84 | 85 | iterator.Reset() 86 | expectedErr := errors.New("something went wrong") 87 | err = iterator.Iterate( 88 | func(vertex *gograph.Vertex[int]) error { 89 | return expectedErr 90 | }, 91 | ) 92 | if err == nil { 93 | t.Error("Expect iter.Iterate(func) returns error, but got nil") 94 | } 95 | 96 | if !errors.Is(err, expectedErr) { 97 | t.Errorf("Expect %+v error, but got %+v", expectedErr, err) 98 | } 99 | } 100 | 101 | func TestTopologyOrderIterator_NotAcyclic(t *testing.T) { 102 | g1 := gograph.New[int](gograph.Directed()) 103 | 104 | _, _ = g1.AddEdge(gograph.NewVertex(1), gograph.NewVertex(2)) 105 | _, _ = g1.AddEdge(gograph.NewVertex(2), gograph.NewVertex(3)) 106 | _, _ = g1.AddEdge(gograph.NewVertex(3), gograph.NewVertex(1)) 107 | 108 | // create the topology order iterator 109 | _, err := NewTopologicalIterator[int](g1) 110 | if err == nil { 111 | t.Error("Expect error, but got nil") 112 | } 113 | 114 | g2 := gograph.New[int](gograph.Directed()) 115 | _, _ = g2.AddEdge(gograph.NewVertex(1), gograph.NewVertex(2)) 116 | _, _ = g2.AddEdge(gograph.NewVertex(2), gograph.NewVertex(3)) 117 | } 118 | -------------------------------------------------------------------------------- /connectivity/tarjan.go: -------------------------------------------------------------------------------- 1 | package connectivity 2 | 3 | import "github.com/hmdsefi/gograph" 4 | 5 | // Tarjan's algorithm is based on depth-first search and is widely used for 6 | // finding strongly connected components in a graph. The algorithm is efficient 7 | // and has a time complexity of O(V+E), where V is the number of vertices and E 8 | // is the number of edges in the graph. 9 | 10 | // tarjanVertex wraps the gograph.Vertex struct to add new fields to it. 11 | type tarjanVertex[T comparable] struct { 12 | *gograph.Vertex[T] // the vertex that being wrapped. 13 | index int // represents the order in which a vertex is visited during the DFS search. 14 | lowLink int // the minimum index of any vertex reachable from the vertex during the search. 15 | onStack bool // a boolean flag that shows if the vertex is in the stack or not. 16 | } 17 | 18 | func newTarjanVertex[T comparable](vertex *gograph.Vertex[T]) *tarjanVertex[T] { 19 | return &tarjanVertex[T]{ 20 | Vertex: vertex, 21 | index: -1, 22 | } 23 | } 24 | 25 | type tarjanSCCS[T comparable] struct { 26 | vertices map[T]*tarjanVertex[T] 27 | } 28 | 29 | func newTarjanSCCS[T comparable](vertices map[T]*tarjanVertex[T]) *tarjanSCCS[T] { 30 | return &tarjanSCCS[T]{vertices: vertices} 31 | } 32 | 33 | // Tarjan is the entry point to the algorithm. It initializes the index, 34 | // stack, and sccs variables and then loops through all the vertices in 35 | // the graph. It returns a slice of vertices' slice, where each inner 36 | // slice represents a strongly connected component of the graph. 37 | func Tarjan[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T] { 38 | var ( 39 | index int 40 | stack []*tarjanVertex[T] 41 | tvertices = make(map[T]*tarjanVertex[T]) 42 | sccs [][]*tarjanVertex[T] 43 | ) 44 | 45 | vertices := g.GetAllVertices() 46 | 47 | for _, v := range vertices { 48 | tv := newTarjanVertex(v) 49 | tvertices[tv.Label()] = tv 50 | } 51 | 52 | tarj := newTarjanSCCS(tvertices) 53 | 54 | for _, v := range tvertices { 55 | if v.index < 0 { 56 | tarj.visit(v, &index, &stack, &sccs) 57 | } 58 | } 59 | 60 | result := make([][]*gograph.Vertex[T], len(sccs)) 61 | for i, list := range sccs { 62 | result[i] = make([]*gograph.Vertex[T], len(list)) 63 | for j := range list { 64 | result[i][j] = list[j].Vertex 65 | } 66 | } 67 | return result 68 | } 69 | 70 | // visit updates the index and lowLink values of the vertex, adds it to 71 | // the stack, and recursively calls itself on each of its neighbors. If 72 | // a neighbor has not been visited before, its index and lowLink values 73 | // are updated, and the recursion continues. If a neighbor has already 74 | // been visited and is still on the stack, its lowLink value is updated. 75 | func (t *tarjanSCCS[T]) visit( 76 | v *tarjanVertex[T], 77 | index *int, 78 | stack *[]*tarjanVertex[T], 79 | sccs *[][]*tarjanVertex[T], 80 | ) { 81 | v.index = *index 82 | v.lowLink = *index 83 | *index++ 84 | *stack = append(*stack, v) 85 | v.onStack = true 86 | 87 | neighbors := v.Neighbors() 88 | for _, w := range neighbors { 89 | tv := t.vertices[w.Label()] 90 | if tv.index == -1 { 91 | t.visit(tv, index, stack, sccs) 92 | v.lowLink = min(v.lowLink, tv.lowLink) 93 | } else if tv.onStack { 94 | v.lowLink = min(v.lowLink, tv.index) 95 | } 96 | } 97 | 98 | if v.lowLink == v.index { 99 | var scc []*tarjanVertex[T] 100 | for { 101 | w := (*stack)[len(*stack)-1] 102 | *stack = (*stack)[:len(*stack)-1] 103 | w.onStack = false 104 | scc = append(scc, w) 105 | if w == v { 106 | break 107 | } 108 | } 109 | *sccs = append(*sccs, scc) 110 | } 111 | } 112 | 113 | // min is a helper function that returns the minimum of two integers. 114 | func min(x, y int) int { 115 | if x < y { 116 | return x 117 | } 118 | return y 119 | } 120 | -------------------------------------------------------------------------------- /traverse/closest_first_iterator.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "github.com/hmdsefi/gograph" 5 | "github.com/hmdsefi/gograph/util" 6 | ) 7 | 8 | // closestFirstIterator implements the Iterator interface to travers 9 | // a graph in a random walk fashion. 10 | // 11 | // Closest-first traversal, also known as the Best-First search or 12 | // Greedy Best-First search, is a graph traversal algorithm that 13 | // explores the graph in a manner that always prioritizes the next 14 | // node to visit based on some evaluation function that estimates 15 | // how close a node is to the goal. 16 | // 17 | // The metric for closest here is the weight of the edge between two 18 | // connected vertices. 19 | type closestFirstIterator[T comparable] struct { 20 | graph gograph.Graph[T] // the graph that being traversed. 21 | start T // the label of starting point of the traversal. 22 | visited map[T]bool // a map that keeps track of whether a vertex has been visited or not. 23 | pq *util.VertexPriorityQueue[T] // a slice of util.VertexWithPriority that represents a min heap. 24 | currDist float64 // the current distance from the start node. 25 | } 26 | 27 | // NewClosestFirstIterator creates a new instance of depthFirstIterator 28 | // and returns it as the Iterator interface. 29 | // 30 | // if the start node doesn't exist, returns error. 31 | func NewClosestFirstIterator[T comparable](graph gograph.Graph[T], start T) (Iterator[T], error) { 32 | v := graph.GetVertexByID(start) 33 | if v == nil { 34 | return nil, gograph.ErrVertexDoesNotExist 35 | } 36 | 37 | pq := util.NewVertexPriorityQueue[T]() 38 | pq.Push(util.NewVertexWithPriority[T](v, 0)) 39 | return &closestFirstIterator[T]{ 40 | graph: graph, 41 | start: start, 42 | visited: make(map[T]bool), 43 | pq: pq, 44 | currDist: 0, 45 | }, nil 46 | } 47 | 48 | // HasNext returns a boolean indicating whether there are more vertices 49 | // to be visited or not. 50 | func (c *closestFirstIterator[T]) HasNext() bool { 51 | for c.pq.Len() > 0 { 52 | if !c.visited[c.pq.Peek().Vertex().Label()] { 53 | return true 54 | } 55 | 56 | c.pq.Pop() 57 | } 58 | 59 | return false 60 | } 61 | 62 | // Next returns the next vertex to be visited in the random walk traversal. 63 | // It chooses one of the neighbors randomly and returns it. 64 | // 65 | // If the HasNext is false, returns nil. 66 | func (c *closestFirstIterator[T]) Next() *gograph.Vertex[T] { 67 | if !c.HasNext() { 68 | return nil 69 | } 70 | 71 | vp := c.pq.Pop() 72 | c.currDist = vp.Priority() 73 | currNode := vp.Vertex() 74 | c.visited[currNode.Label()] = true 75 | 76 | neighbors := currNode.Neighbors() 77 | for _, neighbor := range neighbors { 78 | edge := c.graph.GetEdge(currNode, neighbor) 79 | if !c.visited[neighbor.Label()] { 80 | dist := c.currDist + edge.Weight() 81 | c.pq.Push(util.NewVertexWithPriority(neighbor, dist)) 82 | } 83 | } 84 | 85 | return currNode 86 | } 87 | 88 | // Iterate iterates through the vertices in random order and applies 89 | // the given function to each vertex. If the function returns an error, 90 | // the iteration stops and the error is returned. 91 | func (c *closestFirstIterator[T]) Iterate(f func(v *gograph.Vertex[T]) error) error { 92 | for c.HasNext() { 93 | if err := f(c.Next()); err != nil { 94 | return err 95 | } 96 | } 97 | 98 | return nil 99 | } 100 | 101 | // Reset resets the iterator by setting the initial state of the iterator. 102 | // There is no guarantee that the reset method works as expected, if 103 | // the start vertex being removed. 104 | func (c *closestFirstIterator[T]) Reset() { 105 | c.visited = make(map[T]bool) 106 | c.currDist = 0 107 | 108 | c.pq = util.NewVertexPriorityQueue[T]() 109 | c.pq.Push(util.NewVertexWithPriority(c.graph.GetVertexByID(c.start), 0)) 110 | } 111 | -------------------------------------------------------------------------------- /path/dijkstra.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/hmdsefi/gograph" 7 | "github.com/hmdsefi/gograph/util" 8 | ) 9 | 10 | // DijkstraSimple is a simple implementation of Dijkstra's algorithm. It's using 11 | // a simple slice to keep track of unvisited vertices, and selected the vertex 12 | // with the smallest tentative distance using linear search. 13 | // 14 | // The time complexity of the simple Dijkstra's algorithm implementation is O(V^2). 15 | // 16 | // It returns the shortest distances from the starting vertex to all other vertices 17 | // in the graph. 18 | func DijkstraSimple[T comparable](g gograph.Graph[T], start T) map[T]float64 { 19 | dist := make(map[T]float64) 20 | 21 | startVertex := g.GetVertexByID(start) 22 | if startVertex == nil { 23 | return dist 24 | } 25 | 26 | vertices := g.GetAllVertices() 27 | for _, v := range vertices { 28 | dist[v.Label()] = math.MaxFloat64 29 | } 30 | 31 | dist[start] = 0 32 | visited := make(map[T]bool) 33 | for len(visited) < len(vertices) { 34 | var u *gograph.Vertex[T] 35 | for _, v := range vertices { 36 | if !visited[v.Label()] && (u == nil || dist[v.Label()] < dist[u.Label()]) { 37 | u = v 38 | } 39 | } 40 | visited[u.Label()] = true 41 | neighbors := u.Neighbors() 42 | for _, neighbor := range neighbors { 43 | edge := g.GetEdge(u, neighbor) 44 | if alt := dist[u.Label()] + edge.Weight(); alt < dist[edge.Destination().Label()] { 45 | dist[edge.Destination().Label()] = alt 46 | } 47 | } 48 | } 49 | return dist 50 | } 51 | 52 | // dVertex represents dijkstra vertex. 53 | type dVertex[T comparable] struct { 54 | label T 55 | dist float64 56 | visited bool 57 | prev T 58 | } 59 | 60 | func newDVertex[T comparable](label T) *dVertex[T] { 61 | return &dVertex[T]{ 62 | label: label, 63 | dist: math.MaxFloat64, 64 | visited: false, 65 | } 66 | } 67 | 68 | // Dijkstra is a standard implementation of Dijkstra's Algorithm that uses a 69 | // min heap as a priority queue to find the shortest path between start vertex 70 | // and all other vertices in the specified graph. 71 | // 72 | // The time complexity of the standard Dijkstra's algorithm with a min heap is O((E+V)logV). 73 | // 74 | // It returns the shortest distances from the starting vertex to all other vertices 75 | // in the graph. 76 | func Dijkstra[T comparable](g gograph.Graph[T], start T) map[T]float64 { 77 | startVertex := g.GetVertexByID(start) 78 | if startVertex == nil { 79 | return make(map[T]float64) 80 | } 81 | 82 | // Initialize the heap and the visited map 83 | pq := util.NewVertexPriorityQueue[T]() 84 | visited := make(map[T]bool) 85 | 86 | // Initialize the start vertex 87 | verticesMap := make(map[T]*dVertex[T]) 88 | vertices := g.GetAllVertices() 89 | for _, v := range vertices { 90 | verticesMap[v.Label()] = newDVertex(v.Label()) 91 | } 92 | 93 | verticesMap[start].dist = 0 94 | 95 | // Add the start vertex to the heap 96 | pq.Push(util.NewVertexWithPriority(g.GetVertexByID(start), verticesMap[start].dist)) 97 | 98 | // Main loop 99 | for pq.Len() > 0 { 100 | // Extract the vertex with the smallest tentative distance from the heap 101 | curr := pq.Pop() 102 | visited[curr.Vertex().Label()] = true 103 | 104 | // Update the distances of its neighbors 105 | neighbors := curr.Vertex().Neighbors() 106 | for i, v := range neighbors { 107 | if !visited[v.Label()] { 108 | neighbor := verticesMap[v.Label()] 109 | newDist := curr.Priority() + g.GetEdge(curr.Vertex(), v).Weight() 110 | if newDist < neighbor.dist { 111 | neighbor.dist = newDist 112 | neighbor.prev = curr.Vertex().Label() 113 | pq.Push(util.NewVertexWithPriority(neighbors[i], verticesMap[v.Label()].dist)) 114 | } 115 | } 116 | } 117 | } 118 | 119 | // Return the distances from the start vertex to each other vertex 120 | distances := make(map[T]float64) 121 | for _, v := range verticesMap { 122 | distances[v.label] = v.dist 123 | } 124 | 125 | return distances 126 | } 127 | -------------------------------------------------------------------------------- /connectivity/gabow.go: -------------------------------------------------------------------------------- 1 | package connectivity 2 | 3 | import "github.com/hmdsefi/gograph" 4 | 5 | // Gabow's Algorithm is a linear time algorithm to find strongly connected 6 | // components in a directed graph. It is similar to Tarjan's Algorithm in 7 | // that it uses a stack to keep track of the current strongly connected 8 | // component, but it uses a different approach to find the strongly connected 9 | // components. 10 | // 11 | // The algorithm works by using a depth-first search, but it does not use 12 | // recursion. Instead, it keeps track of the vertices visited so far in an 13 | // array called visited. When a vertex is visited for the first time, it is 14 | // added to the stack and marked as visited. Then, it visits all of its 15 | // neighbors and adds them to the stack if they have not been visited before. 16 | // If a neighbor is already on the stack, then it is part of the same strongly 17 | // connected component, so Gabow's Algorithm keeps track of the minimum index 18 | // of any vertex on the stack that can be reached from the neighbor. This 19 | // minimum index is called the lowLink of the vertex. 20 | // 21 | // When the depth-first search is complete, the algorithm checks if the current 22 | // vertex is the root of a strongly connected component. This is true if the 23 | // lowLink of the vertex is equal to its index. If the vertex is the root of 24 | // a strongly connected component, it pops all the vertices on the stack with 25 | // indices greater than or equal to the vertex's index and adds them to a new 26 | // strongly connected component. 27 | // 28 | // The algorithm continues in this way until all vertices have been visited. 29 | // The resulting strongly connected components are returned by the algorithm. 30 | 31 | // Gabow runs the Gabow's algorithm, and returns a list of strongly 32 | // connected components, where each component is represented as an 33 | // array of pointers to vertex structs. 34 | func Gabow[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T] { 35 | var ( 36 | index int 37 | components [][]*gograph.Vertex[T] 38 | stack []*tarjanVertex[T] 39 | strongLinks func(v *tarjanVertex[T]) 40 | ) 41 | 42 | graphVertices := g.GetAllVertices() 43 | vertices := make(map[T]*tarjanVertex[T]) 44 | for _, v := range graphVertices { 45 | vertices[v.Label()] = newTarjanVertex(v) 46 | } 47 | 48 | // strongLinks is a recursive function that performs the DFS search 49 | // and identifies the strongly connected components. 50 | strongLinks = func(v *tarjanVertex[T]) { 51 | v.index = index 52 | v.lowLink = index 53 | index++ 54 | stack = append(stack, v) 55 | v.onStack = true 56 | 57 | neighbors := v.Neighbors() 58 | 59 | // The DFS search starts at the current vertex, v, and explores all 60 | // of its neighbors. For each neighbor w of v, the algorithm either 61 | // recursively calls strongLinks on w or updates the lowLink field 62 | // of v if w is already on the stack. 63 | // If v is a root node (i.e., has no parent), then v is added to a 64 | // list of strongly connected components when the DFS search is 65 | // complete. If v is not a root node, then it is added to the list 66 | // of strongly connected components when its lowLink field is equal 67 | // to its index field (i.e., when there is no back edge to a node 68 | // with a lower index). 69 | for _, neighbor := range neighbors { 70 | w := vertices[neighbor.Label()] 71 | if w.index == -1 { 72 | strongLinks(w) 73 | if w.lowLink < v.lowLink { 74 | v.lowLink = w.lowLink 75 | } 76 | } else if w.onStack { 77 | if w.index < v.lowLink { 78 | v.lowLink = w.index 79 | } 80 | } 81 | } 82 | 83 | if v.lowLink == v.index { 84 | var ( 85 | component []*tarjanVertex[T] 86 | w *tarjanVertex[T] 87 | ) 88 | for { 89 | w, stack = stack[len(stack)-1], stack[:len(stack)-1] 90 | w.onStack = false 91 | component = append(component, w) 92 | if w == v { 93 | break 94 | } 95 | } 96 | 97 | var temp []*gograph.Vertex[T] 98 | for i := range component { 99 | temp = append(temp, component[i].Vertex) 100 | } 101 | components = append(components, temp) 102 | } 103 | } 104 | 105 | for _, v := range vertices { 106 | if v.index == -1 { 107 | strongLinks(v) 108 | } 109 | } 110 | 111 | return components 112 | } 113 | -------------------------------------------------------------------------------- /path/bellman_ford_test.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/hmdsefi/gograph" 8 | ) 9 | 10 | func TestBellmanFord(t *testing.T) { 11 | g := gograph.New[string](gograph.Weighted(), gograph.Directed()) 12 | 13 | vA := g.AddVertexByLabel("A") 14 | vB := g.AddVertexByLabel("B") 15 | vC := g.AddVertexByLabel("C") 16 | vD := g.AddVertexByLabel("D") 17 | vE := g.AddVertexByLabel("E") 18 | vF := g.AddVertexByLabel("F") 19 | 20 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5)) 21 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1)) 22 | _, _ = g.AddEdge(vB, vD, gograph.WithEdgeWeight(2)) 23 | _, _ = g.AddEdge(vC, vE, gograph.WithEdgeWeight(1)) 24 | _, _ = g.AddEdge(vE, vD, gograph.WithEdgeWeight(-1)) 25 | _, _ = g.AddEdge(vD, vF, gograph.WithEdgeWeight(2)) 26 | _, _ = g.AddEdge(vF, vE, gograph.WithEdgeWeight(3)) 27 | 28 | dist, err := BellmanFord(g, vA.Label()) 29 | if err != nil { 30 | t.Errorf("Expected no errors, but get an err: %s", err) 31 | } 32 | 33 | if dist[vB.Label()] != 5 { 34 | t.Errorf( 35 | "Expected %s to %s shortest distance to be %d, but got %f", 36 | vA.Label(), 37 | vB.Label(), 38 | 5, 39 | dist[vB.Label()], 40 | ) 41 | } 42 | 43 | if dist[vC.Label()] != 6 { 44 | t.Errorf( 45 | "Expected %s to %s shortest distance to be %d, but got %f", 46 | vA.Label(), 47 | vC.Label(), 48 | 6, 49 | dist[vC.Label()], 50 | ) 51 | } 52 | 53 | if dist[vD.Label()] != 6 { 54 | t.Errorf( 55 | "Expected %s to %s shortest distance to be %d, but got %f", 56 | vA.Label(), 57 | vD.Label(), 58 | 6, 59 | dist[vD.Label()], 60 | ) 61 | } 62 | 63 | if dist[vE.Label()] != 7 { 64 | t.Errorf( 65 | "Expected %s to %s shortest distance to be %d, but got %f", 66 | vA.Label(), 67 | vE.Label(), 68 | 7, 69 | dist[vE.Label()], 70 | ) 71 | } 72 | 73 | if dist[vF.Label()] != 8 { 74 | t.Errorf( 75 | "Expected %s to %s shortest distance to be %d, but got %f", 76 | vA.Label(), 77 | vF.Label(), 78 | 8, 79 | dist[vF.Label()], 80 | ) 81 | } 82 | } 83 | 84 | func TestBellmanFord_NegativeCycle(t *testing.T) { 85 | g := gograph.New[string](gograph.Weighted(), gograph.Directed()) 86 | 87 | vA := g.AddVertexByLabel("A") 88 | vB := g.AddVertexByLabel("B") 89 | vC := g.AddVertexByLabel("C") 90 | vD := g.AddVertexByLabel("D") 91 | vE := g.AddVertexByLabel("E") 92 | vF := g.AddVertexByLabel("F") 93 | 94 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5)) 95 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1)) 96 | _, _ = g.AddEdge(vB, vD, gograph.WithEdgeWeight(2)) 97 | _, _ = g.AddEdge(vC, vE, gograph.WithEdgeWeight(1)) 98 | _, _ = g.AddEdge(vE, vD, gograph.WithEdgeWeight(-1)) 99 | _, _ = g.AddEdge(vD, vF, gograph.WithEdgeWeight(2)) 100 | _, _ = g.AddEdge(vF, vE, gograph.WithEdgeWeight(-3)) 101 | 102 | _, err := BellmanFord(g, vA.Label()) 103 | if err == nil { 104 | t.Errorf("Expected error, but got nil") 105 | } 106 | 107 | if !errors.Is(err, ErrNegativeWeightCycle) { 108 | t.Errorf("Expected error \"%s\", but got \"%s\"", ErrNegativeWeightCycle, err) 109 | } 110 | } 111 | 112 | func TestBellmanFord_NotWeighted(t *testing.T) { 113 | g := gograph.New[string](gograph.Directed()) 114 | 115 | vA := g.AddVertexByLabel("A") 116 | vB := g.AddVertexByLabel("B") 117 | vC := g.AddVertexByLabel("C") 118 | 119 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5)) 120 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1)) 121 | 122 | _, err := BellmanFord(g, vA.Label()) 123 | if err == nil { 124 | t.Errorf("Expected error, but got nil") 125 | } 126 | 127 | if !errors.Is(err, ErrNotWeighted) { 128 | t.Errorf("Expected error \"%s\", but got \"%s\"", ErrNotWeighted, err) 129 | } 130 | } 131 | 132 | func TestBellmanFord_NotDirected(t *testing.T) { 133 | g := gograph.New[string](gograph.Weighted()) 134 | 135 | vA := g.AddVertexByLabel("A") 136 | vB := g.AddVertexByLabel("B") 137 | vC := g.AddVertexByLabel("C") 138 | 139 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(5)) 140 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1)) 141 | 142 | _, err := BellmanFord(g, vA.Label()) 143 | if err == nil { 144 | t.Errorf("Expected error, but got nil") 145 | } 146 | 147 | if !errors.Is(err, ErrNotDirected) { 148 | t.Errorf("Expected error \"%s\", but got \"%s\"", ErrNotDirected, err) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /traverse/closest_first_iterator_test.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/hmdsefi/gograph" 8 | ) 9 | 10 | func initClosestFirstIteratorTestGraph() gograph.Graph[string] { 11 | g := gograph.New[string]() 12 | vA := g.AddVertexByLabel("A") 13 | vB := g.AddVertexByLabel("B") 14 | vC := g.AddVertexByLabel("C") 15 | vD := g.AddVertexByLabel("D") 16 | 17 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(2)) 18 | _, _ = g.AddEdge(vA, vC, gograph.WithEdgeWeight(5)) 19 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(1)) 20 | _, _ = g.AddEdge(vB, vD, gograph.WithEdgeWeight(3)) 21 | _, _ = g.AddEdge(vC, vD, gograph.WithEdgeWeight(2)) 22 | return g 23 | } 24 | 25 | func TestClosestFirstIterator_HasNext(t *testing.T) { 26 | // create a graph and a starting vertex for the iterator 27 | g := initClosestFirstIteratorTestGraph() 28 | 29 | // create an iterator with a vertex that doesn't exist 30 | _, err := NewClosestFirstIterator(g, "X") 31 | if err == nil { 32 | t.Error("Expect NewClosestFirstIterator returns error, but got nil") 33 | } 34 | 35 | // create the closest-first iterator 36 | it, err := NewClosestFirstIterator(g, "A") 37 | if err != nil { 38 | t.Errorf("Expect NewClosestFirstIterator doesn't return error, but got %s", err) 39 | } 40 | 41 | // check that the HasNext method returns true before reaching the end of the walk 42 | vertices := len(g.GetAllVertices()) 43 | for i := 0; i < vertices; i++ { 44 | if !it.HasNext() { 45 | t.Errorf("Expected HasNext to return true at step %d, but it returned false", i) 46 | } 47 | it.Next() 48 | } 49 | 50 | // check that the HasNext method returns false after reaching the end of the walk 51 | if it.HasNext() { 52 | t.Errorf("Expected HasNext to return false at end of walk, but it returned true") 53 | } 54 | } 55 | 56 | func TestClosestFirstIterator_Next(t *testing.T) { 57 | // create a graph and a starting vertex for the iterator 58 | g := initClosestFirstIteratorTestGraph() 59 | 60 | // create the random walk iterator 61 | it, err := NewClosestFirstIterator(g, "A") 62 | if err != nil { 63 | t.Errorf("Expect NewClosestFirstIterator doesn't return error, but got %s", err) 64 | } 65 | // check that the Next method returns the expected vertices in the walk 66 | expected := []string{"A", "B", "C", "D"} 67 | for i := 0; i < len(expected); i++ { 68 | v := it.Next() 69 | if v.Label() != expected[i] { 70 | t.Errorf( 71 | "Expected Next to return vertex %s, but it returned vertex %s", 72 | expected[i], v.Label(), 73 | ) 74 | } 75 | } 76 | 77 | v := it.Next() 78 | if v != nil { 79 | t.Errorf("Expected Next returns nil, but got %+v", v) 80 | } 81 | } 82 | 83 | func TestClosestFirstIterator_Iterate(t *testing.T) { 84 | // create a graph and a starting vertex for the iterator 85 | g := initClosestFirstIteratorTestGraph() 86 | 87 | // create the random walk iterator 88 | it, err := NewClosestFirstIterator(g, "A") 89 | if err != nil { 90 | t.Errorf("Expect NewClosestFirstIterator doesn't return error, but got %s", err) 91 | } 92 | 93 | // Initialize a slice to hold the visited vertices 94 | visited := make([]string, 0) 95 | 96 | // Iterate over the closest vertices and add their labels to the visited slice 97 | err = it.Iterate( 98 | func(v *gograph.Vertex[string]) error { 99 | visited = append(visited, v.Label()) 100 | return nil 101 | }, 102 | ) 103 | if err != nil { 104 | t.Errorf("Unexpected error during Iterate method call: %v", err) 105 | } 106 | 107 | // check that the visited slice contains the expected vertices in the walk 108 | expected := []string{"A", "B", "C", "D"} 109 | for i := 0; i < len(expected); i++ { 110 | if visited[i] != expected[i] { 111 | t.Errorf("Expected visited vertex at step %d to be %s, but it was %s", i, expected[i], visited[i]) 112 | } 113 | } 114 | 115 | it.Reset() 116 | if !it.HasNext() { 117 | t.Errorf("Expected HasNext to return true at step %d, but it returned false", 1) 118 | } 119 | 120 | v := it.Next() 121 | if v.Label() != "A" { 122 | t.Errorf( 123 | "Expected Next to return vertex %s, but it returned vertex %s", 124 | "A", v.Label(), 125 | ) 126 | } 127 | 128 | expectedErr := errors.New("something went wrong") 129 | err = it.Iterate( 130 | func(vertex *gograph.Vertex[string]) error { 131 | return expectedErr 132 | }, 133 | ) 134 | if err == nil { 135 | t.Error("Expect iter.Iterate(func) returns error, but got nil") 136 | } 137 | 138 | if !errors.Is(err, expectedErr) { 139 | t.Errorf("Expect %+v error, but got %+v", expectedErr, err) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /traverse/random_walk_iterator.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "crypto/rand" 5 | "math/big" 6 | 7 | "github.com/hmdsefi/gograph" 8 | ) 9 | 10 | // randomWalkIterator implements the Iterator interface to travers 11 | // a graph in a random walk fashion. 12 | // 13 | // Random walk is a stochastic process used to explore a graph, where 14 | // a walker moves through the graph by following random edges. At each 15 | // step, the walker chooses a random neighbor of the current node and 16 | // moves to it, and the process is repeated until a stopping condition 17 | // is met. 18 | // 19 | // In an unweighted graph, each neighboring node has an equal chance of 20 | // being chosen as the next node to visit during the traversal. However, 21 | // in a weighted graph, the probability of choosing a particular neighbor 22 | // as the next node to visit is proportional to the weight of the edge 23 | // connecting the current node and the neighbor. This means that nodes 24 | // connected by heavier edges are more likely to be visited during the 25 | // traversal. 26 | type randomWalkIterator[T comparable] struct { 27 | graph gograph.Graph[T] // the graph that being traversed. 28 | start T // the label of starting point of the traversal. 29 | current *gograph.Vertex[T] // the latest node that has been returned by the iterator. 30 | steps int // the maximum number of steps to be taken during the traversal. 31 | currentStep int // the step counter. 32 | } 33 | 34 | // NewRandomWalkIterator creates a new instance of randomWalkIterator 35 | // and returns it as the Iterator interface. 36 | func NewRandomWalkIterator[T comparable](graph gograph.Graph[T], start T, steps int) (Iterator[T], error) { 37 | v := graph.GetVertexByID(start) 38 | if v == nil { 39 | return nil, gograph.ErrVertexDoesNotExist 40 | } 41 | 42 | return &randomWalkIterator[T]{ 43 | graph: graph, 44 | start: start, 45 | current: v, 46 | steps: steps, 47 | }, nil 48 | } 49 | 50 | // HasNext returns a boolean indicating whether there are more vertices 51 | // to be visited or not. 52 | func (r *randomWalkIterator[T]) HasNext() bool { 53 | return r.current != nil && 54 | r.current.OutDegree() > 0 && 55 | r.currentStep < r.steps 56 | } 57 | 58 | // Next returns the next vertex to be visited in the random walk traversal. 59 | // It chooses one of the neighbors randomly and returns it. 60 | // 61 | // If the HasNext is false, returns nil. 62 | func (r *randomWalkIterator[T]) Next() *gograph.Vertex[T] { 63 | if !r.HasNext() { 64 | return nil 65 | } 66 | 67 | if r.currentStep == 0 { 68 | r.currentStep++ 69 | return r.current 70 | } 71 | 72 | r.currentStep++ 73 | neighbors := r.current.Neighbors() 74 | 75 | if r.graph.IsWeighted() { 76 | r.current = r.randomVertex(r.current) 77 | return r.current 78 | } 79 | 80 | i, _ := rand.Int(rand.Reader, big.NewInt(int64(len(neighbors)))) 81 | r.current = neighbors[i.Int64()] 82 | 83 | return r.current 84 | } 85 | 86 | // Iterate iterates through the vertices in random order and applies 87 | // the given function to each vertex. If the function returns an error, 88 | // the iteration stops and the error is returned. 89 | func (r *randomWalkIterator[T]) Iterate(f func(v *gograph.Vertex[T]) error) error { 90 | for r.HasNext() { 91 | if err := f(r.Next()); err != nil { 92 | return err 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | 99 | // Reset resets the iterator by setting the initial state of the iterator. 100 | func (r *randomWalkIterator[T]) Reset() { 101 | r.current = r.graph.GetVertexByID(r.start) 102 | r.currentStep = 0 103 | } 104 | 105 | func (r *randomWalkIterator[T]) randomVertex(v *gograph.Vertex[T]) *gograph.Vertex[T] { 106 | if v == nil { 107 | return nil 108 | } 109 | 110 | var totalWeight float64 111 | var edges []*gograph.Edge[T] 112 | neighbors := v.Neighbors() 113 | 114 | // calculate the sum of edge weights 115 | for _, neighbor := range neighbors { 116 | if edge := r.graph.GetEdge(v, neighbor); edge != nil { 117 | edges = append(edges, edge) 118 | totalWeight += edge.Weight() 119 | } 120 | } 121 | 122 | // generate a random number between 0 and the sum of edge weights 123 | randNum, _ := rand.Int(rand.Reader, big.NewInt(int64(totalWeight))) 124 | randWeight := float64(randNum.Int64()) 125 | 126 | // find the vertex that corresponds to the random weight 127 | for _, edge := range edges { 128 | randWeight -= edge.Weight() 129 | if randWeight < 0 { 130 | return edge.OtherVertex(v.Label()) 131 | } 132 | } 133 | 134 | return nil 135 | } 136 | -------------------------------------------------------------------------------- /partition/k-cut.md: -------------------------------------------------------------------------------- 1 | # gograph 2 | 3 | ## K-Cut 4 | 5 | ### Randomized Approximate 6 | The Randomized K-Cut algorithm is a probabilistic graph partitioning algorithm. It is a generalization of Karger's famous Min-Cut algorithm. The goal is to find a partition of an undirected graph's vertices into `k` non-empty subsets by removing the fewest number of edges (a minimum k-cut). 7 | 8 | The core idea is simple: repeatedly contract randomly chosen edges until exactly k "super-nodes" remain. Each super-node represents a cluster of the original graph's vertices. The set of edges that were not contracted and that connect these final `k` super-nodes is the candidate k-cut. 9 | 10 | **Time Complexity:** O(n * m) per run, where n is the number of vertices and m is the number of edges in the graph. 11 | **Space Complexity:** O(n + m) for storing vertex sets and edge lists. 12 | 13 | #### How does it work? 14 | ##### Initial Graph (Step 0) 15 | 16 | We begin with the original graph of 7 nodes (A, B, C, D, E, F, G). The true minimum 3-cut for this graph is 4 edges. 17 | 18 |
GoGraph is a lightweight, efficient, and easy-to-use graph data structure 14 | implementation written in Go. It provides a versatile framework for representing 15 | graphs and performing various operations on them, making it ideal for both 16 | educational purposes and practical applications.
17 |
73 |
74 | ## DFS
75 |
76 | DFS iterator is a technique used to implement the Depth-First Search (DFS)
77 | algorithm for traversing a graph or tree in a systematic way. The DFS iterator
78 | recursively visits all the vertices of the graph, starting from a given
79 | source vertex and exploring as far as possible along each branch before
80 | backtracking. The iterator maintains a stack of vertices to be visited,
81 | and it visits each vertex only once.
82 |
83 | One of the most common usages of DFS iterator is to find connected components
84 | in a graph. It can also be used to detect cycles in a graph, find strongly
85 | connected components in a directed graph, and perform topological sorting.
86 | In addition, DFS iterator can be used to find all paths between two vertices
87 | in a graph and to solve puzzles such as the n-queens problem.
88 |
89 | The time complexity of the DFS iterator algorithm is O(V + E), where V is
90 | the number of vertices and E is the number of edges in the graph. This is
91 | because the algorithm visits each vertex and edge exactly once. The space
92 | complexity of DFS iterator is O(V), where V is the number of vertices in
93 | the graph. This is because the algorithm uses a stack to store the vertices
94 | to be visited, and the maximum size of the stack is equal to the maximum
95 | depth of the DFS traversal.
96 |
97 | Here you can see how DFS iterator works:
98 |
99 |
100 | ## Topological Sort
101 |
102 | Topological iterator is a technique used to implement the Topological Sort
103 | algorithm for sorting the vertices of a directed acyclic graph (DAG) in a
104 | linear ordering. The topological sort orders the vertices such that for every
105 | directed edge (u, v), vertex u comes before vertex v in the ordering.
106 | Topological iterator iteratively removes the vertices with no incoming
107 | edges and adds them to the sorted list. It then removes the outgoing edges
108 | of the removed vertex and repeats the process.
109 |
110 | One of the most common usages of topological iterator is to schedule tasks
111 | or dependencies in a project based on their dependencies. It can also be
112 | used to detect cycles in a DAG, which indicates a circular dependency that
113 | makes it impossible to find a topological ordering. In addition, topological
114 | iterator can be used to generate a linear ordering of events in a causal
115 | relationship, such as in a concurrent system or a timeline of events.
116 |
117 | The time complexity of topological iterator algorithm is O(V + E), where V
118 | is the number of vertices and E is the number of edges in the graph.
119 | This is because the algorithm visits each vertex and edge exactly once.
120 | The space complexity of topological iterator is O(V), where V is the number
121 | of vertices in the graph. This is because the algorithm uses a queue to store
122 | the vertices to be visited, and the maximum size of the queue is equal to
123 | the number of vertices in the graph.
124 |
125 | Here you can see how topological ordering iterator works:
126 |
127 |
128 | ## Closest First
129 |
130 | Closest-first traversal, also known as the Best-First search or Greedy Best-First
131 | search, is a technique used to traverse a graph or a tree based on the distance
132 | between vertices and a given source vertex. The algorithm iteratively visits the
133 | vertex closest to the source vertex first and then expands its neighbors, repeating
134 | this process until all vertices have been visited. The distance can be defined in
135 | various ways, such as the number of edges, the weight of the edges, or any other
136 | distance metric.
137 |
138 | One of the most common usages of closest-first iterator is to find the shortest path
139 | between two vertices in a graph or to perform nearest-neighbor searches in machine
140 | learning or recommendation systems. It can also be used for clustering, community
141 | detection, and network analysis.
142 |
143 | The time and space complexity of closest-first iterator depend on the implementation
144 | of the distance metric and the data structure used for storing the vertices and their
145 | distances. If the distance metric is constant and the graph is represented as an
146 | adjacency matrix, the time complexity of closest-first iterator is O(V^2), where V is
147 | the number of vertices in the graph. If the distance metric is variable and the graph
148 | is represented as an adjacency list, the time complexity of closest-first iterator is
149 | O(E log V), where E is the number of edges in the graph. The space complexity of
150 | closest-first iterator is also O(V) for storing the distances and the visited vertices.
151 |
152 | Here you can see how topological ordering iterator works:
153 |
154 |
155 | ## Random Walk
156 |
157 | Random walk iterator is a technique used to traverse a graph in a stochastic manner by
158 | randomly selecting the next vertex to visit based on a probability distribution. In the
159 | context of graph traversal, random walk iterator can be categorized into two types:
160 | weighted and unweighted.
161 |
162 | In the weighted random walk iterator, the probability distribution for selecting the next
163 | vertex to visit is proportional to the weights of the edges connecting the current vertex
164 | to its neighbors. This means that edges with higher weights have a higher probability of
165 | being selected in the random walk. Weighted random walk iterator is commonly used in
166 | applications such as recommendation systems, where we want to find similar items or users based on their interactions in
167 | a network.
168 |
169 | In the unweighted random walk iterator, the probability distribution for selecting the
170 | next vertex to visit is uniform and does not depend on the weights of the edges. This
171 | means that all neighbors of the current vertex have an equal probability of being selected
172 | in the random walk. Unweighted random walk iterator is commonly used in applications such
173 | as web crawling, where we want to explore the web in a random and unbiased manner.
174 |
175 | The time and space complexity of random walk iterator depend on the size and structure
176 | of the graph, the number of vertices visited, and the type of random walk iterator used.
177 | In general, the time complexity of random walk iterator is proportional to the number
178 | of edges in the graph, while the space complexity is proportional to the number of visited
179 | vertices.
--------------------------------------------------------------------------------
/partition/bron_kerbosch.go:
--------------------------------------------------------------------------------
1 | package partition
2 |
3 | import (
4 | "container/heap"
5 | "math/bits"
6 |
7 | "github.com/hmdsefi/gograph"
8 | )
9 |
10 | // MaximalCliques finds all maximal cliques in the input graph using the
11 | // Bron–Kerbosch algorithm with pivot selection, degeneracy ordering, and bitsets.
12 | //
13 | // A **clique** is a subset of vertices where every two distinct vertices are
14 | // connected by an edge. A **maximal clique** is a clique that cannot be extended
15 | // by adding another adjacent vertex.
16 | //
17 | // This implementation is optimized for performance:
18 | // 1. **Degeneracy ordering**: processes vertices in a specific order to reduce
19 | // recursive calls and improve efficiency.
20 | // 2. **Pivot selection**: selects a pivot vertex at each recursive call to
21 | // reduce the number of branches.
22 | // 3. **Bitsets**: represents candidate sets (P, X) efficiently using []uint64
23 | // to speed up set operations on large graphs.
24 | //
25 | // Parameters:
26 | // - g: a gograph.Graph[T] representing the graph. T must be a comparable type.
27 | // Each vertex in the graph can be accessed via g.GetAllVertices() and
28 | // neighbors via Vertex.Neighbors().
29 | //
30 | // Returns:
31 | // - [][]*gograph.Vertex[T]: a slice of maximal cliques. Each clique is a slice
32 | // of pointers to Vertex[T]. Vertices in a clique are guaranteed to be fully
33 | // connected, and no clique is a subset of another.
34 | //
35 | // Complexity:
36 | // - **Time Complexity**: O(3^(n/3)) in the worst case for general graphs,
37 | // where n is the number of vertices. This is the known bound for enumerating
38 | // all maximal cliques. In practice, degeneracy ordering + pivoting reduces
39 | // the number of recursive calls significantly on sparse graphs.
40 | // - **Space Complexity**: O(n^2 / 64) for bitsets plus O(k*n) for storing cliques,
41 | // where k is the number of maximal cliques. Additional recursion stack space
42 | // is O(n) in depth.
43 | //
44 | // Example usage:
45 | //
46 | // g := gograph.New[string]()
47 | // a := g.AddVertexByLabel("A")
48 | // b := g.AddVertexByLabel("B")
49 | // c := g.AddVertexByLabel("C")
50 | // _, _ = g.AddEdge(a, b)
51 | // _, _ = g.AddEdge(b, c)
52 | // _, _ = g.AddEdge(c, a)
53 | //
54 | // cliques := MaximalCliques(g)
55 | // for _, clique := range cliques {
56 | // for _, v := range clique {
57 | // fmt.Print(v.Label(), " ")
58 | // }
59 | // fmt.Println()
60 | // }
61 | //
62 | // Notes:
63 | // - The function returns the actual Vertex pointers from the input graph;
64 | // do not modify the vertices while iterating the results.
65 | // - The order of cliques or vertices within a clique is not guaranteed.
66 | // If deterministic ordering is required, use a normalization function
67 | // (e.g., sort by vertex label).
68 | func MaximalCliques[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T] {
69 | vertices := g.GetAllVertices()
70 | n := len(vertices)
71 | if n == 0 {
72 | return nil
73 | }
74 |
75 | // label -> index
76 | indexOf := make(map[T]int, n)
77 | for i, v := range vertices {
78 | indexOf[v.Label()] = i
79 | }
80 |
81 | // adjacency list (indices) and adjacency bitsets
82 | adj := make([][]int, n)
83 | neighborsBits := make([][]uint64, n)
84 | words := wordLen(n)
85 | for i := 0; i < n; i++ {
86 | neighborsBits[i] = make([]uint64, words)
87 | }
88 |
89 | for i, v := range vertices {
90 | for _, nb := range v.Neighbors() {
91 | if j, ok := indexOf[nb.Label()]; ok {
92 | adj[i] = append(adj[i], j)
93 | setBit(neighborsBits[i], j)
94 | }
95 | }
96 | }
97 |
98 | // degeneracy ordering (returns vertices removed low-degree first)
99 | order := degeneracyOrder(adj, n)
100 |
101 | // posInOrder: index -> position in order (used to split neighbors into P/X)
102 | posInOrder := make([]int, n)
103 | for pos, idx := range order {
104 | posInOrder[idx] = pos
105 | }
106 |
107 | // We'll collect cliques as slices of int indices first
108 | var cliquesIdx [][]int
109 | // scratch P/X for top-level calls
110 | P := make([]uint64, words)
111 | X := make([]uint64, words)
112 |
113 | // For each vertex v in degeneracy order:
114 | for _, v := range order {
115 | // reset P and X
116 | for i := range P {
117 | P[i] = 0
118 | X[i] = 0
119 | }
120 | // Build P = N(v) ∩ {vertices after v in order}
121 | // Build X = N(v) ∩ {vertices before v in order}
122 | for _, w := range adj[v] {
123 | if posInOrder[w] > posInOrder[v] {
124 | setBit(P, w)
125 | } else {
126 | setBit(X, w)
127 | }
128 | }
129 |
130 | // Recurse with R = {v}, cloned P and X
131 | bronKerboschPivot([]int{v}, cloneBitset(P), cloneBitset(X), neighborsBits, n, &cliquesIdx)
132 |
133 | // remove v implicitly (degeneracy ensures no duplicates)
134 | }
135 |
136 | // convert index cliques to []*Vertex[T]
137 | result := make([][]*gograph.Vertex[T], len(cliquesIdx))
138 | for i, cl := range cliquesIdx {
139 | out := make([]*gograph.Vertex[T], len(cl))
140 | for j, idx := range cl {
141 | out[j] = vertices[idx]
142 | }
143 | result[i] = out
144 | }
145 | return result
146 | }
147 |
148 | func wordLen(n int) int { return (n + 63) >> 6 }
149 |
150 | func setBit(b []uint64, i int) {
151 | b[i>>6] |= 1 << i & 63
152 | }
153 |
154 | func clearBit(b []uint64, i int) {
155 | b[i>>6] &^= 1 << i & 63
156 | }
157 |
158 | func cloneBitset(b []uint64) []uint64 {
159 | if b == nil {
160 | return nil
161 | }
162 | c := make([]uint64, len(b))
163 | copy(c, b)
164 | return c
165 | }
166 |
167 | func intersectBitset(a, b []uint64) []uint64 {
168 | n := len(a)
169 | if len(b) < n {
170 | n = len(b)
171 | }
172 | res := make([]uint64, n)
173 | for i := 0; i < n; i++ {
174 | res[i] = a[i] & b[i]
175 | }
176 | return res
177 | }
178 |
179 | func differenceBitset(a, b []uint64) []uint64 {
180 | n := len(a)
181 | res := make([]uint64, n)
182 | for i := 0; i < n; i++ {
183 | var bi uint64
184 | if i < len(b) {
185 | bi = b[i]
186 | }
187 | res[i] = a[i] &^ bi
188 | }
189 | return res
190 | }
191 |
192 | func unionBitset(a, b []uint64) []uint64 {
193 | n := len(a)
194 | if len(b) > n {
195 | n = len(b)
196 | }
197 | res := make([]uint64, n)
198 | for i := 0; i < n; i++ {
199 | var ai, bi uint64
200 | if i < len(a) {
201 | ai = a[i]
202 | }
203 | if i < len(b) {
204 | bi = b[i]
205 | }
206 | res[i] = ai | bi
207 | }
208 | return res
209 | }
210 |
211 | func countBits(b []uint64) int {
212 | c := 0
213 | for _, w := range b {
214 | c += bits.OnesCount64(w)
215 | }
216 | return c
217 | }
218 |
219 | // forEachSetBit calls fn(i) for every set bit in the bitset b.
220 | // If fn returns true, iteration stops early.
221 | func forEachSetBit(b []uint64, fn func(idx int) (stop bool)) {
222 | for wi, word := range b {
223 | for word != 0 {
224 | t := bits.TrailingZeros64(word)
225 | idx := (wi << 6) + t
226 | if fn(idx) {
227 | return
228 | }
229 |
230 | // clear the least significant set bit
231 | word &= word - 1
232 | }
233 | }
234 | }
235 |
236 | // ---------------------
237 | // Degeneracy ordering (min-heap approach)
238 | // ---------------------
239 |
240 | type heapItem struct {
241 | deg int
242 | v int
243 | // idx field not necessary for this simple push-new-updates approach
244 | }
245 | type minHeap []heapItem
246 |
247 | func (h minHeap) Len() int { return len(h) }
248 | func (h minHeap) Less(i, j int) bool { return h[i].deg < h[j].deg }
249 | func (h minHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
250 |
251 | func (h *minHeap) Push(x interface{}) {
252 | *h = append(*h, x.(heapItem)) // nolint
253 | }
254 |
255 | func (h *minHeap) Pop() interface{} {
256 | old := *h
257 | n := len(old)
258 | x := old[n-1]
259 | *h = old[:n-1]
260 | return x
261 | }
262 |
263 | // degeneracyOrder returns an ordering of vertex indices (low-degree first removed).
264 | func degeneracyOrder(adj [][]int, n int) []int {
265 | deg := make([]int, n)
266 | for i := 0; i < n; i++ {
267 | deg[i] = len(adj[i])
268 | }
269 |
270 | h := &minHeap{}
271 | heap.Init(h)
272 | for i := 0; i < n; i++ {
273 | heap.Push(h, heapItem{deg: deg[i], v: i})
274 | }
275 |
276 | removed := make([]bool, n)
277 | order := make([]int, 0, n)
278 |
279 | for h.Len() > 0 {
280 | it := heap.Pop(h).(heapItem) // nolint
281 | v := it.v
282 | // skip outdated entries (we push updated degs rather than decrease-key)
283 | if removed[v] {
284 | continue
285 | }
286 | removed[v] = true
287 | order = append(order, v)
288 | for _, w := range adj[v] {
289 | if removed[w] {
290 | continue
291 | }
292 | deg[w]--
293 | heap.Push(h, heapItem{deg: deg[w], v: w})
294 | }
295 | }
296 |
297 | return order
298 | }
299 |
300 | // bronKerboschPivot does recursion; neighborsBits is adjacency bitset per vertex.
301 | // n is number of vertices (for word sizes and potential masking if needed).
302 | func bronKerboschPivot(
303 | r []int,
304 | p []uint64,
305 | x []uint64,
306 | neighborsBits [][]uint64,
307 | n int, // nolint
308 | cliques *[][]int,
309 | ) {
310 | // if P and X are empty → R is maximal
311 | if countBits(p) == 0 && countBits(x) == 0 {
312 | c := make([]int, len(r))
313 | copy(c, r)
314 | *cliques = append(*cliques, c)
315 | return
316 | }
317 |
318 | // choose pivot u from P ∪ X maximizing |P ∩ N(u)|
319 | unionPX := unionBitset(p, x)
320 | u := -1
321 | best := -1
322 | forEachSetBit(
323 | unionPX, func(idx int) bool {
324 | // compute |P ∩ N(idx)|
325 | cnt := countBits(intersectBitset(p, neighborsBits[idx]))
326 | if cnt > best {
327 | best = cnt
328 | u = idx
329 | }
330 | return false
331 | },
332 | )
333 |
334 | // candidates = P \ N(u)
335 | var candidates []uint64
336 | if u >= 0 {
337 | candidates = differenceBitset(p, neighborsBits[u])
338 | } else {
339 | candidates = cloneBitset(p)
340 | }
341 |
342 | // iterate over set bits in candidates
343 | // We must iterate over a snapshot (indices) because we'll mutate P/X during loop.
344 | var candidateIndices []int
345 | forEachSetBit(
346 | candidates, func(idx int) bool {
347 | candidateIndices = append(candidateIndices, idx)
348 | return false
349 | },
350 | )
351 |
352 | for _, v := range candidateIndices {
353 | // R' = R ∪ {v}
354 | Rp := append(r, v) // nolint
355 |
356 | // P' = P ∩ N(v)
357 | Pp := intersectBitset(p, neighborsBits[v])
358 |
359 | // X' = X ∩ N(v)
360 | Xp := intersectBitset(x, neighborsBits[v])
361 |
362 | // recurse
363 | bronKerboschPivot(Rp, Pp, Xp, neighborsBits, n, cliques)
364 |
365 | // move v from P to X in the current frame
366 | clearBit(p, v)
367 | setBit(x, v)
368 | }
369 | }
370 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------