I am working on this problem that was asked by Google (not to me):
Given the root to a binary tree, implement serialize(root), which
serializes the tree into a string, and deserialize(s), which
deserializes the string back into the tree.
This is what I have so far, but I cannot seem to make the function serialize store the results (from bottom of the tree and up) into a string. So I'm able to print the results, just not store it...
class Tree:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
self.ser_str = None
def insert(self, data):
if self.data:
if data < self.data:
if self.left is None:
self.left = Tree(data)
else:
self.left.insert(data)
elif data >= self.data:
if self.right is None:
self.right = Tree(data)
else:
self.right.insert(data)
else:
self.data = data
def serialize(self):
if self.left:
self.left.serialize()
print(self.data)
if self.right:
self.right.serialize()
root = Tree(23)
root.insert(10);root.insert(124);root.insert(101);root.insert(1);root.insert(40)
print("here comes the sun")
test = root.serialize()
As noted in comments, the problem with your method is that you just print the data, but never join it to a string and return it. However, that format would also not allow for an unambiguous deserialization of the tree.
If you do not want to use libraries like json, which make this trivial, you could resort to an easily parseable format like Polish Notation, where a + b is written as + a b. In the tree case, the data corresponds to the operator and the left and right branch to the operands.
def serialize(t):
if t is None:
return "-"
else:
return f"{t.data} {serialize(t.left)} {serialize(t.right)}"
def deserialize(s):
if isinstance(s, str): s = iter(s.split())
# using an iterator of chunks makes this easier
d = next(s)
if d == "-":
return None
else:
return Tree(d, deserialize(s), deserialize(s))
(Note that I made those functions, not methods, and added a few optional parameters to the Tree constructor to make the code simpler.)
When testing with your tree, this serialized the tree to 23 10 1 - - - 124 101 40 - - - -. (I then deserialized the tree and serialized it again and got the same format, so deserialization should work, too.) You can add parens to better see the tree structure in the string: (23 (10 (1 - -) -) (124 (101 (40 - -) -) -)), but the format is unambiguous even without parens.
This basically corresponds to a simple pre-order traversal of the tree, whereas you are doing an in-order traversal, which, like infix-notation a + b, is not unambiguous without parentheses. In-order traversal returns the elements in sorted order, which is nice for some uses, but not here, as it means that differently structured trees holding the same element will serialize to the same sorted list. You could, of course, just add parens, but that will make parsing/deserialization much harder. With parens and in-order, your tree would be (((- 1 -) 10 -) 23 (((- 40 -) 101 -) 124 -)).
(Note: Even if the serialization is ambiguous, you could recreate a binary tree from that, but the form of that tree will be different; in particular, it will be a degenerate binary tree if you just insert the elements in sorted order, as they come out of in your in-order-traversal.)
Related
Returning list inside of list (not desired)
I keep returning a list inside of a list for the following LeetCode Problem 257. Binary Tree Paths, which is not what the problem wants. I always seem to run into this issue while solving traversal problems.
Here is my current solution along with it's output.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def binaryTreePaths(self, root: TreeNode, val: str="") -> List[str]:
if not root:
return ""
val += str(root.val) + "->"
if not root.left and not root.right:
return val[:-2]
return [path for path in [self.binaryTreePaths(root.left, val), self.binaryTreePaths(root.right, val)] if path]
Input: [1,2,3,null,5]
Output: [["1->2->5"],"1->3"]
Expected: ["1->2->5","1->3"]
Things I've tried
Creating a res variable and using res.extend()
res = []
res.extend(path for path in [self.binaryTreePaths(root.left, val), self.binaryTreePaths(root.right, val)] if path)
Using append instead of extend along with conditionals to try and filter out unwanted elements.
I constantly run into this problem while solving traversal problems so if anyone here has a solution along with some general advice on how to develop an intuition on what's going wrong here and how to solve it in the future, I'd be extremely grateful for your help.
Edit
Well I kept working at trying to fix my solution and after about ~20 minutes, I came up with the following abomination.
class Solution:
def binaryTreePaths(self, root: TreeNode, val: str="") -> List[str]:
if not root:
return ""
val += str(root.val) + "->"
if not root.left and not root.right:
return val[:-2]
r1 = self.binaryTreePaths(root.left, val)
r2 = self.binaryTreePaths(root.right, val)
if isinstance(r1, str):
r1 = [r1]
if isinstance(r2, str):
r2 = [r2]
for i in r1:
if i == "":
r1.pop(r1.index(i))
for i in r2:
if i == "":
r2.pop(r2.index(i))
return r1 + r2
I'm by no means satisfied with my solution as is so if you know of a cleaner fix please continue to post your fix/advice.
The root issue is that the binaryTreePaths function returns different types depending on the conditions. If root is falsy or when there is no left or right set, it returns just a string ("" or val[:-2]), but in all other cases it returns a list.
The list is only needed for the first level of regression (when returning the final value back to the caller), but the way it is setup it can also return a list from any level of recursion. So, any recursion that also meets the criteria to return the list comprehension will end up with a list within a list at the lower levels of recursion.
So, when you travel more than one node, it will be encased in a list for each additional node. Thus, "1->3" isn't in a list because it was immediately returned as a string after the first recursion on the right side, but "1->2->5" was in a list because there was one extra level of recursion before the string was returned.
There are multiple ways to resolve this - you could track the level of regression and only respond with left/right strings at any depth above 1, then return the list only from level 1, for example. Or you could have a parent function that handles the first layer and a helper function (perhaps a child function within it) that does the recursion part.
Half the fun is figuring it out - so I leave the code piece to you. Hopefully this gave you to tools to resolve it yourself (per the second half of your question). The key is to construct at each recursion level exactly what the return value is. The best way to do this in real-time is to run your code in debugging mode, putting a breakpoint at the start of the recursive function, and follow the code through, and look at what the actual arguments and return values are as it recurses. Do this a few times and you will start to be able to think your way through what is happening without the debugging.
(edit: another trick you can use is writing out the list comprehension long-form - with for loops and if/else statements. It's not as compact, but usually makes it easier to understand which values get assigned when.)
For this problem, we can use stack. This'd get accepted:
class Solution:
def binaryTreePaths(self, root):
if not root:
return []
paths = []
stack = [(root, '')]
while stack:
node, path = stack.pop()
if not node.left and not node.right:
paths.append(f'{path}{node.val}')
if node.right:
stack.append((node.right, f'{path}{node.val}->'))
if node.left:
stack.append((node.left, f'{path}{node.val}->'))
return paths
Here is also LeetCode's recursive solution:
class Solution:
def binaryTreePaths(self, root):
"""
:type root: TreeNode
:rtype: List[str]
"""
def construct_paths(root, path):
if root:
path += str(root.val)
if not root.left and not root.right: # if reach a leaf
paths.append(path) # update paths
else:
path += '->' # extend the current path
construct_paths(root.left, path)
construct_paths(root.right, path)
paths = []
construct_paths(root, '')
return paths
References
For additional details, you can see the Discussion Board. There are plenty of accepted solutions with a variety of languages and explanations, efficient algorithms, as well as asymptotic time/space complexity analysis1, 2 in there.
I need to write a recursion for a min-heap binary tree to check if this tree is min-heap. One of the test cases is just NONE.
Is None considered a min-heap tree and returns True, or None is False?
The reason I am asking is that I will reach leaves at some point and their nodes are None and if base case is True then it will return True.
I believe that a none type will be vacuously true as it does not violate the definition of a min-heap tree.
Yes, None is considered a mean-heap tree.
You must mean a Min Heap. When we are dealing with any tree structure the children of a Node are most commonly initialized as None. One of the reasons is that we can easily escape the recursion as such:
def find_node(node, data):
if root is None:
return
if root.data == data:
print "Node found"
find_node(node.left, data)
find_node(node.right, data)
class Node(object):
def __init__(self, data):
self.left = None
self.right = None
self.data = data
In your case you want to check if a tree is min heap by traversing it. You would do something like that
def is_min_heap(root):
#.....check here and then do
return is_min_heap(root.left) and is_min_heap(root.right)
But it depends how you want to handle it. Any one node with no children is a min-heap or a max-heap but it has no meaning. If you want to call
is_min_heap(None) then you are free to do so but it is up to you if you want to say that is True or not.
This question is for school (homework) so I am not asking for code, and I don't want any, just an idea. I have to write a function that returns two lists, a list of the leaves and a list of the internal nodes of a binary tree. My algorithm is:
1) If both the left and the right subtrees are None, it is a leaf, and so I add it to the leaves list.
2) If they are not, then I add it to the internals list, and call the function on the left subtree, and then on the right, if they exist.
This is the code I have written:
def leaves_and_internals(self):
leaves = []
internals = []
if self.left is None and self.right is None:
leaves.append(self.item)
else:
internals.append(self.item)
if self.left != None:
leaves_and_internals(self.left)
else:
leaves_and_internals(self.right)
return internals, leaves
I'm pretty sure that the algorithm is correct, but I think that every time I recurse on the Nodes, the lists will get reset. How can I get around this?
Any help is greatly appreciated. Thanks
I have not looked into the algorithm of your code, and just merely suggesting an answer to the problem you're stuck at. You could pass leaves and internals as arguments to the recursive function, so that their contents get retained across the recursive calls.
In python, if you pass a mutable object to a function/method, the function/method gets a reference to the object. So as long as you still treat it as the same mutable object (i.e. not assign the parameter with something else directly), any changes you make to the object are also visible to the caller. Since list is a mutable type, this behavior is very much helpful for the case you're interested in.
And make sure to initialize the lists to [] before calling the leaves_and_internals function from outside.
def leaves_and_internals(self, leaves, internals):
if self.left is None and self.right is None:
leaves.append(self.item)
else:
internals.append(self.item)
if self.left != None:
leaves_and_internals(self.left, leaves, internals)
else:
leaves_and_internals(self.right, leaves, internals)
return
# Somewhere outside
leaves = []
internals = []
myobj.leaves_and_internals(leaves, internals)
UPDATE:
Since the OP mentions he cannot change the signature of the method nor use instance variables, this is an alternate solution I can think of which returns the leaves and internals to the caller. BTW, I assume some nodes in your tree can have both left and right, so you would need to check both (i.e. use 2 separate if instead of an if...else).
def leaves_and_internals(self):
leaves = []
internals = []
if self.left is None and self.right is None:
leaves = [ self.item ]
else:
if self.left != None:
leaves, internals = leaves_and_internals(self.left)
if self.right != None:
templeaves, tempinternals = leaves_and_internals(self.right)
leaves += templeaves
internals += tempinternals
internals.append(self.item)
return leaves, internals
I am creating a doubly linked structure and am having some issues with comparing if two nodes are equal. The structure is fairly complex in that it has multiple attributes including name, row, column, right, left, up, and down. If two nodes are equal they must agree on all of these attributes. I know in my eq method I could simply hard code checking each attribute versus the other but I figured there would be an easier way to do it and found a way that works most of the time. Thus I have the following:
def __init__ (self,row,col,name=None,up=None,down=None,left=None,right=None):
self.name = name
self.row = row
self.col = col
self.up = up
self.down = down
self.left = left
self.right = right
def __eq__ (self, other):
return vars(self) == vars(other)
And various other methods that aren't really important to this. So my shortcut for determining whether two Nodes was to basically look at the dictionary of their variables and let python compare the two dictionaries for equivalence.
This works great! As long as the two nodes are actually equal. It returns True and I go on my merry way with my code. BUT if the two nodes are actually not equal it falls apart. I get
File "*filename*", line 35 in __eq__ return vars(self) == vars(self)
written to the screen numerous amounts of times until it finally says
RuntimeError: maximum recursion depth exceeded
I know there are some ways around this, i.e. I could explicitly check each attribute, but that's lame and I want to know why this isn't working, and if it can be easily fixed. I have tested this method with other simpler dictionaries and it works so my thought is that the issue has something to do with determining if objects are equal but I have no idea what I could do here. I realize I could also just do a error catch and then make that return False but something other than those two solutions would be appreciated,
It looks like your up, down, etc are pointing to other instances of your class.
Your comparison code is basically saying, to test if self == other, does self.up == other.up? does self.up.up == other.up.up? etc. And then recursing until it runs out of space.
You may instead want to use
def __eq__(self, other):
return self.name == other.name \
and self.row == other.row \
and self.col == other.col \
and self.up is other.up \
and self.down is other.down \
and self.left is other.left \
and self.right is other.right
I have no python at hand, but I guess this is what happens:
in the __dict__ of self and in the __dict__ of other is areference to one of your nodes
now this node is compared for equality (once the one from vars, once the one from other), this causes your comparison method to be called.
If you now have a loop (e.g common parent) you get infinite recursion:
in original comparison:
compare self.parent to other.parent
in parent comparison:
compare self.parent.child to other.parent.child
(parent and child refer to your up and down)
try(untested):
def __eq__(self, other):
for s, o in zip(vars(self),vars(other)):
if not s is o and s != o:
return False
return True
basically what Hugh Bothwell suggested, just in a loop. First check if you have the same object in memory, if so don't compare them, otherwise test.
how do i represent binary search trees in python?
class Node(object):
def __init__(self, payload):
self.payload = payload
self.left = self.right = 0
# this concludes the "how to represent" asked in the question. Once you
# represent a BST tree like this, you can of course add a variety of
# methods to modify it, "walk" over it, and so forth, such as:
def insert(self, othernode):
"Insert Node `othernode` under Node `self`."
if self.payload <= othernode.payload:
if self.left: self.left.insert(othernode)
else: self.left = othernode
else:
if self.right: self.right.insert(othernode)
else: self.right = othernode
def inorderwalk(self):
"Yield this Node and all under it in increasing-payload order."
if self.left:
for x in self.left.inorderwalk(): yield x
yield self
if self.right:
for x in self.right.inorderwalk(): yield x
def sillywalk(self):
"Tiny, silly subset of `inorderwalk` functionality as requested."
if self.left:
self.left.sillywalk()
print(self.payload)
if self.right:
self.right.sillywalk()
etc, etc -- basically like in any other language which uses references rather than pointers (such as Java, C#, etc).
Edit:
Of course, the very existence of sillywalk is silly indeed, because exactly the same functionality is a singe-liner external snippet on top of the walk method:
for x in tree.walk(): print(x.payload)
and with walk you can obtain just about any other functionality on the nodes-in-order stream, while, with sillywalk, you can obtain just about diddly-squat. But, hey, the OP says yield is "intimidating" (I wonder how many of Python 2.6's other 30 keywords deserve such scare words in the OP's judgment?-) so I'm hoping print isn't!
This is all completely beyond the actual question, on representing BSTs: that question is entirely answered in the __init__ -- a payload attribute to hold the node's payload, left and right attribute to hold either None (meaning, this node has no descendants on that side) or a Node (the top of the sub-tree of descendants on the appropriate side). Of course, the BST constraint is that every left descendant of each node (if any) has a payload less or equal than that of the node in question, every right one (again, if any) has a greater payload -- I added insert just to show how trivial it is to maintain that constraint, walk (and now sillywalk) to show how trivial it is to get all nodes in increasing order of payloads. Again, the general idea is just identical to the way you'd represent a BST in any language which uses references rather than pointers, like, for example, C# and Java.