I have implemented the following quickselect algorithm to achieve O(n) complexity for median selection (more generally kth smallest number):
static size_t partition(struct point **points_ptr, size_t points_size, size_t pivot_idx)
{
const double pivot_value = points_ptr[pivot_idx]->distance;
/* Move pivot to the end. */
SWAP(points_ptr[pivot_idx], points_ptr[points_size - 1], struct point *);
/* Perform the element moving. */
size_t border_idx = 0;
for (size_t i = 0; i < points_size - 1; ++i) {
if (points_ptr[i]->distance < pivot_value) {
SWAP(points_ptr[border_idx], points_ptr[i], struct point *);
border_idx++;
}
}
/* Move pivot to act as a border element. */
SWAP(points_ptr[border_idx], points_ptr[points_size - 1], struct point *);
return border_idx;
}
static struct point * qselect(struct point **points_ptr, size_t points_size, size_t k)
{
const size_t pivot_idx = partition(points_ptr, points_size, rand() % points_size);
if (k == pivot_idx) { //k lies on the same place as a pivot
return points_ptr[pivot_idx];
} else if (k < pivot_idx) { //k lies on the left of the pivot
//points_ptr remains the same
points_size = pivot_idx;
//k remains the same
} else { //k lies on the right of the pivot
points_ptr += pivot_idx + 1;
points_size -= pivot_idx + 1;
k -= pivot_idx + 1;
}
return qselect(points_ptr, points_size, k);
}
Then I tried to compare it with a glibc's qsort() with O(nlog(n)) and was surprised by its superior performance. Here is the measurement code:
double wtime;
wtime = 0.0;
for (size_t i = 0; i < 1000; ++i) {
qsort(points_ptr, points_size, sizeof (*points_ptr), compar_rand);
wtime -= omp_get_wtime();
qsort(points_ptr, points_size, sizeof (*points_ptr), compar_distance);
wtime += omp_get_wtime();
}
printf("qsort took %f\n", wtime);
wtime = 0.0;
for (size_t i = 0; i < 1000; ++i) {
qsort(points_ptr, points_size, sizeof (*points_ptr), compar_rand);
wtime -= omp_get_wtime();
qselect(points_ptr, points_size, points_size / 2);
wtime += omp_get_wtime();
}
printf("qselect took %f\n", wtime);
with results similar to qsort took 0.280432, qselect took 8.516676 for an array of 10000 elements. Why is quicksort faster than quickselect?
The first obvious answer is: Maybe qsort does not implement quicksort.
It has been some time since i read the standard, but i don't think that there is anything requiring that qsort() performs quicksort.
Second: Existing C standard libraries are often heavily optimized (eg using special assembly instructions where available). Combined with how complex performance characteristics of modern CPUs are, this may very well lead to a O(n log n) - which quicksort is not - algorithm being faster then a O(n) algorithm.
My guess would be that you are messing up the cache - something that valgrind / cachegrind sould be able to tell you.
Thanks for your suggestions guys, problem with my implementation of quickselect was that it exhibits its worst-case complexity O(n^2) for inputs that contain many repeated elements, which was my case. Glibc's qsort() (it uses mergesort by default) does not exhibit O(n^2) here.
I have modified my partition() function to perform a basic 3-way partitioning and the median-of-three which works nicely for quickselect:
/** \breif Quicksort's partition procedure.
*
* In linear time, partition a list into three parts: less than, greater than
* and equals to the pivot, for example input 3 2 7 4 5 1 4 1 will be
* partitioned into 3 2 1 1 | 5 7 | 4 4 4 where 4 is the pivot.
* Modified version of the median-of-three strategy is implemented, it ends with
* a median at the end of an array (this saves us one or two swaps).
*/
static void partition(struct point **points_ptr, size_t points_size,
size_t *less_size, size_t *equal_size)
{
/* Modified median-of-three and pivot selection. */
struct point **first_ptr = points_ptr;
struct point **middle_ptr = points_ptr + (points_size / 2);
struct point **last_ptr = points_ptr + (points_size - 1);
if ((*first_ptr)->distance > (*last_ptr)->distance) {
SWAP(*first_ptr, *last_ptr, struct point *);
}
if ((*first_ptr)->distance > (*middle_ptr)->distance) {
SWAP(*first_ptr, *middle_ptr, struct point *);
}
if ((*last_ptr)->distance > (*middle_ptr)->distance) { //reversed
SWAP(*last_ptr, *middle_ptr, struct point *);
}
const double pivot_value = (*last_ptr)->distance;
/* Element swapping. */
size_t greater_idx = 0;
size_t equal_idx = points_size - 1;
size_t i = 0;
while (i < equal_idx) {
const double elem_value = points_ptr[i]->distance;
if (elem_value < pivot_value) {
SWAP(points_ptr[greater_idx], points_ptr[i], struct point *);
greater_idx++;
i++;
} else if (elem_value == pivot_value) {
equal_idx--;
SWAP(points_ptr[i], points_ptr[equal_idx], struct point *);
} else { //elem_value > pivot_value
i++;
}
}
*less_size = greater_idx;
*equal_size = points_size - equal_idx;
}
/** A selection algorithm to find the kth smallest element in an unordered list.
*/
static struct point * qselect(struct point **points_ptr, size_t points_size,
size_t k)
{
size_t less_size;
size_t equal_size;
partition(points_ptr, points_size, &less_size, &equal_size);
if (k < less_size) { //k lies in the less-than-pivot partition
points_size = less_size;
} else if (k < less_size + equal_size) { //k lies in the equals-to-pivot partition
return points_ptr[points_size - 1];
} else { //k lies in the greater-than-pivot partition
points_ptr += less_size;
points_size -= less_size + equal_size;
k -= less_size + equal_size;
}
return qselect(points_ptr, points_size, k);
}
Results are indeed linear and better than qsort() (I have used the Fisher-Yates shuffle as #IVlad have suggested, so the absolute qsort() times are worse):
array size qsort qselect speedup
1000 0.044678 0.008671 5.152328
5000 0.248413 0.045899 5.412160
10000 0.551095 0.096064 5.736730
20000 1.134857 0.191933 5.912773
30000 2.169177 0.278726 7.782467
Related
For a programming assignment I had to create a radix sort algorithm that worked with floating point numbers in their binary versions. The end goal of the assignment was to sort 100 million floating point numbers in under 2 minutes.
The algorithm itself took me a while to get working but as of now it has been really efficient. I am able to sort the 100 million numbers in about 30 seconds. The important note is that the floats have to be converted to unsigned ints so that bit-wise operations can be applied to them. The code that I used to do that I saw in a video on Quake 3's Fast Inverse Square Root Algorithm. Here is the code that I wrote for the assignment.
void radixsort(float *array, unsigned int length, unsigned int bits) {
double sum = 0;
// Create buckets
float *bucketsA = (float *) malloc(length * sizeof(float));
float *bucketsB = (float *) malloc(length * sizeof(float));
unsigned int aIndex = 0, bIndex = 0;
for (int i = 0; i < bits; i++) {
// Sort array using digit position d as the key.
for (int j = 0; j < length; j++) {
if (i == 0) sum += array[j];
unsigned int conversion = * (unsigned int *) &array[j];
int positionBit = nthBit(conversion, i);
if (positionBit == 0) {
bucketsA[aIndex] = array[j];
aIndex++;
}
else {
bucketsB[bIndex] = array[j];
bIndex++;
}
}
// Combine and move sorted buckets into original array
if(i == bits - 1) {
reverseArray(bucketsB, bIndex);
memcpy(array, bucketsB, (bIndex + 1) * sizeof(float));
memcpy(array + bIndex, bucketsA, (aIndex + 1) * sizeof(float));
}
else {
memcpy(array, bucketsA, (aIndex + 1) * sizeof(float));
memcpy(array + aIndex, bucketsB, (bIndex + 1) * sizeof(float));
}
// Reset the memory of the buckets
memset(&bucketsA[0], 0, sizeof(float) * length);
memset(&bucketsB[0], 0, sizeof(float) * length);
// Reset bucket index
aIndex = bIndex = 0;
}
printf("Total: %f\n", sum);
// Free reserved memory
free(bucketsA);
free(bucketsB);
}
I have two questions relating to the following code:
The first is that when I commented out freeing the reserved memory at the bottom I was expecting a segmentation fault but nothing happened, why is this? I might be a little confused on how memory on the heap and stack works but I thought that if the memory was not freed even though it is local it should cause an error. Freeing the memory actually slowed down the program as well.
The second question is does anyone see any obvious way to make the program run faster? I am already way below the minimum requirement for time and most of my classmates' programs are sorting the 100 million numbers in 1:30+ so I have nothing to worry about but I saw in a post someone managed to get their radix sort to run through 100 million in about 15 seconds. In this implementation I started with LSB so I wonder if starting with MSB will make the program faster but I don't have much time to really test with finals coming up. Thank you for the help.
EDIT: Here is the code for the nthBit and reverseArray
int nthBit(int number, int n) {
return (number >> n) & 1;
}
void reverseArray(float *array, int size) {
for (int i = 0; i < (size / 2); i++) {
float swap = array[size - 1 - i];
array[size - 1 - i] = array[i];
array[i] = swap;
}
}
I found this post How to do iterative quicksort without using stack in c?
but the answer suggested does use a inline stack array! (Only constant amount of extra space is permitted)
The code in the page in reference makes a bold claim:
STACK My implementation does not use the stack to store data...
Yet the function definition has many variables with automatic storage, among them 2 arrays with 1000 entries, which will end up using a fixed but substantial amount of stack space:
// quickSort
//
// This public-domain C implementation by Darel Rex Finley.
//
// * Returns YES if sort was successful, or NO if the nested
// pivots went too deep, in which case your array will have
// been re-ordered, but probably not sorted correctly.
//
// * This function assumes it is called with valid parameters.
//
// * Example calls:
// quickSort(&myArray[0],5); // sorts elements 0, 1, 2, 3, and 4
// quickSort(&myArray[3],5); // sorts elements 3, 4, 5, 6, and 7
bool quickSort(int *arr, int elements) {
#define MAX_LEVELS 1000
int piv, beg[MAX_LEVELS], end[MAX_LEVELS], i=0, L, R ;
beg[0]=0; end[0]=elements;
while (i>=0) {
L=beg[i]; R=end[i]-1;
if (L<R) {
piv=arr[L]; if (i==MAX_LEVELS-1) return NO;
while (L<R) {
while (arr[R]>=piv && L<R) R--; if (L<R) arr[L++]=arr[R];
while (arr[L]<=piv && L<R) L++; if (L<R) arr[R--]=arr[L]; }
arr[L]=piv; beg[i+1]=L+1; end[i+1]=end[i]; end[i++]=L; }
else {
i--; }}
return YES; }
The indentation style is very confusing. Here is a reformatted version:
#define MAX_LEVELS 1000
bool quickSort(int *arr, int elements) {
int piv, beg[MAX_LEVELS], end[MAX_LEVELS], i = 0, L, R;
beg[0] = 0;
end[0] = elements;
while (i >= 0) {
L = beg[i];
R = end[i] - 1;
if (L < R) {
piv = arr[L];
if (i == MAX_LEVELS - 1)
return NO;
while (L < R) {
while (arr[R] >= piv && L < R)
R--;
if (L < R)
arr[L++] = arr[R];
while (arr[L] <= piv && L < R)
L++;
if (L < R)
arr[R--] = arr[L];
}
arr[L] = piv;
beg[i + 1] = L + 1;
end[i + 1] = end[i];
end[i++] = L;
} else {
i--;
}
}
return YES;
}
Note that 1000 is large but not sufficient for pathological cases on moderately large arrays that are already sorted. The function returns NO on such arrays with a size of 1000 only, which is unacceptable.
A much lower value would suffice with an improved version of the algorithm where the larger range is pushed into the array and the loop iterates on the smaller range. This ensures that an array of N entries can handle a set of 2N entries. It still has quadratic time complexity on sorted arrays but at least would sort arrays of all possible sizes.
Here is a modified and instrumented version:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX_LEVELS 64
int quickSort(int *arr, size_t elements) {
size_t beg[MAX_LEVELS], end[MAX_LEVELS], L, R;
int i = 0;
beg[0] = 0;
end[0] = elements;
while (i >= 0) {
L = beg[i];
R = end[i];
if (L + 1 < R--) {
int piv = arr[L];
if (i == MAX_LEVELS - 1)
return -1;
while (L < R) {
while (arr[R] >= piv && L < R)
R--;
if (L < R)
arr[L++] = arr[R];
while (arr[L] <= piv && L < R)
L++;
if (L < R)
arr[R--] = arr[L];
}
arr[L] = piv;
if (L - beg[i] > end[i] - R) {
beg[i + 1] = L + 1;
end[i + 1] = end[i];
end[i++] = L;
} else {
beg[i + 1] = beg[i];
end[i + 1] = L;
beg[i++] = L + 1;
}
} else {
i--;
}
}
return 0;
}
int testsort(int *a, size_t size, const char *desc) {
clock_t t = clock();
size_t i;
if (quickSort(a, size)) {
printf("%s: quickSort failure\n", desc);
return 1;
}
for (i = 1; i < size; i++) {
if (a[i - 1] > a[i]) {
printf("%s: sorting error: a[%zu]=%d > a[%zu]=%d\n",
desc, i - 1, a[i - 1], i, a[i]);
return 2;
}
}
t = clock() - t;
printf("%s: %zu elements sorted in %.3fms\n",
desc, size, t * 1000.0 / CLOCKS_PER_SEC);
return 0;
}
int main(int argc, char *argv[]) {
size_t i, size = argc > 1 ? strtoull(argv[1], NULL, 0) : 1000;
int *a = malloc(sizeof(*a) * size);
if (a != NULL) {
for (i = 0; i < size; i++)
a[i] = rand();
testsort(a, size, "random");
for (i = 0; i < size; i++)
a[i] = i;
testsort(a, size, "sorted");
for (i = 0; i < size; i++)
a[i] = size - i;
testsort(a, size, "reverse sorted");
for (i = 0; i < size; i++)
a[i] = 0;
testsort(a, size, "constant");
free(a);
}
return 0;
}
Output:
random: 100000 elements sorted in 7.379ms
sorted: 100000 elements sorted in 2799.752ms
reverse sorted: 100000 elements sorted in 2768.844ms
constant: 100000 elements sorted in 2786.612ms
Here is a slighlty modified version more resistant to pathological cases:
#define MAX_LEVELS 48
int quickSort(int *arr, size_t elements) {
size_t beg[MAX_LEVELS], end[MAX_LEVELS], L, R;
int i = 0;
beg[0] = 0;
end[0] = elements;
while (i >= 0) {
L = beg[i];
R = end[i];
if (R - L > 1) {
size_t M = L + ((R - L) >> 1);
int piv = arr[M];
arr[M] = arr[L];
if (i == MAX_LEVELS - 1)
return -1;
R--;
while (L < R) {
while (arr[R] >= piv && L < R)
R--;
if (L < R)
arr[L++] = arr[R];
while (arr[L] <= piv && L < R)
L++;
if (L < R)
arr[R--] = arr[L];
}
arr[L] = piv;
M = L + 1;
while (L > beg[i] && arr[L - 1] == piv)
L--;
while (M < end[i] && arr[M] == piv)
M++;
if (L - beg[i] > end[i] - M) {
beg[i + 1] = M;
end[i + 1] = end[i];
end[i++] = L;
} else {
beg[i + 1] = beg[i];
end[i + 1] = L;
beg[i++] = M;
}
} else {
i--;
}
}
return 0;
}
Output:
random: 10000000 elements sorted in 963.973ms
sorted: 10000000 elements sorted in 167.621ms
reverse sorted: 10000000 elements sorted in 167.375ms
constant: 10000000 elements sorted in 9.335ms
As a conclusion:
yes quick sort can be implemented without recursion,
no it cannot be implemented without any local automatic storage,
yes only a constant amount of extra space is necessary, but only because we live is a small world where the maximum size of the array is bounded by available memory. A size of 64 for the local objects handles arrays larger than the size of the Internet, much larger than current 64-bit systems could address.
Apparently, it is possible to implement a non-recursive quicksort with only constant amount of extra space as stated here. This builds upon the Sedgewick's work for non-recursive formulation of quicksort. Instead of preserving the boundary values(low and high) it essentially performs a linear scan to determine these bounds.
Can quicksort be implemented in C without stack and recursion?
Quicksort requires two paths be followed forward from each non-trivial partitioning: a new partitioning of each (sub)partition. Information about the previous partitioning (the bounds of one of the resulting partitions) needs to be carried forward to each new partitioning. The question, then, is where does that information live? In particular, where does the information about one partition live while the program is working on the other?
For a serial algorithm, the answer is that the information is stored on a stack or a queue or a functional equivalent of one of those. Always, because those are our names for data structures that serve the needed purpose. In particular, recursion is a special case, not an alternative. In a recursive quicksort, the data are stored on the call stack. For an iterative implementation you can implement a stack in a formal sense, but it's possible to instead use a simple and relatively small array as a makeshift stack.
But stack and queue equivalents can go a lot farther than that. You could append data to a file, for example, for later read-back. You could write it to a pipe. You could transmit it to yourself asynchronously over a communications network.
If you're clever, you can even make the input array itself serve the need for a stack, by encoding the partition bounds using relative element order or some other element property, as described by ġurian, for example. This involves a space vs speed tradeoff that is probably not a good deal in most cases. However, it has lower space overhead (O(1)) than do typical quicksort implementations (O(log N)), and it does not change the algorithm's O(N log N) asymptotic time complexity.
If you wanted to go crazy, you could even nest iterations in place of recursing. That would impose a hard upper bound on the size of the arrays that could be handled, but not as tight of one as you might think. With some care and a few tricks, you could handle billion-element arrays with a 25-loop nest. Such a deep nest would be ugly and crazy, but nevertheless conceivable. A human could write it by hand. And in that case, the series of nested loop scopes, with their block-scoped variables, serves as a stack equivalent.
So the answer depends on what exactly you mean by "without stack":
yes, you can use a queue instead, though it would need to have about the same capacity as there are elements to sort;
yes, you can use an array or some other kind of sequential data storage, including the input array itself, to emulate a formal stack or queue;
yes, you can encode a suitable stack equivalent directly into the structure of your program;
yes, you can probably come up with other, more esoteric versions of stacks and queues;
but no, you cannot perform a quicksort without something filling the multi-level data-storage role for which a stack or stack-equivalent is conventionally used.
Well, it can, because I implemented a quicksort in fortran IV (it was a long time ago, and before the language supported recursion - and it was for a bet). However you do need somewhere (a large array would do) to remember your state as you do individual bits of work.
It's a lot easier recursively...
Quicksort is by definition a "divide and conquer" searching algorithm, the idea is that you split the given array into smaller partitions. So you are dividing the problem into subproblems, that is easier to solve.
When using Quicksort without recursion you need a struct of some sort to store the partitions you are not using at the time.
That's why the answer of the post uses an array to make quicksort non recursive.
I know how to calculate complexity for Quick Sort and Insertion Sort alone, but when I mix the two...
Here is the code:
#define THRESHOLD 50
// regular insertion sort
void isort(int *array, size_t n) {
int key, i, j;
for(j = 1; j < n; j++){
key = array[j];
i = j - 1;
while (array[i] > key && i>=0) {
array[i + 1] = array[i];
i--;
}
array[i + 1] = key;
}
}
// modified quick sort
static void sort(int *array, int start, int end) {
if (end - start > THRESHOLD) {
int pivot = array[start];
int l = start + 1;
int r = end;
while(l < r) {
if (array[l] < pivot) {
l += 1;
} else if ( array[r] >= pivot ) {
r -= 1;
} else {
swap(&array[l], &array[r]);
}
}
if (array[l] < pivot) {
swap(&array[start], &array[l]);
l -= 1;
} else {
l -= 1;
swap(&array[start], &array[l]);
}
sort(array, start, l);
sort(array, r, end);
} else {
isort(array, end - start + 1);
}
}
To summarize: this is a recursive Quick Sort, except that when the incoming vector is below the threshold, it uses the Insertion Sort.
How do I calculate the time complexity for best, average and worst cases?
The part that is confusing is that when the partition gets of size 50 or less we do an insertion sort. The particular value (50) is not that important so I will switch it to 64 for a bit easier calculations. When doing the recursive formula for the average case of qsort we assume that sorting a partition of size 64 takes log(64) * 64 operations, which apparently is a constant. Note that 64 is a constant and sorting 64 elements with insert will take in the order of O(64 * 64) even in the worst case. That is still a constant. So we will only change the constant of the asymptotic behavior complexity of qsort, but we will not change the function itself.
That being said my point is that performing another algorithm below a fixed threshold may change the constant factor of your algorithm but it will not change its complexity. It will remain the same for all cases you've mentioned.
Approach 1:
C(n,r) = n!/(n-r)!r!
Approach 2:
In the book Combinatorial Algorithms by wilf, i have found this:
C(n,r) can be written as C(n-1,r) + C(n-1,r-1).
e.g.
C(7,4) = C(6,4) + C(6,3)
= C(5,4) + C(5,3) + C(5,3) + C(5,2)
. .
. .
. .
. .
After solving
= C(4,4) + C(4,1) + 3*C(3,3) + 3*C(3,1) + 6*C(2,1) + 6*C(2,2)
As you can see, the final solution doesn't need any multiplication. In every form C(n,r), either n==r or r==1.
Here is the sample code i have implemented:
int foo(int n,int r)
{
if(n==r) return 1;
if(r==1) return n;
return foo(n-1,r) + foo(n-1,r-1);
}
See output here.
In the approach 2, there are overlapping sub-problems where we are calling recursion to solve the same sub-problems again. We can avoid it by using Dynamic Programming.
I want to know which is the better way to calculate C(n,r)?.
Both approaches will save time, but the first one is very prone to integer overflow.
Approach 1:
This approach will generate result in shortest time (in at most n/2 iterations), and the possibility of overflow can be reduced by doing the multiplications carefully:
long long C(int n, int r) {
if(r > n - r) r = n - r; // because C(n, r) == C(n, n - r)
long long ans = 1;
int i;
for(i = 1; i <= r; i++) {
ans *= n - r + i;
ans /= i;
}
return ans;
}
This code will start multiplication of the numerator from the smaller end, and as the product of any k consecutive integers is divisible by k!, there will be no divisibility problem. But the possibility of overflow is still there, another useful trick may be dividing n - r + i and i by their GCD before doing the multiplication and division (and still overflow may occur).
Approach 2:
In this approach, you'll be actually building up the Pascal's Triangle. The dynamic approach is much faster than the recursive one (the first one is O(n^2) while the other is exponential). However, you'll need to use O(n^2) memory too.
# define MAX 100 // assuming we need first 100 rows
long long triangle[MAX + 1][MAX + 1];
void makeTriangle() {
int i, j;
// initialize the first row
triangle[0][0] = 1; // C(0, 0) = 1
for(i = 1; i < MAX; i++) {
triangle[i][0] = 1; // C(i, 0) = 1
for(j = 1; j <= i; j++) {
triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
}
}
}
long long C(int n, int r) {
return triangle[n][r];
}
Then you can look up any C(n, r) in O(1) time.
If you need a particular C(n, r) (i.e. the full triangle is not needed), then the memory consumption can be made O(n) by overwriting the same row of the triangle, top to bottom.
# define MAX 100
long long row[MAX + 1];
int C(int n, int r) {
int i, j;
// initialize by the first row
row[0] = 1; // this is the value of C(0, 0)
for(i = 1; i <= n; i++) {
for(j = i; j > 0; j--) {
// from the recurrence C(n, r) = C(n - 1, r - 1) + C(n - 1, r)
row[j] += row[j - 1];
}
}
return row[r];
}
The inner loop is started from the end to simplify the calculations. If you start it from index 0, you'll need another variable to store the value being overwritten.
I think your recursive approach should work efficiently with DP. But it will start giving problems once the constraints increase. See http://www.spoj.pl/problems/MARBLES/
Here is the function which i use in online judges and coding contests. So it works quite fast.
long combi(int n,int k)
{
long ans=1;
k=k>n-k?n-k:k;
int j=1;
for(;j<=k;j++,n--)
{
if(n%j==0)
{
ans*=n/j;
}else
if(ans%j==0)
{
ans=ans/j*n;
}else
{
ans=(ans*n)/j;
}
}
return ans;
}
It is an efficient implementation for your Approach #1
Your Recursive Approach is fine but using DP with your approach will reduce the overhead of solving subproblems again.Now since we already have two Conditions-
nCr(n,r) = nCr(n-1,r-1) + nCr(n-1,r);
nCr(n,0)=nCr(n,n)=1;
Now we can easily build a DP solution by storing our subresults in a 2-D array-
int dp[max][max];
//Initialise array elements with zero
int nCr(int n, int r)
{
if(n==r) return dp[n][r] = 1; //Base Case
if(r==0) return dp[n][r] = 1; //Base Case
if(r==1) return dp[n][r] = n;
if(dp[n][r]) return dp[n][r]; // Using Subproblem Result
return dp[n][r] = nCr(n-1,r) + nCr(n-1,r-1);
}
Now if you want to further otimise, Getting the prime factorization of the binomial coefficient is probably the most efficient way to calculate it, especially if multiplication is expensive.
The fastest method I know is Vladimir's method. One avoids division all together by decomposing nCr into prime factors. As Vladimir says you can do this pretty efficiently using Eratosthenes sieve.Also,Use Fermat's little theorem to calculate nCr mod MOD(Where MOD is a prime number).
Using dynamic programming you can easily find the nCr here is the solution
package com.practice.competitive.maths;
import java.util.Scanner;
public class NCR1 {
public static void main(String[] args) {
try (Scanner scanner = new Scanner(System.in)) {
int testCase = scanner.nextInt();
while (testCase-- > 0) {
int n = scanner.nextInt();
int r = scanner.nextInt();
int[][] combination = combination();
System.out.println(combination[n][r]%1000000007);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static int[][] combination() {
int combination[][] = new int[1001][1001];
for (int i = 0; i < 1001; i++)
for (int j = 0; j <= i; j++) {
if (j == 0 || j == i)
combination[i][j] = 1;
else
combination[i][j] = combination[i - 1][j - 1] % 1000000007 + combination[i - 1][j] % 1000000007;
}
return combination;
}
}
unsigned long long ans = 1,a=1,b=1;
int k = r,i=0;
if (r > (n-r))
k = n-r;
for (i = n ; k >=1 ; k--,i--)
{
a *= i;
b *= k;
if (a%b == 0)
{
a = (a/b);
b=1;
}
}
ans = a/b;
Given a snipplet of code, how will you determine the complexities in general. I find myself getting very confused with Big O questions. For example, a very simple question:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.println("*");
}
}
The TA explained this with something like combinations. Like this is n choose 2 = (n(n-1))/2 = n^2 + 0.5, then remove the constant so it becomes n^2. I can put int test values and try but how does this combination thing come in?
What if theres an if statement? How is the complexity determined?
for (int i = 0; i < n; i++) {
if (i % 2 ==0) {
for (int j = i; j < n; j++) { ... }
} else {
for (int j = 0; j < i; j++) { ... }
}
}
Then what about recursion ...
int fib(int a, int b, int n) {
if (n == 3) {
return a + b;
} else {
return fib(b, a+b, n-1);
}
}
In general, there is no way to determine the complexity of a given function
Warning! Wall of text incoming!
1. There are very simple algorithms that no one knows whether they even halt or not.
There is no algorithm that can decide whether a given program halts or not, if given a certain input. Calculating the computational complexity is an even harder problem since not only do we need to prove that the algorithm halts but we need to prove how fast it does so.
//The Collatz conjecture states that the sequence generated by the following
// algorithm always reaches 1, for any initial positive integer. It has been
// an open problem for 70+ years now.
function col(n){
if (n == 1){
return 0;
}else if (n % 2 == 0){ //even
return 1 + col(n/2);
}else{ //odd
return 1 + col(3*n + 1);
}
}
2. Some algorithms have weird and off-beat complexities
A general "complexity determining scheme" would easily get too complicated because of these guys
//The Ackermann function. One of the first examples of a non-primitive-recursive algorithm.
function ack(m, n){
if(m == 0){
return n + 1;
}else if( n == 0 ){
return ack(m-1, 1);
}else{
return ack(m-1, ack(m, n-1));
}
}
function f(n){ return ack(n, n); }
//f(1) = 3
//f(2) = 7
//f(3) = 61
//f(4) takes longer then your wildest dreams to terminate.
3. Some functions are very simple but will confuse lots of kinds of static analysis attempts
//Mc'Carthy's 91 function. Try guessing what it does without
// running it or reading the Wikipedia page ;)
function f91(n){
if(n > 100){
return n - 10;
}else{
return f91(f91(n + 11));
}
}
That said, we still need a way to find the complexity of stuff, right? For loops are a simple and common pattern. Take your initial example:
for(i=0; i<N; i++){
for(j=0; j<i; j++){
print something
}
}
Since each print something is O(1), the time complexity of the algorithm will be determined by how many times we run that line. Well, as your TA mentioned, we do this by looking at the combinations in this case. The inner loop will run (N + (N-1) + ... + 1) times, for a total of (N+1)*N/2.
Since we disregard constants we get O(N2).
Now for the more tricky cases we can get more mathematical. Try to create a function whose value represents how long the algorithm takes to run, given the size N of the input. Often we can construct a recursive version of this function directly from the algorithm itself and so calculating the complexity becomes the problem of putting bounds on that function. We call this function a recurrence
For example:
function fib_like(n){
if(n <= 1){
return 17;
}else{
return 42 + fib_like(n-1) + fib_like(n-2);
}
}
it is easy to see that the running time, in terms of N, will be given by
T(N) = 1 if (N <= 1)
T(N) = T(N-1) + T(N-2) otherwise
Well, T(N) is just the good-old Fibonacci function. We can use induction to put some bounds on that.
For, example, Lets prove, by induction, that T(N) <= 2^n for all N (ie, T(N) is O(2^n))
base case: n = 0 or n = 1
T(0) = 1 <= 1 = 2^0
T(1) = 1 <= 2 = 2^1
inductive case (n > 1):
T(N) = T(n-1) + T(n-2)
aplying the inductive hypothesis in T(n-1) and T(n-2)...
T(N) <= 2^(n-1) + 2^(n-2)
so..
T(N) <= 2^(n-1) + 2^(n-1)
<= 2^n
(we can try doing something similar to prove the lower bound too)
In most cases, having a good guess on the final runtime of the function will allow you to easily solve recurrence problems with an induction proof. Of course, this requires you to be able to guess first - only lots of practice can help you here.
And as f final note, I would like to point out about the Master theorem, the only rule for more difficult recurrence problems I can think of now that is commonly used. Use it when you have to deal with a tricky divide and conquer algorithm.
Also, in your "if case" example, I would solve that by cheating and splitting it into two separate loops that don; t have an if inside.
for (int i = 0; i < n; i++) {
if (i % 2 ==0) {
for (int j = i; j < n; j++) { ... }
} else {
for (int j = 0; j < i; j++) { ... }
}
}
Has the same runtime as
for (int i = 0; i < n; i += 2) {
for (int j = i; j < n; j++) { ... }
}
for (int i = 1; i < n; i+=2) {
for (int j = 0; j < i; j++) { ... }
}
And each of the two parts can be easily seen to be O(N^2) for a total that is also O(N^2).
Note that I used a good trick trick to get rid of the "if" here. There is no general rule for doing so, as shown by the Collatz algorithm example
In general, deciding algorithm complexity is theoretically impossible.
However, one cool and code-centric method for doing it is to actually just think in terms of programs directly. Take your example:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.println("*");
}
}
Now we want to analyze its complexity, so let's add a simple counter that counts the number of executions of the inner line:
int counter = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.println("*");
counter++;
}
}
Because the System.out.println line doesn't really matter, let's remove it:
int counter = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
counter++;
}
}
Now that we have only the counter left, we can obviously simplify the inner loop out:
int counter = 0;
for (int i = 0; i < n; i++) {
counter += n;
}
... because we know that the increment is run exactly n times. And now we see that counter is incremented by n exactly n times, so we simplify this to:
int counter = 0;
counter += n * n;
And we emerged with the (correct) O(n2) complexity :) It's there in the code :)
Let's look how this works for a recursive Fibonacci calculator:
int fib(int n) {
if (n < 2) return 1;
return fib(n - 1) + fib(n - 2);
}
Change the routine so that it returns the number of iterations spent inside it instead of the actual Fibonacci numbers:
int fib_count(int n) {
if (n < 2) return 1;
return fib_count(n - 1) + fib_count(n - 2);
}
It's still Fibonacci! :) So we know now that the recursive Fibonacci calculator is of complexity O(F(n)) where F is the Fibonacci number itself.
Ok, let's look at something more interesting, say simple (and inefficient) mergesort:
void mergesort(Array a, int from, int to) {
if (from >= to - 1) return;
int m = (from + to) / 2;
/* Recursively sort halves */
mergesort(a, from, m);
mergesort(m, m, to);
/* Then merge */
Array b = new Array(to - from);
int i = from;
int j = m;
int ptr = 0;
while (i < m || j < to) {
if (i == m || a[j] < a[i]) {
b[ptr] = a[j++];
} else {
b[ptr] = a[i++];
}
ptr++;
}
for (i = from; i < to; i++)
a[i] = b[i - from];
}
Because we are not interested in the actual result but the complexity, we change the routine so that it actually returns the number of units of work carried out:
int mergesort(Array a, int from, int to) {
if (from >= to - 1) return 1;
int m = (from + to) / 2;
/* Recursively sort halves */
int count = 0;
count += mergesort(a, from, m);
count += mergesort(m, m, to);
/* Then merge */
Array b = new Array(to - from);
int i = from;
int j = m;
int ptr = 0;
while (i < m || j < to) {
if (i == m || a[j] < a[i]) {
b[ptr] = a[j++];
} else {
b[ptr] = a[i++];
}
ptr++;
count++;
}
for (i = from; i < to; i++) {
count++;
a[i] = b[i - from];
}
return count;
}
Then we remove those lines that do not actually impact the counts and simplify:
int mergesort(Array a, int from, int to) {
if (from >= to - 1) return 1;
int m = (from + to) / 2;
/* Recursively sort halves */
int count = 0;
count += mergesort(a, from, m);
count += mergesort(m, m, to);
/* Then merge */
count += to - from;
/* Copy the array */
count += to - from;
return count;
}
Still simplifying a bit:
int mergesort(Array a, int from, int to) {
if (from >= to - 1) return 1;
int m = (from + to) / 2;
int count = 0;
count += mergesort(a, from, m);
count += mergesort(m, m, to);
count += (to - from) * 2;
return count;
}
We can now actually dispense with the array:
int mergesort(int from, int to) {
if (from >= to - 1) return 1;
int m = (from + to) / 2;
int count = 0;
count += mergesort(from, m);
count += mergesort(m, to);
count += (to - from) * 2;
return count;
}
We can now see that actually the absolute values of from and to do not matter any more, but only their distance, so we modify this to:
int mergesort(int d) {
if (d <= 1) return 1;
int count = 0;
count += mergesort(d / 2);
count += mergesort(d / 2);
count += d * 2;
return count;
}
And then we get to:
int mergesort(int d) {
if (d <= 1) return 1;
return 2 * mergesort(d / 2) + d * 2;
}
Here obviously d on the first call is the size of the array to be sorted, so you have the recurrence for the complexity M(x) (this is in plain sight on the second line :)
M(x) = 2(M(x/2) + x)
and this you need to solve in order to get to a closed form solution. This you do easiest by guessing the solution M(x) = x log x, and verify for the right side:
2 (x/2 log x/2 + x)
= x log x/2 + 2x
= x (log x - log 2 + 2)
= x (log x - C)
and verify it is asymptotically equivalent to the left side:
x log x - Cx
------------ = 1 - [Cx / (x log x)] = 1 - [C / log x] --> 1 - 0 = 1.
x log x
Even though this is an over generalization, I like to think of Big-O in terms of lists, where the length of the list is N items.
Thus, if you have a for-loop that iterates over everything in the list, it is O(N). In your code, you have one line that (in isolation all by itself) is 0(N).
for (int i = 0; i < n; i++) {
If you have a for loop nested inside another for loop, and you perform an operation on each item in the list that requires you to look at every item in the list, then you are doing an operation N times for each of N items, thus O(N^2). In your example above you do in fact, have another for loop nested inside your for loop. So you can think about it as if each for loop is 0(N), and then because they are nested, multiply them together for a total value of 0(N^2).
Conversely, if you are just doing a quick operation on a single item then that would be O(1). There is no 'list of length n' to go over, just a single one time operation.To put this in context, in your example above, the operation:
if (i % 2 ==0)
is 0(1). What is important isn't the 'if', but the fact that checking to see if a single item is equal to another item is a quick operation on a single item. Like before, the if statement is nested inside your external for loop. However, because it is 0(1), then you are multiplying everything by '1', and so there is no 'noticeable' affect in your final calculation for the run time of the entire function.
For logs, and dealing with more complex situations (like this business of counting up to j or i, and not just n again), I would point you towards a more elegant explanation here.
I like to use two things for Big-O notation: standard Big-O, which is worst case scenario, and average Big-O, which is what normally ends up happening. It also helps me to remember that Big-O notation is trying to approximate run-time as a function of N, the number of inputs.
The TA explained this with something like combinations. Like this is n choose 2 = (n(n-1))/2 = n^2 + 0.5, then remove the constant so it becomes n^2. I can put int test values and try but how does this combination thing come in?
As I said, normal big-O is worst case scenario. You can try to count the number of times that each line gets executed, but it is simpler to just look at the first example and say that there are two loops over the length of n, one embedded in the other, so it is n * n. If they were one after another, it'd be n + n, equaling 2n. Since its an approximation, you just say n or linear.
What if theres an if statement? How is the complexity determined?
This is where for me having average case and best case helps a lot for organizing my thoughts. In worst case, you ignore the if and say n^2. In average case, for your example, you have a loop over n, with another loop over part of n that happens half of the time. This gives you n * n/x/2 (the x is whatever fraction of n gets looped over in your embedded loops. This gives you n^2/(2x), so you'd get n^2 just the same. This is because its an approximation.
I know this isn't a complete answer to your question, but hopefully it sheds some kind of light on approximating complexities in code.
As has been said in the answers above mine, it is clearly not possible to determine this for all snippets of code; I just wanted to add the idea of using average case Big-O to the discussion.
For the first snippet, it's just n^2 because you perform n operations n times. If j was initialized to i, or went up to i, the explanation you posted would be more appropriate but as it stands it is not.
For the second snippet, you can easily see that half of the time the first one will be executed, and the second will be executed the other half of the time. Depending on what's in there (hopefully it's dependent on n), you can rewrite the equation as a recursive one.
The recursive equations (including the third snippet) can be written as such: the third one would appear as
T(n) = T(n-1) + 1
Which we can easily see is O(n).
Big-O is just an approximation, it doesn't say how long an algorithm takes to execute, it just says something about how much longer it takes when the size of its input grows.
So if the input is size N and the algorithm evaluates an expression of constant complexity: O(1) N times, the complexity of the algorithm is linear: O(N). If the expression has linear complexity, the algorithm has quadratic complexity: O(N*N).
Some expressions have exponential complexity: O(N^N) or logarithmic complexity: O(log N). For an algorithm with loops and recursion, multiply the complexities of each level of loop and/or recursion. In terms of complexity, looping and recursion are equivalent. An algorithm that has different complexities at different stages in the algorithm, choose the highest complexity and ignore the rest. And finally, all constant complexities are considered equivalent: O(5) is the same as O(1), O(5*N) is the same as O(N).