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

相关推荐
HR Zhou17 分钟前
群体智能优化算法-模拟退火优化算法(Simulated Annealing, SA,含Matlab源代码)
人工智能·算法·机器学习·matlab·优化·模拟退火算法·群体智能优化
阑梦清川30 分钟前
蓝桥杯关于栈这个数据结构的一个算法题目
数据结构·算法·蓝桥杯
Luis Li 的猫猫31 分钟前
机器学习--卷积神经网络原理及MATLAB回归实现
人工智能·算法·机器学习·matlab·cnn
圣保罗的大教堂37 分钟前
《算法笔记》9.4小节——数据结构专题(2)->二叉查找树(BST) 问题 A: 二叉排序树
算法
乔冠宇41 分钟前
leetcode热题100道——两数之和
java·javascript·算法·leetcode·职场和发展
Dream it possible!1 小时前
LeetCode 热题 100_跳跃游戏 II(79_45_中等_C++)(贪心算法)
c++·算法·leetcode·贪心算法
Chiyamin1 小时前
高精度算法基础,实战与进阶
c++·算法
深度学习小白1 小时前
ubuntu git clone失败成功解决
算法·ubuntu
ん贤2 小时前
单调队列【C/C++】
开发语言·数据结构·c++·算法·单调队列
Chiyamin2 小时前
C++流模拟JavaSplit
java·c++·算法