17

Can anyone provide either code, pseudocode, or even provide good links to implementing DFS (Depth-first search) and BFS (breadth-first search) in plain JavaScript (No JQuery, or any helper libraries)? I've been trying to understand how to implement either traversal, but I can't seem to really distinguish the difference of a BFS and DFS implementation.

If we want a concrete problem as an example: I want to traverse down the DOM at a given node, and get all the class names.

(The only way I can think to traverse is to go through each parent node, get what I need from that node which in this example is the class name, then see if they have children, recurse for each child. I believe this is DFS? Again, I'm having a hard time understanding the differences in a DOM traversal implementation!)

Finally, sorry if this is a repeat. I've searched everywhere for good, clear examples but haven't found any great answers! If there's already a good answer out there, please let me know :)

gfullam
  • 11,531
  • 5
  • 50
  • 64
pm3
  • 171
  • 1
  • 7
  • did you try to understand dom traversal from http://www.w3schools.com/js/js_htmldom_navigation.asp – Amit Kumar Sep 15 '16 at 05:51
  • you probably didn't find anything on SO because questions like this are usually closed – Jaromanda X Sep 15 '16 at 05:55
  • 2
    @AmitKumar—please do not reference w3schools, it is full of errors. Far better to reference MDN. – RobG Sep 15 '16 at 06:02
  • I can't see that depth–first or breadth–first are necessarily recursive or linear. It would help if you explain what you **think** the difference is in regard to DOM traversal. – RobG Sep 15 '16 at 06:05
  • Do you know the [TreeWalker API](https://developer.mozilla.org/en/docs/Web/API/TreeWalker), no need to write it yourself, you won't get better results than what's already there. – Kaiido Sep 15 '16 at 06:41
  • @Kaiido I hear ya on using TreeWalker API, but I'm mainly asking this question for front-end type of interviews I've been getting where I can't use APIs or anything extra – pm3 Sep 16 '16 at 00:29
  • @pm3, TreeWalker is a Web API, available in every HTML5 browsers. It can't be thought as "*an extra*". It's just the way to do it nowadays. `getElementById` is also part of an API, the Document one. I'd be really astonished if your interviewer required you not to use it. – Kaiido Sep 16 '16 at 00:40
  • @Kaiido While he didn't necessarily specify not to use it he did mention he wanted to see how I'd approach a traversal down the DOM with JS. Just going based on the question/interview, he sounded like he wanted to see answers similar to the ones below – pm3 Sep 16 '16 at 00:50
  • Probably because he doesn't know the best way to do it. Show him, explain that since it's only machine language, then it's faster than any js loop, and you'll have make him learn something and probably made a few points to get the job? – Kaiido Sep 16 '16 at 00:53

3 Answers3

15

Let's use the following HTML code as an example:

<div class="a">
    <div class="aa">
        <span class="aaa">
        </span>
        <span class="aab">
        </span>
    </div>
    <div class="ab">
        <span class="aba">
        </span>
        <span class="abb">
        </span>
    </div>
</div>

DFS will always go to the next level of nodes first, and only if there are no more un-traversed child nodes will it step to a next node on the current level.

A DFS would traverse the nodes of the example in the following order:

a, aa, aaa, aab, ab, aba, abb

BFS will always traverse all the nodes in the current level first and only after that will it go to the next level of nodes.

A BFS would traverse the nodes of the example in the following order:

a, aa, ab, aaa, aab, aba, abb

There isn't a definite answer which one of these you should use. Usually it depends on your needs.

Implementation details:

For a DFS people often use a stack.

Pseudo code:

stack my_stack;
list visited_nodes;
my_stack.push(starting_node);

while my_stack.length > 0
   current_node = my_stack.pop();

   if current_node == null
       continue;
   if current_node in visited_nodes
      continue;
   visited_nodes.add(current_node);

   // visit node, get the class or whatever you need

   foreach child in current_node.children
      my_stack.push(child);

This code will go until there is any nodes in the stack. In each step we get the top node in the stack and if it's not null and if we haven't visited it before than we visit it and add all its children to the stack.

Queue is usually used for the BFS.

Pseudo code:

queue my_queue;
list visited_nodes;
my_queue.enqueue(starting_node);

while my_queue.length > 0
   current_node = my_queue.dequeue();

   if current_node == null
       continue;
   if current_node in visited_nodes
      continue;
   visited_nodes.add(current_node);

   // visit node, get the class or whatever you need

   foreach child in current_node.children
      my_queue.enqueue(child);

This code will go until there is any nodes in the queue. In each step we get the first node in the queue and if it's not null and if we haven't visited it before than we visit it and add all its children to the queue.

Note that the main difference between the two algorithm is the data type we use.

aug
  • 11,138
  • 9
  • 72
  • 93
Robert F.
  • 455
  • 3
  • 14
  • Conveniently avoiding all the text nodes… ;-) – RobG Sep 15 '16 at 06:06
  • 1
    The two pseudo code is just generally about the algorithms. Of course you have to think how you define "children" in your current context. If you only need the class names you don't even need the text nodes though. – Robert F. Sep 15 '16 at 06:08
  • They are important because there's no point to adding a node that has no child nodes to the stack (other types of node don't have children either but text nodes make it obvious). – RobG Sep 15 '16 at 06:13
  • 1
    Of course there is. The pseudo code I provided visits the node when it pops it. So if you don't add a node to the stack because it has no child nodes means you won't visit any nodes that has no child nodes. That's not the general algorithm and I don't see the point of it anyway. We can argue what is the most optimal implementation of a BFS and where should we put our domain specific checks, but I don't see that's the point of this question. You are welcome to provide a pseudo code where you determine if a node should be visited some other place though. – Robert F. Sep 15 '16 at 06:17
  • @RobertF. awesome answer on explaining how the DOM would look with each traversal and walking through the difference between both! – pm3 Sep 16 '16 at 00:53
  • @Robert F. JavaScript implementation - https://jsfiddle.net/_alexander_/xt3rz9h7/ – Oleksandr T. Aug 21 '18 at 09:26
10

DFS:

function m(elem) {
    elem.childNodes.forEach(function(a) {
        m(a);
    });
    //do sth with elem
}
m(document.body);

This loops through all elements, and for each element through each child's and so on.

BFS:

var childs = [];

function m(elem) {
    elem.childNodes.forEach(function(a) {
        childs.push(a);
    });
    b = childs;
    childs = [];
    b.forEach(function(a) {
        m(a);
    });
}
m(document.body);

This loops through the elements, pushes their children onto a stack, and starts again with each of them. As you can see, this consumes much more space (the child's array) which is not the best...

Lexis Hanson
  • 777
  • 5
  • 8
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
6

For DFS you can use the TreeWalker or NodeIterator API and filter with NodeFilter.SHOW_ELEMENT.

the8472
  • 40,999
  • 5
  • 70
  • 122