繁体   English   中英

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

[英]How can I convert this divide and conquer code to compare one point to a list of points?

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

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());

您可以将点存储在更好的数据结构中,以利用它们的位置。 四叉树一样的东西。

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

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

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

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

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

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

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);
}

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


编辑奇怪...这是否是可接受的答案? 贬票没有解释,哦。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM