3. Delete 實現
附上實驗2的第一部分? https://www.cnblogs.com/JayL-zxl/p/14324297.html
3. 1 刪除演算法原理
如果葉子結點中沒有相應的key,則刪除失敗。否則執行下面的步驟
圖片來自於這篇博文https://www.programiz.com/dsa/deletion-from-a-b-plus-tree
-
情況1 要刪除的要素就只在葉子結點
-
刪除葉子結點中對應的key。刪除後若結點的key的個數大於等於\(\frac{m-1}{2}\),刪除操作結束。
刪除40的例子如下
-
若兄弟結點key有多餘(\(>\frac{m-1}{2}\)),向兄弟結點借一個關鍵字,然後用兄弟結點的median key替代父結點。
刪除5的例子如下
-
-
情況2 要刪除的元素不僅在葉子結點而且在內部結點出現
-
如果結點中的元素個數\(> \frac{m-1}{2}\),只需從葉結點刪除key值,同時從內部節點刪除鍵。用key元素的後繼元素補充內部節點的空餘空間。
刪除45
-
如果節點中元素個數等於\(\frac{m-1}{2}\),則刪除該鍵並從其直接兄弟借一個鍵。用借來的鍵填充內部結點中所形成的空空間。
刪除35
-
和情況2的第一種情況類似。只不過空洞結點是當前結點的祖父結點。
刪除25
-
-
情況3 這種情況是樹的高度會縮小的情況。
這種情況有點複雜。請看刪除55的例子
cmu這裡給了演示網站 https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html
演算法描述見下表
3.2 刪除演算法實現
-
如果當前是空樹則立即返回
-
否則先找到要刪除的key所在的page
-
隨後呼叫
RemoveAndDeleteRecord
在葉page上直接刪除key值同樣還是經典的二分查詢
INDEX_TEMPLATE_ARGUMENTS int B_PLUS_TREE_LEAF_PAGE_TYPE::RemoveAndDeleteRecord(const KeyType &key, const KeyComparator &comparator) { int l=0,r=GetSize()-1; if(l>r||comparator(key,array[l].first)<0||comparator(key,array[r].first)>0)return GetSize(); while(l<=r){ int mid=(l+r)>>1; if(comparator(key, KeyAt(mid)) < 0){ r=mid; } else if (comparator(key, KeyAt(mid)) > 0) l=mid+1; else{ memmove(array + mid, array + mid + 1,static_cast<size_t>((GetSize() - mid - 1)*sizeof(MappingType))); IncreaseSize(-1); break; } } return GetSize(); }
-
刪除之後的葉子結點有兩種情況
葉子結點內關鍵字個數小於最小值向下執行。否則結束
-- 呼叫CoalesceOrRedistribute
1.如果當前結點是根節點則呼叫AdjustRoot(node)
INDEX_TEMPLATE_ARGUMENTS
bool BPLUSTREE_TYPE::AdjustRoot(BPlusTreePage *old_root_node) {
//case 2
if (old_root_node->IsLeafPage()) {
if (old_root_node->GetSize() == 0) {
root_page_id_ = INVALID_PAGE_ID;
UpdateRootPageId(false);
return true;
}
return false;
}
// case 1
if (old_root_node->GetSize() == 2) {
auto root =reinterpret_cast<BPlusTreeInternalPage<KeyType, page_id_t,KeyComparator> *>(old_root_node);
root_page_id_ = root->ValueAt(1);
UpdateRootPageId(false);
auto page = buffer_pool_manager_->FetchPage(root_page_id_);
if (page == nullptr) {
throw "no page can used while AdjustRoot";
}
auto new_root =reinterpret_cast<BPlusTreeInternalPage<KeyType, page_id_t,KeyComparator> *>(page);
new_root->SetParentPageId(INVALID_PAGE_ID);
buffer_pool_manager_->UnpinPage(root_page_id_, true);
return true;
}
return false;
}
2.否則應該找它的兄弟節點
預設都是找它左邊的結點。如果當前已經在最左邊即第一個我們找右邊的結點
呼叫CoalesceOrRedistribute
a. 如果兄弟結點的size+當前結點的size大於最大值則需要重新分配
-- 呼叫Redistribute
函式
INDEX_TEMPLATE_ARGUMENTS
template <typename N>
bool BPLUSTREE_TYPE::CoalesceOrRedistribute(N *node, Transaction *transaction) {
if (node->IsRootPage()) {
return AdjustRoot(node);
}
if (node->IsLeafPage()) {
if (node->GetSize() >= node->GetMinSize()) {
return false;
}
} else {
if (node->GetSize() > node->GetMinSize()) {
return false;
}
}
auto page = buffer_pool_manager_->FetchPage(node->GetParentPageId());
if (page == nullptr) {
throw "no page can used while CoalesceOrRedistribute";
}
auto parent =reinterpret_cast<BPlusTreeInternalPage<KeyType, page_id_t,KeyComparator> *>(page);
int value_index = parent->ValueIndex(node->GetPageId());
//sibling page always find left page
int sibling_page_id;
if (value_index == 0) {
sibling_page_id = parent->ValueAt(value_index + 1);
} else {
sibling_page_id = parent->ValueAt(value_index - 1);
}
// fetch sibling node
auto sibling_page = buffer_pool_manager_->FetchPage(sibling_page_id);
if (sibling_page == nullptr) {
throw Exception("all page are pinned while CoalesceOrRedistribute");
}
// put sibling node to PageSet
sibling_page->WLatch();
transaction->AddIntoPageSet(sibling_page);
auto sibling = reinterpret_cast<N *>(sibling_page);
bool is_redistribute = false;
// If sibling's size + input
// page's size > page's max size, then redistribute.
if (sibling->GetSize() + node->GetSize() > node->GetMaxSize()) {
is_redistribute = true;
//TODO need to modify parent
buffer_pool_manager_->UnpinPage(parent->GetPageId(), true);
}
// exec redistribute
if (is_redistribute) {
Redistribute<N>(sibling, node, value_index);
return false;
}
//Otherwise, merge.
bool ret;
if (value_index == 0) {
Coalesce<N>(node, sibling, parent, 1, transaction);
transaction->AddIntoDeletedPageSet(sibling_page_id);
// node should not be deleted
ret = false;
} else {
Coalesce<N>(sibling, node, parent, value_index, transaction);
// node should be deleted
ret = true;
}
//TODO unpin parent
buffer_pool_manager_->UnpinPage(parent->GetPageId(), true);
return ret;
}
重新分配的時候有兩種情況
(1) 移動它左邊結點最大的的元素到當前結點的第一個元素---對應MoveLastToFrontOf
函式
這裡20年版本的實驗把之前版本的傳遞index改成了傳遞key值的引用。並且沒有等號可以用,emm為了偷懶我把它改成了和17年實驗一樣的設定,這裡注意對於實驗給你的標頭檔案好多需要修改。不然模版類就會報錯
注意這裡對於internalPage
和LeafPage
並不一樣
首先看對於LeafPage
的實現
整體邏輯非常簡單
- 就是把元素append到末尾
- 然後就是修改父親結點的元素。
INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_LEAF_PAGE_TYPE::MoveLastToFrontOf(BPlusTreeLeafPage *recipient,int parentIndex,
BufferPoolManager *buffer_pool_manager) {
MappingType pair = GetItem(GetSize() - 1);
IncreaseSize(-1);
recipient->CopyFirstFrom(pair, parentIndex, buffer_pool_manager);
}
INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_LEAF_PAGE_TYPE::CopyFirstFrom(const MappingType &item, int parentIndex,
BufferPoolManager *buffer_pool_manager) {
assert(GetSize() + 1 < GetMaxSize());
memmove(array + 1, array, GetSize()*sizeof(MappingType));
IncreaseSize(1);
array[0] = item;
auto page = buffer_pool_manager->FetchPage(GetParentPageId());
if (page == nullptr) {
throw "no page can used while CopyFirstFrom";
}
// get parent
auto parent =reinterpret_cast<BPlusTreeInternalPage<KeyType, decltype(GetPageId()),KeyComparator> *>(page->GetData());
parent->SetKeyAt(parentIndex, item.first);
buffer_pool_manager->UnpinPage(GetParentPageId(), true);
}
然後看對於InternalPage
的實現
- 這裡和
leafpage
不一樣的就是最後一個元素在GetSize()
處 - 這裡要修改移動元素
value
值(所指向的結點)的parent
結點
INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_INTERNAL_PAGE_TYPE::MoveLastToFrontOf(BPlusTreeInternalPage *recipient, int parent_index,
BufferPoolManager *buffer_pool_manager) {
assert(GetSize() > 1);
IncreaseSize(-1);
MappingType pair = array[GetSize()];
page_id_t child_page_id = pair.second;
recipient->CopyFirstFrom(pair,parent_index, buffer_pool_manager);
// update parent page id
auto page = buffer_pool_manager->FetchPage(child_page_id);
if (page == nullptr) {
throw "no page can used while MoveLastFrontOf";
}
//把要移動元素所指向的結點的parent指標修改。
auto child = reinterpret_cast<BPlusTreePage *>(page->GetData());
child->SetParentPageId(recipient->GetPageId());
assert(child->GetParentPageId() == recipient->GetPageId());
buffer_pool_manager->UnpinPage(child->GetPageId(), true);
}
/* Append an entry at the beginning.
* Since it is an internal page, the moved entry(page)'s parent needs to be updated.
* So I need to 'adopt' it by changing its parent page id, which needs to be persisted with BufferPoolManger
*/
INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_INTERNAL_PAGE_TYPE::CopyFirstFrom(const MappingType &pair, int parent_index,BufferPoolManager *buffer_pool_manager) {
assert(GetSize() + 1 < GetMaxSize());
auto page = buffer_pool_manager->FetchPage(GetParentPageId());
if (page == nullptr) {
throw "no page can used while CopyFirstFrom";
}
auto parent = reinterpret_cast<BPlusTreeInternalPage *>(page->GetData());
auto key = parent->KeyAt(parent_index);
// set parent key to the last of current page
parent->SetKeyAt(parent_index, pair.first);
InsertNodeAfter(array[0].second, key, array[0].second);
array[0].second = pair.second;
buffer_pool_manager->UnpinPage(parent->GetPageId(), true);
}
(2) 移動它右邊結點最小的元素到當前結點的最後一個元素---對應了MoveFirstToEndOf
函式
注意這裡對於internalPage
和LeafPage
並不一樣
首先看對於LeafPage
的實現
- 取右邊的第一個元素,然後把其他元素都向前移動一個位置(用
memmove
實現) - 然後呼叫
CopyLastFrom
函式把元素拷貝過去 - 隨後修改
node
對應parent的key值
INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_LEAF_PAGE_TYPE::MoveFirstToEndOf(BPlusTreeLeafPage *recipient,BufferPoolManager *buffer_pool_manager) {
MappingType pair = GetItem(0);
IncreaseSize(-1);
memmove(array, array + 1, static_cast<size_t>(GetSize()*sizeof(MappingType)));
recipient->CopyLastFrom(pair);
auto page = buffer_pool_manager->FetchPage(GetParentPageId());
if (page == nullptr) {
throw "no page can used while MoveFirstToEndOf";
}
auto parent =reinterpret_cast<BPlusTreeInternalPage<KeyType, decltype(GetPageId()),KeyComparator> *>(page->GetData());
parent->SetKeyAt(parent->ValueIndex(GetPageId()), pair.first);
buffer_pool_manager->UnpinPage(GetParentPageId(), true);
}
/*
* Copy the item into the end of my item list. (Append item to my array)
*/
INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_LEAF_PAGE_TYPE::CopyLastFrom(const MappingType &item) {
assert(GetSize() + 1 <= GetMaxSize());
array[GetSize()] = item;
IncreaseSize(1);
}
然後看對於InternalPage
的實現
- 這裡需要注意的是
internalPage
的一個key是在index=1的位置(因為第一個位置就是一個沒有key值的指標位置) - 因為是內部頁,所以要修改它的孩子結點的指向。
- 還要修改內部結點父結點對應的key
INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_INTERNAL_PAGE_TYPE::MoveFirstToEndOf(BPlusTreeInternalPage *recipient,BufferPoolManager *buffer_pool_manager) {
assert(GetSize() > 1);
MappingType pair{KeyAt(1), ValueAt(0)};
page_id_t child_page_id = ValueAt(0);
SetValueAt(0, ValueAt(1));
Remove(1);
recipient->CopyLastFrom(pair, buffer_pool_manager);
// update child parent page id
auto page = buffer_pool_manager->FetchPage(child_page_id);
if (page == nullptr) {
throw "no page can used while MoveFirstToEndOf";
}
auto child = reinterpret_cast<BPlusTreePage *>(page);
child->SetParentPageId(recipient->GetPageId());
assert(child->GetParentPageId() == recipient->GetPageId());
buffer_pool_manager->UnpinPage(child->GetPageId(), true);
}
/* Append an entry at the end.
* Since it is an internal page, the moved entry(page)'s parent needs to be updated.
* So I need to 'adopt' it by changing its parent page id, which needs to be persisted with BufferPoolManger
*/
INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_INTERNAL_PAGE_TYPE::CopyLastFrom(const MappingType &pair, BufferPoolManager *buffer_pool_manager) {
assert(GetSize() + 1 <= GetMaxSize());
auto page = buffer_pool_manager->FetchPage(GetParentPageId());
if (page == nullptr) {
throw Exception("all page are pinned while CopyLastFrom");
}
auto parent = reinterpret_cast<BPlusTreeInternalPage *>(page);
auto index = parent->ValueIndex(GetPageId());
auto key = parent->KeyAt(index + 1);
array[GetSize()] = {key, pair.second};
IncreaseSize(1);
parent->SetKeyAt(index + 1, pair.first);
buffer_pool_manager->UnpinPage(parent->GetPageId(), true);
}
b.否則需要進行merge操作
-- 呼叫Coalesce
函式
- Coalesce函式比較簡單
- 首先把
node
結點的所有元素都移動到它的兄弟節點上 - 調整父結點。也就是把array向前移動
- 遞迴呼叫
CoalesceOrRedistribute
函式
INDEX_TEMPLATE_ARGUMENTS
template <typename N>
void BPLUSTREE_TYPE::Coalesce(N *neighbor_node, N *node,BPlusTreeInternalPage<KeyType, page_id_t, KeyComparator> *parent,int index, Transaction *transaction) {
// assumption: neighbor_node is predecessor of node
//LOG_DEBUG("index %d",index);
node->MoveAllTo(neighbor_node,index,buffer_pool_manager_);
LOG_DEBUG("size %d",node->GetSize());
// adjust parent
parent->Remove(index);
//recursive
if (CoalesceOrRedistribute(parent, transaction)) {
transaction->AddIntoDeletedPageSet(parent->GetPageId());
}
}
Internal內的 Remove
函式
INDEX_TEMPLATE_ARGUMENTS
void B_PLUS_TREE_INTERNAL_PAGE_TYPE::Remove(int index) {
assert(0 <= index && index < GetSize());
memmove(array+index,array+index+1,(GetSize()-index-1)*sizeof(MappingType));
IncreaseSize(-1);
}
好了刪除演算法已經實現了。首先我們可以通過test函式
cd build
make b_plus_tree_delete_test
./test/b_plus_tree_delete_test --gtest_also_run_disabled_tests
然後我們自己做一些test。這裡我就拿一個例子來看
插入10、5、7、4、9得到下圖是正確的?
然後刪除元素7
可以發現是完全正確的好了。第二部分就完成了。下面就是最後一部分對於?的實現和迭代器的實現