├── 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 |
19 | Image 20 |
21 | 22 | **Step 1: Contracting Edge (E, G) into node EG** 23 | **Correction:** The edge between F and G becomes an edge between F and EG. 24 | 25 |
26 | Image 27 |
28 | 29 | **Nodes:** A, B, C, D, F, EG 30 | **Edges:** A-B, A-C, B-D, B-EG, C-EG, C-F, D-EG, EG-F 31 | 32 | ##### Step 2: Contracting Edge (C, F) into node CF 33 | 34 | **Critical Correction:** Let's list all edges connected to C and F before contraction: 35 | 36 | C is connected to: A, EG, F 37 | 38 | F is connected to: C, EG, G (but G is now part of EG, so this is just EG) 39 | 40 | **After contracting C and F into CF:** 41 | 42 | Edges from C: A-EG, C-F (self-loop, removed) 43 | 44 | Edges from F: EG (already exists), C-F (self-loop, removed) 45 | 46 | The edge **C-F is removed** as a self-loop. 47 | 48 | The new edges are: **A-CF**, **CF-EG**. The edge between CF and EG has multiplicity from C-EG and F-EG. 49 | 50 | **There is no original edge that would create a connection from B or D to CF.** 51 | 52 |
53 | Image 54 |
55 | 56 | **Nodes:** A, B, D, EG, CF 57 | **Edges:** A-B, A-CF, B-D, B-EG, D-EG, CF-EG 58 | 59 | ##### Step 3: Contracting Edge (B, D) into node BD 60 | 61 | **Correction:** Let's list all edges connected to B and D before contraction: 62 | 63 | B is connected to: A, D, EG 64 | 65 | D is connected to: B, EG 66 | 67 | **After contracting B and D into BD:** 68 | 69 | Edges from B: A, D (self-loop, removed), EG 70 | 71 | Edges from D: B (self-loop, removed), EG 72 | 73 | The edges **B-D and D-B are removed** as self-loops. 74 | 75 | The new edges are: **A-BD, BD-EG**. The edge between BD and EG has multiplicity from B-EG and D-EG. 76 | 77 | **There is still no edge between BD and CF.** 78 | 79 |
80 | Image 81 |
82 | 83 | **Nodes:** A, BD, EG, CF 84 | **Edges:** A-BD, A-CF, BD-EG, CF-EG 85 | 86 | ##### Step 4: Final Contraction to reach k=3 87 | 88 | We need to get from 4 nodes down to 3. We must contract one more edge. Our choices are: 89 | 90 | Contract (A, BD) 91 | 92 | Contract (A, CF) 93 | 94 | Contract (BD, EG) 95 | 96 | Contract (CF, EG) 97 | 98 | Let's choose to contract **(CF, EG)** into a new super-node **EGCF**. 99 | 100 | **After contracting CF and EG into EGCF:** 101 | 102 | Edges from CF: A, EG (self-loop, removed) 103 | 104 | Edges from EG: BD, CF (self-loop, removed) 105 | 106 | The new edges are: **A-EGCF, BD-EGCF**. 107 | 108 |
109 | Image 110 |
111 | 112 | **Final Clusters (The 3-Cut):** 113 | 114 | Cluster A: {A} 115 | 116 | Cluster BD: {B, D} 117 | 118 | Cluster EGCF: {E, G, C, F} 119 | 120 | **Edges in the Cut (between clusters):** 121 | 122 | Between **A** and **BD**: Edge A-B 123 | 124 | Between **A** and **EGCF**: Edge A-C 125 | 126 | Between **BD** and **EGCF**: Edges B-E and D-G 127 | 128 | **Cut Size:** 4 edges (A-B, A-C, B-E, D-G). -------------------------------------------------------------------------------- /path/transitive_reduction.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "github.com/hmdsefi/gograph" 5 | "github.com/hmdsefi/gograph/traverse" 6 | ) 7 | 8 | var ( 9 | // ErrNotDAG is returned when a graph contains cycles but a function requires a DAG 10 | ErrNotDAG = gograph.ErrDAGHasCycle 11 | ) 12 | 13 | // TransitiveReduction computes the transitive reduction of a directed graph. 14 | // The transitive reduction of a directed graph G is a graph G' with the same vertices 15 | // such that there is a path from vertex u to vertex v in G' if and only if there is 16 | // a path from u to v in G, and G' has as few edges as possible. 17 | // 18 | // This implementation uses a depth-first search approach inspired by NetworkX library 19 | // to efficiently compute the transitive reduction by identifying and removing edges 20 | // that have alternate paths. 21 | // 22 | // For directed acyclic graphs (DAGs), the transitive reduction can be computed efficiently 23 | // without needing to build the full transitive closure matrix. 24 | // 25 | // It returns an error if the graph is not directed or if the graph contains cycles. 26 | func TransitiveReduction[T comparable](g gograph.Graph[T]) (gograph.Graph[T], error) { 27 | // Transitive reduction requires a directed graph 28 | if !g.IsDirected() { 29 | return nil, ErrNotDirected 30 | } 31 | 32 | // For a general directed graph, we need to ensure it's acyclic 33 | if !g.IsAcyclic() { 34 | // If the graph is not marked as acyclic, topology sort will return an error if it contains cycles 35 | _, err := gograph.TopologySort(g) 36 | if err != nil { 37 | return nil, ErrNotDAG 38 | } 39 | } 40 | 41 | // Create a new graph for the transitive reduction with the same properties as the input 42 | var reducedGraph gograph.Graph[T] 43 | if g.IsWeighted() { 44 | reducedGraph = gograph.New[T](gograph.Weighted(), gograph.Directed()) 45 | } else { 46 | reducedGraph = gograph.New[T](gograph.Directed()) 47 | } 48 | 49 | // Add all vertices from the original graph to the reduced graph 50 | vertices := g.GetAllVertices() 51 | for _, v := range vertices { 52 | reducedGraph.AddVertexByLabel(v.Label()) 53 | } 54 | 55 | // Map to cache descendants for vertices that we've already processed 56 | descendants := make(map[T]map[T]bool) 57 | 58 | // Process each vertex in the graph 59 | for _, u := range vertices { 60 | // Get the neighbors of the current vertex 61 | neighbors := make(map[T]bool) 62 | for _, neighbor := range u.Neighbors() { 63 | neighbors[neighbor.Label()] = true 64 | } 65 | 66 | // For each neighbor of u, remove its descendants from consideration 67 | // as direct neighbors in the transitive reduction 68 | for _, v := range u.Neighbors() { 69 | // Skip if we've already removed this neighbor 70 | if _, exists := neighbors[v.Label()]; !exists { 71 | continue 72 | } 73 | 74 | // Get or compute descendants of v 75 | vDescendants, exists := descendants[v.Label()] 76 | if !exists { 77 | vDescendants = findDescendants(g, v) 78 | descendants[v.Label()] = vDescendants 79 | } 80 | 81 | // Remove v's descendants from u's neighbors 82 | for desc := range vDescendants { 83 | delete(neighbors, desc) 84 | } 85 | } 86 | 87 | // Add edges from u to its remaining neighbors in the reduced graph 88 | uVertex := reducedGraph.GetVertexByID(u.Label()) 89 | for neighbor := range neighbors { 90 | vVertex := reducedGraph.GetVertexByID(neighbor) 91 | 92 | // Preserve edge weight if the graph is weighted 93 | if g.IsWeighted() { 94 | originalEdge := g.GetEdge(u, g.GetVertexByID(neighbor)) 95 | if originalEdge != nil { 96 | _, err := reducedGraph.AddEdge(uVertex, vVertex, gograph.WithEdgeWeight(originalEdge.Weight())) 97 | if err != nil { 98 | return nil, err 99 | } 100 | } 101 | } else { 102 | _, err := reducedGraph.AddEdge(uVertex, vVertex) 103 | if err != nil { 104 | return nil, err 105 | } 106 | } 107 | } 108 | } 109 | 110 | return reducedGraph, nil 111 | } 112 | 113 | // findDescendants returns a map of all descendants of a vertex in the graph 114 | // using the depth-first traversal iterator from the traverse package 115 | func findDescendants[T comparable](g gograph.Graph[T], v *gograph.Vertex[T]) map[T]bool { 116 | descendants := make(map[T]bool) 117 | 118 | // Process each neighbor of the vertex 119 | for _, neighbor := range v.Neighbors() { 120 | // Create a depth-first iterator starting from this neighbor 121 | dfsIter, err := traverse.NewDepthFirstIterator(g, neighbor.Label()) 122 | if err != nil { 123 | continue 124 | } 125 | 126 | // Add all other reachable vertices as descendants 127 | for dfsIter.HasNext() { 128 | descendant := dfsIter.Next() 129 | descendants[descendant.Label()] = true 130 | } 131 | } 132 | 133 | return descendants 134 | } 135 | -------------------------------------------------------------------------------- /traverse/random_walk_iterator_test.go: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/hmdsefi/gograph" 8 | ) 9 | 10 | func initRandomWalkIteratorTestGraph() gograph.Graph[int] { 11 | g := gograph.New[int]() 12 | v1 := g.AddVertexByLabel(1) 13 | v2 := g.AddVertexByLabel(2) 14 | _, _ = g.AddEdge(v1, v2) 15 | return g 16 | } 17 | 18 | func TestRandomWalkIterator_HasNext(t *testing.T) { 19 | // create a graph and a starting vertex for the iterator 20 | g := initRandomWalkIteratorTestGraph() 21 | 22 | // create an iterator with a vertex that doesn't exist 23 | _, err := NewRandomWalkIterator(g, 123, 10) 24 | if err == nil { 25 | t.Error("Expect NewRandomWalkIterator returns error, but got nil") 26 | } 27 | 28 | // create the random walk iterator 29 | it, err := NewRandomWalkIterator(g, 1, 10) 30 | if err != nil { 31 | t.Errorf("Expect NewRandomWalkIterator doesn't return error, but got %s", err) 32 | } 33 | 34 | // check that the HasNext method returns true before reaching the end of the walk 35 | for i := 0; i < 10; i++ { 36 | if !it.HasNext() { 37 | t.Errorf("Expected HasNext to return true at step %d, but it returned false", i) 38 | } 39 | it.Next() 40 | } 41 | 42 | // check that the HasNext method returns false after reaching the end of the walk 43 | if it.HasNext() { 44 | t.Errorf("Expected HasNext to return false at end of walk, but it returned true") 45 | } 46 | } 47 | 48 | func TestRandomWalkIterator_Next(t *testing.T) { 49 | // create a graph and a starting vertex for the iterator 50 | g := initRandomWalkIteratorTestGraph() 51 | 52 | // create the random walk iterator 53 | it, err := NewRandomWalkIterator(g, 1, 10) 54 | if err != nil { 55 | t.Errorf("Expect NewRandomWalkIterator doesn't return error, but got %s", err) 56 | } 57 | // check that the Next method returns the expected vertices in the walk 58 | expected := []int{1, 2, 1, 2, 1, 2, 1, 2, 1, 2} 59 | for i := 0; i < len(expected); i++ { 60 | v := it.Next() 61 | if v.Label() != expected[i] { 62 | t.Errorf( 63 | "Expected Next to return vertex %d at step %d, but it returned vertex %d", 64 | expected[i], i, v.Label(), 65 | ) 66 | } 67 | } 68 | 69 | v := it.Next() 70 | if v != nil { 71 | t.Errorf("Expected Next returns nil, but got %+v", v) 72 | } 73 | } 74 | 75 | func TestRandomWalkIterator_Iterate(t *testing.T) { 76 | // create a graph and a starting vertex for the iterator 77 | g := initRandomWalkIteratorTestGraph() 78 | 79 | // create the random walk iterator 80 | it, err := NewRandomWalkIterator(g, 1, 10) 81 | if err != nil { 82 | t.Errorf("Expect NewRandomWalkIterator doesn't return error, but got %s", err) 83 | } 84 | 85 | // Initialize a slice to hold the visited vertices 86 | visited := make([]int, 0) 87 | 88 | // Iterate over the vertices in the walk and add their labels to the visited slice 89 | err = it.Iterate( 90 | func(v *gograph.Vertex[int]) error { 91 | visited = append(visited, v.Label()) 92 | return nil 93 | }, 94 | ) 95 | if err != nil { 96 | t.Errorf("Unexpected error during Iterate method call: %v", err) 97 | } 98 | 99 | // check that the visited slice contains the expected vertices in the walk 100 | expected := []int{1, 2, 1, 2, 1, 2, 1, 2, 1, 2} 101 | for i := 0; i < len(expected); i++ { 102 | if visited[i] != expected[i] { 103 | t.Errorf("Expected visited vertex at step %d to be %d, but it was %d", i, expected[i], visited[i]) 104 | } 105 | } 106 | 107 | it.Reset() 108 | if !it.HasNext() { 109 | t.Errorf("Expected HasNext to return true at step %d, but it returned false", 1) 110 | } 111 | 112 | v := it.Next() 113 | if v.Label() != 1 { 114 | t.Errorf( 115 | "Expected Next to return vertex %d at step %d, but it returned vertex %d", 116 | 1, 1, v.Label(), 117 | ) 118 | } 119 | 120 | expectedErr := errors.New("something went wrong") 121 | err = it.Iterate( 122 | func(vertex *gograph.Vertex[int]) error { 123 | return expectedErr 124 | }, 125 | ) 126 | if err == nil { 127 | t.Error("Expect iter.Iterate(func) returns error, but got nil") 128 | } 129 | 130 | if !errors.Is(err, expectedErr) { 131 | t.Errorf("Expect %+v error, but got %+v", expectedErr, err) 132 | } 133 | } 134 | 135 | func TestRandomWalkIterator_RandomVertex(t *testing.T) { 136 | g := gograph.New[int](gograph.Weighted()) 137 | v1 := g.AddVertexByLabel(1) 138 | v2 := g.AddVertexByLabel(2) 139 | v3 := g.AddVertexByLabel(3) 140 | v4 := g.AddVertexByLabel(4) 141 | v5 := g.AddVertexByLabel(5) 142 | 143 | _, _ = g.AddEdge(v1, v2, gograph.WithEdgeWeight(2)) 144 | _, _ = g.AddEdge(v1, v3, gograph.WithEdgeWeight(5)) 145 | _, _ = g.AddEdge(v2, v3, gograph.WithEdgeWeight(1)) 146 | _, _ = g.AddEdge(v3, v4, gograph.WithEdgeWeight(3)) 147 | _, _ = g.AddEdge(v4, v5, gograph.WithEdgeWeight(2)) 148 | 149 | // create the random walk iterator 150 | iter, err := NewRandomWalkIterator(g, 3, 10) 151 | if err != nil { 152 | t.Errorf("Expect NewRandomWalkIterator doesn't return error, but got %s", err) 153 | } 154 | 155 | randV := iter.Next() 156 | if randV.Label() < 1 || randV.Label() > 4 { 157 | t.Errorf("Random vertex %v is outside the range of valid vertices 2,3", randV.Label()) 158 | } 159 | 160 | randV = iter.Next() 161 | if randV.Label() < 1 || randV.Label() > 4 { 162 | t.Errorf("Random vertex %v is outside the range of valid vertices 2,3", randV.Label()) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /partition/k_cut_randomized.go: -------------------------------------------------------------------------------- 1 | package partition 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | "github.com/hmdsefi/gograph" 8 | ) 9 | 10 | type KCutResult[T comparable] struct { 11 | Supernodes [][]*gograph.Vertex[T] 12 | CutEdges []*gograph.Edge[T] 13 | } 14 | 15 | // RandomizedKCut computes an approximate k-cut of an undirected graph using 16 | // a randomized contraction algorithm (generalization of Karger’s min-cut). 17 | // 18 | // The algorithm works as follows: 19 | // 1. Initialize each vertex as its own supernode. 20 | // 2. While the number of supernodes > k: 21 | // a. Pick a random edge (u, v) from the remaining edges. 22 | // b. Contract the edge: merge vertices u and v into a single supernode. 23 | // - Redirect all edges of v to u. 24 | // - Remove self-loops. 25 | // 3. When exactly k supernodes remain, the remaining edges between supernodes 26 | // form the approximate k-cut. 27 | // 4. Return both the supernodes and the edges crossing between them. 28 | // 29 | // Notes: 30 | // - This is a randomized algorithm; different runs may produce different cuts. 31 | // - Probability of finding the true minimum k-cut decreases with graph size 32 | // and k. Repeating the algorithm multiple times improves the chances. 33 | // - Only applicable to undirected graphs; for directed graphs, results are 34 | // approximate and may not correspond to a global min-cut. 35 | // - The returned supernodes are slices of vertex pointers representing 36 | // the contracted vertex groups after k-way partitioning. 37 | // - The returned cut edges are edges that connect different supernodes in the 38 | // original graph. 39 | // 40 | // Time Complexity: O(n * m) per run, where n is the number of vertices and m 41 | // is the number of edges in the graph. 42 | // 43 | // Space Complexity: O(n + m) for storing vertex sets and edge lists. 44 | // 45 | // Parameters: 46 | // 47 | // g - The input graph implementing Graph[T] interface. 48 | // k - The number of supernodes desired in the partition (k ≥ 2). 49 | // 50 | // Returns: 51 | // 52 | // *KCutResult[T] - Struct containing: 53 | // - Supernodes: slice of supernodes (vertex groups) after contraction. 54 | // - CutEdges: edges connecting different supernodes (the k-cut). 55 | // error - Non-nil if k < 2 or graph has fewer than k vertices. 56 | // 57 | // Example usage: 58 | // 59 | // g := NewBaseGraph[string](false, false) // undirected, unweighted 60 | // a := g.AddVertexByLabel("A") 61 | // b := g.AddVertexByLabel("B") 62 | // g.AddEdge(a, b) 63 | // result, err := RandomizedKCut(g, 2) 64 | // if err != nil { log.Fatal(err) } 65 | // fmt.Println("Supernodes:", result.Supernodes) 66 | // fmt.Println("Cut edges:", result.CutEdges) 67 | func RandomizedKCut[T comparable](g gograph.Graph[T], k int) (*KCutResult[T], error) { 68 | if k < 2 { 69 | return nil, fmt.Errorf("k must be at least 2") 70 | } 71 | 72 | if int(g.Order()) < k { 73 | return nil, fmt.Errorf("graph has fewer vertices (%d) than k=%d", g.Order(), k) 74 | } 75 | 76 | // 1. Initialize each vertex as its own supernode 77 | supernodes := make(map[T]map[T]*gograph.Vertex[T]) // supernode ID -> set of vertex labels 78 | vertexToSupernode := make(map[T]*gograph.Vertex[T]) // vertex -> supernode ID 79 | for _, v := range g.GetAllVertices() { 80 | vertexToSupernode[v.Label()] = v 81 | supernodes[v.Label()] = map[T]*gograph.Vertex[T]{v.Label(): v} 82 | } 83 | 84 | // 2. Collect all edges 85 | edges := g.AllEdges() 86 | rand.Shuffle(len(edges), func(i, j int) { edges[i], edges[j] = edges[j], edges[i] }) 87 | 88 | // 3. Contract edges randomly until number of supernodes == k 89 | for len(supernodes) > k { 90 | if len(edges) == 0 { 91 | break 92 | } 93 | e := edges[0] 94 | edges = edges[1:] 95 | 96 | u := vertexToSupernode[e.Source().Label()] 97 | v := vertexToSupernode[e.Destination().Label()] 98 | if u == v { 99 | continue // same supernode, skip 100 | } 101 | 102 | // Merge v into u 103 | for label, vertex := range supernodes[v.Label()] { 104 | supernodes[u.Label()][label] = vertex 105 | vertexToSupernode[label] = u 106 | } 107 | delete(supernodes, v.Label()) 108 | } 109 | 110 | // 4. Collect cut edges (edges that connect different supernodes) 111 | var cutEdges []*gograph.Edge[T] 112 | 113 | if len(supernodes) < int(g.Order()) { 114 | seen := make(map[string]bool) // deduplicate undirected edges 115 | for _, e := range g.AllEdges() { 116 | u := vertexToSupernode[e.Source().Label()] 117 | v := vertexToSupernode[e.Destination().Label()] 118 | if u != v { 119 | // canonical key for undirected edge 120 | var key string 121 | if fmt.Sprint(u) < fmt.Sprint(v) { 122 | key = fmt.Sprintf("%v-%v", u, v) 123 | } else { 124 | key = fmt.Sprintf("%v-%v", v, u) 125 | } 126 | if !seen[key] { 127 | cutEdges = append(cutEdges, e) 128 | seen[key] = true 129 | } 130 | } 131 | } 132 | } 133 | 134 | // 5. Convert supernodes map to slices 135 | resultSupernodes := make([][]*gograph.Vertex[T], 0, len(supernodes)) 136 | for _, nodes := range supernodes { 137 | group := make([]*gograph.Vertex[T], 0, len(nodes)) 138 | for _, v := range nodes { 139 | group = append(group, v) 140 | } 141 | resultSupernodes = append(resultSupernodes, group) 142 | } 143 | 144 | return &KCutResult[T]{ 145 | Supernodes: resultSupernodes, 146 | CutEdges: cutEdges, 147 | }, nil 148 | } 149 | -------------------------------------------------------------------------------- /partition/bron_kerbosch.md: -------------------------------------------------------------------------------- 1 | # gograph 2 | 3 | ## Clique Bron–Kerbosch Algorithm with Pivot + Degeneracy 4 | 5 | ### What is a Clique? 6 | 7 | A clique in a graph is a subset of vertices such that every pair of vertices is connected by an edge. 8 | - A maximal clique is a clique that cannot be extended by adding any other vertex from the graph. 9 | - A maximum clique is the largest clique by size (this is a harder problem). 10 | 11 | Example: 12 | - In a triangle graph `A—B—C—A`, the set `{A,B,C}` is a maximal clique (and also maximum). 13 | - If you add another vertex `D` connected only to `B` and `C`, then `{B,C,D}` is another maximal clique. 14 | 15 | ### Why Are Cliques Useful? 16 | 17 | Clique detection has real-world applications in many fields: 18 | - **Social networks:** find groups of people where everyone knows each other. 19 | - **Bioinformatics:** detect protein interaction groups. 20 | - **Recommendation systems:** find strongly related sets of items. 21 | - **Communication networks:** detect communities in graphs. 22 | - **Data mining & clustering:** identify dense subgraphs representing hidden structures. 23 | 24 | ### Bron–Kerbosch Algorithm with Pivot + Degeneracy 25 | This implementation uses the **Bron–Kerbosch algorithm with pivoting, degeneracy ordering, and bitsets:** 26 | 1. **Bron–Kerbosch recursion:** Expands a current partial clique R by exploring candidates P while excluding already processed vertices `X`. 27 | 2. **Pivoting:** Avoids redundant searches by selecting a **pivot vertex** with many neighbors, reducing the branching factor. 28 | 3. **Degeneracy ordering:** Orders vertices by minimum degree expansion, ensuring the recursion explores smaller candidate sets first, greatly improving efficiency on sparse graphs. 29 | 4. **Bitsets:** Efficiently represent and update sets (`P`, `X`, neighbors) using bitwise operations, reducing memory overhead and speeding up intersections. 30 | 31 | ### Time Complexity 32 | - **Worst-case:** The number of maximal cliques in a graph can be **exponential** in the number of vertices `(O(3^(n/3)))`. 33 | - **Example:** A graph of `n/3` disjoint triangles has `3^(n/3)` maximal cliques. 34 | - **With degeneracy ordering:** Time complexity becomes `O(d * n * 3^(d/3))`, where `d` is the graph degeneracy (small for sparse real-world graphs). 35 | - **Practical performance:** On real-world sparse networks, this approach is highly efficient compared to the naive algorithm. 36 | 37 | ### Space Complexity 38 | - Bitset representation uses `O(n^2 / 64)` bits in the worst case (adjacency representation). 39 | - Recursion depth is bounded by the size of the largest clique (`≤ n`). 40 | - Overall space: `O(n^2`) for adjacency + `O(n)` recursion stack. 41 | 42 | ### Example 43 | The algorithm maintains three sets: 44 | - **R →** the current growing clique 45 | - **P →** possible vertices that can expand `R` 46 | - **X →** vertices already considered (to avoid duplicates) 47 | At each step, we expand `R` with candidates from `P`, update sets, and recurse until `P` is empty. When both `P` and `X` are empty → we found a **maximal clique.** 48 | 49 | ```mermaid 50 | graph TD 51 | A --- B 52 | A --- C 53 | B --- C 54 | B --- D 55 | C --- D 56 | D --- E 57 | D --- F 58 | E --- F 59 | G --- H 60 | H --- I 61 | G --- I 62 | ``` 63 | Vertices: `A, B, C, D, E, F, G, H, I` 64 | 65 | 66 | #### Stepwise Visual (Table of R, P, X) 67 | | Step | R (Current Clique) | P (Candidates) | X (Excluded) | Action / Result | 68 | | ---- | ------------------ | ------------------- | ------------ | ------------------------ | 69 | | 0 | {} | {A,B,C,D,E,F,G,H,I} | {} | Start algorithm | 70 | | 1 | {A} | {B,C} | {} | Expand with A | 71 | | 2 | {A,B} | {C} | {} | Expand with B | 72 | | 3 | {A,B,C} | {} | {} | ✅ Maximal clique {A,B,C} | 73 | | 4 | {A,C} | {B,D} | {} | Expand with C | 74 | | 5 | {A,C,B} | {} | {} | Duplicate {A,B,C} | 75 | | 6 | {A,C,D} | {} | {} | Not maximal | 76 | | 7 | {B} | {C,D} | {A} | Expand with B | 77 | | 8 | {B,C} | {D} | {A} | Expand with C | 78 | | 9 | {B,C,D} | {} | {A} | ✅ Maximal clique {B,C,D} | 79 | | 10 | {E} | {D,F} | {} | Expand with E | 80 | | 11 | {E,D} | {F} | {} | Expand with D | 81 | | 12 | {E,D,F} | {} | {} | ✅ Maximal clique {D,E,F} | 82 | | 13 | {G} | {H,I} | {} | Expand with G | 83 | | 14 | {G,H} | {I} | {} | Expand with H | 84 | | 15 | {G,H,I} | {} | {} | ✅ Maximal clique {G,H,I} | 85 | 86 | **Final Cliques** 87 | - {A, B, C} 88 | - {B, C, D} 89 | - {D, E, F} 90 | - {G, H, I} 91 | 92 | #### Recursion Tree 93 | ```mermaid 94 | graph TD 95 | Root["R={} P={A..I} X={}"] 96 | Root --> A["R={A} P={B,C}"] 97 | A --> AB["R={A,B} P={C}"] 98 | AB --> ABC["R={A,B,C} => clique"] 99 | A --> AC["R={A,C} P={B,D}"] 100 | AC --> ACB["R={A,C,B} => clique"] 101 | AC --> ACD["R={A,C,D} (not maximal)"] 102 | 103 | Root --> B1["R={B} P={C,D}"] 104 | B1 --> BC["R={B,C} P={D}"] 105 | BC --> BCD["R={B,C,D} => clique"] 106 | 107 | Root --> E1["R={E} P={D,F}"] 108 | E1 --> ED["R={E,D} P={F}"] 109 | ED --> EDF["R={E,D,F} => clique"] 110 | 111 | Root --> G1["R={G} P={H,I}"] 112 | G1 --> GH["R={G,H} P={I}"] 113 | GH --> GHI["R={G,H,I} => clique"] 114 | ``` 115 | 116 | ### References 117 | - [Bron–Kerbosch algorithm (Wikipedia)](https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm) 118 | - Eppstein, David, et al. Listing all maximal cliques in sparse graphs in near-optimal time. 119 | - Applications of clique problems in [network science](https://en.wikipedia.org/wiki/Clique_problem) 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build](https://github.com/hmdsefi/gograph/actions/workflows/build.yml/badge.svg) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/hmdsefi/gograph)](https://goreportcard.com/report/github.com/hmdsefi/gograph) 3 | [![codecov](https://codecov.io/gh/hmdsefi/gograph/branch/master/graph/badge.svg?token=BstHl9wXTN)](https://codecov.io/gh/hmdsefi/gograph) 4 | [![Go Reference](https://pkg.go.dev/badge/github.com/hmdsefi/gograph.svg)](https://pkg.go.dev/github.com/hmdsefi/gograph) 5 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#science-and-data-analysis) 6 | 7 | 8 | golang generic graph package 9 | 10 | # GoGraph 11 |
12 |
13 |

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 |


18 | 19 | ## Table of Contents 20 | 21 | * [Install](#Install) 22 | * [How to Use](#How-to-Use) 23 | * [Graph](#Graph) 24 | * [Directed](#Directed) 25 | * [Acyclic](#Acyclic) 26 | * [Undirected](#Undirected) 27 | * [Weighted](#Weighted) 28 | * [Traverse](#Traverse) 29 | * [Connectivity](https://github.com/hmdsefi/gograph/tree/master/connectivity#gograph---connectivity) 30 | * [Shortest Path]() 31 | * [Dijkstra](https://github.com/hmdsefi/gograph/blob/master/path/dijkstra.md) 32 | * [Bellman-Ford](https://github.com/hmdsefi/gograph/blob/master/path/bellman-ford.md) 33 | * [Floyd-Warshall](https://github.com/hmdsefi/gograph/blob/master/path/floyd-warshall.md) 34 | * [License](#License) 35 | 36 | ## Install 37 | 38 | Use `go get` command to get the latest version of the `gograph`: 39 | 40 | ```shell 41 | go get github.com/hmdsefi/gograph 42 | ``` 43 | 44 | Then you can use import the `gograph` to your code: 45 | 46 | ```go 47 | package main 48 | 49 | import "github.com/hmdsefi/gograph" 50 | ``` 51 | 52 | ## How to Use 53 | 54 | ### Graph 55 | 56 | `gograph` contains the `Graph[T comparable]` interface that provides all needed APIs to 57 | manage a graph. All the supported graph types in `gograph` library implemented this interface. 58 | 59 | ```go 60 | type Graph[T comparable] interface { 61 | GraphType 62 | 63 | AddEdge(from, to *Vertex[T], options ...EdgeOptionFunc) (*Edge[T], error) 64 | GetAllEdges(from, to *Vertex[T]) []*Edge[T] 65 | GetEdge(from, to *Vertex[T]) *Edge[T] 66 | EdgesOf(v *Vertex[T]) []*Edge[T] 67 | RemoveEdges(edges ...*Edge[T]) 68 | AddVertexByLabel(label T, options ...VertexOptionFunc) *Vertex[T] 69 | AddVertex(v *Vertex[T]) 70 | GetVertexByID(label T) *Vertex[T] 71 | GetAllVerticesByID(label ...T) []*Vertex[T] 72 | GetAllVertices() []*Vertex[T] 73 | RemoveVertices(vertices ...*Vertex[T]) 74 | ContainsEdge(from, to *Vertex[T]) bool 75 | ContainsVertex(v *Vertex[T]) bool 76 | } 77 | ``` 78 | 79 | The generic type of the `T` in `Graph` interface represents the vertex label. The type of `T` 80 | should be comparable. You cannot use slices and function types for `T`. 81 | 82 | #### Directed 83 | 84 | ![directed-graph](https://user-images.githubusercontent.com/11541936/221904292-face2083-16da-491f-a339-2164b7040264.png) 85 | 86 | ```go 87 | graph := New[int](gograph.Directed()) 88 | 89 | graph.AddEdge(gograph.NewVertex(1), gograph.NewVertex(2)) 90 | graph.AddEdge(gograph.NewVertex(1), gograph.NewVertex(3)) 91 | graph.AddEdge(gograph.NewVertex(2), gograph.NewVertex(2)) 92 | graph.AddEdge(gograph.NewVertex(3), gograph.NewVertex(4)) 93 | graph.AddEdge(gograph.NewVertex(4), gograph.NewVertex(5)) 94 | graph.AddEdge(gograph.NewVertex(5), gograph.NewVertex(6)) 95 | ``` 96 | 97 | #### Acyclic 98 | 99 | ![acyclic-graph](https://user-images.githubusercontent.com/11541936/221911652-ce2dfb5f-5547-4f26-8412-94ad9124d4fa.png) 100 | 101 | ```go 102 | graph := New[int](gograph.Acyclic()) 103 | 104 | graph.AddEdge(gograph.NewVertex(1), gograph.NewVertex(2)) 105 | graph.AddEdge(gograph.NewVertex(2), gograph.NewVertex(3)) 106 | _, err := graph.AddEdge(gograph.NewVertex(3), gograph.NewVertex(1)) 107 | if err != nil { 108 | // do something 109 | } 110 | ``` 111 | 112 | #### Undirected 113 | 114 | ![undirected-graph](https://user-images.githubusercontent.com/11541936/221908261-a009049d-2b71-46c3-9026-faa4dcc2a693.png) 115 | 116 | ```go 117 | // by default graph is undirected 118 | graph := New[string]() 119 | 120 | graph.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("B")) 121 | graph.AddEdge(gograph.NewVertex("A"), gograph.NewVertex("D")) 122 | graph.AddEdge(gograph.NewVertex("B"), gograph.NewVertex("C")) 123 | graph.AddEdge(gograph.NewVertex("B"), gograph.NewVertex("D")) 124 | ``` 125 | 126 | #### Weighted 127 | 128 | ![weighted-edge](https://user-images.githubusercontent.com/11541936/221908269-b6db15fb-6104-49d9-b9b9-acc062d94e4a.png) 129 | 130 | ```go 131 | graph := New[string]() 132 | 133 | vA := gograph.AddVertexByLabel("A") 134 | vB := gograph.AddVertexByLabel("B") 135 | vC := gograph.AddVertexByLabel("C") 136 | vD := gograph.AddVertexByLabel("D") 137 | 138 | graph.AddEdge(vA, vB, gograph.WithEdgeWeight(4)) 139 | graph.AddEdge(vA, vD, gograph.WithEdgeWeight(3)) 140 | graph.AddEdge(vB, vC, gograph.WithEdgeWeight(3)) 141 | graph.AddEdge(vB, vD, gograph.WithEdgeWeight(1)) 142 | graph.AddEdge(vC, vD, gograph.WithEdgeWeight(2)) 143 | ``` 144 | 145 | ![weighted-vertex](https://user-images.githubusercontent.com/11541936/221908278-83f3138d-8b28-4c38-825a-627a46d65294.png) 146 | 147 | ```go 148 | graph := New[string]() 149 | vA := gograph.AddVertexByLabel("A", gograph.WithVertexWeight(3)) 150 | vB := gograph.AddVertexByLabel("B", gograph.WithVertexWeight(2)) 151 | vC := gograph.AddVertexByLabel("C", gograph.WithVertexWeight(4)) 152 | 153 | graph.AddEdge(vA, vB) 154 | graph.AddEdge(vB, vC) 155 | ``` 156 | 157 | ### Traverse 158 | 159 | Traverse package provides the iterator interface that guarantees all the algorithm export the same APIs: 160 | 161 | ```go 162 | type Iterator[T comparable] interface { 163 | HasNext() bool 164 | Next() *gograph.Vertex[T] 165 | Iterate(func(v *gograph.Vertex[T]) error) error 166 | Reset() 167 | } 168 | ``` 169 | 170 | This package contains the following iterators: 171 | 172 | - [Breadth-First iterator](https://github.com/hmdsefi/gograph/tree/master/traverse#BFS) 173 | - [Depth-First iterator](https://github.com/hmdsefi/gograph/tree/master/traverse#DFS) 174 | - [Topological iterator](https://github.com/hmdsefi/gograph/tree/master/traverse#Topological-Sort) 175 | - [Closest-First iterator](https://github.com/hmdsefi/gograph/tree/master/traverse#Closest-First) 176 | - [Random Walk iterator](https://github.com/hmdsefi/gograph/tree/master/traverse#random-walk) 177 | 178 | ## License 179 | 180 | Apache License, please see [LICENSE](https://github.com/hmdsefi/gograph/blob/master/LICENSE) for details. 181 | -------------------------------------------------------------------------------- /partition/girvan-newman.md: -------------------------------------------------------------------------------- 1 | # gograph 2 | 3 | ## Girvan–Newman Community Detection Algorithm 4 | 5 | The Girvan–Newman algorithm is a graph partitioning algorithm used for community detection in undirected networks. 6 | Communities (or clusters) are groups of nodes that are more densely connected internally than with the rest of the 7 | graph. The algorithm identifies edges that are likely “bridges” between communities and removes them iteratively to 8 | reveal clusters. 9 | 10 | ### Graph Algorithm Type 11 | 12 | - **Type:** Community detection / Partitioning algorithm 13 | - **Graph Requirements:** 14 | - Works primarily on undirected graphs 15 | - Can be adapted for weighted graphs (edge weights influence betweenness) 16 | - Input graph should allow iteration over vertices and edges 17 | 18 | ### Usage / Applications 19 | 20 | - **Social Networks:** Detect communities or friend groups. 21 | - **Biological Networks:** Identify functional modules (e.g., protein-protein interaction networks). 22 | - **Recommendation Systems:** Discover user or item clusters. 23 | - **Infrastructure Analysis:** Identify weak points in transportation, electrical, or communication networks. 24 | 25 | ### Advantages 26 | 27 | - Intuitive and easy to implement. 28 | - Detects overlapping communities indirectly through edge removal. 29 | - Works on weighted graphs when betweenness is computed using edge weights. 30 | 31 | ### Limitations 32 | 33 | - High computational cost for large graphs. 34 | - Sensitive to noisy data (edges with similar betweenness may be removed arbitrarily). 35 | - Primarily designed for undirected graphs; adaptations are needed for directed graphs. 36 | 37 | ### Time Complexity 38 | 39 | Let V be the number of vertices and E the number of edges: 40 | 41 | - **Edge Betweenness Computation:** O(V * (V + E)) per iteration (BFS from each node using Brandes’ algorithm). 42 | - **Edge Removal Iterations:** In the worst case, up to O(E) edges may be removed one at a time. 43 | - **Total Worst-Case Time Complexity:** O(E * V * (V + E)). 44 | 45 | ### Space Complexity 46 | 47 | - **Graph storage (cloned graph):** O(V + E) 48 | - **BFS queues and dependency maps:** O(V + E) 49 | - **Edge betweenness storage:** O(E) 50 | - **Total Space Complexity:** O(V + E) 51 | 52 | ### How It Works 53 | 54 | 1. **Compute Edge Betweenness** 55 | - Edge betweenness measures the number of shortest paths that pass through each edge. 56 | - High-betweenness edges often connect different communities. 57 | - Computed efficiently using Brandes’ algorithm with BFS from each vertex. 58 | 59 | 2. **Remove High Betweenness Edge(s)** 60 | - Remove the edge with the maximum betweenness centrality. 61 | - This step gradually disconnects the graph along community boundaries. 62 | 63 | 3. **Update Connected Components** 64 | - After removing edges, determine connected components using non-recursive BFS. 65 | - Each connected component represents a potential community. 66 | 67 | 4. **Repeat** 68 | - Recompute edge betweenness in the modified graph. 69 | - Continue removing edges until the desired number of communities k is reached, or until no edges remain. 70 | 71 |
72 | Image 73 |
74 | 75 | ### Example 76 | 77 | **k=3** 78 | **Vertices:** A, B, C, D, E, F, G, H 79 | 80 | #### Step 0: Initial Graph 81 | 82 |
83 | Image 84 |
85 | 86 | - Action: clone graph to avoid mutating original. 87 | - No traversal yet. 88 | 89 | #### Step 1: Compute Edge Betweenness 90 | 91 | **Algorithm:** **Brandes’ algorithm** uses **BFS** (Breadth-First Search) to compute the shortest paths **from each 92 | vertex**. 93 | 94 | **Process:** 95 | 96 | - For each vertex v in V: 97 | - Run BFS to find shortest paths from v to all other vertices. 98 | - Keep track of the number of shortest paths reaching each vertex. 99 | - Accumulate dependency scores backward (like a reverse BFS) to compute contribution to each edge. 100 | 101 | Resulting approximate edge betweenness values: 102 | | Edge | Betweenness | 103 | | ---- | ----------- | 104 | | A-B | 1.0 | 105 | | A-C | 1.0 | 106 | | B-C | 1.0 | 107 | | C-D | 7.0 | 108 | | D-E | 5.0 | 109 | | E-F | 4.0 | 110 | | F-G | 3.0 | 111 | | G-H | 2.0 | 112 | | E-H | 3.5 | 113 | 114 | **Observation:** C-D has highest betweenness → bridge between triangle A-B-C and linear chain. 115 | 116 | #### Step 2: Remove Maximum Betweenness Edge (C-D) 117 | 118 |
119 | Image 120 |
121 | 122 | **Connected Components:** computed using non-recursive BFS: 123 | 124 | - Start from unvisited node, traverse neighbors using a queue. 125 | - Mark visited vertices. 126 | - All vertices visited in the BFS form a component. 127 | 128 | **Components:** 129 | 130 | 1. `{A, B, C}` 131 | 2. `{D, E, F, G, H}` 132 | 133 | #### Step 3: Recompute Betweenness in Remaining Graphs 134 | 135 | **Traversal:** again BFS from each node inside the working graph, recompute shortest paths and edge betweenness. 136 | 137 | New betweenness values (approx): 138 | | Edge | Betweenness | 139 | | ---- | ----------- | 140 | | D-E | 5.0 | 141 | | E-F | 4.0 | 142 | | F-G | 3.0 | 143 | | G-H | 2.0 | 144 | | E-H | 5.0 | 145 | 146 | **Max edges:** `D-E` and `E-H` → remove one (let’s choose E-H). 147 | 148 | #### Step 4: Remove E-H 149 | 150 |
151 | Image 152 |
153 | 154 | `Components:` computed using **BFS** again. 155 | Still connected: `{D, E, F, G, H}` 156 | 157 | #### Step 5: Remove D-E 158 | 159 |
160 | Image 161 |
162 | 163 | **Components:** 164 | 165 | 1. `{A, B, C}` 166 | 2. `{D}` 167 | 3. `{E, F, G, H}` 168 | 169 | Components = 3 → stop (k=3). 170 | 171 | #### Step 6: Final Partition 172 | 173 | **Components:** 174 | 175 | 1. `{A, B, C}` 176 | 2. `{D}` 177 | 3. `{E, F, G, H}` 178 | 179 |
180 | Image 181 |
182 | 183 | #### Step 7: Summary of Traversals 184 | 185 | | Step | Purpose | Traversal | 186 | |------|----------------------------------------|----------------------------------------| 187 | | 1 | Compute edge betweenness | BFS (Brandes) from each node | 188 | | 2 | Identify components | Non-recursive BFS from unvisited nodes | 189 | | 3 | Recompute betweenness | BFS in each component | 190 | | 4–5 | Remove max edges and update components | BFS for components | 191 | 192 | **Key Points for `k=3`:** 193 | - Stop removing edges as soon as the number of connected components reaches k. 194 | - BFS ensures no recursion and no stack overflow, even with larger graphs. 195 | - High-betweenness edges (C-D, E-H, D-E) are removed first → separates weakly connected communities. 196 | 197 | ### References 198 | 199 | 1. Girvan, M., & Newman, M. E. J. (2002). Community structure in social and biological networks. Proceedings of the 200 | National Academy of Sciences, 99(12), 7821–7826. 201 | 2. Brandes, U. (2001). A Faster Algorithm for Betweenness Centrality. Journal of Mathematical Sociology, 25(2), 163–177. -------------------------------------------------------------------------------- /partition/girvan_newman.go: -------------------------------------------------------------------------------- 1 | package partition 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/hmdsefi/gograph" 7 | ) 8 | 9 | // GirvanNewman implements the Girvan-Newman community detection algorithm for 10 | // undirected graphs. It partitions a graph into communities by iteratively 11 | // removing edges with the highest betweenness centrality until either the 12 | // specified number of connected components `k` is reached, or no edges remain 13 | // if k <= 0. 14 | // 15 | // The algorithm proceeds as follows: 16 | // 1. Compute edge betweenness centrality for all edges using Brandes algorithm. 17 | // - BFS is used from each vertex to determine the shortest paths. 18 | // - Dependencies are accumulated to assign betweenness values to edges. 19 | // 2. Identify edges with maximum betweenness. 20 | // 3. Remove one or more edges with the highest betweenness. 21 | // 4. Update connected components using a non-recursive BFS traversal. 22 | // 5. Repeat steps 1-4 until the desired number of components (`k`) is reached. 23 | // If k <= 0, continue until all edges are removed. 24 | // 25 | // Important details: 26 | // - High betweenness edges typically act as bridges between clusters and are 27 | // removed first, effectively separating communities. 28 | // - BFS is used exclusively for both shortest path computation and component 29 | // identification to avoid recursion and stack overflow issues. 30 | // - The input graph `g` is cloned internally to prevent mutation. 31 | // 32 | // Parameters: 33 | // - g: An instance of gograph.Graph[T] representing the undirected input graph. 34 | // - k: The desired number of communities (connected components). If k <= 0, 35 | // the algorithm continues removing edges until no edges remain. 36 | // 37 | // Returns: 38 | // - A slice of gograph.Graph[T], each representing a connected component (community). 39 | // - An error if the operation fails. 40 | // 41 | // Time Complexity: 42 | // - Each iteration requires computing betweenness for all edges using BFS from each node. 43 | // - Let V = number of vertices, E = number of edges. 44 | // - Computing betweenness: O(V * (V + E)) per iteration. 45 | // - In the worst case, up to O(E) edges can be removed one by one. 46 | // - Total worst-case time complexity: O(E * V * (V + E)). 47 | // 48 | // Space Complexity: 49 | // - Storing the cloned graph: O(V + E) 50 | // - BFS traversal queues and dependency maps: O(V + E) 51 | // - Storing betweenness values for edges: O(E) 52 | // - Total space complexity: O(V + E) 53 | func GirvanNewman[T comparable](g gograph.Graph[T], k int) ([]gograph.Graph[T], error) { 54 | if g == nil { 55 | return nil, errors.New("input graph is nil") 56 | } 57 | 58 | // Clone graph 59 | working := cloneGraph(g) 60 | 61 | components := getConnectedComponents(working) 62 | for (k > 0 && len(components) < k) || (k <= 0 && working.Size() > 0) { 63 | // Compute edge betweenness 64 | betweenness := calculateEdgeBetweenness(working) 65 | if len(betweenness) == 0 { 66 | break 67 | } 68 | 69 | // Find max betweenness 70 | maxVal := -1.0 71 | var edgesToRemove []*gograph.Edge[T] 72 | for e, val := range betweenness { 73 | if val > maxVal { 74 | maxVal = val 75 | edgesToRemove = []*gograph.Edge[T]{e} 76 | } else if val == maxVal { 77 | edgesToRemove = append(edgesToRemove, e) 78 | } 79 | } 80 | 81 | // Remove edges 82 | working.RemoveEdges(edgesToRemove...) 83 | 84 | components = getConnectedComponents(working) 85 | if k > 0 && len(components) >= k { 86 | break 87 | } 88 | } 89 | 90 | // Convert components to Graph[T] objects 91 | result := make([]gograph.Graph[T], len(components)) 92 | for i, comp := range components { 93 | subgraph := gograph.New[T]() 94 | // Add vertices 95 | for _, v := range comp { 96 | subgraph.AddVertexByLabel(v.Label(), func(p *gograph.VertexProperties) {}) 97 | } 98 | // Add edges 99 | for _, v := range comp { 100 | for _, e := range g.EdgesOf(v) { 101 | if subgraph.ContainsVertex(e.Source()) && subgraph.ContainsVertex(e.Destination()) { 102 | _, _ = subgraph.AddEdge( 103 | subgraph.GetVertexByID(e.Source().Label()), 104 | subgraph.GetVertexByID(e.Destination().Label()), 105 | gograph.WithEdgeWeight(e.Weight()), 106 | ) 107 | } 108 | } 109 | } 110 | result[i] = subgraph 111 | } 112 | 113 | return result, nil 114 | } 115 | 116 | // cloneGraph deep-copies the graph 117 | func cloneGraph[T comparable](g gograph.Graph[T]) gograph.Graph[T] { 118 | clone := gograph.New[T]() 119 | vertexMap := make(map[T]*gograph.Vertex[T]) 120 | for _, v := range g.GetAllVertices() { 121 | vClone := clone.AddVertexByLabel(v.Label(), gograph.WithVertexWeight(v.Weight())) 122 | vertexMap[v.Label()] = vClone 123 | } 124 | for _, e := range g.AllEdges() { 125 | _, _ = clone.AddEdge( 126 | vertexMap[e.Source().Label()], 127 | vertexMap[e.Destination().Label()], 128 | gograph.WithEdgeWeight(e.Weight()), 129 | ) 130 | } 131 | return clone 132 | } 133 | 134 | // getConnectedComponents returns slices of vertices representing each connected component (non-recursive) 135 | func getConnectedComponents[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T] { 136 | visited := make(map[*gograph.Vertex[T]]bool) 137 | var components [][]*gograph.Vertex[T] 138 | var queue []*gograph.Vertex[T] 139 | 140 | for _, v := range g.GetAllVertices() { 141 | if !visited[v] { 142 | var comp []*gograph.Vertex[T] 143 | queue = append(queue, v) 144 | visited[v] = true 145 | for len(queue) > 0 { 146 | curr := queue[0] 147 | queue = queue[1:] 148 | comp = append(comp, curr) 149 | for _, e := range g.EdgesOf(curr) { 150 | neighbor := e.OtherVertex(curr.Label()) 151 | if neighbor != nil && !visited[neighbor] { 152 | visited[neighbor] = true 153 | queue = append(queue, neighbor) 154 | } 155 | } 156 | } 157 | components = append(components, comp) 158 | } 159 | } 160 | 161 | return components 162 | } 163 | 164 | // calculateEdgeBetweenness computes edge betweenness centrality using Brandes' algorithm 165 | func calculateEdgeBetweenness[T comparable](g gograph.Graph[T]) map[*gograph.Edge[T]]float64 { 166 | betweenness := make(map[*gograph.Edge[T]]float64) 167 | vertices := g.GetAllVertices() 168 | 169 | for _, s := range vertices { 170 | // BFS structures 171 | distance := make(map[*gograph.Vertex[T]]int) 172 | sigma := make(map[*gograph.Vertex[T]]float64) 173 | pred := make(map[*gograph.Vertex[T]][]*gograph.Vertex[T]) 174 | 175 | for _, v := range vertices { 176 | distance[v] = -1 177 | sigma[v] = 0 178 | pred[v] = []*gograph.Vertex[T]{} 179 | } 180 | sigma[s] = 1 181 | distance[s] = 0 182 | 183 | queue := []*gograph.Vertex[T]{s} 184 | var stack []*gograph.Vertex[T] 185 | 186 | // BFS 187 | for len(queue) > 0 { 188 | v := queue[0] 189 | queue = queue[1:] 190 | stack = append(stack, v) 191 | 192 | for _, e := range g.EdgesOf(v) { 193 | w := e.OtherVertex(v.Label()) 194 | if w == nil { 195 | continue 196 | } 197 | if distance[w] < 0 { 198 | distance[w] = distance[v] + 1 199 | queue = append(queue, w) 200 | } 201 | if distance[w] == distance[v]+1 { 202 | sigma[w] += sigma[v] 203 | pred[w] = append(pred[w], v) 204 | } 205 | } 206 | } 207 | 208 | // Accumulation 209 | delta := make(map[*gograph.Vertex[T]]float64) 210 | for len(stack) > 0 { 211 | w := stack[len(stack)-1] 212 | stack = stack[:len(stack)-1] 213 | for _, v := range pred[w] { 214 | c := (sigma[v] / sigma[w]) * (1 + delta[w]) 215 | edge := g.GetEdge(v, w) 216 | if edge != nil { 217 | betweenness[edge] += c 218 | } 219 | delta[v] += c 220 | } 221 | } 222 | } 223 | 224 | // Halve the counts for undirected edges 225 | for e := range betweenness { 226 | betweenness[e] /= 2 227 | } 228 | 229 | return betweenness 230 | } 231 | -------------------------------------------------------------------------------- /path/transitive_reduction_test.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/hmdsefi/gograph" 8 | ) 9 | 10 | func TestTransitiveReduction_Simple(t *testing.T) { 11 | // Create a simple directed acyclic graph 12 | g := gograph.New[string](gograph.Directed()) 13 | 14 | // Add vertices 15 | vA := g.AddVertexByLabel("A") 16 | vB := g.AddVertexByLabel("B") 17 | vC := g.AddVertexByLabel("C") 18 | 19 | // Add edges A->B, B->C, A->C (A->C is the transitive edge that should be removed) 20 | _, _ = g.AddEdge(vA, vB) 21 | _, _ = g.AddEdge(vB, vC) 22 | _, _ = g.AddEdge(vA, vC) 23 | 24 | // Compute transitive reduction 25 | reduced, err := TransitiveReduction(g) 26 | if err != nil { 27 | t.Errorf("TransitiveReduction returned an error: %v", err) 28 | } 29 | 30 | // Reduced graph should have 3 vertices and 2 edges (A->B, B->C) 31 | if len(reduced.GetAllVertices()) != 3 { 32 | t.Errorf("Expected 3 vertices, got %d", len(reduced.GetAllVertices())) 33 | } 34 | if len(reduced.AllEdges()) != 2 { 35 | t.Errorf("Expected 2 edges, got %d", len(reduced.AllEdges())) 36 | } 37 | 38 | // Check specific edges 39 | vAReduced := reduced.GetVertexByID("A") 40 | vBReduced := reduced.GetVertexByID("B") 41 | vCReduced := reduced.GetVertexByID("C") 42 | 43 | if reduced.GetEdge(vAReduced, vBReduced) == nil { 44 | t.Errorf("Edge A->B should exist in reduced graph") 45 | } 46 | if reduced.GetEdge(vBReduced, vCReduced) == nil { 47 | t.Errorf("Edge B->C should exist in reduced graph") 48 | } 49 | if reduced.GetEdge(vAReduced, vCReduced) != nil { 50 | t.Errorf("Edge A->C should not exist in reduced graph") 51 | } 52 | } 53 | 54 | func TestTransitiveReduction_Complex(t *testing.T) { 55 | // Create a more complex directed acyclic graph 56 | g := gograph.New[string](gograph.Directed()) 57 | 58 | // Add vertices 59 | vA := g.AddVertexByLabel("A") 60 | vB := g.AddVertexByLabel("B") 61 | vC := g.AddVertexByLabel("C") 62 | vD := g.AddVertexByLabel("D") 63 | g.AddVertexByLabel("E") 64 | 65 | // Add edges 66 | // A->B, B->C, C->D 67 | // A->C, B->D, A->D (these should be reduced) 68 | // E (isolated vertex) 69 | _, _ = g.AddEdge(vA, vB) 70 | _, _ = g.AddEdge(vB, vC) 71 | _, _ = g.AddEdge(vC, vD) 72 | _, _ = g.AddEdge(vA, vC) // This should be removed (A->B->C) 73 | _, _ = g.AddEdge(vB, vD) // This should be removed (B->C->D) 74 | _, _ = g.AddEdge(vA, vD) // This should be removed (A->B->C->D) 75 | 76 | // Compute transitive reduction 77 | reduced, err := TransitiveReduction(g) 78 | if err != nil { 79 | t.Errorf("TransitiveReduction returned an error: %v", err) 80 | } 81 | 82 | // Reduced graph should have 5 vertices and 3 edges (A->B, B->C, C->D) 83 | if len(reduced.GetAllVertices()) != 5 { 84 | t.Errorf("Expected 5 vertices, got %d", len(reduced.GetAllVertices())) 85 | } 86 | if len(reduced.AllEdges()) != 3 { 87 | t.Errorf("Expected 3 edges, got %d", len(reduced.AllEdges())) 88 | } 89 | 90 | // Check specific edges 91 | vAReduced := reduced.GetVertexByID("A") 92 | vBReduced := reduced.GetVertexByID("B") 93 | vCReduced := reduced.GetVertexByID("C") 94 | vDReduced := reduced.GetVertexByID("D") 95 | 96 | if reduced.GetEdge(vAReduced, vBReduced) == nil { 97 | t.Errorf("Edge A->B should exist in reduced graph") 98 | } 99 | if reduced.GetEdge(vBReduced, vCReduced) == nil { 100 | t.Errorf("Edge B->C should exist in reduced graph") 101 | } 102 | if reduced.GetEdge(vCReduced, vDReduced) == nil { 103 | t.Errorf("Edge C->D should exist in reduced graph") 104 | } 105 | if reduced.GetEdge(vAReduced, vCReduced) != nil { 106 | t.Errorf("Edge A->C should not exist in reduced graph") 107 | } 108 | if reduced.GetEdge(vBReduced, vDReduced) != nil { 109 | t.Errorf("Edge B->D should not exist in reduced graph") 110 | } 111 | if reduced.GetEdge(vAReduced, vDReduced) != nil { 112 | t.Errorf("Edge A->D should not exist in reduced graph") 113 | } 114 | } 115 | 116 | func TestTransitiveReduction_Diamond(t *testing.T) { 117 | // Create a diamond-shaped graph 118 | g := gograph.New[string](gograph.Directed()) 119 | 120 | // Add vertices 121 | vA := g.AddVertexByLabel("A") 122 | vB := g.AddVertexByLabel("B") 123 | vC := g.AddVertexByLabel("C") 124 | vD := g.AddVertexByLabel("D") 125 | 126 | // Add edges - Diamond pattern 127 | // A->B, A->C, B->D, C->D 128 | _, _ = g.AddEdge(vA, vB) 129 | _, _ = g.AddEdge(vA, vC) 130 | _, _ = g.AddEdge(vB, vD) 131 | _, _ = g.AddEdge(vC, vD) 132 | 133 | // Compute transitive reduction 134 | // In this case, all edges should remain since there are no transitive edges 135 | reduced, err := TransitiveReduction(g) 136 | if err != nil { 137 | t.Errorf("TransitiveReduction returned an error: %v", err) 138 | } 139 | 140 | // Reduced graph should have 4 vertices and 4 edges (same as original) 141 | if len(reduced.GetAllVertices()) != 4 { 142 | t.Errorf("Expected 4 vertices, got %d", len(reduced.GetAllVertices())) 143 | } 144 | if len(reduced.AllEdges()) != 4 { 145 | t.Errorf("Expected 4 edges, got %d", len(reduced.AllEdges())) 146 | } 147 | 148 | // Check specific edges - All should still be present 149 | vAReduced := reduced.GetVertexByID("A") 150 | vBReduced := reduced.GetVertexByID("B") 151 | vCReduced := reduced.GetVertexByID("C") 152 | vDReduced := reduced.GetVertexByID("D") 153 | 154 | if reduced.GetEdge(vAReduced, vBReduced) == nil { 155 | t.Errorf("Edge A->B should exist in reduced graph") 156 | } 157 | if reduced.GetEdge(vAReduced, vCReduced) == nil { 158 | t.Errorf("Edge A->C should exist in reduced graph") 159 | } 160 | if reduced.GetEdge(vBReduced, vDReduced) == nil { 161 | t.Errorf("Edge B->D should exist in reduced graph") 162 | } 163 | if reduced.GetEdge(vCReduced, vDReduced) == nil { 164 | t.Errorf("Edge C->D should exist in reduced graph") 165 | } 166 | } 167 | 168 | func TestTransitiveReduction_WeightPreservation(t *testing.T) { 169 | // Create a weighted directed graph 170 | g := gograph.New[string](gograph.Directed(), gograph.Weighted()) 171 | 172 | // Add vertices 173 | vA := g.AddVertexByLabel("A") 174 | vB := g.AddVertexByLabel("B") 175 | vC := g.AddVertexByLabel("C") 176 | 177 | // Add weighted edges 178 | // A->B (weight 1), B->C (weight 2), A->C (weight 10) - A->C should be removed 179 | _, _ = g.AddEdge(vA, vB, gograph.WithEdgeWeight(1.0)) 180 | _, _ = g.AddEdge(vB, vC, gograph.WithEdgeWeight(2.0)) 181 | _, _ = g.AddEdge(vA, vC, gograph.WithEdgeWeight(10.0)) 182 | 183 | // Compute transitive reduction 184 | reduced, err := TransitiveReduction(g) 185 | if err != nil { 186 | t.Errorf("TransitiveReduction returned an error: %v", err) 187 | } 188 | 189 | // Check if the reduced graph is also weighted 190 | if !reduced.IsWeighted() { 191 | t.Errorf("Expected reduced graph to be weighted") 192 | } 193 | 194 | // Check if weights are preserved 195 | vAReduced := reduced.GetVertexByID("A") 196 | vBReduced := reduced.GetVertexByID("B") 197 | vCReduced := reduced.GetVertexByID("C") 198 | 199 | edgeAB := reduced.GetEdge(vAReduced, vBReduced) 200 | edgeBC := reduced.GetEdge(vBReduced, vCReduced) 201 | 202 | if edgeAB == nil { 203 | t.Errorf("Edge A->B should exist in reduced graph") 204 | } 205 | if edgeBC == nil { 206 | t.Errorf("Edge B->C should exist in reduced graph") 207 | } 208 | 209 | if edgeAB != nil && edgeAB.Weight() != 1.0 { 210 | t.Errorf("Edge A->B should have weight 1.0, got %f", edgeAB.Weight()) 211 | } 212 | if edgeBC != nil && edgeBC.Weight() != 2.0 { 213 | t.Errorf("Edge B->C should have weight 2.0, got %f", edgeBC.Weight()) 214 | } 215 | } 216 | 217 | func TestTransitiveReduction_ErrorCases(t *testing.T) { 218 | // Test case 1: Undirected graph 219 | undirectedGraph := gograph.New[string]() 220 | _, err := TransitiveReduction(undirectedGraph) 221 | if err == nil { 222 | t.Errorf("Expected error for undirected graph, got nil") 223 | } 224 | if err != ErrNotDirected { 225 | t.Errorf("Expected ErrNotDirected, got %v", err) 226 | } 227 | 228 | // Test case 2: Graph with cycle 229 | cyclicGraph := gograph.New[string](gograph.Directed()) 230 | vA := cyclicGraph.AddVertexByLabel("A") 231 | vB := cyclicGraph.AddVertexByLabel("B") 232 | vC := cyclicGraph.AddVertexByLabel("C") 233 | 234 | _, _ = cyclicGraph.AddEdge(vA, vB) 235 | _, _ = cyclicGraph.AddEdge(vB, vC) 236 | _, _ = cyclicGraph.AddEdge(vC, vA) // Creates a cycle 237 | 238 | _, err = TransitiveReduction(cyclicGraph) 239 | if err == nil { 240 | t.Errorf("Expected error for cyclic graph, got nil") 241 | } 242 | if !errors.Is(err, ErrNotDAG) { 243 | t.Errorf("Expected ErrNotDAG, got %v", err) 244 | } 245 | } 246 | 247 | func TestTransitiveReduction_EmptyGraph(t *testing.T) { 248 | // Create an empty directed graph 249 | g := gograph.New[string](gograph.Directed()) 250 | 251 | // Compute transitive reduction 252 | reduced, err := TransitiveReduction(g) 253 | if err != nil { 254 | t.Errorf("TransitiveReduction returned an error: %v", err) 255 | } 256 | 257 | // Reduced graph should be empty too 258 | if len(reduced.GetAllVertices()) != 0 { 259 | t.Errorf("Expected 0 vertices, got %d", len(reduced.GetAllVertices())) 260 | } 261 | if len(reduced.AllEdges()) != 0 { 262 | t.Errorf("Expected 0 edges, got %d", len(reduced.AllEdges())) 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /graph.go: -------------------------------------------------------------------------------- 1 | package gograph 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrNilVertices = errors.New("vertices are nil") 9 | ErrVertexDoesNotExist = errors.New("vertex does not exist") 10 | ErrEdgeAlreadyExists = errors.New("edge already exists") 11 | ErrDAGCycle = errors.New("edges would create cycle") 12 | ErrDAGHasCycle = errors.New("the graph contains a cycle") 13 | ) 14 | 15 | // Graph defines methods for managing a graph with vertices and edges. It is the 16 | // base interface in the graph hierarchy. Each graph object contains a set of 17 | // vertices and edges. 18 | // 19 | // Through generics, a graph can be typed to specific classes for vertices' label T. 20 | type Graph[T comparable] interface { 21 | GraphType 22 | 23 | // AddEdge adds an edge from the vertex with the 'from' label to 24 | // the vertex with the 'to' label by appending the 'to' vertex to the 25 | // 'neighbors' slice of the 'from' vertex, in directed graph. 26 | // 27 | // In undirected graph, it also adds an edge from the vertex with 28 | // the 'to' label to the vertex with the 'from' label by appending 29 | // the 'from' vertex to the 'neighbors' slice of the 'to' vertex. it 30 | // means that it create the edges in both direction between the specified 31 | // vertices. 32 | // 33 | // This method accepts additional edge options such as weight and adds 34 | // them to the new edge. 35 | // 36 | // 37 | // It creates the input vertices if they don't exist in the graph. 38 | // If any of the specified vertices is nil, returns nil. 39 | // If edge already exist, returns error. 40 | AddEdge(from, to *Vertex[T], options ...EdgeOptionFunc) (*Edge[T], error) 41 | 42 | // GetAllEdges returns a slice of all edges connecting source vertex to 43 | // target vertex if such vertices exist in this graph. 44 | // 45 | // In directed graph, it returns a single edge. 46 | // 47 | // If any of the specified vertices is nil, returns nil. 48 | // If any of the vertices does not exist, returns nil. 49 | // If both vertices exist but no edges found, returns an empty set. 50 | GetAllEdges(from, to *Vertex[T]) []*Edge[T] 51 | 52 | // AllEdges returns all the edges in the graph. 53 | AllEdges() []*Edge[T] 54 | 55 | // GetEdge returns an edge connecting source vertex to target vertex 56 | // if such vertices and such edge exist in this graph. 57 | // 58 | // In undirected graph, returns only the edge from the "from" vertex to 59 | // the "to" vertex. 60 | // 61 | // If any of the specified vertices is nil, returns nil. 62 | // If edge does not exist, returns nil. 63 | GetEdge(from, to *Vertex[T]) *Edge[T] 64 | 65 | // EdgesOf returns a slice of all edges touching the specified vertex. 66 | // If no edges are touching the specified vertex returns an empty slice. 67 | // 68 | // If the input vertex is nil, returns nil. 69 | // If the input vertex does not exist, returns nil. 70 | EdgesOf(v *Vertex[T]) []*Edge[T] 71 | 72 | // RemoveEdges removes input edges from the graph from the specified 73 | // slice of edges, if they exist. In undirected graph, removes edges 74 | // in both directions. 75 | RemoveEdges(edges ...*Edge[T]) 76 | 77 | // AddVertexByLabel adds a new vertex with the given label to the graph. 78 | // Label of the vertex is a comparable type. This method also accepts the 79 | // vertex properties such as weight. 80 | // 81 | // If there is a vertex with the same label in the graph, returns nil. 82 | // Otherwise, returns the created vertex. 83 | AddVertexByLabel(label T, options ...VertexOptionFunc) *Vertex[T] 84 | 85 | // AddVertex adds the input vertex to the graph. It doesn't add 86 | // vertex to the graph if the input vertex label is already exists 87 | // in the graph. 88 | AddVertex(v *Vertex[T]) 89 | 90 | // GetVertexByID returns the vertex with the input label. 91 | // 92 | // If vertex doesn't exist, returns nil. 93 | GetVertexByID(label T) *Vertex[T] 94 | 95 | // GetAllVerticesByID returns a slice of vertices with the specified label list. 96 | // 97 | // If vertex doesn't exist, doesn't add nil to the output list. 98 | GetAllVerticesByID(label ...T) []*Vertex[T] 99 | 100 | // GetAllVertices returns a slice of all existing vertices in the graph. 101 | GetAllVertices() []*Vertex[T] 102 | 103 | // RemoveVertices removes all the specified vertices from this graph including 104 | // all its touching edges if present. 105 | RemoveVertices(vertices ...*Vertex[T]) 106 | 107 | // ContainsEdge returns 'true' if and only if this graph contains an edge 108 | // going from the source vertex to the target vertex. 109 | // 110 | // If any of the specified vertices does not exist in the graph, or if is nil, 111 | // returns 'false'. 112 | ContainsEdge(from, to *Vertex[T]) bool 113 | 114 | // ContainsVertex returns 'true' if this graph contains the specified vertex. 115 | // 116 | // If the specified vertex is nil, returns 'false'. 117 | ContainsVertex(v *Vertex[T]) bool 118 | 119 | // Order returns the number of vertices in the graph. 120 | Order() uint32 121 | 122 | // Size returns the number of edges in the graph 123 | Size() uint32 124 | } 125 | 126 | // New creates a new instance of base graph that implemented the Graph interface. 127 | func New[T comparable](options ...GraphOptionFunc) Graph[T] { 128 | return newBaseGraph[T](newProperties(options...)) 129 | } 130 | 131 | // Edge represents an edges in a graph. It contains start and end points. 132 | type Edge[T comparable] struct { 133 | source *Vertex[T] // start point of the edges 134 | dest *Vertex[T] // destination or end point of the edges 135 | properties EdgeProperties 136 | metadata any // optional metadata associated with the edge 137 | } 138 | 139 | func NewEdge[T comparable](source *Vertex[T], dest *Vertex[T], options ...EdgeOptionFunc) *Edge[T] { 140 | var properties EdgeProperties 141 | for _, option := range options { 142 | option(&properties) 143 | } 144 | 145 | return &Edge[T]{ 146 | source: source, 147 | dest: dest, 148 | properties: properties, 149 | } 150 | } 151 | 152 | // Weight returns the weight of the edge. 153 | func (e *Edge[T]) Weight() float64 { 154 | return e.properties.weight 155 | } 156 | 157 | // OtherVertex accepts the label of one the vertices of the edge 158 | // and returns the other one. If the input label doesn't match to 159 | // either of the vertices, returns nil. 160 | func (e *Edge[T]) OtherVertex(v T) *Vertex[T] { 161 | if e.source.label == v { 162 | return e.dest 163 | } 164 | 165 | if e.dest.label == v { 166 | return e.source 167 | } 168 | 169 | return nil 170 | } 171 | 172 | // Source returns edge source vertex 173 | func (e *Edge[T]) Source() *Vertex[T] { 174 | return e.source 175 | } 176 | 177 | // Destination returns edge dest vertex 178 | func (e *Edge[T]) Destination() *Vertex[T] { 179 | return e.dest 180 | } 181 | 182 | // Metadata returns the metadata associated with the edge. 183 | func (e Edge[T]) Metadata() any { 184 | return e.metadata 185 | } 186 | 187 | // Vertex represents a node or point in a graph 188 | type Vertex[T comparable] struct { 189 | label T // uniquely identifies each vertex 190 | neighbors []*Vertex[T] // stores pointers to its neighbors 191 | inDegree int // number of incoming edges to this vertex 192 | properties VertexProperties 193 | metadata any // optional metadata associated with the vertex 194 | } 195 | 196 | func NewVertex[T comparable](label T, options ...VertexOptionFunc) *Vertex[T] { 197 | return &Vertex[T]{label: label} 198 | } 199 | 200 | // NeighborByLabel iterates over the neighbor slice and returns the 201 | // vertex which its label is equal to the input label. 202 | // 203 | // It returns nil if there is no neighbor with that label. 204 | func (v *Vertex[T]) NeighborByLabel(label T) *Vertex[T] { 205 | for i := range v.neighbors { 206 | if v.neighbors[i].label == label { 207 | return v.neighbors[i] 208 | } 209 | } 210 | 211 | return nil 212 | } 213 | 214 | // HasNeighbor checks if the input vertex is the neighbor of the 215 | // current node or not. It returns 'true' if it finds the input 216 | // in the neighbors. Otherwise, returns 'false'. 217 | func (v *Vertex[T]) HasNeighbor(vertex *Vertex[T]) bool { 218 | return v.NeighborByLabel(vertex.label) != nil 219 | } 220 | 221 | // InDegree returns the number of incoming edges to the current vertex. 222 | func (v *Vertex[T]) InDegree() int { 223 | return v.inDegree 224 | } 225 | 226 | // OutDegree returns the number of outgoing edges to the current vertex. 227 | func (v *Vertex[T]) OutDegree() int { 228 | return len(v.neighbors) 229 | } 230 | 231 | // Degree returns the total degree of the vertex which is the sum of 232 | // in and out degrees. 233 | func (v *Vertex[T]) Degree() int { 234 | return v.inDegree + v.OutDegree() 235 | } 236 | 237 | // Neighbors returns a copy of neighbor slice. If the caller changed the 238 | // result slice, it won't impact the graph or the vertex. 239 | func (v *Vertex[T]) Neighbors() []*Vertex[T] { 240 | var neighbors []*Vertex[T] 241 | for i := range v.neighbors { 242 | clone := &Vertex[T]{} 243 | *clone = *v.neighbors[i] 244 | neighbors = append(neighbors, clone) 245 | } 246 | 247 | return neighbors 248 | } 249 | 250 | // Label returns vertex label. 251 | func (v *Vertex[T]) Label() T { 252 | return v.label 253 | } 254 | 255 | // Weight returns vertex label. 256 | func (v *Vertex[T]) Weight() float64 { 257 | return v.properties.weight 258 | } 259 | 260 | // Metadata returns the metadata associated with the vertex. 261 | func (v *Vertex[T]) Metadata() any { 262 | return v.metadata 263 | } 264 | -------------------------------------------------------------------------------- /connectivity/README.md: -------------------------------------------------------------------------------- 1 | # gograph - Connectivity 2 | 3 | ### Why Strong Connectivity Is Important? 4 | 5 | The concept of strong connectivity in a graph is important for a variety of reasons. Here are some of the 6 | main reasons why strong connectivity is important: 7 | 8 | - **Robustness:** A graph that is strongly connected is more robust and resilient than a graph that is not 9 | strongly connected. This is because in a strongly connected graph, every vertex can be reached from every 10 | other vertex, which means that even if some vertices or edges are removed, the graph remains connected. 11 | - **Communication:** In many applications, such as network routing and communication networks, it is important 12 | to be able to communicate between any pair of vertices in the graph. A strongly connected graph ensures that 13 | there is a path between any two vertices, which makes communication between them possible. 14 | - **Analysis:** Strong connectivity is often used as a tool for analyzing the structure of a graph. For example, 15 | identifying strongly connected components in a graph can reveal important patterns and relationships between vertices. 16 | - **Optimization:** In some applications, it may be necessary to find the shortest path or the minimum spanning 17 | tree that connects all vertices in the graph. Strong connectivity can help to optimize such algorithms by 18 | reducing the search space and ensuring that all vertices are reachable. 19 | - **Design of algorithms:** Strong connectivity is also an important concept in the design of algorithms for 20 | various graph problems. For example, many graph algorithms rely on identifying strongly connected components 21 | or paths in the graph to find optimal solutions. 22 | - **Network reliability:** In network reliability analysis, strong connectivity is used to determine the 23 | probability that a network will remain connected in the event of failures or disruptions. This is important 24 | in designing robust networks for critical applications such as transportation, energy distribution, and 25 | telecommunications. 26 | - **Social network analysis:** Strong connectivity is also important in social network analysis, where it is 27 | used to identify closely-knit groups or communities within a larger network. This can provide insights into 28 | social structures and relationships that are not immediately apparent from the graph topology. 29 | - **Testing and verification:** Strong connectivity is an important concept in testing and verification of digital 30 | circuits, where it is used to ensure that all nodes in the circuit can be reached from all other nodes. 31 | This is important for ensuring correct functionality and avoiding problems such as deadlocks or race conditions. 32 | - **Transportation and logistics:** Strong connectivity is also important in transportation and logistics, 33 | where it is used to optimize routing and scheduling of vehicles or goods. In a strongly connected graph, 34 | it is possible to find the shortest path or the minimum time required to transport goods between any two locations. 35 | - **Graph drawing and visualization:** Strong connectivity is also an important consideration in graph drawing and 36 | visualization. Many graph layout algorithms aim to minimize edge crossings while maintaining strong connectivity, 37 | which can improve the readability and aesthetics of the graph. 38 | 39 | Strong connectivity is a fundamental concept in graph theory that is used to describe the property of a 40 | directed graph where every vertex is reachable from every other vertex through a directed path. Strong 41 | connectivity has a wide range of practical applications, such as designing robust networks, optimizing 42 | transportation routes, analyzing social structures, and testing digital circuits. 43 | 44 | Several algorithms, such as Tarjan's Algorithm, Kosaraju's Algorithm, Path-Based Strong Component Algorithm, 45 | and Gabow's Algorithm, are available to determine the strong connectivity of a graph, and the choice of 46 | algorithm depends on the specific characteristics of the graph and the requirements of the application. 47 | 48 | Understanding strong connectivity and its implications is essential for anyone working with graphs or 49 | network data. Strongly connected graphs are more robust and resilient, enable communication between 50 | any pair of vertices, and provide insights into social structures and relationships. Additionally, 51 | strong connectivity is important in optimizing algorithms, network reliability, transportation and 52 | logistics, graph drawing, and verification of digital circuits. 53 | 54 | ### Tarjan's Algorithm 55 | 56 | Tarjan's algorithm is a popular algorithm in graph theory used to find strongly connected components 57 | in a directed graph. The algorithm is named after its inventor, Robert Tarjan. The algorithm is based 58 | on depth-first search (DFS) and is very efficient in both time and space complexity. 59 | 60 | The main usage of the Tarjan algorithm is to find strongly connected components in a directed graph. 61 | Strongly connected components are used in many applications, such as finding the shortest path between 62 | two nodes in a graph, identifying the critical paths in a project schedule, and solving problems related 63 | to synchronization in computer science. 64 | 65 | The time complexity of Tarjan's algorithm is O(V + E), where V is the number of vertices in the graph 66 | and E is the number of edges in the graph. The space complexity of the algorithm is O(V), where V is 67 | the number of vertices in the graph. 68 | 69 | To use Tarjan algorithm, you can call the 'Tarjan[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]' function 70 | and path your graph to it: 71 | 72 | ```go 73 | import ( 74 | "github.com/hmdsefi/gograph" 75 | "github.com/hmdsefi/gograph/connectivity" 76 | ) 77 | 78 | func main() { 79 | g := gograph.New[int](gograph.Directed()) 80 | 81 | v1 := g.AddVertexByLabel(1) 82 | v2 := g.AddVertexByLabel(2) 83 | v3 := g.AddVertexByLabel(3) 84 | v4 := g.AddVertexByLabel(4) 85 | v5 := g.AddVertexByLabel(5) 86 | 87 | g.AddEdge(v1, v2) 88 | g.AddEdge(v2, v3) 89 | g.AddEdge(v3, v1) 90 | g.AddEdge(v3, v4) 91 | g.AddEdge(v4, v5) 92 | g.AddEdge(v5, v4) 93 | 94 | sccs := connectivity.Tarjan(g) 95 | } 96 | ``` 97 | 98 | It returns a slice of strongly connected component. 99 | 100 | ### Kosaraju's Algorithm 101 | 102 | Kosaraju's algorithm is another popular algorithm in graph theory used to find strongly connected 103 | components in a directed graph. The algorithm is named after its inventor, Sharadha Sharma Kosaraju. 104 | The algorithm is also based on depth-first search (DFS), but it performs two DFS passes over the graph. 105 | 106 | The main usage of Kosaraju's algorithm is to find strongly connected components in a directed graph. 107 | Strongly connected components are used in many applications, such as finding the shortest path between 108 | two nodes in a graph, identifying the critical paths in a project schedule, and solving problems related 109 | to synchronization in computer science. 110 | 111 | The time complexity of Kosaraju's algorithm is O(V + E), where V is the number of vertices in the graph 112 | and E is the number of edges in the graph. The space complexity of the algorithm is O(V), where V is 113 | the number of vertices in the graph. 114 | 115 | To use Kosaraju algorithm, you can call the 'Kosaraju[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]' function 116 | and path your graph to it: 117 | 118 | ```go 119 | import ( 120 | "github.com/hmdsefi/gograph" 121 | "github.com/hmdsefi/gograph/connectivity" 122 | ) 123 | 124 | func main() { 125 | g := gograph.New[int](gograph.Directed()) 126 | 127 | v1 := g.AddVertexByLabel(1) 128 | v2 := g.AddVertexByLabel(2) 129 | v3 := g.AddVertexByLabel(3) 130 | v4 := g.AddVertexByLabel(4) 131 | v5 := g.AddVertexByLabel(5) 132 | 133 | g.AddEdge(v1, v2) 134 | g.AddEdge(v2, v3) 135 | g.AddEdge(v3, v1) 136 | g.AddEdge(v3, v4) 137 | g.AddEdge(v4, v5) 138 | g.AddEdge(v5, v4) 139 | 140 | sccs := connectivity.Kosaraju(g) 141 | } 142 | ``` 143 | 144 | It returns a slice of strongly connected component. 145 | 146 | ### Gabow's Algorithm 147 | 148 | Gabow's algorithm is another algorithm used to find strongly connected components (SCCs) in a directed graph. 149 | The algorithm was invented by Harold N. Gabow in 1985 and is based on a combination of breadth-first search (BFS) 150 | and depth-first search (DFS). 151 | 152 | The main usage of Gabow's algorithm is to find strongly connected components in a directed graph. It can be used 153 | in many applications, such as detecting cycles in a graph, solving problems related to synchronization, 154 | and analyzing network traffic. 155 | 156 | The time complexity of Gabow's algorithm is O(V + E), where V is the number of vertices in the graph and E 157 | is the number of edges in the graph. The space complexity of the algorithm is O(V), where V is the number 158 | of vertices in the graph. 159 | 160 | To use Gabow algorithm, you can call the 'Gabow[T comparable](g gograph.Graph[T]) [][]*gograph.Vertex[T]' function 161 | and path your graph to it: 162 | 163 | ```go 164 | import ( 165 | "github.com/hmdsefi/gograph" 166 | "github.com/hmdsefi/gograph/connectivity" 167 | ) 168 | 169 | func main() { 170 | g := gograph.New[int](gograph.Directed()) 171 | 172 | v1 := g.AddVertexByLabel(1) 173 | v2 := g.AddVertexByLabel(2) 174 | v3 := g.AddVertexByLabel(3) 175 | v4 := g.AddVertexByLabel(4) 176 | v5 := g.AddVertexByLabel(5) 177 | 178 | g.AddEdge(v1, v2) 179 | g.AddEdge(v2, v3) 180 | g.AddEdge(v3, v1) 181 | g.AddEdge(v3, v4) 182 | g.AddEdge(v4, v5) 183 | g.AddEdge(v5, v4) 184 | 185 | sccs := connectivity.Gabow(g) 186 | } 187 | ``` 188 | 189 | It returns a slice of strongly connected component. 190 | -------------------------------------------------------------------------------- /traverse/README.md: -------------------------------------------------------------------------------- 1 | # gograph - Traverse 2 | 3 | Graph traversal is an important operation in computer science that involves 4 | visiting each node in a graph at least once. There are several graph traversal 5 | algorithms that are commonly used to explore graphs in different ways. 6 | The `gograph/traverse` package is a Go library that provides efficient implementation 7 | of five of these algorithms: 8 | 9 | * [BFS](#BFS) 10 | * [DFS](#DFS) 11 | * [Topological Sort](#Topological-Sort) 12 | * [Closest First](#Closest-First) 13 | * [Random Walk](#Random-Walk) 14 | 15 | All the traversal algorithms in the 'traverse' package are implemented the following 16 | iterator interface: 17 | 18 | ```go 19 | // Iterator represents a general purpose iterator for iterating over 20 | // a sequence of graph's vertices. It provides methods for checking if 21 | // there are more elements to be iterated over, getting the next element, 22 | // iterating over all elements using a callback function, and resetting 23 | // the iterator to its initial state. 24 | type Iterator[T comparable] interface { 25 | // HasNext returns a boolean value indicating whether there are more 26 | // elements to be iterated over. It returns true if there are more 27 | // elements. Otherwise, returns false. 28 | HasNext() bool 29 | 30 | // Next returns the next element in the sequence being iterated over. 31 | // If there are no more elements, it returns nil. It also advances 32 | // the iterator to the next element. 33 | Next() *gograph.Vertex[T] 34 | 35 | // Iterate iterates over all elements in the sequence and calls the 36 | // provided callback function on each element. The callback function 37 | // takes a single argument of type *Vertex, representing the current 38 | // element being iterated over. It returns an error value, which is 39 | // returned by the Iterate method. If the callback function returns 40 | // an error, iteration is stopped and the error is returned. 41 | Iterate(func(v *gograph.Vertex[T]) error) error 42 | 43 | // Reset resets the iterator to its initial state, allowing the 44 | // sequence to be iterated over again from the beginning. 45 | Reset() 46 | } 47 | ``` 48 | 49 | ## BFS 50 | 51 | BFS iterator is a technique used to implement the Breadth-First Search (BFS) 52 | algorithm for traversing a graph or tree in a systematic way. The BFS iterator 53 | iteratively visits all the vertices of the graph, starting from a given source 54 | vertex and moving outwards in levels. The iterator maintains a queue of vertices 55 | to be visited, and it visits each vertex only once. 56 | 57 | One of the most common usages of BFS iterator is to find the shortest path between 58 | two vertices in an unweighted graph. It can also be used to perform level-order 59 | traversal of a binary tree or to find all the vertices at a given distance from a 60 | starting vertex. BFS iterator is also commonly used in graph algorithms, such as 61 | finding connected components, bipartite graphs, and cycle detection. 62 | 63 | The time complexity of the BFS iterator algorithm is O(V + E), where V is the 64 | number of vertices and E is the number of edges in the graph. This is because 65 | the algorithm visits each vertex and edge exactly once. The space complexity of 66 | BFS iterator is O(V), where V is the number of vertices in the graph. This is 67 | because the algorithm uses a queue to store the vertices to be visited. The 68 | maximum size of the queue is equal to the number of vertices at the maximum 69 | depth of the BFS traversal. 70 | 71 | Here you can see how BFS iterator works: 72 | golang generic graph package - BFS traversal 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 | golang generic graph package - DFS traversal 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 | golang generic graph package - Topological ordering traversal 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 | golang generic graph package - Closest-First traversal 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 | --------------------------------------------------------------------------------