Turn recursive program to iterative - java

A friend challenged me to write a program that gets three integers (int a, int n, int k) and computes efficiently as possible, a^n mod k
I came up with this solution
public static int rec(int a,int n,int k) //calc a^n mod k
{
if(n == 1)
return a % k;
int exp1 = rec(a, n/2, k), exp2 = exp1;
if(n % 2 == 1)
exp2 = rec(a, n/2 + 1, k);
return (exp1 * exp2) % k;
}
It's an incredibly simple recursive solution, reliant on the fact that a^(b+c) mod d = (a^b mod d * a^c mod d) mod d, and runs in logarithmic time. At least theoretically.
In practice when we measured our solution, his linear time solution was better than my solution. I suspect it's due to me using recursion rather than loops. Does that make sense? If so - how can I turn this code into an iterative program?

Does that make sense?
Yes. As Boris The Spider pointed out, there is no tail optimization in Java.
how can I turn this code into an iterative program?
Let me copy-paste an iterative solution to calculate power of a number from here
int pow(int x, int n) {
int res = 1;
while(n > 0) {
if(n % 2 == 1) {
res = res * x;
}
x = x * x;
n = n / 2;
}
return res;
}
Disclaimer : Although the above code looks ok to me, I haven't tested it personally.

Related

Algorithms: Hybrid MergeSort and InsertionSort Execution Time

