1. 程式人生 > >B-樹插入、刪除

B-樹插入、刪除

關於B-樹基礎知識可以參看:

B-樹的資料結構如下:

class TreeNode
{
	private:
		int keynum;
		TreeNode* parent;
		TreeNode* ptr[m+1];//0...m use to store pointer
		int key[m+1];//1...m use to store k
	public:
		TreeNode()
		{
			keynum = 0;
		}
		TreeNode(TreeNode* p1, int k, TreeNode *p2)//root node
		{
			keynum = 1;
			key[1] = k;
			parent = 0;
			fill(ptr, ptr+m+1, (TreeNode*)0);
			ptr[0] = p1;
			ptr[1] = p2;

			if(p1)
				p1->parent = this;
			if(p2)
				p2->parent = this;
		}
		TreeNode(TreeNode* p, int *keyarry, TreeNode **ptrarry)
		{
			parent = p;
			fill(ptr, ptr+m+1, (TreeNode*)0);
			int temp = ceil(m/2.0);//rounding up
			ptr[0] = ptrarry[temp];
			if(ptr[0])
				ptr[0]->parent = this;
			for(int i = 1; i+temp <= m; ++i)
			{
				key[i] = keyarry[i+temp];
				ptr[i] = ptrarry[i+temp];
				if(ptr[i])
					ptr[i]->parent = this;
				keyarry[i+temp] = 0;
				ptrarry[i+temp] = 0;
			}
			keyarry[temp] = 0;
			ptrarry[temp] = 0;
			keynum = m-temp;
		}
		friend class Result;
		friend class Btree;
};
class Result
{
	public:
		TreeNode* Resultptr;
		int index;
		bool Resultflag;
		Result(TreeNode* p = 0, int i = 0, bool r=0)
			:Resultptr(p), index(i), Resultflag(r)
		{}
		friend class Btree;
};
class Btree
{
	public:
		Btree()
		{
			root = 0;
		}
		void InsertBtree(const int k);
		Result Find(const int k)const;
		void DeleteBtree(const int k);
		void Insert(const int k, TreeNode* node, TreeNode* a);
		void Display()const;
		void Search(const int k)const;
	private:
		TreeNode* root;
		/*for delete*/
		void BorrowOrCombine(TreeNode* a, const int i, const int type, stack<int>& s);
};

          其中TreeNode是B-樹的結點,TreeNode在private裡包含了結點所有的資訊,同時包含了三個建構函式。其中第一個不帶引數的建構函式,只是為了初始化空節點;第二個建構函式為了建立B-樹的根節點;第三個建構函式,包含三個引數,目的是為了在插入的時候分裂a結點,其中p是a結點的parent指標,keyarry、ptrarry分別為結點a的key陣列以及結點a的ptr指標陣列,a分裂後產生的新的結點的父節點為p。

         Result類主要用於封裝查詢關鍵字的返回結果,其中Resultptr查詢結果的B-樹結點,如果包含key則返回該結點,如果不包含key則返回待插入值key的葉子節點,index為key在該節點中的位置或key將要插入的結點的位置,Resultflag表示是否包含key值。

         Btree類主要用來封裝root結點和與B-樹相關的函式。

         B-樹的成員函式實現結果如下:

#include"btree.h"
#include<iostream>
#include<cmath>
#include<stack>
#include<queue>
using namespace std;

void Btree::InsertBtree(const int k)
{
	if(!root)
	{
		root = new TreeNode(0,k,0);
		return;
	}
	Result res = Find(k);
	TreeNode* a = res.Resultptr;
	if(res.Resultflag == true)
		return;
	Insert(k, 0, a);
}
void Btree::Search(const int k)const
{
	Result res = Find(k);
	if(res.Resultflag == true)
		cout<<"find it"<<endl;
	else 
		cout<<"not find"<<endl;
}
void Btree::Insert(const int k, TreeNode* node, TreeNode* a)
{
	int i = 1;//index to insert
	while(i <= a->keynum)
	{
		if(k <= a->key[i])
		{
			break;
		}
		i++;
	}
	for(int j = a->keynum; j >= i; --j)
	{
		a->key[j+1] = a->key[j];
		a->ptr[j+1] = a->ptr[j];
	}
	a->key[i] = k;
	a->ptr[i] = node;
	if(node)
		node->parent = a;
	++a->keynum;
	if(a->keynum  <= m-1 )
	{
		return;
	}
	else
	{
		/*slipt*/
		int midkey = a->key[(int)ceil(m/2.0)];
		TreeNode* newnode = new TreeNode(a->parent, a->key, a->ptr);
		a->keynum = m - ceil(m/2.0);
		TreeNode* tempa = a;//restore a
		a = a->parent;
		if(!a)
		{
			TreeNode* newRoot = new TreeNode(tempa,midkey,newnode);
			tempa->parent = newRoot;
			newnode->parent = newRoot;
			root = newRoot;
			return;
		}
		else
		{
			Insert(midkey,newnode,a);
		}
	}
}
Result Btree::Find(const int k)const
{
	if(!root)
	{
		cout<<"the tree is empty"<<endl;
		return Result(0,0,0);
	}
	TreeNode* a = root;
	int i = 1;
	while(a)
	{
		i = 1;
		while(i <= a->keynum)
		{
			if(a->key[i] >= k)
			{
				break;
			}
			else
				++i;
		}
		if(k == a->key[i])
		{
			return Result(a, i, 1);
		}
		else
		{
			if(a->ptr[i-1])
			{
				a = a->ptr[i-1];
			}
			else
			{
				return Result(a, i, 0);
			}
		}
	}
}
void Btree::DeleteBtree(const int k)
{
	if(!root)
	{
		cout<<"The tree is null!"<<endl;
		return;
	}

	stack<int> s;//store the all index
	TreeNode* delnode = root;
	int i = 1;
	
	while(delnode)
	{
		i = 1;
		while(i <= delnode->keynum )
		{
			if(k <= delnode->key[i])
			{
				break;
			}
			else
			{
				i++;
			}
		}
		if(k == delnode->key[i])
		{
			cout<<"find it"<<endl;
			break;
		}
		else
		{
			if(delnode->ptr[i-1] == 0)
			{
				cout<<"no this key"<<endl;
				return;
			}
			else
			{
				delnode = delnode->ptr[i-1];
					s.push(i-1);
			}
		}
	}
	TreeNode* p = delnode; //store delnode
	if(delnode->ptr[i])
	{
		s.push(i);
		p = delnode->ptr[i];
		while(p->ptr[0])
		{
			p = p->ptr[0];
			if(!p->ptr[0])
				break;
			s.push(0);
		}
	}
	if(p != delnode)
	{
		delnode->key[i] = p->key[1];
		i = 1;
	}

	BorrowOrCombine(p, i, 0, s);
}
void Btree::BorrowOrCombine(TreeNode* a,const int i,const int type, stack<int>& s)
{
	if(a == root && root->keynum == 1)
	{
		TreeNode* oldroot = root;	
		if(type == -1)
		{
			if(root->ptr[i-1])
				root = root->ptr[i-1];
			else 
				root = 0;
		}
		else if(type == 1)
		{
			if(root->ptr[i])
			{
				root = root->ptr[i];
			}
			else
				root = 0;
		}
		else
		{
			root = 0;
		}
		if(root)
			root->parent = 0;
		delete oldroot;
		return;
	}
	int minnum = ceil(m/2.0)-1;
	TreeNode *la,*ra;
	int j;
	if(a->keynum > minnum|| a == root)
	{
		TreeNode* tempstr;
		if(type == -1)
		{
			tempstr = a->ptr[i-1];
		}
		else
		{
			tempstr = a->ptr[i];
		}
		for(j = i; j < a->keynum; ++j)
		{
			a->key[j] = a->key[j+1]; 
			a->ptr[j] = a->ptr[j+1];
		}
		a->ptr[i-1] = tempstr;
		a->key[j] = 0;
		a->ptr[j] = 0;
		--a->keynum;
	}
	else
	{
		int index = s.top();
		s.pop();
		if(index)//have left brother
		{
			la = a->parent->ptr[index - 1];//left brother
			if(la->keynum > minnum)//left brother has enough elements
			{
				TreeNode* tempstr;
				if(type == -1)
				{
					tempstr = a->ptr[i-1];
				}
				else
				{
					tempstr = a->ptr[i];
				}
				for(j = i; j > 1; --j)
				{
					a->key[j] = a->key[j-1];
					a->ptr[j] = a->ptr[j-1];	
				}
				a->ptr[i] = tempstr;

				a->key[1] = a->parent->key[index];
				a->ptr[0] = la->ptr[la->keynum];
				if(la->ptr[la->keynum])
					la->ptr[la->keynum]->parent = a;
				a->parent->key[index] = la->key[la->keynum];
				la->ptr[la->keynum] = 0;
				la->key[la->keynum] = 0;
				--la->keynum;
				return;
			}
		}
		else if(index < a->parent->keynum)//have right brother
		{
			ra = a->parent->ptr[index+1];
			if(ra->keynum > minnum)// have lots of elements
			{
				TreeNode* tempstr;
				if(type == -1)
				{
					tempstr = a->ptr[i-1];
				}
				else
				{
					tempstr = a->ptr[i];
				}
				for(j = i; j < a->keynum; j++)
				{
					a->key[j] = a->key[j+1];
					a->ptr[j] = a->ptr[j+1]; 
				}
				a->ptr[i-1] = tempstr;

				a->key[a->keynum] = a->parent->key[index+1];
				a->ptr[a->keynum] = ra->ptr[0];
				if(ra->ptr[0])
					ra->ptr[0]->parent = a;
				a->parent->key[index+1] = ra->key[1];
				ra->ptr[0] = ra->ptr[1];
				for(j = 1; j < ra->keynum; j++)
				{
					ra->key[j] = ra->key[j+1];
					ra->ptr[j] = ra->ptr[j+1];
				}
				ra->key[ra->keynum] = 0;
				ra->ptr[ra->keynum] = 0;
				--ra->keynum;
				return;
			}
		}
		if(index)//have left brother donnot hava enough elements
		{
			cout<<la->key[1];
			la = a->parent->ptr[index-1];
			cout<<a->parent->key[index]<<a->key[i]<<endl;
			if(la->keynum == minnum)//donnot have enough elements
			{
				/*combine left brother*/
				la->key[la->keynum+1] = a->parent->key[index];
				for(j =1; j <= i-1; ++j)
				{
					la->ptr[la->keynum+j] = a->ptr[j-1];
					la->key[la->keynum+j+1] = a->key[j];
					if(a->ptr[j-1])
						a->ptr[j-1]->parent = la;
				}
				if(type == -1)
				{
					la->ptr[la->keynum+i] = a->ptr[i-1];
					if(a->ptr[i-1])
						a->ptr[i-1]->parent = la;
				}
				else
				{
					la->ptr[la->keynum+i] = a->ptr[i];
					if(a->ptr[i])
					{
						a->ptr[i]->parent = la;
					}
				}
				for(j = i+1; j <= a->keynum; ++j)
				{
					la->key[la->keynum+j] = a->key[j];
					la->ptr[la->keynum+j] = a->ptr[j];
					if(a->ptr[j])
						a->ptr[j]->parent = la;
				}
				la->keynum = m-1;

				TreeNode* tempp = a->parent;
				tempp->ptr[index] = 0;
				delete a;
				BorrowOrCombine(tempp,index,-1,s);
				return;
			}
		}
		if(index < a->keynum)//have right brother
		{
			ra = a->parent->ptr[index+1];
			cout<<"have right brother"<<endl;
			if( ra->keynum == minnum)// right brother do not have enough elements
			{
				int step = m - 1 - ra->keynum;
				for(j = ra->keynum; j >0; --j)
				{
					ra->key[j + step] = ra->key[j];
					ra->ptr[j + step] = ra->ptr[j];
				}
				ra->key[step] = a->parent->key[index+1];
				ra->ptr[step] = ra->ptr[j];
				--step;
				for(j = a->keynum; j > i; --j,--step)
				{
					ra->ptr[step] = a->ptr[j];
					if(a->ptr[j])
					{	
						a->ptr[j]->parent = ra;
					}
					ra->key[step] = a->key[j];
				}
				if(type == -1)
				{
					ra->ptr[step] = a->ptr[j - 1];
					if(a->ptr[j - 1])
					{
						a->ptr[j - 1]->parent = ra;
					}
				}
				else
				{
					ra->ptr[step] = a->ptr[j];
					if(a->ptr[j])
					{
						a->ptr[j]->parent = ra;
					}
				}
				for(int x = i-1, j = i-1; x > 0; --x, --j)
				{
					ra->key[x] = a->key[j];
					ra->ptr[x-1] = a->ptr[j-1];
					if(ra->ptr[x-1])
						ra->ptr[x-1]->parent = ra;
				}
				ra->keynum = m-1;
				TreeNode* ap = a->parent;
				ap->ptr[index] = 0;
				delete a;
				BorrowOrCombine(ap, index+1, 1, s);
			}
		}
	}
}

