数据结构大总结系列之B树和R树

news2023/12/12 2:00:19

原文:http://www.cnblogs.com/javaspring/archive/2012/08/14/2656223.html

一,B-树

B树是为磁盘或其他直接存储辅助存储设备而设计的一种平衡二叉查找树(通常说的B树是B-树,在1972年由R.Bayer和E.M.McCreight提出,B+树是B树的一种变形),B树与红黑树类似,但在降低磁盘I/O操作次数方面要更好一些,数据库就是通常用B树来进行存储信息。

    B树的结点可以有许多子女,从几个到几千个不等,一个B树结点可以拥有的子女数是由磁盘页的大小所决定,这是因为一个结点的大小通常相当于一个完整的磁盘页。磁盘存取次数是按需要从盘中读出或向盘中写入的信息的页数来度量的,所以,存取磁盘的总时间可以近似为读或写的页数。因此,B树一般都选择大的分支因子,这样可以大大降低树的高度,以及寻找任意关键字所需的磁盘存取次数。一棵分支因子为1001, 高度为2的B树,可以储存超过10亿个关键字,同时因为根节点可以持久地保留在内存中,故在这棵树中,寻找一个关键字至多只需要两次磁盘存取。

相信,从上图你能轻易的看到,一个内结点x若含有n[x]个关键字,那么x将含有n[x]+1个子女。如含有2个关键字D H的内结点有3个子女,而含有3个关键字Q T X的内结点有4个子女。

B树的性质:

1) 每个节点x的域:

a) n[x],x中的关键字数,若x是B树中的内节点,则x有n[x] + 1个子女。

b) n[x]个关键字本身,以非降序排列,key1[x] <= key2[x] <= … <= keyn[x][x]

c) leaf[x],布尔值,如果x是叶节点,则为TRUE,若为内节点,则为FALSE

2) 每个内节点x还包含n[x] + 1个指向其子女的指针c1[x], c2[x], …, cn[x] + 1[x]

3) 如果ki为存储在以ci[x]为根的子树中的关键字,则k1 <= key1[x] <= k2 <= key2[x] <= … <= keyn[x][x] <= keyn[x] + 1

4) 每个叶节点具有相同的深度

5) B树的最小度数t

a) 每个非根的节点必须至少有t – 1个关键字

b) 每个节点可包含至多2t – 1个关键字。

B树的数据结构:

B树插入关键字

B树插入是指插入到一个已知的叶节点上,因为不能把关键字插入到一个满的叶结点上,故引入一个操作,将一个满的结点y(有2t – 1个关键字)按其中间关键字key[y]分裂成两个各含t – 1个关键字的节点,中间关键字提升到y的双亲结点,如果y的双亲也是满的,则自底向上传播分裂。

    如同二叉查找树,插入时,需要从根部沿着树下降到叶子,当沿着树往下查找新关键字所属位置时,就分裂遇到的每一个满结点,这样就能保证,要分裂一个满结点y时,就能确保它的双亲不是满的。

分裂图示:

插入结点伪代码:

B-TREE-INSERT(T, k)作用是对B树用单程下行遍历方式插入关键字。3~9行处理根结点r为满的情况。

B-TREE-SPLIT-CHILD(x, i, y) 第1~8行行创建一个新结点,并将y的t – 1个最大关键字以及相应的t个子女给它,第九行调整关键字计数。第10~16行将z插入为x的一个孩子,提升y的中间关键字到x来分裂y和z,并调整x的关键字计数。

B-TREE-INSERT-NONFULL(x, k) 第3~8行处理x是叶子的情况,将关键字k插入x;如果不是,则第9~11确定向x的哪个子结点递归下降。第13行检查递归是否将降至一个满子结点上,若是,14行用B-TREE-SPLIT-CHILD将该子结点分类成两个非满的孩子,第15~16行确定向两个孩子中的哪一个下降是正确的。

各种情况都包含的插入图示:(最小度数t为3)

 

3)删除操作:

书上没有给出伪代码,只给出了基本思路,设关键字为k,x为节点

