Деревья
В математике деревом называется произвольный связный ациклический неориентированный граф. В информатике же под деревом по-умолчанию понимают существенно более конкретную структуру, называемую в математике рекуррентным или алгебраическим деревом. Говоря неформально, дерево — это либо «лист», либо последовательность деревьев.
Дерево как АДТ
Несколько более формально можно дать следующее определение: дерево — это абстрактный тип данных (АДТ), изоморфный следующему
def leaf(x):
return ("leaf", x)
def branch(x, trees):
return ("branch", x, list(trees))
def reduce(tree, leaf_op, branch_op):
if tree[0] == "leaf":
return leaf_op(tree[1])
elif tree[0] == "branch":
return branch_op(
tree[1],
[reduce(subtree, leaf_op, branch_op)
for subtree in tree[2]])
else:
raise RuntimeError("wrong tree")
Напомним, что под изоморфизмом типов данных имеется в виду биекция между этими типами, «коммутирующая» со всеми операциями, ассоциированными с этими типами.
Например, следующий тип данных (называемый катаморфным представлением или кодировкой Чёрча) тоже является деревом:
def cata_leaf(x):
return (lambda l, b: l(x))
def cata_branch(x, trees):
return (lambda l, b: b(x, [t(l,b) for t in trees]))
def cata_reduce(tree, leaf_op, branch_op):
return tree(leaf_op, branch_op)
А вот — биекция между ними:
def tree_to_cata(tree):
return lambda l, b: reduce(tree, l, b)
def cata_to_tree(cata):
return cata(
lambda x: ("leaf", x),
lambda x, trees: ("branch", x, trees))
Или, если на неё посмотреть немного повнимательнее:
def tree_to_cata(tree):
return reduce(tree, cata_leaf, cata_branch)
def cata_to_tree(cata):
return cata_reduce(cata, leaf, branch)
Общее у двух вышеописанных моделей лишь поведение reduce
относительно leaf
и branch
. А именно, выполняются следующие свойства:
reduce(leaf(x), l, b) = l(x)
reduce(branch(x,trees), l, b) = b(x, [reduce(t,l,b) for t in trees])
Именно они и задают дерево как абстрактный тип данных.
Что это вообще было?
Теперь поясним подробнее то, что сейчас произошло. Для того, чтобы всё встало на свои места, достаточно понять одну простую вещь:
- деревья — то же самое, что алгебраические формулы (с которыми вы общаетесь уже не менее 9 лет)
Слова «то же самое» нужно понимать совершенно буквально: деревья (в информатике) и алгебраические формулы (в математике) — одно и то же.
Например, вот формула:
foo(1,2) + 3*(4**5 + foo(6,7))
Вот — соответствующиее ей дерево:
branch("+", [
branch("foo", [leaf(1), leaf(2)]),
branch("*", [
leaf(3),
branch("+", [
branch("**", [leaf(4), leaf(5)]),
branch("foo", [leaf(6), leaf(7)])])])])
А таинственный на первый взгляд метод reduce
всего лишь описывает
процесс вычисления формулы для заданных интерпретаций операций, в ней
фигурирующих. Например, чтобы получить из дерева выше значение формулы
выше, аналогичное тому, которое получит интерпретатор языка Python,
достаточно применить reduce
к следующим аргументам:
def interpret_leaf(x): return x
def interpret_branch(op, args):
a,b = args
if op == "+": return a+b
if op == "*": return a*b
if op == "**": return a**b
if op == "foo": return foo(a,b)
Кодировка Чёрча же всего лишь иллюстрирует тот факт, что формула — это то же самое, что и алгоритм её вычисления, параметризованный интерпретациями операций (а термином катаморфизм или свёртка называется как раз алгоритм вычисления формулы, параметризованный интерпретациями операций).