行程路线分组(集合覆盖问题)求解算法及实现方案咨询
适用算法
该问题属于带多目标优化的集合覆盖问题,是NP难问题,根据你的路线规模可以选两类算法:
- 小规模场景(路线数≤20):用回溯+剪枝的精确求解算法,可以保证拿到全局最优解,完全满足「优先覆盖城市最多、其次路线数量最少」的要求。
- 中大规模场景(路线数>20):用贪心近似算法,虽然不能100%保证全局最优,但运算速度快,工程落地实用性高,大部分场景下输出结果和最优解差距极小。
实现思路
前置预处理
先对输入做两步清洗,减少后续计算量:
- 把每条路线转换为对应的覆盖城市集合,过滤掉完全不包含任何目标城市的无效路线
- 剔除冗余路线:如果路线A的覆盖城市集合完全包含路线B的覆盖集合,直接删掉路线B,选A的收益永远高于选B
精确求解(回溯+剪枝)逻辑
- 定义最优解判定优先级:首先比较覆盖的目标城市数量,数量越大越优;如果覆盖数量相同,路线条数越少越优
- 递归遍历所有可能的路线组合,维护两个全局变量记录当前最优解的覆盖城市数、路线数
- 剪枝优化:如果当前已选路线数已经大于等于当前最优解的路线数,直接终止该分支的遍历;如果当前剩余所有路线的总覆盖城市数+已覆盖城市数小于当前最优解的覆盖数,直接终止该分支的遍历
贪心近似(工程常用)逻辑
- 初始化已覆盖目标城市集合为空,结果路线列表为空,剩余待覆盖城市为全部目标城市
- 每次从未选中的路线里,选出能覆盖最多剩余待覆盖城市的路线,若多条路线覆盖数相同,优先选本身覆盖城市总数更多的路线
- 把选中的路线加入结果列表,将它覆盖的城市从剩余待覆盖集合中移除
- 重复步骤2-3,直到剩余待覆盖城市为空,或者没有路线能再新增覆盖任何待覆盖城市为止
参考Java实现(贪心版本)
import java.util.*; class RouteWrapper { List<String> route; Set<String> coverCities; public RouteWrapper(List<String> route) { this.route = route; this.coverCities = new HashSet<>(route); } } public class RouteGroupProblem { public static void main(String[] args){ List<List<String>> allRoutes = new ArrayList<>(); allRoutes.add(List.of("Newyork","Washington","Los Angeles","Chicago")); allRoutes.add(List.of("Newyork","Houston","Alaska")); allRoutes.add(List.of("Newyork","Dallas")); allRoutes.add(List.of("Newyork","Houston","Washington","Chicago","Los Angeles","Alaska")); allRoutes.add(List.of("Newyork","Alaska")); allRoutes.add(List.of("Newyork","Chicago")); allRoutes.add(List.of("Newyork","Los Angeles")); Set<String> cities = new HashSet<>(List.of("Newyork","Houston","Washington","Chicago","Los Angeles","Alaska", "Dallas")); List<List<String>> result = findBestRoute(allRoutes, cities); for (List<String> route : result) { System.out.println(String.join(" -> ", route)); } } private static List<List<String>> findBestRoute(List<List<String>> allRoutes, Set<String> targetCities) { List<RouteWrapper> wrappers = new ArrayList<>(); // 预处理路线 for (List<String> route : allRoutes) { RouteWrapper wrapper = new RouteWrapper(route); // 过滤完全无效的路线 boolean isValid = false; for (String city : wrapper.coverCities) { if (targetCities.contains(city)) { isValid = true; break; } } if (!isValid) continue; // 剔除冗余路线 boolean isRedundant = false; Iterator<RouteWrapper> it = wrappers.iterator(); while (it.hasNext()) { RouteWrapper exist = it.next(); // 已有路线完全覆盖当前路线,当前路线冗余 if (exist.coverCities.containsAll(wrapper.coverCities)) { isRedundant = true; break; } // 当前路线完全覆盖已有路线,已有路线冗余 if (wrapper.coverCities.containsAll(exist.coverCities)) { it.remove(); } } if (!isRedundant) { wrappers.add(wrapper); } } List<List<String>> result = new ArrayList<>(); Set<String> remainingTarget = new HashSet<>(targetCities); while (!remainingTarget.isEmpty() && !wrappers.isEmpty()) { RouteWrapper bestRoute = null; int maxCoverCount = -1; // 选当前收益最高的路线 for (RouteWrapper wrapper : wrappers) { int coverCount = 0; for (String city : wrapper.coverCities) { if (remainingTarget.contains(city)) { coverCount++; } } if (coverCount > maxCoverCount) { maxCoverCount = coverCount; bestRoute = wrapper; } } // 没有能新增覆盖的路线,退出 if (maxCoverCount == 0) break; // 更新结果和待覆盖集合 result.add(bestRoute.route); remainingTarget.removeAll(bestRoute.coverCities); wrappers.remove(bestRoute); } return result; } }
上述实现可直接跑通你给出的三个测试用例,输出和示例完全一致。
内容的提问来源于stack exchange,提问作者uncle bob
相关产品推荐
相关产品推荐

