这 是 一 个 伪 代 码!!!
写这篇文章的目的在于理解kd树在KNN算法中的应用, 弄清楚整个搜索和回溯过程
首先, 定义kd树结点的结构体
#include <stdio.h>
typedef struct KD_Node
{
int kindex; //关键点直方图方差最大向量系列位置
int kvalue;//直方图方差最大向量系列中最中间模值
int n; //特征向量的维度
int leaf; //是否是叶子结点, 是则为1
struct KD_Node* left; //左孩子结点
struct KD_Node* right; //右孩子结点
struct Feature_Node* data; //特征向量
}KD_Node;
然后, 定义一个函数, 这个函数的输入是一个kd树根节点和target(即待分类的样本)
输出是target在搜索过程中遇到的叶子结点
KD_Node* bitSearch(KD_Node kdtree, elemtype target, int k)
{
//进行二叉搜索, 找到target所在区域的叶子结点, k为维度
KD_Node* head = kdtree;
int split_dim = kdtree->kindex; //初始的split维度
while(head->leaf == 0) //只要不是叶子结点
{
if(target[split_dim]<=head->data[split_dim])
{
head = head->left;
}
else
{
head = head->right;
}
}//此时head变成了叶子结点
min_dist = Dist(head, target);//求此时叶子结点与target之间的距离,并作为最小距离
return head;
}
接着, 就是KNN的伪代码啦
我们 首先需要将target和kd树的根节点让进二叉搜索的函数, 扔完获得一个堆栈stack, 它保存了在搜索过程(search path)中遇到的每个结点, 同时, 一旦这次搜索过程结束, 我们就可以得到target所属小区域的叶子结点了, 计算与它的距离作为当前最小距离, 并把这个叶子结点初始化为最近邻点
然后; 我们需要从堆栈中一一弹出之前遇到的每个结点, 对于每个结点M:
1.都要计算target与这个结点M所在超平面的距离(在split维度上的坐标差值), 如果距离小于当前最小距离, 则证明在这个超平面的另一侧很可能存在最近邻点. 那我们就得去搜一搜!!也就是得把另一侧的结点和target一起扔进二叉搜索函数找找离target最近的叶子结点. 那么, 如果target在这个超平面的左侧(也就是这个结点的左分支), 我们就要对M的右子树进行搜索, 反之则对左子树搜索.
2. 搜索完事, 如果计算的距离小于当前最小距离, 则更新最小距离和最近邻点
void KNN(KD_Node kdtree, elemtype target)
{
//进行二叉搜索, 找到target所在区域的叶子结点
STACK stack;
KD_Node* head = kdtree;
int split_dim = kdtree->kindex; //初始的split维度
stack.push(head);
while(head->leaf == 0) //只要不是叶子结点
{
if(target[split_dim]<=head->data[split_dim])
{
head = head->left;
stack.push(head);
}
else
{
head = head->right;
stack.push(head);
}
}//此时head变成了叶子结点
stack.push(head);
int min_dist = Dist(head, target);//求此时叶子结点与target之间的距离,并作为最小距离
KD_Node* MIN_node = head; //初始化此时的最近邻结点为叶子结点
//接下来该从叶子结点逆向回溯
while(!stack.Isempty) //只要stack不为空
{
node = stack.pop(); //弹出一个最近搜索过的元素
int split_dim = node->kindex;
if(Dist(node->data[split_dim], target[split_dim]) < min_dist)
//证明在当前结点的子区域内存在比当前结点更近的点
{
if(!node->leaf)//保证了当前结点不是叶子结点, 也就保证了它一定有子区域
{
//如果target在当前结点的左边, 则从右边寻找
if(target[split_dim] < node->data[split_dim])
node = node->right;
else
node = node->left;
}
KD_Node* min_node = bitSearch(node, target);
dist = Dist(min_node, target);//求此时叶子结点与target之间的距离,并作为最小距离
if(min_dist > dist)
{
min_dist = dist;
MIN_node = min_node;
}
}
}
}
拿统计学习方法44页的例题为例:
在第一次二叉搜索过程中, stack中保存的结点依次是 :A->B->D
然后pop D,
计算target结点s到D结点所在平面的距离, 小于当前最小距离, 但是由于D是叶子结点, 因此不存在子节点, 当然在D的另一个子区域内就不存在距离更近的点了
接下来 pop B
计算结点s与B所在平面的距离, 发现大于当前最小距离
接下来pop A
计算结点s与A所在平面的距离, 小于当前最小距离, 因此, 在A的另一侧可能存在距离target更近的点
若target在A的左侧(即左分支), 则应在A的右侧(右分支)找, 为什么呢? 因为A的左侧都已经回溯完了, 就该右侧啦
即此时node变为结点C; 也就是说以C为根节点的分支中可能存在最近邻点.
那么就要从C结点出发, 依次与target作比较. 一直搜索到叶子结点E, 并将结点E与target s计算距离, 若小于当前最小距离, 则更新最近邻点和最近距离