I am implementing mergesort referencing the geeks for geeks implementation as a guide but my implementation is not working.
I have my mergesort function that divides my given array in 2 and calls mergesort on half of the list.
Within mergesort, I use a helper function that merges the sub arrays together.
I have included my 2 functions. Could someone be my second set of eyes, I am have staring at this too long to tell the difference between 1's and l's
It is running but not sorting correctly.
void merge(int arr[], int temp[], int l, int m, int r) {
//TODO: implement merge.
// check arr
if (arr == NULL) {
return;
}
int left = m - l + 1;
int right = r - m;
// copy array into temp array
// first half
int i = 0;
for (i = 0; i < left; i++) {
temp[i] = arr[l + i];
}
// second half
int j = 0;
for (j = m + 1; i < right; j++) {
temp[j] = arr[m + l + i];
}
// compare from each end inserting the lower into the next location of the real array
// beginning index of front sub list
int front = 0;
// beginning of back sub list
int back = left;
// index within array to insert back in
int index = l;
while ((front < left) && (back < right)) {
if (temp[front] <= temp[back]) {
// temp front goes in the next array spot
arr[index] = temp[front];
// increase temp
front++;
} else {
// back is smaller and is put back in the list first
arr[index] = temp[back];
// increase back
back++;
}
// increase array index
index++;
}
while (front < left) {
arr[index] = temp[front];
front++;
index++;
}
while (back < right) {
arr[index] = temp[back];
back++;
index++;
}
}
void mergeSort(int array[], int temp[], int l, int r) {
if (r > l) {
// find middle point
int middle = l + (r - l) / 2;
// call merge on first half
mergeSort(array, temp, l, middle);
// call merge on second half
mergeSort(array, temp, middle + 1, r);
// merge the halves
merge(array, temp, l, middle, r);
}
}
There is a problem here:
// second half
int j = 0;
for (j = m + 1; i < right; j++) {
temp[j] = arr[m + l + i];
}
You should write:
// second half
for (i = 0; i < right; i++) {
temp[left + i] = arr[m + 1 + i];
}
Could someone be my second set of eyes, I am have staring at this too long to tell the difference between 1's and l's?
This is an excellent point! The solution is to never use l as a variable name, and to simplify the mergesort implementation to remove the confusing and error prone +1 / -1 adjustments. For this you just need to use the convention where r is the index of the element after the end of the slice.
Here is a modified version:
void merge(int arr[], int temp[], int lo, int mid, int hi) {
// check arr
if (arr == NULL) {
return;
}
int left = mid - lo;
int right = hi - mid;
// copy array into temp array
// first half
for (int i = 0; i < left; i++) {
temp[i] = arr[lo + i];
}
// second half
for (int i = 0; i < right; i++) {
temp[left + i] = arr[mid + i];
}
// compare from each end inserting the lower into the next location of the real array
// beginning index of front sub list
int front = 0;
// beginning of back sub list
int back = left;
// index within array to insert back in
int index = lo;
while ((front < left) && (back < right)) {
if (temp[front] <= temp[back]) {
// temp front goes in the next array spot
arr[index++] = temp[front++];
} else {
// back is smaller and is put back in the list first
arr[index++] = temp[back++];
}
}
while (front < left) {
arr[index++] = temp[front++];
}
while (back < right) {
arr[index++] = temp[back++];
}
}
void mergeSort(int array[], int temp[], int lo, int hi) {
if (hi - lo >= 2) {
// find middle point
int mid = lo + (hi - lo) / 2;
// call merge on first half
mergeSort(array, temp, lo, mid);
// call merge on second half
mergeSort(array, temp, mid, hi);
// merge the halves
merge(array, temp, lo, mid, hi);
}
}
The initial call should give 0 and the length of the array as index arguments.
I had some trouble understanding your merge logic.
Too many indexes/limits of the form (e.g): m - l + 1
So, I simplified things by having two temp pointers: tmp_l and tmp_r and added some length/count variables.
This allowed some of the indexes to start with 0.
Also, your middle calculation was unusual.
And, I changed the arg in the call to merge from middle to middle + 1
I've refactored the code and added a test suite. I've used cpp conditionals to denote old vs new code:
#if 0
// old code
#else
// new code
#endif
Anyway, here's my [working] version:
#include <stdio.h>
#include <stdlib.h>
#ifdef DEBUG
#define dbgprt(_fmt...) \
do { \
printf(_fmt); \
} while (0)
#else
#define dbgprt(_fmt...) \
do { \
} while (0)
#endif
void
merge(int arr[], int temp[], int l, int m, int r)
{
if (arr == NULL) {
return;
}
// get number of left elements
int lcnt = (m - l);
// get number of right elements
int rcnt = (r - m) + 1;
dbgprt("merge: BEGIN l=%d m=%d r=%d lcnt=%d rcnt=%d\n",
l,m,r,lcnt,rcnt);
int i = 0;
int *tmp_l = &temp[0];
for (i = 0; i < lcnt; i++)
tmp_l[i] = arr[l + i];
int *tmp_r = &temp[lcnt];
for (i = 0; i < rcnt; i++)
tmp_r[i] = arr[m + i];
int front = 0;
int back = 0;
int index = l;
while ((front < lcnt) && (back < rcnt)) {
// temp front goes in the next array spot
if (tmp_l[front] <= tmp_r[back]) {
arr[index] = tmp_l[front];
// increase temp
front++;
}
// back is smaller and is put back in the list first
else {
arr[index] = tmp_r[back];
// increase back
back++;
}
// increase array index
index++;
}
while (front < lcnt) {
arr[index] = tmp_l[front];
index++;
front++;
}
while (back < rcnt) {
arr[index] = tmp_r[back];
index++;
back++;
}
}
void
mergeSort(int array[], int temp[], int l, int r)
{
int middle;
if (r > l) {
// find middle point
#if 0
middle = l + (r - 1) / 2;
#else
middle = (l + r) / 2;
#endif
dbgprt("msort: ENTER l=%d m=%d r=%d\n",l,middle,r);
// call merge on first half
mergeSort(array, temp, l, middle);
// call merge on second half
mergeSort(array, temp, middle + 1, r);
// merge the halves
#if 0
merge(array, temp, l, middle, r);
#else
merge(array, temp, l, middle + 1, r);
#endif
dbgprt("msort: EXIT l=%d m=%d r=%d\n",l,middle,r);
}
}
void
dotest(int tstno)
{
int count = (rand() % 30) + 1;
printf("dotest: %d %d\n",tstno,count);
int *arr = malloc(sizeof(*arr) * count);
int *tmp = malloc(sizeof(*tmp) * count);
for (int idx = 0; idx < count; ++idx) {
arr[idx] = count - idx;
dbgprt(" %d",arr[idx]);
}
dbgprt("\n");
mergeSort(arr,tmp,0,count - 1);
int err = 0;
for (int idx = 0; idx < count; ++idx) {
printf(" %d",arr[idx]);
if (arr[idx] != (idx + 1)) {
printf("?");
err = 1;
}
}
printf("\n");
if (err)
exit(1);
free(arr);
free(tmp);
}
int
main(void)
{
for (int tstno = 1; tstno <= 4; ++tstno)
dotest(tstno);
return 0;
}
void
merge_ORIG(int arr[], int temp[], int l, int m, int r)
{
if (arr == NULL) {
return;
}
int i = 0;
for (i = 0; i < m - l + 1; i++) {
temp[i] = arr[l + i];
}
for (i = 0; i < r + 1; i++) {
temp[i] = arr[m + l + i];
}
int front = l;
int back = m + 1;
int index = l;
while ((front < m - l + 1) && (back < r - m)) {
if (temp[front] <= temp[back]) {
// temp front goes in the next array spot
arr[index] = temp[front];
// increase temp
front++;
}
else {
// back is smaller and is put back in the list first
arr[index] = temp[back];
// increase back
back++;
}
// increase array index
index++;
}
while (front < m - l + 1) {
arr[index] = temp[front];
index++;
front++;
}
while (back < r - m) {
arr[index] = temp[back];
index++;
back++;
}
}
Related
I hope i made my self clear enough in the title but if not i am here to explain my self
i got an array from an input ( like Arr = {, ).
we can use only 1 additional array (1 original 1 additional)
this is what i made so far :
I made a new array named newArr and assigned it all the values Arr contains.
i sorted it (because its requires time complexity of nlogn)
and then moved duplicates to the end.
now what i can't figure out :
now i need to move the original digits to their place according to the main
(all the values in the arrays are positive and they can be bigger then
n-which is the size of the array and ofc they can be also smaller then n)
i also need to return the number of original digits in the array
the original number should stay in the same position and the duplicates in the end of the array their order doesn't matter.
from here we can't use another additional array only the current arrays that we have ( which are 2)
i have been thinking about doing some kind of binary search but all of them went wrong.(like bin_search_first) and original binary and still couldn't manage it.
can some one give me an hint?
here is the code at where i am
#define _CRT_SECURE_NO_WARNINGS
/*Libraries*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
int* input_array(int);
int moveDuplicatesV2(int*, int);
void merge(int* a, int p, int q, int r);
void merge_sort(int* a, int first, int last);
void swap(int* v, int* u);
int bin_search_first(int , int* , int );
int main()
{
int arr[10] = { };
int n = 12;
int k = 0;
int first = 0;
int last = n - 1;
int mid = (first + last) / 2;
int l = n - 1;
int* D = arr + 1;
int j = 0;
size_t dupes_found = 0;
int* newArr = (int*)malloc(12 * sizeof(int));
assert(newArr);
for (int i = 0; i < n; i++)
{
newArr[i] = arr[i];
}
merge_sort(newArr, first, last);
for (size_t i = 0; i < n - 1 - dupes_found;)
{
if (newArr[i] == newArr[i + 1])
{
dupes_found++;
int temp = newArr[i];
memmove(&newArr[i], &newArr[i + 1], sizeof(int) * (n - i - 1));
newArr[n - 1] = temp;
}
else {
i++;
}
}
j = 0;
int key = 0;
first = 0;
for (int i = 0; i < n - dupes_found; i++)
{
key = newArr[i];
first = bin_search_first(key, arr,n);
swap(&newArr[i], &newArr[first]);
newArr[first] = newArr[i];
}
for (int i = 0; i < n; i++)
{
arr[i] = newArr[i];
}
for (int i = 0; i < n; i++)
{
printf("%d", arr[i]);
}
return n - dupes_found;
}
void merge(int* a, int p, int q, int r)
{
int i = p, j = q + 1, k = 0;
int* temp = (int*)malloc((r - p + 1) * sizeof(int));
assert(temp);
while ((i <= q) && (j <= r))
if (a[i] < a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
while (j <= r)
temp[k++] = a[j++];
while (i <= q)
temp[k++] = a[i++];
/* copy temp[] to a[] */
for (i = p, k = 0; i <= r; i++, k++)
a[i] = temp[k];
free(temp);
}
void merge_sort(int* a, int first, int last)
{
int middle;
if (first < last) {
middle = (first + last) / 2;
merge_sort(a, first, middle);
merge_sort(a, middle + 1, last);
merge(a, first, middle, last);
}
}
void swap(int* v, int* u)
{
int temp;
temp = *v;
*v = *u;
*u = temp;
}
int bin_search_first(int key, int* a, int n)
{
int low, high, mid;
low = 0;
high = n - 1;
while (low <= high)
{
mid = (low + high) / 2; // low + (high - low) / 2
if (key > a[mid])
low = mid + 1;
else
if (key < a[mid])
high = mid - 1;
else //key==a[mid]
if ((low == high) || (a[mid - 1] < key))
return mid;
else
high = mid - 1;
}
return -1;
}
Here is my idea:
Sort the array (nlogn)
Loop over the array and for each value, save a pointer to its first occurence (n)
Loop over the original array and insert the value into a result array if it is the values first occurrence. Whether or not it is the first occurrence can be checked using the sorted array: each element in this array has an additional flag that will be set if the value has already been seen. So, search for the element using bsearch, if seen append to back of result array (order does not matter), if not seen append to beginning of array and set seen value. (nlogn, since bsearch doesn't need to seek the first element because it was precomputed thus logn, over the array n)
Here is an example code (you can replace the qsort by mergesort to make the algorithm actually nlogn, I just used qsort because it is given):
#include <stdio.h>
#include <stdlib.h>
struct arr_value {
int value;
int seen;
struct arr_value *first;
};
int compar(const void *p1,const void *p2) {
struct arr_value *v1 = (struct arr_value *)p1;
struct arr_value *v2 = (struct arr_value *)p2;
if(v1->value < v2->value) {
return -1;
} else if(v1->value == v2->value) {
return 0;
}
return 1;
}
int main()
{
#define NumCount (12)
int arr[NumCount] = { 7, 3, 1, 2, 7, 9, 3, 2, 5, 9, 6, 2 };
int arrResult[NumCount];
int resultCount = 0;
int resultCountBack = 0;
struct arr_value arrseen[NumCount];
for(int i = 0; i < NumCount; ++i) {
arrseen[i].value = arr[i];
arrseen[i].seen = 0;
}
qsort(arrseen, NumCount, sizeof(struct arr_value), compar);
struct arr_value *firstSame = arrseen;
firstSame->first = firstSame;
for(int i = 1; i < NumCount; ++i) {
if(arrseen[i].value != firstSame->value) {
firstSame = arrseen + i;
}
arrseen[i].first = firstSame;
}
struct arr_value key;
for(int i = 0; i < NumCount; ++i) {
key.value = arr[i];
struct arr_value *found = (struct arr_value *)bsearch(&key, arrseen, NumCount, sizeof(struct arr_value), compar);
struct arr_value *first = found->first;
if(first->seen) {
// value already seen, append to back
arrResult[NumCount - 1 - resultCountBack] = first->value;
++resultCountBack;
} else {
// value is new, append
arrResult[resultCount++] = first->value;
first->seen = 1;
}
}
for(int i = 0; i < NumCount; ++i) {
printf("%d ", arrResult[i]);
}
return 0;
}
Output:
7 3 1 2 9 5 6 2 9 2 3 7
To begin with, memmove doesn't run in a constant time, so the loop
for (size_t i = 0; i < n - 1 - dupes_found;)
{
if (newArr[i] == newArr[i + 1])
{
dupes_found++;
int temp = newArr[i];
memmove(&newArr[i], &newArr[i + 1], sizeof(int) * (n - i - 1));
newArr[n - 1] = temp;
}
else {
i++;
}
}
drives the time complexity quadratic. You have to rethink the approach.
It seems that you are not using a crucial point:
all the values in the arrays are positive
It seriously hints that changing values to their negatives is a way to go.
Specifically, as you iterate over the initial array, and bin-search its elements in temp, comparing the _ absolute values_. When an element is found in temp, and it is still positive there, flip all its dupes in temp to negative. Otherwise flip it in initial.
So far, it is O(n log n).
Then perform an algorithm known as stable_partition: all positives are moved in front of negatives, retaining the order. I must not spell it here - I don't want to deprive you of a joy figuring it out yourself (still O(n log n)
And finally flip all negatives back to positives.
So this is my code which runs up till free(right); more like it completes merge sort then has an error, any solutions?
#include <stdio.h>
#include <stdlib.h>
void bubble_sort(int *l, int len) {
// Iterate through the list
for (int i = 0; i < len; i++) {
// Iterate through the list
for (int j = 0; j < len - 1; j++) {
// If the current element is greater than the next element, swap them
if (l[j] > l[j + 1]) {
// Swap the elements
int temp = l[j];
l[j] = l[j + 1];
l[j + 1] = temp;
// Print the list
for (int k = 0; k < len; k++) {
printf("%d ", l[k]);
}
printf("\n");
}
}
}
}
void selection_sort(int *l, int len) {
// Iterate through the list
for (int i = 0; i < len; i++) {
// Set the minimum index to the current index
int min_index = i;
// Iterate through the list
for (int j = i + 1; j < len; j++) {
// If the current element is less than the minimum element, set the minimum index to the current index
if (l[j] < l[min_index]) {
min_index = j;
}
}
// Swap the elements
int temp = l[i];
l[i] = l[min_index];
l[min_index] = temp;
// Print the list
for (int k = 0; k < len; k++) {
printf("%d ", l[k]);
}
printf("\n");
}
}
void insertion_sort(int *l, int len) {
// Iterate through the list
for (int i = 1; i < len; i++) {
// Set the current index to the current index
int j = i;
// While the current index is greater than 0 and the previous element is greater than the current element, swap them
while (j > 0 && l[j - 1] > l[j]) {
// Swap the elements
int temp = l[j - 1];
l[j - 1] = l[j];
l[j] = temp;
// Decrement the current index
j--;
}
// Print the list
for (int k = 0; k < len; k++) {
printf("%d ", l[k]);
}
printf("\n");
}
}
void merge(int *left, int left_len, int *right, int right_len) {
// Create a new list
int *result = malloc((left_len + right_len) * sizeof(int));
// Set the left index to 0 and the right index to 0
int i = 0;
int j = 0;
// While the left index is less than the length of the left list and the right index is less than the length of the right list
while (i < left_len && j < right_len) {
// If the left element is less than or equal to the right element, append the left element to the result list and increment the left index
if (left[i] <= right[j]) {
result[i + j] = left[i];
i++;
}
// Else, append the right element to the result list and increment the right index
else {
result[i + j] = right[j];
j++;
}
}
// Append the remaining elements in the left list to the result list
for (int k = i; k < left_len; k++) {
result[k + j] = left[k];
}
// Append the remaining elements in the right list to the result list
for (int k = j; k < right_len; k++) {
result[k + i] = right[k];
}
// Print the result list
for (int k = 0; k < left_len + right_len; k++) {
printf("%d ", result[k]);
}
printf("\n");
// Copy the result list to the original list
for (int k = 0; k < left_len + right_len; k++) {
left[k] = result[k];
}
// Free the result list
free(result);
}
void merge_sort(int *l, int len) {
// If the list is empty or has one element, return the list
if (len <= 1) {
return;
}
// Set the middle index to the length of the list divided by 2
int mid = len / 2;
// Set the left list to the first half of the list
int *left = malloc(mid * sizeof(int));
for (int i = 0; i < mid; i++) {
left[i] = l[i];
}
// Set the right list to the second half of the list
int *right = malloc((len - mid) * sizeof(int));
for (int i = mid; i < len; i++) {
right[i - mid] = l[i];
}
// Sort the left list
merge_sort(left, mid);
// Sort the right list
merge_sort(right, len - mid);
// Merge the left list and the right list
merge(left, mid, right, len - mid);
// Free the left list and the right list
free(left);
free(right); //Error ln 142, in picture below
}
int binary_search(int *l, int len, int target) {
// Set the low index to 0 and the high index to the length of the list minus 1
int low = 0;
int high = len - 1;
// While the low index is less than or equal to the high index
while (low <= high) {
// Set the middle index to the sum of the low index and the high index divided by 2
int mid = (low + high) / 2;
// If the middle element is equal to the target, return the middle index
if (l[mid] == target) {
return mid;
}
// Else if the middle element is less than the target, set the low index to the middle index plus 1
else if (l[mid] < target) {
low = mid + 1;
}
// Else, set the high index to the middle index minus 1
else {
high = mid - 1;
}
}
// If the target is not found, return -1
return -1;
}
int main() {
// Create a list
int l[] = {17, 36, 3, 10, 29, 42, 34, 8};
int len = sizeof(l) / sizeof(l[0]);
// Print the list
printf("Bubble Sort:\n");
// Sort the list using bubble sort
bubble_sort(l, len);
// Print the list
printf("Selection Sort:\n");
// Sort the list using selection sort
selection_sort(l, len);
// Print the list
printf("Insertion Sort:\n");
// Sort the list using insertion sort
insertion_sort(l, len);
// Print the list
printf("Merge Sort:\n");
// Sort the list using merge sort
merge_sort(l, len);
// Print the list
printf("Binary Search:\n");
// Search for the target in the list using binary search
printf("%d\n", binary_search(l, len, 42));
return 0;
}
So I rewrote the code from python to C, and debugging in GDB gives me the error in the screenshot.
GDB ss
I've tried to edit the function itself to rectify the memory issue but it wouldn't work so i reverted back to this and hope someone has some more insight.
The segfault is triggered in merge() used by merge_sort(). Everything else is irrelevant.
In merge_sort() you copy half of the input array l into a newly allocated array left and the other half into another newly allocated array right. Then recursively merge_sort() those two halves which is fine. To combine the two halves merge() is called where you incorrectly assume that the left and right arrays are allocated consecutively:
for (int k = 0; k < left_len + right_len; k++) {
left[k] = result[k];
}
The minimal fix is to make the assumption valid:
void merge_sort(int *l, int len) {
if (len <= 1) {
return;
}
int mid = len / 2;
int *left = malloc(len * sizeof(int));
for (int i = 0; i < mid; i++) {
left[i] = l[i];
}
int *right = left + mid;
for (int i = mid; i < len; i++) {
right[i - mid] = l[i];
}
merge_sort(left, mid);
merge_sort(right, len - mid);
merge(left, mid, right, len - mid);
free(left);
}
A even better resolution would be to:
Strictly separate the code under test and your test harness. In this case you want to delegate to main() the task of duplicating the input array instead of doing that in your sort algorithm. This allows merge_sort() to operate on the input array in-place (merge() still uses the temporary array).
Eliminate the right array pointer argument to merge(). This documents that the left and right arrays are part of the the same array.
Refactor the merge() and merge_sort() interface so the length argument is before the array argument so you can document how they relate.
(Not fixed). You could allocate the temporary space needed for merging once in merge_sort() and pass it to merge_sort2() and merge2(). That way you only have O(n) space overhead instead of O(n*log(n)). It is worth pointing out that malloc() may require a kernel context switch which in turn would be the most expensive operation th the merge() + merge_sort() implementation. Doing 1 instead of n*log(n) calls to malloc() could be a significant (constant) factor in run-time. Sharing the temporary space, however, comes a cost as you would no longer be able to do the otherwise non-overlapping merge sorts in parallel.
Prefer the type size_t to int for lengths. sizeof() in particular returns a size_t value, and the cast to the (signed) int will be problematic for sizes greater than INTMAX.
Prefer memcpy() instead of explicit loops when possible. memcpy() is highly optimized, and succinctly expresses intent.
Prefer passing a variable instead of a type to sizeof(). The former is robust if you change the type of the variable where the latter requires a code change if you didn't use a typedef for the type.
Finally, I added a print() function so you don't need the debug print statements in the sorting functions themselves.
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
void merge(size_t left_len, size_t right_len, int l[left_len + right_len]) {
int *result = malloc((left_len + right_len) * sizeof(*l));
int i = 0;
int j = 0;
while (i < left_len && j < right_len) {
if (l[i] <= l[left_len + j]) {
result[i + j] = l[i];
i++;
} else {
result[i + j] = l[left_len + j];
j++;
}
}
memcpy(result + i + j, l + i, (left_len - i) * sizeof(*l));
memcpy(result + left_len + j, l + left_len + j, (right_len - j) * sizeof(*l));
memcpy(l, result, (left_len + right_len) * sizeof(*l));
free(result);
}
void merge_sort(size_t len, int l[len]) {
if (len < 2) return;
int mid = len / 2;
merge_sort(mid, l);
merge_sort(len - mid, l + mid);
merge(mid, len - mid, l);
}
void print(size_t len, int a[len]) {
for(size_t i = 0; i < len; i++) {
printf("%d%s", a[i], i + 1 < len ? ", " : "\n");
}
}
int main() {
int l[] = {17, 36, 3, 10, 29, 42, 34, 8};
size_t len = sizeof(l) / sizeof(*l);
int l2[len];
memcpy(l2, l, sizeof(l));
merge_sort(len, l2);
print(len, l2);
}
and it returns:
3, 8, 10, 17, 29, 34, 36, 42
valgrind is happy:
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Let say I have array A and B ( always equal size)
A = 5 4 2 1
B = 8 3 6 7
I am to insert elements from B in to A while keeping the relative order of A while minimising inversions.
So the answer would be 3 5 4 1 2 6 7 8 (7 inversions)
I have tried sorting B first then poping min(a[0] b[0]) into an array C but cases like
A = 99999 1 2 3
B = 5 6 7 8
Gives the wrong 5 6 7 8 99999 1 2 3 (15 inversions)
When the correct is 99999 1 2 3 5 6 7 8 (7 inversions)
I am at a lost please help
Okay, this is an interesting question. My current solution works in O(nlog(n)).
The logic behind this solution can be found here.
However, since there was no implementation there, I've decided to place it here.
The overall logic relies on two things:
For the array that does not need to maintain order, sort it in O(n log n).
Find the middle element, and insert it into the location in the array that minimizes inversions. This can be done in T(2n) = O(n).
The elements to the left of that middle element in the sorted array should be on the left of that element in the combined array to minimize inversions. The elements to the right of that middle element in the sorted array should be on the right of that element in the combined array to minimize inversions. This knowledge reduces the space you have to search to find the insertion point with least inversions each iteration. Hence, you can recurse by applying the above logic on the left half of the sorted array with the left half of the combined array.
Now, onto the code:
#include <bits/stdc++.h>
using namespace std;
long long merge(vector<int>& v, int temp[], int left, int mid, int right) {
int i,j,k;
long long inv_count = 0;
i = left;
j = mid;
k = left;
while ((i <= mid - 1) && (j <= right)) {
if (v[i] <= v[j]) {
temp[k++] = v[i++];
} else {
temp[k++] = v[j++];
inv_count += mid - i;
}
}
while (i <= mid - 1)
temp[k++] = v[i++];
while (j <= right)
temp[k++] = v[j++];
for (i = left; i <= right; i++)
v[i] = temp[i];
return inv_count;
}
long long _mergeSort(vector<int>& v, int temp[], int left, int right) {
int mid;
long long inv_count = 0;
if (v.size() < 2) return 0;
if (right > left) {
mid = (right + left) / 2;
inv_count += _mergeSort(v, temp, left, mid);
inv_count += _mergeSort(v, temp, mid + 1, right);
inv_count += merge(v, temp, left, mid + 1, right);
}
return inv_count;
}
long long countInversionsInVector(vector<int>& v) {
int temp[v.size()];
return _mergeSort(v, temp, 0, v.size() - 1);
}
void solveHelper(vector<int> &a, int sa, int ea, vector<int>& b, int sb, int eb, vector<int>& I) {
if (sb >= eb) return;
int mb = (sb + eb) / 2;
int b_elem = b[mb];
vector<int> sliced_a(ea - sa + 1);
for (auto index = a.begin() + sa; index < a.begin() + ea; index++) {
sliced_a.push_back(*index);
}
long long invCount = 0;
for (long long i = sa; i < ea; i++) {
if (a[i] < b_elem) {
invCount += 1;
}
}
long long minInvCount = invCount;
int insertionIndex = sa;
for (int i = sa; i < ea; i++) {
if (a[i] < b[mb]){
invCount -= 1;
} else {
invCount += 1;
}
if (invCount < minInvCount) {
minInvCount = invCount;
insertionIndex = i + 1;
}
}
I[mb] = insertionIndex;
solveHelper(a, sa, insertionIndex, b, sb, mb, I);
solveHelper(a, insertionIndex, ea, b, mb + 1, eb, I);
return;
}
void mergeFinal(vector<int>& a, vector<int>& b, vector<int>& I, vector<int>& final) {
int index_a = 0;
int index_b = 0;
for (int i = 0; i < 2 * a.size(); i++) {
if (i == I[index_b] + index_b) {
final[i] = b[index_b];
index_b++;
} else {
final[i] = a[index_a];
index_a++;
}
}
}
long long solve(vector<int>& a, vector<int>& b) {
sort(b.begin(), b.end());
vector<int> I(b.size());
solveHelper(a, 0, a.size(), b, 0, b.size(), I);
vector<int> final(2*a.size());
mergeFinal(a, b, I, final);
return countInversionsInVector(final);
}
int main() {
int n;
ios_base::sync_with_stdio(false); // speed up reading input
cin >> n;
vector<int> a(n), b(n);
for(int i = 0; i < n; i++) {
cin >> a[i];
}
for(int i = 0; i < n; i++) {
cin >> b[i];
}
cout << solve(a, b) << "\n";
}
If you pass the array size to the function as the index of the last element, then everything works as it should. But if you pass the size - 1, then the sorting does not reach the last element. What is the problem?
void merge(int *array, int start, int middle, int end)
{
int i, j, k;
int leftpoint = middle - start;
int rightpoint = end - middle;
if (leftpoint == 0 || rightpoint == 0)
return;
int leftArray[leftpoint];
int rightArray[rightpoint];
for (i = 0; i < leftpoint; i++) {
leftArray[i] = array[start + i];
}
for (j = 0; j < rightpoint; j++) {
rightArray[j] = array[middle + j];
}
i = 0;
j = 0;
for (k = start; k < end; k++) {
if (j == rightpoint || (i < leftpoint && leftArray[i] <= rightArray[j])) {
array[k] = leftArray[i];
i++;
} else {
array[k] = rightArray[j];
j++;
}
}
}
void mergeSort(int *arr, int start, int end)
{
int middle;
if (end - start > 1) {
middle = start + (end - start) / 2;
mergeSort(arr, start, middle);
mergeSort(arr, middle, end);
merge(arr, start, middle, end);
}
}
Passing the index past the last element for right is idiomatic in C. Many books illustrate a different API, passing the index of the last element. While this would be consistent for 1 based arrays, it cannot reliably represent empty arrays and leads to confusing +1 and -1 adjustments. The posted code does not have this shortcoming and seems correct. It should be called this way:
int array[] = { ... };
int len = sizeof(array) / sizeof(*array);
mergeSort(array, 0, len);
If you observe that merge sorting does not reach the last element, you probably pass the index to the last element to the initial call instead of the number of elements.
Here are some remarks about your implementation:
the index arguments should have type size_t.
if merge is only called from mergeSort, itself called with consistent positive arguments start and end with start <= end, neither leftpoint nor rightpoint can be 0, so the test if (leftpoint == 0 || rightpoint == 0) return; is redundant.
saving the elements from mid to right is unnecessary as they cannot be overwritten before they are copied.
using temporary arrays defined locally with automatic storage can cause a stack overflow for moderately large arrays.
Here is a modified version:
void merge(int *array, size_t start, size_t middle, size_t end) {
int leftpoint = middle - start;
int leftArray[leftpoint];
for (size_t i = 0; i < leftpoint; i++) {
leftArray[i] = array[start + i];
}
for (size_t i = 0, j = middle, k = start; i < leftpoint;) {
if (j == end || (i < leftpoint && leftArray[i] <= array[j])) {
array[k++] = leftArray[i++];
} else {
array[k++] = array[j++];
}
}
}
void mergeSort(int *arr, size_t start, size_t end) {
if (end > start + 1) {
size_t middle = start + (end - start) / 2;
mergeSort(arr, start, middle);
mergeSort(arr, middle, end);
merge(arr, start, middle, end);
}
}
When I use mergeSort to sort my void** array (this array contains void* pointers that point to integers), an extra 1 (a new element) appears to be added to the array. I am nearly certain the issue is in mergeSort or merge, as when print my void** array before calling mergeSort, the data is correct (just unsorted). Here is the code.
#define SIZE 10
void mergeSort(void**, int, int);
void merge(void**, int, int, int);
int compare(void*, void*);
int main(void) {
int array[SIZE] = { 5, 6, 3, 2, 5, 6, 7, 4, 9, 3 };
void *voidArray[SIZE];
int query = 1;
void *queryPointer = &query;
for (int j = 0; j < SIZE; j++) {
voidArray[j] = &array[j];
}
printArray(voidArray);
mergeSort(voidArray, 0, SIZE);
printArray(voidArray);
result = binarySearch(voidArray, 0, SIZE, queryPointer);
if (result == -1) {
puts("Query not found.");
return(0);
}
printf("Query found at index %d.\n", result);
return(0);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void mergeSort(void **array, int head, int tail) {
if (head < tail) {
int middle = (head + ((tail - head) / 2));
mergeSort(array, head, middle);
mergeSort(array, (middle + 1), tail);
merge(array, head, middle, tail);
}
}
void merge(void **array, int head, int middle, int tail) {
int headLength = (middle - head + 1);
int tailLength = (tail - middle);
void *headSide[headLength];
void *tailSide[tailLength];
for (int i = 0; i < headLength; i++) {
headSide[i] = array[head + i];
}
for (int j = 0; j < tailLength; j++) {
tailSide[j] = array[middle + 1 + j];
}
int k = head;
int l = 0;
int m = 0;
while (l < headLength && m < tailLength) {
if (compare(headSide[l], tailSide[m]) == -1) {
array[k] = headSide[l];
l++;
} else {
array[k] = tailSide[m];
m++;
}
k++;
}
while (l < headLength) {
array[k] = headSide[l];
l++;
k++;
}
while (m < tailLength) {
array[k] = tailSide[m];
m++;
k++;
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int compare(void *index, void *query) {
if (*((int *)index) == *((int *)query)) {
return (0);
}
if (*((int*)index) > *((int*)query)) {
return (1);
}
return (-1);
}
The output should have the unsorted array, the sorted array, and whether the query was found. There is no 1 in the unsorted array, but then there is a 1 in the sorted array; also, the number 9 is missing from the sorted results (interestingly, if I perform a binary search for 9, it will tell me that 9 is found at index 10).
Example output (for a query of 1):
5 6 3 2 5 6 7 4 9 3
1 2 3 3 4 5 5 6 6 7
Query found at index 0.
Check your array subscript.
int tailLength = (tail - middle)
tail is the size of array,I think tailLength is incorrect.
headSide[i] = array[head + i];
headSide[i] is void and array[head + i] is void*
There is some confusion in the arguments to margeSort and merge. Passing the index of the last element in the range is not idiomatic in C. It is much simpler to pass the index of the element after the end of the range, which is consistent with passing 0 and SIZE int main(): mergeSort(voidArray, 0, SIZE); and result = binarySearch(voidArray, 0, SIZE, queryPointer);
Here is a modified version with this API:
void mergeSort(void **array, int head, int tail) {
if (tail - head > 1) {
int middle = head + (tail - head) / 2);
mergeSort(array, head, middle);
mergeSort(array, middle, tail);
merge(array, head, middle, tail);
}
}
void merge(void **array, int head, int middle, int tail) {
int headLength = middle - head;
int tailLength = tail - middle;
void *headSide[headLength];
void *tailSide[tailLength];
for (int i = 0; i < headLength; i++) {
headSide[i] = array[head + i];
}
for (int j = 0; j < tailLength; j++) {
tailSide[j] = array[middle + j];
}
int k = head;
int l = 0;
int m = 0;
while (l < headLength && m < tailLength) {
if (compare(headSide[l], tailSide[m]) <= 0) {
array[k++] = headSide[l++];
} else {
array[k++] = tailSide[m++];
}
}
while (l < headLength) {
array[k++] = headSide[l++];
}
while (m < tailLength) {
array[k++] = tailSide[m++];
}
}
Note however that allocating the temporary arrays headSide and tailSide with automatic storage (aka on the stack) is risky for large arrays. Furthermore, it is not necessary to save the elements from the right half into tailSide as they will not be overwritten before they are copied to the final position. Here is a simpler version of merge:
void merge(void **array, int head, int middle, int tail) {
int headLength = middle - head;
void *headSide[headLength];
for (int i = 0; i < headLength; i++) {
headSide[i] = array[head + i];
}
int k = head;
int l = 0;
while (l < headLength && middle < tail) {
if (compare(headSide[l], array[middle]) <= 0) {
array[k++] = headSide[l++];
} else {
array[k++] = array[middle++];
}
}
while (l < headLength) {
array[k++] = headSide[l++];
}
}