拓扑排序

拓扑排序

LCR 113. 课程表 II

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

using namespace std;

class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>> &prerequisites) {
        vector<int> res;
        // 入度
        vector<int> in_degree(numCourses, 0);
        // 邻接表
        vector<vector<int>> graph(numCourses);
        // 初始化邻接表和入度表
        for (const auto &item: prerequisites) {
            graph[item[1]].emplace_back(item[0]);
            in_degree[item[0]]++;
        }

        // 存放入度为 0 的顶点
        queue<int> q;
        for (int i = 0; i < numCourses; ++i)
            if (in_degree[i] == 0) q.emplace(i);

        // 已经加入拓扑序列的节点个数
        int count = 0;
        while (!q.empty()) {
            // 取出入度为 0 的顶点
            int node = q.front();
            q.pop();
            // 加入拓扑序列
            res.emplace_back(node);
            count++;

            // 遍历邻接表,把入度为 0 的节点从图中断开
            for (const auto &next: graph[node]){
                // 下个顶点的入度减一
                in_degree[next]--;
                // 出现入度为 0 的节点,加入队列
                if (in_degree[next] == 0) q.emplace(next);
            }
        }

        return count == numCourses ? res : vector<int>();
    }
};

AB13 【模板】拓扑排序

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

using namespace std;


int main() {
    int n, m;
    cin >> n >> m;
    vector<pair<int, int>> edges(m);
    for (int i = 0; i < m; ++i)
        cin >> edges[i].first >> edges[i].second;

    // 顶点编号从 1 开始
    // 邻接矩阵
    vector<vector<int>> graph(n + 1);
    // 记录入度
    vector<int> in_degree(n + 1, 0);
    for (const auto &edge: edges) {
        graph[edge.first].emplace_back(edge.second);
        in_degree[edge.second]++;
    }

    // 存放入度为 0 的顶点
    queue<int> q;
    for (int i = 1; i <= n; ++i)
        if (in_degree[i] == 0)
            q.emplace(i);

    vector<int> res(n);
    int count = 0;
    // 依次从图中取出入度为 0 的节点
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        res[count++] = node;
        // 遍历指向的顶点,将其入度减一
        for (int i = 0; i < graph[node].size(); ++i) {
            // 下个顶点
            int next = graph[node][i];
            in_degree[next]--;
            if (in_degree[next] == 0) q.emplace(next);
        }
    }

    if (count == n) {
        for (int i = 0; i < n - 1; ++i)
            cout << res[i] << " ";
        cout << res[n - 1];
    } else {
        cout << -1;
    }
}

U107394 拓扑排序模板

  • 有向无环图上有n个点,m条边。求这张图字典序最小的拓扑排序的结果。字典序最小指希望排好序的结果中,比较靠前的数字尽可能小。
  • 把存放入度为 0 的队列换成小顶堆即可按字典序输出
#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    vector<pair<int, int>> edges(m);
    for (int i = 0; i < m; ++i)
        cin >> edges[i].first >> edges[i].second;

    // 顶点编号从 1 开始
    // 邻接矩阵
    vector<vector<int>> graph(n + 1);
    // 记录入度
    vector<int> in_degree(n + 1, 0);
    for (const auto &edge: edges) {
        graph[edge.first].emplace_back(edge.second);
        in_degree[edge.second]++;
    }

    // 存放入度为 0 的顶点
    priority_queue<int, vector<int>, greater<>> q;
    for (int i = 1; i <= n; ++i)
        if (in_degree[i] == 0)
            q.push(i);

    vector<int> res(n);
    int count = 0;
    // 依次从图中取出入度为 0 的节点
    while (!q.empty()) {
        int node = q.top();
        q.pop();
        res[count++] = node;
        // 遍历指向的顶点,将其入度减一
        for (int i = 0; i < graph[node].size(); ++i) {
            // 下个顶点
            int next = graph[node][i];
            in_degree[next]--;
            if (in_degree[next] == 0) q.push(next);
        }
    }

    if (count == n) {
        for (int i = 0; i < n - 1; ++i)
            cout << res[i] << " ";
        cout << res[n - 1];
    } else {
        cout << -1;
    }
}
#include <iostream>
#include <vector>

using namespace std;

// 链式前向星
vector<int> head;
vector<int> nxt;
vector<int> to;
int edgeIndex = 0;
vector<int> in_degree;

void addEdge(int u, int v) {
    edgeIndex++;
    nxt[edgeIndex] = head[u];
    to[edgeIndex] = v;
    head[u] = edgeIndex;
}

// 顶点编号和边的编号从 1 开始
void buildGraph(int vertexNum, int edgeNum, vector<pair<int, int>> &edges) {
    head.resize(vertexNum, 0);
    nxt.resize(edgeNum);
    to.resize(edgeNum);
    for (const auto &item: edges)
        addEdge(item.first, item.second);
}

// 小顶堆存放入度为 0 的顶点编号
vector<int> heap;
int lenOfHeap;

void buildHeap(int max) {
    heap.resize(max);
    lenOfHeap = 0;
}

// 下标从 0 开始
void adjustHeap(int curIndex) {
    int val = heap[curIndex];
    int leftChild = 2 * curIndex + 1;
    while (leftChild <= (lenOfHeap - 1)) {
        // 右孩子更小就选右孩子
        if ((leftChild < (lenOfHeap - 1))
            && heap[leftChild] > heap[leftChild + 1])
            leftChild++;
        // 不需要调整
        if (heap[leftChild] >= val) break;
        // 调整
        heap[curIndex] = heap[leftChild];
        curIndex = leftChild;
        leftChild = 2 * curIndex + 1;
    }
    heap[curIndex] = val;
}

void push(int val) {
    int curIndex = lenOfHeap;
    // 堆大小加一
    lenOfHeap++;
    // 从下往上调整到合适位置
    int parentIndex = (curIndex - 1) >> 1;

    while (parentIndex >= 0 && heap[parentIndex] > val) {
        // 父节点下移
        heap[curIndex] = heap[parentIndex];
        curIndex = parentIndex;
        parentIndex = (curIndex - 1) >> 1;
    }

    heap[curIndex] = val;
}

int getTop() {
    int res = heap[0];
    // 末尾元素移到堆顶
    heap[0] = heap[lenOfHeap - 1];
    lenOfHeap--;
    // 调整堆顶
    adjustHeap(0);
    return res;
}


int main() {
    int n, m;
    cin >> n >> m;
    vector<pair<int, int>> edges(m);
    for (int i = 0; i < m; ++i)
        cin >> edges[i].first >> edges[i].second;

    buildGraph(n + 1, m + 1, edges);
    buildHeap(n + 1);

    // 记录入度
    in_degree.resize(n + 1, 0);
    for (const auto &item: edges)
        in_degree[item.second]++;

    // 初始状态入度为 0 的点加入堆
    for (int i = 1; i < in_degree.size(); ++i)
        if (in_degree[i] == 0) push(i);

    vector<int> res(n);
    int resIndex = 0;
    // 取出入度为 0 的点,并删除关联的边
    while (lenOfHeap != 0) {
        int v = getTop();
        res[resIndex++] = v;
        // 把所有关联的顶点入度减一
        int e = head[v];
        while (e > 0) {
            in_degree[to[e]]--;
            if (in_degree[to[e]] == 0) push(to[e]);
            e = nxt[e];
        }
    }

    if (resIndex == n) {
        for (int i = 0; i < n - 1; ++i)
            cout << res[i] << " ";
        cout << res[n - 1];
    } else {
        cout << -1;
    }
}

LCR 114. 火星词典

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

using namespace std;

class Solution {
public:
    string alienOrder(vector<string> &words) {

        vector<int> in_degree(26, -1);
        // 入度为 -1 的就是没出现过的字符
        for (const auto &str: words)
            for (int i = 0; i < str.length(); ++i)
                in_degree[str[i] - 'a'] = 0;

        // 建图
        vector<vector<int>> graph(26);
        for (int i = 0; i < words.size() - 1; ++i) {
            string cur = words[i];
            string nxt = words[i + 1];
            int j = 0;
            int len = min(cur.length(), nxt.length());
            while (j < len) {
                if (cur[j] != nxt[j]) {
                    // cur[j] 字典序在 nxt[j] 前
                    graph[cur[j] - 'a'].emplace_back(nxt[j] - 'a');
                    in_degree[nxt[j] - 'a']++;
                    break;
                }
                j++;
            }
            // 前面相同,但 nxt 长度更小,没有字典序
            if (j < cur.length() && j == nxt.length()) return "";
        }

        queue<int> q;
        // 字符种类数
        int kinds = 0;
        for (int i = 0; i < 26; ++i) {
            if (in_degree[i] != -1) kinds++;
            if (in_degree[i] == 0) q.push(i);
        }

        string res = "";
        while (!q.empty()) {
            int cur = q.front();
            q.pop();
            // 追加一个字符 cur
            res.append(1, cur + 'a');
            for (const auto &item: graph[cur]) {
                in_degree[item]--;
                if (in_degree[item] == 0) q.push(item);
            }
        }
        return res.length() == kinds ? res : "";
    }
};

936. 戳印序列

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

using namespace std;

class Solution {
public:
    vector<int> movesToStamp(string stamp, string target) {
        int m = stamp.length();
        int n = target.length();
        // 初始时,入度就是以 i 位置开始,匹配不上的位置的总数,为 m
        vector<int> in_degree(n - m + 1, m);

        vector<vector<int>> graph(n);
        queue<int> q;
        // 以 i 开头长度为 m 的字符串与印章对比
        for (int i = 0; i <= n - m; ++i) {
            for (int j = 0; j < m; ++j) {
                if (target[i + j] == stamp[j]) {
                    // 匹配上的位置,入度减一
                    in_degree[i]--;
                    // 入度为 0,入队
                    if (in_degree[i] == 0) q.push(i);
                } else {
                    // 匹配不上的位置 -> 开头下标 i
                    graph[i + j].emplace_back(i);
                }
            }
        }
        // 同一个位置取消错误不重复统计
        vector<bool> visited(n);
        vector<int> res(n - m + 1);
        int size = 0;
        while (!q.empty()) {
            int v = q.front();
            q.pop();
            // 入度为 0 的最后盖章
            res[size++] = v;
            // 以 v 下标开始盖章
            for (int i = 0; i < m; ++i) {
                // 这个位置可以由后面的某次盖章处理掉
                if (visited[v + i] == true) continue;
                // 把仍有错误的位置盖上,变成 true
                visited[v + i] = true;
                // v + i 位置盖上后,找到所有在 v + i 匹配不上的开头下标 item
                // 将 item 开头的位置,入度减小一,表示 v + i 位置匹配上了
                for (const auto &item: graph[v + i]) {
                    in_degree[item]--;
                    // 如果以 item 开头的位置,后面已经全都匹配上了,也就是入度为 0 了,就入队
                    if (in_degree[item] == 0) q.push(item);
                }
            }
        }
        if (size != n - m + 1) return vector<int>();
        // 反转,先加入结果的是最后盖的
        reverse(begin(res), end(res));
        return res;
    }
};

P4017 最大食物链计数

  • 利用拓扑排序的过程,将上游节点的信息逐渐传递到下游节点
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

int main() {
    int MOD = 80112002;
    int n, m;
    cin >> n >> m;
    vector<pair<int, int>> edges(m);
    for (int i = 0; i < m; ++i)
        cin >> edges[i].first >> edges[i].second;

    // 下标从 1 开始
    vector<vector<int>> graph(n + 1);
    vector<int> in_degree(n + 1, 0);
    for (const auto &item: edges) {
        graph[item.first].emplace_back(item.second);
        in_degree[item.second]++;
    }

    queue<int> q;
    // 存放到 i 号节点的路径总数
    vector<int> path(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        if (in_degree[i] == 0) {
            q.push(i);
            path[i] = 1;
        }
    }

    int res = 0;
    while (!q.empty()) {
        int v = q.front();
        q.pop();
        if (graph[v].size() == 0) {
            // 没有后续节点
            res = (res + path[v]) % MOD;
        } else {
            for (const auto &item: graph[v]) {
                in_degree[item]--;
                if (in_degree[item] == 0) q.push(item);
                // 路径总数继承到后续顶点
                path[item] = (path[item] + path[v]) % MOD;
            }
        }
    }

    cout << res;
}

851. 喧闹和富有

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

using namespace std;

class Solution {
public:
    vector<int> loudAndRich(vector<vector<int>> &richer, vector<int> &quiet) {
        int n = quiet.size();
        // 入度表
        vector<int> in_degree(n, 0);
        // 邻接表
        vector<vector<int>> graph(n);

        for (const auto &item: richer) {
            graph[item[0]].emplace_back(item[1]);
            in_degree[item[1]]++;
        }

        queue<int> q;
        for (int i = 0; i < n; ++i)
            if (in_degree[i] == 0)
                q.push(i);

        // 初始状态认为更安静的都是自己
        vector<int> res(n);
        for (int i = 0; i < n; ++i)
            res[i] = i;
        while (!q.empty()) {
            int v = q.front();
            q.pop();

            for (const auto &item: graph[v]) {
                // 加入新的入度为 0 的
                if (--in_degree[item] == 0) q.push(item);
                // 如果弧尾更安静,就把弧头的替换成弧尾记录的更安静的元素
                if (quiet[res[v]] <= quiet[res[item]]) res[item] = res[v];
            }
        }

        return res;
    }
};

2050. 并行课程 III

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

using namespace std;

class Solution {
public:
    int minimumTime(int n, vector<vector<int>> &relations, vector<int> &time) {
        vector<int> in_degree(n + 1, 0);
        vector<vector<int>> graph(n + 1);
        for (const auto &item: relations) {
            graph[item[0]].emplace_back(item[1]);
            in_degree[item[1]]++;
        }

        queue<int> q;
        for (int i = 1; i <= n; ++i)
            if (in_degree[i] == 0)
                q.push(i);

        // 完成先修课程以及自身的最少时间
        vector<int> cost(time);
        int res = 0;
        while (!q.empty()) {
            int v = q.front();
            q.pop();
            res = max(res, cost[v - 1]);
            for (const auto &item: graph[v]) {
                if (--in_degree[item] == 0) q.push(item);
                // 选取最少的时间,实际就是较大的 cost,耗时较长的完成了,耗时较小的能同时完成
                cost[item - 1] = max(cost[item - 1], time[item - 1] + cost[v - 1]);
            }
        }

        return res;
    }
};

2127. 参加会议的最多员工数

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

using namespace std;

class Solution {
public:
    int maximumInvitations(vector<int> &favorite) {
        // favourite 就是图,每个节点出度为 1
        int n = favorite.size();
        vector<int> in_degree(n, 0);
        for (const auto &item: favorite)
            in_degree[item]++;

        // 存储到当前顶点的路径上的节点数
        vector<int> deep(n, 0);

        queue<int> q;
        for (int i = 0; i < n; ++i)
            if (in_degree[i] == 0)
                q.push(i);

        // 去除入度为 0 的顶点,留下环
        while (!q.empty()) {
            int v = q.front();
            q.pop();
            int nxt = favorite[v];
            if (--in_degree[nxt] == 0) q.push(nxt);
            // 更新最长路径上的节点总数
            deep[nxt] = max(deep[nxt], deep[v] + 1);
        }

        int sumOfSmallRing = 0;
        int maxLargeRing = 0;
        for (int i = 0; i < n; ++i) {
            // 跳过入度为 0 的
            if (in_degree[i] == 0) continue;
            in_degree[i] = 0;
            // 入度不为 0,说明在环上
            int ringLen = 1;
            // 计算环的长度
            int nxt = favorite[i];
            while (in_degree[nxt] != 0) {
                ringLen++;
                in_degree[nxt] = 0;
                nxt = favorite[nxt];
            }
            if (ringLen == 2) {
                // 情况1:圆桌上围着的是多个图,每个图中只有一个环,且环中只有 2 个节点。这些图可以共同围着圆桌
                // 加上这个长度为 2 的环能提供的最多数目:包括环自身的 2 个长度,以及向外延伸出去的两条最长路径长
                sumOfSmallRing += deep[i] + deep[favorite[i]] + 2;
            } else {
                // 情况2:如果环中节点数大于 2,那么圆桌上只能存在一个这种图,选择环中节点最多的
                maxLargeRing = max(maxLargeRing, ringLen);
            }
        }
        return max(sumOfSmallRing, maxLargeRing);
    }
};
posted @ 2024-09-12 17:08  n1ce2cv  阅读(7)  评论(0编辑  收藏  举报