Classes Binary Search Tree Python - python

I am new to Python and came across an old problem in HackerRank which defines a Binary Tree as such. I reviewed this video on classes and instances (and the next one) to try to understand what is happening in the below code but I still don't fully grasp what is going on.
I understand the role of the __ init __(self, ...) but I'm not sure what attribute info has. I also do not understand why self.left = None, self.right = None, self.level = None.
In the second class BinarySearchTree, there's an init with no attribute and I also do not understand the self.root = None.
Even though I don't understand most of the code below, I think if someone can explain why the person set self.____= None, it would help me understand how to define a Binary Search Tree.
class Node:
def __init__(self, info):
self.info = info
self.left = None
self.right = None
self.level = None
def __str__(self):
return str(self.info)
class BinarySearchTree:
def __init__(self):
self.root = None
def create(self, val):
if self.root == None:
self.root = Node(val)
else:
current = self.root
while True:
if val < current.info:
if current.left:
current = current.left
else:
current.left = Node(val)
break
elif val > current.info:
if current.right:
current = current.right
else:
current.right = Node(val)
break
else:
break

If you try sketch your tree structure as a bunch of circles with some values inside, you will get something like that:
The 'info' attribute will contain the values that are inside of the circles. Every node of a binary tree can have at most two children, that's what the 'left' and 'right' attributes are used for. If the 'left' attribute is 'None', it basically means there is no child node on the left side yet (like in case of the node 16 on the image). If you create a new node, you usually do not expect it to have any children, that's why the 'left' and 'right' attributes are 'None' by default.
The class 'BinarySearchTree' represents a tree as a whole and keeps the current root node (the top one on the image) in the corresponding 'root' attribute. At the beginning the tree is empty, so the 'root' attribute equals to 'None'.
Hope it helps!

Related

Python: actually modify a node in binary search tree instead of just attaching a label name

I understand how to insert using recursion. I also understand why this code doesn't work as expected because while I'm updating the variable "current" in the "insert" method, I'm only attaching the name label "current" to some node, copy that node to "current" and modify "current", but not the actual node in the binary search tree.
So how can I actually modify the node in the binary search tree using the iterative concept here? And more generally speaking, how can I make a "shallow copy" to any object I created and actually modify that object? A "list" object in Python is an example that has the desired property. What code in "list" makes it behave this way? Thanks in advance.
class Node:
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
class BinarySearchTree:
def __init__(self, root=None):
self.root = root
def insert(self, data):
if self.root:
current = self.root
while current:
if data < current.data:
current = current.left
elif data > current.data:
current = current.right
current = Node(data)
else:
self.root = Node(data)
bst = BinarySearchTree()
bst.insert(2)
bst.insert(1)
bst.insert(3)

Binary Tree: How Do Class Instances Link?

I am trying to understand binary trees, but doing so has brought me to confusion about how class instances interact, how does each instance link to another?
My Implementation:
class Node(object):
def __init__(self, key):
self.key= key
self.L = None
self.R = None
class BinaryTree(object):
def __init__(self):
self.root = None
def get_root(self):
return self.root
def insert(self, key):
if self.get_root()==None:
self.root = Node(key)
else:
self._insert(key, self.root)
def _insert(self, key, node):
if key < node.key:
if node.L == None:
node.L = key
else:
self._insert(key, Node(node.L))
if key > node.key:
if node.R == None:
node.R = key
else:
self._insert(key, Node(node.R))
myTree= BinaryTree()
A Scenario
So lets say I want to insert 10, I do myTree.insert(10) and this will instantiate a new instance of Node(), this is clear to me.
Now I want to add 11, I would expect this to become the right node of the root node; i.e it will be stored in the attribute R of the root node Node().
Now here comes the part I don't understand. When I add 12, it should become the child of the root nodes right child. In my code this creates a new instance of Node() where 11 should the be key and 12 should be R.
So my question is 2-fold: what happens to the last instance of Node()? Is it deleted if not how do I access it?
Or is the structure of a binary tree to abstract to think of each Node() connected together like in a graph
NB: this implementation is heavily derived from djra's implementation from this question How to Implement a Binary Tree?
Make L and R Nodes instead of ints. You can do this by changing the parts of your _insert function from this:
if node.L == None:
node.L = key
to this:
if node.L == None:
node.L = Node(key)
There is also a problem with this line:
self._insert(key, Node(node.L))
The way you're doing it right now, there is no way to access that last reference of Node() because your _insert function inserted it under an anonymously constructed node that has no parent node, and therefore is not a part of your tree. That node being passed in to your insert function is not the L or R of any other node in the tree, so you're not actually adding anything to the tree with this.
Now that we changed the Ls and Rs to be Nodes, you have a way to pass in a node that's part of the tree into the insert function:
self._insert(key, node.L)
Now you're passing the node's left child into the recursive insert, which by the looks of thing is what you were originally trying to do.
Once you make these changes in your code for both the L and R insert cases you can get to the last instance of Node() in your
10
\
11
\
12
example tree via myTree.root.R.R. You can get its key via myTree.root.R.R.key, which equals 12.
Most of you're questions come from not finishing the program; In your current code after myTree.insert(11) you're tree is setting R equal to a int rather than another Node.
If the value isn't found then create the new node at that point. Otherwise pass the next node into the recursive function to keep moving further down the tree.
def _insert(self, key, node):
if key < node.key:
if node.L == None:
node.L = Node(key)
else:
self._insert(key, node.L)
if key > node.key:
if node.R == None:
node.R = Node(key)
else:
self._insert(key, node.R)
P.S. This isn't finished you're going to need another level of logic testing incase something is bigger than the current Node.key but smaller than the next Node.

