So I implemented "find the kth smallest element in an array" using a modified quicksort algorithm. However, right now it is infinite looping. I'm not quite sure where the error is. Updated: The debugger says the error is on line 14: "return kthSmallestError(arr, start, j-1, k); According to print statements, the (start, j-1, k) values are (3, 3, 0)" Thanks for the help!
class kthSmallestElement {
public static void main(String[] args) {
int[] input = {3, 1, 5, 2, 6, 4, 7};
int result = kthSmallestElement(input, 0, input.length-1, 3);
System.out.println(result);
}
public static int kthSmallestElement(int[] arr, int start, int end, int k) {
int j = partition(arr, start, end);
if (j == k) return arr[j];
if (j < k) {
return kthSmallestElement(arr, j+1, end, k-j-1);
}
else {
return kthSmallestElement(arr, start, j-1, k);
}
}
public static int partition(int[] arr, int left, int right) {
int pivot = arr[left+(right-left)/2];
while (left <= right) {
while (arr[left] < pivot) {
left++;
}
while (arr[right] > pivot) {
right--;
}
if (left <= right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}
return left;
}
}
An obvious bug: left, right, j are absolute indices whie k is relative. All comparisons and arithmetics between j and k should be calibrated by start.
Please try this.
thanks,
--john
public static int kthSmallestElement(int[] arr, int start, int end, int k) {
//int j = partition(arr, start, end);
int j = partition2(arr, start, end);
if (j == k)
return arr[j];
if (j < k) {
// without -1 as original code.
return kthSmallestElement(arr, j + 1, end, k - j );
} else {
return kthSmallestElement(arr, start, j - 1, k);
}
}
public static int partition2(int[] arr, int lo, int hi) {
int left = lo;
int right = hi+1;
int p = arr[lo];
while (true) {
while (arr[++left] < p) {
if (left == hi)
break;
}
while (p < arr[--right]) {
if (right == lo) {
break;
}
}
if (left >= right) {
break;
}
exchange(arr, left, right);
}
exchange(arr, lo, right);
return right;
}
Related
I've the following implementation for quicksort, but trying to transfer it to quickselect fails.
class Solution {
public static void main(String[] args) {
for (int i = 1; i < 5; i++) {
int[] arr = new int[]{9,8,7,1,2,3,6,5,4};
System.out.println(quicksort(arr, i));
}
}
private static int quicksort(int[] arr, int k) {
return quicksort(arr, 0, arr.length - 1, k);
}
private static int quicksort(int[] arr, int low, int high, int k) {
int p = partition(arr, low, high);
/* Doesnt work:
if (p == k) {
return arr[k];
} else if (p < k) {
return quicksort(arr, p+1, high, k);
} else {
return quicksort(arr, low, p-1, k);
}
*/
}
private static int partition(int[] arr, int low, int high) {
int i = low;
int j = high;
int pivot = arr[low + (high - low) / 2];
while (true) {
while (arr[i] < pivot) {
i++;
}
while (arr[j] > pivot) {
j--;
}
if (i < j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
} else {
return j;
}
}
}
}
It feels like the information that everything from 0..p is smaller or equal to arr[p] and vice-versa does not help at all.
Example:
9,8,7,1,2,3,6,5,4, after partition: 2, 1, 7, 8, 9, 3, 6, 5, 4 with p = 1 (choosen pivot was 2). So I know the first and second lowest value is in 0..1 range. But it isn't sorted, so I cannot simply say if (p == k-1) return arr[p], because it isn't guaranteed that arr[p] is the max of that half, just that everything in that half is less than the choosen pivot.
Any idea how I can make it work?
I'm trying the implement the MergeSort algorithm in Java so that it takes it sorts an array from A[start..end]. I'm really struggling to implement it so that it doesn't include the last index passed in, in the merge. I'm trying to trace my code but keep getting confused.
Here is my code:
public class MergeSort {
public static void main(String[] args) {
int[] list = new int[] { 3, 7, 5, 2, 9 };
int[] result = mergeSort(list, 0, list.length);
System.out.print("[");
for (int i = 0; i < result.length; i++) {
System.out.print(" " + result[i]);
}
System.out.println("]");
}
public static int[] mergeSort(int[] list, int start, int end) {
if (end - start < 2) {
return list;
} else {
int mid = (start + end) / 2;
mergeSort(list, start, mid);
mergeSort(list, mid + 1, end);
merge(list, start, mid, end);
return list;
}
}
public static void merge(int[] list, int start, int mid, int end) {
int[] copy = new int[list.length];
for (int i = 0; i < list.length; i++) {
copy[i] = list[i];
}
int i = start;
int k = start;
int j = mid + 1;
while (i <= mid && j <= end) {
if (copy[i] <= copy[j]) {
list[k] = copy[i];
i++;
} else {
list[k] = copy[j];
j++;
}
k++;
}
while (i <= mid) {
list[k] = copy[i];
i++;
k++;
}
while (j < end) {
list[k] = copy[j];
j++;
k++;
}
}
}
Calling mergesort with a slice defined with start included and end excluded is indeed a sensible approach as the calling sequence is simpler: merge(array, 0, array.length) and it allows for empty slices, which is necessary for empty arrays.
Your mergesort method has a bug: the right slice starts at mid and ends before end, hence the call should be mergeSort(list, mid, end);
There are problems in the merge method too:
you should not duplicate the whole list, but just the slice from start to end (excluded). It is simpler if you merge into the temporary array and copy it back after merging. With this approach, you can stop the merge when the left part is exhausted as the remaining values from the right part are already in the proper place.
you should use the < operator instead of <= when comparing the running index values to the upper boundaries that are excluded with this approach.
Here is a corrected version:
public class MergeSort {
public static void main(String[] args) {
int[] list = new int[] { 3, 7, 5, 2, 9 };
int[] result = mergeSort(list, 0, list.length);
System.out.print("[");
for (int i = 0; i < result.length; i++) {
System.out.print(" " + result[i]);
}
System.out.println(" ]");
}
public static int[] mergeSort(int[] list, int start, int end) {
if (end - start < 2) {
return list;
} else {
// compute the mid point:
// the left part spans from start included to mid excluded
// the right part spans from mid included to end excluded
// avoid adding start and end to prevent overflow overflow for very large arrays
int mid = start + (end - start) / 2;
mergeSort(list, start, mid);
mergeSort(list, mid, end);
merge(list, start, mid, end);
return list;
}
}
public static void merge(int[] list, int start, int mid, int end) {
int[] temp = new int[end - start];
int k = 0; // index into the temporary array
int i = start; // index into the left part, stop at mid
int j = mid; // index into the right part, stop at end
// select from left or right slices and store into the temp array
while (i < mid && j < end) {
if (list[i] <= list[j]) {
temp[k++] = list[i++];
} else {
temp[k++] = list[j++];
}
}
// copy the remaining elements from the left part
while (i < mid) {
temp[k++] = list[i++];
}
// copy the sorted elements back to the original list
for (i = 0; i < k; i++) {
list[start + i] = temp[i];
}
}
}
Either call mergeSort(list, 0, list.length - 1);
Or call a helper function let's say F. So for example you would call F(list, 0, list.length); Where the only thing F would do is it would call mergeSort(list, 0, list.length - 1);.
That way you don't even touch mergeSort anymore. You just call F.
Edit:
public class MergeSort {
public static void main(String[] args) {
int[] list = new int[] {3, 7, 5, 2, 9};
int[] result = mergeSort(list, 0, list.length);
System.out.print("[");
for (int i = 0; i < result.length; i++) {
System.out.print(" " + result[i]);
}
System.out.println("]");
}
public static int[] mergeSort(int[] list, int start, int end) {
return F(list, start, end - 1);
}
public static int[] F(int[] list, int start, int end) {
if (end - start < 2) {
return list;
} else {
int mid = (start + end) / 2;
F(list, start, mid);
F(list, mid + 1, end);
merge(list, start, mid, end);
return list;
}
}
public static void merge(int[] list, int start, int mid, int end) {
int[] copy = new int[list.length];
for (int i = 0; i < list.length; i++) {
copy[i] = list[i];
}
int i = start;
int k = start;
int j = mid + 1;
while (i <= mid && j <= end) {
if (copy[i] <= copy[j]) {
list[k] = copy[i];
i++;
} else {
list[k] = copy[j];
j++;
}
k++;
}
while (i <= mid) {
list[k] = copy[i];
i++;
k++;
}
while (j < end) {
list[k] = copy[j];
j++;
k++;
}
}
}
I have a mergesort which needs to switch into insertionSort at a specific number, which is my threshold. But my threshold can only be 1, 2 or 3 because in any other cases my mergesort becomes very slow. I cant seem to get the Code to work together.
Here is my code:
public class InsertionSort {
// I haven't found the right Threshold yet, but it should work with any number between 1-100.
public final static int M = 16;
private void Merge(int arr[], int left, int mid, int right) {
int size1 = mid - left + 1;
int size2 = right - mid;
int LeftArray[] = new int[size1];
int RightArray[] = new int[size2];
for (int i = 0; i < size1; ++i) {
LeftArray[i] = arr[left + i];
}
for (int j = 0; j < size2; ++j) {
RightArray[j] = arr[mid + 1 + j];
}
int i = 0;
int j = 0;
int k = left;
while (i < size1 && j < size2) {
if (LeftArray[i] <= RightArray[j]) {
arr[k] = LeftArray[i];
i++;
} else {
arr[k] = RightArray[j];
j++;
}
k++;
}
while (i < size1) {
arr[k] = LeftArray[i];
i++;
k++;
}
while (j < size2) {
arr[k] = RightArray[j];
j++;
k++;
}
}
public void MergeSort(int arr[], int left, int right, int M) {
if ( left < right ) {
int mid = (left + right) / 2;
MergeSort(arr, left, mid, M);
MergeSort(arr, (mid+1), right, M);
Merge(arr, left, mid, right);
} else if ((right - left + 1) <= M) {
insertion_sort(arr, left, right);
}}
static void printArray(int arr[]) {
int n = arr.length;
for (int i = 0; i < n; ++i)
System.out.print(arr[i] + " ");
System.out.println();
}
static int[] readIntfile(String filename) throws Exception {
// Read file into a byte array, and then combine every group of four bytes to an
// int. (Not
// the standard way, but it works!)
byte[] bytes = Files.readAllBytes(Paths.get(filename));
int[] ints = new int[bytes.length / 4];
for (int i = 0; i < ints.length; i++) {
for (int j = 0; j < 4; j++) {
ints[i] += (bytes[i * 4 + j] & 255) << (3 - j) * 8;
}
}
return ints;
}
public static void insertion_sort(int a[], int left, int right) {
int j;
for (int i = left; i <= right; i++) {
int tmp = a[i];
for (j = i; j > 0 && tmp < a[j - 1]; j--) {
a[j] = a[j - 1];
}
a[j] = tmp;
}
}
public static void main(String args[]) throws Exception {
// I have texfile named this with 1000000 numbers
int arr[] = readIntfile("smallints");
// you can also try with this array for example
// int arr[] = {3, 6, 4, 8, 500, 1, 5, 10, 7, 9, 0, 2, 100, 300, 1000, 20, 13, 17, 55, 93};
InsertionSort insert = new InsertionSort();
long before = System.currentTimeMillis();
insert.MergeSort(arr, 0, arr.length-1, M);
long after = System.currentTimeMillis();
printArray(arr);
System.out.println("\n" + "Done " + ((after - before) / 1000.0 + " sek"));
}
}
public void MergeSort(int arr[], int left, int right, int M) {
if ( left < right ) {
int mid = (left + right) / 2;
MergeSort(arr, left, mid, M);
MergeSort(arr, (mid+1), right, M);
Merge(arr, left, mid, right);
} else if ((right - left + 1) <= M) {
insertion_sort(arr, left, right);
}}
You have a serious problem here. You have a class global variable, M, which is your insertion sort threshold, and you have a parameter, M, which is the length of the subarray you want to sort. The parameter is going to shadow the class global. Basically, your public final static int M = 16; is never seen inside the MergeSort method.
Also, if left is not less than right, then there's nothing to sort. I think you want something like this:
public final static int insertionThreshold = 16;
public void MergeSort(int arr[], int left, int right, int M) {
if ( left < right ) {
if (left - right <= insertionThreshold) {
insertion_sort(arr, left, right);
else {
int mid = (left + right) / 2;
MergeSort(arr, left, mid, M);
MergeSort(arr, (mid+1), right, M);
Merge(arr, left, mid, right);
}
}
}
I am working to implement a Hoare partition into a quicksort. I am trying to completely understand the hoare partition but the book doesnt explain everything. Mainly I am just wondering what the while TRUE part means? The excerpt from the book is below. If I were to put the while part in java what would I use and why?
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
try this code for the algo that you have above :
int HoarePartition (int a[],int p, int r)
{
int x=a[p],i=p-1,j=r+1;
while (true)
{
while (a[j] <= x) j--;
while (a[i] >= x) i++;
if (i < j) swap(a[i],a[j]);
else return j;
}
}
Here's a working translation:
public static void quicksort(int[] ar) {
if (ar == null) return;
quicksort(ar, 0, ar.length-1);
}
private static void quicksort(int[] ar, int lo, int hi) {
if (lo < hi) {
int splitPoint = partition(ar, lo, hi);
quicksort(ar, lo, splitPoint);
quicksort(ar, splitPoint+1, hi);
}
}
private static int partition(int[] ar, int lo, int hi) {
int pivot = ar[lo];
int i = lo-1, j = hi+1;
while(true) {
do { i++; } while(ar[i] < pivot);
do { j--; } while(ar[j] > pivot);
if (i < j) swap(ar, i, j);
else return j;
}
}
private static void swap(int[] ar, int i, int j) {
int tmp = ar[i];
ar[i] = ar[j];
ar[j] = tmp;
}
My code works properly (to my knowledge) up until my input array size (a.length) is around 62,000 at which time I consistently get a StackOverFlowError. I previously had used 2 recursive calls to quicksort (less than, and greater than the pivot q) and then I switched to tail recursion. As you can see, I'm selecting the pivot to be the value at the end of the array. I know this isn't the best way to choose a pivot, but I still shouldn't be seeing StackOverFlowErrors with an array size this small, right? What could be causing this? Thanks in advance! Here's my code:
public static void quicksort(int[] a, int p, int r)
{
int q;
while (p < r)
{
q = partition(a, p, r);
quicksort(a, p, q - 1);
p = q + 1;
}
}
public static int partition(int[] a, int p, int r)
{
int j = p - 1;
int x = a[r];
for (int i = p; i < r; i++)
{
if (a[i] <= x)
{
j++;
swap(a, i, j);
}
}
j++;
swap(a, j, r);
return j;
}
private static void swap(int[] a, int i, int j)
{
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
The worst-case input (sorted order) makes quicksort Θ(n^2). Partition always puts a single element on one side of the partition (Cormen et al.). By randomizing the sort (choosing a random pivot) no particular input elicits its worst-case behavior.
import java.util.Random;
public class Quicksort
{
private static Random rand = new Random();
public static void quicksort(int[] arr, int left, int right)
{
if (left < right)
{
int pivot = randomizedPartition(arr, left, right);
quicksort(arr, left, pivot);
quicksort(arr, pivot + 1, right);
}
}
private static int randomizedPartition(int[] arr, int left, int right)
{
int swapIndex = left + rand.nextInt(right - left) + 1;
swap(arr, left, swapIndex);
return partition(arr, left, right);
}
private static int partition(int[] arr, int left, int right)
{
int pivot = arr[left];
int i = left - 1;
int j = right + 1;
while (true)
{
do
j--;
while (arr[j] > pivot);
do
i++;
while (arr[i] < pivot);
if (i < j)
swap(arr, i, j);
else
return j;
}
}
private static void swap(int[] arr, int i, int j)
{
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// Sort 100k elements that are in reversed sorted order
public static void main(String[] args)
{
int arr[] = new int[100000];
for (int i = 0; i < arr.length; i++)
arr[i] = arr.length - i;
System.out.println("First 20 elements");
System.out.print("Before sort: ");
for (int i = 0; i < 20; i++)
System.out.print(arr[i] + " ");
System.out.println();
quicksort(arr, 0, arr.length - 1);
System.out.print("After sort: ");
for (int i = 0; i < 20; i++)
System.out.print(arr[i] + " ");
System.out.println();
}
}
Given the right input, your implementation will recurse once for every single element of the array. 60,000 recursive calls could easily be enough to overflow the stack in Java in the default configuration.