我正在使用Elixir编写一些用于二叉搜索树的程序,并且遇到了一个具有递归计算高度的功能的障碍。
关于树的高度的基本递归公式如下。
其他
递归获取左子树的最大深度,即
致电maxDepth( tree->left-subtree)
递归获取右子树的最大深度,即
致电maxDepth( tree->right-subtree)
获取左右最大深度的最大值
子树并为当前节点添加1。
max_depth = max(max dept of left subtree,max depth of right subtree) + 1
返回最大深度
就我而言,当我尝试使用通用节点结构测试该功能时,出现以下错误。
**(ArithmeticError)算术表达式中的错误参数
我尝试删除left_depth和right_depth加1。那消除了算术错误,但我也没有得到任何数值结果(高度)。
这是我的身高函数。如您所见,它几乎遵循了递归格式。
# calculate the height
@spec height(bst_node) :: number
def height(node) do
if is_nil(node) do
IO.puts "No tree exists. The height is 0"
else
left_depth = height(node.left)
right_depth = height(node.right)
if left_depth >= right_depth do
left_depth + 1
IO.puts "The height of the tree is #{left_depth + 1}"
else
right_depth + 1
IO.puts "The height of the tree is #{right_depth + 1}"
end
end
end
我更希望能够在长生不老药中递归执行此height函数,但是如果我必须采用非递归方法来完成它,那肯定不是世界末日。这应该只显示通用树的高度。
答案 0 :(得分:3)
e剂的三个功能在实施递归解决方案时非常有用:
这是使用前两个功能的示例解决方案:
defmodule Tree do
defstruct [:left, :right]
def height(%Tree{left: nil, right: nil}), do: 0
def height(%Tree{left: nil, right: right}), do: 1 + height(right)
def height(%Tree{left: left, right: nil}), do: 1 + height(left)
def height(%Tree{left: left, right: right}), do: 1 + max(height(left), height(right))
end
首先,用Tree
和left
定义一个名为right
的结构来表示我们的树。
然后,我们定义height
函数的4个子句,该子句使用模式匹配来检查Tree
的{{1}}和left
的值。
right
和left
均为right
,则我们是一片叶子,可以返回高度nil
0
,那么我们可以返回非零树的高度,再加上nil
作为我们自己(当前节点)的高度。1
。请注意,1
样式将导致一堆递归调用,因为它必须跟踪调用堆栈中子节点的高度才能最终执行1 + recursive_call()
操作。我们可以通过将进行中的高度作为1 +
累加器参数传递给函数,并利用尾部调用优化功能来使此过程最小化,这可以使编译器减少最后一件事时所需的堆栈帧数函数要做的就是调用自身。在这种情况下,我们仍然必须在final子句中计算两个子树的acc
,这意味着在大多数情况下我们不使用尾部调用,但出于完整性考虑,我将其包括在内:
max
示例:
def height_tailcall(%Tree{left: nil, right: nil}, acc), do: acc
def height_tailcall(%Tree{left: nil, right: right}, acc) do
height_tailcall(right, acc + 1)
end
def height_tailcall(%Tree{left: left, right: nil}, acc) do
height_tailcall(left, acc + 1)
end
def height_tailcall(%Tree{left: left, right: right}, acc) do
max(height_tailcall(left, acc + 1), height_tailcall(right, acc + 1))
end
输出:
def example do
leaf = %Tree{}
height1 = %Tree{left: leaf, right: leaf}
height2 = %Tree{left: height1, right: height1}
height3 = %Tree{left: height2, right: height1}
height4 = %Tree{left: nil, right: height3}
IO.inspect(height(leaf), label: "leaf")
IO.inspect(height(height1), label: "height1")
IO.inspect(height(height2), label: "height2")
IO.inspect(height(height3), label: "height3")
IO.inspect(height(height4), label: "height4")
IO.inspect(height_tailcall(leaf, 0), label: "leaf (tail recursion)")
IO.inspect(height_tailcall(height1, 0), label: "height1 (tail recursion)")
IO.inspect(height_tailcall(height2, 0), label: "height2 (tail recursion)")
IO.inspect(height_tailcall(height3, 0), label: "height3 (tail recursion)")
IO.inspect(height_tailcall(height4, 0), label: "height4 (tail recursion)")
height4
end
答案 1 :(得分:3)
您的例外情况
**(ArithmeticError)算术表达式中的错误参数
与您的代码正确与否无关。默认情况下,代码块的值是该代码块内部求值的最后一个表达式。当你说:
right_depth + 1
IO.puts "The height of the tree is #{right_depth + 1}"
您的 last 表达式是IO.puts,因此就是从函数调用中返回的内容。
IO.puts
是您的最后一个表达式,它返回一个原子。使用助手i/1 in IEx进行验证:
iex(3)> i(IO.puts "Puts returns an atom")
Puts returns an atom
Term
:ok
Data type
Atom
Reference modules
Atom
:ok
尝试添加两个原子会导致无效操作。确切的消息和错误可以在IEx中重现。
iex(4)> :atom + :atom
** (ArithmeticError) bad argument in arithmetic expression: :atom + :atom
:erlang.+(:atom, :atom)