拓扑排序算法(第七章 P182 算法7.12)

拓扑排序

 

定义        

拓扑排序(Topological Sort) :由某个集合上的一个偏序得到该集合上的一个全序的操作。

◆ 集合上的关系:集合A上的关系是从A到A的关系 

◆ 关系的自反性:若  有(a,a)∈R,称集合A上的关系R是自反的。

◆ 关系的对称性:如果对于a,b∈A ,只要有(a,b)∈R就有(b,a)∈R ,称集合A上的关系R是对称的。

◆ 关系的对称性与反对称性:如果对于a,b∈A ,只要有(a,b)∈R就有(b,a)∈R ,称集合A上的关系R是对称的。如果对于a,b∈A ,仅当a=b时有(a,b)∈R和(b,a)∈R ,称集合A上的关系R是反对称的。

◆ 关系的传递性:若a,b,c∈A,若(a,b)∈R,并且(b,c)∈R ,则(a,c)∈R ,称集合A上的关系R是传递的。

◆ 偏序:若集合A上的关系R是自反的,反对称的和传递的,则称R是集合A上的偏序关系。

◆ 全序:设R是集合A上的偏序关系,对于任意的 a,b∈A,必有aRb或bRa, 则称R是集合A上的全序关系。

 

即偏序是指集合中仅有部分元素之间可以比较,而全序是指集合中任意两个元素之间都可以比较。        

在AOV网中,若有有向边<i, j>,则 i 是 j 的直接前驱,j 是 i 的直接后继;推而广之,若从顶点i到顶点 j 有有向路径,则 i 是 j 的前驱,j 是 i 的后继。        

在AOV网中,不能有环,否则,某项活动能否进行是以自身的完成作为前提条件。    

检查方法:对有向图的顶点进行拓扑排序,若所有顶点都在其拓扑有序序列中,则无环。    

有向图的拓扑排序:构造AOV网中顶点的一个拓扑线性序列(v’1,v’2, ⋯,v’n),使得该线性序列不仅保持原来有向图中顶点之间的优先关系,而且对原图中没有优先关系的顶点之间也建立一种(人为的)优先关系。

 

 

拓扑排序算法

 

算法思想

① 在AOV网中选择一个没有前驱的顶点且输出;

② 在AOV网中删除该顶点以及从该顶点出发的(以该顶点为尾的弧)所有有向弧(边) ;

③ 重复①、②,直到图中全部顶点都已输出(图中无环)或图中不存在无前驱的顶点(图中必有环)。

 

 

算法实现说明

◆ 采用邻接链作为AOV网的存储结构;

◆ 设立堆栈,用来暂存入度为0的顶点;

◆ 删除顶点以它为尾的弧:弧头顶点的入度减1。

 

算法分析

设AOV网有n个顶点,e条边,则算法的主要执行是:

◆ 统计各顶点的入度:时间复杂度是O(n+e) ;

◆ 入度为 0 的顶点入栈:时间复杂度是O(n) ;

◆ 排序过程:顶点入栈和出栈操作执行n次,入度减1的操作共执行e次,时间复杂度是O(n+e) ;      

因此,整个算法的时间复杂度是O(n+e) 。

 

算法实现

typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */
#include<limits.h> //常量INT_MAX和INT_MIN分别表示最大、最小整数

/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2 


#define MAX_NAME 5 /* 顶点字符串的最大长度 */
typedef int InfoType;
typedef char VertexType[MAX_NAME]; /* 字符串类型 */



/* ----------------------------------  图的邻接表存储表示    ----------------------------------*/

#define MAX_VERTEX_NUM 20
typedef enum { DG, DN, AG, AN }GraphKind; /* {有向图,有向网,无向图,无向网} */
typedef struct ArcNode
{
	int adjvex; /* 该弧所指向的顶点的位置 */
	struct ArcNode *nextarc; /* 指向下一条弧的指针 */
	InfoType *info; /* 网的权值指针) */
}ArcNode; /* 表结点 */
typedef struct
{
	VertexType data; /* 顶点信息 */
	ArcNode *firstarc; /* 第一个表结点的地址,指向第一条依附该顶点的弧的指针 */
}VNode, AdjList[MAX_VERTEX_NUM]; /* 头结点 */
typedef struct
{
	AdjList vertices;
	int vexnum, arcnum; /* 图的当前顶点数和弧数 */
	int kind; /* 图的种类标志 */
}ALGraph;

