0038演算法筆記——【分支限界法】旅行員售貨問題
問題描述
某售貨員要到若干城市去推銷商品,已知各城市之間的路程(旅費),他要選定一條從駐地出發,經過每個城市一遍,最後回到駐地的路線,使總的路程(總旅費)最小。
演算法思路
旅行售貨員問題的解空間可以組織成一棵樹,從樹的根結點到任一葉結點的路徑定義了圖的一條周遊路線。旅行售貨員問題要在圖G中找出費用最小的周遊路線。路線是一個帶權圖。圖中各邊的費用(權)為正數。圖的一條周遊路線是包括V中的每個頂點在內的一條迴路。周遊路線的費用是這條路線上所有邊的費用之和。
演算法開始時建立一個最小堆,用於表示活結點優先佇列。堆中每個結點的子樹費用的下界lcost值是優先佇列的優先順序。接著演算法計算出圖中每個頂點的最小費用出邊並用minout記錄。如果所給的有向圖中某個頂點沒有出邊,則該圖不可能有迴路,演算法即告結束。如果每個頂點都有出邊,則根據計算出的minout作演算法初始化。
演算法的while迴圈體完成對排列樹內部結點的擴充套件。對於當前擴充套件結點,演算法分2種情況進行處理:
1、首先考慮s=n-2的情形,此時當前擴充套件結點是排列樹中某個葉結點的父結點。如果該葉結點相應一條可行迴路且費用小於當前最小費用,則將該葉結點插入到優先佇列中,否則捨去該葉結點。
2、當s<n-2時,演算法依次產生當前擴充套件結點的所有兒子結點。由於當前擴充套件結點所相應的路徑是x[0:s],其可行兒子結點是從剩餘頂點x[s+1:n-1]中選取的頂點x[i],且(x[s],x[i])是所給有向圖G中的一條邊。對於當前擴充套件結點的每一個可行兒子結點,計算出其字首(x[0:s],x[i])的費用cc和相應的下界lcost。當lcost<bestc時,將這個可行兒子結點插入到活結點優先佇列中。
演算法中while迴圈的終止條件是排列樹的一個葉結點成為當前擴充套件結點。當s=n-1時,已找到的迴路字首是x[0:n-1],它已包含圖G的所有n個頂點。因此,當s=n-1時,相應的擴充套件結點表示一個葉結點。此時該葉結點所相應的迴路的費用等於cc和lcost的值。剩餘的活結點的lcost值不小於已找到的迴路的費用。它們都不可能導致費用更小的迴路。因此已找到的葉結點所相應的迴路是一個最小費用旅行售貨員迴路,演算法可以結束。
演算法結束時返回找到的最小費用,相應的最優解由陣列v給出。
演算法執行過程最小堆中元素變化過程如下:
{ }—{B}—{C,D,E}—{C,D,J,K}—{C,J,K,H,I}—{C,J,K,I,N}—{C,K,I,N,P}—{C,I,N,P,Q}—{C,N,P,Q,O}—{C,P,Q,O}—{C,Q,O}—{Q,O,F,G}—{Q,O,G,L}—{Q,O,L,M}—{O,L,M}—{O,M}—{M}—{ }
演算法具體實現如下:
1、MinHeap2.h
#include <iostream>
template<class Type>
class Graph;
template<class T>
class MinHeap
{
template<class Type>
friend class Graph;
public:
MinHeap(int maxheapsize = 10);
~MinHeap(){delete []heap;}
int Size() const{return currentsize;}
T Max(){if(currentsize) return heap[1];}
MinHeap<T>& Insert(const T& x);
MinHeap<T>& DeleteMin(T &x);
void Initialize(T x[], int size, int ArraySize);
void Deactivate();
void output(T a[],int n);
private:
int currentsize, maxsize;
T *heap;
};
template <class T>
void MinHeap<T>::output(T a[],int n)
{
for(int i = 1; i <= n; i++)
cout << a[i] << " ";
cout << endl;
}
template <class T>
MinHeap<T>::MinHeap(int maxheapsize)
{
maxsize = maxheapsize;
heap = new T[maxsize + 1];
currentsize = 0;
}
template<class T>
MinHeap<T>& MinHeap<T>::Insert(const T& x)
{
if(currentsize == maxsize)
{
return *this;
}
int i = ++currentsize;
while(i != 1 && x < heap[i/2])
{
heap[i] = heap[i/2];
i /= 2;
}
heap[i] = x;
return *this;
}
template<class T>
MinHeap<T>& MinHeap<T>::DeleteMin(T& x)
{
if(currentsize == 0)
{
cout<<"Empty heap!"<<endl;
return *this;
}
x = heap[1];
T y = heap[currentsize--];
int i = 1, ci = 2;
while(ci <= currentsize)
{
if(ci < currentsize && heap[ci] > heap[ci + 1])
{
ci++;
}
if(y <= heap[ci])
{
break;
}
heap[i] = heap[ci];
i = ci;
ci *= 2;
}
heap[i] = y;
return *this;
}
template<class T>
void MinHeap<T>::Initialize(T x[], int size, int ArraySize)
{
delete []heap;
heap = x;
currentsize = size;
maxsize = ArraySize;
for(int i = currentsize / 2; i >= 1; i--)
{
T y = heap[i];
int c = 2 * i;
while(c <= currentsize)
{
if(c < currentsize && heap[c] > heap[c + 1])
c++;
if(y <= heap[c])
break;
heap[c / 2] = heap[c];
c *= 2;
}
heap[c / 2] = y;
}
}
template<class T>
void MinHeap<T>::Deactivate()
{
heap = 0;
}
2、6d7.cpp
//旅行員售貨問題 優先佇列分支限界法求解
#include "stdafx.h"
#include "MinHeap2.h"
#include <iostream>
#include <fstream>
using namespace std;
ifstream fin("6d7.txt");
const int N = 4;//圖的頂點數
template<class Type>
class Traveling
{
friend int main();
public:
Type BBTSP(int v[]);
private:
int n; //圖G的頂點數
Type **a, //圖G的鄰接矩陣
NoEdge, //圖G的無邊標識
cc, //當前費用
bestc; //當前最小費用
};
template<class Type>
class MinHeapNode
{
friend Traveling<Type>;
public:
operator Type() const
{
return lcost;
}
private:
Type lcost, //子樹費用的下屆
cc, //當前費用
rcost; //x[s:n-1]中頂點最小出邊費用和
int s, //根節點到當前節點的路徑為x[0:s]
*x; //需要進一步搜尋的頂點是x[s+1,n-1]
};
int main()
{
int bestx[N+1];
cout<<"圖的頂點個數 n="<<N<<endl;
int **a=new int*[N+1];
for(int i=0;i<=N;i++)
{
a[i]=new int[N+1];
}
cout<<"圖的鄰接矩陣為:"<<endl;
for(int i=1;i<=N;i++)
{
for(int j=1;j<=N;j++)
{
fin>>a[i][j];
cout<<a[i][j]<<" ";
}
cout<<endl;
}
Traveling<int> t;
t.a = a;
t.n = N;
cout<<"最短迴路的長為:"<<t.BBTSP(bestx)<<endl;
cout<<"最短迴路為:"<<endl;
for(int i=1;i<=N;i++)
{
cout<<bestx[i]<<"-->";
}
cout<<bestx[1]<<endl;
for(int i=0;i<=N;i++)
{
delete []a[i];
}
delete []a;
a=0;
return 0;
}
//解旅行員售貨問題的優先佇列式分支限界法
template<class Type>
Type Traveling<Type>::BBTSP(int v[])
{
MinHeap<MinHeapNode<Type>> H(1000);
Type * MinOut = new Type[n+1];
//計算MinOut[i] = 頂點i的最小出邊費用
Type MinSum = 0; //最小出邊費用和
for(int i=1; i<=n; i++)
{
Type Min = NoEdge;
for(int j=1; j<=n; j++)
{
if(a[i][j]!=NoEdge && (a[i][j]<Min||Min==NoEdge))
{
Min = a[i][j];
}
}
if(Min == NoEdge)
{
return NoEdge; //無迴路
}
MinOut[i] = Min;
MinSum += Min;
}
//初始化
MinHeapNode<Type> E;
E.x = new int[n];
for(int i=0; i<n; i++)
{
E.x[i] = i+1;
}
E.s = 0; //根節點到當前節點路徑為x[0:s]
E.cc = 0; //當前費用
E.rcost = MinSum;//最小出邊費用和
Type bestc = NoEdge;
//搜尋排列空間樹
while(E.s<n-1)//非葉結點
{
if(E.s == n-2)//當前擴充套件節點是葉節點的父節點
{
//再加2條邊構成迴路
//所構成迴路是否優於當前最優解
if(a[E.x[n-2]][E.x[n-1]]!=NoEdge && a[E.x[n-1]][1]!=NoEdge
&& (E.cc+a[E.x[n-2]][E.x[n-1]]+a[E.x[n-1]][1]<bestc
|| bestc == NoEdge))
{
//費用更小的迴路
bestc = E.cc + a[E.x[n-2]][E.x[n-1]]+a[E.x[n-1]][1];
E.cc = bestc;
E.lcost = bestc;
E.s++;
H.Insert(E);
}
else
{
delete[] E.x;//捨棄擴充套件節點
}
}
else//產生當前擴充套件節點的兒子節點
{
for(int i=E.s+1;i<n;i++)
{
if(a[E.x[E.s]][E.x[i]]!=NoEdge)
{
//可行兒子節點
Type cc = E.cc + a[E.x[E.s]][E.x[i]];
Type rcost = E.rcost - MinOut[E.x[E.s]];
Type b = cc + rcost;//下界
if(b<bestc || bestc == NoEdge)
{
//子樹可能含有最優解
//節點插入最小堆
MinHeapNode<Type> N;
N.x = new int[n];
for(int j=0; j<n; j++)
{
N.x[j] = E.x[j];
}
N.x[E.s+1] = E.x[i];
N.x[i] = E.x[E.s+1];
N.cc = cc;
N.s = E.s + 1;
N.lcost = b;
N.rcost = rcost;
H.Insert(N);
}
}
}
delete []E.x;//完成節點擴充套件
}
if(H.Size() == 0)
{
break;
}
H.DeleteMin(E);//取下一擴充套件節點
}
if(bestc == NoEdge)
{
return NoEdge;//無迴路
}
//將最優解複製到v[1:n]
for(int i=0; i<n; i++)
{
v[i+1] = E.x[i];
}
while(true)//釋放最小堆中所有節點
{
delete []E.x;
if(H.Size() == 0)
{
break;
}
H.DeleteMin(E);//取下一擴充套件節點
}
return bestc;
}
程式執行結果如圖: