Reputation: 690
I am trying to implement Merge sort for time analysis and when testing the functions used to implement this, I get some funky results and can't figure out why. I am generating an array of 20 random values, then calling mergeSort
and then printing the results of the "sorted" array.
There is no error message, but the results are not what's expected. The output will appear to be sorted for the first few values, then some 0's in between, and eventually ending in very very large values, even though the numbers being generated should be between 1 and 100. The output is as follows:
>sort-timings
1 3 8 11 0 14 17 24 0 0 29 96 20 2293400 3 2293400 2293400 26085452 1971496002 1971496002 >Exit code: 0 Time: 0.4162
The code I have implemented is:
void merge(int A[], int leftStart, int leftEnd, int rightStart, int rightEnd, int W[]) {
//Merge A[leftStart]....[leftEnd] with A[rightStart]...[rightEnd]
//Into W, indexed by k, copy resulting W into A
int i = leftStart;
int j = rightStart;
int k = leftStart;
while( i <= leftEnd && j <= rightEnd) {
if(A[i] < A[j]) {
W[k++] = A[i++];
}
else if(A[i] > A[j]) {
W[k++] = A[j++];
}
else {
W[k++] = A[i++];
W[k++] = A[j++];
}
}
for(i = leftStart; i <= rightEnd; i++) {
A[i] = W[i];
}
}
void mergeSort(int A[], int low, int high, int W[]) {
//mergeSort Helper Function
if(low == high) {
return; //1 element is sorted
}
int mid = (low + high) / 2;
mergeSort(A, low, mid, W); //Sort first half
mergeSort(A, mid + 1, high, W); //Sort second half
merge(A, low, mid, mid + 1, high, W);
return;
}
void mergeSort(int A[], int W[], int n) {
mergeSort(A, 0, n - 1, W);
}
void generateRandomArray(int A[], int n) {
unsigned int seed = time(0);
srand(seed);
for(int i = 0; i < n; i++) {
A[i] = (rand() % 100) + 1; // 1 <= A[i] <=10000
}
}
int main() {
const int ARRAY_SIZE = 20;
int array[ARRAY_SIZE];
int tempArray[ARRAY_SIZE];
generateRandomArray(array, ARRAY_SIZE);
mergeSort(array, tempArray, ARRAY_SIZE);
for(int i = 0; i < ARRAY_SIZE; i++) {
cout << array[i] << " ";
}
}
Upvotes: 0
Views: 77
Reputation: 28828
Alternate version that uses a flag (mtoa) to keep track of which direction to merge based on the level of recursion, to avoid copying of data. It also only checks for index out of range after incrementing an index in TopDownMerge(); I'm not sure if this make a significant performance difference.
void TopDownMergeSort(int a[], int b[], size_t n)
{
if(n < 2)
return;
TopDownSplitMerge(a, b, 0, n, true);
}
void TopDownSplitMerge(int a[], int b[], size_t ll, size_t ee, bool mtoa)
{
size_t rr;
if ((ee - ll) == 1){ // if size == 1
if(!mtoa) // copy to b if merging a to b
b[ll] = a[ll];
return;
}
rr = (ll + ee)>>1; // midpoint, start of right half
TopDownSplitMerge(a, b, ll, rr, !mtoa);
TopDownSplitMerge(a, b, rr, ee, !mtoa);
if(mtoa) // if merging to a, merge b to a
TopDownMerge(b, a, ll, rr, ee);
else // else merge a to b
TopDownMerge(a, b, ll, rr, ee);
}
void TopDownMerge(int a[], int b[], size_t ll, size_t rr, size_t ee)
{
size_t o = ll; // b[] index
size_t l = ll; // a[] left index
size_t r = rr; // a[] right index
while(1){ // merge data
if(a[l] <= a[r]){ // if a[l] <= a[r]
b[o++] = a[l++]; // copy a[l]
if(l < rr) // if not end of left run
continue; // continue (back to while)
while(r < ee){ // else copy rest of right run
b[o++] = a[r++];
}
break; // and return
} else { // else a[l] > a[r]
b[o++] = a[r++]; // copy a[r]
if(r < ee) // if not end of right run
continue; // continue (back to while)
while(l < rr){ // else copy rest of left run
b[o++] = a[l++];
}
break; // and return
}
}
}
Upvotes: 1
Reputation: 8514
You are stopping your merge
loop to early. It currently stops when i
is out of range or j
is out of range, this leaves some values not copied into W
, leading to uninitialized values in your output.
A simple way to fix this, is to copy the rest of the values after your main loop is finished. If the loop finished because i
was out of range, you want to copy the rest of j
, similarly if the loop finished because j
was out of range, you want to copy the rest of i
.
You can achieve this by adding loops after the main loop to ensure both i
and j
reach the end of their range:
while (i <= leftEnd) {
W[k++] = A[i++];
}
while (j <= rightEnd) {
W[k++] = A[j++];
}
put this before the final for
loop that copies W
into A
.
Another alternative is to change the loop so that the condition is an ||
which will mean it will continue while either number is in range. You then have to test that a number is in range before you use it. There are a number of ways to do this, one simple way is to test it first:
while (i <= leftEnd || j <= rightEnd) {
if (j > rightEnd) {
W[k++] = A[i++];
}
else if (i > leftEnd) {
W[k++] = A[j++];
}
else if (A[i] < A[j]) {
...
Upvotes: 1