连续邮资问题-回溯法

回溯法的设计思想:

回溯法从根节点出发,按照深度优先策略遍历解空间树,搜索满足约束条件的解。在搜索至树中任一节点时,先判断该节点对应的部分解是否满足约束条件I,是否超出目标函数的界,也就是判断该节点是否包含问题的最优解,如果肯定不包含,则跳过对以该节点为根的子树的搜索(即所谓剪枝),返回上一层的节点,从其他子节点寻找通向最优解的路径;否则,进入以该节点为根的子树,继续按照深度优先策略搜索。
回溯法的搜索过程涉及的节点称为搜索空间,只是整个解空间树的一部分,在搜索过程中,通常采用两种策略避免无效搜索:

  • 用约束条件剪去得不到可行解的子树
  • 用目标函数剪去得不到最优解的子树
    这两类函数统称剪枝函数。

需要注意的是:
问题的解空间树是虚拟的,并不需要在算法运行时构造一棵真正的树结构,只需要存储从根节点到当前节点的路径。

连续邮资问题

问题描述:

假设某国家发行了n种不同面值的邮票,并且规定每张信封上最多只允许贴m张邮票。连续邮资问题要求对于给定的n和m,给出邮票面值的最佳设计,在1张信封上贴出从邮资1开始,增量为1的最大连续邮资区间。 例如当n=5,m=4时,面值为1,3,11,15,32的5种邮票可以贴出邮资的最大连续区间是1到70。

输入样例:

5 4
1 3 11 15 32

输出样例:

70

算法思路:

参考:https://www.jianshu.com/p/313d1e98c0d7

每张信封最多贴m张邮票,也就是说可能贴了m张,也可能贴了0张、1张等等。为了便于计算,我们可以把未贴满m张邮票看作贴了x张邮票和m-x张面值为0的邮票,从而构造一棵完全多叉树。若求所有可能的组合情况,解空间是(n+1)^m。
以n=5,m=4为例,解空间为完全多叉树,图中只以一条路径为例:
在这里插入图片描述

实际求解需要对其进行剪枝:解的约束条件是必须满足当前解的邮资可以构成增量为1的连续空间,所以在搜索至树中任一节点时,先判断该节点对应的部分解是否满足约束条件,也就是判断该节点是否包含问题的最优解,如果肯定不包含,则跳过对以该节点为根的子树的搜索,返回上一层的节点,从其他子节点寻找通向最优解的路径;否则,进入以该节点为根的子树,继续按照深度优先策略搜索。
求解过程:

  1. 读入邮票面值数n,每张信封最多贴的邮票数m
  2. 读入邮票面值数组nums[],除了正常面值,再加入一个值为0的面值
  3. 循环求取区间最大值maxValue,maxValue初始设为0
    ① 从0张邮票开始搜索解空间,如果当前未达到叶节点,且过程值temp=temp+nums[i]未达到当前记录的区间最大值maxValue,则继续向下搜索
    ② 若超过了区间最大值maxValue,则当前面值不是可行解,计算下一个面值nums[i+1]。若循环结束,当前节点的所有面值都无法满足,则说明再往下搜索也不可能有可行解,这个时候回溯到上一节点
    ③ 若当前已搜索到叶节点,判断当前路径下的解temp是否满足比当前记录的区间最大值maxValue大1。若满足,则更新区间最大值maxValue;若不满足,回溯到上一节点
  4. 重复步骤3直到没有满足当前区间最大值maxValue+1的可行解,则当前记录的maxValue就是区间最大值

算法实现:

public class Main {

    // n表示邮票面值数,m表示每张信封最多贴的邮票数
    private static int n;
    private static int m;

    // 邮票面值数组
    private static int[] nums;
    // 当前解是否可行
    private static boolean accFlag;
    // 记录区间最大值
    private static int maxValue = 0;
    // 搜索过程中的邮资
    private static int temp = 0;

    public static void main(String[] args) {
        // 读取键盘输入
        Scanner sc = new Scanner(System.in);
        while (true) {
            n = sc.nextInt();
            m = sc.nextInt();
            // 多一个是假设有面值为0的邮票
            nums = new int[n + 1];
            nums[0] = 0;
            for (int i = 1; i < n + 1; i++) {
                nums[i] = sc.nextInt();
            }
            solution(n, m, nums);
            System.out.println("maxValue=" + maxValue);
        }
    }

    public static void solution(int n, int m, int[] nums) {
        // 求解区间最大值,一直搜索,直到确定最大值
        while (true) {
            accFlag = false;
            // 每次都从0张邮票开始搜索解空间
            // 当前求解的目标是判断是否存在比当前区间最大值大1的解
            search(0);
            // 若存在
            if (accFlag) {
                // 连续区间增量加1
                maxValue++;
            } else {
                break;
            }
        }
    }

    /**
     * 搜索解空间,找到比当前区间最大值大1的解
     *
     * @param t 当前邮票数量
     */
    public static void search(int t) {
        // 叶节点,结束搜索
        if (t == m) {
            // 当前解可以将连续区间最大值加1
            if (temp == maxValue + 1) {
                accFlag = true;
            }
            // 此时已经是叶节点,若不是可行解,则需要回溯到t-1
            return;
        }
        // 未结束,继续向下搜索
        // 遍历所有面值
        for (int i = 0; i < nums.length; i++) {
            temp += nums[i];
            // 如果当前未达到叶节点,且过程值未达到当前记录的区间最大值,则继续向下搜索
            if (temp <= maxValue + 1) {
                search(t + 1);
            }
            // 若超过了区间最大值,则当前面值不是可行解,需要回溯,计算下一个面值
            temp -= nums[i];
        }
    }
}

运行结果

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值