How do I get the PreOrder,InOrder,PostOrder to work?
Asked Answered
S

3

0

How i get the PreOrder,InOrder,PostOrder to work?

Tree Diagram Reference

Here are my current code and implementation, see InOrder(),PreOrder(),PostOrder(). I have a reference from Geek4Geek (https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/).

When i do a print(bst.InOrder()) it return None?

import os
import pygraphviz as pgv
from collections import deque
from IPython.display import Image, display

class BST:
    root=None

    def get(self,key):
        p = self.root
        while p is not None:
            if p.key == key:
                return p.val
            elif p.key > key: #if the key is smaller than current node, then go left (since left are all the small ones)
                p = p.left
            else:   # else if key is bigger than current node, go to right (since right are all the big ones)
                p = p.right 
        return None
        
    def put(self, key, val):
        self.root = self.put2(self.root, key, val)

    def put2(self, node, key, val):
        if node is None:
            #key is not in tree, create node and return node to parent
            return Node(key, val)
        if key < node.key:
            # key is in left subtree
            node.left = self.put2(node.left, key, val)
        elif key > node.key:
            # key is in right subtree
            node.right = self.put2(node.right, key, val)
        else:
            node.val = val
        return node

    # draw the graph
    def drawTree(self, filename):
        # create an empty undirected graph
        G=pgv.AGraph('graph myGraph {}')

        # create queue for breadth first search
        q = deque([self.root])
        # breadth first search traversal of the tree
        while len(q) != 0:
            node = q.popleft()
            G.add_node(node, label=node.key+":"+str(node.val))
            if node.left is not None:
                # draw the left node and edge
                G.add_node(node.left, label=node.left.key+":"+str(node.left.val))
                G.add_edge(node, node.left)
                q.append(node.left)
            if node.right is not None:
                # draw the right node and edge
                G.add_node(node.right, label=node.right.key+":"+str(node.right.val))
                G.add_edge(node, node.right)
                q.append(node.right)

        # render graph into PNG file
        G.draw(filename,prog='dot')
        display(Image(filename))

    def createTree(self):
        self.put("F",6)
        self.put('I',9)
        self.put("J",10)
        self.put("G",7)
        self.put("H",8)
        # left side of F:6
        self.put("D",4)
        self.put("C",3)
        self.put("B",2)
        self.put("A",1)
        self.put("E",5)

   

    def createBalancedTree(self):
      self.put("F",6)
      self.put("A",1)
      self.put("B",2)
      self.put("C",3)
      self.put("D",4)
      self.put("E",5)
      self.put("G",7)
      self.put("H",8)
      self.put('I',9)
      self.put("J",10)
    
    def find(self, key):
        p = self.root
        while p is not None:
            if p.key == key:
                return p
            elif p.key > key:
                p = p.left
            else:
                p = p.right
        return

    def size(self,key): 
      return self.size2(self.find(key)) #using the find function which gives the node instead
    
    def size2(self, subtree):
      if not subtree: #if given key is not found in entire tree (means not a node in this tree)
        return 0
      else:
        return 1 + self.size2(subtree.left) + self.size2(subtree.right)
    

    def depth(self,key):
      p = self.root                         # set the default node as Root, since we start from Root then top-bottom approach. 
      if p is not None:
        if p.key == key:                    # if key is root, then return 0 (cus at Root, there is no depth)
          return 0
        elif p.key > key:                   # if Key is smaller than current node, then search in the left side 
          return self.depth2(p.left,key,0)
        else:                               # if key is bigger than current node, search the right side 
          return self.depth2(p.right,key,0)
    
    def depth2(self,node,key,counter):
      # lets say you put a depth(Z), at depth(), it wouldt know Z exits or not, so it will call depth2() to find out. In depth2(), It will check 'Z' throughtout node.key>key and node.key<key..
      # still cannot find after all the iteration, then it will return None
      if node is not None:                 
        if node.key > key:        
          return self.depth2(node.left,key,counter+1)
        elif node.key < key:                     
          return self.depth2(node.right,key,counter+1)
        elif node.key == key:   
          return counter + 1  # this code will only run when you find your key. So example you do depth(E), it will start from F, then D, then found E. In total 2
      else:
        return None
    

 
    def height(self,key):
      x = self.root
      if x == key:
        return 0
      else:
        return self.height2(self.find(key))
    
    def height2(self,subtree):
        if not subtree:
          return -1 #Key is not a node in the tree
        else:
          return max(self.height2(subtree.left), self.height2(subtree.right)) + 1
    

    def InOrder(self):
      if self == self.root:
        InOrder(self.left)
        print(self.key)
        InOrder(self.right)
    
    #def PreOrder(self):
    #def PostOrder(self):
        
      
