You are on page 1of 8

COMP4500™ 2011 Exam

Question 1
(a)
There are 8 valid ways. If 1 represents a pebble and 0 an empty space: {0,0,0,0}, {1,0,0,0},
{0,1,0,0}, {0,0,1,0}, {0,0,0,1}, {1,0,1,0}, {0,1,0,1} {1,0,0,1}.

(b)
Assuming the ‘random integers’ in the grid are all positive. If not then take every max(a, b, …)
function to actually compute max(0, a, b, …).

[i]
int [n][4] weights;
int [n] selectedColumnPatterns;
bool[8][4] patterns = { {0,0,0,0}, {1,0,0,0}, {0,1,0,0}, {0,0,1,0},
{0,0,0,1}, {1,0,1,0}, {0,1,0,1}, {1,0,0,1} }

// Option 1 - Does not match dynamic programming solution below.


int max_pebbles(int i = 0, int currentSum = 0) {
if (i >= n)
return currentSum;

int result = currentSum;


for (int p = 0; p < 8; p++) {
if (i == 0 || compatible(selectedColumnPatterns[i-1], patterns[p])) {
selectedColumnPatterns[i] = p;
columnValue = weights[i] * patterns[p]; // dot product
result = max(result, max_pebbles(i+1, currentSum + columnValue));
}

return result;
}

Ian’s note on the next option: It is closer but doesn't match.


Here max_pebbles(c,prev) calls max_pebbles(c+1,p) and hence in the dynamic programming version I'd
expect the values of the matrix to be filled in in descending order of c.
With a bit of care the body of the inductive case of the recursive version can be used virtually verbatim, with
only the recursive calls replaced by table look ups.
Of course you need the "for" loops around that, and they need to fill in the table in the right order so that
entries are calculated and filled in before they are used.
And it would also help to use the same variables names in the loops as the parameters in the recursive
version.
Pat’s response to that note: I’ve tried to match the recursive and dynamic
solutions to the equivalent question in the 2010 exam. Take a look there for a
closer match, or feel free to make these more equivalent.

// Option 2 - Almost matches solution below. Each call returns the max score
// for the grid with column ‘c’ onwards given that the previous column had
// column pattern ‘prevPattern’ (an index into ‘patterns’).
int max_pebbles(int c = 0, int prevPattern = -1) {
if (c >= n)
return 0;
int result = 0;
for (int p = 0; p < 8; p++) {
if (prevPattern < 0 || compatible(prevPattern, p)) {
int columnValue = weights[c] * patterns[p]; // dot product
result = max(result, columnValue + max_pebbles(c+1, p));
}
}
return result;
}

[ii]

struct patternChoice { int max; int prevPattern; }


int max_pebbles() {
// gridChoices[i][p] stores the max weight of the grid from column i
onwards
// when that column uses the pebble pattern ‘patterns[p]’. It also stores
// the maximising pattern of the previous column in order to reconstruct
the
// overall pebble selection
struct patternChoice gridChoices[n][8];
struct patternChoice choice;

for (int i = 0; i < n; i++) {


for (int p = 0; p < 8; p++) {
int patternWeight = weights[i] * patterns[p]; // dot product

if (i > 0) {
for (int p2 = 0; p2 < 8; p2++) {
int runningTotal = patternWeight + maxWeights[i-1][p2];

if (compatible(patterns[p], patterns[p2]) &&


runningTotal > patternChoice.max ) {
choice.max = runningTotal;
choice.prevPattern = p2;
}
}
} else {
choice.max = patternWeight;
choice.prevPattern = -1;
}

gridChoices[i][p] = choice;
}
}
int maxScore = choice.max;

// to find the maximising selection


int selectedPatterns[n];
int lastColumnPattern = patternChoices[n-1].indexOf( choice );
selectedPatterns[n-1] = lastColumnPattern;
for (int i = n-2; i >= 0; i--) {
selectedPatterns[i] = choice.prevPattern;
choice= gridChoices[i][choice.prevPattern];
}

return maxScore, selectedPatterns;


}

(c)
Say the grid is of size i× j and write P(i)as the number of valid column patterns for columns of
length i. Then the dynamic programming algorithm has complexity:
Θ¿
If i = j = n then this gives:
Θ¿
Determining P(n) exactly isn’t straight forward, but a (very bad) upper bound can easily be found:

P(n) ≤2n
I.e. the number of valid column patterns is bounded by the total number of patterns (each n row
elements can either have a pebble or it can’t, so 2 ×2 ×⋯ × 2=2n).

Therefore the dynamic programming solution for the n x n grid is:

O(n 2 22 n)

which is pretty terrible. Hopefully this upper bound on P(n) is very far off and P(n) is o (2n) .

(d)
Sort all of the elements of the grid into a list which is in descending order. Place pebbles in this
descending order unless placing that pebble would not be a legal placement.

(e)
Say the grid has a weight of 100 at position (i, j) and this is the largest weight on the grid. The
greedy algorithm would place a marble at this position and the adjacent positions (i-1, j), (i, j+1),
(i+1, j), (i, j-1) cannot be used. If the adjacent positions all have weight 99 then the maximum
solution would likely include these four positions whose combined weight 4*99 = 396 >> 100.

Question 2
-Use Floyd-Warshall Algorithm to calculate the all pairs shortest paths
-Calculate eccentricity for each vertex from this result
-Centre is the set of vertices with minimum eccentricity

Find-Centre(G):
D = Floyd-Warshall(G)
min_ecc = ∞

for vertex v in G.V:


v.ecc = max(D[v])
min_ecc = min(min_ecc, v.ecc)

centre = {}
for vertex v in G.V:
if v.ecc == min_ecc:
centre.add(v)

return centre

Question 3
(a)
Bit Array (JT’s solution)

Choose the sets to be bit-vectors of length |V|. Bit 'i' is true if the set contains vertex 'i'. A simple
'bitwise &' can check if two sets contain the same vertex.

Collection of Sets representation (Ian suggested to do this).

ArrayList<HashSet<int>> sets;
for (i in V1) { sets.get(i).add(1); };
for (i in V2) { sets.get(i).add(2) };
for (i in V3) { sets.get(i).add(3) };

bool SameSet(u, v) {
return sets.get(u).intersect(sets.get(v)).length() == 0
}

Disjoint-set forests with union by rank and path compression heuristics (Ian noted that this wouldn’t
work [MDN])

(b)

Check-Tri-Partition(G, V1, V2, V3):


if V1.size + V2.size + V3.size != V
return false

for e in E:
if SameSet(e.left, e.right)
return false

return true

Check_Tri_Partition(G, V1, V2, V3)


{

(c)
All that’s required is to be able to quickly iterate over the edges, so adjacency list is
probably appropriate, if G is tripartite then it also won’t be dense so there is probably no
space advantage to using adjacency matrix.

(d)

Θ(¿ E∨f (n)). Need to perform action Θ(f (n)) for each edge in the graph.

(e)

Given G ,V 1 , V 2 and V 3, it’s possible to check a solution in polynomial time as shown in parts a-d
above, so the problem is in NP.

The 3-COLOUR problem can also be reduced to TRI-PARTITE in polynomial time by inserting all
vertices with colour a into V 1, all vertices with colour b into V 2 and all vertices with colour c into V 3, if
the resulting graph is tri-partite then no edge connects two vertices of the same colour, and hence
the graph has a 3-colouring.

As the 3-COLOUR problem is in NPC (reduced from 3-CNF-SAT), then TRI-PARTITE is also NPC.

Ian’s note: The two problems are decision problems (with yes/no answers). The main thing one has to
argue that a graph is 3-colourable iff it is tripartite. The reduction of the 3-colour problem to the problem of
determining if the graph is tripartite is then absolutely trivial.

Question 4
(a)
AAA(n): T (n)=n × ¿
T (n)=n × ¿
¿ n ׿
n (n+1)
BBB(n): T (n)=T (floor ( n/2))+Θ(n)+T (floor (n /2))+ Θ( )
2
¿ 2 ×T (floor (n /2))+ Θ(n)+ Θ(n2 )
¿ 2 ×T (floor (n /2))+ Θ(n2 )

(b)
[i]
n
T (n)=100 T ( )+1 012
10
a = 100, b = 10, f (n)=1 012 ∈ O(1)
nlo g a=nlo g 100 =n2
b 10

f (n)=O(n lo g a−ϵ )for some ϵ > 0, so case 1 of master theorem applies and T (n)∈ Θ(n2 )
b

(Or if you want it to look more like a maths exam:


“WTS ∃ ϵ : f (n)/nlo g a ∈ O(n−ϵ ) . 1 012 /n2 ∈ O(n−2 )⇒ ϵ=2⇒T (n) ∈Θ(n2 ) by case 1 of master
b

theorem.”)

[ii]
n
T (n)=kT ( )+ nk where k ≥ 2 is a constant
k
a = k, b = k, f (n)=n k
nlo g a=nlo g k =n
b k

WTS ∃ ϵ >0 :f (n)/nlo g a ∈O(n ϵ)


b

n k /n∈ O(nk−1 )⇒ ϵ ≥1 since k ≥ 2

Check regularity:
n
af ( )≤ cf ( n)
b
n
kf ( )≤ cf (n)
k
k ׿
nk k 1
k−1
≤ c n which will be true when c= k−1 <1since k ≥ 2
k k

Therefore case 3 of the master theorem applies and T (n)=Θ ¿)

[iii]
n 1
T (n)=4 T ( )+
2 n
1
a = 4, b = 2, f (n)=
n
nlo g a=nlo g 4 =n2
b 2

WTS ∃ ϵ >0 :f (n)/nlo g a ∈O(n−ϵ )


b

n−1 /n2 ⇒ O(n−3 )⇒ ϵ=3

Therefore T (n)=Θ(n lo g a❑)=Θ(n2) by case 1 of master theorem


b

(c)
n
T (n)=3k T ( )+nk ( log n+ 1)
3
k k
a=3 , b=3, f (n)=n (log n+1)
k

nlo g a=nlo g 3 =nk


b 3

The master theorem cannot be used here as f (n)is not polynomially larger than nlo g b a

BUT extension to the master theorem can be used:


f (n)∈ Θ(nk l g 1 n), so the solution is T (n)∈ Θ(nk l g2 n)

(d)
sort(A)
max = Abs(A[n-1] - A[0])

min = ∞
for i = 2 to len(n):
diff = Abs(n[i] - n[i-1])
if diff < min
min = diff

(e)
The preparation for this algorithm is sorting the list which takes O(nlg(n)) time. After this
has been completed then finding the maximum is instant, and finding the minimum take
O(n) time. Therefore in total the sorting dominates the overall time-complexity taking
O(nlg(n)).

Question 5
(a)

The perfectly balanced tree with height 0 has 1 node, and since root.left and root.right are both
null, the perfectly balanced tree with height 0 has 1 leaf node. 2h=2 0=1, so the statement is true
for the base case.

Assume the perfectly balanced tree with height h has at most2hleaf nodes

The perfectly balanced tree with height h + 1 will have at most 2 ×2h leaf nodes, with 2h in the left
subtree and 2hin the right by the inductive hypothesis. Hence the perfectly balanced tree with
height h + 1 has at most 2h+ 1leaf nodes.

(b)

Question 6
(a)
Explanation of the algorithm
SELECT(A, p, r, k) is used to find the kth smallest element of the array A between indices p to r
(inclusive). The question asks to find the worst-case scenario for SELECT(A, 1, n, k). The
algorithm chooses a random element x in A[1]..A[n] as the pivot and partitions the set (i.e. resulting
set will have all elements less than x to the left of x, and larger than x to the right of x).

Say x ends up in at index q after the partitioning. If k is less than q-p (roughly) then the kth smallest
element will be in A[1]..A[q] so run the algorithm on this input. Otherwise it is above q so run the
algorithm on A[q+1]..A[n] to find the k-(q-p) th element (roughly). This process repeats until the
upper and lower indices converge leaving the result.

As in quicksort we would expect the random pivot to roughly half the size of the array that needs to
be checked. But in the worst case it only removes one element to consider. If by poor luck this
continues every time then there are n recursive calls to SELECT, which means n calls to
PARTITION, therefore O(n 2).

(b)

Following the logic from the previous section, it is expected that each call to partition will roughly
halve the size of the input that needs to be checked (like quicksort where the pivot is chosen as the
median element of the array). This leads to lg(n) recursive calls to SELECT, therefore O(n lg (n)).

(c)

Worst case analysis looks at the maximum time it will take to run an algorithm. This is specified by
O(f (n)) or o (f ( n)) or Θ(f (n)) ; these are the upper and tight bounds for the complexity.

Best case analysis looks at the minimum time it will take to run an algorithm. Therefore Ω(f (n))is
used to look at the complexity.

Average case analysis considers how the expected amount of time to run an algorithm - it is often
used for non-deterministic algorithms such as quicksort with a random pivot. It could use o (f (n))
or ω (f ( n)) or O(f (n)) . (Need to check this last one?)

You might also like