分支限界——TSP问题

分支限界——TSP问题

问题:

TSP问题。

分析:

排列树问题,和之前的子集树问题略有不同,因为活结点可供选择的子节点与前面选择的情况有关,因此如果按照前面子集树的记录每个节点父节点的方式,就很难知道有哪些节点是可选的,哪些节点是选过的。因此对于每个节点,都记录其选过的节点和没选的节点,同时记录其选的顺序,这样就可以直接构造出最优解,因此每个节点都需要有一个数组。

另外需要确定的是其上界和下界。该问题是一个极小化问题,下界需要精确解,上界则仅需是其中一个解。因此确定上界就从当前点开始,每次选距离最近的点,选完所有点,最后再加上回到第一个点的距离,再加上当前已选的点的总距离。确定下界则比较严格,在不考虑哈密顿的前提下,每个点取最短的边,得到的最短的n条边一定是一个下界,这种下界可以再增加条件进行优化,比如每个点至少有一条入边一条出边,那么每个点选与之相连的最短的两个边,加起来之后除以2再向上取整即可(为什么向上取整可以暂时没想明白,在实际操作中还是写成向上取整。当已经有一些点被选择了,那么其下界就是当前已经选择的点的总距离×2加上这个已选出的路径的首尾元素在未选路径中的最小可选边再加上剩下的未选点中的最短的两个边,然后除以2取整。

代码:

#include <bits/stdc++.h>

using namespace std;

#define MAXN 105
#define INF 0x3f3f3f

int m[MAXN][MAXN];

struct Node{
    int cl;// 当前路径长度,不算这个正在决定被选择的点
    int upbound;
    int lowbound;
    int level;
    int res[MAXN];// res中的[1,level]储存着已经选了的点,[level+1,n]储存着未选的点
    Node(int _cl,int _upbound,int _lowbound,int _level,int _res[]){
        cl = _cl;
        upbound = _upbound;
        lowbound = _lowbound;
        level = _level;
        for(int i = 0;i < MAXN;i++){
            res[i] = _res[i];
        }
    }
};

struct cmp{
    bool operator()(Node* n1,Node* n2){
        return n1->lowbound > n2->lowbound;
    }
};

priority_queue<Node*,vector<Node*>,cmp> pq;

// 计算上界
int up(int n, int _res[], int level){
    // 拷贝一次res
    int res[MAXN];
    for(int i = 1;i <= MAXN;i++){
        res[i] = _res[i];
    }

    int u = 0;
    // 先计算当前已选的点的总距离
    for(int i = 1;i < level;i++){
        u += m[res[i]][res[i+1]];
    }

    // 从最后一个选择的点开始贪心寻找最近的点
    for(int i = level+1;i <= n;i++){
        int min = INF;// 最近的边的长度
        int p = -1;// 在res中所处的下标
        // 寻找离当前点最近的未被选择点
        for(int k = level+1;k <= n;k++){
            if(m[res[level]][res[k]] < min){
                min = m[res[level]][res[k]];
                p = k;
            }
        }
        u += min;
        level++;
        swap(res[level], res[p]);
    }

    // 最后再加上回到起点的那一条边
    u += m[res[level]][res[1]];

    return u;
}

// 计算下界
int low(int n, int _res[], int level){
    // 拷贝一次res
    int res[MAXN];
    for(int i = 1;i <= MAXN;i++){
        res[i] = _res[i];
    }

    int l = 0;
    // 先计算当前已选的点的总距离
    for(int i = 1;i < level;i++){
        l += m[res[i]][res[i+1]];
    }
    l *= 2;

    // 选择首尾元素的最近未选择点
    int min1 = INF,min2 = INF;
    int p1 = -1,p2 = -1;
    for(int i = level+1;i <= n;i++){
        if(m[res[1]][res[i]] < min1){
            min1 = m[res[1]][res[i]];
            p1 = i;
        }
        if(m[res[level]][res[i]] < min2){
            min2 = m[res[1]][res[i]];
            p2 = i;
        }
    }
    l += min1;
    l += min2;

    // 在剩余未选择点中选取距离最近的两个未选择点
    for(int i = level+1;i <= n;i++){
        int min1 = INF;
        int p1 = -1;
        for(int k = level+1;k <= n;k++){
            if(m[res[i]][res[k]] < min1){
                min1 = m[res[i]][res[k]];
                p1 = k;
            }
        }
        l += min1;

        int min2 = INF;
        int p2 = -1;
        for(int k = level+1;k <= n;k++){
            if(m[res[i]][res[k]] < min2 && k!=p1){
                min2 = m[res[i]][res[k]];
                p2 = k;
            }
        }
        l += min2;
    }

    return l%2==0?l/2:l/2+1;
}

// 约束函数
bool constrain(int level,int n,const int res[]){
    if(level < 2) return true;
    if(level < n && m[res[level-1]][res[level]]!=INF) return true;
    if(level == n && m[res[level-1]][res[level]]!=INF && m[res[level]][res[1]]!=INF) return true;

    return false;
}

void TSPPrior(int n){

    // 初始化节点数组
    int _res[MAXN];
    for(int i = 1;i <= n;i++){
        _res[i] = i;
    }
    // 新建根节点,插入优先队列
    pq.push(new Node(0,up(n, _res, 1),low(n, _res, 1),1,_res));

    // 最优解的值
    int bestc = pq.top()->upbound;
    // 最优解
    Node* bestNode = NULL;

    // 开始广搜
    while(!pq.empty()){
        // 取出队首元素
        Node* cNode = pq.top();
        pq.pop();

        // 判断其扩展节点是否可行
        for(int i = cNode->level+1;i <= n;i++){
            int res[MAXN];
            for(int i = 0;i < MAXN;i++){
                res[i] = cNode->res[i];
            }
            swap(res[cNode->level+1], res[i]);
            if(constrain(cNode->level+1, n, res) && cNode->lowbound < bestc){
                // 新建可行节点
                Node* tmp1 = new Node(cNode->cl+m[res[cNode->level]][res[cNode->level+1]],up(n, res, cNode->level+1),low(n, res, cNode->level+1),cNode->level+1,res);

                // 将可行节点加入pq
                if(cNode->level == n){
                    if(cNode->cl + m[res[cNode->level]][res[cNode->level+1]] + m[res[cNode->level+1]][res[1]] < bestc){
                        bestc = cNode->cl + m[res[cNode->level]][res[cNode->level+1]] + m[res[cNode->level+1]][res[1]];
                        bestNode = tmp1;
                    }
                }else{
                    pq.push(tmp1);
                }
            }
        }
    }

    // 输出最优解的值
    cout<<bestc<<endl;
  	
  	// 输出最优解
    if(bestNode!=NULL){
        for(int i = 1;i <= n;i++){
            cout<<bestNode->res[i]<<" ";
        }
    }else{
        cout<<"和上次的子集树背包分支限界一样,构造为初始化上界的最优解,懒得写了"<<endl;
    }
}

int main(){
    int n = 5;
    // 初始化操作
    for(int i = 0;i <= n;i++){
        for(int j = 0;j <= n;j++){
            m[i][j] = INF;
        }
    }

    m[1][2] = 3;
    m[1][3] = 1;
    m[1][4] = 5;
    m[1][5] = 8;
    m[2][1] = 3;
    m[2][3] = 6;
    m[2][4] = 7;
    m[2][5] = 9;
    m[3][1] = 2;
    m[3][2] = 6;
    m[3][4] = 4;
    m[3][5] = 2;
    m[4][1] = 5;
    m[4][2] = 7;
    m[4][3] = 4;
    m[4][5] = 3;
    m[5][1] = 8;
    m[5][2] = 9;
    m[5][3] = 2;
    m[5][4] = 3;

    TSPPrior(n);
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值