实验内容:拓扑排序。
任意给定一个有向图,设计一个算法,对它进行拓扑排序。拓扑排序算法思想: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) 输出有回路信息;
#include<iostream> #include<iomanip> using namespace std; #define MAXV 100//最大结点个数 #define INF 32767//表示不相连 typedef struct ANode {//图的存储结构与基本运算方法 int adjvex; struct ANode* nextarc; int weight; }ArcNode; typedef struct Vnode { ArcNode* firstarc; }VNode; typedef struct { VNode adjlist[MAXV]; int n, e; }AdjGraph; void CreateAdj(AdjGraph*& G, int A[MAXV][MAXV], int n, int e) { int i, j; ArcNode* p; G = new 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 = new 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 = e; } void DispAdj(AdjGraph* G) { int i; ArcNode* p; for (i = 0; i < G->n; i++) { p = G->adjlist[i].firstarc; cout << setw(3) << i; while (p != NULL) { cout << setw(3) << p->adjvex << "[" << p->weight << "]->"; p = p->nextarc; } cout << "^" << endl; } }
typedef int ElemType; #define MaxSize 100 typedef struct {//栈的存储结构与基本运算算法 ElemType data[MaxSize]; int top; }SqStack; void InitStack(SqStack*& s) { s = new SqStack; s->top = -1; } void Push(SqStack*& s, ElemType e) { if (s->top == MaxSize - 1) return; s->top++; s->data[s->top] = e; } void Pop(SqStack*& s, ElemType& e) { if (s->top == -1) return; e = s->data[s->top]; s->top--; } bool StackEmpty(SqStack* s) { return s->top == -1; }
拓扑排序的过程如下:
这样操作的结果有两种:一种是图中顶点都被输出,即该图中的所有顶点都在其拓扑序列中,这说明图中不存在回路;另一种就是图中顶点未被全部输出,这说明图中存在回路。所以可以通过对一个有向图进行拓扑排序,看是否产生全部顶点的拓扑序列来确定该图中是否存在回路。 在拓扑排序的过程中,当某个顶点的入度为0时,就将此顶点输出,同时将该顶点及其所有出边删除,实际上没有必要真正删除出边,设置一个indegree数组存放每个顶点的入度,删除一条出边是通过将出边邻接点的入度减1实现的。另外,为了避免重复检测入度为0的顶点,设置一个栈st存放人度为0的顶点,这里采用顺序栈。同时设置累加器count来判断该图是否存在回路。 void TopSort(AdjGraph* G) {//拓扑排序算法 SqStack* S;//定义栈存放顶点 InitStack(S); int count = 0;//累加器count初始化 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;//找到顶点i的邻接点w indegree[w]++;//顶点w的入度增一 p = p->nextarc; } } for (int i = 0; i < G->n; i++) {//入度为0的顶点入栈 if (indegree[i] == 0) Push(S, i); } int i; while (!StackEmpty(S)) {//当栈S非空时循环 Pop(S, i); cout << setw(3) << i;//将顶点i出栈,并访问 count++;//累加器加一 ArcNode* p = G->adjlist[i].firstarc;//寻找第一个邻接点 while (p != NULL) {//循环使顶点i的邻接点减一 int w = p->adjvex; indegree[w]--; if (indegree[w] == 0)//减一后将新的邻接点进栈 Push(S, w); p = p->nextarc; } } if (count < G->n)//当count<图的边数时 cout << "该图存在回路" << endl;//输出有回路信息 }
在主函数中设置了两个图,一个存在回路,一个不存在回路,可以拓扑排序。 int main() { int n = 5, e = 7; int A[MAXV][MAXV] = { {0,1,1,0,0},{0,0,1,0,0},{0,0,0,1,1}, {0,0,0,0,1},{1,0,0,0,0} }; int n1 = 7, e1 = 8; int B[MAXV][MAXV] = { {0,0,1,0,0,0,0},{0,0,0,1,1,0,1},{0,0,0,1,0,0,0}, {0,0,0,0,1,1,0},{0,0,0,0,0,0,0},{0,0,0,0,0,0,0},{0,0,0,0,0,1,0} }; AdjGraph* G; AdjGraph* G1; CreateAdj(G, A, n, e); cout << "图G的邻接表输出为:" << endl; DispAdj(G); CreateAdj(G1, B, n1, e1); cout << "图G1的邻接表输出为:" << endl; DispAdj(G1); cout << "图G的拓扑排序输出为:" << endl; TopSort(G); cout << "图G1的拓扑排序输出为:" << endl; TopSort(G1); }
|
实 验 总 结 |
拓扑排序主要用来解决满足一个给定图的各个结点的顺序问题。因此如何从实际问题中抽象出图是解决问题的关键。在上述拓扑排序算法中,栈st的作用是保存当前所有入度为0的顶点,先输出其中任意哪个顶点不影响拓扑排序的正确性,所以可以用队列代替栈。 |