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

本文介绍了旅行商问题(TSP),即寻找一条包含所有顶点且耗费最小的旅行路线。通过算法设计,阐述了利用分支定界法解决TSP问题的方法,涉及优先级队列和小根堆的应用。算法步骤包括确定解空间、计算耗费下界以及C++实现细节。测试结果显示了算法的有效性。
摘要由CSDN通过智能技术生成

问题描述

给定一个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 
支限界法类又称为剪枝限界法或分支定界法,它类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。它与回溯法有两点不同:①回溯法只通过约束条件剪去非可行解,而分支限界法不仅通过约束条件,而且通过目标函数的限界来减少无效搜索,也就是剪掉了某些不包含最优解的可行解。②在解空间树上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。分支限界法的搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展结点。为了有效地选择下一扩展结点,以加速搜索的进程, 在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。 从活结点表中选择下一扩展结点的不同方式导致不同的分支限界法。最常见的有以下两种方式: ①队列式(FIFO)分支限界法:队列式分支限界法将活结点表组织成一个队列,并按队列的先进先出原则选取下一个结点为当前扩展结点。 ②优先队列分支限界法优先队列分支限界法将活结点表按照某个估值函数C(x)的值组织成一个优先队列,并按优先队列中规定的结点优先级选取优先级最高的下一个结点成为当前扩展结点。 影响分支限界法搜索效率的有两个主要因素:一是优先队列Q的优先级由C(x)确定,它能否保证在尽可能早的情况下找到最优解,如果一开始找到的就是最优解,那么搜索的空间就能降低到最小。二是限界函数u(x),它越严格就越可能多地剪去分支,从而减少搜索空间。 在用分支限界法解决TSP问题时,有不少很好的限界函数和估值函数已经构造出来出了(限于篇幅,这里不做详细介绍), 使得分支限界法在大多数情况下的搜索效率大大高于回溯法。但是,在最坏情况下,该算法的时间复杂度仍然是O(n!),而且有可能所有的(n-1)!个结点都要存储在队列中。 近似算法是指不能肯定找到最优解的算法,但通常找到的也是比较好的解,或称近似最优解。[20]一般而言,近似算法的时间复杂度较低,通常都是多项式时间内的。由于近似算法的时间效率高,所以在实际应用中,主要是使用近似算法,这一类算法也一直是研究的主要对象。传统的近似算法以采用贪心策略和局部搜索为主,而几十年来,随着以遗传算法为代表的新型启发式搜索算法的逐步完善,在解决TSP问题上获得了巨大的成功。遗传算法、模拟退火算法、蚁群算法等已经成为公认的好算法。在本节中,将介绍传统的近似算法。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值