[关闭]
@zsh-o 2019-04-15T21:37:52.000000Z 字数 4434 阅读 1118

旅行商问题 与 状态压缩

算法


问题描述

上周末遇到的某笔试题,就是经典的旅行商问题,以前没写过,现在写下来记录一下

问题大意是,毕业了要进行毕业旅行,打算从北京出发,途径几个城市,最后回到北京。要求每个沿途城市只去一次,并且最终花费最小。

输入:
  1. 4
  2. 0 2 6 5
  3. 2 0 4 4
  4. 6 4 0 2
  5. 5 4 2 0
输出:
  1. 13

思路

由于我没有接触过旅行商问题的解法,所以第一时间是想到会的维特比算法,同样是在状态间跳转求极值,但与维特比算法不同的是,维特比算法在每一个时间点可以取所有状态,而旅行商问题有一个每一个城市只去一次的约束,也就是每个在整个过程中每一个状态只能出现一次。他们两个都可以看成是一条条不同的路径,维特比算法的总路径数为(|c|个状态,时间长度为),旅行商问题总路径数为为中间城市数,对所有中间城市全排列。旅行商问题相当于维特比算法加了一个每个状态只能出现一次的约束。对于维特比算法的详细阐述和应用详见HMM部分。现尝试能否从维特比算法扩展得到旅行商问题的解法,维特比算法可表示为
image.png-33.6kB

需要维护一个二维数组表示为时刻状态为的最小代价,其同样表示了从时间段的一条最小路径,该路径时刻的状态为,并且其使代价最小,其更新公式为

其中表示状态跳转到状态的代价。
这里需要特别提一句的是不止代表了一个最小,其还表征了一条从时刻到时刻的最小路径,并且

到这里,旅行商问题开始出现不一样的地方,其要求每个状态只能出现一次,现在需要做的是如何在更新状态的过程中保证该约束。

更新公式附加上该约束以数学形式写出来为

表示对应的最短路径状态序列,只需要保证当前时刻的要更新的状态不在更新公式的时刻的最短路径状态序列中。当然

从公式可以看到,不需要关注整个全排列组成的状态路径,而只需要知道状态的集合即可,更新公式不直接与路径顺序有关,相当于用每个时间点的所有集合状态去更新去更新下一个时刻的状态。由此,根据全排列得到的路径求及值时可以用把庞大数量的全排列数转换成集合

那么状态更新可以表示成集合的形式表示经过集合状态到达状态的最小代价,则其可以表征成终点是把集合全排列的所有路径中代价最小的路径。其实这一个转换是整个过程中最最最最重要的部分,其他的包括后面的状态压缩都可以看成是,而这一个转换是最难想的,前人能够想到真是优秀

现在看旅行商问题,表示从北京出发,经过城市集合到城市的最小代价,其更新公式为

其中,表示从集合中删掉,更新中间城市为集合目标城市为的最小代价,只需要保证是最小的就可以保证整个式子是对的,那么只需要保证更新顺序从小到大(城市数)依次更新即可,并且每次更新完当前集合的所有状态,例如更新完一个城市的再更新2个城市的再是三个城市的,,,,这样假设有个中间城市,则总共的集合数为

然后大佬们发现,可以用二进制表示每个城市在不在集合里面,这样一个集合就可以用一个二进制表示,并且该二进制表示的从小到大的顺序也符合上面状态更新要求的顺序,例如有个城市的二进制集合状态为,更新时抽取的中间城市需要在集合里面,故筛掉的所有集合为,其肯定满足比其他的都要大,因为相当于用1个0替换掉中的一个1,得到的值肯定比小,更新的状态需要让的状态已经是最优的,由于在二进制顺序中他们都要比小已经被更新过,所以成立,这就是状态压缩

判断不在状态集合里面:!(S & (1<<c))
判断在状态集合里面:S & (1<<k)
S ^ (1<<k)

代码

  1. #include <stdio.h>
  2. #include <string.h>
  3. const int MAXN = 20 + 1;
  4. const int MAXS = 1000 + 1;
  5. int F[1 << (MAXN - 1)][MAXN];
  6. int S[MAXN][MAXN];
  7. int _min(int a, int b);
  8. int main() {
  9. memset(F, 0x00, sizeof(F));
  10. memset(S, 0x00, sizeof(S));
  11. int n, s;
  12. scanf("%d", &n);
  13. for(int i=0; i<n; i++) {
  14. for(int j=0; j<n; j++) {
  15. scanf("%d", &s);
  16. S[i][j] = s;
  17. }
  18. }
  19. int maxn = (1 << n) - 1;
  20. for(int i=1; i<=maxn; i++) {
  21. for(int c=0; c<n; c++) {
  22. F[i][c] = MAXS;
  23. }
  24. }
  25. for(int c=0; c<n; c++) {
  26. F[0][c] = S[0][c];
  27. }
  28. int iters = 0;
  29. for(int state=1; state<=maxn; state++) {
  30. for(int c=0; c<n; c++) {
  31. // c must not in state
  32. if(!(state & (1<<c))) {
  33. for(int k=1; k<n; k++) {
  34. // k must in state
  35. if((state & (1<<k))){
  36. // delete k in state with xor
  37. int current = state ^ (1<<k);
  38. F[state][c] = _min(F[state][c], F[current][k] + S[k][c]);
  39. iters += 1;
  40. }
  41. }
  42. }
  43. }
  44. }
  45. // print for debug
  46. printf("<< F >>\n");
  47. for(int state=0; state<=maxn; state++){
  48. for(int c=0; c<n; c++){
  49. printf("%d \t", F[state][c]);
  50. }
  51. printf("\n");
  52. }
  53. printf("<< F - End >>\n");
  54. //
  55. printf("%d\n", F[maxn-1][0]);
  56. //
  57. printf("Iters: %d", iters);
  58. }
  59. int _min(int a, int b){
  60. return a < b ? a : b;
  61. }
  62. /*
  63. 4
  64. 0 2 6 5
  65. 2 0 4 4
  66. 6 4 0 2
  67. 5 4 2 0
  68. */

输出

  1. 4
  2. 0 2 6 5
  3. 2 0 4 4
  4. 6 4 0 2
  5. 5 4 2 0
  6. << F >>
  7. 0 2 6 5
  8. 1001 1001 1001 1001
  9. 4 1001 1001 1001
  10. 1001 1001 1001 1001
  11. 12 1001 1001 1001
  12. 1001 1001 1001 1001
  13. 1001 1001 1001 1001
  14. 1001 1001 1001 1001
  15. 10 1001 1001 1001
  16. 1001 1001 1001 1001
  17. 1001 1001 1001 1001
  18. 1001 1001 1001 1001
  19. 1001 1001 1001 1001
  20. 1001 1001 1001 1001
  21. 1001 1001 1001 1001
  22. 1001 1001 1001 1001
  23. << F - End >>
  24. 1001
  25. Iters: 36

优化

上面状态包含了北京,而只需要是中间城市即可

  1. #include <stdio.h>
  2. #include <string.h>
  3. const int MAXN = 20 + 1;
  4. const int MAXS = 1000 + 1;
  5. int F[1 << (MAXN - 1)][MAXN];
  6. int S[MAXN][MAXN];
  7. int _min(int a, int b);
  8. int main(){
  9. memset(F, 0x00, sizeof(F));
  10. memset(S, 0x00, sizeof(S));
  11. int n, s;
  12. scanf("%d", &n);
  13. for(int i=0; i<n; i++){
  14. for(int j=0; j<n; j++){
  15. scanf("%d", &s);
  16. S[i][j] = s;
  17. }
  18. }
  19. int maxn = (1 << (n - 1)) - 1;
  20. for(int i=1; i<maxn; i++){
  21. for(int c=0; c<n-1; c++){
  22. F[i][c] = MAXS;
  23. }
  24. }
  25. for(int c=0; c<n-1; c++){
  26. F[0][c] = S[0][c + 1];
  27. }
  28. int iters = 0;
  29. for(int state=1; state<maxn; state++){
  30. for(int c=0; c<n-1; c++){
  31. // c must not in state
  32. if(!(state & (1<<c))){
  33. for(int k=0; k<n-1; k++){
  34. // k must in state
  35. if(state & (1<<k)){
  36. // delete k in state with xor
  37. int current = state ^ (1<<k);
  38. F[state][c] = _min(F[state][c], F[current][k] + S[k+1][c+1]);
  39. iters += 1;
  40. }
  41. }
  42. }
  43. }
  44. }
  45. int res = MAXS;
  46. for(int k=0; k<n-1; k++){
  47. int c = maxn ^ (1<<k);
  48. res = _min(res, F[c][k] + S[k+1][0]);
  49. iters += 1;
  50. }
  51. // print for debug
  52. printf("<< F >>\n");
  53. for(int state=0; state<maxn; state++){
  54. for(int c=0; c<n-1; c++){
  55. printf("%d \t", F[state][c]);
  56. }
  57. printf("\n");
  58. }
  59. printf("<< F - End >>\n");
  60. //
  61. printf("%d\n", res);
  62. //
  63. printf("Iters: %d\n", iters);
  64. }
  65. int _min(int a, int b){
  66. return a < b ? a : b;
  67. }
  68. /*
  69. 4
  70. 0 2 6 5
  71. 2 0 4 4
  72. 6 4 0 2
  73. 5 4 2 0
  74. */

输出

  1. 4
  2. 0 2 6 5
  3. 2 0 4 4
  4. 6 4 0 2
  5. 5 4 2 0
  6. << F >>
  7. 2 6 5
  8. 1001 6 6
  9. 10 1001 8
  10. 1001 1001 8
  11. 9 7 1001
  12. 1001 8 1001
  13. 11 1001 1001
  14. << F - End >>
  15. 13
  16. Iters: 15
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注