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.
Related
I'm working on this program that is essentially using the partition algorithm but it's meant to run from the right and not the left. However, I am having problems as it's not running the correct code.
If my array was [64, 17, 7, 3, 33]; when I run partition my output should be [17, 7, 3, 33, 64] but instead I'm getting [33, 17, 7, 3, 64]. Does someone know what the problem is? and How to fix it.
import java.util.Arrays;
public class PartitionPivotOnRight {
public static int partition(int[] a) {
int left = 0;
int right = a.length - 2;
int pivot = a.length - 1;
while (left <= right) {
while (left < a.length && a[left] < a[pivot])
++left;
while (a[right] > a[pivot])
--right;
if (left <= right)
swap(a, left++, right--);
}
swap(a, left, pivot);
return left;
}
public static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void main(String[] args) {
int N = 10;
int[] a = new int[N];
for (int i = 0; i < N; i++)
a[i] = (int) (Math.random() * 100);
System.out.println(Arrays.toString(a));
System.out.println(partition(a));
System.out.println(Arrays.toString(a));
}
}
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'm trying to write a modified merge sorting as an excercise. But i am stuck in why my code doesn't work and i cannot se where is the problem?! any hint or help is appreciate. Thanks in advance.
Here is my code:
public class MergeSort {
public static void mergeSort(int[] list) {
mergeSort(list, 0, list.length - 1);
}
private static void mergeSort(int[] list, int low, int high) {
if (low < high) {
int middle = (high + low) / 2;
mergeSort(list, low, middle);
mergeSort(list, middle + 1, high);
int[] temp = merge(list, low, high);
System.arraycopy(temp, 0, list, low, high - low + 1);
}
}
private static int[] merge(int[] list, int low, int high) {
int low1 = low;
int high1 = high;
int mid = (low + high) / 2;
int end_low = mid;
int start_high = mid + 1;
while ((low <= end_low) && (start_high <= high1)) {
if (list[low] < list[start_high]) {
low++;
} else {
int temp = list[high - low + 1];
for (int k = start_high - 1; k >= low; k--) {
list[k + 1] = list[k];
}
list[low] = temp;
low++;
end_low++;
start_high++;
}
}
return list;
}
public static void main(String[] args) {
int[] list = { 2, 3, 2, 5, 6, 1, -2, 3, 14, 12, -5 };
mergeSort(list);
for (int i = 0; i < list.length; i++)
System.out.print(list[i] + " ");
}
}