图的拓扑排序

参考:https://wangkuiwu.github.io/2013/04/11/topsort-cplus

拓扑排序介绍

拓扑排序(Topological Order)是指,将一个有向无环图(Directed Acyclic Graph,简称 DAG)进行排序而得到一个有序的线性序列。

比如:有这样一种情景,一个项目包括 A、B、C、D 四个部分来完成,并且 A 依赖于 B 和 D,C 依赖于 D。现在要制定一个计划,写出 A、B、C、D 的执行顺序。这时候,就需要用到拓扑排序,他就是用来确定事物的发生顺序的。

在拓扑排序中,如果存在一条从顶点 A 到顶点 B 的路径,那么在排序结果中 B 必定出现在 A 的后面。

拓扑排序算法图解

拓扑排序的算法基本步骤:

1、构造一个队列 Q (queue) 和拓扑排序的结果队列 T (Topological)。

2、先把所有没有依赖顶点的节点放入 Q(也就是顶点没有入边)。

3、当 Q 中还有顶点的时候,执行下面的步骤:

3.1、从 Q 中取出一个顶点 n(将 n 从 Q 中删掉),并放入 T(将 n 加入到结果结合中)。

3.2、对 n 每一个邻接点 m(n 是起点,m 是终点)。

3.2.1、去掉边 <n, m>。

3.2.2、如果 m 没有依赖顶点,则把 m 放入 Q 中。

注:如果顶点 A 没有依赖顶点,指的是不存在以 A 为终点的边。

如下图:

图的拓扑排序-图1

图 G3

以上图 G3 为例,来进行拓扑排序的演示:

第一步

图的拓扑排序-图2

结果 T:B -> C。

将 B 和 C 加入到排序结果中。顶点 B 和顶点 C 都是没有依赖顶点,因此将 B 和 C 加入到结果集 T 中。假设 A、B、C、D、E、F、G 按顺序存储,因此先访问B,再访问 C。

(1) 将 B 加入到排序结果中,然后去掉边 <B, A> 和 <B, D>;此时,由于 A 和 D 没有依赖顶点,因此并将 A 和 D 加入到队列 Q 中。

(2) 将 C 加入到排序结果中,然后去掉边 <C, F> 和<C, G>;此时,由于 F 有依赖顶点 D,G 有依赖顶点 A,因此不对 F 和 G 进行处理。

第二步

图的拓扑排序-图3

结果 T:B -> C -> A -> D。

将 A、D 依次加入到排序结果中。第 1 步访问之后,A、D 都是没有依赖顶点的,根据存储顺序,先访问 A,然后访问 D。访问之后,删除顶点 A 和顶点 D 的出边。

第三步

图的拓扑排序-图4

结果 T:B -> C -> A -> D -> G -> E -> F。

因为上一步操作后,使得 G、E、F 没有依赖点,所以将 G、E、F 放入排序结果 T 中即可,因为这三个点已经没有出边。

拓扑排序代码实现

拓扑排序是对有向图的排序,使用图的邻接表的表示方法实现比较清晰简单,下面以邻接表的形式进行实现。

此代码实现基于之前的:有向图的邻接矩阵与邻接表详细实现。

#include <iostream>
#include <queue>
using namespace std;

#define MAX 100

class ListDG
{
private:
    struct ENode //每一条边
    {
        int iVex;               //指向的顶点的位置
        ENode *nextEdge = NULL; //指向顶点的下一条边的指针
    };
    struct VNode //数组中存储的顶点
    {
        char data;
        ENode *firstEdge = NULL; //指向第一条该顶点的边
    };

private:
    int mVexNum;      //图的顶点数目
    int mEdgeNum;     //图的边的数目
    VNode mVexs[MAX]; //存储顶点
public:
    ListDG();
    ListDG(char vexs[], int vNum, char edges[][2], int eNum);
    ~ListDG();
    //打印邻接表
    void print();
    //图的拓扑排序
    int TopologicalSort();

private:
    //读取一个合法的输入字符
    char readChar();
    //返回字符ch在的位置
    int getPosition(char ch);
    //将node结点链接到list的最后
    void linkLast(ENode *list, ENode *node);
};

ListDG::ListDG()
{
    char c1, c2;
    int p1, p2;
    ENode *node1;
    cout << "输入顶点数:";
    cin >> mVexNum;
    cout << "输入边数:";
    cin >> mEdgeNum;
    if (mVexNum > MAX || mEdgeNum > MAX || mVexNum < 1 || mEdgeNum < 1 || (mEdgeNum > (mVexNum * (mVexNum - 1))))
    {
        cout << "输入有误!" << endl;
        return;
    }
    for (int i = 0; i < mVexNum; ++i)
    {
        cout << "vertex(" << i << "):";
        mVexs[i].data = readChar();
    }
    //初始化邻接表的边
    for (int j = 0; j < mEdgeNum; ++j)
    {
        cout << "edge(" << j << "):";
        c1 = readChar();
        c2 = readChar();
        p1 = getPosition(c1);
        p2 = getPosition(c2);
        if (p1 == -1 || p2 == -1)
        {
            cout << "输入的边有错误!" << endl;
            return;
        }

        node1 = new ENode();
        node1->iVex = p2;
        if (mVexs[p1].firstEdge == NULL)
            mVexs[p1].firstEdge = node1;
        else
            linkLast(mVexs[p1].firstEdge, node1);
    }
}

