算法學習系列之初章---R-Tree

        工作也有一段時間了,在完成工作的同時,自己也學到了很多的知識,今天答辯,正好對以前所做的東西,所學習到的知識做了一個總結,突然發現自己有很多東西都要忘記了,而且有些東西本來都是一些現有的算法,由於項目需求就直接“拿來主義”,根本沒有吃透,所以,今天開始寫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

發佈了33 篇原創文章 · 獲贊 11 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章