#include "Graph.h" #include "Stopwatch.h" #include #include #include #include #include #include Graph::Graph() { //ctor } Graph::~Graph() { //dtor } unsigned Graph::getVertexNumber() { return vertexNumber; } void Graph::randomGenerateFullGraph(Graph &graph, unsigned maxWeight) { std::random_device randomSrc; std::default_random_engine randomGen(randomSrc()); std::uniform_int_distribution<> weightDist(1, maxWeight); for(int i = 0; i < graph.vertexNumber; i++) { for(int j = 0; j < graph.vertexNumber; j++) { if(i != j) { // Bez warunku na krawedzie juz wygenerowane... // ...z tym radzi sobie juz metoda addEdge int randomWeight = weightDist(randomGen); graph.addEdge(i, j, randomWeight); } } } } std::vector Graph::travellingSalesmanBruteForce(Graph &graph) { // ALGORYTM przegladu zupelnego // Implementacja: Jan Potocki 2017 // (refactoring 2019) std::vector vertexArray; // Generowanie "spisu" wierzcholkow // (od razu w odpowiedniej kolejnosci dla next_permutation) for(int i = 1; i < graph.vertexNumber; i++) vertexArray.push_back(i); std::vector minCombination; int minRoute = -1; // Petla przegladajaca kolejne permutacje do { std::vector combination; // Dodanie wierzcholka startowego i pierwszego na trasie combination.push_back(0); combination.push_back(vertexArray.front()); // W petli reszta wiercholkow for(int i = 1; i < vertexArray.size(); i++) combination.push_back(vertexArray.at(i)); // Powrot do wierzcholka startowego combination.push_back(0); // PEA 2 // Jan Potocki 2017 int route = 0; for(int i = 1; i < combination.size(); i++) route += graph.getWeight(combination.at(i - 1), combination.at(i)); if(minRoute == -1 || route < minRoute) { minRoute = route; minCombination = combination; } } while(next_permutation(vertexArray.begin(), vertexArray.end())); return minCombination; } std::vector Graph::travellingSalesmanBranchAndBound(Graph &graph) { // ALGORYTM pracujacy w oparciu o kolejke priorytetowa i niejawnie utworzone drzewo // Zrodlo: www.ii.uni.wroc.pl/~prz/2011lato/ah/opracowania/met_podz_ogr.opr.pdf // Autor: Mateusz Lyczek 2011 // Implementacja: Jan Potocki 2017 std::priority_queue, std::vector< std::vector >, RouteComparison> routeQueue; std::vector optimalRoute; // Tu bedziemy zapisywac optymalne (w danej chwili) rozwiazanie int optimalRouteLength = -1; // -1 - bedziemy odtad uznawac, ze to jest nieskonczonosc ;-) // UMOWA // Pierwszy element wektora to dlugosc trasy (trzeba ustawic "z palca"!) // Kolejne to wierzcholki na trasie std::vector currentRoute; // Niejawne tworzenie drzewa, tu bedzie korzen currentRoute.push_back(0); // Poczatkowe oszacowanie nie ma znaczenia currentRoute.push_back(0); // Wierzcholek startowy (korzen drzewa rozwiazan) routeQueue.push(currentRoute); // Dodanie do kolejki korzenia while(!routeQueue.empty()) { // Przypisanie korzenia do dalszej roboty currentRoute = routeQueue.top(); routeQueue.pop(); // Sprawdzenie, czy rozwiazanie jest warte rozwijania, czy odrzucic if(optimalRouteLength == -1 || currentRoute.at(0) < optimalRouteLength) { for(int i = 0; i < graph.vertexNumber; i++) { // Petla wykonywana dla kazdego potomka rozpatrywanego wlasnie rozwiazania w drzewie // Ustalenie, czy dany wierzcholek mozna jeszcze wykorzystac, czy juz zostal uzyty bool vertexUsed = false; for(int j = 1; j < currentRoute.size(); j++) { if(currentRoute.at(j) == i) { vertexUsed = true; break; } } if(vertexUsed) continue; // Niejawne utworzenie nowego wezla reprezuntujacego rozpatrywane rozwiazanie... std::vector nextRoute = currentRoute; //unsigned nextLength = graph.getWeight(nextRoute.back(), i); nextRoute.push_back(i); // Dalej bedziemy postepowac roznie... if(nextRoute.size() > graph.vertexNumber) { // Doszlismy wlasnie do liscia // Dodajemy droge powrotna, nie musimy nic szacowac // (wszystko juz wiemy) nextRoute.push_back(0); nextRoute.at(0) = 0; for(int j = 1; j < nextRoute.size() - 1; j++) { // Liczymy dystans od poczatku do konca nextRoute.at(0) += graph.getWeight(nextRoute.at(j), nextRoute.at(j+ 1)); } if(optimalRouteLength == -1 || nextRoute.at(0) < optimalRouteLength) { optimalRouteLength = nextRoute.at(0); nextRoute.erase(nextRoute.begin()); optimalRoute = nextRoute; } } else { // Liczenie tego, co juz wiemy, od nowa... // (dystans od poczatku) nextRoute.at(0) = 0; for(int j = 1; j < nextRoute.size() - 1; j++) { nextRoute.at(0) += graph.getWeight(nextRoute.at(j), nextRoute.at(j + 1)); } // Reszte szacujemy... // Pomijamy od razu wierzcholek startowy for(int j = 1; j < graph.vertexNumber; j++) { // Odrzucenie wierzcholkow juz umieszczonych na trasie bool vertexUsed = false; for(int k = 1; k < currentRoute.size(); k++) { if(j == currentRoute.at(k)) { vertexUsed = true; break; } } if(vertexUsed) continue; int minEdge = -1; for(int k = 0; k < graph.vertexNumber; k++) { // Odrzucenie krawedzi do wierzcholka 0 przy ostatnim wierzcholku w czesciowym rozwiazaniu // Wyjatkiem jest ostatnia mozliwa krawedz if(j == i && k == 0) continue; // Odrzucenie krawedzi do wierzcholka umieszczonego juz na rozwazanej trasie bool vertexUsed = false; for(int l = 2; l < nextRoute.size(); l++) { if(k == nextRoute.at(l)) { vertexUsed = true; break; } } if(vertexUsed) continue; // Odrzucenie samego siebie if(k == j) continue; // Znalezienie najkrotszej mozliwej jeszcze do uzycia krawedzi unsigned consideredLength = graph.getWeight(j, k); if(minEdge == -1) minEdge = consideredLength; else if(minEdge > consideredLength) minEdge = consideredLength; } nextRoute.at(0) += minEdge; } // ...i teraz zastanawiamy sie co dalej if(optimalRouteLength == -1 || nextRoute.at(0) < optimalRouteLength) { routeQueue.push(nextRoute); } } } } else { // Jezeli jedno rozwiazanie odrzucilismy, to wszystkie inne tez mozemy // (kolejka priorytetowa, inne nie moga byc lepsze) break; } } return optimalRoute; } std::vector Graph::travellingSalesmanGreedy(Graph &graph, unsigned startVertex) { // ALGORYTM zachlanny z wierzcholkiem startowym przekazanym w parametrze // Implementacja: Jan Potocki 2017 std::vector route; // std::random_device randomSrc; // std::default_random_engine randomGen(randomSrc()); // std::uniform_int_distribution<> vertexDist(0, graph.vertexNumber - 1); // Losowanie wierzcholka startowego //route.push_back(vertexDist(randomGen)); route.push_back(startVertex); for(int i = 0; i < graph.vertexNumber - 1; i++) { int minEdge = -1; unsigned nextVertex; for(int j = 0; j < graph.vertexNumber; j++) { // Odrzucenie samego siebie lub wierzcholka startowego // (zeby bylo szybciej) if(route.back() == j || route.front() == j) continue; // Odrzucenie krawedzi do wierzcholka umieszczonego juz na trasie bool vertexUsed = false; for(int k = 0; k < route.size(); k++) { if(j == route.at(k)) { vertexUsed = true; break; } } if(vertexUsed) continue; // Znalezienie najkrotszej mozliwej jeszcze do uzycia krawedzi unsigned consideredLength = graph.getWeight(route.back(), j); if(minEdge == -1) { minEdge = consideredLength; nextVertex = j; } else if(minEdge > consideredLength) { minEdge = consideredLength; nextVertex = j; } } route.push_back(nextVertex); } route.push_back(startVertex); return route; } std::vector Graph::travellingSalesmanHybrid(Graph &graph) { // ALGORYTM hybrydowy losowo-zachlanny // Losowa czesc wierzcholkow jest losowana, reszta zachlannie // Implementacja: Jan Potocki 2019 std::vector route; std::random_device randomSrc; std::default_random_engine randomGen(randomSrc()); std::uniform_int_distribution<> vertexNumberDist(1, graph.vertexNumber); std::uniform_int_distribution<> vertexDist(0, graph.vertexNumber - 1); // Liczba losowanych wierzcholkow unsigned randomVertexNumber = vertexNumberDist(randomGen); // Czesc losowa for(int i = 0; i < randomVertexNumber; i++) { unsigned randomVertex; bool vertexUsed; do { randomVertex = vertexDist(randomGen); vertexUsed = false; for(int j = 0; j < route.size(); j++) { if(route.at(j) == randomVertex) { vertexUsed = true; break; } } } while(vertexUsed == true); route.push_back(randomVertex); } // Czesc zachlanna for(int i = 0; i < graph.vertexNumber - randomVertexNumber; i++) { int minEdge = -1; unsigned nextVertex; for(int j = 0; j < graph.vertexNumber; j++) { // Odrzucenie samego siebie lub wierzcholka startowego // (zeby bylo szybciej) if(route.back() == j || route.front() == j) continue; // Odrzucenie krawedzi do wierzcholka umieszczonego juz na trasie bool vertexUsed = false; for(int k = 0; k < route.size(); k++) { if(j == route.at(k)) { vertexUsed = true; break; } } if(vertexUsed) continue; // Znalezienie najkrotszej mozliwej jeszcze do uzycia krawedzi unsigned consideredLength = graph.getWeight(route.back(), j); if(minEdge == -1) { minEdge = consideredLength; nextVertex = j; } else if(minEdge > consideredLength) { minEdge = consideredLength; nextVertex = j; } } route.push_back(nextVertex); } route.push_back(route.front()); return route; } std::vector Graph::travellingSalesmanRandom(Graph &graph) { // ALGORYTM losowy // Implementacja: Jan Potocki 2019 std::vector route; std::random_device randomSrc; std::default_random_engine randomGen(randomSrc()); std::uniform_int_distribution<> vertexDist(0, graph.vertexNumber - 1); for(int i = 0; i < graph.vertexNumber; i++) { unsigned randomVertex; bool vertexUsed; do { randomVertex = vertexDist(randomGen); vertexUsed = false; for(int j = 0; j < route.size(); j++) { if(route.at(j) == randomVertex) { vertexUsed = true; break; } } } while(vertexUsed == true); route.push_back(randomVertex); } route.push_back(route.front()); return route; } std::vector Graph::travellingSalesmanTabuSearch(Graph &graph, unsigned tabuSteps, bool diversification, int iterationsToRestart, unsigned minStopTime, unsigned threadsNumber) { // ALGORYTM wielawotkowy oparty na metaheurystyce tabu search // Pomocniczy kod uruchamiajacy watki wlasciwego algorytmu w najbardziej optymalny sposob // Implementacja: Jan Potocki 2019 std::vector startVertexVector; std::vector threadsVector; std::vector> resultsVector(threadsNumber); std::vector resultsLength(threadsNumber); std::vector optimalResult; int optimalResultIndex; int optimalResultLength; std::random_device randomSrc; std::default_random_engine randomGen(randomSrc()); std::uniform_int_distribution<> vertexDist(0, graph.vertexNumber - 1); for(int i = 0; i < threadsNumber; i++) { std::vector startRoute; unsigned startVertex; bool startVertexUsed; if(i < graph.vertexNumber) { do { startVertex = vertexDist(randomGen); startVertexUsed = false; for(int j = 0; j < startVertexVector.size(); j++) { if(startVertexVector.at(j) == startVertex) { startVertexUsed = true; break; } } } while(startVertexUsed == true); startVertexVector.push_back(startVertex); startRoute = Graph::travellingSalesmanGreedy(graph, startVertex); } else { startRoute = Graph::travellingSalesmanRandom(graph); } threadsVector.push_back(std::thread(Graph::travellingSalesmanTabuSearchEngine, std::ref(graph), tabuSteps, diversification, iterationsToRestart, minStopTime, startRoute, std::ref(resultsVector.at(i)), std::ref(resultsLength.at(i)))); } for(int i = 0; i < threadsNumber; i++) threadsVector.at(i).join(); optimalResultIndex = 0; optimalResultLength = resultsLength.at(0); for(int i = 0; i < threadsNumber; i++) { if(resultsLength.at(i) < optimalResultLength) { optimalResultIndex = i; optimalResultLength = resultsLength.at(i); } } optimalResult = resultsVector.at(optimalResultIndex); return optimalResult; } void Graph::travellingSalesmanTabuSearchEngine(Graph &graph, unsigned tabuSteps, bool diversification, int iterationsToRestart, unsigned minStopTime, std::vector startRoute, std::vector &result, int &resultLength) { // ALGORYTM oparty na metaheurystyce tabu search z dywersyfikacja i sasiedztwem typu swap // Rdzen przeznaczony do uruchamiania jako jeden watek // Projekt i implementacja: Jan Potocki 2017 // (refactoring 2019) Stopwatch onboardClock; std::vector optimalRoute; // Tu bedziemy zapisywac optymalne (w danej chwili) rozwiazanie int optimalRouteLength = -1; // -1 - bedziemy odtad uznawac, ze to jest nieskonczonosc ;-) std::vector currentRoute; // Rozpatrywane rozwiazanie // Wyznaczenie poczatkowego rozwiazania algorytmem zachlannym //currentRoute = Graph::travellingSalesmanGreedy(graph); currentRoute = startRoute; // Inicjalizacja glownej petli... std::vector< std::vector > tabuArray; unsigned currentTabuSteps = tabuSteps; int stopCounter = 0; bool timeNotExceeded = true; onboardClock.start(); // Rdzen algorytmu while(timeNotExceeded == true) { bool cheeseSupplied = true; bool intensification = false; while(cheeseSupplied == true) { std::vector nextRoute; int nextRouteLength = -1; std::vector nextTabu(3, 0); nextTabu.at(0) = currentTabuSteps; // Generowanie sasiedztwa typu swap przez zamiane wierzcholkow // (wierzcholka startowego i zarazem ostatniego nie ruszamy, // pomijamy tez od razu aktualny wierzcholek) for(int i = 1; i < graph.vertexNumber - 1; i++) { for(int j = i + 1; j < graph.vertexNumber; j++) { std::vector neighbourRoute = currentRoute; // Zamiana unsigned buffer = neighbourRoute.at(j); neighbourRoute.at(j) = neighbourRoute.at(i); neighbourRoute.at(i) = buffer; unsigned neighbourRouteLength = 0; for(int i = 1; i < neighbourRoute.size(); i++) neighbourRouteLength += graph.getWeight(neighbourRoute.at(i - 1), neighbourRoute.at(i)); // Sprawdzenie, czy dany ruch nie jest na liscie tabu // (dwa wierzcholki) bool tabu = false; for(int k = 0; k < tabuArray.size(); k++) { if(tabuArray.at(k).at(1) == i && tabuArray.at(k).at(2) == j) { tabu = true; break; } if(tabuArray.at(k).at(1) == j && tabuArray.at(k).at(2) == i) { tabu = true; break; } } // Kryterium aspiracji... if(tabu == true && neighbourRouteLength >= optimalRouteLength) // ...jezeli niespelnione - pomijamy ruch continue; if(nextRouteLength == -1) { nextRouteLength = neighbourRouteLength; nextRoute = neighbourRoute; nextTabu.at(1) = i; nextTabu.at(2) = j; } else if(nextRouteLength > neighbourRouteLength) { nextRouteLength = neighbourRouteLength; nextRoute = neighbourRoute; nextTabu.at(1) = i; nextTabu.at(2) = j; } } } currentRoute = nextRoute; if(optimalRouteLength == -1) { optimalRouteLength = nextRouteLength; optimalRoute = nextRoute; // Reset licznika stopCounter = 0; } else if(optimalRouteLength > nextRouteLength) { optimalRouteLength = nextRouteLength; optimalRoute = nextRoute; // Zaplanowanie intensyfikacji intensification = true; // Reset licznika stopCounter = 0; } // Weryfikacja listy tabu... // ...aktualizacja kadencji na liscie tabu for(int i = 0; i < tabuArray.size(); i++) { tabuArray.at(i).at(0)--; } //...usuniecie zerowych kadencji for(int i = 0; i < tabuArray.size(); i++) { if(tabuArray.at(i).at(0) == 0) tabuArray.erase(tabuArray.begin() + i); } // ...dopisanie ostatniego ruchu do listy tabu tabuArray.push_back(nextTabu); // Zliczenie iteracji stopCounter++; // Zmierzenie czasu onboardClock.stop(); if(onboardClock.read() > minStopTime) timeNotExceeded = false; // Sprawdzenie warunku zatrzymania if(diversification == true) { // Przy aktywowanej dywersyfikacji - po zadanej liczbie iteracji bez poprawy if(stopCounter >= iterationsToRestart || timeNotExceeded == false) cheeseSupplied = false; } else { // Przy nieaktywowanej dywersyfikacji - po uplynieciu okreslonego czasu if(timeNotExceeded == false) cheeseSupplied = false; } } // Dywersyfikacja if(diversification == true) { if(intensification == true) { // Intensyfikacja przeszukiwania przy ostatnim minimum currentRoute = optimalRoute; currentTabuSteps = tabuSteps / 4; intensification = false; } else { // Algorytm hybrydowy losowo-zachlanny currentRoute = Graph::travellingSalesmanHybrid(graph); currentTabuSteps = tabuSteps; intensification = false; } } // Reset licznika iteracji przed restartem stopCounter = 0; } result = optimalRoute; resultLength = optimalRouteLength; }