When does merge_sort beat quick_sort? - c++

SO Posts
When to use merge sort and when to use quick sort?
Quick Sort Vs Merge Sort
Wikipedia
http://en.wikipedia.org/wiki/Merge_sort
http://en.wikipedia.org/wiki/Quicksort
quick_sort is suppose to have worst case O(n^2) but merge_sort is suppose to not have a worst case and always be O (n*log N). I thought that it was dependent upon the ordering of the data set - reverse order, forward order, or random, but when I a run test...quick_sort is always faster. The code I used is below:
/*
Needs a reszie function added
*/
#include "c_arclib.cpp"
template <class T> class dynamic_array
{
private:
T* array;
T* scratch;
public:
int size;
dynamic_array(int sizein)
{
size=sizein;
array = new T[size]();
}
void print_array()
{
for (int i = 0; i < size; i++) cout << array[i] << endl;
}
void merge_recurse(int left, int right)
{
if(right == left + 1)
{
return;
}
else
{
int i = 0;
int length = right - left;
int midpoint_distance = length/2;
int l = left, r = left + midpoint_distance;
merge_recurse(left, left + midpoint_distance);
merge_recurse(left + midpoint_distance, right);
for(i = 0; i < length; i++)
{
if((l < (left + midpoint_distance)) && (r == right || array[l] > array[r]))
{
scratch[i] = array[l];
l++;
}
else
{
scratch[i] = array[r];
r++;
}
}
for(i = left; i < right; i++)
{
array[i] = scratch[i - left];
}
}
}
int merge_sort()
{
scratch = new T[size]();
if(scratch != NULL)
{
merge_recurse(0, size);
return 1;
}
else
{
return 0;
}
}
void quick_recurse(int left, int right)
{
int l = left, r = right, tmp;
int pivot = array[(left + right) / 2];
while (l <= r)
{
while (array[l] < pivot)l++;
while (array[r] > pivot)r--;
if (l <= r)
{
tmp = array[l];
array[l] = array[r];
array[r] = tmp;
l++;
r--;
}
}
if (left < r)quick_recurse(left, r);
if (l < right)quick_recurse(l, right);
}
void quick_sort()
{
quick_recurse(0,size);
}
void rand_to_array()
{
srand(time(NULL));
int* k;
for (k = array; k != array + size; ++k)
{
*k=rand();
}
}
void order_to_array()
{
int* k;
int i = 0;
for (k = array; k != array + size; ++k)
{
*k=i;
++i;
}
}
void rorder_to_array()
{
int* k;
int i = size;
for (k = array; k != array + size; ++k)
{
*k=i;
--i;
}
}
};
int main()
{
dynamic_array<int> d1(1000000);
d1.order_to_array();
clock_t time_start=clock();
d1.merge_sort();
clock_t time_end=clock();
double result = (double)(time_end - time_start) / CLOCKS_PER_SEC;
cout << result;
}

Worst case for quick sort is when the pivot element is the largest or smallest element in the array on every recursion. In that case you will have to do n-1 recursions (one of the arrays you split always only has one element) which gives you an O(n2) overall.
You can reproduce the worst case for quick sort if you use an already sorted array and pick the first or last element as pivot element.

Merge sort works very well for data that won't fit into memory, because each pass is linear and can be read/written to disk. Quick sort isn't even an option in that case, although the two may be combined - quick sort blocks that fit into memory, and merge sort those blocks until done.

Consider the container type as well - mergesort will work much better with a linked list, because you can split the list into equal parts by just traversing it and assigning nodes to alternate sublists; rearranging things around a pivot for quicksort is considerably more involved.

Related

What is wrong with my merge sort implementation refer to CLRS?

