Skip to content

Commit 52ca222

Browse files
More practice problems
1 parent 4d3e73a commit 52ca222

16 files changed

Lines changed: 511 additions & 4 deletions

OMSCS/Courses/GA/Practice Problems/3.7 - Bipartite Graphs.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ tags:
33
- OMSCS
44
- Algorithms
55
- Practice
6+
- Alg-Graph
67
---
78
# 3.7 - Bipartite Graphs
89
![[Pasted image 20260313092928.png]]
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
tags:
3+
- OMSCS
4+
- Algorithms
5+
- Practice
6+
- Alg-Graph
7+
---
8+
# 4.1 - Dijkstra Example
9+
![[Pasted image 20260314160848.png]]
10+
## Graph
11+
```mermaid
12+
graph LR
13+
14+
A --1--> B
15+
A --4--> E
16+
A --8--> F
17+
18+
E --5--> F
19+
20+
B --6--> F
21+
B --2--> C
22+
B --6--> G
23+
24+
C --1--> D
25+
C --2--> G
26+
27+
D --1--> G
28+
D --4--> H
29+
30+
G --1--> F
31+
G --1--> H
32+
```
33+
34+
## 4.1a - Dijkstra Propagation
35+
| t=... | A | B | C | D | E | F | G | H |
36+
| ----- | --- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
37+
| 0 | 0 | inf | inf | inf | inf | inf | inf | inf |
38+
| 1 | 0 | **1** | inf | inf | inf | inf | inf | inf |
39+
| 2 | 0 | 1 | **3** | inf | inf | inf | inf | inf |
40+
| 3 | 0 | 1 | 3 | inf | **4** | inf | inf | inf |
41+
| 4 | 0 | 1 | 3 | **4** | 4 | inf | inf | inf |
42+
| 5 | 0 | 1 | 3 | 4 | 4 | inf | **5** | inf |
43+
| 6 | 0 | 1 | 3 | 4 | 4 | **6** | 5 | inf |
44+
| 7 | 0 | 1 | 3 | 4 | 4 | 6 | 5 | **6** |
45+
Note, there's a couple options here for propagation, but it depends on how the PQ is managed.
46+
47+
- At t=3, we have the option of selecting AE or CD. This only affects the order in which those edges are selected. There are no other downstream consequences of this decision. I selected AE first, because it was discovered before CD.
48+
- At t=5, we have the option of selecting CG or DG. Both result in an $A \rightarrow G$ distance of 5. However, this does change the path length of $A \rightarrow G$, as well as the path lengths from $A \rightarrow F$ and $A \rightarrow H$. For this example graph, I selected CG because
49+
- CG was discovered before DG
50+
- It shortened the SP tree (below)
51+
52+
## 4.1b - SP Tree
53+
```mermaid
54+
graph LR
55+
56+
A --1--> B
57+
A --4--> E
58+
59+
60+
B --2--> C
61+
62+
C --1--> D
63+
64+
C --2--> G
65+
66+
G --1--> F
67+
G --1--> H
68+
```
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
tags:
3+
- OMSCS
4+
- Algorithms
5+
- Practice
6+
- Alg-Graph
7+
---
8+
# 4.11 - Detect Cycles in Cubic Time
9+
> Give an algorithm that takes as input a directed graph with positive edge lengths, and returns the length of the shortest cycle in the graph (if the graph is acyclic, it should say so). Your algorithm should take time at most $O(|V|^3)$.
10+
11+
## Exploration
12+
- We can check if the graph is a DAG by running SCC and seeing if $|V_{SCC}|=|V|$. If this is the case, then each metavertex in the SCC metatraph only contains one vertex, which means no vertices in $G$ are part of a cycle.
13+
- Running SCC on $G$ will produce subsets of vertices which within themselves contain cycles.
14+
- We can use the output of SCC to partition $G$ into $k$ subgraphs. $G_k$ is the subset of $G$ which comprises $v_{k} \in V_{SCC}$. If $v_k$ contains only 1 vertex, we skip it.
15+
16+
There's a possibility that all of this ends up returning the exact same graph, so it's likely that none of it is needed. Also, this likely isn't a problem that's asking for the "shortest cycle" in terms of the number of edges. It's more likely that it's asking for the "shortest cycle" in terms of edge weights.
17+
18+
Given the runtime hint, this is likely a Floyd-Warshall problem.
19+
## Algorithm
20+
Run Floyd-Warshall on the graph, producing a 2D distance array which maps the distance between every pair of vertices. For each pair of distinct vertices in the graph u and v, sum the distance from u to v and the distance from v to u. Take this minimum combined distance across G. The result is the minimum length cycle in G. If the minimum length cycle is infinite, then G is a DAG. Otherwise, return the length of the minimum cycle.
21+
## Justification
22+
This is a fairly simple adaptation of Floyd-Warshall. The only interesting part of this problem is detecting that G is a DAG. If G is a DAG, then for every pair of distinct vertices v and u, one of these must be true:
23+
24+
1. u can reach v, but v cannot reach u
25+
1. $dist[u][v] \ne \infty$
26+
2. $dist[v][u] = \infty$
27+
2. v can reach u, but u cannot reach v
28+
1. $dist[v][u] \ne \infty$
29+
2. $dist[u][v] = \infty$
30+
3. Neither u can reach v, nor can v reach u
31+
1. $dist[u][v] = \infty$
32+
2. $dist[v][u] = \infty$
33+
34+
Under all of these conditions, $dist[u][v]+dist[v][u]=\infty$. Therefore, the minimum distance pair is always $\infty$.
35+
36+
## Runtime
37+
- Floyd-Warshall: $O(n^3)$
38+
- Minimum over $(u,v) \in V$: $O(n^2)$
39+
40+
Overall: $O(n^3)$
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
tags:
3+
- OMSCS
4+
- Algorithms
5+
- Practice
6+
- Alg-Graph
7+
---
8+
# 4.12 - Shortest Cycle Containing Edge
9+
> Give an $O(|V|^2)$ algorithm for the following task.
10+
>
11+
> *Input*: An undirected graph $G=(V,E)$; edge lengths $l_e > 0$; an edge $e \in E$.
12+
>
13+
> *Output:* The length of the shortest cycle containing edge $e$.
14+
15+
## Exploration
16+
We have some office-hours from course staff, which helps clarify that this question is asking about the minimum cost cycle containing $e$.
17+
18+
The runtime constraint excludes FW and BF. The edge weights exclude BFS and DFS from being the only consideration. That leaves Dijkstra's algorithm, which has runtime $O((n+m) \space log \space n)$.
19+
20+
The fact that this problem involves an undirected graph, makes me think we can post-process the results of Dijkstra from either or both of the vertices which define $e=(u,v)$. The postprocessing step will run longer than Dijkstra's does on its own.
21+
22+
Let's suppose that we run Dijkstra's from $u$, that gives us the distance from $u$ to all other vertices in $G$. We'll call this $dist_u$. From here, we need to find cycles. We can find cycles by looking for 2 vertices reachable from $u$ which can also reach each other.
23+
24+
Ignore all this. Can't we just remove $e$ from $G$, run Dijkstra's from $u$, take the shortest path from $u$ to $v$, then add add $e$ to that path?
25+
26+
Yes, that was the TA's solution.
27+
28+
## Algorithm
29+
Remove $e=(u,v)$ from $G$, creating $G'$. Run Dijkstra's algorithm from $u$ in $G'$. Find the shortest path $P$ from $u$ to $v$ in $G'$. Adding $e$ to $P$ creates a cycle. In $G$, this cycle will be the shortest cycle in $G$ which contains $e$.
30+
31+
Since we don't need the actual path, just the distance, we can take the distance from $u$ to $v$ in $G'$ and add $w(e)$ to produce the required answer.
32+
33+
## Justification
34+
We know that $e=(u,v) \in E$ exists within a cycle in $G$. That means there exists at least 2 paths from $u$ to $v$ in G. One path is simply $(u, v)$. The other paths don't use $e$. If we remove $e$ from $G$, we can find the shortest of those alternative paths. Adding $e$ onto that path results in the shortest cycle in $G$ which contains $e$.
35+
36+
## Runtime
37+
- Copying G without $e$: $O(n+m)$
38+
- The alternative is to remove $e$ in $G$ and then fix $G$ later. That cost is $O(n)$ for the removal, and $O(n)$ for the re-addition later.
39+
- Running Dijkstra's in $G'$: $O((n+m) log (n))$
40+
- Finding the cost of the minimum uv-path in $G'$: $O(1)$
41+
- Adding $w(e)$ to that cost: $O(1)$
42+
43+
Overall, running Dijkstra's algorithm dominates the runtime.
44+
45+
$O((n+m) \space log \space n)$
46+
47+
This doesn't necessarily beat the required runtime of $O(n^2)$. In fact, it's worse.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
tags:
3+
- OMSCS
4+
- Algorithms
5+
- Practice
6+
- Alg-Graph
7+
---
8+
# 4.13 - Set of Cities
9+
![[Pasted image 20260315153327.png]]
10+
11+
## Part A
12+
### Algorithm
13+
Make a new graph $G'$, adding all of the vertices of $G$. For each edge in $G$, only add it to $G'$ if $l_e$ is less than $L$. Run BFS from $s$ in $G'$. If $t$ is reachable from $s$, then there exists a feasible route in $G$.
14+
### Justification
15+
By modifying the graph to only contain edges traversable by your vehicle, we can do a simple reachability analysis from $s$ to $t$ in $G'$ to determine whether the prompt is satisfiable.
16+
### Runtime
17+
- Creating $G':$ $O(n+m)$
18+
- Running BFS on $G'$: $O(n+m)$
19+
- Checking `dist[t]`: $O(1)$
20+
21+
Overall: $O(n+m)$
22+
23+
## Part B
24+
### Algorithm
25+
Use Kruskal's to create the minimum spanning tree (MST) of $G$. Run DFS on the MST to produce a path from $s$ to $t$. Find the maximum edge length in that path. This maximum edge length determines the minimum fuel tank capacity required to travel from $s$ to $t$ in $G$.
26+
### Justification
27+
Using the MST of G allows us to create paths through $G$ which have the minimum possible edge cost for traversing cuts of $G$. This minimizes the edge costs for a given path through G from s to t. Taking the maximum edge length of the path from s to t in the MST of G gives you the answer.
28+
29+
Doubling that edge cost would be better, but these are risk-taking travelers.
30+
### Runtime
31+
- Kruskal's on G: $O(m \space log \space n)$
32+
- DFS on MST: $O(n+m)$
33+
- Iterating over prev to find the minimum edge weight: $O(n)$
34+
35+
We don't have information about the connectivity of this graph. Therefore overall: $O(n + (m \space log \space n))$
36+
37+
If we know that the graph is connected, then $O(n) \le O(m)$, and therefore $O(m \space log \space n)$

