|
| 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