本文讨论的是整数划分问题的DFS解法,(题目来自笔者的数据结构作业的附加题)。
题目描述
/*
【问题描述】编程实现某整数的划分的输出
【样例输入】
6
【样例输出】
6
5+1
4+2
4+1+1
3+3
3+2+1
3+1+1+1
2+2+2
2+2+1+1
2+1+1+1+1
1+1+1+1+1+1
*/
此题目的表述并不严谨,应当补充一下输出限制:1、输出若干行行首数字从上到下降序排列。2、每行的数字从左到右降序排列。
简单解释一下DFS(深度优先搜索)
DFS是计算机科学中用于遍历或搜索树、图结构的经典算法。其核心思想是尽可能深地探索分支路径:从根节点出发,递归访问未探索的子节点,直到无法继续深入时回溯,再尝试其他分支。这种“一条路走到底”的策略常用于路径规划、排列组合问题及拓扑排序等场景。(from deepseek 生成)
为什么会用DFS解决问题?
通过阅读题干我们可以知道此题可以通过穷尽所有情况得到答案,所以难点在于如何穷尽所以情况。
如上图,我们以 n = 6 为例,我们可以不断列举 num ~ 1 (sum 为 此时已经列举完的数的和,num 为上次列举完的数) 这些数,当sum等于为n时,就得到了答案;当sum大于n时,就废除该方案;当sum小于n时,就继续列举。
在这里我也不知道怎么引了,就从笔者刚刚画的图,我们可以将列举的模式视为树这一数据结构,而其中的“叶子”就是我们需要的答案,而且我们可以通过剪枝删除错误的列举方案。说到这里我们就可以想到用DFS这一方法解决问题。
敲代码!!!
我们首先温习一下DFS的版子:
void dfs(int step){
if(达到目的地){
输出解
返回
}
合理的剪枝操作
for(int i = 1;i <= 枚举数;i++){
if(满足条件){
更新状态位
dfs(step+1)
恢复状态位
}
}
}
1、DFS函数应该有几个参数?
首先我们需要一个容器来存放已经列举完的数,所以我需要一个参数:一个容器的引用(vector<int>& path);
然后我们列举时需要一个起始点即:上次列举完的数(max_num);
最后就是判断合适需要剪枝,也就是已经列举完数的和(sum);
void dfs(vector<int>& path, int sum, int max_num)
2、到达终点(sum == n):输出容器中的所有数字
if (sum == n) { // 找到有效划分
cout << path[0];
for (int i = 1; i < path.size(); i++) {
cout << "+" << path[i];
}
cout << endl;
return;
}
3、更新状态位、恢复状态位与剪枝
因为代码进入下一层需要添加数字,返回上一层需要删除数字;所以更新状态位与恢复状态位如下代码。
path.push_back(i);
dfs(path, sum + i, i); // 保证后续元素不大于当前i
path.pop_back(); // 回溯
剪枝:sum + i > n 时停止列举,所以sum + i <= n时,可以列举;代码如下:
if (sum + i <= n) { // 剪枝
path.push_back(i);
dfs(path, sum + i, i); // 保证后续元素不大于当前i
path.pop_back(); // 回溯
}
综上:DFS函数的完整内容为:
void dfs(vector<int>& path, int sum, int max_num) {
if (sum == n) { // 找到有效划分
cout << path[0];
for (int i = 1; i < path.size(); i++) {
cout << "+" << path[i];
}
cout << endl;
return;
}
// 从max_num开始递减尝试添加新元素
for (int i = max_num; i >= 1; i--) {
if (sum + i <= n) { // 剪枝
path.push_back(i);
dfs(path, sum + i, i); // 保证后续元素不大于当前i
path.pop_back(); // 回溯
}
}
}
4、初始化与初始调用
初始化(水字数):
cin >> n;
vector<int> path;
初始调用(总和0,当前最大值n):
dfs(path, 0, n)
附上完整的AC代码:
#include <bits/stdc++.h>
using namespace std;
int n;
void dfs(vector<int>& path, int sum, int max_num) {
if (sum == n) { // 找到有效划分
cout << path[0];
for (int i = 1; i < path.size(); i++) {
cout << "+" << path[i];
}
cout << endl;
return;
}
// 从max_num开始递减尝试添加新元素
for (int i = max_num; i >= 1; i--) {
if (sum + i <= n) { // 剪枝
path.push_back(i);
dfs(path, sum + i, i); // 保证后续元素不大于当前i
path.pop_back(); // 回溯
}
}
}
int main() {
cin >> n;
vector<int> path;
dfs(path, 0, n); // 初始调用:总和0,当前最大值n
return 0;
}