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

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

OCaml Immutable Graph: Depth-First Search for Cycle Detection

OCaml

Goal -- WPM

Ready
Exercise Algorithm Area
1(* Immutable directed graph cycle detection using DFS.
2Graph represented as an adjacency list: Map from node ID to list of neighbor IDs.
3Uses three states for nodes during DFS: Unvisited, Visiting, Visited.
4*)
5
6module Graph =
7struct
8type node_id = int
9type graph = (node_id, node_id list) Hashtbl.t
10
11(* States for DFS traversal *)
12type visit_state = Unvisited | Visiting | Visited
13
14(* Helper function to add an edge to the graph *)
15let add_edge (graph : graph) (u : node_id) (v : node_id) : unit =
16let neighbors = try Hashtbl.find graph u with Not_found -> [] in
17Hashtbl.replace graph u (v :: neighbors)
18
19(* Helper function to create a graph from edges *)
20let create_graph (edges : (node_id * node_id) list) : graph =
21let graph = Hashtbl.create 10 in
22List.iter (fun (u, v) -> add_edge graph u v) edges;
23(* Ensure all nodes exist, even if they have no outgoing edges *)
24let all_nodes = List.fold_left (fun acc (u, v) -> u :: v :: acc) [] edges in
25List.sort_uniq compare all_nodes |> List.iter (fun node -> Hashtbl.add_if_absent graph node []) ;
26graph
27
28(* Main DFS function for cycle detection *)
29let rec dfs (graph : graph) (node : node_id) (visited_states : (node_id, visit_state) Hashtbl.t) : bool =
30(* Mark current node as visiting *)
31Hashtbl.replace visited_states node Visiting;
32
33(* Explore neighbors *)
34let neighbors = try Hashtbl.find graph node with Not_found -> [] in
35List.iter (fun neighbor ->
36match Hashtbl.find_opt visited_states neighbor with
37| Some Visiting -> raise (Failure "Cycle detected") (* Back-edge to a node currently in recursion stack *)
38| Some Unvisited | None ->
39(* If neighbor is unvisited, recurse. If it's None, it's implicitly Unvisited. *)
40if dfs graph neighbor visited_states then
41raise (Failure "Cycle detected")
42| Some Visited -> () (* Neighbor already fully explored, ignore *)
43) neighbors;
44
45(* Mark current node as fully visited *)
46Hashtbl.replace visited_states node Visited;
47false (* No cycle found from this path *)
48
49(* Entry point for cycle detection *)
50let has_cycle (graph : graph) : bool =
51let visited_states = Hashtbl.create (Hashtbl.length graph) in
52(* Initialize all nodes to Unvisited *)
53Hashtbl.iter (fun node _ -> Hashtbl.add visited_states node Unvisited) graph;
54
55try
56Hashtbl.iter (fun node _ ->
57match Hashtbl.find_opt visited_states node with
58| Some Unvisited -> ignore (dfs graph node visited_states)
59| _ -> ()
60) graph;
61false (* No cycle found after checking all components *)
62with
63| Failure "Cycle detected" -> true
64| _ -> false (* Should not happen if logic is correct *)
65end
66
67(* Example usage:
68let edges = [(0, 1); (1, 2); (2, 0)] (* Creates a cycle *)
69let graph = Graph.create_graph edges
70let has_cycle = Graph.has_cycle graph
71*)
Algorithm description viewbox

OCaml Immutable Graph: Depth-First Search for Cycle Detection

Algorithm description:

This OCaml code implements a Depth-First Search (DFS) algorithm to detect cycles in a directed graph. The graph is represented immutably using an adjacency list (a Hashtbl mapping node IDs to lists of neighbor IDs). The DFS uses three states (Unvisited, Visiting, Visited) to track nodes during traversal. A cycle is detected if DFS encounters a node that is currently in the 'Visiting' state (i.e., on the current recursion stack). This is a standard algorithm for cycle detection in directed graphs, crucial for tasks like topological sorting and dependency analysis.

Algorithm explanation:

The `dfs` function performs the core traversal. It marks a node as `Visiting` upon entry. For each neighbor, it checks its state: if `Visiting`, a cycle is found. If `Unvisited` (or not yet in the `visited_states` map, implying `Unvisited`), it recursively calls `dfs`. If a neighbor is `Visited`, it's ignored. Upon returning from all neighbor explorations, the node is marked `Visited`. The `has_cycle` function iterates through all nodes, initiating DFS from any `Unvisited` node to handle disconnected components. The time complexity 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. The space complexity is O(V) for storing the `visited_states` and the recursion stack. Edge cases include empty graphs, graphs with no edges, and graphs with multiple disconnected components.

Pseudocode:

function has_cycle(graph):
  visited_states = map of node -> state (Unvisited, Visiting, Visited)
  initialize all nodes in graph to Unvisited

  for each node in graph:
    if node is Unvisited:
      if dfs(graph, node, visited_states) returns true:
        return true
  return false

function dfs(graph, current_node, visited_states):
  set visited_states[current_node] to Visiting

  for each neighbor of current_node:
    neighbor_state = visited_states[neighbor]
    if neighbor_state is Visiting:
      return true (cycle detected)
    else if neighbor_state is Unvisited:
      if dfs(graph, neighbor, visited_states) returns true:
        return true

  set visited_states[current_node] to Visited
  return false