Algorithm 在哪里可以找到从B-树中删除密钥的伪代码算法?

Algorithm 在哪里可以找到从B-树中删除密钥的伪代码算法?,algorithm,pseudocode,b-tree,Algorithm,Pseudocode,B Tree,我正在阅读Cormen第三版算法导论中关于B-树的章节,发现删除过程非常混乱。我可以理解插入算法,因为它提供了伪代码和一些示例,如: 但对于删除,它只是说。。。我们描述了删除是如何工作的,而不是呈现伪代码,然后是非常混乱的步骤。第一步是: 如果密钥k位于节点x中,并且x是叶,则从x中删除密钥k。 但是,如果我从叶中删除一个键,如果键的数量小于所需的最小值,它不会违反B树属性吗?根据Knuth的定义,m阶B树是满足以下属性的树: 每个节点最多有m个子节点。 除根节点外,每个非叶节点至少有⌈m/2⌉

我正在阅读Cormen第三版算法导论中关于B-树的章节,发现删除过程非常混乱。我可以理解插入算法,因为它提供了伪代码和一些示例,如:

但对于删除,它只是说。。。我们描述了删除是如何工作的,而不是呈现伪代码,然后是非常混乱的步骤。第一步是:

如果密钥k位于节点x中,并且x是叶,则从x中删除密钥k。
但是,如果我从叶中删除一个键,如果键的数量小于所需的最小值,它不会违反B树属性吗?

根据Knuth的定义,m阶B树是满足以下属性的树:

每个节点最多有m个子节点。 除根节点外,每个非叶节点至少有⌈m/2⌉ 子节点。 如果根节点不是叶节点,则它至少有两个子节点。 具有k个子节点的非叶节点包含k个− 1把钥匙。 所有的叶子都出现在同一层。 让我们看看下面的B-树顺序5

让我们看看各种可能的删除

删除21

没问题

每个节点最多仍有5个子节点。 包含21的节点是叶,因此所有提到“非叶节点”的规则都不适用。 所有树叶仍显示在同一标高上 删除16

需要重新平衡。根现在包含1个元素,比向下舍入的m/2少1个

为了修复它,我们借用了一个元素,例如18或21,并将它从叶子移动到根本身。如果树更大,我们将递归地向下重复这个过程

总论

请记住,大多数实现都使用标记为已删除的节点,而不是实际删除节点。 与实际执行删除并可能重新平衡树相比,将节点标记为已删除相对容易。
此外,删除通常不会像插入那样频繁发生。

根据Knuth的定义,顺序为m的B-树是满足以下属性的树:

每个节点最多有m个子节点。 除根节点外,每个非叶节点至少有⌈m/2⌉ 子节点。 如果根节点不是叶节点,则它至少有两个子节点。 具有k个子节点的非叶节点包含k个− 1把钥匙。 所有的叶子都出现在同一层。 让我们看看下面的B-树顺序5

让我们看看各种可能的删除

删除21

没问题

每个节点最多仍有5个子节点。 包含21的节点是叶,因此所有提到“非叶节点”的规则都不适用。 所有树叶仍显示在同一标高上 删除16

需要重新平衡。根现在包含1个元素,比向下舍入的m/2少1个

为了修复它,我们借用了一个元素,例如18或21,并将它从叶子移动到根本身。如果树更大,我们将递归地向下重复这个过程

总论

请记住,大多数实现都使用标记为已删除的节点,而不是实际删除节点。 与实际执行删除并可能重新平衡树相比,将节点标记为已删除相对容易。
此外,删除通常不会像插入那样频繁发生。

最终能够解决这个问题。我将使用书中所述的这些案例,但对这些陈述稍加修改,使其看起来更清晰:

案例1:如果k的节点是内部节点,但不是叶。这 对应于原始描述案例2

案例1a:如果y至少有t个键,并且 案例1b:y的键数少于t,但z至少有t个键。 案例1c:y和z都只有t-1键。 案例2:如果此BTree只剩下一个根,并且希望从根中删除一个键。请注意,原始描述遗漏了此案例。虽然在该阶段,根确实成为叶,然后转到案例3,但当最后3个节点合并为一个叶节点时,您需要首先将其设置为叶节点,并且从删除的角度来看,您只能通过这种方式到达一个叶节点

