KD树 C++实现

概述

已知样本空间如何快速查询得到其近邻?唯有以空间换时间,建立索引是最基本的解决方式。但是索引建立的方式各有不同,kd树只是是其中一种。它的思想如同分治法,即:利用已有数据对k维空间进行切分。
注意:在一维空间里面,二叉查找树就是KD树的情形。

在这里插入图片描述
对于一颗二叉查找树,可以在空间上理解:树的每个节点把对应父节点切成的空间再切分,从而形成各个不同的子空间。查找某点的所在位置时,就变成了查找点所在子空间。二叉查找树仅仅是一维,如果换到二维?

如下图, 这样可以将平面分为两个部分。在这里插入图片描述
如下图, 这样可以将平面分为四个部分。在这里插入图片描述
如下图, 这样可以将平面分为8个部分。
在这里插入图片描述
1.节点深度时是偶数值,再细分,利用X坐标(垂直的线)
2.节点深度时是奇数值,然后细分,使用Y坐标(水平线)

例子

在这里插入图片描述

伪代码

在这里插入图片描述

KDTree构建

KDTree::KDNode* KDTree::constructKDTree(std::list<Vector2f>& _data, uint32_t _depth)
{
	auto size = _data.size();
	if (size == 1)
		return new KDNode(_data.front());

	//深度是偶数
	if (_depth % 2 == 0)
		_data.sort([](Vector2f a, Vector2f b)
			{
				return a[X] < b[X];
			});
	//深度是奇数
	else
	{
		_data.sort([](Vector2f a, Vector2f b)
			{
				return a[Y] < b[Y];
			});
	}

	auto mid = size / 2;
	auto mid_ptr = _data.begin();
	std::advance(mid_ptr, mid);//找到中点的迭代器
	auto left_list = std::list<Vector2f>(_data.begin(), mid_ptr);//划分左子集
	auto right_list = std::list<Vector2f>(mid_ptr, _data.end());//划分左子集

	auto left_child = constructKDTree(left_list,_depth+1);
	auto right_child = constructKDTree(right_list, _depth + 1);

	//_depth % 2 表示X还是Y
	return new KDNode((*mid_ptr)[_depth % 2], left_child, right_child);
}
void KDTree::preprocessBoundaries(KDNode* _node, bool _isEventDepth)
{
	if (!_node || isALeaf(_node))
		return;
	//非叶子节点就是左右分界线,这个界限值
	if (_isEventDepth)//偶数深度->对x划分
	{
		if (_node->left)
		{
			_node->left->boundary = _node->boundary;//先初始化原先的边界
			_node->left->boundary.x_max = _node->value;
			preprocessBoundaries(_node->left, !_isEventDepth);
		}
		if (_node->right)
		{
			_node->right->boundary = _node->boundary;//先初始化原先的边界
			_node->right->boundary.x_min = _node->value;
			preprocessBoundaries(_node->right, !_isEventDepth);
		}
	}
	else
	{
		if (_node->left)
		{
			_node->left->boundary = _node->boundary;//先初始化原先的边界
			_node->left->boundary.y_max = _node->value;
			preprocessBoundaries(_node->left, !_isEventDepth);
		}
		if (_node->right)
		{
			_node->right->boundary = _node->boundary;//先初始化原先的边界
			_node->right->boundary.y_min = _node->value;
			preprocessBoundaries(_node->right, !_isEventDepth);
		}
	}
}
KDTree(std::list<Vector2f> _data)
{
	root = constructKDTree(_data, 0);
	//叶子节点是具体的数据,像B+树一样
	//非叶子节点储存的是边界线的值,偶数层是按照x分割,奇数层是按照y分割
	root->boundary = default_bound;
	preprocessBoundaries(root, true);//构造每个非叶子节点的区域边界
}

2D搜寻

例子

在这里插入图片描述
根据颜色依次搜寻
在这里插入图片描述

伪代码

在这里插入图片描述


boolKDTree::isInside(const KDRange& r1, const KDRange& r2)
{
	if (r1.x_min >= r2.x_min &&
		r1.x_max <= r2.x_max &&
		r1.y_min >= r2.y_min &&
		r1.y_max <= r2.y_max)
		return true;
	return false;
}

bool KDTree::isIntersect(const KDRange& r1, const KDRange& r2)
{
	if (r1.x_max < r2.x_min &&
		r1.x_min > r2.x_max)
		return false;
	if (r1.y_max < r2.y_min &&
		r1.y_min > r2.y_max)
		return false;
	return true;
}

bool KDTree::isInRange(const Vector2f& p, const KDRange& r)
{
	if (p[X] >= r.x_min && p[X] <= r.x_max &&
		p[Y] >= r.y_min && p[Y] <= r.y_max)
		return true;
	return false;
}
void KDTree::searchKDTree(KDNode*_node, KDRange _range, std::list<Vector2f>&_list)
{
	if (isALeaf(_node))
	{
		if (isInRange(_node->data, _range))
			_list.push_back(_node->data);
	}
	else//两边都得判断是因为如果_node处于_range之间
	{
		//左边界
		if (isInside(_node->left->boundary, _range))
		{
			traverse(_node->left, _list);
		}
		else if(isIntersect(_node->left->boundary,_range))
		{
			searchKDTree(_node->left, _range, _list);
		}
		//有右边界
		if (isInside(_node->right->boundary, _range))
		{
			traverse(_node->right, _list);
		}
		else if (isIntersect(_node->right->boundary, _range))
		{
			searchKDTree(_node->right, _range, _list);
		}
	}
}

