Post

Binary Tree

Write down description here.

Desktop View

Table of Content

Related Leetcode Questions

How binary trees are stored in memory

Binary trees can be stored in memory using two common representations: pointer-based (linked) structures and array-based structures.

Pointer-Based Storage (Linked Storage)

In pointer-based storage, each node is an object, and each node typically contains:

  • Data: The value or information contained in the node.
  • Left Child Pointer: A pointer to the left child node.
  • Right Child Pointer: A pointer to the right child node.
1
2
3
4
5
class BinaryTree:
    def __init__(self, value):
        self.val = value  # The value or information contained in the node.
        self.left = None  # A pointer to the left child node.
        self.right = None  # A pointer to the right child node.

This method is dynamic, allowing the tree to expand as needed. It is particularly useful for operations that involve frequent insertions and deletions, as nodes can be added or removed without reorganizing the entire structure.

Array-Based Storage

In array-based storage of binary trees:

  • Root: The root element is stored at the beginning of the array.
  • Left Child: The left child of a node located at position n is found at position 2n + 1.
  • Right Child: The right child of a node located at position n is found at position 2n + 2.

Desktop View

This method is space-efficient when the tree is complete and perfect, as it doesn’t require any pointers. However, for sparse trees, this method can waste memory space due to empty indices in the array representing absent nodes.

Array storage is particularly efficient for binary heaps and other binary trees that require accessing children and parents in constant time during insertions and deletions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class ArrayBasedBinaryTree:
    def __init__(self):
        self.tree = []  # Initialize an empty list to store tree elements

    def root(self, value):
        """Insert root node"""
        if not self.tree:
            self.tree.append(value)  # Insert root if tree is empty

    def insert_left(self, parent_index, value):
        """Insert left child"""
        child_index = 2 * parent_index + 1
        if child_index < len(self.tree):
            self.tree[child_index] = value
        else:
            # Extend the list with None to maintain structure
            while len(self.tree) <= child_index:
                self.tree.append(None)
            self.tree[child_index] = value

    def insert_right(self, parent_index, value):
        """Insert right child"""
        child_index = 2 * parent_index + 2
        if child_index < len(self.tree):
            self.tree[child_index] = value
        else:
            # Extend the list with None to maintain structure
            while len(self.tree) <= child_index:
                self.tree.append(None)
            self.tree[child_index] = value

    def display(self):
        """Display the tree"""
        return self.tree

Different types of binary tree

Different types of binary trees are designed to meet specific needs, such as maintaining balance or optimizing search operations.

Binary TreeApplications
Full Binary TreesOften used in applications that require a simple and efficient tree structure
without complex operations.
Complete Binary TreesIdeal for priority queues and heap-based algorithms, where quick access to
elements is necessary.
Binary Search Trees (BST)Serve well in database indexing systems where quick insertion, deletion, and
retrieval of records are frequent.
AVL TreesIn applications where low latency data retrieval is critical, such as in network
routers, where balancing ensures optimal search times.

Full Binary Tree

A full binary tree is a type of binary tree in which every node other than the leaves has two children. In other words, each node has exactly zero or two children.

  • Every node has 0 or 2 children.
  • Leaves are at the same level or one level apart.

Desktop View

Complete Binary Tree

A complete binary tree is a type of binary tree in which all levels, except possibly the last, are completely filled, and all nodes are as left as possible. This arrangement makes complete binary trees ideal for being stored in an array, as their compact and left-filled structure reduces the space complexity.

  • All levels are fully filled except possibly the last level.
  • Nodes are positioned as far left as possible.

Desktop View

Binary Search Tree (BST)

A binary tree in which for each node, the left children are less than the node and the right children are greater. This property makes BSTs immensely useful for search and retrieval operations, as the decision tree of going left or right at each step significantly cuts down the search space.

  • The left subtree of a node contains only nodes with keys lesser than the node’s key.
  • The right subtree of a node contains only nodes with keys greater than the node’s key.
  • There must be no duplicate nodes.

Desktop View

Adelson-Velsky and Landis (AVL) Tree

A self-balancing binary search tree where the height of the two child subtrees of any node differ by no more than one. If at any time the heights differ more than one, rebalancing is done to restore this property. AVL trees are optimized for lookup-intensive applications, where frequent insertions and deletions necessitate quick rebalancing to maintain efficient search times.

Balance factor

The balance factor helps in determining the type of rotation needed to maintain the tree’s balance. It must always be -1, 0, or +1.

Balance factor (node) = height(right subtree) - height(left tree)

Rebalance AVL tree

When a node is added/deleted to/from an AVL tree, performing rotations is needed to ensure that the balance factor of all nodes remains within the allowed range.

Link to note about Rotation of AVL Trees

Tree Traversal

Depth First Search (DFS)

DFS explores as far as possible along each branch before backtracking. Implemented using a stack, either through the system’s call stack via recursion or an explicit stack data structure.

  • Used in solving puzzles with only one solution, like mazes.
  • Employed in topological sorting, scheduling problems, cycle detection in environments where pathways to vertices need exploration.

Desktop View

Link to code about DFS

Preorder

Preorder traversal visits the root first, followed by the left subtree, and then the right subtree. Captures the root node early, which is helpful for operations that need to replicate or examine the tree structure itself.

  • Used to create a copy of the tree.
  • Useful in syntax tree applications, such as in compilers and expression parsers, where the structure of the tree aligns with the order of operations.

Desktop View

Link to code about Preorder Traversal

Inorder

Inorder traversal first visits the left subtree, then the root, and finally the right subtree.

  • Commonly used in binary search trees where an ordered list of elements is required.
  • Useful in algorithms that need to process nodes in a specific order relative to their hierarchical levels.

Desktop View

Link to code about Inorder Traversal

Postorder

Postorder traversal visits the left subtree, the right subtree, and the root last. Ideal for situations where operations on a node require results from its children first.

  • Commonly used in the calculation of a value for a tree where nodes represent operations in postfix expression evaluation.
  • Useful in deleting or freeing nodes and space of the tree in a safe manner.

Desktop View

Link to code about Postorder Traversal

Breadth First Search (BFS)

BFS explores the neighbor nodes at the present depth prior to moving on to nodes at the next depth level.Implemented using a queue to bring the earliest visited nodes for processing first.

  • Used to find the shortest path from a source node to other nodes in an unweighted graph.
  • Useful in any searching algorithm where the shortest path is desired, and all edges are of equal weight.

Desktop View

Link to code about BFS

Level Order

Level order traversal visits nodes level by level from top to bottom. This is the same as Breadth-First Search (BFS) for trees.

  • Widely used in breadth-first search operations in trees and graphs, such as in finding the shortest path in unweighted graphs.
  • Useful in serialization of a tree structure in a way that reconstruction is feasible.

Desktop View

Link to code about Level Order Traversal

This post is licensed under CC BY 4.0 by the author.