- •Диаметр множества
- •1. Введение
- •1.1. Постановка задачи
- •1.2. Основные понятия
- •2. Теоретические материалы
- •2.1. Описание задачи и методов ее решения
- •2.2. Описание алгоритма
- •2.3. Оценка сложности алгоритма
- •3. Примеры работы алгоритма
- •4. Описание программы
- •4.1. Функции программы
- •4.2. Структура программы
- •4.3. Исходные тексты
2.3. Оценка сложности алгоритма
Как было описано выше, сложность алгоритма: O(n log2n). Действительно, рассмотрим алгоритм (в худшем случае, когда все точки множества являются точками выпуклой оболочки):
-
нахождение выпуклой оболочки: O(n log2n);
-
выбор крайних по x точек: O(n);
-
основной ход алгоритма: O(n).
3. Примеры работы алгоритма
Интересно выделить следующие ситуации:
-
все точки множества являются точками выпуклой оболочки (см. рис. 1);
-
большинство точек множества не являются точками выпуклой оболочки, только три точки (треугольник) являются точками выпуклой оболочки (см. рис. 2);
-
произвольный случай множества (см. рис. 3).
Рис. 1
Рис. 2
Рис. 3
Описание демонстрационной программы приведено в документе «Руководство пользователя».
4. Описание программы
Подробное описание программы с точки зрения пользователя приведено в документе «Руководство пользователя». Ниже описаны основные функции программы, структура (компоненты), исходные тексты.
4.1. Функции программы
Основными функциями программы являются следующие:
-
построение выпуклой оболочки (используется алгоритм Джарвиса, текст которого на языке программирования С++ приведен в пункте 4.3. Исходные тексты);
-
нахождение диаметра множества:
-
использование пошагового режима;
-
использование режима предугадывания очередного шага;
-
использование автоматического режима.
При использовании автоматического режима нахождения диаметра множества весь алгоритм выполняется сразу, пользователю при этом выдается информация о найденном диаметре, изображается сам диаметр. Кроме того, в протоколе (вызывается при нажатии на кнопку панели инструментов или при выборе пункта меню Вид->Протокол) отображаются все шаги алгоритма.
4.2. Структура программы
Реализованная демонстрационная программа является однодокументным MFC-приложением, работающим в среде Microsoft Windows. Программа состоит из основного графического окна (и соответствующего ему документа), а также ряда вспомогательных диалоговых окон:
-
справочная информация;
-
информация об алгоритме;
-
настройки генерации множества точек;
-
протокол работы алгоритма.
Все точки множества хранятся в полиморфном контейнере. Работа с множеством осуществляется с использованием итератора для этого контейнера. В программе используются обработчики исключительных ситуаций.
Все диалоговые окна вызываются из главного при помощи выбора соответствующего пункта меню или при помощи нажатия на соответствующую кнопку на панели инструментов.
4.3. Исходные тексты
В данном разделе приведены исходные тексты на языке С++ ключевых участков программы.
1) Описание полиморфного контейнера
template <class T, class A = DiameterAlloc<T>, class C = std::list<T,A> >
class TDiameterCont : public C
{
public:
typedef DiameterIterator<T> iterator;
TDiameterCont(): C();
bool empty();
long size();
iterator begin()
iterator end();
// Возвращает первую вершину, имеющую заданный ID
T GetByID(const int ID) throw (DiameterError::EInvalidNodeID);
// Возвращает первую вершину, лежащую в е-окрестности заданной точки
T GetByXY(const int x, const int y, const double e) throw (DiameterError::EInvalidNodeXY);
void Insert(const T& value ) throw (DiameterError::EInvalidInsert);
void DeleteByID(const int ID) throw (DiameterError::EInvalidDelete);
void ClearDiameterCont() throw (DiameterError::EInvalidDelete);
float GetDistance (const T p1, const T p2);
void ScaleAll(const float xScale, const float yScale);
~TDiameterCont();
void UnMakeCH();
void MakeCH() throw (DiameterError::EInvalidCH); //построение выпуклой оболочки
};
2) Описание алгоритма нахождения выпуклой оболочки по методу Джарвиса
void MakeCH() throw (DiameterError::EInvalidCH) //построение выпуклой оболочки
{
try{
UnMakeCH();
//Джарвис
if (C::size()>2)
{
T MinPoint=0;
T MaxPoint=0;
iterator p = begin();
MinPoint=(*p);MaxPoint=(*p);
++p;
while (p != end())
{
if ((*p)->GetYCoord() < MinPoint->GetYCoord())
{
MinPoint=(*p);
}else if ((*p)->GetYCoord() == MinPoint->GetYCoord())
{
if ((*p)->GetXCoord() < MinPoint->GetXCoord())
{
MinPoint=(*p);
}
}
if ((*p)->GetYCoord() > MaxPoint->GetYCoord())
{
MaxPoint=(*p);
}else if ((*p)->GetYCoord() == MaxPoint->GetYCoord())
{
if ((*p)->GetXCoord() > MaxPoint->GetXCoord())
{
MaxPoint=(*p);
}
}
++p;
}//end of while
// from minimun to maximum
T iTempPoint1=0;
T iTempPoint2=0;
iTempPoint1=MinPoint;
p=begin();
iterator t=begin();
iterator t1=begin();++t1;
float n1,n2;
while(iTempPoint1!=MaxPoint) {
// init point 2
if(iTempPoint1!=(*t))
iTempPoint2=(*t);
else
iTempPoint2=(*t1);
// look for second point
for(p=begin();p!=end();++p) {
if((*p)!=iTempPoint1) {
n1 = (iTempPoint2->GetXCoord()-iTempPoint1->GetXCoord())*((*p)->GetYCoord()-iTempPoint1->GetYCoord());
n2 = ((*p)->GetXCoord()-iTempPoint1->GetXCoord())*(iTempPoint2->GetYCoord()-iTempPoint1->GetYCoord());
if(n1>n2)
iTempPoint2=(*p);
}
}
// set line from 1 to 2
iTempPoint1->SetRight(iTempPoint2);
iTempPoint2->SetLeft(iTempPoint1);
// set p1 to p2
iTempPoint1=iTempPoint2;
}//end of while
// from maximum to minimun
while(iTempPoint1!=MinPoint) {
// init point 2
if(iTempPoint1!=(*t))
iTempPoint2=(*t);
else
iTempPoint2=(*t1);
// look for second point
for(p=begin();p!=end();++p) {
if((*p)!=iTempPoint1) {
n1 = (iTempPoint2->GetXCoord()-iTempPoint1->GetXCoord())*((*p)->GetYCoord()-iTempPoint1->GetYCoord());
n2 = ((*p)->GetXCoord()-iTempPoint1->GetXCoord())*(iTempPoint2->GetYCoord()-iTempPoint1->GetYCoord());
if(n1>n2)
iTempPoint2=(*p);
}
}
// set line from 1 to 2
iTempPoint1->SetRight(iTempPoint2);
iTempPoint2->SetLeft(iTempPoint1);
// set p1 to p2
iTempPoint1=iTempPoint2;
}//end of while
}
}catch(...)
{
throw DiameterError::EInvalidCH("Ошибка при поиске выпуклой оболочки!");
}
}
3) Функция очередного шага работы алгоритма
int CDiameterView::NextStep(int AutType)
{
if (!CH_BUILD) return(-1);//если не построена ВО
CDiameterDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
switch (Step){
case 0:
{//ищем макс и мин по х
iter=0;
NumPare=0;
lDialog.mEdit="Начало алгоритма\r\n";
DiameterIterator<TNode*> p = (pDoc->Diameter.begin());
while (p != (pDoc->Diameter.end())) //ищем вершину из ВО (n)
{
if ((*p)->GetRight()!=0) break;
++p;
}
if (p==(pDoc->Diameter.end())) return (-1);
float MinX=1000000,MaxX=-1;
float MinY=-1,MaxY=1000000;
float tmp=-100;
TNode *tmpFir=(*p);
TNode *tmpCur=(*p);
while ((tmpCur!=tmpFir)||(tmp==-100)) //ищем противолежащие по X точки (n)
{
tmp=tmpCur->GetXCoord();
if (tmp<MinX)
{
MinX=tmp;MinY=tmpCur->GetYCoord();NodeP=tmpCur;
} else if ((tmp==MinX)&&(tmpCur->GetYCoord()>MinY))
{
MinX=tmp;MinY=tmpCur->GetYCoord();NodeP=tmpCur;
}
if (tmp>MaxX)
{
MaxX=tmp;MaxY=tmpCur->GetYCoord();NodeQ=tmpCur;
} else if ((tmp==MaxX)&&(tmpCur->GetYCoord()<MaxY))
{
MaxX=tmp;MaxY=tmpCur->GetYCoord();NodeQ=tmpCur;
}
tmpCur=tmpCur->GetRight();
}
NodeP0=NodeP;
NodeQ0=NodeQ;
DiamNodeP=NodeP;
DiamNodeQ=NodeQ;
MaxDiameter=pDoc->Diameter.GetDistance(NodeP,NodeQ);
Step=1;
break;
}
case 1:
{//выполняем очередной шаг алгоритма (while NodeQ!=NodeP0)
if ((NodeP==NodeQ0)&&(NodeQ==NodeP0))
{
Step=4;break;
}
if ((pDoc->Diameter.GetDistance(NodeP,NodeQ))>MaxDiameter)
{
DiamNodeP=NodeP;
DiamNodeQ=NodeQ;
MaxDiameter=pDoc->Diameter.GetDistance(NodeP,NodeQ);
}
NodeP1=NodeP->GetRight();
NodeQ1=NodeQ->GetRight();
float a1=-Angle(NodeP,NodeP1);
float a2=-Angle(NodeQ1,NodeQ);
if (a1<a2) {NodeP=NodeP1;}
else if (a1>a2) {NodeQ=NodeQ1;}
else
{
if ((pDoc->Diameter.GetDistance(NodeP1,NodeQ))>MaxDiameter)
{
DiamNodeP=NodeP1;
DiamNodeQ=NodeQ;
MaxDiameter=pDoc->Diameter.GetDistance(NodeP1,NodeQ);
}
if ((pDoc->Diameter.GetDistance(NodeP,NodeQ1))>MaxDiameter)
{
DiamNodeP=NodeP;
DiamNodeQ=NodeQ1;
MaxDiameter=pDoc->Diameter.GetDistance(NodeP1,NodeQ1);
}
NodeP=NodeP1;
NodeQ=NodeQ1;
}
if ((NodeP==NodeQ0)&&(NodeQ==NodeP0))
{
Step=4;break;
}
break;
}
}
iter++;
return 0;
}
4) Функция, вызывающаяся при нажатии на кнопку пошагового режима
void CDiameterView::OnAlgoritmNext()
{
// TODO: Add your command handler code here
CDiameterDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
int tmp=CH_BUILD;
ResetFlags();
CH_BUILD=tmp;
FIRST_ENABLED=true;
int CurStep=Step;
int res=NextStep(0);
if ((CurStep==2)&&(Step==1)) {CurStep=Step;res=NextStep(0);}
if ((CurStep==1)&&(Step==3)) {res=NextStep(0);}
if (res==-1)
{
AfxMessageBox("Error!");
return;
}
if (Step!=4) WriteToLog(pDoc);
if ((UserNodeP!=0)&&(UserNodeQ!=0))
{
if (Step==4) AfxMessageBox("Это был последний шаг!",0);
else
{
if ( ((UserNodeP==NodeP)&&(UserNodeQ==NodeQ)) ||
((UserNodeP==NodeQ)&&(UserNodeQ==NodeP)) )
{//угадал
AfxMessageBox("Вы правильно выбрали вершины!",0);
}else
{//не угадал
AfxMessageBox("Вы НЕ правильно выбрали вершины!",16);
}
}
}
UserNodeP=UserNodeQ=0;USER=false;
Invalidate();
if (Step==4)
{
NEXT_ENABLED=false;
ALL_ENABLED=false;
USER_ENABLED=false;
WriteToLog(pDoc);
NodeP=NodeQ=0;
char ss[20];
gcvt(MaxDiameter,8,ss);
CString Out="Диаметр: ";Out+=ss;
AfxMessageBox(Out);
}
}
5) Функция, вызывающаяся при нажатии на кнопку автоматического режима
void CDiameterView::OnAlgoritmAll()
{
// TODO: Add your command handler code here
CDiameterDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
int tmp=CH_BUILD;
ResetFlags();
CH_BUILD=tmp;
UserNodeP=UserNodeQ=0;USER=false;
FIRST_ENABLED=true;
while ((Step!=4)&&(iter<10000))
{
int CurStep=Step;
int res=NextStep(0);
if ((CurStep==2)&&(Step==1)) {CurStep=Step;res=NextStep(0);}
if ((CurStep==1)&&(Step==3)) {res=NextStep(0);}
if (res==-1)
{
AfxMessageBox("Error!");
return;
}
if (Step!=4) WriteToLog(pDoc);
if (Step==4)
{
NEXT_ENABLED=false;
ALL_ENABLED=false;
USER_ENABLED=false;
WriteToLog(pDoc);
NodeP=NodeQ=0;
char ss[20];
gcvt(MaxDiameter,8,ss);
CString Out="Диаметр: ";Out+=ss;
AfxMessageBox(Out);
break;
}
}
Invalidate();
if (iter==10000) AfxMessageBox("Too many!");
}