<?php /****************************/ /* 巡回セールスマン問題 */ /* 反復改善法 */ /* coded by Y.Suganuma */ /****************************/ /*************************/ /* クラスIterationの定義 */ /*************************/ class Iteration { private $city; //都市の位置データ private $max_try; // 最大試行回数 private $n_city; // 都市の数 private $out_d; // 表示間隔 private $rg; // 都市間の距離 private $seq_w1; // 都市を訪れる順序(ワーク) private $seq_w2; // 都市を訪れる順序(ワーク) private $neib; // 近傍(2 or 3) private $out_lvl; // 出力レベル // =0 : 最終出力だけ // n>0 : n回毎に出力(負の時はファイル) private $out_m; // 出力方法 // =-1 : 出力しない // =0 : すべてを出力 // =1 : 評価値だけを出力(最終結果だけはすべてを出力) private $sel; // エッジの選択方法 // =0 : 最良のものを選択 // =1 : 最初のものを選択 private $o_file; // 出力ファイル名 public $seq; // 都市を訪れる順序 /****************************/ /* コンストラクタ */ /* seed : 乱数の初期値 */ /* name : ファイル名 */ /****************************/ function Iteration ($seed, $name) { // 乱数の初期設定 mt_srand($seed); // ファイルのオープン $in = fopen($name, "rb"); if ($in == NULL) exit("***error データファイル名が不適当\n"); // 基本データ fscanf($in, "%*s %d %*s %d %*s %d %*s %d", $this->n_city, $this->max_try, $this->sel, $this->neib); fscanf($in, "%*s %d %*s %d", $this->out_lvl, $this->out_m); fscanf($in, "%*s %s %*s %d", $this->o_file, $this->out_d); // 都市の位置データ $this->city = array($this->n_city); for ($i1 = 0; $i1 < $this->n_city; $i1++) { $this->city[$i1] = array(2); fscanf($in, "%d %d", $this->city[$i1][0], $this->city[$i1][1]); } // ファイルのクローズ fclose($in); // 距離テーブルの作成 $this->rg = array($this->n_city); for ($i1 = 0; $i1 < $this->n_city; $i1++) { $this->rg[$i1] = array($this->n_city); for ($i2 = $i1+1; $i2 < $this->n_city; $i2++) { $x = $this->city[$i2][0] - $this->city[$i1][0]; $y = $this->city[$i2][1] - $this->city[$i1][1]; $this->rg[$i1][$i2] = round(sqrt($x * $x + $y * $y)); } } for ($i1 = 1; $i1 < $this->n_city; $i1++) { for ($i2 = 0; $i2 < $i1; $i2++) $this->rg[$i1][$i2] = $this->rg[$i2][$i1]; } // 都市を訪れる順序(初期設定) $this->seq = array($this->n_city); $this->seq_w1 = array($this->n_city); $this->seq_w2 = array($this->n_city); for ($i1 = 0; $i1 < $this->n_city; $i1++) { $sw = 0; while ($sw == 0) { $ct = intval((mt_rand() / mt_getrandmax()) * $this->n_city); if ($ct >= $this->n_city) $ct = $this->n_city - 1; $this->seq[$i1] = $ct; $sw = 1; for ($i2 = 0; $i2 < $i1 && $sw > 0; $i2++) { if ($ct == $this->seq[$i2]) $sw = 0; } } } } /****************/ /* 最適化の実行 */ /****************/ function Optimize () { // 初期設定 $n_tri = 0; $max = kyori($this->n_city, $this->seq, $this->rg); if ($this->out_m >= 0 && abs($this->out_lvl) > 0) { printf("***試行回数 %d 距離 %d\n", $n_tri, -$max); $this->Output($this->out_lvl, $n_tri, $max); } // 実行 $sw = 1; for ($n_tri = 1; $n_tri <= $this->max_try && $sw > 0; $n_tri++) { // 改善 $sw = $this->Change($max); // 出力 if ($this->out_d > 0 && $n_tri%$this->out_d == 0) printf("***試行回数 %d 距離 %d\n", $n_tri, -$max); if ($this->out_m >= 0 && abs($this->out_lvl) > 0) { if ($n_tri%abs($this->out_lvl) == 0) $this->Output($this->out_lvl, $n_tri, $max); } } // 最終出力 if ($this->out_m >= 0) { $n_tri--; $k1 = $this->out_m; $this->out_m = 0; printf("***試行回数 %d 距離 %d\n", $n_tri, -$max); $this->Output($this->out_lvl, $n_tri, $max); $this->out_m = $k1; } return $n_tri; } /*******************************/ /* 出力 */ /* sw : >=0 : 出力先未定 */ /* < 0 : ファイル */ /* n_tri : 現在の試行回数 */ /* r : 距離の負値 */ /*******************************/ function Output($sw, $n_tri, $r) { $k = 0; if ($sw >= 0) { printf(" 出力先は(0:出力なし,n:画面にn個づつ,-1:ファイル)? "); fscanf(STDIN, "%d", $pr); } else $pr = -1; if ($pr != 0) { if ($pr > 0) { $out = STDOUT; fgets(STDIN); } else { $x = getdate(); $now = $x["hours"]."時".$x["minutes"]."分".$x["seconds"]."秒"; $out = fopen($this->o_file, "ab"); fwrite($out, "***試行回数 ".$n_tri." 距離 ".(-$r)." 時間 ".$now."%s\n"); } if ($this->out_m == 0) { for ($i1 = 0; $i1 < $this->n_city; $i1++) { $n = $this->seq[$i1]; fwrite($out, " ".$n." ".$this->city[$n][0]." ".$this->city[$n][1]."\n"); if ($pr > 0) { $k++; if ($k == $pr) { fgets(STDIN); $k = 0; } } } } if ($pr <= 0) fclose($out); } } /**************************************/ /* エッジの入れ替え */ /* r_m : 距離の負値 */ /* return : =0 : 改善がなかった */ /* =1 : 改善があった */ /**************************************/ function Change(int &$r_m) { $ch = 0; $sw = 0; $max = $r_m; $n3 = intval((mt_rand() / mt_getrandmax()) * ($this->n_city - 2)); if ($n3 > $this->n_city-3) $n3 = $this->n_city - 3; // 2近傍 for ($i1 = 0; $i1 <= $this->n_city-3 && $ch == 0; $i1++) { if ($n3 == 0) $n1 = $this->n_city - 2; else $n1 = $this->n_city - 1; for ($i2 = $n3+2; $i2 <= $n1 && $ch == 0; $i2++) { // 枝の場所((n3,n3+1), (k1,k2)) $k1 = $i2; if ($i2 == $this->n_city-1) $k2 = 0; else $k2 = $i2 + 1; // 枝の入れ替え $this->seq_w1[0] = $this->seq[$n3]; $k = 1; for ($i3 = $k1; $i3 >= $n3+1; $i3--) { $this->seq_w1[$k] = $this->seq[$i3]; $k++; } $nn = $k2; while ($nn != $n3) { $this->seq_w1[$k] = $this->seq[$nn]; $k++; $nn++; if ($nn > $this->n_city-1) $nn = 0; } // 評価 $r = kyori($this->n_city, $this->seq_w1, $this->rg); if ($r > $max) { $max = $r; $sw = 1; for ($i3 = 0; $i3 < $this->n_city; $i3++) $this->seq_w2[$i3] = $this->seq_w1[$i3]; if ($this->sel > 0) $ch = 1; } } $n3++; if ($n3 > $this->n_city-3) $n3 = 0; } // 3近傍 if ($this->neib == 3 && $ch == 0) { for ($i1 = 0; $i1 <= $this->n_city-3 && $ch == 0; $i1++) { $n1 = $this->n_city - 2; $n2 = $this->n_city - 1; for ($i2 = $n3+1; $i2 <= $n1 && $ch == 0; $i2++) { for ($i3 = $i2+1; $i3 <= $n2 && $ch == 0; $i3++) { // 枝の場所((n3,n3+1), (i2,i2+1), (k1,k2)) $k1 = $i3; if ($i3 == $this->n_city-1) $k2 = 0; else $k2 = $i3 + 1; // 枝の入れ替えと評価 // 入れ替え(その1) $this->seq_w1[0] = $this->seq[$n3]; $k = 1; for ($i4 = $i2; $i4 >= $n3+1; $i4--) { $this->seq_w1[$k] = $this->seq[$i4]; $k++; } for ($i4 = $k1; $i4 >= $i2+1; $i4--) { $this->seq_w1[$k] = $this->seq[$i4]; $k++; } $nn = $k2; while ($nn != $n3) { $this->seq_w1[$k] = $this->seq[$nn]; $k++; $nn++; if ($nn > $this->n_city-1) $nn = 0; } // 評価(その1) $r = kyori($this->n_city, $this->seq_w1, $this->rg); if ($r > $max) { $max = $r; $sw = 1; for ($i3 = 0; $i3 < $this->n_city; $i3++) $this->seq_w2[$i3] = $this->seq_w1[$i3]; if ($this->sel > 0) $ch = 1; } // 入れ替え(その2) $this->seq_w1[0] = $this->seq[$n3]; $k = 1; for ($i4 = $k1; $i4 >= $i2+1; $i4--) { $this->seq_w1[$k] = $this->seq[$i4]; $k++; } for ($i4 = $n3+1; $i4 <= $i2; $i4++) { $this->seq_w1[$k] = $this->seq[$i4]; $k++; } $nn = $k2; while ($nn != $n3) { $this->seq_w1[$k] = $this->seq[$nn]; $k++; $nn++; if ($nn > $this->n_city-1) $nn = 0; } // 評価(その2) $r = kyori($this->n_city, $this->seq_w1, $this->rg); if ($r > $max) { $max = $r; $sw = 1; for ($i3 = 0; $i3 < $this->n_city; $i3++) $this->seq_w2[$i3] = $this->seq_w1[$i3]; if ($this->sel > 0) $ch = 1; } // 入れ替え(その3) $this->seq_w1[0] = $this->seq[$n3]; $k = 1; for ($i4 = $i2+1; $i4 <= $k1; $i4++) { $this->seq_w1[$k] = $this->seq[$i4]; $k++; } for ($i4 = $i2; $i4 >= $n3+1; $i4--) { $this->seq_w1[$k] = $this->seq[$i4]; $k++; } $nn = $k2; while ($nn != $n3) { $this->seq_w1[$k] = $this->seq[$nn]; $k++; $nn++; if ($nn > $this->n_city-1) $nn = 0; } // 評価(その3) $r = kyori($this->n_city, $this->seq_w1, $this->rg); if ($r > $max) { $max = $r; $sw = 1; for ($i3 = 0; $i3 < $this->n_city; $i3++) $this->seq_w2[$i3] = $this->seq_w1[$i3]; if ($this->sel > 0) $ch = 1; } // 入れ替え(その4) $this->seq_w1[0] = $this->seq[$n3]; $k = 1; for ($i4 = $i2+1; $i4 <= $k1; $i4++) { $this->seq_w1[$k] = $this->seq[$i4]; $k++; } for ($i4 = $n3+1; $i4 <= $i2; $i4++) { $this->seq_w1[$k] = $this->seq[$i4]; $k++; } $nn = $k2; while ($nn != $n3) { $this->seq_w1[$k] = $this->seq[$nn]; $k++; $nn++; if ($nn > $this->n_city-1) $nn = 0; } // 評価(その4) $r = kyori($this->n_city, $this->seq_w1, $this->rg); if ($r > $max) { $max = $r; $sw = 1; for ($i3 = 0; $i3 < $this->n_city; $i3++) $this->seq_w2[$i3] = $this->seq_w1[$i3]; if ($this->sel > 0) $ch = 1; } } } $n3++; if ($n3 > $this->n_city-3) $n3 = 0; } } // 設定 if ($sw > 0) { $r_m = $max; for ($i1 = 0; $i1 < $this->n_city; $i1++) $this->seq[$i1] = $this->seq_w2[$i1]; } return $sw; } } /*********************************/ /* 距離の計算 */ /* n_c : 都市の数 */ /* p : 都市番号 */ /* rg : 都市間の距離 */ /* return : 距離(負) */ /*********************************/ function kyori($n_c, $p, $rg) { $range = 0; $n1 = $p[0]; for ($i1 = 1; $i1 < $n_c; $i1++) { $n2 = $p[$i1]; $range -= $rg[$n1][$n2]; $n1 = $n2; } $n2 = $p[0]; $range -= $rg[$n1][$n2]; return $range; } /****************/ /* main program */ /****************/ // 入力ミス if (count($argv) <= 1) exit("***error ファイル名を入力して下さい\n"); // 入力OK else { // ファイルのオープン $in = fopen($argv[1], "rb"); if ($in == NULL) exit("***error ファイル名が不適当です\n"); // 入力データファイル名の入力 fscanf($in, "%d", $n); $seed = array($n); $i_file = array($n); for ($i1 = 0; $i1 < $n; $i1++) { fscanf($in, "%s", $i_file[$i1]); $seed[$i1] = 1000 * $i1 + 1234567; } fclose($in); // 実行(乱数の初期値を変える) for ($i1 = 0; $i1 < $n; $i1++) { printf("\n+++++ケース %d+++++\n", $i1+1); // 入力と初期設定 $it = new Iteration($seed[$i1], $i_file[$i1]); // 最適化 $it->Optimize(); } } //------------------------ケーススタディデータ(data.txt)------ /* 3 data1.txt data1.txt data2.txt */ //---------------------データファイル(data1.txt)------------ /* 都市の数 10 最大試行回数 3000 選択方法(0:最良,1:最初) 1 近傍(2or3) 3 出力レベル(負はファイル) 0 出力方法(-1:なし,0:すべて,1:評価値) 1 出力ファイル名 out1.txt 表示間隔 0 -58 37 55 -19 6 -79 27 -30 44 -94 33 -58 -94 87 -9 3 33 69 43 -57 */ //---------------------データファイル(data2.txt)------------ /* 都市の数 50 最大試行回数 3000 選択方法(0:最良,1:最初) 1 近傍(2or3) 3 出力レベル(負はファイル) 0 出力方法(-1:なし,0:すべて,1:評価値) 1 出力ファイル名 out1.txt 表示間隔 0 86 27 82 16 29 37 27 1 90 88 40 92 87 53 24 99 11 80 61 8 30 93 4 81 86 90 84 52 96 45 4 34 53 6 45 12 23 97 61 46 49 16 82 74 48 36 13 5 12 6 33 26 8 50 98 98 79 65 36 56 42 25 52 12 88 79 27 51 86 51 14 35 77 37 44 0 78 13 21 55 82 26 25 88 55 0 10 59 41 33 7 4 56 99 19 34 92 46 27 35 */ ?>