1) 若k在x中,且x是叶节点,则从x中删除k

2) 若k在x中,且x是内节点,则

a) 若x中前于k的子节点y包含至少t个关键字,则找出k在以y为根的子树中的前驱k’。递归地删除k’,并在x中用k’取代k。

b) 若x中后于k的子节点z包含至少t个关键字,则找出k在以z为根的子树中的后继k’。递归地删除k’,并在x中用k’取代k。

c) 否则,将k和z所有关键字合并进y,然后,释放z并将k从y中递归删除。

3) 若k不在x中,则确定必包含k的正确的子树的根ci[x]。若ci[x]只有t – 1个关键字,则执行a或b操作。然后,对合适的子节点递归删除k。

a) 若ci[x]只包含t-1个关键字,但它的相邻兄弟包含至少t个关键字,则将x中的某一个关键字降至ci[x],将ci[x]的相邻兄弟中的某一个关键字升至x,将该兄弟中合适的子女指针迁移到ci[x]中。

b) 若ci[x]与其所有相邻兄弟节点都包含t-1个关键字,则将ci[x]与一个兄弟合并,将x的一个关键字移至新合并的节点。

删除结点图示:(最小度数为2)

完整实现代码: 

[html]  view plain copy print ?
  1. /*  
  2.   
  3. 按关键字的顺序遍历B-树:  
  4.   
  5. (3, 11)  
  6.   
  7. (7, 16)  
  8.   
  9. (12, 4)  
  10.   
  11. (24, 1)  
  12.   
  13. (26, 13)  
  14.   
  15. (30, 12)  
  16.   
  17. (37, 5)  
  18.   
  19. (45, 2)  
  20.   
  21. (50, 6)  
  22.   
  23. (53, 3)  
  24.   
  25. (61, 7)  
  26.   
  27. (70, 10)  
  28.   
  29. (85, 14)  
  30.   
  31. (90, 8)  
  32.   
  33. (100, 9)  
  34.   
  35.    
  36.   
  37. 请输入待查找记录的关键字: 26  
  38.   
  39. (26, 13)  
  40.   
  41.    
  42.   
  43. 5  
  44.   
  45. 没找到  
  46.   
  47.    
  48.   
  49. 37  
  50.   
  51. (37, 5)  
  52.   
  53.    
  54.   
  55. */  
  56.   
  57.    
  58.   
  59. #include<iostream>  
  60.   
  61. #include<cstdio>  
  62.   
  63. #include<cstdlib>  
  64.   
  65. #include<cmath>  
  66.   
  67. using namespace std;  
  68.   
  69.    
  70.   
  71. #define m 3  // B树的阶,暂设为3  
  72.   
  73. //3阶的B-数上所有非终点结点至多可以有两个关键字  
  74.   
  75. #define N 16 // 数据元素个数  
  76.   
  77. #define MAX 5 // 字符串最大长度 + 1    
  78.   
  79.   
  80.   
  81. //记录类型  
  82.   
  83. struct Record{  
  84.   
  85.     int key; // 关键字  
  86.   
  87.     char info[MAX];  
  88.   
  89. };    
  90.   
  91.   
  92.   
  93. //B-树ADT  
  94.   
  95. struct BTreeNode {  
  96.   
  97.     int keynum; // 结点中关键字个数  
  98.   
  99.     struct BTreeNode * parent; // 指向双亲结点  
  100.   
  101.     struct Node { // 结点类型  
  102.   
  103.         int key; // 关键字  
  104.   
  105.         Record * recptr; // 记录指针  
  106.   
  107.         struct BTreeNode * ptr; // 子树指针  
  108.   
  109.     }node[m + 1]; // key, recptr的0号单元未用  
  110.   
  111. };   
  112.   
  113.   
  114.   
  115. typedef BTreeNode BT;  
  116.   
  117. typedef BTreeNode * Position;  
  118.   
  119. typedef BTreeNode * SearchTree;  
  120.   
  121.    
  122.   
  123. //B-树查找结果的类型  
  124.   
  125. typedef struct {  
  126.   
  127.     Position pt; // 指向找到的结点  
  128.   
  129.     int i; // 1..m,在结点中的关键字序号  
  130.   
  131.     int tag; // 1:查找成功,O:查找失败  
  132.   
  133. }Result;   
  134.   
  135.   
  136.   
  137. inline void print(BT c, int i) {// TraverseSearchTree()调用的函数  
  138.   
  139.     printf("(%d, %s)\n", c.node[i].key, c.node[i].recptr->info);  
  140.   
  141. }  
  142.   
  143.    
  144.   
  145. //销毁查找树  
  146.   
  147. void DestroySearchTree(SearchTree tree) {  
  148.   
  149.     if(tree) {// 非空树  
  150.   
  151.         for(int i = 0; i <= (tree)->keynum; i++ ) {  
  152.   
  153.             DestroySearchTree(tree->node[i].ptr); // 依次销毁第i棵子树  
  154.   
  155.         }  
  156.   
  157.         free(tree); // 释放根结点  
  158.   
  159.         tree = NULL; // 空指针赋0  
  160.   
  161.     }  
  162.   
  163. }  
  164.   
  165.    
  166.   
  167. //在p->node[1..keynum].key中查找i, 使得p->node[i].key≤K<p->node[i + 1].key  
  168.   
  169. //返回刚好小于等于K的位置  
  170.   
  171. int Search(Position p, int K) {  
  172.   
  173.     int location = 0;  
  174.   
  175.     for(int i = 1; i <= p->keynum; i++ ) {  
  176.   
  177.         if(p->node[i].key <= K) {  
  178.   
  179.             location = i;  
  180.   
  181.         }  
  182.   
  183.     }  
  184.   
  185.     return location;  
  186.   
  187. }  
  188.   
  189.    
  190.   
  191. /*  
  192.   
  193. 在m阶B树tree上查找关键字K,返回结果(pt, i, tag)。  
  194.   
  195. 若查找成功,tag = 1,指针pt所指结点中第i个关键字等于K;  
  196.   
  197. 若查找失败,tag = 0,等于K的关键字应插入在指针Pt所指结点中第i和第i + 1个关键字之间。  
  198.   
  199. */  
  200.   
  201. Result SearchPosition(SearchTree tree, int K) {  
  202.   
  203.     Position p = treeq = NULL; // 初始化,p指向待查结点,q指向p的双亲  
  204.   
  205.     bool found = false;  
  206.   
  207.     int i = 0;  
  208.   
  209.     Result r;  
  210.   
  211.     while(p && !found) {  
  212.   
  213.         i = Search(p, K); // p->node[i].key≤K<p->node[i + 1].key  
  214.   
  215.         if(i > 0 && p->node[i].key == K) {// 找到待查关键字  
  216.   
  217.             found = true;  
  218.   
  219.         } else {  
  220.   
  221.             q = p;  
  222.   
  223.             p = p->node[i].ptr;  
  224.   
  225.         }  
  226.   
  227.     }  
  228.   
  229.     r.i = i;  
  230.   
  231.     if(found) {// 查找成功  
  232.   
  233.         r.pt = p;  
  234.   
  235.         r.tag = 1;  
  236.   
  237.     } else {//  查找不成功,返回K的插入位置信息  
  238.   
  239.         r.pt = q;  
  240.   
  241.         r.tag = 0;  
  242.   
  243.     }  
  244.   
  245.     return r;  
  246.   
  247. }  
  248.   
  249.    
  250.   
  251. //将r->key、r和ap分别插入到q->key[i + 1]、q->recptr[i + 1]和q->ptr[i + 1]中  
  252.   
  253. void Insert(Position q, int i, Record * r, Position ap) {  
  254.   
  255.     for(int j = q->keynum; j > i; j--) {// 空出q->node[i + 1]  
  256.   
  257.         q->node[j + 1] = q->node[j];  
  258.   
  259.     }  
  260.   
  261.     q->node[i + 1].key = r->key;  
  262.   
  263.     q->node[i + 1].ptr = ap;  
  264.   
  265.     q->node[i + 1].recptr = r;  
  266.   
  267.     q->keynum++;  
  268.   
  269. }  
  270.   
  271.    
  272.   
  273. // 将结点q分裂成两个结点,前一半保留,后一半移入新生结点ap  
  274.   
  275. void split(Position &q, Position &ap) {  
  276.   
  277.     int s = (m + 1) / 2;  
  278.   
  279.     ap = (Position)malloc(sizeof(BT)); // 生成新结点ap  
  280.   
  281.     ap->node[0].ptr = q->node[s].ptr; // 后一半移入ap  
  282.   
  283.     for(int i = s + 1; i <= m; i++ ) {  
  284.   
  285.         ap->node[i-s] = q->node[i];  
  286.   
  287.         if(ap->node[i - s].ptr) {  
  288.   
  289.             ap->node[i - s].ptr->parent = ap;  
  290.   
  291.         }  
  292.   
  293.     }  
  294.   
  295.     ap->keynum = m - s;  
  296.   
  297.     ap->parent = q->parent;  
  298.   
  299.     q->keynum = s - 1; // q的前一半保留,修改keynum  
  300.   
  301. }  
  302.   
  303.    
  304.   
  305. // 生成含信息(T, r, ap)的新的根结点*T,原T和ap为子树指针  
  306.   
  307. void NewRoot(Position &tree, Record *r, Position ap) {  
  308.   
  309.     Position p;  
  310.   
  311.     p = (Position)malloc(sizeof(BT));  
  312.   
  313.     p->node[0].ptr = tree;  
  314.   
  315.     tree = p;  
  316.   
  317.     if(tree->node[0].ptr) {  
  318.   
  319.         tree->node[0].ptr->parent = tree;  
  320.   
  321.     }  
  322.   
  323.     tree->parent = NULL;  
  324.   
  325.     tree->keynum = 1;  
  326.   
  327.     tree->node[1].key = r->key;  
  328.   
  329.     tree->node[1].recptr = r;  
  330.   
  331.     tree->node[1].ptr = ap;  
  332.   
  333.     if(tree->node[1].ptr) {  
  334.   
  335.         tree->node[1].ptr->parent = tree;  
  336.   
  337.     }  
  338.   
  339. }  
  340.   
  341.    
  342.   
  343. /*  
  344.   
  345. 在m阶B-树tree上结点*q的key[i]与key[i + 1]之间插入关键字K的指针r。若引起  
  346.   
  347. 结点过大, 则沿双亲链进行必要的结点分裂调整, 使tree仍是m阶B树。  
  348.   
  349. */  
  350.   
  351. void InsertPosition(SearchTree &tree, Record &r, Position q, int i) {  
  352.   
  353.     Position ap = NULL;  
  354.   
  355.     bool finished = false;  
  356.   
  357.     Record *rx = &r;  
  358.   
  359.    
  360.   
  361.     while(q && !finished) {  
  362.   
  363.         // 将r->key、r和ap分别插入到q->key[i + 1]、q->recptr[i + 1]和q->ptr[i + 1]中  
  364.   
  365.         Insert(q, i, rx, ap);  
  366.   
  367.         if(q->keynum < m) {  
  368.   
  369.             finished = true; // 插入完成  
  370.   
  371.         } else { // 分裂结点*q  
  372.   
  373.             int s = (m + 1) >> 1;  
  374.   
  375.             rx = q->node[s].recptr;  
  376.   
  377.             // 将q->key[s + 1..m], q->ptr[s..m]和q->recptr[s + 1..m]移入新结点*ap  
  378.   
  379.             split(q, ap);  
  380.   
  381.             q = q->parent;  
  382.   
  383.             if(q) {  
  384.   
  385.                 i = Search(q, rx->key); // 在双亲结点*q中查找rx->key的插入位置  
  386.   
  387.             }  
  388.   
  389.         }  
  390.   
  391.     }  
  392.   
  393.     if(!finished) {// T是空树(参数q初值为NULL)或根结点已分裂为结点*q和*ap  
  394.   
  395.         NewRoot(tree, rx, ap); // 生成含信息(T, rx, ap)的新的根结点*T,原T和ap为子树指针  
  396.   
  397.     }  
  398.   
  399. }  
  400.   
  401.    
  402.   
  403. /*  
  404.   
  405. 操作结果: 按关键字的顺序对tree的每个结点调用函数Visit()一次且至多一次  
  406.   
  407. */  
  408.   
  409. void TraverseSearchTree(SearchTree tree, void(*Visit)(BT, int)) {  
  410.   
  411.     if(tree) {// 非空树  
  412.   
  413.         if(tree->node[0].ptr) {// 有第0棵子树  
  414.   
  415.             TraverseSearchTree(tree->node[0].ptr, Visit);  
  416.   
  417.         }  
  418.   
  419.         for(int i = 1; i <= tree->keynum; i++ ) {  
  420.   
  421.             Visit(*tree, i);  
  422.   
  423.             if(tree->node[i].ptr) { // 有第i棵子树  
  424.   
  425.                 TraverseSearchTree(tree->node[i].ptr, Visit);  
  426.   
  427.             }  
  428.   
  429.         }  
  430.   
  431.     }  
  432.   
  433. }  
  434.   
  435.    
  436.   
  437. int main() {  
  438.   
  439.     Record r[N] = {{24, "1"}, {45, "2"}, {53, "3"}, {12, "4"},  
  440.   
  441.                    {37, "5"}, {50, "6"}, {61, "7"}, {90, "8"},  
  442.   
  443.                    {100, "9"}, {70, "10"}, {3, "11"}, {30, "12"},  
  444.   
  445.                    {26, "13"}, {85, "14"}, {3, "15"}, {7, "16"}};  
  446.   
  447.     SearchTree tree = NULL;//初始化一棵空树  
  448.   
  449.     Result res;//存放结果  
  450.   
  451.    
  452.   
  453.     int i;  
  454.   
  455.     for(i = 0; i < N; i++ ) {  
  456.   
  457.         res = SearchPosition(tree, r[i].key);  
  458.   
  459.         if(!res.tag) {  
  460.   
  461.             InsertPosition(tree, r[i], res.pt, res.i);  
  462.   
  463.         }  
  464.   
  465.     }  
  466.   
  467.    
  468.   
  469.     printf("按关键字的顺序遍历B-树:\n");  
  470.   
  471.     TraverseSearchTree(tree, print);  
  472.   
  473.     printf("\n请输入待查找记录的关键字: ");  
  474.   
  475.     while (scanf("%d", &i)) {  
  476.   
  477.         res = SearchPosition(tree, i);  
  478.   
  479.         if(res.tag) {  
  480.   
  481.             print(*(res.pt), res.i);  
  482.   
  483.         } else {  
  484.   
  485.             printf("没找到\n");  
  486.   
  487.         }  
  488.   
  489.         puts("");  
  490.   
  491.     }  
  492.   
  493.     DestroySearchTree(tree);  
  494.   
  495. }   

