Поиск кратчайшего пути в многоугольнике

Агентство по образованию

Тихоокеанский государственный экономический университет

Экономический институт

Поиск кратчайшего пути в многоугольнике

Выполнил: Матвеев А.В.

Владивосток 2009

Введение

Условие решаемой задачи дословно по заданию звучит следующим образом: «В заданном m-угольнике найти кратчайший путь между стартом, лежащим в одной из его вершин, и финишем, находящимся на одной из его сторон».

Для большей эффективности положим старт и финиш произвольными точками внутри m-угольника, выбираемыми пользователем. Предоставим возможность выбирать размерность поля N на N для дальнейшего построения внутри неё, создаваемого пользователем, m-угольника. Графически покажем один из кратчайших путей между стартом финишем.

Перед началом вычисления пользователь должен указывать в программе следующую информацию

- размер поля;

- кол-во опорных точек, для построения m-угольника

- местоположение вершин m-угольника(с помощью мыши)

-место положение финиша и старта внутри m-угольника(также с помощью мыши)

После установки опорных точек программа должна определять принадлежность той или иной точки к внутренней области m-угольника, после чего просчитывать кратчайший путь с учётом доступности(внутри m-угольника) и не доступности(вне m-угольника) точек и, в соответствии с этим, отбирать те из них, которые задействованные в пути.

Программа должна отображать поле, область(m-угольник) и путь между стартом и финишем.

Необходимо предусмотреть контроль целостности вводимых данных, таких как размер поля и кол-во опорных точек.

Не допустить совпадения финиша и старта или установку их вне области а так же дать возможность в заранее построенной области изменять их положение.

Формальная постановка задачи

Положим поле двумерным массивом Shape’ов, основные функции которого дать пользователю возможность задания вершин m-угольника, старта и финиша, а также графическое отображение работы программы. В соответствии ему поставим двумерный булевый массив(доступные и недоступные точки).

Используя булевую матрицу и координаты старта и финиша вычисляем точки кратчайшего пути, которые далее отображаем с помощью массива Shape’ов.

Методы решения задачи

Локализация точек

Существует довольно много различных методов решения подобной задачи, каждый из которых основывается на своих принципах и приемах, имеет уникальные преимущества и, соответственно, недостатки. В данной работе был использован наиболее простой и менее громоздкий с учётом того, что на поле между точками имеется некоторое расстояние.

Суть используемого метода в следующем. По заданным вершинам строится полигон и заливается цветом, отличным от цвета фона. Далее для каждой точки идёт проверка цвета канвы. Если цвет канвы в данной точке совпадает со цветом заливки полигона то точка принадлежит заданной области.

Построение полигона:

with canvas do begin

setlength(tochka,m);

for i:=0 to m-1 do begin

tochka[i].X:=integer(vershina[i].x^)+round(h/(4*n));

tochka[i].Y:=integer(vershina[i].y^)+round(h/(4*n));

end;

Pen.Color:=clred;

Polygon(tochka);

brush.color:=clred;

end;

end;

Здесь здесь vershina[].х и vershina[].у указатели на Top и Left Shape’ов, tochka[]-массив координат центров этих Left Shape’ов.

Проверка цвета:

for i:=0 to n-1 do

for j:= 0 to n-1 do

if canvas.Pixels[a[i,j].Left+round(h/(4*n)),a[i,j].Top+round(h/(4*n))]=clred then

a[i,j].Brush.Color:=clgreen;

Также приведём пример решения этой задачи в более общем случае. Его суть в том, что вначале строится контур области, а потом для каждой точки идет подсчёт кол-ва пересечений горизонтали, проведённой через эту точку, с контурами области слева от определяемой точки. Если кол-во нечётно то она принадлежит области, иначе не принадлежит.

Приведём текст такого метода:

dx:=(bx-ax)/m;

расстояние по горизонтали между двумя соседними точками ребра

dy:=(by-ay)/m;//по вертикали

{Локализация}

x:=ax+dx/2;

for i:=1 to m do begin

y:=ay+dy/2;

//WriteLn(fout);

for j:=1 to m do begin

//Write(fout,'(',x:0:1,',',y:0:1,')',' ');

{(x,y)-локализация}

L:=0; {Число пересечений слева}

for k:=1 to n-1 do begin

x1:=xv[k]; y1:=yv[k]; {Ребро}

x2:=xv[k+1]; y2:=yv[k+1];

if ((y1<y2) and (y1<y) and (y<y2)) or

((y2<y1) and (y2<y) and (y<y1)) then begin

