C++ 拓扑排序(邻接矩阵与邻接表)

   博主新手,最近在学习拓扑排序,查阅网上资料发现有些难懂且很多方法的代码不尽相同,于是就想着试试自己能不能写出来,经过一段时间的尝试,最终实现了拓扑排序,现在将自己的想法发表出来,可能有些瑕疵,希望各位看后不吝赐教(不知道是不是就是网上的方法,之前看网上的没看懂……)。

1.邻接矩阵方法

        思路:

            创建邻接矩阵,输入顶点数和边数,初始化所有顶点间无关(用0表示),然后手动输入有关顶点(用1表示)。 依次排查所有值,那我们可以发现:若顶点a与b之间存在关系,则有order[a][b]=1(order数组即邻接矩阵的数组)且表明a是始点,b是终点。则我们要找的即为对于a的order数组内全为0(即a只可能有出度,没有入度)。 例: 有如下图
《C++ 拓扑排序(邻接矩阵与邻接表)》 则输出结果为:

《C++ 拓扑排序(邻接矩阵与邻接表)》

《C++ 拓扑排序(邻接矩阵与邻接表)》

《C++ 拓扑排序(邻接矩阵与邻接表)》 以下为代码(尽可能的详细注释了):

#include <iostream>
#include <vector>                                                                //动态数组保存拓扑排序序列
using std::cin;
using std::cout;
using std::endl;
using std::vector;
#define max 100

/*邻接矩阵算法*/	
/*思路:初始化后输入顶点间关系,有关系的从始点到终点为1,循环检测,将全为零的一行输出,后将具有相关顶点的
偶序置为零,然后循环检查,最终输出全部顶点,若输出个数与顶点数不同,则说明有环*/
int main() {
	int vertexnum=0,edgenum=0;
	int i=0,j=0,count=0;	
	vector<int>order;							//动态数组order用于储存拓扑排序序列 
	int edge[max][max];
	int visit[max];
	cout<<"请输入顶点数和边数"<<endl;
	cin>>vertexnum>>edgenum;		
	for(i=0;i<vertexnum;i++){						//初始化 
		for(j=0;j<vertexnum;j++)
			edge[i][j]=0;	
		visit[i]=0;
	}
	while(edgenum--)						        //初始化顶点间的关系 
	{
		cout<<"请输入顶点i与j(i表示始点,j表示终点):"<<endl;
		cin>>i>>j;
		edge[i][j]=1;
	}
	cout<<"以每个顶点为出度的邻接矩阵表示为:"<<endl; 
	cout<<'\t';
	for(i=0;i<vertexnum;i++)						//输出初始化后的邻接矩阵 (邻接矩阵每一行代表以一个顶点为出度,它与其它顶点的关系) 
		cout<<i<<'\t';
	cout<<endl;
	for(i=0;i<vertexnum;i++){			
		cout<<i<<'\t';
		for(j=0;j<vertexnum;j++)
		{
			cout<<edge[i][j]<<'\t';	
		}
		cout<<endl;
	}
	cout<<endl;
	cout<<"以每个顶点为入度的邻接矩阵表示为:"<<endl;
	cout<<'\t';
	for(i=0;i<vertexnum;i++)						//输出初始化后的邻接矩阵 (邻接矩阵每一行代表以一个顶点为出度,它与其它顶点的关系) 
		cout<<i<<'\t';
	cout<<endl;
	for(i=0;i<vertexnum;i++){			
		cout<<i<<'\t';
		for(j=0;j<vertexnum;j++)
		{
			cout<<edge[j][i]<<'\t';	
		}
		cout<<endl;
	}
	int targetVertex,check,judge=0;
	for(i=0;i<vertexnum;i++)						//大循环,检查所有输入的顶点 
	{	
		for(targetVertex=0;targetVertex<vertexnum;targetVertex++)	
		{
			for(j=0;j<vertexnum;j++)
			{
				if(targetVertex==j)				//若碰到自身,则跳过(自身和自身间不存在关系) 
					continue;
				if(visit[targetVertex]||edge[j][targetVertex])	//若k为已输出顶点或有以k为入度的边存在 
				{
					judge=1;				// 将检查值设为1,方便后面直接跳过 
					break;
				}
			}	
			if(judge)						//若碰上judge为1的情况(即上面的情况),跳过此次循环(因为已经处理过或不满足入度为1的条件) 
			{	
				judge=0;					//并初始化judge值,方便下次判断 
				continue;
			}
			else							//否则即为未输出且入度为零的顶点 
			{
				order.push_back(targetVertex);			//加入拓扑排序数组 
				count++;
				visit[targetVertex]=1;				//设置为已输出 
				break;						//跳出 
			}
		}
		for(check=0;check<vertexnum;check++)		                //将所有以k顶点为出度的相关边设为0(即删除k)	
			edge[targetVertex][check]=0;
		judge=0;							//判断值初始化 
	}
	if(count!=vertexnum)
		cout<<"图中存在环,其它顶点的拓扑排序为:"<<endl;
	else
		cout<<"顶点的拓扑排序为:"<<endl;
	for(i=0;i<order.size();i++)					        //输出拓扑排序  
	{
		cout<<order[i]<<'\t';
	}
	return 0;
}

以上即为邻接矩阵方法。

