注意,旅行商問題一般有一個默認的起點.
一.問題分析
1.狀態結點的數據類型:
int cl; 表示已走的路程
int id; 表示當前準備判斷的結點序號
int x[N]; 表示一個最佳策略
2.約束條件:下一個結點和當前結點有通路,並且解 < 當前的最優解,並且終點和起點形成迴路
3.限界條件:當前狀態的最大價值上界 > 當前最優解
4.初始條件:進入隊列的第一個結點是數組元素[0],已走過的路程=0,下一步該判斷結點1:最佳路徑是默認值{0,1,2}
cl=0; id=1; x[N]={0,1,2,3,4,5....};
5.終止循環條件:
解向量序號 | 0 | 1 | 2 | 3 | 4 | ... | N-2 | N-1 |
id | 1 | 2 | 3 | 4 | 5 | ... | N-1 | N |
在排列樹中,當遍歷到倒數第二層(N-2)時,該層結點的子節點只有1個 ,x[N]已完全確定
活結點終止循環(約束)條件爲
id==N-1 //遍歷到倒數第二層
T[ livenode.x[ N-1 ]][ livenode.x[ N-2 ]] != //倒數第二個和最後一個聯通
T[ 0 ][ livenode.x[ N-1 ] ] != //最後一個和第一個聯通
livenode.cl + T[ livenode.x[ N-1 ]][ livenode.x[ N-2 ]] + T[ 0 ][ livenode.x[ N-1 ] ] <bestp //相加後小於目前最優值
6.活結點的限界條件:(最優值是最小值的題型,不用寫上界函數)
if( livenode.cl > bestp )
continue;
7.求全排列的題型中,必定要有swap()存在,將子問題的解從原處交換到隊首
二.代碼實現
1.全局變量區
#include"allh.h"
//給定起點
//全局變量
const int N3 = 4; //城市數量
const int INF = INT_MAX;
vector<vector<int>>T3 ={ //鄰接矩陣
{INF,15,30,5},
{15,INF,6,12},
{30,6,INF,3},
{5,12,3,INF },
};
int bestp3; //記錄最優值
vector<int>bestx3(N3,0); //記錄最優解
//記錄狀態
struct State2
{
int cl; //記錄已走過的路程
int id; //記錄當前判斷的結點號
vector<int> x;
State2() {};
State2(int _cl, int _id) {
cl = _cl;
id = _id;
x.resize(N3);
}
//結構體組成優先隊列,需要寫優先函數,該函數不是struct的成員函數,只是作爲友元函數寫在結構體裏
friend bool operator <(const State2& a, const State2& b)
{
return a.cl > b.cl; //只看符號左邊:"怎樣的元素優先值更小呢?cl大的數"
}
};
2.廣度遍歷函數
double TravelingBFS()
{
//先壓入root結點,讓while()開動起來
int t; //記錄當前物品序號
State2 livenode, newnode;
newnode = State2(0, 1);
for (size_t i = 0; i < N3; i++)
newnode.x[i] = i;
priority_queue<State2> q2;
q2.push(newnode);
//開始循環
while (!q2.empty())
{
livenode = q2.top();
q2.pop();
t = livenode.id;
//活結點的終止循環(約束)條件
if (t == N3-1 && //到達倒數第二層
T3[livenode.x[N3 - 2]][livenode.x[N3 - 1]]!=INF && //倒數第二個城市和最後一個城市有連接
T3[livenode.x[ 0 ]][livenode.x[N3 - 1]] != INF && //最後一個城市和起點城市有連接
livenode.cl+ T3[livenode.x[N3 - 2]][livenode.x[N3 - 1]]+ T3[livenode.x[ 0 ]][livenode.x[N3 - 1]]<bestp3
//相加後 < 目前最優值
)
{
bestp3 = livenode.cl + T3[livenode.x[N3 - 2]][livenode.x[N3 - 1]] + T3[livenode.x[0]][livenode.x[N3 - 1]];
bestx3 = livenode.x;
continue; //接着循環
}
//活結點的限界條件
if (livenode.cl >= bestp3)
continue;
//開始搜索子結點
for (size_t i = t; i < N3; i++)
{
int cl = livenode.cl + T3[livenode.x[t - 1]][livenode.x[i]];
//livenode狀態下已處理的結點的序號=livenode.id-1
if (T3[livenode.x[t - 1]][livenode.x[i]] != INF && cl<bestp3 )
{
newnode = State2(cl, livenode.id + 1);
newnode.x = livenode.x;
swap(newnode.x[t], newnode.x[i]); //x[t]是子問題解向量的隊首,x[i]是可行解.交換後讓已知解空間+1
q2.push(newnode);
}
}
}
return bestp3;
}
3.調用&打印
void TSP()
{
//初始化
bestp3 = INF;
bestx3.resize(N3,0);
//遍歷
cout<<"最短距離爲: "<<TravelingBFS();
cout << endl;
cout << "最優旅行順序: ";
for (int i = 0; i <N3; i++)
{
cout << bestx3[i] << "-";
}cout << "0";
}
三.Bug總結
1.按求解值,分支限界法可分爲MAX/MIN兩種.本題爲最小值的問題,不需要Bound()函數作爲限界條件.如果當前值比bestp更大,直接跳過.
2.按最優策略,分支限界法可分爲01型和序列型.本題屬於序列型,注意swap()的使用