算法设计与分析第五章复习

算法设计与分析第五章复习

第5章 回溯法

学习要点

1.理解回溯法的深度优先搜索策略。
2.掌握用回溯法解题的算法框架
(1)递归回溯
(2)迭代回溯
(3)子集树算法框架
(4)排列树算法框架

3.通过应用范例学习回溯法的设计策略。
(1)装载问题;
(2)批处理作业调度;
(3)符号三角形问题
(4)n后问题;
(5)0-1背包问题;
(6)最大团问题;
(7)图的m着色问题
(8)旅行售货员问题
(9)圆排列问题
(10)电路板排列问题
(11)连续邮资问题

有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。
回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。

回溯法的基本思想

(1)针对所给问题,定义问题的解空间;
(2)确定易于搜索的解空间结构;
(3)以深度优先方式搜索解空间,并在搜索过程中用
剪枝函数避免无效搜索。

常用剪枝函数:

用约束函数在扩展结点处剪去不满足约束的子树;
用限界函数剪去得不到最优解的子树。

用回溯法解题的一个显著特征是在搜索过程中动态产生问题的解空间。在任何时刻,算法只保存从根结点到当前扩展结点的路径。如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。而显式地存储整个解空间则需要O(2h(n))或O(h(n)!)内存空间。

递归回溯
迭代回溯

子集树算法框架和排列数算法框架

在这里插入图片描述

一、装载问题

在这里插入图片描述

容易证明,如果一个给定装载问题有解,则采用下面的策略可得到最优装载方案。
(1)首先将第一艘轮船尽可能装满;
(2)将剩余的集装箱装上第二艘轮船。
将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近。由此可知,装载问题等价于以下特殊的0-1背包问题。
在这里插入图片描述
解空间:子集树
•可行性约束函数(选择当前元素):
在这里插入图片描述

•上界函数(不选择当前元素):
当前载重量cw+剩余集装箱的重量r<=当前最优载重量bestw

void backtrack (int i)
{// 搜索第i层结点
	if (i > n) // 到达叶结点
	更新最优解bestx,bestw;return;
	r -= w[i];
	if (cw + w[i] <= c)
	{// 搜索左子树
		x[i] = 1;
		cw += w[i];
		backtrack(i + 1);
		cw -= w[i]; 
	}
	
	if (cw + r > bestw) 
	{
		x[i] = 0; // 搜索右子树
		backtrack(i + 1); 
	}
		r += w[i];
}

未完待续。。。

二、批出理作业调度

在这里插入图片描述
这3个作业的6种可能的调度方案是1,2,3;1,3,2;2,1,3;2,3,1;3,1,2;3,2,1;它们所相应的完成时间和分别是19,18,20,21,19,19。易见,最佳调度方案是1,3,2,其完成时间和为18。

解空间:排列树

void Flowshop::Backtrack(int i)
{
	if (i > n) 
	{
		for (int j = 1; j <= n; j++)
		bestx[j] = x[j];
		bestf = f;
	}
	else
	for (int j = i; j <= n; j++) 
	{
		f1+=M[x[j]][1];
		f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+M[x[j]][2];
		f+=f2[i];
		if (f < bestf) 
		{
			Swap(x[i], x[j]);
			Backtrack(i+1);
			Swap(x[i], x[j]);
		}
	f1- =M[x[j]][1];
	f- =f2[i];
	}
}
class Flowshop 
{
	friend Flow(int**, int, int []);
	private:
	void Backtrack(int i);
	int **M, // 各作业所需的处理时间
		*x, // 当前作业调度
		*bestx, // 当前最优作业调度
		*f2, // 机器2完成处理时间
		f1, // 机器1完成处理时间
		f, // 完成时间和
		bestf, // 当前最优值
		n; // 作业数
}; 

三、符号三角形问题

在这里插入图片描述
•解向量:用n元组x[1:n]表示符号三角形的第一行。
•可行性约束函数:当前符号三角形所包含的“+”个数与“-”个数均不超过n*(n+1)/4
•无解的判断:n*(n+1)/2为奇数

