有向无环图
如果一个有向图的任意顶点都无法通过一些有向边回到自身,那么称这个有向图为有向无环图(DirectedAcyclic Graph,DAG)。
拓扑排序
拓扑排序是将有向无环图G的所有顶点排成一个线性序列,使得对图G中的任意两个顶点u、v,如果存在边u->v,那么在序列中u一定在v前面。这个序列又被称为拓扑序列。
求拓扑排序步骤
- 定义一个队列Q,并把所有入度为0的结点加入队列(算法笔记p251队列)。
- 取队首结点,输出。然后删去所有从它出发的边,并令这些边到达的顶点的入度减1,如果某个顶点的入度减为0,则将其加入队列。
- 反复进行②操作,直到队列为空。
- 如果队列为空时入过队的结点数目恰好为N,说明拓扑排序成功,图G为有向无环图;否则,拓扑排序失败,图G中有环。
代码实现
可使用邻接表实现拓扑排序。显然,由于需要记录结点的入度,因此需要额外建立一个数组inDegree[MAXV],并在程序一开始读入图时就记录好每个结点的入度。接下来就只需要按上面所说的步骤进行实现即可,拓扑排序的代码如下:
- C++实现:
#include <vector>
#include <queue>
#define MAXV 10000
using namespace std;
vector<int> G[MAXV]; // 邻接表
int n, inDegree[MAXV]; // 顶点数、入度
// 拓扑排序
bool topologicalSort() {
int num = 0;
queue<int> q;
for (int i = 0; i < n; ++i)
if (inDegree[i] == 0)
q.push(i); //将所有入度为0的顶点入队
while (!q.empty()) {
int u = q.front(); // 取队首顶点u
// printf("%d", u); // 此处可输出顶点u,作为拓扑序列中的顶点
q.pop();
for (int i = 0; i < G[u].size(); ++i) {
int v = G[u][i]; // u的后继结点v
inDegree[v]--; // 顶点v的入度减1
if (inDegree[v] == 0) // 顶点v的入度减为0则入队
q.push(v);
}
G[u].clear(); // 清空顶点u的所有出边(如无必要可不写)
num++; // 加入拓扑序列的顶点数加1
}
if (num == n) return true; // 加入拓扑排序的顶点数为n,说明拓扑排序成功
else return false; // 加入拓扑排序的顶点数小于n,说明拓扑排序失败
}
- 拓扑排序的很重要的应用就是判断一个给定的图是否是有向无环图。正如上面的代码,如果 topologicalSort0函数返回true,则说明拓扑排序成功,给定的图是有向无环图;否则,说明拓扑排序失败,给定的图中有环。
- 最后指出,如果要求有多个入度为0的顶点,选择编号最小的顶点,那么把queue改成priority_queue(堆实现),并保持队首元素是优先队列中最小的元素即可(算法笔记p335堆)。
- C语言实现:
#include <stdio.h>
#include <stdbool.h>
#define MaxSize 1000
// 图的邻接表实现数据结构定义
typedef int VertexType;
typedef int EdgeType;
// 边表结点
typedef struct ArcNode {
int adjVertex; // 该弧所指向的顶点的位置
EdgeType info; // 弧的权值
struct ArcNode *next; // 指向下一条弧的指针
} ArcNode;
// 顶点表结点
typedef struct VertexNode {
VertexType data; // 顶点信息
struct ArcNode *first; // 指向第一条依附该顶点的弧的指针
} AdjList;
// 图的链式存储(邻接表实现)
typedef struct ALGraph {
AdjList vertices[MaxSize]; // 邻接表
int verNum, arcNum; // 顶点数和弧数
} ALGraph;
// 循环队列实现的数据结构定义
typedef int ElemType;
typedef struct Queue {
ElemType data[MaxSize];
int front, rear;
} Queue;
void init(Queue *q) {
q->rear = 0;
q->front = 0;
}
bool empty(Queue *q) {
return q->front == q->rear;
}
bool full(Queue *q) {
return (q->rear + 1) % MaxSize == q->front;
}
bool push(Queue *q, ElemType value) {
if (full(q))
return false;
q->data[q->rear] = value;
q->rear = (q->rear + 1) % MaxSize;
return true;
}
bool pop(Queue *q, ElemType *p) {
if (empty(q))
return false;
*p = q->data[q->front];
q->front = (q->front + 1) % MaxSize;
return true;
}
// 入度
int inDegree[MaxSize];
// 计算图中各个顶点的入度
void countInDegree(ALGraph *G) {
// 初始化
for (int i = 0; i < G->verNum; ++i)
inDegree[i] = 0;
// 计算入度
for (int i = 0; i < G->verNum; ++i) {
ArcNode *node = G->vertices[i].first;
while (node) {
inDegree[node->adjVertex]++;
node = node->next;
}
}
}
// 拓扑排序
bool topologicalSort(ALGraph *G) {
int num = 0; // 记录加入拓扑排序的顶点数
Queue q; // 拓扑队列
init(&q); // 初始化队列
countInDegree(G); // 计算图的入度
for (int i = 0; i < G->verNum; ++i)
if (inDegree[i] == 0)
push(&q, i); //将所有入度为0的顶点入队
while (!empty(&q)) {
int u;
pop(&q, &u); // 取队首顶点u并出队
// printf("%d", u); // 此处可输出顶点u,作为拓扑序列中的顶点
ArcNode *i = G->vertices[u].first;
while (i) {
int v = G->vertices[i->adjVertex].data;
inDegree[v]--; // 顶点v的入度减1
if (inDegree[v] == 0) // 顶点v的入度减为0则入队
push(&q, v);
i = i->next;
}
G->vertices[u].first = NULL; // 清空顶点u的所有出边(如无必要可不写)
num++; // 加入拓扑序列的顶点数加1
}
if (num == G->verNum) return true; // 加入拓扑排序的顶点数为n,说明拓扑排序成功
else return false; // 加入拓扑排序的顶点数小于n,说明拓扑排序失败
}