算法总结2——拓扑排序

一.概念部分

1.拓扑排序介绍

        将一个有向无环图(Directed Acyclic Graph简称DAG)进行排序进而得到一个有序的线性序列,就是拓扑排列。在C++语言中,我们可以使用邻接表来表示有向图。

       例如,一个项目包括A、B、C、D四个子部分来完成,并且A依赖于B和D,C依赖于D。现在要制定一个计划,写出A、B、C、D的执行顺序。这时,就可以利用到拓扑排序,它就是用来确定事物发生的顺序的。

2.有向无环图介绍

        一个较大的工程往往被划分成许多子工程,我们把这些子工程称作活动(activity)。在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是它的所有前序子工程的结束为先决条件的,但有些子工程没有先决条件,可以安排在任何时间开始。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。一个AOV网就是一个有向无环图。

3.邻接表介绍

        邻接表是图的一种链式存储方法,其数据结构包括两部分:节点和邻接点。

(1)无向图的邻接表
图表如下:

解释:

• 节点V0的邻接点是节点V1,V2 ,V3,其邻接点的存储下标为1,2,3,按照头插法(逆序)将其放入节点V0后面的单链表中;

• 节点V1 的邻接点是节点V0 ,V2,其邻接点的存储下标为0,2,将其放入节点V1 后面的单链表中;

• 节点V2 的邻接点是节点V0,V1,V3,其邻接点的存储下标为0,1,3,将其放入节点V2 后面的单链表中;

• 节点V3 的邻接点是节点V0,V,2,其邻接点的存储下标为0,2,将其放入节点V3 后面的单链表中。

特点如下:

• 如果无向图有n 个节点、e 条边,则节点表有n 个节点,邻接点表有2e 个节点。

• 节点的度为该节点后面单链表中的节点数。

(2)有向图的邻接表(出弧)
图表如下:

   解释:

• 节点V0 的邻接点(只看出边,即出弧)是节点V3,其邻接点的存储下标为3,按照头插法(逆

序)将其放入节点V0 后面的单链表中;

• 节点V1 的邻接点是节点V0 ,V2,其邻接点的存储下标为0,2,将其放入节点V1后面的单链表中;

• 节点V2 的邻接点是节点V0,V1 ,其邻接点的存储下标为0,1,按头插法将其放入节点V0 后面的单链表中;

• 节点V3 没有邻接点,其后面的单链表为空。

特点如下:

• 如果有向图有n 个节点、e 条边,则节点表有n 个节点,邻接点表有e 个节点。

• 节点的出度为该节点后面单链表中的节点数。

(3)有向图的逆邻接表(入弧)
图表如下:

解释:

• 节点V0  的逆邻接点(只看入边,即入弧)是节点V1,V2,其邻接点的存储下标是1,2,其放入

节点V0 后面的单链表中;

• 节点V1  的逆邻接点是节点V2  ,其邻接点的存储下标为2,将其放入节点V1 后面的单链表中;

• 节点V2  的逆邻接点是V1,其邻接点的存储下标为1,按照头插法将其放入节点V2 后面的单链表中;

• 节点V3  的逆邻接点是节点V0  ,其邻接点的存储下标为0,将其放入节点V3 后面的单链表中;

特点如下:

• 如果有向图有n 个节点、e 条边,则节点表有n 个节点,邻接点表有e 个节点。

• 节点的入度为该节点后面的单链表中的节点数。

(4)邻接表的代码实现
//创建有向图的邻接表
#include <iostream>
using namespace std;
const int MaxVnum=100;//顶点数最大值
 
typedef char VexType;//顶点的数据类型为字符型
typedef struct AdjNode{ //定义邻接点类型
	int v; //邻接点下标
	struct AdjNode *next; //指向下一个邻接点
}AdjNode;
 
typedef struct VexNode{ //定义顶点类型
	VexType data; // VexType为顶点的数据类型,根据需要定义
	AdjNode *first; //指向第一个邻接点
}VexNode;
 