class Node:
    left = None
    right = None
    key = 0
    val = 0

    def __init__(self, key, val):
        self.key = key
        self.val = val

What should I do get the print to work?

Slambang answered 20/3, 2021 at 8:35 Comment(1)
The size method ignores its argument. It does always exactly the same. When called recursively, how could it stop?Luster
Q
1

code review and fix

The first problem is that size uses get which returns a value of the tree, not a node. To fix this we rewrite your get function as find, but this time it returns a node -

class BST:
    root=None
    
    def put(self, key, val): # ...
    def put2(self, node, key, val): # ...
    def createTree(self): # ...
    def createBalancedTree(self): # ...

    def find(self,key):
        p = self.root
        while p is not None:
            if p.key == key:
                return p       # return p
            elif p.key > key:
                p = p.left
            else:
                p = p.right 

        return None            # return None, not "None"

We don't need to duplicate this logic in get. Instead we make a call to find which gets the node. If the node is present, then we return the value -

class BST:
    # ...

    def get(self, key):
      p = self.find(key)       # call to find
      if not p:
        return None
      else:
        return p.val           # return p.val

Next, in the size function, we will use find to get the node. And similar to how you wrote a put2 helper, we can write size2 which handles the looping -

class BST:
    # ...

    def size(self,key):
      return self.size2(self.find(key)) # find, not get

    def size2(self, subtree):           # define size2 like you did put2
      if not subtree:
        return 0
      else:
        return 1 + self.size2(subtree.left) + self.size2(subtree.right)

This means we do not define size in the Node class -

class Node:
    left = None
    right = None
    key = 0
    val = 0

    def __init__(self, key, val):
        self.key = key
        self.val = val

    # do not define "size" on the Node class

Let's test it out with your createBalancedTree() -

bst = BST()
bst.createBalancedTree()

#   F
#  / \
# A   G
#  \   \
#   B   H
#    \   \
#     C   I
#      \   \
#       D   J
#        \
#         E
print(bst.size('F')) # 10
print(bst.size('A')) # 5
print(bst.size('H')) # 3
print(bst.size('D')) # 2

height

Updated with your help as well, i tried the same method for finding height(), but its returning wrong anwers.

We can write height similar to size -

class BST:
    # ...
    def height(self,key):
      return self.height2(self.find(key))
    
    def height2(self,subtree):
        if not subtree:
            return 0 
        else:
            return max(self.height2(subtree.left), self.height2(subtree.right)) + 1

depth

So if i do a depth('B'), it should return 3. Since B to F, the depth level is 3. And if i do a depth('F'), it should return 0. Since there is no depth in root F

We can write depth very similar to how we wrote find -

class BST:
    # ...
    def depth(self,key):
        p = self.root
        d = 0
        while p is not None:
            if p.key == key:
                return d
            elif p.key > key:
                p = p.left
            else:
                p = p.right
            d = d + 1 
        return None

And you did a great job! There is no problem with your code, as demonstrated below -

bst2 = BST()
bst2.createTree()

#          F
#        /   \
#       D     I
#      / \   / \
#     C   E G   J
#    /       \
#   B         H
#  /
# A 

print(bst2.depth("F")) # 5
print(bst2.depth("I")) # 3
print(bst2.depth("B")) # 2
print(bst2.depth("Z")) # 0

improvements

Could you explain why there is a need for put2 and a need for size2? Sorry, i didnt came out with the put2... it was a given code for my assignment

You don't actually need put2 or size2 and I would say they are a bad practice. The problem is that all of the tree logic is tangled up in the class. In this section of the answer, I will show you a total revision of your bst module.

First we begin with a basic node interface. Instead of assigning properties, all we need a simple __init__ constructor. key and val are required. left and right are optional and default to None if not specified -

# bst.py

class node:
  def __init__(self, key, val, left = None, right = None):
    self.key = key
    self.val = val
    self.left = left
    self.right = right

Now we write a plain put function. Notice there's no references to special variables like self. Another thing of key importance is that we never mutate (overwrite) nodes by reassigning the left or right properties. Instead a new node is created -

# bst.py (continued)

