算法学习系列之初章---R-Tree

来源:互联网 发布:五毛钱特效制作软件 编辑:程序博客网 时间:2024/06/08 10:56

        工作也有一段时间了,在完成工作的同时,自己也学到了很多的知识,今天答辩,正好对以前所做的东西,所学习到的知识做了一个总结,突然发现自己有很多东西都要忘记了,而且有些东西本来都是一些现有的算法,由于项目需求就直接“拿来主义”,根本没有吃透,所以,今天开始写blog,对以往的知识做一个记录,毕竟,有些东西自己理解的比别人讲述的更深刻,如果不能及时记下来,时间长了一样会忘记。


废话了那么多,进入本次的主题:R-tree。有关R-tree的介绍有很多,我觉得百度百科就介绍的差不多了,
我自己的简洁的理解:
1.R-tree是B-Tree想多维度的一种扩展。理解这句话就得明白什么是B-tree,个人感觉是,B树的查询机制有点象“折半查找”的思想,只不过折半查找是针对有序数组,B树其实不难,先把B树看明白了,然后再看R-tree就明白这到底是个什么东东了!
2.R-tree是一种n叉数,这个N到底等于多少呢,就要看你的节点的大小了,一般,每个非叶子节点所包含的所有节点的大小为一个磁盘页的大小最好,为什么呢,我的理解是,这样能够充分利用支持空间索引结构的数据库的特点,每个非叶子节点占一个磁盘页,这样,在数据库组织数据的时候就很方便,磁盘上读取的时候也更快。(由于本人数据库学的不好,这里就不多少了,言多必失)。
3.这就是讲到了所谓的平衡,为什么R-tree是高度平衡的树呢?这就和他的构造过程和原则有关了,R-Tree是一种向上生长的树,构造过程下面再说。
4.如果一个Rtree是4叉树,那么,每个非叶子节点至少要有2个孩子,当让,最多4个(废话)。这样就保证了每个非叶子节点所包含的孩子能保持差不多,这样增大了空间的利用率,为什么呢,请参见第2!!!
5.还有什么呢,下面直接进入代码的分析吧

个人认为对于一段代码,最难理解的就是各种数据结构的定义,尤其是各种自定义数据类型多了就懵了,下面就想说明一下源码中各种数据类型的用处,因为基本上都有因为注释,这里就简单的说明几个

#define  PAGE_SIZE    512    ////这个就不多说了,磁盘页大小,每一条记录最好存在一个磁盘页中,根据这个大小就可以算出//每一条记录blob的大小,或者说节点的个数typedef struct _RTREEPARTITION{    int            partition[MAXCARD+1];          /////    int            total;                         /////总共待分配的点的个数    int            minfill;                       ////节点最小满足状态    int            taken[MAXCARD+1];              ////记录是否已经分配了父节点    int            count[2];                      ////两个父节点中子节点的个数    RTREEMBR    cover[2];                         /////    REALTYPE    area[2];                          /////面积} RTREEPARTITION;            //////节点分裂,就需要将要分裂的节点下的所有节点和要插入的节点保存下来,///////分裂出来两个节点之后就要为新生成的两个冲分配给个子节点,RTREEBRANCH        BranchBuf[MAXCARD+1];         ///////将要分裂的点的所有分支和要插入的分支存储起来  待分配父节点int                BranchCount;                   /////分支总数RTREEMBR        CoverSplit;                      ////REALTYPE        CoverSplitArea;                  ////RTREEPARTITION    Partitions[METHODS];           ////个人认为应该是有几种重新分配子节点的方法,此源码提供一种 #define INVALID_RECT(x) ((x)->bound[0] > (x)->bound[DIMS_NUMB])  /////验证空间图形的有效性(最小边是否比最大边小)/*下面先来介绍一下插入操作*/RTREEMBR rects[];////////要插入的空间的几个对象的三维范围   boundingbox//下面开始进入主函数,分析插入的过程 RTREENODE* root = RTreeCreate();//初始化根节点 for(i=0; i < nrects; i++)   //循环将所有节点插入 {   RTreeInsertRect(&rects[i],  /* the mbr being inserted */   i+1,        /* i+1 is mbr ID. ID MUST NEVER BE ZERO */     ////    个人认为应该因为根节点已经占据了ID为0,所有以后的节点id从1开始   &root,        /* the address of rtree's root since root can change undernieth*/   0            /* always zero which means to add from the root */    ); }//下面进入插入函数int RTreeInsertRect( RTREEMBR *rc, int tid, RTREENODE **root, int level){#ifdef _DEBUG    int i;#endif    RTREENODE    *newroot;    RTREENODE    *newnode;    RTREEBRANCH b;        assert(rc && root);    assert(level >= 0 && level <= (*root)->level);#ifdef _DEBUG    for (i=0; i<DIMS_NUMB; i++)             ///////////////判断boundbox是否有效   即Xmin是否小于Xmax  ...        assert(rc->bound[i] <= rc->bound[DIMS_NUMB+i]);#endif    /* root split */    if (_RTreeInsertRect(rc, tid, *root, &newnode, level))/*_RTreeInsertRect返回0说明插入的过程没有分裂 返回1说明分裂 然后进入下面的分裂处理*/    {        newroot = RTreeNewNode();  /* grow a new root, & tree taller */        newroot->level = (*root)->level + 1;        b.mbr = RTreeNodeCover(*root);        b.child = *root;        RTreeAddBranch(&b, newroot, NULL);        b.mbr = RTreeNodeCover(newnode);        b.child = newnode;        RTreeAddBranch(&b, newroot, NULL);        *root = newroot;                return 1;    }    return 0;}static int _RTreeInsertRect( RTREEMBR *rc, int tid,  RTREENODE *node, RTREENODE **new_node, int level)  //递归 将要//插入的节点放到          /////与所有//叶子节点同级{    int i;    RTREEBRANCH b;    RTREENODE *n2;    assert(rc && node && new_node);    assert(level >= 0 && level <= node->level);    /* Still above level for insertion, go down tree recursively */    if (node->level > level)    {        i = RTreePickBranch(rc, node);////选取分支,选取的基本原则就是插入那个分支引起的体积增长最小        if (!_RTreeInsertRect(rc, tid, node->branch[i].child, &n2, level))  ////递归        {            /* child was not split */            node->branch[i].mbr = RTreeCombineRect(rc, &(node->branch[i].mbr));  ///不需要分裂,就combine两个立方体            return 0;        }                /* child was split */        node->branch[i].mbr = RTreeNodeCover(node->branch[i].child);   ////返回一个节点的mbr        b.child = n2;        b.mbr = RTreeNodeCover(n2); ///        return RTreeAddBranch(&b, node, new_node);    }        else if (node->level == level)    /* Have reached level for insertion. Add mbr, split if necessary */    {        b.mbr = *rc;#pragma warning(push)    /* C4312 */#pragma warning( disable : 4312 )        b.child = ( RTREENODE *) tid;#pragma warning(pop)        /* child field of leaves contains tid of data record */        return RTreeAddBranch(&b, node, new_node);    }        /* Not supposed to happen */    assert (FALSE);    return 0;}

