I implemented a simple quick sort (code below) to count the average and worst comparison made by quicksort. I declared a global variable to hold the counter for comparisons. I placed 3 counters in different positions that I thought would count the comparisons, the problem is the counter sum does not match the theoretical value of the total number of comparisons made by quick sort. I tried to solve this problem for hours but came up short. I really appreciate if you can point me where I should put the counters and why I should place them there. I assumed a counter should go where ever a comparison is made. apparently I'm wrong.
public int[] quickSort(int[] array, int start, int end){
if (start < end){
counter++;//1st comparison here
int pivot;
pivot = Partition(array, start, end);
quickSort(array, start, pivot - 1);
quickSort(array, pivot + 1, end );
}
return array;
}
private int Partition(int[] array, int start, int end) {
int pivot = array[end];
int i = start - 1;
for(int j = start; j <= end - 1; j++){
counter++;//2nd comparison here
if (array[j] <= pivot){
counter++;//3rd comparison here
i = i + 1;
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
int temp = array[i+1];
array[i+1] = array[end];
array[end] = temp;
return i + 1;
}
For the theory, only the comparisons of array elements are counted, not the comparisons of indices to the bounds, so you should only leave the second counter++; (you need to increment the counter independently of the result of the comparison).
Then there is the question against which theoretical values you compare. There are different implementations of quicksort which use slightly different numbers of comparisons. In particular, your choice of the pivot makes no attempt to avoid extreme values, so this implementation will easily degrade to O(n^2) behaviour.
Related
I'm a little confused on quicksort.
For example, with this algorithm taken from programcreek.com using the middle element as the pivot point:
public class QuickSort {
public static void main(String[] args) {
int[] x = { 9, 2, 4, 7, 3, 7, 10 };
System.out.println(Arrays.toString(x));
int low = 0;
int high = x.length - 1;
quickSort(x, low, high);
System.out.println(Arrays.toString(x));
}
public static void quickSort(int[] arr, int low, int high) {
if (arr == null || arr.length == 0)
return;
if (low >= high)
return;
// pick the pivot
int middle = low + (high - low) / 2;
int pivot = arr[middle];
// make left < pivot and right > pivot
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);
}
}
Can someone explain the 2 recursive calls at the bottom, as well as why there is a need to create an i and j variable to copy the left and right markers.
Also, can someone explain the difference between a quicksort algorithm using the middle element vs using the first or last element as the pivot point? The code looks different in a sense that using the last / first element as the pivot point is usually written with a partition method instead of the code above.
Thanks!
Quicksort is based on divide and conquer method, first we take a pivot element and put all elements that are less than this pivot element on the left and all the elements that are greater than this pivot element on the right and after that we recursively perform the same thing on both sides of pivot for left side Quicksort(array,low,pivot-1) for right side Quicksort(array,low,pivot+1)
This was the answer of your first question
and now what is the difference between choosing the middle or first element as pivot so
when we choose first element as pivot after sorting when i becomes greater than j we swap the pivot element(first element) with j so that the element that we chose as pivot comes at the place where all elements less than it comes at the left side and all elements greater than it comes it the right side.
and when we choose the middle element as pivot its already in the middle so there's no need to swap it.
This is a variation of Hoare partition scheme. The "classic" Hoare partition scheme increments i and decrements j before comparing to pivot. Both this example and the questions example include the partition logic in the main function.
void quickSort(int a[], size_t lo, size_t hi)
{
int pivot = a[lo+(hi-lo)/2];
int t;
if(lo >= hi)
return;
size_t i = lo-1;
size_t j = hi+1;
while(1)
{
while (a[++i] < pivot);
while (a[--j] > pivot);
if (i >= j)
break;
t = a[i];
a[i] = a[j];
a[j] = t;
}
QuickSort(a, lo, j);
QuickSort(a, j+1, hi);
}
The questions code increments i and decrements j after comparing to pivot.
The partition logic splits up a partition so that the left side <= pivot, right side >= pivot. The pivot and elements equal to pivot can end up anywhere on either side, and may not end up in their sorted position until a base case of a sub-array of size 1 is reached.
The reason for using the middle element for pivot is that choosing the first or last element for pivot will result in worst case time complexity of O(n^2) if the array is already sorted or reverse sorted. The example in this answer will fail if the last element is used for pivot (but the questions example will not).
The programs should sort a set of a number using a quick sort. Use Data Sets Starting at 10,000,000 randomly generated numbers. I've been learning about quicksort. Another, I finished merge sort. However, the quicksort is syntax. I don't know why it's syntax.
public static void main(String[] args)throws Exception{
for(int n = 1; n<=100; n++)
{// begin for
int size = 10000000; //change this num to change the size of the random array 10,000,000
int[] a = new int[size];
int[] temp = new int[a.length]; //temporary array, it's empty.
//fill the array with random integers
for(int i = 0; i< a.length; i++)
a[i] = (int)(Math.random()*100000 + 1);
long startTime = System.nanoTime();
quickSort(a, temp, 0,(a.length - 1));
long endTime = System.nanoTime();
long duration = endTime - startTime;
System.out.printf("%12.8f %n", (double)duration/100000000);
}// End for
}// end main
public static void quickSort(int[] a, int[] temp, int startIndex, int endIndex)
{// begin quickSort
int pivotIndex; //the index of pivot returned by the quciksort partition
if(startIndex < endIndex)
{//Begin if
pivotIndex = partition(a, temp, startIndex, endIndex);
quickSort(a, temp, startIndex, pivotIndex -1);
quickSort(a, temp, pivotIndex+1, endIndex);
}//End if
}//end quickSort
public static int partition(int[] a, int[] temp, int startIndex, int endIndex) {
int pivotIndex;
int pivot;
int midIndex = startIndex;
pivotIndex = (startIndex + endIndex) / 2;
pivot = a[pivotIndex];
swap(a, temp, pivotIndex, endIndex);
for (int i = startIndex; i < endIndex; i++) {
if (a[i] < pivot) ;
{
swap(a, temp, i, midIndex);
midIndex = midIndex + 1;
}
}
swap(a, temp, midIndex, endIndex);
return midIndex;
}// end partition
public static void swap(int[]a, int[]temp, int first, int second)
{//Begin swap
for(int i = first; i <= second; i++){
temp[i] = a[i];
}
int c;
c = a[first];
a[first] = a[second];
a[second] = c;
}//End swap
public static void writeLines(int[] a, String fileName) throws Exception
{ // Begin writeLines(int[] a, String fileName)
// create a File class object with the given file name
java.io.File out = new java.io.File(fileName);
// Create a PrintWriter output stream and link it to the File object
java.io.PrintWriter outfile = new java.io.PrintWriter(out);
// write the elements of an int array, separated by spaces
for (int i = 0; i < a.length; i++)
{ // Begin for (int i = 0; i < a.length; i++)
outfile.print(a[i] + " ");
} // End for (int i = 0; i < a.length; i++)
// print a newline at the end of the list of integers
outfile.println();
outfile.close();
} // End writeLines(int[] a, String fileName) throws Exception
}//end quickSORT
I got syntax. It said quick.partition and quick.quickSort.
In simple terms, Hoare's quicksort works by:
having an array of data
picking some middle element
logically dividing the array into 3 categories:
everything to the left of the middle element - we'll call this the lower section
the middle element
everything to the right of the middle element - we'll call this the upper section
remembering the value at the middle element
starting from the very left of the lower section looking for a value that is greater than the middle element, or in other words it tries to find something that shouldn't be in the lower section because it is too big
when it finds one it starts from the right hand side of the upper section looking for an element that is "too small for the upper section"
if values are found that meet these criteria, they're swapped over
this happens repeatedly until everything that is too big for the lower section has been transferred into the upper section and everything too small for the upper section has been transferred into the lower section
the result is a "partly sorted" array; everything to the left of the middle element is smaller than it and everything to the right of the middle element is larger than it
the algorithm starts over, this time treating just the lower section as if it was the entire array, then it does the same with the upper section
by taking the whole, then halving it, then quartering it, then... you reach a point where you're just making sure that a bunch of pairs or single elements are sorted, and then that's all done
Your algorithm uses something like a mix of the Hoare and Lomuto partitioning strategies but mostly more like Lomuto. There are various strategies for partitioning (choosing how to divide the array into sections) depending on things like how the data container works (Hoare's strategy requires containers that can be randomly accessed or accessed from each end, like a double linked list, Lomuto only needs a data container to be readable in one direction) but quicksort's basic algorithm is this notion that you split everything into two piles of "want smaller values" and "want larger values", swap things repeatedly between the piles until "smaller" and "larger" really is true, then do the process again first to the smaller pile, then the larger pile. If you keep going and dividing the work then you reach a point where everything is sorted.
If you're still struggling with it, maybe consider instead: If you think of it like sorting a large amount of words that only use the characters A and Z, into alphabetical order you might have a strategy of putting everything into piles: 2 piles of those that start with A and those that start with Z. Then you make 4 piles, those that start AA, AZ, ZA and ZZ. Then you move to 8 piles of three letters AAA, AAZ, AZA, AZZ, ZAA, ZAZ, ZZA and ZZZ (which is probably appropriate if you're asleep by now 🤪). This is also a "divide and conquer strategy"; you'll reach a point where you've divided the piles so much that they each only contain one thing, then if you gather piles up in order (and if you've been careful when you laid them out they will already be in order) then they are sorted
Oh, and your code has an error in that you've added a semicolon after an if, which creates an empty statement (makes the if useless), and you also don't seem to use the temp array you carry around. It's like this algo was hoping to be a blend of two different sort strategies but is unfinished
I'm attempting to perform an analysis on Quicksort but I'm running into an inconsistent StackOverflowError. I believe it's probably due to unbound recursion with higher data sets but I can't figure out how to fix it. My partition might also be off but debugging hasn't worked out too well for me.
The data set I'm using is a set of randomly generated int arrays of size 10,000 using a pivot (arr[0])
Exception in thread "main" java.lang.StackOverflowError
at AlgorithmTiming.quickSort(AlgorithmTiming.java:78)
at AlgorithmTiming.quickSort(AlgorithmTiming.java:82)
private int[] quickSort(int[] arr, int low, int high, int pivotType){
if (low < high)
{
/* pi is partitioning index, arr[pi] is
now at right place */
int pi = partition(arr, low, high, pivotType);
// Recursively sort elements before
// partition and after partition
quickSort(arr, low, pi-1, pivotType);
quickSort(arr, pi+1, high, pivotType);
}
return arr;
}
private int partition(int arr[], int low, int high, int pivotType)
{
Random rand = new Random();
int pivot = arr[0]; //Default Pivot
if(pivotType == 2) pivot = arr[0] + arr[arr.length / 2] + arr[arr.length-1]; //Median Pivot
else if(pivotType == 3) pivot = arr[rand.nextInt(arr.length)]; //Random Pivot
int i = (low-1); // index of smaller element
for (int j=low; j<=high-1; j++)
{
// If current element is smaller than or
// equal to pivot
if (arr[j] <= pivot)
{
i++;
// swap arr[i] and arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// swap arr[i+1] and arr[high] (or pivot)
int temp = arr[i+1];
arr[i+1] = arr[high];
arr[high] = temp;
return i+1;
}
I'm not going to attempt to debug your code. (For a start, you did not provide an MCVE ... or even a stacktrace.)
However, consider this:
Quicksort is a recursive algorithm that works by "dividing" the sorting problem into two smaller sorting problems until the problems are trivial. This is the partitioning step.
If you are sorting N elements and you correctly divide the sort problems in half each time, then you will only need to log2N (or less) divisions to get down to the (trivial) problem of sorting one element.
And then from the Java perspective:
A StackOverflowError means that your recursion has gotten too deep.
So ... without looking at the code ... it is almost a safe bet that the problem is in the way that you have implemented the partitioning. That's where you should focus your attention.
I would suggest the following:
Eyeball the code to see if you can figure out why the partitioning isn't choosing correct partition boundaries; i.e. low, pi, and hi.
Use a debugger to see what the code is doing.
Add traceprints to see what the values of low, pi, and hi are, and how they change as the algorithm progresses.
The other thing to note is that classic quicksort has an edge case where the choice of partition can lead to O(N2) rather than O(NlogN) behavior; see https://en.wikipedia.org/wiki/Quicksort#Worst-case_analysis. This corresponds to the case where sorting N elements requires N-deep recursion. That will lead to StackOverflowError problems in Java, for large enough N.
I'm trying to implement quicksort in Java to learn basic algorithms. I understand how the algo works (and can do it on paper) but am finding it hard to write it in code. I've managed to do step where we put all elements smaller than the pivot to the left, and larger ones to the right (see my code below). However, I can't figure out how to implement the recursion part of the algo, so sort the left and right sides recursively. Any help please?
public void int(A, p, q){
if(A.length == 0){ return; }
int pivot = A[q];
j = 0; k = 0;
for(int i = 0; i < A.length; i++){
if(A[i] <= pivot){
A[j] = A[i]; j++;
}
else{
A[k] = A[i]; k++;
}
}
A[j] = pivot;
}
Big Disclaimer: I did not write this piece of code, so upvotes is not needed. But I link to a tutorial which explains quicksort in detail. Gave me a much needed refreshment on the algorithm as well! The example given has very good comments that might just help you to wrap your head around it.
I suggest you adapt it to your code and write som tests for it to verify it works
Quicksort is a fast, recursive, non-stable sort algorithm which works by the divide and conquer principle. Quicksort will in the best case divide the array into almost two identical parts. It the array contains n elements then the first run will need O(n). Sorting the remaining two sub-arrays takes 2 O(n/2). This ends up in a performance of O(n log n).
In the worst case quicksort selects only one element in each iteration. So it is O(n) + O(n-1) + (On-2).. O(1) which is equal to O(n^2).*
public class Quicksort {
private int[] numbers;
private int number;
public void sort(int[] values) {
// check for empty or null array
if (values ==null || values.length==0){
return;
}
this.numbers = values;
number = values.length;
quicksort(0, number - 1);
}
private void quicksort(int low, int high) {
int i = low, j = high;
// Get the pivot element from the middle of the list
int pivot = numbers[low + (high-low)/2];
// Divide into two lists
while (i <= j) {
// If the current value from the left list is smaller than the pivot
// element then get the next element from the left list
while (numbers[i] < pivot) {
i++;
}
// If the current value from the right list is larger than the pivot
// element then get the next element from the right list
while (numbers[j] > pivot) {
j--;
}
// If we have found a value in the left list which is larger than
// the pivot element and if we have found a value in the right list
// which is smaller than the pivot element then we exchange the
// values.
// As we are done we can increase i and j
if (i <= j) {
exchange(i, j);
i++;
j--;
}
}
// This is the recursion part you had trouble with i guess?
// Recursion
if (low < j)
quicksort(low, j);
if (i < high)
quicksort(i, high);
}
private void exchange(int i, int j) {
int temp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = temp;
}
}
Link to tutorial
I was trying to solve following programming exercise from some java programming book
Write method that partitions the array using the first element, called a pivot. After the partition, the elements in the list are rearranged so that all the elements before the pivot are less than or equal to the pivot and the elements after the pivot are greater than the pivot. The method returns the index where the pivot is located in the new list. For example, suppose the list is {5, 2, 9, 3, 6, 8}. After the partition, the list becomes {3, 2, 5, 9, 6, 8}. Implement the method in a way that takes at most array.length comparisons.
I've implemented solution, but it takes much more than array.length comparisons.
The book itself has solution, but unfortunately it's just plain wrong (not working with some inputs). I've seen the answer to this similar question, and understood "conquer" part of Quicksort algorithm, but in this algorithm values are partitioned using mid-value, but in my case using of 1st array value as a pivot is required.
This is the pivot routine from the linked answer (adapted from source here).
int split(int a[], int lo, int hi) {
// pivot element x starting at lo; better strategies exist
int x=a[lo];
// partition
int i=lo, j=hi;
while (i<=j) {
while (a[i]<x) i++;
while (a[j]>x) j--;
if (i<=j) swap(a[i++], a[j--]);
}
// return new position of pivot
return i;
}
The number of inter-element comparisons in this algorithm is either n or n+1; because in each main loop iteration, i and j move closer together by at exactly c units, where c is the number of comparisons performed in each of the inner while loops. Look at those inner loops - when they return true, i and j move closer by 1 unit. And if they return false, then, at the end of the main loop, i and j will move closer by 2 units because of the swap.
This split() is readable and short, but it also has a very bad worst-case (namely, the pivot ending at either end; follow the first link to see it worked out). This will happen if the array is already sorted either forwards or backwards, which is actually very frequent. That is why other pivot positions are better: if you choose x=a[lo+hi/2], worst-case will be less common. Even better is to do like Java, and spend some time looking for a good pivot to steer clear from the worst case. If you follow the Java link, you will see a much more sophisticated pivot routine that avoids doing extra work when there are many duplicate elements.
It seem that the algorithm (as taken from "Introduction to algorihtm 3rd ed") can be implemented as follows (C++) should be similar in Java`s generics:
template <typename T> void swap_in_place(T* arr, int a, int b)
{
T tmp = arr[a];
arr[a] = arr[b];
arr[b] = tmp;
}
template <typename T> int partition(T* arr, int l, int r)
{
T pivot = arr[r];
int i = l-1;
int j;
for(j=l; j < r; j++) {
if (arr[j] < pivot /* or cmp callback */) {
// preincrement is needed to move the element
swap_in_place<T>(arr, ++i, j);
}
}
// reposition the pivot
swap_in_place(arr, ++i, j);
return i;
}
template <typename T> void qsort(T* arr, int l, int r)
{
if ( l < r ) {
T x = partition<T>(arr, l, r);
qsort(arr, l, x-1);
qsort(arr, x+1, r);
}
}
However, its a simple pseudocode implementation, I dont know if it`s the best pivot to pick from. Maybe (l+r)/2 would be more proper.
Pretty simple solution with deque:
int [] arr = {3, 2, 5, 9, 6, 8};
Deque<Integer> q = new LinkedBlockingDeque<Integer>();
for (int t = 0; t < arr.length; t++) {
if (t == 0) {
q.add(arr[t]);
continue;
}
if (arr[t] <= arr[0])
q.addFirst(arr[t]);
else
q.addLast(arr[t]);
}
for (int t:q) {
System.out.println(t);
}
Output is:
2
3
5 <-- pivot
9
6
8
There is video that I made on Pivot based partition I explained both the methods of patitioning.
https://www.youtube.com/watch?v=356Bffvh1dA
And based on your(the other) approach
https://www.youtube.com/watch?v=Hs29iYlY6Q4
And for the code. This is a code I wrote for pivot being the first element and it takes O(n) Comparisons.
void quicksort(int a[],int l,int n)
{
int j,temp;
if(l+1 < n)
{
int p=l;
j=l+1;
for(int i=l+1;i<n;++i)
{
if(a[i]<a[p])
{
temp=a[i];
a[i]=a[j];
a[j]=temp;
j++;
}
}
temp=a[j-1];
a[j-1]=a[p];
a[p]=temp;
quicksort(a,l,j);
quicksort(a,j,n);
}
}
The partition function below works as follow:
The last variable points to the last element in the array that has not been compared to the pivot element and can be swapped.
If the element directly next to the pivot element is less than the pivot
element. They are swapped.
Else if the pivot element is less than the next element, the nextelement is swapped with the element whose index is the last variable.
static int partition(int[] a){
int pivot = a[0];
int temp, index = 0;
int last = a.length -1;
for(int i = 1; i < a.length; i++){
//If pivot > current element, swap elements
if( a[i] <= pivot){
temp = a[i];
a[i] = pivot;
a[i-1] = temp;
index = i;
}
//If pivot < current elmt, swap current elmt and last > index of pivot
else if( a[i] > pivot && last > i){
temp = a[i];
a[i] = a[last];
a[last] = temp;
last -= 1;
i--;
}
else
break;
}
return index;
}