B树的C++实现

 最近写一个程序需要用到 B 树,翻书看了下,参照网上的代码写了一个。对于 B 树的定义和生成有不少的文章讲过,我就不多废话,直接贴代码。

代码下载地址是:http://download.csdn.net/detail/wzh_xwjh/6504509


// Tree.h: B 树类头文件 —— 编写:茂盛农庄农夫木

#if !defined(FARMER_WOOD_BTREE_H)
#define FARMER_WOOD_BTREE_H

#include <stdio.h>
class CDataTypeForBtree;

struct treeNode
{
	struct treeNode** children;	//子树地址,动态开辟 order + 1 个空间
	unsigned int* key;		//关键字,实际存放的是关键字地址,动态开辟 order 个
	struct treeNode* parent;//父节点
	unsigned int keyNum;	//当前已添加的关键字个数
};

//B树的实现,树的度需在cpp文件中修改,不修改的话就默认为100
//子树是本类的指针对象,key是void*类型,可以转化为无符号整数或者指针
class CBTree
{
public:
	//反向遍历,用于得到相反序列的结果
	virtual void ReverseTraverse(struct treeNode* subTree, FILE* fp);

	//设置 m_pclassCmp,只能设置一次,多次设置的话后调用的设置无效
	void SetCompareMethord(CDataTypeForBtree* classCmp);

	//遍历,subTree 是要遍历的子树,fp 是数据要输出到的文件
	virtual void Traverse(struct treeNode* subTree, FILE* fp);

	//按关键字查询,key是等查询关键字地址,subTree 可以是 NULL
	//at:查找失败的情况下key应当插入到的在 subTree 中的下标
	//注意:at 是在类的 m_pKey 数组中的下标,一个 key 有左右两个 child
	//subTree:保存查找失败的情况下关键字应当插入的那个子树节点地址
	//通过 subTree 和 at,我们可以用InsertAfter来实现插入。当然这些返回值也可以用在删除上
	//成功则返回关键字地址,否则返回 0
	void* Query(void* key, unsigned int* at, struct treeNode** subTree);

	//pTree 是待插入数据的子树节点,把 key、child 插入到 at 位置
	//key 是待插入关键字地址,child 是在 key 右边待插入的子树,key 右边的子节点
	//at 是应当插入到 pTree 里面 key 数组中的下标,也即 key 应该放在 pTree->key[at]
	void InsertAt(struct treeNode* pTree, void* key, struct treeNode* child, unsigned int at);

	//要有插入才有分裂,key 是新插入的关键字,child 是待插入子节点
	//key 应当插入到 nShouldInsertAt 下标处,分裂 pTree
	void Split(struct treeNode* pTree, void* key, struct treeNode *child, unsigned int nShouldInsertAt);

	//删除关键字为 key 的节点,需要先查找到该键的地址。
	//删除结点后并不释放节点内存,而是返回节点地址供调用者处理
	//在向下查询删除点的时候并不合并路径上遇到的刚好可以组成一个满结点的子树
	void* DelKey(void* key);

	//插入一个关键字,key 是节点地址,插入成功后返回 key 指针
	//该函数是要把 key 插入到叶节点中,参数中不传入子树
	//如果树中已经存在相同的 key,则不对树作修改,返回树中这个 key 的值
	void* Insert(void* key);
	void Release(struct treeNode** node);

	//在子树 pTree 上删除下标 at 关键字,返回该关键字地址
	void* DelAt(struct treeNode* pTree, unsigned int at);

public:
	struct treeNode* GetRoot(void);
	CBTree(CDataTypeForBtree* dataType, unsigned int order = 100);	//order 是该树的阶数,默认为100
	virtual ~CBTree();

protected:

	//结点 node 的下标为 index 的子树不满足 B 树的性质,需对其进行整理
	void Rearrange(struct treeNode* node, unsigned int index);

	//合并结点 node 中下标为 index 和 index + 1 的子树,即向右合并
	void MergeSubTree(struct treeNode* node, unsigned int index);

private:
	struct treeNode *m_pRoot;		//这棵树的根结点
	const unsigned int m_cnOrder;	//阶数

	//用于 key 比较的类,比较两个关键字的大小
	CDataTypeForBtree* m_pclassCmp;

	//树的深度
	unsigned int m_nDepth;
};

#endif // !defined(FARMER_WOOD_BTREE_H)
// Tree.cpp: B 树的实现 —— 编写:茂盛农庄农夫木