案例3:如果节点是一个叶,移除一个键不会 导致节点的键太少。这与原文相符 说明案例1

案例4:如果节点是叶,但删除 来自节点的k的减少会导致节点本身的数量太少 键或其父项的键太少。这与 原始描述案例3

案例4a:两个兄弟姐妹的键数都是0或t-1。这对应于原点描述情况3b。然后,我们需要将同级与父级的上一级键合并。由于这可能导致父节点的键数小于t-1,因为父节点中的键数现在由于此合并而减少了1,因此需要合并父节点并递归检查其祖先节点,直到根节点(如有必要)。 案例4 b:要删除的节点的一个同级具有多于t-1的密钥,因此我们可以从其父节点获取一个密钥,并让父节点从其同级节点获取另一个密钥。这对应于原点描述情况3a。请注意,在这种特殊情况下,父项中的键数不受影响,只受由于此抓取过程而更改的键数的影响。 除此之外,我想不出任何其他情况

为了使这个删除代码更清晰、更容易编写,我编写了以下子程序

    def search(self, k, node=None, pth=[], level=0): 
        """Give a key to search, return the node (as the page in computer 
        system concepts), path to this node (which nodes were went through), 
        idx of the key in the node found, and level in this BTree 
        of this node."""
        node = node or self.root
        idx = bisect.bisect_left(node.keys, k)

        if idx < len(node.keys) and k == node.keys[idx]:
            return (node, pth, idx, level)
        elif node.isLeaf:
            if k in node.keys:
                return (node, pth, idx, level)            
            raise KeyError("Key not found in this BTree!")
        else:
            pth.append(node)
            return self.search(k, node.pointers[idx], pth, level + 1)
    
    def getSiblings(self, node, parent, i=None):
        if i == None:
            i = bisect.bisect_left(parent.keys, node.keys[0])
        leftSibling, rightSibling = None, None
        
        try:
            if i > 0: leftSibling = parent.pointers[i - 1]
        except IndexError: 
            pass
        
        try:
            rightSibling = parent.pointers[i + 1]
        except IndexError: 
            pass
        
        return (leftSibling, rightSibling)
    
    def __mergeWithSibling(self, node, leftSibling, rightSibling, pth, i):
        if leftSibling != None: 
            self.__mergeNodes(leftSibling, node, pth[-1], i - 1, i)
        else:
            self.__mergeNodes(node, rightSibling, pth[-1], i)
        
    def __mergeNodes(self, node, nodeToMerge, parent, i, ptr=None):
        if ptr == None: ptr = i + 1
        node.keys += parent.keys[i]
        node.keys += nodeToMerge.keys
        node.pointers += nodeToMerge.pointers
        
        del parent.keys[i]
        del parent.pointers[ptr]

        
    def __checkAndMerge(self, node, pth=None):
        """Check if a given node should be merged with its sibling."""
        if pth == None:
            pth = self.search(node[0])[1]
        
        if len(node.keys) < self.t - 1:
            i = bisect.bisect_left(pth[-1].keys, node.keys[0])
            leftSibling, rightSibling = self.getSiblings(node, pth[-1], I)
            self.__mergeWithSibling(node, leftSibling, rightSibling, pth, I)
            self.__checkAndMerge(pth[-1], pth[:-1])   

希望这一条能有所帮助,我真诚地寻找这一实现的更简单版本,或者指出潜在的错误或问题,比如从我的角度测试这些代码,以便在BTree中删除、插入和搜索密钥,这是有意义的。

最终能够解决这一问题。我将使用书中所述的这些案例,但对这些陈述稍加修改,使其看起来更清晰:

案例1:如果k的节点是内部节点,但不是叶。这 对应于原始描述案例2

案例1a:如果y至少有t个键,并且 案例1b:y的键数少于t,但z至少有t个键。 案例1c:y和z都只有t-1键。 案例2:如果此BTree只剩下一个根,并且希望从根中删除一个键。请注意,原始描述遗漏了此案例。虽然在该阶段,根确实成为叶,然后转到案例3,但当最后3个节点合并为一个叶节点时,您需要首先将其设置为叶节点,并且从删除的角度来看,您只能通过这种方式到达一个叶节点

案例3:如果节点是一个叶,移除一个键不会 导致节点的键太少。这与原文相符 说明案例1

案例4:如果节点是叶,但删除 来自节点的k的减少会导致节点本身的数量太少 键或其父项的键太少。这与 原始描述案例3

