converting recursion to tail recursion

Viewed 155

I was reading on converting recursive algorithms to iterative algorithms. I came across a blog-post http://blog.moertel.com/posts/2013-05-11-recursive-to-iterative.html explaining the procedure to convert recursive algorithms first to tail recursive algorithms and then convert tail recursive to iterative one. In the post, it is explained that when we want to convert a recursive algorithm to a tail recursive one, we should first understand what is going on between the return of the recursive call and the return statement of the calling function. Once that is done, we should try to add a secret feature/ accumulator parameters to the recursive function and then decide what to return. I have followed the concept for the examples given in the blog-post but I am not able solve the exercise given at the end of the blog. I am not able to decide what should my accumulator parameter be? How should I make decisions based upon that accumulator parameter. I don't want a solution but some pointers regarding how should I think to solve this problem. Here is the exercise code:

def find_val_or_next_smallest(bst, x):
    """Get the greatest value <= x in a binary search tree.

    Returns None if no such value can be found.

"""
    if bst is None:
        return None
    elif bst.val == x:
        return x
    elif bst.val > x:
        return find_val_or_next_smallest(bst.left, x)
    else:
        right_best = find_val_or_next_smallest(bst.right, x)
        if right_best is None:
            return bst.val
        return right_best 

Thanks in advance!

1 Answers

I post this to replace my comments from yesterday and to show the code.

In a recursive algorithm each call creates a stack frame containing the functions's local variables and the passed arguments. All stack frames together hold some kind of state information. When you are going to avoid the recursion, there will be no additional stack frames. The important part of the data must be therefore maintained in the non-recursive function.

Now to the code. I tried to closely follow the instructions.

This is the original source. I just omitted the docstring and replaced elifs occuring immediately after return by ifs (just a matter of preferred style).

def find_val_or_next_smallest1(bst, x):
    if bst is None:
        return None
    if bst.val == x:
        return x
    if bst.val > x:
        return find_val_or_next_smallest1(bst.left, x)
    else:
        right_best = find_val_or_next_smallest1(bst.right, x)
        if right_best is None:
            return bst.val
        return right_best

Now to the tail recursion. There are four branches. Two non-recursive, one already tail recursive and the fourth needs rewriting:

    right_best = find_val_or_next_smallest1(bst.right, x)
    if right_best is None:
        return bst.val
    return right_best

This branch chooses as the result either the bst.val or the result of the call, whichever is better. The call must be done last, so the bst.val simply must be passed to it. The function gets a new parameter with the meaning "return this if you do not find anything better". Before the change it was "return None if you do not find a anything". So we just have to replace the None value. I call the new parameter found, because it is what we have found so far.

def find_val_or_next_smallest2(bst, x, found=None):
    if bst is None:
        return found
    if bst.val == x:
        return x
    if bst.val > x:
        return find_val_or_next_smallest2(bst.left, x, found)
    else:
        return find_val_or_next_smallest2(bst.right, x, found=bst.val)

Straightforward conversion as in the blog:

def find_val_or_next_smallest3(bst, x, found=None):
    while True:
        if bst is None:
            return found
        if bst.val == x:
            return x
        if bst.val > x:
            bst, x, found =  bst.left, x, found
        else:
            bst, x, found =  bst.right, x, bst.val

and cleanup:

def find_val_or_next_smallest4(bst, x):
    found=None
    while True:
        if bst is None:
            return found
        if bst.val == x:
            return x
        if bst.val > x:
            bst = bst.left
        else:
            bst, found = bst.right, bst.val
Related