目录
- 有向无环图
- 拓扑排序
- 求拓扑排序步骤
- 代码实现
- 例题
有向无环图
如果一个有向图的任意顶点都无法通过一些有向边回到自身,那么称这个有向图为有向无环图(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 10000using 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的后继结点vinDegree[v]--; // 顶点v的入度减1if (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的入度减1if (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,说明拓扑排序失败
}
例题
- 2012年中国科学技术大学研究生复试机试题:任务调度