案例4a:两个兄弟姐妹的键数都是0或t-1。这对应于原点描述情况3b。然后,我们需要将同级与父级的上一级键合并。由于这可能导致父节点的键数小于t-1,因为父节点中的键数现在由于此合并而减少了1,因此需要合并父节点并递归检查其祖先节点,直到根节点(如有必要)。 案例4b:要删除的节点的一个同级具有多个t-1密钥,因此我们可以从其父节点获取一个密钥,并让父节点从其同级节点获取另一个密钥。这对应于原点描述情况3a。请注意,在这种特殊情况下,父项中的键数不受影响,只受由于此抓取过程而更改的键数的影响。 除此之外,我想不出任何其他情况

为了使这个删除代码更清晰、更容易编写,我编写了以下子程序

    def search(self, k, node=None, pth=[], level=0): 
        """Give a key to search, return the node (as the page in computer 
        system concepts), path to this node (which nodes were went through), 
        idx of the key in the node found, and level in this BTree 
        of this node."""
        node = node or self.root
        idx = bisect.bisect_left(node.keys, k)

        if idx < len(node.keys) and k == node.keys[idx]:
            return (node, pth, idx, level)
        elif node.isLeaf:
            if k in node.keys:
                return (node, pth, idx, level)            
            raise KeyError("Key not found in this BTree!")
        else:
            pth.append(node)
            return self.search(k, node.pointers[idx], pth, level + 1)
    
    def getSiblings(self, node, parent, i=None):
        if i == None:
            i = bisect.bisect_left(parent.keys, node.keys[0])
        leftSibling, rightSibling = None, None
        
        try:
            if i > 0: leftSibling = parent.pointers[i - 1]
        except IndexError: 
            pass
        
        try:
            rightSibling = parent.pointers[i + 1]
        except IndexError: 
            pass
        
        return (leftSibling, rightSibling)
    
    def __mergeWithSibling(self, node, leftSibling, rightSibling, pth, i):
        if leftSibling != None: 
            self.__mergeNodes(leftSibling, node, pth[-1], i - 1, i)
        else:
            self.__mergeNodes(node, rightSibling, pth[-1], i)
        
    def __mergeNodes(self, node, nodeToMerge, parent, i, ptr=None):
        if ptr == None: ptr = i + 1
        node.keys += parent.keys[i]
        node.keys += nodeToMerge.keys
        node.pointers += nodeToMerge.pointers
        
        del parent.keys[i]
        del parent.pointers[ptr]

        
    def __checkAndMerge(self, node, pth=None):
        """Check if a given node should be merged with its sibling."""
        if pth == None:
            pth = self.search(node[0])[1]
        
        if len(node.keys) < self.t - 1:
            i = bisect.bisect_left(pth[-1].keys, node.keys[0])
            leftSibling, rightSibling = self.getSiblings(node, pth[-1], I)
            self.__mergeWithSibling(node, leftSibling, rightSibling, pth, I)
            self.__checkAndMerge(pth[-1], pth[:-1])   

希望这一条能有所帮助,我真诚地寻找这一实现的更简单版本,或者指出潜在的错误或问题,因为我在测试这些代码时,删除、插入和搜索BTree中的键是有意义的。

在delete 16中,需要重新平衡。为什么?它是根节点,所以第二个表示除根之外的属性不适用于它,对吗?此外,它至少还有2个孩子,因此财产3也令人满意。为什么我不能将[18,21]合并到[9,12]中,然后完全删除16及其右指针?多亏了这句话,似乎没有一个正确的算法可以删除。经过一些思考,我可以看到,如果底部节点不是叶节点,我的方法将失败。你能给出你的答案吗?你的算法是:删除它;从左/右子级借用前辈/后辈;如果从该节点借用使其不平衡,递归地执行上述过程?此算法是否涵盖所有可能的情况?在删除16中,需要重新平衡。为什么?它是根节点,所以第二个表示除根之外的属性不适用于它,对吗?此外,它至少还有2个孩子,因此财产3也令人满意。为什么我不能将[18,21]合并到[9,12]中,然后完全删除16及其右指针?多亏了这句话,似乎没有一个正确的算法可以删除。经过一些思考,我可以看到,如果底部节点不是叶节点,我的方法将失败。你能给出你的答案吗?你的算法是:删除它;从左/右子级借用前辈/后辈;如果从该节点借用使其不平衡,递归地执行上述过程?该算法是否涵盖所有可能的情况?
# -*- coding: utf-8 -*-
"""
Created on Sat Feb 13 17:13:00 2021
@author: Sam_Yan
"""

import bisect

class BTNode:
    
    def __init__(self, keys=None, pointers=None, isLeaf=True):
        self.keys = keys or []
        self.pointers = pointers or []
        self.isLeaf = isLeaf
        
    def __str__(self):
        return ("keys: " + str(self.keys) + "\n")
        

class BTree:
    
    def __init__(self, t=2):
        """t is the degree (# of keys a node contains), ranges between t
        and 2t - 1 (both sides included). When t = 2 is a 2-3-4 tree."""
        assert (t >= 2 and t == int(t)), "t value of a B-Tree should be >= 2!"
        newNode = BTNode()
        self.t = t
        self.treeStr = ""
        self.root = newNode
        
    def insertNonFull(self, node, k): 
        if node.isLeaf:
            bisect.insort(node.keys, k)
            return
        
        i = bisect.bisect(node.keys, k)
        if len(node.pointers[i].keys) == 2 * self.t - 1:
            self.splitChild(node, i)
            if k > node.keys[i]: i += 1 # Determine which subtree to go to.
        
        self.insertNonFull(node.pointers[i], k)

    def insert(self, k):
        r = self.root
        if len(self.root.keys) == 2 * self.t - 1:
            s = BTNode(isLeaf=False)
            self.root = s
            s.pointers.append(r)
            self.splitChild(s, 0)
            self.insertNonFull(s, k)
        else:
            self.insertNonFull(r, k)
    
    def splitChild(self, node, i):
        y = node.pointers[i]
        z = BTNode(isLeaf=y.isLeaf)
        z.keys = y.keys[self.t:]
        
        if not y.isLeaf: # copy pointers if y is not a leaf:
            z.pointers = y.pointers[self.t:]
                
        node.pointers.insert(i + 1, z)
        node.keys.insert(i, y.keys[self.t-1])
        del y.keys[self.t-1:]
        del y.pointers[self.t:]
 
    def __printHelper(self, r=None, level=0):
        r = r or self.root
        if r != self.root:
            self.treeStr += (" " * level + "L-" + str(level) + "-" + str(r)) 
        for node in r.pointers:            
            self.__printHelper(node, level + 1)
            
    def __delHelper(self, node=None):
        if node.isLeaf:
            del node.pointers
            del node.keys
            del node
            return
        
        for c in node.pointers:
            self.__delHelper(c)
        del node.keys
        del node.pointers
    
    def __del__(self): # Destruct this BTree.
        self.__delHelper(self.root)
    
    def __str__(self):
        # Method to obtain string info about this BTree.
        self.treeStr = "Root: " + str(self.root)
        self.__printHelper()
        return self.treeStr

if __name__ == '__main__':
    # Testing samples:
    t1 = BTree(t=2)
    for i in range(28):
        t1.insert(i)
    print(t1)
    print(t1.search(27)[0])

    t2 = BTree(t=3)
    alphas = "AGDJKNCMEORPSXYZTUV"
    alphas = [ch for ch in alphas]

    for ch in alphas:
        t2.insert(ch)
    #print(t2)
    t2.insert('B')
    #print(t2)
    t2.insert('Q')
    #print(t2)
    t2.insert('L')
    #print(t2)
    t2.insert('F')
    #print(t2)
    t2.deleteKey('F')   
    print(t2)
    t2.deleteKey('M')
    print(t2)
    t2.deleteKey('G')
    print(t2)
    t2.deleteKey('B')
    print(t2)
    t2.deleteKey('Z')
    print(t2)
    t2.deleteKey('D')
    print(t2)
    
    """
    t3 = BTree(t=2)
    #for ch in ['F', 'S', 'Q', 'K', 'C', 'L', 'H', 'T', 'V', 'W', 'M',
    #           'R', 'N', 'P', 'A', 'B', 'X', 'Y', 'D', 'Z', 'E']:
    for ch in ['F', 'S', 'Q', 'K', 'C', 'L', 'H', 'T', 'V', 'W', 'M']:
        t2.insert(ch)
    print(t2)    
    """