【プログラミングの質問】巡回セールスマン問題のさまざまな解決策(未完成)

テキスト:
巡回セールスマン問題(英語:巡回セールスマン問題、TSP)は問題です。一連の都市と都市の各ペア間の距離が与えられた場合、各都市を1回だけ訪問し、開始都市に戻る最短のループを解きます。これは、組み合わせ最適化におけるNP困難な問題であり、オペレーションズリサーチや理論計算機科学で非常に重要です。

この問題では、都市をノード、都市間の距離をエッジと見なします。nノードの巡回セールスマン問題はn * nの隣接行列と見なすことができます。
例:ここに画像の説明を挿入します
質問では、ノードの1つから開始し、すべてのノードをトラバースして1回だけ読み取り、最後に元の開始ノードに戻る必要があります。

分析:
TSP問題は明らかにNP問題です(NP問題については、https://blog.csdn.net/csdnnews/article/details/100111395でより明確な理解を確認できます):任意のノードが初期として選択されますノード、パスは閉ループであるため、これとステップは重要ではありませんが、そのn-1ステップでは、n-1の選択肢があります。したがって、ブルートフォースを使用してクラックする場合、時間コストはnです。 !、これは明らかに望ましくありません。したがって、他の方法が必要です。

方法:
1。動的計画法方法
閉ループ上の点sから開始し、関数d(i、v)がノードiから開始し、点セットVの各ノードを1回だけ通過し、最後にに戻ることを示します。開始点■最短経路。次に、Cijを使用してノードiからノードjまでの距離を表すと、関数d(i、v)は次のようになります。

1)Vが空集合の場合(これは、すべてのノードが1回通過したことを意味します)、最後のノードを最初のノードに接続して閉ループを形成する必要があります。次に、d(i、v)= Cis;
2) Vが空集合ではない場合、点集合Vで点kを選択し、Cik + d(i、v- {k})をすべての実行可能解の最適解にする必要があります(この最適解を求めることは、問題自体同じ構造で小規模なサブ問題)。

したがって、関数d(i、v)は次のように表すことができます
ここに画像の説明を挿入します
。4つのノードを例にとると、実行する必要のある計算は次のとおりです。
d(0、{1,2,3})=分

なぜ動的計画法を使用する方が良いのですか?テキストだけから、すべての可能性を横断する必要があるようです。
実際、私の個人的な理解と意見によれば、動的計画法は通常のテーブル作成方法に似ており、完了した計算を脇に保存して、次の計算のコンポーネントの1つとして使用します。動的計画法では、この「計算」は「決定」に置き換えられます。

動的計画法は、問題を段階的にサブ問題に分割し、前の層の再帰的戻りの一部としてサブ問題の解決策を取ります。最下層の2点間の距離は直接知ることができるため、他の層のレコードを記録するためにdpテーブルが必要です。
ここに画像の説明を挿入します

ここで、縦の行は選択されたノードiを表し、横の行は残りのセットを表します。テーブル全体は左から右、上から下派生し、存在しない組み合わせはd(のように0で表されます。 1、{1,2})。(ノードを接続してそれ自体を含めることはできません)

例としてd(1、{2,3})を取り上げます
。d(1、{2,3})= min(C12 + d(2、{3})、C13 + d(3、{2})) 、したがって、d(2、{3})とd(3、{2});
およびd(2、{3})= C23 + d(3、{})が見つかり、上記でC23 = 2が見つかります。次の表では、d(3、{})= 3であるため、d(2、{3})= 5であり、同じようにd(3、{2})= 11である
ため、d(1 、{2,3})= min(C12 + d(2、{3})、C13 + d(3、{2}))= min(2 + 5,3 + 11)= 7

コードの実装
を確認するのは難しくありません。ここでの実装の主なポイントは、dpテーブルを確立することです。バイナリメソッドを使用してポイントセットメンバーを表すことができます。たとえば、111は{1,2,3}を表し、シフト操作を使用して、バイナリ表現を作成できます。m=1<<(n-1)

要素kがあるかどうかを設定するチェックポイントは、以下に基づくことができます。、m>>(k-1)k-1は、k番目の位置に移動するためであるため、最初のk-1ビットを削除する必要があります。

セット内のサブセットを取得するには、つまり、k番目のノードを削除する必要があります。andまたは^操作を使用できます。j^(1<<(k-1))

コード(参照:https://blog.csdn.net/qq_39559641/article/details/101209534):

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>

using namespace std;

//定义节点数量
#define N 4
//设定一个大数,使其表示两节点无法连接,并在选取最小值时被筛选掉
#define INF 10e7

//依照节点数转换为集合组合可能数
static const int M = 1 << (N - 1);

//存储城市间的距离
int g[N][N] = {
    
    {
    
    INF,3,6,7},
	           {
    
    5,INF,2,3},
	           {
    
    6,4,INF,2},
	           {
    
    3,7,5,INF}};
//dp表,由于存储d(i,V)的值
int dp[N][M];
//保存路径
vector<int> path;

//求两者最小值
int Min(int a,int b)
{
    
    
	return (a > b) ? b : a;
}

//核心代码
void TSP()
{
    
    
	//初始化dp[i][0],因为头一列数都是该点直接到出发点s的距离
	for(int i=0;i<N;i++)
	{
    
    
		dp[i][0] = g[i][0];
	}
	//开始推dp表,自左向右,自上向下更新
	//我们第0行已初始化
	for(int j=1;j<M;j++)
	{
    
    
		for(int i=0;i<N;i++)
		{
    
    
			//为了取最小值,所以应该先 往里面填写较大的数
			dp[i][j] = INF;
			//d(i,V)中,i不应存在于集合V内,若存在,则应该跳过
			if(((j>>(i-1))&1)==1)
			{
    
    
				continue;
			}
			//从大集合{1,2,3}中选择,若i存在于V'中,则访问
			for(int k=1;k<N;k++)
			{
    
    
				//如果该点不存在则跳过
				if(((j >> (k - 1)) & 1) == 0)
				{
    
    
					continue;
				}
				//对比大小
				dp[i][j] = Min(dp[i][j], g[i][k] + dp[k][j ^ (1 << (k - 1))]);
			}
		}
	}
	cout << "最短路劲长度为:"<<dp[0][M - 1]<<endl;
}

//判断节点是否已访问,不包括0号
bool isVisited(bool visited[])
{
    
    
	for(int i=1;i<N;i++)
	{
    
    
		if(visited[i]==false)
		{
    
    
			return false;
		}
	}
	return true;
}

//获取最优路劲,保存在path中,根据动态规划反向找出最短路径节点
void getPath()
{
    
    
	//建立访问标记数组
	bool visited[N] = {
    
     false };
	//前驱节点编号
	int pioneer = 0, min = INF, S = M - 1, temp;
	//把初始节点0添加到里面
	path.push_back(0);

	//当存在节点为访问时
	while (!isVisited(visited))
	{
    
    
		//判断子问题中的最优解
		for(int i=1;i<N;i++)
		{
    
    
			//节点未访问且剩余集非空集
			if(visited[i]==false&&(S&(1<<(i-1)))!=0)
			{
    
    
				//寻找最优解
				if (min > g[i][pioneer] + dp[i][(S ^ (1 << (i - 1)))])
				{
    
    
					min = g[i][pioneer] + dp[i][(S ^ (1 << (i - 1)))];
					temp = i;
				}
			}
		}
		//压入子问题最优解
		pioneer = temp;
		path.push_back(pioneer);
		//标记节点已访问
		visited[pioneer] = true;
		//从剩余节点中去除已访问节点
		S = S ^ (1 << (pioneer - 1));
		min = INF;
	}
}

//输出路径
void printPath() {
    
    
	cout << "最小路径为:";
	vector<int>::iterator  it = path.begin();
	for (it; it != path.end();it++) {
    
    
		cout << *it << "--->";
	}
	//单独输出起点编号
	cout << 0;
}
int main()
{
    
    
	TSP();
	getPath();
	printPath();
	return 0;
}

結果:

最短路劲长度为:10
最小路径为:0--->1--->2--->3--->0

おすすめ

転載: blog.csdn.net/qq_43530773/article/details/114698780