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(); }