I tried to implement merge sort using C++, however, something went wrong. I have no idea what is wrong.
The following code is what I wrote based on CLRS. I think it is quite easy to understand the meaning.
#include <iostream>
#include <vector>
using namespace std;
void merge(vector<int>& nums, int p, int q, int r);
void mergeSort(vector<int>& nums, int p, int r){
if (p < r) {
int q = (p + r) / 2;
mergeSort(nums, p, q);
mergeSort(nums, q + 1, r);
merge(nums, p, q, r);
}
}
void merge(vector<int>& nums, int p, int q, int r) {
int s1 = p, s2 = q + 1;
vector<int> l1, l2;
for (int i = s1; i <= q; i++) {
l1.push_back(nums[i]);
}
for (int i = s2; i <= r; i++) {
l2.push_back(nums[i]);
}
int left = 0, right = 0;
int idx = 0;
while (left < l1.size() && right < l2.size()) {
if (l1[left] < l2[right]) {
nums[idx] = l1[left++];
}
else {
nums[idx] = l2[right++];
}
idx++;
}
while (left < l1.size()) {
nums[idx++] = l1[left++];
}
while (right < l2.size()) {
nums[idx++] = l2[right++];
}
}
int main() {
vector<int> vect;
vect.push_back(1);
vect.push_back(3);
vect.push_back(12);
vect.push_back(23);
vect.push_back(4);
vect.push_back(11);
vect.push_back(44);
vect.push_back(322);
mergeSort(vect, 0, vect.size() - 1);
for (int i = 0; i < vect.size(); i++) {
cout << vect[i] << endl;
}
return 0;
}
I want to use the program to sort some integers, however, it only shows many duplicate numbers. What's going on? I don't think there is a problem of the merge function.
The code needs a one line fix:
int idx = p; // not idx = 0
Optimized top down using arrays from Wiki article (note bottom up is slightly faster):
void TopDownMerge(int A[], int bgn, int mid, int end, int B[])
{
int i, j, k;
i = bgn, j = mid, k = bgn;
while(1){
if(A[i] <= A[j]){ // if left smaller
B[k++] = A[i++]; // copy left element
if(i < mid) // if not end of left run
continue; // continue
do // else copy rest of right run
B[k++] = A[j++];
while(j < end);
break; // and break
} else { // else right smaller
B[k++] = A[j++]; // copy right element
if(j < end) // if not end of right run
continue; // continue
do // else copy rest of left run
B[k++] = A[i++];
while(i < mid);
break; // and break
}
}
}
void TopDownSplitMerge(int B[], int bgn, int end, int A[])
{
if (end - bgn <= 1) // if run size == 1
return; // consider it sorted
int mid = (end + bgn) / 2;
TopDownSplitMerge(A, bgn, mid, B);
TopDownSplitMerge(A, mid, end, B);
TopDownMerge(B, bgn, mid, end, A);
}
void TopDownMergeSort(int A[], int n) // n = size (not size-1)
{
if(n < 2)
return;
int *B = new int [n]; // 1 time allocate and copy
for(size_t i = 0; i < n; i++)
B[i] = A[i];
TopDownSplitMerge(B, 0, n, A); // sort data from B[] into A[]
delete B;
}
Afterwards, I finally get to fix the bugs of my program. After modification, here is the code:
class Solution {
public:
vector<int> temp;
vector<int> sortArray(vector<int>& nums) {
temp.resize((int)nums.size(), 0);
mergeSort(nums, 0, nums.size() - 1);
return nums;
}
void mergeSort(vector<int>& nums, int start, int end) {
if (start >= end) return;
int middle = (start + end) / 2;
mergeSort(nums, start, middle);
mergeSort(nums, middle + 1, end);
merge(nums, start, middle, end);
}
void merge(vector<int>& nums, int leftStart, int middle, int rightEnd) {
int leftEnd = middle;
int rightStart = middle + 1;
int i = leftStart, j = rightStart;
int index = 0;
while (i <= leftEnd && j <= rightEnd) {
if (nums[i] < nums[j]) {
temp[index] = nums[i++];
}
else {
temp[index] = nums[j++];
}
index++;
}
while (i <= leftEnd) {
temp[index++] = nums[i++];
}
while (j <= rightEnd) {
temp[index++] = nums[j++];
}
for (int i = 0; i < rightEnd - leftStart + 1; i++) {
nums[i + leftStart] = temp[i];
}
}
};
Here is something should be careful next time:
In the merge part, it is difficult to merge in place. It'd be better to use another temp array to store the merged results and update to the target array (nums in this case).
Readable identifers is very recommended (Although the pseudocode of CLRS may not use that part).
Need to use debuggers to find the bug of program {However, it takes like forever to load local variables of VS Code debugers.

Infinite loop problem with the merge sort during the portion recursively calls mergeSort

Im working on a homework assignment comparing both execution time and theoretical O-notation of 10 separate sorts. However, as the question says I keep having an infinite loop when I get to the point that the code recursively calls merge sort. I know that it is when the left is at 2 and the right is at 3 so middle continuously returns as 3. is merge supposed to be outside of the if statement? I don't think that would be right either though when I look at it.
void merge(int arr[], int left, int middle, int right)
{
// get the temporary array lengths
int first = middle - left + 1;
int second = right - middle;
// make the temp dynamic arrays
int *Left = new int(first);
int *Right = new int(second);
for (int i = 0; i < first; i++)
{
Left[i] = arr[left + i];
}
for (int i = 0; i < second; i++)
{
Right[i] = arr[middle + 1 + i];
}
// merge the arrays back into arr
int i = 0;
int j = 0;
int k = left;
while (i < first && j < second)
{
// check which one is bigger
if (Left[i] <= Right[j])
{
arr[k] = Left[i];
i++;
}
else
{
arr[k] = Right[j];
j++;
}
k++;
}
// copy Left remainder
while (i < first)
{
arr[k] = Left[i];
i++;
k++;
}
// copy right remainder
while (j < second)
{
arr[k] = Right[j];
j++;
k++;
}
}
void mergeSort(int arr[], int left, int right)
{
if (left < right)
{
int middle = left + (right - 1) / 2;
mergeSort(arr, left, middle);
mergeSort(arr, middle + 1, right);
merge(arr, left, middle, right);
}
}
In mergeSort function, you forgot parenthesis when calculating middle. It should be:
int middle = (left + (right - 1)) / 2;
Also, in merge function, Left and Right should have type int[]. Instead of new int(SIZE), you should use new int[SIZE]. Moreover, you should use delete[] to delete them before leaving the function to prevent memory leak.
// make the temp dynamic arrays
int* Left = new int[first];
int* Right = new int[second];
// ...
// at the end of the `merge` function
delete[] Left;
delete[] Right;

Quick Sort program stopped working

I was trying to solve the quick sort - 2 challenge on hackerrank. It said that we had to repeatedly call partition till the entire array was sorted. My program works for some test cases but for some it crashes, "Quick Sort - 2.exe has stopped working". I couldn't find the reason as to why it's happening.
The first element of the array/sub-array was to be taken as pivot element each time.
#include <iostream>
#include <conio.h>
using namespace std;
void swap(int arr[], int a, int b)
{
int c = arr[a];
arr[a] = arr[b];
arr[b] = c;
}
void qsort(int arr[], int m, int n) //m - lower limit, n - upper limit
{
if (n - m == 1)
{
return;
}
int p = arr[m], i, j, t; //p - pivot element, t - temporary
//partition
for (int i = m+1; i < n; i++)
{
j = i;
if (arr[j] < p)
{
t = arr[j];
while (arr[j] != p)
{
arr[j] = arr[j-1];
j--;
}
arr[j] = t; //pivot is at j and j+1
}
}
//check if sorted
int f = 1;
while (arr[f] > arr[f-1])
{
if (f == n-1)
{
f = -1;
break;
}
f++;
}
if (f == -1)
{
cout << "Sub Array Sorted\n";
}
else
{
if (p == arr[m]) //pivot is the smallest in sub array
{
qsort(arr, m+1, n); //sort right sub array
}
else
{
qsort(arr, m, j+1); //sort left sub array
qsort(arr, j+1, n); //sort right sub array
}
}
}
int main()
{
int n;
cin >> n;
int arr[n];
for (int i = 0; i < n; i++)
{
cin >> arr[i];
}
qsort(arr, 0, n);
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
return 0;
}
You have an index out of range problem.
This will not give you the solution, but it may help you to find the reason why your program fails.
I have modified your program so it uses a vector of int rather than a raw array of int, and when you run this program you get an index out of range exception.
The sequence 4 3 7 1 6 4 that triggers the problem is hardcoded, so you don't need to type it each time.
#include <iostream>
#include <vector>
using namespace std;
void swap(vector<int> & arr, int a, int b)
{
int c = arr[a];
arr[a] = arr[b];
arr[b] = c;
}
void qsort(vector<int> & arr, int m, int n) //m - lower limit, n - upper limit
{
if (n - m == 1)
{
return;
}
int p = arr[m], j, t; //p - pivot element, t - temporary
//partition
for (int i = m + 1; i < n; i++)
{
j = i;
if (arr[j] < p)
{
t = arr[j];
while (arr[j] != p)
{
arr[j] = arr[j - 1];
j--;
}
arr[j] = t; //pivot is at j and j+1
}
}
//check if sorted
int f = 1;
while (arr[f] > arr[f - 1])
{
if (f == n - 1)
{
f = -1;
break;
}
f++;
}
if (f == -1)
{
cout << "Sub Array Sorted\n";
}
else
{
if (p == arr[m]) //pivot is the smallest in sub array
{
qsort(arr, m + 1, n); //sort right sub array
}
else
{
qsort(arr, m, j + 1); //sort left sub array
qsort(arr, j + 1, n); //sort right sub array
}
}
}
int main()
{
vector<int> arr = { 4,3,7,1,6,4 };
qsort(arr, 0, arr.size());
for (unsigned int i = 0; i < arr.size(); i++)
{
cout << arr[i] << " ";
}
return 0;
}
First of all, what you made is not quick sort, but some combination of divide-ans-conquer partitioning and insert sort.
Canonical quicksort goes from from lower (p) and upper (q) bounds of array, skipping elements arr[p]m respectively. Then it swaps arr[p] with arr[q], increments/decrements and checks if p>=q. Rinse and repeat until p>=q. Then make calls on sub-partitions. This way p or q holds pivot position and subcalls are obvious.
But you are doing it different way: you insert elements from right side of subarray to left side. Such thing can produce O(N^2) time complexity for one iteration. Consider 1,0,1,0,1,0,1,0,1,0,1,0,... sequence, for example. This can increase worst case complexity over O(N^2).
Out of time complexity... The problem in your function lies in assumption that j holds pivot location in subcalls:
qsort(arr, m, j+1); //sort left sub array
qsort(arr, j+1, n); //sort right sub array
Actually, j is set again and again equal to i in your main for loop. If last element is equal or greater than pivot, you end up with j=n-1, the you call qsort(arr, n, n) and first lines check is passed (sic!), because n-n != 1.
To fix this you should do two things:
1) find pivot location directly after rearrange:
for (int i = m; i < n; i++)
if (p == arr[i])
{
j = i;
break;
}
or initialize it in different variable, update after this line:
arr[j] = t; //pivot is at j and j+1
and update recursive calls to use new variable instead of j
2) make a more bulletproof check in the beginning of your function:
if (n - m <= 1)
the latter will be enough to get some result, but it will be much less effective than your current idea, falling down to probably O(N^3) in worst case.

