B-树总结

本文深入探讨了B-树的定义与特性,详细解析了其数据结构与算法实现,包括初始化、查找、插入等核心操作,适用于多路查找场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前天和昨天一直在写实验六的题目二,想挑战一下自己,所以选择用B-树的数据结构,这篇是一篇关于B-树的总结。
B-树是一种平衡的多路查找树。
一棵m阶的B-树,或者为空树,或为满足下列特征的m叉树:

  1. 树中每个结点至多有m棵子树;

  2. 若根结点不是叶子结点,则至少有两棵子树;

  3. 除根之外的所有非终端结点至少有m/2(向上取)棵子树;

  4. 所有的非终端结点中包含下列信息数据
    (n,A0,K1,A1,K2,A2,…,Kn,An)、
    其中:Ki(i=1,…,n)为关键字,且Ki<K(i+1)(i=1,…,n-1);Ai(i=0,…,n)为指向子树根结点的指针,且指针A(i-1)所指的子树中所有结点的关键字均小于Ki(i=1,…,n),An所指子树中所有结点的关键字均大于Kn,n(m/2(向上取)-1<=n<=m-1)为关键字的个数(或n+1为子树个数)。

  5. 所有的叶子结点都出现在同一层次上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)

B-树的结构体

#define m 3
typedef struct BTNode     //B-树的阶,暂设为3
{
   int keynum;                  //结点中关键字个数,即结点的大小
   struct BTNode *parent;       //指向双亲结点
   KeyType key[m+1];            //关键字向量,0号单元未用
   struct BTNode *ptr[m+1];     //子树指针向量
   Record *recptr[m+1];         //记录指针向量,0号单元未用
}BTNode,*BTree;                 //B-树结点和B-树的类型

typedef struct 
{
  BTNode *pt;      //指向找到的结点
  int i;           //1,....,m,在结点中的关键字序号
  int tag;         //1:查找成功,0:查找失败
}Result;           //B-树的查找结果类型

具体实现算法

Status InitBTree(BTree &t)   //生成一个新B-树
{
	t = NULL;
	return OK;
}
int SearchBTNode(BTNode *p, KeyType k)  //在结点中寻找关键字的位置
{
	int i = 0;
	while (i < p->keynum&&strcmp(p->key[i + 1], k) <= 0)
	{
		i++;
	}
	return i;
}
Result SearchBTree(BTree t, KeyType k)
{
	BTNode *p = t, *q = NULL;      //初始化结点p和结点q,p指向待查结点,q指向p的双亲
	int found_tag = 0;              //设定查找成功与否标志
	int i = 0; 
	Result r;                     //设定返回的查找结果
	while (p != NULL && found_tag == 0)
	{
		i = SearchBTNode(p, k);                //在结点p中查找关键字k
		if (i > 0 && strcmp(p->key[i], k) == 0)      //查找成功
		{
			found_tag = 1;
		}
		else               //查找失败
		{
			q = p;
			p = p->ptr[i];
		}
	}
	if (found_tag == 1)           //查找成功
	{
		r.pt = p;
		r.i = i;
		r.tag = 1;
	}
	else                  //查找失败
	{ 
		r.pt = q;
		r.i = i;
		r.tag = 0;
	}
	return r;                //返回关键字k的位置(或者插入位置)
} 

void InsertBTNode(BTNode *&p, int i, KeyType k, BTNode *q,int a)  //将p->key[s+1,...,m],p->ptr[s+1,...,m]和p->recptr[s+1,...,m]移入新节点q
{
	int j;
	for (j = p->keynum;j > i;j--)          //整体后移空出一个位置
	{
		strcpy(p->key[j + 1], p->key[j]);
		p->recptr[j + 1] = p->recptr[j];
		p->ptr[j + 1] = p->ptr[j];
	}
	strcpy(p->key[i + 1], k);
	p->recptr[i + 1] = a;
	p->ptr[i + 1] = q;
	if (q != NULL)
		q->parent = p;
	p->keynum++;
}

void SplitBTNode(BTNode*&p, BTNode *&q)  //将p->key[s+1,...,m],p->ptr[s+1,...,m]和p->recptr[s+1,...,m]移入新节点q
{
	int i;
	int s = (m + 1) / 2;
	q = (BTNode *)malloc(sizeof(BTNode));    //给结点q分配空间
	q->ptr[0] = p->ptr[s];
	for (i = s + 1;i <= m;i++)       //后一半移入结点q
	{
		strcpy(q->key[i - s], p->key[i]);
		q->recptr[i - s] = p->recptr[i];
		q->ptr[i - s] = p->ptr[i];
	}
	q->keynum = p->keynum - s;
	q->parent = p->parent;
	for (i = 0;i <= p->keynum - s;i++)     //修改双亲指针
	{
		if (q->ptr[i] != NULL)
			q->ptr[i]->parent = q;
	}
	p->keynum = s - 1;               //结点p的前一半保留,修改结点p的keynum
}

