算法设计与分析——旅行售货问题——代码分析和讲解

问题描述
  • 某售货员要到4个城市去推销商品,已知各城市之间的路程,如右图所示。
    在这里插入图片描述

  • 请问他应该如何选定一条从城市1出发,经过每个城市一遍,最后回到城市1的路线,使得总的周游路程最小?

思路分析
确定问题的解空间,先表达问题
  • 首先,都必须将输入数据进行保存,针对图这里使用二维矩阵!然后遍历都是从邻接矩阵出发,进行遍历的!

在这里插入图片描述

  • 而我们在使用树型结构进行图的遍历的时候,很容易发现,重复的点,是不需要进行遍历,只有第一个点,是需要重复遍历的!
    在这里插入图片描述
  • 所以,总的来说,就需要两个结构:
    • 邻接矩阵来保存对应图结构
    • 一维矩阵来记录,每一个点是否被访问。
根据题目要求,确定约束函数和上界函数

在这里插入图片描述

  • 约束函数:记录所有已经访问过的点,需要根绝当前状态是否已经是访问过的结点的情况!
    在这里插入图片描述

  • 上限函数:判定当前的状态的价值是否已经超过了对应当前完成的价值!

实现代码与分析
使用程序语言表示图
约束函数和上限函数的定义
//保存当前已知的最短路径,这里先定义一个极大值,一旦比这个值小,就换成更小的值
int minPath = 99;
//保存当前的状态对应的路径,这里总共有四个结点,总共只需要走四次
int path[4];

/*
	描述:旅行售货问题的约束函数和上限函数,确保路径不重复并且路径的价值最小
	参数:m表示当前问题的状态,也是递归地层数,记录m次选择所在的位置结点
		 pathValue:表示当前的状态对应的路径的值
	返回:true:表示当前的结点符合对应的情况
		  false:表示当前的结点不符合对应的情况
	原理:遍历已有的数组,查看是否会重复
		 比较已经有的保存的最大值,进行判定

*/
bool constrainAndBound(int m,int pathValue){

	//约束函数,查看当前的决定是否符合状态
	for (int i = 0; i < m; ++i)
	{
		if (path[m] == path[i])
		{
			return false;
		}
	}

	//上限函数,判定当前的路径值是否是最短的
	
	//如果是没有到达目标路径,已经是比原来的长,可以直接退出了
	if (m < 3)
	{
		pathValue > minPath;
		return false;
	}
	return true;
}
回溯算法回顾
  • 定义问题的解空间,定义问题的所有情况,包括最优解。
  • 确定易于搜索的解空间结构:
    • 子集树
    • 排列树
  • 确定约束函数或者是限界函数
  • 从根节点进行深度遍历搜索解空间
实现源代码
  • 这道题,有点不同,那就是默认是从第一个结点出发,指定了最初的出发点,而且是制定了完整的循环是五个结点,所以遍历的终止条件应该在第五层。
//下述为旅行售货问题的代码

//保存当前已知的最短路径,这里先定义一个极大值,一旦比这个值小,就换成更小的值
int minPath = 99;
//保存当前的状态对应的路径,这里总共有四个结点,总共只需要走四次
int path[5] = { 0 };
//定义一个地点数
int placeNum = 4;
/*
	描述:旅行售货问题的约束函数和上限函数,确保路径不重复并且路径的价值最小
	参数:m表示当前问题的状态,也是递归地层数,记录m次选择所在的位置结点
		 pathValue:表示当前的状态对应的路径的值
	返回:true:表示当前的结点符合对应的情况
		  false:表示当前的结点不符合对应的情况
	原理:遍历已有的数组,查看是否会重复
		 比较已经有的保存的最大值,进行判定

*/
bool constrainAndBound(int m, int pathValue) {

	//约束函数,查看当前的决定是否符合状态
	
	//限制0号结点的位置
	if (m != 4 && path[m] == 0)	return false;

	for (int i = 1; i < m; ++i)
	{
		//限制不能重复
		if (path[m] == path[i])	return false;
	}

	//上限函数,判定当前的路径值是否是最短的

	//在没有到达重点之前,已经比最短的路径要长,就没有意义了
	if (pathValue > minPath)	return false;

	return true;
}

/*
	 描述:旅行售货问题的具体实现
	 参数:m表示对应递归的深度
		  matrix二维矩阵,表示对应矩阵的形状
		  pathValue表示当前的路径长度
	 返回:无返回值,递归调用
	 原理:在递归的过程中生成树并遍历对应的树
*/
void SellPath(int m, int Matrix[4][4], int pathValue) {

	//首先到达递归的尽头,是已经是第四个目标了
	if (m == 5)
	{
		//到达了最底线,直接比较对应长度
		if (pathValue < minPath)	minPath = pathValue;
		//cout << "最短的路径是:" << minPath << endl;
	}
	else {
		//如果是不满足的情况,就进行递归遍历,遍历的是每一个地点的邻接点
		for (int i = 0; i < placeNum; ++i)
		{
			//遍历到当前的状态,就应该遍历到当前状态对应的结点
			//定位到二维矩阵中对应的行,进行遍历,找到当前目标的结点。
			if (Matrix[path[m - 1]][i] != 0)
			{
				//对路程进行累加
				pathValue += Matrix[path[m - 1]][i];
				//状态进行选择,确定当前的状态是否合法
				path[m] = i;
				int temp = path[m];

				//约束条件,进行判定
				if (constrainAndBound(m, pathValue))
				{
					//如果满足递归条件,就进行递归,往下一个方向进行递归
					SellPath(m + 1, Matrix, pathValue);
				}
				//不满足条件,就减去当前的路径
				pathValue -= Matrix[path[m-1]][i];


			}
		}
	}
}

