判断有向图是否有圈
1. 拓扑排序
拓扑排序是对有向无圈图的顶点的一种排序:如果存在一条vi到vj的路径,则vj排在vi后面(因为只要满足这个特性就是拓扑序列,所以它不一定是唯一的)。比如在众多的大学课程中,有些课有先修课,我们可以将其抽象为拓扑排序,有向边(v, w)表明课程v必须安排在w之前,否则课程w就无法进行。我们可以想象所有的课程以及课与课之间的关系可以用一个图来表示,而拓扑排序就可以知道课程安排的顺序。然而,如果图存在圈,就没有拓扑序列。比如如果要上课程A必须上课程B,要上课程B必须上课程C,而要上课程C必须上课程A,你将无法选择哪门课上前面。虽然有圈图没有拓扑序列,但是我们可以利用拓扑排序的算法来判断一个有向图是否有圈。
算法描述如下:
1. 将所有入度为0的顶点放入队列; 2. 每次从队列中弹出一个顶点v(即访问到该顶点,counter++)直到队列为空; 3. 遍历所有与v相连的顶点,将相邻顶点的入度减一(删边); 4. 若某个相邻顶点入度为0,将其放入队列中,返回第2步; 5. 若counter == N也就是所有顶点均访问到,说明排序完成。否则,说明总 有顶点入度不为0,没有放入队列中,即该有向图有圈。
代码如下:
#include <cstdio>
#include <queue>
using namespace std;
const int MAX_N = 110;
vector<int> graph[MAX_N]; //邻接表存储图
int indegree[MAX_N]; //入度
int n; //顶点数
int m; //边数
bool TopSort()
{
queue<int> que;
int counter = 0; //记录访问到的顶点数
for (int i = 1; i <= n; i++)
{
if (indegree[i] == 0) //将入度为0的顶点全部放入队列
que.push(i);
}
while (!que.empty())
{
int v = que.front();
que.pop();
counter++;
for (int i = 0; i < graph[v].size(); i++)
{
if (--indegree[graph[v][i]] == 0)
que.push(graph[v][i]);
}
}
if (counter != n) //如果有圈,排序失败
return false;
else
return true;
}
int main()
{
while(scanf("%d%d",&n, &m) != EOF)
{
for(int i = 1; i <= n; i++) //将图置空
graph[i].clear();
for(int i=0;i<m;i++)
{
int u, v;
scanf("%d%d", &u, &v);
graph[u].push_back(v);
}
if(TopSort())
printf("Graph does not have a cycle.n");
else
printf("Graph has a cycle.n");
}
return 0;
}
2. DFS
关于DFS的介绍请戳我,通过稍微修改DFS,利用递归的特点,也可以判断有向图是否有圈。修改想法是把原来的visited[]只有true,false两种状态改成如下:
vis[u] = 0代表还没访问; vis[u] = -1代表正在访问中; vis[u] = 1代表访问完全; 如果某个点在访问过程中访问了两次,说明出现了环。 用如下样例模拟出递归过程帮助理解。
图解如下(好吧,画的有点丑,将就看吧(●'◡'●)): 样例一(有环): 3 3 1 2 2 3 3 1
样例二(无环): 3 3 1 2 2 3 1 3
相信通过上面两幅图应该可以大致理解了,现在上代码。
代码如下:
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int MAX_N = 110;
vector<int> graph[MAX_N];
int vis[MAX_N], n, m; //n, m分别是顶点数和边数
bool DFS(int u)
{
vis[u] = -1; //-1用来表示顶点u正在访问
for(int i = 0 ; i < graph[u].size() ; i ++)
{
if(vis[graph[u][i]] == -1)//表示这个点试探了两次,肯定出现了环
return false;
else if(vis[graph[u][i]] == 0)
{
if(!DFS(graph[u][i]))
return false;
}
}
vis[u] = 1;
return true;
}
bool NoCycle()
{
memset(vis, 0, sizeof(vis)); //初始化
for(int i = 1 ; i <= n ; i ++) //图可能不连通
{
if(!vis[i])
{
if(!DFS(i)) return false;
}
}
return true;
}
int main()
{
while(scanf("%d%d",&n, &m) != EOF)
{
for(int i=1;i<=n;i++)
graph[i].clear();
for(int i=0;i<m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
graph[u].push_back(v);
}
if(NoCycle())
printf("Graph does not have a cycle.n");
else
printf("Graph has a cycle.n");
}
return 0;
}
上述利用DFS判断有向图是否有圈实际上是利用了深度优先生成树的性质:有向图无圈当且仅当其深度优先生成树没有回退边, 而上述算法中的vis[graph[u][i]] == -1就是代表有一条u到i的回退边。这篇博客是关于深度优先生成树的介绍:深度优先生成树及其应用。
- bootstrap tab切换如何让鼠标移动自动切换内容
- css自动换行如何设置?url太长会撑开页面
- Histogram of Oriented Gridients(HOG) 方向梯度直方图
- 动态规划系列之最长递增子序列问题解答
- Git SSH Key 生成步骤
- 如何将wordpress所有文章批量改为已发布状态
- dedecms提取某栏目及子栏目名称到首页怎么弄
- Bootstrap速学教程之简要介绍
- Ubuntu 安装 JDK8 的两种方式
- git命令-切换分支
- 如何让dedecms文章点击量增加一定的数值
- dedecms自增标签[field:global.autoindex/]的运用
- dedecms调用副栏目文章怎么操作
- 12个非常有用的JavaScript技巧
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PHP学习记录之面向对象(Object-oriented programming,OOP)基础【接口、抽象类、静态方法等】
- laravel 解决crontab不执行的问题
- PHP+Redis开发的书签案例实战详解
- Python持续监听文件变化代码实例
- laravel框架如何设置公共头和公共尾
- PHP结合Redis+MySQL实现冷热数据交换应用案例详解
- 浅谈Laravel模板实体转义带来的坑
- Vagrant(WSL)+PHPStorm+Xdebu 断点调试环境搭建
- PHP大文件切割上传功能实例分析
- laravel Task Scheduling(任务调度)在windows下的使用详解
- PHP 7.4中使用预加载的方法详解
- PHP设计模式之工厂模式(Factory)入门与应用详解
- Laravel 实现Controller向blade前台模板赋值的四种方式小结
- Referer原理与图片防盗链实现方法详解
- Laravel 简单实现Ajax滚动加载示例