2.邻接表

        思路:

        与邻接矩阵大抵相同,创建邻接表,不过我用邻接表来储存以每个顶点为入度,与其相关的节点。则这里是由firstedge(指向边表的指针)指针指向边表,那么就可以得出,若firstedge指向NULL,说明此顶点没有入度相关的节点,则输出并将其它顶点内储存此顶点的节点删去,不过这里的一个问题就是当顶点a输出后,它在其它顶点的边表中的位置,若在firstedge后,则要将firstedge指向它的后一个,然后删除,若是边表中的第二个或以后,则要用另一指针指向a节点的前驱,然后这一指针的next指向a节点的next节点(因为firstedge指针在顶点表中,所以要在边表内创建另一个指针来用)。 还是以邻接矩阵里的图为基础:
《C++ 拓扑排序(邻接矩阵与邻接表)》 则其邻接表为(以顶点为入边时):
《C++ 拓扑排序(邻接矩阵与邻接表)》 那么首先输出0,顶点0设为已输出,之后删除顶点1,3,4里的0,在第二次循环时,顶点3指向了NULL,所以输出顶点3,依次往下循环进行。 知道了思路,那么邻接表的代码也就能够写出来了,如下:

#include <iostream>
using std::cin;
using std::cout;
using std::endl;
#define max 100
typedef struct Edgenode												//边表结构体 
{
	int adjvex;														//储存边表结点 
	int wight;														//储存顶点间权值(可能用不上) 
	Edgenode *next;													//连接边表结点的指针 
}Edgenode;

typedef struct 														//顶点表结构体 
{
	Edgenode *firstedge;											//顶点表与边表的连接指针 
	bool x;															//判断某一顶点是否已经输出,输出了为ture,判断时自动跳过 
	int data;														//储存顶点值 
}Vertexnode;
typedef Vertexnode AdjList[max];									//顶点表数组,储存顶点
 
typedef struct														//图结构体 
{
	int vertexnum;													//储存顶点个数 
	int edgenum;													//储存边的条数 
	AdjList adjlist;												//创建adjlist数组,用来保存顶点表的顶点 
}Graph;

void create_graph(Graph *G)
{
	cout<<"input the vertexnum and edgenum"<<endl;					//输入顶点数与边数 
	cin>>G->vertexnum>>G->edgenum;
	int i,j;
	Edgenode *e;					
	for(i=0;i<G->vertexnum;i++)										//初始化 
	{
		G->adjlist[i].data=i;
		G->adjlist[i].firstedge=NULL;
		G->adjlist[i].x=false;
	}
	
	while(G->edgenum--)												//初始化输入顶点间关系 
	{
		cout<<"input the two points which be connected by a line"<<endl;
		cin>>i>>j;
		e=new Edgenode;
		e->adjvex=i;
		e->next=G->adjlist[j].firstedge;
		G->adjlist[j].firstedge=e;
	}
}

void order(Graph *G)												//拓扑排序函数 
{
	
	int count,Count=0;												//count用于大循环,检查顶点表数组的每一个已保存顶点,Count用于每次输出一个顶点加一,最后检查count与Count是否相等 ,若相等,说明有环 
	for(count=0;count<G->vertexnum;count++)
	{
		int count1=0;
		if(!G->adjlist[count].x&&!G->adjlist[count].firstedge)		//如果被检测顶点还未输出(x为flase)并且入度为零(firstedge后无结点),则输出并删去其它顶点里拥有它的节点 
		{
			cout<<G->adjlist[count].data;
			G->adjlist[count].x=true;								//将其设置为已输出 
			Count++;
			
			Edgenode *e;
			for(;count1<G->vertexnum;count1++)						//检查其它顶点里是否有已输出顶点的相关节点 
			{
				if(count1==count||G->adjlist[count1].x)				//如果是输出的顶点或已输出的顶点,则跳过(因为已经输出了) 
					continue;
				e=G->adjlist[count1].firstedge;
				while(e)											//循环检查一个顶点的边表 
				{	
					if(e==G->adjlist[count1].firstedge&&e->adjvex==G->adjlist[count].data)//如果边表第一个便是已输出顶点的节点 
					{
						G->adjlist[count1].firstedge=e->next;
						delete e;									//删去 
						e=NULL;										//此边表已经不会再出现第二个已输出顶点的节点,while循环结束 
						continue;
					}
					if(e->next&&e->next->adjvex==G->adjlist[count].data)//如果已输出节点在边表中第二个或后面的 
					{
						Edgenode *e1;
						e1=e->next;
						e->next=e1->next;							//删去 
						delete e1;
					}
					e=e->next;										//往下检查 
				}
				
			}
		}
		
	}
}

void show(Graph *G)													//显示函数,显示每个顶点的邻接表(但此邻接表储存的是每个顶点的入度节点,不是出度节点) 
{
	int count;
	Edgenode *e;
	for(count=0;count<G->vertexnum;count++)
	{
		e=G->adjlist[count].firstedge;
		cout<<G->adjlist[count].data<<'\t'<<'\t';
		while(e)
		{
			cout<<e->adjvex<<'\t';
			e=e->next;
		}
		cout<<endl;
	}
}

int main()
{
	Graph *G;
	G=new Graph;
	create_graph(G);
	show(G);
	cout<<endl<<"begin to order:"<<endl;
	order(G);
	cout<<endl<<"after ordering:<<endl";
	show(G);
	return 0;
}

以上便是邻接表方法的实现。 两种方法的思路差不多,都是先保留顶点间关系,然后在输出后紧接着更新整个表,确保进行下一次循环。 如有疑问,欢迎在评论区提问。

    原文作者:拓扑排序
    原文地址: https://blog.csdn.net/a844761860/article/details/79499327
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