返回

有向图访问计数:深入浅出,轻松掌握

后端

算法实战:有向图访问计数

前言

在算法的浩瀚海洋中,有一道颇具挑战性的难题,它要求我们计算一个有向图中,从一个给定节点出发,能访问到的不同节点的个数。这道题就是 LeetCode 上的 2876:有向图访问计数,被评为难度为困难。它不仅考验我们的算法能力,更让我们深入理解基环森林、内向基环树、拓扑排序等算法概念。本文将带你步步解析这道难题,让你领略算法之美。

算法之旅

理解有向图的奥秘

要解开这道题,首先需要理解有向图的概念。有向图是一种图结构,其中每条边都具有方向,也就是说,边是有起点的和终点的。而这道题的关键就在于基环森林、内向基环树和拓扑排序。

基环森林 是指一个有向图,其中每个连通分量都是一个基环树基环树 是指一个有向图,其中每个节点都属于一个环,并且每个环都只有一个入度节点和一个出度节点。内向基环树 是指一个基环树,其中每个环的入度节点都只有一个。

算法步骤:层层递进

有了这些概念,我们就可以分步解决这道题了:

  1. 构建邻接表: 将有向图表示为一个邻接表,其中每个元素是一个链表,链表中的每个节点表示一个从该节点出发的边。
  2. 计算入度: 计算每个节点的入度,即有多少条边指向该节点。
  3. 进行拓扑排序: 对有向图进行拓扑排序,将节点按顺序排列,使得对于任意两个节点 u 和 v,如果存在从 u 到 v 的边,那么 u 在 v 之前。
  4. 计算访问次数: 从给定节点出发,通过深度优先搜索或广度优先搜索计算能访问到的不同节点的个数。

算法代码:实践出真知

理论知识固然重要,但实践才是检验真理的唯一标准。以下是用 Python 实现的算法代码:

from collections import defaultdict, deque

def count_visits(graph, start):
  """
  计算从一个给定节点出发,能访问到的不同节点的个数。

  Args:
    graph: 有向图,用邻接表表示。
    start: 起始节点。

  Returns:
    能访问到的不同节点的个数。
  """

  # 计算入度
  indegree = defaultdict(int)
  for node in graph:
    for neighbor in graph[node]:
      indegree[neighbor] += 1

  # 进行拓扑排序
  queue = deque()
  for node in graph:
    if indegree[node] == 0:
      queue.append(node)

  visited = set()
  while queue:
    node = queue.popleft()
    visited.add(node)
    for neighbor in graph[node]:
      indegree[neighbor] -= 1
      if indegree[neighbor] == 0:
        queue.append(neighbor)

  # 计算访问次数
  count = 0
  queue = deque([start])
  while queue:
    node = queue.popleft()
    count += 1
    for neighbor in graph[node]:
      if neighbor not in visited:
        queue.append(neighbor)

  return count


# 测试用例
graph = {
  'A': ['B', 'C'],
  'B': ['D', 'E'],
  'C': ['F'],
  'D': ['E'],
  'E': ['F'],
  'F': ['A']
}
start = 'A'

# 计算访问次数
count = count_visits(graph, start)

# 输出结果
print(count)  # 6

总结:算法的魅力

这道题不仅考察了我们的算法能力,更让我们深入理解了有向图的特性和算法的精妙之处。算法学习是一个不断探索和实践的过程,希望这篇文章能为你的算法之旅增添一份助力。

常见问题解答

  1. 什么是基环森林?

    • 基环森林是指一个有向图,其中每个连通分量都是一个基环树。
  2. 什么是内向基环树?

    • 内向基环树是指一个基环树,其中每个环的入度节点都只有一个。
  3. 拓扑排序的意义是什么?

    • 拓扑排序可以将有向图中的节点按顺序排列,使得对于任意两个节点 u 和 v,如果存在从 u 到 v 的边,那么 u 在 v 之前。
  4. 算法的步骤有哪些?

    • 构建邻接表、计算入度、进行拓扑排序、计算访问次数。
  5. 算法的时间复杂度是多少?

    • 算法的时间复杂度为 O(V+E),其中 V 是节点的个数,E 是边的个数。