B-树

B-树的性质

1 空树

2 根节点至少有一个关键字

3 非根的节点的关键字个数是[t-1, 2t-1];

4 由插入过程保证了根和叶节点最多2t-1个关键字;

5 内部节点的孩子个数为 nx + 1, 取值区间就是[t, 2t]

 

 

B-树节点包含如下域:

n[x]  关键字个数

具体n[x]个关键字,非降次序排序

leaf[x], 该节点是否为叶子节点

class B { public: int nx; int key[250]; B & c[251]; //child,不管了,这个定义可能有点问题 bool leaf; };

 

查询时,从根节点一直向下即可 

插入过程中因为可能在满孩子的节点上插入, 需要对节点进行分裂;

删除过程中因为可能在极限小孩子节点上删除, 所以可能需要对节点进行合并。

 

B-树的查找操作:在B-树x中,查找k 

B-tree-search(B &x, int k) { int i = 0; while(i < x.nx && k > x.key[i]) ++i; if(i < x.nx && k == x.key[i]) return (x, i); if(x.leaf) return NIL; else { DISK_READ(x.c[i]); return B-tree-search(x.c[i], k); } }

 

B-树节点分裂

分裂x的第i个孩子节点y x非满, y满. 非满的好处是避免回溯向上的分裂 //x非满如何保证? 由函数B-tree-insert-nonfull保证 B-tree-split(x, i, y) { z = allocate_node(); z.leaf = y.leaf; z.nx = t-1; //t-1个关键字 for(j = 0; j < t-1; j++) //复制关键字 z.key[j] = y.key[t + j]; if(!z.leaf) //非叶节点,就复制其孩子节点 { for(j = 0; j < t; j++) z.c[j] = y.c[t + j]; } y.ny = t-1; for(j = x.nx; j > i; j--) //把x中i以后的孩子指针都后移 x.c[j + 1] = x.c[j]; x.c[i+1] = z; //把新分裂的节点作为x中y的兄弟节点 for(j = x.nx - 1; j >= i; j--) //把x中包括i以后的关键字都后移 x.key[j + 1] = x.key[j]; x.key[i] = y.key[t-1]; //把y中分裂出的关键字保存到父节点x中 x.nx++; //修改x的关键字个数。+1 DISK_WRITE(y); DISK_WRITE(z); DISK_WRITE(X); }

 

B-树的插入操作

NOTE:插入时,只会在也节点上进行插入操作

1 空树 2 根节点至少有一个关键字 3 非根的内部节点的关键字个数是[t-1, 2t-1]; 称作t阶B-树. 非根内部节点包含t个孩子指针; 对应的孩子个数为[t, 2t] B-tree-insert(B &x, int k) //x为根节点 { if(x.nx == 2t - 1) //如果根节点已满, 则分裂根节点; 并在新的根节点上进行插入操作 { s = allocatenode(); s.leaf = false; s.nx = 0; s.c[0] = x; B-tree-split(s, 0, x); B-tree-insert-nonfull(s, k); } else B-tree-insert-nonfull(x, k); } } B-tree-insert-nonfull(B &x, int k) //x非满 { int i = x.nx - 1; if(x.leaf) { while( i >= 0 && k < x.key[i]) { x.key[i+1] = x.key[i]; i--; } x.key[i+1] = k; x.nx++; DISK_WRITE(x); } else while( i >= 0 && k < x.key[i]) { i--; } i++; DISK_READ(x.c[i]); if(x.c[i].nx == 2t - 1) { B-tree-split(x, i, x.c[i]); //由于x非满, 这里即使split导致x.nx增1, 也不会导致x逐级向上分裂 if(k > x.key[i]) i++; } B-tree-insert-nonfull(x.c[i], k); }

 

B-树的删除k:

1 如果在叶节点中找到, 直接删除

2 如果在内节点x中找到,假设为第i个关键字, 则看x.c[i]的关键字个数是否>=t;

2.1 是, 则使用x.c[i]中的k' 代替x中的第i个关键词k; 然后在x.c[i]中递归的调用删除k'操作; 

2.2 否, 则判断x.c[i+1].nx>=t, 是则在x.c[i+1]中进行同样操作;

2.3 否则x.c[i], x.c[i+1]的关键字个数都小于t, 则把x中的x.c[i], x.c[i+1], k合并为一个节点z, 然后回到步骤2递归的再z中删除k。

3 如果内节点x中无k, 则必定要到x的某个孩子节点x.c[i]中继续查找;

3.1 如果x.c[i]只有t-1个关键字,若x.c[i]的兄弟节点有>t-1个关键字, 则把x.key[i]移到x.c[i]中,把x.c[i]的兄弟节点中移出一个关键字到x中; 然后回到步骤2继续查找

3.2 若x.c[i]>t-1个关键字, 则回到2继续再x.c[i]中查找

3.3 若x.c[i]只有t-1个关键字,x.c[i]的兄弟也只有t-1个关键字, 则合并x.c[i]与其兄弟, 然后递归的再合并后的节点中到步骤2查找

 

当要删除时, 必须保证该节点的孩子个数>=t, 比B-树的性质[t-1,2t-1]个孩子的最低个数要大, 就是为了避免再删除一个值后导致内节点不满足该性质而再次向上回溯

 

 

 

 

疑惑:

按照步骤2.3, 当再内节点中找到时,x的关键字可能会减少1。

步骤2.3中,如何保证x合并了之后变成的节点z,满足[t-1,2t-1]个关键字的性质呢 【书上解释说, 这是由算法的结构保证的:保证x中至少有t个关键字】

步骤3中,保证x.c[i]的关键字个数>=t, 从逻辑的正确性上看是多余的吗?
很有迷惑性, 并不是多余的; 这个保证了【至少有t个关键字】中的性质。

但是初始时, 如何保证【至少有t个关键字】的性质呢? (最后的答案就是初始状态步骤2.3不需保证这个性质, 因为初始就能到达2.3则必定在根节点中找到, 而根节点无需满足什么性质)

 

 

 

--------------------------------------------------------

如下理解:
程序起始时, 若从步骤1退出, 则根本无需合并节点致使可能不满足[t-1,2t-1]的性质
若先走了步骤3,自然就满足了“至少为t个关键字”的性质
若先走了步骤2:则必定是在根节点中找到了k; 但是根节点无需满足[t-1,2t-1]的性质。 根节点最少只需要一个关键字。 当恰好根节点只有一个关键字且走到2.3,算法结束后,根节点包含的关键字个数可能为0, 因此在算法末尾判断根节点包含的关键字个数是否为0且为叶子节点, 是则为空树, 否则调整一下根节点位置即可
[e04]

 同理, 当从步骤1删除退出时, 也必定满足【至少有t个关键字】不会导致破坏性质[t-1,2t-1]

 

理解应该是对的, 但书上没有相关说明和解释 :(

 

B-树的性质很拗口

 

 

你可能感兴趣的:(B-树)