设计算法并实现有向无环图的所有拓扑序列(C++)(附原码+详解)
这是我们数据结构课程的的一次课后作业,距离写这篇博客已经过去了两三个月,多少有点忘记了之前的内容。只是凭记忆写的,如果有介绍不太清楚的地方,还请见谅。
一、拓扑排序算法的简单描述
①找一个入度为0的顶点v输出;
②删除v及相关弧;
③重复①,②直到无入度为0的顶点为止。
此时,若所有顶点被输出,则无回路,否则有回路。
注释:本算法根据图的不同存储结构,只能输出一种对应拓扑排序。如果需要输出全部的,则需要各位创建图时考虑多种情况,具体看下面。
上述算法换成我的理解就是:找到一个入度为0的顶点,将其输出,并且同时将该顶点在图中删除,并且连带着以该顶点为弧尾的边也要删除。那么,我们根据我们的存储结构,设计相应的算法,首先就是要初始化保存图各个顶点入度的数组indegree,其实现可以通过扫描链表进行。然后就是,将indegree中,入度为0的节点入栈。然后以栈不空为循环,首先将栈顶出栈,并且输出该顶点,然后就是将以该顶点为弧尾的边删除,在我们的存储结构中,可以表示为,将indegree中,以该顶点所发出的边的弧头相连接的顶点减1.如果减为0,则要入栈。这样循环操作,直到所有入度均为0并且输出之后,就停止了。
值得注意的是,该算法是针对有向无环图的,如果有回路,那么就不适用了。为了判别我们的图是否有回路,我们可以设置一个count分量,每一次输出则count++,最后与顶点的元素个数进行比较,如果少于则有回路。
二、图的建立:
首先一个比较困难的问题就是图的创建,我采用的是邻接表的形式创建的,有一个node1类型的数组ver,其用来存储节点的数据信息,每一个node1类型的struct都有一个data数据域和node2类型的指针域firstadj,用来指向其之后的节点信息。对于node2类型的struct,其也有两种类型的数据,一个是index,用来指示当前节点在之前数组的下标,还有一个就是nextadj,用来指向下一个以数组元素为箭头尾部的节点。具体如下。
struct node2 {
int index;//在数据数组中的下标,如果顶点编号从1开始,则index对应减1
node2* nextadj;//指向邻接表之后的节点
node2() {
index = -1;
nextadj = NULL;
}
};
struct node1 {
int data;//具体顶点值
node2* firstadj;//指向第一个节点
node1(){
firstadj = NULL;
data = -1;
}
};
对应到代码中就是我写的两个函数,一个创建顶点,一个创建边,大家仔细读一读就可以看懂,我就不过多叙述了。
void create_ver();//创建图顶点信息
void create_edge(int k, int length);//创建边信息
三、数据结构的实现
然后呢,对于图结构,我创建了一个类Graph用来保存对图的各种具体的操作。主要如下。我使用的图时6个顶点的,所以private中的ver数组和indegree数组都是长度为6,各位如果使用可以根据自己情况修改。
关于数组ver就是用来存储顶点信息的,而对于数组indegree则是用来存储每个节点的入度信息。求拓扑排序的时候会用到。
//采用邻接表存储图
class Graph {
public:
void create_ver();//创建图顶点信息
void create_edge(int k, int length);//创建边信息
void tuopu();//求拓扑排序
void chushihuashuzu();//初始化存顶点入度信息的数组。
void shuchu();//输出图的信息
private:
node1 ver[6];//存储顶点信息
int indegree[6];//记录各个顶点的入度
};
四、测试数据
下图就是我使用的图,忽略上面的×号,这个是我们PPT上讲拓扑排序时候的图,包含具体求过程的,大家只需要关注顶点和边就可以了。
测试过程:
由于我们使用的是邻接表存储图结构,每次输出拓扑排序的都是本邻接表所对应的拓扑排序。而图的邻接表可能有多种,在本例中,我们给出一种,可以自己多创建几种不同类型的邻接表。具体创建过程给出一个图。如下
通过创建过程可知,我们可以通过控制第一条边和第2条边的顺序,创建不同的邻接链表(但是他们所表示的图是相同的)
完整代码如下:
#include<iostream>
#include"stack.h"
using namespace std;
struct node2 {
int index;//在数据数组中的下标,如果顶点编号从1开始,则index对应减1
node2* nextadj;//指向邻接表之后的节点
node2() {
index = -1;
nextadj = NULL;
}
};
struct node1 {
int data;//具体顶点值
node2* firstadj;//指向第一个节点
node1(){
firstadj = NULL;
data = -1;
}
};
//采用邻接表存储图
class Graph {
public:
void create_ver();//创建图顶点信息
void create_edge(int k, int length);//创建边信息
void tuopu();//求拓扑排序
void chushihuashuzu();//初始化存顶点入度信息的数组。
void shuchu();//输出图的信息
private:
node1 ver[6];//存储顶点信息
int indegree[6];//记录各个顶点的入度
};
void Graph::chushihuashuzu()
{
//初始化为0
for (int i = 0; i < 6; i++)
indegree[i] = 0;
//遍历邻接表,求入度存入数组indegree
for (int i = 0; i < 6; i++)
{
node2* p = ver[i].firstadj;
while (p != NULL)
{
indegree[p->index]++;
p = p->nextadj;
}
}
}
void Graph::create_ver()
{
cout << "请依序输入图的顶点信息:";
for (int i = 0; i < 6; i++)
{
int x;
cin >> x;
ver[i].data = x;
ver[i].firstadj = NULL;
}
}
//K为顶点编号,length为以该顶点为箭头尾部的边的条数
void Graph::create_edge(int k,int length)
{
cout << "*****************************************" << endl;
int x;//需要连接的节点编号
node2* p=NULL;
cout << "请输入顶点" << k << "的边信息:" << endl;
for (int i = 0; i < length; i++)
{
if (i == 0)
{
cout << "请输入第1条边(编号大于1):";
cin >> x;
ver[k - 1].firstadj = new node2;
ver[k - 1].firstadj->index = x - 1;
p = ver[k - 1].firstadj;//p指向新建立的节点
}
else
{
cout << "请输入第"<<i+1<<"条边:";
cin >> x;
p->nextadj = new node2;
p->nextadj->index = x - 1;
p = p->nextadj;//p指向新建的节点
}
}
cout << "*****************************************" << endl;
}
void Graph::tuopu()
{
int count = 0;//用来计数,判断是否有回路
chushihuashuzu();//初始化入度数组
shuchu();//输出所建立的图
cout<<endl << "*****************************************" << endl;
cout << "本图的拓扑排序为:";
stack<int> s;//初始化一个栈,保存入度为0的顶点。
for (int i = 0; i < 6; i++)//入度为0的顶点入栈
{
if (indegree[i] == 0)
s.push(i);
}
while (!s.empty()) {
int x;
//入度为0的出栈,并输出顶点号
s.get_top(x);
s.pop();
cout << ver[x].data;
count++;
node2* p = ver[x].firstadj;
while (p != NULL) {
indegree[p->index]--;
if (indegree[p->index] == 0)//如果减为0则入栈
s.push(p->index);
p = p->nextadj;
}
}
if (count < 6)
{
cout << "该图不是有向无环图,有回路!";
return;
}
cout << endl << "*****************************************" << endl;
}
void Graph::shuchu()
{
cout << "*****************************************" << endl;
cout << "您所创建的图为:" << endl;
cout << "顶点为:" << endl;
for (int i = 0; i < 6; i++)
cout << ver[i].data << " ";
cout << endl;
cout << "邻接链表结构为:" << endl;
for (int i = 0; i < 6; i++)
{
cout << ver[i].data;
node2* p = ver[i].firstadj;
while (p != NULL)
{
cout << "→" << ver[p->index].data;
p = p->nextadj;
}
cout << endl;
}
cout << "入度数组为:";
for (int i = 0; i < 6; i++)
{
cout << "顶点" << ver[i].data << "的入度为:" << indegree[i] << endl;
}
cout << "*****************************************" << endl;
}
int main()
{
Graph G;
//创建图
G.create_ver();
//create_edge的形参一为图顶点的编号,形参2为以该顶点为箭头尾部的边数
G.create_edge(1, 2);
G.create_edge(2, 2);
G.create_edge(3, 2);
G.create_edge(4, 1);
G.create_edge(5, 1);
G.create_edge(6, 0);
//求拓扑排序,并输出所建立的图
G.tuopu();
return 0;
}