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
Related
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.
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)).
I have an assignment for a c++ programming class to write a recursive function without the use of static variables, with the following prototype:
int findmin(const int a[], int n);
My solution works (for very small arrays), however I think ~2^n complexity is excessive and could be improved.
Are there any improvements that could be made within the specified criteria that would make this more efficient?
int findmin(const int a[], int n)
{
if(n == 0)
return a[0];
else
{
if(a[n-1] < findmin(a,(n-1)))
return a[n-1];
else
return findmin(a,(n-1));
}
}
It's a little silly to worry about efficiency, given that there is an obvious, non-recursive way to do it in O(n), one pass. There is even an STL algorithm std::min_element. But then, it's a silly assignment. FIrst be sure your solution is correct. When n==0, will a[0] be valid? Generally, such an n indicates the length of the array, not the lowest index.
To go from O[n^2] to O[n], be sure to compare each element only once. That implies not starting at the beginning of the array on every pass.
#include <algorithm>
#include <cassert>
int findmin(const int a[], int n)
{
assert(n>0);
if(n == 1) // See heyah.
return a[0];
else
{
return std::min(a[0], findmin(a + 1, n - 1));
}
}
In for-real C++ code, if for some reason we were saddled with the old fashion function signature, we would do something like this:
int findmin(const int a[], int n) {
if(n<=0) { throw std::length_error("findmin called on empty array");}
return *std::min_element(a, a+n);
}
You could do conditional operator ?: to get rid of bunch if else statements, to make function cleaner. And instead of calling findmin() twice you could assign return value to variable inside of the statement, this is main advantage of this code vs. original one.
int findmin(const int a[], int n) {
if (n == 0) // base case
return a[0];
return a[n] < (int min = findmin(a, n - 1)) ? a[n] : min;
}
This (a[n] < (int min = findmin(a, n - 1)) ? a[n] : min;) could be done using if statement as well:
if (a[n] < (int min = findmin (a, n - 1))
return a[n];
else
return min;
EDIT:
Per many reputable sources, this is O(n) time. O (n^2) would be if we are comparing each element with all the other elements.
I was doing an exercise for sorting in my Algorithms class where we are required to implement various sorting algorithms and test them against the inputs provided by our professor.
I have the following implementation for quick-sort which is entropy optimal meaning it may be faster than the NlogN bound when a large sequence of elements are equal. The implementation I have done can be found below this post (removed the pastebin link as suggested in the comments)
On running it I found out that it is slower than the std::sort algorithm (I do understand that this is just a difference in the constant for the NlogN) bounds, but as a result I miss the time limits for large input sequences.
Also when the input size is 1000000, std::sort is able to sort but my algorithm gives me a segmentation fault. Can someone please take a look at this and let me know if I am doing something wrong. Thanks in advance.
#include <algorithm>
#include <iostream>
#include <iterator>
#include <random>
#include <utility>
struct Sort {
public:
enum class SortAlg { selection = 0, insertion, shell, merge, mergeBU, quick, heap };
template <typename T, int N>
static void sort(T (&arr) [N], SortAlg alg) {
SortArray<T,N> sortM (arr);
switch (alg) {
case SortAlg::quick:
sortM.quicksort(); break;
default:
sortM.quicksort();
};
}
private:
template <typename T, int N>
class SortArray {
public:
SortArray(T (&a) [N]) : arr(a) {}
void quicksort();
private:
void qsort(int lo, int hi);
std::pair<int, int> partition(int lo, int hi);
T (&arr) [N];
};
};
template <typename T, int N>
void Sort::SortArray<T, N>::quicksort(){
qsort(0, N-1);
}
template <typename T, int N>
void Sort::SortArray<T, N>::qsort(int lo, int hi){
if (lo >= hi) return;
std::pair<int, int> part = partition(lo, hi);
qsort(lo, part.first);
qsort (part.second, hi);
}
//This partitions the algorithm into 3 ranges
//1st range - elements less than the partition element
//2nd range - elements equal to the partition element
//3rd range - elements greater than the partition element
//it returns a pair (a,b) where[a+1, b-1] represents the
//equal range which will be left out of subsequent sorts and
//the next set of sorting will be on [lo,a] and [b,hi]
template <typename T, int N>
std::pair<int, int> Sort::SortArray<T, N>::partition(int lo, int hi){
static int count = 0;
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<int> dis;
int elem = lo + (dis(gen) % (hi-lo+1)); //position of element around which paritioning happens
using std::swap;
swap(arr[lo], arr[elem]);
int val = arr[lo];
//after the while loop below completes
//the range of elements [lo, eqind1-1] and [eqind2+1, hi] will all be equal to arr[lo]
//the range of elements [eqind1, gt] will all be less than arr[lo]
//the range of elements [lt, eqind2] will all be greated than arr[lo]
int lt = lo+1, gt = hi, eqind1 = lo, eqind2 = hi;
while (true){
while(lt <= gt && arr[lt] <= val) {
if (arr[lt] == val){
if(lt == eqind1 + 1)
++eqind1;
else
swap(arr[lt], arr[++eqind1]);
}
++lt;
}
while(gt >= lt && arr[gt] >= val) {
if(arr[gt] == val){
if(gt == eqind2)
--eqind2;
else
swap(arr[gt], arr[eqind2--]);
}
--gt;
};
if(lt >= gt) break;
swap(arr[lt], arr[gt]); ++lt; --gt;
};
swap(arr[lo], arr[gt]);
if (eqind1!=lo){
//there are some elements equal to arr[lo] in the first eqind1-1 places
//move the elements which are less than arr[lo] to the beginning
for (int i = 1; i<lt-eqind1; i++)
arr[lo+i] = arr[lo + eqind1+i];
}
if (eqind2!=hi){
//there are some elements which are equal to arr[lo] in the last eqind2-1 places
//move the elements which are greater than arr[lo] towards the end of the array
for(int i = hi; i>gt; i--)
arr[i] = arr[i-hi+eqind2];
}
//calculate the number of elements equal to arr[lo] and fill them up in between
//the elements less than and greater than arr[lo]
int numequals = eqind1 - lo + hi - eqind2 + 1;
if(numequals != 1){
for(int i = 0; i < numequals; i++)
arr[lo+lt-eqind1+i-1] = val;
}
//calculate the range of elements that are less than and greater than arr[lo]
//and return them back to qsort
int lorange = lo + lt-eqind1-2;
int hirange = lo + lt - eqind1 - 1 + numequals;
return {lorange, hirange};
}
int main() {
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<int> dis;
constexpr int size = 100000;
int arr[size], arr1[size];
for (int i = 0; i < size; i++){
arr[i] = dis(gen)%9;
arr1[i] = arr[i];;
}
std::sort(std::begin(arr1), std::end(arr1));
std::cout << "Standard sort finished" << std::endl;
Sort::sort(arr, Sort::SortAlg::quick);
std::cout << "Custom sort finished" << std::endl;
int i =0;
int countDiffer = 0;
for (; i <size; ++i){
if (arr[i] != arr1[i]){
countDiffer++;
}
}
if (i == size) std::cout << "Sorted" << std::endl;
else std::cout << "Not sorted and differ in " << countDiffer
<< " places" << std::endl;
}
There is two problems with the code.
A) You are creating a stack allocated array that might be large. Once the stacksize overflows the next page might be anything from unmapped to random heap memory.
B) The other weird thing I noticed is that you initialize an RNG with every call of partition (can also be expensive) which wastes stack space for every partition point.
You have two different problems, that should really warrant two different questions. I will however answer one for you.
Oh, and in the future please don't have links to code, what if that link goes dead? Then your question will be useless.
The problem with the crash is that just about all compilers place local variables (including arrays) on the stack, and the stack is limited. On Windows, for example, the default stack for a process is only a single megabyte.
With two such arrays, each of a 1000000 entries each, you will have eight megabytes (which happens to be the default stack-size for Linux processes), plus of course space for the function call stack frames and all the other local variables and arguments etc. This is beyond (or way beyond) the available stack, and you will have undefined behavior and a probable crash.
Microsoft's std::sort uses introsort. Wiki link:
http://en.wikipedia.org/wiki/Introsort
Introsort switches from quicksort to heapsort if the nesting reaches some limit, primarily for performance reasons, since an indicator that quicksort is going to be slow is excessive nesting, but it also has the side benefit of preventing excessive nesting from running a thread out of stack space.
I have homework assignment to implement merge sort in C++ via recursion. I'm not really good at recursion and what follows is the code I implemented, but it gives a stackoverflow error.
Kindly tell me what I am doing wrong. EDITED VERSION
#include<iostream>
using namespace std;
void divide(int A[], int n);
void sort();
int main(){
int A[4]={2,3,0,5};
divide(A, 4);
for(int i =0 ;i<4;i++)
cout<<A[i]<<endl;
getchar();
}
void divide(int A[], int n){
if(n<=2 && n>=1){
for(int i=0; i<n; i++)
if(A[i]>A[i+1]){
int temp=A[i];
A[i]= A[i+1];
A[i+1]=temp;
}
}
else{
divide(A, n/2);
divide(A,(n/2)+1 );
}
}
In the code above, n is the number of elements to sort and A is the array I'm sorting.
Calling the below code with
divide(A, 1);
Should illustrate the problem
void divide(int A[], int n){
if(n==2){ // first time n==1 so no, further calls are n==0 so also no.
for(int i=0; i<2; i++)
if(A[i]>A[i+1]){
int temp=A[i];
A[i]= A[i+1];
}
} else{
divide(A, n/2); // for both n==1 and n== 0 => n==0, calls divide(A, 0)
divide(A,(n/2)+1 ); // calls divide(A, 1) always
}
}
So the program will forever call divide(A, 0) until you run out of memory.
To stop this eternal recursion you need a correct stop condition
if (n<=2) {
// correct code for swapping 1 or 2 elements
} else
You could also check for incorrect values of n, which is 0, negative and larger than length of A.
Lets say you have A[]= {1,2,3} so you call
divide(A, 3);
Now in the else part of the program you need to split up A in to parts, N/2 elements and the rest.
divide(A, n/2);
in our example this gives n/2 = 3/2 = 1 so
divide(A, 1);
and starting in the element just after the n/2'th element
divide(A+(n/2), n-(n/2));
the first element is at A[0], so the remaining start at A[1] and contains n-(n/2)=3-(3/2)=3-1=2 elements.
And now the first if, it looks like a bubble-sort, but fails as it address an element beyond the end of the array.
if(n<=2 && n>=1){
for(int i=0; i<n; i++)
if(A[i]>A[i+1]) {
A[i+1] is beyond the end of the array for i=1 and n=2, n=2 => 2 elements at address A[0] and A[1] so A[i+1]=A[2] which is not part of the array A with length 2.
for(int i=0; i<n-1; i++)
solves that and also takes care of the case with n=1, which means the array contains only one element, which by definition is already sorted.
Now if the algorithm was called divide-sort you would be finished, but you still are missing the merge part.
You're still missing a merge. Merging is going to need a second temp array, I'll call it T and assume it's passed from main:
void divide(int A[], int T[], int n){
if(n < 2)
return;
if(n==2){
// ... swap A[0], A[1] if needed (the existing code is ok)
return;
}
divide(A, T, n/2); // recursively divide "left" half
divide(A+(n/2), T+(n/2), n-(n/2)); // recursively divide "right" half
merge(A, T, n/2, n) // merge the two halves
}
It might be simpler to assume that a partition 0 or 1 element is already sorted. Thus, it is enough to put as stop condition
if (n < 2)
return;