動態規劃法求解TSP問題 C++

此文章借鑑於博文https://blog.csdn.net/shujian_tianya/article/details/80873892,再次基礎上重新進行了分析總結。

一、問題

在這裏插入圖片描述

二、想法

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

三、講解

1、怎麼求頂點子集,即這些怎麼記錄?在這裏插入圖片描述答:例如4個頂點{0,1,2,3},{1,2,3}依次爲{},{1},{2},{1,2},{3},{1,3},{2,3},{1,2,3}。十進制數0、1、2、3、4、5、6、7的二進制分別爲000、001、010、011、100、101、110、111。上述集合中的元素即爲二進制中的位數,例如集合{2,3},可用二進制110(十進制6)代替,因爲二進制110的第一位是0,第二位第三位是1。
整理一下思路——十進制數的二進制表示中,哪位爲1則集合中就有哪個數。十進制6的二進制110中,第二三位是1,則6代表集合{2,3}
沒有爲什麼,這是我們定下的一條規則,方便我們解題。
如此,過程矩陣d[i][j]的縱座標j就多了一個含義。j=0,代表集合{ };j=1,代表集合{1}……j=5,代表集合{1,3}……以此類推。
大家可能發現一個問題,在4個頂點{0,1,2,3}中,十進制3代表{1,2},十進制4代表{3},而我們一般寫的集合順序是{},{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}。集合{3}是第3個,{1,2}是第4個。會發現順序亂了,這個要說一下,集合之間的排序不影響此題的解答,讀者朋友可以將集合以任意順序寫,會發現不影響最終結果的出現。

2、判斷一個頂點是否位於子集中
舉例解答,如要判斷集合j={1,3,5,6,7}是否有頂點3。
集合j={1,3,5,6,7}表示成二進制串爲1110101,其中集合裏面有的數對應的位數寫成1,沒有的寫成0。要在集合中找頂點3,就是要判斷二進制串第3位是不是1,就把1110101右移(3-1)位,得到11101,然後結果和00001進行&運算,如果結果是1說明第3位是1,則說明頂點在子集中。
故判斷公式爲

(j>>(i-1))&1==1

3、填寫過程矩陣過程
arc[i][j]爲圖的代價矩陣。
d[i][j]爲過程矩陣,存放迭代結果。
以d[2][5]爲例,d[2][5]=d(2,{1,3})。d[2][5] 表示從2出發,通過{1,3},最後回到起點。那麼d[2][5] = min{arc[2][1] + d(1,{3}),arc[2][3] + d(3,{1})} = min{arc[2][1] + d[1][4],arc[2][3] + d[3][1]}。從2出發,要去{1,3},先考慮去1的路。去了1後集合{1,3}中只剩下{3} ,{3}對應二進制100,十進制4,所以要求的d表就是d[1][4],這個4可以通過(101)^(1)得到,而(1) = 1<<(1-1).其中,二進制101(十進制5)代表集合{1,3};再看去3的路,去了3後集合{1,3}中只剩下{1},{1}對應二進制001,十進制1,所以要求的d表就是d[3][1],1通過(101) ^ (100)得到,而(100) = 1<<(3-1)。故此處又總結出一個公式

d[i][j] = min{arc[i][k] + d[k][j ^ (1 << (k - 1))]}

四、代碼

