Aleksander Timofiejew

Transkrypt

Aleksander Timofiejew
Ćwiczenie laboratoryjne
„Algorytm przeszukiwania grafu wszerz”
Tematy ćwiczenia

algorytm przeszukiwania grafu wszerz.
Sprawozdanie
Na każdym zajęciu laboratoryjnym sporządza się za pomocą edytora Word sprawozdanie.
Bazowa zawartość sprawozdania musi być przygotowana w domu przed ćwiczeniem (sprawozdanie do ćwiczenia
pierwszego jest przygotowywane w czasie ćwiczenia).
W czasie ćwiczenia do sprawozdania są dodawane wyniki testowania.
Treść sprawozdania:
strona tytułowa,
spis treści sporządzony za pomocą Word'a,
dla każdego zadania rozdziały "Zadanie ", "Opracowanie zadania" (rozdział z tekstem programu i komentarzami),
"Testowanie" (rozdział z opisem danych wejściowych i wynikami testowania, w tym zrzuty aktywnego okna).
Wzorzec strony tytułowej znajduje się w pliku Strona_tytulowa_niestac_AiSD.doc.
Nazwa (bez polskich liter, żeby można było archiwizować) pliku ze sprawozdaniem musi zawierać skrót "AiSD_", numer
ćwiczenia i nazwisko studenta.
Pliki ze sprawozdaniem są przekazywane do archiwum grupy.
Przeszukiwanie grafu wszerz
Zadanie
Zadanie polega na oprogramowaniu algorytmu przeszukiwania grafu wszerz.
Program musi przyjmować konfigurację grafu, wyświetlać graf i zmiany w grafie w trybie krokowym,
przy czym wyświetlać zawartość kolejki oraz zaznaczać front i koniec kolejki.
Musi być realizowana gra "Zgadnij następne wierzchołki". Użytkownik wprowadzi indeksy (lub
nazwy) następnych wierzchołków. Po naciśnięciu przyciska krokowego aplikacja musi porównać
wprowadzone indeksy (lub nazwy) i indeksy (lub nazwy) oczekiwane według algorytmu i wydać
odpowiedni komunikat (złośliwy lub zachęcający).
Opracowanie zadania
(tekst programu)
Testowanie
Wprowadzić pewną konfigurację grafu.
Sprawdzić poprawność przeszukiwania grafu wszerz.
(przedstawić zrzut okna)
Tabela wariantów
Wariant
1
2
3
4
5
6
7
8
9
10
11
12
Rozmiar
grafu
5
6
7
8
5
6
7
8
9
10
11
12
Graf skierowany
Graf spójny
Wariant
tak
tak
nie
nie
tak
tak
nie
nie
tak
tak
nie
nie
tak
tak
tak
tak
nie
nie
nie
nie
tak
tak
tak
tak
13
14
15
16
17
18
19
20
21
22
23
24
Rozmiar
grafu
9
10
11
12
5
6
7
8
5
6
7
8
Graf skierowany
Graf spójny
tak
tak
nie
nie
tak
tak
nie
nie
tak
tak
nie
nie
nie
nie
nie
nie
tak
tak
tak
tak
nie
nie
nie
nie
Wskazówki
1) Środowisko
[-----------Projekt dla tego ćwiczenia pod nazwą np. „cw5” można stworzyć poprzez kopiowanie poprzedniego projektu pod nazwą np. „cw4”.
W tym celu należy:
a) skopiować folder poprzedniego projektu ze wszystkimi plikami i folderami;
b) zmienić nazwy folderów z cw4 na cw5;
c) wprowadzić korektę do nazw plików z rozszerzeniami „.sln” i „.vcxproj”; inne pliki z cw4 w nazwie można skasować;
d) wewnątrz plików z rozszerzeniami „.sln” i „.vcxproj” za pomocą Notatnika zamienić cw4 na cw5;
e) w plikach MyForm.h i MyForm.cpp za pomocą Notatnika zamienić cw4 na cw5;
f) foldery Debug można wyczyścić.
----------]
Wejść do środowiska Microsoft Visual Studio 2015.
Wybrać punkt menu „File / New / Project...”,
zaznaczyć rodzaj aplikacji „CLR” w „Templates / Visual C++” i wybrać "CLR Empty Project",
dalej na dole okna w polu "Name" wprowadzić nazwę projektu, na przykład „Pr1”, a
za pomocą przycisku "Browse" w polu "Location" wyznaczyć folder projektu.
Nacisnąć przycisk "OK".
W menu projektu wybrać "PROJECT / Properties".
W oknie "Property Pages" wpisać do opcji "Configuration Properties / Linker / Advanced / Entry
Point" nazwę "main";
W opcji "Property Pages / Configuration Properties / Linker / Debugging / Generate Debug Info"
ustawić "Optimize for debugging (/DEBUG)";
Nacisnąć przyciski "Zastosuj" i "OK".
W podoknie "Solution Explorer" nacisnąć prawym przyciskiem myszy na nazwę projektu, wybrać
punkt menu "Add / New Item...", wybrać "UI / Windows Form" i nacisnąć "Add".
Do pliku "MyForm.cpp" dodać tekst z głównym podprogramem "main":
using namespace System;
using namespace System::Windows::Forms;
[STAThreadAttribute]
int main(cli::array<System::String^>^ args)
{
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
Application::Run(gcnew Pr::MyForm());
}
gdzie "Pr" to nazwa projektu.
2) Opracowanie graficznej części aplikacji
Nadać formularzowi nagłówek
„Algorytm przeszukiwania grafu wszerz. Autor ... Jan Masztalski”.
-
Dodać do formularza obiekty graficzne:
do wyświetlenia zbioru wierzchołków,
do wprowadzenia konfigurację grafu,
do wyświetlenia grafu i do wyświetlenia zmian stanu grafu.
do wyświetlenia zawartości kolejki i elementu na początku i końcu kolejki,
klawisz trybu krokowego i inne sterujące.
Graf należy rysować na obiekcie typu Panel.
Wprowadzenie indeksu wierzchołka początkowego grafu lepiej realizować za pomocą dwóch
obiektów: okienka typu NumericUpDown i klawiszu typu Button z nazwą buttonPoczatek i napisem
„Akceptuj”. Lepiej obydwa obiekty włożyć do obiektu typu GroupBox z napisem "Początek".
Krok w algorytmie lepiej inicjalizować naciśnięciem na klawisz typu „Button” z nazwą
„buttonKrok” i napisem „Krok”.
Zbiór wierzchołków można wyświetlić w obiekcie typu DataGrid o wysokości równej dwom
wierszom i z liczbą kolumn równej liczbę wierzchołków. Niech obiekt nazywa się dataGridZbior.
Obiekty typu DataGrid wyświetlają zawartość tabel, przechowywanych w obiekcie typu DataSet.
Wprowadzimy taki obiekt do formularza. Gdy środowisko zapyta o typie obiektu, wybierzemy dla niego
typ "Untyped dataset". Niech obiekt typu DataSet nazywa się dataSetZbior. Wybierając właściwość
"Tables" wprowadzimy tabelę z nazwą dataTableZbior.
Obiekt dataGridZbior typu DataGrid należy połączyć z tabelą dataTableZbior. W tym celu
należy właściwość DataSource ustawić na dataSetZbior, a właściwość DataMember - na tabelą.
Konfigurację grafu będziemy wprowadzać przez macierz sąsiedztwa wierzchołków.
Do reprezentacji macierzy sąsiedztwa wierzchołków można skorzystać z obiektu typu DataGrid.
Niech nazwą obiektu będzie dataGridGraf.
Obiekty typu DataGrid wyświetlają zawartość tabel, przechowywanych w obiekcie typu DataSet.
Wprowadzimy taki obiekt do formularza. Gdy środowisko zapyta o typie obiektu, wybierzemy dla niego
typ "Untyped dataset". Niech obiekt typu DataSet nazywa się dataSetGraf. Wybierając właściwość
"Tables" wprowadzimy tabelę z nazwą dataTableGraf .
Obiekt dataGridGraf typu DataGrid należy połączyć z tabelą dataTableGraf. W tym celu
należy właściwość DataSource ustawić na dataSetGraf, a właściwość DataMember - na tabelą.
Do wyświetlenia zawartości i indeksów kolejki jest wygodnie korzystać z obiektu typu ListBox, na
przykład z nazwą "listBoxKolej" . Fragmenty programu dotyczące kolejki można pobrać z materiałów
poprzedniego ćwiczenia.
3) Programowanie operacji
Niech dane przypisane wierzchołkom grafu będą literami alfabetu od litery A.
Rozmieścimy dane w obiektach strukturalnego typu „s_W”.
Dodajmy w przestrzeni nazw (ang. namespace) projektu strukturę s_W, która zawiera pole danych
"dana" oraz pole znacznika odwiedzenia wierzchołka "znacz":
struct s_W
{
wchar_t dana; //pole danych
int znacz; //znacznik odwiedzenia wierzchołka
s_W()
{//konstruktor
dana=L' ';
znacz=0;
}
s_W(wchar_t da,int zn)
{//konstruktor z argumentami
dana=da;
znacz=zn;
}
};
W przestrzeni nazw (ang. namespace) projektu zdefiniujemy zmienne, które będą potrzebne w
programie:
#define RG 20 //maksymalny rozmiar grafu
s_W* m_W[RG]; //tablica z danymi wierzchółków
int m_rozmiar=1;//faktyczny rozmiar grafu
int m_poczW=0;//wierzchołek początkowy
int m_numer=0;//numer kroku przszukiwania grafu
#define RK 20 //maksymalny rozmiar kolejki
int m_kolej[RK];
int m_jp=0; // indeks początku kolejki,
int m_jk=0; // indeks końca kolejki.
W procedurze System::Void Form1_Load() wywoływanej przy tworzeniu formularza umieścimy
instrukcje inicjalizacji:
//inicjalizacja grafu
fInicGraf();
//inicjalizacja kolejki
for (int k=0;k<RK;k++) m_kolej[k]=0;
m_jp=0;
m_jk=0;
//---
listBoxKolej->Items->Clear();
for (int i=0;i<RK;i++) listBoxKolej->Items->Add(m_kolej[i]);
listBoxKolej->SetSelected(m_jp,true);//zaznaczony jest wiersz jp
listBoxKolej->SetSelected(m_jk,true);//zaznaczony jest wiersz jk
////////////
m_poczW=System::Decimal::ToInt32(numericUpDownPocz->Value);
m_numer=0;
W procedurze System::Void Form1_Paint() wywoływanej przy rysowaniu formularza umieścimy
instrukcje rysowania:
////////////
fRysowanieGrafu();
Procedura fInicGraf() do inicjalizacji graficznych obiektów:
private: void fInicGraf()
{
if (dataTableZbior->Rows->Count>0) dataTableZbior->Rows->Clear();
if (dataTableZbior->Columns->Count>0) dataTableZbior->Columns->Clear();
//--String^ ss0=gcnew String(L'0',1);
String^ sss=gcnew String(L' ',1);
//--wchar_t baseCod=L'A';
for (int i=0;i<m_rozmiar;i++)
{
String^ ssi=gcnew String((baseCod+i),1);
dataTableZbior->Columns->Add(ssi,Type::GetType("System.String"));
}
//--DataRow^ workRow=dataTableZbior->NewRow();
for (int j=0;j<m_rozmiar;j++)
workRow[j]=j.ToString();
dataTableZbior->Rows->Add(workRow);
//--dataGridZbior->Visible=true;
//////////////////////////'
if (dataTableGraf->Rows->Count>0) dataTableGraf->Rows->Clear();
if (dataTableGraf->Columns->Count>0) dataTableGraf->Columns->Clear();
//--dataTableGraf->Columns->Add(sss,Type::GetType("System.String"));
for (int i2=0;i2<m_rozmiar;i2++)
{
String^ ss2=gcnew String((baseCod+i2),1);
dataTableGraf->Columns->Add(ss2,Type::GetType("System.String"));
}
for (int j1=0;j1<m_rozmiar;j1++)
{
DataRow^ workRow2=dataTableGraf->NewRow();
String^ ss1=gcnew String((baseCod+j1),1);
workRow2[0]=ss1;
for (int j2=0;j2<m_rozmiar;j2++) workRow2[1+j2]=(Object^)ss0;
dataTableGraf->Rows->Add(workRow2);
}//j1
dataGridGraf->Visible=true;
//alokacja tablicy z danymi i inicjalizacja
if (m_rozmiar>0)
{
for (int k=0;k<RG;k++) if (m_W[k]) delete m_W[k];
for (int i=0;i<m_rozmiar;i++) m_W[i]=new s_W(baseCod+i,0);
}
}
Do wyświetlenia grafu można zastosować odrębną procedurę „fRysowanieGrafu”.
Procedura może korzystać z danych zawartych w macierzy sąsiedztwa wierzchołków
dataTableGraf.
Możliwy tekst procedury:
private: void fRysowanieGrafu()
{
//Oczyszczenie pola do rysowania
System::Drawing::Graphics^ gr=panel1->CreateGraphics();
gr->Clear(System::Drawing::Color::GreenYellow);
if (m_rozmiar <= 0) return;
//obliczenia
const float rr=20.0;
const float rr2=10.0;
const float rr3=25.0;
const float hh=4.0;
const float h2h=8.0;
float* pwX=new float[m_rozmiar]; //X-współrzędne wierzchołków
float* pwY=new float[m_rozmiar]; //Y-współrzędne wierzchołków
float centrumX=(float)(panel1->Width / 2.0);
float centrumY=(float)(panel1->Height / 2.0);
float height2=(float)(panel1->Height * 0.4);
float width2=(float)(panel1->Width * 0.4);
int ii,kk;
for (ii=0;ii<m_rozmiar;ii++)
{
float kat=(float)(((2 * 3.1415) * ii) / m_rozmiar);
float dX=width2 * (float)Math::Sin(kat);
float dY=height2 * (float)Math::Cos(kat);
pwX[ii]=centrumX+dX;
pwY[ii]=centrumY+dY;
}//ii
////////////////
System::Drawing::SolidBrush^ myBrush=gcnew
System::Drawing::SolidBrush(System::Drawing::Color::White);
System::Drawing::Pen^ myPen=gcnew
System::Drawing::Pen(System::Drawing::Color::Black);
System::Drawing::Font^ myFont=gcnew
System::Drawing::Font("Arial",10);
//---rysowanie wierzchołków------------------------for (ii=0;ii<m_rozmiar;ii++)
{
gr->DrawEllipse(myPen,pwX[ii]-rr,pwY[ii]-rr,rr+rr,rr+rr);
if (m_W[ii]->znacz>0)
myBrush->Color=System::Drawing::Color::Silver;
else
myBrush->Color=System::Drawing::Color::Crimson;
gr->FillEllipse(myBrush,pwX[ii]-rr,pwY[ii]-rr,rr+rr,rr+rr);
String^ buf=gcnew String(m_W[ii]->dana,1);
if (m_W[ii]->znacz>0)
{
buf=buf+",";
buf=buf+m_W[ii]->znacz.ToString();
}
if (m_W[ii]->znacz>0)
myBrush->Color=System::Drawing::Color::Black;
else
myBrush->Color=System::Drawing::Color::White;
gr->DrawString(buf,myFont,myBrush,pwX[ii]-rr2,pwY[ii]-rr2);
}//ii
//---rysowanie krawiędzi----------------------------myBrush->Color=System::Drawing::Color::Black;
DataTable^ dtGraf=dataSetGraf->Tables[0];
for (ii=0;ii<m_rozmiar;ii++)
{
DataRow^ dr=dtGraf->Rows[ii];
for (kk=0;kk<m_rozmiar;kk++) //col
{
String^ ss=(String^)dr[1+kk];
if (ss==String::Empty ) break; //kk
int value=System::Int32::Parse(ss);
if (value != 0)
{
if (ii == kk)
{
//rysowanie krawędzi-pętli
float deltaX1=pwX[kk]-centrumX;
float deltaY1=pwY[kk]-centrumY;
float LL1=(float)(Math::Sqrt((deltaX1*deltaX1)+(deltaY1*deltaY1)));
float ddX1=(deltaX1*rr3)/LL1;
float ddY1=(deltaY1*rr3)/LL1;
float x1=pwX[kk]+ddX1-hh;
float y1=pwY[kk]+ddY1-hh;
gr->DrawEllipse(myPen,x1,y1,h2h,h2h);
jeśli graf skierowany,to: gr->FillEllipse(myBrush,x1,y1,h2h,h2h);
}
else
{//ii!=kk
float deltaX2= pwX[kk]-pwX[ii];
float deltaY2= pwY[kk]-pwY[ii];
float LL2=(float)(Math::Sqrt((deltaX2*deltaX2)+(deltaY2*deltaY2)));
float ddX2=(deltaX2*rr3)/LL2;
float ddY2=(deltaY2*rr3)/LL2;
int iix=(int)(pwX[ii]+ddX2);
int kkx=(int)(pwX[kk]-ddX2);
int iiy=(int)(pwY[ii]+ddY2);
int kky=(int)(pwY[kk]-ddY2);
Point point1(iix,iiy);
Point point2(kkx,kky);
gr->DrawLine(myPen,point1,point2);
jeśli graf skierowany,to: gr->FillEllipse(myBrush,pwX[kk]-ddX2-hh,pwY[kk]ddY2-hh,h2h,h2h);
}
}
}//kk
}//ii
}
Można dodać do programu funkcję serwisową - zmiana wartości w komórce przez klikniecie
myszą:
private: System::Void dataGridGraf_MouseUp(System::Object^ sender,
System::Windows::Forms::MouseEventArgs^ e)
{
// Create a HitTestInfo object using the HitTest method.
// Get the DataGrid by casting sender.
DataGrid^ myGrid = dynamic_cast<DataGrid^>(sender);
DataGrid::HitTestInfo ^ myHitInfo = myGrid->HitTest( e->X, e->Y );
int row=myHitInfo->Row;
int col=myHitInfo->Column;
//--String^ ss0=gcnew String(L'0',1);
String^ ss1=gcnew String(L'1',1);
//--if (row>=0 && col>=1)
{
DataRow^ workRow=dataTableGraf->Rows[row];
String^ ss=(String^)workRow[col];
if (ss==ss0) workRow[col]=ss1;
else workRow[col]=ss0;
jeśli graf nieskierowany,to ten blok:
{
int col2=1+row;
int row2=col-1;
DataRow^ workRow2=dataTableGraf->Rows[row2];
workRow2[col2]=workRow[col];
}
dataTableGraf->AcceptChanges();
////////////
fRysowanieGrafu();
}
}
Klawisz „Początek” musi ustawić początkowy wierzchołek. Możliwy tekst procedurę:
private: System::Void buttonPoczatek_Click(System::Object^ sender,
System::EventArgs^ e)
{
m_poczW=System::Decimal::ToInt32(numericUpDownPocz->Value);
}
Po naciśnięciu klawiszu „Krok” trybu krokowego musi być pokazany nowy odwiedzony wierzchołek.
Możliwy tekst procedury:
private: System::Void ButtonKrok_Click(System::Object^
System::EventArgs^ e)
{
if (m_jp==m_jk)
{
m_poczW=(int)numericUpDownPocz->Value;
if (m_W[m_poczW]->znacz==0)
{
//dodawanie do kolejki
m_kolej[m_jk++]=m_poczW;
m_numer=1;
m_W[m_poczW]->znacz=m_numer;
}
else
sender,
{
int ind1=m_poczW+1;
for (;ind1<m_rozmiar;ind1++)
{
if (m_W[ind1]->znacz==0)
{
//dodawanie do kolejki
m_kolej[m_jk++]=ind1;
m_numer++;
m_W[ind1]->znacz=m_numer;
break;
}
}
if (ind1==m_rozmiar)
{
int ind2=0;
for (;ind2<m_poczW;ind2++)
{
if (m_W[ind2]->znacz==0)
{
//dodawanie do kolejki
m_kolej[m_jk++]=ind2;
m_numer++;
m_W[ind2]->znacz=m_numer;
break;
}
}
if (ind2==m_poczW) return;
}
}
}
else
{
//pobranie z początku kolejki
int ind=m_kolej[m_jp++];//index wierzchółka
if (ind>=0 && ind<m_rozmiar)
{
DataRow^ workRow=dataTableGraf->Rows[ind];//row=ind
for (int col=0;col<m_rozmiar;col++)
{
String^ ss=(String^)workRow[1+col];
if (ss==String::Empty) continue;
int value=System::Int32::Parse(ss);
if (value!=0)
{
if (m_W[col]->znacz==0)
{//wierzcholek jest nieodwiedzony
//dodawanie do kolejki
if (m_jk<RK)
{
m_kolej[m_jk++]=col;
}
m_numer++;
m_W[col]->znacz=m_numer;
}
}
}//col
}
}
//---
listBoxKolej->Items->Clear();
for (int i=0;i<RK;i++) listBoxKolej->Items->Add(m_kolej[i]);
listBoxKolej->SetSelected(m_jp,true);//zaznaczony jest wiersz jp
listBoxKolej->SetSelected(m_jk,true);//zaznaczony jest wiersz jk
//--fRysowanieGrafu();
}