推销员最优路径问题
- 前言
推销员路径问题属于无向图的Hamilton cycle 问题,与Hamilton问题不同的是,推销员路径一定存在从起点出发,路径每个城市一次且仅有一次,最后回到起点城市的最短路径。问题可以归纳为,求解从出发点到其它各个顶点(不包含出发点)的最短距离min_distance, 然后计算上末端顶点至出发点的距离。
- 问题分析
把问题分割为两部分,集合一为当前的城市编号,集合二为已经过的城市(1次且仅1次)集合,DP[current][visited cities collections]表示经过访问过的城市以后,到达当前城市的距离,当前经过的城市可以用某个状态整数表示,例如0011表示经过了城市0和城市1,同理1001表示经过城市3和城市0,它表示已经访问的城市集合,如果整数中某位上为1,那么代表此城市已访问,所有由1组成的位数表示已经访问的城市的集合。
状态转移方程可以表示为:
d
p
[
i
]
[
v
i
s
i
t
e
d
s
e
t
s
]
=
m
i
n
{
c
[
i
]
[
k
]
+
d
p
[
k
]
[
v
i
s
i
t
e
d
s
e
t
s
w
i
t
h
o
u
t
k
]
}
;
dp[i][visited\ sets]=min\{c[i][k]+dp[k][visited\ sets\ without\ k]\};
dp[i][visited sets]=min{c[i][k]+dp[k][visited sets without k]};
具体状态如上图所示。
- 问题求解
a.) 头文件定义
其中函数void tsp_path(int matrix[N][N], int memo[N][1 << N], int *tour, int n, int start)的matrix数组表示无向图,它的值表示两个城市之间的所产生的的费用,由于地理和交通影响,一般情况下,A->B和B->A的之间的费用并不严格相等,数组memo表示dp数组,第一个N代表元素总数,就本问题而言代表N个城市,后面1<<N代表每个城市所对应的状态,由于每个城市都有两个状态,所以总的状态为2的N指数数字。
函数void tsp_setup(int matrix[N][N], int memo[N][1 << N], int n, int start)对DP数组进行初始化,默认城市0(A)为出发城市,假定只有4个城市A,B,C,D;此时有
DP[B][0011]=matrix[A][B]
DP[C][0101]=matrix[A][C]
DP[D][1001]=matrix[A][D]
函数void tsp_solve(int matrix[N][N], int memo[N][1 << N], int n, int start) 是本问题的主要处理函数,它的处理流程为,对visited sets状态进行枚举,然后判断start, end以及next是否在这个状态当中,如果在状态当中,那么就根据大小关系求出next 的对应当前状态下的最小值。
/**
* @file tsp_problem.h
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-03-05
*
* @copyright Copyright (c) 2023
*
*/
#ifndef TSP_PROBLEM_H
#define TSP_PROBLEM_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define N 4
#define INFINITY 99999
/**
* @brief find the minimum cost path from start city to start city
* go through each city only once
* @param matrix Matrix graph
* @param memo Memo to keep record of partial completed tour of city
* @param tour Tour path
* @param n Number of city
* @param start Start city index
*/
void tsp_path(int matrix[N][N], int memo[N][1 << N], int *tour, int n, int start);
/**
* @brief Set up the two city minimum cost between each two-cities
*
* @param matrix Matrix graph with adjacent matrix representation
* @param memo Memo arary with (N*2N) space complexity
* @param n Number of city in the graph
* @param start Start city index
*/
void tsp_setup(int matrix[N][N], int memo[N][1 << N], int n, int start);
/**
* @brief Set up the two city minimum cost between each two-cities
*
* @param matrix Matrix graph with adjacent matrix representation
* @param memo Memo arary with (N*2N) space complexity
* @param n Number of city in the graph
* @param start Start city index
*/
void tsp_solve(int matrix[N][N], int memo[N][1 << N], int n, int start);
/**
* @brief Generate all bit sets of size(n) with r bits set to 1
*
* @param subset Subset collections
* @param num_subset Number of subset
* @param r r bits will be set to 1
* @param n Total number of bit set size
*/
void tsp_combination(int **subset,int *num_subset, int r, int n);
/**
* @brief Use backtrack method to find all bit sets of n with r bits set to 1
*
* @param set Basic number with 0
* @param at Start location
* @param subset Subset collections
* @param num_subset Number of subset
* @param r r bits will be set to 1
* @param n Total number of bit set size
*/
void combination(int *set, int at,int **subset, int *num_subset, int r, int n);
/**
* @brief Check if i had been covered in the subset
*
* @param i ith element
* @param subset Subset state
* @return true -Return true if not included in the subset
* @return false -Return false if included in the subset
*/
bool not_in(int i, int subset);
/**
* @brief Return the minimum cost from the path
*
* @param matrix Matrix graph with adjacent matrix representation
* @param memo Memo arary with (N*2N) space complexity
* @param n Number of city in the graph
* @param start Start city index
* @return int -Return minimum cost of path
*/
int find_minimum_cost(int matrix[N][N], int memo[N][1 << N], int n, int start);
/**
* @brief Return the optimal tour from the memo array list
*
* @param matrix Matrix graph with adjacent matrix representation
* @param memo Memo arary with (N*2N) space complexity
* @param tour Tour array list
* @param n Number of city in the graph
* @param start Start city index
* @return int -Return minimum cost of path
*/
void find_optimal_tour(int matrix[N][N], int memo[N][1 << N], int *tour, int n, int start);
int fac(int n);
#endif
b) 函数实现
/**
* @file tsp_problem.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-03-05
*
* @copyright Copyright (c) 2023
*
*/
#ifndef TSP_PROBLEM_C
#define TSP_PROBLEM_C
#include "tsp_problem.h"
void tsp_path(int matrix[N][N], int memo[N][1 << N], int *tour, int n, int start)
{
int min_cost;
tsp_setup(matrix,memo,n,start);
tsp_solve(matrix,memo,n,start);
min_cost=find_minimum_cost(matrix,memo,n,start);
find_optimal_tour(matrix,memo,tour,n,start);
}
void tsp_setup(int matrix[N][N], int memo[N][1 << N], int n, int start)
{
int i;
for(i=0;i<n;i++)
{
if(i!=start)
{
memo[i][(1 << i) | (1 << start)] = matrix[start][i];
//memo[i][3] = 1;
}
}
}
void tsp_solve(int matrix[N][N], int memo[N][1 << N], int n, int start)
{
// void tsp_combination(int **subset,int *num_subset, int r, int n);
int r;
int i;
int next;
int end; //work as the middle element
int min_cost;
int *subset;
int num_subset=0;
int state;
for(r=3;r<=N;r++)
{
tsp_combination(&subset,&num_subset,r,n);
for(i=0;i<num_subset;i++)
{
for(next=0;next<N;next++)
{
if(next!=start && !not_in(next,subset[i]))
{
min_cost = INFINITY;
state=(subset[i])^(1<<next);
for(end=0;end<N;end++)
{
if (end != start && end != next && !not_in(end, subset[i]))
{
if((memo[end][state]+matrix[end][next])<min_cost)
{
min_cost = memo[end][state] + matrix[end][next];
}
}
}
memo[next][subset[i]] = min_cost;
}
}
}
}
}
void tsp_combination(int **subset, int *num_subset, int r, int n)
{
int num;
int set;
*num_subset=fac(n)/(fac(n-r)*fac(r));
*subset=(int *)malloc(sizeof(int)*(*num_subset));
num=0;
set =0;
combination(&set,0,subset,&num,r,n);
}
void combination(int *set, int at, int **subset, int *num_subset, int r, int n)
{
int i;
if(r==0)
{
(*subset)[*num_subset]=*set;
(*num_subset)++;
}
else
{
for (i = at; i < n; i++)
{
(*set) = (*set) | (1 << i);
combination(set, i + 1, subset, num_subset, r - 1, n);
(*set) = (*set)^(1 << i);
}
}
}
bool not_in(int i, int subset)
{
return (((1<<i) & subset)==0);
}
int find_minimum_cost(int matrix[N][N], int memo[N][1 << N], int n, int start)
{
int END_STATE=(1<<N)-1;
int i;
int min_cost=INFINITY;
for(i=0;i<n;i++)
{
if(i!=start)
{
if((memo[i][END_STATE]+matrix[i][start])<min_cost)
{
min_cost = memo[i][END_STATE] + matrix[i][start];
}
}
}
return min_cost;
}
void find_optimal_tour(int matrix[N][N], int memo[N][1 << N], int *tour, int n, int start)
{
int last_index;
int index;
int i;
int j;
int end_state=(1<<N)-1;
last_index=start;
for(i=N-1;i>=1;i--)
{
index=-1;
for(j=0;j<N;j++)
{
if(j!=start && !not_in(j,end_state))
{
if(index==-1)
{
index=j;
}
if((memo[j][end_state]+matrix[j][last_index])<(memo[index][end_state]+matrix[index][last_index]))
{
index=j;
}
}
}
tour[i]=index;
end_state=(end_state ^ (1<<index));
last_index=index;
}
tour[0]=tour[N]=start;
return;
}
int fac(int n)
{
if(n<0)
{
return 0;
}
else if(n==0 || n==1)
{
return 1;
}
else
{
return n*fac(n-1);
}
}
#endif
c) 函数测试
/**
* @file tsp_problem_main.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-03-06
*
* @copyright Copyright (c) 2023
*
*/
#ifndef TSP_PROBLEM_MAIN_C
#define TSP_PROBLEM_MAIN_C
#include "tsp_problem.c"
// void tsp_path(int **matrix, int **memo, int *tour, int n, int start)
int main(void)
{
int matrix[N][N];
int memo[N][1<<N];
int tour[N+1];
int n;
int start;
memset(matrix,0,sizeof(matrix));
memset(memo,0,sizeof(memo));
matrix[0][1]=4;
matrix[0][2] = 1;
matrix[0][3] = 9;
matrix[1][0] = 3;
matrix[1][2] = 6;
matrix[1][3] = 11;
matrix[2][0] = 4;
matrix[2][1] = 1;
matrix[2][3] = 2;
matrix[3][0] = 6;
matrix[3][1] = 5;
matrix[3][2] = -4;
n=N;
start=0;
tsp_path(matrix,memo,tour,n,start);
getchar();
return EXIT_SUCCESS;
}
#endif
- 总结
学习到目前位置,对动态规划DP问题,很多时候感觉力不从心,无从下手,尤其是变化莫测的DP数组,更是增加了理解的难度,在这里感觉数学的思维特别重要,尤其是对于DP状态方程的撰写。
参考文献:
- 《Traveling Salesman Problem》 by William Fiset
- (2条消息) TSP(旅行者问题)——动态规划详解_计算机实务题 旅行家问题_JoeKwok的博客-CSDN博客