KD樹


什么是KD樹

  Kd-樹是K-dimension tree的縮寫,是對數據點在k維空間(如二維(x,y),三維(x,y,z),k維(x,y,z..))中划分的一種數據結構,主要應用於多維空間關鍵數據的搜索(如:范圍搜索和最近鄰搜索)。本質上說,Kd-樹就是一種平衡二叉樹。

    首先必須搞清楚的是,k-d樹是一種空間划分樹,說白了,就是把整個空間划分為特定的幾個部分,然后在特定空間的部分內進行相關搜索操作。想像一個三維空間,kd樹按照一定的划分規則把這個三維空間划分了多個空間,如下圖所示:

                    

 

KD樹的構建

  偽代碼描述:

              

  

  舉一個簡單直觀的實例來介紹k-d樹構建算法。假設有6個二維數據點{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},數據點位於二維空間內,如下圖所示。為了能有效的找到最近鄰,k-d樹采用分而治之的思想,即將整個空間划分為幾個小部分,首先,粗黑線將空間一分為二,然后在兩個子空間中,細黑直線又將整個空間划分為四部分,最后虛黑直線將這四部分進一步划分。

  6個二維數據點{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}構建kd樹的具體步驟為:

  1. 確定:split域=x。具體是:6個數據點在x,y維度上的數據方差分別為39,28.63,所以在x軸上方差更大,故split域值為x;
  2. 確定:Node-data = (7,2)。具體是:根據x維上的值將數據排序,6個數據的中值(所謂中值,即中間大小的值)為7,所以Node-data域位數據點(7,2)。這樣,該節點的分割超平面就是通過(7,2)並垂直於:split=x軸的直線x=7;
  3. 確定:左子空間和右子空間。具體是:分割超平面x=7將整個空間分為兩部分:x<=7的部分為左子空間,包含3個節點={(2,3),(5,4),(4,7)};另一部分為右子空間,包含2個節點={(9,6),(8,1)};
  如上算法所述,kd樹的構建是一個遞歸過程,我們對左子空間和右子空間內的數據重復根節點的過程就可以得到一級子節點(5,4)和(9,6),同時將空間和數據集進一步細分,如此往復直到空間中只包含一個數據點。
 
  與此同時,經過對上面所示的空間划分之后,我們可以看出,點(7,2)可以為根結點,從根結點出發的兩條紅粗斜線指向的(5,4)和(9,6)則為根結點的左右子結點,而(2,3),(4,7)則為(5,4)的左右孩子(通過兩條細紅斜線相連),最后,(8,1)為(9,6)的左孩子(通過細紅斜線相連)。如此,便形成了下面這樣一棵k-d樹:
KD樹的最近鄰搜索算法

  k-d樹查詢算法的偽代碼:

 1 算法:k-d樹最鄰近查找  
 2 輸入:Kd,    //k-d tree類型  
 3      target  //查詢數據點  
 4 輸出:nearest, //最鄰近數據點  
 5      dist      //最鄰近數據點和查詢點間的距離  
 6   
 7 1. If Kd為NULL,則設dist為infinite並返回  
 8 2. //進行二叉查找,生成搜索路徑  
 9    Kd_point = &Kd;                   //Kd-point中保存k-d tree根節點地址  
10    nearest = Kd_point -> Node-data;  //初始化最近鄰點  
11   
12    while(Kd_point)  
13      push(Kd_point)到search_path中; //search_path是一個堆棧結構,存儲着搜索路徑節點指針  
14   
15       If Dist(nearest,target) > Dist(Kd_point -> Node-data,target)  
16        nearest  = Kd_point -> Node-data;    //更新最近鄰點  
17        Min_dist = Dist(Kd_point,target);  //更新最近鄰點與查詢點間的距離  ***/  
18      s = Kd_point -> split;                       //確定待分割的方向  
19   
20      If target[s] <= Kd_point -> Node-data[s]     //進行二叉查找  
21        Kd_point = Kd_point -> left;  
22      else  
23        Kd_point = Kd_point ->right;  
24    End while  
25   
26 3. //回溯查找  
27    while(search_path != NULL)  
28      back_point = 從search_path取出一個節點指針;   //從search_path堆棧彈棧  
29      s = back_point -> split;                      //確定分割方向  
30   
31      If Dist(target[s],back_point -> Node-data[s]) < Max_dist   //判斷還需進入的子空間。這里的Max_dist就是target與當前最近點的距離。  
32        If target[s] <= back_point -> Node-data[s]  
33          Kd_point = back_point -> right;  //如果target位於左子空間,就應進入右子空間  
34        else  
35          Kd_point = back_point -> left;    //如果target位於右子空間,就應進入左子空間  
36        將Kd_point壓入search_path堆棧;  
37   
38      If Dist(nearest,target) > Dist(Kd_Point -> Node-data,target)  
39        nearest  = Kd_point -> Node-data;                 //更新最近鄰點  
40        Min_dist = Dist(Kd_point -> Node-data,target);  //更新最近鄰點與查詢點間的距離的  
41    End while   

 

 

