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?
Related
I'm trying to implement the popular algorithm for finding all inversions of an array using merge sort but it keeps outputting the wrong answer, it counts far too many inversions - I believe part or all of the sub arrays are being iterated too many times in the recurrence calls? I can't quite put my finger on it - I would appreciate some pointers as to why this might be happening. Please see my implementation in java below:
public class inversionsEfficient {
public int mergeSort(int[] list, int[] temp, int left, int right) {
int count = 0;
int mid = 0;
if(right > left) {
mid = (right+left)/2;
count += mergeSort(list, temp, left, mid);
count += mergeSort(list, temp, mid+1, right);
count += merge(list, temp, left, mid+1, right);
}
return count;
}
public int merge(int[] list, int[] temp, int left, int mid, int right) {
int count = 0;
int i = left;
int j = mid;
int k = left;
while((i<=mid-1) && (j<=right)) {
if(list[i] <= list[j]) {
temp[k] = list[i];
k += 1;
i += 1;
}
else {
temp[k] = list[j];
k += 1;
j += 1;
count += mid-1;
}
}
while(i<=mid-1) {
temp[k] = list[i];
k += 1;
i += 1;
}
while(j<=right) {
temp[k] = list[j];
k += 1;
j += 1;
}
for(i=left;i<=right;i++) {
list[i] = temp[i];
}
return count;
}
public static void main(String[] args) {
int[] myList = {5, 3, 76, 12, 89, 22, 5};
int[] temp = new int[myList.length];
inversionsEfficient inversions = new inversionsEfficient();
System.out.println(inversions.mergeSort(myList, temp, 0, myList.length-1));
}
}
This algorithm is based on this pseudocode from Introduction to Algorithms by Cormen:
[1]: https://i.stack.imgur.com/ea9No.png
Instead of -
count += mid - 1;
try -
count += mid - i;
The whole solution becomes as shown below :-
public class inversionsEfficient {
public int mergeSort(int[] list, int[] temp, int left, int right) {
int count = 0;
int mid = 0;
if (right > left) {
mid = (right + left) / 2;
count += mergeSort(list, temp, left, mid);
count += mergeSort(list, temp, mid + 1, right);
count += merge(list, temp, left, mid + 1, right);
}
return count;
}
public int merge(int[] list, int[] temp, int left, int mid, int right) {
int count = 0;
int i = left;
int j = mid;
int k = left;
while ((i <= mid - 1) && (j <= right)) {
if (list[i] <= list[j]) {
temp[k] = list[i];
k += 1;
i += 1;
} else {
temp[k] = list[j];
k += 1;
j += 1;
count += mid - i; // (mid - i), not (mid - 1)
}
}
while (i <= mid - 1) {
temp[k] = list[i];
k += 1;
i += 1;
}
while (j <= right) {
temp[k] = list[j];
k += 1;
j += 1;
}
for (i = left; i <= right; i++) {
list[i] = temp[i];
}
return count;
}
public static void main(String[] args) {
int[] arr = {5, 3, 76, 12, 89, 22, 5};
int[] temp = new int[arr.length];
inversionsEfficient inversions = new inversionsEfficient();
System.out.println(inversions.mergeSort(arr, temp, 0, arr.length - 1));
}
}
The output generated by the above code for the example array mentioned in the question is 8, which is correct because there are 8 inversions in the array [5, 3, 76, 12, 89, 22, 5] -
1. (5, 3)
2. (76, 12)
3. (76, 22)
4. (76, 5)
5. (12, 5)
6. (89, 22)
7. (89, 5)
8. (22, 5)
Explanation for Code Change
This algorithm counts the number of inversions required as the sum of the number of inversions in the left sub-array + number of inversions in the right sub-array + number of inversions in the merge process.
If list[i] > list[j], then there are (mid – i) inversions, because the left and right subarrays are sorted. This implies that all the remaining elements in left-subarray (list[i+1], list[i+2] … list[mid]) will also be greater than list[j].
For a more detailed explanation, have a look at the GeeksForGeeks article on Counting Inversions.
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 written quick sort code as below. The sort takes middle number as pivot:
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] numbers = {3, 6, 9, 1, 34};
int low = 0;
int high = numbers.length - 1;
quicksort(numbers, low, high);
}
static void quicksort(int[] arr, int low, int high) {
int i = low;
int j = high;
int middle = arr[(low + high) / 2];
while(i < j) {
while(arr[i] < middle ) {
i++;
}
while(arr[j] > middle) {
j--;
}
if( i <= j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
if(low < j) {
quicksort(arr, low, j);
}
if(i < high) {
quicksort(arr, i, high);
}
System.out.println(Arrays.toString(arr));
}
}
However, after running the code i am getting stackoverflow exception.
It says Exception in thread "main" java.lang.StackOverflowError
Exception in thread "main" java.lang.StackOverflowError
at QuickSort.quicksort(QuickSort.java:47)
at QuickSort.quicksort(QuickSort.java:47)
at QuickSort.quicksort(QuickSort.java:47)
at QuickSort.quicksort(QuickSort.java:47)
at QuickSort.quicksort(QuickSort.java:47)
at QuickSort.quicksort(QuickSort.java:47)
at QuickSort.quicksort(QuickSort.java:47)
Please help me find out what might be wrong in running the above code.
The value of i and j needs to be incremented and decremented respectively. Please see below the working code:
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] numbers = {3, 9, 6, 1, 34};
int low = 0;
int high = numbers.length - 1;
quicksort(numbers, low, high);
}
static void quicksort(int[] arr, int low, int high) {
int i = low;
int j = high;
int middle = arr[(low + high) / 2];
while(i <= j) {
while(arr[i] < middle ) {
i++;
}
while(arr[j] > middle) {
j--;
}
if( i <= j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
if(low < j) {
quicksort(arr, low, j);
}
if(i < high) {
quicksort(arr, i, high);
}
System.out.println(Arrays.toString(arr));
}
}
Hope this helps!
You can look at this link for better understanding of quick sort,how its work.
public static void quickSort(int[] arr, int low, int high) {
if (arr == null || arr.length == 0)
return;
if (low >= high)
return;
int middle = low + (high - low) / 2;
int pivot = arr[middle];
int i = low, j = high;
while (i <= j) {
while (arr[i] < pivot) {
i++;
}
while (arr[j] > pivot) {
j--;
}
if (i <= j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
// recursively sort two sub parts
if (low < j)
quickSort(arr, low, j);
if (high > i)
quickSort(arr, i, high);
}
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;
}
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.