深圳幻海软件技术有限公司 欢迎您!

【算法经典题集】递归(持续更新~~~)

2023-03-30

😽PREFACE🎁欢迎各位→点赞👍+收藏⭐+评论📝📢系列专栏:算法经典题集🔊本专栏涉及到的知识点或者题目是算法专栏的补充与应用💪种一棵树最好是十年前其次是现在递归递归实现指数型枚举下面给出原理分析过程图:本质就是数学里面的全排列#include<iostream>using
😽 PREFACE
🎁欢迎各位→点赞 👍 + 收藏 ⭐ + 评论 📝
📢系列专栏: 算法经典题集
🔊本专栏涉及到的知识点或者题目是算法专栏的补充与应用
💪 种一棵树最好是十年前其次是现在

递归

递归实现指数型枚举

下面给出原理分析过程图:

本质就是数学里面的全排列

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 16;
  4. int n;
  5. int st[N];//表示状态:0代表考虑,1代表选择,2代表不选择
  6. void dfs(int u)
  7. {
  8. if (u > n)
  9. {
  10. for (int i = 1; i <= n; i++)
  11. {
  12. if (st[i] == 1)
  13. {
  14. printf("%d ", i);
  15. }
  16. }
  17. puts("");
  18. return;
  19. }
  20. else
  21. {
  22. st[u] = 1;//选择
  23. dfs(u + 1);
  24. st[u] = 0;//回溯
  25. st[u] = 2;//不选择
  26. dfs(u + 1);
  27. st[u] = 0;//回溯
  28. }
  29. }
  30. int main()
  31. {
  32. cin >> n;
  33. dfs(1);
  34. return 0;
  35. }

我们也可以优化一下,不用三个状态去表示,采用bool:

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 16;
  4. int n;
  5. bool vis[N];
  6. void dfs(int u)
  7. {
  8. if (u > n)
  9. {
  10. for (int i = 1; i <= n; i++)
  11. {
  12. if (vis[i])
  13. {
  14. printf("%d ", i);
  15. }
  16. }
  17. puts("");
  18. return;
  19. }
  20. else
  21. {
  22. vis[u] = true;
  23. dfs(u + 1);
  24. vis[u] = false;
  25. dfs(u + 1);
  26. }
  27. }
  28. int main()
  29. {
  30. cin >> n;
  31. dfs(1);
  32. return 0;
  33. }

其实不然,递归顾名思义,先递下去,还要归回来,

针对这里的代码,可能有些人认为不会执行下面的false:

dfs(u+1)运行之后不是还有个return吗,这时候就会返回上一级函数,执行下面的false子任务

回到递归树上对应的父亲节点,接着遍历父亲的其他儿子。他在这颗子树的遍历中,父亲节点选过的打上标记,子节点才不会选。dfs完相当于把这颗树遍历完了,所以这个树又可以选了。

递归实现排列型枚举

下面给出图解分析过程:

  1. #include <iostream>
  2. using namespace std;
  3. const int N =10;
  4. int path[N];//保存序列
  5. int state[N];//数字是否被使用过
  6. int n;
  7. void dfs(int u)
  8. {
  9. if(u>n)//数字填完了,输出
  10. {
  11. for(int i=1;i<=n;i++)//输出方案
  12. {
  13. cout<<path[i]<<" ";
  14. }
  15. cout<<endl;
  16. return ;
  17. }
  18. else
  19. {
  20. for(int i=1;i<=n;i++)
  21. {
  22. if(!state[i])//如果数字i没有被用过
  23. {
  24. path[u]=i;//放入空位
  25. state[i]=1;//数字被用,修改状态
  26. dfs(u+1);//填下一位
  27. state[i]=0;//回溯,取出i
  28. }
  29. }
  30. }
  31. }
  32. int main()
  33. {
  34. cin>>n;
  35. dfs(1);
  36. return 0;
  37. }

另外需要注意的是本题的时间复杂度是

下面给出简易的证明:

递归实现组合型枚举

下面给出图解分析过程:

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 30;
  4. int n, m;
  5. int path[N];
  6. void dfs(int u, int s)//u代表当前枚举到哪个位置,s代表当前最小可以从哪个数枚举
  7. {
  8. if (u + n - s < m) return;//剪枝:就算将剩下的数全部选中也凑不齐m个数,所以一定没有答案,所以减掉
  9. if (u == m + 1)
  10. {
  11. for (int i = 1; i <= m; i++) cout << path[i] << " ";
  12. puts("");
  13. return;
  14. }
  15. else
  16. {
  17. for (int i = s; i <= n; i++)
  18. {
  19. path[u] = i;
  20. dfs(u + 1, i + 1);
  21. path[u] = 0;//回溯
  22. }
  23. }
  24. }
  25. int main()
  26. {
  27. cin >> n >> m;
  28. dfs(1, 1);
  29. return 0;
  30. }

