Почему эта реализация Марса Джарвиса ( "Алгоритм упаковки подарков" ) не работает?

Я пытаюсь реализовать алгоритм Джарвиса для нахождения выпуклой оболочки множества точек, но по какой-то причине это не работает. Это моя реализация:

procedure TPointList.ConvexHull(aHull : TPointList); //Return the convex hull of a set of 2D points
var
  vPointOnHull  : TPoint2D;
  vEndpoint     : TPoint2D;
  I             : integer;
begin
  aHull.Clear;
  if Count < 3 then exit;

  vPointOnHull := Self.LeftMostPoint;
  repeat
    aHull.Add(vPointOnHull);
    vEndpoint := Self.Point[0];

    for I := 1 to Self.Count-1 do
      if Orientation(vPointOnHull,vEndpoint,Self.Point[I]) = LeftHandSide then
        vEndpoint := Self.Point[I];

    vPointOnHull := vEndpoint;
  until vEndpoint = aHull.Point[0];
end;
  • TPointList - это простой список точек.
  • Ориентация - это функция из библиотеки Arash Partow "FastGEO"
  • Реализация снята более или менее непосредственно из статьи в Википедии об алгоритме

Что происходит, так это то, что метод начинает добавлять одну и ту же точку в aHull снова и снова. В одном тестовом случае я посылаю в точках (200; 200) (300; 100) (200; 50) и (100; 100), и алгоритм начинается с добавления (100; 100) в aHull, который является правильным, но затем он начинает добавлять (200; 200) снова и снова.

Очевидно, что я сделал что-то не так в моей реализации, но для жизни меня не вижу, что.

UPDATE:

Джонатан Дурси поставил меня на правильный путь. Эта строка

if Orientation(vPointOnHull,vEndpoint,Self.Point[I]) = LeftHandSide then    

следует заменить на это

if (vPointOnHull = vEndpoint) or (Orientation(vPointOnHull,vEndpoint,Self.Point[I]) = LeftHandSide) then

Работает как шарм: -)

Ответы

Ответ 1

Вероятно, это не совпадение, что (200; 200) - точка 0.

Похоже, вы не исключаете текущую точку (vPointOnHull) как конечную точку (vEndPoint), и ваша реализация Orientation не отвергает этот случай; предположительно, он возвращает LHS, если кросс-продукт является положительным, а если vPointOnHull == vEndPoint, кросс-произведение равно нулю, поэтому никогда не LHS. Поэтому ничто никогда не заменяет Точку 0, если выбрана точка 0, et voila.

Вы можете изменить Orientation, чтобы вернуть "Degenerate" или что-то в этом случае, а также отклонить точку или вы можете исключить текущую точку из когда-либо конечной точки. Обратите внимание, что вы не хотите делать очевидную вещь, отфильтровывайте текущие точки CH из набора точек во время прохода, потому что вам нужно найти, что конечная точка является первой точкой для закрытия цикла.

Обновить. Оглядываясь немного на вещи FastGEO, возможно, обновление ориентации - это не тот путь (хотя немного больше мысли должно идти в случае коллинеарных точек в этом алгоритме; являются коллинеарными точками на корпусе, вы действительно хотите, чтобы первый из них был первым, поэтому вам нужно предложение else if Orientation = Collinear then.. update vEndpoint if new point is closer после этого оператора if).

Проще всего просто добавить пару строк, отслеживающих текущие обозначения, чтобы вы могли легко проверить равенство: что-то вроде

iPointOnHull := Self.IndexOfLeftMostPoint;
vPointOnHull := Self.LeftMostPoint
...
vEndpoint := Self.Point[0];
iEndPoint := 0;
if (iPointOnHull = 0) then 
begin
    vEndPoint := Self.Point[1];
    iEndPoint := 1;
end
...
vPointOnHull := vEndPoint;
iPointOnHull := iEndPoint;

Ответ 2

Цикл добавляет эту строку кода:

aHull.Add(vPointOnHull);

vPointOnHull назначается только в следующих строках:

vPointOnHull := Self.LeftMostPoint;
vPointOnHull := vEndpoint;

Вы уже объяснили, что LeftMostPoint добавляется правильно, поэтому повторение должно начинаться с vEndPoint, которое назначается в следующих строках:

vEndpoint := Self.Point[0];
vEndpoint := Self.Point[I];

Итак, я думаю, что последнее назначение (которое находится в инструкции ниже if) никогда не достигается.

  if Orientation(vPointOnHull,vEndpoint,Self.Point[I]) = LeftHandSide then
    vEndpoint := Self.Point[I];

- Йерун