Algorithm Search⌘K

Left-child right-sibling representation in Python

Rooted tree stored with two pointers per node: first child and next sibling.

File
left_child_right_sibling.py, 93 lines
Level
I0 not checked yet
Toolchain
Python 3.14, pytest 9
Test framework
pytest
Run the tests
python3 -m pytest
left_child_right_sibling.pyRawGitHub
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/left-child-right-sibling/. The authors are listed in prov/NWHW83.jsonl.

from typing import Iterator

NIL = -1


class LCRSTree:
    """A rooted ordered tree on the nodes 0, 1, 2, ... with three arrays.

    child[x] is the leftmost child of x, sibling[x] the next sibling of x to
    the right, and parent[x] the parent of x. NIL (-1) marks a missing link.
    Node 0 is the root.
    """

    def __init__(self) -> None:
        self.child: list[int] = [NIL]
        self.sibling: list[int] = [NIL]
        self.parent: list[int] = [NIL]

    def size(self) -> int:
        return len(self.child)

    def add_child(self, p: int) -> int:
        """Add a new node as the leftmost child of p and return it."""
        x = len(self.child)
        self.child.append(NIL)
        self.parent.append(p)  # @line L1
        self.sibling.append(self.child[p])  # @line L2
        self.child[p] = x  # @line L3
        return x

    def children(self, p: int) -> Iterator[int]:
        c = self.child[p]  # @line L4
        while c != NIL:  # @line L5
            yield c  # @line L6
            c = self.sibling[c]  # @line L7

    def degree(self, p: int) -> int:
        return sum(1 for _ in self.children(p))

    def preorder(self, r: int = 0) -> list[int]:
        out: list[int] = []
        stack = [r]  # @line L8
        while stack:  # @line L9
            x = stack.pop()  # @line L10
            out.append(x)  # @line L11
            if x != r and self.sibling[x] != NIL:  # @line L12
                stack.append(self.sibling[x])
            if self.child[x] != NIL:  # @line L13
                stack.append(self.child[x])
        return out

    def postorder(self, r: int = 0) -> list[int]:
        """Children before parents: the inorder of the binary tree (child = left, sibling = right)."""
        out: list[int] = []
        stack: list[tuple[int, bool]] = [(r, False)]
        while stack:
            x, done = stack.pop()
            if done:
                out.append(x)
                continue
            if x != r and self.sibling[x] != NIL:
                stack.append((self.sibling[x], False))
            stack.append((x, True))
            if self.child[x] != NIL:
                stack.append((self.child[x], False))
        return out

    def depth(self, x: int) -> int:
        d = 0
        while self.parent[x] != NIL:
            x = self.parent[x]
            d += 1
        return d


def from_parent_array(parent: list[int]) -> LCRSTree:
    """Build the tree of a parent array (root 0, parent[0] = -1); children in increasing order.

    Node i of the result is node i of the input.
    """
    n = len(parent)
    t = LCRSTree()
    t.child = [NIL] * n
    t.sibling = [NIL] * n
    t.parent = list(parent)
    for x in range(n - 1, 0, -1):
        p = parent[x]
        t.sibling[x] = t.child[p]
        t.child[p] = x
    return t
test_left_child_right_sibling.py 61 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/left-child-right-sibling/. The authors are listed in prov/NWHW83.jsonl.

import random

from left_child_right_sibling import NIL, LCRSTree, from_parent_array

# A=0, B=1, C=2, D=3, E=4, F=5, G=6
PARENT = [-1, 0, 0, 0, 1, 1, 3]


def test_example() -> None:
    t = from_parent_array(PARENT)
    assert t.child == [1, 4, NIL, 6, NIL, NIL, NIL]
    assert t.sibling == [NIL, 2, 3, NIL, 5, NIL, NIL]
    assert list(t.children(0)) == [1, 2, 3]
    assert t.preorder() == [0, 1, 4, 5, 2, 3, 6]
    assert t.postorder() == [4, 5, 1, 2, 6, 3, 0]
    assert t.preorder(1) == [1, 4, 5]
    assert t.depth(6) == 2


def test_add_child_prepends() -> None:
    t = LCRSTree()
    a = t.add_child(0)
    b = t.add_child(0)
    c = t.add_child(a)
    assert list(t.children(0)) == [b, a]
    assert list(t.children(a)) == [c]
    assert t.preorder() == [0, b, a, c]
    assert t.degree(0) == 2 and t.degree(c) == 0


def naive_preorder(kids: list[list[int]], r: int) -> list[int]:
    out = [r]
    for c in kids[r]:
        out += naive_preorder(kids, c)
    return out


def naive_postorder(kids: list[list[int]], r: int) -> list[int]:
    out: list[int] = []
    for c in kids[r]:
        out += naive_postorder(kids, c)
    return out + [r]


def test_random_trees() -> None:
    rng = random.Random(3)
    for _ in range(200):
        n = rng.randrange(1, 40)
        parent = [-1] + [rng.randrange(i) for i in range(1, n)]
        kids: list[list[int]] = [[] for _ in range(n)]
        for x in range(1, n):
            kids[parent[x]].append(x)
        t = from_parent_array(parent)
        for v in range(n):
            assert list(t.children(v)) == kids[v]
            assert t.preorder(v) == naive_preorder(kids, v)
            assert t.postorder(v) == naive_postorder(kids, v)
        assert sum(t.degree(v) for v in range(n)) == n - 1

What the next level needs

  • the tests did not run in this build

Read the full page about Left-child right-sibling representation