5

I've been doing a tutorial in OCaml. The tutorial taught me how to do a breadth-first search on a graph, which I can implement fine. However, I've been struggling with an algorithm for a depth-first search, which is one of those things where the tutorial goes, "We suggest you try this on a depth-first method, but we won't tell you how to do it."

I'm trying to implement it like so: let rec dfs graph start end. Which is to say, I've been trying to do it where I take in a list of edges (graph), a starting node (start), and an ending node (end).

I've created my graph using a list of edges...

let edges = [
  ("a", "b"); ("a", "c");
  ("a", "d"); ("b", "e");
  ("c", "f"); ("d", "e");
  ("e", "f"); ("e", "g")
];;

However, I'm totally lost about where to go from here.

glennsl
  • 28,186
  • 12
  • 57
  • 75
jpabene
  • 129
  • 2
  • 13
  • 3
    The usual one-sentence summary is that depth-first search is the same as breadth-first search except that you replace the queue of unvisited nodes with a stack. You might try modifying your breadth-first search implementation in this way. – Jeffrey Scofield Apr 29 '16 at 01:51
  • 1
    A function to list the successors of a node may make things easier for you. – PatJ Apr 29 '16 at 02:17

1 Answers1

4

So, I did it but I find it strange that you represent a graph as a list if you have to search in it. A map would be much better. Anyway, here's how it's done :

let edges = [
  ("a", "b"); ("a", "c");
  ("a", "d"); ("b", "e");
  ("c", "f"); ("d", "e");
  ("e", "f"); ("e", "g")
];;

let successors n e = 
  List.map (fun (_, v) -> v) (List.filter (fun (u, _) -> n = u) e)

let dfs graph start f =
  let rec rdfs visited node =
    if not (List.mem node visited) then
      begin
        f node;
        let s = successors node graph in
        List.fold_left rdfs (node::visited) s
      end
    else visited
  in rdfs [] start

 let () = let _ = dfs edges "a" print_string in ()

First thing, define a successors functions which will give you every direct successor of a node (the List.filter part gets you the edges, the List.map part split the resulting edges in two parts and keep only the second one (the first one is naturally the node for which you are searching the successors).

The dfs function defines an internal function that do two things, checking if the node we are working on was already visited or not.

  • If yes, nothing changes, we just return the same list of visited nodes (and maybe some other things depending on what you want to do with your search).

  • If no, then

  • We apply the function we gave to dfs to the current node,

  • We add this node to the visited nodes,

  • We take his successors,

  • We apply the function to each one, (here, there's a small trick, since we have

    List.fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a

    and

    rdfs : string list -> string -> string list

    instead of writing

    List.fold_left (fun visited node -> rdfs visited node) ...

    we can write

    List.fold_left rdfs ...

    (something to do with this strange thing called Lambda Calculus and some rule called eta conversion which states that :

    λ x · ux η u (x ∉ fv(u)) (fv(u) being the free variables of u) :-p) (what it means is that in OCaml, if you write fun x -> f x you could have written f instead, it is strictly equivalent.)

  • We return the visited nodes list in which all the visited nodes have been added

Hope I helped.

Lhooq
  • 4,281
  • 1
  • 18
  • 37
  • lambda calculus pros: ??, cons: readability..what are the pros? – Geoff Langenderfer May 02 '21 at 00:18
  • 1
    The pros are that you have an unified language to talk about the theoretical foundations of your language. It's not that hard to read if you consider it as a logical language and it doesn't have the limitations of real programming languages – Lhooq May 03 '21 at 07:35