B- 树的概念
- B树就是B-Tree,B-Tree的定义:
- 根节点子女数为[2,m];
- 除根节点和叶结点以外的所有分支结点至少有[ [m/2] , m]个子女;
- 所有的叶结点都位于同一层;
- m 阶 B-Tree 的节点结构如下:n, S0, (K1,S1), (K2,S2), … ,(Kn,Sn)
其中 Si 是指向子树的指针,0 <= i <= n < m;Ki是关键码,1 <= i <= n< m;Ki < Ki+1,1 <= i < n- 在子树Si中所有的关键码都小于Ki+1,且大于Ki,0 < i < n
- 在子树Sn中所有的关机拿吗都大于Kn
- 在子树S0中的所有关键码都小于K1
- 子树Si也是m路搜索树,0 <= i <= n
事实上,在B数的每个结点中还包含一组指针D[m],指向实际对象的存放地址
K[i] 与 D[i] (1 <= i <= n < m)形成一个索引项(K[i],D[i]),通过K[i]可找到某个对象的存储地址D[i]
B- 树的定义
#define M 5
#define MAXSIZE (M-1) //MAX ELEM 4
#define MINSIZE (M/2) //MIN ELEM 2
typedef char KeyType;
typedef struct {}Record;
typedef struct
{
KeyType key;
Record* recptr;
}ElemType; //key_value
typedef struct BNode
{
int num;
struct BNode* parent;
ElemType data[M + 1];
struct BNode* sub[M + 1];
};
typedef struct
{
struct BNode* root;
int cursize;
}BTree;
typedef struct
{
struct BNode* ptr;
int index;
bool tag;
}Result;
B- 树的插入
#define M 5
#define MAXSIZE (M-1) //MAX ELEM 4
#define MINSIZE (M/2) //MIN ELEM 2 3 4
typedef char KeyType;
typedef struct {}Record;
typedef struct
{
KeyType key;
Record* recptr;
}ElemType; //key_value
typedef struct BNode
{
int num;
struct BNode* parent;
ElemType data[M + 1];
struct BNode* sub[M + 1];
};
typedef struct
{
struct BNode* root;
int cursize;
}BTree;
typedef struct
{
struct BNode* pnode;
int index;
bool tag; //返回false 则需要在index位置插入
}Result; //返回结构
BNode* Buynode()
{
BNode* s = (BNode*)malloc(sizeof(BNode));
if (nullptr == s) exit(1);
memset(s, 0, sizeof(BNode));
return s;
}
void Init_BTree(BTree& tree)
{
tree.root = nullptr;
tree.cursize = 0;
}
Result FindKey(BTree &tree,KeyType kx)
{
Result res = { nullptr,-1,false };
struct BNode* p = tree.root;
while (p != nullptr)
{
p->data[0].key = kx; //放置哨兵位
int i = p->num;
while (i >= 0 && kx < p->data[i].key)
{
--i;
}
res.pnode = p;
res.index = i;
if (i > 0 && kx == p->data[i].key)
{
res.tag = true;
break;
}
p = p->sub[i]; //下面结点寻找
}
return res;
//找不到则将该节点插入在res记录的pnode index+1 的位置
}
BNode* MakeRoot(const ElemType& item, BNode* left, BNode* right)
{
BNode* s = Buynode();
s->num = 1;
s->parent = nullptr;
s->data[1] = item;
s->sub[0] = left;
if (left != nullptr) left->parent = s;
s->sub[1] = right;
if (right != nullptr) right->parent = s;
return s;
}
void Insert_Item(BNode* ptr, int pos, const ElemType& item, BNode* right)
{
for (int i = ptr->num; i > pos; --i)
{
ptr->data[i + 1] = ptr->data[i]; //结点移动
ptr->sub[i + 1] = ptr->sub[i]; //叶子移动
}
ptr->data[pos + 1] = item; //插入数据
ptr->sub[pos + 1] = right; //插入叶子
ptr->num += 1;
}
ElemType Move_Item(BNode* s, BNode* ptr, int pos) //ptr -> s
{ //MINSIZE
for (int i = 0, j = pos + 1; j <= ptr->num; ++i, ++j)
{
s->data[i] = ptr->data[j];
s->sub[i] = ptr->sub[j];
if (s->sub[i] != nullptr) //移动孩子
{
s->sub[i]->parent = s;
}
}
s->num = MINSIZE;
ptr->num = MAXSIZE - MINSIZE - 1;
s->parent = ptr->parent;
return s->data[0]; //返回哨兵 需要将其移动至双亲结点
}
BNode* Splice(BNode* ptr) //分裂
{
BNode* s = Buynode();
ElemType item = Move_Item(s, ptr, MINSIZE);
if (ptr->parent == nullptr)
{
return MakeRoot(item, ptr, s); //创建根
}
BNode* pa = ptr->parent;
int pos = pa->num;
pa->data[0] = item;
while (pos > 0 && item.key < pa->data[pos].key)
{
--pos;
}
Insert_Item(pa, pos, item, s); //找到合适位置在双亲结点插入
if (pa->num > MAXSIZE)
{
return Splice(pa);
}
else
{
return nullptr;
}
}
bool Insert(BTree& tree, const ElemType& item)
{
if (tree.root == nullptr)
{
tree.root = MakeRoot(item, nullptr, nullptr); //创建root
tree.cursize = 1;
return true;
}
Result res = FindKey(tree, item.key); //查找插入位置
if (res.pnode != nullptr && res.tag) return false;
BNode* ptr = res.pnode;
int pos = res.index;
Insert_Item(ptr, pos, item, nullptr); //移动叶子与结点
if (ptr->num > MAXSIZE) //需要进行分裂
{
BNode* newroot = Splice(ptr);
if (newroot != nullptr) //不需要
{
tree.root = newroot;
}
}
tree.cursize += 1;
return true;
}
int main()
{
BTree myt;
Init_BTree(myt);
char ch[] = { "wertyuisdfghjkxcvbnm" };
int i = 0;
while (ch[i] != '\0')
{
ElemType item = { ch[i],nullptr };
cout << Insert(myt, item);
++i;
}
cout << endl;
}
B- 树遍历
void InOrder(BNode* ptr)
{
if (ptr == nullptr) return;
InOrder(ptr->sub[0]);
for (int i = 1; i <= ptr->num; ++i)
{
cout << ptr->data[i].key;
InOrder(ptr->sub[i]);
}
}
首先打印sub[0]结点,之后剩下数据与分支都能进行配对,每个分支都要左右两个孩子
B- 树的删除
假设我们需要删除的结点不为叶子结点,则进行数据转移,寻找其直接前驱
例如 i 的直接前驱 h,对其进行数据转移直接将该叶子结点最后一位进行删除,倘若该前驱的叶子分支大小为 2 MINSIZE,则去寻找后继结点,也就是 j 结点,同样进行替换删除,但是后继结点需要进行数据的移位
再加入,要删除结点的前驱与后继的叶子分支都为 2 MINSIZE,那么则从其左边的叶子结点进行转移(先左后右)
此时e 所处结点大小小于MINSIZE,则将 d 结点进行下移,并将左边的页结点中的 c 进行上移
当我们需要删除 8 结点的时候,左兄弟的结点不够右兄弟也不够,则需要进行合并
将结点 3 移动至 4 结点分支的0号位置,再整体将其进行左合并(先左后右),并将 9 结点向前移动,以及其孩子结点向前移动,c 结点与孩子也同样的进行移动