剪枝
剪枝
前言
图床在 \(Github\) 中 ,加载图片可能需要梯子
剪枝,字面的理解就是减去枝条删去不重要的节点,它实际上是一种来减少计算量的优化,它的应用十分广泛,如:决策树,神经网络,搜索算法,数据库的设计等,本次仅讨论在搜索算法中的剪枝
PS:在蓝桥杯等赛事中,剪枝可以很有效的拿到更多的分
回溯法伪码
int ans = 最坏情况;
void dfs(传入数值) {
if (到达目的地){
ans = 从当前解与已有解中选最优;
return...
}
for (遍历所有可能性)
if (可行) {
进行操作;
dfs(缩小规模);
撤回操作;
}
}
剪枝中最常用的三种方法
记忆化搜索、最优性剪枝、可行性剪枝。
记忆化搜索
定义
记忆化搜索是一种通过记录已经遍历过的状态的信息,从而避免对同一状态重复遍历的搜索实现方式。
因为记忆化搜索确保了每个状态只访问一次,它也是一种常见的动态规划实现方式。
记忆化数组
就拿斐波那契数列来看
\(fib[n]=fib[n-1]+fib[n-2]\),其中\(fib[1]=1,fib[2]=1\)
int fib(int n) {
if (n==1||n==2) return 1;
return fib(n - 1) + fib(n - 2);
}
如果我们要求 \(fib(5)\),以下是计算过程
可以发现 \(fib(3)\) 的值重复分解计算了,在更大的数据量时,重复计算的个数会更多
对此,可以通过用一个数组,记录下已经计算过的值,如果再次需要该状态的值,就不用继续分解计算了
import java.util.Arrays;
public class Main {
static long[] memory = new long[50];
static long fib(int n) {
//如果记忆化数组里的值不等于初始值
//说明此时该状态已经被计算过,直接返回记忆化数组内的值
if (memory[n] != -1) return memory[n];
//接下来在返回此状态的值之前,先赋值给记忆化数组
if (n == 1 || n == 2) {
memory[n] = 1;
} else {
memory[n] = fib(n - 1) + fib(n - 2);
}
return memory[n];
}
static void run() {
Arrays.fill(memory, -1);//给记忆化数组赋初值,该值需要是计算过程中不可能出现的值
System.out.println(fib(45));
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
run();
System.out.println("\n程序运行时间:" + (System.currentTimeMillis() - startTime) + "ms.");
}
}
对于计算 \(fib(45)\)
不适用记忆化数组的程序运行结果约为:\(2178\) 毫秒
而使用记忆化数组的程序运行结果为:小于 \(1\) 毫秒
记忆化搜索伪码
对回溯法使用记忆化数组进行优化
int[] memory=new int[MAXSIZE];
int ans = 最坏情况;
void dfs(传入数值){
if(memory[规模] != 无效数值) return...
if (到达目的地){
ans = 从当前解与已有解中选最优;
return...
}
for (遍历所有可能性){
if (可行) {
进行操作;
dfs(缩小规模);
撤回操作;
}
}
}
main(){
对memory赋初值,该值需要为无效数值
dfs(...);
}
最优性剪枝
简述
如果当前解已经比已有解差时,则不再继续往下搜索
最优性剪枝伪码
int ans = 最坏情况;
void dfs(传入数值){
if(当前解比ans更差) return...
if (到达目的地){
ans = 从当前解与已有解中选最优;
return...
}
for (遍历所有可能性){
if (可行) {
进行操作;
dfs(缩小规模);
撤回操作;
}
}
}
可行性剪枝
简述
如果当前解已经不能用了,后面更不可能用,则不再继续往下搜索
可行性剪枝伪码
int ans = 最坏情况;
void dfs(传入数值){
if(当前解已不可用) return...
if (到达目的地){
ans = 从当前解与已有解中选最优;
return...
}
for (遍历所有可能性){
if (可行) {
进行操作;
dfs(缩小规模);
撤回操作;
}
}
}
例子
024. 字典排列的 \(dfs\) 就可以用可行性剪枝进行优化
public class Main {
static int[] num = new int[10]; //存放十个数字0~9
static boolean[] exist = new boolean[10]; //判断每个数字是否已经存放(默认初始化为false没被存放)
static int n = 9;
static int flag = 0; //计数器
static void dfs(int step) {
if (flag >= 1000000) return;//计数已经完成,后面的就不用走了,直接返回
//终止条件
if (step == n + 1) { //走到最后一个数字啦,搜索结束的标志
flag++;
if (flag == 1000000) {
for (int i = 0; i <= n; i++)
System.out.print(num[i]);
}
return; //返回到上一级
}
for (int i = 0; i <= n; i++) {
if (!exist[i]) {
num[step] = i;
exist[i] = true;
dfs(step + 1);
exist[i] = false;//回溯
}
}
}
}
其中dfs
方法内的if (flag >= 1000000) return;
就是在进行可行性剪枝
总结
通过观察以上方法,均是跳过必不可能的搜索路径
对此,可以在做题时仔细思考边界、极端情况来进行剪枝优化,不必局限于各种方法