#include <string.h>
#include <stdlib.h>
#include "BTree.h"
#include "DataTypeForBtree.h"

CBTree::CBTree(CDataTypeForBtree* dataType, unsigned int order) : m_cnOrder(order)
{
	m_nDepth = 0;
	m_pclassCmp = dataType;

	//开辟空间,开到最大数目,可能会浪费,但也没想出更好办法来
	m_pRoot = new struct treeNode;
	m_pRoot->key = new unsigned int[m_cnOrder];
	//在有子结点的时候才开辟内在,多数关键字在叶结点上,这样可以避免开辟多余内存
	m_pRoot->children = NULL;

	//别忘了数据初始化!!!
	for(unsigned int i = 0; i < m_cnOrder; i++)
	{
		m_pRoot->key[i] = 0;
	}
	m_pRoot->parent = NULL;
	m_pRoot->keyNum = 0;
}

CBTree::~CBTree()
{
	//可以在root调用,在中间结点或者叶结点调用也没事
	//(暂时只是预计没事,还没有实测-2013-5-22_21:25——10月14日运行处理17万条记录无报错)
	Release(&m_pRoot);
	if(NULL != m_pclassCmp)
		delete m_pclassCmp;
}

//释放动态内存,参数为 NULL 则从根开始遍历释放所有结点。
void CBTree::Release(struct treeNode** node)
{
	struct treeNode* tn = *node;
	if(NULL == tn && NULL == (tn = m_pRoot))
	{
		return;
	}

	unsigned int i;
	if(NULL != tn->children)
	{
		//子节点不为空。children比关键字多一个,所以i的结束条件要加等于
		for(i = 0; i <= tn->keyNum; i++)
		{
			Release(tn->children + i);
		}
	}

	//子节点处理完过后
	delete []tn->key;
	if(NULL != tn->children)
		delete []tn->children;
	delete tn;
	tn = NULL;
}

void* CBTree::Insert(void *key)
{
	if(NULL == key || NULL == m_pRoot)
		return NULL;

	if(0 == m_pRoot->keyNum)
	{
		m_pRoot->key[0] = (unsigned int)key;
		m_pRoot->keyNum++;
		m_nDepth = 1;
		return key;
	}

	//pShouldInsertAt是查找失败时保存应当插入位置的指针,ppTree是应当插入点所在的树指针
	struct treeNode *pTree;
	unsigned int nShouldInsertAt;
	void* keyAddr;

	keyAddr = Query(key, &nShouldInsertAt, &pTree);

	if(NULL == keyAddr)
	{
		InsertAt(pTree, key, NULL, nShouldInsertAt);
	}

	return key;
}

//根结点分裂调用此函数时会出现 root.keyNum = 0 的情况
void CBTree::InsertAt(struct treeNode* pTree, void* key, struct treeNode* child, unsigned int at)
{
	if(NULL == pTree || NULL == key)
		return;

	unsigned int n, j;

	n = at;
	//没有满,直接插入
	if(pTree->keyNum < m_cnOrder)
	{
		//顺序后移,给插入点腾出位置来
		for(j = pTree->keyNum; j > n; --j)
		{
			pTree->key[j] = pTree->key[j - 1];
		}
		//有子结点的情况
		if(NULL != pTree->children)
		{
			for(j = pTree->keyNum; j > n; --j)
			{
				pTree->children[j + 1] = pTree->children[j];
			}
			//插入子结点指针
			pTree->children[j + 1] = child;
		}

		//插入节点,子结点已经在前面处理过了
		pTree->key[j] = (unsigned int)key;
		pTree->keyNum++;
		return;
	}

	//需要分裂的情况
	Split(pTree, key, child, n);
}