int main(){
cout << "旅游售货问题的展示" << endl;
			cout << "总共有四个结点,分别是1,2,3,4" << endl;
			cout << "总共有6条边,按照长度由低到高分别是4,5,6,10,20,30" << endl;
			//初始化邻接矩阵
			int nodeNum = 4;
			int Matrix[4][4];
			int pathValue = 0;
			for (int i = 0; i < nodeNum; ++i)
			{
				for (int j = 0; j < nodeNum; ++j)
				{
					Matrix[i][j] = 0;
				}
			}
			Matrix[0][1] = 30;
			Matrix[0][2] = 6;
			Matrix[0][3] = 4;
			Matrix[1][0] = 30;
			Matrix[1][2] = 5;
			Matrix[1][3] = 10;
			Matrix[2][0] = 6;
			Matrix[2][1] = 5;
			Matrix[2][3] = 20;
			Matrix[3][0] = 4;
			Matrix[3][1] = 10;
			Matrix[3][2] = 20;
			cout << "输出矩阵如下:" << endl;

			for (int i = 0; i < 4; ++i)
			{
				for (int j = 0; j < 4; ++j)
				{
					cout << Matrix[i][j] << "    ";
				}
				cout << endl;
			}

			SellPath(1, Matrix, pathValue);
			cout << "最终的结果是:" << minPath << endl;
			cout << endl;
			return 0;
}
出现的问题
在遍历的时候,在1号结点和2号结点之间横跳一次,使得最后找不到最终的目标节点
  • 一开始默认第一个结点是0号结点,而且默认最后一个结点也是0号结点,所以,就要在约束函数中加上对应的判定条件,限制0号结点的位置和选择!

在这里插入图片描述

虽然这里有四个结点,但是你要走五个节点,从1出发,最后还要回到1!

在这里插入图片描述

  • 所以记录递归状态的数组,就要是长度为5的一维数组,但是遍历是从第二个结点开始遍历的,第一个默认是0结点!
主要用到两个东西,保存递归状态的一维矩阵和保存数组信息的二维矩阵
  • 注意:上述两者之间的关系是差了一个状态的,m状态的递归选项是由m-1状态的递归选项决定
    在这里插入图片描述
分析与总结
  • 这道题纠缠了很久,想了很多,也发现了很多不一样的地方!关键是这道题需要对解题过程和回溯过程都要有很深的理解!因为他有很多不一样的地方,很多变式!你要根据情况去设定不同的条件进行判定!
  • 这道题确实让我收获很多,但是我的代码不是很好看,至少在我看来是比较乱的!而且质量不高,应该好好修改!但是奈何在思考解答方法这件事上,已经投入了很多的时间,所以没有时间进行修改!
  • 有事请加扣扣!651378276!可以一块学习,进步!
参考代码——参考课件上
template<class T>
//结点构成数组
T Traveling<T>::TSP1(int v[]){

	//这段就是求取最大的累加值
	bestc = 1;
	//n个结点
	for (int i = 1,MaxCost = 0; i <= n; ++i)
	{
		for (int j = 1; j <= n; ++j)
		{
			//遍历所有的结点对应边,来获取某节点最大的边
			if (a[i][j] != noEdge && a[i][j] > MaxCost)
			{
				MaxCost = a[i][j];
			}
		}

			//如果最大的边都是不存在的边,那就是没有边
			if (MaxCost == noEdge)
			{
				return noEdge;
			}

			//对最大的边进行累加
			bestc += MaxCost;
	}


	//这里记录的是货郎售货的路径
	x = new int[n+1];
	for(i = 1;i <= n;i ++){
		x[i] = i;
	}

	//bestc保存对应的结点的序号,路径的结点序号
	bestx = v;
	//用来临时保存路径
	cc = 0;
	//从第二层开始遍历,因为默认第一个结点就是一号结点
	tSP1(2);

	delete []x;
	return bestc;

}

template<class T>
void Traveling<T>::tSP1(int i){

	//递归终止的条件,这里应该是经过n次的选择,又重新回到了1号结点
	if (i == n)
	{
		//这里是和上界进行比较
		//上一个结点到下一个结点是有边的
		//当前结点到一号结点是有边的
		//并且当前已经保存的路径加上上面的两段距离是小于上界的
		if(a[x[n-1][x[n]] != noEdge && a[x[n]][1] != noEdge 
			&& cc+a[x[n-1]][x[n]] + a[x[n]][1] < bestc){

			for (int j = 0; j <= n; ++i)
			{
				bestx[j] = x[j];
			}

			bestc == cc + a[x[n-1]][x[n]] + a[x[n]][1];
		}

	}else{

		//递归向下进行的条件
		for (int j = 0; j <= n; ++j)
		{
			//递归遍历当前路径下的对应的行列式
			//如果当前的递归的结点的上一个结点到j有边的,并且路径是小于最短路径的
			//将该点和目标路径进行交换
			if(a[x[i-1]][x[j]] != noEdge && (cc + a[x[i-1]][x[j]] < bestc)){
				Swap(x[i],x[j]);
				cc += a[x[i-1][x[i]]];
				//向下进行深一步的递归
				tSP1(i+1);
				cc -= a[x[i-1]][x[i]];
				Swap(x[i],x[j]);
			}
		}


	}

}
  • 我觉得上面的思路很棒,但是理解了很久,交换数组来记录遍历的顺序会不会很累?比较难想。
  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值