计算点之间距离的高效算法

时间:2015-09-23 18:15:04

标签: c# algorithm linq

我有一个非常糟糕的效率问题。 我需要在一组点和我的第一个"蛮力"之间达到最远的距离。算法需要将近80秒。我需要它在1秒内发生。 最糟糕的情况是将计算转移到后台进程并对它们进行多线程处理,但它仍然需要更快,所以这是我的第一个stackoverflow问题。

我拥有的数据是39 000组坐标,每组包含大约200 x,y坐标,我在每组中寻找最远距离。

数据点由x和y表示,我用Math.Sqrt(deltaX * deltaX + deltaY * deltaY)计算它们之间的距离

数据点可以按任何顺序排列。

我的暴力尝试看起来像这样

public static double getAbsoluteMax(IEnumerable<DataPoint> dataPoints)
{
    double maxDistance = 0;

    foreach (DataPoint dp1 in dataPoints)
    {
        foreach (DataPoint dp2 in dataPoints)
        {
            double deltaX = dp1.x - dp2.x;
            double deltaY = dp1.y - dp2.y;
            double distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
            if (distance > maxDistance)
            {
                maxDistance = distance;
            }
        }
    }
    return maxDistance;
}

我每次使用200个值调用此函数.39 000次。

我的第一个想法是在Perl中找到的Memoize,它缓存任何方法调用的结果,然后查找是否使用相同的参数调用相同的方法。也许创建一个包含数学结果的查找表可能有帮助吗?

也许我可以将计算转移到matlab或类似的东西?

应用程序是.net 4.5,计算是在.net 4.5 dll

5 个答案:

答案 0 :(得分:3)

在n log n中找到凸包。它可能会减少你的设置,然后在2 n内找到船体直径。基本图论应该会给你很多表现。

此外,函数调用开销39000次是昂贵的......数据集也会杀死垃圾收集器。你应该尝试创建一些可重用的数组...... 78000个枚举器正在查杀。只需使用200个值的数组并重用它。而不是为每个使用int ...删除sqrt并返回值平方它节省了几百万平方......也许你可以使用int只有没有双打...使用多个线程/核心...并且sse指令或卸载到了gpu

上编译发布版本和代码优化

例如,它在大约2.5秒内运行:

Random r = new Random(100);
double[] x = new double[200];
double[] y = new double[200];
double maxD = 0;
Stopwatch stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 39000; i++)
{
    for (int j = 0; j < 200; j++)
    {
        x[j] = r.Next();
        y[j] = r.Next();
    }
    for (int j = 0; j < 200; j++)
    {
        for (int k = j + 1; k < 200; k++)
        {
            double dx = x[j] - x[k];
            dx = dx * dx;
            double dy = y[j] - y[k];
            dy = dy * dy;
            double d = dx + dy;
            // this is slow (80 secs):
            //double d = Math.Pow(x[j] - x[k], 2) + Math.Pow(y[j] - y[k], 2);
            if (maxD < d) maxD = d;
        }
    }
}
Console.WriteLine($"{stopwatch.ElapsedMilliseconds}");

