1. 問題の説明
巡回セールスマン問題は、旅行者が n 都市を旅行したいと考えており、各都市を 1 回だけ訪れて最初の都市に戻ること、および移動距離が最短であることを要求します。
まず、与えられた無向グラフ、つまり n 個の頂点、m 個の無向エッジを介して、各エッジには 2 点間の距離を表す重みがあり、各点を通過して原点に戻り、最短経路を見つける必要があります。
2. 問題分析
(1) 解析: 初期点から始まる移動ルートは、初期ノードを除く n-1 個のノードの順列数に等しい (n-1)! 個存在するため、巡回セールスマン問題は順列問題となる。(n-1)! 個の移動ルートを列挙することにより、最小コスト アルゴリズムを使用して移動ルートを見つけます。その計算時間は O(n!) です。
(2) 完全な解法: 問題で与えられた無向グラフ (図 1、つまり都市点と他の都市までの必要な距離) を 2 次元配列に結合することで問題を回避できます。スキームでは、元の都市のスキームが次のように表示されます。 図 2. 次に、動的計画法、貪欲法、分枝限定およびその他のアルゴリズムを通じて、対応するアルゴリズムを解決します。
(3) 期待される結果: プログラム実行後に最適解の結果を表示します。
図 1 無向グラフ
図 2 無向グラフに対応する行列
3. アルゴリズムの設計と解析
1. アルゴリズムの入出力
(1) アルゴリズム入力: 都市の数、他の都市までの距離 (それ自体までの距離は無限大に設定されます)。
(2) アルゴリズム出力: すべての都市を通過するために必要な合計パスの最小値
2. アルゴリズムの説明
(1) 貪欲な方法: 貪欲なアルゴリズムは常に現時点で最良の選択を行います。貪欲なアルゴリズムは全体的な最適性を考慮せず、その選択はある意味で局所的な最適な選択にすぎません。貪欲アルゴリズムは、すべての問題に対して全体的な最適解を取得することはできませんが、多くの問題に対して全体的な最適な解を生成できます。たとえば、単一ソース最短パス問題、最小スパニング ツリー問題などです。場合によっては、貪欲アルゴリズムが全体的な最適解を取得できない場合でも、最終結果は最適解の適切な近似になります。
(2) 動的計画法: 動的計画法のアルゴリズムは分割統治法に似ており、その基本的な考え方は、解決すべき問題をいくつかの部分問題に分解することですが、分解によって得られた部分問題は多くの場合、分割統治法とは異なります。互いに独立しています。さまざまな部分問題の数は、多くの場合、多項式の次数にすぎません。分割統治によって解決する場合、一部の部分問題は何度も再計算されます。解いた部分問題の答えを保存し、必要なときに得られた答えを見つけることができれば、大量の繰り返し計算を回避でき、その結果、多項式時間アルゴリズムが実現します。
(3) 分枝限定法: 分枝限定法: 幅優先を採用して状態空間ツリーのノードを生成し、枝刈り関数の方法を使用します。「ブランチ」は、幅優先戦略を使用して、拡張ノードのすべてのブランチ (つまり、子ノード) を順番に生成します。
「Boundary」は、ノードの拡張処理中にノードの上限(または下限)を計算し、検索中に枝刈りを行うことで検索効率を向上させます。
3. アルゴリズムの説明
(1) 貪欲な方法: 最近傍点戦略を採用し、任意の点から開始し、毎回通過していない最も近い点に進み、すべての点が通過するまで、最後に開始点に戻ります。つまり、現在頂点 u にいる場合は、頂点 u に最も近い点 p を取得します。
(2) 動的計画法: 点 i を経由せずに開始点に戻る最短経路の合計は mp[i][0] (デフォルトの初期点は 0)、dp[i][0]= mp[i ][0];(1<=i<n)。点 i から集合 S までの最短経路の和(二進数表現では j )が、集合 S のある点 k を通過した後、点 i から始まる集合 S-{k} の最小値となります。dp[i][j]=min{mp[i][k]+dp[k][j-(1<<(k-1))};
(3) 分岐限定法: まず、貪欲法で解かれた値を上限として使用し、各点の最も近い 2 辺の合計の 1/2 を下限として使用します。分岐限定法では目的関数を設定し、毎回優先キューから目的関数の最小値を持つノードを取得します。まず、n-1 点を通過したかどうかを判断し、n-1 点を通過した場合、最短パスの合計を直接計算し、キュー内の他のノードの目的関数の値と比較することができます。キュー内の他のすべてのノードの目的関数の値が小さい場合、パスと合計を変更することで問題を解決できます。それ以外の場合は、優先キュー内の他のノードの計算を続けます。
4. アルゴリズム解析
(1) 貪欲な方法: 開始点から開始して、すべての点を通過するまで、通過していない点の中から現在の点に最も近い点を次のステップで通過する点として毎回見つけます。 、そして原点に戻ります。上限を見つけるのに適用できます。
(2) 動的計画法: 頂点 s から開始すると仮定し、頂点 i から開始して V' (点の集合) の各頂点を 1 回だけ通過し、最後に頂点に戻ることを d(i, V') とします。開始点 s 最短の経路長。適用できる都市が少なくなります。
(3) 分岐限定法: まず、ストレージ グラフのノード構造、優先キュー、隣接行列を定義し、データを読み込みます。各都市の最小距離と、すべての都市の最小距離の合計を計算します。最初のノードからトラバースを開始し、順列空間ツリーを検索します。ノードを取り出して展開し、葉ノードの親ノードに出会ったら最適解の更新を試み、葉ノードに出会ったら走査を終了します。リーフノードから最適解を取得するパス。多くの都市に適用できます。
4. アルゴリズムフローチャート
(1) 貪欲な方法: 以下の図 3 を参照してください。
図 3 貪欲アルゴリズムのフローチャート
(2) 動的プログラミング法: 以下の図 4 を参照してください。
図4 動的計画法のアルゴリズムフローチャート
(3) 分岐限定法: 以下の図 5 を参照してください。
図5 分岐限定法のアルゴリズムフローチャート
5. テストデータと結果の分析
(1) 貪欲な方法: テスト データは次のコードに示され、実行結果は図 6 に示されます。
分析: まず、visit 関数を使用して都市を通過するかどうかを判断し、次に clastCityDistance 関数を使用して現在の都市に最も近い次の都市を見つけ、最後に TSP 関数を使用して出力を合計します。
図 6 Greedy メソッドの実行結果
#include<iostream>
#define n 4
using namespace std;
int s[n] = { -1,-1,-1,-1 };// 记录已经访问过的城市, 初始化为-1,表示未访问
int distance[n][n] = { {10000,3,6,5},// 城市间距离数组,10000表示自己到自己的距离
{3,10000,5,4},
{6,5,10000,2},
{5,4,2,10000} };
bool visit(int k) {//判断城市k是否被访问过
for (int i = 0; i < n; i++)
if (s[i] == k) return 1;
return 0;
}
void clostCityDistance(int currentCity) { //查找距离当前城市最近的下一个城市
int Dtemp = 10000;//Dtemp暂时存储当前最小路径的值
for (int i = 0; i < n; i++)
{
if (visit(i) == 0 && ::distance[i][s[currentCity]] < Dtemp)
{
Dtemp = ::distance[i][s[currentCity]];
s[currentCity + 1] = i;
//若该城市没有被访问过,且距离当前城市最短,则将访问该城市,存入s[]数组中
}
}
for (int i = 0; i < n; i++)
{//查找是否还有未访问的城市
if (s[i] == -1)
clostCityDistance(s[currentCity + 1]);
}
}
void TSP() {
int sum = 0;// 最短路径之和
s[0] = 2;//从第2个城市出发 ,初始化出发的城市,可在0,1,2,3中任意一个
clostCityDistance(s[0]);//寻找距离2城市最近的城市
for (int i = 0; i < n; i++) {
if (i == n - 1) {
printf("%d", s[i]);
printf("->%d 距离为:%d\n", s[0], ::distance[s[n - 1]][s[0]]);
printf("总距离是 %d\n", sum += ::distance[s[n - 1]][s[0]]);
break;
}
printf("%d->%d 距离为:%d \n", s[i], s[i + 1], ::distance[s[i]][s[i + 1]]);
sum += ::distance[s[i]][s[i + 1]];
}
}
int main() {
TSP();
return 0;
}
(2) 動的プログラミング法: テストデータは次のコードに示され、実行結果は図 7 に示されます。
分析: 最初に Tsp tsp(city_number) 関数を通じて 2 次元行列を初期化し、次に tsp.printCity 関数を通じて都市を出力し、次に tsp.printProcess 関数を通じて計算し、最後に tsp.getShoretstDistance 関数を通じて最短パスを見つけます。 。
図7 動的計画法の実行結果
#include<iostream>
#include<iomanip>
#include<cmath>
using namespace std;
#define MAX_IN 10
class Tsp
{
private:
int city_number; //城市个数
int** distance; //城市距离矩阵
int** process; //求最短路径的过程矩阵
public:
Tsp(int city_number); //构造函数
void correct(); //矫正输入的城市代价矩阵
void printCity(); //打印城市的距离矩阵
void getShoretstDistance(); //动态规划法求最短路径
void printProcess(); //打印过程矩阵
};
//构造函数
Tsp::Tsp(int city_num)
{
int i = 0, j = 0;
city_number = city_num;
//初始化城市距离矩阵
distance = new int* [city_number];
cout << "请输入" << city_number << "个城市之间的距离" << endl;
for (i = 0; i < city_number; i++)
{
distance[i] = new int[city_number];
for (j = 0; j < city_number; j++)
cin >> distance[i][j];
}
//生成过程矩阵
process = new int* [city_number];
for (i = 0; i < city_number; i++)
{
process[i] = new int[1 << (city_number - 1)];
}
}
//纠正用户输入的城市代价矩阵
void Tsp::correct()
{
int i;
for (i = 0; i < city_number; i++)
{
distance[i][i] = 0;
}
}
//打印城市距离
void Tsp::printCity()
{
int i, j;
//打印代价矩阵
cout << "您输入的城市距离如下" << endl;
for (i = 0; i < city_number; i++)
{
for (j = 0; j < city_number; j++)
cout << setw(3) << distance[i][j];
cout << endl;
}
}
//动态规划法求最短路径
void Tsp::getShoretstDistance()
{
int i, j, k;
//初始化第一列
for (i = 0; i < city_number; i++)
{
process[i][0] = distance[i][0];
}
//初始化剩余列
for (j = 1; j < (1 << (city_number - 1)); j++)
{
for (i = 0; i < city_number; i++)
{
process[i][j] = 0x7ffff;//设0x7ffff为无穷大
//对于数字x,要看它的第i位是不是1,通过判断布尔表达式 (((x >> (i - 1) ) & 1) == 1的真值来实现
if (((j >> (i - 1)) & 1) == 1)
{
continue;
}
for (k = 1; k < city_number; k++)
{
//不能达到k城市
if (((j >> (k - 1)) & 1) == 0)
{
continue;
}
if (process[i][j] > distance[i][k] + process[k][j ^ (1 << (k - 1))])
{
process[i][j] = distance[i][k] + process[k][j ^ (1 << (k - 1))];
//cout<<i<<"行"<<j<<"列为:"<<process[i][j]<<endl;
}
}
}
}
cout << "最短路径为" << process[0][(1 << (city_number - 1)) - 1] << endl;
}
//打印过程矩阵
void Tsp::printProcess()
{
int i, j;
for (j = 0; j < 1 << (city_number - 1); j++)
{
cout << setw(3) << j;
}
cout << endl;
for (i = 0; i < city_number; i++)
{
for (j = 0; j < 1 << (city_number - 1); j++)
{
if (process[i][j] == 0x7ffff)
process[i][j] = -1;
cout << setw(3) << process[i][j];
}
cout << endl;
}
}
//主函数
int main(void)
{
int city_number;
while (cin >> city_number)
{
Tsp tsp(city_number); //初始化城市代价矩阵
tsp.correct(); //纠正用户输入的代价矩阵
tsp.printCity(); //打印城市
tsp.getShoretstDistance(); //求出最短路径
tsp.printProcess(); //打印计算矩阵
}
return 0;
}
(3) 分岐限定法: テスト データを次のコードに示し、実行結果を図 8 に示します。
分析: まず main 関数によって 2 次元行列を初期化し、次に Traveling 関数によって移動パスと最短パスを計算し、最後に print 関数によって最短パスを出力します。
図8 分岐限定法の実行結果
#include<iostream>
using namespace std;
#define NoEdge -1
#define MAX 20
int G[MAX][MAX];
int ans[MAX], x[MAX];
int bestc, cc;
void init(int n)
{
int i, j, len;
memset(G, NoEdge, sizeof(G));
while (cin >> i >> j)
{
if (i == 0 && j == 0) break;cin >> len;
G[i][j] = len;
G[j][i] = len;
}
for (i = 1;i <= n;i++) x[i] = i;bestc = 0x7fffff;
cc = 0;
}
void Swap(int& i, int& j)
{
int t = i;
i = j;
j = t;
}
void Traveling(int i, int n)
{
int j;
if (i == n + 1)
{
if (G[x[n - 1]][x[n]] != NoEdge && G[x[n]][1] != NoEdge && (cc + G[x[n]][1] < bestc))
{
for (j = 1;j <= n;j++)ans[j] = x[j];
bestc = cc += G[x[n]][1];
}
}
else {
for (j = i;j <= n;j++) {
if (G[x[i - 1]][x[j]] != NoEdge && (cc + G[x[i - 1]][x[j]] < bestc))
{
Swap(x[i], x[j]);
cc += G[x[i - 1]][x[i]];Traveling(i + 1, n);cc -= G[x[i - 1]][x[i]];Swap(x[i], x[j]);
}
}
}
}
void print(int n)
{
cout << "最小的旅行费用为: " << bestc << endl;
cout << "最佳路径是: ";
for (int i = 1;i <= n;i++)
cout << ans[i] << "->";
cout << ans[1] << endl;
}
int main()
{
int n;
cout << "请输人需要旅行多少个城市: " << endl;
while (cin >> n && n) {
cout << "輸人丙个城市之同的距高,例如1 2 20,輸人00結束" << endl;
init(n);
Traveling(2, n);
print(n);
}
return 0;