二进制搜索树的定义中是否允许重复键?


139

我试图找到二叉搜索树的定义,并且在各处不断找到不同的定义。

有人说,对于任何给定的子树,左子键都小于或等于根。

有人说,对于任何给定的子树,正确的子键都大于或等于根。

我的大学数据结构旧书说“每个元素都有一个键,没有两个元素具有相同的键”。

bst是否有通用定义?特别是关于如何处理具有相同键的多个实例的树。

编辑:也许我不清楚,我看到的定义是

1)左<=根<右

2)左<根<=右

3)左<根<右,因此不存在重复的键。

Answers:


78

许多算法会指定排除重复项。例如,《 MIT算法》一书中的示例算法通常提供没有重复的示例。实现重复项(在节点上作为列表或在一个特定方向上实现)非常简单。

大多数(我见过)将左子代指定为<=,将右子代指定为>。实际上,BST允许左或右子节点等于根节点,将需要额外的计算步骤才能完成搜索,其中允许有重复的节点。

最好利用节点上的列表来存储重复项,因为在节点的一侧插入“ =”值需要在该侧重写树以将节点作为子节点,或者将节点作为大节点放置。 -child,在下面的某个位置,这会消除某些搜索效率。

您必须记住,大多数课堂示例都经过简化以描绘和传达这一概念。在许多现实情况下,它们都不值得一试。但是,在元素节点上使用列表不会违反“每个元素都有一个键,而没有两个元素都具有相同的键”的声明。

因此,请遵循您的数据结构书所说的!

编辑:

二进制搜索树的通用定义涉及基于在两个方向之一上遍历数据结构来存储和搜索密钥。在务实的意义上,这意味着如果值是<>,则您将沿两个“方向”之一遍历数据结构。因此,从这个意义上说,重复值根本没有任何意义。

这与BSP或二进制搜索分区不同,但并没有什么不同。搜索算法具有“旅行”两个方向之一,或者它已经完成(成功与否)。所以我很抱歉,我的原始答案没有解决“通用定义”的概念,因为重复确实是一个独特的概念主题(成功搜索后要处理的事情,而不是二进制搜索的一部分。)


1
在节点上使用列表的缺点是什么?
Pacerier 2012年

1
我认为@Pacerier无需维护列表,而是可以在每个节点上维护引用计数,并在出现重复时更新计数。这样的算法在搜索和存储中将更加容易和有效。而且,将需要对不支持重复的现有算法进行最小的更改。
SimpleGuy

50

如果您的二进制搜索树是一棵红黑树,或者您打算进行任何类型的“树轮换”操作,则重复的节点将引起问题。想象一下,您的树规则是这样的:

左<根<=右

现在想象一个简单的树,其根为5,左子为nil,右子为5。如果在根上进行左旋转,则最后一个左子为5,在根的右子为5。零。现在,左树中的某物等于根,但是您的上述规则假定左<根。

我花了几个小时试图弄清楚为什么有时候我的红/黑树会不规则地穿过,这就是我上面描述的问题。希望有人阅读此书,并节省以后的调试时间!


18
节点相等时不要旋转!向下移动到下一个级别并将其旋转。
Rich

2
其他解决方案是将树规则更改为left <= node <= right,或仅在第一次出现值之前插入。
paxdiablo

实际上这会引起什么问题?在我看来,如果您对左<=节点<=右没问题,那么所有红黑树操作都将正常进行。
比昂·林德奎斯特(BjörnLindqvist)

39

这三个定义都是可以接受且正确的。它们定义了BST的不同变体。

您大学数据结构的书未能阐明其定义并非唯一可能。

当然,允许重复会增加复杂性。如果您使用定义“ left <= root <right”,那么您将有如下所示的树:

      3
    /   \
  2       4

然后向此树添加“ 3”重复键将导致:

      3
    /   \
  2       4
    \
     3

请注意,重复项不是连续的级别。

当允许上述BST表示形式中的重复项时,这是一个大问题:重复项可以按任意数量的级别分开,因此检查重复项的存在并不像检查节点的直接子项那样简单。

避免出现此问题的一种选择是不在结构上表示重复项(作为单独的节点),而是使用一个计数器来计算密钥出现的次数。然后,前面的示例将有一个像这样的树:

      3(1)
    /     \
  2(1)     4(1)

在插入重复的“ 3”键之​​后,它将变为:

      3(2)
    /     \
  2(1)     4(1)

这简化了查找,删除和插入操作,但以一些额外的字节和计数器操作为代价。


令我惊讶的是,我所使用的教科书中甚至从未提到这一点。教授也没有提到它,也没有提到重复的钥匙甚至是一个问题……
Oloff Biermann

22

在BST中,所有降落在节点左侧的值都小于(或等于,请参阅下文)节点本身。类似地,所有降落在节点右侧的值都大于(或等于)节点值(a)

某些BST可能会选择允许重复的值,因此上面的“或等于”限定符。

以下示例可以阐明:

            |
      +--- 14 ---+
      |          |
+--- 13    +--- 22 ---+
|          |          |
1         16    +--- 29 ---+
                |          |
               28         29

这显示了允许重复的BST-您可以看到要找到一个值,您可以从根节点开始,然后根据您的搜索值小于还是大于该节点值在左侧或右侧子树中向下移动。

可以通过类似以下方式递归完成:

def hasVal (node, srchval):
    if node == NULL:
         return false
    if node.val == srchval:
        return true
    if node.val > srchval:
        return hasVal (node.left, srchval)
    return hasVal (node.right, srchval)

并调用:

foundIt = hasVal (rootNode, valToLookFor)

重复操作会增加一点复杂性,因为一旦找到相同值的其他节点的值,您可能需要继续搜索。


(a)如果您希望调整搜索特定键的方式,实际上可以按照相反的方向对它们进行排序。BST只需保持某种排序顺序,而不管它是升序还是降序。


对于重复的情况,您可以只检查正确的子节点是否与node.val == srchval:子句中的当前节点相同,然后如果正确就可以了?
bneil

9

在Cormen,Leiserson,Rivest和Stein撰写的第三本书《算法简介》中,明确将二叉搜索树(BST)定义为允许重复。这可以从图12.1和以下内容(第287页)中看到:

“在二叉搜索树的密钥总是存储在这样一种方式,以满足二叉搜索树的特性:咱们x。在二叉搜索树的一个节点。如果y是在左子树的节点x,那么y:key <= x:key,如果yx然后是的右子树中的一个节点y:key >= x:key。”

另外,然后在第308页上将红黑树定义为:

“红黑树是一种二进制搜索树,每个节点有一个额外的存储空间:它的颜色”

因此,本书定义的红黑树支持重复。


4

任何定义均有效。只要您在实现过程中保持一致(总是将相等的节点放在右边,总是将它们放在左边,或者永远不要允许它们),那么您就可以了。我认为最常见的是不允许使用它们,但是如果允许并放置它们,则仍然是BST。


1
如果您有一组包含重复键的数据,则这些项应全部通过另一种方法(链接列表等)存储在树的1个节点内。树应仅包含唯一键。
尼克

1
还要从Wiki中注意,右子树包含的值“大于或等于”根。因此,Wiki的定义是自相矛盾的。
SoapBox

1
+1:不同的人使用不同的定义。如果您实施新的BST,则需要确保您清楚要对重复项进行哪些假设。
福兹先生08年

1
允许重复时,似乎共识是(左<=根<=右)。但是,有些人对BST的定义不允许重复。也许有人教过这种方法,以避免额外的复杂性。
蒂姆·梅里菲尔德

1
不对!是左<=根<右或左<根<=右,或左>根> =右或左> =根>右
米奇·麦特

3

在执行红黑树实现时,我遇到了用多个键验证树的问题,直到我意识到使用红黑插入旋转时,您必须放松约束才能

left <= root <= right

由于我查看的所有文档都没有允许重复的键,并且我不想重写旋转方法来解决这个问题,因此我决定修改节点以允许该节点内有多个值,并且在其中没有重复的键。那个树。


2

你说的那三件事都是真的。

  • 键是唯一的
  • 左边是小于此键的键
  • 右边的键大于此键

我想您可以反转树并将较小的键放在右侧,但实际上“左”和“右”概念只是这样:一个视觉概念,可以帮助我们考虑一个实际上没有左键的数据结构或正确,所以这并不重要。


1

1.)左<=根<右

2.)左<根<=右

3.)左<根<右,因此不存在重复的键。

我可能不得不去挖掘算法书籍,但是规范形式就不在我的头上了(3)。

(1)或(2)仅在您开始允许重复节点并且将重复节点放入树本身(而不是包含列表的节点)时出现。


您能解释为什么左<=根<=右不理想吗?
Helin Wang

看看@paxdiablo接受的答案-您可以看到可以存在重复的值>=理想值取决于您的要求,但是如果您确实有很多重复值,并且允许重复项存在于结构中,则bst最终可能是线性的-即O(n)。
罗伯特·鲍尔森

1

重复的密钥•如果有多个数据项具有相同的密钥,该怎么办?–这在红黑树中提出了一个小问题。–具有相同密钥的节点分布在具有相同密钥的其他节点的两侧非常重要。–也就是说,如果按键以50、50、50的顺序到达,则•您希望第二个50移到第一个的右侧,而第三个50移到第一个的左侧。•否则,树将变得不平衡。•这可以通过插入算法中的某种随机处理来处理。–但是,如果必须找到具有相同键的所有项目,则搜索过程将变得更加复杂。•使用相同的密钥取缔物品更容易。–在此讨论中,我们假设不允许重复

可以为包含重复键的树的每个节点创建一个链接列表,并将数据存储在列表中。


1

我只想向@Robert Paulson回答添加更多信息。

假设节点包含键和数据。因此,具有相同密钥的节点可能包含不同的数据。
(因此,搜索必须找到具有相同键的所有节点)

1)左<= cur <右

2)左<cur <=右

3)左<=当前<=右

4)left <cur <right && cur包含具有相同密钥的兄弟节点

5)左<cur <右,因此不存在重复的键。

1)和2)如果树没有任何与旋转相关的功能以防止偏斜,则效果很好。
但是此形式不适用于AVL树Red-Black树,因为旋转会破坏主体。
即使search()找到了具有键的节点,它也必须遍历到具有重复键的节点的叶节点。
搜索的时间复杂度= theta(logN)

3)可以与具有旋转功能的任何形式的BST一起使用。
但是搜索将使用O(n),从而破坏了使用BST的目的。
假设我们有下面的树,3)主体。

         12
       /    \
     10     20
    /  \    /
   9   11  12 
      /      \
    10       12

如果我们在该树上执行search(12),即使我们在根上找到了12,也必须同时搜索左右子节点以寻找重复的键。
正如我所告诉的,这需要O(n)时间。

4)是我个人的最爱。假设同级意味着具有相同密钥的节点。
我们可以将上面的树变成下面的树。

         12 - 12 - 12
       /    \
10 - 10     20
    /  \    /
   9   11  12

现在,任何搜索都将使用O(logN),因为我们不必遍历子级来获取重复的键。
并且该主体也适用于AVLRB树


0

元素顺序关系<=是总顺序,因此该关系必须是自反的,但是通常,二叉搜索树(aka BST)是没有重复的树。

否则,如果有重复项,则需要运行两次或更多次相同的删除功能!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.