数字1到n以指定顺序p_1,p_2,...,p_n插入二叉搜索树中。描述一个O(nlog n)时间算法来构造最终的二进制搜索树。
请注意: -
这是一个作业问题。这非常非常重要。事实上,乍一看似乎是不可能的。我已经考虑过了很多。我的观察: -
答案 0 :(得分:4)
诀窍是不要使用构造的BST进行查找。相反,请为查找保留额外的平衡BST。链接叶子。
例如,我们可能有
Constructed Balanced
3 2
/ \ / \
2 D 1 3
/ \ / | | \
1 C a b c d
/ \
A B
其中a, b, c, d
分别是A, B, C, D
的指针,而A, B, C, D
通常是空指针。
要插入,首先插入平衡BST(O(log n)),按照指向构造树(O(1))的指针,执行构造插入(O(1)),然后重新链接新叶子(O(1))。
答案 1 :(得分:2)
由于David Eisenstat没有时间扩展他的答案,我会尝试将更多细节纳入类似的算法。
<强>直觉强>
算法背后的主要直觉基于以下陈述:
声明#1 :如果BST包含值a
和b
(a < b
)并且它们之间没有值,那么{{1 (A
的节点)是a
的父级(可能是间接的)(值B
的节点)或b
是{的{父级} {父} {1}}。
这句话显然是正确的,因为如果它们的最低共同祖先B
是除A
和C
之外的其他节点,则其值A
必须介于{{1}之间}和B
。请注意,对于任何BST(平衡或不平衡),语句#1都是正确的。
声明#2 :如果简单(非平衡)BST包含值c
和a
(b
)并且它们之间没有值我们正在尝试添加值a
,以便b
,然后a < b
(值x
的节点)将是a < x < b
的直接右(更大)子项或直接左(较少)子节点X
中的哪个节点在树中较低。
假设两个节点中较低的一个是x
(另一种情况是对称的)。在插入阶段,值A
在插入过程中将与B
行进相同的路径,因为树不包含a
和x
之间的任何值,即任何比较值{{ 1}}和a
无法区分。这意味着值a
将导航树直到节点x
并且将在某个更早的步骤传递节点a
(参见语句#1)。作为x
,它应该成为x
的正确孩子。此时A
的直接右孩必须为空,因为B
位于x > a
的子树中,即该子树中的所有值都小于A
,因为没有树中A
和A
之间的值,没有值可以是节点B
的右子节点。
请注意,在执行重新平衡之后,对于某些平衡的BST,语句#2可能不正确,尽管这应该是一个奇怪的情况。
声明#3 :对于尚未在树中的任何值b
的平衡BST,您可以在a
时间内找到距离最近且值最接近的值。< / p>
这直接来自语句#1和#2:您需要的只是在BST中找到值b
的潜在插入点(取A
),其中一个值将是插入点的直接父级,并找到另一个需要将树移回根目录(再次使用x
)。
现在,算法背后的 想法 变得清晰:为了快速插入不平衡的BST,我们需要找到值越来越接近的节点。如果我们另外使用与目标(非平衡)BST相同的密钥以及来自该BST的相应节点作为值来维持平衡的BST,我们可以轻松地执行此操作。使用该附加数据结构,我们可以在O(log(N))
时间内找到每个新值的插入点,并在x
时间内使用新值更新此数据结构。
<强>算法强>
O(log(N))
初始化“主要”O(log(N))
和O(log(N))
。O(log(N))
执行:root
指定的树中查找与最接近的(balancedRoot
,指向主BST中的节点null
)相对应的节点,并且最接近的更大(x
,指向主BST中的节点balancedRoot
。
BalancedA
A
BalancedB
或B
。您可以使用存储在节点中的显式B
。如果下方节点为A
(节点数较少),请将A
添加为B
的直接右侧孩子,否则添加level
作为A
的直接左子节点(更大的节点)。或者(更聪明地)你可能会注意到,从#1和#2语句开始,两个候选插入位置中只有一个(x
是右子或A
的左子)为空,您可以在此处插入值x
。将值B
添加到平衡树(可能会从步骤#4重复使用)。
由于循环的内部步骤不超过A
,因此总复杂度为B
Java实施
我懒得自己实现平衡的BST所以我使用标准的Java TreeMap
实现了Red-Black树,并且使用了对应于步骤#4的有用的x
和x
方法算法(您可以查看source code以确保两者实际上都是O(log(N))
)。
O(N*log(N))
答案 2 :(得分:2)
这是线性时间算法。 (我说我不打算在这个问题上工作,所以如果你喜欢这个答案,请把赏金奖给SergGr。)
使用节点1..n创建双向链表并计算p的倒数。对于i从n到1,让q成为列表中p_i的左邻居,让r成为右邻居。如果p ^ -1(q)> p ^ -1(r),然后使p_i成为q的右子。如果p ^ -1(q)< p ^ -1(r),然后使p_i成为r的左子。从列表中删除p_i。
在Python中:
class Node(object):
__slots__ = ('left', 'key', 'right')
def __init__(self, key):
self.left = None
self.key = key
self.right = None
def construct(p):
# Validate the input.
p = list(p)
n = len(p)
assert set(p) == set(range(n)) # 0 .. n-1
# Compute p^-1.
p_inv = [None] * n
for i in range(n):
p_inv[p[i]] = i
# Set up the list.
nodes = [Node(i) for i in range(n)]
for i in range(n):
if i >= 1:
nodes[i].left = nodes[i - 1]
if i < n - 1:
nodes[i].right = nodes[i + 1]
# Process p.
for i in range(n - 1, 0, -1): # n-1, n-2 .. 1
q = nodes[p[i]].left
r = nodes[p[i]].right
if r is None or (q is not None and p_inv[q.key] > p_inv[r.key]):
print(p[i], 'is the right child of', q.key)
else:
print(p[i], 'is the left child of', r.key)
if q is not None:
q.right = r
if r is not None:
r.left = q
construct([1, 3, 2, 0])
答案 3 :(得分:1)
这是我的O(n log ^ 2 n)尝试,不需要构建平衡树。
按照自然顺序(1到n)将节点放入数组中。还可以按插入顺序将它们链接到链接列表中。每个节点都存储其插入顺序以及密钥。
算法是这样的。
输入是链表中的节点,节点数组中索引的范围(low, high)
rootkey
。取消它与列表的链接。(low, rootkey-1)
和(rootkey+1, high)
作为索引范围传递。 每个级别的排序操作为算法提供了额外的log n
复杂度因子。
答案 4 :(得分:1)
这是一个O(n log n)
算法,通过使用Y-fast trie而不是平衡二叉树,也可以适应O(n log log m)
时间,其中m
是范围。
在二叉搜索树中,较低的值保留较高的值。插入顺序与沿最终树行进时的右或左节点选择相对应。任何节点x
的父节点是先前插入的最小数字或先前插入的最小数字,以后插入的为止。
我们可以使用上面O(n log n)
最差时间的逻辑识别并连接列出的节点,并通过维护访问节点的平衡二叉树到目前为止我们遍历插入顺序。
说明:
让我们想象一下拟议的下级父母p
。现在假设在l > p
之前插入了一个x
但仍然低于p
的数字。 (1)p
在插入过程中通过了l
,在这种情况下,x
必须通过l
才能转到p
,但这与x
相矛盾1}}如果达到l
,一定是正确的;或(2)p
未通过l
,在这种情况下,p
位于l
左侧的子树中,但这意味着插入的数字小于{{} 1}}但大于l
,这是一个矛盾。
显然,x
之后插入的l < x
,大于p
的数字也会p
与p
的父母相矛盾1)x
在插入期间传递了l
,这意味着在插入p
时已经分配了p
的正确孩子;或(2)x
位于l
右侧的子树中,这又意味着插入的数字小于p
但大于l
,这是一个矛盾
因此,对于具有较低父级的任何节点x
,该父级必须是低于x
之前插入的最大数字。类似的逻辑涵盖了更高的拟议父母的情景。
现在让我们假设x
的父x
已插入p < x
之前,最小数字大于h
之前插入。{1}}。然后(1)x
通过h
,在这种情况下,p
的右边节点在插入p
时已被分配;或(2)x
位于h
的子树右侧,这意味着之前插入的数字低于p
且大于h
,但这与我们的断言相矛盾x
是迄今为止插入的最小数字,大于h
。
答案 5 :(得分:0)
由于这是一项作业,我正在发布提示而不是答案。
对数字进行排序,同时保持插入顺序。假设您输入了[1,7,3,5,8,2,4]
。排序后,您将获得[[1,0], [2,5], [3,2], [4, 6], [5,3], [7,1], [8,4]]
。这实际上是生成树的有序遍历。在给定有序遍历和插入顺序(这部分将是线性时间)的情况下,仔细考虑如何重构树。
如果你确实需要它们,会有更多提示。