1. 相关概念
1.1 AOV网
用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称AOV网(Activity On Vertex network)。
1.2 AOV网的特点
若从i到j有一条有向路径,则i是j的前驱,j是i的后继。
若<i,j>是网中有向边,则i是j的直接前驱;j是i的直接后继。
AOV网中不允许有回路,因为如果有回路存在,则表明某项活动以自己为先决条件,显然这是荒谬的。
2. 拓扑排序
2.1 概念
在AOV网没有回路的前提下,我们将全部活动排列成一个线性序列,使得若AOV网中有弧<i,j>存在,则在这个序列中,i一定排在j的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序算法称为拓扑排序。
2.2 拓扑排序方法
在有向图中选一个没有前驱的顶点且输出之;
从图中删除该顶点和所有以它为尾的弧;
重复上述两步,直至全部顶点均已输出,或者当图中不存在无前驱的顶点为止。
算法复杂度:O(n+e)
注:由于每次选择的无前驱顶点可能不同,所以一个AOV网的拓扑序列不是唯一的。
2.3 拓扑排序的应用
检测图中是否存在环:
对有向图构造其顶点的拓扑有序序列,若图中所有顶点都在它的拓扑有序序列中,则该图中必定不存在环。
3. 代码
#include <iostream>
using namespace std;
/**************************************************************************************
******************************** 图的邻接表结构定义及操作 ********************************
***************************************************************************************/
typedef char VertexType; // 顶点类型,自定义成char
typedef int WeightType; // 边上的权值类型,自定义成int
#define MAXVEX (100) // 最大顶点数
/* 边表结点 */
typedef struct EdgeNode {
int adjvex; // 邻接点域,存储该顶点对应的下标
// WeightType weight; // 权值域,存储顶点权值,非网图(无权图)不必定义
EdgeNode* next; // 链域,指向下一个邻接点
} EdgeNode;
/* 顶点结点 */
typedef struct VertexNode {
int in; // 顶点入度
VertexType data; // 顶点域,存储顶点值
EdgeNode* firstEdge; // 边表头指针,指向第一个邻接点
} VertexNode, AdjList[MAXVEX]; // AdjList表示邻接表类型
typedef struct {
AdjList adjList; // 邻接表,相当于:VertexNode adjList[MAXVEX];
int Nv, Ne; // 图中顶点数和边数
} ALGraph;
/* 建立图的邻接表结构 */
void createALGraph(ALGraph* G) {
cout << "输入顶点数和边数:";
cin >> G->Nv >> G->Ne;
// 读入顶点信息
for (int i = 0; i < G->Nv; i++)
{
cout << "输入顶点数据及其入度:";
cin >> G->adjList[i].data; // 读入顶点数值
cin >> G->adjList[i].in; // 读入顶点入度
G->adjList[i].firstEdge = nullptr; // 将边表指针置为空
}
// 读入Ne条边,建立邻接表
int i, j;
// WeightType w;
EdgeNode* e;
for (int k = 0; k < G->Ne; k++)
{
/*
cout << "输入边(vi, vj)的下标及i、j及权值w:" << endl;
cin >> i >> j >> w;
*/
cout << "输入边(vi, vj)的下标及i、j:";
cin >> i >> j;
e = new EdgeNode;
e->adjvex = j; // 邻接点下标为j
// e->weight = w; // 边(vi, vj)上的权值
e->next = G->adjList[i].firstEdge; // 将e的next指针指向当前顶点firstEdge指向的结点
G->adjList[i].firstEdge = e; // 将当前顶点的firstEdge指针指向e
/* 上面两行:使用头插法将结点插入链表中 */
// 以下为无向图需要部分,有向图不需要
/*e = new EdgeNode;
e->adjvex = i;
e->weight = w;
e->next = G->adjList[j].firstEdge;
G->adjList[j].firstEdge = e;*/
}
}
/* 销毁邻接表 */
void destroyAdjList(ALGraph* G) {
for (int i = 0; i < G->Nv; i++)
{
while (G->adjList[i].firstEdge)
{
EdgeNode* e = G->adjList[i].firstEdge;
G->adjList[i].firstEdge = e->next;
delete e;
}
}
}
/**************************************************************************************
*************************************** 拓扑排序 ***************************************
***************************************************************************************/
/**
* @brief: 拓扑排序,若G无回路,则输出拓扑排序序列并返回0,若有回路返回-1
* @param GL: 邻接表表示的图指针
* @return: 0 —— 成功
* -1 —— 失败
*/
int TopologicalSort(ALGraph* G)
{
EdgeNode* e;
int i, k, gettop;
int top = -1; // 用于栈指针下标
int count = 0; // 用于统计输出顶点的个数
int* stack; // 建栈存储入度为0的顶点
stack = new int[G->Nv];
for (i = 0; i < G->Nv; i++)
{
if (G->adjList[i].in == 0)
{
stack[++top] = i; // 将入度为0的顶点入栈
}
}
while (top != -1)
{
gettop = stack[top--]; // 出栈
cout << G->adjList[gettop].data << "\t";
count++;
/* 遍历该顶点对应的边表 */
for (e = G->adjList[gettop].firstEdge; e; e = e->next)
{
k = e->adjvex;
G->adjList[k].in--; // 将k号顶点的入度减1
if (G->adjList[k].in == 0) // 若入度为0,入栈
{
stack[++top] = k;
}
}
}
if (count < G->Nv) // 如果count小于顶点数,说明存在环
{
return -1;
}
else
{
return 0;
}
}
int main()
{
ALGraph* G = new ALGraph;
// 建图
createALGraph(G);
// 拓扑排序
TopologicalSort(G);
// 销毁邻接表
destroyAdjList(G);
delete G;
}
运行结果:
以《大话数据结构》图7-8-2为例。