博主新手,最近在学习拓扑排序,查阅网上资料发现有些难懂且很多方法的代码不尽相同,于是就想着试试自己能不能写出来,经过一段时间的尝试,最终实现了拓扑排序,现在将自己的想法发表出来,可能有些瑕疵,希望各位看后不吝赐教(不知道是不是就是网上的方法,之前看网上的没看懂……)。
1.邻接矩阵方法
思路:
创建邻接矩阵,输入顶点数和边数,初始化所有顶点间无关(用0表示),然后手动输入有关顶点(用1表示)。 依次排查所有值,那我们可以发现:若顶点a与b之间存在关系,则有order[a][b]=1(order数组即邻接矩阵的数组)且表明a是始点,b是终点。则我们要找的即为对于a的order数组内全为0(即a只可能有出度,没有入度)。 例: 有如下图
则输出结果为:
以下为代码(尽可能的详细注释了):
#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指针在顶点表中,所以要在边表内创建另一个指针来用)。 还是以邻接矩阵里的图为基础:
则其邻接表为(以顶点为入边时):
那么首先输出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;
}
以上便是邻接表方法的实现。 两种方法的思路差不多,都是先保留顶点间关系,然后在输出后紧接着更新整个表,确保进行下一次循环。 如有疑问,欢迎在评论区提问。