Why does my Quicksort implementation give stack overflow? - java

I have this code
public static void quicksort(int[] array){
quicksort(array, 0, array.length - 1);
}
public static void quicksort(int[] array, int min_index, int max_index){
if(array.length <= 1 || min_index >= max_index){
return;
}
int pivot = array[(max_index + min_index) / 2];
int left = min_index;
int right = max_index;
while(left <= right){
while(array[left] < pivot)
left++;
while(array[right] > pivot)
right--;
if( left <= right ){
int aux = array[left];
array[left] = array[right];
array[right] = aux;
left++;
right--;
}
}
if(right > min_index)
quicksort(array, min_index, right);
if(left < max_index)
quicksort(array, left, max_index);
}
Which works pretty fine, but if I change the pivot to pivot = array[0];, it breaks, giving me a stackoverflow exception. I tried other values, and it only seems to like the middle point. Why does this happen?

This is a well-known problem with Quicksort. For certain inputs, e.g. sorted (or almost sorted) data, and with poorly-selected pivot points, Quicksort will degenerate to a bubble-sort. Since the function is recursive, it is very easy to generate a stack overflow condition (or have the sort execute in O(n2) time). Text-book examples of Quicksort are notorious in this regard. Quicksort makes a nice programming exercise but you should always use the implementation provided with your compiler.
For more information on Quicksort, see the Wikipedia article.

Related