void NewRoot(BTNode *&t, KeyType k, BTNode *p, BTNode *q,int a)
{
	t = (BTNode *)malloc(sizeof(BTNode));    //分配空间
	t->keynum = 1;
	t->ptr[0] = p;
	t->ptr[1] = q;
	strcpy(t->key[1], k);
	t->recptr[1] = a;
	if (p != NULL)             //调整结点p和q的双亲指针
		p->parent = t;
	if (q != NULL)
		q->parent = t;
	t->parent = NULL;
}

void InsertBTree(BTree &t, int i, KeyType k, BTNode *p,int a)
{
	BTNode *q; 
	int b;
	int finish_tag, newroot_tag, s;          //设定需要新结点标志和插入完成标志
	KeyType x;
	if (p == NULL)           //t是空树
		NewRoot(t, k, NULL, NULL,a);             //生成仅含关键字k的根结点t
	else
	{
		strcpy(x,k);
		b = a;
		q = NULL;
		finish_tag = 0;
		newroot_tag = 0;
		while (finish_tag == 0 && newroot_tag == 0)
		{
			InsertBTNode(p, i, x, q,b);  //将关键字x和结点q分别插入到p->key[i+1]和p->[i+1]
			if (p->keynum <= Max)        //插入完成
				finish_tag = 1;
			else
			{
				s = (m + 1) / 2;
				SplitBTNode(p, q);         //分裂结点
				strcpy(x, p->key[s]);
				b = p->recptr[s];
				if (p->parent)         //查找x 的插入位置
				{ 
					p = p->parent;
					i = SearchBTNode(p, x);
				}
				else                    //没找到x,需要新结点
					newroot_tag = 1;
			}
		}
		if (newroot_tag == 1)              //根节点已分裂为p和q
			NewRoot(t, x, p, q,b);          //生成新的根结点,p和q都是子树指针
	}
}

这里的算法,只要认真理解一下,其实不是很难,我自己的一个难点是如何实现对记录进行处理,即对recptr的处理。这个关键就是在进行关键字的插入,移动时,记得一定要同时移动或插入相应的记录。

在这次作业中,我还遇到了几个小问题,这里顺便记一下。

  1. 比较两个字符串s1和s2,不能用s1= =s2,因为这样只是比较第一个字符,而用函数strcmp()才是两个字符串的比较,如果s1<s2,则strcmp的返回值<0,s1= =s2,则返回值= =0,若s1>s2,则返回值>0。
  2. 字符串的定义,用strcpy()函数。
  3. 自定义结构体中,不能使用常量数据类型。

下面是一些参考资料:
https://blog.youkuaiyun.com/geek_jerome/article/details/78895289
https://blog.youkuaiyun.com/geek_jerome/article/details/78895289

最后附上完整代码:


#include "pch.h"
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <string.h>

typedef int Status;
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFESIBLE -1
#define OVERFLOW -2
// 表示函数的状态

#define MAXM  11  //定义B树的最大的阶数

const int m = 4;   //设定B树的阶数
const int Max = m - 1;          //结点的最大关键字数量
const int Min = (m - 1) / 2;    //结点的最小关键字数量
typedef char KeyType[15];         //KeyType为关键字类型

typedef struct node     //B树和B树结点类型
{
	int keynum;             //结点关键字个数
	KeyType key[MAXM];     //关键字数组,key[0]不使用
	struct node *parent;   //双亲结点指针
	struct node *ptr[MAXM];   //孩子结点指针数组
	int recptr[MAXM];       //记录指针向量,0号单元未用
}BTNode, *BTree;

typedef struct    //B树查找结果类型
{
	BTNode *pt;    //指向找到的结点
	int i;        //在结点中的关键位置
	int tag;        //查找成功与否标志
}Result;

Status InitBTree(BTree &t)
{
	t = NULL;
	return OK;
}

int SearchBTNode(BTNode *p, KeyType k)
{
	int i = 0;
	while (i < p->keynum&&strcmp(p->key[i + 1], k) <= 0)
	{
		i++;
	}
	return i;
}

Result SearchBTree(BTree t, KeyType k)
{
	BTNode *p = t, *q = NULL;      //初始化结点p和结点q,p指向待查结点,q指向p的双亲
	int found_tag = 0;              //设定查找成功与否标志
	int i = 0; 
	Result r;                     //设定返回的查找结果
	while (p != NULL && found_tag == 0)
	{
		i = SearchBTNode(p, k);                //在结点p中查找关键字k
		if (i > 0 && strcmp(p->key[i], k) == 0)      //查找成功
		{
			found_tag = 1;
		}
		else               //查找失败
		{
			q = p;
			p = p->ptr[i];
		}
	}
	if (found_tag == 1)           //查找成功
	{
		r.pt = p;
		r.i = i;
		r.tag = 1;
	}
	else                  //查找失败
	{ 
		r.pt = q;
		r.i = i;
		r.tag = 0;
	}
	return r;                //返回关键字k的位置(或者插入位置)
} 

