算法学习系列(四十五):DFS之剪枝与优化

2024-04-03 12:28

本文主要是介绍算法学习系列(四十五):DFS之剪枝与优化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 引言
  • DFS之剪枝与优化
  • 一、小猫爬山
  • 二、木棒
  • 三、数独

引言

关于这个 D F S DFS DFS 的剪枝和优化确实难度是非常的大,从我这篇文章的思路和代码量上就能看出来不是一般的难度,而且难度不亚于 D P DP DP ,而且这个 D F S DFS DFS 也是花费了我三天的时间才基本把这几道例题给搞懂了,并且这种题就是没有固定的模型和套路,每个题都不一样,只有你多做题,这样在考场上才能想到这道题好像跟之前做过的题有点相似,然后再去套用模型。不可能短时间去创造一种算法,所以还是继续刷题吧,加油!


DFS之剪枝与优化

  • 优化搜索顺序:大部分情况下,我们应该优先搜索分支较少的节点。比如背包问题应该先放体积大的物品
  • 排除等效冗余:组合问题中, 1 , 2 , 3 1,2,3 123 1 , 3 , 2 1,3,2 132 是一样的,可以用一个 s t a r t start start 来规定初始
  • 可行性剪枝:发现当前状态不合法,退出
  • 最优性剪枝:如果发现当前方案不可能是最优解了,就退出

搜索顺序模型:
1.给一堆物品及其单个体积和单个篮子的容量,问最小的篮子数。解决方案:枚举每个物品,该物品要么加入到已有的篮子里,要么新开一个篮子。
2.给一堆物品及其单个体积,问刚好能把所有篮子都装满的最小篮子容量。解决方案:枚举每个篮子,然后在每个篮子中枚举每个物品。
3.给一堆物品,装进篮子里,但都有一定的限制。解决方案:枚举每个物品


一、小猫爬山

标签:搜索、深度优先搜索、DFS

思路:首先要保证正确性,也就是枚举的所有方案必须是不重不漏的,然后才对其进行优化,不能本末颠倒了。我们可以按小猫的编号进行枚举,当前小猫要么新坐一辆缆车,要么从已有的缆车里挑一个坐。首先是优化搜索顺序,可以把小猫的重量从大到小排序,进行枚举,然后就是如果当前的方案已经不是最优解了,直接退出,再有就是如果当前缆车已经容不下当前小猫了,退出。

题目描述:

翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。翰翰和达达只好花钱让它们坐索道下山。索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。当然,每辆缆车上的小猫的重量之和不能超过 W。每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?输入格式
第 1 行:包含两个用空格隔开的整数,N 和 W。第 2..N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。数据范围
1≤N≤18,1≤Ci≤W≤108
输入样例:
5 1996
1
2
1994
12
29
输出样例:
2

示例代码:

#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 25;int n, m;
int w[N];
int sum[N];
int ans = N;void dfs(int u, int k)  // 正在选编号为u的小猫,当前的组数为k 
{if(k >= ans) return;  // 最优性剪枝if(u == n)  // u是从0开始的,这是说明已经全部选完了,的组数k {ans = k; return;}for(int i = 1; i <= k; ++i)  // 要么从当前组里调一个组放进去{if(sum[i] + w[u] <= m)  // 可行性剪枝{sum[i] += w[u];dfs(u+1,k);sum[i] -= w[u];}}sum[k+1] = w[u];  // 要么直接新开一个组dfs(u+1,k+1);sum[k+1] = 0;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);cin >> n >> m;for(int i = 0; i < n; ++i) cin >> w[i];sort(w, w + n, greater<int>());  // 搜索顺序优化dfs(0,0);cout << ans << endl;return 0;
}

二、木棒

标签:搜索、DFS、剪枝