java.lang.IllegalArgumentException in QuickSort method [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I am following the following pseudocode:
function quicksort(array)
if length(array) > 1
pivot := select any element of array
left := first index of array
right := last index of array
while left ≤ right
while array[left] < pivot
left := left + 1
while array[right] > pivot
right := right - 1
if left ≤ right
swap array[left] with array[right]
left := left + 1
right := right - 1
quicksort(array from first index to right)
quicksort(array from left to last index)
but when I try to implement it in java, I insert code:
import java.util.Arrays;
public class ALGQuickSort {
public static void main(String[] args) {
int[] array = {6, 3, 4, 8, 9, 10, 1};
quickSort(array);
System.out.println(Arrays.toString(array));
}
public static void quickSort(int[] array) {
int pivot = array[array.length - 1];
if (array.length > 1) {
int left = 0;
int right = array.length - 1;
while (left <= right) {
while (array[left] < pivot) {
left++;
}
while (array[right] > pivot) {
right--;
}
if (left <= right) {
swap(array[left], array[right]);
right--;
left++;
}
int[] array1 = Arrays.copyOfRange(array, 0, right);
int[] array2 = Arrays.copyOfRange(array, left, array.length - 1);
quickSort(array1);
quickSort(array2);
}
}
}
public static void swap(int a, int b) {
int aux = a;
a = b;
b = aux;
}
}
the system shows me the following error on the screen:
Exception in thread "main" java.lang.IllegalArgumentException: 5 > 4
at java.util.Arrays.copyOfRange(Arrays.java:3591) at
alg.quicksort.ALGQuickSort.quickSort(ALGQuickSort.java:43) at
alg.quicksort.ALGQuickSort.quickSort(ALGQuickSort.java:44) at
alg.quicksort.ALGQuickSort.main(ALGQuickSort.java:21)
C:\Users\Alex\AppData\Local\NetBeans\Cache\8.2\executor-snippets\run.xml:53:
Java returned: 1
the error is in the line:
int[] array2 = Arrays.copyOfRange(array, left, array.length - 1);
someone can help me pls?
Improvements/corrections to your quick-sort algorithm:
Correct your swap method: The swap method that you are using will not work.
The recursive call should be outside the for loop: Your pseudo-code is correct, but in your implementation it is not the case.
Place the Pivot at its correct position and then have a recursive call on sub-arrays that now (for-sure) should not contain the pivot element. After the while loop, you are sure that right+1 == left (think on it a bit and you will understand why this is true). Now swap the array[left] with pivot element and give a recursive call on 2 different sub-arrays (left sub-array to the pivot is beginIndex..right and right sub-array to the pivot is left+1..endIndex where I assume that you need to sort array[beginIndex..endIndex])
Avoid copying: Its better to avoid copying a part of the array (instead you can pass the startIndex and the endIndex to denote the sub-array you want to sort)
Use Randomized Quick sort: It would also be better if you don't always select the last element as your pivot (what you can do here is just before starting to sort select any random element and then swap it with the last element of your array. Using this strategy you don't have to make much changes in your existing code) This selection of random element as pivot is much better. See this link for more details.
Make swap method private: Not relevant to the algorithm, but it is good if you make the swap method private as you might not intend to call it from outside of this class.
If you intend to swap the elements of array with index say index1 and index2, then the following code will work:
public static void swap(int[] array, int index1, int index2) {
int aux = array[index1];
array[index1] = array[index2];
array[index2] = aux;
}
The following is the final code with all the above suggested changes:
public static void main(String[] args) {
int[] array = {6, 30, 7, 23, 4, 8, 9, 10, 1, 90};
quickSort(array, 0, array.length - 1);
System.out.println(Arrays.toString(array));
}
public static void quickSort(int[] array, int beginIndex, int endIndex) {
// System.out.println("called quick sort on the following : " + beginIndex + " " + endIndex);
int arrayLength = endIndex - beginIndex + 1;
int pivot = array[endIndex];
if (arrayLength > 1) {
int left = beginIndex;
int right = endIndex - 1;
while (left <= right) {
// System.out.println(left + " " + right);
while (left <= endIndex && array[left] < pivot) {
left++;
}
while (right >= beginIndex && array[right] > pivot) {
right--;
}
if (left <= right) {
swap(array, left, right);
right--;
left++;
}
}
swap(array, left, endIndex); // this is crucial, and you missed it
if (beginIndex < right) {
quickSort(array, beginIndex, right);
}
if (left + 1 < endIndex) {
quickSort(array, left + 1, endIndex);
}
}
}
private static void swap(int[] array, int index1, int index2) {
int aux = array[index1];
array[index1] = array[index2];
array[index2] = aux;
}
Firstly, the two lines
int[] array1 = Arrays.copyOfRange(array, 0, right);
int[] array2 = Arrays.copyOfRange(array, left, array.length - 1);
are wrong. You must not copy the arrays. This will prevent the Quicksort algorithm from working since you sort the temporary copies in your recursive calls. The sorted sub arrays will be discarded afterwards.
Another bug in your program raises the exception: the third parameter of Arrays.copyOfRange is exclusive. I.e. it will not copy elements from to to but from to to - 1. But you already subtracted one from the upper bound, and in Quicksort it might happen that one of the sub arrays has zero size. In this case the range to copy becomes negative. That's the exception.
There is a third bug: you swapped the first two lines of the pseudo code. This will crash on empty arrays.
Finally, you cannot implement the Quicksort algorithm in this way as shown in the pseudo code because Java (unlike e.g. C) does not support array slices.
You need to modify the algorithm in a way that you split it into two methods:
One method to recursively sort an array slice:
Note that I replaced the array start and end by from and to.
public static void quickSort(int[] array, int from, int to) {
if (array.length <= 1)
return;
int pivot = array[to];
int left = from;
int right = to;
while (left <= right) {
while (array[left] < pivot)
left++;
while (array[right] > pivot)
right--;
if (left <= right) {
swap(array[left], array[right]);
right--;
left++;
}
quickSort(array, from, right);
quickSort(array, left, to);
}
}
And a second method to sort an entire array that calls the above one for the entire array.
public static void quickSort(int[] array) {
return quickSort(array, 0, array.length - 1);
}

Quickselect implementation not working

