TREES STRUCTURE (3)

Fred Agbo

2026-03-30

Announcements

  • Welcome back from the spring break!
  • Grading of MP2 was concluded and published.
  • PS4 grading is pending due to a mistake in publishing a wrong .txt starter file.
    • Submission on GitHub encountered hitch due to the modifications made to correct the mistake.
    • Consequently, Problem 1 of PS4 will not be graded.
    • Only Problem 2: Array-based Queue Implementation will be graded.
    • If you have not done so, please private message me your arrayqueue.py on Discord.
  • Short homework: PS5 is posted at the end of this slides
    • Due this week Wednesday
    • Submission is PDF file and on Canvas

Continuation on Binary Trees Application

  • Todays Lecture:
    • Discuss Expression Trees
    • Array-based Trees
    • Heaps implementation
    • Complete the LinkedBST class

Expression Trees

  • A way to process sentences is to build a parse tree during parsing
  • For a language of expressions
    • This structure is also called an expression tree
  • Points to remember:
    • An expression tree is never empty
    • Each interior node represents a compound expression, consisting of an operator and its operands
    • Each leaf node represents an atomic, numeric operand
    • Operators of higher precedence usually appear near the bottom of the tree, unless they are overridden in the source expression by parentheses

Expression Trees

Grammars, parsing, and a descent-parsing strategy

  • A grammar consists of a few parts:
    • A vocabulary (or dictionary or lexicon) consisting of words and symbols allowed in sentences in the language
    • A set of syntax rules that specify how symbols in the language are combined to form sentences
    • A set of semantic rules that specify how sentences in the language should be interpreted For example, the statement x = y might mean “copy the value of y to the variable x

Grammars: Example

  • The following grammar defines the syntax and vocabulary of a little language/expression 4 + 3 − 2
expression = term [ addingOperator term ]
term = factor [ multiplyOperator factor ]
factor = number | "(" expression ")"
number = digit { digit }
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
addingOperator = "+" | "-"
multiplyingOperator = "*" | "/"

Grammar: BNF

  • This type of grammar above is called an Extended Backus-Naur Form (EBNF) grammar
  • An EBNF grammar uses three kinds of symbols:
    • Terminal symbols—These symbols are in the vocabulary of the language and literally appear in programs in the language—for instance, + and * in the preceding examples
    • Nonterminal symbols—These symbols name phrases in the language, such as expression or factor in the preceding examples
    • Metasymbols—These symbols are used to organize the rules in the grammar

Grammar: Metasymbols

Metasymbol Meaning
"..." Enclose literal items
= Means “is defined as”
[ ... ] Enclose optional items
{ ... } Enclose zero or more items
( ... ) Group together required choices
| Indicates a choice

Recognizing, Parsing, and Interpreting Sentences

  • To process sentences in a language you use:
    • Recognizer – analyzes a string to determine if it is a sentence in a given language
    • Parser – has all the features of a recognizer and returns information about the syntactic and semantic structure of a sentence
    • Interpreter – carries out the actions specified by a sentence

Parser Operation

  • When developing a parser:
    • It is convenient to assign the task of recognizing symbols in a string to a lower-level module called a scanner
  • Scanner performs lexical analysis
    • In which individual words are picked out of a stream of characters
  • Output of the scanner is a stream of words called tokens
    • These become the input to a syntax analyzer that uses the tokens and the grammar rules to determine whether the program is syntactically correct

Lexical Analysis and the Scanner

  • A scanner and parser working in tandem

Array Implementation of Binary Trees

An Attempt

  • An array-based implementation of a binary tree is also possible, but it is difficult to define and practical only in some special situations
    • When the tree is complete

Array Implementation of Binary Trees

  • In an array-based implementation, the elements are stored by level

  • We can compute locations of items on the array-based complete binary tree:

  • Item Location
    Parent (i − 1) // 2
    Left sibling (if any) i − 1
    Right sibling (if any) i + 1
    Left child (if any) i * 2 + 1
    Right child (if any) i * 2 + 2

Why difficult to implement array-based incomplete trees?

Wasted Space:

  • If the tree is not complete, many array positions will be unused (null or empty), leading to inefficient memory usage.

Indexing Complexity:

  • The simple formulas (2*i+2) assume every possible child exists. For missing nodes, you must track which indices are valid, complicating traversal and updates.

Insertion/Deletion Issues:

  • Adding or removing nodes can require shifting many elements or leave gaps, making the array harder to manage.

Loss of Structure:

  • The array no longer visually represents the tree’s shape, making debugging and visualization difficult.