def put(t, k, v):
  if not t:
    return node(k, v)
  elif k < t.key:
    return node(t.key, t.val, put(t.left, k, v), t.right)
  elif k > t.key:
    return node(t.key, t.val, t.left, put(t.right, k, v))
  else:
    return node(t.key, v, t.left, t.right)

We will continue writing plain functions in this way. Next we define get which is a specialization of find -

# bst.py (continued)

def get(t, k):
  r = find(t, k)
  if not r:
    return None
  else:
    return r.val

def find(t, k):
  if not t:
    return None
  elif k < t.key:
    return find(t.left, k)
  elif k > t.key:
    return find(t.right, k)
  else:
    return t

Here we will deviate with size a bit. This time it will not take a key argument. Instead the caller will be able to call size on any node. Usage will be demonstrated below -

# bst.py (continued)

def size(t):
  if not t:
    return 0
  else:
    return 1 + size(t.left) + size(t.right)

It would be convenient if we could build trees from a list of nodes. This is an improvement from createBalancedTree which calls .put over and over. We can call it from_list -

# main.py

nodes = \
  [("F",6), ("A",1), ("B",2), ("C",3), ("D",4), ("E",5), ("G",7), ("H",8), ('I',9), ("J",10)]

t = bst.from_list(nodes)

We can implement from_list easily in our bst module -

# bst.py (continued)

def from_list(l):
  t = None
  for (k,v) in l:
    t = put(t, k, v)
  return t

Here's the biggest difference of the module. We write the bst class but it is a simple wrapper around your plain functions, put, find, get, size, and from_list. There is zero complex logic in the class -

# bst.py (continued)

class bst:
  def __init__(self, t): self.t = t
  def put(self, k, v): return bst(put(self.t, k, v))
  def find(self, k): return bst(find(self.t, k))
  def get(self, k): return get(self.t, k)
  def size(self): return size(self.t)
  def from_list(l): return bst(from_list(l))

We're all done. We will write our main program which imports from our bst module -

# main.py

from bst import bst

nodes = \
  [("F",6), ("A",1), ("B",2), ("C",3), ("D",4), ("E",5), ("G",7), ("H",8), ('I',9), ("J",10)]

t = bst.from_list(nodes)
#   F
#  / \
# A   G
#  \   \
#   B   H
#    \   \
#     C   I
#      \   \
#       D   J
#        \
#         E

Remember how I said size does not take a key argument? That's because it can be called on any node. So to find the size of a specific node, we first find it, then size it! This is a core principle of writing reusable functions: each function should do only one thing -

print(t.find('F').size()) # 10
print(t.find('A').size()) # 5
print(t.find('H').size()) # 3
print(t.find('D').size()) # 2

functional

An understated advantage of the technique we used is that our bst module can be used in an object-oriented way (above) or in a functional way (below). This dual interface makes our module extremely flexible as it can be used in a variety of styles -

# functional.py

from bst import from_list, find, size

nodes = \
  [("F",6), ("A",1), ("B",2), ("C",3), ("D",4), ("E",5), ("G",7), ("H",8), ('I',9), ("J",10)]

t = from_list(nodes)

print(size(find(t, 'F'))) # 10
print(size(find(t, 'A'))) # 5
print(size(find(t, 'H'))) # 3
print(size(find(t, 'D'))) # 2

additional reading

I've written extensively about the techniques used in this answer. Follow the links to see them used in other contexts with additional explanation provided -

