C# 实现一种检测自相交多边形的蛮力算法
我最初实现了Hoey-Shamos算法,但是它对于未来的可维护性来说太复杂了(我对此没有发言权),而且它没有正确地报告,所以我将使用一个优化的暴力算法 我的问题是:如何优化此代码以使其可用 目前,我的代码包含一个嵌套的for循环,将同一列表迭代两次 编辑:将行转换为哈希集并使用两个foreach循环。。。从扫描10000次中减少了大约45秒。这还不够C# 实现一种检测自相交多边形的蛮力算法,c#,algorithm,arcgis,shapefile,polygons,C#,Algorithm,Arcgis,Shapefile,Polygons,我最初实现了Hoey-Shamos算法,但是它对于未来的可维护性来说太复杂了(我对此没有发言权),而且它没有正确地报告,所以我将使用一个优化的暴力算法 我的问题是:如何优化此代码以使其可用 目前,我的代码包含一个嵌套的for循环,将同一列表迭代两次 编辑:将行转换为哈希集并使用两个foreach循环。。。从扫描10000次中减少了大约45秒。这还不够 foreach (Line2D g in lines) { foreach (Line2D h in lin
foreach (Line2D g in lines)
{
foreach (Line2D h in lines)
{
if (g.intersectsLine(h))
{
return false;
}
}
} // end 'lines' for each loop
如果我强制我的“intersectsLine()”方法返回false(出于测试目的),扫描10000条记录(我有700000条记录)仍然需要8秒钟。这太长了,所以我需要优化这段代码
在与所有其他行进行比较后,我尝试从列表中删除行,但是存在准确性问题(不知道为什么),并且速度增加几乎不明显
这是我的交叉线方法。我找到了另一种方法,但在所有方法调用和诸如此类的情况下,它看起来会更慢。在我看来,计算坡度似乎不需要太多的计算(如果我错了,请纠正我?)
公共布尔交叉线(Line2D比较线)
{
//调整线(比较线);
如果(此.Equals)(比较线)||
P2.等于(比较线P1)||
P1.等于(比较线P2))
{
返回false;
}
双firstLineSlopeX、firstLineSlopeY、secondLineSlopeX、secondLineSlopeY;
firstLineSlopeX=X2-X1;
firstLineSlopeY=Y2-Y1;
secondLineSlopeX=comparedLine.X2-comparedLine.X1;
secondLineSlopeY=comparedLine.Y2-comparedLine.Y1;
双s,t;
s=(-firstLineSlopeY*(X1-比较行.X1)+firstLineSlopeX*(Y1-比较行.Y1))/(-secondLineSlopeX*第一行slopey+第一行slopex*第二行slopey);
t=(secondLineSlopeX*(Y1-比较线.Y1)-secondLineSlopeY*(X1-比较线.X1))/(-secondLineSlopeX*firstLineSlopeY+firstLineSlopeX*secondLineSlopeY);
如果(s>=0&&s=0&&t有两件事需要检查:
闭合多边形由点的循环序列组成
- 如果此序列中存在同一点不止一次,则该点不是自交多边形
- 请注意,第一个点和最后一个点可以相同(多边形表示不同),在这种情况下,该点必须存在两次以上的棕褐色
检查多边形的所有非相邻线是否相交
- 相邻线路不共享任何点
- 如果有交集,则多边形是自交的
您当前的暴力算法是O(n^2)。对于两个70000线多边形,这几乎是100亿次操作的一个因素,更不用说700000个其他多边形了。显然,仅仅代码优化是不够的,所以您需要某种算法优化来降低O(n^2)没有过分复杂
O(n^2)来自蛮力算法中的嵌套循环,每个循环都以n
为边界,使其成为O(n*n)。改进这一点的最简单方法是找到某种方法来减少内部循环,使其不受n
的约束或依赖。因此,我们需要找到某种方法对内部行列表进行排序或重新排序,以检查外部行,从而只需扫描完整列表的一部分
我所采用的方法利用了这样一个事实,即如果两条线段相交,那么它们的X值范围必须相互重叠。请注意,这并不意味着它们相交,但如果它们的X范围不重叠,那么它们就不能相交,因此无需相互检查。(Y值范围也是如此,但一次只能利用一个维度)
这种方法的优点是,这些X范围可用于对直线的端点进行排序,而这些端点又可作为检查直线相交的起点和终点
因此,我们具体要做的是定义一个自定义类(endpointEntry
),它表示直线两点的高X值或低X值。这些端点都放在相同的列表结构中,然后根据它们的X值进行排序
然后我们实现了一个外循环,在这个外循环中,我们扫描整个列表,就像蛮力算法一样。但是,我们的内循环有很大的不同。我们不是重新扫描整个列表以检查交叉点,而是从外循环的线和端点的高X值端点开始向下扫描排序的端点列表当我们通过同一条线的低X值端点下方时,它就会消失。这样,我们只检查X值范围与外循环线重叠的线
好的,下面是一个c#类演示我的方法:
class CheckPolygon2
{
// internal supporting classes
class endpointEntry
{
public double XValue;
public bool isHi;
public Line2D line;
public double hi;
public double lo;
public endpointEntry fLink;
public endpointEntry bLink;
}
class endpointSorter : IComparer<endpointEntry>
{
public int Compare(endpointEntry c1, endpointEntry c2)
{
// sort values on XValue, descending
if (c1.XValue > c2.XValue) { return -1; }
else if (c1.XValue < c2.XValue) { return 1; }
else // must be equal, make sure hi's sort before lo's
if (c1.isHi && !c2.isHi) { return -1; }
else if (!c1.isHi && c2.isHi) { return 1; }
else { return 0; }
}
}
public bool CheckForCrossing(List<Line2D> lines)
{
List<endpointEntry> pts = new List<endpointEntry>(2 * lines.Count);
// Make endpoint objects from the lines so that we can sort all of the
// lines endpoints.
foreach (Line2D lin in lines)
{
// make the endpoint objects for this line
endpointEntry hi, lo;
if (lin.P1.X < lin.P2.X)
{
hi = new endpointEntry() { XValue = lin.P2.X, isHi = true, line = lin, hi = lin.P2.X, lo = lin.P1.X };
lo = new endpointEntry() { XValue = lin.P1.X, isHi = false, line = lin, hi = lin.P1.X, lo = lin.P2.X };
}
else
{
hi = new endpointEntry() { XValue = lin.P1.X, isHi = true, line = lin, hi = lin.P1.X, lo = lin.P2.X };
lo = new endpointEntry() { XValue = lin.P2.X, isHi = false, line = lin, hi = lin.P2.X, lo = lin.P1.X };
}
// add them to the sort-list
pts.Add(hi);
pts.Add(lo);
}
// sort the list
pts.Sort(new endpointSorter());
// sort the endpoint forward and backward links
endpointEntry prev = null;
foreach (endpointEntry pt in pts)
{
if (prev != null)
{
pt.bLink = prev;
prev.fLink = pt;
}
prev = pt;
}
// NOW, we are ready to look for intersecting lines
foreach (endpointEntry pt in pts)
{
// for every Hi endpoint ...
if (pt.isHi)
{
// check every other line whose X-range is either wholly
// contained within our own, or that overlaps the high
// part of ours. The other two cases of overlap (overlaps
// our low end, or wholly contains us) is covered by hi
// points above that scan down to check us.
// scan down for each lo-endpoint below us checking each's
// line for intersection until we pass our own lo-X value
for (endpointEntry pLo = pt.fLink; (pLo != null) && (pLo.XValue >= pt.lo); pLo = pLo.fLink)
{
// is this a lo-endpoint?
if (!pLo.isHi)
{
// check its line for intersection
if (pt.line.intersectsLine(pLo.line))
return true;
}
}
}
}
return false;
}
}
打破这一点,旧式的for
循环说明符有三个部分:初始化、条件和增量-减量。初始化表达式endpointEntry pLo=pt.fLink;
使用列表中当前点的前向链接初始化pLo
。即列表中的下一个点,降序排序顺序
然后执行内部循环的主体。然后应用增量-减量pLo=pLo.fLink
,它只需使用前向链接(pLo.fLink
)将内部循环的当前点(pLo
)设置为下一个较低的点,从而推进循环
最后,它在测试循环条件(pLo!=null)和&(pLo.XValue>=pt.lo)
后循环,只要新点不是null(这意味着我们在列表的末尾)只要新点的XValue
仍然大于或等于外环当前点的低X值。第二个条件确保
class CheckPolygon2
{
// internal supporting classes
class endpointEntry
{
public double XValue;
public bool isHi;
public Line2D line;
public double hi;
public double lo;
public endpointEntry fLink;
public endpointEntry bLink;
}
class endpointSorter : IComparer<endpointEntry>
{
public int Compare(endpointEntry c1, endpointEntry c2)
{
// sort values on XValue, descending
if (c1.XValue > c2.XValue) { return -1; }
else if (c1.XValue < c2.XValue) { return 1; }
else // must be equal, make sure hi's sort before lo's
if (c1.isHi && !c2.isHi) { return -1; }
else if (!c1.isHi && c2.isHi) { return 1; }
else { return 0; }
}
}
public bool CheckForCrossing(List<Line2D> lines)
{
List<endpointEntry> pts = new List<endpointEntry>(2 * lines.Count);
// Make endpoint objects from the lines so that we can sort all of the
// lines endpoints.
foreach (Line2D lin in lines)
{
// make the endpoint objects for this line
endpointEntry hi, lo;
if (lin.P1.X < lin.P2.X)
{
hi = new endpointEntry() { XValue = lin.P2.X, isHi = true, line = lin, hi = lin.P2.X, lo = lin.P1.X };
lo = new endpointEntry() { XValue = lin.P1.X, isHi = false, line = lin, hi = lin.P1.X, lo = lin.P2.X };
}
else
{
hi = new endpointEntry() { XValue = lin.P1.X, isHi = true, line = lin, hi = lin.P1.X, lo = lin.P2.X };
lo = new endpointEntry() { XValue = lin.P2.X, isHi = false, line = lin, hi = lin.P2.X, lo = lin.P1.X };
}
// add them to the sort-list
pts.Add(hi);
pts.Add(lo);
}
// sort the list
pts.Sort(new endpointSorter());
// sort the endpoint forward and backward links
endpointEntry prev = null;
foreach (endpointEntry pt in pts)
{
if (prev != null)
{
pt.bLink = prev;
prev.fLink = pt;
}
prev = pt;
}
// NOW, we are ready to look for intersecting lines
foreach (endpointEntry pt in pts)
{
// for every Hi endpoint ...
if (pt.isHi)
{
// check every other line whose X-range is either wholly
// contained within our own, or that overlaps the high
// part of ours. The other two cases of overlap (overlaps
// our low end, or wholly contains us) is covered by hi
// points above that scan down to check us.
// scan down for each lo-endpoint below us checking each's
// line for intersection until we pass our own lo-X value
for (endpointEntry pLo = pt.fLink; (pLo != null) && (pLo.XValue >= pt.lo); pLo = pLo.fLink)
{
// is this a lo-endpoint?
if (!pLo.isHi)
{
// check its line for intersection
if (pt.line.intersectsLine(pLo.line))
return true;
}
}
}
}
return false;
}
}
for (endpointEntry pLo = pt.fLink; (pLo != null) && (pLo.XValue >= pt.lo); pLo = pLo.fLink)
int count = lines.Count();
for (int l1idx = 0; l1idx < count-1; l1idx++)
for (int l2idx = l1idx+1; l2idx < count; l2idx++)
{
Line2D g = lines[l1idx];
Line2D h = lines[l2idx];
if (g.intersectsLine(h))
{
return false;
}
}