/* ---------------------------------------------------------------------------------------------*/




/* ---------------------------  需要用的图的邻接表存储的基本操作 --------------------------*/


int LocateVex(ALGraph G, VertexType u)
{ /* 初始条件: 图G存在,u和G中顶点有相同特征 */
  /* 操作结果: 若G中存在顶点u,则返回该顶点在图中位置;否则返回-1 */
	int i;
	for (i = 0; i < G.vexnum; ++i)
		if (strcmp(u, G.vertices[i].data) == 0)
			return i;
	return -1;
}

Status CreateGraph(ALGraph *G)
{ /* 采用邻接表存储结构,构造没有相关信息的图G(用一个函数构造4种图) */
	int i, j, k;
	int w; /* 权值 */
	VertexType va, vb;
	ArcNode *p;
	printf("请输入图的类型(有向图:0,有向网:1,无向图:2,无向网:3): ");
	scanf("%d", &(*G).kind);
	printf("请输入图的顶点数,边数: ");
	scanf("%d,%d", &(*G).vexnum, &(*G).arcnum);
	printf("请输入%d个顶点的值(<%d个字符):\n", (*G).vexnum, MAX_NAME);
	for (i = 0; i < (*G).vexnum; ++i) /* 构造顶点向量 */
	{
		scanf("%s", (*G).vertices[i].data);
		(*G).vertices[i].firstarc = NULL;
	}
	if ((*G).kind == 1 || (*G).kind == 3) /* 网 */
		printf("请顺序输入每条弧(边)的权值、弧尾和弧头(以空格作为间隔):\n");
	else /* 图 */
		printf("请顺序输入每条弧(边)的弧尾和弧头(以空格作为间隔):\n");
	for (k = 0; k < (*G).arcnum; ++k) /* 构造表结点链表 */
	{
		if ((*G).kind == 1 || (*G).kind == 3) /* 网 */
			scanf("%d%s%s", &w, va, vb);
		else /* 图 */
			scanf("%s%s", va, vb);
		i = LocateVex(*G, va); /* 弧尾 */
		j = LocateVex(*G, vb); /* 弧头 */
		p = (ArcNode*)malloc(sizeof(ArcNode));
		p->adjvex = j;
		if ((*G).kind == 1 || (*G).kind == 3) /* 网 */
		{
			p->info = (int *)malloc(sizeof(int));
			*(p->info) = w;
		}
		else
			p->info = NULL; /* 图 */
		p->nextarc = (*G).vertices[i].firstarc; /* 插在表头 */
		(*G).vertices[i].firstarc = p;
		if ((*G).kind >= 2) /* 无向图或网,产生第二个表结点 */
		{
			p = (ArcNode*)malloc(sizeof(ArcNode));
			p->adjvex = i;
			if ((*G).kind == 3) /* 无向网 */
			{
				p->info = (int*)malloc(sizeof(int));
				*(p->info) = w;
			}
			else
				p->info = NULL; /* 无向图 */
			p->nextarc = (*G).vertices[j].firstarc; /* 插在表头 */
			(*G).vertices[j].firstarc = p;
		}
	}
	return OK;
}


Boolean visited[MAX_VERTEX_NUM]; /* 访问标志数组(全局量) */
void(*VisitFunc)(char* v); /* 函数变量(全局量) */


void Display(ALGraph G)
{ /* 输出图的邻接矩阵G */
	int i;
	ArcNode *p;
	switch (G.kind)
	{
	case DG: printf("有向图\n");
		break;
	case DN: printf("有向网\n");
		break;
	case AG: printf("无向图\n");
		break;
	case AN: printf("无向网\n");
	}
	printf("%d个顶点:\n", G.vexnum);
	for (i = 0; i < G.vexnum; ++i)
		printf("%s ", G.vertices[i].data);
	printf("\n%d条弧(边):\n", G.arcnum);
	for (i = 0; i < G.vexnum; i++)
	{
		p = G.vertices[i].firstarc;
		while (p)
		{
			if (G.kind <= 1) /* 有向 */
			{
				printf("%s→%s ", G.vertices[i].data, G.vertices[p->adjvex].data);
				if (G.kind == DN) /* 网 */
					printf(":%d ", *(p->info));
			}
			else /* 无向(避免输出两次) */
			{
				if (i < p->adjvex)
				{
					printf("%s-%s ", G.vertices[i].data, G.vertices[p->adjvex].data);
					if (G.kind == AN) /* 网 */
						printf(":%d ", *(p->info));
				}
			}
			p = p->nextarc;
		}
		printf("\n");
	}
}