Error with Mergesort implementation in C++

I am learning to implement a mergesort algorithm for use to count inversions. Here is my current implementation of mergesort. However it is not working as it does not return a sorted array. Will anyone be able to tell me what is being done wrongly in this code which I have written? It should sort the array v which is input into the function.
void mergeCountInversion(vector<int> v, int l, int r)
{
if (l >= r)
{
return;
}
int m = (l + r)/2;
//call merge sort to return two sorted arrays
mergeCountInversion(v, l, m);
mergeCountInversion(v, m+1, r);
int left = l;
int right = m+1;
std::vector<int> vtemp ;
//merge and sort the two sorted array, storing the sorted array in vtemp
for (int k = l; k <= r; ++k){
if (left >= m+1)
{
vtemp.push_back(v[right]);
right++;
}
else if (right > r)
{
vtemp.push_back(v[left]);
left++;
}
else
{
if (v[left] <= v[right])
{
vtemp.push_back(v[left]);
left++;
}
else{
vtemp.push_back(v[right]);
right++;
count += m + 1 - left;
}
}
}
//replace v with the sorted array vtemp
for (int i = 0; i < vtemp.size(); ++i)
{
v[l+i] = vtemp[i];
}
}
There are several issues in your code.
You are passing the vector by value, but you should pass it by refernce.
If you declare the function as void You can't return 0;, just return;.
When you create vtemp you should know exactly its size: r - l. So you can reserve memory for it and you don't need to push_back.
You have to pass count to the function too.
Your function can be:
void mergeCountInversion(vector<int> & v, int l, int r, int & count) {
if (l >= r) return;
int m = (l + r)/2;
//call merge sort to return two sorted arrays
mergeCountInversion(v, l, m, count);
mergeCountInversion(v, m+1, r, count);
int left = l;
int right = m+1;
std::vector<int> vtemp(r-l);
//merge and sort the two sorted array, storing the sorted array in vtemp
for (int k = l; k <= r; ++k) {
if (left >= m+1) {
vtemp[k] = v[right];
right++;
}
else if (right > r) {
vtemp[k] = v[left];
left++;
}
else {
if (v[left] <= v[right]) {
vtemp[k] = v[left];
left++;
}
else {
vtemp[k] = v[right];
right++;
count += m + 1 - left;
}
}
}
//replace v with the sorted array vtemp
for (int i = 0; i < vtemp.size(); ++i)
v[l+i] = vtemp[i];
}
You defined
void mergeCountInversion(vector<int> v, int l, int r)
then you first call mergeCountInversion recursively, and modify v after the calls returned.
The problem is that the changes to v made in the recursive calls will never be seen, because v is passed by value.
Try to pass v by reference:
void mergeCountInversion(vector<int>& v, int l, int r)
such that all calls work on the same copy of v.

