数据结构|AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)
B-树自在人心|搞定B树的原理与实现
B树原理
B树也是一种平衡树,它有以下几个明显特征:
高度一致:所有叶子结点在同一个水平!
结点阶数m
结点阶数m即为该结点指针数目,最大子树数目
m阶数也称m叉树
如3阶B-树中,阶树m值为3;关键字最大值n为2
结点子树(非空指针数目)
每个结点至多有m颗子树
根结点子树非空子树: 2~m
终端结点子树非空子树:0 但是空指针数为m个
非根结点和非终端结点非空子树:[m/2]~m个
若根结点不是叶子结点,则至少有两颗子树
(这句话我的理解是,如果一颗树只有一个结点,即根结点也是叶子结点,那么该结点无非空子树,否则其他除叶子结点之外的所有结点,都一定要有两颗以上非空子树)
结点关键字树n
结点内存放的元素称为关键字
结点的关键字个数范围从0<=n<m-1个
根节点关键字数:1<=n<=m-1
终端结点,至少有[m/2]-1<=n<=m-1个关键字;
如3阶B-树终端结点关键字为:[3/2]-1=1
5阶B-树终端结点关键字个数为:[5/2]-1=2
非根结点和非终端结点关键字数目n为:[m/2]-1<=n<=m-1
[]为向上取整
归纳
从根结点开始:根结点关键字数至少为1,最多为m-1;非空子树为2~m
非根结点和非终端结点的中间结点:关键字数为至少[m/2]-1至多m-1;非空子树至少[m/2],至多m;
终端结点:关键字数至少[m/2]-1至多m-1;非空子树为0,空子树为m
一颗B-树总共有n个关键字,则空孩子树总共有n+1个。
(每个结点子树一定有m颗,分为空和非空子树)
高度
包括终端结点的子树(NULL,型):所有叶子结点都必须处于同一高度
例
上面左图中,虽然子树不存在,其高度也不存在,但是他们的叶子结点是在同一水平线上(虽然不存在)
但在右图中,5的叶子结点从9出发一直查找到了最下面的第四层null,而最近的叶子结点,从3出发,到第3层停止了。
所以右图不满足B-树。
插入B-树
插入B树的时候要满足**[m/2]**的原则:
当一个结点的关键字数目达到了m个时,要从[m/2]开始拆开这个结点
B树自在人心,看不懂,我当场把这个树吃掉!
B树实现
C语言-B树(B-树)的完整实现
代码是参照这个大佬的,它的删除部分有不足,而且未考虑重复项。
首先,得理清思路,B树不像之前的数据结构那么直接
1.当我插入一个关键字时,首先要查找这个关键字应该插在哪个位置!
2.如果是插入的第一个元素时,和后续的元素时 会有不同的处理方式
因为第一个元素,就没必要去进行查找过程,直接插入第一个位置就行
查找过程
/*这里查找到的i都是是有实际元素的关键字位置,当i不是第一个关键字时,
i+1都是和插入的关键字k有关,要不是它后面插入的位置,要不是树的这个结点,
往下面继续查找的孩子指针*/
Status SerchBTNode(BTNode *p, KeyType x)
{
int i;
for (i = 0; i < p->KeyNum&&p->key[i + 1] <= x; i++);//所有插入的位置 都是i后面一个
return i;
}
Result SerchBTree(BTree tree, KeyType k)
{
BTNode *q = tree, *p = NULL;
int found_tag = 0, i = 0;
Result r;
while (q != NULL&&found_tag == 0)//found_tag 是查找成功的标志
{ //当空树时没有必须查找
i = SerchBTNode(q, k);//查找k适合插入的位置
if (i > 0 && q->key[i] == k)
{
found_tag = 1;
}
else
{
p = q;
q = q->child[i];//这一步很关键,如果在第一个结点没有查到,而根据其大小,在第一个结点找到其孩子指针,然后这里转向孩子 指针 继续查找
}
}
if (found_tag == 1){
r.i = i;
r.p = q;
r.tag = 1;
}
else
{
r.i = i;
r.p = p;
r.tag = 0;
}
return r;
}
上面四个是插入过程的函数,还涉及到两个查找原结点中是否有关键字k的函数 并返回相应的结构体,结构体保存了,被插入的结点,插入的位置(找到的是i,但插入的是i+1)
这一步是往其孩子结点找到适合的位置(在这之前找到了,其适合的孩子位置)
p = q;
q = q->child[i];
插入过程
插入过程涉及四个环节想想就
第一:当树不存在,此时插入的关键字将成为根结点元素
所以要新生成树的结点
当是树的第一个结点时,其孩子 父亲结点都为NULL
而这个函数也会出现在后面第四种环节中
当要拆开一个结点时,是一变二,也可能是一变三
何为一变三,即要分离的那个关键字,往上走时,上面就没有结点,这个时候要新生成一个结点,其孩子指针是分裂的两个结点
/*生成新的结点*/
void NewRoot(BTNode *&tree, KeyType k, BTNode *p, BTNode *q)
{
tree = new Node;
tree->key[1] = k;
tree->KeyNum = 1;
tree->child[0] = p;
tree->child[1] = q;
/*非根结点的话 pq可能存在合法的结点 就要修改其父结点*/
if (p != NULL)
{
p->parent = tree;
}
if (q != NULL)
{
q->parent = tree;
}
tree->parent = NULL;
}
第二个环节就是,当树不是一个空树时,(可能要面临拆开的环节)
上面说了空树新生一个结点
那么树不为空树时,这里的一个过程就是在查找过程所得的结点位置p i(i+1),插入关键字k,当然也要新插入一个空孩子
void InSertBTNode(BTNode *&p, int i, KeyType k, BTNode *q)
{
/*在第i个位置 插入*/
/*涉及到两种情况 1.插在末尾;2.插在中间*/
int j;
for (j = p->KeyNum; j > i; j--)/*往后移*/
{
p->key[j + 1] = p->key[j];
p->child[j + 1] = p->child[j];
}
p->key[i + 1] = k;/*前面查找过程 所得的i是插入的前一个位置坐标*/
p->child[i + 1] = q;
if (q != NULL)
{
q->parent = p;
}
p->KeyNum++;//关键字数目+1
}
当插入成功之后,就要判断是否需要拆分(所以在定义结构体时多保留一位)
//将结点p分裂成两个结点,前一半保留,后一半移入结点q
void SplitBTNode(BTNode *&p, BTNode *&q)
{
q = new Node; //给结点q分配空间
int s = NUM;
q->child[0] = p->child[s]; //后一半移入结点q 并置空
p->child[s] = NULL;
for (int i = s + 1; i <= m; i++)
{
q->child[i - s] = p->child[i];
q->key[i - s] = p->key[i];
p->child[i] = NULL;
p->key[i] = NULL;
}
/*修改结构体内容*/
q->KeyNum = p->KeyNum - s;
q->parent = p->parent;
/*修改 拿过来的子树双亲*/
for (int j = 0; j <= q->KeyNum; j++)
{
if (q->child[j] != NULL)
{
q->child[j]->parent = q;
}
}
p->KeyNum = s - 1;
}
/*完成插入的函数
/*在一颗树的结点p的第i个位置插入关键字k*/
void InsertBTree(BTree &tree, int i, KeyType k, BTNode *p){
/*插入一个新的关键字的话,同时也会生成一个新的子树 尽管可能为空*/
BTNode *q;
int finish_tag, newroot_tag;/*插入成功的标志 和新的结点生成的标志*/ /*如果超过最大关键字数 而往上走没有合适的结点放时就要生成新的结点*/
KeyType x;
if (p == NULL)
{
/*插入的时候 树空 结点也空*//*这时候就要新生成一颗树了*/
NewRoot(tree, k, NULL, NULL);
}
else
{
x = k;
q = NULL;
finish_tag = 0, newroot_tag = 0;
while (finish_tag == 0 && newroot_tag == 0)
{
InSertBTNode(p, i, x, q);/*插入结点p中 关键字插入也要生成一个子树(即为空)*/
if (p->KeyNum <= MAX)/*判断关键字数目是否合法*/
{
finish_tag = 1;/*合法 则修改循环标志 插入成功*/
}
else/*不合法 即关键字数多了 则拆分结点*/
{
int s = NUM;
SplitBTNode(p, q);/*以s为分界线 将p拆开 */
/*处理分割点上的元素 两种情况*/
x = p->key[s];
if (p->parent)/*如果双亲存在 寻找适合插入的位置*/
{
p = p->parent;
i = SerchBTNode(p, x);
//i = SerchBTNode(p->parent, x);/*回到while循环 又开始一场新的插入位置 此时 分裂结点 将变为新的插入结点*/
}
else{/*向上走 没有合适的可以插入 那么新生成一个结点咯*/
newroot_tag = 1;
}
}
}
if (newroot_tag == 1){/*要生成新的结点 说明 原来拆散的是根结点 这里将新生成一个新的根结点*/
NewRoot(tree, x, p, q);
}
}
}
打印输出
直接输出
BTree PrintTree(BTree &tree)
{
if (tree != NULL){
cout << "[";
for (int i = 1; i <= tree->KeyNum; i++)
{
cout << " " << tree->key[i];
}
cout << " ]" ;
for (int j = 0; j < tree->KeyNum+1; j++)
{
if (tree->child[j] != NULL){
PrintTree(tree->child[j]);
}
}
}
return tree;
}
有顺序的打印输出(学到了)
思路:从上之下,从左往右
而这个不是简单的二叉树,两个遍历就行
要用到队列:
先将第一行第一个结点的左子树放入队列的第一个有效结点
然后将第一行结点的关键字依序输出,且将后面的孩子树,依次存入队列
Enqueue(L, tree->child[0]);/*现将最左边的孩子结点入队列*/ //每次入队列 都是排在最后,所以达到了一行一行的处理
for (i = 1; i <= tree->KeyNum; i++){
cout << tree->key[i]<<" ";
Enqueue(L, tree->child[i]);/*后面的孩子接着上*/
}
这样在输入第一行的关键字同时,达到了孩子树入队的效果,且后入的再后面
sum += tree->KeyNum + 1;//用来记录存放的孩子数目
后面根据标识来判断是是否需要换行
if (newLine == 0){
newLine = sum - 1;/*即将处理一个孩子 结点 所以记录下其他孩子数*/
sum = 0;/*归0,后续处理*/
cout << endl;
}
else{
newLine--;
}
删除过程
删除过程原理
删除部分代码实现
1.先找到被删的关键字所在位置(P1,i1)
2.判断是终端结点(直接删)还是非终端结点(寻找合适的叶子结点关键字取代它,然后变成删除叶子结点)
3.并把该结点(P1)其他关键字做排序移位置处理
4.根据被删结点的关键字数量进行判断是否需要做调整
5.调整看我那部分代码就行 左旋右旋 左合并右合并。
查找被删除的关键字,返回其所在结点,和位置
//先查找要删除的关键字,在树中所在的结点关键字数组中的位置
//返回值有三种,结点不存在时返回0
//存在时返回1
//该结点中没有,但是可以从该结点往下走,返回下一个孩子指针的位置
Status FindKeyTree(BTNode *p, int &i,KeyType k)
{
if (k<p->key[1]){
//cout << "该结点中不存在该关键字"<< endl;
i = 0;//关键字数组0不存储元素
return 0;
}
else{
i = p->KeyNum;
while (k < p->key[i] && i>1){
i--;
}
if (k == p->key[i]){
return 1;
}
else
return 0;
}
}
删除关键字
/*直接删除——关键字数目大于最小关键字数,可以直接删去*/
/*直接删除结点中位置i处的关键字——适用于,结点中关键字大于最小关键字数的终端结点*/
/*删除值直接将关键字往前移,在右边空了出来(后面赋值处理)*/
void ReMove(BTNode *p, int i){
int j;
for (j = i + 1; j <= p->KeyNum; j++){
p->key[j - 1] = p->key[j];
p->child[j - 1] = p->child[j];
}
p->key[p->KeyNum] = '\0';
p->KeyNum--;
}
相对于根结点,向左右子树借关键字
//是位于相对根结点向左子树借最大关键字,和位于相对根结点向柚子树借最小关键字——这里只是复制了一下
void SubstitutionLeft(BTNode *p, int i)
{
//p是寻找到相对根结点,i是其中需要替换的关键字的位置,思路寻找左子树最大关键字
BTNode *q;
for (q = p->child[i - 1]; q->child[q->KeyNum] != NULL; q = q->child[q->KeyNum]);
p->key[i] = q->key[q->KeyNum - 1];
}
void SubstitutionRight(BTNode *p, int i)//向右寻找
{
BTNode *q;
for (q = p->child[i]; q->child[0] != NULL; q = q->child[0]);
p->key[i] = q->key[1];
}
删除之后的左旋转,右旋转
这里一定要注意p和i是指向被删关键字的双亲位置,和双亲指向被删关键字的指针。
这部分一定要自己画个图就会有助于理解其中含义(上面有),重点不仅是如何旋转,还有i p的位置。
左旋,右旋
/*这里的左旋 右旋是在删除了关键字之后,对树做调整而写*/
//所以p是被删关键字的结点,i是指向关键字的的孩子指针位置
void MoveRight(BTNode *p, int i)
{
int j;
BTNode *q=p->child[i];//被删除的结点
BTNode *aq = p->child[i - 1];//被借的兄弟结点 当用到这个兄弟结点时,已经筛选了,它的关键字数大于最小量才用
//而且选取左子树最大一个取代
/*之前删除的时候,已经将被删结点,做了往左走的处理*/
/*父结点i处的元素应该赋予在被删结点的最左边,所以要做右走处理*/
for (j = q->KeyNum; j >= 1; j--){
q->key[j + 1] = q->key[j];
q->child[j + 1] = q->child[j];
}
q->child[1] = q->child[0];
q->key[1] = p->key[i];
q->KeyNum++;
/*左子树到相对根结点*/
p->key[i] = aq->key[aq->KeyNum];
p->child[i]->child[0] = aq->child[aq->KeyNum];//在向左兄弟借孩子时,把它的最大孩子指针,带到右兄弟的最小孩子指针
aq->KeyNum--;
}
void MoveLeft(BTNode *p, int i)/*左旋*/
{
int j;
BTNode *q, *aq;
q = p->child[i+1];//右兄弟借孩子
aq = p->child[i];//左兄弟被删的
/*左旋转,相对根的关键字放在,左子树的最大关键字处*/
//而删除操作时,最大位置处是做空的
aq->key[aq->KeyNum + 1] = p->key[i + 1];
aq->KeyNum++;
aq->child[aq->KeyNum] = p->child[i + 1]->child[0];/*画图得知,向右兄弟借孩子的同时,把它的0号孩子指针赋予左兄弟的最大孩子指针*/
/*把右兄弟的孩子(选择最小的)替换掉父结点关键字*/
p->key[i+1]=q->key[1];
q->child[0]=q->child[1];
//向前移
for (j = 2; j <= q->KeyNum; j++){
q->key[j-1]=q->key[j];
q->child[j - 1] = q->child[j];
}
q->KeyNum--;
}
左合并 右合并
/*,被删结点的左兄弟不能合并与右兄弟合并*/
void CombineRight(BTNode *p, int i)
{
int j,count=0;
BTNode *q = p->child[i];//被删结点 /*左兄弟是被删的,且最右边已经空了*/
BTNode *aq=p->child[i+1];//右兄弟
q->KeyNum++;/*步骤1:先将双亲结点第i+1个元素拿到左兄弟中*/
q->key[q->KeyNum] = p->key[i+1];
for (j = i+2; j <= p->KeyNum; j++)/*步骤2:把根结点、孩子指针往左移一个*/
{
p->key[j - 1] = p->key[j];
p->child[j - 1] = p->child[j];
}
p->child[p->KeyNum] = NULL;
p->KeyNum--;
/*步骤3:将右兄弟的关键字与左兄弟合并*/
q->child[q->KeyNum]=aq->child[0];
for (j = 1; j <= aq->KeyNum; j++)
{
q->key[q->KeyNum + j] = aq->key[j];
q->child[q->KeyNum+j]=aq->child[j];
count++;
}
q->KeyNum += count;
free(aq);
}
void CombineLeft(BTNode *p, int i){
/*也是向左合并*/
int j, count = 0;
BTNode *q = p->child[i];/*右兄弟是被删的,且最其右边已经空了*/
BTNode *aq = p->child[i - 1];//左兄弟
/*步骤1:先将双亲结点一个元素拿到右兄弟中的第一个元素*/
for (j = q->KeyNum; j >= 1; j--){/*对右兄弟进行右移处理*/
q->key[j+1]=q->key[j];
q->child[j+1]=q->child[j];
}
q->child[1]=q->child[0];
q->key[1]=p->key[i];
p->key[i] = '\0';
q->KeyNum++;
/*步骤2:将被删结点往左兄弟移*/
for (j = 1; j <= q->KeyNum; j++){
aq->key[aq->KeyNum + j] = q->key[j];
aq->child[aq->KeyNum + j] = q->child[j];
q->key[j] = '\0';
q->child[j] = NULL;
count++;
}
aq->KeyNum += count;
q->KeyNum -= count;
/*步骤3: 将根结点左移*/
for (j = i + 1; j <= p->KeyNum; j++){
p->key[j - 1] = p->key[j];
p->child[j - 1] = p->child[j];
}
p->KeyNum--;
free(q);
}
完整代码
#include<iostream>
using namespace std;
#define m 3
#define MAXM 15
const int MAX = m - 1;//关键字结点数目
const int NUM = (m + 1) / 2;//分割点 [m/2]向上取整
const int MIN = NUM - 1;
typedef int KeyType;
typedef int Status;
typedef struct Node{
int KeyNum;
KeyType key[MAXM];//原本想在刚刚好的数组,但是如果关键字数目大于m-1时,这是关键字x就必须先将结点拆开 才能放,目前来看麻烦//所以选择将数组扩大一个
struct Node *parent;
struct Node *child[MAXM];
}BTNode, *BTree;
//插入之前,在树中查找是否存在关键字x;
//存在即插入失败,//不存在即可以插入,记下插入的结点 结点中关键字的位置 和记录查找 成功与否的标志
typedef struct Result{
Node *p;
int i;
int tag;
}Result;
/*为了有型输出B树,需要构造一个队列 其内容是BTree*/
typedef struct LNode{
BTree data;
LNode *next;
}*LNodeList;
/*输出所用的一个链表*//*现在搞生成 这个后面说 不就是多叉树嘛*/
void InsertBTree(BTree &tree, int i, KeyType k, BTNode *p);
void NewRoot(BTNode *&tree, KeyType k, BTNode *p, BTNode *q);
void InSertBTNode(BTNode *&p, int i, KeyType k, BTNode *q);
void SplitBTNode(BTNode *&p, BTNode *&q);
/*上面四个是插入过程的函数,还涉及到两个查找原结点中是否有关键字k的函数 并返回相应的结构体*/
Result SerchBTree(BTree tree, KeyType k);/*查找树中的 k字*/
Status SerchBTNode(BTNode *p, KeyType x);/*在树的具体结点中查找k字*/
/*判断链表是否为空*/
Status IfEmpty(LNodeList L);
Status SerchBTNode(BTNode *p, KeyType x)
{
int i;
for (i = 0; i < p->KeyNum&&p->key[i + 1] <= x; i++);//所有插入的位置 都是i后面一个
return i;
}
Result SerchBTree(BTree tree, KeyType k)
{
BTNode *q = tree, *p = NULL;
int found_tag = 0, i = 0;
Result r;
while (q != NULL&&found_tag == 0)//found_tag 是查找成功的标志
{
i = SerchBTNode(q, k);
if (i > 0 && q->key[i] == k)
{
found_tag = 1;
}
else
{
p = q;
q = q->child[i];//这一步很关键,如果在第一个结点没有查到,而根据其大小,在第一个结点找到其孩子指针,然后这里转向孩子 指针 继续查找
}
}
if (found_tag == 1){
r.i = i;
r.p = q;
r.tag = 1;
}
else
{
r.i = i;
r.p = p;
r.tag = 0;
}
return r;
}
/*在一颗树的结点p的第i个位置插入关键字k*/
void InsertBTree(BTree &tree, int i, KeyType k, BTNode *p){
/*插入一个新的关键字的话,同时也会生成一个新的子树 尽管可能为空*/
BTNode *q;
int finish_tag, newroot_tag;/*插入成功的标志 和新的结点生成的标志*/ /*如果超过最大关键字数 而往上走没有合适的结点放时就要生成新的结点*/
KeyType x;
if (p == NULL)
{
/*插入的时候 树空 结点也空*//*这时候就要新生成一颗树了*/
NewRoot(tree, k, NULL, NULL);
}
else
{
x = k;
q = NULL;
finish_tag = 0, newroot_tag = 0;
while (finish_tag == 0 && newroot_tag == 0)
{
InSertBTNode(p, i, x, q);/*插入结点p中 关键字插入也要生成一个子树(即为空)*/
if (p->KeyNum <= MAX)/*判断关键字数目是否合法*/
{
finish_tag = 1;/*合法 则修改循环标志 插入成功*/
}
else/*不合法 即关键字数多了 则拆分结点*/
{
int s = NUM;
SplitBTNode(p, q);/*以s为分界线 将p拆开 */
/*处理分割点上的元素 两种情况*/
x = p->key[s];
p->key[s] = '\0';
if (p->parent)/*如果双亲存在 寻找适合插入的位置*/
{
p = p->parent;
i = SerchBTNode(p, x);/*回到while循环 又开始一场新的插入位置 此时 分裂结点 将变为新的插入结点*/
}
else{/*向上走 没有合适的可以插入 那么新生成一个结点咯*/
newroot_tag = 1;
}
}
}
if (newroot_tag == 1){/*要生成新的结点 说明 原来拆散的是根结点 这里将新生成一个新的根结点*/
NewRoot(tree, x, p, q);
}
}
}
/*生成新的结点*/
void NewRoot(BTNode *&tree, KeyType k, BTNode *p, BTNode *q)
{
tree = new Node;
tree->key[1] = k;
tree->KeyNum = 1;
tree->child[0] = p;
tree->child[1] = q;
/*非根结点的话 pq可能存在合法的结点 就要修改其父结点*/
if (p != NULL)
{
p->parent = tree;
}
if (q != NULL)
{
q->parent = tree;
}
tree->parent = NULL;
}
void InSertBTNode(BTNode *&p, int i, KeyType k, BTNode *q)
{
/*在第i个位置 插入*/
/*涉及到两种情况 1.插在末尾;2.插在中间*/
int j;
for (j = p->KeyNum; j > i; j--)/*往后移*/
{
p->key[j + 1] = p->key[j];
p->child[j + 1] = p->child[j];
}
p->key[i + 1] = k;/*前面查找过程 所得的i是插入的前一个位置坐标*/
p->child[i + 1] = q;
if (q != NULL)
{
q->parent = p;
}
p->KeyNum++;//关键字数目+1
}
//将结点p分裂成两个结点,前一半保留,后一半移入结点q
void SplitBTNode(BTNode *&p, BTNode *&q)
{
q = new Node; //给结点q分配空间
int s = NUM;
q->child[0] = p->child[s]; //后一半移入结点q 并置空
p->child[s] = NULL;
for (int i = s + 1; i <= m; i++)
{
q->child[i - s] = p->child[i];
q->key[i - s] = p->key[i];
p->child[i] = NULL;
p->key[i] = NULL;
}
/*修改结构体内容*/
q->KeyNum = p->KeyNum - s;
q->parent = p->parent;
/*修改 拿过来的子树双亲*/
for (int j = 0; j <= q->KeyNum; j++)
{
if (q->child[j] != NULL)
{
q->child[j]->parent = q;
}
}
p->KeyNum = s - 1;
}
/*输出所有的关键字*//*但是无法按照顺序来*/
BTree PrintTree(BTree &tree)
{
if (tree != NULL){
cout << "[";
for (int i = 1; i <= tree->KeyNum; i++)
{
cout << " " << tree->key[i];
}
cout << " ]" ;
for (int j = 0; j < tree->KeyNum+1; j++)
{
//tree = tree->child[j];
if (tree->child[j] != NULL){
/*if (j == 0){
cout << endl;
}*/
PrintTree(tree->child[j]);
}
/*cout <<" ";
if (j == tree->KeyNum){
cout << endl;
}*/
}
}
return tree;
}
/*尝试有形输出平衡树*/
//初始化一个队列
Status InitLinkList(LNodeList &L)
{
L = new LNode;
if (L == NULL){
return NULL;
}
L->next = NULL;
return 0;
}
/*队列构造成功之后,就要尝试将以B树 结点为内容的元素入队列了*/
Status Enqueue(LNode *L, BTNode *q){
if (L == NULL){
InitLinkList(L);
}
/*先进先出*/ /*所以找到最后一个结点,并在最后一个结点后面 新生成一个结点接上,并把树结点存进去*/
while (L->next != NULL){
L = L->next;
}
LNode *p;/*分配一个新的结点*/
p = new LNode;
if (p != NULL){
p->data = q;
p->next = NULL;
}
L->next = p;/*如果是第一个 也是存在 L->next*/
return 1;
}
/*出队列*//*并以结点q返回*/
Status Dequeue(LNode *L,BTNode *&q){
if (L == NULL || L->next == NULL)
{
return 0;
}
/*因为要从头开始释放,且每次只释放一个*/
LNode *p;
//p = new LNode;
p=L->next; //设置一个中间结点,寻找到的下一个结点赋予它,并改变前后指针
L->next = p->next;
q = p->data;
free(p);
return 0;
}
/*实现用队列有序输出B树*/
/*主要难在 一行一行的输出*/
Status PrintBTree(BTree tree,LNodeList L,int newLine,int sum){
int i;
BTree p;
if (tree != NULL)
{
cout << " [ ";
Enqueue(L, tree->child[0]);/*现将最左边的孩子结点入队列*/ //每次入队列 都是排在最后,所以达到了一行一行的处理
for (i = 1; i <= tree->KeyNum; i++){
cout << tree->key[i]<<" ";
Enqueue(L, tree->child[i]);/*后面的孩子接着上*/
}
sum += tree->KeyNum + 1;/*记录存入了多少个孩子*/
cout <<"]";
/*用newLine来判断 是否需要孩子结点处理完毕*/
if (newLine == 0){
newLine = sum - 1;/*即将处理一个孩子 结点 所以记录下其他孩子数*/
sum = 0;/*归0,后续处理*/
cout << endl;
}
else{
newLine--;
}
}
if (IfEmpty(L) == 1){
Dequeue(L,p);
PrintBTree(p,L,newLine,sum);
}
return 0;
}
Status IfEmpty(LNodeList L){
if (L == NULL) //队列不存在
return 0;
if (L->next == NULL) //队列为空
return 0;
return 1; //队列非空
}
/***************************//*在树中删除关键字时的操作*//***************************/
//先查找要删除的关键字,在树中所在的结点关键字数组中的位置
//返回值有三种,结点不存在时返回0
//存在时返回1
//该结点中没有,但是可以从该结点往下走,返回下一个孩子指针的位置
Status FindKeyTree(BTNode *p, int &i,KeyType k)
{
if (k<p->key[1]){
//cout << "该结点中不存在该关键字"<< endl;
i = 0;//关键字数组0不存储元素
return 0;
}
else{
i = p->KeyNum;
while (k < p->key[i] && i>1){
i--;
}
if (k == p->key[i]){
return 1;
}
else
return 0;
}
}
/****************************//*具体删除操作*//************************/
/*直接删除——关键字数目大于最小关键字数,可以直接删去*/
/*直接删除结点中位置i处的关键字——适用于,结点中关键字大于最小关键字数的终端结点*/
/*删除值直接将关键字往前移,在右边空了出来(后面赋值处理)*/
void ReMove(BTNode *p, int i){
int j;
for (j = i + 1; j <= p->KeyNum; j++){
p->key[j - 1] = p->key[j];
p->child[j - 1] = p->child[j];
}
p->key[p->KeyNum] = '\0';
p->KeyNum--;
}
/************//*关键字数目等于最小关键字数目,不能直接删,左右兄弟有大于最小关键数目,向他们借孩子*///**************
//是位于相对根结点向左子树借最大关键字,和位于相对根结点向柚子树借最小关键字——这里只是复制了一下
void SubstitutionLeft(BTNode *p, int i)
{
//p是寻找到相对根结点,i是其中需要替换的关键字的位置,思路寻找左子树最大关键字
BTNode *q;
for (q = p->child[i - 1]; q->child[q->KeyNum] != NULL; q = q->child[q->KeyNum]);
p->key[i] = q->key[q->KeyNum - 1];
}
void SubstitutionRight(BTNode *p, int i)//向右寻找
{
BTNode *q;
for (q = p->child[i]; q->child[0] != NULL; q = q->child[0]);
p->key[i] = q->key[1];
}
/*这里的左旋 右旋是在删除了关键字之后,对树做调整而写*/
//所以p是被删关键字的结点,i是指向关键字的的孩子指针位置
void MoveRight(BTNode *p, int i)
{
int j;
BTNode *q=p->child[i];//被删除的结点
BTNode *aq = p->child[i - 1];//被借的兄弟结点 当用到这个兄弟结点时,已经筛选了,它的关键字数大于最小量才用
//而且选取左子树最大一个取代
/*之前删除的时候,已经将被删结点,做了往左走的处理*/
/*父结点i处的元素应该赋予在被删结点的最左边,所以要做右走处理*/
for (j = q->KeyNum; j >= 1; j--){
q->key[j + 1] = q->key[j];
q->child[j + 1] = q->child[j];
}
q->child[1] = q->child[0];
q->key[1] = p->key[i];
q->KeyNum++;
/*左子树到相对根结点*/
p->key[i] = aq->key[aq->KeyNum];
p->child[i]->child[0] = aq->child[aq->KeyNum];//在向左兄弟借孩子时,把它的最大孩子指针,带到右兄弟的最小孩子指针
aq->KeyNum--;
}
void MoveLeft(BTNode *p, int i)/*左旋*/
{
int j;
BTNode *q, *aq;
q = p->child[i+1];//右兄弟借孩子
aq = p->child[i];//左兄弟被删的
/*左旋转,相对根的关键字放在,左子树的最大关键字处*/
//而删除操作时,最大位置处是做空的
aq->key[aq->KeyNum + 1] = p->key[i + 1];
aq->KeyNum++;
aq->child[aq->KeyNum] = p->child[i + 1]->child[0];/*画图得知,向右兄弟借孩子的同时,把它的0号孩子指针赋予左兄弟的最大孩子指针*/
/*把右兄弟的孩子(选择最小的)替换掉父结点关键字*/
p->key[i+1]=q->key[1];
q->child[0]=q->child[1];
//向前移
for (j = 2; j <= q->KeyNum; j++){
q->key[j-1]=q->key[j];
q->child[j - 1] = q->child[j];
}
q->KeyNum--;
}
/*,被删结点的左兄弟不能合并与右兄弟合并*/
void CombineRight(BTNode *p, int i)
{
int j,count=0;
BTNode *q = p->child[i];//被删结点 /*左兄弟是被删的,且最右边已经空了*/
BTNode *aq=p->child[i+1];//右兄弟
q->KeyNum++;/*步骤1:先将双亲结点第i+1个元素拿到左兄弟中*/
q->key[q->KeyNum] = p->key[i+1];
for (j = i+2; j <= p->KeyNum; j++)/*步骤2:把根结点、孩子指针往左移一个*/
{
p->key[j - 1] = p->key[j];
p->child[j - 1] = p->child[j];
}
p->child[p->KeyNum] = NULL;
p->KeyNum--;
/*步骤3:将右兄弟的关键字与左兄弟合并*/
q->child[q->KeyNum]=aq->child[0];
for (j = 1; j <= aq->KeyNum; j++)
{
q->key[q->KeyNum + j] = aq->key[j];
q->child[q->KeyNum+j]=aq->child[j];
count++;
}
q->KeyNum += count;
free(aq);
}
void CombineLeft(BTNode *p, int i){
/*也是向左合并*/
int j, count = 0;
BTNode *q = p->child[i];/*右兄弟是被删的,且最其右边已经空了*/
BTNode *aq = p->child[i - 1];//左兄弟
/*步骤1:先将双亲结点一个元素拿到右兄弟中的第一个元素*/
for (j = q->KeyNum; j >= 1; j--){/*对右兄弟进行右移处理*/
q->key[j+1]=q->key[j];
q->child[j+1]=q->child[j];
}
q->child[1]=q->child[0];
q->key[1]=p->key[i];
p->key[i] = '\0';
q->KeyNum++;
/*步骤2:将被删结点往左兄弟移*/
for (j = 1; j <= q->KeyNum; j++){
aq->key[aq->KeyNum + j] = q->key[j];
aq->child[aq->KeyNum + j] = q->child[j];
q->key[j] = '\0';
q->child[j] = NULL;
count++;
}
aq->KeyNum += count;
q->KeyNum -= count;
/*步骤3: 将根结点左移*/
for (j = i + 1; j <= p->KeyNum; j++){
p->key[j - 1] = p->key[j];
p->child[j - 1] = p->child[j];
}
p->KeyNum--;
free(q);
}
/*k关键字已经删除,要调整树,p是关键字k的父结点,i是父结点走向k所在结点的孩子指针数*/
void AdjustBTree(BTNode *p, int i){
if (i == 0){ //如果删除的是第一个1个孩子指针
if (p->child[1]->KeyNum > MIN) //右孩子可以借
MoveLeft(p,i);
else{
CombineRight(p, i); //不能借,只能合并
}
}
else if (i == p->KeyNum) //如果删除的是最后一个孩子指针
{
if (p->child[i - 1]->KeyNum > MIN){ //左孩子可借
MoveRight(p, i);
}
else
{
CombineLeft(p, i); //都不能借,只能合并
}
}
else //如果删除的是中间部分的关键字
{
if (p->child[i - 1]->KeyNum > MIN){//左孩子可借
MoveRight(p, i);
}
else if (p->child[i+1]->KeyNum > MIN){//右孩子可借
MoveLeft(p, i);
}
else{
CombineRight(p, i); //都不可以借 只能合并
}
}
}
int BTNodeDelete(BTNode *p, KeyType k){
//在结点p中查找并删除关键字k
int i;
int found_tag; //查找标志
if (p == NULL)
return 0;
else{
found_tag = FindKeyTree(p, i, k); //返回查找结果
if (found_tag == 1)//查找函数返回为1时就是,查找成功 i值就是该删除关键字的位置
{
if (p->child[i] != NULL){ //删除的是非叶子结点
SubstitutionRight(p, i); //寻找相邻关键字(右子树中最小的关键字)
BTNodeDelete(p->child[i], p->key[i]); //执行删除操作
}
else if (p->child[i-1] != NULL){
SubstitutionLeft(p,i);
BTNodeDelete(p->child[i],p->key[i]);
}
else{
ReMove(p, i); //从结点p中位置i处删除关键字
}
}
else
found_tag = BTNodeDelete(p->child[i], k); //沿孩子结点递归查找并删除关键字k
if (p->child[i] != NULL)/*因为上面进入了循环,所以这里每次都会调整一次*/
if (p->child[i]->KeyNum<MIN) //删除后关键字个数小于MIN
AdjustBTree(p, i); //调整B树 这里是被删关键词的父结点
return found_tag;
}
}
void BTreeDelete(BTree &t, KeyType k){
//构建删除框架,执行删除操作
BTNode *p;
int a = BTNodeDelete(t, k); //删除关键字k
if (a == 0) //查找失败
printf(" 关键字%d不在B树中\n", k);
else if (t->KeyNum == 0){ //调整
p = t;
t = t->child[0];
free(p);
}
}
void main()
{
BTree tree=NULL;
Result s;
int fre,i=0;
cout << "请输入决定输入多少个关键字"<< endl;
cin >> fre;
while (i<fre){
int k;
cout << "输入第"<<i+1<<"个关键字"<< endl;
cin >> k;
s = SerchBTree(tree,k);
/*如果需要筛掉重复项,加上这个,否则不需要加*/
while (s.tag == 1){
cout << "输入的关键字k值重复,请重新输入:" << endl;
cin >> k;
s = SerchBTree(tree, k);
}
InsertBTree(tree, s.i, k, s.p);
cout << "实时输出" << endl;
LNodeList L;
InitLinkList(L);
PrintBTree(tree, L, 0, 0);
i++;
}
//PrintTree(tree);//直接输出
/*LNodeList L;
InitLinkList(L);
PrintBTree(tree,L,0,0);*///有序输出
cout << "*******删除********* " << endl;
while (tree->KeyNum >= 1)
{
int k;
cout << "请输入需要删除的字"<< endl;
cin >> k;
BTreeDelete(tree, k);
LNodeList L;
InitLinkList(L);
PrintBTree(tree, L, 0, 0);
}
cout << "shanchu " << endl;
system("pause");
}