void CBTree::Split(struct treeNode* pTree, void* key, struct treeNode *child, unsigned int nShouldInsertAt)
{
	if(pTree == m_pRoot)
	{
		struct treeNode* newRoot = new struct treeNode;
		//数据初始化,该结点做新的根,肯定有子结点,所以 children 一同创建
		newRoot->children = new struct treeNode*[m_cnOrder + 1];
		newRoot->key = new unsigned int[m_cnOrder];
		newRoot->keyNum = 0;
		newRoot->parent = NULL;
		unsigned int l;
		for(l = 0; l < m_cnOrder; l++)
		{
			newRoot->key[l] = 0;
			newRoot->children[l] = NULL;
		}
		newRoot->children[l] = NULL;

		pTree->parent = newRoot;
		newRoot->children[0] = pTree;
		m_pRoot = newRoot;
		//根分裂,深度+1
		m_nDepth++;
	}

    const unsigned int mid = (m_cnOrder + 1) >> 1;
    unsigned int j, i;
    struct treeNode *pNewRChild = new struct treeNode;    //新结点是分裂后的右子树
    if(NULL != pTree->children)
    {
        //该新增结点和 pTree 处于同一层,如果 pTree 有子结点,那么新结点也有子结点
        pNewRChild->children = new struct treeNode*[m_cnOrder + 1];
        for(i = 0; i <= m_cnOrder; ++i)
        {
            pNewRChild->children[i] = NULL;
        }
    }
    else
        pNewRChild->children = NULL;
    pNewRChild->key = new unsigned int[m_cnOrder];
    pNewRChild->keyNum = 0;
    pNewRChild->parent = pTree->parent;
    for(i = 0; i < m_cnOrder; ++i)
    {
        pNewRChild->key[i] = 0;
    }

    //这几个判断先处理当前这一层结点,然后再处理父结点
    if(nShouldInsertAt < mid)
    {
        //应当在前半部分插入新节点
        for(j = mid, i = 0; j < m_cnOrder; j++, i++)
        {
            //把分裂结点的后半部分搬到新结点中
            pNewRChild->key[i] = pTree->key[j];
            pTree->key[j] = 0;
        }
        pNewRChild->keyNum = i;
        pTree->keyNum = mid;

        //把前半部分移动下,给新关键字腾出位置来
        for(j = mid; j > nShouldInsertAt; j--)
        {
            pTree->key[j] = pTree->key[j - 1];
        }
        pTree->key[j] = (unsigned int)key;

        //有子结点的情况
        if(NULL != pTree->children)
        {
            for(j = mid, i = 0; j <= m_cnOrder; j++, i++)
            {
                pNewRChild->children[i] = pTree->children[j];
                pNewRChild->children[i]->parent = pNewRChild;
                pTree->children[j] = NULL;
            }

            for(j = mid - 1; j > nShouldInsertAt; j--)
            {
                pTree->children[j + 1] = pTree->children[j];
            }
            pTree->children[j + 1] = child;
            pTree->children[j + 1]->parent = pTree;
        }
    }
    else if(nShouldInsertAt > mid)
    {
        for(j = mid + 1, i = 0; j < nShouldInsertAt; j++, i++)
        {
            pNewRChild->key[i] = pTree->key[j];
            pTree->key[j] = 0;
        }
        pNewRChild->key[i++] = (unsigned int)key;

        for(; j < m_cnOrder; j++, i++)
        {
            pNewRChild->key[i] = pTree->key[j];
            pTree->key[j] = 0;
        }
        pNewRChild->keyNum = i;
        pTree->keyNum = mid;

        //有子结点
        if(NULL != pTree->children)
        {
            //别忘了在结束条件那儿加等号!!child要比key多一个
            for(j = mid + 1, i = 0; j <= nShouldInsertAt; j++, i++)
            {
                pNewRChild->children[i] = pTree->children[j];
                pNewRChild->children[i]->parent = pNewRChild;
                pTree->children[j] = NULL;
            }
            pNewRChild->children[i] = child;
            pNewRChild->children[i++]->parent = pNewRChild;

            for(; j <= m_cnOrder; j++, i++)
            {
                pNewRChild->children[i] = pTree->children[j];
                pNewRChild->children[i]->parent = pNewRChild;
                pTree->children[j] = NULL;
            }
        }
    }
    else
    {
        //新节点在中间,这种情况下新的key作为父节点key,child在右子树下标0位置处
        for(j = mid, i = 0; j < m_cnOrder; j++, i++)
        {
            pNewRChild->key[i] = pTree->key[j];
            pTree->key[j] = 0;
        }
        //把key放到待分裂子树根节点,以便下面的代码将其作为新建的父节点的一个key
        pTree->key[mid] = (unsigned int)key;
        pNewRChild->keyNum = i;
        pTree->keyNum = mid;

        //有子结点
        if(NULL != pTree->children)
        {
            pNewRChild->children[0] = child;
            pNewRChild->children[0]->parent = pNewRChild;
            for(j = mid + 1, i = 1; j <= m_cnOrder; j++, i++)
            {
                pNewRChild->children[i] = pTree->children[j];
                pNewRChild->children[i]->parent = pNewRChild;
                pTree->children[j] = NULL;
            }
        }
    }

    //查找 pTree 在 parent 中的下标
    for(j = 0; j <= pTree->parent->keyNum; j++)
    {
        if(pTree == pTree->parent->children[j])
            break;
    }
    InsertAt(pTree->parent, (void*)pTree->key[mid], pNewRChild, j);
    pTree->key[mid] = 0;
}

