工作也有一段時間了,在完成工作的同時,自己也學到了很多的知識,今天答辯,正好對以前所做的東西,所學習到的知識做了一個總結,突然發現自己有很多東西都要忘記了,而且有些東西本來都是一些現有的算法,由於項目需求就直接“拿來主義”,根本沒有吃透,所以,今天開始寫blog,對以往的知識做一個記錄,畢竟,有些東西自己理解的比別人講述的更深刻,如果不能及時記下來,時間長了一樣會忘記。
我自己的簡潔的理解:
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