B-树 实现 插入,遍历,查找


B-树适用于:当文件很大时,索引表(树表)本身也在外存,则查找索引时需多次访问外存,并且,访问外存的次数恰为查找路径上的结点数。为减少访问外存的次数,应尽量缩减索引表的深度。此时宜采用m叉的B-树作索引表。m的选择取决于索引项的多少和缓冲区的大小。

插入过程

定义结构体与B-tree类

  1. 结构体BTNode:定义B-tree中每个节点
  2. 构造体Result:定义查找B-tree操作:BTree::SearchBTree(KeyType K)的返回值(K所在的节点,所在的关键字位置,是否查找成功 - 不成功是会之后会进行插入)
  3. 类BTree:定义:查找操作,插入操作,遍历操作
#include <iostream>
using namespace std;
 
typedef int KeyType;
#define M 3
typedef struct BTNode{
    
    
	int keynum;
    struct BTNode *parent;
    KeyType  key[M + 1];
    struct BTNode *ptr[M + 1];
}BTNode;
 
typedef struct Result{
    
    
    BTNode *pt;  // 指向查找到的结点。
    int i;       // 1..m,结点中的关键字标号
    int tag;     // 1:查找成功。 0:查找失败
}Result;         // B-树查找结果类型
 
class BTree{
    
    
public:
    BTree();
    ~BTree();
    Result SearchBTree(KeyType k);
    void Insert(BTNode *p, int i, KeyType x, BTNode *ap);
    void split(BTNode *q, int s, BTNode **ap);
    void NewRoot(BTNode *q, KeyType x, BTNode *ap);
    int InsertBTree(KeyType K, Result rs, int i);
    int ShowBTNode(BTNode *p);
    int TraverseBTree(BTNode *p);
    BTNode * GetRoot();
private:
    BTNode *m_root;  // 记录根节点 
};

// 构造函数 
BTree::BTree()
{
    
    
	m_root = NULL;  // 初始化 
}
 
BTree::~BTree()
{
    
    
	delete m_root;
}

// NewRoot(m_root, x, ap); m_root:根节点,ap:分裂后的新复制的节点 
void BTree::NewRoot(BTNode *q, KeyType x, BTNode *ap)  // 创建一个新的根 
{
    
    
    m_root = new BTNode;
    m_root->keynum = 1;
    m_root->key[1] = x;
    m_root->ptr[1] = ap;
    m_root->parent = NULL;
    m_root->ptr[0] = q;
    if(q != NULL)  // 这个地方的存在是为了一致性使用,第一次插入时会创建根,但还没有q和ap 
    {
    
    
        q->parent = m_root;
    }
    if(ap != NULL)
    {
    
    
        ap->parent = m_root;
    }
}
 
BTNode * BTree::GetRoot()
{
    
    
    return m_root;
}

从根节点开始查找K,要么找到,要么指向最底层的非终端节点中要插入的位置

  • Search(BTNode *p, KeyType K):对p节点中的关键字进行查找
  • BTree::SearchBTree(KeyType K):从整个树中查找,会不断的遍历相应的节点。从根节点开始查找K,要么找到,要么指向最底层的非终端节点要插入的位置
int Search(BTNode *p, KeyType K)  // 查找K应该在p节点中所在的位置 
{
    
    
    int i;
    for(i = 1; i <= p->keynum; ++i)
    {
    
    
        if(p->key[i] <= K)
        {
    
    
            if(i == p->keynum)  // 找到K所在的节点 
            {
    
    
                return i;
            }
            else if(p->key[i + 1] > K)  // 这个节点当前关键字小于K,下一个关键字大于K
            {
    
    
                return i;
            }
        }else  //  p->key[i] > K
        {
    
     
            return 0;
        }
    }
    return i;
}


Result BTree::SearchBTree(KeyType K)  // 从根节点开始查找K,要么找到,要么指向最底层的非终端节点要插入的位置 
{
    
    
    BTNode *p = m_root;
    BTNode *q = NULL;
    bool found = false;
    int i = 0;
    while((p != NULL) && !found) 
    {
    
    
		i = Search(p, K);  // 调用Search函数 
        if((i > 0) && (p->key[i] == K))  // 在这个节点中找到K 
		{
    
    
			found = true;
		}else  // 继续往加一个节点查找 
        {
    
    
            q = p; p = p->ptr[i];
        }
    }  // 退出时,要么找见了,要么p到null 即 q指向要K插入的节点 
    if(found) return Result{
    
    p, i, 1};
    else return Result{
    
    q, i, 0};
}

将值插入到对应的节点的对应的位置 以及 分裂操作

  • BTree::Insert(BTNode *p, int i, KeyType x, BTNode *ap):将值进行插入到其应该在的节点中正确的关键字位置
  • BTree::split(BTNode *q, int s, BTNode **ap):分裂的方法是—生成一新结点,从中间位置把结点(不包括中间位置的关键字)分裂成两部分。前半部分留在旧结点中,后半部分复制到新结点中,中间位置的关键字连同新结点的存储位置插入到父结点中。
    注意两个函数中都有为了代码一致性加入的代码,详细看对应的代码注释
// Insert(p, i, x, ap); p:指向查找到的结点,i:1..m,结点中的关键字标号
// x为关键字的值 
void BTree::Insert(BTNode *p, int i, KeyType x, BTNode *ap)  // 将K插入到对应的节点的对应的位置 
{
    
    
    int j = p->keynum;
    for(;j >= i + 1; --j)
    {
    
    
		p->key[j + 1] = p->key[j];
		p->ptr[j + 1] = p->ptr[j];
    }
    p->key[i + 1] = x;
    p->ptr[i + 1] = ap;  // 这个地方的存在是为了一致性使用,因为最底层的非终端和其他非终端节点ap值不同
							// 终端ap为NULL,非终端ap为分裂时新复制的节点 
    p->keynum++;
 }