B+-tree:是应文件系统所需而产生的一种B-tree的变形树。

一棵m阶的B+树和m阶的B树的差异在于:

1.n棵子树的结点中含有n个关键字; (而B 树n棵子树有n-1个关键字)

2.所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (而B 树的叶子节点并没有包括全部需要查找的信息)

3.所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)

a)     为什么说B+-tree比B 树更适合实际应用中操作系统的文件索引和数据库索引?

1) B+-tree的磁盘读写代价更低

B+-tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)

2) B+-tree的查询效率更加稳定

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

b)    B+-tree的应用: VSAM(虚拟存储存取法)文件(来源论文 the ubiquitous Btree 作者:D COMER - 1979 )

5.B*-tree

B*-treeB+-tree的变体,在B树非根和非叶子结点再增加指向兄弟的指针;B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2)。给出了一个简单实例,如下图所示:

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

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

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

 

二,B+树

B+树:是应文件系统所需而产生的一种B-的变形树。

一棵m阶的B+树和m阶的B树的差异在于:

1.n棵子树的结点中含有n个关键字; (而B 树n棵子树有n-1个关键字)

2.所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (而B 树的叶子节点并没有包括全部需要查找的信息)

3.所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)

a)     为什么说B+-tree比B 树更适合实际应用中操作系统的文件索引和数据库索引?