Heap Implementation

  • A heap is a type of binary tree in which smaller data items are located near the root
  • Heap implementation uses min-heap to design a priority queue
  • interface requires methods to return its size, add, remove, peek, etc
  • The two most critical heap operations are:
    • add method – expects a comparable element as an argument and inserts the element into its proper place in the heap
    • pop method – deletes the topmost node in the heap, returns the element contained there, and maintains the heap property
  • The peek operation returns but does not remove the topmost element in a heap

Heap Interface

Method What It Does
heap.isEmpty() Returns True if heap is empty or False otherwise.
heap.__len__() Same as len(heap). Returns the number of items in heap.
heap.__iter__() Same as iter(heap) or for item in heap:. Visits the items from least to greatest.
heap.__str__() Same as str(heap). Returns a string that shows the shape of the heap.
heap.__contains__(item) Same as item in heap. Returns True if item is in the heap or False otherwise.
heap.__add__(otherHeap) Same as heap + otherHeap. Returns a new heap with the contents of heap and otherHeap.
heap.__eq__(anyObject) Same as heap == anyObject. Returns True if heap equals anyObject or False otherwise.
heap.peek() Returns the topmost item in heap. Precondition: heap is not empty.
heap.add(item) Inserts item in its proper place in heap.
heap.pop() Removes and returns the topmost item in heap. Precondition: heap is not empty.

Heap Implementation: Note

  • Heap implementation uses min-heap to design a priority queue
  • Its tructure is similar to the array represnetation of binary tree seen earlier
    • Each node is stored at a specific index in the array:
      • The root is at index 0 (or 1, depending on convention).
      • For a node at index i:
        • Left child is at index 2*i + 1
        • Right child is at index 2*i + 2
        • Parent is at index (i - 1) // 2
  • But be careful to maintain the properties of min-heap.
    • Each node is less than either of its children.

Heap Implementation: Add Method

def add(self, item):
    self.size += 1
    self.heap.append(item)
    curPos = len(self.heap) - 1
    while curPos > 0:
        parent = (curPos - 1) // 2      # Integer quotient!
        parentItem = self.heap[parent]
        if parentItem <= item:         # Found the spot
            break
        else:                          # Continue walking up
            self.heap[curPos] = self.heap[parent]
            self.heap[parent] = item
            curPos = parent

Heap Implementation: Pop Method

Strategy for removals:

  • Begin by saving pointers to the top element and the bottom element in the heap and by moving the element from the bottom of the heap to the top
  • Walk down the heap from the top, moving the smallest child up one level, until the bottom of the heap is reached

Heap Implementation: Pop Method

def pop(self):
    if self.isEmpty():
        raise AttributeError("Heap is empty")
    self.size -= 1
    topItem = self.heap[0]
    bottomItem = self.heap.pop(len(self.heap) - 1)
    if self.isEmpty():
        return bottomItem
    self.heap[0] = bottomItem
    lastIndex = len(self.heap) - 1
    curPos = 0
    while True:
        leftChild = 2 * curPos + 1
        rightChild = 2 * curPos + 2
        if leftChild > lastIndex:
            break
     if rightChild > lastIndex:
            maxChild = leftChild
        else:
            leftItem = self.heap[leftChild]
            rightItem = self.heap[rightChild]
        if leftItem < rightItem:
            maxChild = leftChild
        else:
            maxChild = rightChild
            maxItem = self.heap[maxChild]
        if bottomItem <= maxItem:
            break
        else:
            self.heap[curPos] = self.heap[maxChild]
            self.heap[maxChild] = bottomItem
            curPos = maxChild
        return topItem

LinkedBST Code

  • use this link to download the codes.

Homework: PS5: submission on Canvas

Instruction:

  • Test the completed LinkedBST class implementation as discussed in the class and ensure that the codes are working as expected.
  • Complete all the tasks below
  • Submit your solution as PDF to Canvas page created for this homework

Tasks 1:

  • Add the methods height and isBalanced to the LinkedBST class. The height method returns the height of the tree, as defined in this chapter.
    • The isBalanced method returns True if the height of the tree is less than twice the \(log_2\) of its number of nodes, or False otherwise.
  • Ensure the following lines of code in the testbst.py are uncommented before running the program.
    • # print("\nHeight =", tree.height())
    • # print("\nIs balanced =", tree.isBalanced())

Homework: PS5

Tasks 2:

  • Analyze your binary tree program output by completing the following:

    • Take a screenshot after completing Task 1 and running your program in the terminal. The screenshot should show the output starting from ========Wonder Tree:============.

    • Using the tree shown in your screenshot, draw its hierarchical (top-to-bottom, pyramid-like) structure.

    • Describe the shape of your binary tree (e.g., perfectly balanced, unbalanced, complete, or full).

    • List all the leaf nodes in your tree.

    • List all nodes at level 2.

    • For your tree, provide the node visitation order for each of the following traversal algorithms:

      • Inorder
      • Preorder
      • Postorder
      • Level order