DAG和拓扑排序

  • DAG: 有向无环图是一个有向图不包含有向环。
  • 拓扑排序:有向图G=(V,E) 的拓扑排序是它的节点的一个顺序v1,v2,…vn,使得所有边 (vi,vj) 都有 i 小于 j (对于边vi–>vj, vi 在拓扑排序中的顺序出现在 vj 之前)

    拓扑排序

  • 应用:

    • 课程的先修图:课程 vi 必须在修课程 vj 之前修完
    • 编译:模块 vi 必须在模块 vj 之前编译。
  • 引理:如果有向图G有拓扑排序,则G是DAG(有向无环图)。证明:

    • 假设有向图G有拓扑顺序 v1,v2,…vn并且G也有环C。
    • vi为C中指针最小的节点,vj为vi的前一位节点,因此(vj,vi)为一条边。
    • 对于选择的i为最小,所有i小于j
    • 由于(vj,vi)为一条边,并且v1,v2,…vn为拓扑顺序,因此j小于i, 矛盾。
  • 引理: 如果G是一个有向无环图,则G中至少有一个节点没有入边。证明:

    • 假设G是有向无环图并且每条边都至少有一条入边
    • 选择任意节点v,开始沿着v往后走。由于v至少有一条入边(u,v),我们可以往后到达u.
    • 由于u至少有一条入边(x,u) ,我们可以往后到达x.
    • 重复直到我们访问一个节点w两次。
    • C为访问节点w两次经过的节点序列,则C为一个环。
      环
  • 引理:如果G是有向无环图,则G有拓扑排序。证明:

    • 当n=1时为真
    • 给定一个DAG有n>1个节点,找到一个节点v没有入边。
    • 由于删除v无法生成环,因此G-{v} 是一个有向无环图。
    • 通过归纳假设,G-{v}有拓扑顺序。
    • 在拓扑顺序中放置v为第一个数,然后不断添加G-{v}中的节点。
    • 由于v没有入边,因此在拓扑排序中上面的描述是合理的。
  • 算法 计算图G的拓扑顺序:

    • 找到一个没有入边的节点,将它放在序列的第一个位置。
    • 从G中删除v
    • 递归的计算G-{v}的拓扑顺序,并且添加这个顺序到v的后面。
  • 运行时间:在O(m+n)时间内可以找到一个图的拓扑顺序。证明:

    • 维护以下信息:
      • count(w)=w剩下的入边数目
      • S= 剩下的入边为0的节点集合
    • 初始化: 扫描一遍图计算所有顶点的入边数和找到S集合需要花费O(m+n)时间
    • 更新:
      • 删除边v
      • 对从v到w有边的节点w的入边数目count(w)减少1,如果w的入边数目变为0,则将w加入集合S中
      • 每条边所花时间为O(1).

例题1 确定比赛名次
http://acm.hdu.edu.cn/showproblem.php?pid=1285
Problem Description
有N个比赛队(1<=N<=500),编号依次为1,2,3,。。。。,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。现在请你编程序确定排名。

Input
输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示队伍的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即P1队赢了P2队。

Output
给出一个符合要求的排名。输出时队伍号之间有空格,最后一名后面没有空格。

其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。

Sample Input
4 3
1 2
2 3
4 3

Sample Output
1 2 4 3

#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
const int maxn=505;
int d[maxn];
int n,m;
vector<int> v[maxn];
void solve(){
    priority_queue<int,vector<int>,greater<int> > p;//节点越小越先出队列
    for(int i=1;i<=n;i++) if(d[i]==0){
        p.push(i);//找到入边为0的节点
    }
    int cnt=0;
    while(!p.empty()){
        int u=p.top();p.pop();
        if(cnt) printf(" ");
        cnt++;
        printf("%d",u);
        for(int i=0;i<v[u].size();i++){
            int x=v[u][i];
            d[x]--;
            if(!d[x]) p.push(x);//不断更新维护入边为0的节点
        }
    }
    printf("\n");
}
int main(){
    while(scanf("%d%d",&n,&m)==2){
        memset(d,0,sizeof(d));
        for(int i=1;i<=n;i++) v[i].clear();
        while(m--){
            int x,y;
            scanf("%d%d",&x,&y);
            v[x].push_back(y);
            d[y]++;//计算入边
        }
        solve();
    }
    return 0;
}