1) B+-tree的磁盘读写代价更低

B+-tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)

2) B+-tree的查询效率更加稳定

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

B-树:

 

B+树:

5.B*-tree

B*-treeB+-tree的变体,在B树非根和非叶子结点再增加指向兄弟的指针;B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2)。给出了一个简单实例,如下图所示:

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

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

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

在大规模数据存储的文件系统中,B~tree系列数据结构,起着很重要的作用,对于存储不同的数据,节点相关的信息也是有所不同,这里根据自己的理解,画的一个查找以职工号为关键字,职工号为38的记录的简单示意图。(这里假设每个物理块容纳3个索引,磁盘的I/O操作的基本单位是块(block),磁盘访问很费时,采用B+树有效的减少了访问磁盘的次数。)

对于像MySQLDB2Oracle等数据库中的索引结构得有较深入的了解才行,建议去找一些B 树相关的开源代码研究。

 

三,R树的数据结构

如上所述,R树是B树在高维空间的扩展,是一棵平衡树。每个R树的叶子结点包含了多个指向不同数据的指针,这些数据可以是存放在硬盘中的,也可以是存在内存中。根据R树的这种数据结构,当我们需要进行一个高维空间查询时,我们只需要遍历少数几个叶子结点所包含的指针,查看这些指针指向的数据是否满足要求即可。这种方式使我们不必遍历所有数据即可获得答案,效率显著提高。下图1是R树的一个简单实例:

