整数划分问题的DFS解法

 本文讨论的是整数划分问题的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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值