图之拓扑排序

简介: 图之拓扑排序

知识点概述


20210528182750898.png

  1. 如果有环的话,那么这个工程将无法完成比如 1->2->3->1 如果从1开始做,那3没完成就不能做1, 而不去做1就无法完成3.
  2. 方向是表示任务之间的优先级.


20210528182829541.png

20210528182854508.png


20210528182912546.png

如果中间有环,则环上的每个节点入度必定>=1,则不会进入拓扑序列中.

20210528183017784.png

算法步骤

20210528183046935.png

邻接表实现

#include<iostream> 
#include<stack>
using namespace std;
const int MaxVnum=100;//顶点数最大值
int indegree[MaxVnum];//入度数组
typedef string VexType;//顶点的数据类型为字符型
typedef struct AdjNode{ //定义邻接点类型
  int v; //邻接点下标
  struct AdjNode *next; //指向下一个邻接点
}AdjNode;
typedef struct VexNode{ //定义顶点类型
  VexType data; // VexType为顶点的数据类型,根据需要定义
  AdjNode *first; //指向第一个邻接点
}VexNode;
typedef struct{//包含邻接表和逆邻接表
    VexNode Vex[MaxVnum]; //定义邻接表
    VexNode converse_Vex[MaxVnum]; //定义逆邻接表
    int vexnum,edgenum; //顶点数,边数
}ALGragh;
int locatevex(ALGragh G,VexType x)
{
    for(int i=0;i<G.vexnum;i++)//查找顶点信息的下标
       if(x==G.Vex[i].data)
        return i;
    return -1;//没找到
}
void insertedge(ALGragh &G,int i,int j)//插入一条边
{
    AdjNode *s1,*s2;
    s1=new AdjNode;//创建邻接表结点
    s1->v=j;
    s1->next=G.Vex[i].first;
    G.Vex[i].first=s1;
    s2=new AdjNode;//创建逆邻接表结点
    s2->v=i;
    s2->next=G.converse_Vex[j].first;
    G.converse_Vex[j].first=s2;
}
void printg(ALGragh G)//输出邻接表
{
   cout<<"----------邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.Vex[i].first;
       cout<<G.Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<t->v<<"]  ";
           t=t->next;
       }
       cout<<endl;
   }
   cout<<"----------逆邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.converse_Vex[i].first;
       cout<<G.converse_Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<t->v<<"]  ";
           t=t->next;
       }
       cout<<endl;
   }
}
void CreateALGraph(ALGragh &G)//创建有向图的邻接表和逆邻接表
{
    int i,j;
    VexType u,v;
    cout<<"请输入顶点数和边数:"<<endl;
    cin>>G.vexnum>>G.edgenum;
    cout << "请输入顶点信息:"<<endl;
    for(i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
    {
        cin>>G.Vex[i].data;
        G.converse_Vex[i].data=G.Vex[i].data;
        G.Vex[i].first=NULL;
        G.converse_Vex[i].first=NULL;
    }
    cout<<"请依次输入每条边的两个顶点u,v"<<endl;
    while(G.edgenum--)
    {
        cin>>u>>v;
        i=locatevex(G,u);//查找顶点u的存储下标
        j=locatevex(G,v);//查找顶点v的存储下标
        if(i!=-1&&j!=-1)
            insertedge(G,i,j);
        else
        {
           cout << "输入顶点信息错!请重新输入!"<<endl;
           G.edgenum++;//本次输入不算
        }
    }
}
void FindInDegree(ALGragh G){//求出各顶点的入度,存入数组indegree
  int i,count;
  for(int i = 0; i <G.vexnum; i++){
    count = 0;
    AdjNode *p = G.converse_Vex[i].first;
    while(p){
      p = p->next;
      count++;
    }
    indegree[i]  = count;//将 入度存入数组 
  } 
  cout<<"入度数组为:"<<endl;
  for(int i = 0; i < G.vexnum ;i++){
    cout<<indegree[i]<<"\t";
  } 
  cout<<endl;
}
bool TopologicalSort(ALGragh G,int topo[]){
  stack<int> s;//因为删除一个顶点后可能会出现多个顶点的 入度为0,但为了保证拓扑序列唯一性,得依赖于栈. 
  FindInDegree(G);
  for(int i = 0; i < G.vexnum; i++){
    if(!indegree[i]){//入度为0的进栈. 
      s.push(i); 
    }
  }
  int x,m;
  m = 0;//控制拓扑序列下标的移动 
  while(!s.empty()){//将图中的点放入到拓扑序列中 
    x = s.top();
    s.pop();
    topo[m] = x;
    m++;
    AdjNode *p = G.Vex[x].first;//p指向i的第一个邻接点
    while(p){//让x的所有邻接点入度-1 
      int k = p->v;
      indegree[k]--;
      if(!indegree[k]){//入度为0则进栈 
        s.push(k); 
      }
      p = p->next;
    } 
  }
  if(m<G.vexnum){//判断是否有回路 
    return false;
  }else{
    return true;
  }
}
int main()
{
  ALGragh G;
  int *topo = new int[G.vexnum];//拓扑数组中存放的是节点的下标。 
  CreateALGraph(G);//创建有向图的邻接表和逆邻接表. 
  printg(G);//打印邻接表和逆邻接表. 
  if(TopologicalSort(G,topo)){
    cout<<"拓扑序列为:"<<endl;
    for(int i = 0; i < G.vexnum; i++){
      int index = topo[i];
      cout<<G.Vex[index].data<<"\t";
    } 
  }else{
    cout<<"该图有环,无拓扑序列!"<<endl; 
  }
  return 0;
}
//1 2
//1 3
//3 2
//3 4
//2 6
//2 4
//6 5
//4 5

运行结果:

20210528183446647.png

20210528183452848.png

算法分析

20210528184024602.png

邻接矩阵实现

#include<iostream>
#include<stack>
#include<string.h>
using namespace std;
const int maxn = 105;
int map[maxn][maxn],indegree[maxn],topo[maxn];
int n,m;
stack<int> s;
void TopoSort(){//拓扑排序 
  int cnt = 0;
  for(int i = 1; i <= n; i++){//入度为0入栈. 
    if(indegree[i]==0){
      s.push(i);
    }
  } 
  while(!s.empty()){
    int u = s.top();
    s.pop();
    topo[cnt++] = u;
    for(int j = 1; j <= n; j++){
      if(map[u][j]){
        if(--indegree[j]==0){
          s.push(j);
        }
      }
    }
  } 
}
int main(){
  cin>>n>>m;
  memset(map,0,sizeof(map));
  memset(indegree,0,sizeof(indegree));
  for(int i = 1;i<=m; i++){
    int u,v;
    cin>>u>>v;
    map[u][v] = 1;
    indegree[v]++;
  }
  TopoSort();
  for(int i = 0; i < n;i++){
    cout<<topo[i]<<"\t";
  }
  cout<<endl;
  return 0;
}

运行结果:

20210528183622822.png

20210528184117738.jpg


第二种实现较为简洁,在读入边的时候就开始初始化入度数组.刷题常用,但时间复杂度为O(n^2),可以使用链式前向星继续优化,则时间复杂度可变为O(n+e)


相关文章
|
8月前
|
算法
图的应用(最小生成树,最短路径,有向无环图)
图的应用(最小生成树,最短路径,有向无环图
60 0
|
8月前
|
NoSQL 容器 消息中间件
图、图的遍历及图的拓扑排序
图、图的遍历及图的拓扑排序
|
存储 算法
图的广度优先遍历和深度优先遍历
本文主要是学习图的广度优先遍历和图的深度优先遍历
209 1
|
存储 索引
树与图中的dfs和bfs—— AcWing 846. 树的重心 AcWing 847. 图中点的层次
树与图中的dfs和bfs—— AcWing 846. 树的重心 AcWing 847. 图中点的层次
84 0
|
存储 算法 搜索推荐
C++实现图 - 05 拓扑排序
今天来讲另一个非常重要的知识点 —— 拓扑排序。咋一看好像是一个排序算法,然而它和排序扯不上半点关系,它可以用于判断我们的图中是否存在有向环。
660 0
C++实现图 - 05 拓扑排序
|
算法
回溯法——图m染色问题
回溯法——图m染色问题
226 0
|
存储 算法 C++
C++实现图 - 03 最小生成树
这一讲来讲一个图中非常重要的内容 —— 最小生成树,在此之前我们先来回顾一下生成树的概念。
243 0
C++实现图 - 03 最小生成树
|
存储
【32. 图中的层次(图的广度优先遍历)】
思路 - 因为所有的`边长都为1`,所以可以使用`宽度优先搜索`的思想,每当队列pop出一个元素时,将其距离为1的节点都加到队列中(层次遍历思想) - `st[]`标记各个节点有没有走过,`d[]`保存1号节点到各个节点的距离,初始时都为-1。
152 0
【32. 图中的层次(图的广度优先遍历)】
|
算法
树与图的广度优先遍历
复习acwing算法基础课的内容,本篇为讲解基础算法:树与图的广度优先遍历,关于时间复杂度:目前博主不太会计算,先鸽了,日后一定补上。
108 0
树与图的广度优先遍历
065.图的深度优先遍利
065.图的深度优先遍利
81 0