Good day SO community,
I am a CS student currently performing an experiment combining MergeSort and InsertionSort. It is understood that for a certain threshold, S, InsertionSort will have a quicker execution time than MergeSort. Hence, by merging both sorting algorithms, the total runtime will be optimized.
However, after running the experiment many times, using a sample size of 1000, and varying sizes of S, the results of the experiment does not give a definitive answer each time. Here is a picture of the better results obtained (Note that half of the time the result is not as definitive):
Now, trying the same algorithm code with a sample size of 3500:
Finally, trying the same algorithm code with a sample size of 500,000 (Note that the y-axis is in milliseconds:
Although logically, the Hybrid MergeSort will be faster when S<=10, as InsertionSort does not have recursive overhead time. However, the results of my mini experiment says otherwise.
Currently, these are the Time Complexities taught to me:
MergeSort: O(n log n)
InsertionSort:
Best Case: θ(n)
Worst Case: θ(n^2)
Finally, I have found an online source: https://cs.stackexchange.com/questions/68179/combining-merge-sort-and-insertion-sort that states that:
Hybrid MergeInsertionSort:
Best Case: θ(n + n log (n/x))
Worst Case: θ(nx + n log (n/x))
I would like to ask if there are results in the CS community that shows definitive proof that a Hybrid MergeSort algorithm will work better than a normal MergeSort algorithm below a certain threshold, S, and if so, why?
Thank you so much SO community, it might be a trivial question, but it really will clarify many questions that I currently have regarding Time Complexities and stuff :)
Note: I am using Java for the coding of the algorithm, and runtime could be affected by the way java stores data in memory..
Code in Java:
public static int mergeSort2(int n, int m, int s, int[] arr){
int mid = (n+m)/2, right=0, left=0;
if(m-n<=s)
return insertSort(arr,n,m);
else
{
right = mergeSort2(n, mid,s, arr);
left = mergeSort2(mid+1,m,s, arr);
return right+left+merge(n,m,s,arr);
}
}
public static int insertSort(int[] arr, int n, int m){
int temp, comp=0;
for(int i=n+1; i<= m; i++){
for(int j=i; j>n; j--){
comp++;
comparison2++;
if(arr[j]<arr[j-1]){
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
else
break;
}
}
return comp;
}
public static void shiftArr(int start, int m, int[] arr){
for(int i=m; i>start; i--)
arr[i] = arr[i-1];
}
public static int merge(int n, int m, int s, int[] arr){
int comp=0;
if(m-n<=s)
return 0;
int mid = (n+m)/2;
int temp, i=n, j=mid+1;
while(i<=mid && j<=m)
{
comp++;
comparison2++;
if(arr[i] >= arr[j])
{
if(i==mid++&&j==m && (arr[i]==arr[j]))
break;
temp = arr[j];
shiftArr(i,j++,arr);
arr[i] = temp;
if(arr[i+1]==arr[i]){
i++;
}
}
i++;
}
return comp;
}
The example code isn't a conventional merge sort. The merge function is shifting an array instead of merging runs between the original array and a temporary working array and back.
I tested top down and bottom up merge sorts and both take about 42 ms == 0.042 seconds to sort 500,000 32 bit integers, versus the apparent results in the graph which are 1000 times slower at about 42 seconds instead of 42 ms. I also tested with 10,000,000 integers and it takes a bit over 1 second to sort.
In the past, using C++, I compared a bottom up merge sort with a hybrid bottom up merge / insertion sort, and for 16 million (2^24 == 16,777,216) 32 bit integers, the hybrid sort was about 8% faster with S == 16. S == 64 was slightly slower than S == 16. Visual Studio std::stable_sort is a variation of bottom up merge sort (the temp array is 1/2 the size of the original array) and insertion sort, and uses S == 32.
For small arrays, insertion sort is quicker than merge sort, a combination of cache locality and fewer instructions needed to sort a small array with insertion sort. For pseudo random data and S == 16 to 64, insertion sort was about twice as fast as merge sort.
The relative gain diminishes as the array size increases. Considering the effect on bottom up merge sort, with S == 16, only 4 merge passes are optimized. In my test case with 2^24 == 16,777,216 elements, that's 4/24 = 1/6 ~= 16.7% of the number of passes, resulting in about an 8% improvement (so the insertion sort is about twice as fast as merge sort for those 4 passes). The total times were about 1.52 seconds for the merge only sort, and about 1.40 seconds for the hybrid sort, a 0.12 second gain on a process that only takes 1.52 seconds. For a top down merge sort, with S == 16, the 4 deepest levels of recursion would be optimized.
Update - Example java code for an hybrid in place merge sort / insertion sort with O(n log(n)) time complexity. (Note - auxiliary storage is still consumed on the stack due to recursion.) The in place part is accomplished during merge steps by swapping the data in the area merged into with the data in the area merged from. This is not a stable sort (the order of equal elements is not preserved, due to the swapping during merge steps). Sorting 500,000 integers takes about 1/8th of a second, so I increased this to 16 million (2^24 == 16777216) integers, which takes a bit over 4 seconds. Without the insertion sort, the sort takes about 4.524 seconds, and with the insertion sort with S == 64, the sort takes about 4.150 seconds, about 8.8% gain. With essentially the same code in C, the improvement was less: from 2.88 seconds to 2.75 seconds, about 4.5% gain.
package msortih;
import java.util.Random;
public class msortih {
static final int S = 64; // use insertion sort if size <= S
static void swap(int[] a, int i, int j) {
int tmp = a[i]; a[i] = a[j]; a[j] = tmp;
}
// a[w:] = merged a[i:m]+a[j:n]
// a[i:] = reordered a[w:]
static void wmerge(int[] a, int i, int m, int j, int n, int w) {
while (i < m && j < n)
swap(a, w++, a[i] < a[j] ? i++ : j++);
while (i < m)
swap(a, w++, i++);
while (j < n)
swap(a, w++, j++);
}
// a[w:] = sorted a[b:e]
// a[b:e] = reordered a[w:]
static void wsort(int[] a, int b, int e, int w) {
int m;
if (e - b > 1) {
m = b + (e - b) / 2;
imsort(a, b, m);
imsort(a, m, e);
wmerge(a, b, m, m, e, w);
}
else
while (b < e)
swap(a, b++, w++);
}
// inplace merge sort a[b:e]
static void imsort(int[] a, int b, int e) {
int m, n, w, x;
int t;
// if <= S elements, use insertion sort
if (e - b <= S){
for(n = b+1; n < e; n++){
t = a[n];
m = n-1;
while(m >= b && a[m] > t){
a[m+1] = a[m];
m--;}
a[m+1] = t;}
return;
}
if (e - b > 1) {
// split a[b:e]
m = b + (e - b) / 2;
w = b + e - m;
// wsort -> a[w:e] = sorted a[b:m]
// a[b:m] = reordered a[w:e]
wsort(a, b, m, w);
while (w - b > 2) {
// split a[b:w], w = new mid point
n = w;
w = b + (n - b + 1) / 2;
x = b + n - w;
// wsort -> a[b:x] = sorted a[w:n]
// a[w:n] = reordered a[b:x]
wsort(a, w, n, b);
// wmerge -> a[w:e] = merged a[b:x]+a[n:e]
// a[b:x] = reordered a[w:n]
wmerge(a, b, x, n, e, w);
}
// insert a[b:w] into a[b:e] using left shift
for (n = w; n > b; --n) {
t = a[n-1];
for (m = n; m < e && a[m] < t; ++m)
a[m-1] = a[m];
a[m-1] = t;
}
}
}
public static void main(String[] args) {
int[] a = new int[16*1024*1024];
Random r = new Random(0);
for(int i = 0; i < a.length; i++)
a[i] = r.nextInt();
long bgn, end;
bgn = System.currentTimeMillis();
imsort(a, 0, a.length);
end = System.currentTimeMillis();
for(int i = 1; i < a.length; i++){
if(a[i-1] > a[i]){
System.out.println("failed");
break;
}
}
System.out.println("milliseconds " + (end-bgn));
}
}

How can I "unroll" this sigma notation as a recursive method

In my java programming class, we have a lab assignment (not worth points) to implement several recursive methods. I have completed the recursive method based on a given recursive function, and I have completed a necessary factorial recursive method for the remaining portion, which is a sigma series that I am currently having a hard time wrapping my head around.
We are given this formula:
s(n) = sigma[(s(n - i) - 1) / i!, i = 1, n] and s(0) = 0
and I have written out the results for s(1)-s(5) (using my graphing calculator to verify my answers as I go), but I am having difficulty figuring out how to correctly implement this recursive process.
I have built a "sigma" method that works appropriately to the best of my knowledge, and so I think I have issues with my "formula" method. Worst of all, the formula looks right (to the best of my thinking) and the code is currently getting stuck in an infinite loop.
// ... Rest of code omitted for brevity
private static double sequence2(int i) {
if (i <= 0) { return 0; }
return (sequence2(max - i) - 1) / factorial(i); // max is defined in other code
}
private static double sigma(int n) {
if (n <= 0) { return 0; }
return sequence2(n) + sigma(n - 1);
}
private static int factorial(int n) {
if (n <= 1) { return 1; }
return n * factorial(n - 1);
}
Where should I begin for figuring out how to correctly unroll this recursive sequence?
According to the formula you've linked the recursion should be(changing the name of the variables to correlate) :
private static double sequence2(int n) {
if (n == 0) { return 0; }
return (sequence2(n-1) - 1) / factorial(n-1);
}
this shall generate the sequence in the reverse order from S(n-1), S(n-2) ... S(1)
After talking to my teacher, he provided the following method as an acceptable way to calculate the correct values:
static double generateSigmaSequnce(int n) {
if (n < 1) { return 0; }
double sum = 0;
for (int i = 1; i <= n; i++) {
sum += (generateSigmaSequnce(n - i) - 1) / factorial(i);
}
return sum;
This code does indeed return the correct values for the sigma sequence, but to my mind it is somewhat unsatisfactory as we've had to use an iterative loop to drive behavior in an exercise in recursion. I will be puzzling out from here to determine if I can create a new method/augment this method to include a recursive summation, but for the time being (and in my teacher's own code) this is correct.

Largest Divisor of N (Excluding Itself)

I'm trying to divide a list into sublists that are as large as possible. If the list can not be divided in such a way, I will handle it as needed, but I need get the largest number besides N itself that divides N evenly.
I wrote a really naive solution, but I feel like there should be maybe a formula or something to do this in constant time. My list are not that big, maximum size is 1000. This probably isn't the critical path, but is there a better algorithm?
public static int largestDivisor(int n){
int divisor = 0;
for (int i = 1; i <= n/2; i++)
if (n % i == 0)
divisor = i;
return divisor;
}
Iterate the values in reverse. Simply return the first one you find (it'll be the greatest). Something like,
public static int largestDivisor(int n) {
for (int i = n / 2; i >= 2; i--) {
if (n % i == 0) {
return i;
}
}
return 1;
}
Alternatively, you might make a slight improvement to #WillemVanOnsem's answer and start with odd values like;
public static int largestDivisor(int n) {
if (n % 2 == 0) {
return n / 2;
}
final int sqrtn = (int) Math.sqrt(n);
for (int i = 3; i <= sqrtn; i += 2) {
if (n % i == 0) {
return n / i;
}
}
return 1;
}
I don't know if you can do this in constant time, but you can certainly do it in less time than this.
Start with 2, and loop through all numbers, checking if n is divisible by that number. When you get to a number that divides n, then you can stop - your answer is n/i. If you get to the end and it still doesn't divide, then n is prime and the answer is just 1.
Instead of ending at n/2 if you don't find a divisor, you can end at √n with this method, which will reduce the big O.
Also, you could start with checking if it's divisible by 2, then go to 3 and only check the odd numbers from there. (If it was divisible by an even number, then it was divisible by 2.) That won't change the big O, but it should cut the processing time almost in half since you're only checking about half the divisors.
You know that if a is dividable by b, it is also dividable by a/b and the smaller b is, the larger is a/b, so once you have found the divisor, return n/divisor:
public static int largestDivisor(int n){
for(int i = 2; i <= n/2; i++)
if(n % i == 0) {
return n/divisor;
}
}
return 0; //or whatever you decide to return if there is no such divisor
}
This is also faster because:
divisors tend to become more rare the larger they get; and
you can already give up at sqrt(n).
So the most efficient approach would be:
public static int largestDivisor(int n){
int sqrtn = (int) Math.sqrt(n);
for(int i = 2; i <= sqrtn; i++)
if(n % i == 0) {
return n/divisor;
}
}
return 0;
}

