有各种具有不同属性和输入的聚类算法。在选择算法之前需要考虑的是您想要实现什么。您在问题中提到的k-means旨在将点集划分为k个簇。因此,输入是所需的簇数。另一方面,您链接的博客中描述的算法是贪婪聚类算法的一种变体,其目的是将一组点划分为一定大小的圆形聚类。输入是所需簇的半径。
有各种算法执行k-means聚类,用于不同的数据和应用,如用超平面分离2个n维子集或用Voronoi图(劳埃德算法)聚类,通常称为k-means算法。在您的问题下方的评论中,还有@anonymousse提到的基于密度的聚类算法。
在这篇文章中,您提到了它是贪婪聚类的层次结构版本。他们必须计算多个缩放级别的聚类,并避免每次使用之前分析级别的聚类质心作为下一级别聚类的点源时分析所有点。然而,在这个答案中,我将展示如何仅在一个级别上实现这个算法。因此,输入将是一组点和一个半径大小的簇。如果需要分层版本,则应计算输出集群的质心,并将其用作下一级算法的输入。
使用Boost。几何R-树一个级别(因此不是层次)的算法可以这样实现(在C++11中):
#include <boost/geometry.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <boost/range/adaptor/indexed.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <iostream>
#include <vector>
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
typedef bg::model::point<double, 2, bg::cs::cartesian> point_t;
typedef bg::model::box<point_t> box_t;
typedef std::vector<point_t> cluster_t;
template <typename First, typename Second>
struct pair_generator
{
typedef std::pair<First, Second> result_type;
template<typename T>
inline result_type operator()(T const& v) const
{
return result_type(v.value(), v.index());
}
};
struct point_data
{
point_data() : used(false) {}
bool used;
};
void find_clusters(std::vector<point_t> const& points,
double r,
std::vector<cluster_t> & clusters)
{
typedef std::pair<point_t, std::size_t> value_t;
typedef pair_generator<point_t, std::size_t> value_generator;
if (r < 0.0)
return;
bgi::rtree<value_t, bgi::rstar<4> >
rtree(points | boost::adaptors::indexed()
| boost::adaptors::transformed(value_generator()));
std::vector<point_data> points_data(rtree.size());
for(auto const& v : rtree)
{
if (points_data[v.second].used)
continue;
point_t const& p = v.first;
double x = bg::get<0>(p);
double y = bg::get<1>(p);
std::vector<value_t> res;
rtree.query(
bgi::intersects(box_t{{x-r, y-r},{x+r, y+r}})
&& bgi::satisfies([&](value_t const& v){
return points_data[v.second].used == false
&& bg::distance(p, v.first) <= r;
}),
std::back_inserter(res));
clusters.push_back(cluster_t());
for(auto const& v : res) {
clusters.back().push_back(v.first);
points_data[v.second].used = true;
}
}
}
int main()
{
std::vector<point_t> points;
for (double x = 0.0 ; x < 10.0 ; x += 1.0)
for (double y = 0.0 ; y < 10.0 ; y += 1.0)
points.push_back(point_t{x, y});
std::vector<cluster_t> clusters;
find_clusters(points, 3.0, clusters);
for(size_t i = 0 ; i < clusters.size() ; ++i) {
std::cout << "Cluster " << i << std::endl;
for (auto const& p : clusters[i]) {
std::cout << bg::wkt(p) << std::endl;
}
}
}
另请参见其实施:
https://github.com/mapbox/supercluster/blob/master/index.js#L216
此外,还要考虑到@Anony Mouse关于全球距离计算准确性的评论。上述解决方案适用于笛卡尔坐标系。如果要使用不同的坐标系,则必须以不同的方式定义点类型,例如使用
bg::cs::spherical_equatorial<bg::degree>
或
bg::cs::geographic<bg::degree>
而不是
bg::cs::cartesian
. 您还必须以不同的方式生成查询边界框。但是
bg::distance()
更改点类型后将自动返回正确的距离。