void* CBTree::DelKey(void *key)
{
	if(NULL == key || NULL == m_pRoot || 0 == m_pRoot->keyNum)
		return NULL;

	//nShouldDelAt是查找失败时保存应当插入位置的指针,pTree是应当插入点所在的树指针
	struct treeNode *pTree;
	unsigned int nShouldDelAt;
	void* keyAddr;

	keyAddr = Query(key, &nShouldDelAt, &pTree);

	//查找成功才删除,失败表示树中没有这个关键字
	if(NULL != keyAddr)
	{
		DelAt(pTree, nShouldDelAt);
	}

	return keyAddr;
}

//key是关键字地址,keyType是类型,1是artist,2是track(双关键字)
void* CBTree::Query(void *key, unsigned int* at, struct treeNode** subTree)
{
	if(NULL == key || NULL == m_pRoot)
	{
		if(NULL != at)
			at = NULL;
		return 0;
	}

	struct treeNode *ptree = m_pRoot;
	int l, h, m, r;
	while(1)
	{
		l = 0;
		h = ptree->keyNum - 1;
		m = (l + h) / 2;

		//用二分法在一个节点里面查找
		while(l <= h)
		{
			//暂时比较指针,用的时候要再写个比较函数,用来比较track或者artist的id字符串
			//如果树中没有节点,则总是刚好停在比节点关键字大的节点上,所以不用担心停在较小的节点上
			m = (l + h) / 2;
			r = m_pclassCmp->Compare(key, (void*)(ptree->key[m]));

			if(r < 0)
			{
				//表示node指向的关键字要小点
				h = m - 1;
			}
			else if(r > 0)
			{
				//node比m节点大,修改m的值只是为了便于本层没有查找成功的情况下直接通过m进入下一层
				l = ++m;
			}
			else
			{
				if(NULL != subTree)
					(*subTree) = ptree;
				*at = m;
				//查找成功,不需要用到nodeAddr
				return (void*)(ptree->key[m]);
			}
		}
		//本层查找结束,有子结点则进入子节点,否则在本层添加
		if(NULL != ptree->children && NULL != ptree->children[m])
		{
			ptree = ptree->children[m];
		}
		else
			break;
	}

	if(NULL != at)
	{
		//二分查找,失败的情况下会停在关键字较小的索引上(当然,数组只有一个元素时除外),在关键字右边插入,下标加1,但循环中已经处理过
		*at = m;
	}

	if(NULL != subTree)
		(*subTree) = ptree;

	return 0;
}

void CBTree::Traverse(struct treeNode* subTree, FILE* fp)
{
	if(NULL == m_pclassCmp)
		return;

	FILE* fpOut = fp;
	unsigned int i;

	//如果传入的结点是空,就从根开始遍历——但一定要小心啊,后面的程序不能出错
	//要是再传空结点的话又要重来了!!!!传子树一定不能为空!!
	if(NULL == subTree)
			subTree = m_pRoot;
	if(NULL == fpOut)
		fpOut = stdout;

	if(NULL != subTree->children)
	{
		//这是有子树的情况
		for(i = 0; i < subTree->keyNum; i++)
		{
			Traverse((struct treeNode*)subTree->children[i], fpOut);
			m_pclassCmp->Print((void*)(subTree->key[i]), fpOut);
		}
		//还有一个最右子树
		if(NULL != subTree->children[i])
			Traverse((struct treeNode*)(subTree->children[i]), fpOut);
	}
	else
	{
		for(i = 0; i < subTree->keyNum; i++)
		{
			m_pclassCmp->Print((void*)subTree->key[i], fpOut);
		}
	}
}

void CBTree::SetCompareMethord(CDataTypeForBtree *classCmp)
{
	if(NULL == m_pclassCmp)
		m_pclassCmp = classCmp;
}

void CBTree::ReverseTraverse(struct treeNode* subTree, FILE *fp)
{
	if(NULL == m_pclassCmp)
		return;

	FILE* fpOut = fp;
	int i;
	if(NULL == subTree)
			subTree = m_pRoot;
	if(NULL == fpOut)
		fpOut = stdout;

	if(NULL != subTree->children)
	{
		//这是有子树的情况
		for(i = subTree->keyNum; i > 0; i--)
		{
			ReverseTraverse((struct treeNode*)(subTree->children[i]), fpOut);
			m_pclassCmp->Print((void*)(subTree->key[i - 1]), fpOut);
		}
		//还有第一个子树
		ReverseTraverse((struct treeNode*)(subTree->children[0]), fpOut);
	}
	else
	{
		for(i = subTree->keyNum - 1; i >= 0; i--)
		{
			m_pclassCmp->Print((void*)(subTree->key[i]), fpOut);
		}
	}
}

struct treeNode* CBTree::GetRoot()
{
	return m_pRoot;
}

void* CBTree::DelAt(treeNode *pTree, unsigned int at)
{
	if(NULL == pTree || pTree->keyNum <= at)
		return NULL;

	void* key;
	unsigned int i = 0, j = 0;
	key = (void*)pTree->key[at];

	//第一种情况,在叶结点,直接删除
	if(NULL == pTree->children)
	{
		pTree->keyNum--;
		for(i = at; i < pTree->keyNum; ++i)
		{
			pTree->key[i] = pTree->key[i + 1];
		}
		pTree->key[i] = 0;
		//此时需判断该结点是否满足最少关键字条件,不满足的话就合并相邻结点或者借关键字
		if(((m_cnOrder + 1) / 2) > pTree->keyNum && pTree != m_pRoot)
		{
			//找出 pTree 在 parent 中的下标
			i = 0;
			while(pTree->parent->children[i] != pTree)
			{
				i++;
			}
			Rearrange(pTree->parent, i);
		}
	}
	//第二种情况,在非叶结点删除,涉及子结点修改
	else
	{
		//删除内部结点,需要用其左子树递归最右叶结点最右关键字填充该位置,
		//或者用右子树递归最左子树叶结点第一个关键字填充
		struct treeNode* tmp;
		if(at > 0)
		{
			//删除的不是第一个,从前面拿一个来放在这个位置
			tmp = pTree->children[at];
			while(NULL != tmp->children)
			{
				tmp = tmp->children[tmp->keyNum];
			}
			pTree->key[at] = tmp->key[tmp->keyNum - 1];
			DelAt(tmp, tmp->keyNum - 1);
		}
		else
		{
			//是第一个,只能从右子树中找一个来替换
			tmp = pTree->children[0];
			while(NULL != tmp->children)
			{
				tmp = tmp->children[0];
			}
			pTree->key[at] = tmp->key[0];
			DelAt(tmp, 0);
		}
	}

	return key;
}

void CBTree::Rearrange(treeNode *node, unsigned int index)
{
	if(NULL == node || index > node->keyNum || NULL == node->children ||
		(m_cnOrder + 1) / 2 < node->children[index]->keyNum)
		return;

	//先考虑跟左边结点合并
	if(0 < index && node->children[index]->keyNum + node->children[index - 1]->keyNum < m_cnOrder)
	{
		//左合并,注意上面的判断是小于,不会有等于的情况,合并后 index 下标的关键字加到合并后的结点里
		MergeSubTree(node, index - 1);
	}
	//再考虑跟右边结点合并
	else if(node->keyNum - 1 > index && node->children[index]->keyNum + node->children[index + 1]->keyNum < m_cnOrder)
	{
		//右合并
		MergeSubTree(node, index);
	}
	//不能合并,那就借一个,先考虑向左借
	else if(index > 0)
	{
		//向左借.如果子结点还有子结点的话,index - 1 的末子结点会成为 index 的首子结点
		//先移动关键字
		int i;
		struct treeNode* tmp1, *tmp2;
		tmp1 = node->children[index - 1];
		tmp2 = node->children[index];
		for(i = tmp2->keyNum; i > 0; --i)
		{
			tmp2->key[i] = tmp2->key[i - 1];
		}
		//父结点的关键字作第一个关键字
		tmp2->key[0] = node->key[index - 1];
		node->key[index - 1] = tmp1->key[tmp1->keyNum - 1];
		tmp1->key[tmp1->keyNum - 1] = 0;
		//子结点有子结点的情况
		if(NULL != tmp2->children)
		{
			for(i = tmp2->keyNum; i >= 0; --i)
			{
				tmp2->children[i + 1] = tmp2->children[i];
			}
			tmp2->children[0] = tmp1->children[tmp1->keyNum];
			tmp1->children[tmp1->keyNum] = NULL;
		}
		tmp2->keyNum++;
		tmp1->keyNum--;
	}
	else
	{
		//向右借
		unsigned int i;
		struct treeNode *tmp1, *tmp2;
		tmp1 = node->children[index];
		tmp2 = node->children[index + 1];
		tmp1->key[tmp1->keyNum] = node->key[index];
		node->key[index] = tmp2->key[0];
		for(i = 0; i < tmp2->keyNum - 1; ++i)
		{
			tmp2->key[i] = tmp2->key[i + 1];
		}
		tmp2->key[i] = 0;
		//有子结点
		if(NULL != tmp2->children)
		{
			tmp1->children[tmp1->keyNum + 1] = tmp2->children[0];
			for(i = 0; i < tmp2->keyNum; ++i)
			{
				tmp2->children[i] = tmp2->children[i + 1];
			}
			tmp2->children[i] = NULL;
		}
		tmp1->keyNum++;
		tmp2->keyNum--;
	}
}

