Algorithms and programming assignment 7, Exercises of Algorithms and Programming

AUBS Algorithms and programming assignment 7 with exercises and solutions

Typology: Exercises

2021/2022

Uploaded on 05/14/2023

unknown user
unknown user 🇱🇧

10 documents

1 / 19

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
CMPS 214 Problem Set 7
Note:
Starting from Problem 4 in this problem set are all DP problems.
Exercises
1.
In class, we saw pseudocode for Dijkstra's algorithm which returned shortest distances
but not shortest paths. In this exercise we'll see how to adapt it to return shortest
paths. One way to do that is shown in the pseudocode below:
Dijkstra_st_path(G, s, t):
for all v in V, set d[v] = Infinity
for all v in V, set p[v] = None
// we will use the information p[v] to reconstruct the path at the end.
d[s] = 0
F=V
D = [] // D is the list of "done" vertices
while F isn't empty:
x = a vertex v in F so that d[v] is minimal
for y in x.outgoing_neighbors:
d[y] = min( d[y], d[x] + weight(x,y) )
if d[y] was changed in the previous line, set p[y] = x
F.remove(x)
D.add(x)
// use the information in p to reconstruct the shortest path:
path = [t]
current = t
while current != s:
current = p[current]
add current to the front of the path
return path, d[t]
Step through
Dijkstra_st_path
(
G; s ; t
)
on the graph
G
shown below. Complete the
table below (on the next page) to show what the arrays
d
and
p
are at each step of the
algorithm, and indicate what path is returned and what its cost is. If it is helpful, the
L
A
T
EXcode for the table is reproduced at the end of the PSET.
1
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13

Partial preview of the text

Download Algorithms and programming assignment 7 and more Exercises Algorithms and Programming in PDF only on Docsity!

CMPS 214 Problem Set 7

Note: Starting from Problem 4 in this problem set are all DP problems.

Exercises

  1. In class, we saw pseudocode for Dijkstra's algorithm which returned shortest distances but not shortest paths. In this exercise we'll see how to adapt it to return shortest paths. One way to do that is shown in the pseudocode below: Dijkstra_st_path(G, s, t): for all v in V, set d[v] = Infinity for all v in V, set p[v] = None // we will use the information p[v] to reconstruct the path at the end. d[s] = 0 F = V D = [] // D is the list of "done" vertices while F isn't empty: x = a vertex v in F so that d[v] is minimal for y in x.outgoing_neighbors: d[y] = min( d[y], d[x] + weight(x,y) ) if d[y] was changed in the previous line, set p[y] = x F.remove(x) D.add(x)

// use the information in p to reconstruct the shortest path: path = [t] current = t while current != s: current = p[current] add current to the front of the path return path, d[t] Step through Dijkstra_st_path(G; s; t) on the graph G shown below. Complete the table below (on the next page) to show what the arrays d and p are at each step of the algorithm, and indicate what path is returned and what its cost is. If it is helpful, the LATEXcode for the table is reproduced at the end of the PSET.

s

u

v

t

G

[We are expecting: The following things:

ˆ The table below lled out ˆ The shortest path and its cost that the algorithm returns.

No justication is required.]

Solution:

d[s] d[u] d[v ] d[t] p[s] p[u] p[v ] p[t] When entering the rst while loop for the rst time, the state is:

0 1 1 1 None None None None

Immediately after the rst ele- ment of D is added, the state is:

0 3 1 9 None s None s

Immediately after the second ele- ment of D is added, the state is:

0 3 5 8 None s u u

Immediately after the third ele- ment of D is added, the state is:

0 3 5 6 None s u v

Immediately after the fourth ele- ment of D is added, the state is:

0 3 5 6 None s u v

Shortest path would be: s; u; v ; t with a cost of d[t] = 6

S(7)

A(10)

B(6)

C(9)

D(8)

E(3)

F(6)

T(0)