I am trying to write code to determine the n smallest item in an array. It's sad that I am struggling with this. Based on the algorithm from my college textbook from back in the day, this looks to be correct. However, obviously I am doing something wrong as it gives me a stack overflow exception.
My approach is:
Set the pivot to be at start + (end-start) / 2 (rather than start+end/2 to prevent overflow)
Use the integer at this location to be the pivot that I compare everything to
Iterate and swap everything around this pivot so things are sorted (sorted relative to the pivot)
If n == pivot, then I think I am done
Otherwise, if I want the 4 smallest element and pivot is 3, for example, then I need to look on the right side (or left side if I wanted the 2nd smallest element).
-
public static void main(String[] args) {
int[] elements = {30, 50, 20, 10};
quickSelect(elements, 3);
}
private static int quickSelect(int[] elements2, int k) {
return quickSelect(elements2, k, 0, elements2.length - 1);
}
private static int quickSelect(int[] elements, int k, int start, int end) {
int pivot = start + (end - start) / 2;
int midpoint = elements[pivot];
int i = start, j = end;
while (i < j) {
while (elements[i] < midpoint) {
i++;
}
while (elements[j] > midpoint) {
j--;
}
if (i <= j) {
int temp = elements[i];
elements[i] = elements[j];
elements[j] = temp;
i++;
j--;
}
}
// Guessing something's wrong here
if (k == pivot) {
System.out.println(elements[pivot]);
return pivot;
} else if (k < pivot) {
return quickSelect(elements, k, start, pivot - 1);
} else {
return quickSelect(elements, k, pivot + 1, end);
}
}
Edit: Please at least bother commenting why if you're going to downvote a valid question.
This won't fix the issue, but there are several problems with your code :
If you do not check for i < end and j > start in your whiles, you may run into out of bounds in some cases
You choose your pivot to be in the middle of the subarray, but nothing proves that it won't change position during partitioning. Then, you check for k == pivot with the old pivot position, which obviously won't work
Hope this helps a bit.
Alright so the first thing I did was rework how I get my pivot/partition point. The shortcoming, as T. Claverie pointed out, is that the pivot I am using isn't technically the pivot since the element's position changes during the partitioning phase.
I actually rewrote the partitioning code into its own method as below. This is slightly different.
I choose the first element (at start) as the pivot, and I create a "section" in front of this with items less than this pivot. Then, I swap the pivot's value with the last item in the section of values < the pivot. I return that final index as the point of the pivot.
This can be cleaned up more (create separate swap method).
private static int getPivot(int[] elements, int start, int end) {
int pivot = start;
int lessThan = start;
for (int i = start; i <= end; i++) {
int currentElement = elements[i];
if (currentElement < elements[pivot]) {
lessThan++;
int tmp = elements[lessThan];
elements[lessThan] = elements[i];
elements[i] = tmp;
}
}
int tmp = elements[lessThan];
elements[lessThan] = elements[pivot];
elements[pivot] = tmp;
return lessThan;
}
Here's the routine that's calls this:
private static int quickSelect(int[] elements, int k, int start, int end) {
int pivot = getPivot(elements, start, end);
if (k == (pivot - start + 1)) {
System.out.println(elements[pivot]);
return pivot;
} else if (k < (pivot - start + 1)) {
return quickSelect(elements, k, start, pivot - 1);
} else {
return quickSelect(elements, k - (pivot - start + 1), pivot + 1, end);
}
}

Java Quick Sort Implementation (Code fix)

I have written my version of the Quick Sort in Java but I'm running into a bit of a problem while calling the second recursion. This is my code:
public static int[] quickSort(int[] array, int start, int end) {
int pIndex;
if (start < end) {
int left = start;
int right = end - 1;
int pivot = array[end];
//Start the partitioning
while (left < right) {
if (array[left] > pivot && array[right] < pivot) {
int temp = array[left];
array[left] = array[right];
array[right] = temp;
left++;
right--;
} else if (array[left] > pivot)
right--;
else
left++;
}
if (array[end] < array[left]) {
int temp = array[left];
array[left] = array[end];
array[end] = temp;
pIndex = left;
} else
pIndex = end;
//End partitioning
quickSort(array, 0, pIndex - 1);
quickSort(array, pIndex + 1, array.length - 1);
}
return array;
}
The parameter start will be the index of the first element in the array and end will be the index of the last one and I am picking the last element as the pivot.
The issue I am running into is at quickSort(array, pIndex + 1, array.length - 1);
The array.length-1 causes this to go on infinitely since it is with reference to the original array. Is there any way for me to fix this without having to pass a new array to the function everytime?
I did try to create a global variable to store the new lengths but I wasn't able to do it quite right.
Thanks in advance.
I'm sorry if the code is not a very nice implementation of the Sort. I wanted to write one from scratch on my own but turns out I ran into problems anyway.
The proper way of using recursion in QuickSort is to use start, pivot and end.
You appear to be using inclusive-end indexing; then you will want to recurse into
quicksort(data, start, pivot - 1)
quicksort(data, pivot + 1, end)
The pivot element is already in its final position.

Recursive Mergesort on LinkedList