typedef struct{//定义邻接表类型
    VexNode  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 *s;
    s=new AdjNode;
    s->v=j;
    s->next=G.Vex[i].first;
    G.Vex[i].first=s;
}
 
void printg(ALGragh G)//输出邻接表
{
   cout<<"----------邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.Vex[i].first;
       cout<<cout<<G.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;
    for(i=0;i<G.vexnum;i++)
        G.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++;//本次输入不算
        }
    }
}
 
int main()
{
    ALGragh G;
    CreateALGraph(G);//创建有向图邻接表
    printg(G);//输出邻接表
    return 0;
}

4.拓扑排序的实现

(1)步骤:

1. 构造一个队列Q(queue) 和 拓扑排序的结果队列T(topological); 

2. 把所有没有依赖顶点的节点放入Q; 

3. 当Q还有顶点的时候,执行下面步骤: 

3.1 从Q中取出一个顶点n(将n从Q中删掉),并放入T(将n加入到结果集中); 

3.2 对n每一个邻接点m(n是起点,m是终点); 

3.2.1 去掉边<n,m>; 

3.2.2 如果m没有依赖顶点,则把m放入Q; 

注:顶点A没有依赖顶点,是指不存在以A为终点的边。

(2)代码实现
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
 
class Graph {
private:
    int V;
    vector<vector<int>> adj;
 
public:
    Graph(int v) : V(v), adj(v) {}
 
    void addEdge(int u, int v) {
        adj[u].push_back(v);
    }
 
    void topologicalSortUtil(int v, vector<bool>& visited, stack<int>& stk) {
        visited[v] = true;
 
        for (int i : adj[v]) {
            if (!visited[i]) {
                topologicalSortUtil(i, visited, stk);
            }
        }
 
        stk.push(v);
    }
 
    void topologicalSort() {
        vector<bool> visited(V, false);
        stack<int> stk;
 
        for (int i = 0; i < V; i++) {
            if (!visited[i]) {
                topologicalSortUtil(i, visited, stk);
            }
        }
 
        cout << "拓扑排序结果:" << endl;
        while (!stk.empty()) {
            cout << stk.top() << " ";
            stk.pop();
        }
        cout << endl;
    }
};
 
int main() {
    int V = 6; // 图的节点数
    Graph g(V);
 
    // 添加图的边
    g.addEdge(5, 2);
    g.addEdge(5, 0);
    g.addEdge(4, 0);
    g.addEdge(4, 1);
    g.addEdge(2, 3);
    g.addEdge(3, 1);
 
    g.topologicalSort();
 
    return 0;
}

二.实例

 New Gae

解题思路:关键信息为 A 赢 B,B 赢 C,则排名为 A → B → C。

题解:

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+100;
const int inf=0x3f3f3f3f;
inline int rd() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch>'9') {
        if (ch == '-')f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}
int t;
int n,m;
vector<int>ve[maxn],res;
int a,b,in[maxn];

signed main() {
    t=rd();
    while(t--) {
        for(int i=1; i<=n; i++)ve[i].clear();
        memset(in,0,sizeof in);
        n=rd();
        m=rd();
        for(int i=1; i<=m; i++) {
            a=rd();
            b=rd();
            ve[a].push_back(b);
            in[b]++;
        }
        priority_queue<int,vector<int>,greater<int> >q;
        for(int i=1; i<=n; i++) {
            if(!in[i]) {
                q.push(i);
            }
        }
        while(!q.empty()) {
            int u=q.top();
            q.pop();

            res.push_back(u);
            for(int i=0; i<ve[u].size(); i++) {
                int v=ve[u][i];
                in[v]--;
                if(!in[v]) {
                    q.push(v);
                }
            }
        }
        for(int i=0; i<res.size(); i++) {
            if(i!=0)printf(" ");
            printf("%lld",res[i]);
        }
        printf("\n");
        res.clear();
    }
    return 0;
}

  • 53
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值