我们在上面说过,R树运用了空间分割的理念,这种理念是如何实现的呢?R树采用了一种称为MBR(Minimal Bounding Rectangle)的方法,在此我把它译作“最小边界矩形”。从叶子结点开始用矩形(rectangle)将空间框起来,结点越往上,框住的空间就越大,以此对空间进行分割。有点不懂?没关系,继续往下看。在这里我还想提一下,R树中的R应该代表的是Rectangle(此处参考wikipedia),而不是大多数国内教材中所说的Region(很多书把R树称为区域树,这是有误的)。我们就拿二维空间来举例吧。下图是Guttman论文中的一幅图。

我来详细解释一下这张图。先来看图(b)吧。首先我们假设所有数据都是二维空间下的点,图中仅仅标志了R8区域中的数据,也就是那个shape of data object。别把那一块不规则图形看成一个数据,我们把它看作是多个数据围成的一个区域。为了实现R树结构,我们用一个最小边界矩形恰好框住这个不规则区域,这样,我们就构造出了一个区域:R8。R8的特点很明显,就是正正好好框住所有在此区域中的数据。其他实线包围住的区域,如R9,R10,R12等都是同样的道理。这样一来,我们一共得到了12个最最基本的最小矩形。这些矩形都将被存储在子结点中。下一步操作就是进行高一层次的处理。我们发现R8,R9,R10三个矩形距离最为靠近,因此就可以用一个更大的矩形R3恰好框住这3个矩形。同样道理,R15,R16被R6恰好框住,R11,R12被R4恰好框住,等等。所有最基本的最小边界矩形被框入更大的矩形中之后,再次迭代,用更大的框去框住这些矩形。我想大家都应该理解这个数据结构的特征了。用地图的例子来解释,就是所有的数据都是餐厅所对应的地点,先把相邻的餐厅划分到同一块区域,划分好所有餐厅之后,再把邻近的区域划分到更大的区域,划分完毕后再次进行更高层次的划分,直到划分到只剩下两个最大的区域为止。要查找的时候就方便了吧。

