我有一些二维坐标点(蓝点)。
我想要得到这三个点,使它们构成一个三角形,并且这个三角形中包含D点(红点)。 如果没有这样的三角形,则可以抛出异常。
所以对于上面的图片,我想要得到黑色点:
目前我做过的事情: 我认为可以通过它们与D点的距离将这些点排序,然后从排序好的列表中取前三个点。但是问题在于,这三个最近的点可能组成一个不包括点D的三角形。这在下面的图片中显示:
除了得到错误的点之外,我还不能确定D是否位于找到的点的凸包内,因此无法确定是否存在包含点D的三角形。 这就是我卡住的地方。
我有一些二维坐标点(蓝点)。
我想要得到这三个点,使它们构成一个三角形,并且这个三角形中包含D点(红点)。 如果没有这样的三角形,则可以抛出异常。
所以对于上面的图片,我想要得到黑色点:
目前我做过的事情: 我认为可以通过它们与D点的距离将这些点排序,然后从排序好的列表中取前三个点。但是问题在于,这三个最近的点可能组成一个不包括点D的三角形。这在下面的图片中显示:
除了得到错误的点之外,我还不能确定D是否位于找到的点的凸包内,因此无法确定是否存在包含点D的三角形。 这就是我卡住的地方。
关键是要在寻找有效解决方案的同时,专注于最优解决方案:
对于每个点: - 存储到目标点的距离 - 存储相对于目标点的位置。
我会使用以下枚举来存储位置:
enum RelativePosition
{
ll,
le,
lg,
eg,
gg,
ge,
gl,
el,
ee
}
第一个字母表示点相对于目标点的x坐标,第二个字母表示点相对于目标点的y坐标。
l表示小于,g表示大于,e表示等于
按距离(升序)排序您的点以接近目标点。从最接近的点开始,根据相对位置获取一组候选点,这些候选点将在目标周围形成三角形。还要从这些候选项中选择与目标最接近的点,并以相同方式继续进行第三个点。
我现在在手机上,很难提供代码,但我将能够在一两个小时内编写它。
编辑:
抱歉耽搁了。以下是一些代码。
您会发现在ValidPositions
方法中,我硬编码了所有相对于第一个和第二个点位置的有效位置。我知道它们之间存在数学关系并且可以生成,但让我们说我将其作为练习。:)
即使有了这种方法,也有些情况下您无法确定目标点是否在三角形区域内(请参见UncertainSolution
方法)。但是,如果TriangleContainsPoint
的数量减少,则测试次数会减少。
编辑2:修复了TriangleContainsPoint方法中的错误。
class Point2D
{
public double X { get; set; }
public double Y { get; set; }
}
enum RelPos2D
{
ll = 1,
le = 2,
lg = 3,
eg = 4,
gg = 5,
ge = 6,
gl = 7,
el = 8,
ee = 0
}
static class Tools2D
{
public static double Distance(Point2D Point1, Point2D Point2)
{
return Math.Sqrt(Math.Pow(Point1.X - Point2.X, 2) + Math.Pow(Point1.Y - Point2.Y, 2));
}
public static RelPos2D RelativePosition(Point2D Of, Point2D To)
{
int xRel = Of.X < To.X ? -1 : Of.X > To.X ? 1 : 0;
int yRel = Of.Y < To.Y ? -1 : Of.Y > To.Y ? 1 : 0;
switch (xRel)
{
case -1:
switch (yRel)
{
case -1: return RelPos2D.ll;
case 0: return RelPos2D.le;
case 1: return RelPos2D.lg;
}
break;
case 0:
switch (yRel)
{
case -1: return RelPos2D.el;
case 0: return RelPos2D.ee;
case 1: return RelPos2D.eg;
}
break;
case 1:
switch (yRel)
{
case -1: return RelPos2D.gl;
case 0: return RelPos2D.ge;
case 1: return RelPos2D.gg;
}
break;
}
return RelPos2D.ee; // never reached
}
public static double TriangleArea(Point2D Point1, Point2D Point2, Point2D Point3)
{
return 1 / 2d *
(
(Point1.X - Point3.X) * (Point2.Y - Point1.Y) -
(Point1.X - Point2.X) * (Point3.Y - Point1.Y)
);
}
public static bool TriangleContainsPoint(Point2D Point1, Point2D Point2, Point2D Point3, Point2D Target)
{
var s = Point1.Y * Point3.X - Point1.X * Point3.Y + (Point3.Y - Point1.Y) * Target.X + (Point1.X - Point3.X) * Target.Y;
var t = Point1.X * Point2.Y - Point1.Y * Point2.X + (Point1.Y - Point2.Y) * Target.X + (Point2.X - Point1.X) * Target.Y;
if ((s < 0) != (t < 0))
return false;
var area = TriangleArea(Point1, Point2, Point3);
var sign = area < 0 ? -1 : 1;
s *= sign;
t *= sign;
area *= sign;
return s > 0 && t > 0 && (s + t) < 2 * area;
}
}
class ProblemSolver
{
private static RelPos2D[] AllPositions = new RelPos2D[]
{
RelPos2D.ee,
RelPos2D.eg,
RelPos2D.el,
RelPos2D.ge,
RelPos2D.gg,
RelPos2D.gl,
RelPos2D.le,
RelPos2D.lg,
RelPos2D.ll,
};
private static RelPos2D[] NoPositions = new RelPos2D[0];
private static RelPos2D[] ValidPositions(RelPos2D Pos1, RelPos2D Pos2)
{
if (Pos1 == RelPos2D.ee || Pos2 == RelPos2D.ee)
return AllPositions;
switch (Pos1)
{
case RelPos2D.ll:
switch (Pos2)
{
case RelPos2D.ll:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg };
case RelPos2D.le:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge };
case RelPos2D.lg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl };
case RelPos2D.eg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl, RelPos2D.el };
case RelPos2D.gg:
return AllPositions;
case RelPos2D.ge:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
case RelPos2D.gl:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
case RelPos2D.el:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg };
}
break;
case RelPos2D.le:
switch (Pos2)
{
case RelPos2D.ll:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge };
case RelPos2D.le:
return NoPositions;
case RelPos2D.lg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl };
case RelPos2D.eg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl, RelPos2D.el };
case RelPos2D.gg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl, RelPos2D.el, RelPos2D.ll };
case RelPos2D.ge:
return AllPositions.Except(new RelPos2D[] { Pos1, Pos2 }).ToArray();
case RelPos2D.gl:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
case RelPos2D.el:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg };
}
break;
case RelPos2D.lg:
switch (Pos2)
{
case RelPos2D.ll:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl };
case RelPos2D.le:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl };
case RelPos2D.lg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl};
case RelPos2D.eg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el };
case RelPos2D.gg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el, RelPos2D.ll };
case RelPos2D.ge:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el, RelPos2D.ll, RelPos2D.le };
case RelPos2D.gl:
return AllPositions;
case RelPos2D.el:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl };
}
break;
case RelPos2D.eg:
switch (Pos2)
{
case RelPos2D.ll:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl, RelPos2D.el };
case RelPos2D.le:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl, RelPos2D.el };
case RelPos2D.lg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el };
case RelPos2D.eg:
return NoPositions;
case RelPos2D.gg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll };
case RelPos2D.ge:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll, RelPos2D.le };
case RelPos2D.gl:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll, RelPos2D.le, RelPos2D.lg };
case RelPos2D.el:
return AllPositions.Except(new RelPos2D[] { Pos1, Pos2}).ToArray();
}
break;
case RelPos2D.gg:
switch (Pos2)
{
case RelPos2D.ll:
return AllPositions;
case RelPos2D.le:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl, RelPos2D.el, RelPos2D.ll };
case RelPos2D.lg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el, RelPos2D.ll };
case RelPos2D.eg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll };
case RelPos2D.gg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll };
case RelPos2D.ge:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le};
case RelPos2D.gl:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le, RelPos2D.lg };
case RelPos2D.el:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le, RelPos2D.lg, RelPos2D.eg };
}
break;
case RelPos2D.ge:
switch (Pos2)
{
case RelPos2D.ll:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
case RelPos2D.le:
return AllPositions.Except(new RelPos2D[] { Pos1, Pos2 }).ToArray();
case RelPos2D.lg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el, RelPos2D.ll, RelPos2D.le };
case RelPos2D.eg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll, RelPos2D.le };
case RelPos2D.gg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le };
case RelPos2D.ge:
return NoPositions;
case RelPos2D.gl:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg };
case RelPos2D.el:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg, RelPos2D.eg };
}
break;
case RelPos2D.gl:
switch (Pos2)
{
case RelPos2D.ll:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
case RelPos2D.le:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg, RelPos2D.ge };
case RelPos2D.lg:
return AllPositions;
case RelPos2D.eg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll, RelPos2D.le, RelPos2D.lg };
case RelPos2D.gg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le, RelPos2D.lg };
case RelPos2D.ge:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg};
case RelPos2D.gl:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg };
case RelPos2D.el:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg };
}
break;
case RelPos2D.el:
switch (Pos2)
{
case RelPos2D.ll:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg };
case RelPos2D.le:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg, RelPos2D.ge };
case RelPos2D.lg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl };
case RelPos2D.eg:
return AllPositions.Except(new RelPos2D[] { Pos1, Pos2 }).ToArray();
case RelPos2D.gg:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le, RelPos2D.lg, RelPos2D.eg };
case RelPos2D.ge:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg, RelPos2D.eg };
case RelPos2D.gl:
return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg };
case RelPos2D.el:
return NoPositions;
}
break;
}
return NoPositions;
}
private static bool UncertainSolution(RelPos2D Pos1, RelPos2D Pos2, RelPos2D Pos3)
{
RelPos2D[] array = new RelPos2D[] { Pos1, Pos2, Pos3 };
return
(array.Contains(RelPos2D.ll) && array.Contains(RelPos2D.gg)) ||
(array.Contains(RelPos2D.lg) && array.Contains(RelPos2D.gl));
}
public Tuple<Point2D, Point2D, Point2D> Solve(Point2D Target, params Point2D[] Points)
{
Dictionary<Point2D, double> distanceToTarget = new Dictionary<Point2D, double>();
Dictionary<Point2D, RelPos2D> relativePosition = new Dictionary<Point2D,RelPos2D>();
List<int> visited = new List<int>();
Dictionary<RelPos2D, int> countPerPosition = new Dictionary<RelPos2D, int>()
{
{RelPos2D.ee,0},
{RelPos2D.eg,0},
{RelPos2D.el,0},
{RelPos2D.ge,0},
{RelPos2D.gg,0},
{RelPos2D.gl,0},
{RelPos2D.le,0},
{RelPos2D.lg,0},
{RelPos2D.ll,0}
};
foreach (var point in Points)
{
distanceToTarget.Add(point, Tools2D.Distance(point, Target));
RelPos2D position = Tools2D.RelativePosition(point, Target);
relativePosition.Add(point, position);
countPerPosition[position]++;
}
//check countPerPosition to see if there are solutions
int pointsCount = Points.Length;
bool noSolutions = false;
foreach (var key in countPerPosition.Keys)
{
if (countPerPosition[key] == pointsCount)
{
noSolutions = true;
break;
}
}
noSolutions = noSolutions ||
countPerPosition[RelPos2D.ll] + countPerPosition[RelPos2D.le] + countPerPosition[RelPos2D.lg] == pointsCount ||
countPerPosition[RelPos2D.lg] + countPerPosition[RelPos2D.eg] + countPerPosition[RelPos2D.gg] == pointsCount ||
countPerPosition[RelPos2D.gg] + countPerPosition[RelPos2D.ge] + countPerPosition[RelPos2D.gl] == pointsCount ||
countPerPosition[RelPos2D.ll] + countPerPosition[RelPos2D.el] + countPerPosition[RelPos2D.gl] == pointsCount;
if (noSolutions)
throw new Exception("No solutions.");
var orderedPoints = Points.OrderBy(point => distanceToTarget[point]);
bool found = false;
Point2D
Point1 = null,
Point2 = null,
Point3 = null;
RelPos2D PosPoint1,
PosPoint2,
PosPoint3;
foreach (var point1 in orderedPoints)
{
Point1 = point1;
PosPoint1 = relativePosition[Point1];
var point2Candidates = orderedPoints.Where(p => p != Point1)
.OrderBy(p => distanceToTarget[p]);
//this should not happen because we know that we have at least one solution
if (point2Candidates.Count() == 0)
continue;
foreach (var point2 in point2Candidates)
{
Point2 = point2;
PosPoint2 = relativePosition[Point2];
var point3ValidPositions = ValidPositions(PosPoint1, PosPoint2);
var point3Candidates = orderedPoints.Where(p => p != Point1 && p != Point2 && point3ValidPositions.Contains(relativePosition[p]))
.OrderBy(p => distanceToTarget[p]);
if (point3Candidates.Count() == 0)
continue;
foreach (var point3 in point3Candidates)
{
Point3 = point3;
PosPoint3 = relativePosition[Point3];
//check if already visited
//hash subject to conflicts
var hash = Point1.GetHashCode() *
Point2.GetHashCode() *
Point3.GetHashCode();
if (visited.Contains(hash))
continue;
if (UncertainSolution(PosPoint1, PosPoint2, PosPoint3))
{
found = Tools2D.TriangleContainsPoint(Point1, Point2, Point3, Target);
}
else
{
found = true;
}
if (found)
break;
visited.Add(hash);
}
if (found)
break;
}
if (found)
break;
}
if (found)
return new Tuple<Point2D, Point2D, Point2D>(Point1, Point2, Point3);
throw new Exception("No solutions.");
}
}
class Program
{
static void Main(string[] args)
{
ProblemSolver ps = new ProblemSolver();
Random r = new Random();
List<Point2D> points = new List<Point2D>();
Point2D target = new Point2D()
{
//X = r.NextDouble() * 10,
//Y = r.NextDouble() * 10
X = r.Next(11),
Y = r.Next(11)
};
for (int i = 0; i < 10; i++)
points.Add(new Point2D()
{
//X = r.NextDouble() * 10,
//Y = r.NextDouble() * 10
X = r.Next(11),
Y = r.Next(11)
});
Console.WriteLine("Target: {0}X: {1}{0}Y: {2}{0}", Environment.NewLine, target.X, target.Y);
Stopwatch sw = new Stopwatch();
sw.Start();
try
{
var solution = ps.Solve(target, points.ToArray());
Console.WriteLine("Solution: {0}X1: {1}{0}Y1: {2}{0}X2: {3}{0}Y2: {4}{0}X3: {5}{0}Y3: {6}{0}",
Environment.NewLine,
solution.Item1.X,
solution.Item1.Y,
solution.Item2.X,
solution.Item2.Y,
solution.Item3.X,
solution.Item3.Y
);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
sw.Stop();
Console.WriteLine("Solved in {0} ms", sw.ElapsedMilliseconds);
Console.ReadLine();
}
这给出了直线方程系数的以下公式:
OrderBy
调用,它们可能会被重复评估。 - BartoszKP