/* --------------------------------------------------------------------------------------------------*/




  /*  输出有向图的一个拓扑序列。实现算法7.12的程序 */

void FindInDegree(ALGraph G, int indegree[])
{ /* 求顶点的入度,算法7.12调用 */
	int i;
	ArcNode *p;
	for (i = 0; i < G.vexnum; i++)
		indegree[i] = 0; /* 赋初值 */
	for (i = 0; i < G.vexnum; i++)
	{
		p = G.vertices[i].firstarc;
		while (p)
		{
			indegree[p->adjvex]++;
			p = p->nextarc;
		}
	}
}

typedef int SElemType; /* 栈类型 */


/* ------------------------------------   栈的顺序存储表示    ----------------------------------*/

#define STACK_INIT_SIZE 10 /* 存储空间初始分配量 */
#define STACKINCREMENT 2 /* 存储空间分配增量 */
typedef struct SqStack
{
	SElemType *base; /* 在栈构造之前和销毁之后,base的值为NULL */
	SElemType *top; /* 栈顶指针 */
	int stacksize; /* 当前已分配的存储空间,以元素为单位 */
}SqStack; /* 顺序栈 */

/* ---------------------------------------------------------------------------------------------*/


/* ---------------------------------  需要用到的顺序栈的基本操作 -------------------------------*/


Status InitStack(SqStack *S)
{ /* 构造一个空栈S */
	(*S).base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
	if (!(*S).base)
		exit(OVERFLOW); /* 存储分配失败 */
	(*S).top = (*S).base;
	(*S).stacksize = STACK_INIT_SIZE;
	return OK;
}

Status StackEmpty(SqStack S)
{ /* 若栈S为空栈,则返回TRUE,否则返回FALSE */
	if (S.top == S.base)
		return TRUE;
	else
		return FALSE;
}

Status Push(SqStack *S, SElemType e)
{ /* 插入元素e为新的栈顶元素 */
	if ((*S).top - (*S).base >= (*S).stacksize) /* 栈满,追加存储空间 */
	{
		(*S).base = (SElemType *)realloc((*S).base, ((*S).stacksize + STACKINCREMENT) * sizeof(SElemType));
		if (!(*S).base)
			exit(OVERFLOW); /* 存储分配失败 */
		(*S).top = (*S).base + (*S).stacksize;
		(*S).stacksize += STACKINCREMENT;
	}
	*((*S).top)++ = e;
	return OK;
}

Status Pop(SqStack *S, SElemType *e)
{ /* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
	if ((*S).top == (*S).base)
		return ERROR;
	*e = *--(*S).top;
	return OK;
}


/* ---------------------------------------------------------------------------------------------*/


Status TopologicalSort(ALGraph G)
{ /* 有向图G采用邻接表存储结构。若G无回路,则输出G的顶点的一个拓扑序列并返回OK, */
  /* 否则返回ERROR。算法7.12 */
	int i, k, count, indegree[MAX_VERTEX_NUM];
	SqStack S;
	ArcNode *p;
	FindInDegree(G, indegree); /* 对各顶点求入度indegree[0..vernum-1] */
	InitStack(&S); /* 初始化栈 */
	for (i = 0; i < G.vexnum; ++i) /* 建零入度顶点栈S */
		if (!indegree[i])
			Push(&S, i); /* 入度为0者进栈 */
	count = 0; /* 对输出顶点计数 */
	while (!StackEmpty(S))
	{ /* 栈不空 */
		Pop(&S, &i);
		printf("%s ", G.vertices[i].data); /* 输出i号顶点并计数 */
		++count;
		for (p = G.vertices[i].firstarc; p; p = p->nextarc)
		{ /* 对i号顶点的每个邻接点的入度减1 */
			k = p->adjvex;
			if (!(--indegree[k])) /* 若入度减为0,则入栈 */
				Push(&S, k);
		}
	}
	if (count < G.vexnum)
	{
		printf("此有向图有回路\n");
		return ERROR;
	}
	else
	{
		printf("为一个拓扑序列。\n");
		return OK;
	}
}

void main()
{
	ALGraph f;
	printf("请选择有向图\n");
	CreateGraph(&f);
	Display(f);
	TopologicalSort(f);
}

运行结果:

有向图:

运行过程图示:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值