算法期中1004. 拓扑序[Special judge]

2 篇文章 0 订阅

Description

在图论中,拓扑序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列. 且该序列必须满足下面两个条件:

  1. 每个顶点出现且只出现一次.
  2. 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面.

对于一个含有n个节点的有向无环图(节点编号0到n-1),输出它的一个拓扑序.

图的节点数和边数均不多于100000,保证输入的图是一个无环图.

请为下面的Solution类实现解决上述问题的topologicalSort函数,函数参数中n为图的节点数,edges是边集,edges[i]表示第i条边从edges[i].first指向edges[i].second. 函数返回值为有向图的一个拓扑序. 有向图有多个拓扑序时,输出任意一个即可.

class Solution {
public:
       vector<int> topologicalSort(int n, vector<pair<int, int>>& edges) {

    }
}; 

例1:
n = 3,edges = {(0, 1), (0, 2)},函数应返回{0, 1, 2}或者{0, 2, 1}.

例2:
n = 4,edges = {(0, 1), (0, 2), (1, 2), (3, 0)},函数应返回{3, 0, 1, 2}.

解析:

以下有关拓扑排序的解释来自于这篇博文:
拓扑排序

一、什么是拓扑排序

在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:

每个顶点出现且只出现一次。
若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。

例如,下面这个图:
这里写图片描述

它是一个 DAG 图,那么如何写出它的拓扑排序呢?这里说一种比较常用的方法:
1、从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
2、从图中删除该顶点和所有以它为起点的有向边。
重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。
于是,得到拓扑排序后的结果是 { 1, 2, 4, 3, 5 }。
通常,一个有向无环图可以有一个或多个拓扑排序序列。

二、拓扑排序的应用

拓扑排序通常用来“排序”具有依赖关系的任务。
比如,如果用一个DAG图来表示一个工程,其中每个顶点表示工程中的一个任务,用有向边

代码

测试main函数:

#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
#include<time.h>
#include<queue>
using namespace std;

int main() {
    Solution solution;
    vector<pair<int, int>> edges = { {0,1}, {0,2}, {1,2}, {3,0} };

    vector<int> res = solution.topologicalSort(4, edges);
    for (int i = 0; i < res.size(); i++) {
        cout << res[i] << " ";
    }
    system("pause");
    return 0;
}

超时了orz,毕竟太粗暴了,复杂度都到三次方了2333。

class Solution {
public:
    vector<int> topologicalSort(int n, vector<pair<int, int>>& edges) {
        vector<int> result;
        // 找出所有点的入度
        vector<int> degree(n, 0);
        for (int i = 0; i < edges.size(); i++) {
            degree[edges[i].second]++;
        }
        // 进行拓扑排序
        for (int j = 0; j < n; j++) {  // 要把n个点轮流输出
            for (int i = 0; i < n; i++) {
                if (degree[i] == 0) {
                    // 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出
                    result.push_back(i);
                    // 从图中删除该顶点和所有以它为起点的有向边
                    degree[i] = -1;
                    for (int k = 0; k < edges.size(); k++) {
                        if (edges[k].first == i) {
                            degree[edges[k].second]--;
                        }
                    }
                    break;
                }
            }
        }
        return result;
    }
};

改进1

使用队列,卧槽,居然第四个测试样例还是超时….这复杂度已经是O(V·E)了…

class Solution {
public:
    vector<int> topologicalSort(int n, vector<pair<int, int>>& edges) {
        vector<int> result;
        // 找出所有点的入度
        vector<int> degree(n, 0);
        for (int i = 0; i < edges.size(); i++) {
            degree[edges[i].second]++;
        }

        // 进行拓扑排序
        queue<int> q;
        // 将所有入度为0的顶点入队
        for (int j = 0; j < n; j++) {  
            if (degree[j] == 0) {
                q.push(j);
            }
        }
        while (!q.empty()) {
            int v = q.front();      // 从队列中取出一个顶点并删除
            q.pop();
            result.push_back(v);
            // 将以该定点为起始边的终点的入度--,如果减过入度为0则入队。
            for (int k = 0; k < edges.size(); k++) {
                if (edges[k].first == v) {
                    if (--degree[edges[k].second] == 0)
                        q.push(edges[k].second);
                }
            }
        }
        return result;
    }
};

改进二

AC了!!!喜大普奔!!!
利用数组可以直接存取数据的特性,用空间换时间。把题目要求的vector<pair<int, int>>& edges换成
vector<vector<int>> edgesMatrix(n, vector<int>())第i行的vector表示第i个点能到达哪些点(的序号)。复杂度是O(V+E)。

class Solution {
public:
    vector<int> topologicalSort(int n, vector<pair<int, int>>& edges) {
        vector<int> result;
        vector<vector<int>> edgesMatrix(n, vector<int>()); 
        // 找出所有点的入度和矩阵(第i行的vector表示第i个点能到达哪些点(的序号))
        vector<int> degree(n, 0);
        for (int i = 0; i < edges.size(); i++) {
            degree[edges[i].second]++;
            edgesMatrix[edges[i].first].push_back(edges[i].second);
        }

        // 进行拓扑排序
        queue<int> q;
        // 将所有入度为0的顶点入队
        for (int j = 0; j < n; j++) {  
            if (degree[j] == 0) {
                q.push(j);
            }
        }
        while (!q.empty()) {
            int v = q.front();      // 从队列中取出一个顶点并删除
            q.pop();
            result.push_back(v);
            // 将以该定点为起始边的终点的入度--,如果减后入度为0则入队。
            for (int k = 0; k < edgesMatrix[v].size(); k++) {
                if (--degree[edgesMatrix[v][k]] == 0)
                    q.push(edgesMatrix[v][k]);
            }
        }
        return result;
    }
};

讲真,我这样慢慢尝试,考试药丸

萌萌的算法老师给的代码:

求原图的逆,再深搜

class Solution {
private:
    vector< vector<int> > V;
    vector<int> ans;
    vector<bool> vis;
    // 深度搜索,因为是有向无环图,
    // 所以从一个点dfs得到最后的点一定是汇点
    // 存储该汇点后,倒数第二个点也就成了汇点
    // 执行dfs后,vis[u] = true 相当于删除从该路找到的汇点
    void dfs(int u)
    {
        int v, i;
        vis[u] = true;
        for (i=0; i<V[u].size(); i++)
        {
            v = V[u][i];
            if (!vis[v]) dfs(v);
        }
        ans.push_back(u);
    }
public:
    vector<int> topologicalSort(int n, vector< pair<int, int> >& edges) {
        int u, v, i;
        V.resize(n);
        vis.resize(n);
        // 求原图的逆Gr(即箭头方向反转)
        // 新构成的V,第i行表示逆图中能到达的点的vector
        // 原图入度为0的点就成了新图的汇点
        for (i=0; i<edges.size(); i++)
        {
            u = edges[i].first;
            v = edges[i].second;
            V[v].push_back(u);
        }
        for (i=0; i<n; i++) vis[i] = false;
        for (i=0; i<n; i++) if (!vis[i]) dfs(i);
        return ans;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值