实验题目
实验题目: 拓扑排序。给定有向图,设计一个算法,对它进行拓扑排序。拓扑排序算法思想:a.在有向图中任选一个没有前趋的顶点输出;b.从图中删除该顶点和所有以它为尾的弧;c.重复上述a、b,直到全部顶点都已输出,此时,顶点输出序列即为一个拓朴有序序列;或者直到图中没有无前趋的顶点为止,此情形表明有向图中存在环。
实验说明:拓扑排序算法伪代码如下:
1. 栈S初始化;累加器count初始化;
2. 扫描顶点表,将没有前驱(即入度为0)的顶点压栈;
3. 当栈S非空时循环
3.1 vj=退出栈顶元素;输出vj;累加器加1;
3.2 将顶点vj的各个邻接点的入度减1;
3.3 将新的入度为0的顶点入栈;
4. if (count<vertexNum) 输出有回路信息;
实验内容
1.问题设计分析
拓扑排序的过程如下:
(1)从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它。
(2)从图中删去该顶点,并且删去从该顶点发出的全部有向边。
(3)重复上述两步,直到剩余的图中不再存在没有前驱的顶点为止。
这样操作的结果有两种:一种是图中顶点都被输出,即该图中的所有顶点都在其拓扑序列中,这说明图中不存在回路;另一种就是图中顶点未被全部输出,这说明图中存在回路。所以可以通过对一个有向图进行拓扑排序,看是否产生全部顶点的拓扑序列来确定该图中是否存在回路。
在拓扑排序的过程中,当某个顶点的入度为0时,就将此顶点输出,同时将该顶点及其所有出边删除,实际上没有必要真正删除出边,设置一个indegree数组存放每个顶点的入度,删除一条出边是通过将出边邻接点的入度减1实现的。另外,为了避免重复检测入度为0的顶点,设置一个栈st存放人度为0的顶点,这里采用顺序栈。
2.程序编码
#include <stdio.h>
#include <malloc.h>
#define INF 32767
#define MAXV 100
#define MaxSize 100
typedef int ElemType;
typedef char InfoType;
typedef struct ANode
{
int adjvex;
struct ANode *nextarc;
int weight;
}ArcNode;
typedef struct Vnode
{
InfoType info;
int count;
ArcNode *firstarc;
}VNode;
typedef struct
{
VNode adjlist[MAXV];
int n,e;
}AdjGraph;
void CreatAdj(AdjGraph *&G,int A[MAXV][MAXV],int n,int e)
{
int i,j;
ArcNode *p;
G=(AdjGraph *)malloc(sizeof(AdjGraph));
for(i=0;i<n;i++)
G->adjlist[i].firstarc=NULL;
for(i=0;i<n;i++)
for(j=n-1;j>=0;j--)
if(A[i][j]!=0&&A[i][j]!=INF)
{
p=(ArcNode *)malloc(sizeof(ArcNode));
p->adjvex=j;
p->weight=A[i][j];
p->nextarc=G->adjlist[i].firstarc;
G->adjlist[i].firstarc=p;
}
G->n=n;G->e=n;
}
void DispAdj(AdjGraph *G)
{
ArcNode *p;
for(int i=0;i<G->n;i++)
{
p=G->adjlist[i].firstarc;
printf("%3d:",i);
while(p!=NULL)
{
printf("%3d[%d]→",p->adjvex,p->weight);
p=p->nextarc;
}
printf("^\n");
}
}
void DestroyAdj(AdjGraph *&G)
{
ArcNode *pre,*p;
for(int i=0;i<G->n;i++)
{
pre=G->adjlist[i].firstarc;
if(pre!=NULL)
{
p=pre->nextarc;
while(p!=NULL)
{
free(pre);
pre=p;p=p->nextarc;
}
free(pre);
}
}
free(G);
}
typedef struct
{
ElemType data[MaxSize];
int top; //栈指针
} SqStack; //顺序栈类型
void InitStack(SqStack *&s) //初始化栈
{
s=(SqStack *)malloc(sizeof(SqStack));
s->top=-1;
}
bool StackEmpty(SqStack *s) //判断栈是否为空
{
return(s->top==-1);
}
bool Push(SqStack *&s,ElemType e) //元素e入栈
{
if (s->top==MaxSize-1) //栈满的情况,即栈上溢出
return false;
s->top++;
s->data[s->top]=e;
return true;
}
bool Pop(SqStack *&s,ElemType &e) //出栈
{
if (s->top==-1) //栈为空的情况,即栈下溢出
return false;
e=s->data[s->top];
s->top--;
return true;
}
void TopSort(AdjGraph *G)
{
SqStack *st;
InitStack(st);
int indegree[MAXV];
for(int i=0;i<G->n;i++)
indegree[i]=0;
for(int i=0;i<G->n;i++)
{
ArcNode *p=G->adjlist[i].firstarc;
while(p!=NULL)
{
int w=p->adjvex;
indegree[w]++;
p=p->nextarc;
}
}
for(int i=0;i<G->n;i++)
if(indegree[i]==0)
Push(st,i);
int i;
while(!StackEmpty(st))
{
Pop(st,i);
printf("C%d-> ",i+1);
ArcNode *p=G->adjlist[i].firstarc;
while(p!=NULL)
{
int w=p->adjvex;
indegree[w]--;
if(indegree[w]==0)
Push(st,w);
p=p->nextarc;
}
}
}
int main()
{
AdjGraph *G;
int A[MAXV][MAXV];
int n=7,e=8;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
A[i][j]=0;
A[0][2]=1;
A[1][3]=1;A[1][6]=1;A[1][4]=1;
A[2][3]=1;
A[3][4]=1;A[3][5]=1;
A[6][5]=1;
CreatAdj(G,A,n,e);
printf("图G的邻接表:\n");
DispAdj(G);
TopSort(G);
DestroyAdj(G);
}
3.运行结果和分析
在上述算法中,我们需要用到图的基本运算算法和图的存储算法:本次用到的是邻接表类型存储的图并且添加了创建图的邻接表、输出图的邻接表、销毁图的邻接表等算法。
还需要用到栈的基本算法:初始化栈、判断栈是否为空、元素入栈、出栈等这些算法。和以往不同的是,此次应该将ElemType定义为int型,否则会报错。结果如下:
4.实验小结
在上述拓扑排序算法中,栈st的作用是保存当前所有入度为0的顶点,先输出其中任意哪个顶点不影响拓扑排序的正确性,所以可以用队列代替栈。