📜  使用分支定界法的旅行商问题

📅  最后修改于: 2021-04-23 05:48:05             🧑  作者: Mango

给定一组城市以及每对城市之间的距离,问题在于找到最短的游览,该游览只对每个城市进行一次访问,然后返回起点。

欧拉1

例如,考虑右侧图所示的图形。图中的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