拓扑排序

拓扑排序


拓扑排序的定义

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列—— [ 百度百科 ]

拓扑排序表示了顶点按照边的方向出现的先后顺序。如果有环,则该图没有拓扑排序。如下图,其拓扑排序就是为A—–>B—–>C.
这里写图片描述

拓扑排序的实现

拓扑排序有两种实现思路,一种是依靠简单粗暴的DFS(深度优先搜索),另一种是Kahn算法(用到了BFS)。

DFS

算法导论中就提到了利用DFS来进行拓扑排序的思路,如下:

TOPOLOGICAL-SORT(G)
1 call DFS(G) to compute finishing times v.f for each vertex v
2 as each vertex is finished, insert it onto the front of a linked list
3 return the linked list of vertexs

简单来说就是当深入优先搜索一个节点,若从该节点出去的所有邻居节点都搜索完毕后,该节点也即将搜索完毕,即将该节点加入到拓扑链表的最前面。实现代码如下:

const int MAXN = 100000;
const int MAXM = 500000;
struct node
{
    vector<int> child;
};
node point_attr[MAXN + 1];
void addEdge(int u,int v)
{
    point_attr[u].child.push_back(v);
}
std::stack<int> st;

void DFS(int nodeNum)
{
    bnode[nodeNum]=true;//标记为已经访问过
    for(auto num: point_attr[nodeNum].child)
    {
        if(!bnode[num])
            DFS(num);
    }
    st.push(nodeNum);//加入到栈中
}

//是否被访问过
bool bnode[MAXN + 1];
int main()
{
        int n,m;
        cin>>n>>m;

        int u=0,v=0;
        for(int i=0;i<m;i++)
        {
             cin >> u >> v;
             addEdge(u,v);
        }
        memset(bnode , 0 , sizeof(bnode));

        for(int i=1;i<=n;i++)
        {
            if(!bnode[i])
                DFS(i);
        }
        while(!st.empty()){
            cout<<st.top()<<" ";
            st.pop();
        }
    return 0;
}

其中用栈来保存DFS过程中已经遍历完成的节点,利用栈的后进先出的特点,最后的输出正好就是需要的拓扑排序。然而我实现上述实现的方法还是存在缺陷的,因为首先要对一个图进行无环的判断,然后才能进行拓扑排序..上述方法最好再加上无环的判定环节,这样就是极好的了.

Kahn算法

算法思想:每次找到找到图中入度数为0的节点,此时将节点插入到拓扑链表的尾部,并将该节点指向的所有节点的入度数减1;不断循环,直到所有的节点都被插入链表或者没有入度数为0的节点。
这个思路相对DFS来说可能更符合人们的思路,每次加入到队列尾部的节点都是已经没有节点指入的。实现代码如下:

int deg[MAXN + 1] = {0}; //记录每个点的入度数

struct node
{
    vector<int> child;
};
node point_attr[MAXN + 1];

void addEdge(int u,int v)
{
    point_attr[u].child.push_back(v);
    ++deg[v];
}
queue<int> rgb;
int main()
{
        int n,m;
        cin>>n>>m;

        int u=0,v=0;
        for(int i=0;i<m;i++)
        {
             cin >> u >> v;
             addEdge(u,v);
        }

        //统计入度为0的结点,并加入队列;
        for (int j = 1; j <= n; j++) {
            if (!deg[j])  rgb.push(j);
        }

        int count = 0;
        while(!rgb.empty())
        {
            int node_num = rgb.front();
            rgb.pop();
            count++;

            for(auto num: point_attr[node_num].child)
            {
                --deg[num];
                if(deg[num] ==0)
                    rgb.push(num);
            }
        }
    return 0;
}

最终rgb中存放节点的顺序就是拓扑排序。代码中不断将入度数为0的节点放到队列中其实就是一种广度优先搜索。

两种方法的比较

两种方法思路不同,但本质相同。我之前看到一篇博客里面讲到,DFS是从出度的角度来考虑,Kahn算法是从入度的角度出发。

当一个节点的入度数为0时,显然已经没有节点指向该节点,理应是剩余节点中拓扑排序中的第一位;当一个节点的出度数为0,它指向的节点方向的所有节点都已经被处理(加入到拓扑队列中),那么该节点就该被加入到队列的最前面。

两种方法的时间复杂度都是O(V+E).

拓扑排序的应用

之前在hihocoder上看到一个题目,是典型的拓扑排序的应用,看一下这个题目,可以让我们更加熟悉拓扑排序。题目链接:hihocoder-拓扑排序
题目大意就是当图中的某个节点感染了病毒之后,会沿着边的方向不断传递,若一个图(如下)一开始节点1上有病毒.
这里写图片描述
那么最后完全感染完毕之后的病毒分布图如下:
这里写图片描述
也就是说一个节点有多个入度的话,该节点要被感染多次。此题目直观上可以用DFS的方法来实现,即对每一个初始感染的节点进行DFS,但是这样会进行大量的重复计算,就如同递推求解斐波那契数一样,会存在着大量的重复项计算。实际上在求拓扑排序的过程中就能完成病毒数目的计算,具体思路参见hihocoder上的讲解,我的实现代码如下:(欢迎大家讨论)

const int MAXN = 100000;
const int MAXM = 500000;
const int MOD =142857;
vector<int> deg(MAXN+1,0);//rudu shu

struct node
{
    vector<int> child;
    int value;
};

node point_attr[MAXN + 1];

void addEdge(int u,int v)
{
    point_attr[u].child.push_back(v);
    ++deg[v];
}

queue<int> rgb;


int main()
{
    int n,m,k;
    cin>>n>>m>>k;
    int t;
    while(k--)
    {
        cin>>t;
        point_attr[t].value =1;
    }

    int u=0,v=0;
    for(int i=0;i<m;i++)
    {
        cin >> u >> v;
        addEdge(u,v);
    }

    for (int j = 1; j <= n; j++) 
    {
       if (!deg[j])  rgb.push(j);
    }

    int sum=0;

    while(!rgb.empty())
    {
        int node_num = rgb.front();
        rgb.pop();
        sum = sum+point_attr[node_num].value;
        if(sum >MOD) sum-=MOD;

        for(auto num: point_attr[node_num].child)
        {
            point_attr[num].value = point_attr[num].value +point_attr[node_num].value;
            if(point_attr[num].value > MOD) 
                point_attr[num].value -=MOD;
            --deg[num];
            if(deg[num] ==0)
                rgb.push(num);
        }
    }

    cout<<sum<<endl;

    //int i;
//  cin>>i;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值