Skip to content

pydsa.tree

build_tree

build_tree(arr: List[Optional[int]]) -> Optional[TreeNode]

Utility function to build a binary tree from a level-order list representation. Null/None values are skipped for node creation.

Source code in pydsa/tree.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def build_tree(arr: List[Optional[int]]) -> Optional[TreeNode]:
    """
    Utility function to build a binary tree from a level-order list representation.
    Null/None values are skipped for node creation.
    """
    if not arr or arr[0] is None:
        # should this be [] ?
        return None

    root = TreeNode(arr[0])
    queue = [root]
    i = 1

    while queue and i < len(arr):
        current = queue.pop(0)

        # Left child
        if i < len(arr) and arr[i] is not None:
            current.left = TreeNode(arr[i])
            queue.append(current.left)
        i += 1

        # Right child
        if i < len(arr) and arr[i] is not None:
            current.right = TreeNode(arr[i])
            queue.append(current.right)
        i += 1

    return root

draw_tree

draw_tree(root: Optional[TreeNode]) -> None

Utility function to draw a binary tree in the terminal using PrettyPrintTree. Requires 'PrettyPrintTree' library: pip install PrettyPrintTree For colors, requires 'colorama': pip install colorama Supports node.color attribute for coloring the node value (e.g., 'blue', 'red', etc.). Uses horizontal orientation for a wider (bigger) print.

Source code in pydsa/tree.py
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def draw_tree(root: Optional[TreeNode]) -> None:
    """
    Utility function to draw a binary tree in the terminal using PrettyPrintTree.
    Requires 'PrettyPrintTree' library: pip install PrettyPrintTree
    For colors, requires 'colorama': pip install colorama
    Supports node.color attribute for coloring the node value (e.g., 'blue', 'red', etc.).
    Uses horizontal orientation for a wider (bigger) print.
    """
    if not root:
        print("Empty tree")
        return

    print("\n")

    def get_value(node: TreeNode) -> str:
        val_str = str(node.val)
        if hasattr(node, "color"):
            from colorama import Fore, Style

            color = node.color.lower()
            color_map = {
                "black": Fore.BLACK,
                "red": Fore.RED,
                "green": Fore.GREEN,
                "yellow": Fore.YELLOW,
                "blue": Fore.BLUE,
                "magenta": Fore.MAGENTA,
                "cyan": Fore.CYAN,
                "white": Fore.WHITE,
            }
            if color in color_map:
                val_str = color_map[color] + val_str + Style.RESET_ALL
        return val_str

    pt = _pretty_print_tree()(
        lambda x: [c for c in (x.left, x.right) if c],
        get_value,
        border=True,
        color="\x1b[30;43m",
    )
    pt(root)

draw_general_tree

draw_general_tree(root: Optional[Node]) -> None

Draws a general tree (each node may have multiple children). Each node has: - .label: str - .children: List[Node]

Source code in pydsa/tree.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def draw_general_tree(root: Optional[Node]) -> None:
    """
    Draws a general tree (each node may have multiple children).
    Each node has:
        - .label: str
        - .children: List[Node]
    """
    if not root:
        print("Empty tree")
        return

    print("\n")

    class _Wrapper:
        def __init__(self, node: Node):
            self.node = node

    def get_children(w: _Wrapper):
        children: Any = w.node.children
        if isinstance(children, dict):
            children = children.values()
        return [_Wrapper(child) for child in children]

    def get_value(w: _Wrapper):
        return str(w.node.label) if hasattr(w.node, "label") else str(w.node.val)

    pt = _pretty_print_tree()(get_children, get_value, border=True, color="\x1b[30;43m")
    pt(_Wrapper(root))

generate_full_binary_tree

generate_full_binary_tree(
    height: int,
) -> Optional[TreeNode]

Generates a full (perfect) binary tree of the given height. Height 0 returns a single node. Node values are assigned sequentially from 1 in level-order.

Source code in pydsa/tree.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def generate_full_binary_tree(height: int) -> Optional[TreeNode]:
    """
    Generates a full (perfect) binary tree of the given height.
    Height 0 returns a single node.
    Node values are assigned sequentially from 1 in level-order.
    """
    if height < 0:
        return None

    # Calculate the total number of nodes in a perfect binary tree of height h: 2^(h+1) - 1
    num_nodes = (1 << (height + 1)) - 1
    # Create a level-order list of node values
    arr: List[Optional[int]] = list(range(1, num_nodes + 1))
    # Use the provided build_tree utility to construct the tree
    return build_tree(arr)

generate_random_tree

