We were asked to improve the merge sort algorithm by introducing insertion sort to the code. We have been tasked with doing this by utilising a "levels" logic. Here is the exact description of the mentioned task:
This parameter will be incremented by 1 inside the merge_sort before the two recursive calls. It will keep track of the level index of the recursion tree. You can set this parameter to 1 before calling merge_sort. Based on this, the index for the first level of the recursion tree becomes 1.
The usual method of merge sort + insertion sort suggested does not use "levels"
(Provided by Bernhard Barker under a similar question as an answer.)
static final int THRESHOLD = 10;
static void mergeSort(int f[],int lb, int ub){
if (ub - lb <= THRESHOLD)
insertionSort(f, lb, ub);
else
{
int mid = (lb+ub)/2;
mergeSort(f,lb,mid);
mergeSort(f,mid,ub);
merge(f,lb,mid,ub);
}
}
The improved merge sort method I have come up with is this:
public static void merge_sort_improved(int [] A, int p, int r, int level, int max_level)
{
int q = (int) Math.floor((p + r) / 2);
if (p < r)
{
if (level >= max_level)
insertion_sort_2(A, p, r);
else
{
level++;
merge_sort_improved(A, p, q, level, max_level);
merge_sort_improved(A, q + 1, r, level, max_level);
}
merge(A, p, q, r);
level--;
}
}
However, I have observed during testing that this method takes longer than the implementation above in some cases but is faster in others.
The whole code:
// import java.util.Arrays;
import java.util.Random;
public class ImprovedMergeSort {
public static void main(String[] args) {
int array_size = 65536;
// int array_size = 16;
int[] array = new int[array_size];
int[] array_2 = new int[array_size];
int[] array_3 = new int[array_size];
long start_time, end_time, elapsed_time, min_elapsed_time;
int best_max_level = 1;
Random rand = new Random();
rand.setSeed(System.currentTimeMillis());
for (int i = 0; i < array_size; i++) {
array[i] = rand.nextInt(100);
array_2[i] = array[i];
array_3[i] = array[i];
}
// Running time of merge sort
start_time = System.nanoTime();
merge_sort(array, 0, array_size - 1);
end_time = System.nanoTime();
elapsed_time = end_time - start_time;
System.out.printf("Elapsed time in nanoseconds for original merge sort: %d\n", elapsed_time);
// part (d)
// Running time of improved merge sort
start_time = System.nanoTime();
merge_sort_improved(array_2, 0, array_size - 1, 1, 13);
end_time = System.nanoTime();
elapsed_time = end_time - start_time;
System.out.printf("Elapsed time in nanoseconds for improved merge sort with max_level=13: %d\n", elapsed_time);
// start_time = System.nanoTime();
// merge_sort_improved_2(array_2, 0, array_size - 1, 13);
// end_time = System.nanoTime();
//
// elapsed_time = end_time - start_time;
//
// System.out.printf("Elapsed time in nanoseconds for improved merge sort with max_level=13: %d\n", elapsed_time);
// part (e)
min_elapsed_time = 0;
for (int max_level = 1; max_level <= 17; max_level++) {
// copy the original array back to input array
for (int i = 0; i < array_size; i++)
array[i] = array_3[i];
// compute running time of merge sort improved here
start_time = System.nanoTime();
merge_sort_improved(array, 0, array_size - 1, 1, max_level);
end_time = System.nanoTime();
elapsed_time = end_time - start_time;
if ((max_level == 1) || (elapsed_time < min_elapsed_time)) {
min_elapsed_time = elapsed_time;
best_max_level = max_level;
}
System.out.printf("Max level: %d, Elapsed time in nanoseconds: %d\n", max_level, elapsed_time);
}
System.out.printf("Best max level: %d, Min elapsed time in nanoseconds: %d\n", best_max_level,
min_elapsed_time);
}
// parts (b)-(d) implement improved merge sort here
public static void merge_sort_improved(int [] A, int p, int r, int level, int max_level)
{
int q = (int) Math.floor((p + r) / 2);
if (p < r)
{
if (level >= max_level) {
insertion_sort_2(A, p, r);
}
else
{
level++;
// q = (int) Math.floor((p + r) / 2);
merge_sort_improved(A, p, q, level, max_level);
merge_sort_improved(A, q + 1, r, level, max_level);
}
merge(A, p, q, r);
level--;
}
}
public static void merge_sort_improved_2(int[] A,int p, int r, int threshold){
if (r - p <= threshold)
insertion_sort_2(A, p, r);
else
{
int q = (p + r) / 2;
merge_sort(A, p, q);
merge_sort(A, q, r);
merge(A, p, q, r);
}
}
// indices p and r can start from 0
public static void merge_sort(int[] A, int p, int r) {
int q;
if (p < r) {
q = (int) Math.floor((p + r) / 2);
merge_sort(A, p, q);
merge_sort(A, q + 1, r);
merge(A, p, q, r);
}
}
public static void merge(int[] A, int p, int q, int r) {
int n1, n2;
int i, j;
n1 = q - p + 1;
n2 = r - q;
int[] L = new int[n1];
int[] R = new int[n2];
for (i = 0; i < n1; i++)
L[i] = A[p + i];
for (i = 0; i < n2; i++)
R[i] = A[q + i + 1];
i = 0;
j = 0;
for (int k = p; k <= r; k++) {
if (i >= n1) // the left array finished, copy from right array
{
A[k] = R[j];
j++;
continue;
}
if (j >= n2) // the right array finished, copy from left array
{
A[k] = L[i];
i++;
continue;
}
if (L[i] <= R[j]) {
A[k] = L[i];
i++;
} else {
A[k] = R[j];
j++;
}
}
}
// insertion sort algorithm
public static void insertion_sort_2(int[] A, int start_index, int end_index) {
int key;
int i;
for (int j = start_index + 1; j < end_index; j++) {
key = A[j];
// insert A[j] into the sorted sequence A[starting_index..j-1]
i = j - 1;
while ((i >= 0) && (A[i] > key)) {
A[i + 1] = A[i];
i = i - 1;
}
A[i + 1] = key;
}
}
// insertion sort algorithm
public static void insertion_sort(int[] A) {
int key;
int i;
for (int j = 1; j < A.length; j++) {
key = A[j];
// insert A[j] into the sorted sequence A[1..j-1]
i = j - 1;
while ((i >= 0) && (A[i] > key)) {
A[i + 1] = A[i];
i = i - 1;
}
A[i + 1] = key;
}
}
// prints the elements of the array A on the screen
public static void print_array(int[] A) {
System.out.printf("[");
for (int i = 0; i < A.length - 1; i++) {
System.out.printf("%d, ", A[i]);
}
System.out.printf("%d]\n", A[A.length - 1]);
}
}
As I expected, it is faster than the "default" merge sort algorithm, and these are the results:
Elapsed time in nanoseconds for original merge sort: 9551833
Elapsed time in nanoseconds for improved merge sort with max_level=13: 8766042
Max level: 1, Elapsed time in nanoseconds: 868102916
Max level: 2, Elapsed time in nanoseconds: 127934125
Max level: 3, Elapsed time in nanoseconds: 100636084
Max level: 4, Elapsed time in nanoseconds: 53176500
Max level: 5, Elapsed time in nanoseconds: 40008875
Max level: 6, Elapsed time in nanoseconds: 30925333
Max level: 7, Elapsed time in nanoseconds: 18650458
Max level: 8, Elapsed time in nanoseconds: 18098958
Max level: 9, Elapsed time in nanoseconds: 7862125
Max level: 10, Elapsed time in nanoseconds: 6666667
Max level: 11, Elapsed time in nanoseconds: 3863416
Max level: 12, Elapsed time in nanoseconds: 3646500
Max level: 13, Elapsed time in nanoseconds: 2776000
Max level: 14, Elapsed time in nanoseconds: 3010667
Max level: 15, Elapsed time in nanoseconds: 3280834
Max level: 16, Elapsed time in nanoseconds: 4677084
Max level: 17, Elapsed time in nanoseconds: 5324667
Best max level: 13, Min elapsed time in nanoseconds: 2776000
I'm trying to understand the logic and workings of this method and wonder if I was successful at it, so any help would be greatly appreciated.