C++实现图 - 05 拓扑排序

简介: 今天来讲另一个非常重要的知识点 —— 拓扑排序。咋一看好像是一个排序算法,然而它和排序扯不上半点关系,它可以用于判断我们的图中是否存在有向环。
写在前面:
今天来讲另一个非常重要的知识点 —— 拓扑排序。咋一看好像是一个排序算法,然而它和排序扯不上半点关系,它可以用于判断我们的图中是否存在有向环。

什么是有向无环图?

听名字应该很好理解,就是图中存在有向环,我们先看一个有向无环图。
1.jpg

我们只需要改动上图一条边,它就成为了一个有环图。

2.jpg

拓扑排序

算法思想

拓扑排序就是对一个有向无环图构造拓扑序列的过程,听起来可能不知道从何下手,其实算法步骤很简单:

  1. 在有向图中选一个没有前驱的顶点并输出。
  2. 从图中删除该顶点和所有以它为尾的弧。

重复上述两步,直至全部顶点均已输出,或者当前图不存在无前驱的顶点为止,后一种情况说明有向图中存在环。

下面我们将用有向无环图和有向有环图进行举例。

有向无环图

10.jpg

第一步:找到图中没有前驱的结点,目前图中只有一个结点 1 没有前驱。

11.jpg

第二步:输出并删除结点 1 以及所有以它为尾的弧。

12.jpg

第三步:找到没有前驱的结点 2 和结点 3 ,假设先输出结点 2 。

13.jpg

第四步:输出并删除结点 2 以及所有以它为尾的弧。

14.jpg

第五步:找到没有前驱的结点 3 和结点 4 ,假设先输出结点 4 。

15.jpg

第六步:输出并删除结点 4 以及以它为尾的弧。

16.jpg

第七步:找到没有前驱的结点 3 并输出。

17.jpg

第八步:输出并删除结点 3 以及以它为尾的弧。

18.jpg

至此,只剩结点 5 ,故该图是一个有向无环图。

有向有环图

其实操作和上面类似,我们直接上图:

19.jpg

初始图如上,结点 2、3、4 构成了一个环,按照上面的操作进行,我们依次删除没有前驱的结点,最终会得到下面这张图:

20.jpg

可以发现,算法结束后仍然存在三个结点,故该图是一个有环图。

算法实现

为了实现这个算法,这里我们需要用到邻接表,并且我们还额外需要一个数组存储每个结点入度(指向该结点的结点数量),如果为 0 则说明该结点没有前驱结点即没有结点指向它。

所以现在就很清楚了,我们首先要做的就是去寻找数组中入度为 0 的点,然后将它们全部放入一个栈中,再依次弹出来进行后续操作,直接上图(这里就拿有向无环图来举例啦):

3.jpg

第一步:遍历所有结点,将入度为 0 的点入栈即将结点 1 入栈。

4.jpg

第二步:弹出栈顶元素即结点 1 并输出,然后遍历结点 1 的邻接结点,将它们的入度减 1 。先将结点 3 的入度减 1 ,发现为 0 则入栈;再将结点 4 的入度减 1 ,发现不为 0 则不如栈;最后将结点 2 的入度减 1 ,发现为 0 则入栈。

5.jpg

第三步:弹出栈顶元素即结点 2 并输出,然后遍历结点 2 的邻接结点,将它们的入度减 1 。将结点 4 的入度减 1 ,发现为 0 则入栈。

6.jpg

第四步:弹出栈顶元素即结点 4 并输出,然后遍历结点 4 的邻接结点,将它们的入度减 1 。将结点 5 的入度减 1 ,发现不为 0 则不入栈。

7.jpg

第五步:弹出栈顶元素即结点 3 并输出,然后遍历结点 3 的邻接结点,将它们的入度减 1 。将结点 5 的入度减 1 ,发现为 0 则入栈。

8.jpg

第六步:弹出栈顶元素即结点 5 并输出,结点 5 没有后继结点。

9.jpg

第七步:栈中元素为空,算法结束。此时数组中所有结点入度都为 0 ,故该图是有向无环图。

全部代码

这里的栈操作我们通过手写实现,如果对该操作不太熟悉的小伙伴可以回溯到我们之前的栈那一讲看看~

#include <bits/stdc++.h>
using namespace std;
#define MAXVERTIES 20
#define OK 1
#define ERROR 0

int indegree[MAXVERTIES] = { 0 };    //用于存储入度信息

/*
5
1 2 3 4 5
6
1 2
1 4
1 3
2 4
3 5
4 5
*/

//定义结点
struct VexNode {
    int data;
    VexNode *next;
};

//定义弧
struct ArcNode {
    int data;
    VexNode *firstacr = NULL;
};

//定义邻接表
struct GraphList {
    ArcNode arclist[MAXVERTIES];
    int vexnum, arcnum;
};

//定义栈
struct Stack {
    int Sacklist[MAXVERTIES] = { 0 };
    int top = -1;
};

//入栈操作
void Push(Stack &S, int key) {
    if (S.top == MAXVERTIES) {
        cout << "栈已满!" << endl;
        return;
    }
    S.top++;
    S.Sacklist[S.top] = key;
}

//出栈操作
int Pop(Stack &S) {
    if (S.top == -1) {
        cout << "栈为空!" << endl;
        return -1;
    }
    int temp = S.Sacklist[S.top];
    S.top--;
    return temp;
}