{Уравнение прямой через 2 точки}

x3:=(y-y1)/(y2-y1)*(x2-x1)+x1;

if x3<x then L:=L+1;

end;

end;

y:=y+dy;

//WriteLn(fout,'L=',L);

if (L mod 2) =0 then b[m-j+1,i]:=0 else b[m-j+1,i]:=1;

end;

x:=x+dx;

end;

for i:=1 to m do begin

WriteLn(fout);

for j:=1 to m do begin

Write(fout,b[i,j]);

end;

end;

Поиск кратчайшего пути

Суть реализованного алгоритма состоит в том что, в соответствие булевой матрице, отражающей доступность точек, ставится целочисленная матрица меток. В её элементы записываются кол-ва ходов, за которое можно попасть из финиша в данную точку булевой матрицы. Когда устанавливается значение в метку, соответствующий старту начинается обратный ход. Программа ищет соседнюю старту точку, метка которой на 1 меньше метки старта. Далее из найденной точки повторяется та же операция и так до тех пор пока не будет достигнут финиш.

procedure Tgraph.find(var z:Tmatrix;a,b:Txy;n:Integer);

var i,j,i1,j1:integer;

c:Integer;//для записи значений в метки

yyy:Boolean;//используется как условие выхода из цикла

LABEL LBL;

begin

ny:=0;//длина пути

//зополнение матрицы меток бесконечностями

for i:=0 to n-1 do

for j:=0 to n-1 do metka1[i,j]:=$7fff;

metka1[b.x,b.y]:=0;//метка соответствующая финишу

//процедура записывает в конкретную метку кол-во ходов,

//необходимых чтобы попасть в неё с финиша

c:=-1;

while 1000>=c do begin

c:=c+1;

for i:=0 to n-1 do begin

for j:=0 to n-1 do begin

if metka1[i,j]=c then begin

for i1:=-1 to 1 do begin

for j1:=-1 to 1 do begin

if (i1=0) and (j1=0) then continue;//что бы не проверять саму точку

if not z[i+i1,j+j1] or (metka1[i+i1,j+j1]<>$7fff) then continue;//точка не доступ- //на или путь к ней отсутствует

metka1[i+i1,j+j1]:=c+1;

if (i+i1=a.x) and (j+j1=a.y) then begin//попали на старт

goto LBL;

end;

end;

end;

end;

end;

end;

end;

//запись полученной матрицы меток в текстовый файл

LBL:

//процедура двигаясь от старта к финишу по полученным меткам

//заносит пройденные точки в массив точек пути

if metka1[a.x,a.y]=$7fff then begin

exit;

end;

c:=metka1[a.x,a.y];//кол-во ходов от старта до финиша

i:=a.x;

j:=a.y;

yWay[1]:=a;

ny:=1;//кол-во точек, использованных в пути

while c>0 do begin

c:=c-1;

yyy:=False;

for i1:=-1 to 1 do begin

for j1:=-1 to 1 do begin

if (i1=0) and (j1=0) then continue;//чтобы не проверять саму точку

if metka1[i+i1,j+j1]<>c then continue;

ny:=ny+1;//увеличение длины пути

yWay[ny].x:=i+i1;//добавление точки

yWay[ny].y:=j+j1;// в путь

if (i+i1=b.x) and (j+j1=b.y) then exit;

i:=i+i1;

j:=j+j1;

yyy:=TRUE;//используется для выхода из первого цикла “FOR”

break;

end;

if yyy then break;

end;

end;

end;

Текст программы

В данном пункте приводятся тексты основного модуля без текста модуля для расчёта пути, так как его главная часть приведена выше.

unit MainUnit;

interface

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, ExtCtrls, StdCtrls,Sgraph;

Const

nMaxShape=25;

type

coordinate=record

x:pointer;

y:pointer

end;

razmetka=array[0..nMaxShape,0..nMaxShape] of TShape;

TForm1 = class(TForm)

Panel1: TPanel;

btnstroi: TButton;

btnfinish: TButton;

btnstart: TButton;

btnnew: TButton;

Edit1: TEdit;

Edit2: TEdit;

btnGraph: TButton;

Label1: TLabel;

Label2: TLabel;

procedure matriza();

procedure btnstroiClick(Sender: TObject);

procedure btnnewClick(Sender: TObject);

