So I've been trying to implement a quicksort myself, but it generates a stackoverflowerror, but I can't seem to find what the cause is.
Can someone help me?
public static int partition(int[] a, int p, int q){
int i = 0;
for (int j = p; j < q; ++j){
if(a[j] <= a[q]){
int tmp = a[j];
a[j] = a[i];
a[i] = tmp;
i++;
}
}
int tmp = a[i];
a[i] = a[q];
a[q] = tmp;
return i;
}
public static void qsort(int[] a, int p, int q){
if(p < q){
int x = partition(a, p, q);
qsort(a, p, x - 1);
qsort(a, x + 1, q);
}
}
public static void main(String args[]){
int[] a = {4, 6, 2, 9, 8, 23, 0, 7};
qsort(a, 0, a.length - 1);
for(int i : a){
System.out.print(i + " ");
}
}
There are several bugs, but the immediate one you're hitting is that in partition(), i is not constrained to be between p and q. You pretty quickly end up in a situation where p=2, q=3 yet the final value of i is 1. This results in infinite recursion as qsort() keeps calling itself with identical arguments.
A stack overflow error means the stop condition for the recursion is never reached, in this case p < q is never true. Use a debugger, set a breakpoint for that line, and look for when qsort() is repeatedly recursively called with the same parameters.
Related
Challenge: within a lecture on data structures and algorithms I encountered a version of merge sort which uses the merge routine in a way that the second half is being reversed from the splitting index and from there compares the first and the last element. I tried to implement in java and it always failed somehow.
Problem: The array is being sorted so that the output is [1, 2, 4, 8, 6] so the 6 is not sorted. It seems as if the recursive call is not looking at the element 6 in the last merge call.
What I tried: Shifting different indices and adding different print statements for checking.
I tried to make j = r before the last for loop within merge which lead to stack overflow every time. I tried to change the way how the size of the array is being calculated, since I was not sure if the pseudo code excepts the array to start from 1 or 0. I tried to shift if(p < r-1) to if(p <= r-1) but get a stack overflow.
I looked at different implementations of java merge routine and every I found so far seems to work with two arrays. Is there a serious reason why the approach above is not working correctly or any idea how to fix this issue?
Given the following pseudo code:
void merge_sort(array<T>& A, int p, int r) {
if (p < r - 1) {
int q = Floor((p + r) / 2);
merge_sort(A, p, q);
merge_sort(A, q + 1, r);
merge(A, p, q, r);
}
}
void merge(array<T>& A, int p, int q, int r) {
array<T> B(p, r - 1);
int i, j;
for (i = p; i < q; i++)
B[i] = A[i];
// Now i=q
for (j = r; i < r; i++)
B[--j] = A[i];
i = p;
j = r - 1;
for (int k = p; k < r; k++)
A[k] = (B[i] < B[j]) ? B[i++] : B[j--];
}
I tried to implement in java like so:
import java.util.Arrays;
public class Mergesort {
private static int[] A = new int[]{ 4, 2, 1, 8, 6 };
public static void main(String[] args) {
merge_sort(0, A.length - 1);
System.out.println(Arrays.toString(A));
}
public static void merge_sort(int p, int r) {
if (p < r - 1) {
int q = Math.floor((p + r) / 2);
merge_sort(p, q);
merge_sort(q + 1, r);
merge(p, q, r);
}
}
public static void merge(int p, int q, int r) {
int[] B = new int[r - p];
int i, j;
for (i = p; i < q; i++)
B[i] = A[i]
for (j = r; i < r; i++)
B[--j] = A[i];
i = p;
j = r - 1;
for (int k = p; k < r; k++)
A[k] = (B[i] < B[j])? B[i++] : B[j--];
}
}
There are multiple problems in your code:
the temporary array is too short: since r is the index of the last element, the size should be r - p + 1. It is much simpler to pass r as the index one past the last element of the slice to sort.
the first for loop is incorrect: you should use a different index into B and A.
the second for loop copies to B[r - 1] downwards, but it should use B[r - p] instead.
the merging loop is incorrect: you should test if i and j are still within the boundaries of the respective halves before accessing B[i] and/or B[j].
[minor] there is no need for int q = Math.floor((p + r) / 2); in java as p and r are have type int, so the division will use integer arithmetics.
Here is a modified version:
public class Mergesort {
private static int[] A = new int[]{ 4, 2, 1, 8, 6 };
public static void main(String[] args) {
merge_sort(0, A.length);
System.out.println(Arrays.toString(A));
}
public static void merge_sort(int p, int r) {
if (r - p >= 2) {
int q = p + (r - p) / 2;
merge_sort(p, q);
merge_sort(q, r);
merge(p, q, r);
}
}
public static void merge(int p, int q, int r) {
int m = q - p; // zero based index of the right half
int n = r - p; // length of the merged slice
int[] B = new int[n];
int i, j, k;
for (i = p, j = 0; j < m; j++)
B[j] = A[i++];
for (i = r, j = m; j < n; j++)
B[j] = A[--i];
for (i = 0, j = n, k = p; k < r; k++) {
// for stable sorting, i and j must be tested against their boundary
// A[k] = (i < m && (j <= m || B[i] <= B[j - 1])) ? B[i++] : B[--j];
// stability is not an issue for an array of int
A[k] = (B[i] <= B[j - 1]) ? B[i++] : B[--j];
}
}
}
Reversing the second half allows for a simpler merge loop without boundary tests. Note however that there is a simpler approach that uses less memory and might be more efficient:
public static void merge(int p, int q, int r) {
int m = q - p; // length of the left half
int[] B = new int[m];
int i, j, k;
// only save the left half
for (i = p, j = 0; j < m; j++)
B[j] = A[i++];
for (i = 0, j = q, k = p; i < m; k++) {
A[k] = (j >= r || B[i] <= A[j]) ? B[i++] : A[j++];
}
}
I was completing some practice problems today on HackerRank, and on a problem asking me to write an algorithm that left-shifts all elements in an array n-times (i.e. rotLeft({1, 2, 3, 4, 5}, 1) returns {2, 3, 4, 5, 1}), I ran into the classic timeout error due to my algorithm being inefficient. This isn't the first time I have been dinged for inefficient algo writing by an online coding system. Really I have two questions: 1) How can I specifically rewrite my left-shift algorithm to be more time-efficient? 2) In general, how do I improve performance-wise upon an algorithm which runs inefficiently?
public static final int NULL = -2147483648;
static int[] rotLeft(int[] a, int d) {
return rotLeftRec(a, d, 0);
}
static int[] rotLeftRec(int[] a, int d, int numRot) {
if (numRot >= d) {
return a;
} else {
int first = a[0];
int temp1 = NULL;
int temp2 = NULL;
for (int i = a.length - 1; i >= 0; i--) {
if (i == a.length - 1) {
temp1 = a[i];
} else {
if (temp1 == NULL) {
temp1 = a[i];
a[i] = temp2;
temp2 = NULL;
} else {
temp2 = a[i];
a[i] = temp1;
temp1 = NULL;
}
}
}
a[a.length - 1] = first;
}
return rotLeftRec(a, d, numRot + 1);
}
I was curious about the arraycopy approach, looks like this. There's no need to call the method proposed here m times where you shift the array by n each time. You can call it with the product m*n and you're there as well (I think). Would be curious if that one passes.
Btw. ArrayList uses arrayCopy, I got it from there.
static int[] rotLeft(int[] a, int d) {
if (d < 0 || a == null || a.length == 0) {
throw new IllegalArgumentException();
}
int shift = d % a.length;
if (shift == 0) {
return a;
}
int[] result = new int[a.length];
System.arraycopy(a, shift, result, 0, a.length - shift);
System.arraycopy(a, 0, result, a.length - shift, shift);
return result;
}
You recurse for d times shifting once. That is inefficient as you can immediately find the correct value at index i by i+d % a.length.
The point is left-rotating by d would seem to need multiple variables. For this you should use recursion.
static void rotLeft(int[] a, int d) {
rotLeftRec(a, d, 0);
}
static void rotLeftRec(int[] a, int d, int i) {
if (i >= a.length) {
return;
}
int j = (i + d) % a.length;
int aj = a[j];
rotLeftRec(a, d, i + 1);
a[i] = aj;
}
The trick is, that after coming back from the recursion you fill in the data a[i] with the value of a[j] you remembered before the recursive call.
You see, that the recursion enables you to hold a stack of i and aj.
Implementing such recursion is doing the simple step and looking at the time line of variables, what should be remembered beforehand, what future partial result is needed (some return value of a recursive call).
Transformation to iterative version (still further optimizable)
static void rotLeft(int[] a, int d) {
int[] stack = new int[a.length];
for (int i = 0; i < a.length; ++i) {
int j = (i + d) % a.length;
int aj = a[j];
stack[i] = aj;
}
System.arraycopy(stack, 0, a, 0, a.length);
}
I was trying to implement in Java the merge sort algorithm according to Cormen's Introduction to Algorithms. The problem with my code (below) is that the main array is duplicating some of its entries during the merge step.
Is someone able to catch what I'm doing wrong?
Thank you!
static void merge(int a[], int p, int q, int r)
{
int n1 = q - p;
int n2 = (r - q);
int [] left = new int[n1 + 1];
int [] right = new int[n2 + 1];
int pp = p;
int qq = q;
for(int i = 0; i < n1; i++)
{
left[i] = a[++pp];
}
for(int i = 0; i < n2; i++)
{
right[i] = a[++qq];
}
left[left.length-1] = Integer.MAX_VALUE;
right[right.length-1] = Integer.MAX_VALUE;
int i = 0;
int j = 0;
for(int k = p; k < r; k++)
{
if(left[i] <= right[j])
{
a[k] = left[i];
i++;
}
else
{
a[k] = right[j];
j++;
}
}
}
static int [] mergeSort(int a[], int p, int r)
{
if(p < r)
{
int q = (p + r)/2;
mergeSort(a, 1, q);
mergeSort(a, q + 1, r);
merge(a, p, q, r);
}
return a;
}
Part of the issue here is the example from the book apparently uses index range from 1 to length. It will be simpler if you change the index range from 0 to length-1, which I assume in the rest of my answer.
Use post increment while copying to left[] and right[] as answered by laune (since index range 0 to length-1).
left[i] = a[pp++];
...
right[i] = a[qq++];
The main issue is the merge function is not checking to see if it reached the end of the left or right run during a merge. This can be fixed by changing the inner if to:
if (i < n1 && (j >= n2 || left[i] <= right[j]))
The recursive calls to merge sort should be:
mergeSort(a, p, q);
mergeSort(a, q, r);
Not shown, but the initial call to mergeSort should be:
mergeSort(a, 0, a.length);
There's no need to allocate the extra element in left and right (since index range is 0 to length-1).
int [] left = new int[n1];
int [] right = new int[n2];
I think that this is in error (as well as its sibling in the next loop):
left[i] = a[++pp];
You want to copy starting with pp = p, so don't increment before you access the array element:
left[i] = a[pp++];
I have a pseudo code of a Quicksort in my book, that I am following step-by-step. But the end output is not what I want. I have turned it into this code:
public class quickSort {
public int[] quick(int[] A, int p, int r){
int q;
if(p<r){
q = partition(A, p, r);
//First partition comfirmed to "work"
quick(A, p, q-1);
quick(A, q+1, r);
}
return A;
}
public int partition(int[] A, int p, int r){
int x = A[r];
int i = p-1;
int temp;
for(int j=p; j<r-1; j++){
if(A[j]<=x){
i = i+1;
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
temp = A[i+1];
A[i+1] = A[r];
A[r] = temp;
return i+1;
}
}
My book illustrates how the first partition is being handled. With the input:
2 8 7 1 3 5 6 4
.. the first partition "sorts" this into
2 1 3 4 7 5 6 8
.. and this I have comfirmed. And so, if this work, and it is just calling itself with smaller parts to do the exact same thing, why is it in the end giving the output:
2 3 1 4 5 7 8 7
.. and not something that is sorted???
The error is that only elements [p, r-2] are going to be processed in public int partition() instead of [p, r-1]. A[r-1] might happen to be less than pivot, but it is not swapped and stays in its place
Alright so here's your answer tweaked and it works:
import java.util.Arrays;
public class QuickSort
{
public QuickSort()
{
int array[] = { 2, 8, 7, 1, 3, 5, 6, 4 };
quickSort(array, 0, array.length - 1);
System.out.println(Arrays.toString(array));
}
void quickSort(int[] array, int p, int r)
{
if (p < r)
{
int q = partition(array, p, r);
quickSort(array, p, q - 1);
quickSort(array, q + 1, r);
}
}
int partition(int array[], int p, int r)
{
int x = array[r];
int i = p - 1;
for (int j = p; j < r; j++)
{
if (array[j] <= x)
{
i += 1;
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
int temp = array[i + 1];
array[i + 1] = array[r];
array[r] = temp;
return i + 1;
}
public static void main(String[] args)
{
new QuickSort();
}
}
But by now I'm sure you got to know what went wrong. I had to keep my word though and that's why I'm updating my answer here.
it should be for(int j=p; j<=r-1; j++)
public int partition(int[] A, int p, int r){
int x = A[r];
int i = p-1;
int temp;
for(int j=p; j<=r-1; j++){
if(A[j]<=x){
i = i+1;
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
temp = A[i+1];
A[i+1] = A[r];
A[r] = temp;
return i+1;
}
I am working on a quicksort from my data structures and algorithms book. In the book it lists a quicksort method then a hoare partition that it wants you to use with the quick sort. I seem to be having an issue where my hoare partition is using out of bounds numbers on the array. Either it uses 8 or if I try to fix that it goes to -1. Am I converting the books pseudo correctly into java?
Quicksort pseudo code
QuickSort(A, p, r)
if p<r
q = partition(A, p, r);
QuickSort(A, p, q - 1);
QuickSort(A, q, r);
Hoare-Partition Pseudo Code
Hoare-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 < l
exchange A[i] with A[j]
else return j
My code
public class RunSort {
/**
* #param args
*/
public static void main(String[] args) {
int[] sortNumbers = {4,5,6,2,3,7,2,1};
int[] sorted = new int[sortNumbers.length];
sorted = QuickSort(sortNumbers, 1, sortNumbers.length);
System.out.print(sorted);
}
public static int[] QuickSort(int[] A, int p, int r){
if(p < r){
int q = partition(A, p, r);
QuickSort(A, p, q - 1);
QuickSort(A, q, r);
}
return A;
}
public static int partition(int[] A, int p, int r){
int x = A[p];
int i = p - 1;
int j = r + 1;
int temp;
while(true){
while(A[j] <= x && j != 0){
j--;
}
while(A[i] >= x && i != A.length){
i++;
}
if(i < j){
temp = A[i];
A[i] = A[j];
A[j] = temp;
}else{
return j;
}
}
}
}
Hint: repeat {...} until (condition) does not do the same thing as while (condition) {...}.
Depending on the text, pseudocode often uses 1..arrayLength as the index bounds on an array, but in Java, et al., it's 0..arrayLength-1. You'll need to adjust the arguments to the main QuickSort call in main.
(As a nitpick, QuickSort should start with a lowercase letter by convention.)