CSc 345: Homework 3 Solution (Written Part)
Transcription
CSc 345: Homework 3 Solution (Written Part)
CSc 345: Homework 3 Solution (Written Part) 1. (a) Illustrate the operation of Partition on the array A = {20, 18, 3, 5, 12, 7, 15, 25, 1, 10}. Answer. The following figure illustrates the operation of partition on A: i j 20 18 3 i 5 12 7 15 25 1 10 j 20 18 3 i 5 12 7 15 25 1 10 j 20 18 3 i 5 12 7 15 25 1 10 j 3 18 20 5 12 7 15 25 1 10 i j 3 5 20 18 12 7 15 25 1 10 3 5 20 18 12 7 15 25 1 10 3 5 i j i j 7 18 12 20 15 25 1 10 i j 3 5 7 18 12 20 15 25 1 10 3 5 7 18 12 20 15 25 1 10 3 5 7 1 12 20 15 25 18 10 3 5 7 1 10 20 15 25 18 12 i j i (b) Let A = {4, 1, 2, 3, 5, 6, 8, 7, 9} be an array that has been partitioned using the PARTITION procedure of QUICKSORT. Which element could have been the pivot element? (If there are more than one possibility, list all the possible elements.) Answer. Recall that after partition, the pivot element divides the array in two parts, where each number before the pivot element is less than it and each number after the pivot element is greater than it, only 5, 6 and 9 could have been the pivot element in the array. 2. Design and analyze an algorithm that takes an array A with n numbers and rearranges its numbers so that all odd numbers come before all even numbers in A. You can use only a constant amount of additional memory (temporary variables). Answer. Our algorithm here is similar to the Partition Procedure of QuickSort. Instead of splitting the array in two parts with number less than and greater than a pivot element, we split it in two parts with odd numbers and even numbers. Here is the pseudocode for the algorithm: Algorithm ODD-EVEN((A)) 1. i ← 0 2. for j ← 1 to A.length 3. if A[j]%2 = 1 4. then i ← i + 1 5. swap(A, i, j) 1 Proof of Correctness: Like the Partition Procedure, Algorithm ODD-EVEN keeps three (possibly empty) parts of the array during its execution: the first part (A[1 . . . i]) contains only odd numbers; the second part (A[i + 1 . . . j − 1]) contains only even numbers; and the remaining part of the array is unexamined up to that point. Formally, we prove that Algorithm ODD-EVEN maintains the following loop invariant: “At the beginning of each iteration of the loop of lines 2–5, L = A[1 . . . i] contains only odd numbers, and R = A[i + 1 . . . j − 1] contains only even numbers.” Initialization: At the beginning of the first iteration, i = 0, j = 1, hence both L and R are empty, and the invariant is staisfied. Maintenance: There are two cases two consider. If A[j] is even, then only j is incremented by 1. Then the invariant is satisfied for A[j] (before the increment) and all other entries are unchanged; therefore A[1 . . . i] and A[i + 1 . . . j − 1] (after the increment) contains only odd and only even numbers. If A[j] is odd, then i is incremented it is swapped with A[i] (after the increment of i) with A[j]; hence A[1 . . . i] now contains only odd numbers. With the swap A[j] now contains an even number; hence when j is incremented, A[i + 1 . . . j − 1] again contains only even numbers. Thus the loop invariant is maintained in both cases. Termination: At the termination j = A.length+1; thus A[1 . . . i] have odd numbers and A[i+1 . . . A.length] have only even numbers for some i. Thus each odd number comes before each even number. Running Time: The loop at lines 2–5 runs for n iterations, each time doing a constant amount of work. The running time hence is Θ(n). 3. (a) Starting with the skiplist in Figure 3a, show the results of the following operations, performed one after another. For the insertions, assume that we move an element to a higher level when we have HEAD in our coin flip. Answer. We show the skiplist after each of the operations, performed one after another: −∞ ∞ −∞ −∞ 5 −∞ 5 −∞ 5 12 10 12 15 25 32 ∞ 32 ∞ 32 38 32 38 ∞ ∞ 40 i. insert 16 (4 heads) −∞ ∞ −∞ 16 −∞ 16 32 ∞ 16 32 ∞ 16 32 38 32 38 −∞ 5 −∞ 5 −∞ 5 12 10 12 15 16 2 ∞ 25 ∞ 40 ∞ ii. delete 5 −∞ ∞ −∞ 16 −∞ 16 32 ∞ −∞ 16 32 ∞ 16 32 38 32 38 −∞ −∞ 12 10 12 15 ∞ 16 25 ∞ ∞ 40 iii. insert 35 (1 head) −∞ ∞ −∞ 16 −∞ 16 32 ∞ −∞ 16 32 ∞ 16 32 35 38 32 35 38 −∞ −∞ 12 10 12 15 ∞ 16 25 ∞ ∞ 40 iv. insert 45 (0 heads) −∞ ∞ −∞ 16 −∞ 16 32 ∞ −∞ 16 32 ∞ 16 32 35 38 32 35 38 −∞ −∞ 12 10 12 15 16 ∞ 25 3 ∞ 40 45 ∞ v. delete 25. −∞ ∞ −∞ 16 −∞ 16 32 ∞ −∞ 16 32 ∞ 16 32 35 38 16 32 35 38 −∞ −∞ 12 10 12 15 ∞ ∞ 40 45 ∞ (b) Design and analyze an algorithm that takes two numbers a and b as input and outputs all the elements in a skiplist S that are in the range [a, b] in O(log n + k) times, where n is the number of elements in S and k is the number of elements in the range [a, b]. Answer. The following algorithm outputs all elements in a skiplist S in the range [a, b]. Using SkipSearch it first finds the pointer to an first element at the bottommost level of S, that has a key greater than or equal to a. We then perform a linear search and output each key until we get an element with key greater than b. Algorithm Range-Search(S, a, b) 1. p ← SkipSearch(S,a) 2. while p.key ≤ b 3. print p.key 4. p ← p.right Analysis: SkipSearch returns the pointer p to the leftmost element at the bottom level of S with a key greater than or equal to a. From that point on, we search and print each consecutive element to the right until we get an element with key greater than b. By the property of skiplist, all elements to the left of p have key less than a and all elements to the right of where the search ends, have key greater than b. Thus we output each key in the range [a, b]. The expected running time for SkipSearch is O(log n); after that we spend constant time for each output. Thus the total expected running time is O(log n + k), where k is the number of elements in S in the range [a, b]. 4. (a) Design a Θ(n)-time nonrecursive procedure that reverses a singly linked list of n elements, using only constant additional memory. Answer. In order to reverse a singly linked list L, we will traverse the nodes in L, and put the nodes in a new list. While we insert a node in the new list, we insert it at the begining of the list, therefore, in effect the nodes are put in the new list in the reverse order. Finally we use the head of L to point to the new list. We therefore will need a temporary pointer as the head of the new list and another temporary node for transferring nodes from L to the new list. The following is a psudocode for the algorithm to reverse a linked list in Θ(n) time: Algorithm Reverse(L) 1. create a pointer newHead 2. while head[L] 6= N IL 3. dummy ← head[L] 4. head[L] ← head[L].next 5. dummy.next ← newHead 6. newHead ← dummy 7. head[L] ← newHead Analysis: The algorithm takes each element in L and insert it at the beginning of a new list with head newHead. Therefore the elements in the new list are arranged in the reverse order. Finally head[L] points to the new list, which have all the elements in reversed order. Only additional memory here are the pointer newHead and the linked list node dummy. The running time is Θ(n), since the loop at lines 2–6 iterates over each element of L once and performs constant amount of operations. 4 (b) A linked list is a palindrome if it reads the same going left-to-right and right-to-left. Design a Θ(n)time non-recursive algorithm that tests whether a singly linked-list is a palindrome. You can use only constant additional memory. Answer. We use the algorithm in part (a) here. Our algorithm is as follows: Algorithm Palindrome(L) 1. Find the mid-point of L; either by traversing the whole list once, keeping a counter n; and then traversing L again up to the n/2-th node; or by keeping two pointers: first pointer traverses one node at a time and the other skips one node in between 2. mid ← the pointer to the mid-point of L 3. Reverse the second half of L by calling Reverse on mid. 4. Use two pointers, one in the start of the list and the other at mid, and traverse the list while untill the value at the two pointers are not equal or the first pointer reaches mid. In the first case output FALSE; in the second case output TRUE. Note that the above algorithm will differ slightly based on whether L has even or odd number of elements. If it has an even number of elements n, then mid points to the (n/2 + 1)-th element and the last step makes at most n/2 comparisons. On the other hand, if n is odd, then mid points to the (dn/2e + 1)-th element; and at the last step, we make at most bn/2c comparisons. Analysis: The correctness is trivial; we reverse the list L from the middle; then L was a palindrome if and only if after the reversal, the two halfs of the list are equal. Steps (1) and (4) traverse the list once, taking Θ(n) time. step (2) is a constant-time operation and step (3) runs in linear time (due to part (a)). Thus the total running time is Θ(n). 5. A stack is sorted if it can pop its elements in sorted order. Similarly a queue is sorted if it can dequeue its elements in sorted order. (a) Design and analyze an efficient algorithm that sorts an input stack using only one additional stack and constant additional memory. Answer. We simulate the InsertionSort for this algorithm. Let S be the input stack and S 0 be the additional stack. We pop one element at a time from S and then put this in S 0 in such a way that S 0 always remains sorted in the non-increasing order. At the end we pop each element in S 0 and push it to S to find a sorted stack in S. Here is the psudocode: Algorithm Stack-Sort(S,S 0 ) 1. Push(S 0 , −∞) 2. while S is not empty 3. temp←Pop(S) 4. Push(S, #) 5. while Top(S 0 )> temp 6. Push(S,Pop(S 0 )) 7. Push(S 0 , temp) 8. while Top(S)6= # 9. Push(S 0 ,Pop(S)) 10. Pop(S) 11. while Top(S 0 )> −∞ 12. Push(S,Pop(S’)) Proof of Correctness: The algorithm first puts all elements of S into S 0 in the non-increasing order (Lines 2–10). It then copies S 0 into S so that the order is now reversed making S a sorted stack (Lines 11-12). First we analyze the first part of the algorithm (Lines 2–10). We maintain the folloiwng loop invariant for the loop in Lines 2–10: “At the beginning of each iteration, Stack S 0 is in nonincreasing order” Initialization: At the beginning S 0 contains only −∞, and since it contains only one element, it is in nonincreasing order. Hence the invaraint is satisfied. Maintenance: We pop an element from S to temp. We then pop each element in S 0 greater than temp and put them in S in the reverse order. At this point each element in S 0 is less than temp and are in nonincreasing order. Next we put temp in S 0 , followed by all elements that we have just put to S, now again in non-increasing order. Thus S 0 remains in nonincreasing order. 5 Termination: At the end of the loop, S 0 contains all elements of S in nonincreasing order. Finally in the loop of Lines 11-12, we put all the elements back to S, reversing the order, thus S becomes sorted. Running Time: In the worst case, if S was sorted in decreasing order, then in each iteration of the while loop in lines 2–10, each element of S 0 will be put to S and then back to S 0 . Thus the running time in the worst case is O(1 + 2 + . . . + n) = O(n2 ), which dominates the O(n) running time of the last step of copying the elements from S 0 back to S. (b) Given a sorted stack S, design an efficient procedure to insert another element in S, keeping S sorted. You can again use an additional stack and constant additional memory. Analyze the running time for your insertion procedure. Answer. We follow a similar technique as in part (a) where we inserted an element in S 0 keeping S 0 in non-increasing order. Here we want to insert an element to an sorted stack S, keeping S sorted. Algorithm Insert-Sorted(S, S 0 , x) 1. while Top(S)< x 2. Push(S 0 ,Pop(S)) 3. Push(S 0 , x) 4. while S 0 is not empty 5. Push(S,Pop(S 0 )) Analysis: We first put all elements of S smaller than x in S 0 in the reverse order. At this point each element of S are smaller than x and they are in sorted order. Next we push x to S, followed by all elements greater than x to S, back from S 0 . Thus S remains sorted. In the worst case all elements of S can be smaller than x, and hence they all might be inserted to S 0 and then back to S. Thus the running time is O(n) in the worst case. (c) Design and analyze an algorithm that sorts an input queue using only an additional queue and constant additional memory. Answer. Once again we simulate the InsertionSort, this time using queues. Let Q be the input queue and Q0 be the additional queue. We dequeue one element at a time from Q and then put this in Q0 in such a way that Q0 remains sorted. At the end we dequeue each element in Q0 and push it to Q to find a sorted queue in Q. Here is the psudocode: Algorithm Queue-Sort(Q, Q0 )E 1. nqueue(Q0 , ∞) 2. while Q is not empty 3. temp ← Dequeue(Q) 4. while Front(Q0 )< temp 5. Enqueue(Q0 ,Dequeue(Q0 )) 6. Enqueue(Q0 , temp) 7. while Front(Q0 )< ∞ 8. Enqueue(Q0 ,Dequeue(Q0 )) 9. Enqueue(Q0 ,Dequeue(Q0 )) 10. while Front(Q0 )< ∞ 11. Enqueue(Q,Dequeue(Q0 )) Proof of Correctness: The algorithm first puts all elements of Q into Q0 in the sorted order (Lines 2–9). It then copies Q0 into Q (Lines 10-11). First we analyze the first part of the algorithm (Lines 2–9). We maintain the folloiwng loop invariant for the loop in Lines 2–9: “At the beginning of each iteration, Stack Q0 is sorted” Initialization: At the beginning Q0 contains only ∞, a sorted list with one element. Hence the invaraint is satisfied. Maintenance: We dequeue an element from Q to temp. We then enqueue each element in Q0 smaller than temp and put them at the back of Q0 (after ∞). At this point each element in Q0 up to ∞ is greater than temp and each element after ∞ in Q0 are smaller than temp; while both parts are sorted by themselves. Next we put temp in Q0 and dequeue and enqueue each element in Q0 greater than temp, including ∞, thereby making Q0 sorted. Termination: At the end of the loop, Q0 contains all elements of Q in the sorted order. Finally in the loop of Lines 10-11, we put all the elements back to Q, thus Q becomes sorted. 6 Running Time: In each iteration of the while loop in lines 2–9, each element of Q0 is dequeued and enqueued back to Q0 . Thus the running time is O(1 + 2 + . . . + n) = O(n2 ), which dominates the O(n) running time of the last step of copying the elements from Q0 back to Q. [[[Note: this algorithm in part (c) runs the same time on all inputs, unlike the algorithm in part (a). Can you make the algorithm in part (c) to run in just a single queue?]]] Extra Credit: When rock-climbing a route with n holds, the experts can go up 1 or 2 holds in one step. How many different ways are there to get to the top of the route? Answer. Let f (n) be the number of ways to climb n holds. Then f (1) = 1 since there are only 1 way to climb n hold; f (2) = 2 since there are two ways to clomb two holds: either one by one or both holds at once. In general for f (n), one can either (i) climb 1 hold in the first step and the climb the remaining n − 1 holds in f (n − 1) ways ;or (ii) climb 2 holds in the first step and then climb the remaining n − 2 holds in f (n − 2) ways. Therefore f (n) = f (n − 1) + f (n − 2). With f (1) = 1, f (2) = 2; then f (n) is the (n − 1)-th fibonacci number. 7