35 / 69

Deletion from a B+ Tree

B+ daraxtdan elementni o'chirish

B+ daraxtdan elementni o'chirish uchta asosiy bosqichdan iborat: o'chirilishi kerak bo'lgan kalit joylashgan tugunni qidirish, kalitni o'chirish va zarurat tug'ilganda daraxtni qayta muvozanatlash.

O'chirish jarayonida tugunda ruxsat etilgan minimal kalitlar sonidan kam kalit qolib ketishi natijasida underflow (yetishmovchilik) holati yuzaga kelishi mumkin.


O'chirish amali

O'chirish bosqichlarini o'rganishdan oldin, darajasi m bo'lgan B+ daraxt haqidagi ushbu qoidalarni esda tutish lozim:

  • Tugun eng ko'pi bilan m ta farzandga ega bo'lishi mumkin (masalan, 3).
  • Tugun eng ko'pi bilan m - 1 ta kalitni saqlashi mumkin (masalan, 2).
  • Tugun kamida ⌈m / 2⌉ ta farzandga ega bo'lishi kerak (masalan, 2).
  • Tugun (ildiz tugundan tashqari) kamida ⌈m / 2⌉ - 1 ta kalitni o'z ichiga olishi kerak (masalan, 1).

B+ daraxtda kalitni o'chirishda ichki tugunlarda (indekslarda) mavjud bo'lgan kalitlarga ham e'tibor qaratish kerak, chunki B+ daraxtda qiymatlar barglarda ham, indekslarda ham takrorlangan (redundant) bo'lishi mumkin. O'chiriladigan kalit topilgach, quyidagi holatlar ko'rib chiqiladi:


1-Holat: Kalit faqat barg tugunda mavjud bo'lsa

Bunda ikkita vaziyat bo'lishi mumkin:

  1. Tugunda minimal sondan ortiq kalit mavjud:
    Shunchaki kalitni barg tugundan o'chirib tashlang:

B-daraxtdan 40 ni o'chirish

B+ daraxtdan 40 kalitini o'chirish

  1. Tugunda aniq minimal sondagi kalitlar mavjud:
    Kalitni o'chiring va bevosita qo'shni tugundan bitta kalit qarz oling. Qo'shni tugunning o'rta kalitini (yoki yangilangan bo'linuvchi kalitni) ota tugunga ko'taring:

B-daraxtdan 5 ni o'chirish

B+ daraxtdan 5 kalitini o'chirish va qo'shnidan qarz olish


2-Holat: Kalit ichki tugunlarda ham mavjud bo'lsa

Agar o'chirilayotgan kalit ichki indeks tugunlarida ham mavjud bo'lsa, uni ichki tugunlardan ham o'chirish/yangilash talab etiladi:

  1. Tugunda minimal sondan ortiq kalit bo'lsa:
    Kalitni barg tugundan o'chiring va ichki tugundagi mos kalitni uning inorder successori (o'ng qism-daraxtning eng kichik kaliti) bilan almashtiring:

45 ni o'chirish

45 kalitini o'chirish va indeksni inorder successor bilan almashtirish

  1. Tugunda aniq minimal sondagi kalit bo'lsa:
    Kalitni o'chiring va ota tugun orqali bevosita qo'shnisidan kalit qarz oling. Ichki tugunda paydo bo'lgan bo'sh joyni olingan kalit bilan to'ldiring:

35 ni o'chirish

35 kalitini o'chirish va qo'shnidan qarz olib indeksni yangilash

  1. Bo'sh joy bevosita ota tugundan yuqorida paydo bo'lsa:
    Kalitni o'chirgandan so'ng bo'sh joyni uning qo'shnisi bilan birlashtiring (merge). Bobo (grandparent) tugunidagi bo'sh joyni esa inorder successor bilan to'ldiring:

25 ni o'chirish

25 kalitini o'chirish va tugunlarni birlashtirish


3-Holat: Daraxt balandligi qisqarganda

Ushbu holatda daraxtning balandligi qisqaradi. Masalan, quyidagi daraxtdan 55 elementini o'chirish daraxt ildizining qisqarishiga va butun daraxt balandligining bir darajaga pasayishiga olib keladi:

55 ni o'chirish va balandlik qisqarishi

55 kalitini o'chirish va daraxt balandligining qisqarishi


B+ daraxtdan elementni o'chirish dasturiy kodi

package main

import "fmt"

type BPNode struct {
	isLeaf   bool
	keys     []int
	children []*BPNode
	next     *BPNode
}

func newBPNode(isLeaf bool) *BPNode {
	return &BPNode{
		isLeaf:   isLeaf,
		keys:     make([]int, 0),
		children: make([]*BPNode, 0),
		next:     nil,
	}
}

