《算法竞赛》学习记录之状态压缩dp旅行商TSP问题

本文介绍了旅行商问题的背景和解决思路,这是一个经典的NP问题。通过动态规划方法,利用二进制状态压缩降低时间复杂度,最终达到2^n×(n×n)。文中给出了详细的状态转移方程和代码片段。

旅行商问题

问题:有n个城市,已知任何两个城市之间的距离(或者费用),一个旅行商从某城市出发,经过每一个城市并且只经过一次,最后回到出发的城市,输出最短(或者费用最少)的线路。
背景:旅行商问题是一个经典的NP问题,不存在多项式时间内的解,使用暴力法时间复杂度将达到n!,但是可以使用动态规划来解,时间复杂度为2^n×(n×n)。(模板的TSP问题应该可以使用模拟退火算法解决,但是还没有写)。
思路
对于暴力法,每次选择一个城市进行选择,然后枚举下一个城市,第一次选择次数为n,第二次选择次数为n-1,直到第n次为1个选择,时间为n*(n-1)…1=n!。
动态规划设计如下:
由于一个城市只有两种状态,即走或者不走,所以使用二进制来表示将节省大量,我们设dp[S][v]表示此时已走过的城市(或待走的城市)状态为S,其中S的二进制中第i为1则表示已走(或未走),为0则未走(或已走);其值为到达这种状态(即此时走过城市状态为S,目前在v号城市这种状态)所需的最少花费;例如此时有四个城市,S=6=二进制下的0100,此时已走过了三号城市,还未走一号二号四号城市。目的状态为dp[(1<<n)-1][0];(1<<n)-1的值为11…111,即连续n个1。
状态转移方程如下:
dp[0][0]=0;//表示目前一个城市也没有走,且当前在初始城市,此时花费为0
dp[S][v]=min(dp[S][v],dp[S-(1<<u)][u]);//其中u为S已走过的一个城市,在第u位上为1,表示走过城市状态为S,目前在v号城市这种状态从走过城市状态为S’,目前在u号城市这种状态经过一步u->v得到;其中S’为S-{u}。
代码如下

import java.util.Arrays;
import java.util.Scanner;

public class Main2 {
	static int[][] dis;
	static int[][] dp;
	static int n;
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Scanner sc=new Scanner(System.in);
		System.out.println("输入多少个城市 多少条道路:");
		n=sc.nextInt();
		dp=new int[1<<n][n];
		dis=new int[n][n];
		int m=sc.nextInt();
		for(int i=0;i<n;i++) {
			Arrays.fill(dis[i],10000);
			dis[i][i]=0;
		}
		System.out.println("输入从 i 到 j 的值为 x");
		for(int i=0;i<m;i++) {
			int x=sc.nextInt();
			int y=sc.nextInt();
			int val=sc.nextInt();
			dis[x][y]=dis[y][x]=val;
		}
		solve();
	}
	static void solve() {
		for(int i=0;i<1<<n;i++)
			Arrays.fill(dp[i],10000);
		dp[0][0]=0;
		for(int s=0;s<(1<<n);s++) {	//枚举所有城市状态,可以证明大的状态一定由比它小的状态得到
			for(int v=0;v<n;v++) {	//枚举当前城市
				for(int u=0;u<n;u++) {	//枚举之前一步所在城市
					if((s>>u&1)!=0)	//之前一步必须为走过的城市,当前城市却可以是走过也可以是没走过
						dp[s][v]=Math.min(dp[s][v],dp[s-(1<<u)][u]+dis[u][v]);
//					System.out.println(s+" "+u+" "+dp[s][u]);
				}
			}
		}
		if(dp[(1<<n)-1][0]!=10000)
			System.out.println(dp[(1<<n)-1][0]);
		else
			System.out.println("无解");
	}
}

PS:其中v的选择既可以是走过的城市或者是未走过的城市,因为最后我们需要求dp[(1<<n)-1][0]的值,最后时候要回到初始节点,所以最后将会选择一个已走过的城市。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值