算法设计与分析:第五章 回溯法 5.1TSP之货郎担问题

/*
货郎担问题:
四个顶点的货郎担问题。求从顶点1出发,
最后回到顶点1的最短路线。
	v1		v2		v3		v4
v1	无穷	无穷	1		7
v2	8		无穷	5		1
v3	7		2		无穷	1
v4	2		5		3		无穷

算法分析:
因为是采用回溯法来做,肯定是递归,然后还需要现场清理。
要设置一个二维数组来标识矩阵内容,然后回溯还需要设计
一个二维标记数组来剪枝,设定一个目标变量,初始为无穷大, 
后续如果有比目标变量值小的就更新。剪枝的条件就是如果走到当前节点的耗费值>=目标变量,就直接不再往下面走,
向上走。

深度优先 = 递归
递归基:如果到达叶子节点的上一个节点,那么就进行是否更新的判断
递归步:如果没有到达叶子节点,就进行剪枝操作,判断能否进入下一个节点,如果能,更新最优值


输入:
4
0 0  1 7
8 0	 5 1
7 2  0 1
2 5  3 0
输出:
1 3 2 4 1
6
*/

/*
关键:
1 	//递归基:如果已经遍历到叶子节点的上一层节点,i标识递归深度
	if(i == g_n)
	{
		//判断累加和是否超过最大值,如果有0,应该排除;满足这个条件,才打印
		if((g_iArr[pArr[i-1]][pArr[i]] != 0) && (g_iArr[pArr[g_n]][1] != 0)  && 
			(g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1] < g_iResult ))
		{
			g_iResult = g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1];
			//用当前最优路径去更新最优路径,防止下一次没有
			for(int k = 1 ; k <= g_n ; k++)
			{
				g_iBestPath[k] = pArr[k];

2 	//递归步:判断能否进入子树,需要尝试每一个节点
	else
	{
		//尝试不同的组合
		for(int j = i ; j <= g_n ; j++)
		{
			//判断能否进入子树:如果当前值+下一个连线值的和 < 最优值,就进入,0要pass
			if( (g_iArr[pArr[i-1]][pArr[j]] != 0) && (g_iCurResult + g_iArr[ pArr[i-1] ][ pArr[j] ] < g_iResult) )

3 				//交换i与j,则i为当前可以尝试的范围
				//为完成后面k个元素的排列,逐一对数组第n-k~n个元素互换。数组第一个元素为1,生成后面n-1个元素的排列
				//数组第一个元素与第二个元素互换,第一个元素为2,第2个元素为1,生成后面的n-1个元素的排列...
				swap(&pArr[i],&pArr[j]);
				//更新当前累加值,是i-1与i的
				g_iCurResult += g_iArr[ pArr[i-1] ][ pArr[i] ];
				//递归
				backTrace(i+1,pArr);
				//回溯,清空累加值;能够走到这里,说明上述结果不是最优解,需要向求解树上一层回退
				g_iCurResult -= g_iArr[pArr[i-1]][ pArr[i] ];
				swap(&pArr[i],&pArr[j]);
*/
#include 
#include 

const int MAXSIZE = 100;
const int MAX = 1000000000;
int g_iArr[MAXSIZE][MAXSIZE];//邻接矩阵
int g_iResult;//存放最优解
int g_iPath[MAXSIZE];//存放最优路径上
int g_n;//元素个数
int g_iCurResult;//当前累加路径和
int g_iBestPath[MAXSIZE];//还需要设置一个数组,用来保存最优解

void swap(int* pI,int* pJ)
{
	int iTemp = *pI;
	*pI=  *pJ;
	*pJ = iTemp;
}

void printResult(int n,int* pArr)
{
	printf("%d\n",g_iResult);
	for(int i = 1 ; i <= n ; i++)
	{
		if( i != 1)
		{
			printf(" %d",pArr[i]);
		}
		else
		{
			printf("%d",pArr[i]);
		}
	}
	printf(" 1\n");
}

//可以做成字符串全排列的性质track(int i,int* pArr,int* pResult),其中pArr是用于存放最优解的路径
void backTrace(int i,int* pArr)
{
	//递归基:如果已经遍历到叶子节点的上一层节点
	if(i == g_n)
	{
		//判断累加和是否超过最大值,如果有0,应该排除;满足这个条件,才打印
		if((g_iArr[pArr[i-1]][pArr[i]] != 0) && (g_iArr[pArr[g_n]][1] != 0)  && 
			(g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1] < g_iResult ))
		{
			g_iResult = g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1];
			//用当前最优路径去更新最优路径,防止下一次没有
			for(int k = 1 ; k <= g_n ; k++)
			{
				g_iBestPath[k] = pArr[k];
			}
		}
	}
	//递归步:判断能否进入子树,需要尝试每一个节点
	else
	{
		//尝试不同的组合
		for(int j = i ; j <= g_n ; j++)
		{
			//判断能否进入子树:如果当前值+下一个连线值的和 < 最优值,就进入,0要pass
			if( (g_iArr[pArr[i-1]][pArr[j]] != 0) && (g_iCurResult + g_iArr[ pArr[i-1] ][ pArr[j] ] < g_iResult) )
			{
				//交换i与j,则i为当前可以尝试的范围
				//为完成后面k个元素的排列,逐一对数组第n-k~n个元素呼唤。数组第一个元素为1,生成后面n-1个元素的排列
				//数组第一个元素与第二个元素互换,第一个元素为2,第2个元素为1,生成后面的n-1个元素的排列...
				swap(&pArr[i],&pArr[j]);
				//更新当前累加值,是i-1与i的
				g_iCurResult += g_iArr[ pArr[i-1] ][ pArr[i] ];
				//递归
				backTrace(i+1,pArr);
				//回溯,清空累加值;能够走到这里,说明上述结果不是最优解,需要向求解树上一层回退
				g_iCurResult -= g_iArr[pArr[i-1]][ pArr[i] ];
				swap(&pArr[i],&pArr[j]);
			}
		}
	}
}

void process()
{
	//初始化
	while(EOF != scanf("%d",&g_n))
	{
		//获取输入
		for(int i = 1 ; i <= g_n ; i++)
		{
			for(int j = 1 ; j <= g_n ; j++)
			{
				scanf("%d",&g_iArr[i][j]);
			}
		}
		//初始化根节点,最优路径初始化
		for(int j = 1 ; j <= g_n ; j++)
		{
			g_iPath[j] = j;
		}
		g_iResult = MAX;
		g_iCurResult = 0;
		backTrace(2,g_iPath);
		printResult(g_n,g_iBestPath);
	}
}

int main(int argc,char* argv[])
{
	process();
	getchar();
	return 0;
}

你可能感兴趣的:(算法设计与分析)