有关R树的详细介绍请看博文http://blog.csdn.net/v_july_v/article/details/6530142

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dtcms.cn/news/show-326088.html

如若内容造成侵权/违法违规/事实不符,请联系七分地网进行投诉反馈,一经查实,立即删除!

相关文章

七大查找算法

原文&#xff1a;http://www.cnblogs.com/yw09041432/p/5908444.html 七大查找算法 阅读目录 1. 顺序查找2. 二分查找3. 插值查找4. 斐波那契查找5. 树表查找6. 分块查找7. 哈希查找 查找是在大量的信息中寻找一个特定的信息元素&#xff0c;在计算机应用中&#xff0c;查找是常…

android垂直公告,【Android之垂直翻页公告】

该源码是android studio版本&#xff0c;本文最后提供的是eclipse版本&#xff0c;以供大家学习参考。效果如图&#xff1a;源码分析&#xff1a;核心类&#xff1a;MarqueeView.java核心思想是根据公告内容的数量动态生成相应数量的TextView对象&#xff0c;将所有的TextView对…

python enumerate 行号 序号

原文&#xff1a;http://blog.csdn.net/churximi/article/details/51648388 python enumerate用法总结 enumerate()说明 enumerate()是python的内置函数enumerate在字典上是枚举、列举的意思对于一个可迭代的&#xff08;iterable&#xff09;/可遍历的对象&#xff08;如列表、…

