Algorithm Search⌘K

Tree sort in Python

Insert all keys into a binary search tree, then read them in order; Theta(n log n) average.

File
tree_sort.py, 70 lines
Level
I0 not checked yet
Toolchain
Python 3.14, pytest 9
Test framework
pytest
Run the tests
python3 -m pytest
tree_sort.pyRawGitHub
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/tree-sort/. The authors are listed in prov/P5E3N3.jsonl.

from typing import Generic, Protocol, TypeVar


class Ordered(Protocol):
    def __lt__(self, other: "Ordered", /) -> bool: ...


T = TypeVar("T", bound=Ordered)


class Node(Generic[T]):
    __slots__ = ("key", "left", "right")

    def __init__(self, key: T) -> None:
        self.key = key
        self.left: "Node[T] | None" = None
        self.right: "Node[T] | None" = None


def insert(root: "Node[T] | None", x: T) -> tuple["Node[T]", int]:
    """Insert x without recursion: left if x < key, else right. Return the root and the number of comparisons."""
    node = Node(x)
    if root is None:
        return node, 0
    u = root
    count = 0
    while True:
        count += 1
        if x < u.key:
            if u.left is None:
                u.left = node
                return root, count
            u = u.left
        else:
            if u.right is None:
                u.right = node
                return root, count
            u = u.right


def inorder(root: "Node[T] | None") -> list[T]:
    """Return the keys in inorder, with an explicit stack."""
    out: list[T] = []
    stack: list[Node[T]] = []
    u = root
    while stack or u is not None:
        while u is not None:
            stack.append(u)
            u = u.left
        u = stack.pop()
        out.append(u.key)
        u = u.right
    return out


def tree_sort(a: list[T]) -> int:
    """Sort the list a in place, stably. Return the number of comparisons."""
    root: Node[T] | None = None  # @line L1
    total = 0
    for x in a:  # @line L2
        root, c = insert(root, x)  # @line L3
        total += c
    i = 0  # @line L4
    for key in inorder(root):  # @line L5
        a[i] = key  # @line L6
        i += 1
    return total
test_tree_sort.py 59 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/tree-sort/. The authors are listed in prov/P5E3N3.jsonl.

import random

from tree_sort import inorder, insert, tree_sort


def test_example() -> None:
    a = [5, 3, 8, 1, 4, 7, 9, 2, 6]
    assert tree_sort(a) == 16
    assert a == list(range(1, 10))


def test_sorted_input_is_quadratic() -> None:
    a = list(range(10))
    assert tree_sort(a) == 45
    a = list(range(3000))
    assert tree_sort(a) == 3000 * 2999 // 2


def test_inorder() -> None:
    root = None
    for x in [2, 1, 3]:
        root, _ = insert(root, x)
    assert inorder(root) == [1, 2, 3]
    assert inorder(None) == []


def test_edges() -> None:
    for a in ([], [1], [2, 1], [1, 1, 1], [3, 2, 1]):
        b = list(a)
        tree_sort(b)
        assert b == sorted(a)


def test_stable() -> None:
    class Key:
        def __init__(self, k: int, tag: int) -> None:
            self.k, self.tag = k, tag

        def __lt__(self, other: "Key") -> bool:
            return self.k < other.k

    rng = random.Random(4)
    for _ in range(200):
        pairs = [(rng.randint(0, 4), i) for i in range(rng.randint(0, 30))]
        b = [Key(k, t) for k, t in pairs]
        tree_sort(b)
        assert [(x.k, x.tag) for x in b] == sorted(pairs)


def test_random_against_sorted() -> None:
    rng = random.Random(9)
    for _ in range(100):
        a = [rng.randint(-50, 50) for _ in range(rng.randint(0, 200))]
        b = list(a)
        tree_sort(b)
        assert b == sorted(a)

What the next level needs

  • the tests did not run in this build

Read the full page about Tree sort