Поиск кратчайшего пути в многоугольнике
Агентство по образованию
Тихоокеанский государственный экономический университет
Экономический институт
Поиск кратчайшего пути в многоугольнике
Выполнил: Матвеев А.В.
Владивосток 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.Построим вогнутый многоугольник. Выберем начало и конец так, чтобы по прямой между ними имелись точки, не принадлежащие области.
Сменим начальную и конечную точки.