C语言-数据结构 有向图拓扑排序TopologicalSort(邻接表存储)
拓扑排序算法的实现还是比较简单的,我们需要用到一个顺序栈辅助,采用邻接表进行存储,顶点结点存储入度、顶点信息、指向邻接结点的指针,算法过程是:我们先将入度为0的顶点入栈,然后弹出栈顶结点,记录结点个数的count+1,然后遍历所有邻接结点将其入度都减1,如果有入度为0的顶点那么就进栈,当栈不为空就继续循环,最后通过判断弹出栈的元素个数count是否小于全部顶点数,如果小于说明有环,否则无环(即构成拓扑排序)。注意:拓扑排序的情况在邻接表确定的情况下是唯一的。看文字理解确实有点费劲,不过这个实现的代码不难,如果你理解了栈的情况下,那么直接跟着TopologicalSort代码走一遍很快就能领悟到拓扑排序的奥妙!
下面我们将创建一个有向无环图和一个有向有环图
有向无环图如下:
代码中我们使用头插法进行创建有向无环图邻接表:
#define MAXVEX 8 // 最大顶点数
typedef char VertexType; // 顶点类型,使用字符表示
typedef int EdgeType; // 边上的权值类型,使用整数表示
// 边表结点
typedef struct EdgeNode {
int adjvex; // 顶点下标,表示该边的终点
struct EdgeNode* next; // 指向下一条边的指针
} EdgeNode;
// 顶点结点
typedef struct VertexNode {
int in;
VertexType data; // 顶点数据
EdgeNode* first; // 指向该顶点的第一条边
} VertexNode, AdjList[MAXVEX];
// 图的邻接表表示
typedef struct {
AdjList adjList; // 顶点数组
int numNodes; // 图的顶点数
int numEdges; // 图的边数
} GraphAdjList;
//有向无环图邻接表创建
void CreateALGraphNotEncircle(GraphAdjList* G) {
int i, j;
EdgeNode* e = NULL;
char str[] = "ABCDEFGH"; // 顶点数据
// 初始化邻接表
for (i = 0; i < G->numNodes; i++) {
G->adjList[i].data = str[i]; // 设置顶点数据
G->adjList[i].first = NULL; // 边表初始化为空
}
G->adjList[0].in = 1;
G->adjList[1].in = 1;
G->adjList[2].in = 1;
G->adjList[3].in = 1;
G->adjList[4].in = 3;
G->adjList[5].in = 0;
G->adjList[6].in = 2;
G->adjList[7].in = 1;
// 添加边 A->B
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 1; // 邻接顶点序号为B
e->next = G->adjList[0].first; // 插入到邻接表的第一个位置
G->adjList[0].first = e;
// 添加边 B->C->G
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 6; // 邻接顶点序号为G
e->next = G->adjList[1].first;
G->adjList[1].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 2; // 邻接顶点序号为C
e->next = G->adjList[1].first;
G->adjList[1].first = e;
// 添加边 C->D
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 3; // 邻接顶点序号为D
e->next = G->adjList[2].first;
G->adjList[2].first = e;
// 添加边 D->H
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 7; // 邻接顶点序号为H
e->next = G->adjList[3].first;
G->adjList[3].first = e;
// 添加边 F->A->E->G
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 6; // 邻接顶点序号为G
e->next = G->adjList[5].first;
G->adjList[5].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[5].first;
G->adjList[5].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 0; // 邻接顶点序号为A
e->next = G->adjList[5].first;
G->adjList[5].first = e;
// 添加边 G->E
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[6].first;
G->adjList[6].first = e;
// 添加边 H->E
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[7].first;
G->adjList[7].first = e;
// 打印邻接表(字母)和入度
EdgeNode* p = NULL;
printf("边结点按邻接顶点字母打印 (入度):\n");
for (i = 0; i < G->numNodes; i++) {
printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);
p = G->adjList[i].first;
while (p != NULL) {
printf("->%c", G->adjList[p->adjvex].data); // 打印邻接顶点字母
p = p->next;
}
printf("\n");
}
// 打印邻接表(下标)和入度
printf("\n边结点按邻接下标打印 (入度):\n");
for (i = 0; i < G->numNodes; i++) {
printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);
p = G->adjList[i].first;
while (p != NULL) {
printf("->%d", p->adjvex); // 打印邻接顶点下标
p = p->next;
}
printf("\n");
}
}
有向有环图如下:
代码中我们使用头插法进行创建有向有环图邻接表:
#define MAXVEX 8 // 最大顶点数
typedef char VertexType; // 顶点类型,使用字符表示
typedef int EdgeType; // 边上的权值类型,使用整数表示
// 边表结点
typedef struct EdgeNode {
int adjvex; // 顶点下标,表示该边的终点
struct EdgeNode* next; // 指向下一条边的指针
} EdgeNode;
// 顶点结点
typedef struct VertexNode {
int in;
VertexType data; // 顶点数据
EdgeNode* first; // 指向该顶点的第一条边
} VertexNode, AdjList[MAXVEX];
// 图的邻接表表示
typedef struct {
AdjList adjList; // 顶点数组
int numNodes; // 图的顶点数
int numEdges; // 图的边数
} GraphAdjList;
//有向有环图邻接表创建
void CreateALGraphHaveEncircle(GraphAdjList* G) {
int i, j;
EdgeNode* e = NULL;
char str[] = "ABCDEFGH"; // 顶点数据
// 初始化邻接表
for (i = 0; i < G->numNodes; i++) {
G->adjList[i].data = str[i]; // 设置顶点数据
G->adjList[i].first = NULL; // 边表初始化为空
}
G->adjList[0].in = 1;
G->adjList[1].in = 1;
G->adjList[2].in = 1;
G->adjList[3].in = 1;
G->adjList[4].in = 3;
G->adjList[5].in = 1;
G->adjList[6].in = 2;
G->adjList[7].in = 1;
// 添加边 A->B
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 1; // 邻接顶点序号为B
e->next = G->adjList[0].first; // 插入到邻接表的第一个位置
G->adjList[0].first = e;
// 添加边 B->C->G
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 6; // 邻接顶点序号为G
e->next = G->adjList[1].first;
G->adjList[1].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 2; // 邻接顶点序号为C
e->next = G->adjList[1].first;
G->adjList[1].first = e;
// 添加边 C->D
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 3; // 邻接顶点序号为D
e->next = G->adjList[2].first;
G->adjList[2].first = e;
// 添加边 D->H
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 7; // 邻接顶点序号为H
e->next = G->adjList[3].first;
G->adjList[3].first = e;
// 添加边 F->A->E
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[5].first;
G->adjList[5].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 0; // 邻接顶点序号为A
e->next = G->adjList[5].first;
G->adjList[5].first = e;
// 添加边 G->E->F
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 5; // 邻接顶点序号为F
e->next = G->adjList[6].first;
G->adjList[6].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[6].first;
G->adjList[6].first = e;
// 添加边 H->E
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[7].first;
G->adjList[7].first = e;
// 打印邻接表(字母)和入度
EdgeNode* p = NULL;
printf("边结点按邻接顶点字母打印 (入度):\n");
for (i = 0; i < G->numNodes; i++) {
printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);
p = G->adjList[i].first;
while (p != NULL) {
printf("->%c", G->adjList[p->adjvex].data); // 打印邻接顶点字母
p = p->next;
}
printf("\n");
}
// 打印邻接表(下标)和入度
printf("\n边结点按邻接下标打印 (入度):\n");
for (i = 0; i < G->numNodes; i++) {
printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);
p = G->adjList[i].first;
while (p != NULL) {
printf("->%d", p->adjvex); // 打印邻接顶点下标
p = p->next;
}
printf("\n");
}
}
拓扑排序(TopologicalSort)算法,里面包括顺序栈的实现代码非常简洁:
int TopologicalSort(GraphAdjList GL) {
EdgeNode* e; // 边节点指针,用于遍历邻接表中的边
int i, k, gettop; // 迭代变量和临时变量
int top = 0; // 栈顶指针,初始值为0
int count = 0; // 已输出的节点计数
int* stack; // 存储拓扑排序的栈
stack = (int*)malloc(sizeof(int) * MAXVEX); // 动态分配栈空间
// 遍历所有节点,将入度为0的节点入栈
for (i = 0; i < GL.numNodes; i++) {
if (0 == GL.adjList[i].in) {
stack[++top] = i; // 入栈,并更新栈顶指针
}
}
printf("\n拓扑排序序列为:\n");
// 当栈不为空时,进行拓扑排序
while (top != 0) {
gettop = stack[top--]; // 出栈操作,获取栈顶元素,并更新栈顶指针
printf("%c -> ", GL.adjList[gettop].data); // 打印当前节点的值
count++; // 已处理节点计数器加1
// 遍历当前节点的所有邻接节点
for (e = GL.adjList[gettop].first; e; e = e->next) {
k = e->adjvex; // 获取邻接节点的索引
if (!(--GL.adjList[k].in)) { // 将邻接节点的入度减1,并检查是否变为0
stack[++top] = k; // 如果入度为0,将邻接节点入栈
}
}
}
// 如果已处理的节点数小于图中节点总数,说明存在环
if (count < GL.numNodes) {
return FALSE; // 拓扑排序失败
}
return TRUE; // 拓扑排序成功
}
完整代码(有向无环图、有环图的邻接表创建、TopologicalSort算法)
#include<stdio.h>
#include<stdlib.h>
#define MAXVEX 8 // 最大顶点数
#define TRUE 1
#define FALSE 0
typedef char VertexType; // 顶点类型,使用字符表示
typedef int EdgeType; // 边上的权值类型,使用整数表示
typedef int Boolean; // 布尔类型
// 边表结点
typedef struct EdgeNode {
int adjvex; // 顶点下标,表示该边的终点
struct EdgeNode* next; // 指向下一条边的指针
} EdgeNode;
// 顶点结点
typedef struct VertexNode {
int in;
VertexType data; // 顶点数据
EdgeNode* first; // 指向该顶点的第一条边
} VertexNode, AdjList[MAXVEX];
// 图的邻接表表示
typedef struct {
AdjList adjList; // 顶点数组
int numNodes; // 图的顶点数
int numEdges; // 图的边数
} GraphAdjList;
//有向无环图邻接表创建
void CreateALGraphNotEncircle(GraphAdjList* G) {
int i, j;
EdgeNode* e = NULL;
char str[] = "ABCDEFGH"; // 顶点数据
// 初始化邻接表
for (i = 0; i < G->numNodes; i++) {
G->adjList[i].data = str[i]; // 设置顶点数据
G->adjList[i].first = NULL; // 边表初始化为空
}
G->adjList[0].in = 1;
G->adjList[1].in = 1;
G->adjList[2].in = 1;
G->adjList[3].in = 1;
G->adjList[4].in = 3;
G->adjList[5].in = 0;
G->adjList[6].in = 2;
G->adjList[7].in = 1;
// 添加边 A->B
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 1; // 邻接顶点序号为B
e->next = G->adjList[0].first; // 插入到邻接表的第一个位置
G->adjList[0].first = e;
// 添加边 B->C->G
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 6; // 邻接顶点序号为G
e->next = G->adjList[1].first;
G->adjList[1].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 2; // 邻接顶点序号为C
e->next = G->adjList[1].first;
G->adjList[1].first = e;
// 添加边 C->D
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 3; // 邻接顶点序号为D
e->next = G->adjList[2].first;
G->adjList[2].first = e;
// 添加边 D->H
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 7; // 邻接顶点序号为H
e->next = G->adjList[3].first;
G->adjList[3].first = e;
// 添加边 F->A->E->G
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 6; // 邻接顶点序号为G
e->next = G->adjList[5].first;
G->adjList[5].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[5].first;
G->adjList[5].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 0; // 邻接顶点序号为A
e->next = G->adjList[5].first;
G->adjList[5].first = e;
// 添加边 G->E
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[6].first;
G->adjList[6].first = e;
// 添加边 H->E
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[7].first;
G->adjList[7].first = e;
// 打印邻接表(字母)和入度
EdgeNode* p = NULL;
printf("边结点按邻接顶点字母打印 (入度):\n");
for (i = 0; i < G->numNodes; i++) {
printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);
p = G->adjList[i].first;
while (p != NULL) {
printf("->%c", G->adjList[p->adjvex].data); // 打印邻接顶点字母
p = p->next;
}
printf("\n");
}
// 打印邻接表(下标)和入度
printf("\n边结点按邻接下标打印 (入度):\n");
for (i = 0; i < G->numNodes; i++) {
printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);
p = G->adjList[i].first;
while (p != NULL) {
printf("->%d", p->adjvex); // 打印邻接顶点下标
p = p->next;
}
printf("\n");
}
}
//有向有环图邻接表创建
void CreateALGraphHaveEncircle(GraphAdjList* G) {
int i, j;
EdgeNode* e = NULL;
char str[] = "ABCDEFGH"; // 顶点数据
// 初始化邻接表
for (i = 0; i < G->numNodes; i++) {
G->adjList[i].data = str[i]; // 设置顶点数据
G->adjList[i].first = NULL; // 边表初始化为空
}
G->adjList[0].in = 1;
G->adjList[1].in = 1;
G->adjList[2].in = 1;
G->adjList[3].in = 1;
G->adjList[4].in = 3;
G->adjList[5].in = 1;
G->adjList[6].in = 2;
G->adjList[7].in = 1;
// 添加边 A->B
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 1; // 邻接顶点序号为B
e->next = G->adjList[0].first; // 插入到邻接表的第一个位置
G->adjList[0].first = e;
// 添加边 B->C->G
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 6; // 邻接顶点序号为G
e->next = G->adjList[1].first;
G->adjList[1].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 2; // 邻接顶点序号为C
e->next = G->adjList[1].first;
G->adjList[1].first = e;
// 添加边 C->D
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 3; // 邻接顶点序号为D
e->next = G->adjList[2].first;
G->adjList[2].first = e;
// 添加边 D->H
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 7; // 邻接顶点序号为H
e->next = G->adjList[3].first;
G->adjList[3].first = e;
// 添加边 F->A->E
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[5].first;
G->adjList[5].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 0; // 邻接顶点序号为A
e->next = G->adjList[5].first;
G->adjList[5].first = e;
// 添加边 G->E->F
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 5; // 邻接顶点序号为F
e->next = G->adjList[6].first;
G->adjList[6].first = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[6].first;
G->adjList[6].first = e;
// 添加边 H->E
e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = 4; // 邻接顶点序号为E
e->next = G->adjList[7].first;
G->adjList[7].first = e;
// 打印邻接表(字母)和入度
EdgeNode* p = NULL;
printf("边结点按邻接顶点字母打印 (入度):\n");
for (i = 0; i < G->numNodes; i++) {
printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);
p = G->adjList[i].first;
while (p != NULL) {
printf("->%c", G->adjList[p->adjvex].data); // 打印邻接顶点字母
p = p->next;
}
printf("\n");
}
// 打印邻接表(下标)和入度
printf("\n边结点按邻接下标打印 (入度):\n");
for (i = 0; i < G->numNodes; i++) {
printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);
p = G->adjList[i].first;
while (p != NULL) {
printf("->%d", p->adjvex); // 打印邻接顶点下标
p = p->next;
}
printf("\n");
}
}
int TopologicalSort(GraphAdjList GL) {
EdgeNode* e; // 边节点指针,用于遍历邻接表中的边
int i, k, gettop; // 迭代变量和临时变量
int top = 0; // 栈顶指针,初始值为0
int count = 0; // 已输出的节点计数
int* stack; // 存储拓扑排序的栈
stack = (int*)malloc(sizeof(int) * MAXVEX); // 动态分配栈空间
// 遍历所有节点,将入度为0的节点入栈
for (i = 0; i < GL.numNodes; i++) {
if (0 == GL.adjList[i].in) {
stack[++top] = i; // 入栈,并更新栈顶指针
}
}
printf("\n拓扑排序序列为:\n");
// 当栈不为空时,进行拓扑排序
while (top != 0) {
gettop = stack[top--]; // 出栈操作,获取栈顶元素,并更新栈顶指针
printf("%c -> ", GL.adjList[gettop].data); // 打印当前节点的值
count++; // 已处理节点计数器加1
// 遍历当前节点的所有邻接节点
for (e = GL.adjList[gettop].first; e; e = e->next) {
k = e->adjvex; // 获取邻接节点的索引
if (!(--GL.adjList[k].in)) { // 将邻接节点的入度减1,并检查是否变为0
stack[++top] = k; // 如果入度为0,将邻接节点入栈
}
}
}
// 如果已处理的节点数小于图中节点总数,说明存在环
if (count < GL.numNodes) {
return FALSE; // 拓扑排序失败
}
return TRUE; // 拓扑排序成功
}
int main() {
GraphAdjList GL;
GL.numNodes = MAXVEX; // 设置顶点数
//情况1有向无环图
printf("测试有向无环图是否构成拓扑排序\n\n");
CreateALGraphNotEncircle(&GL);
if (TopologicalSort(GL)) {
printf("\n能构成拓扑排序,图中无环\n");
}
else {
printf("\n不能构成拓扑排序,图中有环\n");
}printf("\n");
//情况2有向有环图
printf("测试有向有环图是否构成拓扑排序\n\n");
CreateALGraphHaveEncircle(&GL);
if (TopologicalSort(GL)) {
printf("\n能构成拓扑排序,图中无环\n");
}
else {
printf("\n不能构成拓扑排序,图中有环\n");
}
return 0;
}
运行结果:
有向无环图
有向有环图(A->B->G->F是环)