您吃了吗?---拓扑排序实战:如何用 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 在拓扑排序中的应用!🚀

相关推荐
黄卷青灯7710 分钟前
标定系数为什么会存储在相机模组里面,在标定的时候,算法是在割草机的X3板上运行的啊?
数码相机·算法·相机内参
螺丝钉的扭矩一瞬间产生高能蛋白27 分钟前
PID算法基础知识
算法
HVACoder1 小时前
复习下线性代数,使用向量平移拼接两段线
c++·线性代数·算法
爱coding的橙子1 小时前
每日算法刷题Day77:10.22:leetcode 二叉树bfs18道题,用时3h
算法·leetcode·职场和发展
Swift社区1 小时前
LeetCode 404:左叶子之和(Sum of Left Leaves)
算法·leetcode·职场和发展
南枝异客2 小时前
查找算法-顺序查找
python·算法
QuantumLeap丶2 小时前
《数据结构:从0到1》-06-单链表&双链表
数据结构·算法
李牧九丶2 小时前
从零学算法59
算法
一匹电信狗2 小时前
【C++】手搓AVL树
服务器·c++·算法·leetcode·小程序·stl·visual studio
月疯2 小时前
离散卷积,小demo(小波信号分析)
算法