msambol / dsa

Data structures and algorithms in X minutes. Code examples from my YouTube channel.

Home Page:https://youtube.com/michaelsambol

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Doubt in the deletion.

18Abhinav07 opened this issue · comments

def delete_example():
first_leaf = Node(True)
first_leaf.keys = [1, 3,4]

second_leaf = Node(True)
second_leaf.keys = [7, 8, 14]

third_leaf = Node(True)
third_leaf.keys = [16, 17, 19]

fourth_leaf = Node(True)
fourth_leaf.keys = [26, 27, 28]

fifth_leaf = Node(True)
fifth_leaf.keys = [30,35]

sixth_leaf = Node(True)
sixth_leaf.keys = [42, 43]

seventh_leaf = Node(True)
seventh_leaf.keys = [51, 52,53]

eighth_leaf = Node(True)
eighth_leaf.keys = [65,67,68]

root_left_child = Node()
root_left_child.keys = [5,15,20]
root_left_child.children.append(first_leaf)
root_left_child.children.append(second_leaf)
root_left_child.children.append(third_leaf)
root_left_child.children.append(fourth_leaf)

root_right_child = Node()
root_right_child.keys = [40, 50,60]
root_right_child.children.append(fifth_leaf)
root_right_child.children.append(sixth_leaf)
root_right_child.children.append(seventh_leaf)
root_right_child.children.append(eighth_leaf)


root = Node()
root.keys = [29]
root.children.append(root_left_child)
root.children.append(root_right_child)

B = BTree(2)
B.root = root
print('\n--- Original B-Tree ---\n')
B.print_tree(B.root)

print('\n--- Case 1: DELETED 29 ---\n')
B.delete(B.root, 29)
B.print_tree(B.root)

if we use this delete example function when i try to delete the root it has none on level 0 and i am confused.

also during the line 142 and 147 , we should have individual calls to the delete predecessor , we should have a call at index n+1.

also in case in delete predecessor if the n th children doesnt have t keys then we merge but what if the n+1 th child has 2t-1 keys then , i am confused what will happen..

IF you could please help me resolve my doubt...

also i am confused if the delete_predecessor call at 145 should be a part of return statement.

class NODE:
def init(self, leaf=False):
self.keys = []
self.children = []
self.leaf = leaf

class BTREE:
def init(self, t):
self.root = NODE(True)
# branching factor
self.t = t

def search(self, key, node=None):
    node = self.root if node is None else node

    it = 0
    # iterate till you find a key greater than the key:
    while it < len(node.keys) and key > node.keys[it]:
        it += 1
    # found the key:
    if it < len(node.keys) and key == node.keys[it]:
        return node, it
    # searched till the leaf node:
    if node.leaf:
        return None
    # call for the children to search:
    else:
        return self.search(key.node.children[it])

def split_child(self, parent_node, child_index):
    t = self.t

    # we know that the i th child of the parent node is full
    child_node = parent_node.children[child_index]

    # create a new node that will now act as a sibling to the current child
    sibling = NODE(child_node.leaf)
    # need to point to this children by the parent node.
    parent_node.children.insert((child_index + 1), sibling)

    # managing the values 0-(t-1) in child and t-(2t-1) in sibling
    # also the children if the child is not leaf , the median value of t will be put in the parent
    parent_node.keys.insert(child_index, child_node.keys[t - 1])
    sibling.keys = child_node.keys[t: ((2 * t) - 1)]
    child_node.keys = child_node.keys[0:t - 1]

    if not child_node.leaf:
        sibling.children = child_node.children[t:((2 * t) - 1)]
        child_node.children = child_node.children[0:t]

def insert_non_full(self, current_node, key):
    t = self.t
    it = len(current_node.keys) - 1

    if current_node.leaf:
        # find the index where we will put the key.
        current_node.append(None)  # this will help in shifting.
        while it >= 0 and key < current_node.keys[it]:
            current_node.keys[it + 1] = current_node.keys[it]
            it -= 1
        current_node.keys[it + 1] = key

    else:
        while it >= 0 and key < current_node.keys[it]:
            it -= 1
        it += 1
        # we need to insert the key in the it+1 child of the current node.

        if len(current_node.children[it].keys) == 2 * t - 1:
            self.split_child(current_node, it)

            # after the split the it index will have the median element from the children
            # thus we need to check if the current value is less than or greater than the key

            if current_node.keys[it] < key:
                it += 1
        self.insert_non_full(current_node.children[it], key)

