What is wrong with this merge sort algorithm? - c++

Can someone please tell what is wrong with this piece of code? This code is to sort a set of elements in an array using merge sort.
#include<iostream>
void merge(int arr[], int left, int mid, int right){
int left_ptr = left;
int right_ptr = mid + 1;
int size = right - left + 1;
int temp[size];
int k = left;
while (left_ptr <= mid && right_ptr <= right)
{
if(arr[left_ptr] <= arr[right_ptr]){
temp[k] = arr[left_ptr];
left_ptr++;
k++;
}
else{
temp[k] = arr[right_ptr];
right_ptr++;
k++;
}
}
while (left_ptr <= mid)
{
temp[k] = arr[left_ptr];
left_ptr++;
k++;
}
while (right_ptr <= right)
{
temp[k] = arr[right_ptr];
right_ptr++;
k++;
}
for (int i = left_ptr; i < k; i++)
{
arr[i] = temp[i];
}
}
void mergeSort(int arr[], int left, int right){
int mid;
if (left < right)
{
mid = (right + left)/2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
int main(){
int arr[] = {45,8,9,7,4,58,2,34,2,58};
std::cout << arr << std::endl;
int size = sizeof(arr)/sizeof(int);
mergeSort(arr, 0, size - 1);
for (int i = 0; i < size; i++)
{
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
I double checked it with many online codes and I see no error... What do you think went wrong? I tried to implement this using an array in-place (something like quick-sort).

Here is a list of things wrong with your code. I'm taking "wrong" broadly here. For each bit of code, my primary criticism is style based not "correctness", where the style aimed for is one that makes correctness easier to spot.
Along the way, one of the style criticisms results in spotting what looks like a bug.
void merge(int arr[], int left, int mid, int right){
You are using int to refer to offsets in an array.
You are using int[] parameters, which is a legacy C syntax for int* arr. Use something like std::span instead.
Going on:
int left_ptr = left;
If your goal is to preserve the original arguments and work on copies, make the original arguments const so someone doesn't have to prove they aren't mutated in the body of the function.
int right_ptr = mid + 1;
You have variables called _ptr that aren't pointers.
int size = right - left + 1;
you appear to not be using half-open intervals. Use and learn to use half-open intervals. They are conventional in C++ and they really do get rid of lots of fence-post correcting code.
int temp[size];
This is not compliant C++. Practically, even on compilers that support this, many C++ implementations have much smaller stacks than the memory of arrays you might want to sort. This then results in your code blowing its stack.
Correctness is more important than performance. Creating dynamically sized objects on the stack leads to programs that engage in undefined behavior or crash on otherwise reasonable inputs.
int k = left;
this variable does not describe what it does.
while (left_ptr <= mid && right_ptr <= right)
while (left_ptr <= mid)
while (right_ptr <= right)
there is a lot of code duplication in these loops.
DRY - don't repeat yourself. Here, if there is a bug in any one of the repeats, if you DRY the bug would be in all uses and easier to spot. There are a lot of ways to DRY here (lambdas, helper functions, slightly more complex branching and one loop); use one of them.
for (int i = left_ptr; i < k; i++)
{
arr[i] = temp[i];
}
looks like a manual std copy? Also looks like it has a bug, because of course manually reimplementing std copy means you did it wrong.
void mergeSort(int arr[], int left, int right){
again, legacy C-style array passing.
int mid;
No need to declare this without initializing it. Move declarations as close as possible to their point of first use, and have them fall out of scope as soon as possible.
if (left < right)
{
mid = (right + left)/2;
make this int mid =.
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
An example of how closed intervals make you have to do annoying fenceposting.
merge(arr, left, mid, right);
mergeSort(arr, 0, size - 1);
another fencepost +/- 1 here.

I see two possible errors in the code:
Declaring int temp[size]; in merge is not valid, as size is not a constant. You will need to allocate the array dynamically.
Secondly, in the last segment of the merge function (the for loop), you initialize i = left_ptr. However, left_ptr is set equal to mid before that. I believe you actually want to initialize i = left.
EDIT: Just noticed: temp does not necessarily have to start at the beginning of arr. What I mean is, that each element of temp is mapped to a specific element of arr, but your code assumes at several plases, that temp[0] is mapped to arr[0], which is not true (temp[0] is actually mapped to arr[right]). There are two ways to fix that.
You can fix the pieces that are based on this assumption. Instead of arr[i] = temp[i], use arr[i + right] = temp[i] in the final for loop, and initialize k as zero.
Second option is to, instead of creating and deleting temp in every single call to merge, create it to be equal in size to arr and hold onto it for the entirety of execution of the algorithm (that could be done by creating it outside the algorithm and passing it to each call to merge or MergeSort) That way, the equal offset assumption would actually be correct.

Related

How do I make my merge sort algorithm more efficient

I'm going to keep this short and sweet, I have an assignment to do for university, and it runs through an online judge, if the time limit is too long (time it takes to run all the inputs, etc. then it hits a time limit)
I have tried bubble sorting, quick sorting, insertion sorting and merge sorting.
Ultimately, merge sorting seems to be one of the stable ones, with a time complexity of Nlog(N), so I'm deciding to stick with this one, my code is the following:
void merge(vector<Order>& orderVector, int low, int high, int mid)
{
int i, j, k;
vector<Order> c(orderVector.size(), orderVector.at(0));
i = low;
k = low;
j = mid + 1;
while (i <= mid && j <= high) {
if ((orderVector.at(i).selection_time < orderVector.at(j).selection_time) || (orderVector.at(i).selection_time == orderVector.at(j).selection_time && orderVector.at(i).shipping_time < orderVector.at(j).shipping_time)) {
c.at(k) = orderVector.at(i);
k++;
i++;
}
else {
c.at(k) = orderVector.at(j);
k++;
j++;
}
}
while (i <= mid) {
c.at(k) = orderVector.at(i);
k++;
i++;
}
while (j <= high) {
c.at(k) = orderVector.at(j);
k++;
j++;
}
for (i = low; i < k; i++) {
orderVector.at(i) = c.at(i);
}
}
void sort(vector<Order>& orderVector, int low, int high)
{
int mid;
if (low < high) {
mid = (low + high) / 2;
sort(orderVector, low, mid);
sort(orderVector, mid + 1, high);
merge(orderVector, low, high, mid);
}
}
I really don't understand what's so inefficient about this, it's literally the standard merge sort code you'd find. I'm referencing orderVector so it makes changes, orderVector is simply just a vector full of "Order" classes, which is just a class with 3 variables (id, selection_time, shipping_time)
What I do is I get the selection_time and check and sort that, once they're sorted I make sure the shipping_time gets sorted only once selection_time is equal, that way it sorts the first value then by the second value.
I think this method is pretty efficient, I have no idea why it does not choose to run efficiently according to this online judge.
The printing code, and the code that adds to the vector (should it be important is the following)
int main(int argc, char *argv[]){
string data;
getline(cin, data);
vector<string> data_v = split(data, "; ", numeric_limits<size_t>::max());
vector<Order> orderVector;
for(string d : data_v){
Order *orders = new Order(id, selection, shipping);
orderVector.push_back(*orders);
}
sort(orderVector, 0, orderVector.size() - 1);
for(long unsigned int i = 0; i < orderVector.size(); i++)
{
cout << orderVector.at(i).id << " ";
}
cout << endl;
}
please keep in mind i removed the basic template my university gave me so the actual data being put into Order is not shown here, but I don't see how this affects efficiency
A couple of things you should try:
The object in the vector Order.
You should prefer to move it rather than copy it.
You keep reallocating space for the temporary buffer c.
Allocate a temporary buffer once and re-use.
Don't use at() when you know the index can not be out of range.
Use operator[] as it does not do the extra work of range checking.
Don't copy back to the original location if you don't need to:
for (i = low; i < k; i++) {
orderVector.at(i) = c.at(i);
}
Do this part only if needed after the container has been sorted.
As a side note: Simplify this:
if ((orderVector.at(i).selection_time < orderVector.at(j).selection_time) || (orderVector.at(i).selection_time == orderVector.at(j).selection_time && orderVector.at(i).shipping_time < orderVector.at(j).shipping_time)) {
By defining a comparison operator for Order objects (or put it in a function).
As a side note: Don't limit your self to vector. Define your interface in terms of an iterator.
As a side note: Prefer to use standard algorithms rather than writing loops all over the place.
while (j <= high) {
c.at(k) = orderVector.at(j);
k++;
j++;
}
// OR
std::move(iterator_j, iterator_jend, iterator_k);
This can be improved:
for(string d : data_v){
Order *orders = new Order(id, selection, shipping);
orderVector.push_back(*orders);
}
Try this:
for(string d : data_v){
orderVector.emplace_back(id, selection, shipping);
}
You can also ask for a full code review.
Or you can have a look at some other peoples attempts and their code reviews:

How does the quicksort function work in a quicksort algorithm?

I've understood how the partitioning part is done in the quicksort algorithm, but I'm having trouble understanding the quicksort recursive function. Could someone please explain to me step by step how it works? I'm pasting here the C++ code.
using namespace std;
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int myArray[], int low, int high) {
int i = (low - 1);
int pivot = myArray[high];
for (int j = low; j <= high - 1; j++) {
if (myArray[j] < pivot) {
i++;
swap(&myArray[i], &myArray[j]);
}
}
swap(&myArray[i + 1], &myArray[high]);
return (i + 1);
}
void quickSort(int myArray[], int low, int high) {
if (low < high) {
int pi = partition(myArray, low, high);
quickSort(myArray, low, pi - 1);
quickSort(myArray, pi + 1, high);
}
}
void cinArray(int myArray[], int n) {
for (int p = 0; p < n; p++) {
cin >> myArray[p];
}
}
void print(int myArray[], int n) {
for (int z = 0; z < n; z++) {
cout << myArray[z] << "\t";
}
}
int main() {
int myArray[10];
int size = sizeof(myArray) / sizeof(myArray[0]);
cout << "Write 10 numbers: ";
cinArray(myArray, size);
quickSort(myArray, 0, size - 1);
print(myArray, size);
return 0;
}
My logic by far (step by step) is the following:
if (low < high) will always be true. The first time, the
low (0) and high (9) values are taken from the int main.
Pi will be equal to the returned value of the partition funcion
(i+1), and I suppose that in order to return that value the function
has to run first.
We call the quicksort function giving new arguments for the function twice. Once for
the values before and once for the values after i+1 from the original partitioning. I want to focus on what happens on the first one, the one that has the values before i+1.
Function starts again, the if statement is true (always is), pi calls the function partition and returns i+1, pi is equal to i+1. And what if at this point the values are still not sorted? I suppose the quicksort function restarts again (It feels like a loop). But since the IF statement will always be true, when will this loop situation stop?
Also, assuming my logic at point 4 is correct, how is the code run the first time? Does it start with the first quickSort(myArray, low, pi - 1); function call and loops until something stops it and then does the same for the second call quickSort(myArray, pi + 1, high);? Or does it partition before i+1 then after i+1 and restarts the function?
I know it's a basic question, but I'm really having a hard time wraping my head around this algorithm.
if (low < high) will always be true.
Not correct. It will be true on the first call, but QuickSort calls itself recursive with progressively smaller intervals between low and high. This if is why the algorithm eventually terminates - you ask about that below.
Pi will be equal to the returned value of the partition funcion (i+1)
Right. pi is short for pivot index, i.e. the location where the chosen pivot ended up after partitioning.
And what if at this point the values are still not sorted?
After partitioning, you know that no value in the left partition is greater than the pivot value and that no value in the right partition is less than the pivot value. That's all you know, and all the algorithm needs to know in order to eventually succeed. Each partition is recursively partitioned until it only has one element in it.
when will this loop situation stop?
See my first point.
The partition function places the pivot element in place and returns the index to the pivot element. The next two calls exclude the element, and in the worst case, one call will be for zero elements, the other for n-1 elements, and continuing with the worst case, the size of what is passed on only decreases by 1 element for each level of recursion, with time complexity O(n^2). The best case is if the pivot ends up in the middle, with an even split on each level of recursion, or near even split for time complexity O(n log(n)).

Descending Mergesort

I wrote some code to perform descending Merge Sort.
void Merge(int a[], int low, int high, int mid)
{
int i=low,j=mid+1,k=0;
int temp[high-low+1];
while(i<=mid && j<= high)
{
if(a[i]>a[j]) //comparison step
temp[k++]=a[i++];
else
temp[k++]=a[j++];
}
while(i<=mid)
{
temp[k++]=a[i++];
}
while(j<=high)
{
temp[k++]=a[j++];
}
for(i=low;i<=high;i++)
{
a[i]=temp[i-low];
}
return;
}
void MergeSort(int a[],int low, int high)
{
int mid;
if(low<high)
{
mid=(low+high)/2;
MergeSort(a,low,mid);
MergeSort(a,mid+1,high);
Merge(a,low,high,mid);
}
return;
}
void output(int *a,int n)
{
for(int i=0;i<n;i++)
{
cout<<a[i]<<"\t";
}
}
int main()
{
int n=12;
int a[n]={23,45,1,2,8,0,9,56,73,5070,20,16};
MergeSort(a,0,n);
output(a,n);
}
This code works perfectly when the order is ascending, ie. comparsion is a[i] < a[j]
However by using a[j] > a[i] MergeSort beings sorting in descending order, but it will include some random large number right at the beginning of the array. I really can't figure out why this is happening.
Arrays in C++ are zero based. However, your code happily access a[high] which gets passed the value 12. Thus, you got an out of bounds access. The same error happens if you sort your array ascending but since you don't print a[12] (you output() function uses a range exclusive of n) you don't see it.
I strongly recommend to adopt a programming style where ranges are inclusive of the start value and exclusive of the end value. Of course, I'd also recommend using iterators for ranges rather than indices anyway. For these the convention is more obvious.
The quick fix (which doesn't change how your MergeSort() to be exclusive of the last element) is to apply two changes:
Change the termination check in MergeSort() to become
if (1 < high - low)
Call MergeSort() with the last element of the array
MergeSort(a, 0, n - 1);
Note that variable sized built-in arrays like int temp[high - low + 1] are not part of standard C++ even though they are supported by some compilers as an extension (e.g., g++). For bigger arrays they also cause problems as they are bound to overflow the stack. You are much better off using std::vector<int>:
std::vector<int> temp(high - low + 1);
For the test array you can use a static sized array for which the compiler determines the size and have the compiler also figure out the array:
int a[]={23,45,1,2,8,0,9,56,73,5070,20,16};
int n = std::end(a) - std::begin(a); // need <iterator> to be included

Quicksort Partition Algorithm

I am trying to learn Quick Sort. To do so, I followed the logic for quick sort in this article where the last element is picked as the pivot and you work through the array from both ends swapping elements as needed. Now after a long time of trying to come up with my own algorithm based on this, here is what I have so far:
using namespace std;
int a[] = {10,4,3,2,8,5};
int j;
int partition(int left, int right, int array[]){
int pivot = array[right];
while(1){
while(array[left]<pivot){
left = left+1;
}
while(array[right]>pivot){
right=right-1;
}
if(left>=right){
return right;
}
int temp1 = array[left];
int temp2 = array[right];
array[left] = temp2;
array[right] = temp1;
}
}
void quicksort(int left, int right, int array[]){
if(left<right){
int p = partition(left, right, array);
quicksort(left, p-1, array);
quicksort(p+1, right, array);
}
}
int main(){
quicksort(0, sizeof(a)/sizeof(a[0])-1, a);
for(int i=0; i<(sizeof(a)/sizeof(a[0])); i++){
cout << a[i] << endl;
}
}
So after testing this with various arrays of different sizes and elements, it does output a correctly sorted array however in the algorithms that I have found online (ex. Hoare's) they always decrement right and increment left at the start of the partition. Also the pivot does not move in most algorithms I have found, but I move it.
What I am wondering is, am I doing this wrong? Why does it work? It's been a lot of trial and error so that it be understandable if I did it wrong, I am also slightly new to algorithms and data structures so I wouldn't be surprised if I made some mistakes.
Regarding the pivot for Qsort, there's no fixed rule for selecting the pivot. Choosing the pivot really depends on which strategy you want to impose on your input data, and that largely depends on the properties of the input data - range, size etc.
Generally, we choose the pivot so that it splits the array into two equal halves (as much as possible). This is done so that we don't end up pushing all the elements on to one-side of the Qsort. If this happens your algorithm will become O(n^2) - think of skew trees if you're not able to see why.

Quick sort code explanation [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
This is code that I came across implementing the quick sort algorithm. Can you please explain how the recursion works here?
void quickSort(int arr[], int left, int right)
{
int i = left, j = right;
int tmp;
int pivot = arr[(left + right) / 2];
/* partition */
while (i <= j) {
while (arr[i] < pivot)
i++;
while (arr[j] > pivot)
j--;
if (i <= j) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
}
}
/* recursion */
if (left < j)
quickSort(arr, left, j);
if (i < right)
quickSort(arr, i, right);
}
And please note, this is not homework.
Not sure what you mean with "explain how the recursion is working". But here you go:
The function you posted takes an array of ints and two indexes. It will not sort the whole array, but only the part of it between the two indexes, ignoring anything that is outside them. This means the same function can sort the whole array if you pass the first and last indexes, or just a sub array if you pass a left value that is not the index of the first element of the array and/or a right value that is not the index of the last element.
The sorting algorithm is the well known quicksort. The as pivot it uses the central element (it could as well have used any other element). It partitions the array into the less than (or equal to) pivot subarray and the greater than (or equal to) pivot subarray, leaving an element equal to the pivot between the two partitions.
Then it recursively calls itself to sort the two partitions, but only does it if it is necessary (hence the ifs before the recursive calls).
The implementation works, but is sub-optimal in many ways, and could be improved.
Here are some possible improvements:
switch to another sorting algorithm if the array is sufficiently short
chose the pivot value as median of three values (generally first, last and mid)
initially move one pivot value out of the array (put it in first or last position and reduce the focus to the rest of the array) then change the tests to pass over values that are equal to the pivot to reduce the number of swaps involving them. You'll put the pivot value back in with a final exchange at the end. This is especially useful if you do not follow suggestion 2 and chose the firs/last element instead of the mid one as in this implementation.
late reply but I just added some prints and it might help whoever comes across this understand the code.
#include<iostream>
using namespace std;
void quickSort(int arr[], int left, int right)
{
int i = left, j = right;
int tmp;
int pivot = arr[abs((left + right) / 2)];
cout<<"pivot is"<<pivot<<endl;
/* partition */
while (i <= j) {
while (arr[i] < pivot)
i++;
while (arr[j] > pivot)
j--;
if (i <= j) {
cout<<"i and j are"<<i<<" "<<j<<"and corresponding array value is"<<arr[i]<<" " <<arr[j]<<endl;
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
cout<<"entering first big while loop"<<endl;
for(int i=0;i<7;i++)
cout<<arr[i]<<" "<<endl ;
}
}
cout<<"recursion"<<endl;
/* recursion */
if (left < j)
quickSort(arr, left, j);
if (i< right)
quickSort(arr, i, right);
}
int main(){
int arr[7]= {2,3,8,7,4,9,1};
for(int i=0;i<7;i++)
cout<<arr[i]<<" " ;
quickSort(arr,0,6);
cout<<endl;
for(int i=0;i<7;i++)
cout<<arr[i]<<" " ;
int wait;
cin>>wait;
return 0;
}
Here is your answer -- in the common case both the recursive calls will be executed because the conditions above them will be true. However, in the corner case you could have the pivot element be the largest (or the smallest) element. In which case you have to make only one recursive call which will basically attempt the process one more time by choosing a different pivot after removing the pivot element from the array.