CS302 --- Graph Searching
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.