如何转换此划分并征服代码以将一个点与一个点列表进行比较?

时间:2011-10-11 17:11:24

标签: c# algorithm

我在网站http://rosettacode.org/wiki/Closest-pair_problem上找到了这个代码,我采用了C#版本的分而治之的方法来找到最接近的点,但我想要做的是调整它以便只用于查找最接近一个特定点。我搜索了很多,搜索这个网站找到例子,但没有一个像这样。我不完全确定要改变什么,以便它只针对一个点检查列表而不是检查列表以找到最接近的两个。我想让我的程序尽可能快地运行,因为它可以搜索几千个点的列表来找到最接近我当前坐标点的。

public class Segment
{
    public Segment(PointF p1, PointF p2)
    {
        P1 = p1;
        P2 = p2;
    }

    public readonly PointF P1;
    public readonly PointF P2;

    public float Length()
    {
        return (float)Math.Sqrt(LengthSquared());
    }

    public float LengthSquared()
    {
        return (P1.X - P2.X) * (P1.X - P2.X)
            + (P1.Y - P2.Y) * (P1.Y - P2.Y);
    }
}

    public static Segment Closest_BruteForce(List<PointF> points)
    {
        int n = points.Count;
        var result = Enumerable.Range(0, n - 1)
            .SelectMany(i => Enumerable.Range(i + 1, n - (i + 1))
                .Select(j => new Segment(points[i], points[j])))
                .OrderBy(seg => seg.LengthSquared())
                .First();

        return result;
    }

    public static Segment MyClosestDivide(List<PointF> points)
    {
        return MyClosestRec(points.OrderBy(p => p.X).ToList());
    }

    private static Segment MyClosestRec(List<PointF> pointsByX)
    {
        int count = pointsByX.Count;
        if (count <= 4)
            return Closest_BruteForce(pointsByX);

        // left and right lists sorted by X, as order retained from full list
        var leftByX = pointsByX.Take(count / 2).ToList();
        var leftResult = MyClosestRec(leftByX);

        var rightByX = pointsByX.Skip(count / 2).ToList();
        var rightResult = MyClosestRec(rightByX);

        var result = rightResult.Length() < leftResult.Length() ? rightResult : leftResult;

        // There may be a shorter distance that crosses the divider
        // Thus, extract all the points within result.Length either side
        var midX = leftByX.Last().X;
        var bandWidth = result.Length();
        var inBandByX = pointsByX.Where(p => Math.Abs(midX - p.X) <= bandWidth);

        // Sort by Y, so we can efficiently check for closer pairs
        var inBandByY = inBandByX.OrderBy(p => p.Y).ToArray();

        int iLast = inBandByY.Length - 1;
        for (int i = 0; i < iLast; i++)
        {
            var pLower = inBandByY[i];

            for (int j = i + 1; j <= iLast; j++)
            {
                var pUpper = inBandByY[j];

                // Comparing each point to successivly increasing Y values
                // Thus, can terminate as soon as deltaY is greater than best result
                if ((pUpper.Y - pLower.Y) >= result.Length())
                    break;

                Segment segment = new Segment(pLower, pUpper);
                if (segment.Length() < result.Length())
                    result = segment;// new Segment(pLower, pUpper);
            }
        }

        return result;
    }

我在程序中使用此代码来查看速度和分界的实际差异并轻松征服胜利。

        var randomizer = new Random(10);
        var points = Enumerable.Range(0, 10000).Select(i => new PointF((float)randomizer.NextDouble(), (float)randomizer.NextDouble())).ToList();
        Stopwatch sw = Stopwatch.StartNew();
        var r1 = Closest_BruteForce(points);
        sw.Stop();
        //Debugger.Log(1, "", string.Format("Time used (Brute force) (float): {0} ms", sw.Elapsed.TotalMilliseconds));
        richTextBox.AppendText(string.Format("Time used (Brute force) (float): {0} ms", sw.Elapsed.TotalMilliseconds));
        Stopwatch sw2 = Stopwatch.StartNew();
        var result2 = MyClosestDivide(points);
        sw2.Stop();
        //Debugger.Log(1, "", string.Format("Time used (Divide & Conquer): {0} ms", sw2.Elapsed.TotalMilliseconds));
        richTextBox.AppendText(string.Format("Time used (Divide & Conquer): {0} ms", sw2.Elapsed.TotalMilliseconds));
        //Assert.Equal(r1.Length(), result2.Length());

4 个答案:

答案 0 :(得分:2)

您可以将点存储在更好的数据结构中,以利用它们的位置。类似于quadtree

您尝试使用的分而治之算法并不适用于此问题。

答案 1 :(得分:1)

根本不要使用此算法,只需逐个浏览一个列表,比较距参考点的距离,最后返回最接近的点。这将是O(n)。

你可以添加一些额外的加速,但这应该足够好了。

如果你愿意,我可以写一些示例代码。

答案 2 :(得分:1)

你混淆了两个不同的问题。分裂和征服最接近的对问题的唯一原因是比蛮力更快,它避免将每个点与每个其他点进行比较,因此它得到O(n log n)而不是O(n * n)。但找到最接近一点的点只是O(n)。如何检查n个点列表中的最近点,同时检查少于n个点?你想做的事情甚至没有意义。

我不能说为什么你的分而治之的时间比你的蛮力更少;也许linq实现运行得更慢。但是我认为你会发现两件事:1)从绝对意义上说,你实施1分的分而治之的时间比你实施1分的强力实施的时间少,但它们仍然具有相同的O(n )。 2)如果你只是尝试一个简单的foreach循环并记录最小距离的平方,你将获得比你的分而治之更好的绝对时间 - 而且,它仍然是O(n)。

答案 3 :(得分:0)

public static float LengthSquared(PointF P1, PointF P2)
{
    return (P1.X - P2.X) * (P1.X - P2.X)
        + (P1.Y - P2.Y) * (P1.Y - P2.Y);
}

如果您的问题表明,您希望将1(已知)点与点列表进行比较以找到最接近的点,那么请使用此代码。

public static Segment Closest_BruteForce(PointF P1, List<PointF> points)
{
    PointF closest = null;
    float minDist = float.MaxValue;
    foreach(PointF P2 in points) 
    {
       if(P1 != P2) 
       {
           float temp = LengthSquared(P1, P2);
           if(temp < minDist) 
           {
               minDist = temp;
               closest = P2;
           }
       }
    }
    return new Segment(P1, closest);
}

但是,如果您的示例显示,您想要从点列表中找到最接近的2个点,请尝试以下内容。

public static Segment Closest_BruteForce(List<PointF> points)
{
    PointF closest1;
    PointF closest2;
    float minDist = float.MaxValue;
    for(int x=0; x<points.Count; x++)
    {
        PointF P1 = points[x];
        for(int y = x + 1; y<points.Count; y++)
        {
            PointF P2 = points[y];
            float temp = LengthSquared(P1, P2);
            if(temp < minDist) 
            {
               minDist = temp;
               closest1 = P1;
               closest2 = P2;
            }
        }
    }
    return new Segment(closest1, closest2);
}

note 上面的代码是在浏览器中编写的,可能有一些语法错误。


编辑奇怪......这是否是可接受的答案?没有解释的下来,哦,好吧。