【问题标题】:Generating uniformly random curious binary trees生成均匀随机的好奇二叉树
【发布时间】:2010-07-13 22:34:45
【问题描述】:

如果一个有 N 个节点的二叉树是一个节点值为 1、2、..、N 并且满足以下属性的二叉树,那么它就是“好奇”的。

  • 树的每个内部节点都恰好有一个大于它的后代。
  • 1,2, ..., N 中的每个数字在树中只出现一次。

奇异二叉树的例子

  4
 / \
5   2
   / \
  1   3

你能给出一个算法来生成一个具有 n 个节点的均匀随机好奇二叉树,它在 O(n) 保证时间内运行吗?

假设您只能使用随机数生成器,它可以为您提供范围 [1, k] 中任意 1

O(nlogn) 时间的解决方案也会得到我的支持。

请遵循标记二叉树不同的通常定义,以考虑不同的好奇二叉树。

【问题讨论】:

  • 好的。我很闷。下班休息日:-)
  • 随机作为不同树的均匀分布或从 [1,n] 中的均匀随机分布中挑选的每个节点?
  • @Jacob:随机分布在不同的树上。节点总是 1,2,...,n。编辑问题以澄清。
  • 对于奇数 N>2,有偶数个树。运行 RNG c 次将给出 c^N 个可能的输出,一个奇数。我们甚至可以保证运行时的任何事情吗?
  • @Nabb:对不起,我不明白你的反对意见。

标签: algorithm puzzle


【解决方案1】:

“好奇”二叉树和标准堆之间存在双射。也就是说,给定一个堆,递归地(从顶部开始)将每个内部节点与其最大的子节点交换。而且,正如我不久前在 StackOverflow 中了解到的,堆相当于 1,2,...,N 的排列。所以你应该做一个随机排列,把它变成一个堆;或以与进行随机排列相同的方式递归地创建堆。之后,您可以将堆转换为“奇怪的树”。

【讨论】:

  • 你将如何在确定的 O(n) 时间内做到这一点?
  • 使用排序方法在 O(n(log n)) 时间内看起来并不难。但是线性时间?从随机堆到随机“好奇树”的转换是线性时间,那里有一点运气。我会问你是否可以在线性时间内制作一个随机堆。您可以使用 Knuth 排序在线性时间内进行随机排列。不确定随机堆。
  • 我相信我们可以使用 Range Minimum Query 算法(O(n) time O(n) space)使用这种方法在线性时间内解决问题。
【解决方案2】:

啊哈,我想我知道如何在 O(N) 时间内创建一个随机堆。 (之后,使用 Greg Kuperberg 的答案中的方法转换为“好奇”二叉树。)

edit 2:用于直接生成随机最小堆的粗略伪代码。最大堆是相同的,只是插入到堆中的值是相反的数字顺序。

struct Node {
   Node left, right;
   Object key;
   constructor newNode() { 
     N = new Node; 
     N.left = N.right = null; 
     N.key = null;
   }
}

function create-random-heap(RandomNumberGenerator rng, int N)
{
   Node heap = Node.newNode();
   // Creates a heap with an "incomplete" node containing a null, and having
   // both child nodes as null.

   List incompleteHeapNodes = [heap];
   // use a vector/array type list to keep track of incomplete heap nodes.

   for k = 1:N
   {
      // loop invariant: incompleteHeapNodes has k members. Order is unimportant.

     int m = rng.getRandomNumber(k);
     // create a random number between 0 and k-1
     Node node = incompleteHeapNodes.get(m);
     // pick a random node from the incomplete list, 
     // make it a complete node with key k.
     // It is ok to do so since all of its parent nodes
     // have values less than k.
     node.left = Node.newNode();
     node.right = Node.newNode();
     node.key = k;

     // Now remove this node from incompleteHeapNodes
     // and add its children. (replace node with node.left,
     // append node.right)

     incompleteHeapNodes.set(m, node.left);
     incompleteHeapNodes.append(node.right);

     // All operations in this loop take O(1) time.
   }

   return prune-null-nodes(heap);
}

// get rid of all the incomplete nodes.
function prune-null-nodes(heap)
{
   if (heap == null || heap.key == null)
      return null;
   heap.left = prune-null-nodes(heap.left);
   heap.right = prune-null-nodes(heap.right);
}

【讨论】:

  • 我想知道这是否会产生一个堆分布,就像你有一个随机排列并按顺序将排列中的每个数字插入标准堆一样。不知道如何证明或反驳它们是相同的分布。
  • @Jason:选择任何特定的堆并考虑使用您的方法获得该堆的概率。我相信我们可以证明概率正好是 1/n!。
  • 我可以相信...但是其他分布(选择随机排列,一个一个地插入元素)也同样分布吗?我猜如果有n!结果的不同堆,它必须是,否则会少于n!不同的堆(只有 n! 个排列),这会遗漏一些。
  • 是的,另一种选择随机分布并创建堆的方法(但不是逐个插入,请参阅:stackoverflow.com/questions/3153620/counting-treaps)也可以。其实我相信也可以做成O(n),但是那里的算法比较复杂。
  • 它有效。该算法以升序将数字插入表示为堆结构的列表中。堆的空叶子代表可以插入每个新元素的间隙。该算法假设间隙的可能性相同,并且确实如此。它是用于进行随机排列的线性时间算法的堆版本。 (如果你想要排列,堆也可以在线性时间内读出。)很有趣。
猜你喜欢
  • 2015-03-04
  • 2020-06-26
  • 2018-10-04
  • 1970-01-01
  • 1970-01-01
  • 2020-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多