Skip to content

Commit b80189d

Browse files
Merge pull request #545 from Shubham-Khetan-2005/feat/DFSAlgo
DFS Algorithm in Go
2 parents 136493e + 5f81f24 commit b80189d

1 file changed

Lines changed: 211 additions & 0 deletions

File tree

go/graph/DFSTraversal.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// DFSTraversal.go
2+
//
3+
// Depth-First Search (DFS) Traversal - Numeric Node IDs
4+
//
5+
// Description:
6+
// DFS is a graph traversal algorithm that explores as far as possible
7+
// along each branch before backtracking. This implementation uses recursion
8+
// and returns nodes in preorder (the order they are first discovered).
9+
//
10+
// Purpose / Use cases:
11+
// - Enumerate nodes by depth-first order.
12+
// - Detect cycles, compute connected components, topological sorts (with mods).
13+
// - Useful in search problems, path finding (not shortest by edges), etc.
14+
//
15+
// Approach / Methodology:
16+
// - Represent an undirected graph with an adjacency list map[int][]int.
17+
// - Maintain a visited set and recursively visit neighbors in insertion order.
18+
// - The order nodes are appended when first visited is the DFS visit order.
19+
//
20+
// Complexity Analysis:
21+
// - Time complexity: O(V + E)
22+
// - Space complexity: O(V) for visited set + recursion stack (worst-case O(V)).
23+
//
24+
// File contents:
25+
// - Graph type and methods (AddEdge, AddNode).
26+
// - DFS function that returns visit order (slice of ints) or nil if start missing.
27+
// - Simple test harness with deterministic small numeric graphs and pass/fail checks.
28+
//
29+
// Author: (your name)
30+
// Date: (optional)
31+
32+
package main
33+
34+
import (
35+
"fmt"
36+
"os"
37+
"strconv"
38+
)
39+
40+
// Graph represents an undirected graph with integer node IDs.
41+
type Graph struct {
42+
adj map[int][]int // adjacency list: node -> list of neighbors
43+
}
44+
45+
// NewGraph creates and returns an empty Graph.
46+
func NewGraph() *Graph {
47+
return &Graph{adj: make(map[int][]int)}
48+
}
49+
50+
// AddNode ensures a node entry exists in the adjacency map.
51+
// It's safe to call this before adding edges if you want isolated nodes.
52+
func (g *Graph) AddNode(id int) {
53+
if _, ok := g.adj[id]; !ok {
54+
g.adj[id] = []int{}
55+
}
56+
}
57+
58+
// AddEdge adds an undirected edge between a and b.
59+
// If nodes don't exist yet they are created automatically.
60+
// The order of AddEdge calls determines neighbor order and therefore
61+
// DFS deterministic visit order for the same insertion order.
62+
func (g *Graph) AddEdge(a, b int) {
63+
g.adj[a] = append(g.adj[a], b)
64+
g.adj[b] = append(g.adj[b], a)
65+
}
66+
67+
// DFS runs depth-first search starting from `start` (preorder).
68+
// Returns a slice with the visit order (nodes in the order first discovered).
69+
// If the start node is not present in the graph, DFS returns nil.
70+
//
71+
// Time complexity: O(V + E)
72+
// Space complexity: O(V)
73+
func (g *Graph) DFS(start int) []int {
74+
// If the start node is not present, return nil to indicate an invalid start.
75+
if _, ok := g.adj[start]; !ok {
76+
return nil
77+
}
78+
79+
visited := make(map[int]bool, len(g.adj))
80+
visitOrder := make([]int, 0, len(g.adj))
81+
82+
var dfsRec func(int)
83+
dfsRec = func(u int) {
84+
visited[u] = true
85+
visitOrder = append(visitOrder, u)
86+
for _, nb := range g.adj[u] {
87+
if !visited[nb] {
88+
dfsRec(nb)
89+
}
90+
}
91+
}
92+
93+
dfsRec(start)
94+
return visitOrder
95+
}
96+
97+
// compareSlices checks equality of two integer slices.
98+
func compareSlices(a, b []int) bool {
99+
if a == nil && b == nil {
100+
return true
101+
}
102+
if (a == nil) != (b == nil) {
103+
return false
104+
}
105+
if len(a) != len(b) {
106+
return false
107+
}
108+
for i := range a {
109+
if a[i] != b[i] {
110+
return false
111+
}
112+
}
113+
return true
114+
}
115+
116+
// expect checks the DFS output against expected result and prints pass/fail.
117+
func expect(got, expected []int, testName string) {
118+
// Always print the DFS order to make results explicit.
119+
fmt.Printf("%s - DFS order: %v\n", testName, got)
120+
if compareSlices(got, expected) {
121+
fmt.Printf("[PASS] %s\n\n", testName)
122+
} else {
123+
fmt.Printf("[FAIL] %s\n", testName)
124+
fmt.Printf(" Got : %v\n", got)
125+
fmt.Printf(" Expected: %v\n\n", expected)
126+
}
127+
}
128+
129+
// runTests builds small numeric graphs and runs compact, deterministic tests.
130+
func runTests() {
131+
fmt.Println("DFS Traversal Tests (numeric nodes)\n")
132+
133+
// Graph G:
134+
// 1 - 2 - 4
135+
// |
136+
// 3 - 5 - 6
137+
//
138+
// (Edges are added in order to make DFS deterministic for the tests)
139+
g := NewGraph()
140+
g.AddEdge(1, 2) // neighbors: 1:[2], 2:[1]
141+
g.AddEdge(1, 3) // 1:[2,3], 3:[1]
142+
g.AddEdge(2, 4) // 2:[1,4], 4:[2]
143+
g.AddEdge(3, 5) // 3:[1,5], 5:[3]
144+
g.AddEdge(5, 6) // 5:[3,6], 6:[5]
145+
// Add isolated node 7 (no edges)
146+
g.AddNode(7)
147+
148+
// Test 1: start = 1 (connected component)
149+
// Expected DFS preorder:
150+
// 1,2,4,3,5,6
151+
expected1 := []int{1, 2, 4, 3, 5, 6}
152+
got1 := g.DFS(1)
153+
expect(got1, expected1, "Test 1: start=1 (connected)")
154+
155+
// Test 2: start = 3 (middle node)
156+
// Expected order: 3,1,2,4,5,6
157+
expected2 := []int{3, 1, 2, 4, 5, 6}
158+
got2 := g.DFS(3)
159+
expect(got2, expected2, "Test 2: start=3 (middle node)")
160+
161+
// Test 3: isolated node start = 7
162+
// Node 7 exists but is isolated => visit order [7]
163+
expected3 := []int{7}
164+
got3 := g.DFS(7)
165+
expect(got3, expected3, "Test 3: start=7 (isolated node)")
166+
167+
// Test 4: start missing => nil returned
168+
got4 := g.DFS(99)
169+
expect(got4, nil, "Test 4: start missing (99) => nil")
170+
171+
// Test 5: empty graph => start not present => nil
172+
empty := NewGraph()
173+
got5 := empty.DFS(1)
174+
expect(got5, nil, "Test 5: empty graph => start not found")
175+
176+
fmt.Println("Tests completed.")
177+
}
178+
179+
// main: runs tests by default. If first CLI argument is present and is an integer,
180+
// builds the sample graph and runs DFS starting from that node, printing only the visit order.
181+
func main() {
182+
// If CLI arg provided, run DFS interactively on the sample graph and print the visit order.
183+
if len(os.Args) > 1 {
184+
startStr := os.Args[1]
185+
start, err := strconv.Atoi(startStr)
186+
if err != nil {
187+
fmt.Printf("Invalid start node: %q. Provide integer node id.\n", startStr)
188+
return
189+
}
190+
// Build the same small sample graph used in tests:
191+
g := NewGraph()
192+
g.AddEdge(1, 2)
193+
g.AddEdge(1, 3)
194+
g.AddEdge(2, 4)
195+
g.AddEdge(3, 5)
196+
g.AddEdge(5, 6)
197+
g.AddNode(7)
198+
199+
order := g.DFS(start)
200+
if order == nil {
201+
fmt.Printf("Start node %d not found in graph\n", start)
202+
return
203+
}
204+
// Print the DFS visit order explicitly for interactive runs.
205+
fmt.Printf("DFS visit order from %d: %v\n", start, order)
206+
return
207+
}
208+
209+
// No CLI arg: run the compact test suite.
210+
runTests()
211+
}

0 commit comments

Comments
 (0)