#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
	int n, i, j, k, m = 1;
	cout << "頂點個數:";
	cin >> n;
	for (i = 1; i < n; i++)//n個頂點有m個子集,m=2^(n-1)
		m = m * 2;
	//創建動態數組,節省空間
	int **arc = new int*[n];//圖的代價矩陣
	for (i = 0; i < n; i++)
		arc[i] = new int[n];
	int **d = new int*[n];//存放迭代結果,即過程矩陣,過程表
	for (i = 0; i < n; i++)
		d[i] = new int[m];
	//輸入圖的代價矩陣
	cout << "請以矩陣形式輸入頂點之間的距離" << endl;
	for (i = 0; i < n; i++)
		for (j = 0; j < n; j++)
			cin >> arc[i][j];
	//糾正用戶輸入的數據
	for (i = 0; i < n; i++)
		arc[i][i] = -1;
	cout << "您輸入的頂點之間的距離如下" << endl;
	for (i = 0; i < n; i++)
	{
		for (j = 0; j < n; j++)
			cout << setw(3) << arc[i][j];
		cout << endl;
	}
	//初始化第0列
	for (i = 0; i < n; i++)
		d[i][0] = arc[i][0];
	//填過程矩陣,第一行,因爲第一行代表從頂點0開始經過一些頂點再回到0,但是我們只需要d[0][m-1]這一個值,所以第一行先不計算,等最後再只計算d[0][m-1],節省時間。
	for (j = 1; j < m; j++)//j就代表m個子集,如j=5(二進制爲101)代表{1,3},j=3(二進制爲011)代表{1,2}。二進制1011就代表集合{1,2,4}。這是設定的一種規則。
	{
		for (i = 1; i < n; i++)
		{
			d[i][j] = 0x7ffff;//設0x7ffff爲無窮大
			if (((j >> (i - 1)) & 1) == 1)	//對於數字x,要看它的第i位是不是1,通過判斷布爾表達式 (((x >> (i - 1) ) & 1) == 1的真值來實現
				continue;					//若第i位是1,就說明子集j裏包含i這個元素,d[i][j]這個空就無需計算
			for (k = 1; k < n; k++)
			{
				/*找出集合j中有哪些元素,比如,(6>>(2-1))&1==1,說明集合{2.3}中有元素2。之所以這麼做,
				是因爲我們人知道j=6(二進制110)代表集合{2.3},但是計算機不知道,所以要有找元素這一步*/
				if (((j >> (k - 1)) & 1) == 0)	//集合中沒有此元素就跳過
					continue;
				/*以d[2][5]爲例,d[2][5]=d(2,{1,3})。d[2][5] 表示從2出發,通過{1,3},最後回到起點。
				那麼d[2][5] = min{arc[2][1] + d(1,{3}),arc[2][3] + d(3,{1})} = min{arc[2][1]  + d[1][4],arc[2][3] + d[3][1]}。
				從2出發,要去{1,3},先考慮去1的路。去了1後集合{1,3}中只剩下{3} ,{3}對應二進制100,十進制4,所以要求的d表就是d[1][4],這個4可以通過(101)^(1)得到,而(1) = 1<<(1-1).其中,二進制101(十進制5)代表集合{1,3};
				再看去3的路,去了3後集合{1,3}中只剩下{1},{1}對應二進制001,十進制1,所以要求的d表就是d[3][1],1通過(101) ^ (100)得到,而(100) = 1<<(3-1)。*/
				if (d[i][j] > arc[i][k] + d[k][j ^ (1 << (k - 1))])
					d[i][j] = arc[i][k] + d[k][j ^ (1 << (k - 1))];
			}
		}
	}
	//計算d[0][m-1]
	for (j = 0; j < m - 1; j++)
		d[0][j] = -1;
	d[0][m - 1] = 0x7ffff;
	for (k = 1; k < n; k++)
	{
		
		if ((( m-1 >> (k - 1)) & 1 ) == 0)	
			continue;
		if (d[0][m - 1] > arc[0][k] + d[k][(m - 1) ^ (1 << (k - 1))])
			d[0][m - 1] = arc[0][k] + d[k][(m - 1) ^ (1 << (k - 1))];
	}

	cout << "最短路徑爲:" << d[0][m-1] << endl;
	cout << "過程矩陣爲:" << endl;
	//輸出過程矩陣
	cout << '\t';
	for (j = 0; j < m; j++)
		cout << j << '\t';
	cout << endl;
	for (i = 0; i < n; i++)
	{
		cout << i << '\t';
		for (j = 0; j < m; j++)
		{
			if (d[i][j] == 0x7ffff)
				d[i][j] = -1;
			cout << d[i][j] << '\t';
		}
		cout << endl;
	}
	getchar();
	getchar();
	return 0;
}

五、結果截圖

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章