Related
In one of the previous intro to cs exams there was a question: calculate the space and time complexity of the function f1 as a function of n, assume that the time complexity of malloc(n) is O(1) and its space complexity is O(n).
int f1(int n) {
if(n < 3)
return 1;
int* arr = (int*) malloc(sizeof(int) * n);
f1(f1(n – 3));
free(arr);
return n;
}
The official solution is: time complexity: O(2^(n/3)), space complexity: O(n^2)
I tried to solve it but i didn't know how until i saw a note in my notebook that said: since the function returns n then we can treat f(f(n-3)) as f(n-3)+f(n-3) or as 2f(n-3). In this case the question becomes very similar to this one: Space complexity of recursive function
I tried solving it this way and i got the correct answer.
For the time complexity:
T(n)=2T(n-3)+1 , T(0)=1
T(n-3)=2T(n-3*2)+1
T(n)=2*2T(n-3*2)+2+1
T(n-3*2)=2T(n-3*3)+1
T(n)=2*2*2T(n-3*3)+2*2+2+1
...
T(n)=(2^k)T(n-3*k)+2^(k-1)+...+2^2+2+1
n-3*k=0
k=n/3
===> 2^(n/3)+...+2^2+2+1=2^(n/3)[1+(1/2)+(1/2^2)+...]=2^(n/3)*constant
Thus I got O(2^(n/3))
For the space complexity: the tree depth is n/3 and each time we do malloc so we get (n/3)^2 thus O(n^2).
My question:
why can we treat f1(f1(n – 3)) as f1(n-3)+f1(n-3) or as 2f1(n-3)?
if the function didn't return n but changed it, for example: return n/3 instead of return n, then how do we solve it? do we treat it as 2f1((n-3)/3)?
if we can't always treat f1(f1(n – 3)) as f1(n-3)+f1(n-3) or as 2f1(n-3) then how do we draw the recursion tree and how do we write and solve it using the induction method T(n)?
Why can we treat f1(f1(n – 3)) as f1(n-3)+f1(n-3) or as 2f1(n-3)?
Because i) the nested f1 is evaluated first, and its return value is used to call the outer f1; so these nested calls are equivalent to:
int result = f1(n - 3);
f1(result);
... and ii) the return value of f1 is just its argument (except for the base case, but it doesn't matter asymptotically), so the above is further equivalent to:
f1(n - 3);
f1(n - 3); // result = n - 3
If the function didn't return n but changed it, for example: return n/3 instead of return n, then how do we solve it? do we treat it as 2f1((n-3)/3)?
Only the outer call is affected. Again, using the equivalent expression from before:
f1(n - 3); // = (n - 3) / 3
f1((n - 3) / 3);
i.e. just f1(n - 3) + f1((n - 3) / 3) for your example.
If we can't always treat f1(f1(n – 3)) as f1(n-3)+f1(n-3) or as 2f1(n-3) then how do we draw the recursion tree and how do we write and solve it using the induction method T(n)?
You can always separate them into two separate calls as above, and again remember that only the second call is affected by the return result. If this is different to n - 3 then you would need a recursion tree instead of simple expansion. Depends on the specific problem, needless to say.
XQuery
Input: (1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28)
Output: (1,7,14,17,24,28)
I tried to remove consecutive numbers from the input sequence using the XQuery functions but failed doing so
xquery version "1.0" encoding "utf-8";
declare namespace ns1="http://www.somenamespace.org/types";
declare variable $request as xs:integer* external;
declare function local:func($reqSequence as xs:integer*) as xs:integer* {
let $nonRepeatSeq := for $count in (1 to count($reqSequence)) return
if ($reqSequence[$count+1] - $reqSequence) then
remove($reqSequence,$count+1)
else ()
return
$nonRepeatSeq
};
local:func((1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28))
Please suggest how to do so in XQuery functional language.
Two simple ways to do this in XQuery. Both rely on being able to assign the sequence of values to a variable, so that we can look at pairs of individual members of it when we need to.
First, just iterate over the values and select (a) the first value, (b) any value which is not one greater than its predecessor, and (c) any value which is not one less than its successor. [OP points out that the last value also needs to be included; left as an exercise for the reader. Or see Michael Kay's answer, which provides a terser formulation of the filter; DeMorgan's Law strikes again!]
let $vseq := (1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28)
for $v at $pos in $vseq
return if ($pos eq 1
or $vseq[$pos - 1] ne $v - 1
or $vseq[$pos + 1] ne $v + 1)
then $v
else ()
Or, second, do roughly the same thing in a filter expression:
let $vseq := (1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28)
return $vseq[
for $i in position() return
$i eq 1
or . ne $vseq[$i - 1] + 1
or . ne $vseq[$i + 1] - 1]
The primary difference between these two ways of performing the calculation and your non-working attempt is that they don't say anything about changing or modifying the sequence; they simply specify a new sequence. By using a filter expression, the second formulation makes explicit that the result will be a subsequence of $vseq; the for expression makes no such guarantee in general (although because for each value it returns either the empty sequence or the value itself, we can see that here too the result will be a subsequence: a copy of $vseq from which some values have been omitted.
Many programmers find it difficult to stop thinking in terms of assignment to variables or modification of data structures, but its worth some effort.
[Addendum] I may be overlooking something, but I don't see a way to express this calculation in pure XPath 2.0, since XPath 2.0 seems not to have any mechanism that can bind a variable like $vseq to a non-singleton sequence of values. (XPath 3.0 has let expressions, so it's not a challenge there. The second formulation above is itself pure XPath 3.0.)
In XSLT this can be done as:
<xsl:for-each-group select="$in" group-adjacent=". - position()">
<xsl:sequence select="current-group()[1], current-group()[last()]"/>
</xsl:for-each-group>
In XQuery 3.0 you can do it with tumbling windows, but I'm too lazy to work out the detail.
An XPath 2.0 solution (assuming the input sequence is in $in) is:
for $i in 1 to count($in)
return $in[$i][not(. eq $in[$i - 1]+1 and . eq $in[$i+1]-1)]
There are several logic and XQuery usage errors in your solution, but the main problem with it is that variables in XQuery are immutable, so you cannot reassign a value to one once assigned. Therefore, it's often easier to think about these types of problems in terms of recursive solutions:
declare function local:non-consec(
$prev as xs:integer?,
$rest as xs:integer*
) as xs:integer*
{
if (empty($rest)) then ()
else
let $curr := head($rest)
let $next := subsequence($rest, 2, 1)
return (
if ($prev eq $curr - 1 and $curr eq $next - 1)
then () (: This number is part of a consecutive sequence :)
else $curr,
local:non-consec(head($rest), tail($rest))
)
};
local:non-consec((), (1,2,3,4,5,6,7,14,15,16,17,24,25,26,27,28))
=>
1
7
14
17
24
28
Maybe this question does not belong as this is not a programming question per se, and i do apologize if this is the case.
I just had an exam in abstract data structures, and there was this question:
the rank of a tree node is defined like this: if you are the root of the tree, your rank is 0. Otherwise, your rank is the rank of your parents + 1.
Design an algorithm that calculates the sum of the ranks of all nodes in a binary tree. What is the runtime of your algorithm?
My answer I believe solves this question, my psuedo-code is as such:
int sum_of_tree_ranks(tree node x)
{
if x is a leaf return rank(x)
else, return sum_of_tree_ranks(x->left_child)+sum_of_tree_ranks(x->right_child)+rank(x)
}
where the function rank is
int rank(tree node x)
{
if x->parent=null return 0
else return 1+rank(x->parent)
}
it's very simple, the sum of ranks of a tree is the sum of the left subtree+sum of the right subtree + rank of the root.
The runtime of this algorithm I believe is n^2. i believe this is the case because we were not given the binary tree is balanced. it could be that there are n numbers in the tree but also n different "levels", as in, the tree looks like a linked list rather than a tree. so to calculate the rank of a leaf, potentially we go n steps up. the father of the leaf will be n-1 steps up etc...so thats n+(n-1)+(n-2)+...+1+0=O(n^2)
My question is, is this correct? does my algorithm solve the problem? is my analysis of the runtime correct? and most importantly, is there a better solution to solve this, that does not run in n^2?
Your algorithm works. your analysis is correct. The problem can be solved in O(n) time: (take care of leaves by yourself)
int rank(tree node x, int r)
{
if x is a leaf return r
else
return rank(x->left_child, r + 1)+ ranks(x->right_child, r + 1) + r
}
rank(tree->root, 0)
You're right but there is an O(n) solution providing you can use a more "complex" data structure.
Let each node hold its rank and update the ranks whenever you add/remove, that way you can use the O(1) statement:
return 1 + node->left.rank + node->right.rank;
and do this for each node on the tree to achieve O(n).
A thumb rule for reducing Complexity time is: if you can complex the data structure and add features to adapt it to your problem, you can reduce Complexity time to O(n) most of the times.
It can be solved in O(n) time where n is number of Nodes in Binary tree .
It's nothing but sum of height of all nodes where height of root node is zero .
As
Algorithm:
Input binary tree with left and right child
sum=0;
output sum
PrintSumOfrank(root,sum):
if(root==NULL) return 0;
return PrintSumOfrank(root->lchild,sum+1)+PrintSumOfRank(root->Rchild,sum+1)+sum;
Edit:
This can be also solved using queue or level order of traversal tree.
Algorithm using Queue:
int sum=0;
int currentHeight=0;
Node *T;
Node *t1;
if(T!=NULL)
enque(T);
while(Q is not empty) begin
currentHeight:currentHeight+1 ;
for each nodes in Q do
t1 = deque();
if(t1->lchild!=NULL)begin
enque(t1->lchild);sum = sum+currentHeight;
end if
if(t1->rchild!=NULL)begin
enque(t1->rchild);sum = sum+currentHeight;
end if
end for
end while
print sum ;
I got this question in an interview. So, this seems to me a messed up Fibonacci seq. sum generator and this gives a stackoverflow. Because if(n==0) should be if(n<3) (exit condition is wrong). What should be the precise answer to this question? What was expected as an answer?
foo (int n) {
if (n == 0) return 1;
return foo(n-1) + foo(n-2);
}
UPDATE
What does this recursive function do?
What do you infer when you see these 3 lines of code. No debugging.
Yes, it's just a recursive Fibonacci method with an incorrect base case (although I don't think I'd use n < 3 either... it depends on how you define it).
As for "what was expected as an answer" - that would depend on the question. Were you asked exactly the question in the title of your post? If so, the answer is "it recurses forever until it blows up the stack when you pass it any value other than 0" - with an explanation, of course. (It will never end because either n-1 isn't 0, or n-2 isn't 0, or both.)
EDIT: The above answers the first question. To answer "What do you infer when you see these 3 lines of code" - I would infer that the developer in question has never run the code with a value other than 0, or doesn't care about whether or not the code works.
The problem with the code posted is that if we evaluate foo(1) we need to find foo(0) and foo (-1), foo(-1) then needs to find foo(-2) and foo(-3) and so on. This will keep putting more calls to foo() until there is no more space in the memory resulting in a stack overflow. How many calls to foo are made will depend on the size of the call stack, which will be implementation specific.
When I see these lines of code I immediately get the impression that whoever wrote it hasn't thought about all the possible inputs that could be fed to the function.
To make a recursive Fibonacci function that doesn't fail for foo(1) or a negative input we get:
foo (int n) {
if( n < 0 ) return 0;
if (n == 0) return 1;
return foo(n-1) + foo(n-2);
}
Edit: perhaps the return for a negative number should be something else, as the fibonacci sequence isn't implicitly defined for negative indexes.
However if we use the extension that fib(-n) = (-1)^(n+1) fib(n) we could get the following code:
int fib(int n) {
if( n == -1){
return 1;
}else if ( n < 0 ){
return ( (-1)^(-n+1 ) * fib(-n) );
}else if (n == 0){
return 1;
}else{
return fib(n-1) + fib(n-2);
}
}
suppose, you call foo ( 1 ), it will have two sub calls of foo(0) and foo(-1). As you can see, once you get to foo(-1), n will decrease indefinitely and will never reach a terminating condition.
The precise answer would be:
foo (int n) {
if (n <= 1) return 1; // assuming 0th and 1st fib is 1
return foo(n-1) + foo(n-2);
}
Its a recursive fibonacci function with 0th element being 1.
Ignoring the incorrect termination condition, the code is the "naive" recursive implementation of the Fibonacci function. It has very poor big-O time complexity. It would work fine for small n, but if n is greater than say 50 (I'm just guessing that number), then it would take "forever" to run.
A theoretical question here about the base or halting case in a recursive method, what's its standards?
I mean, is it normal not to have body in it, just a return statement?
Is it always like the following:
if (input operation value)
return sth;
Do you have different thoughts about it?
The pattern for recursive functions is that they look something like this:
f( value )
if ( test value )
return value
else
return f( simplify value )
I don't think you can say much more than that about general cases.
The base case is to terminate the loop (avoid becoming an infinite recursion). There's no standard in the base case, any input that is simple enough to be solved exactly can be chosen as one.
For example, this is perfectly valid:
int factorial (int n) {
if (n <= 5) {
// Not just a return statement
int x = 1;
while (n > 0) {
x *= n;
-- n;
}
return x;
} else {
return n * factorial(n-1);
}
}
In some cases, your base case is
return literal
In some cases, your base case is not simply "return a literal".
There cannot be a "standard" -- it depends on your function.
The "Syracuse Function" http://en.wikipedia.org/wiki/Collatz_conjecture for example,
doesn't have a trivial base case or a trivial literal value.
"Do you have different thoughts about it??" Isn't really a sensible question.
The recursion has to terminate, that's all. A trivial tail recursion may have a "base case" that returns a literal, or it may be a calculation. A more complex recursion may not have a trivial "base case".
It depends entirely on the particular recursive function. In general, it can't be an empty return; statement, though - for any recursive function that returns a value, the base case should also return a value of that type, since func(base) is also a perfectly valid call. For example, a recursive factorial function would return a 1 as the base value.