void Triangle::Backtrack(int t)
{
	if ((count>half)||(t*(t-1)/2-count>half)) 
	return;
	if (t>n) 
	sum++;
	else
	for (int i=0;i<2;i++) 
	{
		p[1][t]=i;
		count+=i;
		for (int j=2;j<=t;j++) 
		{
			p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2];
			count+=p[j][t-j+1];
		}
		Backtrack(t+1);
		for (int j=2;j<=t;j++)
		count-=p[j][t-j+1];
		count-=i;
	}
}

复杂度分析
计算可行性约束需要O(n)时间,在最坏情况下有O(2n)个结点需要计算可行性约束,故解符号三角形问题的回溯算法所需的计算时间为 O(n2n)。

四、n后问题

在这里插入图片描述
解向量:(x1, x2, … , xn)
显约束:xi=1,2, … ,n
隐约束:1)不同列:xi<>xj
2)不处于同一正、反对角线:|i-j|<>|xi-xj|
x[i]表示皇后i放在棋盘的第i行的第x[i]列

bool Queen::Place(int k) 
{
	for (int j=1;j<k;j++)
	if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k])) 
	return false;
	return true;
}
void Queen::Backtrack(int t) 
{
	if (t>n) 
	sum++;
	else
	for (int i=1;i<=n;i++) 
	{
		x[t]=i;
		if (Place(t)) 
		Backtrack(t+1);
	}
}

五、0-1背包问题

解空间:子集树
在这里插入图片描述

template<class Typew, class Typep>
Typep Knap<Typew, Typep>::Bound(int i)
{// 计算上界
	Typew cleft = c
	- cw; // 剩余容量
	Typep b = cp;
// 以物品单位重量价值递减序装入物品
	while (i <= n && w[i] <= cleft) 
	{
		cleft-= w[i];
		b += p[i];
		i++; 
	}
// 装满背包
	if (i <= n) 
	b += p[i]/w[i] * cleft;
	return b;
}

六、最大团问题

•解空间:子集树
•可行性约束函数:顶点i到已选入的顶点集中每一个顶点都有边相连。
•上界函数:有足够多的可选择顶点使得算法有可能在右子树中找到更大的团。

void Clique::Backtrack(int i)
{// 计算最大团
	if (i > n) 
	{// 到达叶结点
		for (int j = 1; j <= n; j++) 
		bestx[j] = x[j];
		bestn = cn; 
		return;
	}
// 检查顶点 i 与当前团的连接
	int OK = 1;
	for (int j = 1; j < i; j++)
	if (x[j] && a[i][j] == 0) 
	{
	// i与j不相连
		OK = 0; 
		break;
	}
	if (OK) 
	{// 进入左子树
		x[i] = 1; 
		cn++;
		Backtrack(i+1);
		x[i] = 0; 
		cn--;
	}
if (cn + n - i > bestn) 
{// 进入右子树
	x[i] = 0;
	Backtrack(i+1);}
}

复杂度分析
最大团问题的回溯算法backtrack所需的计算时间显然为O(n2n)。

七、旅行售货员问题

template<class Type>
void Traveling<Type>::Backtrack(int i)
{
	if (i == n) 
	{
		if (a[x[n-1]][x[n]] != NoEdge && a[x[n]][1] != NoEdge &&
		(cc + a[x[n-1]][x[n]] + a[x[n]][1] < bestc || bestc == NoEdge)) 
		{
			for (int j = 1; j <= n; j++) bestx[j] = x[j];
			bestc = cc + a[x[n-1]][x[n]] + a[x[n]][1];}
		}
		else 
		{
			for (int j = i; j <= n; j++)
// 是否可进入x[j]子树?
			if (a[x[i-1]][x[j]] != NoEdge &&
			(cc + a[x[i-1]][x[i]] < bestc || bestc == NoEdge)) 
			{
				// 搜索子树	
				Swap(x[i], x[j]);
				cc += a[x[i-1]][x[i]];
				Backtrack(i+1);
				cc -= a[x[i-1]][x[i]];
				Swap(x[i], x[j]);
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值