I've been getting headaches trying to implement a recursive mergesort but I keep getting problem after problem.
Right now, I have a lot of trouble when adding elements which has caused 75% of my problems earlier.
This is the code of the implementation, the main problem is the merge part:
static public void DoMerge(LinkedList <Contacto> L, int left, int mid, int right)
{
LinkedList <Contacto> temp = new LinkedList <Contacto>();
int i, left_end, num_elements, tmp_pos, comp;
left_end = (mid - 1);
tmp_pos = left;
num_elements = (right - left + 1);
while ((left <= left_end) && (mid <= right))
{
comp= L.get(left).get_name().compareTo(L.get(mid).get_name());
if (comp<=0)
temp.add(tmp_pos++,L.get(left++));
else
temp.add(tmp_pos++,L.get(mid++));
}
while (left <= left_end)
temp.add(tmp_pos++,L.get(left++));
while (mid <= right)
temp.add(tmp_pos++,L.get(mid++));
for (i = 0; i < num_elements; i++)
{
L.set(right, temp.get(right));
right--;
}
}static public void MergeSort_Recursive(LinkedList <Contacto> L, int left, int right)
{
int mid;
if (right > left)
{
mid = (right + left) / 2;
MergeSort_Recursive(L, left, mid);
MergeSort_Recursive(L, (mid + 1), right);
DoMerge(L, left, (mid+1), right);
}
}
The main problem again is the merge part which is constantly troubling me, specially adding the elements to the temporary list. The compiler throws me an out of bounds exception.
The problem is
LinkedList <Contacto> temp = new LinkedList <Contacto>();
You initialized an empty list. But, this line:
temp.add(tmp_pos++,L.get(left++));
You insert an Object to index tmp_pos which can be larger than the current size of temp (first, size of temp is zero). (Read more about add.)
You can fix this by understanding that, for merge sort, temp actually is used as a stack, so this part is not necessary temp.add(tmp_pos++,L.get(left++));, use temp.add(L.get(left++)); instead. (Replace other statements in similar manner).
And for the last part, just use
for (i = 0; i < num_elements; i++)
{
L.set(right, temp.removeLast());
right--;
}

Is finishing an in place mergesort algorithm with insertion sort possible/viable?

I have written an in place mergesort algorithm for sorting a large set of data of random size (100,000 elements or more). I was thinking about putting in insertion sort for when the data is almost sorted to make the algorithm run a little bit faster. I was wondering if this is possible with in place mergesort?
Here is some of my code.
public static void merge(ArrayList<String> list, int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
merge(list, low, mid);
merge(list, mid + 1, high);
mergeSort(list, low, mid, high);
}
}
public static void mergeSort(ArrayList<String> list, int first, int mid,
int last) {
int left = first;
int right = mid + 1;
String holder = "";
// if mid <= mid+1 skip merge
if (compareTo(list.get(mid), list.get(right)) <= 0) {
return;
}
while (left <= mid && right <= last) {
// if left index <= right index then just add to left
if (compareTo(list.get(left), list.get(right)) <= 0) {
left++;
} else {
holder = list.get(right);
copyList(list, left, right - left);//moves everything from left to right-left up one index in the arraylist
list.set(left, holder);
left++;
mid++;
right++;
}
}
// what is left is in place
}
public static void copyList(ArrayList<String> source, int srcPos, int length) {
String temp1 = "";
String temp2 = source.get(srcPos);
for (int i = 0; i < length; i++) {
temp1 = source.get(srcPos + 1);
source.set(srcPos + 1, temp2);
temp2 = temp1;
srcPos++;
}
}
Now, I was thinking of implementing Insertion sort by counter the number of elements when I first throw them into the arraylist and then changing my merge method to the following.
public static void merge(ArrayList<String> list, int low, int high) {
if(high-low==dataSize-1){
int mid = (low + high) / 2;
merge(list, low, mid);
merge(list, mid + 1, high);
insertionSort(list);
}else if (low < high) {
int mid = (low + high) / 2;
merge(list, low, mid);
merge(list, mid + 1, high);
mergeSort(list, low, mid, high);
}
}
However, this actually makes my algorithm to take an eternity. Im guessing I'm doing this wrong and the algorithm is taking n^2 to run since the data is completely randomly generated and no where close to almost sorted.
What am I doing wrong? Any suggestions? My guess is since its in place merge-sort it wont work.
Thanks!
Such algorithms are complicated and easy to get wrong. I implemented something very similar: an in-place stable merge sort. It also uses insertion sort for small sub-lists. I suggest to have a look at the source code and compare it with what you are doing. You might also be interested in a in-place stable quicksort.
Unless I'm mistaken your implementation is not stable (it might re-arrange elements that are equal). Depending on the use case, this may or may not be a problem.
Also, it seems your implementation is O(n^2) because the copyList method is O(n) and it is called n times.
About the insertionSort: what is dataSize and why do you compare it using equals? Don't you want to use < instead? If you do, the else if (low < high) is redundant (it is always true).

Categories