ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

Immutable Graph Traversal (DFS)

Haskell

Goal -- WPM

Ready
Exercise Algorithm Area
1module ImmutableGraphDFS where
2
3import qualified Data.Map as Map
4import qualified Data.Set as Set
5
6-- | Represents an immutable graph using an adjacency map.
7type Graph = Map.Map Int [Int]
8
9-- | Performs Depth First Search (DFS) starting from a given node.
10-- Returns a list of visited nodes in DFS order.
11dfs :: Graph -> Int -> Set.Set Int -> [Int]
12dfs graph startNode visited
13| Set.member startNode visited = [] -- Already visited, stop this path
14| otherwise =
15let newVisited = Set.insert startNode visited
16neighbors = Map.findWithDefault [] startNode graph
17-- Recursively visit unvisited neighbors
18unvisitedNeighbors = filter (
19\neighbor -> not (Set.member neighbor newVisited)
20) neighbors
21-- Collect results from recursive calls
22resultsFromNeighbors = concatMap (
23\neighbor -> dfs graph neighbor newVisited
24) unvisitedNeighbors
25in startNode : resultsFromNeighbors
26
27-- | Traverses all connected components of the graph using DFS.
28-- Returns a list of all visited nodes.
29fullDfs :: Graph -> [Int]
30fullDfs graph =
31let nodes = Map.keys graph
32-- Helper to traverse starting from a node if not already visited
33traverseComponent :: [Int] -> Set.Set Int -> [Int]
34traverseComponent [] _ = []
35traverseComponent (n:ns) visitedSet
36| Set.member n visitedSet = traverseComponent ns visitedSet
37| otherwise =
38let componentNodes = dfs graph n visitedSet
39newVisitedSet = visitedSet `Set.union` Set.fromList componentNodes
40in componentNodes ++ traverseComponent ns newVisitedSet
41in traverseComponent nodes Set.empty
42
43-- | Main function to demonstrate DFS on an immutable graph.
44main :: IO ()
45main = do
46let graph1 :: Graph
47graph1 = Map.fromList [
48(1, [2, 3]),
49(2, [4]),
50(3, [4]),
51(4, [])
52]
53
54putStrLn "DFS on graph1 starting from node 1:"
55print $ dfs graph1 1 Set.empty
56
57putStrLn "Full DFS traversal of graph1:"
58print $ fullDfs graph1
59
60let graph2 :: Graph -- Disconnected graph
61graph2 = Map.fromList [
62(1, [2]),
63(2, []),
64(3, [4]),
65(4, [])
66]
67
68putStrLn "Full DFS traversal of graph2:"
69print $ fullDfs graph2
70
71let emptyGraph :: Graph
72emptyGraph = Map.empty
73
74putStrLn "Full DFS traversal of empty graph:"
75print $ fullDfs emptyGraph
Algorithm description viewbox

Immutable Graph Traversal (DFS)

Algorithm description:

This Haskell code implements Depth First Search (DFS) for an immutable graph represented by an adjacency map. The `dfs` function recursively explores nodes, keeping track of visited nodes using a `Set` to avoid cycles and redundant work. The `fullDfs` function ensures that all connected components of the graph are traversed. This algorithm is fundamental for tasks like finding connected components, cycle detection, and topological sorting in graph-based problems.

Algorithm explanation:

The `dfs` function takes the graph, a starting node, and a set of already visited nodes. If the `startNode` is already in `visited`, it returns an empty list, terminating that path. Otherwise, it adds `startNode` to `visited`, retrieves its neighbors, filters out already visited neighbors, and then recursively calls `dfs` on each unvisited neighbor. The results from these recursive calls are concatenated, and the `startNode` is prepended to form the final list of visited nodes for this path. The `fullDfs` function iterates through all nodes in the graph. For each node, if it hasn't been visited yet, it initiates a `dfs` traversal from that node, effectively exploring a new connected component. The `visited` set is updated globally across component traversals. The time complexity for DFS is O(V + E), where V is the number of vertices and E is the number of edges, as each vertex and edge is visited at most once. Space complexity is O(V) for the recursion stack and the `visited` set.

Pseudocode:

define Graph as a map from node ID to a list of adjacent node IDs

function dfs(graph, startNode, visited_set):
  if startNode is in visited_set:
    return empty list
  else:
    add startNode to visited_set
    get neighbors of startNode from graph
    initialize results_list as empty
    for each neighbor in neighbors:
      if neighbor is not in visited_set:
        recursively call dfs(graph, neighbor, visited_set)
        append the returned list to results_list
    return list containing startNode followed by results_list

function fullDfs(graph):
  get all node IDs from graph
  initialize visited_set as empty
  initialize all_visited_nodes as empty list
  for each node_id in all node IDs:
    if node_id is not in visited_set:
      component_nodes = dfs(graph, node_id, visited_set)
      add component_nodes to all_visited_nodes
      update visited_set with nodes from component_nodes
  return all_visited_nodes

main:
  define graph1
  call dfs on graph1 starting from node 1
  call fullDfs on graph1
  define graph2 (disconnected)
  call fullDfs on graph2
  define emptyGraph
  call fullDfs on emptyGraph