带分数

分析过程:

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 10;
  4. int target;//题目条件给的数
  5. int num[N];//用来保存全排列的结果
  6. bool used[N];//生成全排列的过程中标记是否被使用过
  7. int cnt;//计数,最后的输出结果
  8. int calc(int l, int r)//计算num数组中一段的数是多少
  9. {
  10. int res = 0;
  11. for (int i = l; i <= r; i++)
  12. {
  13. res = res * 10 + num[i];//小学数学的加法进位
  14. }
  15. return res;
  16. }
  17. void dfs(int u)//生成全排列
  18. {
  19. if (u == 9)
  20. {
  21. //要把全排列分成三段
  22. for (int i = 0; i < 7; i++)//这里的i是位置,跟else里面的i不同
  23. {
  24. for (int j = i + 1; j < 8; j++)
  25. {
  26. int a = calc(0, i);
  27. int b = calc(i + 1, j);
  28. int c = calc(j + 1, 8);
  29. //这里一定要把除法变成乘法,因为c++里面除法是整除,写成除法的形式容易出错
  30. if (c * target == a * c + b)
  31. {
  32. cnt++;
  33. }
  34. }
  35. }
  36. return;
  37. }
  38. else
  39. {
  40. for (int i = 1; i <= 9; i++)//这里的i是数字
  41. {
  42. if (!used[i])
  43. {
  44. used[i] = true;//只要进if里面来,就是标记使用
  45. num[u] = i;
  46. dfs(u + 1);
  47. used[i] = false;//回溯,还原现场
  48. }
  49. }
  50. }
  51. }
  52. int main()
  53. {
  54. cin >> target;
  55. dfs(0);
  56. cout << cnt << endl;
  57. return 0;
  58. }

本题是蓝桥杯某年省赛的原题,下面再给出一个直接调用 next_permutation() 函数的做法,可以代替手写暴搜来枚举全排列,蓝桥杯是可以使用这个函数的

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. const int N = 10;
  5. int target;
  6. int num[N];
  7. int calc(int l, int r)
  8. {
  9. int res = 0;
  10. for (int i = l; i <= r; i++)
  11. {
  12. res = res * 10 + num[i];
  13. }
  14. return res;
  15. }
  16. int main()
  17. {
  18. cin >> target;
  19. for (int i = 0; i < 9; i++)
  20. {
  21. num[i] = i + 1;
  22. }
  23. int cnt = 0;
  24. do
  25. {
  26. for (int i = 0; i < 7; i++)
  27. {
  28. for (int j = i + 1; j < 8; j++)
  29. {
  30. int a = calc(0, i);
  31. int b = calc(i + 1, j);
  32. int c = calc(j + 1, 8);
  33. if (a == 0 || b == 0 || c == 0)//特殊情况,需要单独讨论一下
  34. {
  35. continue;
  36. }
  37. if (a * c + b == c * target)
  38. {
  39. ++cnt;
  40. }
  41. }
  42. }
  43. // 调用函数生成全排列
  44. } while (next_permutation(num, num + 9));
  45. cout << cnt << '\n';
  46. return 0;
  47. }
  • 为什么 next_permutation() 函数选用do-while循环结构?
    因为你初始化的时候数组是一种情况,直接全排列的话第一种情况直接就少掉了。这也是 next_permutation() 的一个固定方式。

[补充] next_permutation() 函数

另外补充一下 next_permutation() 函数的用法:

对于next_permutation函数,其函数原型为:

  1. #include <algorithm>
  2. bool next_permutation(iterator start,iterator end)

如果当前序列不存在下一个排列时,函数返回false,否则返回true

例:将1,2,3,4,5进行全排列

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. int main()
  5. {
  6. int num[5] = { 1,2,3,4,5 };
  7. do
  8. {
  9. cout << num[0] << " " << num[1] << " " << num[2] <<" "<<num[3]<<" "<<num[4]<< endl;
  10. } while (next_permutation(num, num + 5));
  11. return 0;
  12. }

如果将+5改为+2:

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. int main()
  5. {
  6. int num[5] = { 1,2,3,4,5 };
  7. do
  8. {
  9. cout << num[0] << " " << num[1] << " " << num[2] <<" "<<num[3]<<" "<<num[4]<< endl;
  10. } while (next_permutation(num, num + 2));
  11. return 0;
  12. }

由此可以看出,next_permutation(num,num+n)函数是对数组num中的前n个元素进行全排列,同时并改变num数组的值。

此外,需要强调的是,next_permutation()在使用前需要对欲排列数组按升序排序,否则只能找出该序列之后的全排列数。

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览42515 人正在系统学习中
孤单听雨的猫21
微信公众号
小编本人在学习数学和编程过程中的总结和笔