generate_random_tree(
    size: int,
    seed: Optional[int] = None,
    sparsity: float = 0.0,
    skew: float = 0.0,
    values: Optional[Iterable[int]] = None,
    bst: bool = False,
) -> Optional[TreeNode]

A random binary tree with size nodes.

The tree grows one node at a time. Each new node fills one of the empty child slots of the tree so far. Which slot is chosen is controlled by:

sparsity in [0, 1]: 0 fills the shallowest slots first, so the tree is as bushy as a complete tree; 1 fills the deepest slots, so the tree is a long chain. In between, each node picks deep with probability sparsity and shallow otherwise. skew in [-1, 1]: -1 always takes a left slot when one is available at the chosen depth, 1 always takes a right one, 0 is even. values: the node values, in level order; default is 1..size shuffled. With bst=True the values are sorted and assigned in inorder, so the tree is a valid BST with the same shape. seed: fixes the tree; the global random state is untouched.

Source code in pydsa/tree.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def generate_random_tree(
    size: int,
    seed: Optional[int] = None,
    sparsity: float = 0.0,
    skew: float = 0.0,
    values: Optional[Iterable[int]] = None,
    bst: bool = False,
) -> Optional[TreeNode]:
    """A random binary tree with `size` nodes.

    The tree grows one node at a time. Each new node fills one of the empty
    child slots of the tree so far. Which slot is chosen is controlled by:

    sparsity in [0, 1]: 0 fills the shallowest slots first, so the tree is
        as bushy as a complete tree; 1 fills the deepest slots, so the tree
        is a long chain. In between, each node picks deep with probability
        `sparsity` and shallow otherwise.
    skew in [-1, 1]: -1 always takes a left slot when one is available at
        the chosen depth, 1 always takes a right one, 0 is even.
    values: the node values, in level order; default is 1..size shuffled.
        With bst=True the values are sorted and assigned in inorder, so the
        tree is a valid BST with the same shape.
    seed: fixes the tree; the global random state is untouched.
    """
    if size <= 0:
        return None
    rng = random.Random(seed)
    vals = list(values) if values is not None else list(range(1, size + 1))
    if len(vals) != size:
        raise ValueError(f"need {size} values, got {len(vals)}")
    if values is None:
        rng.shuffle(vals)

    root = TreeNode(0)
    nodes = [root]
    # (depth, parent, side): every empty child slot in the tree so far
    slots = [(1, root, "left"), (1, root, "right")]
    for _ in range(size - 1):
        deep = rng.random() < sparsity
        depth = max(d for d, _, _ in slots) if deep else min(d for d, _, _ in slots)
        at = [s for s in slots if s[0] == depth]
        right = rng.random() < (1 + skew) / 2
        side = [s for s in at if s[2] == ("right" if right else "left")] or at
        depth, parent, name = rng.choice(side)
        slots.remove((depth, parent, name))
        child = TreeNode(0)
        setattr(parent, name, child)
        nodes.append(child)
        slots += [(depth + 1, child, "left"), (depth + 1, child, "right")]

    if bst:
        order: List[TreeNode] = []
        stack: List[TreeNode] = []
        cur: Optional[TreeNode] = root
        while stack or cur:
            while cur:
                stack.append(cur)
                cur = cur.left
            cur = stack.pop()
            order.append(cur)
            cur = cur.right
        for node, v in zip(order, sorted(vals)):
            node.val = v
    else:
        for node, v in zip(nodes, vals):
            node.val = v
    return root

build_general_tree

build_general_tree(nested: List[Any]) -> GeneralNode

A general tree from its nested form [val, [child, child, ...]], where each child is again [val, [...]]. Children get keys 0, 1, 2, ...

Source code in pydsa/tree.py
286
287
288
289
290
def build_general_tree(nested: List[Any]) -> GeneralNode:
    """A general tree from its nested form [val, [child, child, ...]], where
    each child is again [val, [...]]. Children get keys 0, 1, 2, ..."""
    val, kids = nested
    return GeneralNode(val, {i: build_general_tree(kid) for i, kid in enumerate(kids)})

get_general_tree

get_general_tree(
    root: Optional[Node],
) -> Optional[List[Any]]

The nested form [val, [child, child, ...]] of a general tree, the inverse of build_general_tree. Children come in the order of .children.

Source code in pydsa/tree.py
293
294
295
296
297
298
299
def get_general_tree(root: Optional[GeneralNode]) -> Optional[List[Any]]:
    """The nested form [val, [child, child, ...]] of a general tree, the
    inverse of build_general_tree. Children come in the order of .children."""
    if root is None:
        return None
    kids = root.children.values() if isinstance(root.children, dict) else root.children
    return [root.val, [get_general_tree(kid) for kid in kids]]