ListDG::ListDG(char *vexs, int vNum, char (*edges)[2], int eNum)
{
    if (vNum > MAX || eNum > MAX)
        return;
    char c1, c2;
    int p1, p2;
    ENode *node1;
    mVexNum = vNum;
    mEdgeNum = eNum;
    for (int i = 0; i < mVexNum; ++i)
    {
        mVexs[i].data = vexs[i];
    }
    for (int j = 0; j < mEdgeNum; ++j)
    {
        c1 = edges[j][0];
        c2 = edges[j][1];
        p1 = getPosition(c1);
        p2 = getPosition(c2);
        if (p1 == -1 || p2 == -1)
        {
            cout << "输入的边有错误!" << endl;
            return;
        }
        node1 = new ENode();
        node1->iVex = p2;
        if (mVexs[p1].firstEdge == NULL)
            mVexs[p1].firstEdge = node1;
        else
            linkLast(mVexs[p1].firstEdge, node1);
    }
}

ListDG::~ListDG() {}

void ListDG::linkLast(ENode *list, ENode *node)
{
    ENode *p = list;
    while (p->nextEdge)
        p = p->nextEdge;
    p->nextEdge = node;
}

int ListDG::getPosition(char ch)
{
    for (int i = 0; i < mVexNum; ++i)
    {
        if (mVexs[i].data == ch)
            return i;
    }
    return -1;
}

char ListDG::readChar()
{
    char ch;
    do
    {
        cin >> ch;
    } while (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')));
    return ch;
}

int ListDG::TopologicalSort()
{
    int i, j;
    std::queue<int> queue;          //辅助队列
    int *ins = new int[mVexNum];    //入度数组
    char *tops = new char[mVexNum]; //拓扑排序结果
    int top_index = 0;
    ENode *node;
    //先统计每个顶点的入度
    for (i = 0; i < mVexNum; ++i)
    {
        node = mVexs[i].firstEdge;
        while (node != NULL)
        {
            ins[node->iVex]++;
            node = node->nextEdge;
        }
    }
    //将所有入度为0的顶点入队列
    for (i = 0; i < mVexNum; ++i)
    {
        if (ins[i] == 0)
        {
            queue.push(i); //入队列
        }
    }
    while (!queue.empty())
    {
        //出队列
        j = queue.front();
        queue.pop();
        tops[top_index++] = mVexs[j].data; //将该顶点添加到tops中,tops是排序结果
        node = mVexs[j].firstEdge;         //获得以该顶点为起点的出边的队列
        //将与该node关联的节点的入度减1,若减1之后,该节点的入度为0,则将该节点添加到队列中。
        while (node != NULL)
        {
            ins[node->iVex]--;
            //若该节点的入度为0,将其入队列
            if (ins[node->iVex] == 0)
            {
                queue.push(node->iVex);
            }
            node = node->nextEdge;
        }
    }
    if (top_index != mVexNum) //拓扑排序失败
    {
        cout << "该图中有回路,拓扑排序失败" << endl;
        delete[] ins;
        delete[] tops;
        return 1;
    }
    //拓扑排序成功,打印结果
    cout << "TopSort:";
    for (i = 0; i < mVexNum; ++i)
    {
        cout << tops[i] << " ";
    }
    cout << endl;
    delete[] ins;
    delete[] tops;
    return 0;
}

void ListDG::print()
{
    ENode *node;
    for (int i = 0; i < mVexNum; ++i)
    {
        cout << i << "(" << mVexs[i].data << "):";
        node = mVexs[i].firstEdge;
        while (node != NULL)
        {
            cout << node->iVex << "(" << mVexs[node->iVex].data << ")";
            node = node->nextEdge;
        }
        cout << endl;
    }
}

int main(int argc, char **argv)
{
    char vexs[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
    char edges[][2] = {
        {'A', 'G'},
        {'B', 'A'},
        {'B', 'D'},
        {'C', 'F'},
        {'C', 'G'},
        {'D', 'E'},
        {'D', 'F'}};
    int vNum = sizeof(vexs) / sizeof(vexs[0]);
    int eNum = sizeof(edges) / sizeof(edges[0]);
    //1. 根据提供的数据生成
    ListDG ldg(vexs, vNum, edges, eNum);
    ldg.print();
    ldg.TopologicalSort();
    //2. 手动生成
    // ListDG ldg1;
    // ldg1.print();
    return 0;
}

程序运行结果如下:

$ ./test
0(A):6(G)
1(B):0(A)3(D)
2(C):5(F)6(G)
3(D):4(E)5(F)
4(E):
5(F):
6(G):
TopSort:B C A D G E F 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值