CS302 --- Graph Searching

Brad Vander Zanden

Graph Searching


I. Graph Searching Terminology

    A. Tree (or visited) Vertices: Vertices that have already been visited

    B. Fringe Vertices: Vertices adjacent to tree vertices but not yet visited

    C. Unseen Verticies: Vertices that haven't been encountered at all yet

II. Depth-first search: A search of a graph in which fringe vertices are
	visited in LIFO order (last-in, first-out):

    A. Depth-first search requires O(V + E) time if implemented with 
	adjacency lists

    B. Pseudo-Code

        const int UNSEEN = 0;
        const int VISITED = 1;

       void dfs( Vertex *v ) {
           v->setVisited(VISITED);
           for (v->firstEdge(); !v->endOfEdges(); v->nextEdge()) {
	       Vertex *w = v->getEdge()->getVertex2();
	        if ( w.getVisited() == UNSEEN ) 
		   dfs( w )
	   }
       }

    C. Notes
    
        1. I've placed the dfs function outside the vertex class because
	   it's not really possible to come up with a generic depth-first
	   search method that can be used in all situations. In my experience
	   you usually have to tweak the code everytime you use it, which
	   means that it's not suitable for being placed inside the vertex
	   class.

    D. Time Complexity

        1. Depth-first search requires O(V + E) time if implemented with 
	   adjacency lists

	2. Depth-first search requires O(V2) time if implemented
	   with an adjacency matrix

III. Breadth-first search: A search of a graph in which fringe vertices are
	visited in FIFO order (first-in, first-out):

    A. Strategy: Remove vertices from the front of a queue and add their
        adjacent vertices to the back of the queue. This strategy ensures
	that while level l vertices are being processed, level l+1 vertices
	are being added to the back of the queue. The level l+1 vertices
	will not be visited until all level l vertices have been exhausted.

    B. Pseudo-Code

        const int UNSEEN = 0;
        const int FRINGE = 1;
        const int VISITED = 2;

        bfs(Vertex *v) {
	    dList<Vertex *v> unvisited;
	    Vertex *current_vtx;

	    while (!unvisited.empty()) {
	       unvisited.first();
	       current_vtx = unvisited.get();
	       unvisited.deleteNode();
	       current_vtx->setVisited(VISITED);

	       for (current_vtx->firstEdge(); !current_vtx->endOfEdges();
	            current_vtx->nextEdge()) {
		   Vertex *w = current_vtx->getEdge()->getVertex2();
	           if ( w.getVisited() == UNSEEN ) {
		       w.setVisited(FRINGE)
		       unvisited.append(w);
		   }
	        }
	    }
         }

    C. Time Complexity

        1. Breadth-first search requires O(V + E) time if implemented with 
	   adjacency lists

	2. Breadth-first search requires O(V2) time if implemented
	   with an adjacency matrix

IV. Priority-first search: A search of a graph in which fringe vertices
      are assigned a priority and then visited in order of highest
      priority (e.g., priority might be the number of miles from a
      starting vertex).

    A. Strategy: 

        1. When vertices are added to the fringe, add them to
           a priority queue. 

        2. Use deletemin/deletemax to remove vertices from the queue.

	3. If a vertex's priority gets updated before it is removed from
	   the fringe then move it to a new position in the queue

    B. Pseudo-Code

     1. Before starting the search, assign a sentinel value as
	   the priority of each vertex

     2. Let VtxQueue be a priority queue

         pfs(Vertex *v) {
	    VtxQueue.insert(v);
	    while (!VtxQueue.empty()) {
	       current_vtx = VtxQueue.deletemin() {
	       for (current_vtx->firstEdge(); !current_vtx->endOfEdges();
	            current_vtx->nextEdge()) {
		   Vertex *w = current_vtx->getEdge()->getVertex2();
	           if ( w->getVisited() != VISITED )
		       if (VtxQueue.update(w, priority(current_vtx, w))
		           w->setValue(priority(current_vtx, w))
	       }
            }
          }

    3. Interpretation of depth-first and breadth-first search

        a. depth-first search: most recently visited vertex has the
	    highest priority

	    i. implemented by keeping a counter that tracks the number
	       of vertices seen thus far. Increment the counter each
	       time a vertex is seen.

	    ii. each time a previously unseen vertex is encountered,
	        assign the current counter value to it, increment the
		counter, and add the vertex to the priority queue

	    iii. use a max priority queue and always remove the vertex
	        with the highest counter value

	b. breadth-first search: assign counter values to vertices as in
	    depth-first search.

	    i. use a min priority queue and always remove the vertex with
	        the lowest conter value
	      
    C. Time Complexity

        1. Every vertex must be inserted into and deleted from the priority
	   queue. Each insertion and deletion takes O(log V) time, where V
	   is the number of vertices. Therefore the total number of insertions
	   and deletions is O(V log V). 

	2. Every edge may update a vertice's priority and this update may
	   require O(log V) time. Hence the total number of updates may require
	   O(E log V) time.

	3. The total running time is therfore O((E + V)log V). We have
	   to include both V and E because we do not know which one will
	   be greater. In general every vertex will have an edge, so normally
	   E > V. However, in very sparse graphs where some vertices have
	   no edges, E < V. Hence we have to include both the E and V terms
	   in the final Big-O total.