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

相关推荐
秋难降32 分钟前
栈:从基础概念到实战解题(详细)
数据结构·算法·排序算法
ezl1fe1 小时前
RAG 每日一技(十):向量检索的“死穴”?用混合搜索(Hybrid Search)来拯救!
后端·算法
Ahu_iii1 小时前
【图论基础】理解图的“闭环”:Tarjan 强连通分量算法全解析
算法·图论
PixelMind1 小时前
【IQA技术专题】DISTS代码讲解
图像处理·人工智能·python·算法·iqa
项目申报小狂人1 小时前
2025年1中科院1区顶刊SCI-投影迭代优化算法Projection Iterative Methods-附完整Matlab免费代码
开发语言·算法·matlab
AI 嗯啦1 小时前
机械学习--逻辑回归
算法·机器学习·逻辑回归
山烛2 小时前
逻辑回归详解:从数学原理到实际应用
python·算法·机器学习·逻辑回归
爱煲汤的夏二2 小时前
扩展卡尔曼滤波器 (EKF) 与无人机三维姿态估计:从理论到实践
单片机·嵌入式硬件·算法·无人机
sali-tec2 小时前
C# 基于halcon的视觉工作流-章27-带色中线
开发语言·人工智能·算法·计算机视觉·c#
范特西_2 小时前
字典树/前缀树
c++·算法