init
This commit is contained in:
commit
3d1a4b8f4b
|
@ -0,0 +1,2 @@
|
|||
clrs.db
|
||||
exam.html
|
|
@ -0,0 +1,81 @@
|
|||
# CLRS practice exam generator
|
||||
|
||||
Generates practice exams by randomly selecting questions and their answers from the CLRS textbook
|
||||
|
||||
## Why?
|
||||
|
||||
[Interleaved practice](https://en.wikipedia.org/wiki/Varied_practice) is when you mix practicing a bunch of subjects together. Textbooks are designed such that all the questions relevant to a particular topic are grouped together. While this is good for reference, it's not all that good for practice.
|
||||
|
||||
## How?
|
||||
|
||||
[Peng-Yu Chen](https://pengyuc.com/) and [contributors to the walkccc/CLRS](https://github.com/walkccc/CLRS/graphs/contributors) have collected solutions to many (but not all!) of the problems in the third edition of CLRS. They have also conviently and seemingly accidentally structured them in a way that lends itself well to being parsed into a database. I've written a script that automatically sorts all of these problems into an SQLite database, and another you can use to query it and generate nice-looking HTML practice exams.
|
||||
|
||||
This is probably best described as something that barely works. As in, it's something I duct taped together while bored in class and bothered to do very little error-handling.
|
||||
|
||||
Not to mention, `walkccc/CLRS` is incomplete as far as data sets go. It's the largest and most well-formated that I'm aware of, anyways.
|
||||
|
||||
### How can I generate new practice exams?
|
||||
|
||||
This setup assumes you have Python 3, Git, and the `cmarkgfm` Python package. Before you get started, make sure you have the `cmarkgfm` package installed. On Debian:
|
||||
|
||||
```bash
|
||||
$ sudo apt install python3-cmarkgfm
|
||||
```
|
||||
|
||||
Or with pip, call:
|
||||
```bash
|
||||
$ pip install cmarkgfm
|
||||
```
|
||||
|
||||
Next, you can run this script if you're on some kind of Unix machine. Something similar on Windows but idk how that stuff works.
|
||||
|
||||
```bash
|
||||
# 1. Make yourself a workspace
|
||||
mkdir work
|
||||
cd work
|
||||
|
||||
# 2. Get the problems
|
||||
git clone https://github.com/walkccc/CLRS
|
||||
|
||||
# 3. Get the scripts
|
||||
git clone https://git.nats.solutions/nat/clrs-practice-exam
|
||||
cd clrs-practice-exam
|
||||
|
||||
# 4. Clean the data
|
||||
python clean-data.py ../CLRS/docs
|
||||
|
||||
# 5. Generate the practice exam
|
||||
python practice-exam-generator.py "select * from problem where chapter in (3,4,7,12,15,16,22,24,26,34) order by random() limit 20" --template template.html
|
||||
```
|
||||
|
||||
### Specifics
|
||||
|
||||
`practice-exam-generator.py` takes a positional argument that's an SQL query. The query in the example above is the one I used to generate the example exams, and it's kind of but not super reflective of the stuff I've covered in my algorithms course.
|
||||
|
||||
The relevant table in the database is made like this:
|
||||
|
||||
```sql
|
||||
create table if not exists problem (
|
||||
problem_number number not null,
|
||||
question text not null,
|
||||
answer text not null,
|
||||
chapter number not null,
|
||||
section number,
|
||||
starred number not null
|
||||
)
|
||||
```
|
||||
|
||||
So, you can use all of this information to construct your SQL query.
|
||||
|
||||
Both scripts take an `-h` argument that breaks down what you can pass to it to tweak things based on your setup.
|
||||
|
||||
The `template.html` document should probably include [KaTeX](https://katex.org/) lest the many, many LaTeX equations be broken. This template must include the string "%$%content" somewhere in it; it'll be replaced with all the selected problems when the exam is being generated
|
||||
|
||||
## "License"
|
||||
|
||||
This software (that is, the scripts; not including anything you'll find in `./examples`) is a gift from me to you. You form a relationship with all those from whom you accept gifts, and with this relationship comes certain expectations. Namely:
|
||||
|
||||
* When you share this gift with others, you will share it in the same spirit as I share it with you.
|
||||
* You will not use this gift to hurt people, any living creatures, or the planet
|
||||
|
||||
The questions were written by Michelle Bodnar and Andrew Lohr and used here on a fair-use basis. The answers are organized in the [walkccc/CLRS](https://github.com/walkccc/CLRS) repository, by [Peng-Yu Chen](https://github.com/walkccc) and [contributors](https://github.com/walkccc/CLRS/graphs/contributors). Their repository is shared under the MIT license.
|
|
@ -0,0 +1,73 @@
|
|||
import argparse
|
||||
import sqlite3
|
||||
import sys
|
||||
import os
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='CLRS problem databse generator',
|
||||
description='Takes the walkccc.me CLRS solutions and parses them into a SQLite database')
|
||||
parser.add_argument('source', help='the directory to walk in search of problems and solutions')
|
||||
|
||||
def main(args):
|
||||
con = sqlite3.connect("clrs.db")
|
||||
cur = con.cursor()
|
||||
|
||||
new_rows = []
|
||||
|
||||
cur.execute("""
|
||||
create table if not exists problem (
|
||||
problem_number number not null,
|
||||
question text not null,
|
||||
answer text not null,
|
||||
chapter number not null,
|
||||
section number,
|
||||
starred number not null
|
||||
)
|
||||
""")
|
||||
|
||||
chapter_dirs = [os.path.join(args.source, x) for x in os.listdir(args.source) if x.startswith("Chap")]
|
||||
|
||||
for chapter_dir in chapter_dirs:
|
||||
chapter_number = int(os.path.basename(os.path.normpath(chapter_dir))[-2:])
|
||||
|
||||
section_files = [os.path.join(chapter_dir, f) for f in os.listdir(chapter_dir) if os.path.isfile(os.path.join(chapter_dir, f))]
|
||||
|
||||
for section_path in section_files:
|
||||
section_number = os.path.basename(os.path.normpath(section_path)).split('.')[1]
|
||||
|
||||
with open(section_path, 'r') as section_file:
|
||||
section_file_contents = section_file.read()
|
||||
|
||||
section_parts = [ x.strip() for x in section_file_contents.split("##") if x != '' ]
|
||||
|
||||
for section_part in section_parts:
|
||||
try:
|
||||
part_header, remainder = section_part.split("\n\n", 1)
|
||||
except ValueError:
|
||||
# This will occur when a section has no exercises
|
||||
continue
|
||||
|
||||
starred = False
|
||||
try:
|
||||
part_number = int(part_header.split('-')[1]) # get number after dash
|
||||
except ValueError:
|
||||
# It's got a star after it
|
||||
starred = True
|
||||
part_number = int(part_header.split('-')[1].split(' ')[0])
|
||||
|
||||
part_question, remainder = remainder.split('\n\n', 1)
|
||||
part_question = part_question[1:] # trim leading >
|
||||
|
||||
part_answer = remainder.strip()
|
||||
|
||||
new_rows.append((part_number, part_question, part_answer, chapter_number, section_number, 1 if starred else 0))
|
||||
|
||||
cur.executemany("insert into problem values (?, ?, ?, ?, ?, ?)", new_rows)
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parser.parse_args()
|
||||
sys.exit(main(args))
|
|
@ -0,0 +1,311 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
renderMathInElement(document.body, {
|
||||
delimiters: [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\(", right: "\\)", display: false},
|
||||
{left: "\\begin{equation}", right: "\\end{equation}", display: true},
|
||||
{left: "\\begin{align}", right: "\\end{align}", display: true},
|
||||
{left: "\\begin{alignat}", right: "\\end{alignat}", display: true},
|
||||
{left: "\\begin{gather}", right: "\\end{gather}", display: true},
|
||||
{left: "\\begin{CD}", right: "\\end{CD}", display: true},
|
||||
{left: "\\[", right: "\\]", display: true}
|
||||
],
|
||||
throwOnError : false
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<title>Practice Exam</title>
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin-left: 20%;
|
||||
margin-right: 20%;
|
||||
}
|
||||
|
||||
summary {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media screen and (max-width:1000px) {
|
||||
body {
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Practice Exam</h1>
|
||||
<h2>24.3-6</h2>
|
||||
<p>We are given a directed graph $G = (V, E)$ on which each edge $(u, v) \in E$ has an associated value $r(u, v)$, which is a real number in the range $0 \le r(u, v) \le 1$ that represents the reliability of a communication channel from vertex $u$ to vertex $v$. We interpret $r(u, v)$ as the probability that the channel from $u$ to $v$ will not fail, and we assume that these probabilities are independent. Give an efficient algorithm to find the most reliable path between two given vertices.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Removed)</p>
|
||||
</details>
|
||||
<h2>22.3-2</h2>
|
||||
<p>Show how depth-first search works on the graph of Figure 22.6. Assume that the <strong>for</strong> loop of lines 5–7 of the $\text{DFS}$ procedure considers the vertices in alphabetical order, and assume that each adjacency list is ordered alphabetically. Show the discovery and finishing times for each vertex, and show the classification of each edge.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>The following table gives the discovery time and finish time for each vetex in the graph.</p>
|
||||
<p>See the <a href="https://github.com/walkccc/CLRS-cpp/blob/master/Chap22/22.3.cpp">C++ demo</a>.</p>
|
||||
<p>$$
|
||||
\begin{array}{ccc}
|
||||
\text{Vertex} & \text{Discovered} & \text{Finished} \\
|
||||
\hline
|
||||
q & 1 & 16 \\
|
||||
r & 17 & 20 \\
|
||||
s & 2 & 7 \\
|
||||
t & 8 & 15 \\
|
||||
u & 18 & 19 \\
|
||||
v & 3 & 6 \\
|
||||
w & 4 & 5 \\
|
||||
x & 9 & 12 \\
|
||||
y & 13 & 14 \\
|
||||
z & 10 & 11
|
||||
\end{array}
|
||||
$$</p>
|
||||
<ul>
|
||||
<li><strong>Tree edges:</strong> $(q, s)$, $(s, v)$, $(v, w)$, $(q, t)$, $(t, x)$, $(x, z)$, $(t, y)$, $(r, u)$.</li>
|
||||
<li><strong>Back edges:</strong> $(w, s)$, $(z, x)$, $(y, q)$.</li>
|
||||
<li><strong>Forward edges:</strong> $(q, w)$.</li>
|
||||
<li><strong>Cross edges:</strong> $(r, y)$, $(u, y)$.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<h2>26.2-11</h2>
|
||||
<p>The <strong><em>edge connectivity</em></strong> of an undirected graph is the minimum number $k$ of edges that must be removed to disconnect the graph. For example, the edge connectivity of a tree is $1$, and the edge connectivity of a cyclic chain of vertices is $2$. Show how to determine the edge connectivity of an undirected graph $G = (V, E)$ by running a maximum-flow algorithm on at most $|V|$ flow networks, each having $O(V)$ vertices and $O(E)$ edges.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Create an directed version of the graph. Then create a flow network out of it, resolving all antiparallel edges. All edges' capacities are set to $1$. Pick any vertex that wasn't created for antiparallel workaround as the sink and run maximum-flow algorithm with all vertexes that aren't for antipararrel workaround (except the sink) as sources. Find the minimum value out of all $|V| - 1$ maximum flow values.</p>
|
||||
</details>
|
||||
<h2>34.2-9</h2>
|
||||
<p>Prove that $\text P \subseteq \text{co-NP}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>16.4-4 *</h2>
|
||||
<p>Let $S$ be a finite set and let $S_1, S_2, \ldots, S_k$ be a partition of $S$ into nonempty disjoint subsets. Define the structure $(S, \mathcal I)$ by the condition that $\mathcal I = \{A: \mid A \cap S_i \mid \le 1$ for $i = 1, 2, \ldots, k\}$. Show that $(S, \mathcal I)$ is a matroid. That is, the set of all sets $A$ that contain at most one member of each subset in the partition determines the independent sets of a matroid.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Suppose $X \subset Y$ and $Y \in \mathcal I$. Then $(X \cap S_i) \subset (Y \cap S_i)$ for all $i$, so</p>
|
||||
<p>$$|X \cap S_i| \le |Y \cap S_i| \le 1$$</p>
|
||||
<p>for all $1 \le i \le k$. Therefore $\mathcal M$ is closed under inclusion.</p>
|
||||
<p>Now Let $A, B \in \mathcal I$ with $|A| > |B|$. Then there must exist some $j$ such that $|A \cap S_j| = 1$ but $|B \cap S_j| = 0$. Let $a \in A \cap S_j$. Then $a \notin B$ and $|(B \cup \{a\}) \cap S_j| = 1$. Since</p>
|
||||
<p>$$|(B \cup \{a\}) \cap S_i| = |B \cap S_i| \le 1$$</p>
|
||||
<p>for all $i \ne j$, we must have $B \cup \{a\} \in \mathcal I$. Therefore $\mathcal M$ is a matroid.</p>
|
||||
</details>
|
||||
<h2>24.2-3</h2>
|
||||
<p>The PERT chart formulation given above is somewhat unnatural. In a more natural structure, vertices would represent jobs and edges would represent sequencing constraints; that is, edge $(u, v)$ would indicate that job $u$ must be performed before job $v$. We would then assign weights to vertices, not edges. Modify the $\text{DAG-SHORTEST-PATHS}$ procedure so that it finds a longest path in a directed acyclic graph with weighted vertices in linear time.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>There are two ways to transform a PERT chart $G = (V, E)$ with weights on the vertices to a PERT chart $G' = (V', E')$ with weights on edges. Both ways satisfy $|V'| \le 2|V|$ and $|E'| \le |V| + |E|$, so we can scan $G'$ using the same algorithm to find the longest path through a directed acyclic graph.</p>
|
||||
<p>In the first way, we transform each vertex $v \in V$ into two vertices $v'$ and $v''$ in $V'$. All edges in $E$ that enters $V$ will also enter $V'$ in $E'$, and all edges in $E$ that leaves $V$ will leave $V''$ in $E'$. Thus, if $(u, v) \in E$, then $(u'', v') \in E'$. All such edges have weight 0, so we can put edges $(v', v'')$ into $E'$ for all vertices $v \in V$, and these edges are given the weight of the corresponding vertex $v$ in $G$. Finally, we get $|V'| \le 2|V|$ and $|E'| \le |V| + |E|$, and the edge weight of each path in $G'$ equals the vertex weight of the corresponding path in $G$.</p>
|
||||
<p>In the second way, we leave vertices in $V$, but try to add one new source vertex $s$ to $V'$, given that $V' = V \cup \{s\}$. All edges of $E$ are in $E'$, and $E'$ also includes an edge $(s, v)$ for every vertex $v \in V$ that has in-degree 0 in $G$. Thus, the only vertex with in-degree 0 in $G'$ is the new source $s$. The weight of edge $(u, v) \in E'$ is the weight of vertex $v$ in $G$. We have the weight of each entering edge in $G'$ is the weight of the vertex it enters in $G$.</p>
|
||||
</details>
|
||||
<h2>15.4-4</h2>
|
||||
<p>Show how to compute the length of an $\text{LCS}$ using only $2 \cdot \min(m, n)$ entries in the $c$ table plus $O(1)$ additional space. Then show how to do the same thing, but using $\min(m, n)$ entries plus $O(1)$ additional space.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Since we only use the previous row of the $c$ table to compute the current row, we compute as normal, but when we go to compute row $k$, we free row $k - 2$ since we will never need it again to compute the length. To use even less space, observe that to compute $c[i, j]$, all we need are the entries $c[i − 1, j]$, $c[i − 1, j − 1]$, and $c[i, j − 1]$. Thus, we can free up entry-by-entry those from the previous row which we will never need again, reducing the space requirement to $\min(m, n)$. Computing the next entry from the three that it depends on takes $O(1)$ time and space.</p>
|
||||
</details>
|
||||
<h2>15.1-4</h2>
|
||||
<p>Modify $\text{MEMOIZED-CUT-ROD}$ to return not only the value but the actual solution, too.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<pre lang="cpp"><code>MEMOIZED-CUT-ROD(p, n)
|
||||
let r[0..n] and s[0..n] be new arrays
|
||||
for i = 0 to n
|
||||
r[i] = -∞
|
||||
(val, s) = MEMOIZED-CUT-ROD-AUX(p, n, r, s)
|
||||
print "The optimal value is" val "and the cuts are at" s
|
||||
j = n
|
||||
while j > 0
|
||||
print s[j]
|
||||
j = j - s[j]
|
||||
</code></pre>
|
||||
<pre lang="cpp"><code>MEMOIZED-CUT-ROD-AUX(p, n, r, s)
|
||||
if r[n] ≥ 0
|
||||
return r[n]
|
||||
if n == 0
|
||||
q = 0
|
||||
else q = -∞
|
||||
for i = 1 to n
|
||||
(val, s) = MEMOIZED-CUT-ROD-AUX(p, n - i, r, s)
|
||||
if q < p[i] + val
|
||||
q = p[i] + val
|
||||
s[n] = i
|
||||
r[n] = q
|
||||
return (q, s)
|
||||
</code></pre>
|
||||
</details>
|
||||
<h2>26.3-4 *</h2>
|
||||
<p>A <strong><em>perfect matching</em></strong> is a matching in which every vertex is matched. Let $G = (V, E)$ be an undirected bipartite graph with vertex partition $V = L \cup R$, where $|L| = |R|$. For any $X \subseteq V$, define the <strong><em>neighborhood</em></strong> of $X$ as</p>
|
||||
<blockquote>
|
||||
<p>$$N(X) = \{y \in V: (x, y) \in E \text{ for some } x \in X\},$$</p>
|
||||
<p>that is, the set of vertices adjacent to some member of $X$. Prove <strong><em>Hall's theorem</em></strong>: there exists a perfect matching in $G$ if and only if $|A| \le |N(A)|$ for every subset $A \subseteq L$.</p>
|
||||
</blockquote>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>First suppose there exists a perfect matching in $G$. Then for any subset $A \subseteq L$, each vertex of $A$ is matched with a neighbor in $R$, and since it is a matching, no two such vertices are matched with the same vertex in $R$. Thus, there are at least $|A|$ vertices in the neighborhood of $A$.</p>
|
||||
<p>Now suppose that $|A| \le |N(A)|$ for all $A \subseteq L$. Run Ford-Fulkerson on the corresponding flow network. The flow is increased by $1$ each time an augmenting path is found, so it will suffice to show that this happens $|L|$ times. Suppose the while loop has run fewer than $L$ times, but there is no augmenting path. Then fewer than $L$ edges from $L$ to $R$ have flow $1$.</p>
|
||||
<p>Let $v_1 \in L$ be such that no edge from $v_1$ to a vertex in $R$ has nonzero flow. By assumption, $v_1$ has at least one neighbor $v_1' \in R$. If any of $v_1$'s neighbors are connected to $t$ in $G_f$ then there is a path, so assume this is not the case. Thus, there must be some edge $(v_2, v_1)$ with flow $1$. By assumption, $N(\{v_1, v_2\}) \ge 2$, so there must exist $v_2' \ne v_1'$ such that $v_2'\in N(\{v_1, v_2 \})$. If $(v_2', t)$ is an edge in the residual network we're done since $v_2'$ must be a neighbor of $v_2$, so $s$, $v_1$, $v_1'$, $v_2$, $v_2'$, and $t$ is a path in $G_f$. Otherwise $v_2'$ must have a neighbor $v_3 \in L$ such that $(v_3, v_2')$ is in $G_f$. Specifically, $v_3 \ne v_1$ since $(v_3, v_2')$ has flow $1$, and $v_3 \ne v_2$ since $(v_2, v_1')$ has flow $1$, so no more flow can leave $v_2$ without violating conservation of flow. Again by our hypothesis, $N(\{v_1, v_2, v_3\}) \ge 3$, so there is another neighbor $v_3' \in R$.</p>
|
||||
<p>Continuing in this fashion, we keep building up the neighborhood $v_i'$, expanding each time we find that $(v_i', t)$ is not an edge in $G_f$. This cannot happen $L$ times, since we have run the Ford-Fulkerson while-loop fewer than $|L|$ times, so there still exist edges into $t$ in $G_f$. Thus, the process must stop at some vertex $v_k'$, and we obtain an augmenting path</p>
|
||||
<p>$$s, v_1, v_1', v_2, v_2', v_3, \ldots, v_k, v_k', t,$$</p>
|
||||
<p>contradicting our assumption that there was no such path. Therefore the while loop runs at least $|L|$ times. By Corollary 26.3 the flow strictly increases each time by $f_p$. By Theorem 26.10 $f_p$ is an integer. In particular, it is equal to $1$. This implies that $|f| \ge |L|$. It is clear that $|f| \le |L|$, so we must have $|f| = |L|$. By Corollary 26.11 this is the cardinality of a maximum matching. Since $|L| = |R|$, any maximum matching must be a perfect matching.</p>
|
||||
</details>
|
||||
<h2>22.2-2</h2>
|
||||
<p>Show the $d$ and $\pi$ values that result from running breadth-first search on the undirected graph of Figure 22.3, using vertex $u$ as the source.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>$$
|
||||
\begin{array}{c|cccccc}
|
||||
\text{vertex} & r & s & t & u & v & w & x & y \\
|
||||
\hline
|
||||
d & 4 & 3 & 1 & 0 & 5 & 2 & 1 & 1 \\
|
||||
\pi & s & w & u & \text{NIL} & r & t & u & u
|
||||
\end{array}
|
||||
$$</p>
|
||||
</details>
|
||||
<h2>4.4-7</h2>
|
||||
<p>Draw the recursion tree for $T(n) = 4T(\lfloor n / 2 \rfloor) + cn$, where $c$ is a constant, and provide a tight asymptotic bound on its solution. Verify your answer with the substitution method.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<ul>
|
||||
<li>
|
||||
<p>The subproblem size for a node at depth $i$ is $n / 2^i$.</p>
|
||||
<p>Thus, the tree has $\lg n + 1$ levels and $4^{\lg n} = n^{\lg 4} = n^2$ leaves.</p>
|
||||
<p>The total cost over all nodes at depth $i$, for $i = 0, 1, 2, \ldots, \lg n - 1$, is $4^i(cn / 2^i) = 2^icn$.</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & = \sum_{i = 0}^{\lg n - 1} 2^icn + \Theta(n^2) \\
|
||||
& = \frac{2^{\lg n} - 1}{2 - 1}cn + \Theta(n^2) \\
|
||||
& = \Theta(n^2).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>For $O(n^2)$, we guess $T(n) \le dn^2 - cn$,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & \le 4d(n / 2)^2 - 4c(n / 2) + cn \\
|
||||
& = dn^2 - cn.
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>For $\Omega(n^2)$, we guess $T(n) \ge dn^2 - cn$,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & \ge 4d(n / 2)^2 - 4c(n / 2) + cn \\
|
||||
& = dn^2 - cn.
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<h2>15.2-4</h2>
|
||||
<p>Describe the subproblem graph for matrix-chain multiplication with an input chain of length $n$. How many vertices does it have? How many edges does it have, and which edges are they?</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>The vertices of the subproblem graph are the ordered pair $v_{ij}$, where $i \le j$.</p>
|
||||
<ul>
|
||||
<li>If $i = j$, the vertex $v_{ij}$ has no output edge.</li>
|
||||
<li>If $i < j$, for each $k$, s.t. $i \le k < j$, the subproblem graph contains edges $(v_{ij}, v_{ik})$ and $(v_{ij}, v_{k+1, j})$, and these edges indicate that to solve the subproblem of optimally parenthesizing the product $A_i \cdots A_j$, we need to solve subproblems of optimally parenthesizing the products $A_i \cdots A_k$ and $A_{k + 1} \cdots A_j$.</li>
|
||||
</ul>
|
||||
<p>The number of vertices is</p>
|
||||
<p>$$\sum_{i = 1}^n \sum_{j = i}^n = \frac{n(n + 1)}{2}.$$</p>
|
||||
<p>The number of edges is</p>
|
||||
<p>$$\sum_{i = 1}^n \sum_{j = i}^n (j - i) = \frac{(n - 1)n(n + 1)}{6}.$$</p>
|
||||
</details>
|
||||
<h2>24.3-5</h2>
|
||||
<p>Professor Newman thinks that he has worked out a simpler proof of correctness for Dijkstra's algorithm. He claims that Dijkstra's algorithm relaxes the edges of every shortest path in the graph in the order in which they appear on the path, and therefore the path-relaxation property applies to every vertex reachable from the source. Show that the professor is mistaken by constructing a directed graph for which Dijkstra's algorithm could relax the edges of a shortest path out of order.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Removed)</p>
|
||||
</details>
|
||||
<h2>26.2-12</h2>
|
||||
<p>Suppose that you are given a flow network $G$, and $G$ has edges entering the source $s$. Let $f$ be a flow in $G$ in which one of the edges $(v, s)$ entering the source has $f(v, s) = 1$. Prove that there must exist another flow $f'$ with $f'(v, s) = 0$ such that $|f| = |f'|$. Give an $O(E)$-time algorithm to compute $f'$, given $f$, and assuming that all edge capacities are integers.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Removed)</p>
|
||||
</details>
|
||||
<h2>22.2-8 *</h2>
|
||||
<p>The <strong><em>diameter</em></strong> of a tree $T = (V, E)$ is defined as $\max_{u,v \in V} \delta(u, v)$, that is, the largest of all shortest-path distances in the tree. Give an efficient algorithm to compute the diameter of a tree, and analyze the running time of your algorithm.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Suppose that a and b are the endpoints of the path in the tree which achieve the diameter, and without loss of generality assume that $a$ and $b$ are the unique pair which do so. Let $s$ be any vertex in $T$. We claim that the result of a single $\text{BFS}$ will return either $a$ or $b$ (or both) as the vertex whose distance from $s$ is greatest.</p>
|
||||
<p>To see this, suppose to the contrary that some other vertex $x$ is shown to be furthest from $s$. (Note that $x$ cannot be on the path from $a$ to $b$, otherwise we could extend). Then we have</p>
|
||||
<p>$$d(s, a) < d(s, x)$$</p>
|
||||
<p>and</p>
|
||||
<p>$$d(s, b) < d(s, x).$$</p>
|
||||
<p>Let $c$ denote the vertex on the path from $a$ to $b$ which minimizes $d(s, c)$. Since the graph is in fact a tree, we must have</p>
|
||||
<p>$$d(s, a) = d(s, c) + d(c, a)$$</p>
|
||||
<p>and</p>
|
||||
<p>$$d(s, b) = d(s, c) + d(c, b).$$</p>
|
||||
<p>(If there were another path, we could form a cycle). Using the triangle inequality and inequalities and equalities mentioned above we must have</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
d(a, b) + 2d(s, c) & = d(s, c) + d(c, b) + d(s, c) + d(c, a) \\
|
||||
& < d(s, x) + d(s, c) + d(c, b).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
<p>I claim that $d(x, b) = d(s, x) + d(s, b)$. If not, then by the triangle inequality we must have a strict less-than. In other words, there is some path from $x$ to $b$ which does not go through $c$. This gives the contradiction, because it implies there is a cycle formed by concatenating these paths. Then we have</p>
|
||||
<p>$$d(a, b) < d(a, b) + 2d(s, c) < d(x, b).$$</p>
|
||||
<p>Since it is assumed that $d(a, b)$ is maximal among all pairs, we have a contradiction. Therefore, since trees have $|V| - 1$ edges, we can run $\text{BFS}$ a single time in $O(V)$ to obtain one of the vertices which is the endpoint of the longest simple path contained in the graph. Running $\text{BFS}$ again will show us where the other one is, so we can solve the diameter problem for trees in $O(V)$.</p>
|
||||
</details>
|
||||
<h2>26.3-3</h2>
|
||||
<p>Let $G = (V, E)$ be a bipartite graph with vertex partition $V = L \cup R$, and let $G'$ be its corresponding flow network. Give a good upper bound on the length of any augmenting path found in $G'$ during the execution of $\text{FORD-FULKERSON}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Removed)</p>
|
||||
</details>
|
||||
<h2>26.2-3</h2>
|
||||
<p>Show the execution of the Edmonds-Karp algorithm on the flow network of Figure 26.1(a).</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>If we perform a breadth first search where we consider the neighbors of a vertex as they appear in the ordering $\{s, v_1, v_2, v_3, v_4, t\}$, the first path that we will find is $s, v_1, v_3, t$. The min capacity of this augmenting path is $12$, so we send $12$ units along it. We perform a $\text{BFS}$ on the resulting residual network. This gets us the path $s, v_2, v_4, t$. The min capacity along this path is $4$, so we send $4$ units along it. Then, the only path remaining in the residual network is $\{s, v_2, v_4, v_3, t\}$ which has a min capacity of $7$, since that's all that's left, we find it in our $\text{BFS}$. Putting it all together, the total flow that we have found has a value of $23$.</p>
|
||||
</details>
|
||||
<h2>26.3-1</h2>
|
||||
<p>Run the Ford-Fulkerson algorithm on the flow network in Figure 26.8 (c) and show the residual network after each flow augmentation. Number the vertices in $L$ top to bottom from $1$ to $5$ and in $R$ top to bottom from $6$ to $9$. For each iteration, pick the augmenting path that is lexicographically smallest.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>First, we pick an augmenting path that passes through vertices 1 and 6. Then, we pick the path going through 2 and 8. Then, we pick the path going through 3 and 7. Then, the resulting residual graph has no path from $s$ to $t$. So, we know that we are done, and that we are pairing up vertices $(1, 6)$, $(2, 8)$, and $(3, 7)$. This number of unit augmenting paths agrees with the value of the cut where you cut the edges $(s, 3)$, $(6, t)$, and $(7, t)$.</p>
|
||||
</details>
|
||||
<h2>24.2-2</h2>
|
||||
<p>Suppose we change line 3 of $\text{DAG-SHORTEST-PATHS}$ to read</p>
|
||||
<blockquote>
|
||||
<pre lang="cpp"><code> 3 for the first |V| - 1 vertices, taken in topologically sorted order
|
||||
</code></pre>
|
||||
<p>Show that the procedure would remain correct.</p>
|
||||
</blockquote>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>When we reach vertex $v$, the last vertex in the topological sort, it must have $out\text-degree$ $0$. Otherwise there would be an edge pointing from a later vertex to an earlier vertex in the ordering, a contradiction. Thus, the body of the for-loop of line 4 is never entered for this final vertex, so we may as well not consider it.</p>
|
||||
</details>
|
||||
<h2>4.1-1</h2>
|
||||
<p>What does $\text{FIND-MAXIMUM-SUBARRAY}$ return when all elements of $A$ are negative?</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>It will return the greatest element of $A$.</p>
|
||||
</details>
|
||||
|
||||
|
||||
<footer>
|
||||
<hr>
|
||||
<p>
|
||||
built by nat, from questions written by Michelle Bodnar and Andrew Lohr (CLRS 3rd Edition), the solutions to which were prepared and organized by <a href="https://pengyuc.com/">Peng-Yu Chen</a> and <a href="https://github.com/walkccc/CLRS/graphs/contributors">contributors to walkccc/CLRS on GitHub</a>.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,295 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
renderMathInElement(document.body, {
|
||||
delimiters: [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\(", right: "\\)", display: false},
|
||||
{left: "\\begin{equation}", right: "\\end{equation}", display: true},
|
||||
{left: "\\begin{align}", right: "\\end{align}", display: true},
|
||||
{left: "\\begin{alignat}", right: "\\end{alignat}", display: true},
|
||||
{left: "\\begin{gather}", right: "\\end{gather}", display: true},
|
||||
{left: "\\begin{CD}", right: "\\end{CD}", display: true},
|
||||
{left: "\\[", right: "\\]", display: true}
|
||||
],
|
||||
throwOnError : false
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<title>Practice Exam</title>
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin-left: 20%;
|
||||
margin-right: 20%;
|
||||
}
|
||||
|
||||
summary {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media screen and (max-width:1000px) {
|
||||
body {
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Practice Exam</h1>
|
||||
<h2>15.3-6</h2>
|
||||
<p>Imagine that you wish to exchange one currency for another. You realize that instead of directly exchanging one currency for another, you might be better off making a series of trades through other currencies, winding up with the currency you want. Suppose that you can trade $n$ different currencies, numbered $1, 2, \ldots, n$, where you start with currency $1$ and wish to wind up with currency $n$. You are given, for each pair of currencies $i$ and $j$ , an exchange rate $r_{ij}$, meaning that if you start with $d$ units of currency $i$ , you can trade for $dr_{ij}$ units of currency $j$. A sequence of trades may entail a commission, which depends on the number of trades you make. Let $c_k$ be the commission that you are charged when you make $k$ trades. Show that, if $c_k = 0$ for all $k = 1, 2, \ldots, n$, then the problem of finding the best sequence of exchanges from currency $1$ to currency $n$ exhibits optimal substructure. Then show that if commissions $c_k$ are arbitrary values, then the problem of finding the best sequence of exchanges from currency $1$ to currency $n$ does not necessarily exhibit optimal substructure.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>First we assume that the commission is always zero. Let $k$ denote a currency which appears in an optimal sequence $s$ of trades to go from currency $1$ to currency $n$. $p_k$ denote the first part of this sequence which changes currencies from $1$ to $k$ and $q_k$ denote the rest of the sequence. Then $p_k$ and $q_k$ are both optimal sequences for changing from $1$ to $k$ and $k$ to $n$ respectively. To see this, suppose that $p_k$ wasn't optimal but that $p_k'$ was. Then by changing currencies according to the sequence $p_k'q_k$ we would have a sequence of changes which is better than $s$, a contradiction since $s$ was optimal. The same argument applies to $q_k$.</p>
|
||||
<p>Now suppose that the commissions can take on arbitrary values. Suppose we have currencies $1$ through $6$, and $r_{12} = r_{23} = r_{34} = r_{45} = 2$, $r_{13} = r_{35} = 6$, and all other exchanges are such that $r_{ij} = 100$. Let $c_1 = 0$, $c_2 = 1$, and $c_k = 10$ for $k \ge 3$.</p>
|
||||
<p>The optimal solution in this setup is to change $1$ to $3$, then $3$ to $5$, for a total cost of $13$. An optimal solution for changing $1$ to $3$ involves changing $1$ to $2$ then $2$ to $3$, for a cost of $5$, and an optimal solution for changing $3$ to $5$ is to change $3$ to $4$ then $4$ to $5$, for a total cost of $5$. However, combining these optimal solutions to subproblems means making more exchanges overall, and the total cost of combining them is $18$, which is not optimal.</p>
|
||||
</details>
|
||||
<h2>12.2-3</h2>
|
||||
<p>Write the $\text{TREE-PREDECESSOR}$ procedure.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<pre lang="cpp"><code>TREE-PREDECESSOR(x)
|
||||
if x.left != NIL
|
||||
return TREE-MAXIMUM(x.left)
|
||||
y = x.p
|
||||
while y != NIL and x == y.left
|
||||
x = y
|
||||
y = y.p
|
||||
return y
|
||||
</code></pre>
|
||||
</details>
|
||||
<h2>4.3-7</h2>
|
||||
<p>Using the master method in Section 4.5, you can show that the solution to the recurrence $T(n) = 4T(n / 3) + n$ is $T(n) = \Theta(n^{\log_3 4})$. Show that a substitution proof with the assumption $T(n) \le cn^{\log_3 4}$ fails. Then show how to subtract off a lower-order term to make the substitution proof work.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>We guess $T(n) \le cn^{\log_3 4}$ first,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & \le 4c(n / 3)^{\log_3 4} + n \\
|
||||
& = cn^{\log_3 4} + n.
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
<p>We stuck here.</p>
|
||||
<p>We guess $T(n) \le cn^{\log_3 4} - dn$ again,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & \le 4(c(n / 3)^{\log_3 4} - dn / 3) + n \\
|
||||
& = 4(cn^{\log_3 4} / 4 - dn / 3) + n \\
|
||||
& = cn^{\log_3 4} - \frac{4}{3}dn + n \\
|
||||
& \le cn^{\log_3 4} - dn,
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
<p>where the last step holds for $d \ge 3$.</p>
|
||||
</details>
|
||||
<h2>16.2-1</h2>
|
||||
<p>Prove that the fractional knapsack problem has the greedy-choice property.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Let $I$ be the following instance of the knapsack problem: Let $n$ be the number of items, let $v_i$ be the value of the $i$th item, let $w_i$ be the weight of the $i$th item and let $W$ be the capacity. Assume the items have been ordered in increasing order by $v_i / w_i$ and that $W \ge w_n$.</p>
|
||||
<p>Let $s = (s_1, s_2, \ldots, s_n)$ be a solution. The greedy algorithm works by assigning $s_n = \min(w_n, W)$, and then continuing by solving the subproblem</p>
|
||||
<p>$$I' = (n - 1, \{v_1, v_2, \ldots, v_{n - 1}\}, \{w_1, w_2, \ldots, w_{n - 1}\}, W - w_n)$$</p>
|
||||
<p>until it either reaches the state $W = 0$ or $n = 0$.</p>
|
||||
<p>We need to show that this strategy always gives an optimal solution. We prove this by contradiction. Suppose the optimal solution to $I$ is $s_1, s_2, \ldots, s_n$, where $s_n < \min(w_n, W)$. Let $i$ be the smallest number such that $s_i > 0$. By decreasing $s_i$ to $\max(0, W - w_n)$ and increasing $s_n$ by the same amount, we get a better solution. Since this a contradiction the assumption must be false. Hence the problem has the greedy-choice property.</p>
|
||||
</details>
|
||||
<h2>7.3-2</h2>
|
||||
<p>When $\text{RANDOMIZED-QUICKSORT}$ runs, how many calls are made to the random number generator $\text{RANDOM}$ in the worst case? How about in the best case? Give your answer in terms of $\Theta$-notation.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>In the worst case, the number of calls to $\text{RANDOM}$ is</p>
|
||||
<p>$$T(n) = T(n - 1) + 1 = n = \Theta(n).$$</p>
|
||||
<p>As for the best case,</p>
|
||||
<p>$$T(n) = 2T(n / 2) + 1 = \Theta(n).$$</p>
|
||||
<p>This is not too surprising, because each third element (at least) gets picked as pivot.</p>
|
||||
</details>
|
||||
<h2>34.2-3</h2>
|
||||
<p>Show that if $\text{HAM-CYCLE} \in P$, then the problem of listing the vertices of a hamiltonian cycle, in order, is polynomial-time solvable.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>4.4-2</h2>
|
||||
<p>Use a recursion tree to determine a good asymptotic upper bound on the recurrence $T(n) = T(n / 2) + n^2$. Use the substitution method to verify your answer.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<ul>
|
||||
<li>
|
||||
<p>The subproblem size for a node at depth $i$ is $n / 2^i$.</p>
|
||||
<p>Thus, the tree has $\lg n + 1$ levels and $1^{\lg n} = 1$ leaf.</p>
|
||||
<p>The total cost over all nodes at depth $i$, for $i = 0, 1, 2, \ldots, \lg{n - 1}$, is $1^i (n / 2^i)^2 = (1 / 4)^i n^2$.</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & = \sum_{i = 0}^{\lg n - 1} \Big(\frac{1}{4}\Big)^i n^2 + \Theta(1) \\
|
||||
& < \sum_{i = 0}^\infty \Big(\frac{1}{4}\Big)^i n^2 + \Theta(1) \\
|
||||
& = \frac{1}{1 - (1 / 4)} n^2 + \Theta(1) \\
|
||||
& = \Theta(n^2).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>We guess $T(n) \le cn^2$,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & \le c(n / 2)^2 + n^2 \\
|
||||
& = cn^2 / 4 + n^2 \\
|
||||
& = (c / 4 + 1)n^2 \\
|
||||
& \le cn^2,
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
<p>where the last step holds for $c \ge 4 / 3$.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<h2>26.3-3</h2>
|
||||
<p>Let $G = (V, E)$ be a bipartite graph with vertex partition $V = L \cup R$, and let $G'$ be its corresponding flow network. Give a good upper bound on the length of any augmenting path found in $G'$ during the execution of $\text{FORD-FULKERSON}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Removed)</p>
|
||||
</details>
|
||||
<h2>15.1-3</h2>
|
||||
<p>Consider a modification of the rod-cutting problem in which, in addition to a price $p_i$ for each rod, each cut incurs a fixed cost of $c$. The revenue associated with a solution is now the sum of the prices of the pieces minus the costs of making the cuts. Give a dynamic-programming algorithm to solve this modified problem.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>We can modify $\text{BOTTOM-UP-CUT-ROD}$ algorithm from section 15.1 as follows:</p>
|
||||
<pre lang="cpp"><code>MODIFIED-CUT-ROD(p, n, c)
|
||||
let r[0..n] be a new array
|
||||
r[0] = 0
|
||||
for j = 1 to n
|
||||
q = p[j]
|
||||
for i = 1 to j - 1
|
||||
q = max(q, p[i] + r[j - i] - c)
|
||||
r[j] = q
|
||||
return r[n]
|
||||
</code></pre>
|
||||
<p>We need to account for cost $c$ on every iteration of the loop in lines 5-6 but the last one, when $i = j$ (no cuts).</p>
|
||||
<p>We make the loop run to $j - 1$ instead of $j$, make sure $c$ is subtracted from the candidate revenue in line 6, then pick the greater of current best revenue $q$ and $p[j]$ (no cuts) in line 7.</p>
|
||||
</details>
|
||||
<h2>4.6-3 *</h2>
|
||||
<p>Show that case 3 of the master method is overstated, in the sense that the regularity condition $af(n / b) \le cf(n)$ for some constant $c < 1$ implies that there exists a constant $\epsilon > 0$ such that $f(n) = \Omega(n^{\log_b a + \epsilon})$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
af(n / b) & \le cf(n) \\
|
||||
\Rightarrow f(n / b) & \le \frac{c}{a} f(n) \\
|
||||
\Rightarrow f(n) & \le \frac{c}{a} f(bn) \\
|
||||
& = \frac{c}{a} \left(\frac{c}{a} f(b^2n)\right) \\
|
||||
& = \frac{c}{a} \left(\frac{c}{a}\left(\frac{c}{a} f(b^3n)\right)\right) \\
|
||||
& = \left(\frac{c}{a}\right)^i f(b^i n) \\
|
||||
\Rightarrow f(b^i n) & \ge \left(\frac{a}{c}\right)^i f(n).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
<p>Let $n = 1$, then we have</p>
|
||||
<p>$$f(b^i) \ge \left(\frac{a}{c}\right)^i f(1) \quad (*).$$</p>
|
||||
<p>Let $b^i = n \Rightarrow i = \log_b n$, then substitue back to equation $(*)$,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
f(n) & \ge \left(\frac{a}{c}\right)^{\log_b n} f(1) \\
|
||||
& \ge n^{\log_b \frac{a}{c}} f(1) \\
|
||||
& \ge n^{\log_b a + \epsilon} & \text{ where $\epsilon > 0$ because $\frac{a}{c} > a$ (recall that $c < 1$)} \\
|
||||
& = \Omega(n^{\log_b a + \epsilon}).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</details>
|
||||
<h2>16.4-2 *</h2>
|
||||
<p>Given an $m \times n$ matrix $T$ over some field (such as the reals), show that $(S, \mathcal I)$ is a matroid, where $S$ is the set of columns of $T$ and $A \in \mathcal I$ if and only if the columns in $A$ are linearly independent.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Let $c_1, \dots, c_m$ be the columns of $T$. Suppose $C = \{c_{i1}, \dots, c_{ik}\}$ is dependent. Then there exist scalars $d_1, \dots, d_k$ not all zero such that $\sum_{j = 1}^k d_jc_{ij} = 0$. By adding columns to $C$ and assigning them to have coefficient $0$ in the sum, we see that any superset of $C$ is also dependent. By contrapositive, any subset of an independent set must be independent.</p>
|
||||
<p>Now suppose that $A$ and $B$ are two independent sets of columns with $|A| > |B|$. If we couldn't add any column of $A$ to be whilst preserving independence then it must be the case that every element of $A$ is a linear combination of elements of $B$. But this implies that $B$ spans a $|A|$-dimensional space, which is impossible. Therefore, our independence system must satisfy the exchange property, so it is in fact a matroid.</p>
|
||||
</details>
|
||||
<h2>34.4-5</h2>
|
||||
<p>Show that the problem of determining the satisfiability of boolean formulas in disjunctive normal form is polynomial-time solvable.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>22.3-10</h2>
|
||||
<p>Modify the pseudocode for depth-first search so that it prints out every edge in the directed graph $G$, together with its type. Show what modifications, if any, you need to make if $G$ is undirected.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>If $G$ is undirected we don't need to make any modifications.</p>
|
||||
<p>See the <a href="https://github.com/walkccc/CLRS-cpp/blob/master/Chap22/22.3-10/22.3-10.cpp">C++ demo</a>.</p>
|
||||
<pre lang="cpp"><code>DFS-VISIT-PRINT(G, u)
|
||||
time = time + 1
|
||||
u.d = time
|
||||
u.color = GRAY
|
||||
for each vertex v ∈ G.Adj[u]
|
||||
if v.color == WHITE
|
||||
print "(u, v) is a tree edge."
|
||||
v.π = u
|
||||
DFS-VISIT-PRINT(G, v)
|
||||
else if v.color == GRAY
|
||||
print "(u, v) is a back edge."
|
||||
else if v.d > u.d
|
||||
print "(u, v) is a forward edge."
|
||||
else
|
||||
print "(u, v) is a cross edge."
|
||||
u.color = BLACK
|
||||
time = time + 1
|
||||
u.f = time
|
||||
</code></pre>
|
||||
</details>
|
||||
<h2>24.5-5</h2>
|
||||
<p>Let $G = (V, E)$ be a weighted, directed graph with no negative-weight edges. Let $s \in V$ be the source vertex, and suppose that we allow $v.\pi$ to be the predecessor of $v$ on <em>any</em> shortest path to $v$ from source $s$ if $v \in V - \{s\}$ is reachable from $s$, and $\text{NIL}$ otherwise. Give an example of such a graph $G$ and an assignment of $\pi$ values that produces a cycle in $G_\pi$. (By Lemma 24.16, such an assignment cannot be produced by a sequence of relaxation steps.)</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Suppose that we have a grap hon three vertices $\{s, u, v\}$ and containing edges $(s, u), (s, v), (u, v), (v, u)$ all with weight $0$. Then, there is a shortest path from $s$ to $v$ of $s$, $u$, $v$ and a shortest path from $s$ to $u$ of $s$ $v$, $u$. Based off of these, we could set $v.\pi = u$ and $u.\pi = v$. This then means that there is a cycle consisting of $u, v$ in $G_\pi$.</p>
|
||||
</details>
|
||||
<h2>34.5-7</h2>
|
||||
<p>The <strong><em>longest-simple-cycle problem</em></strong> is the problem of determining a simple cycle (no repeated vertices) of maximum length in a graph. Formulate a related decision problem, and show that the decision problem is $\text{NP-complete}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>22.5-2</h2>
|
||||
<p>Show how the procedure $\text{STRONGLY-CONNECTED-COMPONENTS}$ works on the graph of Figure 22.6. Specifically, show the finishing times computed in line 1 and the forest produced in line 3. Assume that the loop of lines 5–7 of $\text{DFS}$ considers vertices in alphabetical order and that the adjacency lists are in alphabetical order.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>The finishing times of each vertex were computed in exercise 22.3-2. The forest consists of 5 trees, each of which is a chain. We'll list the vertices of each tree in order from root to leaf: $r$, $u$, $q - y - t$, $x - z$, and $s - w - v$.</p>
|
||||
</details>
|
||||
<h2>26.4-4</h2>
|
||||
<p>Suppose that we have found a maximum flow in a flow network $G = (V, E)$ using a push-relabel algorithm. Give a fast algorithm to find a minimum cut in $G$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Removed)</p>
|
||||
</details>
|
||||
<h2>26.2-1</h2>
|
||||
<p>Prove that the summations in equation $\text{(26.6)}$ equal the summations in equation $\text{(26.7)}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Removed)</p>
|
||||
</details>
|
||||
<h2>12.2-7</h2>
|
||||
<p>An alternative method of performing an inorder tree walk of an $n$-node binary search tree finds the minimum element in the tree by calling $\text{TREE-MINIMUM}$ and then making $n - 1$ calls to $\text{TREE-SUCCESSOR}$. Prove that this algorithm runs in $\Theta(n)$ time.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>To show this bound on the runtime, we will show that using this procedure, we traverse each edge twice. This will suffice because the number of edges in a tree is one less than the number of vertices.</p>
|
||||
<p>Consider a vertex of a BST, say $x$. Then, we have that the edge between $x.p$ and $x$ gets used when successor is called on $x.p$ and gets used again when it is called on the largest element in the subtree rooted at $x$. Since these are the only two times that that edge can be used, apart from the initial finding of tree minimum. We have that the runtime is $O(n)$. We trivially get the runtime is $\Omega(n)$ because that is the size of the output.</p>
|
||||
</details>
|
||||
<h2>16.2-7</h2>
|
||||
<p>Suppose you are given two sets $A$ and $B$, each containing $n$ positive integers. You can choose to reorder each set however you like. After reordering, let $a_i$ be the $i$th element of set $A$, and let $b_i$ be the $i$th element of set $B$. You then receive a payoff of $\prod_{i = 1}^n a_i^{b_i}$. Give an algorithm that will maximize your payoff. Prove that your algorithm maximizes the payoff, and state its running time.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Since an idential permutation of both sets doesn't affect this product, suppose that $A$ is sorted in ascending order. Then, we will prove that the product is maximized when $B$ is also sorted in ascending order. To see this, suppose not, that is, there is some $i < j$ so that $a_i < a_j$ and $b_i > b_j$. Then, consider only the contribution to the product from the indices $i$ and $j$. That is, $a_i^{b_i}a_j^{b_j}$, then, if we were to swap the order of $b_i$ and $b_j$, we would have that contribution be $a_i^{b_j}a_j^{b_i}$. we can see that this is larger than the previous expression because it differs by a factor of $\left(\frac{a_j}{a_i}\right)^{b_i - b_j}$ which is bigger than one. So, we couldn't of maximized the product with this ordering on $B$.</p>
|
||||
</details>
|
||||
|
||||
|
||||
<footer>
|
||||
<hr>
|
||||
<p>
|
||||
built by nat, from questions written by Michelle Bodnar and Andrew Lohr (CLRS 3rd Edition), the solutions to which were prepared and organized by <a href="https://pengyuc.com/">Peng-Yu Chen</a> and <a href="https://github.com/walkccc/CLRS/graphs/contributors">contributors to walkccc/CLRS on GitHub</a>.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,290 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
renderMathInElement(document.body, {
|
||||
delimiters: [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\(", right: "\\)", display: false},
|
||||
{left: "\\begin{equation}", right: "\\end{equation}", display: true},
|
||||
{left: "\\begin{align}", right: "\\end{align}", display: true},
|
||||
{left: "\\begin{alignat}", right: "\\end{alignat}", display: true},
|
||||
{left: "\\begin{gather}", right: "\\end{gather}", display: true},
|
||||
{left: "\\begin{CD}", right: "\\end{CD}", display: true},
|
||||
{left: "\\[", right: "\\]", display: true}
|
||||
],
|
||||
throwOnError : false
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<title>Practice Exam</title>
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin-left: 20%;
|
||||
margin-right: 20%;
|
||||
}
|
||||
|
||||
summary {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media screen and (max-width:1000px) {
|
||||
body {
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Practice Exam</h1>
|
||||
<h2>24.4-5</h2>
|
||||
<p>Show how to modify the Bellman-Ford algorithm slightly so that when we use it to solve a system of difference constraints with $m$ inequalities on $n$ unknowns, the running time is $O(nm)$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>We can follow the advice of problem 14.4-7 and solve the system of constraints on a modified constraint graph in which there is no new vertex $v_0$. This is simply done by initializing all of the vertices to have a $d$ value of $0$ before running the iterated relaxations of Bellman Ford. Since we don't add a new vertex and the $n$ edges going from it to to vertex corresponding to each variable, we are just running Bellman Ford on a graph with $n$ vertices and $m$ edges, and so it will have a runtime of $O(mn)$.</p>
|
||||
</details>
|
||||
<h2>34.2-11</h2>
|
||||
<p>Let $G$ be a connected, undirected graph with at least $3$ vertices, and let $G^3$ be the graph obtained by connecting all pairs of vertices that are connected by a path in $G$ of length at most $3$. Prove that $G^3$ is hamiltonian. ($\textit{Hint:}$ Construct a spanning tree for $G$, and use an inductive argument.)</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>34.2-9</h2>
|
||||
<p>Prove that $\text P \subseteq \text{co-NP}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>34.5-5</h2>
|
||||
<p>The <strong><em>set-partition problem</em></strong> takes as input a set $S$ of numbers. The question is whether the numbers can be partitioned into two sets $A$ and $\bar A = S - A$ such that $\sum_{x \in A} x = \sum_{x \in \bar A} x$. Show that the set-partition problem is $\text{NP-complete}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>7.1-2</h2>
|
||||
<p>What value of $q$ does $\text{PARTITION}$ return when all elements in the array $A[p..r]$ have the same value? Modify $\text{PARTITION}$ so that $q = \lfloor (p + r) / 2 \rfloor$ when all elements in the array $A[p..r]$ have the same value.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>It returns $r$.</p>
|
||||
<p>We can modify $\text{PARTITION}$ by counting the number of comparisons in which $A[j] = A[r]$ and then subtracting half that number from the pivot index.</p>
|
||||
</details>
|
||||
<h2>22.4-2</h2>
|
||||
<p>Give a linear-time algorithm that takes as input a directed acyclic graph $G = (V, E)$ and two vertices $s$ and $t$, and returns the number of simple paths from $s$ to $t$ in $G$. For example, the directed acyclic graph of Figure 22.8 contains exactly four simple paths from vertex $p$ to vertex $v: pov$, $poryv$, $posryv$, and $psryv$. (Your algorithm needs only to count the simple paths, not list them.)</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>The algorithm works as follows. The attribute $u.paths$ of node $u$ tells the number of simple paths from $u$ to $v$, where we assume that $v$ is fixed throughout the entire process. First of all, a topo sort should be conducted and list the vertex between $u$, $v$ as $\{v[1], v[2], \dots, v[k - 1]\}$. To count the number of paths, we should construct a solution from $v$ to $u$. Let's call $u$ as $v[0]$ and $v$ as $v[k]$, to avoid overlapping subproblem, the number of paths between $v_k$ and $u$ should be remembered and used as $k$ decrease to $0$. Only in this way can we solve the problem in $\Theta(V + E)$.</p>
|
||||
<p>An bottom-up iterative version is possible only if the graph uses adjacency matrix so whether $v$ is adjacency to $u$ can be determined in $O(1)$ time. But building a adjacency matrix would cost $\Theta(|V|^2)$, so never mind.</p>
|
||||
<pre lang="cpp"><code>SIMPLE-PATHS(G, u, v)
|
||||
TOPO-SORT(G)
|
||||
let {v[1], v[2]..v[k - 1]} be the vertex between u and v
|
||||
v[0] = u
|
||||
v[k] = v
|
||||
for j = 0 to k - 1
|
||||
DP[j] = ∞
|
||||
DP[k] = 1
|
||||
return SIMPLE-PATHS-AID(G, DP, 0)
|
||||
</code></pre>
|
||||
<pre lang="cpp"><code>SIMPLE-PATHS-AID(G, DP, i)
|
||||
if i > k
|
||||
return 0
|
||||
else if DP[i] != ∞
|
||||
return DP[i]
|
||||
else
|
||||
DP[i] = 0
|
||||
for v[m] in G.adj[v[i]] and 0 < m ≤ k
|
||||
DP[i] += SIMPLE-PATHS-AID(G, DP, m)
|
||||
return DP[i]
|
||||
</code></pre>
|
||||
</details>
|
||||
<h2>4.2-6</h2>
|
||||
<p>How quickly can you multiply a $kn \times n$ matrix by an $n \times kn$ matrix, using Strassen's algorithm as a subroutine? Answer the same question with the order of the input matrices reversed.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<ul>
|
||||
<li>$(kn \times n)(n \times kn)$ produces a $kn \times kn$ matrix. This produces $k^2$ multiplications of $n \times n$ matrices.</li>
|
||||
<li>$(n \times kn)(kn \times n)$ produces an $n \times n$ matrix. This produces $k$ multiplications and $k - 1$ additions.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<h2>34.5-6</h2>
|
||||
<p>Show that the hamiltonian-path problem is $\text{NP-complete}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>16.2-2</h2>
|
||||
<p>Give a dynamic-programming solution to the $0$-$1$ knapsack problem that runs in $O(nW)$ time, where $n$ is the number of items and $W$ is the maximum weight of items that the thief can put in his knapsack.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Suppose we know that a particular item of weight $w$ is in the solution. Then we must solve the subproblem on $n − 1$ items with maximum weight $W − w$. Thus, to take a bottom-up approach we must solve the $0$-$1$ knapsack problem for all items and possible weights smaller than W. We'll build an $n + 1$ by $W + 1$ table of values where the rows are indexed by item and the columns are indexed by total weight. (The first row and column of the table will be a dummy row).</p>
|
||||
<p>For row $i$ column $j$, we decide whether or not it would be advantageous to include item i in the knapsack by comparing the total value of of a knapsack including items $1$ through $i − 1$ with max weight $j$, and the total value of including items $1$ through $i − 1$ with max weight $j − i.weight$ and also item $i$. To solve the problem, we simply examine the $n$, $W$ entry of the table to determine the maximum value we can achieve. To read off the items we include, start with entry $n$, $W$. In general, proceed as follows: if entry $i$, $j$ equals entry $i - 1$, $j$, don't include item $i$, and examine entry $i - 1$, $j$ next. If entry $i$, $j$ doesn't equal entry $i − 1$, $j$, include item $i$ and examine entry $i − 1$, $j − i$.weight next. See algorithm below for construction of table:</p>
|
||||
<pre lang="cpp"><code>0-1-KNAPSACK(n, W)
|
||||
Initialize an (n + 1) by (W + 1) table K
|
||||
for i = 1 to n
|
||||
K[i, 0] = 0
|
||||
for j = 1 to W
|
||||
K[0, j] = 0
|
||||
for i = 1 to n
|
||||
for j = 1 to W
|
||||
if j < i.weight
|
||||
K[i, j] = K[i - 1, j]
|
||||
else
|
||||
K[i, j] = max(K[i - 1, j], K[i - 1, j - i.weight] + i.value)
|
||||
</code></pre>
|
||||
</details>
|
||||
<h2>4.4-2</h2>
|
||||
<p>Use a recursion tree to determine a good asymptotic upper bound on the recurrence $T(n) = T(n / 2) + n^2$. Use the substitution method to verify your answer.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<ul>
|
||||
<li>
|
||||
<p>The subproblem size for a node at depth $i$ is $n / 2^i$.</p>
|
||||
<p>Thus, the tree has $\lg n + 1$ levels and $1^{\lg n} = 1$ leaf.</p>
|
||||
<p>The total cost over all nodes at depth $i$, for $i = 0, 1, 2, \ldots, \lg{n - 1}$, is $1^i (n / 2^i)^2 = (1 / 4)^i n^2$.</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & = \sum_{i = 0}^{\lg n - 1} \Big(\frac{1}{4}\Big)^i n^2 + \Theta(1) \\
|
||||
& < \sum_{i = 0}^\infty \Big(\frac{1}{4}\Big)^i n^2 + \Theta(1) \\
|
||||
& = \frac{1}{1 - (1 / 4)} n^2 + \Theta(1) \\
|
||||
& = \Theta(n^2).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>We guess $T(n) \le cn^2$,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & \le c(n / 2)^2 + n^2 \\
|
||||
& = cn^2 / 4 + n^2 \\
|
||||
& = (c / 4 + 1)n^2 \\
|
||||
& \le cn^2,
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
<p>where the last step holds for $c \ge 4 / 3$.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<h2>26.1-1</h2>
|
||||
<p>Show that splitting an edge in a flow network yields an equivalent network. More formally, suppose that flow network $G$ contains edge $(u, v)$, and we create a new flow network $G'$ by creating a new vertex $x$ and replacing $(u, v)$ by new edges $(u, x)$ and $(x, v)$ with $c(u, x) = c(x, v) = c(u, v)$. Show that a maximum flow in $G'$ has the same value as a maximum flow in $G$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Suppose the maximum flow of a graph $G = (V, E)$ with source $s$ and destination $t$ is $|f| = \sum{f(s, v)}$, where $v \in V$ are vertices in the maximum flow between $s$ and $t$.</p>
|
||||
<p>We know every vertex $v \in V$ must obey the Flow conservation rule. Therefore, if we can add or delete some vertices between $s$ and $t$ without changing $|f|$ or violating the Flow conversation rule, then the new graph $G' = (V', E')$ will have the same maximum flow as the original graph $G$, and that's why we can replace edge $(u, v)$ by new edges $(u, x)$ and $(x, v)$ with $c(u, x) = c(x, v) = c(u, v)$.</p>
|
||||
<p>After doing so, vertex $v_1$ and $v_2$ still obey the Flow conservation rule since the values flow in to or flow out of $v_1$ and $v_2$ do not change at all.
|
||||
Meanwhile, the value $|f| = \sum{f(s, v)}$ remains the same.</p>
|
||||
<p>In fact, we can split any edges in this way, even if two vertex $u$ and $v$ doesn't have any connection between them, we can still add a vertex $y$ and make $c(u, y) = c(y, v) = 0$.</p>
|
||||
<p>To conclude, we can transform any graph with or without antiparallel edges into an equivalent graph without antiparallel edges and have the same maximum flow value.</p>
|
||||
</details>
|
||||
<h2>3.2-8</h2>
|
||||
<p>Show that $k\ln k = \Theta(n)$ implies $k = \Theta(n / \lg n)$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>From the symmetry of $\Theta$,</p>
|
||||
<p>$$k\ln k = \Theta(n) \Rightarrow n = \Theta(k\ln k).$$</p>
|
||||
<p>Let's find $\ln n$,</p>
|
||||
<p>$$\ln n = \Theta(\ln(k\ln k)) = \Theta(\ln k + \ln\ln k) = \Theta(\ln k).$$</p>
|
||||
<p>Let's divide the two,</p>
|
||||
<p>$$\frac{n}{\ln n} = \frac{\Theta(k\ln k)}{\Theta(\ln k)} = \Theta\Big({\frac{k\ln k}{\ln k}}\Big) = \Theta(k).$$</p>
|
||||
</details>
|
||||
<h2>22.2-6</h2>
|
||||
<p>Give an example of a directed graph $G = (V, E)$, a source vertex $s \in V$, and a set of tree edges $E_\pi \subseteq 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 $\text{BFS}$ on $G$, no matter how the vertices are ordered in each adjacency list.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Let $G$ be the graph shown in the first picture, $G_\pi = (V, E_\pi)$ be the graph shown in the second picture, and $s$ be the source vertex.</p>
|
||||
<p>We could see that $E_\pi$ will never be produced by running BFS on $G$.</p>
|
||||
<center>
|
||||
![](../img/22.2-6-2.png)
|
||||
![](../img/22.2-6-1.png)
|
||||
</center>
|
||||
<ul>
|
||||
<li>If $y$ precedes $v$ in the $Adj[s]$. We'll dequeue $y$ before $v$, so $u.\pi$ and $x.\pi$ are both $y$. However, this is not the case.</li>
|
||||
<li>If $v$ preceded $y$ in the $Adj[s]$. We'll dequeue $v$ before $y$, so $u.\pi$ and $x.\pi$ are both $v$, which again isn't true.</li>
|
||||
</ul>
|
||||
<p>Nonetheless, the unique simple path in $G_\pi$ from $s$ to any vertex is a shortest path in $G$.</p>
|
||||
</details>
|
||||
<h2>4.6-3 *</h2>
|
||||
<p>Show that case 3 of the master method is overstated, in the sense that the regularity condition $af(n / b) \le cf(n)$ for some constant $c < 1$ implies that there exists a constant $\epsilon > 0$ such that $f(n) = \Omega(n^{\log_b a + \epsilon})$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
af(n / b) & \le cf(n) \\
|
||||
\Rightarrow f(n / b) & \le \frac{c}{a} f(n) \\
|
||||
\Rightarrow f(n) & \le \frac{c}{a} f(bn) \\
|
||||
& = \frac{c}{a} \left(\frac{c}{a} f(b^2n)\right) \\
|
||||
& = \frac{c}{a} \left(\frac{c}{a}\left(\frac{c}{a} f(b^3n)\right)\right) \\
|
||||
& = \left(\frac{c}{a}\right)^i f(b^i n) \\
|
||||
\Rightarrow f(b^i n) & \ge \left(\frac{a}{c}\right)^i f(n).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
<p>Let $n = 1$, then we have</p>
|
||||
<p>$$f(b^i) \ge \left(\frac{a}{c}\right)^i f(1) \quad (*).$$</p>
|
||||
<p>Let $b^i = n \Rightarrow i = \log_b n$, then substitue back to equation $(*)$,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
f(n) & \ge \left(\frac{a}{c}\right)^{\log_b n} f(1) \\
|
||||
& \ge n^{\log_b \frac{a}{c}} f(1) \\
|
||||
& \ge n^{\log_b a + \epsilon} & \text{ where $\epsilon > 0$ because $\frac{a}{c} > a$ (recall that $c < 1$)} \\
|
||||
& = \Omega(n^{\log_b a + \epsilon}).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</details>
|
||||
<h2>26.2-8</h2>
|
||||
<p>Suppose that we redefine the residual network to disallow edges into $s$. Argue that the procedure $\text{FORD-FULKERSON}$ still correctly computes a maximum flow.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Removed)</p>
|
||||
</details>
|
||||
<h2>26.1-2</h2>
|
||||
<p>Extend the flow properties and definitions to the multiple-source, multiple-sink problem. Show that any flow in a multiple-source, multiple-sink flow network corresponds to a flow of identical value in the single-source, single-sink network obtained by adding a supersource and a supersink, and vice versa.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Capacity constraint: for all $u, v \in V$, we require $0 \le f(u, v) \le c(u, v)$.</p>
|
||||
<p>Flow conservation: for all $u \in V - S - T$, we require $\sum_{v \in V} f(v, u) = \sum_{v \in V} f(u, v)$.</p>
|
||||
</details>
|
||||
<h2>34.2-10</h2>
|
||||
<p>Prove that if $\text{NP} \ne \text{co-NP}$, then $\text P \ne \text{NP}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>12.4-1</h2>
|
||||
<p>Prove equation $\text{(12.3)}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Consider all the possible positions of the largest element of the subset of $n + 3$ of size $4$. Suppose it were in position $i + 4$ for some $i \le n − 1$. Then, we have that there are $i + 3$ positions from which we can select the remaining three elements of the subset. Since every subset with different largest element is different, we get the total by just adding them all up (inclusion exclusion principle).</p>
|
||||
</details>
|
||||
<h2>34.1-3</h2>
|
||||
<p>Give a formal encoding of directed graphs as binary strings using an adjacency-matrix representation. Do the same using an adjacency-list representation. Argue that the two representations are polynomially related.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>A formal encoding of the adjacency matrix representation is to first encode an integer $n$ in the usual binary encoding representing the number of vertices. Then, there will be $n^2$ bits following. The value of bit $m$ will be $1$ if there is an edge from vertex $\lfloor m / n \rfloor$ to vertex $(m \mod n)$, and $0$ if there is not such an edge.</p>
|
||||
<p>An encoding of the adjacency list representation is a bit more finessed. We'll be using a different encoding of integers, call it $g(n)$. In particular, we will place a $0$ immediately after every bit in the usual representation. Since this only doubles the length of the encoding, it is still polynomially related. Also, the reason we will be using this encoding is because any sequence of integers encoded in this way cannot contain the string $11$ and must contain at least one $0$. Suppose that we have a vertex with edges going to the vertices indexed by $i_1, i_2, i_3, \dots, i_k$. Then, the encoding corresponding to that vertex is $g(i_1)11g(i_2)11 \dots 11g(i_k)1111$. Then, the encoding of the entire graph will be the concatenation of all the encodings of the vertices. As we are reading through, since we used this encoding of the indices of the vertices, we won’t ever be confused about where each of the vertex indices ends or when we are moving on to the next vertex's list.</p>
|
||||
<p>To go from the list to matrix representation, we can read off all the adjacent vertices, store them, sort them, and then output a row of the adjacency matrix. Since there is some small constant amount of space for the adjacency list representation for each vertex in the graph, the size of the encoding blows up by at most a factor of $O(n)$, which means that the size of the encoding overall is at most squared.</p>
|
||||
<p>To go in the other direction, it is just a matter of keeping track of the positions in a given row that have $1$'s, encoding those numerical values in the way described, and doing this for each row. Since we are only increasing the size of the encoding by a factor of at most $O(\lg n)$ (which happens in the dense graph case), we have that both of them are polynomially related.</p>
|
||||
</details>
|
||||
<h2>16.2-6 *</h2>
|
||||
<p>Show how to solve the fractional knapsack problem in $O(n)$ time.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>First compute the value of each item, defined to be it's worth divided by its weight. We use a recursive approach as follows, find the item of median value, which can be done in linear time as shown in chapter 9. Then sum the weights of all items whose value exceeds the median and call it $M$. If $M$ exceeds $W$ then we know that the solution to the fractional knapsack problem lies in taking items from among this collection. In other words, we're now solving the fractional knapsack problem on input of size $n / 2$. On the other hand, if the weight doesn't exceed $W$, then we must solve the fractional knapsack problem on the input of $n / 2$ low-value items, with maximum weight $W − M$. Let $T(n)$ denote the runtime of the algorithm. Since we can solve the problem when there is only one item in constant time, the recursion for the runtime is $T(n) = T(n / 2) + cn$ and $T(1) = d$, which gives runtime of $O(n)$.</p>
|
||||
</details>
|
||||
|
||||
|
||||
<footer>
|
||||
<hr>
|
||||
<p>
|
||||
built by nat, from questions written by Michelle Bodnar and Andrew Lohr (CLRS 3rd Edition), the solutions to which were prepared and organized by <a href="https://pengyuc.com/">Peng-Yu Chen</a> and <a href="https://github.com/walkccc/CLRS/graphs/contributors">contributors to walkccc/CLRS on GitHub</a>.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,344 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
renderMathInElement(document.body, {
|
||||
delimiters: [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\(", right: "\\)", display: false},
|
||||
{left: "\\begin{equation}", right: "\\end{equation}", display: true},
|
||||
{left: "\\begin{align}", right: "\\end{align}", display: true},
|
||||
{left: "\\begin{alignat}", right: "\\end{alignat}", display: true},
|
||||
{left: "\\begin{gather}", right: "\\end{gather}", display: true},
|
||||
{left: "\\begin{CD}", right: "\\end{CD}", display: true},
|
||||
{left: "\\[", right: "\\]", display: true}
|
||||
],
|
||||
throwOnError : false
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<title>Practice Exam</title>
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin-left: 20%;
|
||||
margin-right: 20%;
|
||||
}
|
||||
|
||||
summary {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media screen and (max-width:1000px) {
|
||||
body {
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Practice Exam</h1>
|
||||
<h2>15.4-6 *</h2>
|
||||
<p>Give an $O(n\lg n)$-time algorithm to find the longest monotonically increasing subsequence of a sequence of $n$ numbers. ($\textit{Hint:}$ Observe that the last element of a candidate subsequence of length $i$ is at least as large as the last element of a candidate subsequence of length $i - 1$. Maintain candidate subsequences by linking them through the input sequence.)</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>The algorithm $\text{LONG-MONOTONIC}(A)$ returns the longest monotonically increasing subsequence of $A$, where $A$ has length $n$.</p>
|
||||
<p>The algorithm works as follows: a new array B will be created such that $B[i]$ contains the last value of a longest monotonically increasing subsequence of length $i$. A new array $C$ will be such that $C[i]$ contains the monotonically increasing subsequence of length $i$ with smallest last element seen so far.</p>
|
||||
<p>To analyze the runtime, observe that the entries of $B$ are in sorted order, so we can execute line 9 in $O(\lg n)$ time. Since every other line in the for-loop takes constant time, the total run-time is $O(n\lg n)$.</p>
|
||||
<pre lang="cpp"><code>LONG-MONOTONIC(A)
|
||||
let B[1..n] be a new array where every value = ∞
|
||||
let C[1..n] be a new array
|
||||
L = 1
|
||||
for i = 1 to n
|
||||
if A[i] < B[1]
|
||||
B[1] = A[i]
|
||||
C[1].head.key = A[i]
|
||||
else
|
||||
let j be the largest index of B such that B[j] < A[i]
|
||||
B[j + 1] = A[i]
|
||||
C[j + 1] = C[j]
|
||||
INSERT(C[j + 1], A[i])
|
||||
if j + 1 > L
|
||||
L = L + 1
|
||||
print C[L]
|
||||
</code></pre>
|
||||
</details>
|
||||
<h2>4.3-2</h2>
|
||||
<p>Show that the solution of $T(n) = T(\lceil n / 2 \rceil) + 1$ is $O(\lg n)$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>We guess $T(n) \le c\lg(n - a)$,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & \le c\lg(\lceil n / 2 \rceil - a) + 1 \\
|
||||
& \le c\lg((n + 1) / 2 - a) + 1 \\
|
||||
& = c\lg((n + 1 - 2a) / 2) + 1 \\
|
||||
& = c\lg(n + 1 - 2a) - c\lg 2 + 1 & (c \ge 1) \\
|
||||
& \le c\lg(n + 1 - 2a) & (a \ge 1) \\
|
||||
& \le c\lg(n - a),
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</details>
|
||||
<h2>16.3-9</h2>
|
||||
<p>Show that no compression scheme can expect to compress a file of randomly chosen $8$-bit characters by even a single bit. ($\textit{Hint:}$ Compare the number of possible files with the number of possible encoded files.)</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>If every possible character is equally likely, then, when constructing the Huffman code, we will end up with a complete binary tree of depth $7$. This means that every character, regardless of what it is will be represented using $7$ bits.</p>
|
||||
<p>This is exactly as many bits as was originally used to represent those characters,
|
||||
so the total length of the file will not decrease at all.</p>
|
||||
</details>
|
||||
<h2>22.5-4</h2>
|
||||
<p>Prove that for any directed graph $G$, we have $((G^\text T)^{\text{SCC}})^\text T = G^{\text{SCC}}$. That is, the transpose of the component graph of $G^\text T$ is the same as the component graph of $G$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>First observe that $C$ is a strongly connected component of $G$ if and only if it is a strongly connected component of $G^\text T$. Thus the vertex sets of $G^{\text{SCC}}$ and $(G^\text T)^{\text{SCC}}$ are the same, which implies the vertex sets of $((G^\text T)^\text{SCC})^\text T$ and $G^{\text{SCC}}$ are the same. It suffices to show that their edge sets are the same. Suppose $(v_i, v_j)$ is an edge in $((G^\text T)^{\text{SCC}})^\text T$. Then $(v_j, v_i)$ is an edge in $(G^\text T)^{\text{SCC}}$. Thus there exist $x \in C_j$ and $y \in C_i$ such that $(x, y)$ is an edge of $G^\text T$, which implies $(y, x)$ is an edge of $G$. Since components are preserved, this means that $(v_i, v_j)$ is an edge in $G^{\text{SCC}}$. For the opposite implication we simply note that for any graph $G$ we have $(G^\text T)^{\text T} = G$.</p>
|
||||
</details>
|
||||
<h2>34.5-5</h2>
|
||||
<p>The <strong><em>set-partition problem</em></strong> takes as input a set $S$ of numbers. The question is whether the numbers can be partitioned into two sets $A$ and $\bar A = S - A$ such that $\sum_{x \in A} x = \sum_{x \in \bar A} x$. Show that the set-partition problem is $\text{NP-complete}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>26.4-1</h2>
|
||||
<p>Prove that, after the procedure $\text{INITIALIZE-PREFLOW}(G, S)$ terminates, we have $s.e \le -|f^*|$, where $f^*$ is a maximum flow for $G$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Removed)</p>
|
||||
</details>
|
||||
<h2>26.1-5</h2>
|
||||
<p>State the maximum-flow problem as a linear-programming problem.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>$$
|
||||
\begin{array}{ll}
|
||||
\max & \sum\limits_{v \in V} f(s, v) - \sum\limits_{v \in V} f(v, s) \\
|
||||
s.t. & 0 \le f(u, v) \le c(u, v) \\
|
||||
& \sum\limits_{v \in V} f(v, u) - \sum\limits_{v \in V} f(u, v) = 0
|
||||
\end{array}
|
||||
$$</p>
|
||||
</details>
|
||||
<h2>22.3-3</h2>
|
||||
<p>Show the parenthesis structure of the depth-first search of Figure 22.4.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>The parentheses structure of the depth-first search of Figure 22.4 is $(u(v(y(xx)y)v)u)(w(zz)w)$.</p>
|
||||
</details>
|
||||
<h2>24.3-7</h2>
|
||||
<p>Let $G = (V, E)$ be a weighted, directed graph with positive weight function $w: E \rightarrow \{1, 2, \ldots, W\}$ for some positive integer $W$, and assume that no two vertices have the same shortest-path weights from source vertex $s$. Now suppose that we define an unweighted, directed graph $G' = (V \cup V', E')$ by replacing each edge $(u, v) \in E$ with $w(u, v)$ unit-weight edges in series. How many vertices does $G'$ have? Now suppose that we run a breadth-first search on $G'$. Show that the order in which the breadth-first search of $G'$ colors vertices in $V$ black is the same as the order in which Dijkstra's algorithm extracts the vertices of $V$ from the priority queue when it runs on $G$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>$V + \sum_{(u, v) \in E} w(u, v) - E$.</p>
|
||||
</details>
|
||||
<h2>12.2-2</h2>
|
||||
<p>Write recursive versions of $\text{TREE-MINIMUM}$ and $\text{TREE-MAXIMUM}$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<pre lang="cpp"><code>TREE-MINIMUM(x)
|
||||
if x.left != NIL
|
||||
return TREE-MINIMUM(x.left)
|
||||
else return x
|
||||
</code></pre>
|
||||
<pre lang="cpp"><code>TREE-MAXIMUM(x)
|
||||
if x.right != NIL
|
||||
return TREE-MAXIMUM(x.right)
|
||||
else return x
|
||||
</code></pre>
|
||||
</details>
|
||||
<h2>26.3-4 *</h2>
|
||||
<p>A <strong><em>perfect matching</em></strong> is a matching in which every vertex is matched. Let $G = (V, E)$ be an undirected bipartite graph with vertex partition $V = L \cup R$, where $|L| = |R|$. For any $X \subseteq V$, define the <strong><em>neighborhood</em></strong> of $X$ as</p>
|
||||
<blockquote>
|
||||
<p>$$N(X) = \{y \in V: (x, y) \in E \text{ for some } x \in X\},$$</p>
|
||||
<p>that is, the set of vertices adjacent to some member of $X$. Prove <strong><em>Hall's theorem</em></strong>: there exists a perfect matching in $G$ if and only if $|A| \le |N(A)|$ for every subset $A \subseteq L$.</p>
|
||||
</blockquote>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>First suppose there exists a perfect matching in $G$. Then for any subset $A \subseteq L$, each vertex of $A$ is matched with a neighbor in $R$, and since it is a matching, no two such vertices are matched with the same vertex in $R$. Thus, there are at least $|A|$ vertices in the neighborhood of $A$.</p>
|
||||
<p>Now suppose that $|A| \le |N(A)|$ for all $A \subseteq L$. Run Ford-Fulkerson on the corresponding flow network. The flow is increased by $1$ each time an augmenting path is found, so it will suffice to show that this happens $|L|$ times. Suppose the while loop has run fewer than $L$ times, but there is no augmenting path. Then fewer than $L$ edges from $L$ to $R$ have flow $1$.</p>
|
||||
<p>Let $v_1 \in L$ be such that no edge from $v_1$ to a vertex in $R$ has nonzero flow. By assumption, $v_1$ has at least one neighbor $v_1' \in R$. If any of $v_1$'s neighbors are connected to $t$ in $G_f$ then there is a path, so assume this is not the case. Thus, there must be some edge $(v_2, v_1)$ with flow $1$. By assumption, $N(\{v_1, v_2\}) \ge 2$, so there must exist $v_2' \ne v_1'$ such that $v_2'\in N(\{v_1, v_2 \})$. If $(v_2', t)$ is an edge in the residual network we're done since $v_2'$ must be a neighbor of $v_2$, so $s$, $v_1$, $v_1'$, $v_2$, $v_2'$, and $t$ is a path in $G_f$. Otherwise $v_2'$ must have a neighbor $v_3 \in L$ such that $(v_3, v_2')$ is in $G_f$. Specifically, $v_3 \ne v_1$ since $(v_3, v_2')$ has flow $1$, and $v_3 \ne v_2$ since $(v_2, v_1')$ has flow $1$, so no more flow can leave $v_2$ without violating conservation of flow. Again by our hypothesis, $N(\{v_1, v_2, v_3\}) \ge 3$, so there is another neighbor $v_3' \in R$.</p>
|
||||
<p>Continuing in this fashion, we keep building up the neighborhood $v_i'$, expanding each time we find that $(v_i', t)$ is not an edge in $G_f$. This cannot happen $L$ times, since we have run the Ford-Fulkerson while-loop fewer than $|L|$ times, so there still exist edges into $t$ in $G_f$. Thus, the process must stop at some vertex $v_k'$, and we obtain an augmenting path</p>
|
||||
<p>$$s, v_1, v_1', v_2, v_2', v_3, \ldots, v_k, v_k', t,$$</p>
|
||||
<p>contradicting our assumption that there was no such path. Therefore the while loop runs at least $|L|$ times. By Corollary 26.3 the flow strictly increases each time by $f_p$. By Theorem 26.10 $f_p$ is an integer. In particular, it is equal to $1$. This implies that $|f| \ge |L|$. It is clear that $|f| \le |L|$, so we must have $|f| = |L|$. By Corollary 26.11 this is the cardinality of a maximum matching. Since $|L| = |R|$, any maximum matching must be a perfect matching.</p>
|
||||
</details>
|
||||
<h2>4.4-3</h2>
|
||||
<p>Use a recursion tree to determine a good asymptotic upper bound on the recurrence $T(n) = 4T(n / 2 + 2) + n$. Use the substitution method to verify your answer.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<ul>
|
||||
<li>
|
||||
<p>The subproblem size for a node at depth $i$ is $n / 2^i$.</p>
|
||||
<p>Thus, the tree has $\lg n + 1$ levels and $4^{\lg n} = n^2$ leaves.</p>
|
||||
<p>The total cost over all nodes at depth $i$, for $i = 0, 1, 2, \ldots, \lg n - 1$, is $4^i(n / 2^i + 2) = 2^i n + 2 \cdot 4^i$.</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & = \sum_{i = 0}^{\lg n - 1} (2^i n + 2 \cdot 4^i) + \Theta(n^2) \\
|
||||
& = \sum_{i = 0}^{\lg n - 1} 2^i n + \sum_{i = 0}^{\lg n - 1} 2 \cdot 4^i + \Theta(n^2) \\
|
||||
& = \frac{2^{\lg n} - 1}{2 - 1}n + 2 \cdot \frac{4^{\lg n} - 1}{4 - 1} + \Theta(n^2) \\
|
||||
& = (2^{\lg n} - 1)n + \frac{2}{3} (4^{\lg n} - 1) + \Theta(n^2) \\
|
||||
& = (n - 1)n + \frac{2}{3}(n^2 - 1) + \Theta(n^2) \\
|
||||
& = \Theta(n^2).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>We guess $T(n) \le c(n^2 - dn)$,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & = 4T(n / 2 + 2) + n \\
|
||||
& \le 4c[(n / 2 + 2)^2 - d(n / 2 + 2)] + n \\
|
||||
& = 4c(n^2 / 4 + 2n + 4 - dn / 2 - 2d) + n \\
|
||||
& = cn^2 + 8cn + 16c - 2cdn - 8cd + n \\
|
||||
& = cn^2 - cdn + 8cn + 16c - cdn - 8cd + n \\
|
||||
& = c(n^2 - dn) - (cd - 8c - 1)n - (d - 2) \cdot 8c \\
|
||||
& \le c(n^2 - dn),
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
<p>where the last step holds for $cd - 8c - 1 \ge 0$.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<h2>22.2-3</h2>
|
||||
<p>Show that using a single bit to store each vertex color suffices by arguing that the $\text{BFS}$ procedure would produce the same result if lines 5 and 14 were removed.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>The textbook introduces the $\text{GRAY}$ color for the pedagogical purpose to distinguish between the $\text{GRAY}$ nodes (which are enqueued) and the $\text{BLACK}$ nodes (which are dequeued).</p>
|
||||
<p>Therefore, it suffices to use a single bit to store each vertex color.</p>
|
||||
</details>
|
||||
<h2>16.2-4</h2>
|
||||
<p>Professor Gekko has always dreamed of inline skating across North Dakota. He plans to cross the state on highway U.S. 2, which runs from Grand Forks, on the eastern border with Minnesota, to Williston, near the western border with Montana. The professor can carry two liters of water, and he can skate $m$ miles before running out of water. (Because North Dakota is relatively flat, the professor does not have to worry about drinking water at a greater rate on uphill sections than on flat or downhill sections.) The professor will start in Grand Forks with two full liters of water. His official North Dakota state map shows all the places along U.S. 2 at which he can refill his water and the distances between these locations.</p>
|
||||
<blockquote>
|
||||
<p>The professor's goal is to minimize the number of water stops along his route across the state. Give an efficient method by which he can determine which water stops he should make. Prove that your strategy yields an optimal solution, and give its running time.</p>
|
||||
</blockquote>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>The greedy solution solves this problem optimally, where we maximize distance we can cover from a particular point such that there still exists a place to get water before we run out. The first stop is at the furthest point from the starting position which is less than or equal to $m$ miles away. The problem exhibits optimal substructure, since once we have chosen a first stopping point $p$, we solve the subproblem assuming we are starting at $p$. Combining these two plans yields an optimal solution for the usual cut-and-paste reasons. Now we must show that this greedy approach in fact yields a first stopping point which is contained in some optimal solution. Let $O$ be any optimal solution which has the professor stop at positions $o_1, o_2, \dots, o_k$. Let $g_1$ denote the furthest stopping point we can reach from the starting point. Then we may replace $o_1$ by $g_2$ to create a modified solution $G$, since $o_2 - o_1 < o_2 - g_1$. In other words, we can actually make it to the positions in $G$ without running out of water. Since $G$ has the same number of stops, we conclude that $g_1$ is contained in some optimal solution. Therefore the greedy strategy works.</p>
|
||||
</details>
|
||||
<h2>4.3-6</h2>
|
||||
<p>Show that the solution to $T(n) = 2T(\lfloor n / 2 \rfloor + 17) + n$ is $O(n\lg n)$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>We guess $T(n) \le c(n - a)\lg(n - a)$,</p>
|
||||
<p>$$
|
||||
\begin{aligned}
|
||||
T(n) & \le 2c(\lfloor n / 2 \rfloor + 17 - a)\lg(\lfloor n / 2 \rfloor + 17 - a) + n \\
|
||||
& \le 2c(n / 2 + 17 - a)\lg(n / 2 + 17 - a) + n \\
|
||||
& = c(n + 34 - 2a)\lg\frac{n + 34 - 2a}{2} + n \\
|
||||
& = c(n + 34 - 2a)\lg(n + 34 - 2a) - c(n + 34 - 2a) + n & (c > 1, n > n_0 = f(a)) \\
|
||||
& \le c(n + 34 - 2a)\lg(n + 34 - 2a) & (a \ge 34) \\
|
||||
& \le c(n - a)\lg(n - a).
|
||||
\end{aligned}
|
||||
$$</p>
|
||||
</details>
|
||||
<h2>16.3-3</h2>
|
||||
<p>What is an optimal Huffman code for the following set of frequencies, based on</p>
|
||||
<blockquote>
|
||||
<p>the first $8$ Fibonacci numbers?</p>
|
||||
<p>$$a:1 \quad b:1 \quad c:2 \quad d:3 \quad e:5 \quad f:8 \quad g:13 \quad h:21$$</p>
|
||||
<p>Can you generalize your answer to find the optimal code when the frequencies are the first $n$ Fibonacci numbers?</p>
|
||||
</blockquote>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>$$
|
||||
\begin{array}{c|l}
|
||||
a & 1111111 \\
|
||||
b & 1111110 \\
|
||||
c & 111110 \\
|
||||
d & 11110 \\
|
||||
e & 1110 \\
|
||||
f & 110 \\
|
||||
g & 10 \\
|
||||
h & 0
|
||||
\end{array}
|
||||
$$</p>
|
||||
<p><strong>GENERALIZATION</strong></p>
|
||||
<p>In what follows we use $a_i$ to denote $i$-th Fibonacci number. To avoid any confusiion we stress that we consider Fibonacci's sequence beginning $1$, $1$, i.e. $a_1 = a_2 = 1$.</p>
|
||||
<p>Let us consider a set of $n$ symbols $\Sigma = \{c_i ~|~ 1 \le i \le n \}$ such that for each $i$ we have $c_i.freq = a_i$. We shall prove that the Huffman code for this set of symbols given by the run of algorithm HUFFMAN from CLRS is the following code:</p>
|
||||
<ul>
|
||||
<li>$code(c_n) = 0$</li>
|
||||
<li>$code(c_{i - 1}) = 1code(c_i)$ for $2 \le i \le n - 1$ (i.e. we take a code for symbol $c_i$ and add $1$ to the beginning)</li>
|
||||
<li>$code(c_1) = 1^{n - 1}$</li>
|
||||
</ul>
|
||||
<p>By $code(c)$ we mean the codeword assigned to the symbol $c_i$ by the run of HUFFMAN($\Sigma$) for any $c \in \Sigma$.</p>
|
||||
<p>First we state two technical claims which can be easily proven using the proper induction. Following good manners of our field we leave the proofs to the reader :-)</p>
|
||||
<ul>
|
||||
<li>(HELPFUL CLAIM 1) $ (\forall k \in \mathbb{N}) ~ \sum\limits_{i = 1}^{k} a_i = a_{k + 2} - 1$</li>
|
||||
<li>(HELPFUL CLAIM 2) Let $z$ be an inner node of tree $T$ constructed by the algorithm HUFFMAN. Then $z.freq$ is sum of frequencies of all leafs of the subtree of $T$ rooted in $z$.</li>
|
||||
</ul>
|
||||
<p>Consider tree $T_n$ inductively defined by</p>
|
||||
<ul>
|
||||
<li>$T_2.left = c_2$, $T_2.right = c_1$ and $T_2.freq = c_1.freq + c_2.freq = 2$</li>
|
||||
<li>$(\forall i; 3 \le i \le n) ~ T_i.left = c_i$, $T_i.right = T_{i - 1}$ and $T_i.freq = c_i.freq + T_{i - 1}.freq$</li>
|
||||
</ul>
|
||||
<p>We shall prove that $T_n$ is the tree produced by the run of HUFFMAN($\Sigma$).</p>
|
||||
<p><strong>KEY CLAIM:</strong> $T_{i + 1}$ is exactly the node $z$ constructed in $i$-th run of the for-cycle of HUFFMAN($\Sigma$) and the content of the priority queue $Q$ just after $i$-th run of the for-cycle is exactly $Q = (a_{i + 2}, T_{i + 1}, a_{i + 3}, \dots, a_n)$ with $a_{i + 2}$ being the minimal element for each $1 \le i < n$. (Since we prefer not to overload our formal notation we just note that for $i = n - 1$ we claim that $Q = (T_n)$ and our notation grasp this fact in a sense.)</p>
|
||||
<p><strong>PROOF OF KEY CLAIM</strong> by induction on $i$.</p>
|
||||
<ul>
|
||||
<li>for $i = 1$ we see that the characters with lowest frequencies are exactly $c_1$ and $c_2$, thus obviously the algorithm HUFFMAN($\Sigma$) constructs $T_2$ in the first run of its for-cycle. Also it is obvious that just after this run of the for-cycle we have $Q = (a_3, T_{2}, a_4, \dots, a_n)$.</li>
|
||||
<li>for $2 \le i < n$ we suppose that our claim is true for all $j < i$ and prove the claim for $i$. Since the claim is true for $i - 1$, we know that just before $i-th$ execution of the for-cycle we have the following content of the priority queue $Q=(a_{i + 1}, T_i, a_{i + 2}, \dots, a_n)$. Thus line 5 of HUFFMAN extracts $a_{i + 1}$ and sets $z.left = a_{i + 1}$ and line 6 of HUFFMAN extracts $T_i$ and sets $z.right = T_i$. Now we can see that indeed $z$ is exactly $T_{i + 1}$. Using (CLAIM 2) and observing the way $T_{i + 1}$ is defined we get that $z.freq = T_{i + 1}.freq = \sum\limits_{i=1}^{i + 1} a_i$. Thus using (CLAIM 1) one can see that $a_{i + 2} < T_{i + 1}.freq < a_{i + 3}$. Therefore for the content of the priority queue $Q$ just after the $i$-th execution of the for-cycle we have $Q=(a_{i + 2}, T_{i + 2}, a_{i + 3}, \dots, a_n)$.</li>
|
||||
</ul>
|
||||
<p><strong>KEY CLAIM</strong> tells us that just after the last execution of the for-cycle we have $Q = (T_n)$ and therefore the line 9 of HUFFMAN returns $T_n$ as the result. One can easily see that the code given in the beginning is exactly the code which corresponds to the code-tree $T_n$.</p>
|
||||
</details>
|
||||
<h2>34.1-6</h2>
|
||||
<p>Show that the class $P$, viewed as a set of languages, is closed under union, intersection, concatenation, complement, and Kleene star. That is, if $L_1, L_2 \in P$, then $L_1 \cup L_2 \in P$, $L_1 \cap L_2 \in P$, $L_1L_2 \in P$, $\bar L_1 \in P$, and $L_1^* \in P$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>(Omit!)</p>
|
||||
</details>
|
||||
<h2>16.5-1</h2>
|
||||
<p>Solve the instance of the scheduling problem given in Figure 16.7, but with each penalty $w_i$ replaced by $80 - w_i$.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>$$
|
||||
\begin{array}{c|ccccccc}
|
||||
a_i & 1 & 2 & 3 & 4 & 5 & 6 & 7 \\
|
||||
\hline
|
||||
d_i & 4 & 2 & 4 & 3 & 1 & 4 & 6 \\
|
||||
w_i & 10 & 20 & 30 & 40 & 50 & 60 & 70
|
||||
\end{array}
|
||||
$$</p>
|
||||
<p>We begin by just greedily constructing the matroid, adding the most costly to leave incomplete tasks first. So, we add tasks $7, 6, 5, 4, 3$. Then, in order to schedule tasks $1$ or $2$ we need to leave incomplete more important tasks. So, our final schedule is $\langle 5, 3, 4, 6, 7, 1, 2 \rangle$ to have a total penalty of only $w_1 + w_2 = 30$.</p>
|
||||
</details>
|
||||
<h2>16.2-2</h2>
|
||||
<p>Give a dynamic-programming solution to the $0$-$1$ knapsack problem that runs in $O(nW)$ time, where $n$ is the number of items and $W$ is the maximum weight of items that the thief can put in his knapsack.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Suppose we know that a particular item of weight $w$ is in the solution. Then we must solve the subproblem on $n − 1$ items with maximum weight $W − w$. Thus, to take a bottom-up approach we must solve the $0$-$1$ knapsack problem for all items and possible weights smaller than W. We'll build an $n + 1$ by $W + 1$ table of values where the rows are indexed by item and the columns are indexed by total weight. (The first row and column of the table will be a dummy row).</p>
|
||||
<p>For row $i$ column $j$, we decide whether or not it would be advantageous to include item i in the knapsack by comparing the total value of of a knapsack including items $1$ through $i − 1$ with max weight $j$, and the total value of including items $1$ through $i − 1$ with max weight $j − i.weight$ and also item $i$. To solve the problem, we simply examine the $n$, $W$ entry of the table to determine the maximum value we can achieve. To read off the items we include, start with entry $n$, $W$. In general, proceed as follows: if entry $i$, $j$ equals entry $i - 1$, $j$, don't include item $i$, and examine entry $i - 1$, $j$ next. If entry $i$, $j$ doesn't equal entry $i − 1$, $j$, include item $i$ and examine entry $i − 1$, $j − i$.weight next. See algorithm below for construction of table:</p>
|
||||
<pre lang="cpp"><code>0-1-KNAPSACK(n, W)
|
||||
Initialize an (n + 1) by (W + 1) table K
|
||||
for i = 1 to n
|
||||
K[i, 0] = 0
|
||||
for j = 1 to W
|
||||
K[0, j] = 0
|
||||
for i = 1 to n
|
||||
for j = 1 to W
|
||||
if j < i.weight
|
||||
K[i, j] = K[i - 1, j]
|
||||
else
|
||||
K[i, j] = max(K[i - 1, j], K[i - 1, j - i.weight] + i.value)
|
||||
</code></pre>
|
||||
</details>
|
||||
<h2>12.3-2</h2>
|
||||
<p>Suppose that we construct a binary search tree by repeatedly inserting distinct values into the tree. Argue that the number of nodes examined in searching for a value in the tree is one plus the number of nodes examined when the value was first inserted into the tree.</p>
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
<p>Number of nodes examined while searching also includes the node which is searched for, which isn't the case when we inserted it.</p>
|
||||
</details>
|
||||
|
||||
|
||||
<footer>
|
||||
<hr>
|
||||
<p>
|
||||
built by nat, from questions written by Michelle Bodnar and Andrew Lohr (CLRS 3rd Edition), the solutions to which were prepared and organized by <a href="https://pengyuc.com/">Peng-Yu Chen</a> and <a href="https://github.com/walkccc/CLRS/graphs/contributors">contributors to walkccc/CLRS on GitHub</a>.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,68 @@
|
|||
import string
|
||||
import cmarkgfm
|
||||
import argparse
|
||||
import sqlite3
|
||||
import sys
|
||||
import os
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='CLRS practice exam generator',
|
||||
description='Generates practice exams from your clrs.db')
|
||||
|
||||
parser.add_argument('--db', default="clrs.db", dest='db_path', help='Path of the database to query')
|
||||
parser.add_argument('--output', dest='output_path', help='Path (including file name) of the resulting HTML document', default="exam.html")
|
||||
parser.add_argument('--template', dest='template_path', help='Path (including file name) of the HTML template', default="./template.html")
|
||||
parser.add_argument('query', help='SQLite query string to select the desired problems')
|
||||
|
||||
class ModifiedTemplate(string.Template):
|
||||
delimiter = '%$%'
|
||||
|
||||
markdownOptions = (
|
||||
cmarkgfm.cmark.Options.CMARK_OPT_UNSAFE |
|
||||
cmarkgfm.cmark.Options.CMARK_OPT_GITHUB_PRE_LANG
|
||||
)
|
||||
|
||||
def main(args):
|
||||
con = sqlite3.connect(args.db_path)
|
||||
con.row_factory = sqlite3.Row
|
||||
cur = con.cursor()
|
||||
|
||||
practice_exam = ""
|
||||
template_string = "%$%content"
|
||||
|
||||
if args.template_path is not None:
|
||||
with open(args.template_path, "r") as template_file:
|
||||
template_string = template_file.read()
|
||||
|
||||
template = ModifiedTemplate(template_string)
|
||||
|
||||
cur.execute(args.query)
|
||||
problems = cur.fetchall()
|
||||
|
||||
for problem in problems:
|
||||
chapter = problem["chapter"]
|
||||
section = problem["section"]
|
||||
problem_number = problem["problem_number"]
|
||||
question = problem["question"]
|
||||
answer = problem["answer"]
|
||||
starred = problem["starred"]
|
||||
|
||||
practice_exam = practice_exam + f"""
|
||||
## {chapter}.{section}-{problem_number} {"*" if starred else ""}
|
||||
|
||||
{question}
|
||||
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
{cmarkgfm.markdown_to_html(answer, markdownOptions)}
|
||||
</details>
|
||||
"""
|
||||
|
||||
with open(args.output_path, "w") as output_file:
|
||||
output_file.write(template.substitute(content=cmarkgfm.markdown_to_html(practice_exam, markdownOptions)))
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parser.parse_args()
|
||||
sys.exit(main(args))
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
renderMathInElement(document.body, {
|
||||
delimiters: [
|
||||
{left: "$$", right: "$$", display: true},
|
||||
{left: "$", right: "$", display: false},
|
||||
{left: "\\(", right: "\\)", display: false},
|
||||
{left: "\\begin{equation}", right: "\\end{equation}", display: true},
|
||||
{left: "\\begin{align}", right: "\\end{align}", display: true},
|
||||
{left: "\\begin{alignat}", right: "\\end{alignat}", display: true},
|
||||
{left: "\\begin{gather}", right: "\\end{gather}", display: true},
|
||||
{left: "\\begin{CD}", right: "\\end{CD}", display: true},
|
||||
{left: "\\[", right: "\\]", display: true}
|
||||
],
|
||||
throwOnError : false
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<title>Practice Exam</title>
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin-left: 20%;
|
||||
margin-right: 20%;
|
||||
}
|
||||
|
||||
summary {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media screen and (max-width:1000px) {
|
||||
body {
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Practice Exam</h1>
|
||||
%$%content
|
||||
|
||||
<footer>
|
||||
<hr>
|
||||
<p>
|
||||
built by nat, from questions written by Michelle Bodnar and Andrew Lohr (CLRS 3rd Edition), the solutions to which were prepared and organized by <a href="https://pengyuc.com/">Peng-Yu Chen</a> and <a href="https://github.com/walkccc/CLRS/graphs/contributors">contributors to walkccc/CLRS on GitHub</a>.
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue