Нахождение выпуклой оболочки для множества точек. Медленный вариант.

Давно хотел начать разбирать алгоритмы вычислительной геометрии и вот начал.

Это, самый первый алгоритм из книги «Вычислительная геометрия. Алгоритмы и приложения».  В книге он называется 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;
}

Запись опубликована в рубрике Без рубрики с метками , . Добавьте в закладки постоянную ссылку.