将最大堆转换为二叉搜索树

时间:2011-02-11 05:48:21

标签: c algorithm heap binary-tree

我们得到一个2 m 的数组 - 1个不同的,可比较的元素,从1开始索引。

我们可以将数组视为完整的二叉树:

Node is placed at index i.
Left child is placed at 2i.
Right child is placed at 2i+1.

例如,数组

[7 6 4 5 2 3 1]

是树

       7
    /    \
   6       4
  /  \    / \
 5    2   3  1 

现在,当被视为二叉树时,这些元素满足堆属性,节点大于其子节点:

A[i] > A[2i] and A[i] > A[2i+1]

是否有相当快速的就地算法来重新排列数组元素,以便生成的二叉树(如上所述)是二进制搜索树?

回想一下,在二叉搜索树中,一个节点大于其所有左后代,并且少于其所有右后代。

例如,上述数组的重新洗牌将是

[4 2 6 1 3 5 7]

对应于二叉搜索树

       4
    /    \
   2       6
  /  \    / \
 1    3   5  7 

3 个答案:

答案 0 :(得分:7)

首先我们注意到,我们可以 - 不失一般性 - 假设我们的二叉树中有元素1,2,3,... 2^m-1。所以,从现在开始,我们假设我们有这些数字。

然后,我的尝试将是一个将排序数组(即1 2 3 4 5)转换为表示已排序二叉树的数组的函数。

在带有(2^m)-1元素的已排序二进制树中,我们始终认为树的“底部”包含所有不均匀的数字,例如: m=3

     4
  2     6
 1 3   5 7

这意味着,在相应的数组中,我们得到的最后一个数字都是不均匀的数字:

4 2 6 1 3 5 7
      -------
         ^
         uneven numbers!

因此,我们可以通过确保相应数组中的最后2^(m-1)个数字都是不均匀的数字来构造二叉树的最后一行。因此,我们需要为最后一行做的就是构造一个函数,将具有不均匀索引的位置处的所有元素移动到最后一行。

因此,现在让我们假设我们有一个例程 - 给定一个排序数组作为输入 - 正确建立最后一行。

然后我们可以调用整个数组的例程来构造最后一行,而所有其他元素都保持排序。当我们在数组1 2 3 4 5 6 7上应用此例程时,我们遇到以下情况:

2 4 6 1 3 5 7
      -------
         ^
         correct!

在第一轮之后,我们将例程应用于剩余的子数组(即2 4 6),它构造了我们的二叉树的第二个“行”,而我们保留其余的元素,所以我们得到以下:

 now correct as well!
   v
  ---
4 2 6 1 3 5 7
      -------
         ^
         correct from run before

所以我们要做的就是构造一个能够正确安装最后一行(即数组的后半部分)的函数!

这可以在O(n log n)中完成,其中n是数组的输入大小。因此,我们只是从一端到另一端遍历数组,并以最后一行(即数组的后半部分)正确的方式交换不均匀位置。这可以就地完成。然后,我们对数组的前半部分进行排序(使用例如heapsort)。因此,该子例程的整个运行时为O(n log n)

因此总大小为n的数组的运行时为:

O(n log n) + O(n/2 log n/2) + O(n/4 log n/4) + ...O(n log n)相同。请注意,我们必须使用就地排序算法,例如Heapsort,这样整个内容就可以完全就位。

对不起,我不能进一步详细说明,但我认为你可以得到这个想法。

答案 1 :(得分:2)

设n = 2 m - 1.在线性时间内,我们都可以生成最大堆并按排序顺序提取二叉搜索树的元素,因此我们可以期望最好(假设基于比较的算法)是O(n log n)时间和O(1)空间。这是一个算法。

  1. 对于j = n下降到1,从j元素max-heap中弹出max元素并将其存储在(新腾出的)位置j。这会对数组进行排序。

  2. 使用分而治之策略将已排序的数组转换为二叉搜索树。 (天真这是Omega(log n)空间,但我相信我们可以将堆栈压缩为O(1)log(n)位字。)

    一个。树化少于根的元素。

    湾树化大于根的元素。

    ℃。通过将小于根的叶子旋转到位置(=三个反转)来合并树木,以便留下一半大小的子问题(O(n))。

    (08 04 12 02 06 10 14 01 03 05 07 09 11 13 15)16(24 20 28 18 22 26 30 17 19 21 23 25 27 29 31)

    (08 04 12 02 06 10 14)16(24 20 28 18 22 26 30)01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31

    (08 04 12)16(24 20 28)02 06 10 14 18 22 26 30 01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31

    (08)16(24)04 12 20 28 02 06 10 14 18 22 26 30 01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31

    16 08 24 04 12 20 28 02 06 10 14 18 22 26 30 01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31

答案 2 :(得分:0)

只是一些基本想法:

  1. 二叉搜索树是二叉树。
  2. 根的两个孩子都是零或自己的二元搜索树
  3. 这些值满足以下条件:left child< root<合适的孩子
  4. 条件1不是问题 - 堆也是二叉树。 条件2存在问题但建议采用自下而上的方法。 条件3也不满意。

    自下而上意味着: - 我们从所有叶子开始 - 这是没有问题的,它们是二叉搜索树。 - 现在我们继续通过递归遍历每个级别的父级到根。    - 如果左孩子比右孩子大,则交换子树。    - 用两个孩子的较大值交换根(这是正确的孩子)    - 这可能还不够 - 您可能需要继续纠正正确的子树,直到它再次成为二叉搜索树。

    这应该有效。但仍然 - 删除顶部元素并将其插入自平衡树将是更快/更好的方法,并且更容易实现(例如使用标准组件,如c ++中的std :: map)。

    二进制搜索树的另一个想法保存了一个左右根遍历树的属性获取排序值。这可以逆转。获取从堆中排序的值也应该很容易。试着将它结合起来 - 从堆中读取并直接从排序值中写入树。这可以在O(n)中完成我认为 - 但我不确定它是否可以在原地进行 - 我猜不是。