درخت B+ شکل پیشرفتهای از درخت خود متعادل کننده است که در آن تمام مقادیر در سطح برگ قرار دارند. در این مقاله به بررسی این نوع درخت میپردازیم.
ویژگیهای درخت B+
یک مفهوم مهم که باید قبل از یادگیری درخت B+ درک شود، شاخصسازی چندسطحی است. در شاخصسازی چندسطحی، شاخصها مانند شکل زیر ایجاد میشوند. این کار، دسترسی به دادهها را آسانتر و سریعتر میکند.
ویژگیهای درخت B+ شامل موارد زیر است:
- همه برگها در یک سطح هستند.
- ریشه حداقل دو فرزند دارد.
- هر گره به جز ریشه میتواند حداکثر m فرزند و حداقل m/2 فرزند داشته باشد (m مرتبه درخت B+ را مشخص میکند).
- هر گره میتواند حداکثر دارای m - 1 کلید و حداقل ⌈m/2⌉ - 1 کلید باشد.
عملیاتهای درخت B+
در ادامه عملیاتهای جستجو، حذف و درج در درخت B+ را بررسی خواهیم کرد.
عملیات درج در درخت B+
مراحل عملیات درج در درخت B+ بهصورت زیر است:
- گره جدید را به عنوان یک گره برگ وارد کنید.
- اگر برگ فضای لازم را ندارد، گره را تقسیم کرده و گره میانی را در گره شاخص بعدی کپی کنید.
- اگر گره شاخص فضای لازم را ندارد، گره را تقسیم کرده و عنصر میانی را در صفحه شاخص بعدی کپی کنید.
فرض کنید میخواهیم مقدار 195 را در درخت B+ از مرتبه 5 که در شکل زیر نشان داده شده است وارد کنیم.
195 بعد از 190 در زیر درخت سمت راست 120 درج می شود. آن را در این مکان درج میکنیم.
گره سمت راست 120 حاوی بیش از حداکثر تعداد عناصر یعنی 4، است. آن را تقسیم میکنیم و گره میانی یعنی 190 را والد قرار میدهیم.
اکنون، گره ریشه شامل 6 فرزند و 5 کلید است که ویژگی درخت B+ را نقض میکند بنابراین باید آن را تقسیم کنیم که به صورت زیر نشان داده شده است.
عملیات حذف از درخت B+
مراحل عملیات حذف در درخت B+ بهصورت زیر است:
- کلید و دادهها را از برگهها حذف کنید.
- اگر گره برگ حاوی کمتر از حداقل تعداد عناصر است، گره را با خواهر و برادرش ادغام کرده و کلید بین آنها را حذف کنید.
- اگر گره شاخص حاوی کمتر از حداقل تعداد عناصر است، گره را با خواهر و برادر ادغام کرده و کلید بین آنها را به پایین حرکت دهید.
برای مثال فرض کنید میخواهید کلید 200 را از درخت B+ که در شکل زیر نشان داده شده است حذف کنید.
200 در زیر درخت سمت راست 190 بعد از 190 قرار دارد، آن را حذف کنید.
دو گره را با استفاده از 195، 190، 154 و 129 ادغام کنید.
اکنون، عنصر 120 تنها عنصر موجود در گره است که خصوصیات درخت B+ را نقض میکند بنابراین، باید آن را با استفاده از 60، 78، 108 و 120 ادغام کنیم.
اکنون ارتفاع درخت 1 واحد کاهش یافت.
عملیات جستجو در درخت B+
مراحل زیر برای جستجوی دادهها در درخت B+ از مرتبه m دنبال میشود. فرض کنید داده مورد جستجو k باشد.
- از گره ریشه شروع کنید. k را با کلیدهای گره ریشه [k1, k2, k3,......km - 1] مقایسه کنید.
- اگر k < k1، به فرزند سمت چپ گره ریشه بروید.
- اگر k > k1، به فرزند سمت راست گره ریشه بروید.
- در غیر این صورت اگر k == k1، k2 را برمیگردانیم.
- مراحل بالا را تکرار کنید تا به یک گره برگ برسید.
مثال زیر را در نظر بگیرید. فرض کنید به دنبال گره 129 در این درخت B+ هستیم:
ابتدا 129 را با 108 مقایسه میکنیم. چون 108 < 129 پس به زیر درخت سمت راست میرویم.
چون 120 < 129 پس به زیر درخت سمت راست میرویم.
گره مورد نظر را پیدا کردیم.
پیچیدگی عملیاتهای درخت B+
پیچیدگی زمانی عملیاتهای جستجو، حذف و درج در درخت B+ را به قرار زیر خواهند بود.
عملیات درج
پیچیدگی زمانیپیچیدگی زمانی الگوریتم چیست؟ معرفی نماد های مجانبیاین صفحه عالی به معرفی پیچیدگی زمانی الگوریتم پرداخته، همچنین انواع نماد های مجانبی و پیچیدگی زمانی های برخی از الگوریتم های مرتب سازی و جستجو را توضیح داده عملیات درج درخت B+ از مرتبه Θ(t.logt n) است.
عملیات حذف
پیچیدگی زمانی عملیات حذف از درخت B+ از مرتبه Θ(logn) است.
عملیات جستجو
اگر جستجوی خطی در داخل یک گره اجرا شود، پیچیدگی کل Θ(logt n) است (Θ(t) پیچیدگی زمانی جستجوی خطی است).
اگر از جستجوی دودویی استفاده شود، پیچیدگی کل Θ(log2t.logt n) است.
پیاده سازی درخت B+
در ادامه به پیاده سازی درخت B+ در زبان های برنامه نویسیزبان های برنامه نویسی چیست؟این مقاله عالی توضیح داده که زبان های برنامه نویسی چیست؟ و انواع زبان های برنامه نویسی و بهترین زبان برنامه نویسی برای شروع و پردرآمدترین آنها را معرفی کرده پایتونزبان برنامه نویسی پایتون چیست؟ – نحوه شروع و دلایل محبوبیتزبان برنامه نویسی پایتون (Python) چیست؟ این مقاله عالی به بررسی دلایل محبوبیت پایتون، موارد استفاده از پایتون و نحوه شروع به برنامه نویسی پایتون پرداخته و سی پلاس پلاسبرنامه نویسی سی پلاس پلاس چیست؟ مزایای برنامه نویسی C++؟برنامه نویسی سی پلاس پلاس چیست و چه کاربردی دارد؟ این صفحه عالی به بررسی مزایای برنامه نویسی C++ پرداخته و نمونه هایی از کدهای زبان برنامه نویسی ++C را آورده میپردازیم:
Python
# B+ tee in python
import math
# Node creation
class Node:
def __init__(self, order):
self.order = order
self.values = []
self.keys = []
self.nextKey = None
self.parent = None
self.check_leaf = False
# Insert at the leaf
def insert_at_leaf(self, leaf, value, key):
if (self.values):
temp1 = self.values
for i in range(len(temp1)):
if (value == temp1[i]):
self.keys[i].append(key)
break
elif (value < temp1[i]):
self.values = self.values[:i] + [value] + self.values[i:]
self.keys = self.keys[:i] + [[key]] + self.keys[i:]
break
elif (i + 1 == len(temp1)):
self.values.append(value)
self.keys.append([key])
break
else:
self.values = [value]
self.keys = [[key]]
# B plus tree
class BplusTree:
def __init__(self, order):
self.root = Node(order)
self.root.check_leaf = True
# Insert operation
def insert(self, value, key):
value = str(value)
old_node = self.search(value)
old_node.insert_at_leaf(old_node, value, key)
if (len(old_node.values) == old_node.order):
node1 = Node(old_node.order)
node1.check_leaf = True
node1.parent = old_node.parent
mid = int(math.ceil(old_node.order / 2)) - 1
node1.values = old_node.values[mid + 1:]
node1.keys = old_node.keys[mid + 1:]
node1.nextKey = old_node.nextKey
old_node.values = old_node.values[:mid + 1]
old_node.keys = old_node.keys[:mid + 1]
old_node.nextKey = node1
self.insert_in_parent(old_node, node1.values[0], node1)
# Search operation for different operations
def search(self, value):
current_node = self.root
while(current_node.check_leaf == False):
temp2 = current_node.values
for i in range(len(temp2)):
if (value == temp2[i]):
current_node = current_node.keys[i + 1]
break
elif (value < temp2[i]):
current_node = current_node.keys[i]
break
elif (i + 1 == len(current_node.values)):
current_node = current_node.keys[i + 1]
break
return current_node
# Find the node
def find(self, value, key):
l = self.search(value)
for i, item in enumerate(l.values):
if item == value:
if key in l.keys[i]:
return True
else:
return False
return False
# Inserting at the parent
def insert_in_parent(self, n, value, ndash):
if (self.root == n):
rootNode = Node(n.order)
rootNode.values = [value]
rootNode.keys = [n, ndash]
self.root = rootNode
n.parent = rootNode
ndash.parent = rootNode
return
parentNode = n.parent
temp3 = parentNode.keys
for i in range(len(temp3)):
if (temp3[i] == n):
parentNode.values = parentNode.values[:i] + \
[value] + parentNode.values[i:]
parentNode.keys = parentNode.keys[:i +
1] + [ndash] + parentNode.keys[i + 1:]
if (len(parentNode.keys) > parentNode.order):
parentdash = Node(parentNode.order)
parentdash.parent = parentNode.parent
mid = int(math.ceil(parentNode.order / 2)) - 1
parentdash.values = parentNode.values[mid + 1:]
parentdash.keys = parentNode.keys[mid + 1:]
value_ = parentNode.values[mid]
if (mid == 0):
parentNode.values = parentNode.values[:mid + 1]
else:
parentNode.values = parentNode.values[:mid]
parentNode.keys = parentNode.keys[:mid + 1]
for j in parentNode.keys:
j.parent = parentNode
for j in parentdash.keys:
j.parent = parentdash
self.insert_in_parent(parentNode, value_, parentdash)
# Delete a node
def delete(self, value, key):
node_ = self.search(value)
temp = 0
for i, item in enumerate(node_.values):
if item == value:
temp = 1
if key in node_.keys[i]:
if len(node_.keys[i]) > 1:
node_.keys[i].pop(node_.keys[i].index(key))
elif node_ == self.root:
node_.values.pop(i)
node_.keys.pop(i)
else:
node_.keys[i].pop(node_.keys[i].index(key))
del node_.keys[i]
node_.values.pop(node_.values.index(value))
self.deleteEntry(node_, value, key)
else:
print("Value not in Key")
return
if temp == 0:
print("Value not in Tree")
return
# Delete an entry
def deleteEntry(self, node_, value, key):
if not node_.check_leaf:
for i, item in enumerate(node_.keys):
if item == key:
node_.keys.pop(i)
break
for i, item in enumerate(node_.values):
if item == value:
node_.values.pop(i)
break
if self.root == node_ and len(node_.keys) == 1:
self.root = node_.keys[0]
node_.keys[0].parent = None
del node_
return
elif (len(node_.keys) < int(math.ceil(node_.order / 2)) and node_.check_leaf == False) or (len(node_.values) < int(math.ceil((node_.order - 1) / 2)) and node_.check_leaf == True):
is_predecessor = 0
parentNode = node_.parent
PrevNode = -1
NextNode = -1
PrevK = -1
PostK = -1
for i, item in enumerate(parentNode.keys):
if item == node_:
if i > 0:
PrevNode = parentNode.keys[i - 1]
PrevK = parentNode.values[i - 1]
if i < len(parentNode.keys) - 1:
NextNode = parentNode.keys[i + 1]
PostK = parentNode.values[i]
if PrevNode == -1:
ndash = NextNode
value_ = PostK
elif NextNode == -1:
is_predecessor = 1
ndash = PrevNode
value_ = PrevK
else:
if len(node_.values) + len(NextNode.values) < node_.order:
ndash = NextNode
value_ = PostK
else:
is_predecessor = 1
ndash = PrevNode
value_ = PrevK
if len(node_.values) + len(ndash.values) < node_.order:
if is_predecessor == 0:
node_, ndash = ndash, node_
ndash.keys += node_.keys
if not node_.check_leaf:
ndash.values.append(value_)
else:
ndash.nextKey = node_.nextKey
ndash.values += node_.values
if not ndash.check_leaf:
for j in ndash.keys:
j.parent = ndash
self.deleteEntry(node_.parent, value_, node_)
del node_
else:
if is_predecessor == 1:
if not node_.check_leaf:
ndashpm = ndash.keys.pop(-1)
ndashkm_1 = ndash.values.pop(-1)
node_.keys = [ndashpm] + node_.keys
node_.values = [value_] + node_.values
parentNode = node_.parent
for i, item in enumerate(parentNode.values):
if item == value_:
p.values[i] = ndashkm_1
break
else:
ndashpm = ndash.keys.pop(-1)
ndashkm = ndash.values.pop(-1)
node_.keys = [ndashpm] + node_.keys
node_.values = [ndashkm] + node_.values
parentNode = node_.parent
for i, item in enumerate(p.values):
if item == value_:
parentNode.values[i] = ndashkm
break
else:
if not node_.check_leaf:
ndashp0 = ndash.keys.pop(0)
ndashk0 = ndash.values.pop(0)
node_.keys = node_.keys + [ndashp0]
node_.values = node_.values + [value_]
parentNode = node_.parent
for i, item in enumerate(parentNode.values):
if item == value_:
parentNode.values[i] = ndashk0
break
else:
ndashp0 = ndash.keys.pop(0)
ndashk0 = ndash.values.pop(0)
node_.keys = node_.keys + [ndashp0]
node_.values = node_.values + [ndashk0]
parentNode = node_.parent
for i, item in enumerate(parentNode.values):
if item == value_:
parentNode.values[i] = ndash.values[0]
break
if not ndash.check_leaf:
for j in ndash.keys:
j.parent = ndash
if not node_.check_leaf:
for j in node_.keys:
j.parent = node_
if not parentNode.check_leaf:
for j in parentNode.keys:
j.parent = parentNode
# Print the tree
def printTree(tree):
lst = [tree.root]
level = [0]
leaf = None
flag = 0
lev_leaf = 0
node1 = Node(str(level[0]) + str(tree.root.values))
while (len(lst) != 0):
x = lst.pop(0)
lev = level.pop(0)
if (x.check_leaf == False):
for i, item in enumerate(x.keys):
print(item.values)
else:
for i, item in enumerate(x.keys):
print(item.values)
if (flag == 0):
lev_leaf = lev
leaf = x
flag = 1
record_len = 3
bplustree = BplusTree(record_len)
bplustree.insert('5', '33')
bplustree.insert('15', '21')
bplustree.insert('25', '31')
bplustree.insert('35', '41')
bplustree.insert('45', '10')
printTree(bplustree)
if(bplustree.find('5', '34')):
print("Found")
else:
print("Not found")
C++
// Searching on a B+ tree in C++
#include <climits>
#include <fstream>
#include <iostream>
#include <sstream>
using namespace std;
int MAX = 3;
// BP node
class Node
{
bool IS_LEAF;
int *key, size;
Node **ptr;
friend class BPTree;
public:
Node();
};
// BP tree
class BPTree
{
Node *root;
void insertInternal(int, Node *, Node *);
Node *findParent(Node *, Node *);
public:
BPTree();
void search(int);
void insert(int);
void display(Node *);
Node *getRoot();
};
Node::Node()
{
key = new int[MAX];
ptr = new Node *[MAX + 1];
}
BPTree::BPTree()
{
root = NULL;
}
// Search operation
void BPTree::search(int x)
{
if (root == NULL)
{
cout "Tree is empty\n";
}
else
{
Node *cursor = root;
while (cursor->IS_LEAF == false)
{
for (int i = 0; i cursor->size; i++)
{
if (x cursor->key[i])
{
cursor = cursor->ptr[i];
break;
}
if (i == cursor->size - 1)
{
cursor = cursor->ptr[i + 1];
break;
}
}
}
for (int i = 0; i < cursor->size; i++)
{
if (cursor->key[i] == x)
{
cout "Found\n";
return;
}
}
cout << "Not found\n";
}
}
// Insert Operation
void BPTree::insert(int x)
{
if (root == NULL)
{
root = new Node;
root->key[0] = x;
root->IS_LEAF = true;
root->size = 1;
}
else
{
Node *cursor = root;
Node *parent;
while (cursor->IS_LEAF == false)
{
parent = cursor;
for (int i = 0; i cursor->size; i++)
{
if (x cursor->key[i])
{
cursor = cursor->ptr[i];
break;
}
if (i == cursor->size - 1)
{
cursor = cursor->ptr[i + 1];
break;
}
}
}
if (cursor->size < MAX)
{
int i = 0;
while (x > cursor->key[i] && i cursor->size)
i++;
for (int j = cursor->size; j > i; j--)
{
cursor->key[j] = cursor->key[j - 1];
}
cursor->key[i] = x;
cursor->size++;
cursor->ptr[cursor->size] = cursor->ptr[cursor->size - 1];
cursor->ptr[cursor->size - 1] = NULL;
}
else
{
Node *newLeaf = new Node;
int virtualNode[MAX + 1];
for (int i = 0; i MAX; i++)
{
virtualNode[i] = cursor->key[i];
}
int i = 0, j;
while (x > virtualNode[i] && i < MAX)
i++;
for (int j = MAX + 1; j > i; j--)
{
virtualNode[j] = virtualNode[j - 1];
}
virtualNode[i] = x;
newLeaf->IS_LEAF = true;
cursor->size = (MAX + 1) / 2;
newLeaf->size = MAX + 1 - (MAX + 1) / 2;
cursor->ptr[cursor->size] = newLeaf;
newLeaf->ptr[newLeaf->size] = cursor->ptr[MAX];
cursor->ptr[MAX] = NULL;
for (i = 0; i < cursor->size; i++)
{
cursor->key[i] = virtualNode[i];
}
for (i = 0, j = cursor->size; i < newLeaf->size; i++, j++)
{
newLeaf->key[i] = virtualNode[j];
}
if (cursor == root)
{
Node *newRoot = new Node;
newRoot->key[0] = newLeaf->key[0];
newRoot->ptr[0] = cursor;
newRoot->ptr[1] = newLeaf;
newRoot->IS_LEAF = false;
newRoot->size = 1;
root = newRoot;
}
else
{
insertInternal(newLeaf->key[0], parent, newLeaf);
}
}
}
}
// Insert Operation
void BPTree::insertInternal(int x, Node *cursor, Node *child)
{
if (cursor->size MAX)
{
int i = 0;
while (x > cursor->key[i] && i cursor->size)
i++;
for (int j = cursor->size; j > i; j--)
{
cursor->key[j] = cursor->key[j - 1];
}
for (int j = cursor->size + 1; j > i + 1; j--)
{
cursor->ptr[j] = cursor->ptr[j - 1];
}
cursor->key[i] = x;
cursor->size++;
cursor->ptr[i + 1] = child;
}
else
{
Node *newInternal = new Node;
int virtualKey[MAX + 1];
Node *virtualPtr[MAX + 2];
for (int i = 0; i MAX; i++)
{
virtualKey[i] = cursor->key[i];
}
for (int i = 0; i < MAX + 1; i++)
{
virtualPtr[i] = cursor->ptr[i];
}
int i = 0, j;
while (x > virtualKey[i] && i < MAX)
i++;
for (int j = MAX + 1; j > i; j--)
{
virtualKey[j] = virtualKey[j - 1];
}
virtualKey[i] = x;
for (int j = MAX + 2; j > i + 1; j--)
{
virtualPtr[j] = virtualPtr[j - 1];
}
virtualPtr[i + 1] = child;
newInternal->IS_LEAF = false;
cursor->size = (MAX + 1) / 2;
newInternal->size = MAX - (MAX + 1) / 2;
for (i = 0, j = cursor->size + 1; i < newInternal->size; i++, j++)
{
newInternal->key[i] = virtualKey[j];
}
for (i = 0, j = cursor->size + 1; i < newInternal->size + 1; i++, j++)
{
newInternal->ptr[i] = virtualPtr[j];
}
if (cursor == root)
{
Node *newRoot = new Node;
newRoot->key[0] = cursor->key[cursor->size];
newRoot->ptr[0] = cursor;
newRoot->ptr[1] = newInternal;
newRoot->IS_LEAF = false;
newRoot->size = 1;
root = newRoot;
}
else
{
insertInternal(cursor->key[cursor->size], findParent(root, cursor), newInternal);
}
}
}
// Find the parent
Node *BPTree::findParent(Node *cursor, Node *child)
{
Node *parent;
if (cursor->IS_LEAF || (cursor->ptr[0])->IS_LEAF)
{
return NULL;
}
for (int i = 0; i < cursor->size + 1; i++)
{
if (cursor->ptr[i] == child)
{
parent = cursor;
return parent;
}
else
{
parent = findParent(cursor->ptr[i], child);
if (parent != NULL)
return parent;
}
}
return parent;
}
// Print the tree
void BPTree::display(Node *cursor)
{
if (cursor != NULL)
{
for (int i = 0; i cursor->size; i++)
{
cout cursor->key[i] " ";
}
cout << "\n";
if (cursor->IS_LEAF != true)
{
for (int i = 0; i cursor->size + 1; i++)
{
display(cursor->ptr[i]);
}
}
}
}
// Get the root
Node *BPTree::getRoot()
{
return root;
}
int main()
{
BPTree node;
node.insert(5);
node.insert(15);
node.insert(25);
node.insert(35);
node.insert(45);
node.insert(55);
node.insert(40);
node.insert(30);
node.insert(20);
node.display(node.getRoot());
node.search(15);
}
کاربردهای درخت B+
از کاربردهای درخت B+ میتوان به موارد زیر اشاره کرد:
- شاخصسازی چندسطحی
- عملیات سریعتر روی درخت (درج، حذف، جستجو)
- شاخصسازی
مقایسه درخت B+ و درخت B
برجستهترین نقاط مقایسه بین B-tree و B+tree شامل موارد زیر است:
- در B+trees، کلیدهای جستجو را میتوان تکرار کرد، اما این مورد برای B-trees نیست.
- درختان B به دادهها اجازه میدهند فقط در گرههای برگ ذخیره شوند، در حالی که درختان B دادهها را در گرههای برگ و داخلی ذخیره میکنند.
- در B+trees، دادههای ذخیره شده در گره برگ، جستجو را کارآمدتر میکند، زیرا میتوانیم کلیدهای بیشتری را در گرههای داخلی ذخیره کنیم - این بدان معناست که ما نیاز به دسترسی به گرههای کمتری داریم.
- حذف دادهها از B+tree آسانتر و سریعتر است زیرا فقط باید دادهها را از گرههای برگ حذف کنیم.
- گره های برگ در B+tree به هم مرتبط هستند و عملیات جستجو را کارآمد و سریع میکنند.
جمعبندی
B+ Tree توسعهای از B Tree است که امکان درج، حذف و عملیات جستجو را فراهم میکند. در B Tree، کلیدها و رکوردها را میتوان در گره های داخلی و برگ ذخیره کرد. در درخت B+، رکوردها (دادهها) فقط روی گرههای برگ نگهداری میشوند، در حالی که گرههای داخلی فقط میتوانند مقادیر کلیدی را ذخیره کنند. در این مقاله، درخت B+ را از نزدیک بررسی کردیم. ما یاد گرفتیم که آنها چگونه ساخته میشوند و یاد گرفتیم که چگونه میتوانیم یک مقدار خاص را جستجو، درج یا حذف کنیم؛ همچنین پیادهسازیهایی از این نوع درخت را دیدیم و با برخی کاربردها و تفاوت آن با درخت B آشنا شدیم.
درخت B+ چیست؟
درخت B+ از ریشه، گره های داخلی و برگ تشکیل شده است. ریشه ممکن است یک برگ یا یک گره با دو یا چند فرزند باشد. درخت B+ را میتوان به عنوان یک درخت B مشاهده کرد که در آن هر گره فقط حاوی کلیدها است (نه جفتهای کلید-مقدار) و یک سطح در پایین با برگهای مرتبط اضافه می کند.
درخت B+ چگونه در پایگاه داده استفاده میشود؟
پایگاههای داده SQL از درختهای B+ برای ذخیره کارآمد دادهها و انجام عملیاتهایی مانند درج، بهروزرسانی، یافتن و حذف با پیچیدگی زمانی O(log n) استفاده میکنند. این امر مدیریت و ذخیره حجم زیادی از داده ها را قابل مدیریت و کارآمدتر می کند.
چرا درخت B+ سریعتر از B-tree است؟
جستجو در درخت B ناکارآمد است زیرا رکوردها در گره های برگ یا درونی ذخیره می شوند. از آنجا که تمام رکوردها در گره های برگ درخت B+ ذخیره میشوند، جستجو بسیار کارآمد یا سریعتر است. در مقابل عملیات درج بیشتر طول میکشد و گاهی اوقات غیرقابل پیشبینی است.