Ответ 1
Ваша функция не может быть рекурсивной. Причина в том, что ваши рекурсивные вызовы insert
не заканчивают вычисление, они используются как подвыражения, в этом случае в new Node(...)
. Например. если бы вы просто искали нижний элемент, было бы легко сделать его хвостом рекурсивным.
Что происходит:. Когда вы опускаете дерево вниз, вызывая insert
на каждом из узлов, но вы должны помнить путь назад к корню, так как вам нужно восстановить дерево после замены нижнего листа новым значением.
Возможное решение: Запомните путь вниз, а не в стеке. Для примера используйте упрощенную структуру данных:
sealed trait Tree;
case object EmptyTree extends Tree;
case class Node(elem: Int, left:Tree, right:Tree) extends Tree;
Теперь определите, что такое путь: это список узлов вместе с информацией, если мы поехали направо или налево. Корень всегда находится в конце списка, лист в начале.
type Path = List[(Node, Boolean)]
Теперь мы можем сделать хвостовую рекурсивную функцию, которая вычисляет путь с заданным значением:
// Find a path down the tree that leads to the leaf where `v` would belong.
private def path(tree: Tree, v: Int): Path = {
@tailrec
def loop(t: Tree, p: Path): Path =
t match {
case EmptyTree => p
case [email protected](w, l, r) =>
if (v < w) loop(l, (n, false) :: p)
else loop(r, (n, true) :: p)
}
loop(tree, Nil)
}
и функция, которая берет путь и значение и восстанавливает новое дерево со значением как новый node в нижней части пути:
// Given a path reconstruct a new tree with `v` inserted at the bottom
// of the path.
private def rebuild(path: Path, v: Int): Tree = {
@tailrec
def loop(p: Path, subtree: Tree): Tree =
p match {
case Nil => subtree
case (Node(w, l, r), false) :: q => loop(q, Node(w, subtree, r))
case (Node(w, l, r), true) :: q => loop(q, Node(w, l, subtree))
}
loop(path, Node(v, EmptyTree, EmptyTree))
}
Вставка легко:
def insert(tree: Tree, v: Int): Tree =
rebuild(path(tree, v), v)
Обратите внимание, что эта версия не особенно эффективна. Вероятно, вы могли бы сделать его более эффективным, используя Seq
, или даже больше, используя изменяемый стек для хранения пути. Но при List
идея может быть хорошо выражена.
Отказ от ответственности: Я только скомпилировал код, я его вообще не тестировал.
Примечания:
- Это специальный пример использования молнии для перемещения по функциональному дереву. С застежками-молниями вы можете применять ту же идею на любом дереве и ходить по дереву в разных направлениях.
- Если вы хотите, чтобы дерево было полезно для больших наборов элементов (что вы, вероятно, делаете, если вы получаете переполнение стека), я настоятельно рекомендую сделать его самоуравновешенной. То есть, обновите структуру дерева так, чтобы она была всегда O (log n). Тогда ваши операции будут быстрыми во всех случаях, вам не придется беспокоиться о переполнении стека, и вы можете использовать свою версию, основанную на стеках, а не на хвостовой рекурсии. Вероятно, самым простым вариантом дерева с балансировкой является дерево .