图论 —— AOV 网与拓扑排序

2024-06-17 22:08
文章标签 图论 排序 拓扑 aov

本文主要是介绍图论 —— AOV 网与拓扑排序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【AOV网】

日常生活中,一项大的工程可以看作是由若干个子工程组成的集合,这些子工程之间必定存在一定的先后顺序,即某些子工程必须在其他的一些子工程完成后才能开始。

我们用有向图来表现子工程之间的先后关系,子工程之间的先后关系为有向边,这种有向图称为“顶点活动网络”,即:AOV 网。

一个有向无环图称为无环图(Directed Acyclic Graph),简称 DAG 图,因此一个 AOV 网必定是一个有向无环图,即不带有回路。与 DAG 不同的是,AOV 的活动都表示在边上。

如下图,共有11项活动(11条边),9个事件(9个点),只有一个源点(入度为零的点)和一个汇点(一个出度为零的点),路径的长度是边上活动耗费的时间,则可定义概念——关键路径:从源点到汇点的最长路径的长度,下图中的:1-2-5-7-9 即为一条关键路径,权值的和为18。

【基本概念】

  1. 活动:子工程组成的集合,每个子工程即为一个活动。
  2. 前驱活动:有向边起点的活动称为终点的前驱活动(只有当一个活动的前驱全部都完成后,这个活动才能进行)。
  3. 后继活动:有向边终点的活动称为起点的后继活动。
  4. 拓扑排序:将 AOV 网中所有活动排成一个序列,使得每个活动的前驱活动都排在该活动的前面。
  5. 拓扑序列:经过拓扑排序后得到的活动序列(一个 AOV 网的拓扑序列不是唯一的)。
  6. 关键路径:AOV 网中从源点到汇点的最长路径的长度(一个 AOV 网中的拓扑排序不是唯一的)。

【拓扑排序思想】

  1. 选择一个入度为 0 的顶点并输出。
  2. 从 AOV 网中删除此顶点及以此顶点为起点的所有关联边。
  3. 重复上述两步,直到不存在入度为 0 的顶点为止。
  4. 若输出的顶点数小于 AOV 网中的顶点数,则说明 AOV 网中回路,不是一个标准的 AOV 网。

【算法分析】

以下图为例

开始时,只有 A 入度为 0,A 入栈。

栈:A

栈顶元素 A 出栈,输出 A,A 的后继节点 B、C 入度减 1(相当于删除 A 的所有关联边)。

栈:空

拓扑序列:A

B、C 入度都为 0,依次将  B、C 入栈

栈:BC(入栈顺序不唯一)

拓扑序列:A

栈顶元素 C 出栈,输出 C,C 的后继结点 D 入度减 1(相当于删除 C 的所有关联边)。

栈:B

拓扑序列:AC

栈顶元素 B 出栈,输出 B,B 的后继结点 D 入度减 1(相当于删除 B 的所有关联边),此时 D 的入度为 0,入栈。

栈:D

拓扑序列:ACB

栈顶元素 D 出栈,输出 D。

栈:空

拓扑序列:ACBD(不唯一)

【AOV 网的判定】

有时,给出一个 n 个点 m 条边的有向图,需要判定图是否是 AOV 网,也即判断图是否可以进行拓扑排序。

一个有向图无法进行拓扑排序时只有一种情况:该有向图中存在环。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<vector>
#define N 10001
using namespace std;
int n,m;
int in[N];//节点入度
vector<int> G[N];//G[i]表示i节点所指向的所有其他点
bool judgeTopsort()//判断该图是否可拓扑排序
{stack<int> S;int cnt=0;//记录可拆解的点数目for(int i=1;i<=n;i++)//枚举编号从1到n的点if(in[i]==0)//入度为0,入栈S.push(i);while(!S.empty()) {int x=S.top();//取栈顶元素S.pop();cnt++;//可拆点数+1for(int i=0;i<G[x].size();i++){int y=G[x][i];in[y]--;//入度减一if(in[y]==0)//入度为0,出栈S.push(y);}}if(cnt==n)//AOV网点数等于图的点数,不存在环,可进行拓扑排序return true;else//AOV网点数等于图的点数,存在环,不可进行拓扑排序return false;
}
int main()
{while(scanf("%d%d",&n,&m)==2&&n){memset(in,0,sizeof(in));for(int i=1;i<=n;i++)G[i].clear();while(m--) {int x,y;scanf("%d%d",&x,&y);G[x].push_back(y);in[y]++;}printf("%s\n",judgeTopsort()?"YES":"NO");}return 0;
}

 【拓扑排序的输出】

1.输出任意一条拓扑排序结果

当给出一 n 个点 m 条边的有向边时,要输出一个可行的点的拓扑序列,此时可根据上述的 AOV 网判定代码,修改后存储路径输出即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<vector>
#define N 10001
using namespace std;
int n,m;
int in[N];//节点入度
int path[N];//存储路径
vector<int> G[N];//G[i]表示i节点所指向的所有其他点
void Topsort()//拓扑排序
{stack<int> S;int cnt=0;//记录可拆解的点数目for(int i=1;i<=n;i++)//枚举编号从1到n的点if(in[i]==0)//入度为0,入栈S.push(i);while(!S.empty()) {int x=S.top();//取栈顶元素S.pop();path[++cnt]=x;//存储可拆点for(int i=0;i<G[x].size();i++){int y=G[x][i];in[y]--;//入度减一if(in[y]==0)//入度为0,出栈S.push(y);}}
}
int main()
{while(scanf("%d%d",&n,&m)==2&&n){memset(in,0,sizeof(in));for(int i=1;i<=n;i++)G[i].clear();while(m--) {int x,y;scanf("%d%d",&x,&y);G[x].push_back(y);in[y]++;}Topsort();for(int i=1;i<=n;i++)printf("%d ",path[i]);printf("\n");}return 0;
}