Math.Pow调用(来自http://referencesource.microsoft.com/#mscorlib/system/math.cs):

  [System.Security.SecuritySafeCritical]  // auto-generated
  [ResourceExposure(ResourceScope.None)]
  [MethodImplAttribute(MethodImplOptions.InternalCall)]
  public static extern double Pow(double x, double y);

答案 1 :(得分:1)

对于任何点集:如果两个点a和b的距离大于或等于所有其他点对,则它们是凸包的一部分。

知道这一点,你可以先计算凸包,然后用你的方法只计算凸包上的点。

Matlab中的示例实现

示例数据:

points=rand(2000,2);

使用pdist的简单解决方案

distance_matrix=squareform(pdist(points));
[distance,index]=max(distance_matrix(:));
[a,b]=ind2sub(size(distance_matrix),index);
fprintf('the points with index %d and %d have the largest distance\n',a,b)

&#34;智能&#34;使用凸包的方式

c=convhull(points(:,1),points(:,2));
%don't need the duplicated last/first entry which loops
c=c(1:end-1);
distance_matrix=squareform(pdist(points(c,:)));
[distance,index]=max(distance_matrix(:));
[a,b]=ind2sub(size(distance_matrix),index);
fprintf('the points with index %d and %d have the largest distance\n',c(a),c(b))

对于200个随机点,两种解决方案的运行时间均小于0.01秒。

答案 2 :(得分:0)

public static double GetAbsoluteMax(List<DataPoint> list) {
    double max = 0;
    // calc dist for each distinct pair Dist(P_1, P_2) == Dist(P_2, P_1)
    for(var i=0; i<list.Count-1; i++) {
        for(var j=i+1; j<list.Count; j++) {
            var dX = list[i].X - list[j].X;
            var dY = list[i].Y - list[j].Y;
            // don't calculate the Square Root yet
            var dist = dX * dX + dY * dY;
            if(dist > max) {
                max = dist;
            }
        }
    }
    return Math.Sqrt(max);
}

ideone http://ideone.com/KSfm0X

你能做的最好的不是重复距离计算,也不计算直到最后的平方根。

编辑这里是一个额外的多线程/并行版本(我使用的是ThreadPool,所以它可以在IDEONE.COM中运行,但如果你的目标是使用任务可能会更合适.NET 4.5

using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
public class Test
{
    public class DataPoint {
        public double X {get; set;}
        public double Y {get; set;}
    }
    public static void Main()
    {
        int count = 39;
        // generate our points
        List<DataPoint>[] sets = new List<DataPoint>[count];
        double[] result = new double[count];
        for(var i=0; i<sets.Length; i++) { 
            sets[i] = GetRandomDataPoints(200); 
        }
        // run our calculations async
        int running = count;
        using(ManualResetEvent resetEvent = new ManualResetEvent(false)){
            for(int i=0; i<count; i++) {
                int idx = i;
                ThreadPool.QueueUserWorkItem(
                    new WaitCallback(o => {
                        result[idx] = GetAbsoluteMax(sets[idx]);
                        if (Interlocked.Decrement(ref running) == 0) {
                            resetEvent.Set();
                        }
                    }),
                    null
                );
            }
            resetEvent.WaitOne();
        }
        foreach(var max in result) {
            Console.WriteLine(max);
        }
    }
    public static double GetAbsoluteMax(List<DataPoint> list) {
        double max = 0;
        // calc dist for each distinct pair Dist(P_1, P_2) == Dist(P_2, P_1)
        for(var i=0; i<list.Count-1; i++) {
            for(var j=i+1; j<list.Count; j++) {
                var dX = list[i].X - list[j].X;
                var dY = list[i].Y - list[j].Y;
                // don't calculate the Square Root yet
                var dist = dX * dX + dY * dY;
                if(dist > max) {
                    max = dist;
                }
            }
        }
        return Math.Sqrt(max);
    }
    public static List<DataPoint> GetRandomDataPoints(int size) {
        var result = new List<DataPoint>();
        var rnd = new Random();
        for(var i=0; i<size; i++) {
            result.Add(new DataPoint() { 
                X=rnd.NextDouble()*100, 
                Y=rnd.NextDouble()*100 
                });
        }
        return result;
    }
}

答案 3 :(得分:-1)

以下是您如何确保两次不比较数据点,并且只迭代IEnumerable一次。它还会保存Math.Sqrt以结束。

public static double getAbsoluteMax(IEnumerable<DataPoint> dataPoints)
{
    double maxDistanceSquared = 0;

    var previousPoints = new List<DataPoint> { dataPoints.FirstOrDefault() };

    foreach (DataPoint dp1 in dataPoints.Skip(1))
    {
        foreach (DataPoint dp2 in previousPoints)
        {
            double deltaX = dp1.x - dp2.x;
            double deltaY = dp1.y - dp2.y;
            double distanceSquared = (deltaX * deltaX) + (deltaY * deltaY);
            if (distanceSquared > maxDistanceSquared)
            {
                maxDistanceSquared = distanceSquared;
            }
        }

        previousPoints.Add(dp1);
    }

    return Math.Sqrt(maxDistanceSquared);
}

基本上它的作用是将第一个点放入一个列表然后迭代其余的点。对于每个点,它然后迭代列表并进行距离检查。然后它将该点添加到列表中。因此,每次迭代只会将该点与之前的点进行比较。

答案 4 :(得分:-1)

它仍然是蛮力,但我把它从12秒降到了6:

1)将列表预处理为单独的x&amp; y数组

2)缓存外循环中的点

    public static double GetAbsoluteMax(double[] xs, double[] ys)
    {
        double maxDistanceSqd = 0;

        int nPts = xs.Length;

        for ( int i =0; i < nPts ; ++i )
        {
            double x1 = xs[i];
            double y1 = ys[i];

            for (int j = 0; j < nPts; ++j)
            {
                double deltaX = x1 - xs[j];
                double deltaY = y1 - ys[j];
                double distsqd = (deltaX * deltaX + deltaY * deltaY);
                if (distsqd > maxDistanceSqd)
                {
                    maxDistanceSqd = distsqd;
                }
            }
        }
        return Math.Sqrt(maxDistanceSqd);
    }