I was doing some review on sorting algorithms since they don't actually come up that often in real life for me, and this one about drove me mad. I've also not done Java in a while so I thought maybe I had forgotten some language esotarica, but I don't think so.
What I found is that success or failure depends on how I make the recursive call to split the array. So, if I use the return value of the split call as a parameter to the merge call, it works. However, if I call split recursively first, then call merge, it fails. However, it seems to me that they should both work. It seems to have something to do with how the stack unwinds, but I can't quite wrap my head around it.
static Comparable[] mergesort(Comparable[] src) {
if (src.length < 2)
return src;
int middle = src.length / 2;
if (src.length % 2 > 0)
middle++;
Comparable[] left = new Comparable[src.length / 2];
Comparable[] right = new Comparable[middle];
System.arraycopy(src, 0, left, 0, left.length);
System.arraycopy(src, src.length / 2, right, 0, right.length);
// THIS DOESN'T WORK, BUT I DON'T KNOW WHY
// mergesort(left);
// mergesort(right);
// return mergearrays(left, right);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// THIS ONE DOES WORK
//
return mergearrays(mergesort(left), mergesort(right));
}
static Comparable[] mergearrays(Comparable[] left, Comparable[] right) {
Comparable[] retval = new Comparable[left.length + right.length];
int i = 0, j = 0, k = 0;
for (; i < left.length && j < right.length;) {
if (left[i].compareTo(right[j]) >= 0) {
retval[k++] = right[j++];
} else
retval[k++] = left[i++];
}
while (k < retval.length) {
if (i < left.length) {
retval[k++] = left[i++];
}
if (j < right.length) {
retval[k++] = right[j++];
}
}
return retval;
}
Your mergearrays method creates a new array in which it stores the merged sorted array. Therefore if you don't use the arrays returned by mergesort(left) and mergesort(right) (which are themselves arrays created by previous calls to mergearrays), you are discarding the sorted arrays and passing unsorted arrays (left and right) to mergearrays.
Therefore this code is wrong:
mergesort(left); // returns a new sorted array which you ignore, doesn't modify left
mergesort(right); // returns a new sorted array which you ignore, doesn't modify right
return mergearrays(left, right); // merges two unsorted arrays, and therefore is wrong
While this code is right:
// merges the two sorted arrays returned by mergesort(left) and mergesort(right)
return mergearrays(mergesort(left), mergesort(right));
Some merge sort implementations perform the merge step on the original array (i.e. they sort the array in place instead of returning a new sorted array), in which case neither the mergearrays method nor the mergesort need to return anything, and the following can work:
mergesort(left);
mergesort(right);
mergearrays(left, right);
Related
I was going through the below sample program and was trying to understand how the below recursion works, I couldn't understand how the left and the right array elements are sorted, finally merging the two subarrays as below. Any pictorial explanation of the below method would be of great help, as I try to understand the below recursive code.
public static int[] mergeSort(int[] arrayToSort) {
// BASE CASE: arrays with fewer than 2 elements are sorted
if (arrayToSort.length < 2) {
return arrayToSort;
}
// STEP 1: divide the array in half
// we use integer division, so we'll never get a "half index"
int midIndex = arrayToSort.length / 2;
int[] left = Arrays.copyOfRange(arrayToSort, 0, midIndex);
int[] right = Arrays.copyOfRange(arrayToSort, midIndex, arrayToSort.length);
// STEP 2: sort each half
int[] sortedLeft = mergeSort(left);
int[] sortedRight = mergeSort(right);
// STEP 3: merge the sorted halves
int[] sortedArray = new int[arrayToSort.length];
int currentLeftIndex = 0;
int currentRightIndex = 0;
for (int currentSortedIndex = 0; currentSortedIndex < arrayToSort.length;
currentSortedIndex++) {
// sortedLeft's first element comes next
// if it's less than sortedRight's first
// element or if sortedRight is exhausted
if (currentLeftIndex < sortedLeft.length
&& (currentRightIndex >= sortedRight.length
|| sortedLeft[currentLeftIndex] < sortedRight[currentRightIndex])) {
sortedArray[currentSortedIndex] = sortedLeft[currentLeftIndex];
currentLeftIndex++;
} else {
sortedArray[currentSortedIndex] = sortedRight[currentRightIndex];
currentRightIndex++;
}
}
return sortedArray;
}
The sorting is performed in the merging loop:
if the array is very small (0 or 1 element), mergeSort() returns it immediately.
otherwise, it splits the array into 2 subarrays of approximately the same size, left and right, which are sorted by calling the same method recursively:
// STEP 2: sort each half
int[] sortedLeft = mergeSort(left);
int[] sortedRight = mergeSort(right);
the final step iterates over the sorted halves to produce a new sorted array.
The recursive calls complete because they are only performed with sub arrays strictly smaller than the argument array.
I'm trying to implement the merge sort algorithm on a list of strings of size N, and I've managed to get it to sort, but for some reason the original values are being added onto the end of the sorted list.
I'm quite new to implementing sorting algorithms (read: very new), so would really appreciate anyone letting me know if I've missed something.
public static void mergeSortWords(int n, List<String> words) {
if (n < 2) {
return;
}
int mid = n / 2; // Getting the mid-point of the array
List<String> l = new ArrayList<String>(mid); // Left side of array
List<String> r = new ArrayList<String>(n-mid); // Right side of array
for (int i = 0; i < mid; i++) {
l.add(i, words.get(i));
}
for (int j = mid; j < n; j++) {
r.add(j - mid, words.get(j));
}
mergeSortWords(mid, l); // recursively sort the left side
mergeSortWords(n-mid, r); // recursively sort the right side
mergeWords(n, words, l, r, mid, n-mid); // merge the sorted arrays back together
}
public static void mergeWords(int n, List<String> words, List<String> l, List<String> r, int left, int right) {
if (words.size() > n) {
return;
}
int i = 0, j = 0, k = 0;
while (i < left && j < right) {
if (l.get(i).compareToIgnoreCase(r.get(j)) < 0) { // comparing the strings alphabetically
words.add(k++, l.get(i++));
}
else {
words.add(k++, r.get(j++));
}
}
while (i < left) {
words.add(k++, l.get(i++));
}
while (j < right) {
words.add(k++, r.get(j++));
}
}
I unit tested like so:
#Test
public void mergeSortWordsTest() {
List<String> actual = new ArrayList<String>();
List<String> expected = new ArrayList<String>();
actual.add("hello");
actual.add("yo");
actual.add("hi");
actual.add("what");
actual.add("bottle");
expected.add("bottle");
expected.add("hello");
expected.add("hi");
expected.add("what");
expected.add("yo");
mergeSortWords(actual.size(), actual);
Assert.assertEquals(expected, actual);
And I receive:
java.lang.AssertionError:
Expected :[bottle, hello, hi, what, yo]
Actual :[bottle, hello, hi, what, yo, hello, yo, hi, what, bottle]
Thank you for any pointers!
Because the words list you pass to mergeWords is never cleared. mergeWords will just add new elements to this list without caring about the elements that it already contains. Simply do a
words.clear();
at the beginning of mergeWords.
Alternatively, you can overwrite the existing elements with .set(int index, E element) instead of .add(). But you need to make sure that the list is of the correct size.
A few unrelated comments:
In your function calls, you are always passing the size of the lists as an additional parameter (n, left, right). This is redundant (you can get the size with list.size()). Anything that is redundant can easily become inconsistent (i.e., what happens if you pass a wrong size?). So it is better to remove those parameters.
When you add an element to a list, you use the overload add(int index, E element). This is perfectly fine, but I think using the overload add(E element) is much easier to handle as you don't need to keep track of where to add the elements. The overload will just append the new element to the end of the list.
I am working on trying to write a program where a user will enter 6 strings and then it will sort the array in reverse alphabetical order using a recursive method. This is one concept I do not understand despite multiple videos, readings and attempts. Any support and insight is greatly appreciated. Thank you.
import java.util.Arrays;
import java.util.Scanner;
public class SRecusion {
public static void sort2 (String[] sort2) {
int i;
int min = 0;
int max;
for (i = 0; i <sort2.length -1; i++) {
if (sort2[i].charAt(0)> sort2[i=1].charAt(0)) {
sort2[i] = sort2[min];
}
else {
min = (sort2(sort2[i-1]));
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
String [] test = new String[6];
Scanner scnr = new Scanner(System.in);
String userEntry = "";
for(int i = 0; i <= test.length - 1; i++) {
System.out.println("Please enter a word:");
test[i] = scnr.nextLine();
}
sort2(test);
System.out.println("your list is" + Arrays.asList(test));
System.out.println();
}
}
Sorting is a pretty broad topic as there are many different sorting methods (quicksort, merge sort, etc.) However, a pretty basic and simple sorting method is bubble sort. Although it isn't the fastest one, it's pretty easy to understand and code using recursion.
Essentially, bubble sort with iterate through the elements in pairs of 2 and swap the two elements if they're in the wrong order.
For example, let's sort (3, 2, 5, 4, 1) using bubble sort.
(2, 3, 5, 4, 1) First, it'll look at the first two elements swap them if needed. Since 3 is greater than 2, it'll swap them.
(2, 3, 5, 4, 1) Next, it'll look at 3 and 5. Since 3 is less than 5, there is no need to swap
(2, 3, 4, 5, 1) It now looks at 5 and 4 and swaps them.
(2, 3, 4, 1, 5) Finally, it looks at 5 and 1 and swaps them.
Now start from the beginning and repeat the whole process. The sorting ends if exactly 0 swaps are made during an iteration.
If you're still a bit confused, try watching a tutorial on bubble sort or visit this link.
So from what I was asking above as to why you need a recursive sorting algorithm Here it goes I will try to explain how recursive sorting works. It took my some time to figure it out as I am sure it does for most people who first come in contact with it.
public static void Qsort(int[] array, int start, int end)
{
//find the current center of the whole or parital array part I am working on.
int center = (start+end)/2;
///System.out.println("\n This is the center : " + center);
int pivot, i, pivotplace;
i = 0;
pivot = 0;
pivotplace = 0;
//if start = end then we are at a single element. just return to the previous iterative call.
if(start == end)
{
// System.out.println("\n Inside base case return :");
return;
}
//find the pivot value we are using. using a 3 prong selection we are assured to at least get some type of median value and avoid the N^2 worst case.
pivot = getpivot(array[start], array[center], array[end]); //gets median value of start, center and end values in the array.
// System.out.println("\n pivotvalue is : " + pivot);
//find where the current pivot is located and swap it with the last element in the current portion of the array.
if(array[start] == pivot)
{
//System.out.print("\n Inside pivot at start");
swap(array, start, end);
}
else
{
if(array[center] == pivot)
{
//System.out.print("\n Inside pivot at center");
swap(array, center, end);
}
}
//due to iteration the pivot place needs to start at the passed in value of 'start' and not 0.
pivotplace = start;
//due to iteration the loop needs to go from the passed in value of start and not 0 and needs to go
//until it reaches the end value passed in.
for(i = start; i < end; i++)
{
//if the current slot of the array is less than then pivot swap it with the current pivotplace holder
//since the pivotplace keeps getting iterated up be each swap the final place of pivot place
//is where the pivot will actually be swapped back to after the loop cpompletes.
if(array[i] < pivot)
{
//System.out.print("\n Swapping");
swap(array, i, pivotplace);
pivotplace++;
}
}
//loop is finished, swap the pivot into the spot it belongs in.
swap(array, pivotplace, end);
//there are 2 cases for recursive iteration.
//The first is from the start to the slot before the pivot
if(start < pivotplace){Qsort(array, start, pivotplace-1);}
//the second is from the slot after the pivot to the end.
if(pivotplace+1 < end){Qsort(array, pivotplace+1, end);}
}
public static int getpivot(int a, int b, int c)
{
if((a > b) && (a < c))
{
return a;
}
if((b > a) && (b < c))
{
return b;
}
return c;
}
public static void swap(int[] array, int posa, int posb)
{
int temp;
temp = array[posa];
array[posa] = array[posb];
array[posb] = temp;
}
This is a basic Quick Sort or recursive sort I wrote this while in programming classes. You will probably not need to use the getpivot code as you are dealing with a small set of strings, but if you do some research you will see using a possible sample of 3 drastically speeds up the recursion due to balanced work load of the recursion tree.
Sort Array using recursion in kotlin
fun main() {
print(sortArray(arrayListOf(1,3,2,6,8,3)))
}
fun sortArray(arr: MutableList<Int>): MutableList<Int>{
if(arr.size==1) {
return arr
}
val lastValue = arr.last()
arr.removeLast()
sortArray(arr)
insert(arr, lastValue)
return arr
}
fun insert (arr: MutableList<Int>, value: Int): MutableList<Int> {
if(arr.size == 0 || arr.last() < value) {
arr.add(value)
return arr
}
val lastValue = arr.last()
arr.removeLast()
insert(arr, value)
arr.add(lastValue)
return arr
}
I tried to implement an efficient sorting algorithm in Java. For this reason, I also implemented quicksort and use the following code:
public class Sorting {
private static Random prng;
private static Random getPrng() {
if (prng == null) {
prng = new Random();
}
return prng;
}
public static void sort(int[] array) {
sortInternal(array, 0, array.length - 1);
}
public static void sortInternal(int[] array, int start, int end) {
if (end - start < 50) {
insertionSortInternal(array, start, end);
} else {
quickSortInternal(array, start, end);
}
}
private static void insertionSortInternal(int[] array, int start, int end) {
for (int i=start; i<end - 1; ++i) {
for (int ptr=i; ptr>0 && array[ptr - 1] < array[ptr]; ptr--) {
ArrayUtilities.swap(array, ptr, ptr - 1);
}
}
}
private static void quickSortInternal(int[] array, int start, int end) {
int pivotPos = getPrng().nextInt(end - start);
int pivot = array[start + pivotPos];
ArrayUtilities.swap(array, start + pivotPos, end - 1);
int left = start;
int right = end - 2;
while (left < right) {
while (array[left] <= pivot && left < right) {
++left;
}
if (left == right) break;
while (array[right] >= pivot && left < right) {
right--;
}
if (left == right) break;
ArrayUtilities.swap(array, left, right);
}
ArrayUtilities.swap(array, left, end - 1);
sortInternal(array, start, left);
sortInternal(array, left + 1, end);
}
}
ArrayUtilities.swap just swaps the two given elements in the array. From this code, I expect O(n log(n)) runtime behaviour. But, some different lengths of arrays to sort gave the following results:
10000 elements: 32ms
20000 elements: 128ms
30000 elements: 296ms
The test ran 100 times in each case, and then the arithmetic mean of the running times was calculated. But clearly, as opposed to the expected behaviour, the runtime is O(n^2). What's wrong with my algorithm?
In your insertion-sort implementation your array will be sorted in descending order, while in your quick-sort the array is sorted in ascending order. So replace(for descending order):
for (int ptr=i; ptr>0 && array[ptr - 1] < array[ptr]; ptr--)
with
for (int ptr=i; ptr>0 && array[ptr - 1] > array[ptr]; ptr--)
It also seems like your indexing is not correct.
Try to replace:
sortInternal(array, 0, array.length - 1);
with:
sortInternal(array, 0, array.length);
And in the insertions sort first for loop you don't need to do end - 1, i.e. use:
for (int i=start; i<end; ++i)
Finally, add if (start >= end) return; at the beginning of the quick-sort method.
And as #ljeabmreosn mentioned, 50 is a little bit too large, I would have chosen something between 5 and 20.
Hope that helps!
The QuickSort "optimized" with Insertion Sort for arrays with length less than 50 elements seems to be a problem.
Imagine I had an array of size 65, and the pivot happened to be the median of that array. If I ran the array through your code, your code would use Insertion Sort on the two 32 length subarrays to the left and right of the pivot. This would result in ~O(2*(n/2)^2 + n) = ~O(n^2) average case. Using quick sort and implementing a pivot picking strategy for the first pivot, the time average case would be ~O((nlog(n)) + n) = ~O(n(log(n) + 1)) = ~O(n*log(n)). Don't use Insertion Sort as it is only used when the array is almost sorted. If you are using Insertion Sort solely because of the real running time of sorting small arrays might run faster than the standard quick sort algorithm (deep recursion), you can always utilize a non-recursive quick sort algorithm which runs faster than Insertion Sort.
Maybe change the "50" to "20" and observe the results.
I have written a recursive method for a partition sort that sorts the array however when I use an array of more than 10-20 elements the program takes a really long time to complete (On my computer a bubble sort of a 100,000 int array will take about 15-20 seconds but with an array of only 30 ints my partition sort is taking around 45 seconds to be sorted.
Here is the code.
public static int[] partitionSortRecursive(int[] array, int beginning, int end)
{
if (end < beginning)
return array;
int pivot = (array[beginning] + array[end]) / 2;
int firstUnknown = beginning;
int lastS1 = beginning - 1;
int firstS3 = end + 1;
while (firstUnknown < firstS3)
{
if (array[firstUnknown] == pivot)
{
firstUnknown++;
}
else if (array[firstUnknown] > pivot)
{
firstS3--;
int temp = array[firstUnknown];
array[firstUnknown] = array[firstS3];
array[firstS3] = temp;
}
else
{
lastS1++;
int temp = array[firstUnknown];
array[firstUnknown] = array[lastS1];
array[lastS1] = temp;
firstUnknown++;
}
}
partitionSortRecursive(array, 0, lastS1);
partitionSortRecursive(array, firstS3, end);
return array;
}
You do not use the correct pivot element. You calculate the average of the left and right value but you have to take a sample value from the sub array to partition instead.
You may take the rightmost, the center or any other element. So your first line of codes should look like this
int pivot = array[(beginning + end) / 2];
// or
int pivot = array[end];
You could also take any other element (e.g. random)
EDIT: This does not solve the performance issue.
To my understanding, quick sort will divide an array into two sub arrays A and B where all elements in A are smaller than any element in B and then perform the same operation onto the two sub arrays.
So the basic call structure should be like this
void DoSort (array, i, j)
{
pivot = Partition (array, i, j)
DoSort (array, i,pivot)
DoSort (array, pivot + 1, j)
}
Put your implementation is basically
void DoSort (array, i, j)
{
pivot = Partition (array, i, j)
DoSort (array, 0, pivot) // <<<<<< notice the '0' instead of 'i'
DoSort (array, pivot + 1, j)
}
So you always start from the very beginning of the original array which will most likely take a while
Instead of direct recoursive call like this
partitionSortRecursive(array, 0, lastS1);
partitionSortRecursive(array, firstS3, end);
Organize internal stack where you can save index pairs. While the stack is not empty get the next pair from the stack. In the end of function don't call the same function but put in the stack 2 pairs (0, lastS1) and (firstS3, end)