DFS解题
目录
递归
区分递归和递推
DFS的主要思想
DFS---凑算式问题(可以用next_permutation())
蓝桥杯-三羊献瑞
蓝桥杯-凑算式
整数划分有多少种划分
DFS 排列组合问题
递归
区分递归和递推

写递归的代码时,每一次调用函数只做一小件事,然后把剩下的工作传到新一次调用里
1.要找到每一次工作的相似性
2.不能相似的原因很可能是缺少参数,因为有时候不加一个参数,每次传的参数都无法发生变化,就会陷入死循环,栈溢出
3.注意递归的出口
几个常见的用递归能解的小题:
//字符串反转
string F(string s){if(s.size()==1)return s;return F(s.substr(1,s.size()-1))+s[0];
}
int main(){string s="evol";cout<
//求杨辉三角第m层的第n个数
int F(int m,int n){if(n==1)return 1;if(m==n)return 1;return F(m-1,n-1)+F(m-1,n);
}
int main(){cout<
//3个A,2个B一共有多少种排列组合
int F(int A,int B){if(A==0 || B==0)return 1;return F(A-1,B)+F(A,B-1);
}
DFS的主要思想

使用栈保存未被检测的结点,结点按照深度优先的次序被访问并依次被压入栈中,并以相反的次序出栈进行新的检测
DFS解题时通常用到两个数组
- 一个用来标记该点是否被访问过
- 一个用来把该点放入
从一个走迷宫常用的解题模板引入:
const int maxn = 100; //最大范围
bool visit[maxn][maxn]; // 访问标记
int map[maxn][maxn]; // 坐标范围
int dir[4][2] = { 0,1,0,-1,1,0,-1,0 }; // 方向向量,(x,y)周围的四个方向
bool Check(int x, int y) // 边界条件和约束条件的判断
{if (!visit[x][y] && ...) // 满足条件且此点没有被访问过return 1;else // 与约束条件冲突或者此点已经被访问过return 0;
}
void dfs(int x, int y)
{visit[x][y] = 1; // 标记该节点被访问过if (map[x][y] == G) // 出现目标G{...... // 做相应处理return;}for (int i = 0; i < 4; i++){if (Check(x + dir[i][0], y + dir[i][1]))// 按照规则生成下一个节点,这样就试探了所有的情况dfs(x + dir[i][0], y + dir[i][1]); }return; // 上边的递归已经运行完退出来了,回溯
}
下面整理一些遇到的典型的DFS题目类型
DFS---凑算式问题(可以用next_permutation())
蓝桥杯-三羊献瑞

思路:本题是一个典型的凑算式问题,一共有八个不可以重复的数,要让它们满足这个等式
一共8个数,从0~9中找——依次对应为a[0]~a[7]
不可以重复——设立visit数组,如果i已经放入a数组了,visit[i]=1
如何深度搜索——dfs的参数就写a[index]的下标index,找遍这8个数
递归退出的条件——找够了8个数且满足这个算式,这样也刚好与上边的深度搜索吻合了,
正因为index++,最终才会==8,得以退出递归
#include
using namespace std;int a[8];
bool visit[10];void dfs(int index) {//退出条件if (index == 8) {if ((a[0] * 1000 + a[1] * 100 + a[2] * 10 + a[3]) +(a[4] * 1000 + a[5] * 100 + a[6] * 10 + a[1])== (a[4] * 10000 + a[5] * 1000 + a[2] * 100 + a[1] * 10 + a[7])) {cout << a[4] << a[5] << a[6] << a[1];return;}}else {for (int i = 0; i <= 9; i++) {if (index == 0 && i == 0) continue;//不可能把0赋值给a[0]if (index == 4 && i != 1)continue;//a[4]必定==1//最精髓的地方if (visit[i] == 0) { //如果0~9中这个数还没用过visit[i] = 1; //现在用了a[index] = i; //给当前这层递归的a[index]赋上这个还没用过的数dfs(index + 1); //去下一层递归给a[index]赋一个值visit[i] = 0; //之前的递归都退出后,相当于试完了一种情况,现在0~9又都能用了,开始新的一轮}}}}int main() {dfs(0); //显然index从0开始
}
蓝桥杯-凑算式

思路:和上边的题一模一样,上边是汉字对应数字凑算式,这个是字母对应数字
1.要a[9]
2.要visit[9]
3.深度靠index++
4.退出条件是找够9个且凑成了算式
5.一些微妙的不同,上边是输出,return,这个是计数,其实都一个道理
6.细节,这个题是1~9,而visit数组对应的下标是(1~9)-1
7.犯错的点,注意不管怎样都要return
#include
using namespace std;int a[9];
bool visit[9];
int cnt = 0;void dfs(int index) {if (index == 9) {if (a[0] + a[1] / a[2] + (a[3]*100 + a[4] *10+ a[5]) / (a[6] * 100 + a[7] * 10 + a[8]) == 10)cnt++;return; //要么cnt++要么不加,但是最终一定要return的}else {for (int i = 1; i <= 9; i++) {if (visit[i-1] == 0) {visit[i-1] = 1;a[index] = i;dfs(index + 1);visit[i-1] = 0;}}}
}int main() {dfs(0); //显然index从0开始cout << cnt;
}
【关于为什么要回溯的理解】所有开关串联一盏灯,现在
有的开关开着有的开关没开,要试出怎样能点亮这盏灯——首先拉一个开关,拉下,不亮,要拉上去….直到试完所有的一个开关的情况
然后到了两个开关的试探,拉两盏灯,拉下,不亮,要拉上去再试新的
试探完一种情况,拉上去这个过程,就是递归中dfs结束后的回溯
但是在做了上面两道题之后,发现了next_permutation(a,a+n)函数......
需要头文件
next_permutation()的返回值:当当前序列不存在下一个排列时,返回false,否则返回true
next_permutation(num,num+n)函数是对数组num中的前n个元素进行全排列,同时并改变num数组的值
#include
#include
using namespace std;
int main()
{ int num[3]={1,2,3}; do { cout<
输出结果为:

需要强调的是,next_permutation()在使用前需要对欲排列数组按升序排序,否则只能找出该序列之后的全排列数
于是...三羊代码变成
#include
#include
using namespace std;int a[10]={0,1,2,3,4,5,6,7,8,9};int main() {do{if(a[4]==1 && (a[0] * 1000 + a[1] * 100 + a[2] * 10 + a[3]) +(a[4] * 1000 + a[5] * 100 + a[6] * 10 + a[1])== (a[4] * 10000 + a[5] * 1000 + a[2] * 100 + a[1] * 10 + a[7])){cout << a[4] << a[5] << a[6] << a[1];break;}}while(next_permutation(a,a+10));}
于是,凑算式代码变成
#include
#include
using namespace std;int a[9]={1,2,3,4,5,6,7,8,9};int main(){int cnt=0;do{if (a[0] + a[1] / a[2] + (a[3]*100 + a[4] *10+ a[5]) / (a[6] * 100 + a[7] * 10 + a[8]) == 10)cnt++;} while(next_permutation(a,a+9));cout<
国赛里也有一道题是这样,w(゚Д゚)w


那么,可以对字符串之类不是数的东西进行全排列吗?
void dfs(string s,int k){ //k代表当前关注点是字符数组中的第几位,这个参数挺常用的if(k==s.size()-1) cout<
整数划分有多少种划分
令n=m1+m2+...+mi;
(1)当n=1时,不论m的值为多少(m>0),只有一种划分即{1};
(2)当m=1时,不论n的值为多少,只有一种划分即n个1,{1,1,1,...,1};
(3)当n=m时,根据划分中是否包含n,可以分为两种情况:
(a)划分中包含n的情况,只有一个即{n};
(b)划分中不包含n的情况,这时划分中最大的数字也一定比n小,即n的所有(n-1)划分。 因此 q(n,n) =1 + q(n,n-1);
(4)当n
(5)但n>m时,根据划分中是否包含最大值m,可以分为两种情况:
(a)划分中包含m的情况,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和为n-m,因此这情况下为q(n-m,m)
(b)划分中不包含m的情况,则划分中所有值都比m小,即n的(m-1)划分,个数为q(n,m-1);
因此 q(n, m) = q(n-m, m)+q(n,m-1);
#include
using namespace std;
int F(int n, int m) {if(n < 1 || m < 1) return 0;if(n == 1 || m == 1) return 1;if(n < m) return F(n, n);if(n == m) return F(n, m-1) + 1;return F(n, m-1) + F(n-m, m);
}
int main() {cout<
DFS 排列组合问题
【在n个球中任意取出m个不放回,求有多少种不同的取法】
这道题的核心思想是,当前这个球取还是不取?
取---->它的子问题是在剩下的n-1个中取m-1个
不取---->它的子问题是在剩下的n-1个中取m个
再写好出口条件,最后把取不取两种情况加起来就好
//在n个球里取m个
int dfs(int n,int m){//dfs(n-1,m-1)的出口 if(m==0) return 1;//dfs(n-1,m)的出口 if(n==m) return 1;return dfs(n-1,m)+dfs(n-1,m-1);
}
上边一道同理的题:
//3个A,2个B一共有多少种排列组合
int F(int A,int B){if(A==0 || B==0)return 1;return F(A-1,B)+F(A,B-1);
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

