Strategie optymalizacji Algorytm siłowy, algorytm brute force (z ang. "brutalna siła") opiera się na kolejnym sprawdzeniu wszystkich możliwych kombinacji rozwiązania problemu. Jest to zwykle nieoptymalna czasowo, ale najprostsza pod względem implementacji i najbardziej skuteczna metoda postępowania. Algorytm zachłanny (ang. greedy algorithm) w każdym kolejnym kroku dokonuje wyboru zachłannego, tj. optymalnego w danym momencie. Nie dla każdego problemu ten algorytm odnajduje optymalny wynik. Programowanie dynamiczne opiera się na podziale rozwiązywanego problemu na podproblemy (niekoniecznie rozłączne jak w metodzie dziel-i-rządź). Wyznacza się funkcję celu dla całego problemu na podstawie optymalnych wartości funkcji celu dla podproblemów. Rozwiązania kolejnych podproblemów prowadzi się poczynając od najmniejszego podproblemu i zapisując optymalne wartości w tablicy. Ostateczne rozwiązanie jest zawsze optymalne. Zadanie dla Ciebie: Niedawno (rok 2009) producent zupek błyskawicznych, firma Amino, ogłosiła loterię. Na opakowaniach zupek umieszczano liczby naturalne. Aby coś wygrać należało zebrać takie opakowania, z których liczby po zsumowaniu dawały wartości z zakresu od 1000 do 10000 podzielne przez 1000, a więc 1000, 2000, 3000,, 10000. Wykorzystując dane z pliku: amino.txt (przykładowy zestaw wartości liczbowych zebranych z opakowań zupek Amino) sprawdź czy udałoby się coś wygrać Tu zajmę się problemem pakowanie plecaka ( ang. knapsack problem) i rozwiążę go na trzy sposoby, wykorzystując powyższe trzy algorytmy. Mam zbiór elementów, każdy element o określonej wadze i cenie. Należy wybrać z nich taki zestaw, który będzie mieć najwyższą wartość (sumę cen) i nie przekroczy dopuszczalnej wagi plecaka. Wczytuję dane z pliku plecak.txt: dopuszczalną wagę plecaka, oraz dane elementów (wagę i cenę) do wektora. vector to klasa która odpowiada tablicy 1-wymiarowej o zmiennym rozmiarze. Elementy dołącza się na koniec vector a metodą push_back(). Odwołania do elementów - za pomocą indeksów, jak w zwykłej tablicy. Potrzebne jest #include <vector> Rozwiązanie brute-force skuteczne ale czasochłonne Dla n elementów mam m=2 n możliwych zestawów. Użyję reprezentacji binarnej kolejnych liczb i z zakresu od 0 do m-1, na przykład dla i=20 binarnie = 10100, oznacza: weź pierwszy i trzeci element. Rozważę wszystkie możliwe kombinacje elementów. Wybiorę z nich te kombinacje, których waga nie przekracza dopuszczalnej wagi i poszukam wśród nich maksymalnej wartości sumy cen. struct element { int waga, cena; ; int main() { element e; int W; vector <element> E; // pojedynczy element // dopuszczalna waga plecaka // wektor elementów, wymaga #include <vector>
ifstream we("plecak.txt"); // czytanie danych int x; we>>w; cout<<"dopuszczalna waga="<<w<<endl; while (we>>x){ // dopóki jest jakaś dana na wejsciu... e.waga=x; we>>e.cena; cout<<" waga="<<e.waga<<" cena="<<e.cena<<endl; E.push_back(e); // dodanie nowego elementu //na koniec wektora E we.close(); int n=e.size(); // n = liczba elementów wektora E // n elementów daje m=2 n możliwości zestawów elementów int m=1; for (int i=1; i<=n; i++) m=m*2; Reprezentacja binarna liczby dziesiętnej d wyrażona przy pomocy n cyfr binarnych string bin(int d, int n) { string s=""; while (d>0) { if (d%2==0) s="0"+s; else s="1"+s; d=d/2; while (s.length()<n) s="0"+s; return s; cout<<n<<" elementow daje "<<m<<" kombinacji upakowania\n"; cout<<"wybiorę z nich tylko te kombinacje ktore maja wage <= "<<W<<"\n\n"; int cmaxi; // max cena int wmaxi=0; // waga przy max cenie int mmaxi=0; // kombinacja elementów m która zapewnia max cenę for (int i=0; i<m; i++) { // przegląd wszystkich kombinacji elementów string b=bin(i,n); int w=0, c=0; // waga i cena bieżącej kombinacji for (int j=0; j<n; j++) { if (b[j]=='1'){ // jeżeli w reprezentacji binarnej na j-tym miejscy jest 1 to weź j-ty element w=w+e[j].waga; c=c+e[j].cena; if (w<=w) // jeżeli waga tej kombinacji nie przekracza dopuszczalnej... { cout<<bin(i,n)<<" waga="<<w<<" wartosc="<<c<<endl; if (c>cmaxi) // jeśli cena jest większa od bieżącego maksimum {cmaxi=c; wmaxi=w; mmaxi=i; cout<<"\nnajkorzystniej:\n"; cout<<bin(mmaxi,n)<<" waga="<<wmaxi<<" wartosc="<<cmaxi<<endl; system("pause"); return EXIT_SUCCESS; Rozwiązanie brute-force ma sens tylko w przypadku niewielkiej liczby elementów, inaczej czas działania algorytmu wydłuża się tak bardzo, że nie ma szans doczekać końca obliczeń.
Rozwiązanie zachłanne nie zawsze daje optymalny wynik Najpierw sortuję elementy według ceny w porządku malejącym. Jeszcze lepszym pomysłem byłoby posortowanie według ciężaru jednostkowego (stosunku masy do wagi) każdego elementu. Następnie biorę elementy po kolei elementy począwszy od najcenniejszego, sprawdzam czy zmieści się wagowo w plecaku i jeśli tak to go wrzucam. Powtarzam do wyczerpania elementów lub plecaka. Tu akurat wyniki są identyczne z rozwiązaniem siłowym, ale często w przypadku problemu dyskretnego (gdy elementów nie można dzielić na mniejsze części) rozwiązanie zachłanne nie daje optymalnego wyniku. struct element { int waga, cena; ; int main() { element e; vector <element> E; int W; // pojedynczy element // wektor elementów, wymaga #include <vector> // dopuszczalna waga plecaka ifstream we("plecak.txt"); // czytanie danych we>>w; cout<<"dopuszczalna waga="<<w<<endl; cout<<"\nelementy:\n"; int x; while (we>>x) { e.waga=x; we>>e.cena; cout<<"waga="<<e.waga<<" cena="<<e.cena<<endl; E.push_back(e); // dodanie elementu do wektora we.close(); int n=e.size(); // liczba elementów wektora E Sortowanie wektora D, algorytm InsertionSort : Dopóki j>0 oraz cena poprzedniego [j-1] jest mniejsza od bieżącego[j] przestawiaj elementy: [j] oraz [j-1] Wektor przykazywany jest do funkcji przez referencję void sort( vector <element> &D, int n) { for (int i=1; i<n; i++) { int j=i; while (j>0 && (D[j-1].cena<D[j].cena)) { swap(d[j-1],d[j]); j--; sort(e,n); // sortuj wektor E, n-elementowy, malejąco według ceny cout<<"\npo sortowaniu wedlug ceny: \n"; for (int i=0; i<n; i++) cout<<" waga="<<e[i].waga<<" cena="<<e[i].cena<<endl; int w=0; // bieżąca waga plecaka int c=0; // bieżąca wartość plecaka cout<<"pusty plecak\n"; int i=0; // indeks elementu przymierzanego do plecaka while ( w<w && i<n ){ if (w+e[i].waga <=W) { w=w+e[i].waga; c=c+e[i].cena; cout<<"dorzucam "<<E[i].waga<<" kg warte "<<E[i].cena<<" zl, "; cout<<"razem waga="<<w<<", cena="<<c<<endl; i++; cout<<"koniec pakowania\n\n"; system("pause"); return EXIT_SUCCESS;
Rozwiązanie metodą programowania dynamicznego daje zawsze optymalny wynik {w 1,, w n - wagi elementów {c 1,, c n ceny (wartości) elementów W - maksymana pojemność plecaka Rozpatruję wszystkie moŝliwe wartosci wagi plecaka od 0 do W. Kolejno próbuję umieszczać elementy, poczynając od pierwszego elementu i najmniejszej moŝliwej pojemności plecaka. Patrzę co się zmieści i co będzie więcej warte. A(i,j) - wartość optymalnie wypełnionego plecaka o wadze j przedmiotami, których indeksy mieszczą się między 1 a i. A(0,j) = 0 A(i,j) = max { cj + S(i - wj ) : wj <= i A(n,W) będzie rozwiązaniem problemu największą moŝliwą wartością plecaka. for(j = 0; j <= W; j++) W[j] = 0; for(i = 1; i< n; i++) { for(j = 0; j <= W; j++) { // zerowanie górnego wiersza tabeli // (wartość plecaka o pojemności j gdy nie zawiera Ŝadnego elementu) // próbujemy dorzucić i-ty element do plecaka o pojemnosci j // dla wszystkich moŝliwych pojemności plecaka if( w[i] <= j ) // jezeli waga i-tego elementu nie przekracza j { // wybierz wiekszą wartość z dwóch moŝliwości: // dotychczasowej wartości plecaka bez i-tego elementu (z poprz. wiersza), lub // (wartość plecaka z poprz. wiersza lŝejszego o w i ) + wartość c i i-tego elem. A[i,j] = max( A[i-1,j], A[i-1,j - w[i]] + c[i]); 10 zł z poprzedniego wiersza tej samej kolumny czy wartość z poprzedniego wiersza 2 kolumny w lewo(o 2 kg mniej ) 10 zł + cena elementu z tego wiersza 1 zł = 11 zł? oczywiście 11 zł wszystkie możliwe wartości wagi plecaka od 0 do 15 kg i\j 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 waga cena 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 chleb 9 10 1 0 0 tu się 0 9-kilowy 0 0 chleb 0 nie 0 zmieści 0 0 10 10 10 10 10 10 10 cola 12 7 2 0 0 tu się 0 nie zmieści 0 0 ani 0 chleb 0 ani 0 cola 0 10 10 10 10 10 10 10 sól 2 1 3 0 0 1 tu 1 się zmieści 1 1 sól 1 1 1 10 10 11 11 11 11 11 mąka 7 3 4 0 0 1 1 1 1 1 3 3 10 10 11 11 11 11 11 ryż 5 2 5 0 0 1 1 1 2 2 3 3 10 10 11 11 11 12 12 11 zł z poprzedniego wiersza tej samej kolumny czy wartość z poprzedniego wiersza 5 kolumn w lewo(o 5 kg mniej ) 10 zł + cena elementu z tego wiersza 2 zł = 12 zł? oczywiście 12 zł wynik Tabela A ij przechowuje jedynie łączną wartość optymalnie spakowanego plecaka. Aby móc pokazać zestaw rzeczy składajacych sie na optymalnie spakowany plecak, najwygodniej jest zbudować drugą tabelę: Bij, która przechowywać będzie informację o elementach pakowanych do plecaka: Bij=1 jeśli element i został zapakowany do plecaka o pojemności j, lub Bij=0 jeśli nie został zapakowany.
struct element { int waga,cena; ; int main( ) { vector <element> E; element e = {0,0; // dodaję na początku wektora element zerowy pusty E.push_back(e); // aby ułatwić zapis indeksowania elementów: od 1 do n int W; // dopuszczalna waga plecaka int n=0; // liczba elementów ifstream we("plecak.txt"); // czytanie danych we>>w; cout<<"dopuszczalna waga="<<w<<endl; while (we>>e.waga) { we>>e.cena; E.push_back(e); n++; cout<<"element nr "<<n<<" waga="<<e.waga<<" cena="<<e.cena<<endl; we.close(); // przygotowanie do pakowania... vector < vector<int> > A, B; // wektory dwuwymiarowe [i][j] o rozmiarach i=<0,n>, j=<0,w> // A[i][j] - optymalne wartości plecaka o wadze j z wyborem elementow od 1 do i // B[i][j] - informacja czy i-element jest "wzięty" do j-plecaka: 1 czy odrzucony: 0 for (int i=0; i<=n; i++) { // wypełniam wstępnie wektory A i B elementami o wartościach 0 A.push_back(vector<int>()); B.push_back(vector<int>()); for (int j=0; j<=w; j++) { A[i].push_back(0); B[i].push_back(0); cout<<"\npakujemy...\n"; // pakowanie for (int i=1; i<=n; i++) { for (int j=1;j<=w; j++) { if (E[i].waga>j) A[i][j]=A[i-1][j]; // jezeli waga elementu przekracza pojemność plecaka // to pozostaw plecak nie zmieniony (przepisz wartość z poprzedniego wiersza) else { int jp=j-e[i].waga; // waga j pomniejszona o wagę i-tego elementu int ap=a[i-1][jp]+e[i].cena; // wartość plecaka o tej_pomniejszonej wadze + cena i_tego elementu if (ap>a[i-1][j]) { // jeŝeli warto dołoŝyc ten element... A[i][j]=ap; // to umieść w tablicy Aij nową większą wartość plecaka B[i][j]=1; // i odnotuj to w tabeli "wziętych" elementów else { A[i][j]=A[i-1][j]; // pozostaw plecak niezmieniony (przepisz wartość z poprzedniego wiersza) B[i][j]=0; // i nie notuj elementu w tabeli "wziętych" elementów for (int j=0;j<=w; j++) { cout.width(3); cout<<a[i][j]<<" "; cout<<"\nnajwieksza wartosc plecaka = "<<A[n][W]<<endl<<endl; // tu jest rozwiązanie for (int i=1; i<=n; i++) { for (int j=0;j<=w; j++) cout<<b[i][j]<<" "; // wyświetlam jeszcze zawartość wektora B
cout<<"\nnalezy zapakowac elementy o numerach: "; int k=w; // plecak ma wagę W // lista zapakowanych elementów for (int i=n; i>0; i--) { // zaczynam od n-tego wiersza i W-tej kolumny wektora B ij if (B[i][k]==1) { // jeśli jest 1 cout<<i<<" "; // to znaczy Ŝe i-ty element jest zapakowany k=k-e[i].waga; // zmniejszam wagę plecaka o wagę tego elementu if (k<1) {break; // nastepnego elementu szukam w wierszu i-1 i kolumnie k system("pause"); return EXIT_SUCCESS; Inne problemy, które można próbować rozwiązać powyższymi metodami: Problem kasjera/bankomatu wypłacenie określonej kwoty pieniędzy: Dysponujesz nominałami: 1zł, 2 zł, 5 zł, 10 zł, 20 zł, 50 zł, każdy nominał w nieograniczonej ilości. Napisz program który pobiera z klawiatury wartość żądanej kwoty, a następnie wypłaca ją przy pomocy minimalnej liczby monet/banknotów. Załadunek (z arkusza maturalnego) Na rampie magazynu znajdują się paczki o różnych masach. Ładowanie paczek na naczepę tir-a musi być prowadzone w sposób następujący: z rampy do samochodu wkładane są paczki od najcięższej do najlżejszej; załadunek naczepy zostaje przerwany, gdy dołożenie kolejnej paczki spowodowałoby, że średnia arytmetyczna masy załadowanych na naczepę paczek będzie mniejsza od granicznej wartości D lub została już osiągnięta maksymalna liczba paczek M, które zmieszczą się na naczepie, lub załadowano wszystkie paczki. Napisz program który pobiera dane: D: wartość graniczna dodatnia liczba całkowita, M: maksymalna liczba paczek, które zmieszczą się na naczepie dodatnia liczba całkowita, N: liczba paczek wystawionych na rampę dodatnia liczba całkowita, We[1...N]: tablica zawierająca masy paczek dodatnie, różne liczby całkowite. zaś oblicza i zwraca liczbę paczek załadowanych na naczepę.