问题描述
在离线数据仓库中,某些表的数据产出会依赖更上游的表。例如,一张用于电商销售额统计分析表,会依赖商品销售表和商品表,而商品表又会依赖商品类目表。在数据表的依赖关系中,如果存在循环依赖,数据将无法顺利产出。
给定一组数据表的依赖关系,判断数据是否能顺利产出。
输入格式
一个数据表的依赖关系列表,每一行表示一个依赖关系,如:A,B,C 表示 A 表数据产出会依赖 B 表和 C 表。以下是一组依赖关系举例,该依赖关系中 A 依赖 B,B 依赖 D,D 又依赖 A,形成了循环依赖:
A,B,C
B,D
C,E
D,A
输出格式
数据依赖关系正常能产出数据,返回 true,否则返回 false。
输入样例
A,B,C
B,D
C,E
D,A
输出样例
false
解题思路
要判断数据表的依赖关系是否存在循环依赖,可以使用图的拓扑排序算法。如果图中存在环(即循环依赖),则无法进行拓扑排序,反之则可以。
具体步骤
-
构建图:
- 使用一个哈希表(
Map<String, List<String>>)来存储每个表的依赖关系。 - 使用一个入度表(
Map<String, Integer>)来记录每个表的入度(即有多少表依赖于它)。
- 使用一个哈希表(
-
初始化入度表:
- 遍历所有依赖关系,增加每个表的入度。
- 确保所有表都在入度表中,即使它们没有依赖关系。
-
拓扑排序:
- 使用一个队列来存储所有入度为0的表。
- 从队列中取出一个表,将其所有依赖表的入度减1,如果某个依赖表的入度变为0,则将其加入队列。
- 记录已经处理过的表的数量。
-
检查结果:
- 如果所有表都被处理过,说明不存在循环依赖,返回
true。 - 否则,存在循环依赖,返回
false。
- 如果所有表都被处理过,说明不存在循环依赖,返回
代码实现
import java.util.*;
public class Main {
public static boolean solution(String[][] relations) {
// 构建图
Map<String, List<String>> graph = new HashMap<>();
Map<String, Integer> inDegree = new HashMap<>();
// 初始化图和入度表
for (String[] relation : relations) {
String source = relation[0];
for (int i = 1; i < relation.length; i++) {
String target = relation[i];
graph.computeIfAbsent(source, k -> new ArrayList<>()).add(target);
inDegree.put(target, inDegree.getOrDefault(target, 0) + 1);
}
inDegree.putIfAbsent(source, 0); // 确保所有表都在入度表中
}
// 初始化队列,存储所有入度为0的表
Queue<String> queue = new LinkedList<>();
for (Map.Entry<String, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0) {
queue.offer(entry.getKey());
}
}
// 拓扑排序
int processed = 0;
while (!queue.isEmpty()) {
String table = queue.poll();
processed++;
if (graph.containsKey(table)) {
for (String dependent : graph.get(table)) {
inDegree.put(dependent, inDegree.get(dependent) - 1);
if (inDegree.get(dependent) == 0) {
queue.offer(dependent);
}
}
}
}
// 检查是否所有表都被处理过
return processed == inDegree.size();
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution(new String[][] {{"A", "B", "C"}, {"B", "D"}, {"C", "E"}, {"D", "A"}}) == false);
System.out.println(solution(new String[][] {
{"A", "B", "C", "D", "E"},
{"F", "G", "H", "I"},
{"J", "K", "L", "M", "A"},
{"N", "O", "P", "Q"},
{"E", "H", "I", "J"},
{"R", "S", "T", "U"},
{"V", "W", "X"},
{"Y", "Z"}}) == false);
}
}
代码解释
-
构建图:
graph是一个哈希表,键是表名,值是一个列表,存储该表依赖的所有表。inDegree是一个哈希表,键是表名,值是该表的入度。
-
初始化入度表:
- 遍历所有依赖关系,增加每个表的入度。
- 确保所有表都在入度表中,即使它们没有依赖关系。
-
拓扑排序:
- 使用队列存储所有入度为0的表。
- 从队列中取出一个表,将其所有依赖表的入度减1,如果某个依赖表的入度变为0,则将其加入队列。
- 记录已经处理过的表的数量。
-
检查结果:
- 如果所有表都被处理过,说明不存在循环依赖,返回
true。 - 否则,存在循环依赖,返回
false。
- 如果所有表都被处理过,说明不存在循环依赖,返回
测试用例
-
测试用例1:
- 输入:
A,B,C B,D C,E D,A - 输出:
false - 解释:存在循环依赖
A -> B -> D -> A。
- 输入:
-
测试用例2:
- 输入:
A,B,C,D,E F,G,H,I J,K,L,M,A N,O,P,Q E,H,I,J R,S,T,U V,W,X Y,Z - 输出:
false - 解释:存在循环依赖
A -> B -> C -> D -> E -> H -> I -> J -> K -> L -> M -> A。
- 输入:
通过这种方法,我们可以有效地检测数据表的依赖关系是否存在循环依赖,从而确保数据能够顺利产出。