Good day.
I have a problem implementing a depth first search based on a Strategy, which is defined in a strategy.py class. There is also a graph and a traversal class. The traversal class is responsible for well, traversing the graph.
The strategy class is as follows:
class Strategy:
init_priority = 0
def __init__(self, init_pri = 0):
self.init_priority = init_pri
def init(self, graph, node):
"""Called at beginning of traversal process. Expected that
this will carry out any necessary initialisation for the
specific traversal process
"""
pass
def visit(self, node, pri):
"""Called whenever NODE is visited by a traversal process.
PRI is the priority associated with the node in the priority
queue used by the traversal process.
"""
pass
def complete(self, node):
"""Called at the end of all the processing performed in visiting NODE.
"""
pass
def discover(self, nbr, node, weight, pri):
"""Return the priority that should be associated with NBR when it is
added to the priority queue.
Called whenever NBR is discovered for the first time. NODE
is the node from which the neighbour was discovered, and
WEIGHT is the value on the edge from NODE to NBR. PRI is the
value associated with NODE in the priority queue, at the time
of discovering NBR.
"""
def rediscover(self, nbr, node, weight, pri):
"""Return the priority that should be associated with NBR when it is
added to the priority queue.
Called whenever NBR is rediscovered. NODE is the node from
which the neighbour is rediscovered, and WEIGHT is the value
associated with the edge from NODE to NBR. PRI is the
priority of NODE in the priority queue. It is provided in
case it is relevant to the traversal strategy (e.g. for Dijkstra's)
"""
pass
def getResult(self):
"""Called at the end of the traversal process. It should
return whatever is relevant or appropriate for the type of
traversal implemented by this strategy.
"""
pass
I managed to implement a breadth first search as follows:
class BreadthFirst(Strategy):
sequence = None # the sequence in which nodes are visted
treeEdges = None # the edges used to visit the nodes traversed
root = -1 # the origin of the traversal
last_pri = -1 # the most recent priority used
def __init__(self):
"""The BreadthFirst strategy uses an initial priority of 0"""
Strategy(0)
def init(self, graph, node):
"""We reset all our state information so that old traversals do not
affect the one that is about to start."""
self.last_pri = self.init_priority
self.treeEdges = []
self.sequence = []
self.root = -1
def visit(self, node, src, pri):
"""Breadth first traversal pays no attention to weights."""
self.sequence.append(node)
if src == -1:
self.root = node
else:
self.treeEdges.append((src, node))
def complete(self, node):
pass
def discover(self, nbr, node, pri):
"""Want FIFO behaviour so increment priority (ignore weights)"""
self.last_pri += 1
return self.last_pri
def rediscover(self, nbr, node, pri):
"""Rules for rediscovery same as for discovery (because weights are
ignored)"""
self.last_pri += 1
return self.last_pri
def getResult(self):
"""Return the details of the traversal as a dictionary."""
return {"origin":self.root,
"tree":self.treeEdges,
"sequence":self.sequence}
Depth first is giving me a hassle of a time though. Here's what I have so far:
class DepthFirst(Strategy):
forward = None # the forward sequence in which nodes are visted
back = None # the backward sequence in which nodes are visited
treeEdges = None # the edges used to visit the nodes traversed
cross = None
root = -1 # the origin of the traversal
last_pri = -1 # the most recent priority used
def __init__(self):
"""The DepthFirst strategy uses an initial priority of 0"""
Strategy(0)
def init(self, graph, node):
"""Called at beginning of traversal process. Expected that
this will carry out any necessary initialisation for the
specific traversal process
"""
self.last_pri = self.init_priority
self.treeEdges = []
self.forward = []
self.back = []
self.cross = []
def visit(self, node, src, pri):
"""Called whenever NODE is visited by a traversal process.
PRI is the priority associated with the node in the priority
queue used by the traversal process.
"""
self.forward.append(node)
if src == -1:
self.root = node
else:
self.treeEdges.append((src, node))
def complete(self, node):
"""Called at the end of all the processing performed in visiting NODE.
"""
if node not in self.forward:
self.cross.append(node)
def discover(self, nbr, node, pri):
"""Return the priority that should be associated with NBR when it is
added to the priority queue.
Called whenever NBR is discovered for the first time. NODE
is the node from which the neighbour was discovered, and
WEIGHT is the value on the edge from NODE to NBR. PRI is the
value associated with NODE in the priority queue, at the time
of discovering NBR.
"""
self.forward.append((node, nbr))
self.last_pri -= 1
return self.last_pri
def rediscover(self, nbr, node, pri):
"""Return the priority that should be associated with NBR when it is
added to the priority queue.
Called whenever NBR is rediscovered. NODE is the node from
which the neighbour is rediscovered, and WEIGHT is the value
associated with the edge from NODE to NBR. PRI is the
priority of NODE in the priority queue. It is provided in
case it is relevant to the traversal strategy (e.g. for Dijkstra's)
"""
self.back.append((nbr, node))
self.last_pri -= 1
return self.last_pri
def getResult(self):
"""Called at the end of the traversal process. It should
return whatever is relevant or appropriate for the type of
traversal implemented by this strategy.
"""
return {"tree":self.treeEdges,
"forward":self.forward,
"back":self.back,
"cross":self.cross}
Any tips, pointers? They would be well appreciated.
if you were just writing the two, you'd do the usual iterative loop, using a stack for DFS and a queue for BFS. here you are unifying those with a priority queue. so you need to make the priorities up so that those two behaviours come out. for DFS that means that every time you add something it has higher priority than before (so it comes out before what's already in there) - an increasing positive number is fine. for BFS it needs to be lower than anything you have added so far (so it comes out after what's already in there) - a decreasing negative number works well.
this is just my take from scanning your code. i may be wrong and i'm not going to look in detail - i just thought it was an interesting way of looking at things that might help.
ps it's normal to tag homework with "homework". if you don't, people will bitch.
Related
I am working on Project 0 for Harvard's CS50 Artificial Intelligence class and we had to write a function called shortest_path which is used in the program in finding the shortest path between two actors in a movie database based on shared movies they have worked on with other actors. I did write the function but am getting an infinite loop and can't figure out why I am getting it. I followed the directions from lecture where it says to only add a node to the frontier if the node does not exist in the frontier and also does not exist in previously explored nodes. Overlooking this is something that could lead to an infinite loop but I am still getting one. The test case I used is when the name of the source actor is Kevin and target actor is Kevin. I just pick two different Kevins from the list that's provided after entering Kevin as sources first and targets first name. I am posting my shortest_path function and a link where the rest of the project files can be downloaded with the relevant CSV files for the data being used to test the project.
Link to project files: Download the distribution code from https://cdn.cs50.net/ai/2020/x/projects/0/degrees.zip and unzip it.
The only function we had to modify was the shortest_path function in the distribution code provided so that's all I am posting, along with the classes for the node and Queuefrontier data structure being used. The rest of the code is in the distribution files in case you need to see how it all fits into place.
My code for shortest_path function:
def shortest_path(source, target):
"""
Returns the shortest list of (movie_id, person_id) pairs
that connect the source to the target.
If no possible path, returns None.
"""
path = []
# Keep track of number of states explored
num_explored = 0
# Initialize frontier to just the starting position
start = Node(state=source, parent=None, action=None)
frontier = QueueFrontier()
frontier.add(start)
# Initialize an empty explored set
explored = set()
# Keep looping until solution found
counter = 0
while True:
print(counter)
counter = counter + 1
# If nothing left in frontier, then no path
if frontier.empty():
return None
# Choose a node from the frontier
node = frontier.remove()
num_explored += 1
# If node is the goal, then we have a solution
if node.state == target:
actions = []
cells = []
path=[]
while node.parent is not None:
#actions.append(node.action)
#cells.append(node.state)
path.append(node.action,node.state)
node = node.parent
#actions.reverse()
#cells.reverse()
path.reverse()
#solution = (actions, cells)
return path
# Mark node as explored
explored.add(node.state)
# Add neighbors to frontier
for action, state in neighbors_for_person(node.state):
if not frontier.contains_state(state) and state not in explored:
child = Node(state=state, parent=node, action=action)
frontier.add(child)
Code for Class Node and QueueFrontier:
class Node():
def __init__(self, state, parent, action):
self.state = state
self.parent = parent
self.action = action
def toString(self):
print(f"State={self.state}, Parent={self.parent}")
class StackFrontier():
def __init__(self):
self.frontier = []
def add(self, node):
self.frontier.append(node)
def contains_state(self, state):
return any(node.state == state for node in self.frontier)
def empty(self):
return len(self.frontier) == 0
def remove(self):
if self.empty():
raise Exception("empty frontier")
else:
node = self.frontier[-1]
self.frontier = self.frontier[:-1]
return node
class QueueFrontier(StackFrontier):
def remove(self):
if self.empty():
raise Exception("empty frontier")
else:
node = self.frontier[0]
self.frontier = self.frontier[1:]
return node
I have created a program but I feel the def common(friendships, person1, person2) function can be more efficient, if anyone has any good idea how I can improve it I would appreciate it.
from My_graph import Graph
def friends_of_friends(friendships, person):
"""Return the set of friends of the person's friends.
Don't return people that are already friends of person.
"""
assert person in friendships.nodes()
result = set()
people = friendships.nodes()
for person1 in people:
for person2 in people:
# the () around the whole condition are necessary to
# break the condition over multiple lines
if (friendships.has_edge(person1, person2) and
friendships.has_edge(person2, person) and
not friendships.has_edge(person1, person) and
person1 != person):
result.add(person1)
return result
def common(friendships, person1, person2):
"""Return the number of common friends of person1 and person2."""
assert person1 != person2
assert person1 in friendships.nodes()
assert person2 in friendships.nodes()
mutual = 0
for person in friendships.nodes():
if (friendships.has_edge(person, person1) and
friendships.has_edge(person, person2)):
mutual = mutual + 1
return mutual
def suggested_friends(friendships, person):
"""Return a list of suggested people for person to befriend.
Each suggestion is a friend of a friend of person but isn't person's friend.
The suggestions are ordered from most to fewest mutual friends with person.
"""
assert person in friendships.nodes()
scored_suggestions = []
for friend_of_friend in friends_of_friends(friendships, person):
score = common(friendships, person, friend_of_friend)
scored_suggestions.append((score, friend_of_friend))
scored_suggestions.sort(reverse=True)
suggestions = []
for (score, suggestion) in scored_suggestions:
suggestions.append(suggestion)
return suggestions
class Graph:
# Representation
# --------------
# We use the adjacency list representation, but with sets instead of lists.
# The graph is a dictionary where each key is a node and
# the corresponding value is the set of the node's neighbours.
# The graph being undirected, each edge is represented twice.
# For example, the dictionary {1: {2, 3}, 2: {1}, 3: {1}} represents a
# graph with three nodes and two edges connecting node 1 with the other two.
# Creator
# -------
def __init__(self):
"""Initialise the empty graph."""
self.graph = dict()
# Inspectors
# ----------
def has_node(self, node):
"""Return True if the graph contains the node, otherwise False."""
return node in self.graph
def has_edge(self, node1, node2):
"""Check if there's an edge between the two nodes.
Return False if the edge or either node don't exist, otherwise True.
"""
return self.has_node(node1) and node2 in self.graph[node1]
def neighbours(self, node):
"""Return the set of neighbours of node.
Assume the graph has the node.
"""
assert self.has_node(node)
# copy the set of neighbours, to prevent clients modifying it directly
result = set()
for neighbour in self.graph[node]:
result.add(neighbour)
return result
def nodes(self):
"""Return the set of all nodes in the graph."""
result = set()
for node in self.graph:
result.add(node)
return result
def edges(self):
"""Return the set of all edges in the graph.
An edge is a tuple (node1, node2).
Only one of (node1, node2) and (node2, node1) is included in the set.
"""
result = set()
for node1 in self.nodes():
for node2 in self.nodes():
if self.has_edge(node1, node2):
if (node2, node1) not in result:
result.add((node1, node2))
return result
def __eq__(self, other):
"""Implement == to check two graphs have the same nodes and edges."""
nodes = self.nodes()
if nodes != other.nodes():
return False
for node1 in nodes:
for node2 in nodes:
if self.has_edge(node1, node2) != other.has_edge(node1, node2):
return False
return True
# Breadth-first search
# --------------------
def bfs(self, start):
"""Do a breadth-first search of the graph, from the start node.
Return a list of nodes in visited order, the first being start.
Assume the start node exists.
"""
assert self.has_node(start)
# Initialise the list to be returned.
visited = []
# Keep the nodes yet to visit in another list, used as a queue.
# Initially, only the start node is to be visited.
to_visit = [start]
# While there are nodes to be visited:
while to_visit != []:
# Visit the next node at the front of the queue.
next_node = to_visit.pop(0)
visited.append(next_node)
# Look at its neighbours.
for neighbour in self.neighbours(next_node):
# Add node to the back of the queue if not already
# visited or not already in the queue to be visited.
if neighbour not in visited and neighbour not in to_visit:
to_visit.append(neighbour)
return visited
# Depth-first search
# ------------------
def dfs(self, start):
"""Do a depth-first search of the graph, from the start node.
Return a list of nodes in visited order, the first being start.
Assume the start node exists.
"""
assert self.has_node(start)
# Use the BFS algorithm, but keep nodes to visit in a stack.
visited = []
to_visit = [start]
while to_visit != []:
next_node = to_visit.pop()
visited.append(next_node)
for neighbour in self.neighbours(next_node):
if neighbour not in visited and neighbour not in to_visit:
to_visit.append(neighbour)
return visited
# Modifiers
# ---------
def add_node(self, node):
"""Add the node to the graph.
Do nothing if the graph already has the node.
Assume the node is of an immutable type.
"""
if not self.has_node(node):
self.graph[node] = set()
def add_edge(self, node1, node2):
"""Add to the graph an edge between the two nodes.
Add any node that doesn't exist.
Assume the two nodes are different and of an immutable type.
"""
assert node1 != node2
self.add_node(node1)
self.add_node(node2)
self.graph[node1].add(node2)
self.graph[node2].add(node1)
When we have, for example,
friendships={1: {2, 3}, 2: {1, 3, 8}}, person1=1, person2=2
the expected answer is obviously 1. This can be efficiently computed as the length of the intersection of two sets, friendships[person1] and friendships[person2].
Your Adjacency Graph already allows efficient access to the neighbours of each node. In turn, the neighbours allow efficient computation of overlaps. Use them.
def common(friendships, person1, person2):
"""Return the number of common friends of person1 and person2."""
assert person1 != person2
assert person1 in friendships.nodes()
assert person2 in friendships.nodes()
mutual_friends = friendships.neighbours(person1) & friendships.neighbours(person2)
return len(mutual_friends)
I have a Linked Lists assignment for school although I am just getting the hang of class constructors. I am trying to simply get the basics of the linked list data structure down, and I understand the basic concept. I have watched lots of Youtube tutorials and the like, but where I am failing to understand is how to print out the cargo or data in my nodes using a loop.
I have written something along these lines:
class Node:
def __init__(self, value, pointer):
self.value = value
self.pointer = pointer
node4 = Node(31, None)
node3 = Node(37, None)
node2 = Node(62, None)
node1 = Node(23, None)
Now...I understand that each node declaration is a call to the class constructor of Node and that the list is linked because each node contains a pointer to the next node, but I simply don't understand how to print them out using a loop. I've seen examples using global variables for the "head" and I've seen subclasses created to accomplish the task. I'm old and dumb. I was wondering if someone could take it slow and explain it to me like I'm 5. If anyone out there has the compassion and willingness to hold my hand through the explanation, I would be greatly obliged. Thank you in advance, kind sirs.
First of all, your nodes should be created something like this :
node4 = Node(31, node3)
node3 = Node(37, node2)
node2 = Node(62, node1)
node1 = Node(23, None)
Now, i am sure you can see that the last node in the list would point to None. So, therefore, you can loop through the list until you encounter None. Something like this should work :
printhead = node4
while True:
print(printhead.value)
if printhead.pointer is None:
break;
else :
printhead = printhead.pointer
This is a very basic linked list implementation for educational purposes only.
from __future__ import print_function
"""The above is needed for Python 2.x unless you change
`print(node.value)` into `print node.value`"""
class Node(object):
"""This class represents list item (node)"""
def __init__(self, value, next_node):
"""Store item value and pointer to the next node"""
self.value = value
self.next_node = next_node
class LinkedList(object):
"""This class represents linked list"""
def __init__(self, *values):
"""Create nodes and store reference to the first node"""
node = None
# Create nodes in reversed order as each node needs to store reference to next node
for value in reversed(values):
node = Node(value, node)
self.first_node = node
# Initialize current_node for iterator
self.current_node = self.first_node
def __iter__(self):
"""Tell Python that this class is iterable"""
return self
def __next__(self):
"""Return next node from the linked list"""
# If previous call marked iteration as done, let's really finish it
if isinstance(self.current_node, StopIteration):
stop_iteration = self.current_node
# Reset current_node back to reference first_node
self.current_node = self.first_node
# Raise StopIteration to exit for loop
raise stop_iteration
# Take the current_node into local variable
node = self.current_node
# If next_node is None, then the current_node is the last one, let's mark this with StopIteration instance
if node.next_node is None:
self.current_node = StopIteration()
else:
# Put next_node reference into current_node
self.current_node = self.current_node.next_node
return node
linked_list = LinkedList(31, 37, 62, 23)
for node in linked_list:
print(node.value)
This doesn't handle many cases properly (including break statement in the loop body) but the goal is to show minimum requirements for linked list implementation in Python.
I have a basic question related to space usage, and I'm using DFS as an example. I'm not sure if the space usage in these few implementations are the same, or if a few actually differ. My interpretation of space usage is directly correlated to what the function allocates. Can anyone help me verify the space usage of these few examples I made? This is a question on space complexity, and not time + functionality
Example 1: We allocate a dictionary that will store N nodes. I'm positive this one allocates O(N) space.
class Node:
def __init__(self, children):
self.children = children
def getChildren(self):
return self.children
def dfs(start):
stack = []
visited = {}
stack.append(start)
while(len(stack) > 0):
node = stack.pop()
if(node not in visited):
visited[node] = True
for child in node.getChildren():
stack.append(child)
Example 2: We don't allocate anything in the dfs function, but instead we are given a flag to set on the Node. We aren't allocating anything in the dfs function so it is O(1) space usage.
class Node:
def __init__(self, children):
self.children = children
self.visited = False
def getChildren(self):
return self.children
def getVisited(self):
return self.visited
def setVisited(self, visit):
self.visited = visit
def dfs(start):
stack = []
stack.append(start)
while(len(stack) > 0):
node = stack.pop()
if(!node.getVisited()):
node.setVisited(True)
for child in node.getChildren():
stack.append(child)
Example 3: We have an object Node that can be manipulated, but does not have a flag attribute up front. DFS is manually creating a flag on each Node, and thus allocating O(N) space.
class Node:
def __init__(self, children):
self.children = children
def getChildren(self):
return self.children
def dfs(start):
stack = []
stack.append(start)
while(len(stack) > 0):
node = stack.pop()
if(node.visited is not None):
node.visited = True
for child in node.getChildren():
stack.append(child)
Space complexity is not determined by where the space gets allocated but by how much space (memory) is required to hold a given data structure in relation to the number of objects to be processed by an algorithm.
In your examples all data structures require O(N) space (N = # of nodes)
I need to make a function that builds a tree from a preorder and inorder traversal, but I'm not sure where I should put the MakeNode and recursive calls to construct the tree properly.
Assuming inorder and preorder are both a list of ints, is the following algorithm correct to reconstruct the tree using the traversals?
def build(preorder, inorder):
root = preorder[0]
left subtree = inorder[:root-1]
right subtree = inorder[root+1:]
If so - How can one take that and construct a heap (ArrayHeap) using that algorithm recursively?
I have a class designed to construct nodes and then I can simply use heap.add(node) to create the heap.
Say my class to build a node is named "MakeNode" and is constructed as follows (for syntax purposes):
Class MakeNode():
def __init__(self, character, left=None, right=None):
To create the root node I would need to edit the function like this:
def build(preorder, inorder, heap):
root = preorder[0]
node = MakeNode(root) # Creating root node here
heap.add(node) # Adding to heap
left subtree = inorder[:root-1]
right subtree = inorder[root+1:]
But how should I use recursion to build the rest of the tree?
I can incorporate the left and right preorder for ordering purposes by doing this:
def build(preorder, inorder, heap):
root = preorder[0]
node = MakeNode(root)
heap.add(node)
left subtree = inorder[:root-1]
# order of left subtree = preorder[1:1+left subtree]
right subtree = inorder[root+1:]
# order of right subtree = preorder[root+1:]
I don't really know how to incorporate the recursive calls to build the tree or what exactly to put for the left or right parameters when doing so.
If anybody has any suggestions I would appreciate them, and I'm sorry if I've been unclear.
What you need to do is add the root of the pre-ordered list to the tree, and removing it from preorder list. Split the in order list as you are doing, then passing both the left and right branches recursively. Keep adding the first element of pre-order to the left of the previous node unless left_subtree is empty, then you need to add it to the right.
This is python code (already tested):
class Tree():
def __init__(self, inorder, preorder):
self.root = preorder[0]
lt = inorder[:inorder.index(self.root)]
rt = inorder[inorder.index(self.root) + 1:]
self.build(self.root, lt, rt, preorder[1:])
def build(self, last_node, left_subtree, right_subtree, preorder):
left_preorder = [node for node in preorder if node in left_subtree]
right_preorder = [node for node in preorder if node in right_subtree]
if len(left_subtree) > 0:
last_node.left = left_preorder[0]
lt = left_subtree[:left_subtree.index(last_node.left)]
rt = left_subtree[left_subtree.index(last_node.left) + 1:]
self.build(last_node.left, lt, rt, left_preorder)
if len(right_subtree) > 0:
last_node.right = right_preorder[0]
lt = right_subtree[:right_subtree.index(last_node.right)]
rt = right_subtree[right_subtree.index(last_node.right) + 1:]
self.build(last_node.right, lt, rt, right_preorder)
From http://www.cse.hut.fi/en/research/SVG/TRAKLA2/tutorials/heap_tutorial/taulukkona.html, you have:
parent(i) = i/2
left(i) = 2i
right(i) = 2i+1
so you can define a class:
class ArrayHeapNode:
def __init__(self, elements, index):
self.elements = elements
self.index = index
def left(self):
next = self.index * 2
if next >= len(self.elements):
return None
return ArrayHeapNode(self.elements, next)
def right(self):
next = (self.index * 2) + 1
if next >= len(self.elements):
return None
return ArrayHeapNode(self.elements, next)
def value(self):
return self.elements[self.index]
def set_value(self, _value):
self.elements[self.index] = _value
This gives you a class that can then function as a tree on an array representation of a binary heap according to the article. If there is no element in that branch, None is returned.
You can now create tree traversal algorithms (https://en.wikipedia.org/wiki/Tree_traversal):
def inorder_traversal(node, action):
if not node: return
inorder_traversal(node.left(), action)
action(node.value())
inorder_traversal(node.right(), action)
def preorder_traversal(node, action):
if not node: return
action(node.value())
preorder_traversal(node.left(), action)
preorder_traversal(node.right(), action)
These will also work with a traditional binary tree node:
class BinaryTreeNode:
def __init__(self, value, left, right):
self._value = value
self._left = left
self._right = right
def left(self):
return self._left
def right(self):
return self._right
def value(self):
return self._value
def set_value(self, _value):
self._value = _value
Now, you can make the traversal algorithms more flexible and python-like by doing:
def inorder(node):
if node:
for item in inorder(node.left()):
yield item
yield node.value()
for item in inorder(node.right()):
yield item
This allows you to write things like:
for item in inorder(tree):
print item
You can then count the elements from the node by doing:
n = sum(1 for e in inorder(root))
This then allows you to create an empty array capable of holding n elements for the elements in the heap:
elements = [0 for x in range(n)]
or combined:
elements = [0 for x in inorder(root)]
heap = ArrayHeapNode(elements, 0)
Now, you can iterate over both trees at the same time using:
for a, b in zip(inorder(root), inorder(heap)):
b = a
This should then assign all elements in the binary tree (root) to the correct elements in the array heap (heap) using inorder traversal. The same can be done by implementing a preorder function.
NOTE: I have not tested this code.
def pre_order(node):
print node #pre order ... print ourself first
pre_order(node.left)
pre_order(node.right)
def post_order(node):
post_order(node.left)
post_order(node.right)
print node # post order print self last...
def in_order(node):
in_order(node.left)
print node #in order ... between its children
in_order(node.right)
if you have any one of these you should be able to reproduce the tree
assume we have a tree like this
0
1 2
3 4 5 6
our traversals would be
0,1,3,4,2,5,6 #pre order
3,1,4,0,5,2,6 #in order
so from this we know that
zero is our root
3 is our left most deepest node
6 is our right most deepest node
0
...
3 6
our left over nodes are
1,4,2,5 # preorder
1,4,5,2 # in-order
from this we know that
1 is a child of zero
1 is the next level leftmost node
2 is the next level rightmost node
so we now have
0
1 2
3 6
leaving us with
4,5 # pre order
4,5 # in order
from this we know that 4 is a child of 1, and therefore 5 must be a child of 2 ...
now write a function that does all that
this article may help
http://leetcode.com/2011/04/construct-binary-tree-from-inorder-and-preorder-postorder-traversal.html