[转载]【数据结构】B-Tree, B+Tree, B*树介绍

本文从基础知识出发,深入解析B-树、B+树和B*树的特性、搜索原理、插入机制及其在数据库索引优化中的应用。重点介绍了B树的基本概念、B+树的特性和优势,以及B*树的改进之处,为理解文件索引结构提供了清晰的路径。

[转载]【数据结构】B-Tree, B+Tree, B*树介绍

 

转载链接:http://blog.sina.com.cn/s/blog_6776884e0100ohvr.html

【摘要】

      最近在看Mysql的存储引擎中索引的优化,神马是索引,支持啥索引.全是浮云,目前Mysql的MyISAM和InnoDB都支持B-Tree索引,InnoDB还支持B+Tree索引,Memory还支持Hash.今天从最基础的学起,学习了解BTree,B-Tree和B+Tree。

【主题】

  • B-Tree 介绍
  • B-Tree 特性搜索插入等
  • B+Tree 介绍
  • B*Tree 介绍

【内容】

1. B-Tree 介绍

     1970年,R.Bayer和E.mccreight提出了一种适用于外查找的树,它是一种平衡的多叉树,称为B树,其定义如下:

一棵m阶的B树满足下列条件:

  • 树中每个结点至多有m个孩子;
  • 除根结点和叶子结点外,其它每个结点至少有m/2个孩子;
  • 若根结点不是叶子结点,则至少有2个孩子;
  • 所有叶子结点(失败节点)都出现在同一层,叶子结点不包含任何关键字信息;
  • 所有非终端结点中包含下列信息数据 ( n, A0 , K1 , A1 , K2 , A2 , … , Kn , An ),其中: Ki (i=1,…,n)为关键字,且Ki < Ki+1 , Ai (i=0,…,n)为指向子树根结点的指针, n为关键字的个数
  • 非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;

     在B树中,每个结点中关键字从小到大排列,并且当该结点的孩子是非叶子结点时,该k-1个关键字正好是k个孩子包含的关键字的值域的分划。因为叶子结点不包含关键字,所以可以把叶子结点看成在树里实际上并不存在外部结点,指向这些外部结点的指针为空,叶子结点的数目正好等于树中所包含的关键字总个数加1。B树中的一个包含n个关键字,n+1个指针的结点的一般形式为:   (n,P0,K1,P1,K2,P2,…,Kn,Pn)其中,Ki为关键字,K1 <K2 <… <Kn,   Pi   是指向包括Ki到Ki+1之间的关键字的子树的指针。

btree1

-- 图1 B-tree示意图

2. B-Tree特性

2.1 B-Tree 特性

  • 关键字集合分布在整颗树中;
  • 任何一个关键字出现且只出现在一个结点中;
  • 搜索有可能在非叶子结点结束;
  • 其搜索性能等价于在关键字全集内做一次二分查找;
  • 自动层次控制;

2.2 B-Tree搜索原理

B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;因此,B-Tree的查找过程是一个顺指针查找结点和在结点的关键字中进行查找的交叉进行的过程。

b树

b树2

-- 图2 高度与关键码的计算过程

2.3 B-Tree 插入

     B-树是从空树起,逐个插入关键码而生成的。

     在B-树,每个非失败结点的关键码个数都在[ m/2 -1, m-1]之间。插入在某个叶结点开始。如果在关键码插入后结点中的关键码个数超出了上界 m-1,则结点需要“分裂”,否则可以直接插入。

     实现结点“分裂”的原则是:

     设结点 A 中已经有 m-1 个关键码,当再插入一个关键码后结点中的状态为( m, A0, K1, A1, K2, A2, ……, Km, Am)其中 Ki < Ki+1, 1 =< m

这时必须把结点 p 分裂成两个结点 p 和 q,它们包含的信息分别为:

     结点 p:( m/2 -1, A0, K1, A1, ……, Km/2 -1, Am/2 -1)

     结点 q:(m - m/2, Am/2, Km/2+1, Am/2+1, ……, Km, Am)

     位于中间的关键码 Km/2 与指向新结点 q 的指针形成一个二元组 ( Km/2, q ),插入到这两个结点的双亲结点中去。

3. B+Tree

3.1 B+Tree定义

     B+树可以看作是B树的一种变形,在实现文件索引结构方面比B树使用得更普遍。