//合并 node 结点的下标为 index 和 index + 1 的子结点
void CBTree::MergeSubTree(struct treeNode* node, unsigned int index)
{
	//判断结点空和不满足合并条件的情况
	if(NULL == node || index >= m_cnOrder || NULL == node->children ||
		node->children[index]->keyNum + node->children[index + 1]->keyNum >= m_cnOrder)
		return;

	unsigned int i, j;
	//先处理子结点的修改
	struct treeNode *child1, *child2;
	child1 = node->children[index];
	child2 = node->children[index + 1];
	child1->key[child1->keyNum++] = node->key[index];
	for(i = child1->keyNum, j = 0; j < child2->keyNum; ++i, ++j)
	{
		//把 child2 的关键字和子结点移到 child1 来
		child1->key[i] = child2->key[j];
	}
	//有子结点。子结点和父结点都存的是地址,移动前后地址不变,
	//只是把这些涉及变化结点的地址复制到了另一结点里面,
	//所以不牵涉 child2 的子结点的父结点的变化
	if(NULL != child2->children)
	{
		for(i = child1->keyNum, j = 0; j <= child2->keyNum; ++i, ++j)
		{
			//把 child2 的关键字和子结点移到 child1 来
			child1->children[i] = child2->children[j];
		}
		delete []child2->children;
	}
	child1->keyNum += child2->keyNum;

	//删除 child2
	delete []child2->key;
	delete child2;

	//子结点处理完,再处理当前结点,也就是把后面的结点依次往前移一位
	for(i = index; i < node->keyNum - 1; ++i)
	{
		node->key[i] = node->key[i + 1];
		node->children[i + 1] = node->children[i + 2];
	}
	node->children[i + 1] = NULL;
	node->keyNum--;

	//关键字减少,还要继续判断是否需要向上传递
	if(((m_cnOrder + 1) / 2) > node->keyNum && node != m_pRoot)
	{
		i = 0;
		while(node->parent->children[i] != node)	//肯定会有 node 这个子节点,不作 i 的范围判断
		{
			i++;
		}
		Rearrange(node->parent, i);
	}
}

  
这里面主要包含两个类,CBTree 和 CDataTypeForBtree,其余几个是测试用的。B 树的实现类 CBTree 里面,关键字实际保存的是地址,虽然也可以保存无符号整数,但显然这不是我的初衷,我想让这一个 B 树类能够处理不同类型的数据。到这里也许有人会想,既然保存的是地址,那数据怎样比较大小呢?这就是另一个类的任务了。B 树调用 CDataTypeForBtree 派生类的函数处理数据,一个这样的派生类对应一个数据类型。用的时候先要从这派生一个类出来,在派生类中实现数据比较函数 Compare,数据输出函数 Print。
然后在堆上创建一个数据类对象,把地址
作为构造函数的参数
创建树对象,
下面给出一个使用示例。

#include <string.h>
#include <stdio.h>
#include "DataTypeForBtree.h"
#include "BTree.h"

struct tree_data
{
	char tid[37];
	char tname[63];
	unsigned int aid;
	unsigned int id;
};