// 分裂的方法是:生成一新结点,从中间位置把结点(不包括中间位置的关键字)分裂成两部分。前半部分留在旧结点中,
// 				后半部分复制到新结点中,中间位置的关键字连同新结点的存储位置插入到父结点中。 
// split(q, s, &ap); q:指向查找到的结点  s:为中间节点对应的位置索引
void BTree::split(BTNode *q, int s, BTNode **ap)  // ap为新复制的节点 
{
    
    
    int i;
    *ap = new BTNode;
    (*ap)->parent = q->parent;
    (*ap)->keynum = M - s;
	(*ap)->ptr[0] = q->ptr[s];
	for(i = 1; i <= M - s; ++i)
	{
    
    
		(*ap)->key[i] = q->key[s + i];
		(*ap)->ptr[i] = q->ptr[s + i];
		
		if(q->ptr[s + i] != NULL)  // 这个地方的存在是为了一致性使用,因为最底层的非终端节点和其他非终端节点q->ptr[s + i]不一样 
		{
    
    
			q->ptr[s + i]->parent = (*ap);
    	}
	}
	q->keynum = s - 1;
}

定义总的插入操作

包含了:将对应的插入值放到对应的节点中对应的关键字位置 以及 对不满足条件的节点进行分裂

// 先插入到某个最底层的非终端结点上,然后再进行分裂
// 分裂的方法是:生成一新结点,从中间位置把结点(不包括中间位置的关键字)分裂成两部分。前半部分留在旧结点中,
// 				后半部分复制到新结点中,中间位置的关键字连同新结点的存储位置插入到父结点中。 
int BTree::InsertBTree(KeyType K, Result rs, int i)  // q指向查找到的结点,i:1..m,结点中的关键字标号 
{
    
    
	if(rs.tag == 1) return 1;  // 之前查找到了K,就不用再插入 
	BTNode *q = rs.pt;
    KeyType x = K;
    BTNode *ap = NULL;  // ap为分裂时新复制的节点
    bool finished = false;
    while((q != NULL) && !finished)   
    {
    
    
        Insert(q, i, x, ap);  // 在q节点中i关键字位置插入值x
        if(q->keynum < M)  // 判断这个节点的关键字个数是否满足 
        {
    
    
          finished = true;
        }else  // 不满足时分裂 
        {
    
    
            int s = M / 2 + 1;  // 找到中间节点,之后要上升到父节点 
            split(q, s, &ap);  // ap为分裂时新复制的节点
			 
            // 准备对中间位置关键字插入父节点 
			x = q->key[s];  // 向父节点插入的值x 
            q = q->parent;  // q不断指向父节点 
            if(q != NULL)
            {
    
    
                i = Search(q, x);  // 查找K应该在q节点中所在的位置 
            }
        }
    }
    if(!finished)  // q==NULL说明此时根节点已经分裂,需要创建新的根;或者第一次插入时创建根 
    {
    
    
        NewRoot(m_root, x, ap); 
    }
    return 1;
}

定义如何进行展示B-tree

int BTree::ShowBTNode(BTNode *p)
{
    
    
    int i = 1;
    for(; i <= p->keynum; ++i)
    {
    
    
        cout << i << ":" << p->key[i] << "| ";
    }
    cout << endl;
    return 1;
}
 
int BTree::TraverseBTree(BTNode *p)
{
    
    
    int i;
    if(p != NULL)
    {
    
    
        ShowBTNode(p);  // 对这个节点关键字输出 
        for(i = 0; i <= p->keynum; ++i)
        {
    
    
            TraverseBTree(p->ptr[i]);  // 递归遍历所有子节点 
        }
    }
    return 1;
}

主函数

// 不要将终端节点误认为是最底层非终端点。
int main()
{
    
    
    BTree bt;
    Result rs;
    rs = bt.SearchBTree(45);  // 从根节点开始查找K,要么找到,要么指向最底层的非终端节点要插入的位置,如果节点存在就不用插入 
    	bt.InsertBTree(45, rs, rs.i);
    rs = bt.SearchBTree(24);
    	bt.InsertBTree(24, rs, rs.i);
    rs = bt.SearchBTree(53);
    	bt.InsertBTree(53, rs, rs.i);
    rs = bt.SearchBTree(90);
    bt.InsertBTree(90, rs, rs.i);
      	rs = bt.SearchBTree(3);
    bt.InsertBTree(3, rs, rs.i);
        rs = bt.SearchBTree(12);
    bt.InsertBTree(12, rs, rs.i);
        rs = bt.SearchBTree(37);
    bt.InsertBTree(37, rs, rs.i);
    	rs = bt.SearchBTree(50);
    bt.InsertBTree(50, rs, rs.i);
    	rs = bt.SearchBTree(61);
    bt.InsertBTree(61, rs, rs.i);
    	rs = bt.SearchBTree(70);
    bt.InsertBTree(70, rs, rs.i);
    	rs = bt.SearchBTree(100);
    bt.InsertBTree(100, rs, rs.i);
    
    bt.TraverseBTree(bt.GetRoot());
}

阅读代码时请注意代码注释中加入的"为了一致性使用"这些代码段,因为这些代码段设计的十分巧妙

可以自己定义遍历的形式,只需要修改对应的函数即可
运行结果为:
在这里插入图片描述

参考笔记

参考文章:
博客园:B树的实现算法:多阶树的增,删,遍历
CSDN:B树实现

猜你喜欢

转载自blog.csdn.net/qq_43779658/article/details/120919331