android如何避免内存泄露,Android开发中应该避免的内存泄露

一、背景和目的&#xff1a;目前许多开发人员在Android开发过程中&#xff0c;较少关注实现细节和内存使用&#xff0c;容易会造成内存泄露&#xff0c;导致程序OOM。本文会通过代码向大家介绍在Android开发过程中常见的内存泄露。二、常见的内存泄露代码1、使用Handler造成的内…

二叉树深度优先遍历和广度优先遍历

二叉树深度优先遍历和广度优先遍历

华为鸿蒙第一期名单,机型正式确认,鸿蒙2.0第一批名单曝光!花粉:华为不够厚道...

最新消息&#xff1a;华为的鸿蒙OS2.0暂定在12月18日开放Beta测试版本&#xff0c;明年一二月份将面向部分手机用户提供升级渠道(机型下方公布)&#xff0c;与此同时&#xff0c;华为方面表示&#xff0c;市面上百分之九十的华为机型都可以升级鸿蒙。鸿蒙姗姗来迟从去年开始&am…

HashMap解决hash冲突的方法

HashMap解决hash冲突的方法 博客分类&#xff1a; jvm虚拟机 在Java编程语言中&#xff0c;最基本的结构就是两种&#xff0c;一种是数组&#xff0c;一种是模拟指针(引用),所有的数据结构都可以用这两个基本结构构造&#xff0c;HashMap也一样。当程序试图将多个 key-value 放…

快速理解平衡二叉树、B-tree、B+tree、B*tree

原文:https://my.oschina.net/u/3370829/blog/1301732 觉得这篇文章比较好,特此分享 1、平衡二叉树 (1)由来:平衡二叉树是基于二分法的策略提高数据的查找速度的二叉树的数据结构; (2)特点: 平衡二叉树是采用二分法思维把数据按规则组装成一个树形结构的数据,用…

android popupwindow dialog区别,Android PopUpWindow使用详解

释放双眼&#xff0c;带上耳机&#xff0c;听听看~&#xff01;一、概述1、PopupWindow与AlertDialog的区别最关键的区别是AlertDialog不能指定显示位置&#xff0c;只能默认显示在屏幕最中间(当然也可以通过设置WindowManager参数来改变位置)。而PopupWindow是可以指定显示位置…

深入浅出K-Means算法

深入浅出K-Means算法 摘要&#xff1a;在数据挖掘中&#xff0c;K-Means算法是一种 cluster analysis 的算法&#xff0c;其主要是来计算数据聚集的算法&#xff0c;主要通过不断地取离种子点最近均值的算法。在数据挖掘中&#xff0c;K-Means算法是一种cluster analysis的算法…

kmeans算法原理以及实践操作

