Давно хотел начать разбирать алгоритмы вычислительной геометрии и вот начал.
Это, самый первый алгоритм из книги «Вычислительная геометрия. Алгоритмы и приложения». В книге он называется SlowConvexHull. Работает он неидеально — из-за того, что в нем нет разницы в том, где находится точка — на/перед/позади отрезка. Хотя это не проблема алгоритма, а проблема моей реализации.
Писал его на C#. Думаю, надо бы понемногу начинать осваивать что-то на Python для операций вроде нахождения положения точки относительно отрезка. Вначале меня эта формулировка очень смутила, потому что не подумал, что у этого отрезка есть направление. Вероятно, аналогичных операций еще очень много и не уверен, стоит ли все их реализовывать самостоятельно по формулам.
Код программки для реализации алгоритма.
Класс направленного отрезка
/// <summary>
/// Направленный отрезок (p -> q).
/// </summary>
public class LineSegment
{
public Point P { get; private set; }
public Point Q { get; private set; }
public LineSegment(Point p, Point q)
{
this.P = p;
this.Q = q;
}
}
Сам алгоритм:
/// <summary>
/// Получение минимальной выпуклой оболочки множества точек.
/// </summary>
/// <param name="set"></param>
/// <returns></returns>
public static List<Point> SlowConvexHull(IEnumerable<Point> set)
{
//получаем все возможные векторы для множества точек
var vectors = GetAllLineSegmentsForPointsSet(set.ToArray());
//вектора, входящие в выпуклую оболочку
List<LineSegment> E = new List<LineSegment>();
//оставляем только те вектора, которые являются частью выпуклой оболочки.
bool valid;
foreach(var ve in vectors)
{
valid = true;
//точки, не являющиеся частью текущего вектора ve
var points = set.Where(a => (a.X != ve.P.X && a.Y != ve.P.Y) || (a.X != ve.Q.X && a.Y != ve.Q.Y));
foreach (var p in points)
{
if (WherePoint(ve, p) > 0)
{
valid = false;
break;
}
}
if (valid)
E.Add(ve);
}
//получаем уникальные вектора
E = E.GroupBy(a => new { a.P, a.Q }).Select(a => a.First()).ToList();
//получаем вершины полигона, отсортированные в порядке движения
//по оболочке по часовой стрелке
var res = OrderLineSegments(E);
return res;
}
/// <summary>
/// Определяет, где находится точка относительно вектора.
/// (bx-ax)*(py-ay)-(by-ay)*(px-ax)
/// </summary>
/// <returns>i. i больше 0, если точка слева от вектора,
/// a меньше 0, если точка справа от вектора
/// и = 0, если точка на векторе, прямо по вектору или сзади вектора</returns>
public static int WherePoint(LineSegment vector, Point p)
{
return (vector.Q.X - vector.P.X) * (p.Y - vector.P.Y)
- (vector.Q.Y - vector.P.Y) * (p.X - vector.P.X);
}
/// <summary>
/// Получение упорядоченного в порядке обхода по часовой стрелке
/// списка вершин выпуклой оболочки из списка направленных отрезков.
/// </summary>
/// <returns></returns>
public static List<Point> OrderLineSegments(List<LineSegment> vectors)
{
List<Point> L = new List<Point>();
var fst = vectors.First();
L.Add(fst.P);
L.Add(fst.Q);
while (true)
{
var po = vectors.Where(v => v.P.X == L.Last().X && v.P.Y == L.Last().Y).First().Q;
L.Add(po);
if (L.First().X == po.X && L.First().Y == po.Y)
break;
}
return L;
}
/// <summary>
/// Получение всех возможных векторов для множества точек.
/// </summary>
/// <param name="points"></param>
/// <returns></returns>
public static List<LineSegment> GetAllLineSegmentsForPointsSet(Point[] points)
{
List<LineSegment> res = new List<LineSegment>();
for (int i = 0; i < points.Length; i++)
{
for (int j = 0; j < points.Length; j++)
{
if(i != j)
{
res.Add(new LineSegment(points[i], points[j]));
res.Add(new LineSegment(points[j], points[i]));
}
}
}
res = res.GroupBy(x => new { x.P, x.Q }).Select(x => x.First()).ToList<LineSegment>();
return res;
}
Выполнение алгоритма:
public static void Main(string[] args)
{
//множество точек на плоскости
List<Point> P = GetRandomPoints(5, maxXY: 10);
//тест:
P = GetRandomPointsTest();
//вершины оболочки выпуклой плоскости CH(P) в порядке обхода по часовой стрелке
IEnumerable<Point> L = SlowConvexHull(P);
//рисуем все точки и минимальную выпуклую оболочку для них
DrawPointsSetWithConvexHull(P, L);
foreach(var po in L)
Console.WriteLine(string.Format("X: {0} and Y: {1}", po.X, po.Y));
Console.ReadLine();
}
/// <summary>
/// Получение множества случайных точек.
/// </summary>
/// <param name="num">вернуть это количество точек</param>
/// <param name="maxXY">максимальная длина по абсциссе и ординате
/// (отсчет от 0, без отрицательных значений)</param>
/// <returns></returns>
public static List<Point> GetRandomPoints(int num, int maxXY = 25)
{
List<Point> res = new List<Point>();
Random r = new Random();
int x;
int y;
for(int i = 0; i < num; i++)
{
x = r.Next(maxXY + 1);
y = r.Next(maxXY + 1);
res.Add(new Point(x, y));
}
res =
res.GroupBy(a => new { a.X, a.Y })
.Select(a => a.First())
.ToList();
return res;
}