CPSC 327 | Data Structures and Algorithms | Spring 2024 |
There is not a redo opportunity for exam 3, however, there will be an optional section on the final exam covering similar material. A better grade on that section will replace the exam 3 grade.
For graph modeling problems, the graph is modeling the problem input and the structure of that graph is dictated by the nature of that input, not whether or not that aspect would be relevant in the desired solution. For #1, whether the graph is cyclic depends on whether or not there is a series of moves that results in returning to a particular state (position and velocity), not whether or not the path that gets from start to finish in the fewest moves would actually follow that cycle. Whether or not the graph is directed does not depend on having a goal of getting from start to finish — a path from one vertex to another can be found whether or not the graph is directed. A edge ($u$,$v$) should be directed if there's not also an edge ($v$,$u$) in the graph (or if there's something different about the reverse edge, such as a different weight).
"Multiedges" refers to multiple edges between the same pair of vertices — either multiple undirected edges $u\ —\ v$ or multiple directed edges $u \rightarrow v$. Having directed edges $u \rightarrow v$ and $v \rightarrow u$ is not having multiedges.
It can be useful when tracing Dijkstra's algorithm to explicitly keep track of the PQ contents — at each step, the vertex with the lowest dist label is removed (breaking ties alphabetically for this problem). (Keep in mind that it is priority queue, not a plain queue like breadth-first search!)
For #5, simply traversing the graph structure and reversing edges isn't quite enough of an algorithm — if the changes are being done in-place (in $G$ instead of creating a new graph that is the reversed version of $G$), how do you know which edges have been reversed already and which ones still need to be flipped? Also identify what "reverse an edge" means — what operations need to be done? The intent here was to think about the whole algorithm in terms of the adjacency list and adjacency matrix implementations discussed in class — a directed edge stores its source and destination endpoints and each adjacency list vertex has a list of its in incident edges and a list of its out incident edges. For the running time, also address the time to create a new graph (if you aren't reversing the edges in-place), the time to create/initialize any thing you might need to keep tracking of which edges have been reversed (if you are reversing the edges in-place), and the running time of the reversing, removing, adding, etc operations done for each vertex and edge — acknowledge steps even if they are O(1).
For #6, again be sure to cover all the key "how" parts of the algorithm. It's fine to say "use DFS to check if there's a cycle" because how to use DFS to detect cycles is a well-known thing, but you should identify that DFS is the method by which cycles are checked as that is important for the running time. Also, be clear about where DFS is done — are you checking for cycles in the original graph or in the MST that is being constructed? This is an important dettail for the algorithm as well as the running time — DFS is O($n+m$) where $n$ and $m$ are the number of vertices and edges in the graph DFS is running on. In the context of this problem, $n$ and $m$ would be assumed to refer to the original graph. The MST has the same number of vertices, but how many edges will it have?
Be careful to not only identify the running time for each operation, but also how many times each operation is done. For #6, checking if a cycle is formed is needed for every edge considered for inclusion in the MST, so you need to multiply the DFS runtime by the number of edges considered.
Also be careful not to dismiss the running time of a part of an algorithm without acknowledging it first — for #6, give the running time for both sorting the edges and the actual build-the-MST parts of the algorithm before concluding that one or the other parts dominates the running time. This can help keep you from coming to the wrong conclusion about the whole algorithm's running time as well as providing a justification for your correct conclusion and a basis for what to target to try to improve the running time.
Keep in mind the limits of big-Oh — it gives you a big-picture view, but only a big-picture view. Furthermore, saying that the time or space requirements are O($f(n)$) doesn't mean that the algorithm requires $f(n)$ units of time or space — big-Oh is a growth rate, not an exact value, and is better thought of as saying the actual time/space requirements are proportional to $f(n)$. (Recall the definitions: $f(n) = O(g(n))$ if there exists $c > 0$ such that $f(n) \leq c g(n)$ and $f(n) = \Theta(g(n))$ if there exists $c_1, c_2 > 0$ such that $c_1 g(n) \leq f(n) \leq c_2 g(n)$) For #7, this means that when the graph is dense, the constant factors matter in the choice — think of the adjacency matrix and adjacency list representations discussed in class. Compared to the adjacency matrix, the adjacency list has two additional references in every edge (but one less integer per vertex) and has two doubly-linked list nodes for every edge compared to an $n\times n$ array of edges. That's $2m-n+2m-n^2 = 4m-n-n^2$ more space for the adjacency list compared to the adjacency matrix — for sparse graphs, $4m-n-n^2 < 0$ and adjacency list is the clear winner, but it also means that as the graph gets denser — in particular, if $m > (n^2+n)/4$ — adjacency list is worse. (By comparison, a complete undirected graph has $n(n-1)/2 = (n^2-n)/2$ edges.) You don't need to work out the space requirements to the exact byte, but there are two key points: that when the big-Ohs are the same, you should look to constant factors to make a decision and not just conclude that the options are equivalent, and that a glance at the actual space (or time) requirements can indicate where the threshold is. For #7, more space per edge for adjacency list indicates that adjacency matrix is very likely to be better for (a) and potentially better for (b). Noting that there are roughly three more values per edge for the adjacency list compared to the adjacency matrix can address (b) more definitively — compare $n+m+3m$ to $n^2$.