CS62 - Spring 2011 - Lecture 38
QUIZ! Problem 16.11
Cycle detection
- How can we determine if an undirected graph has a cycle?
- run DFS started at some node marking nodes as visited as we go
- if we try and visit a node that is already marked, we've found a cycle
- what if the graph isn't connected?
- we must make sure to run DFS on all of the different parts
- how can we make sure this happens?
- traverse all of the nodes, if we find a node that hasn't been visited, run DFS from there
- look at hasCycles in
graph_algorithms.cpp code
- why do we need to pass the parent?
- why do we need two methods?
- what is the parameter passing mechanism used to pass the graph? why?
- what is the running time of our cycle detection algorithm?
- how many times do we call dfsCycle on each vertex?
- exactly once,
- the first thing we do is set visited to true for that vertex
- and will never revisit a visited vertex
- what is the cost of each call to dfsCycle?
- depends on the representation
- adjacency matrix:
- we need to traverse all V entries to get the neighbors
- O(|V|^2) overall
- adjacency list:
- a little trickier
- how many times do we process each edge?
- once
- O(|V| + |E|), which for a connected graph is O(|E|)
connectedness: given an undirected graph, is the graph connected?
- how might we do this?
- run depth first search (or breadth first search)
- keep track of which nodes we visit
- if we visit all of the nodes, then it's connected
- first, let's take a look at dfs in graph_algorithms.cpp
- what are the parameters?
- the current vertex v
- the set of visited vertices
- the graph itself
- what does it do?
- just like before, visits the current node
- then recursively visits the unvisited neighbors
- what does grop_isConnected do?
- runs depth first search
- what is adjMap.begin()->first?
- adjMap.begin() is an iterator to the first element of the map/graph
- ->first will give us the key, i.e. the vertex
- basically, gets any vertex from the graph
- what does the for loop check?
- checks to see if there are any unvisited nodes
- if there are, then it's NOT connected
- notice again that we need to use a const_iterator
- what is the running time?
- Again, we'd like to ask it with respect to |V| and |E|
- It's going to depend on the graph... what is the worst case running time?
- what is the running time of grop_isConnected (without the dfs call)?
- O(|V|)
- how many times do we call dfs on for each vertex?
- exactly once
- what is the cost of each call to dfs?
- again, will depend on the graph representation
- adjacency matrix:
- we need to traverse all V entries to get the neighbors
- O(|V|^2) overall
- adjacency list:
- again, how many times do we process each edge?
- once
- O(|V| + |E|), which for a connected graph is O(|E|)
- is this surprising?
- NO, it's very similar to cycle detection, utilizing DFS
connected components: given an undirected graph, return the largest subgraphs that are connected
- how does it differ from just asking about connectedness?
- similar idea as connectedness, but like cycle detection, we need to make sure we examine all of the vertices in the graph
- how might we do this?
- pick a node
- run depth first search on that node keeping track of both the nodes we visited
- all nodes visited during THAT dfs are one connected subgraph
- pick a node that is unvisited and repeat
- stop when all nodes have been visited
- we can code this in psudocode:
void dfs_get_component(vertex v, list of vertices: component) {
set v as visited
add v to component
for (all neighbors u of v){
if (we have NOT visited u ){
dfs_get_component(u, component)
} }
(list of lists of vertices) grop_connected_components(const map<int, list<int> >& adjMap){
list of lists of vertices connected_components;
for (all vertices v){
if (we have NOT visited v) { create a new empty list of vertices: current
dfs_get_component(v, current)
add current to connected_components
}
}
return connected_components
}
- dfs_get_component looks is like DFS except we also keep track of the component
- why doesn't it return anything?
- the work that is actually done is modifying component
- what must that mean for component?
- either it must be a pointer or
- it must be passed by reference (for our graph assignment, we'll do this option so you don't have to worry about memory management)
- what does grop_connected_components do?
- connected_components keeps track of the different subgraphs
- we traverse each of the vertices, if we haven't visited it, call dfs_get_component
- if we're representing vertices as ints, what are the types of:
- component?
- list<int>
- connected_componenents?
- list<list<int> >
- how are we keeping track of visited?
- for each call to grop_connected_components, we'll need to keep track of whether or not each node has been visited
- will need to communicate that to dfs_get_component and make sure it's updated there
- what is the running time?
- using similar analysis to the last two approaches O(|V|^2) for adjacency matrix and O(|V| + |E|) for adjacency list
random C++ for the day: sizeof
- the sizeof command allows us to get the number of bytes of a type (built-in types, classes, etc.)
- it's used by the compiler in a number of places, including for knowing how far to increment a pointer
- look at
sizeof.cpp code
- we can ask for the size of built-in types like char, int, float and double
- as well for classes like vector, string and map
- notice that we can also ask the size of particular variables
- what do the results tell us?
- char, int, float and double are all what we would expect
- for map and vector must use pointers (which we'd expect) since they're the same size, regardless of type
- arrays are sizeof(arrayType) * number of elements
- strings must also be implemented with pointers
- though literal strings are character arrays