舉例:查詢點(2.1,3.1)

  星號表示要查詢的點(2.1,3.1)。通過二叉搜索,順着搜索路徑很快就能找到最鄰近的近似點,也就是葉子節點(2,3)。而找到的葉子節點並不一定就是最鄰近的,最鄰近肯定距離查詢點更近,應該位於以查詢點為圓心且通過葉子節點的圓域內。為了找到真正的最近鄰,還需要進行相關的‘回溯'操作。也就是說,算法首先沿搜索路徑反向查找是否有距離查詢點更近的數據點。

    以查詢(2.1,3.1)為例:

  1. 二叉樹搜索:先從(7,2)點開始進行二叉查找,然后到達(5,4),最后到達(2,3),此時搜索路徑中的節點為<(7,2),(5,4),(2,3)>,首先以(2,3)作為當前最近鄰點,計算其到查詢點(2.1,3.1)的距離為0.1414,
  2. 回溯查找:在得到(2,3)為查詢點的最近點之后,回溯到其父節點(5,4),並判斷在該父節點的其他子節點空間中是否有距離查詢點更近的數據點。以(2.1,3.1)為圓心,以0.1414為半徑畫圓,如下圖所示。發現該圓並不和超平面y = 4交割,因此不用進入(5,4)節點右子空間中(圖中灰色區域)去搜索;
  3. 最后,再回溯到(7,2),以(2.1,3.1)為圓心,以0.1414為半徑的圓更不會與x = 7超平面交割,因此不用進入(7,2)右子空間進行查找。至此,搜索路徑中的節點已經全部回溯完,結束整個搜索,返回最近鄰點(2,3),最近距離為0.1414。

 

 

 

 

 

舉例:查詢點(2,4.5)

    一個復雜點了例子如查找點為(2,4.5),具體步驟依次如下:

  1. 同樣先進行二叉查找,先從(7,2)查找到(5,4)節點,在進行查找時是由y = 4為分割超平面的,由於查找點為y值為4.5,因此進入右子空間查找到(4,7),形成搜索路徑<(7,2),(5,4),(4,7)>,但(4,7)與目標查找點的距離為3.202,而(5,4)與查找點之間的距離為3.041,所以(5,4)為查詢點的最近點;
  2. 以(2,4.5)為圓心,以3.041為半徑作圓,如下圖所示。可見該圓和y = 4超平面交割,所以需要進入(5,4)左子空間進行查找,也就是將(2,3)節點加入搜索路徑中得<(7,2),(2,3)>;於是接着搜索至(2,3)葉子節點,(2,3)距離(2,4.5)比(5,4)要近,所以最近鄰點更新為(2,3),最近距離更新為1.5;
  3. 回溯查找至(5,4),直到最后回溯到根結點(7,2)的時候,以(2,4.5)為圓心1.5為半徑作圓,並不和x = 7分割超平面交割,如下圖所示。至此,搜索路徑回溯完,返回最近鄰點(2,3),最近距離1.5。

  上述兩次實例表明,當查詢點的鄰域與分割超平面兩側空間交割時,需要查找另一側子空間,導致檢索過程復雜,效率下降。

 

    一般來講,最臨近搜索只需要檢測幾個葉子結點即可,如下圖所示:   
    但是,如果當實例點的分布比較糟糕時,幾乎要遍歷所有的結點,如下所示:
 
  研究表明N個節點的K維k-d樹搜索過程時間復雜度為:t worst=O(kN 1-1/k)。
  同時,以上為了介紹方便,討論的是二維或三維情形。但在實際的應用中,如SIFT特征矢量128維,SURF特征矢量64維,維度都比較大,直接利用k-d樹快速檢索(維數不超過20)的性能急劇下降,幾乎接近貪婪線性掃描。假設數據集的維數為D,一般來說要求數據的規模N滿足N»2 D,才能達到高效的搜索。所以這就引出了一系列對k-d樹算法的改進:BBF算法,和一系列M樹、VP樹、MVP樹等高維空間索引樹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM