分支定界法——旅行商(TSP)问题

问题描述

给定一个n顶点网络(有向或无向),找出一个包含n个顶点且具有最小耗费的换路。任何一个包含网络所有顶点的换路称为一个旅行。旅行商问题(Traveling Salesman Problem,TSP)是要寻找一条耗费最少的旅行。

              四顶点网络 
图1 四顶点网络

如图1是一个四顶点无向网络。这个网络的一些旅行:1,2,4,3,1;1,3,2,4,1和1,4,3,2,1。旅行1,2,4,3,1的耗费为66,;旅行1,3,2,4,1的耗费为25;旅行1,4,3,2,1的耗费为55.故1,3,2,4,1是网络中耗费最少的旅行。

算法设计

和回溯法解决TSP问题一样,首先确定问题的解空间是一个排列树,从顶点1出发最后回到顶点1,所以排列可以表示为[1,x2,…,xn,1]。和子集树一样,使用一个优先级队列求解。要求旅行的耗费最小,故使用小根堆储存活动节点,堆中每个活动节点的子树耗费所能取得的下界lowerCost 是优先级队列的优先级,即当前节点确定的途径下继续完成排列能取得最低耗费(并不一定保证能最终达到这个值)。

每个活动节点是小根堆的一个元素,元素结构包括:

lowerCost;      //当前节点往后排列,整个回路所能取得的总耗费的下限 
currentCost; //从根到当前节点的当前排列的耗费
restMinCost; //从当前节点到最后一个节点,每个节点的最小出边的耗费之和
s; //从根节点到当前节点的路径为[0:s]
currentTour[]; //从根节点到当前节点的路径(排列)

如何获得lowerCost
对于每个活动节点,可知当前耗费currentCost,对于剩余的顶点,计算每个顶点的最小出边之和restMinCost即为剩余路径的最小耗费的下界,lowerCost=currentCost + restMinCost

算法步骤

1、准备工作:建立小根堆,用于存储活动节点。计算每个顶点的最小出边,若存在某个顶点没有出边,则算法终止。初始化树根(顶点1)为第一个活动节点。 
2、判断节点是否是叶结点的父节点:是的话,则检查是否一定有最低耗费,若是加入小根堆;
3、不是叶结点的父节点,则生成子节点,并判断子节点是否有可能取得最低耗费,若可能则加入小根堆;
4、取出下一个节点作为活动节点,若该节点已经是叶结点,返回当前最低耗费值,即为最优旅行。若不是叶结点则循环2、3步。

排列树 
图2 四顶点网络的解空间树
小根堆中的元素变化:{}——>{C,D,E}——>{C,D,K,J}——>{C,K,J,H,I}——>{C,K,J,I,N}——>{C,K,I,N}——>{C,K,I}

最后取出活动节点N,但是N已经是叶结点,故返回最优值25,和最佳途径1->3->2->4->1,并终止算法。

事实上,检查节点J的子节点P时执行的是步骤2,也能得到最优值25,但是由于N已经加入小根堆,并且当前最优值已经是25,所以P没有加入小根堆,途径1->4->2->3->1也就被排除了。

需要注意的是,对于以下的C++程序,需要手动释放掉每个活动节点的数据成员currentTour数组的内寸,包括最后小根堆中剩余的节点。

C++实现

小根堆minHeap元素结构

#pragma once
struct heapNode
{
    int lowerCost;      //当前节点往后排列,整个回路所能取得的总耗费的下限
    int currentCost;    //从根到当前节点的当前排列的耗费
    int restMinCost;    //从当前节点到最后一个节点,每个节点的最小出边的耗费之和
    int s;              //从根节点到当前节点的路径为[0:s]
    int *currentTour;   //从根节点到当前节点的路径(排列)

    //算术运算时的类型转换
    operator int() { return lowerCost; }

    //重载大于号运算符,用于小根堆比较
    bool operator>(const heapNode &right)
    {
        return lowerCost > right.lowerCost;
    }
};

小根堆minHeap 部分源码

#pragma once
#include<iostream>
#include <algorithm>

using namespace std;

template<class T>
void changeLength1D(T*& a, int oldLength, int newLength)
{
    if 
  • 22
    点赞
  • 119
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
好的,以下是一个用 C++ 实现的分支定界法解决旅行商问题TSP)的代码。 ```c++ #include <iostream> #include <vector> #include <algorithm> #include <queue> using namespace std; const int INF = 1e9; const int MAX_N = 10; int n; // 城市数量 int dist[MAX_N][MAX_N]; // 城市间距离矩阵 int best_dist = INF; // 最优解 int best_path[MAX_N]; // 最优路径 bool vis[MAX_N]; // 标记每个城市是否被访问 // 定义状态结构体 struct Node { int d; // 当前已经走过的距离 int path[MAX_N]; // 当前已经走过的路径 int level; // 当前搜索的层数 bool vis[MAX_N]; // 标记每个城市是否被访问 bool operator<(const Node &other) const { return d > other.d; // 按照距离从小到大排序 } }; // 计算从城市i到城市j的距离 int calc_dist(int i, int j) { // 这里使用无向图,所以dist[i][j]和dist[j][i]的值相同 return dist[i][j]; } // 分支定界法搜索 void BranchAndBound() { // 初始化起点 Node start; start.d = 0; start.level = 0; start.path[0] = 0; // 起点为0号城市 start.vis[0] = true; priority_queue<Node> q; q.push(start); while (!q.empty()) { Node cur = q.top(); q.pop(); // 如果当前路径已经超过了最优解,剪枝 if (cur.d >= best_dist) continue; // 如果已经搜索到叶子节点,更新最优解 if (cur.level == n - 1) { int last_city = cur.path[n - 1]; cur.d += calc_dist(last_city, 0); if (cur.d < best_dist) { best_dist = cur.d; for (int i = 0; i < n; i++) { best_path[i] = cur.path[i]; } } continue; } // 枚举下一个城市的所有可能 for (int i = 1; i < n; i++) { if (cur.vis[i]) continue; Node next; next.level = cur.level + 1; next.d = cur.d + calc_dist(cur.path[cur.level], i); for (int j = 0; j < n; j++) { next.path[j] = cur.path[j]; next.vis[j] = cur.vis[j]; } next.path[next.level] = i; next.vis[i] = true; q.push(next); } } } int main() { // 读入城市数量 cin >> n; // 读入城市间距离矩阵 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { cin >> dist[i][j]; } } // 开始搜索 BranchAndBound(); // 输出最优解 cout << "Best distance: " << best_dist << endl; cout << "Best path: "; for (int i = 0; i < n; i++) { cout << best_path[i] << " "; } cout << endl; return 0; } ``` 以上就是一个用 C++ 实现的分支定界法解决旅行商问题的代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值