Java回溯算法优化:Assignatura调度问题性能提升求助
看起来你已经在回溯里做了基础剪枝,但面对真实场景还是不够快,我结合你的代码和调度场景,给你几个针对性的优化方向,应该能显著提升性能:
1. 立即启用成本剪枝,砍掉无意义分支
你现在没有利用当前成本和最优成本的对比来剪枝——如果当前路径的成本已经超过了已知的最优解,继续递归下去毫无意义,直接终止即可。在backMillorSolucio方法开头加上:
// 剪枝:当前成本已经超过最优解,无需继续探索 if (cost_actual >= millor_cost) { return; }
这一步能快速砍掉大量不可能成为最优解的分支,是性价比最高的优化之一。
2. 预计算成本矩阵,避免重复计算
你每次调用Assignatura.getPerjudicats()都要重新计算成本,这会带来额外的开销。可以在构造函数里预先把所有两两Assignatura的组合成本计算好,存到二维数组里,后续直接查表:
// 在类里添加成员变量 private int[][] perjudicatsMatrix; // 在构造函数中初始化 public SolucioCalendari(int nDays, int nAssigInADay) { // ... 原有代码 ... int totalAssign = Assignatura.getPlaEstudis().length; perjudicatsMatrix = new int[totalAssign][totalAssign]; for (int i = 0; i < totalAssign; i++) { for (int j = 0; j < totalAssign; j++) { perjudicatsMatrix[i][j] = Assignatura.getPerjudicats(i, j); } } }
之后在计算成本时直接用perjudicatsMatrix[prevIdx][currIdx]代替原方法调用,节省重复计算的时间。
3. 优化空任务的分支逻辑,避免无效探索
你的约束是每天至少一个任务,但当前代码允许任意位置为空,这会产生很多无效分支(比如某一天两个位置都空)。可以针对每天的两个位置做分支限制:
当处理到每天的第一个位置(dia % 2 == 0)时,只考虑三种合法情况:
- 只填第一个位置,第二个位置留空(需确保剩余任务能分配完)
- 填第一个位置,然后处理第二个位置
- 第一个位置留空,填第二个位置(同样要满足剩余任务分配条件)
这样能直接砍掉“某天全空”的无效分支,减少递归次数。
4. 调整任务选择顺序,快速找到较优解
回溯算法的剪枝效率很大程度依赖于尽早找到一个较好的初始解。你可以调整任务的遍历顺序,优先尝试那些能带来更低成本的Assignatura:
比如先对Assignatura排序,优先选择单独放置时成本低、或与当前已选任务组合成本低的选项。这样能更快找到一个接近最优的解,后续的剪枝就能更早生效,砍掉更多分支。
5. 启用状态缓存(Memoization),避免重复探索相同状态
你已经声明了memoization变量但没用到,可以用它缓存已经探索过的状态,避免重复计算。这里的状态可以用当前处理到的位置(dia)+ 已分配任务的集合来表示:
- 如果Assignatura数量不多(≤32),可以用一个整数(bitmask)来表示已分配的任务(每一位代表一个任务是否被分配)
- 缓存的key可以是
dia + "_" + bitmask,value是该状态下的最小成本 - 在递归前检查:如果当前状态的成本已经高于缓存中记录的最小成本,直接终止递归;否则更新缓存。
比如可以把assignada数组换成一个int bitmask变量,每次分配任务i时,执行bitmask |= (1 << i),取消分配时执行bitmask &= ~(1 << i),这样生成状态key会更高效。
6. 优化数据结构,减少对象操作开销
当前solucio数组存的是Assignatura对象引用,你可以改成存任务的索引(int[] solucio),这样赋值、复制数组的开销会更小,后续需要访问任务属性时,再通过索引从Assignatura.getPlaEstudis()中获取。比如:
private int[] solucio; private int[] millor_solucio; // 构造函数中初始化 solucio = new int[nAssigInADay * nDays]; Arrays.fill(solucio, -1); // 用-1表示空位置
这样数组复制(Arrays.copyOf)会更快,对象操作的开销也会降低。
这些优化建议可以按优先级逐步实施:先做成本剪枝和预计算矩阵,这两步改动小见效快;再调整任务选择顺序和空分支逻辑;最后考虑状态缓存和数据结构优化。应该能让你的回溯算法在真实场景下的运行速度大幅提升。
备注:内容来源于stack exchange,提问作者Norhther