原文&#xff1a;http://www.cnblogs.com/dudumiaomiao/p/5839905.html kmeans算法原理以及实践操作(多种k值确定以及如何选取初始点方法) kmeans一般在数据分析前期使用&#xff0c;选取适当的k&#xff0c;将数据聚类后&#xff0c;然后研究不同聚类下数据的特点。 算法原理&…

define 双引号 其他宏_当年宏语言不受欢迎?背后的原因你知道吗?

人类用计算机处理文本主要是依赖宏语言以及一些专用的文本编辑器。事实上&#xff0c;早期的文本编辑器只提供基本的文本编辑功能&#xff0c;然后借助宏语言进行功能扩展。结果人类很快就发现&#xff0c;基于宏扩展的编辑器&#xff0c;功能越复杂&#xff0c;它的行为就越诡…

K-means聚类算法的三种改进(K-means++,ISODATA,Kernel K-means)介绍与对比

原文&#xff1a;http://www.cnblogs.com/yixuan-xu/p/6272208.html K-means聚类算法的三种改进(K-means,ISODATA,Kernel K-means)介绍与对比 一、概述 在本篇文章中将对四种聚类算法(K-means,K-means,ISODATA和Kernel K-means)进行详细介绍&#xff0c;并利用数据集来真实地反…

数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)

原文&#xff1a;http://blog.csdn.net/sup_heaven/article/details/39313731 数据结构中常见的树&#xff08;BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B树、B*树&#xff09; 转载 2014年09月16日 12:07:0827624 BST树 即二叉搜索树&#xff1a; 1.所有非叶子结点至…

html载入字体,css怎么导入字体?

在做前端页面的时候&#xff0c;有些内容文字需要通过css来指定第三方的字体进行显示&#xff0c;而不是默认支持的一些字体&#xff0c;这就需要自己制作或者获得字体文件&#xff0c;然后放入对应的字体目录&#xff0c;并通过font-family来指定字体。在 CSS3 之前&#xff0…

奇异值的物理意义

知乎上的解释特别好&#xff0c;非常佩服&#xff0c;在此转载一下&#xff0c;做个笔记 原文&#xff1a;https://www.zhihu.com/question/22237507/answer/28007137 奇异值作用&#xff1a;图片压缩&#xff0c;去燥&#xff0c;模糊等等矩阵的奇异值是一个数学意义上的概念…

mabatisplus怎么给实体类自定义属性_如果你的角色属性可以自定义,你会怎么点?...

如今越来越多的人抱怨小时候学的一些特长&#xff0c;长大之后根本没什么用处&#xff0c;比如乐器、绘画等等。反而是一些实用的比如烹饪和动手能力更受到大家的欢迎。阿庆想说的是&#xff0c;你肯定是没有坚持&#xff0c;到最后自然只能像方仲永一样“泯然众人矣”了。在游…

selector多路复用_超详细的I/O多路复用概念、常用I/O模型、系统调用等介绍

概述当我们要编写一个echo服务器程序的时候&#xff0c;需要对用户从标准输入键入的交互命令做出响应。在这种情况下&#xff0c;服务器必须响应两个相互独立的I/O事件&#xff1a;1)网络客户端发起网络连接请求&#xff0c;2)用户在键盘上键入命令行。我们先等待哪个事件呢&am…

余弦计算相似度度量

目录 pytorch 余弦相似度&#xff0c; 余弦计算相似度度量 pytorch 余弦相似度&#xff0c; 余弦相似度1到-1之间&#xff0c;1代表正相关&#xff0c;0代表不相关&#xff0c;-1代表负相关 def l2_norm(input, axis1):norm torch.norm(input, 2, axis, True)output torc…

程序员表白简短html代码,【杂谈】2018浪漫七夕:7款程序员必备表白源码(超炫酷)...

2018七夕将要来临&#xff0c;ki4网给大家准备了七款程序员表白专用源码&#xff0c;让你可以一举俘获美人心&#xff0c;下面就来看一看这七款表白代码大全&#xff0c;包含html、html5、CSS、JQ编写的一些非常简单实用的表白代码&#xff0c;非常炫酷、浪漫&#xff01;1、CS…