Quidnunc answered 20/3, 2021 at 16:26 Comment(7)
Thank you, it was what Gilles Ottervanger was suggesting too. But you help me understand that I do not have to define 'size' in my class. Could you explain why there is a need for put2 and a need for size2? Sorry, i didnt came out with the put2... it was a given code for my assignment. Trying to understand how this whole thing works. Thank you once again for the vivid drawing and explanation:)Slambang
Fumin i'm very happy to assist. I added another section that I hope answers your other questions. If there's anything else lmk and I'll do my best to help out.Quidnunc
WOW! hahah thank you for the explanation, your way of implemeting the tree is SO MUCH CLEARER! However, due to this assignment criteria, i cant change to what you suggested T.T. I have a clearer picture of whats going on now. I also edited my code to the latest. Updated with your help as well, i tried the same method for finding depth(), but its returning wrong anwers.Slambang
I added a depth section to the answer. You did a terrific job writing depth and depth2 on your own. Keeping with the initial pattern of put, put2, size, and size2, it's what I would've done. Perhaps you are visualizing the input tree incorrectly? I provided a tree visual for bst.createTree and the depth measurements in my code sample are matching my expectations.Quidnunc
I think i didnt explain the depth() function clearly :( It was suppose to return how deep it is from the root. So if i do a depth('B'), it should return 3. Since B to F, the depth level is 3. And if i do a depth('F'), it should return 0. Since there is no depth in root F.Slambang
are you there ?? @Thank youSlambang
I see your updates. I think height and depth are good names for these functions. I will provide another answer for the inorder functionQuidnunc
H
0

It seems to me like your stop condition is incorrect. The default values for children (and the root) are None so you should check for z == None. Also it seems like you are mixing up child nodes and keys. It seems to me the best approach would be to first find the node with the desired key and then calculate the subtree size recursively on this node. See the code below.

# a function in the BST class that finds the node with a specific key
class BST:
    def find(self, key):
        p = self.root
        while p is not None:
            if p.key == key:
                return p
            elif p.key > key:
                p = p.left
            else:
                p = p.right
        return

    # the function that you asked about
    def getSize(self, key):
        subroot = self.find(key)
        if subroot:
            return subroot.size()
        return 0 # if the key is not found return zero

# a function in the node class to find the subtree size with that node as root
class Node:
    def size(self): 
      return 1 + (self.left.size() if self.left else 0) + (self.right.size() if self.right else 0)
Hertzfeld answered 20/3, 2021 at 9:27 Comment(5)
Thank you, however im still unclear how this whole thing is working. I implement your code but it return this error "AttributeError: 'int' object has no attribute 'size'." Sorry im new to this... i have edited the code above. Please have a look.. idk whats wrongSlambang
I see in your edit that you used subroot = self.get(key) instead of subroot = self.find(key). Your get function returns the value of the node while my find function returns the node object itself (or None if it cannot be found). Besides that @vpfb noticed a mistake in my size function. I updated my answer with a corrected version.Hertzfeld
Could you explain how you does your recursive function work? This line "return 1 + (self.left.size() if self.left else 0) + (self.right.size() if self.right else 0)". My understanding of it is, if you find a node on the self.left, plus1 else 0...similarly if you found node on the right side, plus 1 else 0. Its my understanding correct? Thank you btw, it works. But im trying to understand whats going on.Slambang
Yes, the 1 + is for the current node and note that self.left.size() if self.left else 0 first checks the condition self.left so it checks if there is a left child and if there is it call its member function left.size() returning the size of the left subtree (this is the recursive call). If there is no left subtree that means the size of the left subtree is zero. This has to be done because you wrote size as a member function. If size were not part of the class you could do this check internally as in @thank-you's answer. BTW, if my answer was useful to you, please give it your upvote.Hertzfeld
I do not have enough repuation to give my upvotes :( im sorry. THank you for your explanationSlambang
Q
0

inorder

You should be opening new post for each unique question you have about this code. The current question has deviated a lot from the original. Anyway, following the patterns with your existing code, here's how I might approach inorder -

class BST:
    # ...
    def inorder(self):
        return self.inorder2(self.root)

    def inorder2(self, t):
        if not t: return
        yield from self.inorder2(t.left)
        yield (t.key, t.val)
        yield from self.inorder2(t.right)

Another way to write this is with a nested function -

class BST:
    # ...
    def inorder(self):
        def loop(t):
            if not t: return
            yield from loop(t.left)
            yield t
            yield from loop(t.right)
        return loop(self.root)

Notice how print is disentangled from the inorder function. This allows the caller to use traversal logic and choose the operation to for each node -

for node in bst.inorder():
  print(node.key, node.val)

reusability

After you have inorder defined, you can redefine some of the other functions in your BST class -

class BST:
  # ...
  def find(self, key):
    for node in self.inorder():
      if node.key == key:
        return node

  def size(self, key):
    p = self.find(key)
    if not p: return 0
    return sum(1 for _ in BST(p).inorder())
Quidnunc answered 21/3, 2021 at 16:6 Comment(1)
Hello, so sorry for the confusion!!! As im new to stackoverflow, im only allowed to post once every 3-4 days. Anyways, i have already figured it out hehe. Thank you so much for your help all these while :) Really grateful for that, you have helped me understand much clearer than what was being taught to me.Slambang

© 2022 - 2024 — McMap. All rights reserved.