1. 程式人生 > >B樹的C++實現

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、有需要本人修改的,請與本人聯絡:[email protected]。發現並解決了問題,也請跟我說下,非常感謝!
3、轉載、使用請註明出處,修改也請予以說明。

茂盛農莊農夫木,2013.10.30 第一次編寫。

2013.11.05第二次修改,修改了 CBTree::Split,原函式有錯誤,現已改過來。歡迎大家對該程式碼作測試或者使用!

相關推薦

B B+ C++ 實現程式碼

本程式碼花了我四天時間,還有不足之處,希望對大家有一點幫助。 相關理論知識參見 《資料結構基礎》 張力譯版  ,另有一篇轉載的部落格作為參考; 我是先實現的B—樹, 在B-樹的基礎上實現的B+樹   可以先看B-樹 ,再看B+樹 。二者實現我已經儘量的使他們相互獨立了。

[原始碼和文件分享]基於C語言的B-實現

1 軟體結構設計 1.1 軟體功能結構 用下圖所示的方式描述軟體的功能結構。 1.1.1 B-樹的查詢 B-樹的查詢過程:根據給定值查詢結點和在結點的關鍵字中進行查詢交叉進行。首先從根結點開始重複如下過程: 若比結點的第一個關鍵字小,則查詢在該結點第一個指標指向的結點進行;

B+C語言實現

B+樹C語言版本,編譯通過,測試正確 插入部分實現,其中刪除和插入類似 #include<stdlib.h> #include<stdio.h> #include<stdbool.h> #include<string.h>

資料結構 B+c程式碼實現

template<typename Type> class BTree; template<typename Type> class BTreeNode{ public:     friendBTree<Type>;     BTreeNode(): m_nMaxS

BC語言實現-建立、插入、刪除

1. 課程設計題目標題:  B樹的基本操作演算法(建立、插入、刪除) 問題描述:  在電腦科學中,B樹在查詢、訪問、插入、刪除操作上時間複雜度為O(log2~n),與自平衡二叉查詢樹不同的是B樹對大塊資料讀寫的操作有更優的效能,其通常在資料庫和檔案系統中被使用。   對於

紅黑C++實現

con colors end ase 復制代碼 設置 typename ucc 技術 1 /* 2 * rbtree.h 3 * 1. 每個節點是紅色或者黑色 4 * 2. 根節點是黑色 5 * 3. 每個葉子節點是黑色(該葉子節點就空的節點)

數據結構之---二叉C實現

pac con fonts lib 內容 family aid size .com 學過數據結構的都知道樹。那麽什麽是樹? 樹(tree)是包括n(n>0)個結點的有窮集。當中: (1)每一個元素稱為結點(node); (2)有一個特定的結點被稱為根結

哈夫曼C++實現詳解

哈夫曼樹的介紹 Huffman Tree,中文名是哈夫曼樹或霍夫曼樹,它是最優二叉樹。 定義:給定n個權值作為n個葉子結點,構造一棵二叉樹,若樹的帶權路徑長度達到最小,則這棵樹被稱為哈夫曼樹。 這個定義裡面涉及到了幾個陌生的概念,下面就是一顆哈夫曼樹,我們來看圖解答。 (01) 路徑和路徑長度

AVLC++實現(插入,刪除,查詢,清空,遍歷操作)

AVL.h檔案程式碼 #pragma once #include<iostream> #include<stack> #include <assert.h> using namespace std; using namespace std; template<cl

java b+實現

B+樹的定義: 1.任意非葉子結點最多有M個子節點;且M>2; 2.除根結點以外的非葉子結點至少有 M/2個子節點; 3.根結點至少有2個子節點; 4.除根節點外每個結點存放至少M/2和至多M個關鍵字;(至少2個關鍵字) 5.非葉子結點的子樹指標與關鍵字個數相同;

B+實現,主要講解刪除操作

關於B+樹的基本定義,隨便一本資料結構的書或者演算法導論中都有,就不做介紹了。雖然網上和書本上都有很多對B+樹的介紹,但是有很多資料對於B+樹的操作或者介紹不全,或者有描寫錯誤的地方,我這裡參考這位大神的文章http://blog.csdn.net/xinghongduo/

【演算法總結】B+實現

【參考資料】 【B+樹是什麼】 b+樹是b樹的變種。b+樹與b樹最大的不同在於:b+樹的關鍵字數量跟孩子節點數量一致,這與b樹不一樣。並且,b+樹的葉子節點包含有所有關鍵字及其對應資訊,非葉子節點只包含部分關鍵字(這部分關鍵字相當於邊界索引),不含具體資料,下面這幅圖就

B+實現

             B+樹是B樹的變形,它在B樹的節點裡刪除了關鍵字的指標域,只保留了連結節點的指標域,在葉節點上,這個連結節點的指標域用來儲存關鍵字資訊。B+樹中的關鍵字在樹中可能不止出現一次(最多出現兩次),但一定在葉節點出現一次。相鄰的葉節點用單鏈表形式連線起

B實現

#include<iostream> using namespace std; template<class K, int M = 3> struct BTreeNode { K _keys[M]; BTreeNode<K, M>* _subs[M + 1]; si

b實現(2)---java版程式碼

原文地址: http://blog.csdn.net/cdnight/article/details/10619599 [java] view plain copy print? 感覺上,b樹的插入及刪除操作都不如RB樹複雜。當年插紅黑樹的各種操作解釋文章都

B(Java)實現

import java.util.LinkedList; import java.util.Queue; /** * Created by Kali on 14-5-26.\ * Abstract node. */ public abstract class AbstractBTreeNode<

隨想錄(B+實現

【 宣告:版權所有,歡迎轉載,請勿用於商業用途。  聯絡信箱:feixiaoxing @163.com】      關於樹的資料結構其實有很多種,常見的結構有二叉平衡樹、b樹、b+樹。這中間b+樹用的尤其多,特別是檔案系統方面。因為大家都知道,對於fs的node而言,我們必須

B-tree(C++實現)

#pragma once template<class T> class CBTree { private: static const int M = 3; //B樹的最小度數 static const int KEY_MAX = 2*M-1;

【演算法】b實現(1)

【前言】 這個內容是重點,因為它涉及到資料庫的儲存方式,查詢效率等。 先埋坑,到時候有時間再填上去。 還有一篇文章,裡面詳細講述了刪除節點的幾種情況: 【勘誤】我推薦的第二篇文章針對需要合併操作的操作是錯誤的,原因是作者沒有考慮到當父節點不夠數(譬如:5階數,關鍵

BB實現 B-Tree

根據演算法導論的描述。B樹是一種特殊的平衡樹和查詢樹。其主要用於磁碟內資料的儲存和查詢。因此,B樹一般每個結點會比較大,包含許多資料關鍵字,最好佔一個頁面(page),這樣存取的時候直接存取一個結點的資料。 B樹的基本性質(M階): 1、B樹的每個葉子節點的高度均一致。