-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add HasCycle algorithm * feat: add FindAllCycles algorithm * docs: add comments to findAllCycles and HasCycle algorithm * test: add test for hasCycle Algorithm * test: add test for FindAllCycles Algorithm * hide imp detail for pkg out side graph/cycle.go Co-authored-by: Taj <[email protected]> * hide imp detail for pkg out side graph/cycle.go Co-authored-by: Taj <[email protected]> * feat: change FindAllcycles return type to graph * test: update findAllcycles tests * fix: fix HasCycle docs dictation --------- Co-authored-by: user <[email protected]> Co-authored-by: Taj <[email protected]>
- Loading branch information
1 parent
e33cfa9
commit 75c4951
Showing
2 changed files
with
163 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// cycle.go | ||
// this file handle algorithm that related to cycle in graph | ||
// reference: https://en.wikipedia.org/wiki/Cycle_(graph_theory) | ||
// [kiarash hajian](https://github.com/kiarash8112) | ||
|
||
package graph | ||
|
||
func (g *Graph) HasCycle() bool { | ||
//this implimetation referred as 3-color too | ||
all := map[int]struct{}{} | ||
visiting := map[int]struct{}{} | ||
visited := map[int]struct{}{} | ||
|
||
for v := range g.edges { | ||
all[v] = struct{}{} | ||
} | ||
|
||
for current := range all { | ||
if g.hasCycleHelper(current, all, visiting, visited) { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
|
||
} | ||
|
||
func (g Graph) hasCycleHelper(v int, all, visiting, visited map[int]struct{}) bool { | ||
delete(all, v) | ||
visiting[v] = struct{}{} | ||
|
||
neighbors := g.edges[v] | ||
for v := range neighbors { | ||
if _, ok := visited[v]; ok { | ||
continue | ||
} else if _, ok := visiting[v]; ok { | ||
return true | ||
} else if g.hasCycleHelper(v, all, visiting, visited) { | ||
return true | ||
} | ||
} | ||
delete(visiting, v) | ||
visited[v] = struct{}{} | ||
return false | ||
} | ||
|
||
// this function can do HasCycle() job but it is slower | ||
func (g *Graph) FindAllCycles() []Graph { | ||
all := map[int]struct{}{} | ||
visiting := map[int]struct{}{} | ||
visited := map[int]struct{}{} | ||
|
||
allCycles := []Graph{} | ||
|
||
for v := range g.edges { | ||
all[v] = struct{}{} | ||
} | ||
|
||
for current := range all { | ||
foundCycle, parents := g.findAllCyclesHelper(current, all, visiting, visited) | ||
|
||
if foundCycle { | ||
foundCycleFromCurrent := false | ||
//this loop remove additional vertex from detected cycle | ||
//using foundCycleFromCurrent bool to make sure after removing vertex we still have cycle | ||
for i := len(parents) - 1; i > 0; i-- { | ||
if parents[i][1] == parents[0][0] { | ||
parents = parents[:i+1] | ||
foundCycleFromCurrent = true | ||
} | ||
} | ||
if foundCycleFromCurrent { | ||
graph := Graph{Directed: true} | ||
for _, edges := range parents { | ||
graph.AddEdge(edges[1], edges[0]) | ||
} | ||
allCycles = append(allCycles, graph) | ||
} | ||
|
||
} | ||
|
||
} | ||
|
||
return allCycles | ||
|
||
} | ||
|
||
func (g Graph) findAllCyclesHelper(current int, all, visiting, visited map[int]struct{}) (bool, [][]int) { | ||
parents := [][]int{} | ||
|
||
delete(all, current) | ||
visiting[current] = struct{}{} | ||
|
||
neighbors := g.edges[current] | ||
for v := range neighbors { | ||
if _, ok := visited[v]; ok { | ||
continue | ||
} else if _, ok := visiting[v]; ok { | ||
parents = append(parents, []int{v, current}) | ||
return true, parents | ||
} else if ok, savedParents := g.findAllCyclesHelper(v, all, visiting, visited); ok { | ||
parents = append(parents, savedParents...) | ||
parents = append(parents, []int{v, current}) | ||
return true, parents | ||
} | ||
} | ||
delete(visiting, current) | ||
visited[current] = struct{}{} | ||
return false, parents | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package graph | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestHasCycle(t *testing.T) { | ||
graph := Graph{Directed: true} | ||
edges := [][]int{{0, 1}, {1, 2}, {2, 0}, {4, 0}} | ||
for _, edge := range edges { | ||
graph.AddEdge(edge[0], edge[1]) | ||
} | ||
if !graph.HasCycle() { | ||
t.Error("answer of hasCycle is not correct") | ||
} | ||
|
||
graph = Graph{Directed: true} | ||
edges = [][]int{{0, 1}, {1, 2}, {2, 6}, {4, 0}} | ||
for _, edge := range edges { | ||
graph.AddEdge(edge[0], edge[1]) | ||
} | ||
if graph.HasCycle() { | ||
t.Error("answer of hasCycle is not correct") | ||
} | ||
} | ||
|
||
func TestFindAllCycles(t *testing.T) { | ||
graph := Graph{Directed: true} | ||
edges := [][]int{{0, 4}, {1, 3}, {2, 3}, {3, 4}, {4, 7}, {5, 2}, {6, 3}, {7, 3}} | ||
for _, edge := range edges { | ||
graph.AddEdge(edge[0], edge[1]) | ||
} | ||
|
||
res := graph.FindAllCycles() | ||
|
||
if len(res) != 1 { | ||
t.Error("number of cycles is not correct") | ||
} | ||
|
||
firstCycle := res[0] | ||
if len(firstCycle.edges) != 3 { | ||
t.Error("number of vertex in cycle is not correct") | ||
} | ||
if _, ok := firstCycle.edges[3][4]; !ok { | ||
t.Error("connection in cycle is not correct") | ||
} | ||
if _, ok := firstCycle.edges[4][7]; !ok { | ||
t.Error("connection in cycle is not correct") | ||
} | ||
if _, ok := firstCycle.edges[7][3]; !ok { | ||
t.Error("connection in cycle is not correct") | ||
} | ||
} |