Need help understanding python simple linked list program

Below is a simple linked list program, I know how a linked list works conceptually ( adding, removing, etc) but I am finding it hard to understand how it works from an object oriented design perspective.
Code:
class Node():
def __init__(self,d,n=None):
self.data = d
self.next_node = n
def get_next(self):
return self.next_node
def set_next(self,n):
self.next_node = n
def get_data(self):
return self.data
def set_data(self,d):
self.data = d
class LinkedList():
def __init__(self,r = None):
self.root = r
self.size = 0
def get_size(self):
return self.size
def add(self,d):
new_node = Node(d,self.root)
self.root = new_node
self.size += 1
def get_list(self):
new_pointer = self.root
while new_pointer:
print new_pointer.get_data()
new_pointer = new_pointer.get_next()
def remove(self,d):
this_node = self.root
prev_node = None
while this_node:
if this_node.get_data() == d:
if prev_node:
prev_node.set_next(this_node.get_next())
else:
self.root = this_node
self.size -= 1
return True
else:
prev_node = this_node
this_node = this_node.get_next()
return False
def find(self,d):
this_node = self.root
while this_node:
if this_node.get_data() == d:
return d
else:
this_node = this_node.get_next()
return None
myList = LinkedList()
myList.add(5)
myList.add(8)
myList.add(12)
myList.get_list()
I have couple questions here..
How is it storing the values. As far as I understand each variable can hold one value. So how does data / next_node hold multiple values. And does next_node hold the memory location of the next node?
new_pointer.get_data() How is new_pointer able to access get_data()? Don't we need to have an instance to access methods of Node?
This question may be silly, but I am quiet new to object oriented programming. If someone can answer these questions or post an external link addressing these questions it would be really helpful.
Thanks in advance.
next_node is an instance of Node and so it has its own data field. next_node is a reference to the node object, which is some memory address (however it is not a C-like pointer, as you don't need to dereference it or anything).
I'm assuming you are talking about get_list(). new_pointer is an instance of Node. (unless it is None, in which case you would never get into the get_data() call). When you do an add, you create this instance of Node and set root to it. Then in get_list you set new_pointer to root.
myList.root is storing one value only that is the root of the list. See initially when you do:
myList = LinkedList()
in memory myList.root = None (according to __init__ of LinkedList). Now:
myList.add(1)
Then this statement is called:
new_node = Node(d,self.root) #Note here self.root = None
and then:
def init(self,d,n=None):
self.data = d
self.next_node = n
So our list is : 1--> None.Now:
myList.add(2)
then again this statement is called:
new_node = Node(d,self.root) #Note here self.root has some value
now a new node object is created and its next is assigned to myList.root.
So our list becomes : 2-->1--> None
Going in similar fashion whole list is assigned.
Key thing to note here is that myList.root is always storing the top most node which in turn holds the next node and so on.
For your second question, it is quite clear from above explaination that always the object of class node is available to myList.root which in turn has next_node which is again an object of 'node'. So they all have access to 'get_data()' method.

Delete a root from a tree in python

I am implementing a deletion of a node from a binary search tree in python.
I got stuck in an edge case.
Consider next code:
class Node():
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
root = Node(5)
root.right = Node(10)
How to implement a function that deletes the root.
I do not want the function return new root.
In C++ I can modify pointer to make root point to its child, however in python variables are labels essentially. Is it even possible to do in python?
There is indeed no way to replace root and have it replaced wherever the instance pointed to by the name root appears.
The only way to achieve what you want is to mutate root to duplicate the child node.
def delete(node, inheritLeft=True):
child = node.left if inheritLeft else node.right
node.value = child.value
node.left = child.left
node.right = child.right
(Obviously you might want to do something smarter regarding choosing which node to inherit).

Binary Search Tree - storing reference to parent node

I hope someone can help me, I'm not a programming professional, but am using Python to learn and experiment with binary trees.
Below is the code I have, and have attempted to try and store a reference to a node's parent in it's node, the storing of it's parent node, won't work for leaf nodes though. Is there a way of doing this during the process of building the tree?
I'd also like to know for a given node, whether is's a 'Left or 'Right' node. I thought seeing as the node is stored in an instance of TreeNode.left or TreeNode.right, I might be able to get a reference to this in Python, as in n._name_ or something like that. Could you tell me the correct way to find whether a node is left or right?
My ultimate goal will be to visualise my tree through a level order traversal.
class TreeNode:
left, right, data = None, None, 0
def __init__(self,nodeData, left = None, right = None, parent = None):
self.nodeData = nodeData
self.left = left
self.right = right
self.parent = self
class Tree:
def __init__(self):
self.root = None
def addNode(self, inputData):
return TreeNode(inputData)
def insertNode(self, parent, root, inputData):
if root == None:
return self.addNode(inputData)
else:
root.parent = parent
if inputData <= root.nodeData:
root.left = self.insertNode(root, root.left, inputData)
else:
root.right = self.insertNode(root, root.right, inputData)
return root
There are many, many things wrong with this. Since it's homework, I'll supply one hint.
def __init__(self,nodeData, left = None, right = None, parent = None):
self.nodeData = nodeData
self.left = left
self.right = right
self.parent = self
Why isn't self.parent set to parent?

Categories

Resources