def insert(self, key):
    t = self.t
    root = self.root

    if len(root.keys) == (2 * t) - 1:
        new_root = NODE()
        self.root = new_root
        new_root.children.insert(0, root)
        self.split_child(new_root, 0)
        self.insert_non_full(new_root, key)
    else:
        self.insert_non_full(root, key)

def delete(self, current_node, key):
    if current_node is None:
        current_node = self.root

    t = self.t
    it = 0

    # case 1: leaf case when leaf has more than t keys.
    while it < len(current_node.keys) and key > current_node.keys[it]:
        it += 1

    if current_node.leaf:
        if it < len(current_node.keys) and current_node.keys[it] == key:
            current_node.keys.pop(it)
        return

    # case 2: if the internal node has the key , here we need to check how the children of this
    # internal node are affected by the changes caused by deletion in the node.
    if it < len(current_node.keys) and current_node.keys[it] == key:
        return self.delete_internal_node(current_node, key, it)
    # case 3: if the children has the key and that child has more than t keys.
    # then you can easily delete recursive from the child.
    elif len(current_node.children[it].keys) >= t:
        self.delete(current_node.children[it], key)

    # case4: when the key is in this child subtree but the child doesn't have more than t keys.
    # here we need to check that this child's children are disturbed and may need balancing.
    # also the case when the leaf where the key may lie has not enough keys.
    else:
        # we make the child eligible for deletion propagation by making it have enough keys.

        self.make_eligible(current_node, it)
        if it != len(current_node.keys):
            self.delete(current_node.children[it], key)
        else:
            self.delete(current_node.children[it - 1], key)

def delete_internal_node(self, current_node, key, it):
    t = self.t

    if current_node.leaf:
        if current_node.keys[it] == key:
            current_node.keys.pop(it)
        return

    if len(current_node.children[it].keys) >= t:  # if this child has enough keys.
        current_node.keys[it] = self.delete_predecessor(current_node.children[it])
        return
    elif len(current_node.children[it + 1].keys) >= t:  # if the next child has enough keys.
        current_node.keys[it] = self.delete_successor(current_node.children[it + 1])
        return
    else:  # the case when both the children don't have enough keys.
        self.delete_merge(current_node, it, it + 1)
        # now propagate the deletion to the merged child.
        self.delete_internal_node(current_node.children[it], key, t - 1)  # t-1 is the index where the key will lie.

def delete_predecessor(self, node):
    if node.leaf:
        return node.keys.pop()  # return the biggest key in the node.

    # now comes the propagation , if this is not a leaf then we need to
    # propagate the predecessor to now children of this node.

    # copy the last key of the leaf of this subtree and call delete for this key to propagate delete.

    current_node = node
    while not current_node.leaf:
        current_node = current_node.children[-1]
    last_key = current_node.keys[-1]
    self.delete(node.children[-1], last_key)
    return last_key

def delete_successor(self, node):

    if node.leaf:
        return node.keys.pop(0)  # return the smallest key in the node.

    # copy the last key of the leaf of this subtree and call delete for this key to propagate delete.

    current_node = node
    while not current_node.leaf:
        current_node = current_node.children[0]
    first_key = current_node.keys[0]
    self.delete(node.children[0], first_key)
    return first_key

def delete_merge(self, current_node, left_child_index, right_child_index):
    # merge the two children and the key at left child index of the parent node
    # to the t-1 th index and copy all the children if the children are non leaf.

    left_child = current_node.children[left_child_index]
    right_child = current_node.children[right_child_index]

    left_child.keys.append(current_node.keys[left_child_index])  # as there are less than t keys in the child
    # thus the next index must be t-1

    for k in range(len(right_child.keys)):
        left_child.keys.append(right_child.keys[k])
        if len(right_child.children) > 0:
            left_child.children.append(right_child.children[k])
    # above lines copy keys and children and the later check if child is left.
    if len(right_child.children) > 0:
        left_child.children.append(right_child.children.pop())

    # pop the node that called merges key and last child.
    current_node.keys.pop(left_child_index)
    current_node.children.pop(right_child_index)

    # if the root wanted merge then we need to acknowledge the change in the root now.
    if current_node == self.root and len(current_node.keys) == 0:
        self.root = left_child

