对于一个邮差需要送信,以出发地为起点,沿着不同城市的连通路径遍历各个城市,以便把信交到对应城市的收件热手中,问题在于在返回出发点之前,邮差如何用最短的行程将每个城市遍历一遍,而且要求是每个城市只能被遍历一遍,假定城市间的连通路径如下图:
添加图片注释,不超过 140 字(可选)
上图中的每个节点都代表了一个城市,节点间的连线的数值就是对应的城市之间的路径的长短距离,假设邮差从节点1出发,如何用最短的距离遍历每个节点然后回到节点1,同时要求每个节点最多被遍历一次,这也就是所谓的NP问完全问题,无论计算机运行有多快,只要问题的规模稍微变大一些,就会导致计算机的算力会很快就被用完,而且还得不到有效的结果。
对于这个问题首先想到的也就是暴力穷举法,也就是罗列出所有可能得遍历方式,如果有n个城市,那以其中一个城市作为起点,要罗列出其他n-1个城市的遍历次序就会产生出n-1的阶乘种可能。所以暴力穷举法不是解决该问题的有效方法,这里主要是考虑使用动态规划的思维来解决,将该问题分解成子问题,我们用集合
添加图片注释,不超过 140 字(可选)
来表示出去起始节点之外的其他节点集合,有差遍历的最短路径为
添加图片注释,不超过 140 字(可选)
假设倒数第二个节点是j,那么一定有路径以上图的最短路径。
使用python实现的代码如下:
N = 5 #节点数
D = np.zeros((N, N)).astype(int)
S = set() #对应点的集合
for i in range(1, N):
S.add(i)
D[0][0] = 0 #根据图5-5进行初始化
D[0][1] = 2
D[0][2] = 4
D[0][3] = 1
D[0][4] = 2
D[1][0] = 3
D[1][1] = 0
D[1][2] = 3
D[1][3] = 2
D[1][4] = 3
D[2][0] = 4
D[2][1] = 3
D[2][2] = 0
D[2][3] = 4
D[2][4] = 2
D[3][0] = 1
D[3][1] = 2
D[3][2] = 4
D[3][3] = 0
D[3][4] = 2
D[4][0] = 2
D[4][1] = 3
D[4][2] = 2
D[4][3] = 2
D[4][4] = 0
def get_subset(point_set, n): #根据点数构造子集
if n == 0:
return []
sub_sets = []
for p in point_set:
s = set()
s.add(p)
set_copy = point_set.copy()
set_copy.discard(p)
sets = get_subset(set_copy, n - 1)
if len(sets) == 0:
sub_sets.append(s)
else:
for each_set in sets:
s_union = s.copy().union(each_set)
sub_sets.append(s_union)
return sub_sets
def find_shortest_path(): #计算最短路径
C = {}
for point_count in range(1, N):
sub_sets = get_subset(S.copy(), point_count) #获得包含给定点数的子集
for the_set in sub_sets:
distances = {} #记录起始点到集合内每一点的最短距离
for the_point in the_set: #计算当前集合内的点作为回到起始点前一个点时对应的最短距离
after_discard = the_set.copy()
after_discard.discard(the_point)
if len(after_discard) == 0:
distances[the_point] = D[0][the_point]
else:
'''
如果集合S包含三个点{1,2,3},从节点0开始遍历集合中所有点的最短路径算法为,先找出从起始点0开始遍历集合{1,2},{1,3},{2,3}的最短路径,
然后用这三条路径长度分别加上D[2][3],D[3][2],D[3][1],三个结果中的最小值就是从起始点0开始,遍历集合{1,2,3}中所有点后的最短路径.
因此当集合为{1,2,3}时,C[{1,2,3}]对应key值为1,2,3的一个map对象,map[1]表示集合点为{1,2,3}时,遍历路径最后一个节点时1时的最短距离
'''
set_copy = the_set.copy()
set_copy.discard(the_point)
distance_to__points = C[frozenset(set_copy)]
d = sys.maxsize
for p in set_copy:
if D[p][the_point] + distance_to__points[p] < d:
d = D[p][the_point] + distance_to__points[p]
distances[the_point] = d
C[frozenset(the_set)] = distances.copy() #记录起始点到当前集合内每个点的最短距离,根据python语法,fronzenset才能作为key
distances.clear()
distances = C[frozenset(S)]
d = sys.maxsize
for point in S: #先找到起始点到集合{1,2...n}中对应点的嘴短距离
if distances[point] + D[point][0] < d:
d = distances[point] + D[point][0]
return d, C
shortest_distance, C = find_shortest_path()
print("the shortest distance for visiting all points is : ", shortest_distance)