It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
This is code that I came across implementing the quick sort algorithm. Can you please explain how the recursion works here?
void quickSort(int arr[], int left, int right)
{
int i = left, j = right;
int tmp;
int pivot = arr[(left + right) / 2];
/* partition */
while (i <= j) {
while (arr[i] < pivot)
i++;
while (arr[j] > pivot)
j--;
if (i <= j) {
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
}
}
/* recursion */
if (left < j)
quickSort(arr, left, j);
if (i < right)
quickSort(arr, i, right);
}
And please note, this is not homework.
Not sure what you mean with "explain how the recursion is working". But here you go:
The function you posted takes an array of ints and two indexes. It will not sort the whole array, but only the part of it between the two indexes, ignoring anything that is outside them. This means the same function can sort the whole array if you pass the first and last indexes, or just a sub array if you pass a left value that is not the index of the first element of the array and/or a right value that is not the index of the last element.
The sorting algorithm is the well known quicksort. The as pivot it uses the central element (it could as well have used any other element). It partitions the array into the less than (or equal to) pivot subarray and the greater than (or equal to) pivot subarray, leaving an element equal to the pivot between the two partitions.
Then it recursively calls itself to sort the two partitions, but only does it if it is necessary (hence the ifs before the recursive calls).
The implementation works, but is sub-optimal in many ways, and could be improved.
Here are some possible improvements:
switch to another sorting algorithm if the array is sufficiently short
chose the pivot value as median of three values (generally first, last and mid)
initially move one pivot value out of the array (put it in first or last position and reduce the focus to the rest of the array) then change the tests to pass over values that are equal to the pivot to reduce the number of swaps involving them. You'll put the pivot value back in with a final exchange at the end. This is especially useful if you do not follow suggestion 2 and chose the firs/last element instead of the mid one as in this implementation.
late reply but I just added some prints and it might help whoever comes across this understand the code.
#include<iostream>
using namespace std;
void quickSort(int arr[], int left, int right)
{
int i = left, j = right;
int tmp;
int pivot = arr[abs((left + right) / 2)];
cout<<"pivot is"<<pivot<<endl;
/* partition */
while (i <= j) {
while (arr[i] < pivot)
i++;
while (arr[j] > pivot)
j--;
if (i <= j) {
cout<<"i and j are"<<i<<" "<<j<<"and corresponding array value is"<<arr[i]<<" " <<arr[j]<<endl;
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
cout<<"entering first big while loop"<<endl;
for(int i=0;i<7;i++)
cout<<arr[i]<<" "<<endl ;
}
}
cout<<"recursion"<<endl;
/* recursion */
if (left < j)
quickSort(arr, left, j);
if (i< right)
quickSort(arr, i, right);
}
int main(){
int arr[7]= {2,3,8,7,4,9,1};
for(int i=0;i<7;i++)
cout<<arr[i]<<" " ;
quickSort(arr,0,6);
cout<<endl;
for(int i=0;i<7;i++)
cout<<arr[i]<<" " ;
int wait;
cin>>wait;
return 0;
}
Here is your answer -- in the common case both the recursive calls will be executed because the conditions above them will be true. However, in the corner case you could have the pivot element be the largest (or the smallest) element. In which case you have to make only one recursive call which will basically attempt the process one more time by choosing a different pivot after removing the pivot element from the array.
Related
Can someone please tell what is wrong with this piece of code? This code is to sort a set of elements in an array using merge sort.
#include<iostream>
void merge(int arr[], int left, int mid, int right){
int left_ptr = left;
int right_ptr = mid + 1;
int size = right - left + 1;
int temp[size];
int k = left;
while (left_ptr <= mid && right_ptr <= right)
{
if(arr[left_ptr] <= arr[right_ptr]){
temp[k] = arr[left_ptr];
left_ptr++;
k++;
}
else{
temp[k] = arr[right_ptr];
right_ptr++;
k++;
}
}
while (left_ptr <= mid)
{
temp[k] = arr[left_ptr];
left_ptr++;
k++;
}
while (right_ptr <= right)
{
temp[k] = arr[right_ptr];
right_ptr++;
k++;
}
for (int i = left_ptr; i < k; i++)
{
arr[i] = temp[i];
}
}
void mergeSort(int arr[], int left, int right){
int mid;
if (left < right)
{
mid = (right + left)/2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
int main(){
int arr[] = {45,8,9,7,4,58,2,34,2,58};
std::cout << arr << std::endl;
int size = sizeof(arr)/sizeof(int);
mergeSort(arr, 0, size - 1);
for (int i = 0; i < size; i++)
{
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
I double checked it with many online codes and I see no error... What do you think went wrong? I tried to implement this using an array in-place (something like quick-sort).
Here is a list of things wrong with your code. I'm taking "wrong" broadly here. For each bit of code, my primary criticism is style based not "correctness", where the style aimed for is one that makes correctness easier to spot.
Along the way, one of the style criticisms results in spotting what looks like a bug.
void merge(int arr[], int left, int mid, int right){
You are using int to refer to offsets in an array.
You are using int[] parameters, which is a legacy C syntax for int* arr. Use something like std::span instead.
Going on:
int left_ptr = left;
If your goal is to preserve the original arguments and work on copies, make the original arguments const so someone doesn't have to prove they aren't mutated in the body of the function.
int right_ptr = mid + 1;
You have variables called _ptr that aren't pointers.
int size = right - left + 1;
you appear to not be using half-open intervals. Use and learn to use half-open intervals. They are conventional in C++ and they really do get rid of lots of fence-post correcting code.
int temp[size];
This is not compliant C++. Practically, even on compilers that support this, many C++ implementations have much smaller stacks than the memory of arrays you might want to sort. This then results in your code blowing its stack.
Correctness is more important than performance. Creating dynamically sized objects on the stack leads to programs that engage in undefined behavior or crash on otherwise reasonable inputs.
int k = left;
this variable does not describe what it does.
while (left_ptr <= mid && right_ptr <= right)
while (left_ptr <= mid)
while (right_ptr <= right)
there is a lot of code duplication in these loops.
DRY - don't repeat yourself. Here, if there is a bug in any one of the repeats, if you DRY the bug would be in all uses and easier to spot. There are a lot of ways to DRY here (lambdas, helper functions, slightly more complex branching and one loop); use one of them.
for (int i = left_ptr; i < k; i++)
{
arr[i] = temp[i];
}
looks like a manual std copy? Also looks like it has a bug, because of course manually reimplementing std copy means you did it wrong.
void mergeSort(int arr[], int left, int right){
again, legacy C-style array passing.
int mid;
No need to declare this without initializing it. Move declarations as close as possible to their point of first use, and have them fall out of scope as soon as possible.
if (left < right)
{
mid = (right + left)/2;
make this int mid =.
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
An example of how closed intervals make you have to do annoying fenceposting.
merge(arr, left, mid, right);
mergeSort(arr, 0, size - 1);
another fencepost +/- 1 here.
I see two possible errors in the code:
Declaring int temp[size]; in merge is not valid, as size is not a constant. You will need to allocate the array dynamically.
Secondly, in the last segment of the merge function (the for loop), you initialize i = left_ptr. However, left_ptr is set equal to mid before that. I believe you actually want to initialize i = left.
EDIT: Just noticed: temp does not necessarily have to start at the beginning of arr. What I mean is, that each element of temp is mapped to a specific element of arr, but your code assumes at several plases, that temp[0] is mapped to arr[0], which is not true (temp[0] is actually mapped to arr[right]). There are two ways to fix that.
You can fix the pieces that are based on this assumption. Instead of arr[i] = temp[i], use arr[i + right] = temp[i] in the final for loop, and initialize k as zero.
Second option is to, instead of creating and deleting temp in every single call to merge, create it to be equal in size to arr and hold onto it for the entirety of execution of the algorithm (that could be done by creating it outside the algorithm and passing it to each call to merge or MergeSort) That way, the equal offset assumption would actually be correct.
I've understood how the partitioning part is done in the quicksort algorithm, but I'm having trouble understanding the quicksort recursive function. Could someone please explain to me step by step how it works? I'm pasting here the C++ code.
using namespace std;
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int myArray[], int low, int high) {
int i = (low - 1);
int pivot = myArray[high];
for (int j = low; j <= high - 1; j++) {
if (myArray[j] < pivot) {
i++;
swap(&myArray[i], &myArray[j]);
}
}
swap(&myArray[i + 1], &myArray[high]);
return (i + 1);
}
void quickSort(int myArray[], int low, int high) {
if (low < high) {
int pi = partition(myArray, low, high);
quickSort(myArray, low, pi - 1);
quickSort(myArray, pi + 1, high);
}
}
void cinArray(int myArray[], int n) {
for (int p = 0; p < n; p++) {
cin >> myArray[p];
}
}
void print(int myArray[], int n) {
for (int z = 0; z < n; z++) {
cout << myArray[z] << "\t";
}
}
int main() {
int myArray[10];
int size = sizeof(myArray) / sizeof(myArray[0]);
cout << "Write 10 numbers: ";
cinArray(myArray, size);
quickSort(myArray, 0, size - 1);
print(myArray, size);
return 0;
}
My logic by far (step by step) is the following:
if (low < high) will always be true. The first time, the
low (0) and high (9) values are taken from the int main.
Pi will be equal to the returned value of the partition funcion
(i+1), and I suppose that in order to return that value the function
has to run first.
We call the quicksort function giving new arguments for the function twice. Once for
the values before and once for the values after i+1 from the original partitioning. I want to focus on what happens on the first one, the one that has the values before i+1.
Function starts again, the if statement is true (always is), pi calls the function partition and returns i+1, pi is equal to i+1. And what if at this point the values are still not sorted? I suppose the quicksort function restarts again (It feels like a loop). But since the IF statement will always be true, when will this loop situation stop?
Also, assuming my logic at point 4 is correct, how is the code run the first time? Does it start with the first quickSort(myArray, low, pi - 1); function call and loops until something stops it and then does the same for the second call quickSort(myArray, pi + 1, high);? Or does it partition before i+1 then after i+1 and restarts the function?
I know it's a basic question, but I'm really having a hard time wraping my head around this algorithm.
if (low < high) will always be true.
Not correct. It will be true on the first call, but QuickSort calls itself recursive with progressively smaller intervals between low and high. This if is why the algorithm eventually terminates - you ask about that below.
Pi will be equal to the returned value of the partition funcion (i+1)
Right. pi is short for pivot index, i.e. the location where the chosen pivot ended up after partitioning.
And what if at this point the values are still not sorted?
After partitioning, you know that no value in the left partition is greater than the pivot value and that no value in the right partition is less than the pivot value. That's all you know, and all the algorithm needs to know in order to eventually succeed. Each partition is recursively partitioned until it only has one element in it.
when will this loop situation stop?
See my first point.
The partition function places the pivot element in place and returns the index to the pivot element. The next two calls exclude the element, and in the worst case, one call will be for zero elements, the other for n-1 elements, and continuing with the worst case, the size of what is passed on only decreases by 1 element for each level of recursion, with time complexity O(n^2). The best case is if the pivot ends up in the middle, with an even split on each level of recursion, or near even split for time complexity O(n log(n)).
I have something like this and I want to have first element as pivot.
Why this program is still does not working?
void algSzyb1(int tab[],int l,int p)
{
int x,w,i,j;
i=l; //l is left and p is pivot, //i, j = counter
j=p;
x=tab[l];
do
{
while(tab[i]<x) i++;
while(tab[j]>x) j--;
if(i<=j)
{
w=tab[i];
tab[i]=tab[j];
tab[j]=w;
i++;
j--;
}
}
while(!(i<j));
if(l<j) algSzyb1(tab,l,j);
if(i<p) algSzyb1(tab,i,p);
}
Looking at the code, not really checking what it does, just looking at the individual lines, this one line stands out:
while(!(i<j));
I look at that line, and I think: There is a bug somewhere round here. I haven't actually looked at the code so I don't know what the bug is, but I look at this single line and it looks wrong.
I think you need to decrement j before incrementing i.
while (tab[j]>x ) j--;
while (tab[i]<x && i < j) i++;
Also I have added an extra condition to ensure that i doesn't sweep past j. (Uninitialized memory read).
The pivot is slightly mis-named, as the end result is a sorted element, but this and the wikipedia page : quicksort both move the pivot into the higher partition, and don't guarantee the item in the correct place.
The end condition is when you have swept through the list
while( i < j ); /* not !(i<j) */
At the end of the search, you need to test a smaller set. The code you had created a stack overflow, because it repeatedly tried the same test.
if (l<j) algSzyb1(tab, l, j);
if (j+1<p) algSzyb1(tab, j+1, p);
Full code
void algSzyb1(int tab[], int l, int p)
{
int x, w, i, j;
i = l;
j = p;
x = tab[l]; //wróć tu później :D
do
{
while (tab[j]>x ) j--;
while (tab[i]<x && i < j) i++;
if (i < j)
{
w = tab[i];
tab[i] = tab[j];
tab[j] = w;
i++;
j--;
}
} while ((i<j));
if (l<j) algSzyb1(tab, l, j);
if (j+1<p) algSzyb1(tab, j+1, p);
}
I was solving a problem on spoj. Problem has a simple recursive solution.
Problem: Given an array of numbers of size n, select a set of numbers such that no two elements in the set are consecutive and sum of subset elements will be as close as possible to k, but should not exceed it.
My Recursive Approach
I used a approach similar to knapsack, at dividing the problem such that one includes the current element and other ignores it.
function solve_recursively(n, current, k)
if n < 0
return current
if n == 0
if current + a[n] <= k
return current + a[n]
else
return current
if current + a[n] > k
return recurse(n-1, current, k)
else
return max(recurse(n-1, current, k), recurse(n-2, current+a[n], k))
Later as it is exponential in nature, I used map (in C++) to do memoization to reduce complexity.
My source code:
struct k{
int n;
int curr;
};
bool operator < (const struct k& lhs, const struct k& rhs){
if(lhs.n != rhs.n)
return lhs.n < rhs.n;
return lhs.curr < rhs.curr;
};
int a[1001];
map<struct k,int> dp;
int recurse(int n, int k, int curr){
if(n < 0)
return curr;
struct k key = {n, curr};
if(n == 0)
return curr + a[0] <= k ? curr + a[0] : curr;
else if(dp.count(key))
return dp[key];
else if(curr + a[n] > k){
dp[key] = recurse(n-1, k, curr);
return dp[key];
}
else{
dp[key] = max(recurse(n-1, k, curr), recurse(n-2, k, curr+a[n]));
return dp[key];
}
}
int main(){
int t,n,k;
scanint(t);
while(t--){
scanint(n);
scanint(k);
for(int i = 0; i<n; ++i)
scanint(a[i]);
dp.clear();
printf("Scenario #%d: %d\n",j, recurse(n-1, k, 0));
}
return 0;
}
I checked for given test cases. It cleared them. But I am getting wrong answer on submission.
EDIT: Earlier my output format was wrong, so I was getting Wrong Answer. But, now its showing Time Limit Exceeded. I think bottom-up approach would be helpful, but I am having problem in formulating one. I am approaching it as bottom-up knapsack, but having some difficulties in exact formulation.
To my understanding, you almost have the solution. If the recurrence relation is correct but too inefficient, you just have change the recursion to iteration. Apparently, you already have the array dp which represents the states and their respective values. Basically you should be able to solve fill dp with three nested loops for n, k and curr, which would increase respectively to ensure that each value from dp which is needed has already been calculated. Then you replace the recursive calls to recurse with accesses to dp.
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 11 years ago.
I am trying to implement quicksort in this for just research. But i have no idea how quicksort works im looking at this algorithm but have no idea what or how to implement it right now im using bubble sort but how do i go ahead and implement quicksort?
# choose pivot
swap a[1,rand(1,n)]
# 2-way partition
k = 1
for i = 2:n, if a[i] < a[1], swap a[++k,i]
swap a[1,k]
→ invariant: a[1..k-1] < a[k] <= a[k+1..n]
# recursive sorts
sort a[1..k-1]
sort a[k+1,n]
This is my code below
int main()
{
srand(time(NULL));
int length = 250000;
double arr[length];
for(int i = 0; i<length; ++i) arr[i] = rand();
// mergeSort2(arr, arr+length-1);
for(int i = 0; i < (length-1); i++)
{
for(int j = i+1; j < length; j++)
{
if( arr[i] > arr[j])
{
swap(arr[i], arr[j]);
}
}
}
ofstream ofs("input.txt");
for(int i = 0; i<length; ++i) ofs << i << " " << arr[i] << endl;
}
This animation is really helpful.
The heart of quick sort (Dive and Conquer) is simply the following:
Pick an element as pivot
In practice most of the time we don't care which one. As you will see by proof, picking a pivot randomly (instead of front / end / mid) will minimize the possibility of running into the worst-case.
For test purpose, pick the middle guy is a good choice.
Partitions (l = left, r = right)
The goal is to partition your original array into two sets (virtually!!)
S_left = { x in S - {pivot} : x less than or equal to pivot }
S_right = { x in S - {pivot} : x greater than or equal to pivot }
To ease the notation:
a[l]....a[i-1] are less than or equal to a[i]
a[i+1]...a[r] are greater than or equal to a[i]
and hence, at the end, a[i] (the pivot element) should be in its proper place.
Strategy
There are two common ways to program QS, but in general QS takes three parameters (Array, low, high), where low is the left-end index of the array, and high is the right-end index of the array (could be 2,3, 5,10, not necessarily 0, length-1)
Initially
l = low-1
r = high
advances l when A[l] less than or equal to pivot
decrements r when A[r] is greater than or equal to pivot
swap A[l] and A[r]
repeat this process until l is greater than or equal to r, and swap (pivot, A[l])
Call QuickSort(A, left, l-1)
Call QuickSort(A, l+1, right)
I am giving you most of the implementation.
Just work out with a small example (size 9 seems reasonable to me) on paper. Don't use rand until your implementation is correct.
Try this array:
myArray = 9,6,2,5,11,4,20,1,3
I can write more tomorrow.