这只是整个R-tree构建过程的一个主要框架,那么,这个过程有哪些地方比较难呢,无疑就是节点的分裂与合并,
首先说下分裂由于在办公室,上传不了图片,所以不能很直观的描述了


那就简单的说,比如现在有个非叶子节点,已经有四个孩子a,b,c,d了(四叉树),那么,当第五个孩子e来了怎么办呢?没办法,这个非叶子节点就要一分为二,分裂后,这五个孩子怎么分配呢,根据原则,每个非叶子节点至少有两个孩子,那么,怎么选呢?这五个孩子都是一个矩形范围,那么,相信大家找了相关资料后也知道什么是传说中的最小外接矩形了吧,这五个孩子,两两计算最小外接矩形,哪两个孩子所构成的最小外接矩形最大,就得把他们分开放到分裂出来的两个新节点中!相信大家也明白为什么吧!不明白?好吧,其实,这就涉及到对R-tree的算法改进问题了,为什么要改进呢?原因就是最小外接矩形重叠的太多了,这样搜索的效率就会降低,所以,理想状态下所有的外界矩形都不相交重合最好对吧,如果你将那两个构成的最小外接矩形最大的孩子放一起了,是不是就增大了矩形之间的重叠呢!清楚了吧!


至于删除节点的时候的节点合并的问题,我做的项目中暂时没有用到,所以也没有深入的研究,但是我感觉其原理和分裂的是差不多的一个过程吧,肯定也会涉及到孩子的分配问题,用最小增长面积法就可以解决了。
那么,现在这颗树就算是成功的建立了,至于怎么将数据写到数据库中,那就要用到了树的层次遍历了。学习数据结构的时候感觉被树的各种前中后遍历,广度深度优先遍历整的一愣一愣的,不过,层次遍历却是不是很难,在网上找找代码,自己阅读就可以,最好是跟代码,一步一步,其实我对R-tree的理解也是跟代码跟出来的,纸上得来终觉浅,绝知此事要躬行!一步一步跟代码,比任何人讲的都要清楚明了!想要源代码?自己找去,网上一大片呢。


后记:R-tree有很多的变种,这些变种的最终目的就是想减小外接矩形的重叠,提高搜索的效率,很多变种我也没有深入的研究过,不过 希尔伯特R-tree貌似很高端的样子,不过首先要理解希尔伯特曲线,而且看到过一个论文,题目是基于聚类的希尔伯特R-tree算法的实现!是不是显得更高端,当然,要想弄明白,还要认真学习啊!
菜鸟贴,高手看了笑笑就好,哪里表述错了还请指正,在此,多谢!
2014-02-17

0 0
原创粉丝点击