有向图及拓扑排序

有向图
在无向图中,边没有方向,两条边之间的顶点是单向可达的,而有向图的边是单向的。虽然边的性质不同,但我们仍然可以用邻接表来表示有向图。对有向图的结构定义如下:

#include <map>
#include <forward_list>
 
using namespace std;
 
struct DirectedGraph
{
    size_t V, E; //V表示顶点数,E表示边数
    map<int, forward_list<int>> adj; //邻接表
};


有向图在计算机中有广泛的应用,如任务调度条件、网络等。有向图的顶点之间的联系是描述现实世界的有利工具,如计算机的任务调度系统中,根据多任务之间的先后关联需要给出任务的执行顺序,拓扑排序就是可以得到这一顺序的算法。

拓扑排序
给定一幅有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素(或者说明无法做到这一点)。

还是以任务调度系统为例,假设一个任务x必须在任务y之前完成,任务y必须在任务z之前完成,任务z又必须在任务x之前完成,这样的问题肯定是无解的。也就是说,拓扑排序能得出结果的前提是图必须是有向无环图(Directed Acyclic Graph,DAG),即一幅不含有环路的有向图。

一种拓扑排序的思想是基于深度优先搜索的顶点排序。它的基本思想是深度优先搜索会沿着开始顶点一直向下搜索,且正好只会访问每个顶点一次。基于深度优先搜索的拓扑排序基于一个重要命题:一幅有向图的拓扑顺序即为所有顶点的逆后序排列。所谓逆后序遍历即在路径达到最大深度后再保存(打印)顶点,得到后序遍历,将其逆向输出即可。

下面是基于深度优先搜索来得到拓扑排序后的顶点顺序(默认无环,因此未给出判断是否存在有向环的代码):

void dfs(DirectedGraph &g, vector<int> &visited, stack<int> &st, int v)
{
    visited[v] = 1;
    for (int &i : g.adj[v])
    {
        if (!visited[v])
        {
            dfs(g, visited, st, i);
        }
    }
    st.push(v);
}
 
void TopologicalSort(DirectedGraph &g, int v)
{
    stack<int> st;
    vector<int> visited(g.V);
    dfs(g, visited, st, v);
    while (!st.empty())
    {
        cout << st.top() << " ";
        st.pop();
    }
}


另一种思路是得到所有顶点的入度,循环执行以下两个步骤直到不存在入度为0的顶点:

(1)选择一个入度为0的顶点,输出

(2)将该顶点其出边全部删除,同时更新出边所到达顶点的入度

这种算法不需要太多代码判断是否存在有向环,只要最后输出的顶点数小于有向图的顶点数就说明了存在有向环。

bool TopologicalSort(DirectedGraph &g, vector<int> &in_degree)
{
    queue<int> qu;
    int cnt = 0;
    for (auto ite = in_degree.begin(); ite != end(); ++ite)
    {
        if (0 == *ite)
        {
            qu.push(*ite);
        }
    }
    while (!qu.empty())
    {
        int v = qu.front();
        qu.pop();
        cout << v << " ";
        ++cnt;
        for (const int &i : g.adj.at(v))
        {
            if (0 == --in_degree[i])
            {
                qu.push(i);
            }
        }
    }
    if (cnt < V)
    {
        return false;
    }
    return true;
}


 

posted @ 2022-10-12 16:56  車輪の唄  阅读(37)  评论(0编辑  收藏  举报  来源