OMSCS/Courses/GA/Practice Problems/4.14 - Shortest Path through Vertex.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@ Overall Runtime: $O((n+m) \space log \space n)$
3737

3838
> You are given a **strongly connected** directed graph.
3939
40-
Strongly connected means that $m>n$, therefore $O(m)>O(n)$, therefore $O(n+m)=O(m)$. Note again that strongly connected does not equal fully connected, so $m \le n^2$.
40+
Strongly connected means that $O(m) \ge O(n)$, therefore $O(n+m)=O(m)$. Note again that strongly connected does not equal fully connected, so $m \le n^2$.
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
---
2+
tags:
3+
- OMSCS
4+
- Algorithms
5+
- Practice
6+
- Alg-Graph
7+
---
8+
# 4.2 - Bellman-Ford Example
9+
![[Pasted image 20260314160922.png]]
10+
11+
The "previous problem": [[4.1 - Dijkstra Example]]
12+
## Graph
13+
```mermaid
14+
graph LR
15+
16+
S --[6]--> C
17+
S --[7]--> A
18+
S --[5]--> F
19+
S --[6]--> E
20+
21+
A --[-2]--> C
22+
A --[4]--> B
23+
24+
25+
B --[-4]--> H
26+
B --[-2]--> G
27+
28+
C --[2]--> D
29+
C --[1]--> F
30+
31+
E --[-2]--> F
32+
E --[3]--> H
33+
34+
F --[3]--> D
35+
36+
G --[-1]--> I
37+
H --[1]--> G
38+
I --[1]--> H
39+
```
40+
41+
## 4.2a - BF Propagation
42+
There are 10 vertices, so we need to iterate 9 times. 16 edges by 9 iterations means 144 `update()` operations, where `update()` is defined:
43+
44+
$update((u,v) \in E)$
45+
$dist(v)=\min\{dist(v), dist(u)+l(u,v)\}$
46+
47+
We can reduce the amount of updates performed if we exit once no updates are performed in a given iteration.
48+
49+
```python
50+
E_RAW = """...""" # The serialized mermaid diagram
51+
edge_regex = re.compile(r"(\w) --\[(\-?\d+)\]--> (\w)")
52+
53+
E = []
54+
for line in E_RAW.strip().splitlines():
55+
match = edge_regex.match(line)
56+
if match:
57+
source, weight, target = match.groups()
58+
E.append((source, target, int(weight)))
59+
60+
DIST = {**{v: float("inf") for v in V}, "S": 0}
61+
62+
for iteration in range(0, len(V) - 1):
63+
update_performed = False
64+
for u, v, l_uv in E:
65+
if DIST[v] > DIST[u] + l_uv:
66+
DIST[v] = DIST[u] + l_uv
67+
update_performed = True
68+
69+
if not update_performed:
70+
break
71+
```
72+
73+
By injecting some print statements into this procedure, we can extract the updates performed to the DIST table, to show how it evolves over time. There are no updates performed during iteration 3, so the last update made during iteration 2 represents the output of the table.
74+
75+
| iter. | Edge | S | A | B | C | D | E | F | G | H | I |
76+
| ----- | ---------- | --- | ------- | -------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- |
77+
| t=0 | `nil` | 0 | inf | inf | inf | inf | inf | inf | inf | inf | inf |
78+
| t=1 | $\vec{SC}$ | 0 | inf | inf | **-6-** | inf | inf | inf | inf | inf | inf |
79+
| t=1 | $\vec{SA}$ | 0 | **-7-** | inf | 6 | inf | inf | inf | inf | inf | inf |
80+
| t=1 | $\vec{SF}$ | 0 | 7 | inf | 6 | inf | inf | **-5-** | inf | inf | inf |
81+
| t=1 | $\vec{SE}$ | 0 | 7 | inf | 6 | inf | **-6-** | 5 | inf | inf | inf |
82+
| t=1 | $\vec{AC}$ | 0 | 7 | inf | **-5-** | inf | 6 | 5 | inf | inf | inf |
83+
| t=1 | $\vec{AB}$ | 0 | 7 | **-11-** | 5 | inf | 6 | 5 | inf | inf | inf |
84+
| t=1 | $\vec{BH}$ | 0 | 7 | 11 | 5 | inf | 6 | 5 | inf | **-7-** | inf |
85+
| t=1 | $\vec{BG}$ | 0 | 7 | 11 | 5 | inf | 6 | 5 | **-9-** | 7 | inf |
86+
| t=1 | $\vec{CD}$ | 0 | 7 | 11 | 5 | **-7-** | 6 | 5 | 9 | 7 | inf |
87+
| t=1 | $\vec{EF}$ | 0 | 7 | 11 | 5 | 7 | 6 | **-4-** | 9 | 7 | inf |
88+
| t=1 | $\vec{GI}$ | 0 | 7 | 11 | 5 | 7 | 6 | 4 | 9 | 7 | **-8-** |
89+
| t=1 | $\vec{HG}$ | 0 | 7 | 11 | 5 | 7 | 6 | 4 | **-8-** | 7 | 8 |
90+
| t=2 | $\vec{GI}$ | 0 | 7 | 11 | 5 | 7 | 6 | 4 | 8 | 7 | **-7-** |
91+
92+
## 4.2b - SP Tree
93+
94+
The BF algorithm also iteratively keeps track of a $prev[]$ vector, building a tree. However, we canalso reconstruct this tree from the $dist[]$ vector. Starting from S in the table, we can build the SP Tree by checking the edges out of S ($(S,v) \in E$) to see if the distance from S to $v$ matches what's in the table. Here's the final resulting row of the $dist()$ array.
95+
96+
| S | A | B | C | D | E | F | G | H | I |
97+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
98+
| 0 | 7 | 11 | 5 | 7 | 6 | 4 | 8 | 7 | 7 |
99+
100+
For the edges leaving S
101+
- $l(\vec{SC}) = 6 \ne dist(C)$
102+
- $l(\vec{SA}) =7 = dist(A)$
103+
- $l(\vec{SF}) = 5 \ne dist(F)$
104+
- $l(\vec{SE}) = 6 = dist(E)$
105+
106+
This leaves us with this tree so far.
107+
108+
```mermaid
109+
graph LR
110+
111+
S --[7]--> A
112+
S --[6]--> E
113+
```
114+
115+
Now we can check the edges leaving A and E.
116+
- For the edges leaving A ($dist(A)=7$)
117+
- $7+l(\vec{AC})=7-2=5=dist(C)$
118+
- $7 + l(\vec{AB})=7+4=11=dist(B)$
119+
- For the edges leaving E ($dist(E)=6$)
120+
- $6 + l(\vec{EF}) = 6 - 2 = 4 = dist(F)$
121+
- $6 + l(\vec{EH}) = 6 + 3 = 9 \ne dist(H)$
122+
123+
We can add C, B, and F to the tree.
124+
125+
```mermaid
126+
graph LR
127+
128+
S --[7]--> A
129+
S --[6]--> E
130+
131+
A --[-2]--> C
132+
A --[4]--> B
133+
E --[-2]--> F
134+
```
135+
136+
Now we can check the edges leaving C, B, and F
137+
- For the edges leaving C ($dist(C)=5$)
138+
- $5 + l(\vec{CD}) = 5 + 2 = 7 = dist(D)$
139+
- $\vec{CF}:$ F is already in the tree.
140+
- For the edges leaving B ($dist(B)=11$)
141+
- $11 + l(\vec{BH}) = 11 - 4 = 7 = dist(H)$
142+
- $11 + l(\vec{BG}) = 11 - 2 = 9 \ne dist(G)$
143+
- For the edges leaving F ($dist(F)=4$)
144+
- $4+l(\vec{FD})=4+3=7=dist(D)$
145+
146+
We can now add D and H to the tree, though we arrived at D 2 ways. We'll pick $(S,A,C,D)$ instead of $(S,E,F,D)$, because of node order. This demonstrates the importance of relying on the `prev[]` array instead of trying to rebuild the tree from `dist[]`.
147+
148+
```mermaid
149+
graph LR
150+
151+
S --[7]--> A
152+
S --[6]--> E
153+
154+
A --[-2]--> C
155+
A --[4]--> B
156+
E --[-2]--> F
157+
158+
C --[2]--> D
159+
B --[-4]--> H
160+
```
161+
162+
We can check the edge leaving H now, though there's only 1.
163+
- $dist(H)=7$
164+
- $7+l(\vec{HG})=7+1=8=dist(G)$
165+
166+
We can also shortcut the last step, because only $\vec{GI}$ reaches I. Walking through the tree from S to all vertices and keeping track of the distances successfully reconstructs the `dist[]` table.
167+
168+
```mermaid
169+
graph LR
170+
171+
S --[7]--> A
172+
S --[6]--> E
173+
174+
A --[-2]--> C
175+
A --[4]--> B
176+
E --[-2]--> F
177+
178+
C --[2]--> D
179+
B --[-4]--> H
180+
181+
H --[1]--> G
182+
G --[-1]--> I
183+
```

0 commit comments

Comments
 (0)