B树的实现

B树是什么,不用说了,相信大家都懂,我今天只想详细地说一下B树的插入和删除的详细算法,为了以后自己看起来方便,并且附上自己的代码,希望对各位也能够有所帮助。

B树的插入算法:

1)用查找的方法为x找到所在的位置,查找路径终于某个空树,然后把x插入到其父结点的有序位置上,注意B树所有的插入都是在叶结点上进行。

2)如果插入x后,结点不超长,则插入完毕;否则,进入下一步,进行超长处理。

3)将超长结点一分为二,将“中间元素”递归地插入上层结点中。

4)如插入波及根,当根上溢时,把根一分为2,并将中间元素上移,而产生含单元素的新根,使B树升高。

将超长结点一分为二的操作方法:

设结点d超长,即d的元素达M个,令k=M/2向上取整,元素k作为中间元素,其左侧的元素和相应的指针仍保存在结点d中,但是需要修改d的长度,将排在k元素右侧的元素和相应的指针移入新结点e中,将k连同指向结点e的指针一起插入到d的父结点f中,排在指向结点d的指针右侧。

注意:

为了避免回溯,我的代码从上到下在查找x位置的时候,只要遇见的结点是满的,就先分解,这样,当我们插入完成后,即使需要超长处理,也只会停止在其父亲结点,不会波及更上层的结点。

我的插入代码如下:

bool CBTree::InsertNode(int nKey)
{
	int i = 0;

	if (m_Root == NULL)
	{
		/*
		如果是空树,就生成一个新的节点,修改关键字个数,设置关键字,设为叶节点,然后,修改根指针。
		*/

		BNode * pNewNode = new BNode;
		if (!pNewNode)
			return false;
		pNewNode->m_Key[0] = 1;
		pNewNode->m_Key[1] = nKey;
		pNewNode->m_bLeaf = true;
		m_Root = pNewNode;
		return true;
	}

	//非空,查找叶节点

	BNode * pCurNode = m_Root;
	BNode * pNewNode = NULL;

	BNode * pParent = NULL;
	int nParentIndex = 0;

	BNode * pChild = NULL;
	int nChildIndex = 0;

	int nMidKey = 0;
	int k = 0;

	/*
	寻找到叶节点,因为B树是在叶节点上插入,在从根节点到叶节点的路径上,如果遇见满节点,就对其进行分裂,这样的好处,就是如果
	叶节点分裂,不需要回溯,直接在父亲节点上插入即可,因为,在到达叶节点之前,已经对所有的满节点进行了分裂。
	*/

	while (pCurNode->m_bLeaf == false)
	{
		/*
		遍历当前节点的关键字,如果有相等的,就直接返回,因为已经存在了,否则,找到子树,并保存在pChild和nChildIndex。
		*/

		for (i = 1; i <= pCurNode->m_Key[0]; i++)
		{
			if (nKey == pCurNode->m_Key[i])
			{
				//已经存在,插入返回
				return true;
			}
			if (nKey < pCurNode->m_Key[1])
			{
				pChild = pCurNode->m_ChildRoot[0];
				nChildIndex = 0;
				break;
			}
			if (nKey < pCurNode->m_Key[i])
			{
				pChild = pCurNode->m_ChildRoot[i-1];	
				nChildIndex = i - 1;
				break;
			}
		}
		
		if (i > pCurNode->m_Key[0])
		{
			pChild = pCurNode->m_ChildRoot[i - 1];
			nChildIndex = pCurNode->m_Key[0];
		}

		//判断节点是否满了,如果满了则分裂。

		if (pCurNode->m_Key[0] == B_TREE_ORDER - 1)
		{
			pNewNode = new BNode;

			if (!pNewNode)
				return false;

			pNewNode->m_bLeaf = false;

			nMidKey = (B_TREE_ORDER + 1) / 2; //向上取整加1

			//拷贝关键字

			for (i = nMidKey + 1; i <= pCurNode->m_Key[0]; i++)
			{
				pNewNode->m_Key[pNewNode->m_Key[0] + 1] = pCurNode->m_Key[i];
				pNewNode->m_Key[0]++;
			}

			//拷贝孩子
			for (i = nMidKey ; i <= pCurNode->m_Key[0] + 1; i++)
			{
				pNewNode->m_ChildRoot[i - nMidKey] = pCurNode->m_ChildRoot[i];
			}

			if (pParent == NULL)
			{
				//根节点
				BNode * pTmp = new BNode;
				if (!pTmp)
				{
					if (pNewNode)
						delete pNewNode;
					return false;
				}
				pTmp->m_bLeaf = false;
				pTmp->m_Key[0] = 1;
				pTmp->m_Key[1] = pCurNode->m_Key[nMidKey];
				pTmp->m_ChildRoot[0] = pCurNode;
				pTmp->m_ChildRoot[1] = pNewNode;
				m_Root = pTmp;

				pCurNode->m_Key[0] = nMidKey - 1;
			}
			else
			{
				//非根节点,移动关键字,移动孩子
				for (i = pParent->m_Key[0]; i > nParentIndex; i--)
				{
					pParent->m_Key[i + 1] = pParent->m_Key[i];
				}

				for (i = pParent->m_Key[0] + 1; i > nParentIndex; i--)
				{
					pParent->m_ChildRoot[i + 1] = pParent->m_ChildRoot[i];
				}

				pParent->m_Key[nParentIndex + 1] = pCurNode->m_Key[nMidKey];

				pParent->m_ChildRoot[nParentIndex + 1] = pNewNode;

				pParent->m_Key[0]++;

				pCurNode->m_Key[0] = nMidKey - 1;
			}

			if (nChildIndex >= nMidKey)
			{
				pParent = pNewNode;
				nParentIndex = nChildIndex - nMidKey;
			}
			else
			{
				pParent = pCurNode;
				nParentIndex = nChildIndex;
			}
		}
		else
		{
			pParent = pCurNode;
			nParentIndex = nChildIndex;
		}

		pCurNode = pChild;
	}

	//已经完成了子树查找

	//在叶子插入数据
		
	for (i = 1; i <= pCurNode->m_Key[0]; i++)
	{
		if (pCurNode->m_Key[i] == nKey)
			return true;

		if (nKey < pCurNode->m_Key[i])
		{
			break;
		}
	}

	nChildIndex = i;

	int m = 0;
	
	if (pCurNode->m_Key[0] < B_TREE_ORDER - 1)
	{
		//插入完毕
		for (m = pCurNode->m_Key[0]; m >= nChildIndex; m--)
		{
			pCurNode->m_Key[m + 1] = pCurNode->m_Key[m];
		}
		pCurNode->m_Key[nChildIndex] = nKey;
		pCurNode->m_Key[0]++;
	}
	else
	{
		//分解

		pNewNode = new BNode;

		nMidKey = (B_TREE_ORDER + 1) / 2;

		int nMidKeyVal = pCurNode->m_Key[nMidKey];

		//因为叶节点没有孩子,所以不需要拷贝孩子。

		for (m = nMidKey + 1; m <= pCurNode->m_Key[0]; m++)
		{
			pNewNode->m_Key[pNewNode->m_Key[0]+1] = pCurNode->m_Key[m];
			pNewNode->m_Key[0]++;
		}

		pNewNode->m_bLeaf = true;

		pCurNode->m_Key[0] = nMidKey - 1;

		//非根节点,移动关键字,移动孩子

		if (pParent == NULL)
		{
			//叶子节点就是根节点
			BNode * pTmp = new BNode;
			if (!pTmp)
			{
				if (pNewNode)
					delete pNewNode;
				return false;
			}
			pTmp->m_bLeaf = false;
			pTmp->m_Key[0] = 1;
			pTmp->m_Key[1] = nMidKeyVal;
			pTmp->m_ChildRoot[0] = pCurNode;
			pTmp->m_ChildRoot[1] = pNewNode;
			m_Root = pTmp;
		}
		else
		{
			//移动关键字
			for (i = pParent->m_Key[0]; i > nParentIndex; i--)
			{
				pParent->m_Key[i + 1] = pParent->m_Key[i];
			}

			//移动孩子
			for (i = pParent->m_Key[0]; i > nParentIndex; i--)
			{
				pParent->m_ChildRoot[i + 1] = pParent->m_ChildRoot[i];
			}

			pParent->m_Key[nParentIndex + 1] = pCurNode->m_Key[nMidKey];

			pParent->m_ChildRoot[nParentIndex + 1] = pNewNode;

			pParent->m_Key[0]++;
		}

		if (nKey > nMidKeyVal)
		{
			//在新节点插入
			for (i = 1; i <= pNewNode->m_Key[0]; i++)
			{
				if (nKey < pNewNode->m_Key[i])
				{
					for (k = pNewNode->m_Key[0]; k >= i; k--)
					{
						pNewNode->m_Key[k + 1] = pNewNode->m_Key[k];
					}

					pNewNode->m_Key[i] = nKey;

					pNewNode->m_Key[0]++;

					break;
				}
			}
			if (i > pNewNode->m_Key[0])
			{
				pNewNode->m_Key[++(pNewNode->m_Key[0])] = nKey;
			}
		}
		else
		{
			//在老节点插入

			for (i = 1; i <= pCurNode->m_Key[0]; i++)
			{
				if (nKey < pCurNode->m_Key[i])
				{
					for (k = pCurNode->m_Key[0]; k >= i; k--)
					{
						pCurNode->m_Key[k + 1] = pCurNode->m_Key[k];
					}

					pCurNode->m_Key[i] = nKey;

					pCurNode->m_Key[0]++;

					break;
				}
			}
			if (i > pCurNode->m_Key[0])
			{
				pCurNode->m_Key[++(pCurNode->m_Key[0])] = nKey;
			}
		}
	}
	
	return true;
}

