拓扑排序

拓扑排序的两个算法:

1. DFS

执行一次DFS遍历,并记住顶点变成死端(即退出遍历栈)的顺序。将该顺序反过来就得到了拓扑排序的一个解。当然,在遍历的时候不能遇到回边。如果遇到一条回边,该图就不是无环有向图,并且对它顶点的拓扑排序是不可能的。

 

难点:

Q:如何实现“将该顺序反过来”?

A:利用双端队列

#include <deque>
deque <int> 

//in dfs():
tp.push_front(u);

//output:
while(!tp.empty()){
    printf("%d ",tp.front());
    tp.pop_front();
}

代码实现(以UVa10305为例)

伪代码:

Adapted from: https://class.coursera.org/algo-009/lecture/52

topo_sort()

- mark all nodes as unvisited

- for each vertex i∈G:

  - if i not yet visited:

    - dfs(i)

dfs(int u)

- mark u as currently being visited(-1)

- for each edge (u,v):

  -if v is currently being visited:

    - cyclic!

  - if v not yet visited:

    - dfs(v)

- put u at the front of the queue

- mark u as already visited

/*
    Source: UVa10305
    Status: AC
    Done: 25/11/2015
    Remarks: topological sort (dfs)
*/
#include <cstdio>
#include <deque>
#include <cstring>
using namespace std;
int G[105][105],visited[105],n,m;
deque <int> tp;
void dfs(int u){
    int v;
    visited[u] = -1; //u is currently being visited
    for(v = 1; v <= n; v ++){
        if(G[u][v] == 1){
            if(visited[v] == -1)return; //cyclic 
            if(visited[v] == 0) dfs(v);
        }    
    }
    tp.push_front(u);
    visited[u]=1; // u is already visited
    return;
}
void topo_sort(){
    int i;
    memset(visited,0,sizeof(visited)); //mark all nodes as unvisited
    for(i = 1; i <= n; i ++){ //for each vertex in G
        if(visited[i] == 0)dfs(i); //if(u is not yet visited)
    }
}
int main(){
    int u,v,i,j;
    while(1){
        scanf("%d %d",&n,&m);
        if(n == 0 && m == 0)break;
        for(i = 1; i <= m; i ++){
            scanf("%d %d",&u,&v);
            G[u][v] = 1;
        }
        topo_sort();
        while(!tp.empty()){
            printf("%d ",tp.front());
            tp.pop_front();
        }
        printf("\n");
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
                G[i][j]=0;
    }
    return 0;
}

 

2. Kahn

不断地做这样的一件事,在余下的有向图中求出一个源,它是一个没有输入边的顶点,然后把它和所有从它出发的边都删除。如果有多个这样的源,可以任意选择一个。如果这样的源不存在,算法停止,因为该问题是无解的。 

盲点:

1. current=1 (The importance of initialisation!)

2. 不断将新数加入队列从而实现循环

3. G[u][v]的判断(见注释)

/*
    Source: UVa10305
    Status: AC
    Done: 25/11/2015
    Remarks: topological sort (Kahn)
*/
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
int G[105][105],deg[105],ans[105],n,m,current=1;
queue <int> tp;
void topo_sort(){
    int i,j,v,u;
    for(i = 1; i <= n; i ++){
        if(deg[i] == 0){
            tp.push(i);    
        }
    }
    while(!tp.empty()){
        u=tp.front(); //u是当前的“根” 
        tp.pop();
        ans[current++]=u;
        deg[u] = -1;
        for(v = 1; v <= n; v ++){
            if(G[u][v] == 1){
                deg[v] --;
            }
        }
        for(v = 1; v <= n; v ++){
            if(deg[v] == 0 && G[u][v]){ 
                            //G[u][v]判断v是否为u的子结点,避免把其他“根”重复加入队列 
                            //样例数据: now u=1 tp:4 若无此判断 4会再次被加入队列 
                G[u][v] = 0;
                tp.push(v);    
            }
        }
    }
    return;
}
int main(){
    int u,v,i,j;
    while(1){
        scanf("%d %d",&n,&m);
        if(n == 0 && m == 0)break;
        for(i = 1; i <= m; i ++){
            scanf("%d %d",&u,&v);
            G[u][v] = 1;
            deg[v]++;
        }
        topo_sort();
        for(i=1;i<=n;i++)
         printf("%d ",ans[i]);
        printf("\n");
        memset(G,0,sizeof(G));
        memset(deg,0,sizeof(deg));
        memset(ans,0,sizeof(ans));
        current = 1;
    }
    return 0;
}

 

posted @ 2015-11-25 15:16  Peccavi  阅读(185)  评论(0编辑  收藏  举报