给定一组城市以及每对城市之间的距离,问题在于找到最短的游览,该游览只对每个城市进行一次访问,然后返回起点。
例如,考虑右侧图所示的图形。图中的TSP漫游为0-1-3-2-0。游览费用为10 + 25 + 30 + 15,即80。
我们已经讨论了以下解决方案
1)天真的和动态的编程
2)使用MST的近似解
分支定界解决方案
如前几篇文章所述,在“分支定界”方法中,对于树中的当前节点,我们计算了一个最佳可能解的界线,如果我们将该节点下移,则可以得到该最佳解。如果最佳可能解决方案的边界本身比当前最佳解决方案差(到目前为止已计算出最好),那么我们将忽略以该节点为根的子树。
注意,通过节点的成本包括两个成本。
1)从根节点到达节点的成本(当我们到达节点时,我们已计算出此成本)
2)从当前节点到叶子的答案的成本(我们计算此成本的界限,以决定是否忽略该节点的子树)。
- 在最大化问题的情况下,如果遵循给定节点,则上限将告诉我们最大可能的解决方案。例如,在0/1背包中,我们使用Greedy方法来查找上限。
- 在最小化问题的情况下,如果遵循给定的节点,则下限会告诉我们最小的可能解决方案。例如,在“工作分配问题”中,我们通过为工人分配成本最低的工作来获得下界。
在分支定界中,具有挑战性的部分是找出一种方法来计算最佳可能解决方案的定界。以下是用于计算旅行商问题边界的想法。
任何旅行的费用可以写成如下。
Cost of a tour T = (1/2) * ∑ (Sum of cost of two edges
adjacent to u and in the
tour T)
where u ∈ V
For every vertex u, if we consider two edges through it in T,
and sum their costs. The overall sum for all vertices would
be twice of cost of tour T (We have considered every edge
twice.)
(Sum of two tour edges adjacent to u) >= (sum of minimum weight
two edges adjacent to
u)
Cost of any tour >= 1/2) * ∑ (Sum of cost of two minimum
weight edges adjacent to u)
where u ∈ V
例如,考虑上面显示的图形。以下是与每个节点相邻的两条边的最低成本。
Node Least cost edges Total cost
0 (0, 1), (0, 2) 25
1 (0, 1), (1, 3) 35
2 (0, 2), (2, 3) 45
3 (0, 3), (1, 3) 45
Thus a lower bound on the cost of any tour =
1/2(25 + 35 + 45 + 45)
= 75
Refer this for one more example.
现在我们有了下界计算的想法。让我们看看如何将其应用于状态空间搜索树。我们开始枚举所有可能的节点(最好按字典顺序)
1.根节点:在不失一般性的前提下,我们假设我们从顶点“ 0”开始,其上限已经在上面计算出。
处理级别2:下一级别枚举了我们可以去到的所有可能的顶点(请记住,在任何路径中,一个顶点仅必须出现一次),即1,2,3…n(请注意图形是完整的) 。考虑我们正在为顶点1计算,因为我们从0移到1,所以我们的游览现在包括边0-1。这使我们可以对根的下限进行必要的更改。
Lower Bound for vertex 1 =
Old lower bound - ((minimum edge cost of 0 +
minimum edge cost of 1) / 2)
+ (edge cost 0-1)
它是如何工作的?要包括边0-1,我们要增加边成本0-1,并减去边权重,以使下限保持尽可能紧密,这将是0和1的最小边之和除以2。 ,减去的边不能小于此值。
处理其他级别:在进入下一个级别时,我们再次枚举所有可能的顶点。对于上述情况,在1之后,我们检查2、3、4…n。
当我们从1移到1时,请考虑2的下限,我们将边1-2包含到游览中,并更改此节点的新下限。
Lower bound(2) =
Old lower bound - ((second minimum edge cost of 1 +
minimum edge cost of 2)/2)
+ edge cost 1-2)
注意:公式中的唯一变化是,这次我们为1包括了第二个最小边缘成本,因为最小边缘成本已在上一个级别中减去。
C++
// C++ program to solve Traveling Salesman Problem
// using Branch and Bound.
#include
using namespace std;
const int N = 4;
// final_path[] stores the final solution ie, the
// path of the salesman.
int final_path[N+1];
// visited[] keeps track of the already visited nodes
// in a particular path
bool visited[N];
// Stores the final minimum weight of shortest tour.
int final_res = INT_MAX;
// Function to copy temporary solution to
// the final solution
void copyToFinal(int curr_path[])
{
for (int i=0; i lower bound of the root node
// curr_weight-> stores the weight of the path so far
// level-> current level while moving in the search
// space tree
// curr_path[] -> where the solution is being stored which
// would later be copied to final_path[]
void TSPRec(int adj[N][N], int curr_bound, int curr_weight,
int level, int curr_path[])
{
// base case is when we have reached level N which
// means we have covered all the nodes once
if (level==N)
{
// check if there is an edge from last vertex in
// path back to the first vertex
if (adj[curr_path[level-1]][curr_path[0]] != 0)
{
// curr_res has the total weight of the
// solution we got
int curr_res = curr_weight +
adj[curr_path[level-1]][curr_path[0]];
// Update final result and final path if
// current result is better.
if (curr_res < final_res)
{
copyToFinal(curr_path);
final_res = curr_res;
}
}
return;
}
// for any other level iterate for all vertices to
// build the search space tree recursively
for (int i=0; i
Java
// Java program to solve Traveling Salesman Problem
// using Branch and Bound.
import java.util.*;
class GFG
{
static int N = 4;
// final_path[] stores the final solution ie, the
// path of the salesman.
static int final_path[] = new int[N + 1];
// visited[] keeps track of the already visited nodes
// in a particular path
static boolean visited[] = new boolean[N];
// Stores the final minimum weight of shortest tour.
static int final_res = Integer.MAX_VALUE;
// Function to copy temporary solution to
// the final solution
static void copyToFinal(int curr_path[])
{
for (int i = 0; i < N; i++)
final_path[i] = curr_path[i];
final_path[N] = curr_path[0];
}
// Function to find the minimum edge cost
// having an end at the vertex i
static int firstMin(int adj[][], int i)
{
int min = Integer.MAX_VALUE;
for (int k = 0; k < N; k++)
if (adj[i][k] < min && i != k)
min = adj[i][k];
return min;
}
// function to find the second minimum edge cost
// having an end at the vertex i
static int secondMin(int adj[][], int i)
{
int first = Integer.MAX_VALUE, second = Integer.MAX_VALUE;
for (int j=0; j lower bound of the root node
// curr_weight-> stores the weight of the path so far
// level-> current level while moving in the search
// space tree
// curr_path[] -> where the solution is being stored which
// would later be copied to final_path[]
static void TSPRec(int adj[][], int curr_bound, int curr_weight,
int level, int curr_path[])
{
// base case is when we have reached level N which
// means we have covered all the nodes once
if (level == N)
{
// check if there is an edge from last vertex in
// path back to the first vertex
if (adj[curr_path[level - 1]][curr_path[0]] != 0)
{
// curr_res has the total weight of the
// solution we got
int curr_res = curr_weight +
adj[curr_path[level-1]][curr_path[0]];
// Update final result and final path if
// current result is better.
if (curr_res < final_res)
{
copyToFinal(curr_path);
final_res = curr_res;
}
}
return;
}
// for any other level iterate for all vertices to
// build the search space tree recursively
for (int i = 0; i < N; i++)
{
// Consider next vertex if it is not same (diagonal
// entry in adjacency matrix and not visited
// already)
if (adj[curr_path[level-1]][i] != 0 &&
visited[i] == false)
{
int temp = curr_bound;
curr_weight += adj[curr_path[level - 1]][i];
// different computation of curr_bound for
// level 2 from the other levels
if (level==1)
curr_bound -= ((firstMin(adj, curr_path[level - 1]) +
firstMin(adj, i))/2);
else
curr_bound -= ((secondMin(adj, curr_path[level - 1]) +
firstMin(adj, i))/2);
// curr_bound + curr_weight is the actual lower bound
// for the node that we have arrived on
// If current lower bound < final_res, we need to explore
// the node further
if (curr_bound + curr_weight < final_res)
{
curr_path[level] = i;
visited[i] = true;
// call TSPRec for the next level
TSPRec(adj, curr_bound, curr_weight, level + 1,
curr_path);
}
// Else we have to prune the node by resetting
// all changes to curr_weight and curr_bound
curr_weight -= adj[curr_path[level-1]][i];
curr_bound = temp;
// Also reset the visited array
Arrays.fill(visited,false);
for (int j = 0; j <= level - 1; j++)
visited[curr_path[j]] = true;
}
}
}
// This function sets up final_path[]
static void TSP(int adj[][])
{
int curr_path[] = new int[N + 1];
// Calculate initial lower bound for the root node
// using the formula 1/2 * (sum of first min +
// second min) for all edges.
// Also initialize the curr_path and visited array
int curr_bound = 0;
Arrays.fill(curr_path, -1);
Arrays.fill(visited, false);
// Compute initial bound
for (int i = 0; i < N; i++)
curr_bound += (firstMin(adj, i) +
secondMin(adj, i));
// Rounding off the lower bound to an integer
curr_bound = (curr_bound==1)? curr_bound/2 + 1 :
curr_bound/2;
// We start at vertex 1 so the first vertex
// in curr_path[] is 0
visited[0] = true;
curr_path[0] = 0;
// Call to TSPRec for curr_weight equal to
// 0 and level 1
TSPRec(adj, curr_bound, 0, 1, curr_path);
}
// Driver code
public static void main(String[] args)
{
//Adjacency matrix for the given graph
int adj[][] = {{0, 10, 15, 20},
{10, 0, 35, 25},
{15, 35, 0, 30},
{20, 25, 30, 0} };
TSP(adj);
System.out.printf("Minimum cost : %d\n", final_res);
System.out.printf("Path Taken : ");
for (int i = 0; i <= N; i++)
{
System.out.printf("%d ", final_path[i]);
}
}
}
/* This code contributed by PrinciRaj1992 */
Python3
# Python3 program to solve
# Traveling Salesman Problem using
# Branch and Bound.
import math
maxsize = float('inf')
# Function to copy temporary solution
# to the final solution
def copyToFinal(curr_path):
final_path[:N + 1] = curr_path[:]
final_path[N] = curr_path[0]
# Function to find the minimum edge cost
# having an end at the vertex i
def firstMin(adj, i):
min = maxsize
for k in range(N):
if adj[i][k] < min and i != k:
min = adj[i][k]
return min
# function to find the second minimum edge
# cost having an end at the vertex i
def secondMin(adj, i):
first, second = maxsize, maxsize
for j in range(N):
if i == j:
continue
if adj[i][j] <= first:
second = first
first = adj[i][j]
elif(adj[i][j] <= second and
adj[i][j] != first):
second = adj[i][j]
return second
# function that takes as arguments:
# curr_bound -> lower bound of the root node
# curr_weight-> stores the weight of the path so far
# level-> current level while moving
# in the search space tree
# curr_path[] -> where the solution is being stored
# which would later be copied to final_path[]
def TSPRec(adj, curr_bound, curr_weight,
level, curr_path, visited):
global final_res
# base case is when we have reached level N
# which means we have covered all the nodes once
if level == N:
# check if there is an edge from
# last vertex in path back to the first vertex
if adj[curr_path[level - 1]][curr_path[0]] != 0:
# curr_res has the total weight
# of the solution we got
curr_res = curr_weight + adj[curr_path[level - 1]]\
[curr_path[0]]
if curr_res < final_res:
copyToFinal(curr_path)
final_res = curr_res
return
# for any other level iterate for all vertices
# to build the search space tree recursively
for i in range(N):
# Consider next vertex if it is not same
# (diagonal entry in adjacency matrix and
# not visited already)
if (adj[curr_path[level-1]][i] != 0 and
visited[i] == False):
temp = curr_bound
curr_weight += adj[curr_path[level - 1]][i]
# different computation of curr_bound
# for level 2 from the other levels
if level == 1:
curr_bound -= ((firstMin(adj, curr_path[level - 1]) +
firstMin(adj, i)) / 2)
else:
curr_bound -= ((secondMin(adj, curr_path[level - 1]) +
firstMin(adj, i)) / 2)
# curr_bound + curr_weight is the actual lower bound
# for the node that we have arrived on.
# If current lower bound < final_res,
# we need to explore the node further
if curr_bound + curr_weight < final_res:
curr_path[level] = i
visited[i] = True
# call TSPRec for the next level
TSPRec(adj, curr_bound, curr_weight,
level + 1, curr_path, visited)
# Else we have to prune the node by resetting
# all changes to curr_weight and curr_bound
curr_weight -= adj[curr_path[level - 1]][i]
curr_bound = temp
# Also reset the visited array
visited = [False] * len(visited)
for j in range(level):
if curr_path[j] != -1:
visited[curr_path[j]] = True
# This function sets up final_path
def TSP(adj):
# Calculate initial lower bound for the root node
# using the formula 1/2 * (sum of first min +
# second min) for all edges. Also initialize the
# curr_path and visited array
curr_bound = 0
curr_path = [-1] * (N + 1)
visited = [False] * N
# Compute initial bound
for i in range(N):
curr_bound += (firstMin(adj, i) +
secondMin(adj, i))
# Rounding off the lower bound to an integer
curr_bound = math.ceil(curr_bound / 2)
# We start at vertex 1 so the first vertex
# in curr_path[] is 0
visited[0] = True
curr_path[0] = 0
# Call to TSPRec for curr_weight
# equal to 0 and level 1
TSPRec(adj, curr_bound, 0, 1, curr_path, visited)
# Driver code
# Adjacency matrix for the given graph
adj = [[0, 10, 15, 20],
[10, 0, 35, 25],
[15, 35, 0, 30],
[20, 25, 30, 0]]
N = 4
# final_path[] stores the final solution
# i.e. the // path of the salesman.
final_path = [None] * (N + 1)
# visited[] keeps track of the already
# visited nodes in a particular path
visited = [False] * N
# Stores the final minimum weight
# of shortest tour.
final_res = maxsize
TSP(adj)
print("Minimum cost :", final_res)
print("Path Taken : ", end = ' ')
for i in range(N + 1):
print(final_path[i], end = ' ')
# This code is contributed by ng24_7
输出 :
Minimum cost : 80
Path Taken : 0 1 3 2 0
时间复杂度:分支和边界的最坏情况复杂度仍然与蛮力相同,这是因为在最坏情况下,我们可能永远都没有机会修剪节点。而实际上,根据TSP的不同实例,它的性能非常好。复杂度还取决于边界函数的选择,因为它们是决定要修剪多少个节点的边界函数。
参考:
http://lcm.csa.iisc.ernet.in/dsa/node187.html