[We are expecting: A list of vertices from G that are visited by the Dijkstra's algorithm in the order of when they are rst marked sure.]

Solution:

S; B; E; T

Problems

  1. [Negative Dijkstra's Algorithm.] It turns out Dijkstra's algorithm only works for graphs with nonnegative edge weights. In this question, we'll explore why this is the case. For this question assume that the graph is directed and that all edge weights are integers. (a) A negative cycle is a cycle where the sum of the edge weights is negative. Why can't we compute shortest paths in a graph with negative cycles? [We are expecting: An informal explanation.] Solution: In a graph with negative cycles, the notion of a shortest path does not make sense because the weight of any given path can be made arbitrarily smaller by making the path go over the negative cycle multiple times.

(b) Even if a graph contains no negative cycles (but still contains negative edges) we might still be in trouble. Please draw a graph G, which contains both positive and negative edges but does not contain negative cycles, and specify some source s 2 V where Dijkstra(G, s) does not correctly compute the shortest paths from s. [We are expecting: A graph G and a brief explanation on the shortest path that is not correctly computed.]

Solution: In order to come up with such an example, remember a core assumption of the standard Dijkstra's algorithm. Once you mark a vertex as "sure", you know you have found the shortest path from the vertex to it. This, however, is not necessarily the case when the graph we are dealing with includes negative edges. In other words, in some cases, we may have to undo marking some vertices as "sure". Take a look at the example below:

The shortest path from vertex A to vertex D is clearly A; C; B; D. However, Dijk- stra's algorithm with A as the source applied on this graph works as follows: First, A is selected, it relaxes the distances of its neighbors (d[D] becomes 1, d[B] be- comes 2, d[C] becomes 4), and we set A to "sure". Then we pick D, which is the "unsure" vertex with the smallest d, and we set it to "sure". Then, we pick B, d[B] + 3 is not smaller than d[D] so d[D] is unchanged, then we set B to sure. Finally, we pick C; C relaxes d[B] because d[C] 100 is smaller than d[B]. Since all vertices were marked as "sure", we terminate.

One way to x the algorithm above would be to "unset" the "sure" ag of B after

It should be easy to see that Negative-Dijkstra does not do its intended job. The reason why that is the case is that it punishes shortest paths with a lot of edges.

Consider the graph G above. The shortest path from A to C is clearly A; D; E; C. However, once we apply Negative-Dijkstra, the shortest path from A to C becomes A; B; C.

  1. [Longest Increasing Subsequence.] In this exercise we'll practice designing and analyzing dynamic programming algorithms. Let A be an array of length n con- taining real numbers. A longest increasing subsequence (LIS) of A is a sequence 0  i 0 < i 1 <    < i 1 < n so that A[i 0 ] < A[i 1 ] <    < A[i 1 ] and that ` is as large as possible. For example, if A = [6; 3 ; 2 ; 5 ; 6 ; 4 ; 8], then a LIS is i 0 = 1; i 1 = 3; i 2 = 4; i 3 = 6 corresponding to the subsequence 3 ; 5 ; 6 ; 8. (Notice that a longest increasing subse- quence doesn't need to be unique). In the following parts, we'll walk through the recipe that we saw in class for coming up with DP algorithms to develop an O(n^2 )-time algorithm for nding an LIS. Note: Actually, there is an O(n log n)-time algorithm to nd an LIS, which is faster than the DP solution in this exercise! (a) (Identify optimal sub-structure and a recursive relationship). We'll come up with the sub-problems and recursive relationship for you, although you will have to justify it. Let D[i] be the length of the longest increasing subsequence of [A[0]; : : : ; A[i]] that ends on A[i]. Explain why

D[i] = max (fD[k] + 1 : 0  k < i; A[k] < A[i]g [ f 1 g) :

[We are expecting: A short informal explanation (a paragraph or so). It might be good practice to write a formal proof, but this is not required for credit.] Solution: The formal proof of correctness is left as an exercise to you (think induction, as you almost always should when you are dealing with dynamic programming algorithms).

There are two cases that we need to look at. If A[i] is the minimum of the array A[0:::i], then D[i] is clearly 1. Otherwise, we look at all the optimal solutions that we have already precomputed and we check which of those we can append A[i] to (there must exist at least one of those). We can append A[i] to a LIS if and only if A[i] is bigger than the LIS's biggest element, which explains needing to check whether A[k] is smaller than A[i] for all k between 0 and i (exclusive), and we know that this is sucient to guarantee the safety of appending A[i] because by denition, D[k] is the maximum length of a LIS ending at k, i.e. A[k] is the last element of the LIS considered for D[k]. (b) (Develop a DP algorithm to nd the value of the optimal solution). Use the relationship above to design a dynamic programming algorithm that returns the length of the longest increasing subsequence. Your algorithm should run in time O(n^2 ) and should ll in the array D dened above. [We are expecting: Pseudocode of your algorithm. No justication is required.] Solution:

  1. [Duck Dance-o.] Two dancing duck troupes are having a dance-o. The rules are as follows. There is a dance oor, which is laid out as a row of n squares, where n is an even number. Each square has a score (a positive number), which is given by an array D of length n. Each duck receives the score of the square it dances in, and the score for the whole team is the sum of the scores of each dancer in that team. The two dancing duck troupe take turns adding dancers to the dance oor; but the rules are that a new dancer can only join next to an existing dancer, or next to the edge of the dance oor. The two troupes are colored green and white, and green goes rst. For example, the following would be a legal dance-o, with a dance-oor-array D = [5; 7 ; 3 ; 4 ; 4 ; 6].

Round 1

Round 2

Round 3

Round 4

Round 5

Round 6

At the end of this dance-o, the green ducks have a score of 5 + 4 + 6 = 15, while the white ducks have a score of 7 + 3 + 4 = 14, so the green ducks win. Notice that in the above example, the ducks may not have been using the optimal strategy. For the questions below, green ducks refers to the dance troupe that goes rst in this dance-o. In this problem, you will design an algorithm to compute the best score that the green ducks can obtain, assuming that the white ducks are playing optimally. Your algorithm should run in time O(n^2 ). (a) What sub-problems will you use in your dynamic programming algorithm? What is the recursive relationship which is satised between the sub-problems? [We are expecting: ˆ A clear description of your sub-problems.

ˆ A recursive relationship that they satisfy, along with a base case. ˆ An informal justication that the recursive relationship is correct. ] Solution: A good place to start would be convincing ourselves that a greedy solution does

not work here (a greedy solution would pick the maximum of the two endpoints at every round). Assume that our input dance oor is [5; 100 ; 1 ; 1]. The green duck troupe would go for 5 , however, that makes them automatically lose, because it makes it possible for the white duck troupe to pick up 100 points and win; it should not be too hard to see that there exists a winning strategy for the green duck troupe for this input. The reason why this is being mentioned here is because such observations motivate the denition of the substructure (or at least helps us think about it).

The rst observation is that both players are playing optimally. The second obser- vation is that base cases happen on subarrays of size 1. The third observation is that, in the DP matrix representing the problem (we haven't discussed the matrix yet), we need to keep track of two values per cell: the optimal result if it is the green ducks' troupe's turn next, and the optimal result if it is the white ducks' troupe's turn next.

We will represent the problem by a matrix dp[n][n][2], i.e. a n  n  2 matrix. dp[i][j][0] is the maximum obtainable number of points for the subarray A[i:::j] for the green ducks' troupe if it is the green ducks' troupe's turn next, dp[i][j][1] is the maximum obtainable number of points for the subarray A[i:::j] for the green ducks' troupe if it is the white ducks' troupe's turn next. Since we know that the green ducks always go rst, we are interested in the eventual value at dp[0][n 1][0]. We still have to indicate our substructure:

dp[i][j][0] = max(D[i] + dp[i + 1][j][1]; D[j] + dp[i][j 1][1])

dp[i][j][1] is dictated by whoever's turn it currently is, so it follows directly from the denition of dp[i][j][0] that dp[i][j][1] = min(dp[i + 1][j][0]; dp[i][j 1][0]). Naturally, our base cases are dp[i][i][0] = D[i] 8 i and dp[i][i][1] = 0 8 i.

If you were not able to see the substructure, it is strongly recommended that you look at the smallest instances, namely dp[i][i][0] and dp[i][i][1] 8 i. Then try to see how you can get the results for subarrays of size 2 from those, and so on... In fact, that is exactly what we will be doing in code!

(b) Write pseudocode for your algorithm. Your algorithm should take as input the array D, and return a single number which is the best score the green team can achieve. Your algorithm does not need to output the optimal strategy. It should run in time O(n^2 ). [We are expecting: Pseudocode AND a clear English description. You do not need to justify that your algorithm is correct, but correctness should follow from

  1. [MinElementSum.] Consider the following problem, MinElementSum.

MinElementSum(n; S): Let S be a set of positive integers, and let n be a non-negative integer. Find the minimal number of elements of S needed to write n as a sum of elements of S (possibly with repetitions). If there is no way to write n as a sum of elements of S, return None. For example, if S = f 1 ; 4 ; 7 g and n = 10, then we can write n = 1 + 1 + 1 + 7 and that uses four elements of S. The solution to the problem would be 4." On the other hand if S = f 4 ; 7 g and n = 10, then the solution to the problem would be None, because there is no way to make 10 out of 4 and 7. Your friend has devised a divide-and-conquer algorithm to solve MinElementSum. Their pseudocode is below: def minElementSum(n, S): if n == 0: return 0 if n < min(S): return None candidates = [] for s in S: cand = minElementSum( n-s, S ) if cand is not None: candidates.append( cand + 1 ) if len(candidates) == 0: return None return min(candidates)

Your friend's algorithm correctly solves MinElementSum. Before you start doing the problems on the next page, it would be a good idea to walk through the algorithm and to understand what this algorithm is doing and why it works.

[Questions on next page]

(a) Argue that for S = f 1 ; 2 g, your friend's algorithm has exponential running time. (That is, running time of the form 2 (n)). You may assume that Fibonacci numbers grow exponentially, i.e., let f (n) be a function that returns n-th Fibonacci number, you may assume that f (n) = 2 (n). [We are expecting: ˆ A recurrence relation that the running time of your friend's algorithm satises when S = f 1 ; 2 g. ˆ A convincing argument that the closed form for this expression is 2 (n). You do not need to write a formal proof. ]

Solution: Our friend's algorithm when ran as follows: minElementSum(n; f 1 ; 2 g), where n is any integer > 1, will need to compute minElementSum(n 1 ; f 1 ; 2 g) and minElementSum(n 2 ; f 1 ; 2 g). The subproblems will in turn compute their own subproblems unless one of two base cases are met: n == 0 or n < min(S). With S = f 1 ; 2 g the base case can be shortened to just n < 1. So, clearly the recurrence relation of our friend's algorithm with S = f 1 ; 2 g will be the following:

T (n) =

O(1) if n < 1 T (n 1) + T (n 2) + O(1) otherwise

Notice that the recurrence relation is identical to that representing the running time for computing the n-th Fibonacci number with the naive implementation (no memoization), the only dierence being the base cases. So, given that that f (n), the function that returns the n-th Fibonacci number runs in 2 (n), and given that minElementSum(n; f 1 ; 2 g), where n is any integer > 1, has an almost identical recurrence relation, the only dierence being the base cases, then it too runs in 2 (n).

work, hence the desired runtime is achieved. (c) Turn your friend's algorithm into a bottom-up dynamic programming algorithm. Your algorithm should take time O(njSj). Hint: Fill in the array you used in part (b) iteratively, from the bottom up. [We are expecting:

ˆ Pseudocode AND a short English description of the idea of your algorithm. ˆ An informal justication of the running time. ]

def minElementSumDPBottomDown(n, S): DP = [-1] * (n + 1) DP[0] = 0 min_S = min(S)

for i in range(1, n + 1): candidates = [] for s in S: if(n - s >= min_S): candidates.append(DP[n - s] + 1) DP[i] = min(candidates)

return DP[n]

The above pseudocode does exactly the same thing as the pseudocode in part (b), except it follows a bottom up approach where the array DP is lled iteratively from DP [0] till DP [n]. Similarly to before each of these elements require O(jSj) work to compute, and so the runtime of the entire algorithm will also be O(njSj).

  1. [Fish sh eat eat sh.] Plucky the Pedantic Penguin enjoys sh, and it has discovered that on some days the sh supply is better in Lake A and some days the sh supply is better in Lake B. Plucky has access to two tables A and B, where A[i] is the number of sh it can catch in Lake A on day i, and B[i] is the number of sh it can catch in Lake B on day i, for i = 0; : : : ; n 1. If Plucky is at Lake A on day i and wants to be at Lake B on day i + 1, it may pay L sh to a polar bear who can take it from Lake A to Lake B overnight; the same is true if it wants to go from Lake B back to Lake A. The polar bear does not accept credit, so Plucky must pay before it travels. And if Plucky cannot pay, it cannot travel). Assume that when day 0 begins, Plucky is at Lake A, and it has zero sh. Also assume that A[i] and B[i] are positive integers for i = 0; 1 ; : : : ; n 1 and that L is also a positive integer. For example, suppose that n = 3, L = 3, and that A and B are given by

A = [5; 2 ; 3] B = [2; 7 ; 4]:

Then Plucky might do: Lake A Lake B

Day 0

0 sh

5 sh

Day 1

2 sh

9 sh

Day 2

9 sh

13 sh

So Plucky's total sh at the end of day n 1 = 2 is 13. In this question, you will design an O(n)-time dynamic programming algorithm that nds the maximum number of sh that Plucky can have at the end of day n 1. Do this by answering the two parts below.

(b) Design a dynamic programming algorithm that takes as input A; B; L and n, and in time O(n) returns the maximum number of sh that Plucky can have at the end of day n 1. [We are expecting: Pseudocode AND a short English description of what it does and why it works, and a justication of why it runs in time O(n).] def plucky(A, B, L, n): AV = [-1]n BV = [-1]n

AV[0] = A[0] BV[0] = -inf for i in range(1, n): if BV[i - 1] < L: AV[i] = AV[i - 1] + A[i] else: AV[i] = max(AV[i - 1] + A[i], BV[i - 1] - L + A[i])

if AV[i - 1] < L: BV[i] = BV[i - 1] + B[i] else: BV[i] = max(BV[i - 1] + B[i], AV[i - 1] - L + B[i])

return max(AV[n - 1], BV[n - 1])

The code runs in O(n) because it computes two arrays of size n, with each entry needing operations that run in constant time. The algorithm is a bottom up approach that implements the recursive relationships from part (a) i.e. it populates arrays AV and BV from entry 0 to n and outputs the result at day n by computing max(AV[n], BV[n]).