void InsertBTNode(BTNode *&p, int i, KeyType k, BTNode *q,int a)   //在m阶B-树上结点*q的key[i]和key[i+1]之间插入关键字k
{
	int j;
	for (j = p->keynum;j > i;j--)          //整体后移空出一个位置
	{
		strcpy(p->key[j + 1], p->key[j]);
		p->recptr[j + 1] = p->recptr[j];
		p->ptr[j + 1] = p->ptr[j];
	}
	strcpy(p->key[i + 1], k);
	p->recptr[i + 1] = a;
	p->ptr[i + 1] = q;
	if (q != NULL)
		q->parent = p;
	p->keynum++;
}

void SplitBTNode(BTNode*&p, BTNode *&q)   //将p->key[s+1,...,m],p->ptr[s+1,...,m]和p->recptr[s+1,...,m]移入新节点q
{
	int i;
	int s = (m + 1) / 2;
	q = (BTNode *)malloc(sizeof(BTNode));    //给结点q分配空间
	q->ptr[0] = p->ptr[s];
	for (i = s + 1;i <= m;i++)       //后一半移入结点q
	{
		strcpy(q->key[i - s], p->key[i]);
		q->recptr[i - s] = p->recptr[i];
		q->ptr[i - s] = p->ptr[i];
	}
	q->keynum = p->keynum - s;
	q->parent = p->parent;
	for (i = 0;i <= p->keynum - s;i++)     //修改双亲指针
	{
		if (q->ptr[i] != NULL)
			q->ptr[i]->parent = q;
	}
	p->keynum = s - 1;               //结点p的前一半保留,修改结点p的keynum
}

void NewRoot(BTNode *&t, KeyType k, BTNode *p, BTNode *q,int a)   //s生成含信息(p,q,k)的新的根节点*t,q和p为子树指针
{
	t = (BTNode *)malloc(sizeof(BTNode));    //分配空间
	t->keynum = 1;
	t->ptr[0] = p;
	t->ptr[1] = q;
	strcpy(t->key[1], k);
	t->recptr[1] = a;
	if (p != NULL)             //调整结点p和q的双亲指针
		p->parent = t;
	if (q != NULL)
		q->parent = t;
	t->parent = NULL;
}

void InsertBTree(BTree &t, int i, KeyType k, BTNode *p,int a)
{
	BTNode *q; 
	int b;
	int finish_tag, newroot_tag, s;          //设定需要新结点标志和插入完成标志
	KeyType x;
	if (p == NULL)           //t是空树
		NewRoot(t, k, NULL, NULL,a);             //生成仅含关键字k的根结点t
	else
	{
		strcpy(x,k);
		b = a;
		q = NULL;
		finish_tag = 0;
		newroot_tag = 0;
		while (finish_tag == 0 && newroot_tag == 0)
		{
			InsertBTNode(p, i, x, q,b);  //将关键字x和结点q分别插入到p->key[i+1]和p->[i+1]
			if (p->keynum <= Max)        //插入完成
				finish_tag = 1;
			else
			{
				s = (m + 1) / 2;
				SplitBTNode(p, q);         //分裂结点
				strcpy(x, p->key[s]);
				b = p->recptr[s];
				if (p->parent)         //查找x 的插入位置
				{ 
					p = p->parent;
					i = SearchBTNode(p, x);
				}
				else                    //没找到x,需要新结点
					newroot_tag = 1;
			}
		}
		if (newroot_tag == 1)              //根节点已分裂为p和q
			NewRoot(t, x, p, q,b);          //生成新的根结点,p和q都是子树指针
	}
}

int main()
{
	BTNode *t = NULL;
	Result s,i;
	int j, n = 10;
	KeyType k;
	KeyType a[10], str = {0};
	char ch[15];
	int b[10];
	srand((unsigned int)time(0));
	for (int k = 0;k < 10;k++)
	{
		b[k] = (rand()<<7)+rand();      //左移7位生成随机数
		for (int j = 0; j < 4; j++) { str[j] = rand() % 26 + 'A'; }    //随机生成字符串
		strcpy(a[k], str);
	}
	for (j = 0;j < n;j++)
	{
		s = SearchBTree(t, a[j]);
		if (s.tag == 0)
			InsertBTree(t, s.i, a[j], s.pt,b[j]);
	}
	puts("电话查询系统:");
	puts("------------------------------------------------------------------");
	for (int k = 0;k < 10;k++)
	{
		printf("%s:%d\t", a[k], b[k]);
		if (0 == k % 5&&k!=0)
			printf("\n");
	}
	printf("\n");
	puts("------------------------------------------------------------------");
	while (*ch != '0')
	{
		puts("请输入你想要找的人的姓名(区别大小写),输入0表示结束");
		scanf("%s", ch);
		i = SearchBTree(t, ch);
		if (i.tag == 0)
			puts("没有找到这个人");
		else
			printf("这个人的电话号码是:%d\n", i.pt->recptr[i.i]);
	}

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值