动态规划 TSP 问题

标签: 动态规划问题
14人阅读 评论(0) 收藏 举报

    TSP 问题是旅行商从一个城市出发,各个城市仅经过一次,最后回到出发的城市,求出最短的路径的距离。使用动态规划解决问题,首先需要证明问题具有最优子结构性质。可以使用反证法证明TSP 问题具有最优子结构性质。

    1. 问题假设

        假设有4个城市,编号分别为0、1、2、3。设distance(k,V')  且V' = V-k 表示从顶点i出发,经过V'集合中的每个点,并且只经过一次最后回到出发点i的最短距离。所以可以有下面的表达式:

           d(k,{}) = cki  以及d(i,V') = min(cik+d(k,V'-{k}))  (k 属于V')

    2. 算法描述

        首先使用n*n的二维矩阵表示城市之间的距离,如1行2列表示城市1到城市2的距离。将城市到自己的距离设为最大值。使用另一个矩阵,行表示各个城市,列表示城市集合的各个子集,按照子集的大小排列。求解某个点开始经过集合中的所有的点一次的最短距离可以通过比较各个自问题获得。即:distance(i,V') = min(cik,distance(k,V''))  其中k 为V' 集合中任意取出的一个顶点,V'表示从总集合中减去i点的集合,V'' 表示在V' 的基础上减去取出的顶点k的集合。

    3. 实现代码

        代码实现中,在集合的表示与子集的获得上遇到了一些问题。之后先通过全排列获得所有的组合,之后再全排列中加上判断,即当前附加的顶点的编号必须大于前面的编号,最后就获得了某个集合的所有的子集。之后再对子集拍一下序就可以了。下面是获得所有子集的方法:

	void getSubsets(deque<int> &seq,bool *visited,int depth,int n) {
		if (depth == 0) {
			subsets.push_back(deque<int>());	//加入空集合
			getSubsets(seq, visited, depth + 1, n);
		}
		else if (depth >= n) {
			//已经达到了最大的深度
			return;
		}
		else {
			for (int i = 1; i < n; ++i) {
				//判断是否访问过,比较大小,防止出现顺序不同但集合元素相同的情况
				if (!visited[i]) {
					if (!seq.empty() && i < seq.back())
						continue;
					visited[i] = true;
					seq.push_back(i);
					subsets.push_back(*new deque<int>(seq));	//保存当前的序列
					getSubsets(seq, visited, depth + 1, n);
					visited[i] = false;
					seq.pop_back();
				}
			}
		}
	}

下面是具体的实现代码。在求解的前面的各个阶段,集合中没有包括起始顶点v0,直到最后才求解distance(v0,V)。

#include <deque>
#include <iostream>
#include <cmath>
#include <fstream>
#include <algorithm>

using namespace std;

class Solution {
private:
	/**
	*	distance 为该结构体的二维矩阵
	*	用于保存每个子问题的最短路径以及依赖的前一个子问题在distance 矩阵中的坐标
	*/
	struct distanceEle {
		int minVal;
		int prei;
		int prej;

		distanceEle() :minVal(0), prei(0), prej(0) {}
	};


	/**
	*	seq 当前的序列
	*	visited 标识当前访问过的元素
	*	depth 当前的深度 从0开始
	*	n 最大的深度
	*/
	void getSubsets(deque<int> &seq,bool *visited,int depth,int n) {
		if (depth == 0) {
			subsets.push_back(deque<int>());	//加入空集合
			getSubsets(seq, visited, depth + 1, n);
		}
		else if (depth >= n) {
			//已经达到了最大的深度
			return;
		}
		else {
			for (int i = 1; i < n; ++i) {
				//判断是否访问过,比较大小,防止出现顺序不同但集合元素相同的情况
				if (!visited[i]) {
					if (!seq.empty() && i < seq.back())
						continue;
					visited[i] = true;
					seq.push_back(i);
					subsets.push_back(*new deque<int>(seq));	//保存当前的序列
					getSubsets(seq, visited, depth + 1, n);
					visited[i] = false;
					seq.pop_back();
				}
			}
		}
	}

	static bool cmp(const deque<int> &obj1, const deque<int> &obj2) {
		if (obj1.size() < obj2.size())
			return true;
		else
			return false;
	}

	/**
	*	用于求出某个集合关于总的集合的补集
	*/
	deque<int> getsupplementSet(deque<int> &fullSet, deque<int> &subset) {
		//首先对集合排序
		sort(fullSet.begin(), fullSet.end());
		sort(subset.begin(), subset.end());

		deque<int>::iterator iterFull = fullSet.begin(), iterSub = subset.begin();
		deque<int> result;
		
		while (iterSub != subset.end()) {
			if (*iterFull < *iterSub) {
				result.push_back(*iterFull);
				++iterFull;
			}
			else {
				++iterFull, ++iterSub;
			}
		}

		while (iterFull != fullSet.end()) {
			result.push_back(*iterFull);
			++iterFull;
		}

		return result;
	}


	/**
	*	在subsets 中定位subset 的坐标
	*/
	int locateSubsetIndex(deque<int> subset) {
		bool flag = true;
		for (int i = 0; i < subsets.size(); ++i) {
			if (subset.size() != subsets[i].size())
				continue;
			flag = true;
			for (int j = 0; j < subsets[i].size(); ++j) {
				if (subsets[i][j] != subset[j])
					flag = false;
			}
			if (flag == true)
				return i;
		}
		return -1;
	}

	/**
	*	依次解决子问题
	*/
	void dynamicProgram(int n,deque<int> fullset) {
		deque<int> supplementSet;
		for (int i = 1; i < subsets.size(); ++i) {
			supplementSet = getsupplementSet(fullset, subsets[i]);	//获取当前子集的补集

			for (auto ele : supplementSet) {		//对补集中的每一个元素进行操作
				int tmp;
				deque<int> tmpSet;
				distance[ele][locateSubsetIndex(subsets[i])].minVal = INT_MAX;	//先初始化一个大的值

				for (int j = 0; j < subsets[i].size(); ++j) {
					tmpSet = deque<int>(subsets[i]);
					tmp = tmpSet[j];
					tmpSet.erase(tmpSet.begin() + j);
					//记录基于前面的子问题的最短路径
					if (arc[ele][tmp] + distance[tmp][locateSubsetIndex(tmpSet)].minVal 
                                                    < distance[ele][locateSubsetIndex(subsets[i])].minVal) {
						distance[ele][locateSubsetIndex(subsets[i])].minVal = 
                                                    arc[ele][tmp] + distance[tmp][locateSubsetIndex(tmpSet)].minVal;
						distance[ele][locateSubsetIndex(subsets[i])].prei = tmp;
						distance[ele][locateSubsetIndex(subsets[i])].prej = 
                                                     locateSubsetIndex(tmpSet);
					}
				}
			}
		}
	}

	/**
	*	处理动态规划的最后一个阶段
	*	distanceColNum 表示distance 的列的个数
	*/
	void endProgram(deque<int> fullSet,int distanceColNum) {
		int tmp;
		deque<int> tmpSet;

		distance[0][distanceColNum - 1].minVal = INT_MAX;
		for (int i = 0; i < fullSet.size(); ++i) {
			tmpSet = deque<int>(fullSet);
			tmp = tmpSet[i];
			tmpSet.erase(tmpSet.begin() + i);
			if (arc[0][tmp] + distance[tmp][locateSubsetIndex(tmpSet)].minVal
                                     < distance[0][distanceColNum - 1].minVal) {
				distance[0][distanceColNum - 1].minVal = arc[0][tmp] + 
                                            distance[tmp][locateSubsetIndex(tmpSet)].minVal;
				distance[0][distanceColNum - 1].prei = tmp;
				distance[0][distanceColNum - 1].prej = locateSubsetIndex(tmpSet);
			}
		}
	}
	

public:
	deque<deque<int>> subsets;	//所有的子集的集合
	int **arc;	//带权的矩阵
	distanceEle **distance;	//动态规划中的填表

	void findShortest() {
		ifstream infile("input.txt");
		int n = 0;
		infile >> n;
		bool *visited = new bool[n];
		for (int i = 0; i < n; ++i)
			visited[i] = false;
		deque<int> seq;
		getSubsets(seq, visited, 0, n);
		sort(subsets.begin(), subsets.end(), &Solution::cmp);	//对子集进行排序

		//读取带权图的矩阵
		int tmp = 0;
		arc = new int*[n];
		for (int i = 0; i < n; ++i) {
			arc[i] = new int[n];
			for (int j = 0; j < n; ++j) {
				infile >> tmp;
				if (tmp == -1)
					tmp = INT_MAX;
				arc[i][j] = tmp;
			}
		}
		infile.close();

		//测试

		cout << "遍历arc :" << endl;
		for (int i = 0; i < n; ++i) {
			for (int j = 0; j < n; ++j) {
				cout << arc[i][j] << "  ";
			}
			cout << endl;
		}

		cout << "遍历子集:" << endl;
		for (int i = 0; i < subsets.size(); ++i) {
			for (auto ele : subsets[i])
				cout << ele << "  ";
			cout << endl;
		}
		deque<int> fullSet;
		for (int i = 1; i < n; ++i)
			fullSet.push_back(i);
		//求取各个子集的补集
		cout << "对应的补集为:" << endl;
		for (int j = 0; j < subsets.size(); ++j) {
			for (auto ele : getsupplementSet(fullSet, subsets.at(j))) {
				cout << ele << "  ";
			}
			cout << endl;
		}
		///
		//初始化distance
		distance = new distanceEle*[n];
		for (int i = 0; i < n; ++i) {
			distance[i] = new distanceEle[subsets.size()];
		}
		//初始化distance 第0列,1~n-1行
		for (int i = 1; i < n; ++i)
			distance[i][0].minVal = arc[i][0];
		//对求出来的各个子集,按照大小,从空集合开始动态规划
		dynamicProgram(n, fullSet);
		endProgram(fullSet, subsets.size());
		//测试输出distance
		cout << "遍历distance :" << endl;
		for (int i = 0; i < n; ++i) {
			for (int j = 0; j < subsets.size(); ++j) {
				cout << "(" << distance[i][j].minVal << ", " << 
                                        distance[i][j].prei << ", " << distance[i][j].prej
					<< ")   ";
			}
			cout << endl;
		}

	}


	/**
	*	输出最短路径
	*/
	void outputShortestPath(int distanceColNum) {
		int indexi = 0, indexj = distanceColNum - 1;
		int i, j;

		while (indexj > 0) {
			cout << indexi << "---->";
			i = distance[indexi][indexj].prei;
			j = distance[indexi][indexj].prej;
			indexi = i;
			indexj = j;
		}
		cout << indexi << endl;
	}
	

};

int main()
{
	Solution solution;
	solution.findShortest();
	solution.outputShortestPath(solution.subsets.size());
    return 0;
}

程序使用的输入文件的内容:


运行的截图:



参考:《算法设计与实现》(第二版)




查看评论

用动态规划解决TSP问题

include"stdio.h" #include"stdlib.h" #define MIN(a,b) (a
  • shiyang6017
  • shiyang6017
  • 2016-03-30 16:33:41
  • 1811

动态规划求解TSP(旅行商)问题

某推销员要从城市v1出发,访问其它城市v2,v3,…,v6各一次且仅一次,最后返回v1。D为各城市间的距离矩阵。问:该推销员应如何选择路线,才能使总的行程最短?   1、变量设定 阶段k...
  • masikkk
  • masikkk
  • 2012-11-28 20:29:40
  • 9501

动态规划方法解旅行商问题(TSP Traveling Salesperson Problem)

本文依照具体例子说明如何用动态规划算法解tsp货郎商问题 网上很多相关文章介绍的时候都缺乏例子,以至于太抽象不够直观。本文用具体例子来介绍动态规划法解决tsp。tsp是为了在一个图中求得一个最优路径,...
  • HelloNerd
  • HelloNerd
  • 2016-03-18 08:27:17
  • 6589

旅行售货员问题(TSP)的动态规划算法(递归)

  • 2010年06月11日 20:02
  • 2KB
  • 下载

TSP(旅行者问题)——动态规划详解

原文地址: http://blog.csdn.net/gfaiswl/article/details/4749713 1.问题定义       TSP问题(旅行商问题)是指旅行家要旅行...
  • Derek_Tian
  • Derek_Tian
  • 2015-04-15 12:05:46
  • 5084

动态规划法解决TSP问题(C++)

/*旅行商问题(Traveling Saleman Problem,TSP)又译为旅行推销员问题、货郎担问题,简称为TSP问题,是最基本的路线问题,该问题是在寻求单一旅行者由起点出发,通过所有给定的需...
  • rootsongjc
  • rootsongjc
  • 2011-12-19 10:32:01
  • 9147

TSP:旅行商问题与内存优化的动态规划

旅行商问题(TSP)的内存优化
  • ljhandlwt
  • ljhandlwt
  • 2016-10-21 09:02:10
  • 2198

TSP问题动态规划解决

#include using namespace std; double dp[21][1100000]; int n; struct City { double x; double...
  • ghsy950525
  • ghsy950525
  • 2016-05-09 12:27:34
  • 176

用动态规划方法旅行商问题(TSP问题)

某推销员要从城市v1 出发,访问其它城市v2,v3,…,v6 各一次且仅一次,最后返回v1。D为各城市间的距离矩阵。问:该推销员应如何选择路线,才能使总的行程最短?   以下是用动态规划方法,Linu...
  • zouxinfox
  • zouxinfox
  • 2007-12-04 21:50:00
  • 27443

动态规划法解旅行商问题(TSP)问题的java实现

  • 2012年05月08日 21:22
  • 12KB
  • 下载
    个人资料
    持之以恒
    等级:
    访问量: 1846
    积分: 164
    排名: 104万+
    文章分类