2.输出按字典序最小的拓扑排序结果

求字典序最小的拓扑序列时,要用优先队列,且是最小值优先的队列,其大致思想是队列 Q 总是将当前在入度为 0 的最小节点优先取出,从而保证了字典序最小。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#define N 10001
using namespace std;
int n,m;
int in[N];//节点入度
int path[N];//存储路径
vector<int> G[N];//G[i]表示i节点所指向的所有其他点
void Topsort()//拓扑排序
{priority_queue< int,vector<int>,greater<int> > Q;//最小值先出列int cnt=0;//记录可拆解的点数目for(int i=1;i<=n;i++)//枚举编号从1到n的点if(in[i]==0)//入度为0,入列Q.push(i);while(!Q.empty()) {int x=Q.top();//队列首元素Q.pop();path[++cnt]=x;//存储可拆点for(int i=0;i<G[x].size();i++){int y=G[x][i];in[y]--;//入度减一if(in[y]==0)//入度为0,出列Q.push(y);}}
}
int main()
{while(scanf("%d%d",&n,&m)!=EOF&&n){memset(in,0,sizeof(in));for(int i=1;i<=n;i++)G[i].clear();for(int i=1;i<=m;i++){int x,y;scanf("%d%d",&x,&y);G[x].push_back(y);in[y]++;}Topsort();for(int i=1;i<=n;i++)printf("%d ",path[i]);printf("\n");}return 0;
}

【例题】

1.拓扑排序的判定

  1. Legal or Not(HDU-3342):点击这里
  2. Triangle LOVE(HDU-4324):点击这里

2.输出拓扑排序结果

  1. Genealogical tree(POJ-2367)(输出任一条拓扑排序结果):点击这里
  2. 确定比赛名次(HDU-1285)(输出字典序最小的拓扑排序结果):点击这里
  3. Following Orders(POJ-1270)(按字典序输出所有拓扑排序结果):点击这里

3.拓扑排序的应用

  1. 烦人的幻灯片(信息奥赛一本通-T1395)(拓扑排序思想):点击这里
  2. 家谱树(信息奥赛一本通-T1351)(构造拓扑排序):点击这里
  3. 奖金(信息奥赛一本通-T1352)(构造拓扑排序):点击这里
  4. Cow Traffic(POJ-3272)(双向拓扑排序):点击这里
  5. Ponds(HDU-5438)(拓扑排序删点+dfs):点击这里
  6. 病毒(信息奥赛一本通-T1396)(给出字典序,找出拓扑排序关系):点击这里
  7. 处女座的比赛资格(2019牛客寒假算法基础集训营 Day3-B)(拓扑排序求最短路):点击这里
  8. Sorting It All Out(POJ-1094)(拓扑排序+差分约束系统):点击这里
  9. Labeling Balls(POJ-3687)(拓扑排序+逆向思维):点击这里

这篇关于图论 —— AOV 网与拓扑排序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1070588

相关文章

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Python中lambda排序的六种方法

《Python中lambda排序的六种方法》本文主要介绍了Python中使用lambda函数进行排序的六种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录1.对单个变量进行排序2. 对多个变量进行排序3. 降序排列4. 单独降序1.对单个变量进行排序

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

hdu 1285(拓扑排序)

题意: 给各个队间的胜负关系,让排名次,名词相同按从小到大排。 解析: 拓扑排序是应用于有向无回路图(Direct Acyclic Graph,简称DAG)上的一种排序方式,对一个有向无回路图进行拓扑排序后,所有的顶点形成一个序列,对所有边(u,v),满足u 在v 的前面。该序列说明了顶点表示的事件或状态发生的整体顺序。比较经典的是在工程活动上,某些工程完成后,另一些工程才能继续,此时

《数据结构(C语言版)第二版》第八章-排序(8.3-交换排序、8.4-选择排序)

8.3 交换排序 8.3.1 冒泡排序 【算法特点】 (1) 稳定排序。 (2) 可用于链式存储结构。 (3) 移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,n较大时, 此算法不宜采用。 #include <stdio.h>#include <stdlib.h>#define MAXSIZE 26typedef int KeyType;typedef char In

【软考】希尔排序算法分析

目录 1. c代码2. 运行截图3. 运行解析 1. c代码 #include <stdio.h>#include <stdlib.h> void shellSort(int data[], int n){// 划分的数组,例如8个数则为[4, 2, 1]int *delta;int k;// i控制delta的轮次int i;// 临时变量,换值int temp;in

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图

鸡尾酒排序算法

目录 引言 一、概念 二、算法思想 三、图例解释 1.采用冒泡排序:   2.采用鸡尾酒排序:  3.对比总结 四、算法实现  1.代码实现  2.运行结果 3.代码解释   五、总结 引言 鸡尾酒排序(Cocktail Sort),也被称为双向冒泡排序,是一种改进的冒泡排序算法。它在冒泡排序的基础上进行了优化,通过双向遍历来减少排序时间。今天我们将学习如何在C