The Great Tree-List Recursion Problem (2000)
cslibrary.stanford.edu
cslibrary.stanford.edu
class Linked:
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
def tree_to_list(self):
if self.left is not None:
first, left = self.left.tree_to_list()
left.right = self
self.left = left
else:
first = self
if self.right is not None:
right, last = self.right.tree_to_list()
right.left = self
self.right = right
else:
last = self
first.left = last
last.right = first
return first, last-
Convert a sorted doubly linked list into a BST
C++: http://elementsofprogramminginterviews.com/solutions/cpp/sor...
Java: http://elementsofprogramminginterviews.com/solutions/java/So...
Python: http://elementsofprogramminginterviews.com/solutions/python/...
-
Convert a BST to a sorted doubly linked list
C++: http://elementsofprogramminginterviews.com/solutions/cpp/bst...
Java: http://elementsofprogramminginterviews.com/solutions/java/BS...
Python: http://elementsofprogramminginterviews.com/solutions/python/...
With a singly-linked list, it's much harder; I don't know (without googling) if a more efficient than "build and rebalance as you go" algorithm exists.
You could even do this in place without increasing the size of each node at the cost of doubling the size of variables that point to nodes and slightly lowing down stepping through the list. You do this by storing the next and previous pointers for each node in the same location, xor'ed together.
That doubles the size of variables that point to nodes and slows down stepping because you have to turn them into structs that store both a pointer to the node and a pointer to an adjacent node and update both of these pointers as you move through the list.
This assumes of course that the list is sorted. If not, you should probably sort the list first to avoid driving yourself insane.
If memory is too tight to make temporary storage and the list is too big to sort and you want a perfectly balanced tree then you've got an interesting problem on your hands.
EDIT: demonstration:
sortList [] = []
sortList [x] = [x]
sortList xs = merge (sortList as) (sortList bs)
where
(as, bs) = split xs
split [] = ([], [])
split [x] = ([x], [])
split (x:y:rest) = let (xs, ys) = split rest in (x:xs, y:ys)
merge xs [] = xs
merge [] ys = ys
merge (x:xs') ys@(y:_) | x <= y = x : merge xs' ys
merge xs (y:ys) = y : merge xs ys
data Tree a = Empty | Node a (Tree a) (Tree a) deriving(Show)
balancedTree xs = mktree (length xs) xs
where
mktree 0 [] = Empty
mktree n xs = Node x (mktree n' less) (mktree (n - 1 - n') more)
where
n' = (n - 1) `div` 2
(less, (x:more)) = splitAt n' xs
listToBalancedTree :: Ord a => [a] -> Tree a
listToBalancedTree = balancedTree . sortList> [F]ind middle, use it as root, recursively build two subtrees.
That's not DSW. DSW should be way more efficient.
> With a singly-linked list, it's much harder; I don't know [...] if a more efficient [...] algorithm exists.
This is DSW. DSW starts with a sorted linked list--called a "vine"--and builds the tree from that, in place. The vine is a singly linked list in the sense that you only need the successor pointer (though each node obviously needs space for a predecessor pointer since it's being turned into a tree).
The algorithm is O(n) time and O(1) space and is actually very elegant. Essentially, you treat the list as a completely unbalanced tree skewed to the right. Then you repeatedly apply a `compress` routine that scans down the far right path in the tree to balance it.
From Wikipedia
routine vine-to-tree(root, size)
leaves ← size + 1 − 2^⌊log2(size + 1))⌋
compress(root, leaves)
size ← size − leaves
while size > 1
compress(root, ⌊size / 2⌋)
size ← ⌊size / 2⌋
routine compress(root, count)
scanner ← root
for i ← 1 to count
child ← scanner.right
scanner.right ← child.right
scanner ← scanner.right
child.right ← scanner.left
scanner.left ← child
Both the naive algorithm and DSW are O(n), but the naive algorithm has a time complexity of about 2n while DSW has a time complexity of about 0.63n, assuming we start with a vine.For example, here is a Prolog definition of the tree from the article:
tree(node(4,
node(2,
node(1,nil,nil),
node(3,nil,nil)),
node(5,nil,nil))).
I am using terms of the form nil/0 and node/3 to represent the tree.Let us start by converting the tree to a list. As requested, we shall consider the in-order traversal of the tree elements, described as a list. For such situations, Prolog DCG notation is very convenient, since it easily allows us to describe a list in relation to other terms (in this case, a tree):
tree_to_list(nil) --> [].
tree_to_list(node(E,Left,Right)) -->
tree_to_list(Left),
[E],
tree_to_list(Right).
Here is a sample query and its result: ?- tree(T), phrase(tree_to_list(T), Ls).
T = node(4, ...),
Ls = [1, 2, 3, 4, 5].
OK, so we now have the in-order traversal. Of course, lists are already implicitly "linked" in Prolog in one direction: Given a list, we can readily reason about the first element and the remaining elements. The task requires us to do more, i.e., reason about the reverse direction as well.One way to express this is to use rational trees, also known as cyclic terms in Prolog. For example, here is a case of cyclic term:
?- append([a,b,c], Ls, Ls).
Ls = [a, b, c|Ls].
Here, Ls represents the circular list [a,b,c,a,b,c,a,...].We can use this feature to our advantage as follows:
list_with_backlinks([], []).
list_with_backlinks([L|Ls0], [L-Prev|Ls]) :-
append([L|Ls], Cs, Cs),
foldl(with_backlink, Ls0, Ls, Cs, Prev).
with_backlink(E, E-Cs0, Cs0, Cs) :- Cs0 = [_|Cs].
Sample query and answer: ?- list_with_backlinks([a,b,c], Cs).
Cs = [a-[_S2|_S1], b-_S1, _S2], % where
_S1 = [a, b-_S1, _S2|_S1],
_S2 = c-[b-_S1, _S2|_S1] .
Thus, as desired, we can generate a doubly-linked list from the original tree: ?- tree(T), phrase(tree_to_list(T), Ls0),
list_with_backlinks(Ls0, Ls).
T = node(4, ...),
Ls0 = [1, 2, 3, 4, 5],
Ls = [1-[_S4|_S1], 2-_S1, _S2, _S3, _S4], % where
_S1 = [1, 2-_S1, _S2, _S3, _S4|_S1],
_S2 = 3-[2-_S1, _S2, _S3, _S4|_S1],
_S3 = 4-[_S2, _S3, _S4|_S1],
_S4 = 5-[_S3, _S4|_S1] .
Of course, this does not completely solve the task, which requires in-place modifications of the original tree. Pure Prolog lacks destructive modifications, so we cannot fully translate the code to Prolog. On the plus side, no pointers at all were necessary, and the recursions are pretty straight-forward.Also the C answer is missing a brace at the end of treeToList and that makes me a little crazy
Even a naive approach would work balanced or unbalanced and that would be simply to pop the lowest value in the tree and construct your list until you run out of nodes.
The trick is to do it in O(n) time and use recursion.
EDIT: Never expected this to devolve into an argument about the merits of gotos! I thought we were all past that, but apparently not.
http://www.u.arizona.edu/~rubinson/copyright_violations/Go_T...
All the arguments it makes no longer apply but people still quote the snappy headline.
That essay does make an excellent argument against programming with many tiny functions though, as those do cause the execution cursor to jump all over the source code document.
Nothing. But the thing they want to do -- altering the datastructure on the fly -- is not the best way to solve the problem.
I would solve this by writing two functions: one that creates an array from a binary tree, and one that creates a doubly-linked list from an array.
Would this use up more RAM and take longer to run? Yes
Would the code be easier to understand (and therefore more bug-free)? Yes
Many programmers treat 'goto' the same way that we treat radioactive waste, but they don't know why. Perhaps it is a holdover from courses with teachers who automatically graded 'fail' on programs that used a goto. Perhaps the programmers have only heard that gotos were bad.
Sometimes 'goto' is the correct answer. For state machines it can be an pretty efficient technique. For "ordinary" code it is sometimes the best way out of a situation (e.g., busting out of a deeply nested set of loops).
The "Goto considered harmful" movement came out of the 1970s, when GOTO was used all the time and the normal state for a big project was abject spaghetti. I think we can all agree that was a very bad thing. But the utter avoidance of goto is an overreaction that seems to be based on unreasoning fear, or toxic peer pressure.
so, goto is efficient at escaping inefficiently structured code? That doesn't make it really efficient. I mean there's a reasonable rule of thumb: don't indent the code too deep. Because what you are talking about is one kind of spaghetti, the kind cut into digestable chunks. The interleaving of active and inactive blocks leads to unreadable code sooner or later.
Of course this type of dogmatism gives us FP gems akin to [eating] a piece of Sushi with a steak knife.
I suggest reading the actual "goto" paper sometime… it's a classic.
But I do think the while+switch representation is superior.
state = START;
break;
Translates directly to: goto start;
Except while+switch is nicer if you have transition tables. YMMVBut it does make for the same exact behavior.
It might not be called goto, but isn't setjmp/longjmp essentially the same?
You can do some dangerous things with longjmp to implement coroutines, but it's horribly non-portable.
Writing state machines is one of the moments when the 1d top-down method of programming really hurts. I feel like I can easily understand an FSM drawing, but splay it out in code and it's difficult to track.
Functional programming as a paradigm makes sense when you want to limit the context a programmer has to be aware of when working on a problem, but in this case recursion and imperative programming accomplish the same goal.