type BPlusTree struct {
	root  *BPNode
	order int
}

func NewBPlusTree(order int) *BPlusTree {
	return &BPlusTree{root: newBPNode(true), order: order}
}

func (tree *BPlusTree) Insert(k int) {
	root := tree.root
	if len(root.keys) == tree.order-1 {
		newRoot := newBPNode(false)
		newRoot.children = append(newRoot.children, root)
		tree.splitChild(newRoot, 0, root)
		tree.root = newRoot
		tree.insertNonFull(newRoot, k)
	} else {
		tree.insertNonFull(root, k)
	}
}

func (tree *BPlusTree) insertNonFull(node *BPNode, k int) {
	if node.isLeaf {
		idx := 0
		for idx < len(node.keys) && node.keys[idx] < k {
			idx++
		}
		if idx < len(node.keys) && node.keys[idx] == k {
			return
		}
		node.keys = append(node.keys, 0)
		for j := len(node.keys) - 1; j > idx; j-- {
			node.keys[j] = node.keys[j-1]
		}
		node.keys[idx] = k
		return
	}

	idx := 0
	for idx < len(node.keys) && k >= node.keys[idx] {
		idx++
	}

	child := node.children[idx]
	if len(child.keys) == tree.order-1 {
		tree.splitChild(node, idx, child)
		if k >= node.keys[idx] {
			idx++
		}
	}
	tree.insertNonFull(node.children[idx], k)
}

func (tree *BPlusTree) splitChild(parent *BPNode, idx int, child *BPNode) {
	mid := len(child.keys) / 2
	promotedKey := child.keys[mid]
	newNode := newBPNode(child.isLeaf)

	if child.isLeaf {
		newNode.keys = append(newNode.keys, child.keys[mid:]...)
		child.keys = child.keys[:mid]
		newNode.next = child.next
		child.next = newNode
	} else {
		newNode.keys = append(newNode.keys, child.keys[mid+1:]...)
		newNode.children = append(newNode.children, child.children[mid+1:]...)
		child.keys = child.keys[:mid]
		child.children = child.children[:mid+1]
	}

	parent.children = append(parent.children, nil)
	for j := len(parent.children) - 1; j > idx+1; j-- {
		parent.children[j] = parent.children[j-1]
	}
	parent.children[idx+1] = newNode

	parent.keys = append(parent.keys, 0)
	for j := len(parent.keys) - 1; j > idx; j-- {
		parent.keys[j] = parent.keys[j-1]
	}
	parent.keys[idx] = promotedKey
}

func (tree *BPlusTree) Delete(k int) {
	tree.deleteInternal(tree.root, k)
	if len(tree.root.keys) == 0 && !tree.root.isLeaf {
		tree.root = tree.root.children[0]
	}
}

func (tree *BPlusTree) deleteInternal(node *BPNode, k int) {
	if node.isLeaf {
		for i, val := range node.keys {
			if val == k {
				node.keys = append(node.keys[:i], node.keys[i+1:]...)
				return
			}
		}
		return
	}

	idx := 0
	for idx < len(node.keys) && k >= node.keys[idx] {
		idx++
	}

	tree.deleteInternal(node.children[idx], k)

	// Agar o'chirilgan kalit ichki tugunda indeks sifatida mavjud bo'lsa, uni yangilaymiz
	for i, val := range node.keys {
		if val == k {
			// Inorder successor ni topish (o'ng farzandning eng chap kaliti)
			succ := node.children[i+1]
			for !succ.isLeaf {
				succ = succ.children[0]
			}
			if len(succ.keys) > 0 {
				node.keys[i] = succ.keys[0]
			}
		}
	}
}

func (tree *BPlusTree) PrintLeaves() {
	curr := tree.root
	for !curr.isLeaf {
		curr = curr.children[0]
	}
	fmt.Print("Barglar: ")
	for curr != nil {
		for _, k := range curr.keys {
			fmt.Printf("%d ", k)
		}
		curr = curr.next
	}
	fmt.Println()
}

func main() {
	tree := NewBPlusTree(4)
	for _, val := range []int{5, 15, 25, 35, 45, 55} {
		tree.Insert(val)
	}

	fmt.Print("Boshlang'ich daraxt: ")
	tree.PrintLeaves()

	tree.Delete(25)
	fmt.Print("25 o'chirilgandan so'ng: ")
	tree.PrintLeaves()
}

O'chirish murakkabligi

  • Vaqt murakkabligi (Time Complexity): Θ(t * log_t n). Bu asosan mos barg tugunni qidirish vaqti Θ(log_t n) bilan belgilanadi.
  • Xotira murakkabligi (Space Complexity): Θ(n).