思路:首先这道题是找最小的木棒长度,也就是组数是无所谓的,只要能凑出来每组最小的容量即可。之前求最小组数的思路是,枚举每一个物品,然后这件物品,要么加入到当前任意一个组里,要么新开一个组,在此基础上进行剪枝优化。而这道题是从小到大枚举容量,如果当前容量恰好能装满每一个组,那就是答案了。然后在每一个组的基础上枚举每一件物品,放入该组里,如果当前组满了,然后再开下一个组,然后在此进行剪枝优化。首先是优化搜索顺序,先从大到小排序,然后最最小的也就是最长的那根木棍了。然后因为是每个组都要枚举每一件物品,所以避免出现组合型冗余,规定一个 s t a r t start start 枚举顺序,因为之前的已经枚举过了,如果继续从 0 0 0 开始,那么还要枚举重复的,比如说: 1 , 2 , 3 和 3 , 1 , 2 1,2,3和3,1,2 1,2,33,1,2 之类的方案。然后就是可行性剪枝了,就是如果当前组装不下,那就不装了,另外如果当前木棍是第一个装的,但没有满足要求,那么该类方案一定没有解,因为如果该木棍是下一组的,可以通过在组内排顺序把它变成第一个,那么上一次没有满足要求,这次也就不会满足要求了。其次如果该木棍是最后一根刚好装满了,但是之后的方案却没有满足,那么该方案之后的所有都不会满足的,因为如果满足要求了,那么肯定会有几根木棍凑出来最后一根,因为这两长度是一样的,所以可以替换,但因为之前的最后一根已经不满足了,所以肯定不会有满足要求的情况存在。

题目描述:

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。输入格式
输入包含多组数据,每组数据包括两行。第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。第二行是截断以后,所得到的各节木棍的长度。在最后一组数据之后,是一个零。输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。数据范围
数据保证每一节木棍的长度均不大于 50。输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
6
5

示例代码:

#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 100;int n;
int w[N];
bool st[N];
int sum, len;bool dfs(int u, int cur, int start)  // 当前u组里,目前的容量为cur,从start开始
{if(u * len == sum) return true;  // 说明前u个组里已经满了,并且长度已经达到总和if(cur == len) return dfs(u+1,0,0);  // 当前组已经满了,新开下一个组for(int i = start; i < n; ++i)  // 排序冗余{if(st[i]) continue;  // 可行性剪枝 只能用一次if(cur + w[i] <= len)  // 可行性剪枝 不能超过容量{st[i] = true;if(dfs(u,cur+w[i],i+1)) return true;st[i] = false;}if(!cur || cur + w[i] == len) return false;  // 可行性剪枝int j = i + 1;while(j < n && w[i] == w[j]) j++;i = j - 1;}return false;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);while(cin >> n, n){sum = 0, len = 0; for(int i = 0; i < n; ++i) {cin >> w[i];sum += w[i];len = max(len,w[i]);}sort(w, w + n, greater<int>());memset(st, 0, sizeof st);while(1){if(sum % len == 0 && dfs(0,0,0)){cout << len << endl;break;}len++;}} return 0;
}

三、数独

标签:搜索、深度优先搜索、DFS

思路:这道题就是枚举每一个空格,如果最后没有空格了,说明正确直接返回。每一行每一列和每一个田字格可以用一个 9 9 9 位的二进制数来表示,每一位为 1 1 1 说明当前位是空位。然后优化顺序,可以每次找到最少可能的格子来先枚举,可以用与运算行列和当前的田字格,来知道当前格子的 9 9 9 位二进制状态,然后可以提前打表,知道这个数是有几个 1 1 1 组成的,然后枚举该格子的多种可能,然后可以用 l o w b i t lowbit lowbit 来知道最后一位 1 1 1 对应的数,然后在可以通过提前打表知道该数所对应的位,然后暴搜各种可能即可。

题目描述:

数独是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得数独中每行、每列、每个 3×3 的九宫格内数字 
1∼9 均恰好出现一次。请编写一个程序填写数独。输入格式
输入包含多组测试用例。每个测试用例占一行,包含 81 个字符,代表数独的 81 个格内数据(顺序总体由上到下,同行由左到右)。每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。您可以假设输入中的每个谜题都只有一个解决方案。文件结尾处为包含单词 end 的单行,表示输入结束。输出格式
每个测试用例,输出一行数据,代表填充完全后的数独。输入样例:
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
输出样例:
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936

示例代码:

#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 9, M = 1 << 9;int ones[M], mmap[M];
int col[N], row[N], cell[3][3];
char str[100];void init()
{for(int i = 0; i < N; ++i) col[i] = row[i] = (1 << 9) - 1;for(int i = 0; i < 3; ++i){for(int j = 0; j < 3; ++j){cell[i][j] = (1 << 9) - 1;}}
}void draw(int x, int y, int t, bool is_set)
{if(is_set) str[x * N + y] = t + '1';  // 这里写1,是因为之后 -v 的缘故 else str[x * N + y] = '.';int v = 1 << t;if(!is_set) v = -v;row[x] -= v;  // v得是从1开始的 已经设置了说明当前该位的1要变为0 col[y] -= v;cell[x/3][y/3] -= v;
}int get(int x, int y)
{return row[x] & col[y] & cell[x/3][y/3];
}int lowbit(int x)
{return x & -x;
}bool dfs(int cnt)
{if(!cnt) return true;int minv = 10, x, y;for(int i = 0; i < N; ++i){for(int j = 0; j < N; ++j){if(str[i * N + j] == '.'){int state = get(i,j);if(ones[state] < minv)  // 从里面找能填的最小的 {minv = ones[state];x = i, y = j;}}}}int state = get(x,y);for(int i = state; i; i -= lowbit(i))  // 枚举1的次数 {int t = mmap[lowbit(i)];  draw(x,y,t,true);if(dfs(cnt-1)) return true;draw(x,y,t,false); }return false;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);for(int i = 0; i < N; ++i) mmap[1 << i] = i;for(int i = 0; i < M; ++i){for(int j = 0; j < N; ++j){if(i >> j & 1) ones[i]++;}}while(cin >> str, str[0] != 'e'){init();int cnt = 0;for(int i = 0; i < N; ++i){for(int j = 0; j < N; ++j){if(str[i * N + j] != '.'){int t = str[i * N + j] - '1';draw(i,j,t,true);}else cnt++;}}dfs(cnt);cout << str << endl; }return 0;
}

这篇关于算法学习系列(四十五):DFS之剪枝与优化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/872912

相关文章

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

Java中的雪花算法Snowflake解析与实践技巧

《Java中的雪花算法Snowflake解析与实践技巧》本文解析了雪花算法的原理、Java实现及生产实践,涵盖ID结构、位运算技巧、时钟回拨处理、WorkerId分配等关键点,并探讨了百度UidGen... 目录一、雪花算法核心原理1.1 算法起源1.2 ID结构详解1.3 核心特性二、Java实现解析2.

MyBatisPlus如何优化千万级数据的CRUD

《MyBatisPlus如何优化千万级数据的CRUD》最近负责的一个项目,数据库表量级破千万,每次执行CRUD都像走钢丝,稍有不慎就引起数据库报警,本文就结合这个项目的实战经验,聊聊MyBatisPl... 目录背景一、MyBATis Plus 简介二、千万级数据的挑战三、优化 CRUD 的关键策略1. 查

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

SpringBoot中HTTP连接池的配置与优化

《SpringBoot中HTTP连接池的配置与优化》这篇文章主要为大家详细介绍了SpringBoot中HTTP连接池的配置与优化的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、HTTP连接池的核心价值二、Spring Boot集成方案方案1:Apache HttpCl

使用雪花算法产生id导致前端精度缺失问题解决方案

《使用雪花算法产生id导致前端精度缺失问题解决方案》雪花算法由Twitter提出,设计目的是生成唯一的、递增的ID,下面:本文主要介绍使用雪花算法产生id导致前端精度缺失问题的解决方案,文中通过代... 目录一、问题根源二、解决方案1. 全局配置Jackson序列化规则2. 实体类必须使用Long封装类3.

PyTorch高级特性与性能优化方式

《PyTorch高级特性与性能优化方式》:本文主要介绍PyTorch高级特性与性能优化方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、自动化机制1.自动微分机制2.动态计算图二、性能优化1.内存管理2.GPU加速3.多GPU训练三、分布式训练1.分布式数据

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

MySQL中like模糊查询的优化方案

《MySQL中like模糊查询的优化方案》在MySQL中,like模糊查询是一种常用的查询方式,但在某些情况下可能会导致性能问题,本文将介绍八种优化MySQL中like模糊查询的方法,需要的朋友可以参... 目录1. 避免以通配符开头的查询2. 使用全文索引(Full-text Index)3. 使用前缀索