著者:佐藤 楓真
今回は、「焼きなまし法」と呼ばれる手法で、すべての都市を1回訪問して出発地点に帰るまでの最短経路を求める「巡回セールスマン問題」を近似的に解いてみます。焼きなまし法のベースとなる「山登り法」についても解説します。使用するプログラミング言語は、C++です。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。
図3 図1の巡回セールスマン問題を山登り法で解くC++プログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <iostream> #include <algorithm> using namespace std; #define TL 3 //制限時間 #define N 4 int d[N][N] = {{0, 2, 10, 5}, {2, 0, 5, 3}, {10, 5, 0, 4}, {5, 3, 4, 0}}; int ans[N+1] = {0, 1, 3, 2, 0}; int rand_num(void) { return rand() % N + 1; } int calc_dist(void) { int sum = 0; for (int i = 0; i < N; i++) { sum += d[ans[i]][ans[i+1]]; } return sum; } int main(void) { srand((unsigned)time(NULL)); int st_time = clock(); while ((clock() - st_time) / CLOCKS_PER_SEC < TL) { int L = rand_num(), R = rand_num(); if (L > R) swap(L, R); int bf_dist = calc_dist(); reverse(ans + L, ans + R); int af_dist = calc_dist(); if (bf_dist < af_dist) reverse(ans + L, ans + R); } for (int i = 0; i < N+1; i++) { cout << ans[i] + 1 << " "; } cout << endl << calc_dist() << endl; return 0; } |
図4 都市数「5000」のテスト用データを作成するC++プログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <iostream> #include <vector> #include <fstream> #include <string> using namespace std; #define N 5000 int main(void) { srand((unsigned)time(NULL)); for (int num = 1; num <= 100; num++) { vector<vector<int>> d(N, vector<int>(N)); for (int i = 0; i < N; i++) { for (int j = i; j < N; j++) { if (i == j) { d[i][j] = 0; } else { d[i][j] = rand() % 5000 + 1; } } } for (int i = 0; i < N; i++) { for (int j = 0; j < i; j++) { d[i][j] = d[j][i]; } } string name = "case" + to_string(num) + ".txt"; ofstream out(name); out << N << endl; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { out << d[i][j] << " "; } out << endl; } } return 0; } |
図5 テスト用データに基づく巡回セールスマン問題を山登り法で解くC++プログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <iostream> #include <algorithm> #include <vector> #include <string> #include <fstream> using namespace std; #define TL 3 //制限時間 int calc_dist(vector<vector<int>>& dist, vector<int>& ans, int N) { int sum = 0; for (int i = 0; i < N; i++) { sum += dist[ans[i]][ans[i+1]]; } return sum; } int rand_num(int N){ return rand() % N + 1; } int main(void) { long long ave = 0; for (int num = 1; num <= 100; num++) { srand((unsigned)time(NULL)); string name = "case" + to_string(num) + ".txt"; ifstream infile(name); int N; infile >> N; vector<vector<int>> d(N, vector<int>(N)); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { infile >> d[i][j]; } } vector<int> ans(N + 1); for (int i = 0; i < N; i++) ans[i] = i; ans[N] = 0; int st_time = clock(); while ((clock() - st_time) / CLOCKS_PER_SEC < TL) { int bf_dist = calc_dist(d, ans, N); int L = rand_num(N), R = rand_num(N); if (L > R) swap(L, R); reverse(ans.begin() + L, ans.begin() + R); int af_dist = calc_dist(d, ans, N); if (af_dist > bf_dist) reverse(ans.begin() + L, ans.begin() + R); } ave += calc_dist(d, ans, N); } cout << ave / 100.0 << endl; return 0; } |
図7 テスト用データに基づく巡回セールスマン問題を焼きなまし法で解くC++プログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
#include <iostream> #include <algorithm> #include <vector> #include <string> #include <fstream> #include <cmath> using namespace std; #define MAX_TMP 100.0 //初期温度 #define TL 3 //制限時間 int calc_dist(vector<vector<int>>& dist, vector<int>& ans, int N) { int sum = 0; for (int i = 0; i < N; i++) { sum += dist[ans[i]][ans[i+1]]; } return sum; } int rand_num(int N) { return rand() % N + 1; } double rand_p(void) { return (double)rand() / RAND_MAX; } int main(void) { srand((unsigned)time(NULL)); long long ave = 0; for (int num = 1; num <= 100; num++) { string name = "case" + to_string(num) + ".txt"; ifstream infile(name); int N; infile >> N; vector<vector<int>> d(N, vector<int>(N)); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { infile >> d[i][j]; } } vector<int> ans(N + 1); for (int i = 0; i < N; i++) ans[i] = i; ans[N] = 0; int st_time = clock(); //焼きなまし開始時間 while ((clock() - st_time) / CLOCKS_PER_SEC < TL ) { int bf_dist = calc_dist(d, ans, N); int L = rand_num(N), R = rand_num(N); if (L > R) swap(L, R); reverse(ans.begin() + L, ans.begin() + R); int af_dist = calc_dist(d, ans, N); double t = (double)(clock() - st_time) / CLOCKS_PER_SEC; //経過時間 double tmp = MAX_TMP - MAX_TMP * (t / TL); //現在の温度 double p = exp((bf_dist - af_dist) / tmp); //採用確率 if (rand_p() > p) reverse(ans.begin() + L, ans.begin() + R); } ave += calc_dist(d, ans, N); } cout << ave / 100.0 <<endl; return 0; } |