## Lecture 15: Mergesort 4/10

DJW section 10.3, pp. 690-697,

The second fast sorting algorithm is mergesort. Mergesort is a divide and conquer algorithms. A divide and conquer algorithm has the following general form:

1. Split the problem into subproblems.
2. Recusively solve each subproblem
3. Combine the solutions into a solution of the original problem.

### High-level code

```int[] mergesort(int[] A) {
if (A.length == 1) return A;   // Base case of the recursion
B1 = first half of A;         //  Divide
B2 = second half of A;
C1 = mergesort(B1);           // Recursively solve on the subproblems
C2 = mergesort(B2);
D = merge C1 and C2           // Combine the solutions
using the two-fingered method for combining ordered lists;
return D;
}
```

### Example 1:

```A = [31,41,59,26,53,58,27,18,28,45,9,14,42,12,17]
A.length=15
mergesort(A)
B1 = [31,41,59,26,53,58,27,18]
B2 = [28,45,9,14,42,12,17]

C1 = mergesort(B1)
A = [31,41,59,26,53,58,27,18]
B1 = [31,41,59,26]
B2 = [53,58,27,18]

C1 = mergesort(B1)
A = [31,41,59,26]
B1 = [31,41]
B2 = [59,26]

C1 = mergesort(B1)
A = [31,41]
B1 = [31]
B2 = [41]

C1 = mergesort(B1) return [31]
C2 = mergesort(B2) return [41]
D = merge([31],[41]) = [31,41]
C1 = [31,41]

C2 = mergesort(B2)
A = [59,26]
B1 = [59]
B2 = [26]

C1 = mergesort(B1) return [59]
C2 = mergesort(B2) return [26]
D = merge([26],[59]) return [26,59]
C2 = [26,59]
D = merge([31,41],[26,59])

C1 = [26,31,41,59]
// From here down I'll stop the recurrence at 4
C2 = mergesort([53,58,27,18]) = [18,27,53,58]
D = merge([26,31,41,59],[18,27,53,58]) = [18,26,27,31,41,53,58,59]

C1 = [18,26,27,31,41,53,58,59]
C2 = mergesort([28,45,9,14,42,12,17])
A = [28,45,9,14,42,12,17])
B1 = [28,45,9,14]
B2 = [42,12,17]

C1 = mergesort([28,45,9,14]) = [9,14,28,45]
C2 = mergesort([42,12,17]) = [12,17,42].
D = merge(C1,C2) = [9,12,14,17,28,42,45]
C2 = [9,12,14,17,28,42,45]
D = merge([18,26,27,31,41,53,58,59], [9,12,14,17,28,42,45]) =
[9,12,14,17,18,26,27,28,31,41,42,45,53,58,59]
```

### Coding tricks for efficiency: part 1

• Stop recursion at N=7 or so and go to an insertion sort (faster for small N)
• It is not necessary to split A into two arrays B1 and B2; just use segments of A.
```Top level call: mergesort(A, 0, A.length-1);

int[] mergesort2(int[] A, int start, int end)
if (end-start < = 7)                    // Base case of the recursion
return insertionSort(A,start,end);
m = (start+end)/2;
C1 = mergesort2(A,start,m);      // Recursively solve on the subproblems
C2 = mergesort2(A,m+1,end);
D = merge(C1,C2)
return D;
}
```

### More optimization

• Divide A (conceptually) into groups of 7.
• Insertion sort each group of 7.
• Merge pairs of groups of 7 into groups of 14 in sequence in a new array.
• Move pairs of groups of 14 into groups of 28 in sequence in a new array.
• Keep doing this until your group is the whole array: then stop.
• Slosh back and forth between two arrays. (Unlike the other sorts, there is no way to do mergesort in a single array -- not an in-place sort.)
```static int smallSize = 7;

mergesort3(int[] A) {
l = A.length;
for (s = 0; s < A.length; s = s+smallSize)
insertionSort(A,s,min(s+SmallSize,A.length-1));
groupSize = smallSize;
slosh = true;                     // direction to move in
int [] D = new int[A.length];
while (groupSize < A.length) {
for (s = 0; s < A.length-groupSize-1; s = s+2*groupSize) {
if (slosh)
merge(A,D,s,groupSize);
else
merge(D,A,s,groupSize);
slosh = !slosh;
groupSize = 2*groupSize;
}
}
if (!slosh)
for (i=0; i < A.length; i++)
A[i]=D[i];
}`

merge(B,C,s,groupSize)   {
i = s;           // finger through the first part of B;
j = s+groupSize: // finger through the second part of B;
top = min(j+groupSize,B.length) // upper limit on second part.
for (k = s; k < top; k++) {    // finger through C
if (i >= s+groupSize) {      // come to the end of the first half
C[k] = B[j];
j++;
}
else if (j >= top)  {        // come to the end of the second half
C[k] = B[i];
i++;
else                         // working on both halfs
if (B[i] < B[j]) {         // merge item from first half
C[k] = B[i];
i++;
}
else {                     // merge item from second half
C[k] = B[j];
j++
}
}  // end for loop
}
```
Note: We've turned the recursive procedure into an iterative procedure, in a rather unusual way.

### Example 2

Take smallSize = 3. Example:
```A = [31,41,59,26,53,58,27,18,28,45,9,14,42,12,17,32,30]

Insertion sort in groups of 3:
A = [31,41,59 | 26,53,58 | 27,18,28 | 45,9,14 | 42,12,17 | 32,30] input
A = [31,41,59 | 26,53,58 | 18,27,28 | 9,14,45 | 12,17,42 | 30,32] result.

groupSize = 3
A = [31,41,59|  26,53,58 | 18,27,28 | 9,14,45 | 12,17,42 | 30,32] result.
|           |          |         |          |         |
|---merge---|          |--merge--|          |--merge--|
slosh=true
D = [26,31,41,53,58,59   | 9,14,18,27,38,45   | 12,17,30,32,42]
|                     |                    groupSize=6
|---------merge-------|                    slosh=false

A = [9,14,18,26,27,31,38,41,45,53,58,59       | 12,17,30,32,42]
|                                         |    groupSize=12
|---------------------merge---------------|    slosh=true

D = [9,12,14,17,18,26,27,30,31,32,38,41,42,45,53,58,59]
```

### External sort

Very good for sorting on external media.

Magnetic tape: Two tapes for A; first and second half. Two tapes for D: first and second half. Merge groups in corresponding position on the two tapes, rather than consecutive groups, thus:

```A (tape1) = [31,41,59|  26,53,58 | 18,27,28 |
A (tape2)    9,14,45 |  12,17,42 | 30,32]
```
Merge the groups in the same column.

The sloshing technique allows you to do this with 4 tapes. On each pass, you are reading/writing each tape in forward order, which is how tapes like to be read/written.

Disk: smallSize is the size of a disk block. The advantage of this sort is that data is read and written in blocks.

### Time Analysis

Construct the tree of recursive calls:
Each call to mergesort requires time O(N) for the merge plus the time for the recursive calls. So we can calculate the overall time by labelling each node with the time for the merge, and adding these up over the whole tree.

If we add these across levels of constant depth:

At the root, there is one node of size N.
At level 1, there are two nodes of size N/2, for a total of N.
At level 2, there are 4 nodes of size N/4, for a total of N .
... At level H, there are N nodes of size 1, for a total of N .
So the overall total is N*H. But H = log(N) + 1, so the overall time is O(N*log(N)).