C++ Getting StackOverflow error in quicksort function - c++

I am getting stackoverflow error when I am trying to sort using quicksort an array of large size, and this array is in descending order. I want to sort it in ascending order using the code below:
int partition_lastElementPivot(int * arr, int lo, int hi)
{
int x = arr[hi];
int i = lo - 1;
for (int j = lo; j < hi; j++)
{
if (arr[j] <= x)
{
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[hi];
arr[hi] = arr[i + 1];
arr[i + 1] = temp;
return i + 1;
}
void quicksortLastElementPivot(int*arr, int lo, int hi)
{
if (lo<hi)
{
int mid = partition_lastElementPivot(arr, lo, hi);
quicksortLastElementPivot(arr, lo, mid - 1);
quicksortLastElementPivot(arr, mid + 1, hi);
}
}
This code works fine when I randomly generate an array of any size, suppose of size 5000. But when I generate an array of size 5000 sorted in descending order and then try to sort using this code, I get a stackoverflow error. Does C++ limits the memory useable by stack and why is this happening.
int arr[5000];
int count = 5001;
for(int i=0; i<5000; i++)
{
arr[i] = count;
count--;
}
quicksortLastElementPivot(arr, 0, 4999)
Thanks

Quicksort has a truly dreadful worst-case performance, as you have discovered here. It is calling itself at a stack depth of 5000. This Wikipedia article has a good discussion on the subject. In particular, it mentions tail recursion as a solution to your stack overflow problem.
Briefly, this means that instead of the last call to quicksortLastElementPivot, followed immediately by a return, you just loop back to the start of the function. This has the same effect, but the tail recursion doesn't increase the stack size. For this to work, you have to make sure that the smaller of the two partitions is sorted first, using traditional recursion, and the larger partition is sorted by tail recursion. Something like this (not tested!):
void quicksortLastElementPivot(int*arr, int lo, int hi)
{
TailRecurse:
if (lo<hi)
{
int mid = partition_lastElementPivot(arr, lo, hi);
if (mid < (lo + hi) / 2)
{ // First partition is smaller
quicksortLastElementPivot(arr, lo, mid - 1); // Sort first partition
lo = mid + 1; goto TailRecurse; // Sort second partition
}
else
{ // Second partition is smaller
quicksortLastElementPivot(arr, mid + 1, hi); // Sort second partition
hi = mid - 1; goto TailRecurse; // Sort first partition
}
}
}

C++ standard does not define the stack-size of an executable program.
This limit is typically defined in the make file or the linker-command file of your project.
Depending on your IDE, you might also find it within your project settings (under linker-configuration).
The answer given by TonyK is doing quite a good job in explaining the stack usage of quick-sort under the worst-case scenario (which is exactly the case in your code, where arr is sorted in reversed order).

#include <iostream>
using namespace std;
void QuickSort(int *arr, int left, int right)
{
int i = left;
int j = right;
int pivot = arr[rand() % (right - left) + left];
while (i < j)
{
while (arr[i] < pivot)
{
i++;
}
while (arr[j] > pivot)
{
j--;
}
if (i <= j)
{
swap(arr[i], arr[j]);
i++;
j--;
}
}
if (left < j)
{
QuickSort(arr, left, j);
}
if (i < right)
{
QuickSort(arr, i, right);
}
}
int main()
{
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
int n;
cin >> n;
int *arr = new int[n];
for (int i = 0; i < n; i++)
{
cin >> arr[i];
}
QuickSort(arr, 0, n - 1);
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
delete arr;
}

Related

How does Hoare partitioning work in QuickSort?

Here is the pseudocode straight from the book (CORMEN):
Partition(A,p,r)
x=A[p]
i=p-1
j=r+1
while(TRUE)
repeat
j=j-1
until A[j]<=x
repeat
i=i+1
until A[i]>=x
if i<j
SWAP A[i] <=> A[j]
else return j
Here is code in C++:
#include<bits/stdc++.h>
using namespace std;
int partition(int a[], int low, int high)
{
int pivot = a[low];
int i = low - 1;
int j = high + 1;
while (1)
{
do {
i++;
} while (a[i] < pivot);
do {
j--;
} while (a[j] > pivot);
if (i >= j) {
cout<<j<<endl;
return j;
}
swap(a[i], a[j]);
}
}
/* The main function that implements QuickSort
arr[] --> Array to be sorted,
low --> Starting index,
high --> Ending index */
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
/* pi is partitioning index, arr[p] is now
at right place*/
int pi = partition(arr, low, high);
// Separately sort elements before
// partition and after partition
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
/* Function to print an array */
void printArray(int arr[], int size)
{
int i;
for (i=0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}
// Driver program to test above functions
int main()
{
int arr[] = {7,3,2,6,4,1,3,5};
int n = sizeof(arr)/sizeof(arr[0]);
cout<<"partition:\n";
partition(arr,0,7);
printArray(arr, n);
quickSort(arr, 0, n-1);
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}
If I use this array in input:
[5,3,2,6,4,1,3,7]
everything works logically well because the array returned by the partitioning will be:
[3,3,2,1,4,6,5,7]
Termination i=5 and j=4 so my pivot is 4. And all elements to the left of 4 are minor and all to the right are major
Now if I use this array in input:
[7,3,2,6,4,1,3,5]
I will have this situation at the end of the partition
[5,3,2,6,4,1,3,7]
which will return to me as pivot j = 6 that is 3. Now the elements on the left of 3 are not all minor and on the right are major.
But how is it possible that this works? Shouldn't I have the elements to the left of the pivot minor and to the right major?
With Hoare partition the pivot and values equal to the pivot can end up anywhere. The returned index is not an index to the pivot, but just a separator. For the code above, when partition is done, then elements <= pivot will be at or to the left of j, and elements >= pivot will be to the right of j. After doing a partition step, the C++ code should be:
quickSort(arr, low, pi); // not pi - 1
quickSort(arr, pi + 1, high);
example code that includes testing of quicksort:
uint32_t Rnd32()
{
static uint32_t r = 0;
r = r*1664525 + 1013904223;
return r;
}
int Partition(int ar[], int lo, int hi)
{
int pv = ar[lo+(hi-lo)/2];
int i = lo - 1;
int j = hi + 1;
while(1){
while(ar[++i] < pv);
while(ar[--j] > pv);
if(i >= j)
return j;
std::swap(ar[i], ar[j]);
}
}
void QuickSort(int ar[], int lo, int hi)
{
while (lo < hi){
int pi = Partition(ar, lo, hi);
if((pi - lo) < (pi - hi)){
QuickSort(ar, lo, pi);
lo = pi + 1;
} else {
QuickSort(ar, pi + 1, hi);
hi = pi;
}
}
}
#define COUNT (16*1024*1024)
int main(int argc, char**argv)
{
size_t i;
int * ar = new int [COUNT];
for(i = 0; i < COUNT; i++){
ar[i] = Rnd32();
}
QuickSort(ar, 0, COUNT-1);
for(i = 1; i < COUNT; i++)
if(ar[i-1] > ar[i])
break;
if(i == COUNT)
std::cout << "passed" << std::endl;
else
std::cout << "failed" << std::endl;
delete[] ar;
return(0);
}

Stack Overflow while sorting (merge sort and quick sort)

We are supposed to compare the speeds of each sort with 10000 inputs. They all work by themselves but when I add them together in the program I think perhaps merge sort takes a lot of space so I always get an unhandled exception: StackOverflow once I reach quicksort. Does anyone know a solution to this problem to perhaps make it so merge sort doesn't take a lot of space (if that is the problem)? Specifically, the exception is thrown at the partition function for quicksort.
#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;
void merge(int arr[], int l, int m, int r) {
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;
int *L = new int[n1];
int *R = new int[n2];
for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];
i = 0;
j = 0;
k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
void mergeSort(int arr[], int l, int r) {
if (l < r) {
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
}
int partition(int arr[], int start, int end) { //start is 0 and end is counter-1
int pivot = start; //remember start here
int imp = arr[end];
for (int i = start; i < end; i++) { //remember this is start and end;
if (arr[i] <= imp) {
int temp = arr[i];
arr[i] = arr[pivot];
arr[pivot] = temp;
pivot++;
}
}
int p = arr[pivot];
arr[pivot] = arr[end];
arr[end] = p;
return pivot;
}
void quicksort(int arr[], int start, int end) {
if (start < end) {
int index = partition(arr, start, end);
quicksort(arr, start, index - 1);
quicksort(arr, index + 1, end);
}
}
int main() {
clock_t timereq;
double time_taken;
ifstream in("input3.txt");
int size;
in >> size;
int num;
int *arrq = new int[size];
int i = 0;
while (!in.eof()) {
in >> num;
arrq[i] = num;
i++;
}
timereq = clock();
mergeSort(arrq, 0,size-1);
timereq = clock() - timereq;
time_taken = double(timereq) / CLOCKS_PER_SEC; /// double(CLOCKS_PER_SEC);
cout << time_taken << endl;
timereq = clock();
quicksort(arrq, 0,size-1);
timereq = clock() - timereq;
time_taken = double(timereq) / CLOCKS_PER_SEC; /// double(CLOCKS_PER_SEC);
cout << time_taken << endl;
for (i = 0; i < size; i++) {
cout << arrq[i] << endl;
}
}
The input looks for example like this, the number of values and then the values:
8
3 1 4 1 5 9 2 6
You should really follow the suggestions given in the comments above, and directly tackle the root of the problem (limited stack size) by redesigning your code with stack data structures in place, so to specifically avoid memory-draining recursions.
However, you could also in principle cut corners, and adopt a dirtier and quicker solution: just add flags to your compiler to let it increase the size of the stack.
If you use gcc, you can do this by inserting the -Wl, --stack,<size> keys if compiling from the prompt.
The <size> key above could be any size bigger than your current stack size, -Wl, --stack,1000000000 (9 zeros) for instance.
If you instead are using an IDE, I happen to know how to do this on Codeblocks: go to Settings->Compiler->Global Compiler Settings->Linker Settings-> add the line above under the Other Linker Options field.
See also stackoverflow: How to increase stack size when compiling a C++ program using MinGW compiler
The problem with quicksort is its worse case behavior:
if the partition function does not split the dataset in balanced halves, the complexity can reach O(N2) instead of the average O(N.log(N)).
in your case, the worst case occurs when the list is already sorted: The pivot splits the array into N-1 and 1 parts, causing the recursion to occur N times, probably too much for the default stack depth.
there is a logic error in your benchmark that causes this worst case behavior to occur every time: you measure the time for mergeSort() to sort arrq ad then you do the same for quicksort on the same array, that was just sorted by mergeSort. You should make a copy of the original array and pass that to quicksort, but you must also fix quicksort to avoid this stack overflow.
You can fix this problem by changing the quicksort function to recurse on the smaller half and iterate on the larger one:
void quicksort(int arr[], int start, int end) {
while (start < end) {
int index = partition(arr, start, end);
if (index - start < end - index) {
quicksort(arr, start, index - 1);
start = index + 1;
} else {
quicksort(arr, index + 1, end);
end = index - 1;
}
}
}
This should solve the stack overflow bug, but will not reduce the time complexity. You would need to change the partition function for that, for example by choosing a pivot value at random if the default choice leads to a pathological split.

How to sort data in linked list by Quicksort?

I want to sort the data in a linked list by Quicksort . Here is my code:
struct stu
{
int id;
char name[100];
int score;
stu* next;
}head;
int address(stu* StudentList)
{
//return the index of data
int index = 0;
stu* test = head;
for (index = 0; test != StudentList; temp++)
{
test = test->next;
}
return index;
}
stu* section(int low)
{
//Classification discussion on data
stu* StudentList1=head;
for (int i = 0; i < low; i++)
{
StudentList1 = StudentList1->next;
}
return StudentList1;
}
void swap(stu *Student1,stu *Student2)
{
int t_id = 0, t_score = 0;
char t_name[10] = "000";
//exchange id
t_id = Student1->id;
Student1->id = Student2->id;
Student2->id = t_id;
//exchange name
strcpy(t_name, Student1->name);
strcpy(Student1->name, Student2->name);
strcpy(Student2->name, t_name);
//exchange score
t_score = Student1->score;
Student1->score = Student2->score;
Student2->score = t_score;
}
void sort(int low, int high)
{
stu* StudentList1 = head, * StudentList2 = StudentList1->next;
if (low >= high)
return;
else
{
StudentList1=section(low);
StudentList2 = StudentList1->next;
stu* key = StudentList1;
int i;
for (int i = 0; i <= (high - low) && StudentList2 != NULL; i++)
{
if (StudentList2->score >= key->score)
{
StudentList1 = StudentList1->next;
swap(StudentList1, StudentList2);
}
StudentList2 = StudentList2->next;
}
swap(key, StudentList1);
int temp = address(StudentList1);
StudentList2 = head;
for (i = 0; StudentList2->next!=NULL;StudentList2=StudentList2->next)
{
if (StudentList2->next->score <= StudentList2->score)
{
i++;
}
}
high = temp - 1;
low = temp + 1;
if (i < num - 1)
{
sort(0, high);
sort(low, num - 1);
}
return;
}
}
The problem is the The program is in a infinite loop . I tried to modify it but it didn't help . So I have to turn into Stackoverflow . Thank you !
I just modified my code . But there are still some problems . When processing 7 data , the program can run well . However , when processing 8 data , the compiler told me stack overflow in "section" . Hope that you can point out the problems . Thank you sincerely !
One potential issue is this line:
sort(0, high);
The recursive calls should always be at least one node less than sort(lo, hi). There may be other issues.
Assuming this is a class assignment, I'm not sure why quicksort was chosen as a method for sorting a linked list, when merge sort is normally used. There is also the choice of "sorting" a linked list by rearranging the data in the nodes, versus sorting by reordering the nodes within a linked list. Since the question doesn't specify the assignment requirements, I can't be sure what the goal is here.
Using indexing for a linked list is inefficient. You could use pointer based quicksort such as:
void QuickSort(stu * lo, stu * hi);
Here is an a variation of Lomuto partition scheme that can be converted to sort a linked list, where lo, hi, i, j, k would be converted to pointers to node.
void QuickSort(int a[], int lo, int hi);
void QuickSort(int a[])
{
int lo = 0;
int hi = sizeof(a)/sizeof(a[0])-1;
if(lo >= hi)
return;
QuickSort(a, lo, hi);
}
void QuickSort(int a[], int lo, int hi)
{
int p = a[lo];
int i = lo;
int j = lo;
int k;
for(k = lo+1; k <= hi; k++){
if(a[k] < p){
j = i++;
std::swap(a[k], a[i]);
}
}
std::swap(a[i], a[lo]);
if(j != lo)
QuickSort(a, lo, j);
if(i != hi)
QuickSort(a, i+1, hi);
}
You could also consider a quicksort like method, that creates 3 lists with each level of recursion, sorting nodes instead of swapping data. The 3 lists would be nodes < pivot, nodes == pivot, nodes > pivot. Recursion would be used on the 2 lists with nodes != pivot, then the 3 lists would be concatenated.

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.

Two sorting algorithms give me two different outputs on the same array (quickSort and heapSort)!

I don't understand why they give me different output when I compile them. For example ... when I compile only one algorithm the answer is good, the same is for the other one, but when I compile them both at the same time they give me some weird output.
My code:
#include <iostream>
using namespace std;
int parent(int i){
return i/2;
}
int leftChild(int i){
return 2*i+1;
}
int rightChild(int i){
return 2*i+2;
}
void maxHeapify(int a[], int i, int n){
int largest;
int temp;
int l = leftChild(i);
int r = rightChild(i);
// p.countOperation("CMPbottomUp",n);
if (l <= n && (a[l] > a[i]))
largest = l;
else
largest = i;
// p.countOperation("CMPbottomUp",n);
if (r <= n && (a[r] > a[largest]))
largest = r;
if (largest != i){
// p.countOperation("ATTbottomUp",n);
temp = a[i];
// p.countOperation("ATTbottomUp",n);
a[i] = a[largest];
//p.countOperation("ATTbottomUp",n);
a[largest] = temp;
maxHeapify(a, largest, n);
}
}
void buildMaxHeap(int a[], int n){
for (int i=n/2; i>=0; i--){
maxHeapify(a, i, n);
}
}
void heapSort(int a[],int n){
buildMaxHeap(a,n);
int n1=n;
int temp;
for(int i=n1;i>0;i--){
temp = a[0];
a[0] = a[i];
a[i] = temp;
n1--;
maxHeapify(a,0,n1);
}
}
int partitionArray(int arr[], int left, int right){
int i = left, j = right;
int tmp;
int pivot = arr[(left + right) / 2];
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--;
}
}
return i;
}
void quickSort(int arr[], int left, int right) {
int index;
index = partitionArray(arr, left, right);
if (left < index - 1)
quickSort(arr, left, index - 1);
if (index < right)
quickSort(arr, index, right);
}
int main(){
int x[8]= {5,87,21,4,12,7,44,3};
int a[8];
for(int i=0;i<8;i++){
a[i] = x[i];
}
heapSort(x,8);
quickSort(a,0,8);
for(int i=0;i<8;i++){
cout<<a[i]<<' ';
}
cout<<endl;
for(int j=0;j<8;j++){
cout<<x[j]<<' ';
}
return 0;
}
Example output:
1) When I compile only one algorithm the output is : 3,4,5,7,12,21,44,87 (which is good)
2) When I compile both of them in the code the output is: 87,4,5,7,12,21,44,87 (quickSort) and 3,3,4,5,7,12,21,44 (heapSort)
I think that should work:
heapSort(x,7);
quickSort(a,0,7);
Arrays a and x are right next to each others in stack. Seeing how you have duplicate value 87 in output, it seems your sort functions access memory outside the array you give to them. This is buffer overrun, a type of Undefined Behaviour. With that, your code could do anything because you have corrupted variable values (or worse, corrupted addresses/pointers).
Double check how you access arrays. Remember that C array indexes for your arrays of length 8 are 0..7!