I've implemented a QuickSort algorithm along with a Time-Complexity control. It works fine with smaller N but once i get closer to larger N the StackOverflow is inevitable. Im thinking that having the pivot element as the last element might be what's causing this.
My first thought was to simply always use the middle element as the pivot element to avoid this but since the test-program throws an 'unsorted exception', it's not a valid solution.
Any ideas how i can work my way around this?
public class QuickSorter implements IntSorter{
int partition (int a[], int lo, int hi) {
int pivot = a[hi]; // pivot element
int i = (lo - 1);
for (int j = lo; j <= hi - 1; j++) {
if (a[j] < pivot) {
i++;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
int temp = a[i+1];
a[i+1] = a[hi];
a[hi] = temp;
return (i + 1);
}
#Override
public void sort(int[] a) {
int lo = 0;
int hi = a.length-1;
if (lo < hi) {
int p = partition(a, lo, hi);
sort(a, lo, p - 1);
sort(a, p + 1, hi);
}
}
private void sort(int[] a, int lo, int hi) {
if (lo < hi) {
int p = partition(a, lo, hi);
sort(a, lo, p - 1);
sort(a, p + 1, hi);
}
}
}
Testcode:
private static void testSort(IntSorter sorter, int firstN, boolean ordered) {
double t1 = 0;
int N = firstN/2;
while (t1 < 0.7 && N < 10000000) {
N *= 2;
int[] a = create(N, ordered);
t1 = timeit(sorter, a);
System.out.println("T("+N+")="+t1);
ArrayUtil.testOrdered(a);
}
int[] a = create(4*N, ordered);
double t4 = timeit(sorter, a);
ArrayUtil.testOrdered(a);
double t01 = t1 / (N * Math.log(N ));
double t04 = t4 / (4*N * Math.log(4*N));
System.out.println("T("+4*N+")="+t4+" growth per N log N: "+t04/t01);
if (t04/t01 > 1.25) {
System.out.println(sorter.getClass().getName()+".sort appears not to run in O(N log N) time");
System.exit(1);
}
}
public static void testOrdered(int[] a) {
int N = a.length;
for (int i = 1; i < N; i++) {
if (a[i] < a[i-1]) {
throw new SortingException("Not sorted, a["+(i-1)+"] > a["+i+"]");
}
}
}
As Thomas comments, using the middle element as the pivot should work fine. It's a common choice, actually, because it works well with input arrays that happen to be already fully or partially sorted.
As for avoiding stack overflow, a common approach is to only recurse on the shorter part after a partitioning step - this ensures at least a halving of the array being processed at each level, so e.g. a 1,000,000 element array will have a maximum call depth of roughly 20 ( log2(1,000,000) ).
So, instead of
private void sort(int[] a, int lo, int hi) {
if (lo < hi) {
int p = partition(a, lo, hi);
sort(a, lo, p - 1);
sort(a, p + 1, hi);
}
}
You do
private void sort(int[] a, int lo, int hi) {
while (lo < hi) {
int p = partition(a, lo, hi);
// recurse on smaller part, loop on larger part
if (((p - 1) - lo) > (hi - (p + 1))) {
sort(a, p + 1, hi);
hi = p - 1;
}
else {
sort(a, lo, p - 1);
lo = p + 1;
}
}
}
Shuffle the array before sorting with the method below seems to have fixed the issues i had aswell
public void shuffle(int[] a) {
int N = a.length;
Random randomGenerator = new Random();
for (int i = 0; i < N; i++) {
int r = i + randomGenerator.nextInt(N-i); // between i and N-1
int t = a[i]; a[i] = a[r]; a[r] = t;
}
}
Related
I have tried to implement the Quicksort algorithm, but different pivots don't seem to work.
When the pivot element is not the first element, it always ends in a recursive loop resulting in a crash where the variable i falls off of the array and it calls itself again with the full array and nothing changes. I think the error is the comparison comparing toSort[j] with the pivot value, or the first element after being swapped with the other element.
public static void quickSort(int[] toSort, int l, int r){
if(r - l <= 1)return;
counter += r - l - 1;
int p = choosePivot(l, r);
int pivot = toSort[p];
int oldP = toSort[p];
toSort[p] = toSort[l];
toSort[l] = oldP;
int i = l + 1;
for(int j = l + 1; j < r; j++){
if(toSort[j] < pivot){
int swap = toSort[j];
toSort[j] = toSort[i];
toSort[i] = swap;
i++;
}
}
oldP = toSort[i - 1];
toSort[i - 1] = toSort[l];
toSort[l] = oldP;
quickSort(toSort, l, i);
quickSort(toSort, i, r);
}
public static int choosePivot(int m, int n){
return n - 1;
//return m;
}
The problem is that the recursive call to quickSort() is not converging. A slight change in the last two lines will work like magic.
public static void quickSort(int[] toSort, int l, int r){
if(r - l <= 1)return;
counter += r - l - 1;
int p = choosePivot(l, r);
int pivot = toSort[p];
int oldP = toSort[p];
toSort[p] = toSort[l];
toSort[l] = oldP;
int i = l + 1;
for(int j = l + 1; j < r; j++){
if(toSort[j] < pivot){
int swap = toSort[j];
toSort[j] = toSort[i];
toSort[i] = swap;
i++;
}
}
oldP = toSort[i - 1];
toSort[i - 1] = toSort[l];
toSort[l] = oldP;
quickSort(toSort, l, i-1);
quickSort(toSort, i, r);
}
public static int choosePivot(int m, int n){
return n - 1;
//return m;
}
You can check the Ideone link for updated code.
Sorry, beginner here.This is what I have right now:
public class MergeSort
{
public static void main(String[] args)
{
int[] arr = {3, 5, 2, 4, 1};
sort(arr, 0, arr.length - 1);
for(int i = 0; i < arr.length; i++)
{
System.out.print(arr[i] + " ");
}
}
private static void sort(int[] arr, int lo, int hi)
{
if(lo >= hi)
{
return;
}
int mid = (lo + hi)/2;
sort(arr, lo, mid);
sort(arr, mid + 1, hi);
int size = hi - lo + 1;
int[] temp = new int[size]; //new array to merge into
merge(arr, temp, lo, mid + 1, hi);
for(int i = 0; i < size; i++)
{
arr[i + lo] = temp[i];
}
}
private static void merge(int[] arr, int[] temp, int lower, int mid, int upper)
{
int tempIndex = 0;
int leftLo = lower;
int leftHi = mid - 1;
int rightLo = mid;
int rightHi = upper;
while(leftLo <= leftHi && rightLo <= rightHi)
{
if(arr[leftLo] < arr[rightLo])
{
temp[tempIndex] = arr[leftLo];
tempIndex++;
leftLo++;
}
else
{
temp[tempIndex] = arr[rightLo];
tempIndex++;
rightLo++;
}
}
}
}
I know it's the merge function that is not working, because right now it prints out only the smallest element and the rest as 0's. I think it has something to do with needing another while loop to copy the array, but I don't know how to write that, or even the purpose of it, as right now it seems that the array is being merged into the temp array in a correct order. Why is it only printing the first element correctly? Thanks.
In merge, you copy values as long as leftLo and rightLo both haven't reached their limit yet. Typically one of them reaches early. Then you need to copy the remaining values of the other one. You can copy the remaining elements by adding these two loops:
while (leftLo <= leftHi) {
temp[tempIndex] = arr[leftLo];
tempIndex++;
leftLo++;
}
while (rightLo <= rightHi) {
temp[tempIndex] = arr[rightLo];
tempIndex++;
rightLo++;
}
That is, the complete method becomes:
private static void merge(int[] arr, int[] temp, int lower, int mid, int upper) {
int tempIndex = 0;
int leftLo = lower;
int leftHi = mid - 1;
int rightLo = mid;
int rightHi = upper;
while (leftLo <= leftHi && rightLo <= rightHi) {
if (arr[leftLo] < arr[rightLo]) {
temp[tempIndex] = arr[leftLo];
tempIndex++;
leftLo++;
} else {
temp[tempIndex] = arr[rightLo];
tempIndex++;
rightLo++;
}
}
while (leftLo <= leftHi) {
temp[tempIndex] = arr[leftLo];
tempIndex++;
leftLo++;
}
while (rightLo <= rightHi) {
temp[tempIndex] = arr[rightLo];
tempIndex++;
rightLo++;
}
}
I was trying to sort the index of an array using mergesort. The mergesort works perfectly , and the final answer is exactly correct but I am not sure why the indexes do not work out in correct positions. I do not want to sort the array, all I want to do is sort the perm[] array which is the index list.
To avoid confusions, Heres an example:
perm array holds the initial indices of the original array nums[] (i.e. 0 to nums.length - 1)
I want to move the indices in the perm array based on the data in the nums[] array such that the indices represent the sorted order.
For example :
Array -> (-1,9,-5,3,0)
Initial perm -> (0,1,2,3,4)
After sorting perm based on array - > (2,0,4,3,1)
Here's my code:
import java.util.Arrays;
public class IndexSort {
public boolean leq(Comparable u, Comparable v) {
return u.compareTo(v) <= 0;
}
public void merge(Comparable a[], int temp[], int perm[], int lo, int mid, int hi) {
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
temp[k] = perm[k];
}
for (int k = lo; k <= hi; k++) {
if (i > mid) {
perm[k] = temp[j++];
} else if (j > hi) {
perm[k] = temp[i++];
} else if (leq(a[perm[i]], a[perm[j]])) {
perm[k] = temp[i++];
} else {
perm[k] = temp[j++];
}
}
}
public void mergeSort(Comparable a[], int temp[], int perm[], int lo, int hi) {
if (hi <= lo)
return;
int mid = (hi + lo) / 2;
mergeSort(a, temp, perm, lo, mid);
mergeSort(a, temp, perm, mid + 1, hi);
merge(a, temp, perm, lo, mid, hi);
System.out.println(" lo = " + lo + " mid = " + mid + " hi = " + hi);
System.out.println(Arrays.toString(perm));
}
public void indexSort(Comparable nums[], int perm[]) {
int temp[] = new int[nums.length];
Comparable temp2[] = new Comparable[nums.length];
mergeSort(nums, temp, perm, 0, nums.length - 1);
}
public static void main(String[] args) {
IndexSort o1 = new IndexSort();
Comparable nums[] = { 12, -12, 0, 123, -123, 1, 2, 3, 4, -4, -4, -3, -2, 1 };
int perm[] = new int[nums.length];
for (int i = 0; i < perm.length; i++) {
perm[i] = i;
}
System.out.println(Arrays.toString(nums));
System.out.println(Arrays.toString(perm));
o1.indexSort(nums, perm);
System.out.println(Arrays.toString(perm));
}
}
I think this line needs to change from perm to temp:
} else if (leq(a[temp[i]], a[temp[j]])) {
I've been banging my head on the table on this one.
I need to create an n sized array that is optimized for QuickSort Partition. It will be used to demonstrate the growth of QuickSort's best case. I know that for best case, QuickSort must select a pivot that divides the array in half for every recursive call.
I cannot think of a way to create an n-sized optimized array to test. Any help would be greatly appreciated.
Here is the algorithm in Java.
public class QuickSort {
private int length;
private void quickSort(int[] a, int p, int r) {
if (p < r) {
int q = partition(a, p, r);
quickSort(a, p, q - 1);
quickSort(a, q + 1, r);
}
}
private int partition(int[] a, int p, int r) {
int x = a[r];
int i = p - 1;
for (int j = p; j < r; j++) {
if (a[j] <= x) {
i++;
exchange(a, i, j);
}
}
exchange(a, i + 1, r);
return i + 1;
}
public void exchange(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
QuickSort(int[] a) {
if (a == null || a.length == 0) {
return;
}
length = a.length;
quickSort(a, 0, length - 1);
}
}
I know this is an old question, but I had the same question and finally developed a solution. I'm not a Java programmer, so don't blame me for Java code issues, please. I assumed that the quicksort algorithm always takes the first item as a pivot when partitioning.
public class QuickSortBestCase
{
public static void generate(int[] arr, int begin, int end)
{
int count = end - begin;
if(count < 3)
return;
//Find a middle element index
//This will be the pivot element for the part of the array [begin; end)
int middle = begin + (count - 1) / 2;
//Make the left part best-case first: [begin; middle)
generate(arr, begin, middle);
//Swap the pivot and the start element
swap(arr, begin, middle);
//Make the right part best-case, too: (middle; end)
generate(arr, ++middle, end);
}
private static void swap(int[] arr, int i, int j)
{
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
private static void fillArray(int[] arr)
{
for(int i = 0; i != arr.length; ++i)
arr[i] = i + 1;
}
private static void printArray(int[] arr)
{
for(int item : arr)
System.out.print(item + " ");
}
public static void main(String[] args)
{
if(args.length == 0)
return;
int intCount = Integer.parseInt(args[0]);
int[] arr = new int[intCount];
//We basically do what quicksort does in reverse
//1. Fill the array with sorted values from 1 to arr.length
fillArray(arr);
//2. Recursively generate the best-case array for quicksort
generate(arr, 0, arr.length);
printArray(arr);
}
}
This program produces the same output for the array of 15 items, as described here: An example of Best Case Scenario for Quick Sort. And in case someone needs a solution in C++:
template<typename RandomIterator,
typename Compare = std::less<typename RandomIterator::value_type>>
void generate_quicksort_best_case_sorted(RandomIterator begin, RandomIterator end)
{
auto count = std::distance(begin, end);
if (count < 3)
return;
auto middle_index = (count - 1) / 2;
auto middle = begin + middle_index;
//Make the left part best-case first
generate_quicksort_best_case_sorted(begin, middle);
//Swap the pivot and the start element
std::iter_swap(begin, middle);
//Make the right part best-case, too
generate_quicksort_best_case_sorted(++middle, end);
}
template<typename RandomIterator,
typename Compare = std::less<typename RandomIterator::value_type>>
void generate_quicksort_best_case(RandomIterator begin, RandomIterator end)
{
{
auto current = begin;
RandomIterator::value_type value = 1;
while (current != end)
*current++ = value++;
}
generate_quicksort_best_case_sorted(begin, end);
}
I was implementing a merge sort in Algorithms in Java 4th edition.
My basic merge sort works, and I want to improve the algorithm by using insertion sort when the array size is less than 7.
I thought it is obvious an efficient improvement, but actually the original one is faster than the improved one for large data.
Here is my improved merge sort, CUTOFF = 7:
private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
// Copy to aux[]
for (int i = lo; i <= hi; i++) {
aux[i] = a[i];
}
// Merge back to a[]
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (less(aux[i], aux[j])) a[k] = aux[i++];
else a[k] = aux[j++];
}
}
private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
// #1 improvement
// Stop condition for this recursion.
// This time we add a CUTOFF, when the items in array
// is less than 7, we will use insertion sort.
if (hi <= lo + CUTOFF - 1) {
Insertion.sort(a, lo, hi);
return;
}
int mid = lo + (hi - lo) / 2;
sort(a, aux, lo, mid);
sort(a, aux, mid + 1, hi);
if (!less(a[mid+1], a[mid])) return;
merge(a, aux, lo, mid, hi);
}
public static void sort(Comparable[] a) {
Comparable[] aux = new Comparable[a.length];
sort(a, aux, 0, a.length - 1);
}
The insertion sort code:
public static void sort(Comparable[] a, int lo, int hi) {
for (int i = lo; i <= hi; i++) {
for (int j = i; j > 0 && less(a[j], a[j - 1]); j--) {
exch(a, j, j - 1);
}
}
}
I used a SortCompare.java to compare the execute time:
public class SortCompare {
public static double time(String alg, Comparable[] a) {
Stopwatch timer = new Stopwatch();
if (alg.equals("Insertion")) Insertion.sort(a);
if (alg.equals("Selection")) Selection.sort(a);
if (alg.equals("Shell")) Shell.sort(a);
if (alg.equals("Merge")) Merge.sort(a);
if (alg.equals("MergeWithImprovements")) MergeWithImprovements.sort(a);
//if (alg.equals("Quick")) Quick.sort(a);
//if (alg.equals("Heap")) Heap.sort(a);
if (alg.equals("InsertionWithSentinel")) InsertionWithSentinel.sort(a);
return timer.elapsedTime();
}
public static double timeRandomInput(String alg, int N, int T) {
// Use alg to sort T random arrays of length N.
double total = 0.0;
Double[] a = new Double[N];
for (int t = 0; t < T; t++) {
for (int i = 0; i < N; i++) {
a[i] = StdRandom.uniform();
}
total += time(alg, a);
}
return total;
}
public static void main(String[] args) {
String alg1 = args[0];
String alg2 = args[1];
int N = Integer.parseInt(args[2]);
int T = Integer.parseInt(args[3]);
double t1 = timeRandomInput(alg1, N, T); // Total for alg1
double t2 = timeRandomInput(alg2, N, T);
StdOut.printf("For %d random Doubles\n %s is", N, alg1);
StdOut.printf(" %.1f times faster than %s\n", t2/t1, alg2);
}
}
I generated 100 arrays with 10000 elements each. The original merge sort is 30 times faster than the improved one!
You insertion sort function is definitely wrong. Note the j > 0 end condition. You pass in [lo..hi] but your code can iterate j all the way down to 1. I think you want something like:
public static void sort(Comparable[] a, int lo, int hi) {
for (int i = lo + 1; i <= hi; i++) {
for (int j = i; j > lo && less(a[j], a[j - 1]); j--) {
exch(a, j, j - 1);
}
}
}