KNN邻近搜寻

顾名思义,最近邻搜索是指接近性在给定集合中寻找最接近(或最相似的)到一个给定的点。
在这里插入图片描述
特殊情况,找到点4,2但是实际点是3,4
在这里插入图片描述

伪代码

在这里插入图片描述

void KDTree::nearestNeighbour(KDNode* _node, const Vector2f& _value, float& _current_distance, bool _even_depth, Vector2f& _current_nn)
{
	//如果是叶子节点那么就要计算距离并更新
	if (isALeaf(_node))
	{
		auto distance = sqrd_distance(_value, _node->data);
		if (distance < _current_distance)
		{
			_current_distance = distance;
			_current_nn = _node->data;
			return;
		}
	}
	else
	{
		auto index = _even_depth ? X : Y;
		if (_value[index] < _node->value)
		{
			nearestNeighbour(_node->left, _value, _current_distance, !_even_depth, _current_nn);
			if(fabs(_value[index]-_node->value)<_current_distance)//关键,如果距离另一边更近则继续搜寻
				nearestNeighbour(_node->right, _value, _current_distance, !_even_depth, _current_nn);
		}
		else
		{
			nearestNeighbour(_node->right, _value, _current_distance, !_even_depth, _current_nn);
			if (fabs(_value[index] - _node->value) < _current_distance)//关键,如果距离另一边更近则继续搜寻
				nearestNeighbour(_node->left , _value, _current_distance, !_even_depth, _current_nn);
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 可以回答这个问题。kd树搜索算法是一种高效的数据结构,用于解决多维空间中的最近邻搜索问题。它通过将空间划分为多个子空间,然后递归地构建kd树实现搜索。在搜索时,它可以快速地定位到目标点所在的叶子节点,并在该节点的邻居中查找最近邻点。 ### 回答2: KD树是一种用于点集搜索的数据结构。它是一种二叉树,每个节点代表一个k维的点。树的构建过程是根据点集分割空间,并将每个点作为节点插入到树中。 构建KD树的过程大致如下: 1. 选择分割平面:根据算法选择一个维度作为分割的平面。 2. 判断中位数:对于选定的维度,计算点集在该维度上的中位数。 3. 通过中位数划分点集:将点集根据中位数划分成小于中位数和大于中位数的两个子集。 4. 递归构建子树:对于划分出来的子集,递归地进行构建子树的过程。 在树的每个节点中,存储了一个k维点的坐标,以及指向左子树和右子树的指针。通过这样的结构,我们可以方便地对点集进行搜索。 当需要对KD树进行搜索时,可以采用以下算法: 1. 从根节点开始,找到最近的子节点作为当前最近点。 2. 在当前子节点的同一维度上沿着树向下搜索,直到叶子节点。 3. 更新当前最近点,如果当前节点更近则更新。 4. 回溯,判断当前节点的另一个子节点是否需要搜索。 5. 如果需要搜索则跳到2,否则结束搜索。 这样的搜索算法可以快速地找到给定点集中距离目标点最近的邻居点。同时,因为KD树对空间进行了分割,可以有效减少搜索过程中的计算量。 总之,KD树是一种用于点集搜索的高效算法,它通过对空间的划分构建了一个二叉树的数据结构,通过判断距离和回溯的方式进行搜索,可以快速找到距离目标点最近的邻居点。 ### 回答3: kd树搜索算法是一种高效的搜索算法,用于在高维空间中搜索最近邻点。该算法利用kd树的结构,在搜索过程中通过不断划分空间,将搜索范围缩小到最小。 具体来说,kd树是一种二叉树结构,在每个节点上选择一个维度进行划分,将数据集中该维度上的值分别划分到左右子树中。通过这样的划分,kd树能够将高维空间划分为多个低维空间。kd树的构建过程可以通过递归的方式实现。 在搜索过程中,首先根据目标点的值和当前节点的划分维度比较,确定搜索的方向。然后递归地在对应的子树中进行搜索。在搜索过程中,通过判断目标点到当前最近邻点的距离与当前节点到目标点的距离的关系,决定是否需要进一步搜索其他子树。最终,找到离目标点最近的点作为最近邻点。 kd树搜索算法的时间复杂度与树的深度相关,平均情况下能够达到O(logn)的时间复杂度。该算法在处理高维空间的搜索问题上具有较好的效果,比如在图像识别、文本分类等领域被广泛应用。 总之,kd树搜索算法是一种高效的搜索算法,在处理高维空间搜索问题上具有重要的应用价值。它通过划分空间、递归搜索等策略,能够快速找到最近邻点,为解决实际问题提供了有效的解决方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yhaida

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值