Table of Contents

5.1 The Mergesort Algorithm

Summary

Mergesort sorts a list by dividing it into two equal parts, sorting each half by recursion, then combining resulting sorted components. The recurrence relation for mergesort is T(n) ⇐ 2T(n/2) + cn when n>2 and T(2) ⇐ c. There are two ways to solve the recurrence relation: unrolling the recursion and substituting a solution. Unrolling the recurrence includes analyzing the first few levels, identifying a level pattern, and sum over all levels of recursive calls. Substituting requires you to is where you guess the solution, substitute it in, and prove that it works. The total runtime of mergesort is O(nlogn).

Mergesort
Approaches to Solving a Recurrence Relation
Unrolling the Mergesort Recurrence
Substituting a Solution into the Mergesort Recurrence
An Approach Using Partial Substitution
Additional Information

At first, the concept of unrolling a recurrence was difficult for me to grasp. However, now, it is easy to see that you just see the number of problems on each level, the size of each problem, and the time cost of each problem on the level. Once you add them all up and are able to generalize a level rule, then you just have to think about how many times the recursion can occur, then you're done! You have a runtime.

On a scale of 1 to 10, the readability of this section is an 8. The only reason this is not a 10 is that at first, unrolling was a difficult concept for me to grasp. However, now, after seeing unrolling in class a few times and reading all of the sections for this reading, unrolling makes a lot of sense now. It was a really great idea for the authors to unroll an algorithm that we already knew the runtime of and knew how it worked.

5.2 Further Recurrence Relations

Summary

Now, we need to consider divide and conquer algorithms that create recursive calls on q subproblems of size n/2 and comibine the results in linear time. We already handled when q=2 with mergesort. When q > 2, the recurrence relation is T(n) ⇐ qT(n/2) + cn when n > 2 and T(2) ⇐c. By unrolling the recursion, we come to the conclusion that the runtime of the algorithm is O(n^(log2q)). When q = 1, by unrolling the recurrence, we get that T(n) ⇐ 2cn = O(n). A related recurrence of T(n) ⇐2T(n/2) +cn^2 when n > 2 and T(2) ⇐ c has a runtime of O(n^2), which we see by unrolling the recurrence.

The Case of q>2 Subproblems
The Case of One Suproblem
Question
Additional Information

At first, I was confused on why the general q>2 subproblems still had log2n recurrences, but now I understand. The number of problems are the different than in mergesort, but the size of each problem is spilt in half on each call of the recursion like with mergesort. Therefore, there would still have the be log2n levels. It is not dependent on the number of problems; it is dependent on the size of the problem.

On a scale of 1 to 10, the readability of this section was a 9. The progression of the general solution of what was discussed in the previous chapter to the special cases of q = 1 was a really great structuring on the authors' part. They really knew the most natural progression of information and presented it in a way that was clear and easy to understand. Another thing that helped is that there aren't a bunch of really long proofs with new notation, which are sometimes hard to comprehend.

5.3 Counting Inversions

Summary

We want to be able to tell how far away a list is from being in order. In order to do this, we just have to count the number of inversions. The brute force solution to the algorithm runs in O(n^2) time, but we can do better than that. We will divide the list in half, count the number of inversions in each piece, and make the algorithm recursively sort the two halves. Once we get the two sorted halves, we want to combine them into a single sorted list while counting the number of inversions as we go. The Merge-and-Count routine takes O(n) time and the Sort-and-Count algorithm takes a total O(nlogn) time.

The Problem
Designing the Algorithm
Merge-and-Count
   Merge-and-Count(A, B)
        Maintain a current pointer into each list, initialize to point to the front elements
        Maintain a variable Count for the number of inversion, initialized to 0
        While both lists are nonempty:
             Let ai and bj be the elements pointed to by the Current pointer
             Append the smaller of these two to the output list
             If bj is the smaller element:
                  Increment Count by the number of elements remaining in A
             Endif
             Advance the Current pointer in the list from which the smaller element was selected
        Endwhile
        Once one list is empty, append the remainder of the other list to the output
        Return Count and the merged list
Sort-and-Count
   Sort-and-Count(L)
        If the list has one element:
             There are no inversions
        Else:
             Divide the list into two halves:
                  A contains the first half of the elements (ceiling of n/2)
                  B contains the remaining elements (floor of n/2)
             (rA, A) = Sort-and-Count(A)
             (rB, B) = Sort-and-Count(B)
             (r, L) = Merge-and-Count(A, B)
        Endif
        Return r = rA + rB + r, and the sorted list L
Additional Information

At first, I was confused on exactly how we were counting the inversions by sorting. Later, it became clear. In the Merge-and-Count list merges the two sorted lists together and if an element in the second list (which was later in the ordering) comes before elements in the first list, you increment the inversion count by the number of elements left in the first list. Inversions are also counted when the two separate lists in divide and conquer are sorted.

On a scale of 1 to 10, the readability of this section was an 8. We had already learned about inversions before this section in regards to exchange arguments, so the problem was easy to comprehend, and the solution was very clever but once they told us what it was, it was not difficult to comprehend. It kind of built off mergesort, which is what we learned about in the first section.