一棵 m 阶B+树可以定义如下:

  • 树中每个非叶结点最多有 m 棵子树;
  • 根结点 (非叶结点) 至少有 2 棵子树。除根结点外, 其它的非叶结点至少有 ém/2ù 棵子树;有 n 棵子树的非叶结点有 n-1 个关键码。
  • 所有叶结点都处于同一层次上,包含了全部关键码及指向相应数据对象存放地址的指针,且叶结点本身按关键码从小到大顺序链接;
  • 每个叶结点中的子树棵数 n 可以多于 m,可以少于 m,视关键码字节数及对象地址指针字节数而定。
  • 若设结点可容纳最大关键码数为 m1,则指向对象的地址指针也有 m1 个。
  • 结点中的子树棵数 n 应满足 n 属于[m1/2, m1]
  • 若根结点同时又是叶结点,则结点格式同叶结点。
  • 所有的非叶结点可以看成是索引部分,结点中关键码 Ki 与指向子树的指针 Pi 构成对子树 (即下一层索引块) 的索引项 ( Ki, Pi ),Ki 是子树中最小的关键码。
  • 特别地,子树指针 P0 所指子树上所有关键码均小于 K1。结点格式同B树。
  • 叶结点中存放的是对实际数据对象的索引。
  • 在B+树中有两个头指针:一个指向B+树的根结点,一个指向关键码最小的叶结点。

B+Tree与B-Tree区别

  1. 非叶子结点的子树指针与关键字个数相同;
  2. 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
  3. 为所有叶子结点增加一个链指针;
  4. 所有关键字都在叶子结点出现;

对B+树进行两种搜索运算

  • 循叶结点链顺序搜索
  • 另一种是从根结点开始,进行自顶向下,直至叶结点的随机搜索。

alt

-- 图3 B+Tree示意图

3.2 B+Tree特性

     B+Tree的搜索与B-Tree也基本相同,区别是B+Tree只有达到叶子结点才命中(B-Tree可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;

B+Tree的特性

  • 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
  • 不可能在非叶子结点命中;
  • 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
  • 更适合文件索引系统

4. B*Tree

4.1 B*Tree

     B*Tree是B+树的变体,在B+Tree的非根和非叶子结点再增加指向兄弟的指针;

alt

-- 图4 B*Tree示意图

     B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);

     B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;

     B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;

     所以,B*树分配新结点的概率比B+树要低,空间使用率更高;

【结束】

自我总结:

  • B-树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;
  • B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;
  • B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3;

参考资料:

class DisjointSetUnion{/*并查集数据结构*/ private: vector<int>f,rank; int n; public: DisjointSetUnion(int _n){ n=_n; rank.resize(n,1);/*设置元素个数为传入的_n,并把所有元素的值初始化为1*/ f.resize(n);/*把元素个数设置为n*/ for(int i=0;i<n;i++){ f[i]=i;/*循环赋值,为每一个元素提供唯一标号*/ } } int find(int x){/*查找父节点*/ return f[x]==x?x:f[x]=find(f[x]);/*如果父节点是自己,就返回自己,否则迭代寻找*/ } int unionSet(int x,int y){ int fx=find(x),fy=find(y);/*此处fy和fx是根节点编号*/ if(fx==fy){ return false; } if(rank[fx]<rank[fy]){/*rank是该根节点元素个数*/ swap(fx,fy);/*令元素个数少的节点依附到元素个数大的节点*/ } rank[fx]+=rank[fy];/*没有改变从属关系,只是更新了元素大小*/ return true; } }; class BIT{ public: vector<int>tree,idRec; int n; BIT(int _n){ n=_n; tree.resize(n,INT_MAX); idRec.resize(n,-1); } int lowbit(int k){ return k&(-k); } void update(int pos,int val,int id){ while(pos>0){ if(tree[pos>val]){ tree[pos]=val; idRec[pos]=id; } pos-=lowbit(pos); } } int query(int pos){ int minval=INT_MAX; int j=-1; while(pos<n){ if(minval>tree[pos]){ minval=tree[pos]; j=idRec[pos]; } pos+=lowbit(pos); } return j; } }; struct Edge{ int len,x,y; Edge(int len,int x,int y):len(len),x(x),y(y){ } bool operator<(const Edge&a)const{ return len<a.len; } }; struct Pos{ int id,x,y; bool operator<(const Pos&a)const{ return x==a.x?a.y:x<a.x; } }; class Solution { public: vector<Edge>edges; vector<Pos>pos; void build(int n){ sort(pos.begin(),pos.end()); vector<int>a(n),b(n); for(int i=0;i<n;i++){ a[i]=pos[i].y-pos[i].x; b[i]=pos[i].y-pos[i].x; } sort(b.begin(),b.end()); b.erase(unique(b.begin(),b.end()),b.end()); int num=b.size(); BIT bit(num+1); for(int i=n-1;i>=0;i--){ int poss=lower_bound(b.begin(),b.end(),a[i])-b.begin()+1; int j=bit.query(poss); if(j!=-1){ int dis=abs(pos[i].x-pos[j].x)+abs(pos[i].y-pos[j].y); edges.emplace_back(dis,pos[i].id,pos[j].id); } bit.update(poss,pos[i].id,pos[j].id); } } void solve(vector<vector<int>>&points,int n){ pos.resize(n); for(int i=0;i<n;i++){ pos[i].x=points[i][0]; pos[i].y=points[i][1]; pos[i].id=1; } build(n); for(int i=0;i<n;i++){ swap(pos[i].x,pos[i].y); } build(n); for(int i=0;i<n;i++){ pos[i].x=-pos[i].x; } build(n); for(int i=0;i<n;i++){ swap(pos[i].x,pos[i].y); } build(n); } int minCostConnectPoints(vector<vector<int>>& points) { int n=points.size(); solve(points,n); DisjointSetUnion dsu(n); sort(edges.begin(),edges.end()); int ret =0,num=1; for(auto&[len,x,y]:edges){ if(dsu.unionSet(x,y)){ ret+=len; num++; if(num==n){ break; } } } return ret; } }; class DisjointSetUnion { private: vector<int> f, rank; int n; public: DisjointSetUnion(int _n) { n = _n; rank.resize(n, 1); f.resize(n); for (int i = 0; i < n; i++) { f[i] = i; } } int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } int unionSet(int x, int y) { int fx = find(x), fy = find(y); if (fx == fy) { return false; } if (rank[fx] < rank[fy]) { swap(fx, fy); } rank[fx] += rank[fy]; f[fy] = fx; return true; } }; class BIT { public: vector<int> tree, idRec; int n; BIT(int _n) { n = _n; tree.resize(n, INT_MAX); idRec.resize(n, -1); } int lowbit(int k) { return k & (-k); } void update(int pos, int val, int id) { while (pos > 0) { if (tree[pos] > val) { tree[pos] = val; idRec[pos] = id; } pos -= lowbit(pos); } } int query(int pos) { int minval = INT_MAX; int j = -1; while (pos < n) { if (minval > tree[pos]) { minval = tree[pos]; j = idRec[pos]; } pos += lowbit(pos); } return j; } }; struct Edge { int len, x, y; Edge(int len, int x, int y) : len(len), x(x), y(y) { } bool operator<(const Edge& a) const { return len < a.len; } }; struct Pos { int id, x, y; bool operator<(const Pos& a) const { return x == a.x ? y < a.y : x < a.x; } }; class Solution { public: vector<Edge> edges; vector<Pos> pos; void build(int n) { sort(pos.begin(), pos.end()); vector<int> a(n), b(n); for (int i = 0; i < n; i++) { a[i] = pos[i].y - pos[i].x; b[i] = pos[i].y - pos[i].x; } sort(b.begin(), b.end()); b.erase(unique(b.begin(), b.end()), b.end()); int num = b.size(); BIT bit(num + 1); for (int i = n - 1; i >= 0; i--) { int poss = lower_bound(b.begin(), b.end(), a[i]) - b.begin() + 1; int j = bit.query(poss); if (j != -1) { int dis = abs(pos[i].x - pos[j].x) + abs(pos[i].y - pos[j].y); edges.emplace_back(dis, pos[i].id, pos[j].id); } bit.update(poss, pos[i].x + pos[i].y, i); } } void solve(vector<vector<int>>& points, int n) { pos.resize(n); for (int i = 0; i < n; i++) { pos[i].x = points[i][0]; pos[i].y = points[i][1]; pos[i].id = i; } build(n); for (int i = 0; i < n; i++) { swap(pos[i].x, pos[i].y); } build(n); for (int i = 0; i < n; i++) { pos[i].x = -pos[i].x; } build(n); for (int i = 0; i < n; i++) { swap(pos[i].x, pos[i].y); } build(n); } int minCostConnectPoints(vector<vector<int>>& points) { int n = points.size(); solve(points, n); DisjointSetUnion dsu(n); sort(edges.begin(), edges.end()); int ret = 0, num = 1; for (auto& [len, x, y] : edges) { if (dsu.unionSet(x, y)) { ret += len; num++; if (num == n) { break; } } } return ret; } }; 作者:力扣官方题解 链接:https://leetcode.cn/problems/min-cost-to-connect-all-points/solutions/565801/lian-jie-suo-you-dian-de-zui-xiao-fei-yo-kcx7/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
最新发布
08-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值