总结算法设计与分析课程期末必记知识点。
回溯法实际上是一个类似穷举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时就“回溯”,尝试其他路径,所以回溯法有“通用的解题法”之称。
可行解和最优解
(1)结点是如何扩展的。 (2)在解空间树中按什么方式搜索,一种是采用深度优先遍历(DFS),回溯法就是这种方式;另一种是采用广度优先遍历(BFS),下一章介绍的分支限界法就是这种方式。 (3)解空间树通常是十分庞大的,如何高效地找到问题的解。
当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树称为子集树。当所给的问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。
活结点:指自身已生成但其孩子结点没有全部生成的结点 扩展结点:指正在产生孩子结点的结点 死结点:指其所有子结点均已产生的结点
一是:约束函数 二是:限界函数 这两类统称为剪枝函数
(1)针对给定的问题确定问题的解空间树,问题的解空间树应至少包含问题的一个解或者最优解。 (2)确定结点的扩展搜索规则。 (3)以深度优先方式搜索解空间树,并在搜索过程中可以采用剪枝函数来避免无效搜索。其中,深度优先方式可以选择递归回溯或者迭代(非递归)回溯。
相同点
不同的
在通常情况下,回溯法的效率会高于蛮力法
子集树的时间复杂度为:
排列树的时间复杂度为:
第一种
void dfs(int i, int tw, int tv, int rw, int op[])//求解0/1背包问题
{
if (i > n) //找到一个叶子结点
{
if (tw == W && tv > maxv) //找到一个满足条件的更优解,保存它
{
maxv = tv;
for (int j = 1; j <= n; j++) //复制最优解
x[j] = op[j];
}
}
else //尚未找完所有物品
{
if (tw + w[i] < W) //左孩子结点剪枝
{
op[i] = 1; //选取第i个物品
dfs(i + 1, tw + w[i], tv + v[i], rw - w[i], op);
}
if (tw + rw - w[i] >= W) //右孩子结点剪枝
{
op[i] = 0; //不选取第i个物品,回溯
dfs(i + 1, tw, tv, rw - w[i], op);
}
}
}
第二种
void dfs(int i, int tw, int tv, int op[])//求解0/1背包问题
{
if (i > n) //找到一个叶子结点
{
maxv = tv; //存放更优解
for (int j = 1; j <= n; j++)
x[i] = op[j];
}
else //尚未找完所有物品
{
if (tw + A[i].w <= W) //左孩子结点剪枝
{
op[i] = 1; //选取序号为i的物品
dfs(i + 1, tw + A[i].w, tv + A[i].v, op);
}
if (bound(i, tw, tv) > maxv) //右孩子结点剪枝
{
op[i] = 0; //不选取序号为i的物品,回溯
dfs(i + 1, tw, tv, op);
}
}
}
重点记dfs函数
考法一:
#include<stdio.h>
#define MAXN 20 //最多整数个数
//问题表示
int n = 4, W = 31;
int w[] = { 0,11,13,24,7 };//存放所有整数,不用下标0的元素
int count = 0; //累计解个数
void dispasolution(int n)//输出一个解
{
for (int j = 1; j <= n; j++)
if (w[j] == 1)
printf("\t将第%d个集装箱装上第一艘轮船\n", j);
else
printf("\t将第%d个集装箱装上第二艘轮船\n", j);
}
void dfs(int i, int tw, int rw, int x[])//求解子集和
{ //tw为考虑第i个整数时选取的整数和,rw为剩下的整数和
if (i > n)//找到一个叶子结点
{
if (tw == W)//找到一个满足条件的解,输出它
dispasolution(tw);
}
else //尚未找完所有整数
{
if (tw + w[i] <= W)//左孩子结点剪枝:选取满足条件的整数w[i]
{
x[i] = 1;//选取第i个整数
dfs(i + 1, tw + w[i], rw - w[i], x);
}
if (tw + rw - w[i] >= W)//右孩子结点剪枝
{
x[i] = 0; //不选取第i个整数,回溯
dfs(i + 1, tw, rw - w[i], x);
}
}
}
int main() {
int x[MAXN];
int rw = 0;//存放一个解向量
for (int j = 1; j <= n; j++)
rw += w[j];//求所有整数和rw
dfs(1, 0, rw, x);//i从1开始
return 0;
}
考法二:
#include<stdio.h>
#define MAXN 20 //最多整数个数
//问题表示
int n = 4, W;
int w[] = { 0,11,13,24,7 }; //存放所有整数,不用下标0的元素
bool dfs(int i, int tw, int rw) //存放所有整数,不用下标0的元素
{
if (i > n) //找到一个叶子结点
{
if (tw == W) //找到一个满足条件的解,返回true
{
return true;
}
}
else //尚未找完所有物品
{
if (tw + w[i] <= W) //左孩子结点剪枝
return dfs(i + 1, tw + w[i], rw - w[i]);//选取第i个整数
if (tw + rw - w[i] >= W) //有孩子结点剪枝
return dfs(i + 1, tw, rw - w[i]); //不选取第i个整数,回溯
}
return false;
}
考法三:
int count; //全局变量,累计解个数
void dfs(int i, int tw, int rw) //求解子集和
{
if (i > n) //找到一个叶子结点
{
if (tw == W) //找到一个满足条件的解,count++
{
count++;
}
}
else //尚未找完所有物品
{
if (tw + w[i] <= W) //左孩子结点剪枝
dfs(i + 1, tw + w[i], rw - w[i]);//选取第i个整数
if (tw + rw - w[i] >= W) //有孩子结点剪枝
dfs(i + 1, tw, rw - w[i]); //不选取第i个整数,回溯
}
}
第五章回溯法结束,下一章——第六章分支限界法