[Algorithm] Write a Depth First Search Algorithm for Graphs in JavaScript

Depth first search is a graph search algorithm that starts at one node and uses recursion to travel as deeply down a path of neighboring nodes as possible, before coming back up and trying other paths.

 
const {createQueue} = require('./queue');

function createNode(key) {
    let children = [];
    return {
        key,
        children,
        addChild(child) {
            children.push(child)
        }
    }
}

function createGraph(directed = false) {
    const nodes = [];
    const edges = [];

    return {
        nodes,
        edges,
        directed,

        addNode(key) {
            nodes.push(createNode(key))
        },

        getNode (key) {
            return nodes.find(n => n.key === key)
        },

        addEdge (node1Key, node2Key) {
            const node1 = this.getNode(node1Key);
            const node2 = this.getNode(node2Key);

            node1.addChild(node2);

            if (!directed) {
                node2.addChild(node1);
            }

            edges.push(`${node1Key}${node2Key}`)
        },

        print() {
            return nodes.map(({children, key}) => {
                let result = `${key}`;

                if (children.length) {
                    result += ` => ${children.map(n => n.key).join(' ')}`
                }

                return result;
            }).join('\n')
        },
        /**
         * Breadth First Search
         */
        bfs (startNodeKey = "", visitFn = () => {}) {
            /**
             * Keytake away:
             * 1. Using Queue to get next visit node
             * 2. Enqueue the node's children for next run
             * 3. Hashed visited map for keep tracking visited node
             */
            const startNode =  this.getNode(startNodeKey);
           // create a hashed map to check whether one node has been visited
           const visited = this.nodes.reduce((acc, curr) => {
               acc[curr.key] = false;
               return acc;
           }, {});  
        
           // Create a queue to put all the nodes to be visited
           const queue = createQueue();
           queue.enqueue(startNode);
        
           // start process
           while (!queue.isEmpty()) {
              const current = queue.dequeue();
        
              // check wheather the node exists in hashed map
              if (!visited[current.key]) {
                  visitFn(current);
                  visited[current.key] = true;
        
                  // process the node's children
                  current.children.map(n => {
                    if (!visited[n.key]) {
                        queue.enqueue(n);
                    }
                  });
              }
           }
        },

        /**
         * Depth First Search
         */
        dfs (startNodeKey = "", visitFn = () => {}) {
            // get starting node
            const startNode = this.getNode(startNodeKey);
            // create hashed map
            const visited = this.nodes.reduce((acc, curr) => {
                acc[curr] = false;
                return acc;
            }, {});
            function explore(node) {
                // if already visited node, return
                if (visited[node.key]) {
                    return;
                }
                // otherwise call the callback function
                visitFn(node);
                // Set nodekey to be visited
                visited[node.key] = true;
                // Continue to explore its children
                node.children.forEach(n => {
                    explore(n);
                });
            }
            // start exploring
            explore(startNode);
        }
    }
}

const graph = createGraph(true)

graph.addNode('Kyle')
graph.addNode('Anna')
graph.addNode('Krios')
graph.addNode('Tali')

graph.addEdge('Kyle', 'Anna')
graph.addEdge('Anna', 'Kyle')
graph.addEdge('Kyle', 'Krios')
graph.addEdge('Kyle', 'Tali')
graph.addEdge('Anna', 'Krios')
graph.addEdge('Anna', 'Tali')
graph.addEdge('Krios', 'Anna')
graph.addEdge('Tali', 'Kyle')

console.log(graph.print())



const nodes = ['a', 'b', 'c', 'd', 'e', 'f']
const edges = [
  ['a', 'b'],
  ['a', 'e'],
  ['a', 'f'],
  ['b', 'd'],
  ['b', 'e'],
  ['c', 'b'],
  ['d', 'c'],
  ['d', 'e']
]

const graph2 = createGraph(true)
nodes.forEach(node => {
    graph2.addNode(node)
  })
  
  edges.forEach(nodes => {
    graph2.addEdge(...nodes)
  })

  console.log('***Breadth first graph***')
  graph2.bfs('a', node => {
    console.log(node.key)
  })

  console.log('***Depth first graph***')
  graph2.dfs('a', node => {
    console.log(node.key)
  })

 

So Depth first Search VS Breadth first Search:

Using 'depth' in JS, we should remind ourselves recursion, which using Stack data structure, FILO; 

Using 'breadth', we should remind ourselves Queue, it is FIFO data structure, we just need to enqueue the all the children.

posted @ 2018-12-17 02:19  Zhentiw  阅读(582)  评论(0编辑  收藏  举报