概述
要求解一个问题,最可靠的方法是:列出所有候选解,然后逐个检查,理论上在遍历了所有的候选解之后,就能得到所需要的解。但是,对于实际运用中的问题,候选解的规模庞大,即使使用速度最快的计算机,也很难在合理的时间内解决。
回溯法和分支定界法师对复杂问题进行系统检查的系统方法。通过使用限制条件和界定函数,可以省去对很大一部分候选解的检查。
回溯法——深度优先搜索
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的方法称为回溯法。满足回溯条件的某个状态的点称为活动节点, 而当前节点为E-节点, 一旦从E-节点不能达到新的节点,则活动节点被杀死,开始回溯。
算法思想
回溯法思想是:首先定义问题的解空间,并把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
基本思想类同于:
- 图的深度优先搜索
- 二叉树的后序遍历
算法步骤
1)定义问题的一个解空间,它包含了对问题实例的解。
2)用适合于搜索的方式组织解空间。
3)用深度优先搜索解空间,利用界定函数避免进入无解的子空间。
回溯法的实现有一个有意义的特性:再继续搜索的同时产生解空间。在搜索过程中的任何时刻,仅保留从开始节点到当前E-节点。因此,回溯算法的空间需求为O(从开始节点起最长路径的长度)。
分值定界法——广度搜索优先
类似于回溯法,也是一种在问题的解空间上搜索问题解的算法。但与回溯法不同,分值定界法一般用广度优先搜索或最小耗费方法来搜索解空间。
算法思想
分支定界法的思想是:首先定义问题的解空间,并把问题的解空间转化成了图或者树的结构表示,然后使用广度优先搜索或者最小耗费(最大收益)策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
基本思想类同于:
- 图的广度优先搜索
- 二叉树的层序遍历
与回溯法的E-节点扩展方式不同,分值定界法在E-节点处,首先生成其满足条件的所有儿子结点(分支),然后再从当前的活动结点表中选择下一个E-节点。为了有效地选择下一E-节点,以加速搜索的进程,在每一个活动节点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活动节点表中选择一个最有利的节点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
选择下一个E-结点的方式不同,则会有几种不同的分支搜索方式。
1)FIFO搜索
2)LIFO搜索
3)优先队列式搜索
算法步骤
1)定义问题的一个解空间,它包含了对问题实例的解。
2)用适合于搜索的方式组织解空间。
3)创建并维护一一张活动节点表,使用广度优先搜索解空间,利用界定函数避免进入无解的子空间。
回溯法和分支定界法的区别
常见解空间
不管是回溯法还是分支定界法,定义和组织解空间都是算法实现的前提,而常见的解空间有子集树和排列树。
子集树
所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间成为子集树。遍历子集树的算法复杂度为Ω(2^n)。
如0/1背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。
回溯法搜索子集树的一般描述如下:
void backtrack (int t)
{
if (t>n)
output(x);
else
for (int i=0;i<=1;i++)
{
x[t]=i;
if (constraint(t)&&bound(t))
backtrack(t+1);
}
}
排列树
所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。遍历排列树的算法复杂度为Ω(n!)。
例如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。
回溯法搜索排列树的一般描述如下:
void backtrack(int t)
{
if (t > n)
output(x);
else
for (int i = t; i < n; i++)
{
swap(x[t], x[i]);
if (constraint(t) && bound(t))
backtrack(t + 1);
swap(x[t], x[i]);
}
}