User:Zelhar/Algorithms0 maman13

Q 1
A = [10 22 7 6 15 99 22 35 22 15 10 3 4] heap-size(A) = length(A) ( = 13) BUILD-HEAP(A): HEAPIFY(A,6): A = [10 22 7 6 15 99 22 35 22 15 10 3 4] HEAPIFY(A,5): A = [10 22 7 6 15 99 22 35 22 15 10 3 4] HEAPIFY(A,4): A = [10 22 7 35 15 99 22 6 22 15 10 3 4]
 * HEAPIFY(A,8): A = [10 22 7 35 15 99 22 6 22 15 10 3 4]

HEAPIFY(A,3): A = [10 22 99 35 15 7 22 6 22 15 10 3 4]
 * HEAPIFY(A,6): A = [10 22 99 35 15 7 22 6 22 15 10 3 4]

HEAPIFY(A,2): A = [10 35 99 22 15 7 22 6 22 15 10 3 4]
 * HEAPIFY(A,4): A = [10 35 99 22 15 7 22 6 22 15 10 3 4]

HEAPIFY(A,1): A = [99 35 10 22 15 7 22 6 22 15 10 3 4]
 * HEAPIFY(A,3): A = [99 35 22 22 15 7 10 6 22 15 10 3 4]
 * HEAPIFY(A,7): A = [99 35 22 22 15 7 10 6 22 15 10 3 4]

The Heap is built. exchange A[1],A[13] decrease heap-size (to 12), HEAPIFY(A,1): A = [35 4 22 22 15 7 10 6 22 15 10 3 99] A[1],A[2] switch HEAPIFY(A,2): A = [35 22 22 4 15 7 10 6 22 15 10 3 99] A[2],A[4] switch HEAPIFY(A,4): A = [35 22 22 22 15 7 10 6 4 15 10 3 99] A[4],A[9] switch, and A[9] is a leaf. Exchange A[1],A[12] decrease heap-size (to 11), HEAPIFY(A,1): A = [22 3 22 22 15 7 10 6 4 15 10 35 99] HEAPIFY(A,2): A = [22 22 22 3 15 7 10 6 4 15 10 35 99] HEAPIFY(A,4): A = [22 22 22 6 15 7 10 3 4 15 10 35 99] HEAPIFY(A,8): A[8] is a leaf. Exchange A[1],A[11] decrease heap-size (to 10), HEAPIFY(A,1): A = [22 10 22 6 15 7 10 3 4 15 22 35 99] HEAPIFY (A,2): A = [22 15 22 6 10 7 10 3 4 15 22 35 99] HEAPIFY (A,5): A = [22 15 22 6 15 7 10 3 4 10 22 35 99] HEAPIFY (A,10): no change. Exchange A[1],A[10] decrease heap-size (to 9), HEAPIFY(A,1): A = [22 15 10 6 15 7 10 3 4 22 22 35 99] HEAPIFY (A,3): no change. Exchange A[1],A[9] decrease heap-size (to 8), HEAPIFY(A,1): A = [15 4 10 6 15 7 10 3 22 22 22 35 99] HEAPIFY (A,2): A = [15 15 10 6 4 7 10 3 22 22 22 35 99] HEAPIFY (A,5): no change. Exchange A[1],A[8] decrease heap-size (to 7), HEAPIFY(A,1): A = [15 3 10 6 4 7 10 15 22 22 22 35 99] HEAPIFY (A,2): A = [15 6 10 3 4 7 10 15 22 22 22 35 99] HEAPIFY (A,4): no change Exchange A[1],A[7] decrease heap-size (to 6), HEAPIFY(A,1): A = [10 6 10 3 4 7 15 15 22 22 22 35 99] Exchange A[1],A[6] decrease heap-size (to 5), HEAPIFY(A,1): A = [10 6 7 3 4 10 15 15 22 22 22 35 99] HEAPIFY(A,3): no change. Exchange A[1],A[5] decrease heap-size (to 4), HEAPIFY(A,1): A = [7 6 4 3 10 10 15 15 22 22 22 35 99] HEAPIFY(A,3): no change. Exchange A[1],A[4] decrease heap-size (to 3), HEAPIFY(A,1): A = [6 3 4 7 10 10 15 15 22 22 22 35 99] HEAPIFY(A,2): no change. Exchange A[1],A[3] decrease heap-size (to 2), HEAPIFY(A,1): A = [4 3 6 7 10 10 15 15 22 22 22 35 99] Exchange A[1],A[2] decrease heap-size (to 1), HEAPIFY(A,1): A = [3 4 6 7 10 10 15 15 22 22 22 35 99] A has been sorted. 

2.1
No: let A = [1 2 3]. If we use BUILD-HEAP(A), then the result is: HEAPIFY(A,1)-> [3 2 1]. If we use BUILD-HEAP'(A): HEAP-INSERT(A,A[2]) -> A=[2 1 3], HEAP-INSERT(A,A[3]) -> A=[3 1 2].

2.2
let n=2^(k+1)-1, then n is the size of a complete binary tree, and it has 2^k > n/2 leaves. Let A be the array defined by A[i]=0 for i=1...2^k-1, A[i]=1+i-2^k for i=2^k ... n. Each of the 2^k leaves is inserted up to the root requiring θ (k)= θ(lg(n)) from HEAP-INSERT,since k is the height of each leaf. And since there are 2^k > n/2 leaves BUILD-HEAP'(A) has running time of θ(nlg(n)) (The for loop in BUILD-HEAP' is trunnig on i=2..n with increment 1).

3.1
n=5, The number of leaves in a decision tree is n!=120, which is the number of permutations of n. A tree of height h has at most 2^h leaves. Since 2^6=64, there must be a leaf of height 7, each node in the path from the root (at height 0) down to the leaf (excluding it), represents one comparison, totaling at 7 comparisons.

3.2
//I will also use the function MERGE(A,p,q,r) the same that is used in the //MERGE-SORT algorithm. //The Algorithm: sort-5-in-7(A) if A[2]<A[1]: exchange(A[1],A[2]) if A[4]<A[3]: exchange(A[3],A[4]) if A[5]<A[2]: exchange(A[2],A[5]) if A[5]<A[3]: exchange(A[3],A[5]) //now A[5] is the maximum and we jest have to merge (A[1],A[2]) and //(A[3],[A[4]) (both pairs are sorted) MERGE(A,1,2,4) //Merging two sorted pairs takes at most 3 comparisons. //The 5-array is sorted using at most 7 comparisons.

3.3
Insert-Sort: in the worst case (when the input array is sorted in decreasing order) it requires 4+3+2+1=10 comparisons between elements in the array.

Merge-Sort: The array A = [5 1 2 4 3] requires 8 comparisons to sort: it is splits into [5 1 2], [4 3]. The left sub-array then splits and merged at the cost of 1 comparison. The right sub-array splits into [5 1], [2]. 1 comparisons is required to sort out the leftmost, [1 5], The merger of the sub-arrays [1 5], [2] into [1 2 5] takes 2 comparisons. Then the merger of [1 2 5], [3 4] takes an additional 4 comparisons, totaling 8 comparisons.

Heap-Sort: for each node i with two sons, HEAPIFY(A,i) costs at list 2 comparisons (one for each son) and perhaps more, in case an exchange occured and then HEAPIFY is recursed. BUILD-HEAP therefor costs at list 4 comparisons, because node 1 and 2 both have two sons. HEAP-SORT calls for BUILD-SORT and then HEAPIFY(A,1) costs more than 8 comparisons (because for for the first two calls to HEAPIFY(A,1) we have heap-size(A)>3 and so node 1 has two sons.

Quick-Sort In the worst case (partitioning at either edge in each iteration) it takes at list 5 + 4 + 3 +2 +1 = 15 comparisons.

Q 4
First I define a function that calculates the real value of element i in a heap (as defined for this exercise) by summing up the elements along the path from i to the root

EXTRACT-VALUE(A,i) //extracts the original value of A[i] from the heap 01   x=0 02   for j=2 to i: //a telescopic sum that adds up to x=A[1]-A[i] 03       x=x+A[i] 04       i=PARENT(i) 05   x=A[1]-x 06   return x

This functions adds up the element in the path from node i up to the root. It has already been proved in the textbook that the height of a nearly complete binary tree of size i is lg(i), therefore The above function running time is θ(lg(i)).

HEAPIFY:
HEAPIFY(A, i)      //The original function 1 l = LEFT(i) 2 r = RIGHT(i) 3 if l ≤ heap-size[A] and A[l] > A[i]: 4   largest = l     5    else: largest = i     6 if r ≤ heap-size[A] and A[r] > A[largest]: 7   largest = r     8 if largest ≠ i:     9    exchange A[i] = A[largest] 10   HEAPIFY(A, largest)

The running time of the original function was shown to be θ(lg(n)) in the textbook.

HEAPIFY-M(A, i)    //The modified version 01   l=RIGHT(i) 02   r=LEFT(i) 03   if l ≤ heap-size[A] and A[l] > A[i]: 04       largest=l 05   else: 06       largest=i 07   if r ≤ heap-size[A] and A[r] > A[largest]: 08       largest=r 09   if largest ≠ i:    10        if LEFT(largest)<=heap-size[A]: 11           A[LEFT(larget)]=A[largest] - A[LEFT(larget)] //turn it into //the root of its sub-heap 12       if RIGHT(largest)<=heap-size[A]:                 //turn it into //the root of its sub-heap 13           A[RIGHT(larget)]=A[largest] - A[RIGHT(larget)] 14       exchange A[i] ↔ A[largest] 15       HEAPIFY-M(A, largest) 16   else:           //make A[i]'s sons contain the subtraction value. 17       A[l]=A[i]-A[l] 18       A[r]=A[i]-A[r] The modified function, HEPIFY-M has the same recursion pattern as the original, (they only differ in a θ(1) element of the non-recursive part. That is T(n) ≤ T(2n/3) + θ(1) = θ(lg(n))

BUILD-HEAP:
BUILD-HEAP(A)      //The Original 1 heap-size[A] = length[A] 2 for i = floor(length[A]/2) downto 1: 3      HEAPIFY(A, i)

BUILD-HEAP-M(A) //The modification 1  heap-size[A] = length[A] 2  for i = floor(length[A]/2) downto 1 : 3      HEAPIFY-M(A,I)

The modified function has asymptotically the same running time as the original since it only differ by calling to HEAPIFY-M instead of HEAPIFY but both functions has the same asymptotic running time as shown above.

HEAPSORT:
HEAPSORT(A)    //The Original 1 BUILD-HEAP(A) 2 for i = length[A] downto 2: 3   exchange A[1] ↔ A[i] 4   heap-size[A] ← heap-size[A] - 1 5   HEAPIFY(A, 1)

HEAPSORT-M(A) 1  BUILD-HEAP-M(A) 2  for i=length[A] downto 2: 3      r=RIGHT(1) 4      l=LEFT(1) 5      if r<=heap-size(A): //turn the right and left sons into roots of their respective sub-heaps 6          A[l]=A[1]-A[l] 7          A[r]=A[1]-A[r] 8      else-if l<=heap-size(A): 9          A[l]=A[1]-A[l] 10     A[i]=EXTRACT-VALUE(A,i) //extract the value of A[i] as defined above. 11     exchange(A[i],A[1]) 12     heap-size(A)=heap-size(A)-1 13     HEAPIFY-M(A,1)

There is a call to EXTRACT-VALUE inside the loop but its running time is asymptotically the same as that of HEAPIFY (and HEAPIFY-M) that is called inside the loop of the original HEAPSORT. The running time of HEAPSORT-M is therefor the same as the original, θ(nlg(n)).

HEAP-EXTRACT-MAX:
HEAP-EXTRACT-MAX(A)    //The Original 1 if heap-size[A] < 1: 2  error "heap underflow" 3 max = A[1] 4 A[1] = A[heap-size[A]] 5 heap-size[A] = heap-size[A] - 1 6 HEAPIFY(A, 1) 7 return max

HEAP-EXTRACT-M(A)      //the modification 1  if heap-size[A]<1: 2      "error underflow" 3  max=A[1] 4  A[1]=EXTRACT-VALUE(A,heap-size(A)) 5  heap-size[A] = heap-size[A] - 1 6  HEAPIFY-M(A,1) 7  return max

Since HEAPIFY, HEAPIFY-M and EXTRACT-VALUE all have running time of θ(lg(n)), the running time of the modified function is also (θ(lg(n))), same as the original.

HEAP-INSERT:
HEAP-INSERT(A,key)     //Original 1  heap-size(A)=heap-size(A)+1 2  i=heap-size(A) 3  while i>1 and A[PARENT(i)]1 and y<0: 06       A[i]=A[PARENT(i)] 07       k=RIGHT(i)+LEFT(i)-j 08       if k<=heap-size(i):  //k needs to be adjusted since its //grandfather become its father 09           A[k]=A[k]+A[j] 10       j=i 11       i=PARENT(i) 12       x=A(i)-x  //Extracts the value of A[PARENT(i)] 13       y=x-key 14   A[i]=y 15   A[RIGHT(i)]=key - EXTRACT-VALUE(A,RIGHT(i)) 16   A[LEFT(i)]=key - EXTRACT-VALUE(A,LEFT(i))

The while loop iterated along the path from the new leaf up to the root θ(lg(n)) cycles. Each iteration costs a constant time (its bound is independent of i and of the specific data). Outside the loop there is a call for the function EXTRACT-VALUE which takes time O(lg(n)). Therefore The running time for HEAP-INSERT is θ(lg(n)) asymptotically the same as the original.

Q 5
Let key be the value to search for in the sorted array A. Whenever key is compared to some A[i], We know that if A[i]key the we need not search in A[i+1]...A[n]. For any comparison-based search algorithm, the worst case scenario is when key is not equal to any element, and in each comparison, we can eliminate the no more than half of the remaining elements, for example if n=10 i=2, the worst case is A[2]<key so we still have to search in A[3]...A[10] and we can only eliminate A[1]. Therefor we can deduce that binary search is the optimal comparison-based search algorithm, because it always checks the middle, and hence it always eliminates half the elements (or all of them if key is found). At worst case, binary search will have to do lg(n) comparisons, since that's the length of a sequences of middle values, (for example: n, n/2, n/4, ... 1 which by what we know from HEAP-SORT, is a sequence of length=height(HEAP)=lg(n)).

6.1
 A = [3 7 3 3 8 8 8 7 5 9 9 5 5 9 9 9 7 5 3 9] (length(A)=20, k=9) C = [0 0 0 0 0 0 0 0 0] for j=1 to 20: C(A[j])=C[A[j]]+1: j=1: A[1]=3 so C[3]=C[3]+1=1 j=2: A[2]=7 so C[7]=c[7]+1=1 and so forth... The result is: C = [0 0 4 0 4 0 3 3 6] for i=2 to k: C[i]=C[i]+C[i-1]: C[2]=C[2]+C[1]=0, C[3]=4+0=4, C[4]=0+4=4, C[5]=4+4=8, C[6]=0+8=8, C[7]=3+8=11,C[8]=3+11=14 C[9]=6+14=20, C = [0 0 4 4 8 8 11 14 20] for j=20 downto 1:
 * B[C[A[j]]]=A[j]
 * C[A[j]]=C[A[j]]-1

j=20: A[20]==9 C[9]==20 B[20]=A[20]==9, C[9]=19 j=19: A[19]==3 C[3]==4 B[4]=A[19]=3, C[3]=3 j=18: A[18]==5 C[5]==8 B[8]=A[18]=5 C[5]=7 C = [0 0 3 4 7 8 11 14 19] j=17: A[17]==7 C[7]==11 B[11]=A[17]=7 C[7]=10 C = [0 0 3 4 7 8 10 14 19] j=16: A[16]==9 C[9]==19 B[19]=A[16]=9 C[9]=18 C = [0 0 3 4 7 8 10 14 18] j=15: A[15]==9 C[9]==18 B[18]=A[15]=9 C[9]=17 C = [0 0 3 4 7 8 10 14 17] j=14: A[14]==9 C[9]==17 B[17]=A[14]=9 C[9]=16 C = [0 0 3 4 7 8 10 14 16] j=13: A[13]==5 C[5]==7 B[7]=A[13]=5 C[5]=6 C = [0 0 3 4 6 8 10 14 16] j=12: A[12]==5 C[5]==6 B[6]=A[12]=5 C[5]=5 C = [0 0 3 4 5 8 10 14 16] j=11: A[11]==9 C[9]==16 B[16]=A[11]=9 C[9]=15 C = [0 0 3 4 5 8 10 14 15] j=10: A[10]==9 C[9]==15 B[15]=A[10]=9 C[9]=14 C = [0 0 3 4 5 8 10 14 14] j=9: A[9]==5 C[5]==5 B[5]=A[9]=5 C[5]=4 C = [0 0 3 4 4 8 10 14 14] B = [0 0 0 3 5 5 5 5 0 0 7 0 0 0 9 9 9 9 9 9] j=8: A[8]==7 C[7]==10 B[10]=A[8]=7 C[7]=9 C = [0 0 3 4 4 8 9 14 14] j=7: A[7]==8 C[8]==14 B[14]=A[7]=8 C[8]=13 C = [0 0 3 4 4 8 9 13 14] j=6: A[6]==8 C[8]==13 B[13]=A[6]=8 C[8]=12 C = [0 0 3 4 4 8 9 12 14] j=5: A[5]==8 C[8]==12 B[12]=A[5]=8 C[8]=11 C = [0 0 3 4 4 8 9 11 14] j=4: A[4]==3 C[3]==3 B[3]=A[4]=3 C[3]=2 C = [0 0 2 4 4 8 9 11 14] j=3: A[3]==3 C[3]==2 B[2]=A[3]=3 C[3]=1 C = [0 0 1 4 4 8 9 11 14] j=2: A[2]==7 C[7]==9 B[9]=A[2]=7 C[7]=8 C = [0 0 1 4 4 8 8 11 14] j=1: A[1]==3 C[3]==1 B[1]=A[1]=3 C[3]=0 C = [0 0 0 4 4 8 8 11 14] B = [3 3 3 3 5 5 5 5 7 7 7 8 8 8 9 9 9 9 9 9] The array is sorted. 

6.2
A = [0.33 0.75 0.33 0.88 0.77 0.99 0.7 0.35 0.75 0.9] (10) insert A[i] into list B[floor(n*A[i])] i=1..10: B[3] = [0.33 0.33 0.35] B[7] = [0.75 0.77 0.7 0.75] B[8] = [0.88] B[9] = [0.99 0.9] Insert-Sort each list: B[3] = [0.33 0.33 0.35] B[7] = [0.7 0.75 0.75 0.77] B[8] = [0.88] B[9] = [0.9 0.99] Concatenate the lists and into A:  A = B[3]B[7]B[8]B[9] = [0.33 0.33 0.35 0.7 0.75 0.75 0.77 0.88 0.8 0.99] The Array is sorted.