Answers:
许多算法会指定排除重复项。例如,《 MIT算法》一书中的示例算法通常提供没有重复的示例。实现重复项(在节点上作为列表或在一个特定方向上实现)非常简单。
大多数(我见过)将左子代指定为<=,将右子代指定为>。实际上,BST允许左或右子节点等于根节点,将需要额外的计算步骤才能完成搜索,其中允许有重复的节点。
最好利用节点上的列表来存储重复项,因为在节点的一侧插入“ =”值需要在该侧重写树以将节点作为子节点,或者将节点作为大节点放置。 -child,在下面的某个位置,这会消除某些搜索效率。
您必须记住,大多数课堂示例都经过简化以描绘和传达这一概念。在许多现实情况下,它们都不值得一试。但是,在元素节点上使用列表不会违反“每个元素都有一个键,而没有两个元素都具有相同的键”的声明。
因此,请遵循您的数据结构书所说的!
编辑:
二进制搜索树的通用定义涉及基于在两个方向之一上遍历数据结构来存储和搜索密钥。在务实的意义上,这意味着如果值是<>,则您将沿两个“方向”之一遍历数据结构。因此,从这个意义上说,重复值根本没有任何意义。
这与BSP或二进制搜索分区不同,但并没有什么不同。搜索算法具有“旅行”两个方向之一,或者它已经完成(成功与否)。所以我很抱歉,我的原始答案没有解决“通用定义”的概念,因为重复确实是一个独特的概念主题(成功搜索后要处理的事情,而不是二进制搜索的一部分。)
如果您的二进制搜索树是一棵红黑树,或者您打算进行任何类型的“树轮换”操作,则重复的节点将引起问题。想象一下,您的树规则是这样的:
左<根<=右
现在想象一个简单的树,其根为5,左子为nil,右子为5。如果在根上进行左旋转,则最后一个左子为5,在根的右子为5。零。现在,左树中的某物等于根,但是您的上述规则假定左<根。
我花了几个小时试图弄清楚为什么有时候我的红/黑树会不规则地穿过,这就是我上面描述的问题。希望有人阅读此书,并节省以后的调试时间!
left <= node <= right
,或仅在第一次出现值之前插入。
这三个定义都是可以接受且正确的。它们定义了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)
这简化了查找,删除和插入操作,但以一些额外的字节和计数器操作为代价。
在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只需保持某种排序顺序,而不管它是升序还是降序。
任何定义均有效。只要您在实现过程中保持一致(总是将相等的节点放在右边,总是将它们放在左边,或者永远不要允许它们),那么您就可以了。我认为最常见的是不允许使用它们,但是如果允许并放置它们,则仍然是BST。
1.)左<=根<右
2.)左<根<=右
3.)左<根<右,因此不存在重复的键。
我可能不得不去挖掘算法书籍,但是规范形式就不在我的头上了(3)。
(1)或(2)仅在您开始允许重复节点并且将重复节点放入树本身(而不是包含列表的节点)时出现。
>=
。理想值取决于您的要求,但是如果您确实有很多重复值,并且允许重复项存在于结构中,则bst最终可能是线性的-即O(n)。
我只想向@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),因为我们不必遍历子级来获取重复的键。
并且该主体也适用于AVL或RB树。