In the classic text by Abelson/Sussman, Structure and Interpretation of Computer Programs, in Section 1.2.2 on tree recursion and the Fibonacci sequence, they show this image:
The tree-recursive process generated in computing for the 5th Fibonacci number
Then they write: "Notice that the entire computation of (fib 3) - almost half the work - is duplicated. In fact, it is not hard to show that the number of times the procedure will compute (fib 1) or (fib 0) (the number of leaves in the above tree, in general) is precisely Fib(n + 1)."
I understand that they're making a point about tree-recursion and how this classic case of the Fibonacci tree-recursion is inefficient because the recursive function calls itself twice:
The tree-recursive function for computing a Fibonacci number
My question is, why is it obvious (i.e. "not hard to show") that the number of leaves is equal to the next Fibonacci number in the sequence? I can see visually that it is the case, but I'm not seeing the connection as to why the number of leaves (the reduced down fib 1 and fib 0 calculations) should be an indicator for the next Fibonacci number (in this case 8, which is Fib 6, i.e. the 6th Fibonacci number, i.e. Fib n+1 where n is 5).
It is obvious how the Fibonacci sequence is computed - the sum of the previous two numbers in the sequence yields the current number, but why does the number of leaves precisely equal the next number in the sequence? What is the connection there (other than the obvious, that looking at it and adding up the 1 and 0 leaves does, in fact, yield a total count of 8 in this case, which is the next (6th) Fibonacci number, and so on)?
"Not hard to show" is harder than "obvious".
Use induction with two base cases.
Let's call the number of computations in Fib(x), Fib01(x).
Then,
Fib01(0) = 1 by definition, which is Fib(1)
Fib01(1) = 1 by definition, which is Fib(2)
Now assume that Fib01(k) = Fib(k+1) for k < n:
Fib01(n) = Fib01(n-1) + Fib01(n-2)
= Fib(n) + Fib(n-1)
= Fib(n+1) by definition
QED.
The number of n=1 clauses must be equal to fib(n), because that is the only place a non-zero number comes from, and if the sum of some number of 1s is equal to fib(n), there must be fib(n) of them.
Since fib(n+1) = fib(n) + fib(n-1), we just need to show that there are fib(n-1) leaves computing fib(0). It's less obvious to me how to show this, but perhaps it falls inductively out of the previous case?
Perhaps a simpler approach is to just do the whole thing inductively, then.
For our base cases:
N=0: there are fib(N+1)=fib(1)=1 leaves in the tree. Proof by inspection.
N=1: there are fib(N+1)=fib(2)=1 leaves in the tree. Proof by inspection.
Induction step: to compute fib(N) for an arbitrary N, we compute fib(N-1) once, and fib(N-2) once, and add their results. By induction, there are fib(N) leaves in the tree coming from our computation of fib(N-1), and fib(N-1) leaves in the tree coming from our computation of fib(N-2).
There are therefore fib(N) + fib(N-1) leaves in our overall tree, which is equal to fib(N+1). QED.
We can prove this by extrapolation.
The number of leaves for Fib(0) = 1.
The number of leaves for Fib(1) = 1.
Now, the expression Fib(2) is basically the sum of Fib(1) + Fib(0), i.e., Fib(2) = Fib(1) + Fib(0). So from the tree itself, you can see that the number of leaves for Fib(2) is equal to the sum of leaves in case of Fib(1) and Fib(0). Therefore, the number of leaves for Fib(2) is equal to 2.
Next, for Fib(3) the number of leaves will be sum of leaves for Fib(2) and Fib(1), i.e., 2 + 1 = 3
As you must have observed by now, this follows a pattern similar to Fibonacci series. Infact if we define the number of leaves for Fib(n) to be FibLeaves(n), then we can see that this series is Fib(n) shifted left by 1 space.
Fib(n) = 0, 1, 1, 2, 3, 5, 8, 13, 21, ..
FibLeaves(n) = 1, 1, 2, 3, 5, 8, 13, 21, ..
And thus, the number of leaves will be equal to Fib(n + 1)
Look at it this way:
It is true that we can generate a part of the fibonnaci sequence by picking any two consecutive terms from anywhere in the full fibonnaci sequence and following the rules of generating the next term
i.e Full fibonnaci sequence = 0,1,1,2,3,5,8,13,21....
So if I picked any two consecutive terms e.x 3 and 5, and followed the rules of generating the next term in the sequence, I would generate a part of the fibonnaci sequence
i.e Part of fibonnaci sequence = 3,5,8,13,21,34...
It is true that the number of leaves for a term is equal to the sum of the number of leaves of its two previous terms. This rule is the same as that for generating a term in the fibonnaci sequence
So lets try to get the number of leaves for the second term i.e number of leaves for zeroth term + number of leaves for first term
The number of leaves for the zeroth term and first term is 1
The number of leaves for the second term becomes 1 + 1 = 2
Now, 1,1 are consecutive terms from the full fibonacci sequence and the rule for getting the number of leaves is the same as the rule for getting a term in the fibonacci sequence
Nice question! It is immediately obvious (after a moment's thought), because the number of leaves in a binary tree node is the sum of respective numbers for its two branches, and, vacuously, 1 for leaves -- which is the definition of Fibonacci numbers ... with this specific shape of a tree.
Implicit in the above imprecise general statement is the proof by induction that
N(0) = 1
N(1) = 1
N(n+2) = N(n+1) + N(n)
which directly maps onto that statement, making it specific and concrete!
Related
Description of the Problem:
I have an array of two-digit numbers going from 00 to 99. I must choose a number at random anywhere in the array; let's call this result r. I may now take up to n steps within the array to "travel" to another number in the array, according to the following rules:
Adding or subtracting 1 from r takes one (1) step; I cannot add 1 if there is a 9 in the ones place (ex: 09, 19, 29, ...) and I cannot subtract 1 if there is a 0 in the ones place (ex: 00, 10, 20, ...)
Adding or subtracting 10 from r takes one (1) step; I cannot bring the result to lower than 00 or higher than 99.
By taking two (2) steps, I can swap the digits in the ones and tens place (ex: 13 -> 31, 72 -> 27); however, I can't perform the swap if the digits are the same (ex: can't swap 00, 11, 22, ...)
For a given number x (00 <= x <= 99) I want to count the set of unique values of r from which I can travel to x, given that I can take between 0 and n steps. I call this count how "accessible" x is. I'd like to express this as a formula, A(x, n), rather than just brute-forcing the results for each combination of x and n.
What I Have Tried:
A(x, 0) is easy enough to calculate: A(x, 0) = 1 for all values in the array, because the only way to reach x from r is for r = x; I take zero (0) steps to reach it.
A(x, 1) is trickier, but still simple: you just take into account the new paths available if I spend my one step on either Rule #1 or Rule #2, and add them to A(x, 0). A(x, 2) is where I have to start including Rule #3, but also includes the problem of backtracking. For instance, if I want to reach x and x = r, and I have two (2) steps available, I could perform the following operation: Step 1, r -> r' = r+1 (Rule #1); Step 2, r' -> r'' = r'-1 (Rule #1); r'' = x AND r'' = r. This does not add to my count of unique values from which I can travel to x from.
Where I am Stuck:
I cannot figure out how to count the number of backtracking paths in order to remove them from the otherwise simple calculations of A(x, n), so my values of accessibility are coming out too high.
This problem gives you a positive integer number which is less than or equal to 100000 (10^5). You have to find out the following things for the number:
i. Is the number prime number? If it is a prime number, then print YES.
ii. If the number is not a prime number, then can we express the number as summation of unique prime numbers? If it is possible, then print YES. Here unique means, you can use any prime number only for one time.
If above two conditions fail for any integer number, then print NO. For more clarification please see the input, output section and their explanations.
Input
At first you are given an integer T (T<=100), which is the number of test cases. For each case you will be given a positive integer X which is less than or equal 100000.
Output
For every test case, print only YES or NO.
Sample
Input Output
3
7
6
10 YES
NO
YES
Case – 1 Explanation: 7 is a prime number.
Case – 2 Explanation: 6 is not a prime number. 6 can be expressed as 6 = 3 + 3 or 6 = 2 + 2 + 2. But you can’t use any prime number more than 1 time. Also there is no way to express 6 as two or three unique prime numbers summation.
Case – 3 Explanation: 10 is not prime number but 10 can be expressed as 10 = 3 + 7 or 10 = 2 + 3 + 5. In this two expressions, every prime number is used only for one time.
Without employing any mathematical tricks (not sure if any exist...you'd think as a mathematician I'd have more insight here), you will have to iterate over every possible summation. Hence, you'll definitely need to iterate over every possible prime, so I'd recommend the first step being to find all the primes at most 10^5. A basic (Sieve of Eratosthenes)[https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes] will probably be good enough, though faster sieves exist nowadays. I know your question is language agnostic, but you could consider the following as vectorized pseudocode for such a sieve.
import numpy as np
def sieve(n):
index = np.ones(n+1, dtype=bool)
index[:2] = False
for i in range(2, int(np.sqrt(n))):
if index[i]:
index[i**2::i] = False
return np.where(index)[0]
There are some other easy optimizations, but for simplicity this assumes that we have an array index where the indices correspond exactly to whether the number is prime or not. We start with every number being prime, mark 0 and 1 as not prime, and then for every prime we find we mark every multiple of it as not prime. The np.where() at the end just returns the indices where our index corresponds to True.
From there, we can consider a recursive algorithm for actually solving your problem. Note that you might feasibly have a huge number of distinct primes necessary. The number 26 is the sum of 4 distinct primes. It is also the sum of 3 and 23. Since the checks are more expensive for 4 primes than for 2, I think it's reasonable to start by checking the smallest number possible.
In this case, the way we're going to do that is to define an auxiliary function to find whether a number is the sum of precisely k primes and then sequentially test that auxiliary function for k from 1 to whatever the maximum possible number of addends is.
primes = sieve(10**5)
def sum_of_k_primes(x, k, excludes=()):
if k == 1:
if x not in excludes and x in primes:
return (x,)+excludes
else:
return ()
for p in (p for p in primes if p not in excludes):
if x-p < 2:
break
temp = sum_of_k_primes(x-p, k-1, (p,)+excludes)
if temp:
return temp
return ()
Running through this, first we check the case where k is 1 (this being the base case for our recursion). That's the same as asking if x is prime and isn't in one of the primes we've already found (the tuple excludes, since you need uniqueness). If k is at least 2, the rest of the code executes instead. We check all the primes we might care about, stopping early if we'd get an impossible result (no primes in our list are less than 2). We recursively call the same function for smaller k, and if we succeed we propagate that result up the call stack.
Note that we're actually returning the smallest possible tuple of unique prime addends. This is empty if you want your answer to be "NO" as specified, but otherwise it allows you to easily come up with an explanation for why you answered "YES".
partial = np.cumsum(primes)
def max_primes(x):
return np.argmax(partial > x)
def sum_of_primes(x):
for k in range(1, max_primes(x)+1):
temp = sum_of_k_primes(x, k)
if temp:
return temp
return ()
For the rest of the code, we store the partial sums of all the primes up to a given point (e.g. with primes 2, 3, 5 the partial sums would be 2, 5, 10). This gives us an easy way to check what the maximum possible number of addends is. The function just sequentially checks if x is prime, if it is a sum of 2 primes, 3 primes, etc....
As some example output, we have
>>> sum_of_primes(1001)
(991, 7, 3)
>>> sum_of_primes(26)
(23, 3)
>>> sum_of_primes(27)
(19, 5, 3)
>>> sum_of_primes(6)
()
At a first glance, I thought caching some intermediate values might help, but I'm not convinced that the auxiliary function would ever be called with the same arguments twice. There might be a way to use dynamic programming to do roughly the same thing but in a table with a minimum number of computations to prevent any duplicated efforts with the recursion. I'd have to think more about it.
As far as the exact output your teacher is expecting and the language this needs to be coded in, that'll be up to you. Hopefully this helps on the algorithmic side of things a little.
int recursiveFunc(int n) {
if (n == 1) return 0;
for (int i = 2; i < n; i++)
if (n % i == 0) return i + recursiveFunc(n / i);
return n;
}
I know Complexity = length of tree from root node to leaf node * number of leaf nodes, but having hard time to come to an equation.
This one is tricky, because the runtime is highly dependent on what number you provide in as input in a way that most recursive functions are not.
For starters, notice that the way that this recursion works, it takes in a number and then either
returns without making any further calls if the number is prime, or
recursively calls itself on number divided by that proper factor.
This means that in one case, the function, called on a number n, will do Θ(n) work and make no calls (which happens if the number is prime), and in the other case will do Θ(d) work and then make a recursive call on the number n / d, which happens if n is composite and is the largest divisor of n.
One useful fact we'll use to analyze this function is that given a composite number n, the smallest factor d of n is never any greater than √n. If it were, then we would have that n = df for some other factor f, and since d is the smallest proper divisor, we'd have that f ≥ d, so df > √n √ n = n, which would be impossible.
With that in mind, we can argue that the worst-case runtime of this function is O(n), and in fact that happens when n is prime. Here's how to see this. Imagine the worst-case amount of time this function can take if it ends up making a recursive call. In that case, the function will do at most Θ(√n) work (let's assume our smallest divisor is as large as possible), then recursively makes a call on a number whose size is at most n / 2 (which is the absolute largest number we could get as part of the recursive call. In that case, we'd get this recurrence relation under the pessimistic assumption that we do the maximum work possible
T(n) = T(n / 2) + √n
This solves, by the Master Theorem, to Θ(√n), which is less work than what we'd do if we had a prime number as an input.
But what happens if, instead, we do the maximum amount of work possible for some number of iterations, and then end up with a prime number and stop? In that case, using the iteration method, we'd see that the work done would be
n1/2 + n1/4 + ... + n / 2k,
which would happen if we stopped after k iterations. In this case, notice that this expression is maximized when we pick k to be as small as possible - which would correspond to stopping as soon as possible, which happens if we pick a prime number for n.
So in this sense, the worst-case runtime of this function is Θ(n), which happens for n being a prime number, with composite numbers terminating much faster than this.
So how fast can this function be? Well, imagine, for example, that we have a number of the form pk, where p is some prime number. In that case, this function will do Θ(p) work to discover p as a prime factor, then recursively call itself on the number pk-1. If you think about what this will look like, this function will end up doing Θ(p) work Θ(k) times for a total runtime of Θ(pk). And since n = pk, we'd have k = logp n, so the runtime would be Θ(p logp n). That's minimized at either p = 2 or p = 3, and in either case gives us a runtime of Θ(log n) in this case.
I strongly suspect that's the best case here, though I'm not entirely sure. But what this does mean is that
the worst-case runtime is definitely Θ(n), occurring at prime numbers, and
the best-case runtime is O(log n), which I'm fairly certain is a tight bound but I'm not 100% sure how to prove.
The josephus problem can be solved by the below recursion:
josephus(n, k) = (josephus(n - 1, k) + k-1) % n + 1
josephus(1, k) = 1
How this recurrence relation has been derived?
josephus(n, k) = (josephus(n - 1, k) + k-1) % n + 1 ...... (1)
To put it in simple words -
starting with the "+1" in the formula. It implies that 1 iteration of the recurrence has already been done. Now, we would be left with n-1 persons/elements. We need to process n-1 elements recursively at intervals of k. But, now, since the last element to be removed was at kth location, we would continue from thereof. Thus, k-1 added. Further, this addition might upset the indexing of the array. Thus %n done to keep the array index within the bounds.
Hope it is lucid and elaborate enough :) .
This paragraph is sufficient from wikipedia..
When the index starts from one, then the person at s shifts from the
first person is in position ((s-1)\bmod n)+1, where n is the total
number of persons. Let f(n,k) denote the position of the survivor.
After the k-th person is killed, we're left with a circle of n-1, and
we start the next count with the person whose number in the original
problem was (k\bmod n)+1. The position of the survivor in the
remaining circle would be f(n-1,k) if we start counting at 1;
shifting this to account for the fact that we're starting at (k\bmod
n)+1 yields the recurrence
f(n,k)=((f(n-1,k)+k-1) \bmod n)+1,\text{ with }f(1,k)=1\,,
I'm trying to create "make-change" that will return a ls of coins whose sum = the input, and it needs to contain the least number of coins possible.
Ex: (make-change 99)
=> (quarter quarter quarter dime dime penny penny penny penny)
Here's the lines along which make-change should operate:
If the remaining amount is exactly equal to 1, 5, 10, or 25 then return the appropriate coin.
Otherwise, cons the largest coin you can use onto the result of (make-change (- x value)) where value is the amount of the coin that you just used.
You can tell this procedure will terminate, since the amount will become smaller and smaller via step 2 until it is finally amenable to concluding with step 1.