Breakdown of the cost and time required for Fizzbuzz in worst case - algorithm

The Fizz-Buzz function (in pseudocode) takes any positive integer n. I'm especially curious about the algebraic breakdown of the cost and time required of the if-else statement. I know its worst case running time is O(n).
Fizz-Bizz(n)
for i = 1 to n
if (n % 3 == 0)
print "fizz"
if (n % 5 == 0)
print "buzz"
if (n % 3 != 0 and n % 5 != 0)
print n
Example of breakdown of another algorithm:

The time complexity is O(n) because the if statement has no real effect on that. The complexity of the if statement is constant over a large enough dataset.
The if statement may actually do a different amount of work in iterations where you have a multiple of three or five but, the amount of extra work per loop iteration is not dependent on n. In fact, it averages out to a constant as n becomes bigger.
And, as an aside, I think that code may be wrong. At multiples of fifteen, it should print both fizz and buzz.
If you want to do it to the level in your edit (the added breakdown), you simply need to assign an arbitrary cost ci to each statement (and this cost is constant for a single execution of that statement) then figure out how many times each statement is run.
For example, the first if sequence runs n times, the print "fizz" runs for one-third of those, n/3. So you end up with something like this table:
cost times
Fizz-Buzz(n)
for i = 1 to n c1 n
if (n % 3 == 0) c2 n
print "fizz" c3 n / 3 [call this a]
else
if (n % 5 == 0) c4 n - a
print "buzz" c5 (n - a) / 5 [call this b]
else
print n c6 n - a - b
Add up all of those as per your example (substituting the n-equations for a and b) and, in the end, you'll still end up with something dependent on n, hence an O(n) algorithm. It'll look something like:
c1*n + c2*n + c3*n/3 + c4*(n-a) + c5*(n-a)/5 + c6*(n-a-b)
= c1*n + c2*n + (c3/3)*n + c4*(n-n/3) + (c5/5)*(n-n/3) + c6*(n-n/3-(n-n/3)/5)
= c1*n + c2*n + (c3/3)*n + c4*(2/3)*n + (c5/5)*(2/3)*n + c6*(n-n/3-(n-n/3)/5)
= c1*n + c2*n + (c3/3)*n + (c4*2/3)*n + (c5*2/15)*n + c6*(n*8/15)
= c1*n + c2*n + (c3/3)*n + (c4*2/3)*n + (c5*2/15)*n + (c6*8/15)*n
/ 1 2 2 8 \
= ( c1 + c2 + - c3 + - c4 + -- c5 + -- c6 ) * n
\ 3 3 15 15 /
All those values inside parentheses are in fact constants (since they're multiples of constants) so the whole thing is a constant multiplier of n.
Now, if you find a minor mistake in those equations above, I wouldn't be too surprised - I haven't done this level of math for quite a few years, and I may well have thrown in a furphy in case this is for homework and you try copying it verbatim :-)
But the only mistake you're likely to find is the value of the constant multiplier itself. It will still be a constant multiplier of some description, I'll guarantee that.

Related

How do I write a pseudo code if a recurrence is given

