一文了解M-Tree

wlg於初發表於2020-12-01

概括

這篇部落格分析了論文Indexing Metric Spaces with M-tree(後文簡稱M-Tree),我將從以下幾個方面介紹它:

  1. 背景
  2. M-Tree的結構
  3. M-Tree的插入
  4. M-Tree的分裂
  5. 如何使用M-Tree進行range搜尋
  6. M-Tree的程式碼

背景

M-tree被設計為了解決最近鄰搜尋問題(NN)或範圍搜尋問題。以生活中的例子作為說明,最近鄰搜尋問題(NN)指的是,給定一個地理位置,在資料集中找到距離給定位置最近的目標(比如最近的飯店,最近的銀行等等)。
範圍搜尋問題指的是,給定一個地理位置和搜尋範圍,在資料集中找到距離小於範圍的所有目標。

M-Tree核心思路

M-tree使用圓形(二維情況)覆蓋空間區域。每個圓涵蓋了一部分資料所在的區域。對於查詢目標,M-tree確定哪個圓和目標區域相交,若相交,則繼續探測對應的圓。若不相交則濾除對應的圓,這意味著該圓涵蓋的資料都不需要進行判斷,減少了需要探測的資料量。本質上,M-tree從上到下就是一個大圓包含小圓的結構。
在這裡插入圖片描述

M-Tree的結構

在這一節分析如果構建M-tree。M-Tree使用圓來劃分割槽域,因此每個結點存在一個圓心和一個半徑。M-Tree的分為兩類結點:內結點,葉結點。每個結點儲存0~M個孩子結點。每個結點的多個屬性我們用一個entry類來組織,不同型別的結點entry類是不同的:

內節點:它是包含了

  • entry ⁡ ( O r ) = [ O r , ptr ⁡ ( T ( O r ) ) , r ( O r ) , d ( O r , P ( O r ) ) ] \operatorname{entry}\left(O_{r}\right)=\left[O_{r}, \operatorname{ptr}\left(T\left(O_{r}\right)\right), r\left(O_{r}\right), d\left(O_{r}, P\left(O_{r}\right)\right)\right] entry(Or)=[Or,ptr(T(Or)),r(Or),d(Or,P(Or))]

O r O_r Or是結點圓心值, d ( O j , P ( O j ) ) d\left(O_{j}, P\left(O_{j}\right)\right) d(Oj,P(Oj))是當前結點圓心值到父節點圓心值的距離, r ( O r ) r(O_r) r(Or)是圓的半徑, p t r ( T ( O r ) ) ptr \left(T\left(O_{r}\right)\right) ptr(T(Or))是指向孩子結點的指標。

葉結點:

  •  entry  ( O j ) = [ O j ,  oid  ( O j ) , d ( O j , P ( O j ) ) ] \text { entry }\left(O_{j}\right)=\left[O_{j}, \text { oid }\left(O_{j}\right), d\left(O_{j}, P\left(O_{j}\right)\right)\right]  entry (Oj)=[Oj, oid (Oj),d(Oj,P(Oj))]

O j O_j Oj是目標的值, d ( O j , P ( O j ) ) d\left(O_{j}, P\left(O_{j}\right)\right) d(Oj,P(Oj))是當前結點值和父節點值的距離, o i d ( O j ) oid(O_j) oid(Oj)是葉節點儲存的資料在資料集中的id或指標,

M-Tree的插入

在這一節描述M-tree的插入過程。插入過程需要將一個資料插入到對應的葉節點當中(同樣地,葉節點只能存放0-M個資料)。
從根節點開始,找到插入資料對應的葉節點有以下關鍵思路:

找到正確的孩子結點(最小範圍擴大):

  • 計算每個孩子結點的圓心值和插入資料的距離dist
  • 如果存在dist小於某些孩子結點的半徑(這意味著插入帶點不用擴大結點的半徑),則選擇最小dist對應的孩子結點。
  • 若不存在,選擇dist減去半徑最小的孩子結點作為結果

葉節點開始分裂

  • 到達葉節點後,若葉節點未滿,則直接將資料插入
  • 若葉節點滿了,則需要將結點進行分裂。

M-Tree的分裂

