您吃了吗?---拓扑排序实战:如何用 BFS 解决配方依赖问题?

拓扑排序实战:如何用 BFS 解决配方依赖问题?

在算法面试中,拓扑排序(Topological Sorting) 是一个高频考点,常见于 任务调度、依赖管理、编译顺序、配方制作 等问题。这篇文章将通过 食谱制作问题 ,带你掌握如何利用 BFS 进行拓扑排序

1. 题目背景:配方制作问题

假设你有一些配方(recipes) ,每个配方都需要一定的原料(ingredients)才能制作。同时,你还拥有一些初始原料(supplies)

目标是找出所有可以最终制作出来的配方

示例:

ini 复制代码
String[] recipes = {"bread", "sandwich", "burger"};
List<List<String>> ingredients = List.of(
    List.of("flour", "water"), 
    List.of("bread", "ham"), 
    List.of("sandwich", "lettuce")
);
String[] supplies = {"flour", "water", "ham", "lettuce"};
  • bread 需要 flourwater,可以直接制作。
  • sandwich 需要 breadhambread 可以制作,因此 sandwich 也能制作。
  • burger 需要 sandwichlettucesandwich 可制作,因此 burger 也能制作。

最终输出:["bread", "sandwich", "burger"]

2. 拓扑排序解决方案

这个问题的核心在于 配方之间的依赖关系 ,可以用 拓扑排序 来解决。

解题思路:

  1. 构建有向图(Graph)

    • 把**食材(原料)**视为节点。
    • 配方依赖于原料,形成有向边。
    • 记录 每个配方的入度(in-degree) ,即它所需的原料个数。
  2. 初始化队列(BFS)

    • 将所有 初始可用的原料(supplies) 加入队列。
    • 遍历队列,找到 入度为 0 的 recipe,表示它的所有原料都已经具备。
  3. 执行拓扑排序

    • 不断取出队列中的元素,减少依赖它的 recipe 的入度。
    • 当某个 recipe 的入度变为 0,说明它可以制作,加入队列。

代码实现

ini 复制代码
import java.util.*;

public class RecipeMaker {
    public List<String> findAllRecipes(String[] recipes, List<List<String>> ingredients, String[] supplies) {
        Map<String, List<Integer>> graph = buildGraph(recipes, ingredients);
        Map<String, Integer> inDegree = new HashMap<>();
        Deque<String> queue = new ArrayDeque<>();
        List<String> result = new ArrayList<>();

        for (String supply : supplies) {
            queue.offer(supply);
        }

        while (!queue.isEmpty()) {
            String curIngredient = queue.poll();
            if (!graph.containsKey(curIngredient)) continue;

            for (int recipeIndex : graph.get(curIngredient)) {
                inDegree.put(recipes[recipeIndex], inDegree.get(recipes[recipeIndex]) - 1);
                if (inDegree.get(recipes[recipeIndex]) == 0) {
                    result.add(recipes[recipeIndex]);
                    queue.offer(recipes[recipeIndex]);
                }
            }
        }
        return result;
    }

    private Map<String, List<Integer>> buildGraph(String[] recipes, List<List<String>> ingredients) {
        Map<String, List<Integer>> graph = new HashMap<>();
        Map<String, Integer> inDegree = new HashMap<>();

        for (int recipeIndex = 0; recipeIndex < recipes.length; recipeIndex++) {
            inDegree.put(recipes[recipeIndex], ingredients.get(recipeIndex).size());
            for (String ingredient : ingredients.get(recipeIndex)) {
                graph.putIfAbsent(ingredient, new ArrayList<>());
                graph.get(ingredient).add(recipeIndex);
            }
        }
        return graph;
    }
}

3. 关键规律总结

🟢 规律 1:拓扑排序适用于依赖关系

  • 若 A 依赖 B,则 B 先制作。
  • 适用于 任务调度、编译顺序、配方制作、课程安排等问题

🟢 规律 2:入度(in-degree)控制 recipe 何时可制作

  • 入度 0 代表该 recipe 所有原料可用
  • 逐步减少依赖项,保证 recipe 有序解锁

🟢 规律 3:BFS 逐步解锁更多可制作的 recipe

  • 先制作 直接可用的原料
  • 再解锁 依赖这些原料的 recipe

4. 易错点分析

1. 没有正确初始化 inDegree

  • 需要为每个 recipe 初始化原料数量

2. 漏掉 queue.offer(recipe)

  • 当 recipe 入度变 0 时,必须入队列,否则 BFS 不会继续。

3. 误解 graph 结构

  • graph 存储的是 原料 -> 依赖它的 recipe,而不是 recipe 本身。

5. 经典测试用例

基础用例:所有 recipe 可制作
ini 复制代码
String[] recipes = {"bread", "sandwich"};
List<List<String>> ingredients = List.of(
    List.of("flour", "water"),
    List.of("bread", "ham")
);
String[] supplies = {"flour", "water", "ham"};

输出: ["bread", "sandwich"]

无法制作的 recipe
ini 复制代码
String[] recipes = {"cake"};
List<List<String>> ingredients = List.of(List.of("flour", "sugar", "egg"));
String[] supplies = {"flour", "sugar"};

输出: [] (缺少 egg,无法制作 cake

环形依赖(不可制作)
ini 复制代码
String[] recipes = {"cake", "muffin"};
List<List<String>> ingredients = List.of(
    List.of("muffin"),
    List.of("cake")
);
String[] supplies = {"flour"};

输出: []cake 依赖 muffinmuffin 依赖 cake,形成环)

6. 结语

拓扑排序是一个非常实用的算法技巧,掌握它可以解决大量 任务调度、依赖关系 相关的问题。希望这篇文章能帮你更深入理解 BFS 在拓扑排序中的应用!🚀

相关推荐
我爱C编程36 分钟前
基于Qlearning强化学习的1DoF机械臂运动控制系统matlab仿真
算法
chao_7891 小时前
CSS表达式——下篇【selenium】
css·python·selenium·算法
chao_7891 小时前
Selenium 自动化实战技巧【selenium】
自动化测试·selenium·算法·自动化
YuTaoShao1 小时前
【LeetCode 热题 100】24. 两两交换链表中的节点——(解法一)迭代+哨兵
java·算法·leetcode·链表
怀旧,1 小时前
【数据结构】8. 二叉树
c语言·数据结构·算法
泛舟起晶浪1 小时前
相对成功与相对失败--dp
算法·动态规划·图论
地平线开发者2 小时前
地平线走进武汉理工,共建智能驾驶繁荣生态
算法·自动驾驶
IRevers2 小时前
【自动驾驶】经典LSS算法解析——深度估计
人工智能·python·深度学习·算法·机器学习·自动驾驶
前端拿破轮2 小时前
翻转字符串里的单词,难点不是翻转,而是正则表达式?💩💩💩
算法·leetcode·面试
凤年徐2 小时前
【数据结构与算法】203.移除链表元素(LeetCode)图文详解
c语言·开发语言·数据结构·算法·leetcode·链表·刷题