设计算法并实现有向无环图的所有拓扑序列(C++)(附原码+详解)

设计算法并实现有向无环图的所有拓扑序列(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值