B树是一颗多路的平衡搜索树,其规定树根至少有两个孩子,每个内部节点有两个或以上的孩子。用来衡量B树规模的一个指标是“最小度数”t,其表示B树所有内部节点的孩子数为t~2t个。
B树的一个结点有两个存储域,分别是关键字和孩子结点,关键字用于划分孩子节点,一个关键字的左右两侧各有一个孩子。类似于二叉搜索树,一个关键字x的左孩子的关键字值都比x小,右孩子的关键字值都比x大。因此,一个结点的关键字的个数是t-1~2t-1个。
下图是一个简单的B树,该树的最小度数t是2。
B树有以下性质:
性质1. 根结点至少有两个子女;
性质2. 每个非根节点所包含的关键字个数 j 满足:t-1 <= j <= 2t-1;
性质3. 除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1;
性质4. 所有的叶子结点都位于同一层。
下面给出实际编程时B树以及B树结点的数据结构定义。
1.1 单链表操作
下面给出实际编程时B树以及B树结点的数据结构定义。
typedef struct LinkKey
{
int key;
struct LinkKey *next;
} LinkKey;
typedef struct LinkNode
{
void *node;
struct LinkNode *next;
} LinkNode;
typedef struct BNode
{
int n; // 孩子个数,so key个数为n-1
bool leaf;
LinkKey *key;
LinkNode *children;
struct BNode *parent;
} BNode;
typedef struct BTree
{
BNode *root;
int t; // B树的最小度数
} Btree;
考虑到B树结点中多关键字和多结点的遍历,删除以及插入操作以及其数目的不确定性,为了降低时间和空间复杂度,我们不使用数组对这些数据进行组织,取而代之的是单链表。上述定义中的LinkKey和LinkNode就是存储一个结点的关键字和孩子结点指针的链表数据结构。一个结点的关键字以非递减的顺序存储,结点指针的存储顺序与关键字顺序对应。BNode的leaf属性记录该结点是否为叶子结点(true/false)。
1. B树的插入
B树的插入操作需要先根据要插入的关键字k值找到正确的插入位置,该过程与二叉搜索树的插入类似。从树根出发,遍历当前结点的关键字链表,找到一个前面的关键字比k小,后面的关键字比k大的位置,然后再取出这个位置的孩子指针,继续遍历该孩子结点的关键字。如此往复,直到当前结点为叶子结点时,找到合适的位置将关键字k插入叶子结点的关键字链表中即可。
1.1 单链表操作
该过程主要涉及到的是对单链表的操作,这里不做详细讨论,只给出后续程序中使用到的方法的定义。
/**
* 以下是链表的操作
*/
LinkKey *initLinkKey()
{
LinkKey *head = (LinkKey *)malloc(sizeof(LinkKey));
head->key = INT_MIN;
head->next = NULL;
return head;
}
void destroyLinkKey(LinkKey *link)
{
while (link != NULL)
{
LinkKey *next = link->next;
free(link);
link = next;
}
}
int getKey(LinkKey *link, int which)
{
int i = -1;
while (i < which && link->next != NULL)
{
link = link->next;
i++;
}
if (i == -1) return INT_MIN;
return link->key;
}
void insertLinkKey(LinkKey *link, int key)
{
while (link->next != NULL)
{
if (key <= link->next->key)
{
break;
}
link = link->next;
}
LinkKey *node = (LinkKey *)malloc(sizeof(LinkKey));
node->key = key;
node->next = link->next;
link->next = node;
}
bool deleteLinkKey(LinkKey *link, int key)
{
while (link->next != NULL && key > l