-
Notifications
You must be signed in to change notification settings - Fork 0
/
hw6.typ
293 lines (239 loc) · 7.83 KB
/
hw6.typ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#import "@preview/cetz:0.2.2": *
#import "utils.typ": *
== HW6 (Week 7)
Due: 2024.04.21
=== Question 20.1-3
The transpose of a directed graph $G=(V,E)$ is the graph $G^T = (V, E^T)$ where $ E^T = {(v,u) in V times V:(u,v) in E} $ That is, $G^T$ is $G$ with all its edges reversed. *Describe efficient algorithms for computing $G^T$ from $G$, for both the adjacency-list and adjacency-matrix representation of $G$, Analyze the running times of your algorithms.*
#ans[
- adjacency-list:
```txt
def MAKE_ADJACENCY_LIST_TRANSPOSE(G):
GT = EMPTY_GRAPH()
GT.V = G.V
for uT in GT.V
uT.adj = []
for u in G.V
for v in u.adj
GT.v.adj.append(u)
```
Time complexity: $O(abs(V)+abs(E))$
- adjacency-matrix:
Same as matrix transpose(flipping the matrix along the diagonal).
Time complexity: $O(V^2)$. (with special design of matrix representation, sparse matrix: $O(abs(V)+abs(E))$, dense matrix: $O(V^2)$, lazy transpose: $O(1)$)
]
=== Question 20.1-8
Suppose that instead of a linked list, each array entry $"Adj"[u]$ is a hash table containing the vertices $v$ for which $(u,v) in E$, with collisions resolved by chaining.
Under the assumption of uniform independent hashing, if all edge lookups are equally likely,
- what is the expected time to determine whether an edge is in the graph?
#ans[
$O(1)$
]
- What disadvantages does this scheme have compared to the linked-list representation?
#ans[
Worst case time complexity is $O(V)$, while linked-list representation is $O(abs(u."adj"))$.
]
- Suggest an alternate data structure for each edge list that solves these problems. Does your alternative have disadvantages compared with the hash table?
#ans[
Use a balanced binary search tree to store the edge list.
- Time complexity: $O(log(abs(u."adj")))$. (worst case time complexity)
- Disadvantages: $O(log(abs(u."adj"))) > O(1)$. (worse average time complexity)
]
=== Question 20.2-6
Give an example of a directed graph $G=(V,E)$, a source vertex $s in V$, and a set of tree edges $E_pi subset.eq E$ such that for each vertex $v in V$, the unique simple path in the graph $(V, E_pi)$ from $s$ to $v$ is a shortest path in $G$, yet the set of edges $E_pi$ cannot be produced by running BFS on $G$, no matter how the vertices are ordered in each adjacency list.
#ans[
Consider the following graph $G$:
#let data = (
[$1$],
([$2$], [$4$]),
([$3$], [$5$]),
)
#align(center)[
#canvas(
length: 1cm,
{
import draw: *
set-style(
content: (padding: .2),
fill: gray.lighten(80%),
stroke: gray.lighten(70%),
)
tree.tree(
data,
spread: 1.5,
grow: 1.4,
draw-node: (node, ..) => {
circle((), radius: .45, stroke: none)
content((), node.content)
},
draw-edge: (from, to, ..) => {
line(
(a: from, number: .6, b: to),
(a: to, number: .6, b: from),
mark: (end: ">"),
)
},
name: "tree",
)
let (a, b) = ("tree.0-0", "tree.0-1-0")
line((a, .6, b), (b, .6, a), mark: (end: ">"))
let (a, b) = ("tree.0-1", "tree.0-0-0")
line((a, .6, b), (b, .6, a), mark: (end: ">"))
},
)
]
Only the following $(V, E_pi)$ could be generated:
#align(center)[
#table(
columns: (auto, auto),
stroke: (),
[
#let data = (
[$1$],
([$2$], [$4$], [$5$]),
([$3$]),
)
#canvas(
length: 1cm,
{
import draw: *
set-style(
content: (padding: .2),
fill: gray.lighten(80%),
stroke: gray.lighten(70%),
)
tree.tree(
data,
spread: 1.5,
grow: 1.4,
draw-node: (node, ..) => {
circle((), radius: .45, stroke: none)
content((), node.content)
},
draw-edge: (from, to, ..) => {
line(
(a: from, number: .6, b: to),
(a: to, number: .6, b: from),
mark: (end: ">"),
)
},
name: "tree",
)
},
)
],
[
#let data = (
[$1$],
([$3$], [$4$], [$5$]),
([$2$]),
)
#canvas(
length: 1cm,
{
import draw: *
set-style(
content: (padding: .2),
fill: gray.lighten(80%),
stroke: gray.lighten(70%),
)
tree.tree(
data,
spread: 1.5,
grow: 1.4,
draw-node: (node, ..) => {
circle((), radius: .45, stroke: none)
content((), node.content)
},
draw-edge: (from, to, ..) => {
line(
(a: from, number: .6, b: to),
(a: to, number: .6, b: from),
mark: (end: ">"),
)
},
name: "tree",
)
},
)
],
)
]
but not the following:
#let data = (
[$1$],
([$2$], [$4$]),
([$3$], [$5$]),
)
#align(center)[
#canvas(
length: 1cm,
{
import draw: *
set-style(
content: (padding: .2),
fill: gray.lighten(80%),
stroke: gray.lighten(70%),
)
tree.tree(
data,
spread: 1.5,
grow: 1.4,
draw-node: (node, ..) => {
circle((), radius: .45, stroke: none)
content((), node.content)
},
draw-edge: (from, to, ..) => {
line(
(a: from, number: .6, b: to),
(a: to, number: .6, b: from),
mark: (end: ">"),
)
},
name: "tree",
)
},
)
]
which is trivially the shortest path.
]
=== Question 20.4-5
Another way to topologically sort a directed acyclic graph $G=(V,E)$ is to repeatedly find a vertex of in-degree $0$, output it, and remove it and all of its outgoing edges from the graph. *Explain how to implement this idea so that is runs in time $O(V + E)$. What happens to this algorithm if $G$ has cycles?*
#ans[
- Implementation:
```txt
def TOPOLOGICAL_SORT(G):
for u in G.V
u.indegree = 0
for u in G.V
for v in u.adj
v.indegree += 1
Q = []
for u in G.V
if u.indegree == 0
Q.append(u)
while Q
u = Q.pop()
print(u)
for v in u.adj
v.indegree -= 1
if v.indegree == 0
Q.append(v)
```
Time complexity: $O(V+E)$
- If $G$ has cycles:
#rev1_note[
当 $G$ 中有环时, 所有的环和环的后继都不会入队, 不会出现在拓扑序中.
]
The algorithm will not terminate, since there is no vertex of in-degree $0$.
]
=== Question 20.5-4
Prove that for any directed graph $G$, the transpose of the component graph $G^T$ is the same as the component graph of $G$. That is $((G^T)^(S C C))^T = G^(S C C)$
#ans[
Let $C$ be a component of $G$.
- If $C$ is a single vertex, then $C$ is also a component of $G^T$.
- If $C$ is a strongly connected component, then $C$ is also a strongly connected component of $G^T$.
- Vertex sets of $((G^T)^(S C C))^T$ and $G^(S C C)$ are the same.
- To show edge sets are the same:
For all $(v_i, v_j) in E_(((G^T)^(S C C))^T)$, $(v_j, v_i)$ is an edge in $(G^T)^(S C C)$, then there's $x in C_j, y in C_i$, $(x,y) in E_(G^T)$, $(y,x)$ is an edge of $G$ $=>$ $(v_i, v_j)$ is an edge in $G^(S C C)$.
Discussing the other direction is similar, thus $((G^T)^(S C C))^T = G^(S C C)$.
]