在之前了解並復習了下B+樹之后還是需要實戰一下
之前的B+樹文章https://www.cnblogs.com/yangj-Blog/p/12944301.html
演示如下
代碼如下
BPulsTree.h
/* BPlusTree.h B+樹定義文件,本程序實行一個簡單的B+樹 Definition (from http://www.seanster.com/BplusTree/BplusTree.html ): (1) A B+ tree of order v consists of a root, internal nodes and leaves. (2) The root my be either leaf or node with two or more children. (3) Internal nodes contain between v and 2v keys, and a node with k keys has k + 1 children. (4) Leaves are always on the same level. (5) If a leaf is a primary index, it consists of a bucket of records, sorted by search key. If it is a secondary index, it will have many short records consisting of a key and a pointer to the actual record. (1) 一個v階的B+樹由根結點、內部結點和葉子結點組成。 (2) 根結點可以是葉子結點,也可以是有兩個或更多子樹的內部結點。 (3) 每個內部結點包含v - 2v個鍵。如果一個內部結點包含k個鍵,則有且只有k+1個指向子樹的指針。 (4) 葉子結點總是在樹的同一層上。 (5) 如果葉子結點是主索引,它包含一組按鍵值排序的記錄;如果葉子結點是從索引,它包含一組短記錄,每個短記錄包含一個鍵以及指向實際記錄的指針。 (6) 內部結點的鍵值和葉子結點的數據值都是從小到大排序的。 (7) 在中間結點中,每個鍵的左子樹中的所有的鍵都小於這個鍵,每個鍵的右子樹中的所有的鍵都大於等於這個鍵。 */ /* B+ 樹的階,即內部結點中鍵的最小數目v。 也有些人把階定義為內部結點中鍵的最大數目,即2v。 一般而言,葉子結點中最大數據個數和內部結點中最大鍵個數是一樣的,也是2v。(我想這樣做的目的是為了把內部結點和葉子結點統一到同一個結構中吧) */ #define ORDER_V 2 /* 為簡單起見,把v固定為2,實際的B+樹v值應該是可配的。這里的v是內部節點中鍵的最小值 */ #define MAXNUM_KEY (ORDER_V * 2) /* 內部結點中最多鍵個數,為2v */ #define MAXNUM_POINTER (MAXNUM_KEY + 1) /* 內部結點中最多指向子樹的指針個數,為2v */ #define MAXNUM_DATA (ORDER_V * 2) /* 葉子結點中最多數據個數,為2v */ /* 鍵值的類型*/ typedef int KEY_TYPE; /* 為簡單起見,定義為int類型,實際的B+樹鍵值類型應該是可配的 */ /*備注: 為簡單起見,葉子結點的數據也只存儲鍵值*/ /* 結點類型 */ enum NODE_TYPE { NODE_TYPE_ROOT = 1, // 根結點 NODE_TYPE_INTERNAL = 2, // 內部結點 NODE_TYPE_LEAF = 3, // 葉子結點 }; #define NULL 0 #define INVALID 0 #define FLAG_LEFT 1 #define FLAG_RIGHT 2 /* 結點數據結構,為內部結點和葉子結點的父類 */ class CNode { public: CNode(); virtual ~CNode(); //獲取和設置結點類型 NODE_TYPE GetType() { return m_Type; } void SetType(NODE_TYPE type) {m_Type = type;} // 獲取和設置有效數據個數 int GetCount() { return m_Count;} void SetCount(int i) { m_Count = i; } // 獲取和設置某個元素,對中間結點指鍵值,對葉子結點指數據 virtual KEY_TYPE GetElement(int i) {return 0;} virtual void SetElement(int i, KEY_TYPE value) { } // 獲取和設置某個指針,對中間結點指指針,對葉子結點無意義 virtual CNode* GetPointer(int i) {return NULL;} virtual void SetPointer(int i, CNode* pointer) { } // 獲取和設置父結點 CNode* GetFather() { return m_pFather;} void SetFather(CNode* father) { m_pFather = father; } // 獲取一個最近的兄弟結點 CNode* GetBrother(int& flag); // 刪除結點 void DeleteChildren(); protected: NODE_TYPE m_Type; // 結點類型,取值為NODE_TYPE類型 int m_Count; // 有效數據個數,對中間結點指鍵個數,對葉子結點指數據個數 CNode* m_pFather; // 指向父結點的指針,標准B+樹中並沒有該指針,加上是為了更快地實現結點分裂和旋轉等操作 }; /* 內部結點數據結構 */ class CInternalNode : public CNode { public: CInternalNode(); virtual ~CInternalNode(); // 獲取和設置鍵值,對用戶來說,數字從1開始,實際在結點中是從0開始的 KEY_TYPE GetElement(int i) { if ((i > 0 ) && (i <= MAXNUM_KEY)) { return m_Keys[i - 1]; } else { return INVALID; } } void SetElement(int i, KEY_TYPE key) { if ((i > 0 ) && (i <= MAXNUM_KEY)) { m_Keys[i - 1] = key; } } // 獲取和設置指針,對用戶來說,數字從1開始 CNode* GetPointer(int i) { if ((i > 0 ) && (i <= MAXNUM_POINTER)) { return m_Pointers[i - 1]; } else { return NULL; } } void SetPointer(int i, CNode* pointer) { if ((i > 0 ) && (i <= MAXNUM_POINTER)) { m_Pointers[i - 1] = pointer; } } // 在結點pNode上插入鍵value bool Insert(KEY_TYPE value, CNode* pNode); // 刪除鍵value bool Delete(KEY_TYPE value); // 分裂結點 KEY_TYPE Split(CInternalNode* pNode, KEY_TYPE key); // 結合結點(合並結點) bool Combine(CNode* pNode); // 從另一結點移一個元素到本結點 bool MoveOneElement(CNode* pNode); protected: KEY_TYPE m_Keys[MAXNUM_KEY]; // 鍵數組 CNode* m_Pointers[MAXNUM_POINTER]; // 指針數組 }; /* 葉子結點數據結構 */ class CLeafNode : public CNode { public: CLeafNode(); virtual ~CLeafNode(); // 獲取和設置數據 KEY_TYPE GetElement(int i) { if ((i > 0 ) && (i <= MAXNUM_DATA)) { return m_Datas[i - 1]; } else { return INVALID; } } void SetElement(int i, KEY_TYPE data) { if ((i > 0 ) && (i <= MAXNUM_DATA)) { m_Datas[i - 1] = data; } } // 獲取和設置指針,對葉子結點無意義,只是實行父類的虛函數 CNode* GetPointer(int i) { return NULL; } // 插入數據 bool Insert(KEY_TYPE value); // 刪除數據 bool Delete(KEY_TYPE value); // 分裂結點 KEY_TYPE Split(CNode* pNode); // 結合結點 bool Combine(CNode* pNode); public: // 以下兩個變量用於實現雙向鏈表 CLeafNode* m_pPrevNode; // 前一個結點 CLeafNode* m_pNextNode; // 后一個結點 protected: KEY_TYPE m_Datas[MAXNUM_DATA]; // 數據數組 }; /* B+樹數據結構 */ class BPlusTree { public: BPlusTree(); virtual ~BPlusTree(); // 查找指定的數據 bool Search(KEY_TYPE data, char* sPath); // 插入指定的數據 bool Insert(KEY_TYPE data); // 刪除指定的數據 bool Delete(KEY_TYPE data); // 清除樹 void ClearTree(); // 打印樹 void PrintTree(); // 旋轉樹 BPlusTree* RotateTree(); // 檢查樹是否滿足B+樹的定義 bool CheckTree(); void PrintNode(CNode* pNode); // 遞歸檢查結點及其子樹是否滿足B+樹的定義 bool CheckNode(CNode* pNode); // 獲取和設置根結點 CNode* GetRoot() { return m_Root; } void SetRoot(CNode* root) { m_Root = root; } // 獲取和設置深度 int GetDepth() { return m_Depth; } void SetDepth(int depth) { m_Depth = depth; } // 深度加一 void IncDepth() { m_Depth = m_Depth + 1; } // 深度減一 void DecDepth() { if (m_Depth > 0) { m_Depth = m_Depth - 1; } } public: // 以下兩個變量用於實現雙向鏈表 CLeafNode* m_pLeafHead; // 頭結點 CLeafNode* m_pLeafTail; // 尾結點 protected: // 為插入而查找葉子結點 CLeafNode* SearchLeafNode(KEY_TYPE data); //插入鍵到中間結點 bool InsertInternalNode(CInternalNode* pNode, KEY_TYPE key, CNode* pRightSon); // 在中間結點中刪除鍵 bool DeleteInternalNode(CInternalNode* pNode, KEY_TYPE key); CNode* m_Root; // 根結點 int m_Depth; // 樹的深度 };
BPulsTree.cpp
----------------------------------------------
#include "BPlusTree.h" #include "stdio.h" #include "stdlib.h" CNode::CNode() { m_Type = NODE_TYPE_LEAF; m_Count = 0; m_pFather = NULL; } CNode::~CNode() { DeleteChildren(); } // 獲取一個最近的兄弟結點 CNode* CNode::GetBrother(int& flag) { CNode* pFather = GetFather(); //獲取其父結點指針 if (NULL == pFather) { return NULL; } CNode* pBrother = NULL; for (int i = 1; i <= pFather->GetCount() + 1; i++) //GetCount()表示獲取數據或關鍵字數,要比指針數小1。 { // 找到本結點的位置 if (pFather->GetPointer(i) == this) { if (i == (pFather->GetCount() + 1)) //表示其為父結點的最右邊孩子。 { pBrother = pFather->GetPointer(i - 1); // 本身是最后一個指針,只能找前一個指針 flag = FLAG_LEFT; } else { pBrother = pFather->GetPointer(i + 1); // 優先找后一個指針 flag = FLAG_RIGHT; } } } return pBrother; } // 遞歸刪除子結點 void CNode::DeleteChildren() // 疑問:這里的指針下標是否需要從0開始 { for (int i = 1; i <= GetCount(); i++) //GetCount()為返回結點中關鍵字即數據的個數 { CNode * pNode = GetPointer(i); if (NULL != pNode) // 葉子結點沒有指針 { pNode->DeleteChildren(); } delete pNode; } } //將內部節點的關鍵字和指針分別初始化為0和空 CInternalNode::CInternalNode() { m_Type = NODE_TYPE_INTERNAL; int i = 0; for (i = 0; i < MAXNUM_KEY; i++) { m_Keys[i] = INVALID; } for (i = 0; i < MAXNUM_POINTER; i++) { m_Pointers[i] = NULL; } } CInternalNode::~CInternalNode() { for (int i = 0; i < MAXNUM_POINTER; i++) { m_Pointers[i] = NULL; } } // 在中間結點中插入鍵。 /*疑問:中間結點需要插入值嗎?在插入值時,通常都是先找到在葉子結點中的位置,然后再插入。 中間結點通常當葉子結點需要分裂時將分裂后的兩個孩子結點插入其中*/ bool CInternalNode::Insert(KEY_TYPE value, CNode* pNode) { int i; // 如果中間結點已滿,直接返回失敗 if (GetCount() >= MAXNUM_KEY) { return false; } int j = 0; // 找到要插入鍵的位置 for (i = 0; (value > m_Keys[i]) && ( i < m_Count); i++) { } // 當前位置及其后面的鍵依次后移,空出當前位置 for (j = m_Count; j > i; j--) { m_Keys[j] = m_Keys[j - 1]; } // 當前位置及其后面的指針依次后移 for (j = m_Count + 1; j > i + 1; j--) { m_Pointers[j] = m_Pointers[j - 1]; } // 把鍵和指針存入當前位置 m_Keys[i] = value; m_Pointers[i + 1] = pNode; // 注意是第i+1個指針而不是第i個指針 pNode->SetFather(this); // 非常重要 該函數的意思是插入關鍵字value及其所指向子樹 m_Count++; // 返回成功 return true; } // 在中間結點中刪除鍵,以及該鍵后的指針 bool CInternalNode::Delete(KEY_TYPE key) { int i,j,k; for (i = 0; (key >= m_Keys[i]) && (i < m_Count); i++) { } for (j = i - 1; j < m_Count - 1; j++) { m_Keys[j] = m_Keys[j + 1]; } m_Keys[j] = INVALID; for (k = i; k < m_Count; k++) { m_Pointers[k] = m_Pointers[k + 1]; } m_Pointers[k] = NULL; m_Count--; return true; } /* 分裂中間結點 分裂中間結點和分裂葉子結點完全不同,因為中間結點不僅有2V鍵,還有2V+1指針,如果單純地一分為2,指針將無法分 配。 因此根據http://www.seanster.com/BplusTree/BplusTree.html ,分裂中 間結點的算法是: 根據要插入的鍵key判斷: (1)如果key小於第V個鍵,則把第V個鍵提出來,其左右的鍵分別分到兩個結點中 (2) 如果key大於第V+1個鍵,則把第V+1個鍵提出來,其左右的鍵分別分到兩個結點中 (3)如果key介於第V和V+1個鍵之間,則把key作為 要提出的鍵,原來的鍵各分一半到兩個結點中 提出來的RetKey作用是便於后續插入到祖先結點 */ KEY_TYPE CInternalNode::Split(CInternalNode* pNode, KEY_TYPE key) //key是新插入的值,pNode是分裂結點 { int i = 0, j = 0; // 如果要插入的鍵值在第V和V+1個鍵值中間,需要翻轉一下,因此先處理此情況 if ((key > this->GetElement(ORDER_V)) && (key < this->GetElement(ORDER_V + 1))) { // 把第V+1 -- 2V個鍵移到指定的結點中 for (i = ORDER_V + 1; i <= MAXNUM_KEY; i++) { j++; pNode->SetElement(j, this->GetElement(i)); this->SetElement(i, INVALID); } // 把第V+2 -- 2V+1個指針移到指定的結點中 j = 0; for (i = ORDER_V + 2; i <= MAXNUM_POINTER; i++) { j++; this->GetPointer(i)->SetFather(pNode); // 重新設置子結點的父親 pNode->SetPointer(j, this->GetPointer(i)); this->SetPointer(i, INVALID); } // 設置好Count個數 this->SetCount(ORDER_V); pNode->SetCount(ORDER_V); // 把原鍵值返回 return key; } // 以下處理key小於第V個鍵值或key大於第V+1個鍵值的情況 // 判斷是提取第V還是V+1個鍵 int position = 0; if (key < this->GetElement(ORDER_V)) { position = ORDER_V; } else { position = ORDER_V + 1; } // 把第position個鍵提出來,作為新的鍵值返回 KEY_TYPE RetKey = this->GetElement(position); // 把第position+1 -- 2V個鍵移到指定的結點中 j = 0; for (i = position + 1; i <= MAXNUM_KEY; i++) { j++; pNode->SetElement(j, this->GetElement(i)); this->SetElement(i, INVALID); } // 把第position+1 -- 2V+1個指針移到指定的結點中(注意指針比鍵多一個) j = 0; for (i = position + 1; i <= MAXNUM_POINTER; i++) { j++; this->GetPointer(i)->SetFather(pNode); // 重新設置子結點的父親 pNode->SetPointer(j, this->GetPointer(i)); this->SetPointer(i, INVALID); } // 清除提取出的位置 this->SetElement(position, INVALID); // 設置好Count個數 this->SetCount(position - 1); pNode->SetCount(MAXNUM_KEY - position); return RetKey; } //結合結點,把指定中間結點的數據全部剪切到本中間結點 bool CInternalNode::Combine(CNode* pNode) { // 參數檢查 if (this->GetCount() + pNode->GetCount() + 1> MAXNUM_DATA) // 預留一個新鍵的位置 { return false; } // 取待合並結點的第一個孩子的第一個元素作為新鍵值 KEY_TYPE NewKey = pNode->GetPointer(1)->GetElement(1); //疑問:感覺應該改為KEY_TYPE NewKey = pNode->GetElement(1); m_Keys[m_Count] = NewKey; m_Count++; m_Pointers[m_Count] = pNode->GetPointer(1); //疑問:感覺應該為m_Pointers[m_Count+1] = pNode->GetPointer(1); for (int i = 1; i <= pNode->GetCount(); i++) { m_Keys[m_Count] = pNode->GetElement(i); m_Count++; m_Pointers[m_Count] = pNode->GetPointer(i+1); } return true; } // 從另一結點移一個元素到本結點 bool CInternalNode::MoveOneElement(CNode* pNode) { // 參數檢查 if (this->GetCount() >= MAXNUM_DATA) { return false; } int i,j; // 兄弟結點在本結點左邊 if (pNode->GetElement(1) < this->GetElement(1)) { // 先騰出位置 for (i = m_Count; i > 0; i--) { m_Keys[i] = m_Keys[i -1]; } for (j = m_Count + 1; j > 0; j--) { m_Pointers[j] = m_Pointers[j -1]; } // 賦值 // 第一個鍵值不是兄弟結點的最后一個鍵值,而是本結點第一個子結點的第一個元素的值 m_Keys[0] = GetPointer(1)->GetElement(1); // 第一個子結點為兄弟結點的最后一個子結點 m_Pointers[0] = pNode->GetPointer(pNode->GetCount() + 1); // 修改兄弟結點 pNode->SetElement(pNode->GetCount(), INVALID); pNode->SetPointer(pNode->GetCount() + 1, INVALID); } else // 兄弟結點在本結點右邊 { // 賦值 // 最后一個鍵值不是兄弟結點的第一個鍵值,而是兄弟結點第一個子結點的第一個元素的值 m_Keys[m_Count] = pNode->GetPointer(1)->GetElement(1); // 最后一個子結點為兄弟結點的第一個子結點 m_Pointers[m_Count + 1] = pNode->GetPointer(1); // 修改兄弟結點 for (i = 1; i < pNode->GetCount() - 1; i++) { pNode->SetElement(i, pNode->GetElement(i + 1)); } for (j = 1; j < pNode->GetCount(); j++) { pNode->SetPointer(j, pNode->GetPointer(j + 1)); } } // 設置數目 this->SetCount(this->GetCount() + 1); pNode->SetCount(pNode->GetCount() - 1); return true; } // 清除葉子結點中的數據 CLeafNode::CLeafNode() { m_Type = NODE_TYPE_LEAF; for (int i = 0; i < MAXNUM_DATA; i++) { m_Datas[i] = INVALID; } m_pPrevNode = NULL; m_pNextNode = NULL; } CLeafNode::~CLeafNode() { } // 在葉子結點中插入數據 bool CLeafNode::Insert(KEY_TYPE value) { int i,j; // 如果葉子結點已滿,直接返回失敗 if (GetCount() >= MAXNUM_DATA) { return false; } // 找到要插入數據的位置 for (i = 0; (value > m_Datas[i]) && ( i < m_Count); i++) { } // 當前位置及其后面的數據依次后移,空出當前位置 for (j = m_Count; j > i; j--) { m_Datas[j] = m_Datas[j - 1]; } // 把數據存入當前位置 m_Datas[i] = value; m_Count++; // 返回成功 return true; } bool CLeafNode::Delete(KEY_TYPE value) { int i,j; bool found = false; for (i = 0; i < m_Count; i++) { if (value == m_Datas[i]) { found = true; break; } } // 如果沒有找到,返回失敗 if (false == found) { return false; } // 后面的數據依次前移 for (j = i; j < m_Count - 1; j++) { m_Datas[j] = m_Datas[j + 1]; } m_Datas[j] = INVALID; m_Count--; // 返回成功 return true; } // 分裂葉子結點,把本葉子結點的后一半數據剪切到指定的葉子結點中 KEY_TYPE CLeafNode::Split(CNode* pNode) { // 把本葉子結點的后一半數據移到指定的結點中 int j = 0; for (int i = ORDER_V + 1; i <= MAXNUM_DATA; i++) { j++; pNode->SetElement(j, this->GetElement(i)); this->SetElement(i, INVALID); } // 設置好Count個數 this->SetCount(this->GetCount() - j); pNode->SetCount(pNode->GetCount() + j); // 返回新結點的第一個元素作為鍵 return pNode->GetElement(1); } // 結合結點,把指定葉子結點的數據全部剪切到本葉子結點 bool CLeafNode::Combine(CNode* pNode) { // 參數檢查 if (this->GetCount() + pNode->GetCount() > MAXNUM_DATA) { return false; } for (int i = 1; i <= pNode->GetCount(); i++) { this->Insert(pNode->GetElement(i)); } return true; } BPlusTree::BPlusTree() { m_Depth = 0; m_Root = NULL; m_pLeafHead = NULL; m_pLeafTail = NULL; } BPlusTree::~BPlusTree() { ClearTree(); } // 在樹中查找數據 bool BPlusTree::Search(KEY_TYPE data, char* sPath) { int i = 0; int offset = 0; if (NULL != sPath) { (void)sprintf(sPath+offset, "The serach path is:"); offset+=19; } CNode * pNode = GetRoot(); // 循環查找對應的葉子結點 while (NULL != pNode) { // 結點為葉子結點,循環結束 if (NODE_TYPE_LEAF == pNode->GetType()) { break; } // 找到第一個鍵值大於等於key的位置 for (i = 1; (data >= pNode->GetElement(i) )&& (i <= pNode->GetCount()); i++) { } if (NULL != sPath) { (void)sprintf(sPath+offset, " %3d -->", pNode->GetElement(1)); offset+=8; } pNode = pNode->GetPointer(i); } // 沒找到 if (NULL == pNode) { return false; } if (NULL != sPath) { (void)sprintf(sPath+offset, "%3d", pNode->GetElement(1)); offset+=3; } // 在葉子結點中繼續找 bool found = false; for (i = 1; (i <= pNode->GetCount()); i++) { if (data == pNode->GetElement(i)) { found = true; } } if (NULL != sPath) { if (true == found) { (void)sprintf(sPath+offset, " ,successed."); } else { (void)sprintf(sPath+offset, " ,failed."); } } return found; } /* 在B+樹中插入數據 插入數據首先要找到理論上要插入的葉子結點,然后分三種情況: (1) 葉子結點未滿。直接在該結點中插入即可; (2) 葉子結點已滿,且無父結點(即根結點是葉子結點),需要首先把葉子結點分裂,然后選擇插入原結點或新結點,然后新生成根節點; (3) 葉子結點已滿,但其父結點未滿。需要首先把葉子結點分裂,然后選擇插入原結點或新結點,再修改父結點的指針; (4) 葉子結點已滿,且其父結點已滿。需要首先把葉子結點分裂,然后選擇插入原結點或新結點,接着把父結點分裂,再修改祖父結點的指針。 因為祖父結點也可能滿,所以可能需要一直遞歸到未滿的祖先結點為止。 */ bool BPlusTree::Insert(KEY_TYPE data) // { // 檢查是否重復插入 bool found = Search(data, NULL); if (true == found) { return false; } // for debug //if (289 == data) //{ // printf("\n%d,check failed!",data); //} // 查找理想的葉子結點 CLeafNode* pOldNode = SearchLeafNode(data); // 如果沒有找到,說明整個樹是空的,生成根結點 if (NULL == pOldNode) { pOldNode = new CLeafNode; m_pLeafHead = pOldNode; m_pLeafTail = pOldNode; SetRoot(pOldNode); } // 葉子結點未滿,對應情況1,直接插入 if (pOldNode->GetCount() < MAXNUM_DATA) { return pOldNode->Insert(data); } // 原葉子結點已滿,新建葉子結點,並把原結點后一半數據剪切到新結點 CLeafNode* pNewNode = new CLeafNode; KEY_TYPE key = INVALID; key = pOldNode->Split(pNewNode); // 在雙向鏈表中插入結點 CLeafNode* pOldNext = pOldNode->m_pNextNode; pOldNode->m_pNextNode = pNewNode; pNewNode->m_pNextNode = pOldNext; pNewNode->m_pPrevNode = pOldNode; if (NULL == pOldNext) { m_pLeafTail = pNewNode; } else { pOldNext->m_pPrevNode = pNewNode; } // 判斷是插入到原結點還是新結點中,確保是按數據值排序的 if (data < key) { pOldNode->Insert(data); // 插入原結點 } else { pNewNode->Insert(data); // 插入新結點 } // 父結點 CInternalNode* pFather = (CInternalNode*)(pOldNode->GetFather()); // 如果原結點是根節點,對應情況2 if (NULL == pFather) { CNode* pNode1 = new CInternalNode; pNode1->SetPointer(1, pOldNode); // 指針1指向原結點 pNode1->SetElement(1, key); // 設置鍵 pNode1->SetPointer(2, pNewNode); // 指針2指向新結點 pOldNode->SetFather(pNode1); // 指定父結點 pNewNode->SetFather(pNode1); // 指定父結點 pNode1->SetCount(1); SetRoot(pNode1); // 指定新的根結點 return true; } // 情況3和情況4在這里實現 bool ret = InsertInternalNode(pFather, key, pNewNode); return ret; } /* 刪除某數據 刪除數據的算法如下: (1) 如果刪除后葉子結點填充度仍>=50%,只需要修改葉子結點,如果刪除的是父結點的鍵,父結點也要相應修改; (2) 如果刪除后葉子結點填充度<50%,需要先找到一個最近的兄弟結點(左右均可),然后分兩種情況: A. 如果該兄弟結點填充度>50%,把該兄弟結點的最近一個數據剪切到本結點,父結點的鍵值也要相應修改。 B. 如果該兄弟結點的填充度=50%,則把兩個結點合並,父結點鍵也相應合並。(如果合並后父結點的填充度<50%,則需要遞歸) */ bool BPlusTree::Delete(KEY_TYPE data) { // 查找理想的葉子結點 CLeafNode* pOldNode = SearchLeafNode(data); // 如果沒有找到,返回失敗 if (NULL == pOldNode) { return false; } // 刪除數據,如果失敗一定是沒有找到,直接返回失敗 bool success = pOldNode->Delete(data); if (false == success) { return false; } // 獲取父結點 CInternalNode* pFather = (CInternalNode*)(pOldNode->GetFather()); if (NULL == pFather) { // 如果一個數據都沒有了,刪除根結點(只有根節點可能出現此情況) if (0 == pOldNode->GetCount()) { delete pOldNode; m_pLeafHead = NULL; m_pLeafTail = NULL; SetRoot(NULL); } return true; } // 刪除后葉子結點填充度仍>=50%,對應情況1 if (pOldNode->GetCount() >= ORDER_V) { for (int i = 1; (data >= pFather->GetElement(i)) && (i <= pFather->GetCount()); i++) { // 如果刪除的是父結點的鍵值,需要更改該鍵 if (pFather->GetElement(i) == data) { pFather->SetElement(i, pOldNode->GetElement(1)); // 更改為葉子結點新的第一個元素 } } return true; } // 找到一個最近的兄弟結點(根據B+樹的定義,除了葉子結點,總是能找到的) int flag = FLAG_LEFT; CLeafNode* pBrother = (CLeafNode*)(pOldNode->GetBrother(flag)); // 兄弟結點填充度>50%,對應情況2A KEY_TYPE NewData = INVALID; if (pBrother->GetCount() > ORDER_V) { if (FLAG_LEFT == flag) // 兄弟在左邊,移最后一個數據過來 { NewData = pBrother->GetElement(pBrother->GetCount()); } else // 兄弟在右邊,移第一個數據過來 { NewData = pBrother->GetElement(1); } pOldNode->Insert(NewData); pBrother->Delete(NewData); // 修改父結點的鍵值 if (FLAG_LEFT == flag) { for (int i = 1; i <= pFather->GetCount() + 1; i++) { if (pFather->GetPointer(i) == pOldNode && i > 1) { pFather->SetElement(i - 1 , pOldNode->GetElement(1)); // 更改本結點對應的鍵 } } } else { for (int i = 1; i <= pFather->GetCount() + 1; i++) { if (pFather->GetPointer(i) == pOldNode && i > 1) { pFather->SetElement(i - 1, pOldNode->GetElement(1)); // 更改本結點對應的鍵 } if (pFather->GetPointer(i) == pBrother && i > 1) { pFather->SetElement(i - 1 , pBrother->GetElement(1)); // 更改兄弟結點對應的鍵 } } } return true; } // 情況2B // 父結點中要刪除的鍵 KEY_TYPE NewKey = NULL; // 把本結點與兄弟結點合並,無論如何合並到數據較小的結點,這樣父結點就無需修改指針 if (FLAG_LEFT == flag) { (void)pBrother->Combine(pOldNode); NewKey = pOldNode->GetElement(1); CLeafNode* pOldNext = pOldNode->m_pNextNode; pBrother->m_pNextNode = pOldNext; // 在雙向鏈表中刪除結點 if (NULL == pOldNext) { m_pLeafTail = pBrother; } else { pOldNext->m_pPrevNode = pBrother; } // 刪除本結點 delete pOldNode; } else { (void)pOldNode->Combine(pBrother); NewKey = pBrother->GetElement(1); CLeafNode* pOldNext = pBrother->m_pNextNode; pOldNode->m_pNextNode = pOldNext; // 在雙向鏈表中刪除結點 if (NULL == pOldNext) { m_pLeafTail = pOldNode; } else { pOldNext->m_pPrevNode = pOldNode; } // 刪除本結點 delete pBrother; } return DeleteInternalNode(pFather, NewKey); } // 清除整個樹,刪除所有結點 void BPlusTree::ClearTree() { CNode* pNode = GetRoot(); if (NULL != pNode) { pNode->DeleteChildren(); delete pNode; } m_pLeafHead = NULL; m_pLeafTail = NULL; SetRoot(NULL); } // 旋轉以重新平衡,實際上是把整個樹重構一下,結果不理想,待重新考慮 BPlusTree* BPlusTree::RotateTree() { BPlusTree* pNewTree = new BPlusTree; int i = 0; CLeafNode * pNode = m_pLeafHead; while (NULL != pNode) { for (int i = 1; i <= pNode->GetCount(); i ++) { (void)pNewTree->Insert(pNode->GetElement(i)); } pNode = pNode->m_pNextNode; } return pNewTree; } // 檢查樹是否滿足B+樹的定義 bool BPlusTree::CheckTree() { CLeafNode * pThisNode = m_pLeafHead; CLeafNode * pNextNode = NULL; while (NULL != pThisNode) { pNextNode = pThisNode->m_pNextNode; if (NULL != pNextNode) { if (pThisNode->GetElement(pThisNode->GetCount()) > pNextNode->GetElement(1)) { return false; } } pThisNode = pNextNode; } return CheckNode(GetRoot()); } // 遞歸檢查結點及其子樹是否滿足B+樹的定義 bool BPlusTree::CheckNode(CNode* pNode) { if (NULL == pNode) { return true; } int i = 0; bool ret = false; // 檢查是否滿足50%的填充度 if ((pNode->GetCount() < ORDER_V) && (pNode != GetRoot())) { return false; } // 檢查鍵或數據是否按大小排序 for (i = 1; i < pNode->GetCount(); i++) { if (pNode->GetElement(i) > pNode->GetElement(i + 1)) { return false; } } if (NODE_TYPE_LEAF == pNode->GetType()) { return true; } // 對中間結點,遞歸檢查子樹 for (i = 1; i <= pNode->GetCount() + 1; i++) { ret = CheckNode(pNode->GetPointer(i)); // 只要有一個不合法就返回不合法 if (false == ret) { return false; } } return true; } // 打印整個樹 void BPlusTree::PrintTree() { CNode* pRoot = GetRoot(); if (NULL == pRoot) return; CNode* p1, *p2, *p3; int i, j, k; int total = 0; printf("\n第一層\n | "); PrintNode(pRoot); total = 0; printf("\n第二層\n | "); for (i = 1; i <= MAXNUM_POINTER; i++) { p1 = pRoot->GetPointer(i); if (NULL == p1) continue; PrintNode(p1); total++; if (total%4 == 0) printf("\n | "); } total = 0; printf("\n第三層\n | "); for (i = 1; i <= MAXNUM_POINTER; i++) { p1 = pRoot->GetPointer(i); if (NULL == p1) continue; for (j = 1; j <= MAXNUM_POINTER; j++) { p2 = p1->GetPointer(j); if (NULL == p2) continue; PrintNode(p2); total++; if (total%4 == 0) printf("\n | "); } } total = 0; printf("\n第四層\n | "); for (i = 1; i <= MAXNUM_POINTER; i++) { p1 = pRoot->GetPointer(i); if (NULL == p1) continue; for (j = 1; j <= MAXNUM_POINTER; j++) { p2 = p1->GetPointer(j); if (NULL == p2) continue; for (k = 1; k <= MAXNUM_POINTER; k++) { p3 = p2->GetPointer(k); if (NULL == p3) continue; PrintNode(p3); total++; if (total%4 == 0) printf("\n | "); } } } } // 打印某結點 void BPlusTree::PrintNode(CNode* pNode) { if (NULL == pNode) { return; } for (int i = 1; i <= MAXNUM_KEY; i++) { printf("%3d ", pNode->GetElement(i)); if (i >= MAXNUM_KEY) { printf(" | "); } } } // 查找對應的葉子結點 CLeafNode* BPlusTree::SearchLeafNode(KEY_TYPE data) { int i = 0; CNode * pNode = GetRoot(); // 循環查找對應的葉子結點 while (NULL != pNode) { // 結點為葉子結點,循環結束 if (NODE_TYPE_LEAF == pNode->GetType()) { break; } // 找到第一個鍵值大於等於key的位置 for (i = 1; i <= pNode->GetCount(); i++) { if (data < pNode->GetElement(i)) { break; } } pNode = pNode->GetPointer(i); } return (CLeafNode*)pNode; } //遞歸函數:插入鍵到中間結點 bool BPlusTree::InsertInternalNode(CInternalNode* pNode, KEY_TYPE key, CNode* pRightSon) { if (NULL == pNode || NODE_TYPE_LEAF ==pNode->GetType()) { return false; } // 結點未滿,直接插入 if (pNode->GetCount() < MAXNUM_KEY) { return pNode->Insert(key, pRightSon); } CInternalNode* pBrother = new CInternalNode; //C++中new 類名表示分配一個類需要的內存空間,並返回其首地址; KEY_TYPE NewKey = INVALID; // 分裂本結點 NewKey = pNode->Split(pBrother, key); if (pNode->GetCount() < pBrother->GetCount()) { pNode->Insert(key, pRightSon); } else if (pNode->GetCount() > pBrother->GetCount()) { pBrother->Insert(key, pRightSon); } else // 兩者相等,即鍵值在第V和V+1個鍵值中間的情況,把字節點掛到新結點的第一個指針上 { pBrother->SetPointer(1,pRightSon); pRightSon->SetFather(pBrother); } CInternalNode* pFather = (CInternalNode*)(pNode->GetFather()); // 直到根結點都滿了,新生成根結點 if (NULL == pFather) { pFather = new CInternalNode; pFather->SetPointer(1, pNode); // 指針1指向原結點 pFather->SetElement(1, NewKey); // 設置鍵 pFather->SetPointer(2, pBrother); // 指針2指向新結點 pNode->SetFather(pFather); // 指定父結點 pBrother->SetFather(pFather); // 指定父結點 pFather->SetCount(1); SetRoot(pFather); // 指定新的根結點 return true; } // 遞歸 return InsertInternalNode(pFather, NewKey, pBrother); } // 遞歸函數:在中間結點中刪除鍵 bool BPlusTree::DeleteInternalNode(CInternalNode* pNode, KEY_TYPE key) { // 刪除鍵,如果失敗一定是沒有找到,直接返回失敗 bool success = pNode->Delete(key); if (false == success) { return false; } // 獲取父結點 CInternalNode* pFather = (CInternalNode*)(pNode->GetFather()); if (NULL == pFather) { // 如果一個數據都沒有了,把根結點的第一個結點作為根結點 if (0 == pNode->GetCount()) { SetRoot(pNode->GetPointer(1)); delete pNode; } return true; } // 刪除后結點填充度仍>=50% if (pNode->GetCount() >= ORDER_V) { for (int i = 1; (key >= pFather->GetElement(i)) && (i <= pFather->GetCount()); i++) { // 如果刪除的是父結點的鍵值,需要更改該鍵 if (pFather->GetElement(i) == key) { pFather->SetElement(i, pNode->GetElement(1)); // 更改為葉子結點新的第一個元素 } } return true; } //找到一個最近的兄弟結點(根據B+樹的定義,除了根結點,總是能找到的) int flag = FLAG_LEFT; CInternalNode* pBrother = (CInternalNode*)(pNode->GetBrother(flag)); // 兄弟結點填充度>50% KEY_TYPE NewData = INVALID; if (pBrother->GetCount() > ORDER_V) { pNode->MoveOneElement(pBrother); // 修改父結點的鍵值 if (FLAG_LEFT == flag) { for (int i = 1; i <= pFather->GetCount() + 1; i++) { if (pFather->GetPointer(i) == pNode && i > 1) { pFather->SetElement(i - 1 , pNode->GetElement(1)); // 更改本結點對應的鍵 } } } else { for (int i = 1; i <= pFather->GetCount() + 1; i++) { if (pFather->GetPointer(i) == pNode && i > 1) { pFather->SetElement(i - 1, pNode->GetElement(1)); // 更改本結點對應的鍵 } if (pFather->GetPointer(i) == pBrother && i > 1) { pFather->SetElement(i - 1 , pBrother->GetElement(1)); // 更改兄弟結點對應的鍵 } } } return true; } // 父結點中要刪除的鍵:兄弟結點都不大於50,則需要合並結點,此時父結點需要刪除鍵 KEY_TYPE NewKey = NULL; // 把本結點與兄弟結點合並,無論如何合並到數據較小的結點,這樣父結點就無需修改指針 if (FLAG_LEFT == flag) { (void)pBrother->Combine(pNode); NewKey = pNode->GetElement(1); delete pNode; } else { (void)pNode->Combine(pBrother); NewKey = pBrother->GetElement(1); delete pBrother; } // 遞歸 return DeleteInternalNode(pFather, NewKey); }
---------------------------------------
-Demo.cpp
#include <stdlib.h> #include <stdio.h> #include <time.h> #include "BPlusTree.h" // 隨機建立一棵樹 void Test1(BPlusTree* pTree, int count) { pTree->ClearTree(); //srand( (unsigned)time( NULL ) );//這是一個種子,如果不要隨機功能,請把此句話注釋掉 for (int i = 0; i < count; i++) { int x = rand()%999 + 1; (void)pTree->Insert(x); } printf("successed!\n"); } // 在樹中查找某數據 void Test2(BPlusTree* pTree, int data) { char sPath[255] = {0, }; (void)pTree->Search(data, sPath); printf("\n%s", sPath); } // 在樹中插入某數據 void Test3(BPlusTree* pTree, int data) { bool success = pTree->Insert(data); if (true == success) { printf("\nsuccessed!\n"); } else { printf("\nfailed!\n"); } } // 在樹中刪除某數據 void Test4(BPlusTree* pTree, int data) { bool success = pTree->Delete(data); if (true == success) { printf("\nsuccessed!\n"); } else { printf("\nfailed!\n"); } } // 對樹進行旋轉 BPlusTree* Test5(BPlusTree* pTree) { BPlusTree* pNewTree = pTree->RotateTree(); delete pTree; printf("\nsuccessed!\n"); return pNewTree; } // 打印 void Test6(BPlusTree* pTree) { pTree->PrintTree(); } // 對樹進行檢查 void Test7(BPlusTree* pTree) { bool success = pTree->CheckTree(); if (true == success) { printf("\nsuccessed!\n"); } else { printf("\nfailed!\n"); } } int main(int argc, char* argv[]) { BPlusTree* pTree = new BPlusTree; int x = 1; int y = 0; while (0 != x) { printf("\n\n"); printf(" *******************************************************************\n"); printf(" * 歡迎進入B+樹演示程序,請選擇相應功能。 *\n"); printf(" * 1。隨機建立一棵B+樹; *\n"); printf(" * 2。在B+樹中查找一個數; *\n"); printf(" * 3。在B+樹中插入一個數; *\n"); printf(" * 4。在B+樹中刪除一個數; *\n"); printf(" * 5。對B+樹旋轉,進行重新平衡; *\n"); printf(" * 6。顯示整個B+樹; *\n"); printf(" * 7。檢查整個B+樹; *\n"); printf(" * 0。退出程序; *\n"); printf(" *******************************************************************\n"); printf("\n 您的選擇是:"); scanf("%d", &x); switch (x) { case 1: printf("請輸入數據個數(10-150):"); scanf("%d", &y); Test1(pTree, y); break; case 2: printf("請輸入要查找的數值:"); scanf("%d", &y); Test2(pTree, y); break; case 3: printf("請輸入要插入的數值:"); scanf("%d", &y); Test3(pTree, y); break; case 4: printf("請輸入要刪除的數值:"); scanf("%d", &y); Test4(pTree, y); break; case 5: pTree = Test5(pTree); break; case 6: Test6(pTree); break; case 7: Test7(pTree); break; case 0: delete pTree; return 0; break; default: break; } } delete pTree; return 0; }