参考: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 为终点的边。
如下图:
图 G3
以上图 G3 为例,来进行拓扑排序的演示:
第一步:
结果 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 进行处理。
第二步:
结果 T:B -> C -> A -> D。
将 A、D 依次加入到排序结果中。第 1 步访问之后,A、D 都是没有依赖顶点的,根据存储顺序,先访问 A,然后访问 D。访问之后,删除顶点 A 和顶点 D 的出边。
第三步:
结果 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