侧边栏壁纸
博主头像
牛马 OJ 博主等级

行动起来,活在当下

  • 累计撰写 9 篇文章
  • 累计创建 2 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

拓扑排序

Administrator
2025-03-04 / 0 评论 / 2 点赞 / 83 阅读 / 0 字

一、考察频率以及难度

考察频率:⭐⭐⭐

笔试题难度:⭐⭐⭐

算法难度:⭐⭐⭐

一般是作为笔试倒数2道题出现。

二、学习技巧

  1. 理解好拓扑排序的算法过程,熟记拓扑排序的模板。

  2. 积累常见的拓扑排序的应用场景。

三、应用场景

在DAG(有向无环图)节点之间有依赖关系,并且需要按照依赖关系进行遍历的,此时可以考虑拓扑排序的算法来进行求解。

四、算法讲解

拓扑排序是在有向图的基础上,对节点进行排序,使得对于每一条有向边u→v,u都在v之间出现。如下图所示:

输出顺序为[a, b, d, e, c](或者[a, b, e, d, c] )

解决这个问题最经典的算法就是使用卡恩(kahn)算法。

核心思想如下:首先,先拿出所有入度为0的点排在前面,并在原图中将它们删除;这时有些点的入度减少了,于是再拿出当前所有入度为0的点放在已经排序的序列后面,然后删除;循环这个过程即可。

当然这个过程必须要保证图是无环的。

五、例题与解析

习题1 勇敢者之旅

测评链接

你是一名勇敢的探险家,准备进入一个神秘的地牢探险。地牢由 numRooms 个房间组成,记为 0 到 numRooms - 1。在进入某些房间之前,你需要先通过其他房间。房间的先后顺序按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi],表示如果要进入房间 ai 则 必须 先进入房间 bi。例如,先修房间对 [0, 1] 表示:想要进入房间 0,你需要先进入房间 1。

请你判断是否可能按照这些规定的顺序通过所有房间进行探险?如果可以,输出 Yes;否则,输出 No。

输入格式

  • 第一行包含一个整数 numRooms (1 <= numRooms <= 10^5),表示房间的数量。

  • 第二行包含一个整数 m (0 <= m <= 5000),表示先决条件的数量。

  • 接下来的 m 行,每行包含两个整数 ai 和 bi (0 <= ai, bi < numRooms),表示进入房间 ai 之前必须先进入房间 bi。

输出格式

  • 如果可以按照规定顺序通过所有房间,输出 "Yes";否则,输出 "No"。

示例1

输入

2
1
1 0

输出

true

示例2

输入

2
2
1 0
0 1

输出

false

提示

  • 1 <= numRooms <= 10^5

  • 0 <= m <= 5000

  • 每个 prerequisites[i] 中的所有房间对互不相同

题目解析

此题的核心其实是判断是否成环。是否成环问题的判断其实是可以使用拓扑排序来解决的,如果使用kafh算法遍历完以后,遍历的节点数量与所有节点的数量相等,那么说明不成环,否则说明成环。

C++

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int main() {
    int numCourses, m;
    cin >> numCourses >> m;
    vector<pair<int, int>> edges(m);

    for (int i = 0; i < m; ++i) {
        int a, b;
        cin >> a >> b;
        edges[i] = {a, b};
    }

    vector<int> indegree(numCourses, 0);
    vector<vector<int>> outdegree(numCourses);

    for (const auto& edge : edges) {
        int a = edge.first;
        int b = edge.second;
        indegree[a]++;
        outdegree[b].push_back(a);
    }

    queue<int> q;
    for (int i = 0; i < numCourses; ++i) {
        if (indegree[i] == 0) {
            q.push(i);
        }
    }

    int cnt = 0;
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        cnt++;
        for (int nx : outdegree[node]) {
            indegree[nx]--;
            if (indegree[nx] == 0) {
                q.push(nx);
            }
        }
    }

    if (cnt == numCourses) {
        cout << "Yes" << endl;
    } else {
        cout << "No" << endl;
    }

    return 0;
}

Java

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int numCourses = scanner.nextInt();
        int m = scanner.nextInt();
        List<int[]> edges = new ArrayList<>();

        for (int i = 0; i < m; i++) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();
            edges.add(new int[]{a, b});
        }

        int[] indegree = new int[numCourses];
        List<List<Integer>> outdegree = new ArrayList<>(numCourses);

        for (int i = 0; i < numCourses; i++) {
            outdegree.add(new ArrayList<>());
        }

        for (int[] edge : edges) {
            int a = edge[0];
            int b = edge[1];
            indegree[a]++;
            outdegree.get(b).add(a);
        }

        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (indegree[i] == 0) {
                queue.add(i);
            }
        }

        int cnt = 0;
        while (!queue.isEmpty()) {
            int node = queue.poll();
            cnt++;
            for (int nx : outdegree.get(node)) {
                indegree[nx]--;
                if (indegree[nx] == 0) {
                    queue.add(nx);
                }
            }
        }

        if (cnt == numCourses) {
            System.out.println("Yes");
        } else {
            System.out.println("No");
        }

        scanner.close();
    }
}

Python

from collections import deque

numCourses = int(input())
m = int(input())
edges = []
for _ in range(m):
    a,b = map(int, input().split())
    edges.append((a, b))

indegree = [0] * numCourses
outdegree = [[] for _ in range(numCourses)]
for a, b in edges:
    indegree[a] += 1
    outdegree[b].append(a)

queue = deque()
for i in range(numCourses):
    if indegree[i] == 0:
            queue.append(i)

cnt = 0
while queue:
    node = queue.popleft()
    cnt += 1
    for nx in outdegree[node]:
        indegree[nx] -= 1
        if indegree[nx] == 0:
            queue.append(nx)

if cnt == numCourses:
    print("Yes")
else:
    print("No")

习题2 外星语言

[测评链接](https://niumacode.com/problem/P1131)

你是一名宇航员,刚刚发现了一颗新的外星星球,并接触到了一种使用英语字母的外星语言。这门语言的字母顺序与英语顺序不同。你获得了一份外星词典,其中的单词已经按照这门新语言的字母顺序进行了排序。你需要根据这份词典还原出外星语言中的字母顺序,并按字母递增顺序排列。如果不存在合法字母顺序,返回 -1。如果存在多种可能的合法字母顺序,返回字典序最小的。

输入格式

  • 第一行包含一个整数 n (1 <= n <= 100),表示词典中单词的数量。

  • 接下来的 n 行,每行包含一个字符串 word (1 <= word.length <= 100),表示词典中的一个单词。这些单词仅由小写英文字母组成。

输出格式

  • 如果存在合法的字母顺序,按字母递增顺序输出该顺序。

  • 如果不存在合法的字母顺序,返回 "-1"。

示例1

输入

5
wrt
wrf
er
ett
rftt

输出

wertf

示例2

输入

2
z
x

输出

zx


示例3

输入

3
z
x
z

输出

-1

提示

  • 1 <= n <= 100

  • 1 <= words[i].length <= 100

  • words[i] 仅由小写英文字母组成

题目解析

按照给定的words的顺序,构建图,然后进行拓扑排序即可,最后的拓扑序就是答案。如果拓扑排序进行完,依然存在节点的度数不为0,那么此时成环。

需要注意的是,题目要求结果的字典序最小,所以我们拓扑排序的队列需要使用优先队列,保证每次出队列的一定是字典序最小的字符,这样可以保证结果是字典序最小的。

C++

#include <iostream>
#include <vector>
#include <unordered_map>
#include <queue>
#include <string>
#include <algorithm>

using namespace std;

string alienOrder(vector<string>& words) {
    unordered_map<char, vector<char>> graph;
    unordered_map<char, int> inDegree;

    // Initialize the in_degree with all unique characters
    for (const auto& word : words) {
        for (char c : word) {
            inDegree[c] = 0;
        }
    }

    int n = words.size();
    for (int i = 0; i < n - 1; ++i) {
        string word1 = words[i];
        string word2 = words[i + 1];
        int mm = min(word1.length(), word2.length());
        if (word1.substr(0, mm) == word2.substr(0, mm) && word1.length() > word2.length()) {
            return "";
        }
        for (int j = 0; j < mm; ++j) {
            if (word1[j] != word2[j]) {
                graph[word1[j]].push_back(word2[j]);
                inDegree[word2[j]]++;
                break;
            }
        }
    }

    priority_queue<char, vector<char>, greater<char>> pq;
    for (const auto& pair : inDegree) {
        if (pair.second == 0) {
            pq.push(pair.first);
        }
    }

    string result;
    while (!pq.empty()) {
        char node = pq.top();
        pq.pop();
        result += node;
        for (char neighbor : graph[node]) {
            inDegree[neighbor]--;
            if (inDegree[neighbor] == 0) {
                pq.push(neighbor);
            }
        }
    }

    for (const auto& pair : inDegree) {
        if (pair.second > 0) {
            return "";
        }
    }

    return result;
}

int main() {
    int n;
    cin >> n;
    vector<string> words(n);
    for (int i = 0; i < n; ++i) {
        cin >> words[i];
    }
    string result = alienOrder(words);
    if (result.empty()) {
        cout << "-1" << endl;
    } else {
        cout << result << endl;
    }
    return 0;
}

Java

import java.util.*;

public class Main {
    public static String alienOrder(String[] words) {
        Map<Character, List<Character>> graph = new HashMap<>();
        Map<Character, Integer> inDegree = new HashMap<>();

        // Initialize the in_degree with all unique characters
        for (String word : words) {
            for (char c : word.toCharArray()) {
                inDegree.put(c, 0);
            }
        }

        int n = words.length;
        for (int i = 0; i < n - 1; i++) {
            String word1 = words[i];
            String word2 = words[i + 1];
            int mm = Math.min(word1.length(), word2.length());
            if (word1.substring(0, mm).equals(word2.substring(0, mm)) && word1.length() > word2.length()) {
                return "";
            }
            for (int j = 0; j < mm; j++) {
                if (word1.charAt(j) != word2.charAt(j)) {
                    graph.computeIfAbsent(word1.charAt(j), k -> new ArrayList<>()).add(word2.charAt(j));
                    inDegree.put(word2.charAt(j), inDegree.get(word2.charAt(j)) + 1);
                    break;
                }
            }
        }

        PriorityQueue<Character> pq = new PriorityQueue<>();
        for (Map.Entry<Character, Integer> entry : inDegree.entrySet()) {
            if (entry.getValue() == 0) {
                pq.add(entry.getKey());
            }
        }

        StringBuilder result = new StringBuilder();
        while (!pq.isEmpty()) {
            char node = pq.poll();
            result.append(node);
            if (graph.containsKey(node)) {
                for (char neighbor : graph.get(node)) {
                    inDegree.put(neighbor, inDegree.get(neighbor) - 1);
                    if (inDegree.get(neighbor) == 0) {
                        pq.add(neighbor);
                    }
                }
            }
        }

        for (int value : inDegree.values()) {
            if (value > 0) {
                return "";
            }
        }

        return result.toString();
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        String[] words = new String[n];
        for (int i = 0; i < n; i++) {
            words[i] = scanner.next();
        }
        String result = alienOrder(words);
        if (result.isEmpty()) {
            System.out.println("-1");
        } else {
            System.out.println(result);
        }
        scanner.close();
    }
}

Python

import heapq
from collections import defaultdict, deque
from typing import List

n = int(input())
words = []
for _ in range(n):
    words.append(input())
graph = defaultdict(list)
indgre = defaultdict(int)
for word in words:
    for c in word:
        indgre[c] = 0
n = len(words)

for i in range(n-1):
    word1 = words[i]
    word2 = words[i+1]
    mm = min(len(word1), len(word2))
    if word1[:mm] == word2[:mm] and len(word1) > len(word2):
        print('')
    for j in range(min(len(word1), len(word2))):
        if word1[j] != word2[j]:
            graph[word1[j]].append(word2[j])
            indgre[word2[j]] += 1
            break

q = []
for key in indgre:
    if indgre[key] == 0:
        heapq.heappush(q, key)
res = ''
while q:
    node = heapq.heappop(q)
    res += node
    for neighbor in graph[node]:
        indgre[neighbor] -= 1
        if indgre[neighbor] == 0:
            heapq.heappush(q, neighbor)
if sum(indgre.values()) > 0:
    print('')
else:
    print(res)

习题3 迷宫的环路

[测评链接](https://niumacode.com/problem/P1132)

你是一名探险家,刚刚进入了一座古老的迷宫。迷宫中的每个节点代表一个房间,每个房间至多有一个出口。房间之间的连接用一个数组表示,其中 edges[i] 表示从房间 i 可以到达的下一个房间。如果 edges[i] == -1,则表示房间 i 没有出口。你需要找到迷宫中最长的环路,如果没有任何环路,请返回 -1。

输入格式

  • 第一行包含一个整数 n (2 <= n <= 10^5),表示房间的数量。

  • 第二行包含 n 个整数 edges[i] (-1 <= edges[i] < n),表示每个房间的出口。如果房间 i 没有出口,则 edges[i] == -1。

输出格式

  • 输出一个整数,表示迷宫中最长环路的长度。如果没有任何环路,返回 -1。



示例

输入

5
3 3 4 2 3

输出

3

解释:图中的最长环是:2 -> 4 -> 3 -> 2 。这个环的长度为 3 ,所以返回 3 。

输入

4
2 -1 3 1

输出

-1

解释:图中没有任何环。

提示

  • n == edges.length

  • 2 <= n <= 10^5

  • -1 <= edges[i] < n

  • edges[i] != i

题目解析

由于题目说明了, 每个点只有一个出度边,所以直接拓扑排序就可以把非环中的点找到。然后再去遍历剩余的节点,找到所有环并且记录大小即可。

CPP

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> edges(n);
    for (int i = 0; i < n; ++i) {
        cin >> edges[i];
    }

    queue<int> q;
    vector<int> indegree(n, 0);
    for (int nx : edges) {
        if (nx == -1) continue;
        indegree[nx]++;
    }

    for (int i = 0; i < n; ++i) {
        if (indegree[i] == 0) {
            q.push(i);
        }
    }

    vector<bool> visited(n, false);
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        visited[node] = true;
        int nx = edges[node];
        if (nx == -1) continue;
        indegree[nx]--;
        if (indegree[nx] == 0) {
            q.push(nx);
        }
    }

    int ans = 0;
    for (int i = 0; i < n; ++i) {
        if (visited[i]) continue;
        int cur = 0;
        int node = i;
        while (!visited[node]) {
            visited[node] = true;
            cur++;
            node = edges[node];
        }
        ans = max(ans, cur);
    }

    if (ans != 0) {
        cout << ans << endl;
    } else {
        cout << -1 << endl;
    }

    return 0;
}

Java

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] edges = new int[n];
        for (int i = 0; i < n; i++) {
            edges[i] = scanner.nextInt();
        }

        Queue<Integer> queue = new LinkedList<>();
        int[] indegree = new int[n];
        for (int nx : edges) {
            if (nx != -1) {
                indegree[nx]++;
            }
        }

        for (int i = 0; i < n; i++) {
            if (indegree[i] == 0) {
                queue.add(i);
            }
        }

        boolean[] visited = new boolean[n];
        while (!queue.isEmpty()) {
            int node = queue.poll();
            visited[node] = true;
            int nx = edges[node];
            if (nx == -1) continue;
            indegree[nx]--;
            if (indegree[nx] == 0) {
                queue.add(nx);
            }
        }

        int ans = 0;
        for (int i = 0; i < n; i++) {
            if (visited[i]) continue;
            int cur = 0;
            int node = i;
            while (!visited[node]) {
                visited[node] = true;
                cur++;
                node = edges[node];
            }
            ans = Math.max(ans, cur);
        }

        if (ans != 0) {
            System.out.println(ans);
        } else {
            System.out.println(-1);
        }

        scanner.close();
    }
}

Python

from collections import deque
from typing import List

n = int(input())
edges = [int(c) for c in input().split()]

q = deque()
n = len(edges)
indegre = [0] * (n)
for nx in edges:
    if nx == -1: continue
    indegre[nx] += 1

for i in range(n):
    if indegre[i] == 0:
        q.append(i)

vst = [False] * n
while q:
    node = q.popleft()
    vst[node] = True
    nx = edges[node]
    if nx == -1: continue
    indegre[nx] -= 1
    if indegre[nx] == 0:
        q.append(nx)

ans = 0
for i in range(n):
    if vst[i]: continue
    cur = 0
    node = i
    while not vst[node]:
        vst[node] = True
        cur += 1
        node = edges[node]
    ans = max(ans, cur)
if ans != 0:
    print(ans)
else:
    print(-1)

六、常见陷阱

  1. 对于比较绕的背景容易分析不出来拓扑排序的应用,特别是题目没有明显解释有依赖关系的.

2
博主关闭了所有页面的评论