void Btree::Display()const
{
	if(!root)
	{
		cout<<"Btree is empty!"<<endl;
	}
	else
	{
		TreeNode* p = root;
		queue<TreeNode*> qu;
		while(p)
		{
			qu.push(p->ptr[0]);
			for(int j = 1; j<= p->keynum; j++)
			{
				if(p->ptr[j])
					qu.push(p->ptr[j]);
				cout<<p->key[j]<<"-";
			}
			cout<<"his parent"<<endl;
			TreeNode* q = p->parent;
			if(q)	
			{
				for(int j = 1; j<= q->keynum; j++)
				{
				//qu.push(q->ptr[j]);
					cout<<q->key[j]<<"-";
				}
			}
			cout<<"   ";
			p = qu.front();
			qu.pop();
			cout<<endl;
		}
	}
}

        其中,插入和刪除操作是整個實現程式碼的核心。

        插入操作的思想是,先插入後分裂。即不管待插入結點的元素數量,先將元素插入到指定位置,然後再判斷元素數量是否超過B-樹的最大值,如果超越,通過TreeNode的第三個建構函式對結點進行分裂,分裂後將結點的中心值midkey插入到父節點的相應位置,再次判斷,直到滿足條件結束。程式碼通過不斷遞迴實現。

        刪除操作是B-樹的難點和核心。刪除一個key值首先判斷key值在不在葉子節點,如果不在通過下面的方法將問題轉化為刪除葉子節點的一個key值。

        非葉子節點key值刪除轉換方法,首先將key中序後繼(前驅,本程式碼選擇後續)的key值代替待刪除的key,也就是說使用當前節點key值的右子樹的最左節點的第一個key值替換待刪除的key,這樣將問題轉化為刪除葉子節點的key值。

        刪除葉子節點key值分為四種情況:

       1、當前葉子節點的keynum>minnum時,只需將待刪除key值後面的可以key值以及ptr值順序向前覆蓋,即可完成。

       2、當前葉子節點的keynum == minum,葉子節點的左兄弟keynum > minnum,此時將葉子節點key值前面的key序列和ptr序列順序後移一位,騰出第一個key值的位置和ptr[0]的位置,然後將對應的父節點的值移入到key[1],左兄弟的最後一個節點對應的指標賦值給當前節點ptr[0],左兄弟最後一個key值賦值給對應的父節點的key。即可完成。

       3、當前葉子節點的keynum== minum,葉子節點的右兄弟keynum >minnum,參照情況二。

       4、當前葉子節點的keynum == minum,葉子節點左兄弟為keynum ==minnum,這時候就需要對結點進行合併,將左節點、父節點以及當前節點合併,合併的時候需要注意,只將父節點對應的值置為0,不改變父節點的結構,同時傳遞一個有效指標的位置引數。即當前節點與左兄弟合併時,傳遞-1,表示父節點值對應的左邊子樹指標為有效節點,同理與右兄弟合併時傳遞1。繼續以與左兄弟合併為例,左兄弟的內容不變,在後面加入父節點對應的值以及當前節點剩餘的值以及指標,指標的順序不改變。合併完成後,對父節點進行調整,我們知道父節點已經少了一個值,同時接收了標記引數type,接下來從1開始再次進行判斷,知道遞迴完成。

       5、當前葉子節點的keynum == minum,葉子節點右兄弟為keynum ==minnum,參照情況4將當前節點、父節點對應的key值以及右兄弟合併。同時一步步遞迴直到完成。

       演算法的一個比較重要的設計想法是,在進行合併操作時只進行本層節點的合併,對於父節點留作下一此迭代來完成,同時將有效指標的引數type傳遞給上層節點。