在实现的过程中,发现了一个细节,就是2-3树不是B树,最小的B树是2-3-4树。

下面我们说一下,B树的删除算法,具体如下:

1)经查找,在某一结点c中找到x。

2)根据x所在的位置不同,做出不同的处理。

a)如果c是叶结点,直接删除x。

b) 如果c不是叶结点,查找ta的中序前驱,调换x和它的中序前驱,x的中序前驱一定在叶结点中。

将最终的叶结点记为d。

3)如果删除d后不下溢,删除结束;否则,进入下溢处理。

4)如果d是根,转到步骤8;否则,进入下一步。

5)找d的临近兄弟e,如果e的长度没有到达下限,那么借e的一个元素,注意相应的指针也要借过来,否则分支结点的借取就会出错。然后调整d和e之间的那个元素。

6)如果d的临近兄弟e都已经到达下限,那么合并d和e,注意合并的时候,d的父结点f中的相应关键字也会合并进来。

7)如果导致d的父结点f下溢,那么令d=f,然后转到步骤4。

8)根下溢,即根的长度变为0,删除根,整个B的高度下降。删除结束。

删除代码具体如下:

bool CBTree::RemoveNode(int nKey)
{
	//查找结点
	int i = 0;
	int k = 0;

	//下溢
	int nLowNum = (B_TREE_ORDER + 1) / 2 - 1;

	BNode * pTmp = m_Root;
	if (!pTmp)
		return false;

	BNode * pFind = NULL;
	int nFindIndex = 0;

	stack<BNode *> sPath;

	BNode * pLeft = NULL;

	BNode * pRight = NULL;

	while (pTmp)
	{
		for (i = 1; i <= pTmp->m_Key[0]; i++)
		{
			if (nKey == pTmp->m_Key[i])
			{
				pFind = pTmp;
				nFindIndex = i;
				break;
			}

			if (nKey < pTmp->m_Key[i])
			{
				break;
			}
		}

		if (pFind)
			break;

		//将父节点入栈
		sPath.push(pTmp);

		if (i > pTmp->m_Key[0])
		{
			pTmp = pTmp->m_ChildRoot[pTmp->m_Key[0]];
		}
		else
		{
			pTmp = pTmp->m_ChildRoot[i - 1];
		}
	}

	if (!pFind)
	{
		printf("not found %d\n", nKey);
		return false;
	}

	//printf("find %d\n", nKey);

	if (pFind->m_bLeaf == false)
	{
		//不是叶节点,调成叶节点
		//查找中序前驱
		BNode * pPrevRoot = NULL;

		for (i = 1; i <= pFind->m_Key[0]; i++)
		{
			if (pFind->m_Key[i] == nKey)
			{
				pPrevRoot = pFind->m_ChildRoot[i - 1];
				break;
			}
		}

		if (i > pFind->m_Key[0])
			return false;

		while (pPrevRoot->m_bLeaf == false)
		{
			sPath.push(pPrevRoot);
			pPrevRoot = pPrevRoot->m_ChildRoot[pPrevRoot->m_Key[0]];
		}

		int nTmp = pPrevRoot->m_Key[pPrevRoot->m_Key[0]];

		pPrevRoot->m_Key[pPrevRoot->m_Key[0]] = nKey;

		pFind->m_Key[i] = nTmp;

		pFind = pPrevRoot;
	}

	//叶节点删除,有可能本来就是叶节点,也有可能是调成的叶节点

	for (i = 1; i <= pFind->m_Key[0]; i++)
	{
		if (pFind->m_Key[i] == nKey)
		{
			for (k = i; k <= pFind->m_Key[0]; k++)
			{
				pFind->m_Key[i] = pFind->m_Key[i + 1];
			}

			pFind->m_Key[0]--;
		}
	}

	//判断是否下溢
	if (sPath.empty())
	{
		//根节点下溢,因为根节点也有可能是叶节点
		if (pFind->m_Key[0] <= 0)
		{
			m_Root = NULL;
			delete pFind;
		}

		return true;
	}
	else
	{
		//非根节点下溢
		if (nLowNum > pFind->m_Key[0])
		{
			//下溢
			while (sPath.empty() == false)
			{
				BNode * pParent = sPath.top();

				sPath.pop();

				if (pParent)
				{
					for (i = 0; i <= pParent->m_Key[0]; i++)
					{
						if (pParent->m_ChildRoot[i] == pFind)
						{
							if (i == 0)
							{
								pLeft = NULL;
								pRight = pParent->m_ChildRoot[1];
							}
							else if (i < pParent->m_Key[0])
							{
								pLeft = pParent->m_ChildRoot[i - 1];
								pRight = pParent->m_ChildRoot[i + 1];
							}
							else
							{
								pLeft = pParent->m_ChildRoot[i - 1];
								pRight = NULL;
							}

							break;
						}
					}

					if (i > pParent->m_Key[0])
					{
						printf("error:458");
						return false;
					}

					//左借
					if (pLeft && (pLeft->m_Key[0] > nLowNum))
					{
						int nTmp = pParent->m_Key[i];
						pParent->m_Key[i] = pLeft->m_Key[pLeft->m_Key[0]];
						for (k = pFind->m_Key[0]; k >= 1; k--)
						{
							pFind->m_Key[k + 1] = pFind->m_Key[k];
						}
						for (k = pFind->m_Key[0]; k >= 0; k--)
						{
							pFind->m_ChildRoot[k + 1] = pFind->m_ChildRoot[k];
						}
						pFind->m_Key[1] = nTmp;
						pFind->m_ChildRoot[0] = pLeft->m_ChildRoot[pLeft->m_Key[0]];
						pFind->m_Key[0]++;
						pLeft->m_Key[0]--;
						return true;
					}

					//右借
					if (pRight && (pRight->m_Key[0] > nLowNum))
					{
						int nTmp = pParent->m_Key[i+1];
						BNode * pRightFirstChilid = pRight->m_ChildRoot[0];

						pParent->m_Key[i+1] = pRight->m_Key[1];
						for (k = 2; k <= pRight->m_Key[0]; k++)
						{
							pRight->m_Key[k-1] = pRight->m_Key[k];
						}
						for (k = 1; k <= pRight->m_Key[0]; k++)
						{
							pRight->m_ChildRoot[k - 1] = pRight->m_ChildRoot[k];
						}
						pRight->m_Key[0]--;
						pFind->m_Key[pFind->m_Key[0] + 1] = nTmp;
						pFind->m_Key[0]++;
						pFind->m_ChildRoot[pFind->m_Key[0]] = pRightFirstChilid;
						return true;
					}

					//左合并

					if (pLeft && pLeft->m_Key[0] >= 1)
					{
						pLeft->m_Key[pLeft->m_Key[0] + 1] = pParent->m_Key[i];

						pLeft->m_Key[0]++;

						for (k = 0; k <= pFind->m_Key[0]; k++)
						{
							pLeft->m_ChildRoot[pLeft->m_Key[0] + k] = pFind->m_ChildRoot[k];
						}

						for (k = 1; k <= pFind->m_Key[0]; k++)
						{
							pLeft->m_Key[pLeft->m_Key[0] + 1] = pFind->m_Key[k];
							pLeft->m_Key[0]++;
						}

						for (k = i; k < pParent->m_Key[0]; k++)
						{
							pParent->m_Key[k] = pParent->m_Key[k + 1];
							pParent->m_ChildRoot[k] = pParent->m_ChildRoot[k + 1];
						}

						pParent->m_Key[0]--;

						delete pFind;

						if (pParent->m_Key[0] < nLowNum)
						{
							pFind = pParent;

							continue;
						}

						return true;
					}

					//右合并

					if (pRight && pRight->m_Key[0] >= 1)
					{
						pFind->m_Key[pFind->m_Key[0] + 1] = pParent->m_Key[i + 1];
						pFind->m_Key[0]++;

						for (k = 0; k <= pRight->m_Key[0]; k++)
						{
							pFind->m_ChildRoot[pFind->m_Key[0] + k] = pRight->m_ChildRoot[k];
						}

						for (k = 1; k <= pRight->m_Key[0]; k++)
						{
							pFind->m_Key[pFind->m_Key[0] + 1] = pRight->m_Key[k];
							pFind->m_Key[0]++;
						}

						for (k = i + 1; k < pParent->m_Key[0]; k++)
						{
							pParent->m_Key[k] = pParent->m_Key[k + 1];
							pParent->m_ChildRoot[k] = pParent->m_ChildRoot[k + 1];
						}

						pParent->m_Key[0]--;

						delete pRight;

						if (pParent->m_Key[0] < nLowNum)
						{
							pFind = pParent;

							continue;
						}

						return true;
					}

					printf("error:503\n");

					return false;
				}

				printf("error:596\n");

				return false;
			}

			//根节点下溢只要不是0就可以
			if (pFind->m_Key[0] == 0)
			{
				m_Root = pFind->m_ChildRoot[0];
				delete pFind;
			}

			return true;
		}
		
		return true;
	}
}

具体运行效果如下:

打印1-10的B树

需要代码可以从下面下载:

https://download.csdn.net/download/u011711997/10414495

阅读更多
文章标签: B-Tree B树
个人分类: 算法杂谈
想对作者说点什么? 我来说一句

B树算法的C++实现

2017年10月24日 929KB 下载

没有更多推荐了,返回首页

不良信息举报

B树的实现

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