返回顶部

拓扑排序

基于贪心和基于dfs实现的拓扑排序

拓扑排序的定义

对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列(并非全序/线序),使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。通俗的说就是,一张有向无环图的拓扑排序可以使得任意的起点u,它的一个终点v,使得在序列中的顺序是u在前v在后

NOTE:并不是所有图都存在拓扑排序,拓扑排序在图中也不是唯一的.
不含环路的有向图必包含入度为零的顶点—>因此一定存在拓扑排序

废话少说,直接实战:P1137:旅行计划



思路:由于x城市要么在y东边,要么在其西边,所以这个图一定是一个有向无环图(Directed Acyclic Graph简称DAG)。
记得DP需要满足什么原则吗?无后效性。如果不是在拓扑序中进行DP,会完全破坏无后效性。正是因为拓扑序u在前,v在后的性质,这才选择使用拓扑排序。

这里我们选择用链式向前星来存储图:链式向前星

Kahn算法(贪心)实现(拓扑排序+DP):

#include <iostream>
#include <cstdio>
#include <queue>

using namespace std;

/*
    贪心实现拓扑排序的样例:P1137旅行计划
    为了保证dp的无后效性,先拓扑排序再dp
 先转化:只往东走,以第i个城市为终点最多能游览多少个城市 <==> 只往西走,以第i个城市为起点最多能浏览多少个城市
    转移方程(已拓扑排序后): dp[u] = max(dp[u], dp[v]+1),如果存在一条从u到v的边
 */

queue<int>q1;

typedef struct qianlao
{
    int u[200001], v[200001], w[200001]; //从u到v
    int first[100001], next[200001];
    int indu[100001]; //统计入度来判断起始点
    int n, m;
}list;  //创建符合题目的邻接表

list G;
int Topans[100001], t = 1;

void Topsort(void);
int main(void)
{
    scanf("%d %d",&G.n, &G.m); //n个点,m个关系
    for(int i = 1; i <= G.n; i++)
    {
        G.first[i] = -1;
        G.indu[i] = 0;           //邻接表初始化
    }
    for(int i = 1; i <= G.m; i++)
    {
        G.next[i] = -1;          //邻接表初始化
    }
    for(int i = 1; i <= G.m; i++)
    {
        scanf("%d %d",&G.v[i], &G.u[i]); //注意这里逆向的读入DAG
        G.w[i] = 1;     //这里权值无用
        G.next[i] = G.first[G.u[i]];
        G.first[G.u[i]] = i;
        G.indu[G.v[i]]++;         //输入邻接表以及入度
    }
    Topsort(); //拓扑排序
    int dp[G.n+1]; //dp[i]表示以第i个城市为起点最多能浏览多少个城市
    for(int i = 1; i <= G.n; i++)
    {
        dp[i] = 1;
    }
    int k;
    for(int i = G.n; i >= 1; i--)   //进行简单dp
    {
        k = G.first[Topans[i]];
        while(k!=-1)
        {
            dp[G.u[k]] = max(dp[G.u[k]], dp[G.v[k]]+1);   //dp转移
            k = G.next[k];
        }
    }
    for(int i = 1; i <= G.n; i++)
    {
        printf("%d\n",dp[i]);
    }
    return 0;
}

void Topsort(void)
{
    int k;
    for(int i = 1; i <= G.n; i++)   //注意这里是拓扑排序的逆序顺序
        if(G.indu[i]==0)          //初始点(入度为0的点)入队
            q1.push(i);
    while(!q1.empty())
    {
        Topans[t] = q1.front();    //贪心的取出队列中的点到Topans中
        t++;
        q1.pop();        //出列
        k = G.first[Topans[t-1]];
        while(k!=-1)               //遍历所有由刚刚出列点指向的点
        {
            G.indu[G.v[k]]--;
            if(G.indu[G.v[k]]==0)   //如果删边后入度为0,则入队
                q1.push(G.v[k]);
            k = G.next[k];
        }
    }
}


dfs实现(拓扑排序+DP):

posted on 2019-10-06 22:59  进击の辣条  阅读(321)  评论(0编辑  收藏  举报

导航