热线电话:13121318867

登录
首页精彩阅读KD树的构建_数据分析师
KD树的构建_数据分析师
2014-11-30
收藏

KD树的构建_数据分析师

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树:

 

    k-d树的数据结构

    针对上表给出的kd树的数据结构,转化成具体代码如下所示(注,本文以下代码分析基于Rob Hess维护的sift库)

  1. /** a node in a k-d tree */  
  2. struct kd_node  
  3. {  
  4.     int ki;                      /**< partition key index *///关键点直方图方差最大向量系列位置  
  5.     double kv;                   /**< partition key value *///直方图方差最大向量系列中最中间模值  
  6.     int leaf;                    /**< 1 if node is a leaf, 0 otherwise */  
  7.     struct feature* features;    /**< features at this node */  
  8.     int n;                       /**< number of features */  
  9.     struct kd_node* kd_left;     /**< left child */  
  10.     struct kd_node* kd_right;    /**< right child */  
  11. };  

    也就是说,如之前所述,kd树中,kd代表k-dimension,每个节点即为一个k维的点。每个非叶节点可以想象为一个分割超平面,用垂直于坐标轴的超平面将空间分为两个部分,这样递归的从根节点不停的划分,直到没有实例为止。经典的构造k-d tree的规则如下:

  1. 随着树的深度增加,循环的选取坐标轴,作为分割超平面的法向量。对于3-d tree来说,根节点选取x轴,根节点的孩子选取y轴,根节点的孙子选取z轴,根节点的曾孙子选取x轴,这样循环下去。
  2. 每次均为所有对应实例的中位数的实例作为切分点,切分点作为父节点,左右两侧为划分的作为左右两子树。

    对于n个实例的k维数据来说,建立kd-tree的时间复杂度为O(k*n*logn)。

    以下是构建k-d树的代码:

  1. struct kd_node* kdtree_build( struct feature* features, int n )  
  2. {  
  3.     struct kd_node* kd_root;  
  4.   
  5.     if( ! features  ||  n <= 0 )  
  6.     {  
  7.         fprintf( stderr, "Warning: kdtree_build(): no features, %s, line %d\n",  
  8.                 __FILE__, __LINE__ );  
  9.         return NULL;  
  10.     }  
  11.   
  12.     //初始化  
  13.     kd_root = kd_node_init( features, n );  //n--number of features,initinalize root of tree.  
  14.     expand_kd_node_subtree( kd_root );  //kd tree expand  
  15.   
  16.     return kd_root;  
  17. }  

    上面的涉及初始化操作的两个函数kd_node_init,及expand_kd_node_subtree代码分别如下所示:

  1. static struct kd_node* kd_node_init( struct feature* features, int n )  
  2. {                                     //n--number of features  
  3.     struct kd_node* kd_node;  
  4.   
  5.     kd_node = (struct kd_node*)(malloc( sizeofstruct kd_node ) ));  
  6.     memset( kd_node, 0, sizeofstruct kd_node ) ); //0填充  
  7.     kd_node->ki = -1; //???????  
  8.     kd_node->features = features;  
  9.     kd_node->n = n;  
  10.   
  11.     return kd_node;  
  12. }  
  1. static void expand_kd_node_subtree( struct kd_node* kd_node )  
  2. {  
  3.     /* base case: leaf node */  
  4.     if( kd_node->n == 1  ||  kd_node->n == 0 )  
  5.     {   //叶节点               //伪叶节点  
  6.         kd_node->leaf = 1;  
  7.         return;  
  8.     }  
  9.   
  10.     assign_part_key( kd_node ); //get ki,kv  
  11.     partition_features( kd_node ); //creat left and right children,特征点ki位置左树比右树模值小,kv作为分界模值  
  12.                                  //kd_node中关键点已经排序  
  13.     if( kd_node->kd_left )  
  14.         expand_kd_node_subtree( kd_node->kd_left );  
  15.     if( kd_node->kd_right )  
  16.         expand_kd_node_subtree( kd_node->kd_right );  
  17. }  

    构建完kd树之后,如今进行最近邻搜索呢?从下面的动态gif图中,你是否能看出些许端倪呢?

    k-d树算法可以分为两大部分,除了上部分有关k-d树本身这种数据结构建立的算法,另一部分是在建立的k-d树上各种诸如插入,删除,查找(最邻近查找)等操作涉及的算法。

数据分析咨询请扫描二维码

最新资讯
更多
客服在线
立即咨询