1. 项目背景与核心需求
华为OD(Outstanding Developer)机考是华为面向开发者的重要技术能力评估环节,其中双机位监考模式对编程题的自动化评测提出了更高要求。这道"启动多任务排序"题目属于C卷经典题型,主要考察考生在多线程环境下的任务调度与资源管理能力。
实际业务场景中,类似需求广泛存在于分布式任务调度系统。例如在云计算资源分配时,需要根据任务优先级和资源依赖关系,动态调整任务执行顺序以避免死锁。题目通过简化的模型,考察开发者对以下核心问题的处理能力:
- 多任务间的启动依赖关系判断
- 线程安全的执行顺序控制
- 循环依赖检测与异常处理
2. 题目分析与解题思路
2.1 题目原型解析
题目给出N个任务的启动列表(如["T1", "T2", "T3"])和依赖关系列表(如["T1->T2", "T2->T3"]),要求输出合法的任务启动顺序。关键约束条件包括:
- 当任务A依赖任务B时(A->B),必须保证B在A之前启动
- 若存在循环依赖(如A->B->C->A)则返回错误
- 可能存在独立无依赖的任务
2.2 拓扑排序算法选择
这是典型的有向无环图(DAG)拓扑排序问题。我们采用Kahn算法实现,其核心步骤:
- 构建入度表(in-degree)和邻接表
- 初始化队列,存入当前入度为0的节点
- 不断从队列取出节点,并减少其邻接节点的入度
- 当新节点入度变为0时加入队列
- 最终检查是否所有节点都被处理
选择该算法的理由:
- 时间复杂度O(V+E)适合机考场景
- 无需递归避免栈溢出风险
- 便于在过程中检测环的存在
3. Java实现详解
3.1 数据结构设计
java复制// 使用邻接表存储图结构
Map<String, List<String>> graph = new HashMap<>();
// 记录每个任务的入度
Map<String, Integer> inDegree = new HashMap<>();
// 保存最终结果
List<String> result = new ArrayList<>();
3.2 核心算法实现
java复制public List<String> taskOrder(String[] tasks, String[] dependencies) {
// 初始化数据结构
for (String task : tasks) {
graph.put(task, new ArrayList<>());
inDegree.put(task, 0);
}
// 构建图
for (String dep : dependencies) {
String[] parts = dep.split("->");
String from = parts[0], to = parts[1];
graph.get(from).add(to);
inDegree.put(to, inDegree.get(to) + 1);
}
// 拓扑排序
Queue<String> queue = new LinkedList<>();
for (String task : inDegree.keySet()) {
if (inDegree.get(task) == 0) queue.offer(task);
}
while (!queue.isEmpty()) {
String curr = queue.poll();
result.add(curr);
for (String neighbor : graph.get(curr)) {
inDegree.put(neighbor, inDegree.get(neighbor) - 1);
if (inDegree.get(neighbor) == 0) {
queue.offer(neighbor);
}
}
}
// 检查环
if (result.size() != tasks.length) {
throw new RuntimeException("存在循环依赖");
}
return result;
}
3.3 关键实现细节
- 输入处理:使用
split("->")解析依赖关系时,要注意处理可能的空格等特殊情况 - 线程安全:虽然本题不涉及多线程,但在实际系统中建议使用
ConcurrentHashMap - 异常处理:检测到环时应立即终止处理,避免无效计算
4. 测试用例设计
4.1 常规测试用例
java复制@Test
public void testNormalCase() {
String[] tasks = {"T1", "T2", "T3", "T4"};
String[] deps = {"T1->T2", "T2->T3", "T1->T4"};
List<String> result = taskOrder(tasks, deps);
assertTrue(result.indexOf("T1") < result.indexOf("T2"));
assertTrue(result.indexOf("T2") < result.indexOf("T3"));
}
4.2 边界条件测试
java复制@Test(expected = RuntimeException.class)
public void testCycleDependency() {
String[] tasks = {"T1", "T2", "T3"};
String[] deps = {"T1->T2", "T2->T3", "T3->T1"};
taskOrder(tasks, deps);
}
@Test
public void testNoDependency() {
String[] tasks = {"T1", "T2", "T3"};
String[] deps = {};
assertEquals(3, taskOrder(tasks, deps).size());
}
5. 性能优化与扩展
5.1 时间复杂度分析
- 构建图:O(V + E)
- 拓扑排序:O(V + E)
- 总体复杂度:O(V + E)
在华为OD机考环境下,该算法能高效处理V≤1000,E≤10000的数据规模。
5.2 实际工程扩展
在实际任务调度系统中,还需要考虑:
- 优先级调度:为每个任务添加权重属性
- 资源约束:检查任务所需的CPU/内存资源
- 容错机制:任务失败后的重试策略
改进后的数据结构示例:
java复制class Task {
String id;
int priority;
Set<String> dependencies;
ResourceRequirement resources;
}
6. 机考实战技巧
-
快速建图技巧:使用
computeIfAbsent简化代码java复制graph.computeIfAbsent(from, k -> new ArrayList<>()).add(to); -
调试建议:在关键步骤添加日志输出
java复制System.out.println("Processing: " + curr + ", remaining: " + queue); -
时间管理:先完成核心算法,再处理边界条件
注意:在真实机考环境中,建议先写出算法框架,再逐步完善异常处理等细节,避免因小失大。