對於一個滿的結點,我們需要將它分裂成兩個結點。然後

  • 孩子結點按一定方式分配到兩個結點中
  • 修改父節點的指標,使其指向兩個結點在這裡插入圖片描述

下面我以下面的程式碼舉例分析

  • 生成兩個新結點(行6-7)
  • 儲存原結點的全部孩子結點和帶插入結點到陣列entrys中
  • 在陣列entrys中選擇兩個結點,將其屬性更新到兩個新結點。選擇結點有很多中方式,比如最小範圍覆蓋,隨機等 (行13)
  • 將陣列entrys中的全部結點劃分到兩個新結點中 (行16)
  • 如果當前節點不是葉節點:
  • 修改新結點和父節點的關係(行39-42)
  • 將其中新結點1的全部屬性劃分到當前結點中,刪除新結點1(行44和48,注意45,46行是PM-Tree的內容,和本文無關)
  • 如果父節點未滿,則將新結點二插入到父節點中(行54)
  • 如果父節點滿,向上分裂父節點(行50-52)
  • 如果當前節點不是葉節點:類似處理
1 void PM_Tree::Split(M_Node_St ** cur_node_ptr_address_, M_Node_St ** insert_node_address_)
2 {
3 	M_Node_St * cur_node_ = *cur_node_ptr_address_;
4 	M_Node_St * insert_node_ = *insert_node_address_;
5 	/*spilt two new nodes */
6 	M_Node_St* new_Mnode_1 = new M_Node_St(nullptr, 0, -1, -1);
7 	M_Node_St* new_Mnode_2 = new M_Node_St(nullptr, 0, -1, -1);
8 
9 	vector<M_Node_St*>  entries = cur_node_->ptr_sub_tree;
10	entries.emplace_back(insert_node_);
11	
12	/*decide featuer value of node_1 and node_2  basing on entries*/
13	Promote(entries, new_Mnode_1, new_Mnode_2);
14	
15	/*divide ertries into node_1 and node_2  */
16	Partition(entries, new_Mnode_1, new_Mnode_2);
17
18	if (Is_Root_Node(cur_node_)) {
19		M_Node_St* new_root = new M_Node_St(nullptr,0,-1,-1);
20		new_root->feature_val = new_Mnode_1->feature_val;/*update root feature value*/
21
22		/*update information*/
23		new_Mnode_1->patent_node = new_root;
24		new_Mnode_1->dist_to_parent = Cal_Dist_To_Parent(new_Mnode_1);
25		new_Mnode_2->patent_node = new_root;
26		new_Mnode_2->dist_to_parent = Cal_Dist_To_Parent(new_Mnode_2);
27		Merge_subNode_HR(new_Mnode_1);
28		Merge_subNode_HR(new_Mnode_2);
29
30
31		new_root->ptr_sub_tree.emplace_back(new_Mnode_1);
32		new_root->ptr_sub_tree.emplace_back(new_Mnode_2);
33		new_root->range = Cal_Cover_Radius(new_root);
34		Merge_subNode_HR(new_root);
35		root = new_root;
36	}
37	else {
38		/*update information*/
39		new_Mnode_1->patent_node = cur_node_->patent_node;
40		new_Mnode_1->dist_to_parent = Cal_Dist_To_Parent(new_Mnode_1);
41		new_Mnode_2->patent_node = cur_node_->patent_node;
42		new_Mnode_2->dist_to_parent = Cal_Dist_To_Parent(new_Mnode_2);
43		/*將new_Mnode_1中元素替換cur_node_元素,並更新父節點*/
44		assign_Node_All_Value(cur_node_, new_Mnode_1);
45		Merge_subNode_HR(cur_node_);
46 		Merge_subNode_HR(new_Mnode_2);
47		/*delete memory*/
48		delete(new_Mnode_1);
49
50		if (Is_Full(new_Mnode_2->patent_node)) {
51			Split(&(new_Mnode_2->patent_node), &new_Mnode_2);
52		}
53		else {		
54			new_Mnode_2->patent_node->ptr_sub_tree.emplace_back(new_Mnode_2);
55		}
56	}
57}
58

使用M-Tree進行range搜尋

M-Tree的程式碼

很早以前寫的程式碼,大致上的PM-tree是沒有問題的。在程式設計上存在血多瑕疵,希望大家多多指點
https://github.com/duoyw/PM-Tree

相關文章