【旅行商售货员问题】“分支限界法”——《算法设计与分析(第五版)》


一、算法要求

某售货员要到若干城市去推销商品,已知各城市之间的路程(或旅费)。
他要选定一条从驻地出发,经过每个城市一次,最后回到驻地的路线,使总的路程(或总旅费)最小。

1. 思路

现在我们从景点A出发,要去B、C、D、E共4个景点,按上面顺序给景点编号1~5,每个景点用一个结点表示,可以直接到达的景点有连线,连线上的数字代表两个景点之间的路程(时间)。那么要去的景点地图就转化成了一个无向带权图。
在无向带权图G=(V,E)中,结点代表景点,连线上的数字代表景点之间的路径长度。我们从1号结点出发,先走哪些景点,后走哪些景点呢?只要是可以直接到达的,即有边相连的,都是可以走的。问题就是要找出从出发地开始的一个景点排列,按照这个顺序旅行,不重复地走遍所有景点回到出发地,所经过的路径长度是最短的。
因此,问题的解空间是一棵排列树。显然,对于任意给定的一个无向带权图,存在某两个景点之间没有直接路径的情况。也就是说,并不是任何一个景点排列都是一条可行路径(问题的可行解),因此需要设置约束条件,判断排列中相邻的两个景点之间是否有边相连,有边的则可以走通;反之,不是可行路径。另外,在所有的可行路径中,要求找出一条最短路径,因此需要设置限界条件。
在这里插入图片描述


二、完整代码

1. 主文件

main.cpp:

// Project4: 着色问题

#include"Improve1.h"
#include <iostream>
#include <iomanip>
using namespace std;

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]
};


//优先队列式分支限界法
template<class Type>
Type Traveling<Type>::BBTSP(int v[]){
	MinHeap<MinHeapNode<Type>> chessBoard(1000);		//定义最小堆的容量为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++;
				chessBoard.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;
						chessBoard.Insert(N);
					}
				}
			}
			delete[]E.x;			//完成节点扩展
		}
		if (chessBoard.Size() == 0) {
			break;					//堆空
		}
		chessBoard.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 (chessBoard.Size() == 0) {
			break;					//堆空
		}
		chessBoard.DeleteMin(E);				//取下一扩展节点
	}
	return bestc;
}


int main()
{
	int bestx[N + 1],
		** a = new int* [N + 1],		//用a[][]存储邻接矩阵
		simpleArray[4][4] = { {0,30,6,4},
							{30,0,5,10} ,
							{6,5,0,20} ,
							{4,10,20,0} };

	cout << "\nThe number of vertices in the graph: n = " << N << endl;

	//初始化对应位置的邻接矩阵
	for (int i = 0; i <= N; i++) {
		a[i] = new int[N + 1];
	}
	//输出数据输入情况
	cout << "\nThe adjacency matrix of the graph is as follows: " << endl;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			a[i + 1][j + 1] = simpleArray[i][j];
			cout << "|" << setw(4) << a[i + 1][j + 1];
		}
		cout << "|" << endl;
	}

	Traveling<int> example;
	example.a = a;
	example.n = N;

	cout << "\n#The length of the shortest loop is:" << example.BBTSP(bestx) << endl;
	cout << "\n#The path of the shortest loop is:";
	for (int i = 1; i <= N; i++) {
		cout << setw(3) << bestx[i] << "  ==>";
	}
	cout << setw(3) << bestx[1] << endl;

	//析构,**a删除申请的空间
	for (int i = 0; i < N + 1; i++) {
		delete[]a[i];
	}
	delete[]a;

	return 0;
}

2. 头文件

Improve1.h:

#pragma once
#ifndef __IMPROVE1__
#define __IMPROVE1__
#include <iostream>
using namespace std;

//============================最小堆============================//
template<class T>
class MinHeap{	//最小堆
public:
	MinHeap(int maxheapsize = 10) {
		maxsize = maxheapsize;
		heap = new T[maxsize + 1];
		currentsize = 0;
	}

	~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<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;
}

#endif



3. 效果展示

在这里插入图片描述


三、补充

(1)时间复杂度
最坏情况下,如图6-46所示。除了最后一层外,有1+n+n(n-1)+…+(n-1)(n-2)…2≤n(n-1)!个结点需要判断约束函数和限界函数,判断两个函数需要O(1)的时间,因此耗时O(n!),时间复杂度为O(n!)。
(2)空间复杂度
程序中我们设置了每个结点都要记录当前的解向量x[]数组,占用空间为O(n),结点的个数最坏为O(n!),所以该算法的空间复杂度为O(n*n!)。

文档供本人学习笔记使用,仅供参考。

  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NI'CE'XIAN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值