例题2 All Discs Considered
http://algorithm.openjudge.cn/algorithmb/F/

Operating systems are large software artefacts composed of many packages, usually distributed on several media, e.g., discs. You probably remember the time when your favourite operating system was delivered on 21 floppy discs, or, a few years later, on 6 CDs. Nowadays, it will be shipped on several DVDs, each containing tens of thousands of packages.

The installation of certain packages may require that other packages have been installed previously. Therefore, if the packages are distributed on the media in an unsuitable way, the installation of the complete system requires you to perform many media changes, provided that there is only one reading device available, e.g., one DVD-ROM drive. Since you have to start the installation somehow, there will of course be one or more packages that can be installed independently of all other packages.

Given a distribution of packages on media and a list of dependences between packages, you have to calculate the minimal number of media changes required to install all packages. For your convenience, you may assume that the operating system comes on exactly 2 DVDs.
输入
The input contains several test cases. Every test case starts with three integers N1, N2, D. You may assume that 1<=N1,N2<=50000 and 0<=D<=100000. The first DVD contains N1 packages, identified by the numbers 1, 2, …, N1. The second DVD contains N2 packages, identified by the numbers N1+1, N1+2, …, N1+N2. Then follow D dependence specifications, each consisting of two integers xi, yi. You may assume that 1<=xi,yi<=N1+N2 for 1<=i<=D. The dependence specification means that the installation of package xi requires the previous installation of package yi. You may assume that there are no circular dependences. The last test case is followed by three zeros.
输出
For each test case output on a line the minimal number of DVD changes required to install all packages. By convention, the DVD drive is empty before the installation and the initial insertion of a disc counts as one change. Likewise, the final removal of a disc counts as one change, leaving the DVD drive empty after the installation.
样例输入
3 2 1
1 2
2 2 2
1 3
4 2
2 1 1
1 3
0 0 0
样例输出
3
4
3

需要安装两个DVD中的程序包,但是安装一些程序包时必须先安装其他程序包才能安装此程序包,问需要换几次DVD才能把所有程序包安装完。

#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100010;
int n1,n2,d,deg[maxn],deg1[maxn];
vector<int> v[maxn];
queue<int> q[2];
int topo(int u){
    int i;
    int sum=1;
    for(i=1;i<=n1;i++) if(!deg[i]) q[0].push(i);
    for(i=n1+1;i<=n1+n2;i++) if(!deg[i]) q[1].push(i);//将入边为0的点加入队列中
    while(!q[1].empty()||!q[0].empty()){//若第一插入的磁盘u为空,则也会加1
        while(!q[u].empty()){
            int x=q[u].front();q[u].pop();
            for(i=0;i<v[x].size();i++){
                int w=v[x][i];
                deg[w]--;//点的入边减1
                if(deg[w]==0) {//如果入边为0
                    if(w<=n1) q[0].push(w);
                    else q[1].push(w);
                }
            }           
        }
        sum++;//所有磁盘为空时拔出也会加1
        u=1-u;//换磁盘
    }
    return sum;
}
int main(){
    int i;
    while(scanf("%d%d%d",&n1,&n2,&d)==3){
        if(!n1&&!n2&&!d) break;
        memset(deg1,0,sizeof(deg1));
        for(i=1;i<=n1+n2;i++) v[i].clear();
        while(d--){
            int x,y;
            scanf("%d%d",&x,&y);
            v[y].push_back(x);
            deg1[x]++;//计算所有点的入边数目
        }
        for(i=1;i<=n1+n2;i++) deg[i]=deg1[i];
        int a=topo(0);//先插入第一个磁盘
        for(i=1;i<=n1+n2;i++) deg[i]=deg1[i];
        int b=topo(1);//先插入第二个磁盘
        printf("%d\n",min(a,b));
    }
    return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值