Find the value of pi from the infinite series (pi=4-4/3+4/5-4/7+4/9-4/11…. ) using recursion in java

I have written multiple attempts to this problem, but I think this is the closest I could get. This solution makes the method recurse infinitely, because I don't have a base case, and I can't figure it out. The counter++ line is unreachable, and I can't get this to work, and I am very tired. This would be very easy with a loop, but recursion is kind of a new concept to me, and I would be thankful if someone helped me solve this.
public static double pi(int a, double b){
int counter=0;
if (counter %2==0){
return a-(a/(pi(a,b+2)));
counter++;
} else {
return a+(a/(pi(a,b+2)));
counter++;
}
You could pass in another int, say limit, and add this code:
if (b > limit) {
return a;
}
Or you could pass in some tolerance value:
if (pi(a,b+2) < tolerance) {
return a;
}
Whenever you're working with recursion it's good to establish an exit strategy up front.
Here is an implementation that works. Do not use it:
public static double term(double acc, int n, int r) {
if (r-- > 0) {
double sgn = (n % 4 == 1) ? +1.0 : -1.0;
acc += sgn * 4.0 / n;
n += 2;
return term(acc, n, r);
} else {
return acc;
}
}
public static double pi() {
return term(0.0, 1, 1000);
}
The reason not to use it is that this particular infinite series is a particularly poor way of calculating π because it converges very slowly. In the example above event after 1000 iterations are performed it's still only correct to 3 decimal places because the final calculated term is 4 / 1000.
Going much beyond 1000 iterations results in a stack overflow error with little improvement in the accuracy even though the term function is (I think) potentially tail recursive.

Java: avoiding factorial overflow

I'm supposed to optimize the following code so that it calculates the central binomial coefficient up to the max value of integer (up to n = 16).
public static int factorial(int n)
{
int result= 1;
for(int i = 2; i <= n; i++) result *= i;
return result;
}
public static int centralbinom(int n)
{
return factorial(2*n) / (factorial(n) * factorial(n));
}
Naturally I get an overflow for every n > 6.
How do I 'break down' the factorial function so that doesn't has to deal with big numbers such as 2n = 2*16 = 32 ?
Or is there a better way to calculate the central binomial coefficient?
Here are several optimizations that you can do in addition to using BigIntegers that may reduce your calculations, in most of you cases overflow that you may be having in your program.
Since you need factorial(n) at least two time. Calculate it once and store it in a variable.
Factorial(2*n) has factorial(n) in it. Since you have already calculated factorial(n) before all you need to do is calculate till factorial(2n....n) and then multiply factorial(n) to it. Here's one way how that can be done.
//Pseudocode
//find factorial of n given I know already till k
int findFactorial(n, k) {
int result = 1
for i n to 1
if(i==k)
break;
result = result * n;
return result
}
//factorial(2*n) = facorial(n, k) * factorial(k)
This will reduce your calculations a lot and in case if you expect your program not to have an overflow, you can go away with BigIntegers.
If you need a factorial of big number, you have to use BigInteger class to calculate result:
public static BigInteger factorial(int n) {
BigInteger result = BigInteger.ONE;
for (int i = 2; i <= n; ++i) {
result = result.multiply(BigInteger.valueOf(i));
}
return result;
}
If the central binomial coefficient of 17 is greater than the integer max, and you only need to compute it for 17 numbers, then the obvious solution is a lookup table. Create a 17-element array containing the central binomial coefficient for n = 0 to 16. I think you'll find this solution is extremely efficient.
You can find a list of them here. http://oeis.org/A000984
Just compress your factorial by gamma.
set gamma to 10 is good enough
public static double factorial(int n, double gamma)
{
double result= 1;
double gammaInv = 1.0/gamma;
for(int i = 2; i <= n; i++) result *= pow(i,gammaInv);
return result;
}
public static int centralbinom(int n, double gamma)
{
return pow(factorial(2*n,gamma) /
(factorial(n,gamma) * factorial(n),gamma),
gamma);
}

Categories

Resources