我有这段代码来查找二叉树的直径。 二叉树的直径:树的直径(有时称为宽度)是树中两个叶子之间最长路径上的节点数。
我试图理解下面的代码和递归。我正试着用这棵简单的树干跑。我理解当root为20时高度将变为1(Max(0,0)+1)然后返回Math.Max(0 + 0 + 1,Max(0,0))。我的理解是它将ldiameter设置为1,返回值为root = 10.这是正确的吗?并且在这一点上lh变为1.它如何变为1?另外,如果你可以帮助我逐步干这个简单的树,这将是非常有用的。
10
/ \
20 30
public int FindDiameter_util(Node root, ref int height)
{
/* lh --> Height of left subtree
rh --> Height of right subtree */
int lh = 0, rh = 0;
/* ldiameter --> diameter of left subtree
rdiameter --> Diameter of right subtree */
int ldiameter = 0, rdiameter = 0;
if (root == null)
{
height = 0;
return 0; /* diameter is also 0 */
}
/* Get the heights of left and right subtrees in lh and rh
And store the returned values in ldiameter and ldiameter */
ldiameter = FindDiameter_util(root.left, ref lh);
rdiameter = FindDiameter_util(root.right, ref rh);
/* Height of current node is max of heights of left and
right subtrees plus 1*/
height = Math.Max(lh, rh) + 1;
return Math.Max(lh + rh + 1, Math.Max(ldiameter, rdiameter));
}
答案 0 :(得分:3)
递归是一种基于堆栈的方法。函数的递归调用将比颁发者更早执行。如果考虑函数组合的概念,可以更容易理解递归。让我们看看这个示例函数调用:
f(g(x))
正如您所看到的,f
的参数是g(x)
,这意味着在g(x)
执行f(g(x))
之前需要先计算g(x)
,因此f(g(x))
}是g
的依赖项。现在,假设f
也是f(f(x))
,所以你打电话给
f(x)
以类似的方式,f(f(x))
是f(f(x))
的依赖关系,因为如果没有f(x)
的结果,则无法计算f
。
如果您理解这个纯粹的数学概念,那么下一步就是将算法添加到f(f(x))
作为上下文。在编程中,FindDiameter_util
不一定只是计算,但在此过程中可能会发生某些状态更改。
下一步是理解重复递归的概念。在我们的情况下,我们事先不知道应该从FindDiameter_util
内调用root == null
多少次,因为它应该适用于任何树。那么,让我们稍微分析一下这个功能。
事实:
return
这一事实中识别出来,这也是结束符号(参见root == null
)此处使用的策略称为Divide et Impera。这包括几个阶段: - 将任务划分为类似但较小的子任务,直到达到琐碎为止 - 征服结果,获得对逐渐更复杂的子任务的响应,直到你得到初始问题的答案
在我们的例子中,算法,简而言之就是从根到叶子,直到它在所有子树中达到平凡,这由class SomeClass{
constructor(){
let name="john doe"
}
static newName(){
//i want to get the "name" variable here
}
}
的结束符号确定,然后使用琐碎的答案得到下一个琐碎问题的答案。所以,你要从根到叶子分裂,然后从叶子回到根,征服。
答案 1 :(得分:1)
让我们通过你的简单树递归:
[] <---- root
/ \
[] [] <---- children
最初调用函数时,root == 0
为真,因此输入高度初始化为0:
[h=0] <---- root
/ \
[] [] <---- children
然后,您将根左侧和右侧子树的高度设置为0:
[h = 0, lh = 0, rh = 0] <---- root
/ \
[] [] <---- children
然后你递归左边的孩子,传递lh
作为身高参数:
[h = 0, lh = 0, rh = 0] <---- root
/ \
[h=0] [] <---- children
左子将为其自己的左右子树初始化其高度变量:
[h = 0, lh = 0, rh = 0] <---- root
/ \
[h=0, lh=0, rh=0] [] <---- children
然后左边的孩子将尝试递归其自己的左子树(即使没有一个;它是null
):
[h = 0, lh = 0, rh = 0] <---- root
/ \
[h=0, lh=0, rh=0] [] <---- children
/
null
在这个空节点上,我们将其识别为这样,并返回0
,然后返回到父节点,lh
设置为0
(同样,没有更改):
[h = 0, lh = 0, rh = 0] <---- root
/ \
[h=0, lh=0, rh=0] [] <---- children
然后我们对正确的子树进行递归,但它也是null:
[h = 0, lh = 0, rh = 0] <---- root
/ \
[h=0, lh=0, rh=0] [] <---- children
\
null
因此我们将0
的高度返回给父级,将rh
设置为0
(再次):
[h = 0, lh = 0, rh = 0] <---- root
/ \
[h=0, lh=0, rh=0] [] <---- children
到目前为止,非常无趣。但是现在我们知道了子树的高度,我们可以将当前树的高度计算为max(lh, rh) + 1
,这为我们提供了这片叶子的1
高度(只有一棵树有一棵树)高度为1,所以只有一个根的子树的高度为1)才有意义。
[h = 0, lh = 0, rh = 0] <---- root
/ \
[h=1, lh=0, rh=0] [] <---- children
但是,此级别的h
实际上是对根目录lh
的引用,因此它也变为1
:
[h = 0, lh = 1, rh = 0] <---- root
/ \
[h=1, lh=0, rh=0] [] <---- children
现在左子树已经完成,所以我们以相同的方式递归到右子树(详细信息省略):
[h = 0, lh = 1, rh = 1] <---- root
/ \
[h=1, lh=0, rh=0] [h=1, lh=0, rh=0] <---- children
现在我们已经在两个子树上递归,我们返回到根,现在知道它的左右子树的高度(都是1
),所以它可以计算:
height = Math.Max(lh, rh) + 1;
是
height = Math.Max(1, 1) + 1 = 2
因此root的高度设置为2:
[h = 2, lh = 1, rh = 1] <---- root
/ \
[h=1, lh=0, rh=0] [h=1, lh=0, rh=0] <---- children
答案 2 :(得分:0)
最后一行最重要的是:
return Math.Max(lh + rh + 1, Math.Max(ldiameter, rdiameter));
当前树有3种情况:
1)左子树中最长的简单路径(对于currenet树)
2)右子树中最长的简单路径(对于currenet树)
3)最长的简单路径由3个部分组成:从最深节点到左子树中的根的路径,当前节点,从根到最右边子树中最深节点的路径。
我们可以递归地计算出3种可能的直径,然后选择它们的最大值。