procedure vershini(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

procedure FormCreate(Sender: TObject);

procedure btnstartClick(Sender: TObject);

procedure btnfinishClick(Sender: TObject);

procedure FormPaint(Sender: TObject);

procedure FormResize(Sender: TObject);

procedure btnGraphClick(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

function min(x,y:integer):integer;

procedure DrawWay;

procedure myShape;

public

k:integer;

a:razmetka;

end;

var

index1,index2:boolean;//проверка возможности расчёта

Form1: TForm1;

n,h,m:integer;

vershina: array of coordinate;

tochka:array of Tpoint;

matr: TMatrix;

nachialo,konez:Txy;

implementation

{$R *.dfm}

//выбор и отображение нужного кол-ва Shape'ов

procedure TForm1.myShape;

var i,j:integer;

begin

for i:=0 to n-1 do

for j:=0 to n-1 do begin

a[i,j].Shape:=stcircle;

a[i,j].Parent:=self;

a[i,j].Brush.Color:=clwhite;

a[i,j].Height:=round(h/(2*n));

a[i,j].Width:=round(h/(2*n));

a[i,j].Top:=round(i*h/n);

a[i,j].Left:=round(j*h/n);

a[i,j].Show;

end;

end;

//создание массива шейпов

procedure TForm1.btnstroiClick(Sender: TObject);

var i,j:integer;

begin

try

m:=strtoint(edit2.Text);//кол-во опорных точек

n:=strtoint(edit1.Text);//размерность

if (n<=nMaxShape)and(m<n)then begin

setlength(vershina,m); myShape();btnStroi.Enabled:=False

end

else begin

application.MessageBox ('введите кол-во точек<размерность <'+'25','ошибка');

edit1.Clear;edit2.clear; edit1.SetFocus;

end;

except

application.MessageBox('введите целое число','ошибка');

edit1.Clear;edit1.Clear;edit1.SetFocus;

end;

end;

procedure TForm1.btnnewClick(Sender: TObject);

var j,i:integer;

begin

wGraph.ny:=0; //Нет пути

k:=0;

for i:=0 to n-1 do

for j:=0 to n-1 do a[i,j].Hide;

invalidate;

edit1.Clear;

edit1.SetFocus;

edit2.Clear;

index1:=false;index2:=false;

btnStroi.Enabled:=True;

end;

//создание области по выбранным вершинам(ShapeClick)

procedure TForm1.vershini(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

var i,j:integer;

begin

if k<m then

begin //получение массива точек для полигона

vershina[k].x:=@(sender as TShape).left;

vershina[k].y:=@(sender as TShape).top;

(sender as TShape).brush.Color:=clgreen;

k:=k+1;

if k=m then

begin formpaint(self);//закраска области

//определение принадлежности точки области

for i:=0 to n-1 do

for j:= 0 to n-1 do

if canvas.Pixels[a[i,j].Left+round(h/(4*n)),a[i,j].Top+round(h/(4*n))]=clred then

a[i,j].Brush.Color:=clgreen;

btnstart.Enabled:=true;

btnfinish.Enabled:=true;

invalidate

end;

end;

//изменение начала

if ((btnstart.Tag=1)and((sender as tshape).Brush.Color=clyellow))

then index2:=false;

if (btnstart.Tag=1)and((sender as tshape).Brush.Color=clgreen)

or((btnstart.Tag=1)and((sender as tshape).Brush.Color=clyellow))

then begin(sender as tshape).Brush.Color:=clblue;index1:=true;

btnstart.Tag:=0 end;

//изменение конца

if ((btnfinish.Tag=1)and((sender as tshape).Brush.Color=clblue))

then index1:=false;

if (btnfinish.Tag=1)and((sender as tshape).Brush.Color=clgreen)

or((btnfinish.Tag=1)and((sender as tshape).Brush.Color=clblue))

then begin btnfinish.Tag:=0;index2:=true;

(sender as tshape).Brush.Color:=clyellow end;

if (index1=true) and (index2=true) then btnGraph.Enabled:=true;

end;

procedure TForm1.FormCreate(Sender: TObject);

var i,j,n:integer;

begin

k:=0;

panel1.Tag:=0;

btnstart.Enabled:=false;

btnfinish.Enabled:=false;

btnGraph.Enabled:=false;

n:=nMaxShape;

//self.WindowState:=wsMaximized;

for i:=0 to n-1 do

for j:=0 to n-1 do begin

a[i,j]:=tshape.Create(self);

a[i,j].Shape:=stcircle;

a[i,j].Parent:=self;

a[i,j].Brush.Color:=clwhite;

a[i,j].Height:=41;

a[i,j].Width:=41;

a[i,j].Top:=round(i*100/n);

a[i,j].Left:=round(j*100/n);

a[i,j].onmousedown:=form1.vershini;

WriteLn(wgraph.fout,i:3,j:3);

a[i,j].Hide;

end;

end;

//постановка начала

procedure TForm1.btnstartClick(Sender: TObject);

var i,j:integer;

begin

index1:=false;

btnstart.Tag:=1;

for i:=0 to n-1 do

for j:= 0 to n-1 do

if a[i,j].Brush.Color=clblue then

a[i,j].Brush.Color:=clgreen

end;

//постановка конца

procedure TForm1.btnfinishClick(Sender: TObject);

var i,j:integer;

begin

index2:=false;

btnfinish.Tag:=1;

for i:=0 to n-1 do

for j:= 0 to n-1 do

if a[i,j].Brush.Color=clyellow then

a[i,j].Brush.Color:=clgreen

end;

procedure TForm1.FormPaint(Sender: TObject);

var i:integer;

begin

if k=m then begin

with canvas do begin

setlength(tochka,m);

for i:=0 to m-1 do begin

tochka[i].X:=integer(vershina[i].x^)+round(h/(4*n));

tochka[i].Y:=integer(vershina[i].y^)+round(h/(4*n));

end;

Pen.Color:=clred;

Polygon(tochka);

brush.color:=clred;

end;

end;

DrawWay();//вызов рисования кратчайшего пути

end;

function TForm1.min(x,y:integer):integer;

begin

if x<y then result:=x else result:=y;

end;

procedure TForm1.FormResize(Sender: TObject);

var i,j:integer;

begin

h:=form1.min(Form1.ClientWidth-Panel1.Width,Form1.ClientHeight);

for i:=0 to n-1 do

for j:=0 to n-1 do begin

a[i,j].Top:=round(i*h/n);

a[i,j].Left:=round(j*h/n);

end;

Invalidate;

end;

//создание матрицы для графа

procedure TForm1.matriza();

var i,j:integer;

begin

for i:=-1 to n do

for j:=-1 to n do matr[i,j]:=False;

for i:=0 to n-1 do

for j:=0 to n-1 do begin

if a[i,j].Brush.Color=clWhite then matr[i,j]:=false

else matr[i,j]:=true;

if a[i,j].Brush.Color=clBlue then begin

nachialo.x:=i;

nachialo.y:=j;

end;

if a[i,j].Brush.Color=clYellow then begin

konez.x:=i;

konez.y:=j;

end;

end;

end;

procedure TForm1.btnGraphClick(Sender: TObject);

var i,j:integer;

begin

matriza();

wGraph.find(matr,nachialo,konez,n);

for i:=0 to n-1 do

for J:=0 to n-1 do

if a[i,j].Brush.Color=rgb(0,255,0)

then a[i,j].Brush.Color:=clGreen;

Invalidate;

end;

//процедура рисования кратчайшего пути

procedure TForm1.DrawWay;

var i,ik,jk:integer;

begin

for i:=1 to wGraph.ny do begin

ik:=wGraph.yWay[i].x;

jk:=wGraph.yWay[i].y;

a[ik,jk].Brush.Color:=RGB(0,255,0);

end;

Интерфейс(руководство пользователю)

При разработке приложения применялся принятый в среде Delphi объектно-ориентированный подход реализации интерфейса. При реализации алгоритмов обработки данных использовался структурный подход при проектировании к написании программ приложения.

Окно интерфейса приложения представлено на рисунке. Прежде всего заполняются поля размер и кол-во опорных точек.

Далее по нажатию кнопки старт формируется поле Shape’ов заданной размерности. Кликами мыши выбираются опорные Shape в кол-ве заданном в поле «кол-во опорных точек».

После выбора всех опорных точек отображается построенная на них область. Теперь необходимо установить начало и конец сначала нажав на соответствующую кнопку а затем на нужный Shape.Повторным нажатием на одну из этих кнопок можно изменить положение начала и конца.

По нажатию кнопки «Расчёт» будет построен кратчайший путь, но только если между данным началом и концом он вообще существует. Для перерасчёта с изменением начала и конца следует их заново установить и нажать кнопку «Расчёт». Для изменения области нужно нажать кнопку «Новый» и приступить ко всем изложенным операциям сначала.

Тестовый пример программы

Положим размер поля равным 20 и кол-во опорных точек 10.Построим вогнутый многоугольник. Выберем начало и конец так, чтобы по прямой между ними имелись точки, не принадлежащие области.

Сменим начальную и конечную точки.