//返回结点在结点数组中的下标
int Location(GraphList &G, int key) {
    for (int i = 0; i < G.vexnum; i++) {
        if (G.arclist[i].data == key) {
            return i;
        }
    }
    return -1;
}

//创建图
void CreatGraph(GraphList &G) {
    cout << "请输入顶点数:" << endl;
    cin >> G.vexnum;
    cout << "请输入顶点信息:" << endl;
    for (int i = 0; i < G.vexnum; i++) {
        cin >> G.arclist[i].data;
    }
    cout << "请输入弧数:" << endl;
    cin >> G.arcnum;
    cout << "请输入弧端点信息:" << endl;
    for (int i = 0; i < G.arcnum; i++) {
        int v1, v2;
        cin >> v1 >> v2;
        int Location1 = Location(G, v1);
        int Location2 = Location(G, v2);
        VexNode *new_node = new VexNode;
        new_node->data = Location2;
        new_node->next = G.arclist[Location1].firstacr;
        G.arclist[Location1].firstacr = new_node;
        indegree[Location2]++;
    }
}

//拓扑排序
int TopoSort(GraphList &G, int *topolist) {
    Stack S;
    int topo = 0;
    //先将所有入度为0的结点入栈
    for (int i = 0; i < G.vexnum; i++) {
        if (indegree[i] == 0) {
            Push(S, i);
        }
    }
    //依次出栈入度为0的结点
    while (S.top != -1) {
        int vx = Pop(S);
        topolist[topo++] = G.arclist[vx].data;    //输出结点
        VexNode *temp = G.arclist[vx].firstacr;
        //删除以该结点为尾的弧
        while (temp != NULL) {
            int index = temp->data;
            indegree[index]--;    //将该弧的弧头结点入度减1
            //如果入度为0,则入栈
            if (indegree[index] == 0) {
                Push(S, index);
            }
            temp = temp->next;
        }
    }
    topolist[topo] = -1;
    //如果拓扑序列中的元素个数等于所有元素个数,则该图无环,否则该图有环
    if (topo == G.vexnum) {
        return OK;
    } else {
        return ERROR;
    }
}

int main() {
    GraphList GL;
    CreatGraph(GL);
    int topolist[MAXVERTIES] = { 0 };
    int vx = TopoSort(GL, topolist);
    if (!vx) {
        cout << "有环!" << endl;
    } else {
        cout << "有向无环!" << endl;
        cout << "拓扑序列如下:" << endl;
        for (int i = 0; topolist[i] != -1; i++) {
            cout << topolist[i] << " ";
        }
    }
    return 0;
}

image.png

如果大家有什么问题的话,欢迎在下方评论区进行讨论哦~

目录
相关文章
|
4月前
|
算法 测试技术 C#
C++排序、前缀和算法的应用:英雄的力量
C++排序、前缀和算法的应用:英雄的力量
|
6月前
|
算法 C++
剑指offer(C++)-JZ41:数据流中的中位数(算法-排序)
剑指offer(C++)-JZ41:数据流中的中位数(算法-排序)
剑指offer(C++)-JZ41:数据流中的中位数(算法-排序)
|
1月前
|
算法 搜索推荐 大数据
在C++语言中排序、查找和算法的作用
在C++语言中排序、查找和算法的作用
10 0
|
3月前
|
Java Go C++
Golang每日一练(leetDay0115) 重新安排行程、递增的三元子序列
Golang每日一练(leetDay0115) 重新安排行程、递增的三元子序列
33 0
Golang每日一练(leetDay0115) 重新安排行程、递增的三元子序列
|
3月前
|
Java Go 人工智能
Java每日一练(20230502) BST公共祖先、随机分组、K个一组翻转链表
Java每日一练(20230502) BST公共祖先、随机分组、K个一组翻转链表
25 0
Java每日一练(20230502) BST公共祖先、随机分组、K个一组翻转链表
|
3月前
|
Java Go C++
C/C++每日一练(20230426) 不喜欢带钱的小C、数组排序、超级素数
C/C++每日一练(20230426) 不喜欢带钱的小C、数组排序、超级素数
36 0
C/C++每日一练(20230426) 不喜欢带钱的小C、数组排序、超级素数
|
3月前
|
算法 Java C++
Java每日一练(20230424) 二叉树中序遍历、交换链表节点、不同子序列
Java每日一练(20230424) 二叉树中序遍历、交换链表节点、不同子序列
36 0
Java每日一练(20230424) 二叉树中序遍历、交换链表节点、不同子序列
|
3月前
|
Go C++ Java
C/C++每日一练(20230412) 二维数组找最值、排序
C/C++每日一练(20230412) 二维数组找最值、排序
27 0
C/C++每日一练(20230412) 二维数组找最值、排序
|
3月前
|
Go C++ 算法
C/C++每日一练(20230404) 旋转排序数组最小值、石头剪刀布、三数之和
C/C++每日一练(20230404) 旋转排序数组最小值、石头剪刀布、三数之和
20 0
C/C++每日一练(20230404) 旋转排序数组最小值、石头剪刀布、三数之和
|
3月前
|
C++
C++如何进行sort的使用——C++如何进行排序
C++如何进行sort的使用——C++如何进行排序
27 0