
Linear data structures such as lists, stacks, and queues store data in a sequence. However, real-world data like file systems or family charts are often arranged hierarchically, and linear models are not efficient at modelling such data. Here, a Binary Tree in Python is an important non-linear method to manage hierarchical data distributions effectively.
One of the main pillars to master DSA in Python is to know how to build, traverse and optimise trees. This structure guide breaks down complex tree variations into simple parts, with clear algorithmic flows and clean object-oriented Python examples.
The tree data structure is a non-linear hierarchical model, where the central collection units are called nodes and connected by directional paths called edges. It is an inverted natural tree layout that grows from a single origin point downwards.
While in linear structures each element is connected to only one other element, in a tree one element can branch out into several sub-elements.
To work with tree architectures effectively, you must understand these foundational structural terms:
Node: A standalone data container holding a specific value along with links to other linked nodes.
Root Node: The topmost origin node of a tree that possesses no incoming parent link.
Edge: The direct link or pointer connection established between a parent node and its child node.
Parent Node: Any node that has an outgoing edge connecting it to another sub-node lower in the hierarchy.
Child Node: A node that descends directly from a parent node situated above it.
Leaf Node: A terminal node located at the bottom-most ends of the branches that has zero children.
Degree of a Node: The total count of direct branches or child nodes originating from that specific node.
Standard tree configurations always adhere to a strict set of structural rules:
Zero Cycles: A tree can never contain a loop or cycle. A path starting from a node can never loop back to itself through a series of child nodes
Connected Architecture: Every single node within the tree must be accessible from the root node through a valid path
The Edge Rule: If a valid tree structure contains exactly $n$ nodes, it will always possess exactly $n - 1$ edges. This occurs because every node except the root has exactly one parent link.
A Binary Tree in Python is a specialized version of a standard tree where every individual node can have a maximum of two children. These children are referred to as the left child and the right child.
Binary trees change structural behavior based on how their child nodes are distributed.
A tree structure where every single internal node has either exactly zero children or exactly two children. No node in a full binary tree can ever have just a single child.
A highly symmetrical model where all internal nodes branch into exactly two children, and every single leaf node sits flat on the exact same depth level.
A tree layout where every single level is completely filled with nodes, except possibly the bottom-most level. On the final level, elements must fill in sequentially from the left side toward the right side without leaving open gaps.
A tree where every internal node has only one child link. This causes the structure to grow in a single line, changing it into a performance layout similar to a standard linked list.
A tree where the height difference between the left sub-tree and the right sub-tree for every node is at most one. Keeping a tree balanced prevents it from turning into a single line, which keeps search times optimal.
|
Tree Type |
Left/Right Child Distribution Rule |
Leaf Node Alignment Rule |
|
Full |
Nodes have 0 or 2 children |
Can reside at any depth level |
|
Perfect |
Internal nodes must have exactly 2 children |
All must align at the exact same depth |
|
Complete |
Standard 0 to 2 distribution allowed |
Must fill out from left to right sequentially |
|
Balanced |
Sub-tree height variance cannot exceed 1 |
Can reside across adjacent depths |
To implement a functional tree, you must define a custom class. This class uses explicit object-oriented pointers to handle data fields along with distinct left and right child relationships.
Python
class Node:
def __init__(self, data):
self.data = data # Core data storage field
self.left = None # Pointer pointing to the left child node
self.right = None # Pointer pointing to the right child node
# Constructing a sample manual binary tree
root = Node(5) # Instantiating the origin node
root.left = Node(3) # Linking a left child node
root.right = Node(1) # Linking a right child node
root.left.right = Node(8) # Deep chaining a right child beneath the left node [00:20:41]
Traversing means visiting every single node in a tree exactly once to inspect or process values [38:16]. Because trees are non-linear, you can traverse them using two main strategies: Depth-First Search (DFS) or Breadth-First Search (BFS).
DFS techniques use a deep vertical approach, tracking down explicit branch lines before pivoting across alternate pathways. These are easily built using recursive function calls.
Processes the current node first, then moves down the left sub-tree, and finishes across the right sub-tree.
Traverses the left sub-tree first, processes the central node, and then moves down the right sub-tree.
Processes both the left and right sub-trees completely before managing the parent root node.
Level Order Traversal processes nodes flatly, floor by floor, moving from left to right across each depth level. This strategy relies on a FIFO queue data structure instead of using recursion.
Python
from collections import deque
def level_order(root):
if not root:
return []
result = []
queue = deque([root]) # Initialize the queue with the root node
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft() # Remove element from the front of the queue
current_level.append(node.data)
if node.left:
queue.append(node.left) # Enqueue left child
if node.right:
queue.append(node.right) # Enqueue right child
result.append(current_level)
return result
A binary search tree Python implementation introduces a strict value placement rule: the value of any node must be strictly greater than all node values in its left sub-tree, and strictly less than all node values in its right sub-tree.
Searching in a BST is highly efficient because you can skip half the remaining tree paths with each step. If your target value is smaller than the current node's value, you move left; if it's larger, you move right.
To add a new value while keeping the BST properties intact, search down the tree until you find an open slot at a leaf boundary, then attach the new node.
Deleting a node from a BST requires handling three different scenario setups:
Case 1: Node is a Leaf: Simply remove the link to this node.
Case 2: Node has One Child: Bypass the target node by pointing its parent link directly to its child node.
Case 3: Node has Two Children: Find the node's in-order successor (the smallest value in its right sub-tree). Copy the successor's value into the target node, then delete that successor node from the right sub-tree.

