
1. 项目概述从“两点之间直线最短”到代码实现“欧几里得距离”这个名字听起来可能有点学术但它的概念其实简单到我们每天都在用。想象一下你在一个城市的地图上想知道从家到公司有多远。你大概率不会去计算要拐多少个弯、经过多少条街而是会本能地看地图上连接两点的直线长度。这条直线的长度就是欧几里得距离最直观的体现。在数学上它描述的是欧几里得空间中两点间的“普通”直线距离是几何学中最基础、最核心的概念之一。在编程的世界里尤其是在C/C这类追求性能与控制的领域欧几里得距离算法绝不仅仅是一个数学公式的简单翻译。它是一切空间计算、数据分析、图形处理和人工智能算法的基石。无论是游戏开发中判断角色与敌人是否进入攻击范围还是机器学习中K近邻KNN算法计算样本相似度抑或是计算机视觉中特征点的匹配背后都离不开高效、准确的欧几里得距离计算。我见过不少新手朋友觉得这不过是一个sqrt((x2-x1)*(x2-x1) (y2-y1)*(y2-y1))的调用但在实际项目中尤其是在处理高维数据、追求极致性能或需要数值稳定性的场景下这里面的门道可多了去了。比如什么时候该用浮点数什么时候用双精度直接平方相加再开方会不会溢出有没有更快的方法这些都是在写“源码”之前必须想清楚的问题。本文将从一个资深C/C开发者的视角彻底拆解欧几里得距离算法。我不会只给你一个冷冰冰的函数定义而是会带你深入原理探讨不同维度的实现、性能优化的技巧、实际应用中的陷阱并最终提供一份工业级、可直接复用的源码。无论你是正在学习数据结构与算法的新手还是需要在项目中集成空间计算功能的老手这篇文章都将为你提供从理论到实践的完整路径。2. 算法核心原理与数学基础2.1 欧几里得距离的数学定义欧几里得距离的公式相信大家都不陌生。对于二维平面上的两点P1(x1, y1)和P2(x2, y2)其距离d定义为d √[(x2 - x1)² (y2 - y1)²]这个公式来源于勾股定理。我们将两点在x轴和y轴上的差值看作直角三角形的两条直角边那么它们之间的直线距离就是斜边的长度。将其推广到n维空间对于两点P(p1, p2, ..., pn)和Q(q1, q2, ..., qn)欧几里得距离公式为d √[Σ (qi - pi)²]其中求和Σ从 i1 到 in。 这个公式的本质是计算每个维度上差值的平方和再取平方根。它衡量的是两点在多维空间中的“直线”间隔是最符合人类直觉的距离度量方式。注意这里有一个非常重要的前提——欧几里得距离只有在欧几里得空间即我们熟悉的平坦空间中才有明确的几何意义。在非欧几何如球面上两点间的最短路径不是直线这个公式就不适用了。但在绝大多数计算机应用场景中我们默认处理的数据都在欧氏空间内。2.2 与其他距离度量的对比理解欧几里得距离最好将它放在一个“距离家族”中来看。不同的距离度量适用于不同的场景选错了度量方式算法效果可能大打折扣。曼哈顿距离城市街区距离公式为d Σ |qi - pi|。想象你在曼哈顿的棋盘式街道上行走你不能斜穿大楼只能沿着街道走直角。这种距离计算的是各维度绝对差值的和。它在某些优化问题和网格系统中非常有用计算比欧氏距离更快避免了乘法和开方。切比雪夫距离公式为d max(|qi - pi|)。它取所有维度差值绝对值的最大值。在国际象棋中国王的移动就是切比雪夫距离可以横、竖、斜走一格。它适用于那些只关心最大差异的场景。闵可夫斯基距离这是一个通式d [Σ |qi - pi|^p]^(1/p)。当p1时就是曼哈顿距离p2时就是欧几里得距离p趋近于无穷大时就是切比雪夫距离。欧氏距离可以看作是闵可夫斯基距离在p2时的特例。为什么欧氏距离最常用因为它具有旋转不变性。无论坐标系如何旋转两点间的欧氏距离保持不变。这个性质在图像处理、物理仿真等领域至关重要。而曼哈顿距离则依赖于坐标轴的方向。2.3 从数学公式到计算问题的转化将数学公式转化为计算机代码我们首先遇到的是数据类型的选择。坐标值可能是整数如图像像素坐标也可能是浮点数如物理引擎中的位置。对于整数输入计算差值平方时可能导致溢出例如int类型差值很大时平方会超出INT_MAX。对于浮点数输入则需要关注精度和性能。其次开平方根操作sqrt()是一个相对昂贵的运算。在需要计算大量距离例如在KNN算法中为每个样本计算距离时它可能成为性能瓶颈。这就引出了一个重要的优化技巧在很多只需要比较距离大小、而不需要具体距离值的场景下比如找最近邻我们可以直接比较距离的平方因为平方根函数是单调递增的距离d的大小关系与d²的大小关系完全一致。这样可以省去所有耗时的sqrt()调用。最后是维度灾难。公式中的求和项Σ意味着随着维度n增加计算量线性增长。在高维空间中例如成百上千维欧氏距离的行为会变得反直觉所有点对之间的距离会趋于相似这使得基于距离的算法如KNN效果下降。虽然我们无法改变数学规律但在代码实现时必须意识到高维计算带来的性能和精度挑战。3. C/C实现详解从基础到优化3.1 基础版本实现清晰第一我们先从最直接、最易读的实现开始。这个版本的目标是正确性和可读性适合学习理解和不那么苛刻的性能场景。#include cmath // 用于 sqrt 函数 #include vector // 二维空间基础版 double euclideanDistance2D(double x1, double y1, double x2, double y2) { double dx x2 - x1; double dy y2 - y1; return std::sqrt(dx * dx dy * dy); } // 多维空间通用版使用 std::vector double euclideanDistance(const std::vectordouble point1, const std::vectordouble point2) { // 安全检查确保两点维度相同 if (point1.size() ! point2.size()) { // 在实际项目中这里应该抛出更明确的异常或返回错误码 return -1.0; // 或 throw std::invalid_argument(Points must have the same dimension.); } double sum 0.0; for (size_t i 0; i point1.size(); i) { double diff point2[i] - point1[i]; sum diff * diff; } return std::sqrt(sum); }代码解析与注意事项头文件cmath提供了std::sqrt。在C语言中对应math.h和sqrt()。参数传递多维版本使用了const std::vectordouble这是常量引用避免了不必要的拷贝是C中传递容器参数的推荐做法。维度检查这是至关重要的一步。如果输入的两个点维度不同后续循环和计算将毫无意义甚至导致内存访问越界。生产代码中绝不能省略。循环与累加使用size_t作为索引类型与vector::size()返回类型匹配避免有符号/无符号比较警告。累加变量sum初始化为0.0。返回值基础版本直接返回double。错误处理通过返回-1或抛异常实现具体取决于项目的错误处理策略。实操心得在编写这类数学工具函数时我养成了一个习惯永远先写断言或检查。像维度匹配、指针非空这种前提条件在函数入口处就验证掉能节省大量后期调试的时间。对于基础版本清晰性和正确性远比那一点微小的性能开销重要。3.2 性能优化版本榨干CPU当你的代码需要每秒计算数百万甚至上亿次距离时比如实时图形处理、大规模机器学习推理优化就变得至关重要。优化主要围绕几个点减少函数调用开销、利用现代CPU指令集、避免重复计算。1. 使用距离平方进行比较如前所述这是最立竿见影的优化。// 计算欧氏距离的平方避免开方 double euclideanDistanceSquared(const std::vectordouble p1, const std::vectordouble p2) { // ... 维度检查同上 ... double sum 0.0; for (size_t i 0; i p1.size(); i) { double diff p2[i] - p1[i]; sum diff * diff; } return sum; // 注意返回的是平方和不是距离 } // 用法示例寻找距离最近的点 int findNearestPoint(const std::vectorstd::vectordouble points, const std::vectordouble target) { int nearestIdx -1; double minDistSq std::numeric_limitsdouble::max(); // 初始化为最大值 for (size_t i 0; i points.size(); i) { double distSq euclideanDistanceSquared(points[i], target); if (distSq minDistSq) { minDistSq distSq; nearestIdx static_castint(i); } } return nearestIdx; // 返回最近点的索引 }2. 循环展开与编译器优化现代编译器非常智能简单的循环通常能自动优化。但在某些关键路径上手动展开循环可以减少循环控制开销。// 手动循环展开示例假设维度是4的倍数 double euclideanDistanceSquaredUnrolled(const double* p1, const double* p2, size_t n) { double sum0 0.0, sum1 0.0, sum2 0.0, sum3 0.0; size_t i 0; for (; i 3 n; i 4) { double diff0 p2[i] - p1[i]; double diff1 p2[i1] - p1[i1]; double diff2 p2[i2] - p1[i2]; double diff3 p2[i3] - p1[i3]; sum0 diff0 * diff0; sum1 diff1 * diff1; sum2 diff2 * diff2; sum3 diff3 * diff3; } double total_sum sum0 sum1 sum2 sum3; // 处理剩余的尾部元素 for (; i n; i) { double diff p2[i] - p1[i]; total_sum diff * diff; } return total_sum; }这个版本使用了指针和已知维度并进行了4路循环展开。它减少了循环次数和条件判断允许CPU更好地进行指令级并行。但请注意过早优化是万恶之源。务必先 profiling性能剖析确定距离计算确实是瓶颈后再进行此类优化并且要测试展开因子这里是4对不同CPU架构的最佳效果。3. 使用SIMD指令集单指令多数据流这是性能优化的“大招”。SIMD如SSE、AVX允许一条指令同时处理多个数据。对于欧氏距离这种对大量数据执行相同操作的计算SIMD可以带来数倍的性能提升。#include immintrin.h // AVX 指令集头文件 // 使用AVX指令集计算双精度浮点数距离平方假设维度是4的倍数且内存对齐 double euclideanDistanceSquaredAVX(const double* p1, const double* p2, size_t n) { __m256d sum_vec _mm256_setzero_pd(); // 初始化一个256位寄存器存放4个double全为0 for (size_t i 0; i n; i 4) { // 加载4个double __m256d v1 _mm256_loadu_pd(p1 i); // _loadu 允许未对齐加载对齐加载用 _load_pd __m256d v2 _mm256_loadu_pd(p2 i); // 计算差值 __m256d diff _mm256_sub_pd(v2, v1); // 计算差值的平方 __m256d diff_sq _mm256_mul_pd(diff, diff); // 累加到和向量 sum_vec _mm256_add_pd(sum_vec, diff_sq); } // 将向量寄存器中的4个部分和sum_vec水平相加得到一个标量 double sum_array[4]; _mm256_storeu_pd(sum_array, sum_vec); double sum sum_array[0] sum_array[1] sum_array[2] sum_array[3]; // 处理可能的尾部元素如果n不是4的倍数 // ... (略) return sum; }使用SIMD需要一定的学习成本并且代码可移植性会变差需要检查CPU是否支持特定指令集。通常只在性能极度敏感的核心库如线性代数库、游戏引擎中使用。对于大多数应用编译器自动向量化优化已经足够好。3.3 模板化与泛型设计为了让我们的距离函数更通用可以将其模板化以支持不同的数据类型float,double,int和容器。#include type_traits #include cmath templatetypename T struct EuclideanDistance { // 一个 traits用于决定使用什么类型来存储平方和及结果防止溢出。 // 例如对于int平方和可能超出int范围需要用更大的类型如long long来存储。 using AccumType typename std::conditional std::is_integralT::value, long long, // 如果是整数类型用long long累加 double // 如果是浮点类型用double累加 ::type; templatetypename Container static AccumType squared(const Container a, const Container b) { assert(a.size() b.size()); AccumType sum 0; auto it_a a.begin(); auto it_b b.begin(); for (; it_a ! a.end(); it_a, it_b) { AccumType diff static_castAccumType(*it_b) - static_castAccumType(*it_a); sum diff * diff; } return sum; } templatetypename Container static double compute(const Container a, const Container b) { AccumType sumSq squared(a, b); return std::sqrt(static_castdouble(sumSq)); } }; // 使用示例 std::vectorint point1_int {1, 2, 3}; std::vectorint point2_int {4, 5, 6}; long long distSq_int EuclideanDistanceint::squared(point1_int, point2_int); double dist_int EuclideanDistanceint::compute(point1_int, point2_int); std::vectorfloat point1_float {1.0f, 2.0f}; std::vectorfloat point2_float {3.0f, 4.0f}; double dist_float EuclideanDistancefloat::compute(point1_float, point2_float);这个模板类通过AccumType巧妙地处理了整数计算可能溢出的问题并且通过迭代器使得它可以兼容任何支持begin()和end()的容器如std::array,std::vector,std::list的一部分甚至原生数组配合std::begin()。这是工业级库代码的常见设计思路。4. 实战应用场景与代码集成理解了原理和实现我们来看看它如何融入真实的项目。欧几里得距离很少被单独使用它总是作为更大算法或系统的一个组成部分。4.1 场景一K近邻KNN分类器核心KNN是机器学习中最直观的分类算法之一其核心就是计算待分类样本与所有训练样本的欧氏距离。#include vector #include algorithm #include cmath struct LabeledPoint { std::vectordouble features; int label; }; int knnClassify(const std::vectorLabeledPoint trainingData, const std::vectordouble queryPoint, int k) { // 1. 计算距离 std::vectorstd::pairdouble, int distances; // (距离, 索引) for (size_t i 0; i trainingData.size(); i) { double dist euclideanDistance(trainingData[i].features, queryPoint); distances.emplace_back(dist, i); } // 2. 按距离排序取前k个 std::partial_sort(distances.begin(), distances.begin() std::min(k, (int)distances.size()), distances.end(), [](const auto a, const auto b) { return a.first b.first; }); // 3. 统计前k个邻居的标签 std::unordered_mapint, int labelCount; for (int i 0; i k i distances.size(); i) { int label trainingData[distances[i].second].label; labelCount[label]; } // 4. 返回出现次数最多的标签 int bestLabel -1; int maxCount 0; for (const auto entry : labelCount) { if (entry.second maxCount) { maxCount entry.second; bestLabel entry.first; } } return bestLabel; }性能优化点在实际的KNN中我们不需要对所有距离进行完全排序只需要找到最小的k个。因此使用std::partial_sort或利用std::nth_element结合std::sort的部分排序比std::sort更高效。此外对于大规模数据通常会使用空间索引结构如KD-Tree、Ball Tree来避免计算所有距离但这超出了本文范围。4.2 场景二游戏开发中的距离判定在游戏里距离计算无处不在攻击范围、声音传播、触发器、AI视野等。// 一个简单的2D游戏实体类 class GameEntity { public: Vec2 position; // 假设 Vec2 是一个包含 x, y 的结构体 float attackRange; bool isWithinAttackRange(const GameEntity target) const { // 使用距离平方进行比较避免开方 float dx target.position.x - this-position.x; float dy target.position.y - this-position.y; float squaredDistance dx * dx dy * dy; float squaredRange attackRange * attackRange; return squaredDistance squaredRange; } // 更复杂的例子扇形攻击范围检测结合距离和角度 bool isWithinSector(const GameEntity target, const Vec2 direction, float angleDeg) const { Vec2 toTarget target.position - this-position; float distSq toTarget.lengthSquared(); // 假设 Vec2 有计算长度平方的方法 if (distSq attackRange * attackRange) { return false; // 距离太远 } // 计算夹角余弦值 float cosAngle toTarget.normalized().dot(direction.normalized()); float cosThreshold std::cos(angleDeg * M_PI / 180.0f / 2); // 半角的余弦 return cosAngle cosThreshold; } };关键技巧在游戏这种实时性要求极高的场景永远优先使用距离的平方进行比较。sqrt函数调用在每帧成千上万次的计算中是不可承受之重。Vec2::lengthSquared()是一个应该提供的基础方法。4.3 场景三计算机视觉与特征匹配在OpenCV等库中特征点匹配如SIFT、ORB经常需要计算描述子之间的欧氏距离或汉明距离。这里以计算两个特征描述向量std::vectoruchar或cv::Mat的欧氏距离为例展示如何与现有库结合。#include opencv2/opencv.hpp double computeDescriptorDistance(const cv::Mat desc1, const cv::Mat desc2) { // 假设 desc1 和 desc2 是 CV_32F 类型的行向量 CV_Assert(desc1.type() CV_32F desc2.type() CV_32F); CV_Assert(desc1.cols desc2.cols desc1.rows 1 desc2.rows 1); // 方法1使用OpenCV的norm函数内部可能优化 // return cv::norm(desc1, desc2, cv::NORM_L2); // 方法2手动计算便于理解或自定义优化 const float* p1 desc1.ptrfloat(); const float* p2 desc2.ptrfloat(); int dim desc1.cols; double sum 0.0; for (int i 0; i dim; i) { float diff p2[i] - p1[i]; sum static_castdouble(diff) * diff; // 用double累加防止精度丢失 } return std::sqrt(sum); } // 在特征匹配中我们通常寻找距离最小的匹配对 void matchFeatures(const std::vectorcv::Mat descriptors1, const std::vectorcv::Mat descriptors2, std::vectorcv::DMatch matches) { matches.clear(); for (size_t i 0; i descriptors1.size(); i) { int bestIdx -1; double bestDist std::numeric_limitsdouble::max(); double secondBestDist std::numeric_limitsdouble::max(); for (size_t j 0; j descriptors2.size(); j) { double dist computeDescriptorDistance(descriptors1[i], descriptors2[j]); if (dist bestDist) { secondBestDist bestDist; bestDist dist; bestIdx static_castint(j); } else if (dist secondBestDist) { secondBestDist dist; } } // 使用比率测试Lowes ratio test过滤模糊匹配 if (bestDist 0.8 * secondBestDist) { // 常见阈值 0.6-0.8 matches.emplace_back(i, bestIdx, bestDist); } } }在这个场景下数据通常是高维如128维的SIFT描述子且需要计算大量点对之间的距离。此时除了使用距离平方优化还可以考虑使用更快的数学库如Intel MKL、Eigen等它们提供了高度优化的矩阵和向量运算。近似最近邻搜索当数据量极大时使用FLANN、Annoy等库进行近似搜索牺牲少量精度换取速度的巨大提升。量化与降维将浮点描述子量化为二进制字符串如ORB使用汉明距离按位异或进行计算速度极快。5. 常见陷阱、调试技巧与高级话题5.1 数值稳定性与精度问题计算欧氏距离时数值问题是一个隐形的杀手。下溢与精度丢失当两个点非常接近时差值的平方可能小到超出浮点数的有效精度范围导致求和后开方结果不准确甚至为0。虽然这种情况较少但在迭代优化算法如梯度下降中如果步长极小可能会遇到。对策对于极端精密的科学计算可以考虑使用long double或高精度数学库。或者重新审视问题是否真的需要如此高的精度。上溢溢出这是更常见的问题。当坐标值很大时例如地理坐标以米为单位差值的平方可能超过double甚至float能表示的最大值导致结果为无穷大inf。对策归一化数据在计算距离前先将所有数据缩放到一个合理的范围内如[0,1]或[-1,1]。这是机器学习数据预处理的标准步骤。使用更宽的数据类型对于整数坐标使用long long存储平方和。使用稳健的公式虽然数学等价但计算√(Σ(x_i-y_i)²)在数值上可能不如某些变体稳定。一种方法是先找出最大差值进行缩放但会引入额外开销。通常数据归一化是首选方案。// 一个简单的数据归一化函数Min-Max Scaling void normalizePoints(std::vectorstd::vectordouble points) { if (points.empty()) return; size_t dim points[0].size(); std::vectordouble mins(dim, std::numeric_limitsdouble::max()); std::vectordouble maxs(dim, std::numeric_limitsdouble::lowest()); // 找到每个维度的最小最大值 for (const auto p : points) { for (size_t i 0; i dim; i) { if (p[i] mins[i]) mins[i] p[i]; if (p[i] maxs[i]) maxs[i] p[i]; } } // 执行归一化 for (auto p : points) { for (size_t i 0; i dim; i) { if (maxs[i] ! mins[i]) { // 防止除零 p[i] (p[i] - mins[i]) / (maxs[i] - mins[i]); // 归一化到[0,1] // 或者 p[i] 2 * ((p[i] - mins[i]) / (maxs[i] - mins[i])) - 1; // 归一化到[-1,1] } else { p[i] 0.0; // 该维度所有值相同 } } } }5.2 维度灾难与距离失效在高维空间中欧氏距离会逐渐失去其区分能力。所有点对之间的距离会变得非常接近均值与方差的比值会发生变化。这对KNN、聚类等算法是致命的。现象当你将数据维度从10增加到1000会发现“最近邻”和“最远邻”的距离比值趋近于1。应对策略特征选择剔除不相关或冗余的特征降低维度。降维技术使用主成分分析PCA、t-SNE、UMAP等方法将数据投影到低维空间同时尽可能保留原始结构。改用其他距离度量在某些高维场景下如文本分析余弦相似度可能比欧氏距离更有效。重新思考问题高维数据是否真的需要基于距离的算法或许模型-based的方法如神经网络更合适。5.3 性能分析与调试工具当你怀疑距离计算是性能热点时如何验证和优化Profiling性能剖析Linux/macOS使用perf、gprof、Valgrind的callgrind工具。Windows使用 Visual Studio 自带的性能分析器。关键是要看到函数调用次数和耗时占比确认sqrt或你的距离函数是否真的是瓶颈。编译器优化标志-O2/-O3启用高级优化包括自动向量化。-ffast-math为了速度放宽浮点数计算的严格标准如不考虑NaNInf可以显著提升sqrt等数学函数性能但可能影响精度和可移植性需谨慎使用。-marchnative生成针对本机CPU架构优化的代码可能启用AVX等指令集。调试技巧单元测试为你的距离函数编写单元测试覆盖常规情况、边界情况如零向量、相同点、超大数值点。使用assert在调试版本中加入断言检查维度、指针有效性等。打印中间结果对于奇怪的结果可以临时打印出差值、平方和等定位计算在哪一步出了问题。5.4 一个综合性的工业级源码示例最后我将展示一个结合了上述许多考量的、相对健壮的距离计算模块头文件。它提供了模板化、带安全检查、可选是否开方的接口。// EuclideanDistance.h #pragma once #include cmath #include type_traits #include iterator #include cassert namespace GeometryUtils { templatetypename T struct DistanceTraits { // 默认使用 double 作为累加和结果类型 using AccumType double; using ResultType double; }; // 特化对于 float累加用 double 防止精度丢失 template struct DistanceTraitsfloat { using AccumType double; using ResultType float; }; // 特化对于整数类型用 long long 累加防止溢出 template struct DistanceTraitsint { using AccumType long long; using ResultType double; // 结果可能需要开方所以是浮点 }; templatetypename Iter1, typename Iter2 auto euclideanDistanceSquared(Iter1 begin1, Iter1 end1, Iter2 begin2) - typename DistanceTraitstypename std::iterator_traitsIter1::value_type::AccumType { using ValueType typename std::iterator_traitsIter1::value_type; using Traits DistanceTraitsValueType; using AccumType typename Traits::AccumType; AccumType sum 0; auto it1 begin1; auto it2 begin2; while (it1 ! end1) { AccumType diff static_castAccumType(*it2) - static_castAccumType(*it1); sum diff * diff; it1; it2; } // 注意这里没有检查 begin2 是否也有足够的元素调用者需保证。 return sum; } templatetypename Container1, typename Container2 auto euclideanDistance(const Container1 c1, const Container2 c2) - typename DistanceTraitstypename Container1::value_type::ResultType { assert(c1.size() c2.size()); using ValueType typename Container1::value_type; using Traits DistanceTraitsValueType; using AccumType typename Traits::AccumType; using ResultType typename Traits::ResultType; AccumType sumSq euclideanDistanceSquared(std::begin(c1), std::end(c1), std::begin(c2)); return static_castResultType(std::sqrt(static_castdouble(sumSq))); } // 一个便利函数直接计算距离带安全检查的指针版本 templatetypename T double euclideanDistancePtr(const T* p1, const T* p2, size_t n) { if (n 0) return 0.0; using Traits DistanceTraitsT; using AccumType typename Traits::AccumType; AccumType sum 0; for (size_t i 0; i n; i) { AccumType diff static_castAccumType(p2[i]) - static_castAccumType(p1[i]); sum diff * diff; } return std::sqrt(static_castdouble(sum)); } } // namespace GeometryUtils这个头文件提供了高度的灵活性和安全性。它通过迭代器泛型支持各种容器通过DistanceTraits解决不同类型的数据累加问题并提供了带安全检查的容器版本和高效的指针版本。在实际项目中你可以将此文件放入你的工具库中并根据需要添加SIMD特化版本。欧几里得距离的实现之旅到此告一段落。从最基础的公式到考虑性能、精度、泛型和安全的工业级代码每一步都蕴含着对问题更深层次的理解。记住没有最好的代码只有最适合当前场景的代码。在开始编码前先问自己我的数据规模多大维度多高对精度和速度的要求如何回答清楚这些问题你自然能选出最合适的实现方式。下次当你在代码中写下sqrt(dx*dx dy*dy)时希望你能会心一笑想起背后还有这么多值得琢磨的细节。