def make_eligible(self, current_node, it):
    # here lie three cases if we can borrow from some sibling.
    # or merge the children to have a bigger node.

    # if the left sibling can give us a borrow.
    if it != 0 and len(current_node.children[it - 1].keys) >= self.t:
        self.borrow_left(current_node, it)

    if it != len(current_node.keys) and len(current_node.children[it + 1].keys) >= self.t:
        self.borrow_right(current_node, it)

    else:
        # if we are at the last child then we need to merge it and its previous child.
        if it == len(current_node.keys):
            self.delete_merge(current_node, it - 1, it)
        # vice versa as above.
        else:
            self.delete_merge(current_node, it, it + 1)

def borrow_left(self, current_node, it):

    receiver = current_node.children[it]
    donor = current_node.children[it + 1]

    # this is like a left rotate of keys from donor to current node to the receiver.
    receiver.keys.insert(0, current_node.keys[it - 1])
    current_node.keys[it - 1] = donor.keys[-1]
    if len(donor.children) > 0:
        receiver.children.insert(0, donor.children[-1])

    donor.keys.pop()
    donor.children.pop()

def borrow_right(self, current_node, it):

    receiver = current_node.children[it]
    donor = current_node.children[it + 1]

    # this is like a left rotate of keys from donor to current node to the receiver.
    receiver.keys.append(current_node.keys[it])
    current_node.keys[it] = donor.keys[0]
    if len(donor.children) > 0:
        receiver.children.append(donor.children[0])

    donor.keys.pop(0)
    donor.children.pop(0)

def print_tree(self, x, level=0):
    print(f'Level {level}', end=": ")

    for i in x.keys:
        print(i, end=" ")

    print()
    level += 1

    if len(x.children) > 0:
        for i in x.children:
            self.print_tree(i, level)

def delete_example():
first_leaf = NODE(True)
first_leaf.keys = [1, 9]

second_leaf = NODE(True)
second_leaf.keys = [17, 19, 21]

third_leaf = NODE(True)
third_leaf.keys = [23, 25, 27]

fourth_leaf = NODE(True)
fourth_leaf.keys = [31, 32, 39]

fifth_leaf = NODE(True)
fifth_leaf.keys = [41, 47, 50]

sixth_leaf = NODE(True)
sixth_leaf.keys = [56, 60]

seventh_leaf = NODE(True)
seventh_leaf.keys = [72, 90]

root_left_child = NODE()
root_left_child.keys = [15, 22, 30]
root_left_child.children.append(first_leaf)
root_left_child.children.append(second_leaf)
root_left_child.children.append(third_leaf)
root_left_child.children.append(fourth_leaf)

root_right_child = NODE()
root_right_child.keys = [55, 63]
root_right_child.children.append(fifth_leaf)
root_right_child.children.append(sixth_leaf)
root_right_child.children.append(seventh_leaf)

root = NODE()
root.keys = [40]
root.children.append(root_left_child)
root.children.append(root_right_child)

B = BTREE(3)
B.root = root
print('\n--- Original B-Tree ---\n')
B.print_tree(B.root)

print('\n--- Case 1: DELETED 40 ---\n')
B.delete(B.root, 40
         )
B.print_tree(B.root)

print('\n--- Case 2a: DELETED 30 ---\n')
B.delete(B.root, 30)
B.print_tree(B.root)

print('\n--- Case 2b: DELETED 27 ---\n')
B.delete(B.root, 27)
B.print_tree(B.root)

print('\n--- Case 2c: DELETED 22 ---\n')
B.delete(B.root, 22)
B.print_tree(B.root)

print('\n--- Case 3b: DELETED 17 ---\n')
B.delete(B.root, 17)
B.print_tree(B.root)

print('\n--- Case 3a: DELETED 9 ---\n')
B.delete(B.root, 9)
B.print_tree(B.root)

def main():
delete_example()

main()

made changes to the code , it works fine now.

@18Abhinav07 Glad you sorted it out 👍🏼 Is there a PR you want to push to the repo? I'm unsure which change you made.

@18Abhinav07 I'm going to close this issue and point others to your fork: #8. I want to keep the code as close as possible to the videos. If there is a smaller PR you can make that with a bug fix, happy to merge that. Thanks!