lab4完成过程及反思 与 kd-tree学习
Part1. lab4
题目大义
给定n组坐标,构建kd-tree,有m次询问,每次询问给出一个目标坐标,返回到此坐标的最近点坐标(距离计算方法为:曼哈顿距离、欧几里得距离、球坐标距离)
什么是kd-tree(详解kd-tree留到part2)
数据规模
节点数:n<=5000,询问数m<=100000
简单分析
建树
在数据结构课学习过平衡树之后,我们都能意识到二叉树的高度对询问的稳定性的显著影响。因此,对于这样的一个新品种二叉树,尽可能把它建的平衡是有必要的(先生,你也不想每次遍历整个链表吧)借鉴平衡树的思维,我们每次取序列中位数,即可保证左右子树规模相近,递归执行即可。
那么问题变成了,如何查找中位数。一个显而易见的想法:每次取中位数都对序列按照关键字排序。树高应该是 $O(logn) $ ,每次排序最快是 nlogn的。虽然对于本题可以接受,但如果,我是说如果,我拿出 $10^{5} $ 的数据,阁下又如何应对?
我们不妨借鉴快速排序的思想,我们只需要确定此次对中位数的划分是正确的即可,而划分的左右两侧并不要求有序。所以可以用快速排序的思想线性的解决这个问题。
好在,STL为我们提供了一个强力的函数:nth_element,可以减轻苦逼大学生的工作量
nth_element
template<typename _RAIter>
_GLIBCXX20_CONSTEXPR
void
nth_element(_RAIter, _RAIter, _RAIter);
template<typename _RAIter, typename _Compare>
_GLIBCXX20_CONSTEXPR
void
nth_element(_RAIter, _RAIter, _RAIter, _Compare);
这是他的源定义,_Compare是比较函数,而且 _Compare要求只能有两个参数,且这两个参数必须是被比较的同一类的两个元素
template<typename _RandomAccessIterator>
_GLIBCXX20_CONSTEXPR
inline void
nth_element(_RandomAccessIterator __first, _RandomAccessIterator __nth,
_RandomAccessIterator __last)
{
// concept requirements
__glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
_RandomAccessIterator>)
__glibcxx_function_requires(_LessThanComparableConcept<
typename iterator_traits<_RandomAccessIterator>::value_type>)
__glibcxx_requires_valid_range(__first, __nth);
__glibcxx_requires_valid_range(__nth, __last);
__glibcxx_requires_irreflexive(__first, __last);
if (__first == __last || __nth == __last)
return;
std::__introselect(__first, __nth, __last,
std::__lg(__last - __first) * 2,
__gnu_cxx::__ops::__iter_less_iter());
}
这个是STL实现nth_element的方法,本质上就是以nth为划分的一次快排,所以是O(n)的
求解思路解析
如果没有助教的提示,我们会怎么思考这个问题?
基于kd-tree 我们会有几个思路
1.遍历
每次询问从树根遍历每一个点,记录最短距离,并更新答案。每次查询都是O(n)的,爽翻了
2.遍历+剪枝
从根开始,按一定顺序遍历节点。如果target到当前节点代表的划分的距离大于当前最优距离,那么这个节点的另一个儿子就不用被遍历。加入这个剪枝,可以减少很多不必要的计算,从而提高程序效率。
层序遍历 76分
先序遍历96分
还有一个细节,可以进一步提高运行速度:我们优先查找target所在的那棵子树,这样子可以最快的找到期望解。即使它不是最后的解,但他能最大限度的剪枝,从而提高运算效率。
通过这个方法可以通过这道题,用时:3.350s
反思
这次lab最大的收获便是kd-tree这样的数据结构以及搜索和剪枝的思想。如何让搜索树变得更瘦,其实是这个题的核心问题。
但如何写tree.build函数,也会很大的影响debug的效率(本人曾写了一个100+行的build,而最后的版本只有20行
Part2.kd-tree
留个坑,等我周末学完再写