1. 程式人生 > >0038演算法筆記——【分支限界法】旅行員售貨問題

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;
}
     程式執行結果如圖: