在 O(logn) 中打印二叉树中的随机元素
Print random element from binary tree in O(logn)
给定一个二叉树 TreeNode
比如:
class TreeNode {
int data;
TreeNode left;
TreeNode right;
int size;
}
其中size
为(左子树+右子树+1)的节点数。
Print a random element from the tree in O(logn) running time.
注意:这个post不同于this,因为它清楚地提到我们有一个size
与每个此问题中的节点。
PS:post 的灵感来自 this。
有一种简单的方法,复杂度为 O(n)。
为了提高复杂性,我们需要在每次迭代时创建自己的分支顺序,而不是像 BFS 和 DFS 那样按顺序进行。我们可以通过每个节点的size
属性来决定是遍历左sub-tree还是右sub-tree。方法如下:
- 生成1到
root.size
范围内的随机数(比如r
)
- 从根节点开始遍历,决定是向左sub-tree、right-subtree还是打印root:
- 如果
r <= root.left.size
,向左遍历sub-tree
- 如果
r == root.left.size + 1
,打印根(我们已经找到要打印的随机节点)
- 如果
r > root.left.size + 1
,向右遍历sub-tree
- 本质上,我们定义了一个顺序,其中当前节点的排序为(当前左子树的大小)+ 1。
- 由于我们在每次迭代时消除了向左或向右遍历sub-tree,因此其运行时间为O(logn)。
pseudo-code 看起来像这样:
traverse(root, random)
if r == root.left.size + 1
print root
else if r > root.left.size + 1
traverse(root.right, random - root.left.size - 1)
else
traverse(root.left, random)
以下是 java 中的实现:
public static void printRandom(TreeNode n, int randomNum) {
int leftSize = n.left == null ? 0 : n.left.size;
if (randomNum == leftSize + 1)
System.out.println(n.data);
else if (randomNum > leftSize + 1)
printRandom(n.right, randomNum - (leftSize + 1));
else
printRandom(n.left, randomNum);
}
使用尺寸!
在 0 和 n 之间选择一个随机数 q。
从根开始。如果left->size == q
return 当前节点值。如果 left->size < q
向右走,否则你向左走。如果你向右减去 q -= left->size + 1
。重复直到输出一个节点。
这给你o(树的高度)。如果树是平衡的,你会得到 O(LogN)。
如果树不平衡但你仍然想保持 O(logN) 你可以做同样的事情但限制最大迭代次数。请注意,在这种情况下,并非所有节点都具有相同的 returned 概率。
是的,使用尺寸!
正如索林所说,在0
和n - 1
之间选择一个随机数i
(不是n
)
然后执行以下指令:
Treenode rand_node = select(root, i);
其中 select 可以如下所示:
TreeNode select_rec(TreeNode r, int i) noexcept
{
if (i == r.left.size)
return r;
if (i < r.left.size)
return select_rec(r.left, i);
return select_rec(r.right, i - r.left.size - 1);
}
现在一个非常重要的技巧:空节点必须是大小设置为 0 的哨兵节点,这是有意义的,因为空树有零个节点。您可以避免使用哨兵,但是 select()
操作稍微复杂一些。
如果树是平衡的,那么select()
是O(log n)
给定一个二叉树 TreeNode
比如:
class TreeNode {
int data;
TreeNode left;
TreeNode right;
int size;
}
其中size
为(左子树+右子树+1)的节点数。
Print a random element from the tree in O(logn) running time.
注意:这个post不同于this,因为它清楚地提到我们有一个size
与每个此问题中的节点。
PS:post 的灵感来自 this。
有一种简单的方法,复杂度为 O(n)。
为了提高复杂性,我们需要在每次迭代时创建自己的分支顺序,而不是像 BFS 和 DFS 那样按顺序进行。我们可以通过每个节点的size
属性来决定是遍历左sub-tree还是右sub-tree。方法如下:
- 生成1到
root.size
范围内的随机数(比如r
) - 从根节点开始遍历,决定是向左sub-tree、right-subtree还是打印root:
- 如果
r <= root.left.size
,向左遍历sub-tree - 如果
r == root.left.size + 1
,打印根(我们已经找到要打印的随机节点) - 如果
r > root.left.size + 1
,向右遍历sub-tree
- 如果
- 本质上,我们定义了一个顺序,其中当前节点的排序为(当前左子树的大小)+ 1。
- 由于我们在每次迭代时消除了向左或向右遍历sub-tree,因此其运行时间为O(logn)。
pseudo-code 看起来像这样:
traverse(root, random)
if r == root.left.size + 1
print root
else if r > root.left.size + 1
traverse(root.right, random - root.left.size - 1)
else
traverse(root.left, random)
以下是 java 中的实现:
public static void printRandom(TreeNode n, int randomNum) {
int leftSize = n.left == null ? 0 : n.left.size;
if (randomNum == leftSize + 1)
System.out.println(n.data);
else if (randomNum > leftSize + 1)
printRandom(n.right, randomNum - (leftSize + 1));
else
printRandom(n.left, randomNum);
}
使用尺寸!
在 0 和 n 之间选择一个随机数 q。
从根开始。如果left->size == q
return 当前节点值。如果 left->size < q
向右走,否则你向左走。如果你向右减去 q -= left->size + 1
。重复直到输出一个节点。
这给你o(树的高度)。如果树是平衡的,你会得到 O(LogN)。
如果树不平衡但你仍然想保持 O(logN) 你可以做同样的事情但限制最大迭代次数。请注意,在这种情况下,并非所有节点都具有相同的 returned 概率。
是的,使用尺寸!
正如索林所说,在0
和n - 1
之间选择一个随机数i
(不是n
)
然后执行以下指令:
Treenode rand_node = select(root, i);
其中 select 可以如下所示:
TreeNode select_rec(TreeNode r, int i) noexcept
{
if (i == r.left.size)
return r;
if (i < r.left.size)
return select_rec(r.left, i);
return select_rec(r.right, i - r.left.size - 1);
}
现在一个非常重要的技巧:空节点必须是大小设置为 0 的哨兵节点,这是有意义的,因为空树有零个节点。您可以避免使用哨兵,但是 select()
操作稍微复杂一些。
如果树是平衡的,那么select()
是O(log n)