T(n)=4T(n/4) + log4n is the recurrence provided and I was wondering how to write a pseudocode based on it.
This says: make four recursive calls, and have each recursive call do an amount of work equal to log 4n (rounded down).
Note that log 4n = log 4 + log n = 2 + log n.
Something like this gets pretty close::
function foo(array[1...n])
if n <= 1 then return 1
c = 1
while c < n do
c *= 4
return c + foo(arr[1...n/4]) + foo(arr[n/4+1...n/2]) + foo(arr[n/2+1...3n/4]) + foo(arr[3n/4+1...n]
The recurrence here is T(n) = 4T(n/4) + log 4n + 15, if I counted correctly, and making some assumptions about operations taking the same time to run.
We can bring that 15 down by turning the algorithm more and more silly. Store n/4 in a variable, call foo on the first 1/4 of arr four times, but only return c; this brings the 15 down to 1, I think. To get rid of that 1 operation, start c at 4 instead, killing one loop iteration (2 ops) and add one op back at the end, like return c + 1 instead of return c.

Recursion with random numbers

function foo(n)
if n = 1 then
return 1
else
return foo(rand(1, n))
end if
end function
If foo is initially called with m as the parameter, what is the expected number times that rand() would be called ?
BTW, rand(1,n) returns a uniformly distributed random integer in the range 1 to n.
A simple example is how many calls it takes to calculate f(2). Say this time is x, then x = 1 + 0/2 + x/2 because we do the actual call 1, then with probability 1/2 we go to f(1) and with probability 1/2 we stay at f(2). Solving the equation gives us x = 2.
As with most running time analysis of recursion, we try to get a recursive formula for the running time. We can use linearity of expectation to proceed through the random call:
E[T(1)] = 0
E[T(2)] = 1 + (E[T(1)] + E[T(2)])/2 = 2
E[T(n)] = 1 + (E[T(1)] + E[T(2)] + ... E[T(n)])/n
= 1 + (E[T(1)] + E[T(2)] + ... E[T(n-1)])/n + E[T(n)]/n
= 1 + (E[T(n-1)] - 1)(n-1)/n + E[T(n)]/n
Hence
E[T(n)](n-1) = n + (E[T(n-1)] - 1)(n-1)
And so, for n > 1:
E[T(n)] = 1/(n-1) + E[T(n-1)]
= 1/(n-1) + 1/(n-2) + ... + 1/2 + 2
= Harmonic(n-1) + 1
= O(log n)
This is also what we intuitively might have expected, since n should approximately half at each call to f.
We may also consider the 'Worst case with high probability'. For this it's easy to use Markov's inequality, which says P[X <= a*E[X]] >= 1-1/a. Setting a = 100 we get that with 99% probability, the algorithm makes less than 100 * log n calls to rand.

Recursive algorithm for the sum of odd number positive integers

I am expressing the algorithms in pseudo code. I'm just wondering if my design works just as well as the original one displayed below. The algorithm is supposed to compute the sum of n odd positive integers.
This is how the algorithm should look:
procedure sumofodds(n:positive integer)
if n = 1
return 1
else
return sumofodds(n-1) + (2n-1)
This is how i designed my algorithm:
procedure odd(n: positive integer)
if n = 1
return 1
if n % 2 > 0
return n + odd(n-1) // this means n is odd
if n % 2 = 0
return 0 + odd(n-1) // this means its even
Your algorithm is not the same as the original.
The original computes the sum of the first n odd numbers.
Your algorithm computes the sum of all the odd numbers in the range 1..n.
So for an input of n=3, the first algorithm will compute 1+3+5, while your algorithm will compute 1+3.
(If you want a quicker way, then the formula n*n computes the sum of the first n odd numbers)
One small improvement that might help is defining it with tail recursion. Tail recursion happens when the very last thing to execute is the recursive call. To make this tail recursive, use a helper method and pass the running sum as a parameter. I'm pretty sure the pseudo code below is tail recursive since, regardless of the result of the (if odd) check, the final step is the recursive call (the math happens before the recursive call).
procedure SumOdds(n)
return SumOddsHelper(n, 0)
procedure SumOddsHelper(n, sum)
if n = 1 return 1
if n is odd return SumOddsHelper(n-1, sum + n)
else return SumOddsHelper(n-1, sum)
Let me suggest that you implement your idea in Python. You may be surprised to see that the working code is very similar to pseudocode.
This is the original algorithm:
def sum_of_n_odds(n):
if n == 1:
return 1
else:
return sum_of_n_odds(n-1) + (2*n-1)
And this is the one you wrote:
def sum_of_odds_up_to_n(n):
if n == 1:
return 1
if n % 2 > 0: # this means n is odd
return n + sum_of_odds_up_to_n(n-1)
if n % 2 == 0: # this means it's even
return 0 + sum_of_odds_up_to_n(n-1)
These two algorithms compute different things. Calling sum_of_n_odds(10) yields the same result as calling sum_of_odds_up_to_n(19) or sum_of_odds_up_to_n(20). In general, sum_of_odds_up_to_n(n) is equivalent to sum_of_n_odds((n+1)//2), where // means integer division.
If you're interested in making your implementation a little more efficient, I suggest that you omit the final if condition, where n % 2 == 0. An integer is either odd or even, so if it isn't odd, it must be even.
You can get another performance gain by making the recursive call sum_of_odds_up_to(n-2) when n is odd. Currently you are wasting half of your function calls on even numbers.
With these two improvements, the code becomes:
def sum_of_odds_up_to_n(n):
if n <= 0:
return 0
if n % 2 == 0:
return sum_of_odds_up_to_n(n-1)
return n + sum_of_odds_up_to_n(n-2)
And this is the tail-recursive version:
def sum_of_odds_up_to_n(n, partial=0):
if n <= 0:
return partial
if n % 2 == 0:
return sum_of_odds_up_to_n(n-1, partial)
return sum_of_odds_up_to_n(n-2, partial+n)
You should not expect performance gains from the above because Python does not optimize for tail recursion. However, you can rewrite tail recursion as iteration, which will run faster because it doesn't spend time allocating a stack frame for each recursive call:
def sum_of_odds_up_to_n(n):
partial = 0
if n % 2 == 0:
n -= 1
while n > 0:
partial += n
n -= 2
return partial
The fastest implementation of all relies on mathematical insight. Consider the sum:
1 + 3 + 5 + ... + (n-4) + (n-2) + n
Observe that you can pair the first element with the last element, the second element with the second last element, the third element with the third last element, and so on:
(1 + n) + (3 + n-2) + (5 + n-4) + ...
It is easy to see that this is equal to:
(n + 1) + (n + 1) + (n + 1) + ...
How many terms (n + 1) are there? Since we're pairing up two terms at a time from the original sequence, there are half as many terms in the (n + 1) sequence.
You can check for yourself that the original sequence has (n + 1) / 2 terms. (Hint: see what you get if you add 1 to every term.)
The new sequence has half as many terms as that, or (n + 1) / 4. And each term in the sequence is (n + 1), so the sum of the whole sequence is:
(n + 1) * (n + 1) / 4
The resulting Python program is this:
def sum_of_odds_up_to_n(n):
if n <= 0:
return 0
if n % 2 == 0:
n -= 1
return (n+1)*(n+1)//4

How to do recurrence relations?

nSo we were taught about recurrence relations a day ago and we were given some codes to practice with:
int pow(int base, int n){
if (n == 0)
return 1;
else if (n == 1)
return base;
else if(n%2 == 0)
return pow(base*base, n/2);
else
return base * pow(base*base, n/2);
}
The farthest I've got to getting its closed form is T(n) = T(n/2^k) + 7k.
I'm not sure how to go any further as the examples given to us were simple and does not help that much.
How do you actually solve for the recurrence relation of this code?
Let us count only the multiplies in a call to pow, denoted as M(N), assuming they dominate the cost (a nowadays strongly invalid assumption).
By inspection of the code we see that:
M(0) = 0 (no multiply for N=0)
M(1) = 0 (no multiply for N=1)
M(N), N>1, N even = M(N/2) + 1 (for even N, recursive call after one multiply)
M(N), N>1, N odd = M(N/2) + 2 (for odd N, recursive call after one multiply, followed by a second multiply).
This recurrence is a bit complicated by the fact that it handles differently the even and odd integers. We will work around this by considering sequences of even or odd numbers only.
Let us first handle the case of N being a power of 2. If we iterate the formula, we get M(N) = M(N/2) + 1 = M(N/4) + 2 = M(N/8) + 3 = M(N/16) + 4. We easily spot the pattern M(N) = M(N/2^k) + k, so that the solution M(2^n) = n follows. We can write this as M(N) = Lg(N) (base 2 logarithm).
Similarly, N = 2^n-1 will always yield odd numbers after divisions by 2. We have M(2^n-1) = M(2^(n-1)-1) + 2 = M(2^(n-2)-1) + 4... = 2(n-1). Or M(N) = 2 Lg(N+1) - 2.
The exact solution for general N can be fairly involved but we can see that Lg(N) <= M(N) <= 2 Lg(N+1) - 2. Thus M(N) is O(Log(N)).

how to calculate time complexity of for (int i = n - 1; i != 0; i /= 2) ?

for (int i = n - 1; i != 0; i /= 2) ++k;
I am not able to understand how to calculate time complexity for above. I am unable to make out its behavior when n is negative. can anyone please help me to get there. I tried when n is positive.
Statement Code Time
1a i=n-1 1
1b i != 0 log2n+1
1c i = i/2 log2n
2 ++k log 2n
Total running time 3 log 2n+2
I got these values when i analyzed the code for n to be positive . but i failed to get when n is negative
This algorithm belongs to O(log(n)). The longest running time occurs when abs(n - 1) is a power of 2, because in all other cases some of the i /= 2 steps will cause i to take a value (whose absolute value is) slightly less than abs(i / 2), due to truncation.
When n - 1 is a power of 2, so n - 1 == 2**a for some a, then the loop will be executed a + 1 times (once for i taking each of the values 1 = 2**0, 2 = 2**1, 4 = 2**2, …, n - 1 = 2**a). That is, the loop will be executed lg(n - 1) + 1 times.
I think some of your confusion stems from you trying to account for how many steps are taken inside the loop, but remember that these constant factors don't matter for the asymptotic runtime. To prove that the runtimes is (say) O(log(n)) you need only show that the limit of "Actual runtime for n" / log(n), as n approaches infinity, is less than infinity. If each iteration of the loop takes three steps or four steps, or a thousand steps, who cares? As long as the gap between the actual runtime and log(n) is bound from above by some finite constant then it makes no difference. For this same reason you don't need to worry about the base of the logarithm (2, or 10, or e, it's just a constant factor), or even whether the loop is executed lg(n - 1) times or lg(n - 1 +(-) m) +(-) p times for any constants m and p.

Resources