class CMyDataTypeForBtree : public CDataTypeForBtree  
{
public:
	virtual void Print(void *key, FILE* fp)
	{
		if(NULL == key || NULL == fp)
			return;

		struct tree_data* tr = (struct tree_data*)key;
		fprintf(fp, "%u	%u	%s	%s\n", tr->id, tr->aid, tr->tid, tr->tname);
	}

	virtual int Compare(void *p1, void *p2)
	{
		if(NULL == p1 || NULL == p2)
			return (int)(((unsigned int)(~0)) >> 1);	//返回一个大点的数表示失败
	
		struct tree_data *tr1, *tr2;
		tr1 = (struct tree_data*)p1;
		tr2 = (struct tree_data*)p2;

		if(tr2->aid != tr1->aid)
		{
			return tr1->aid - tr2->aid;
		}

		if('\0' == tr1->tid[0] && '\0' == tr2->tid[0])
		{
			return strcmp(tr1->tname, tr2->tname);
		}

		return strcmp(tr1->tid, tr2->tid);
	}

	CMyDataTypeForBtree()
	{
	}
	virtual ~CMyDataTypeForBtree()
	{
	}
};

int main(int argc, char* argv[])
{
	CMyDataTypeForBtree *dt = new CMyDataTypeForBtree;
	CBTree tree(dt, 5);
	struct tree_data tr[101] = {{"asd", "4Hero", 1, 1}, {"abc", "Underworld", 1, 0}, {"bac", "Samantha", 1, 2}, {"cass", "Gelka", 1, 3},
	{"mark", "Clark", 1, 4}, {"gone", "Woolfy", 1, 5}, {"word", "Production", 1, 6}, {"paper", "Jimpster", 1, 7}, {"Richie", "Hawtin", 1, 8},
	{"John", "Matthias", 1, 9}, {"Lou", "Donaldson", 1, 10}, {"Lady", "Alma", 1, 11}, {"Mass", "Slick", 1, 12}, {"Clyde", "Alexander", 1, 13},
	//……省略若干,省略部分在下载包里面有
	{"", "I'M Not Sayin' Get 'Er Done, But Don'T Just Stand There", 11, 101},};

	for(int i = 0; i < 101; i++)
	{
		tree.Insert((void*)(tr + i));
	}
	tree.DelKey((void*)(tr + 5));
tree.Traverse(NULL, NULL);
	tree.DelKey((void*)(tr + 13));
tree.Traverse(NULL, NULL);	//输出到标准输出,可以重定向到文件

	return 0;
}

  实现过程中参考了《算法导论》和一份网上下载的 C++ 代码。但很遗憾,由于一次电脑故障,维修人员把我的硬盘全部重新分区,我参考的这份代码没有备份,最后也没有恢复出来,只记得代码里面 B 树的类名是 BnTree,在此对这位不知名的朋友表示感谢,同时也深感很愧疚!如果作者看到这份说明可以与我联系,我将把对您的代码参考写进去,如果哪位朋友知道这份代码出处也请告知,谢谢!
  该代码中,关键字的添加和删除均会向上传递。考虑以下这种情况:刚添加一个关键字,沿途满了的节点分裂,然后马上删除该关键字,沿途部分节点又要合并,所以,我还是选择必要的时候才分裂和合并——在一个满了的结点插入的时候分裂;只在一个节点关键字太少以至于不满足 B 树特征,而且可以与其左或右相邻结点合并时才合并。
这个类没有考虑并发的情况,只适合在单线程中使用,如果有并发操作的需要,请修改后使用。

  最后说明下:
  1、由于我的程序里面没用上,关键字的删除我只作了简单测试——也就是示例代码里面那样的测试。编程水平有限,也许代码还有漏洞,若是使用该代码请自行检测,欢迎大家作学习、非商业的和商业的使用,由此带来的利益与责任都请自行承担。
  2、有需要本人修改的,请与本人联系:air_xwjh@163.com。发现并解决了问题,也请跟我说下,非常感谢!
  3、转载、使用请注明出处,修改也请予以说明。

  茂盛农庄农夫木,2013.10.30 第一次编写。

  2013.11.05第二次修改,修改了 CBTree::Split,原函数有错误,现已改过来。欢迎大家对该代码作测试或者使用!

    原文作者:B树
    原文地址: https://blog.csdn.net/wzh_xwjh/article/details/13624033
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