Fork Join not sorting correctly

I'm trying to do an adaptation of the merge sort using a fork join.
I'm using this as a fork/join example so I need to keep it basic.
I want to edit the regular merge sort so that when the segment size goes below 101 (100 or less) it will use an insertion sort to sort that segment and then come out of the recursive call and start merging the segments back together.
The sort is just simply not working. If I change the order of the invoke and join(to stop other code from running) it works fine so I'm assuming it is because my sorts are running concurrently that it is not correct.
For example if I write
int mid = (lb+ub)/2;
MergeInsertSortQ left = new MergeInsertSortQ(f,lb,mid);
MergeInsertSortQ right = new MergeInsertSortQ(f,mid,ub);
left.fork(); left.join();
right.fork(); right.join();
merge(f,lb,mid,ub);
It sorts fine, but this is esentially sequential so is not really what I'm trying to do.
Here is the code I am using (including a little test in main)
import java.util.concurrent.*;
public class MergeInsertSortQ extends RecursiveAction{
private int[] f;
private int lb, ub;
public MergeInsertSortQ(int f[], int lb, int ub)
{
this.f = f;
this.lb=lb;
this.ub=ub;
}
protected void compute(){
//Insertion Sort performed when a segment of size
//100 or less is reached otherwise do merge sort
if(ub-lb>100)
{
int mid = (lb+ub)/2;
MergeInsertSortQ left = new MergeInsertSortQ(f,lb,mid);
MergeInsertSortQ right = new MergeInsertSortQ(f,mid,ub);
invokeAll(left,right);
left.join();
right.join();
merge(f,lb,mid,ub);
}
else if(ub-lb>=1)
{
for (int i = lb; i<ub;i++)
{
int temp = f[i];
int j = i-1;
while (j >= 0 && f[j] > temp)
{
f[j+1] = f[j];
j = j-1;
}
f[j+1] = temp;
}
}
}
protected void merge(int f[], int lower, int middle, int top){
int i = lower; int j = middle;
//use temp array to store merged sub-sequence
int temp[] = new int[top-lower]; int t = 0;
while(i < middle && j < top)
{
if(f[i] <= f[j])
{
temp[t]=f[i];i++;t++;
}
else{
temp[t] = f[j]; j++; t++;
}
}
//tag on remaining sequence
while(i < middle)
{
temp[t]=f[i]; i++; t++;
}
while(j < top)
{
temp[t] = f[j]; j++; t++;
}
//copy temp back to f
i = lower; t = 0;
while(t < temp.length)
{
f[i] = temp[t]; i++; t++;
}
}
public static void main(String[] args)
{
//Initialise & print array before sorting
int A[]=new int[200];
for(int j=0;j<A.length;j++)
{
A[j]=(int)(Math.random()*10000);
System.out.print(A[j]+" ");
}
System.out.println();
System.out.println("**********************************");
System.out.println();
// Do the sort
ForkJoinPool fjPool=new ForkJoinPool();
fjPool.invoke(new MergeInsertSortQ(A,0,A.length));
//Check if array sorted and print
boolean sorted=true;
for(int i=0;i<A.length-1;i++)
{
System.out.print(A[i] + " ");
if (A[i]>A[i+1])
{
System.out.println();
System.out.println(A[i]+" is greater than "+A[i+1]+", location "+i);
sorted=false;
//break;
}
}
System.out.println();
if (sorted) System.out.println("The array is sorted!!!");
else System.out.println("WARNING: The array is NOT SORTED");
}
}