数据结构与算法--图论-深度优先搜索及其应用

深度优先搜索

  • 深度优先搜索(depth-first search) 是对先序遍历(preorder traversal)的推广,我们从某个顶点v开始处理v,然后递归的遍历所有与v邻接顶点。如果这个过程是对一棵树进行,那么,由于E=O(V),因此树的所有顶点在总时间O(E)内都会被访问到。
  • 方法:
    • 当访问顶点v时候,我们标记该节点以及访问过,并且对于未被标记的所有邻接顶点递归调用深度优先搜索,
    • 加上我们对无向图每条边(v,w)在邻接表中出现两次:一次(v,w),另外一次(w,v)
    • 如下图过程中执行一次深度优先搜索,什么都没有操作的一次遍历,只是深度优先搜索的案例:
/*** @author liaojiamin* @Date:Created in 16:29 2021/1/8*/
public class GraphDepthFirstSearch {//递归调用深度优先搜索实现public void depthFirst(Vertex v){v.setKnow(true);for (Vertex vertex : v.getVertexList()) {if(!vertex.isKnow()){depthFirst(vertex);}}}
}
  • 以上算法中,对每个顶点的known都是初始化为false,通过只对未访问过的节点进行进行递归调用,我们以此保证不进入无线循环。
  • 如果图是无向的且不连通的,或者是有向的非强连通的(上一节中有对应说明)不能保证所有节点都能范文到,比如我们的v节点是从中途选取的一个节点,并不是图的入口节点。
  • 次数我们搜索一个未被标记的节点,然后以这个节点为入口节点进行深度优先搜索,继续执行这两个步骤直到所有节点都被标记为止

无向图

  • 当我们从无向图的任何一个节点开始深度优先搜索的时候能访问到图中的所有节点,那么这个无向图是连通无向图。
  • 假设我们所处理的图都是连通的,如果不连通我们可以找出所有连通分支并将我们的算法一次运用到每个分支上即可(同上步骤)
  • 作为深度优先搜索的案例,在下图中,我们从A点开始。用以下遍历流程:
    • 从A节点出发,A已经被访问过,标记A为true,并递归调用dfs(B)
    • dfs(B)标记B节点为true并且递归调用dfs(C)
    • dfs(C)标记C节点为true并且递归调用dfs(D)
    • dfs(D)遇到A,B但是A与B都已经被标记为true,因此没有递归调用可以进行,此时D-A,D-B为非必要的路径
    • dfs(D)同时也是邻接C的顶点,但是C也是被标记为true,因此这里也没有递归调用,此时D-C也是非必要路径
    • 于是按照递归dfs(D)返回到dfs(C)
    • dfs(C)看到B是邻接节点,但是B已经是true,并且E也是C的邻接节点,因此调用dfs(E)。
    • dfs(E)将E标记为true,邻接节点是A,C,都是true,因此忽略A,C,所以E-A,E-C是非必要路径,因为C-E已经是被范问过的已经存在的路径,所以剩下E-A是非必要路径,继续返回上一层dfs(C)
    • dfs(C)返回dfs(B),dfs(B)忽略A,D,同理BA是已知路径,上面已经走过,所以BD是非必要路径
    • 返回上一层dfs(A)忽略D,E,两个都是非必要路径
    • 按以上算法递归得到,我们实际上对每个表都范问了两次,一次作为边(v,w),一次作为边(w,v),这实际上是每个邻接表遍历一次而已
      在这里插入图片描述
深度优先生成树
  • 我们用深度优先生成树(depth-first spanning tree)一图形的方式来标识上面的递归步骤,树的根节点是入口节点A,第一个被访问到的顶点,图中每一条表(v,w)都出现在树上
    • 如果我们处理(v,w)的时候发现w是为未被标记,或者处理(w,v)的时候发现v是未被标记,那么我们就用树的一条边来标识他
    • 如果我们处理(v,w)时候发现w已经被标记,并且处理(w,v)时候发现v已经有标记,那么我们就用虚线称为背向边(上述步骤中的非必要路径),标识这条边实际上不是这棵树的一部分,也就是我们去掉这些边也可以全部遍历所有节点。
    • 如下图

在这里插入图片描述

  • 如果图不是连通图,我们上面讨论过,需要多次执行这个算法,直到所有节点都已经被访问,每次都按照算法生成一颗树,整个集合就是深度优先生成森林(depth-first spanning forest)
双连通性
  • 一个连通的无向图如果不存在被删除之后使得剩下的图有不在连通的顶点,那么这样的无向连通图就称为双连通(biconnected)的。上面案例中的图是双连通的。
  • 案例: 如果节点标识计算机,边是链路,如果一台计算机宕机,则网络是不受影响的,当然这台坏的计算机除外。
  • 如果存在一个图不是双连通的,那么将其删除后使得图不在连通的那些顶点叫做割点(articulation point)。这些节点在许多应用中很重要。下图中所示不是双连通的:顶点C,D都是割点。删除顶点C,使的G不在连通,删除D使得E,F单独分离开
  • 深度优先所搜提供一种找出连通图中的所有割点的线性时间算法:
    • 首先从图中任意一顶点开始,执行深度优先搜索,并在顶点被访问的时候编号。对每个顶点v,我们称为先序编号为Num(v)。然后对于深度优先搜索生成树
    • 然后对于深度优先搜索生成树上的每个顶点v,计算编号最低的顶点,也就是在邻接表中每个节点的邻接节点中Num(v)的最小值,我们称为Low(v),该节点可以从v开始通过树的0条边(节点本身),或者多条表且有一条背向边而达到
    • 如下图中,依据深度优先搜索树首先得出先序编号,然后支出在上述方法下面可以达到的最低的编号。

请添加图片描述

在这里插入图片描述

  • 如上图,如A 顶点数据(1/1),第一个数据表示Num(v),是先序遍历的顺序字段,图中树结构的先序遍历顺序是A,B,C,D,E,F,G。

  • 第二个数据表示的是Low(v),所有顶点的Low(v)开始都等于Num(v),顶点A的low(v)是本身,

  • D的邻接节点中存在A节点,所以按以上规则Low(v)是最小的Num(v)所以取A 顶点的Num(v)

  • 同理C节点邻接节点是D也是1,B节点邻接C也是1

  • E节点邻接F,F背向边到D,所以F,E,都是D节点的Num(v) = 4.

  • G没有背向节点,所以是本身 7

  • 我们依据以上Low定义可以指定Low(v)是满足如下规则:

    • 初始值Num(v)
    • 如果有背向边的话,取所有背向边(v,w)中最低的Num(w)
    • 取树的所有边(v,w)中最低的Low(w)中的最小者
  • 以上规则中第一条是不选取边,本身就是最小,第二条规则是不选取树的边而是选取一条背向边,第三种规则我们可以用递归调用简单的描述,由于我们需要对v的所有子节点算出Low值后才能得到最小Low(v),因此这是一个后续遍历。对于任意的(v,w),只要检查Num(v)和Num(w)就知道他是树的一条边还是一条背向边,(因为按树生成算法背向边总数从大的 num值 到小的num值)。因此Low(v)容易计算,我们只需要扫描v的邻接节点,然后记住最小值。时间复杂度在O(E+V)。

  • 接着我们需要做的就是利用以上信息找到所有割点。

    • 根是割点的时候只有在根节点有大于一个儿子节点的时候成立,如果有2个儿子节点,删除根则使得不同子树上节点不连通
    • 对于任何其他顶点v,他是割点的虫咬条件是,当他的某个儿子节点w使得Low(w) >= Num(v),满足
    • 还是上图中的案例,C和D是割点,D有一个儿子节点E,且Lov(E) >= Num(D),两个都是4
    • 同理C也是割点Low(G) >= Num©

算法实现

  • 最后我们给出以下代码实现该算法,我们需要在之前的Vertex图节点中修改几个属性,num, low,沿用之前的known,path(父节点),用类变了counter做统计为先序遍历num编号,如下:
/*** @author liaojiamin* @Date:Created in 16:29 2021/1/8*/
public class GraphDepthFirstSearch {//假设图基本数据结构已经被读入邻接表中private static final List<Vertex_v1> vertices = new ArrayList<>();private Integer counter = 0;/*** 深度优先递归模板* */public void depthFirst(Vertex_v1 v){v.setKnow(true);for (Vertex_v1 vertex : v.getVertexList()) {if(!vertex.isKnow()){depthFirst(vertex);}}}/*** 深度优先递归赋值nnum* */public void assignNum(Vertex_v1 v){v.setNum(counter++);v.setKnow(true);for (Vertex_v1 vertex_v1 : v.getVertexList()) {if(!vertex_v1.isKnow()){vertex_v1.setPath(v);assignNum(vertex_v1);}}}/*** 同样的算法,深度优先对Low赋值* */public void assignLow(Vertex_v1 v){v.setLow(v.getNum());for (Vertex_v1 w : v.getVertexList()) {//w > v 标识w是v的子节点if(w.getNum() > v.getNum()){assignLow(w);//割点规则if(w.getLow() > v.getNum()){System.out.println(v.getNum() + " is an articulation point");}v.setLow(Math.min(v.getLow(), w.getLow()));}else if(v.getPath().compareTo(w) != 0){//此处不能写成: v.setLow(Math.min(v.getLow(), w.getLow()));因为此种情况是在最后二次访问边路径的时候得到,可能w的Low已经被修改成更小的值v.setLow(Math.min(v.getLow(), w.getNum()));}}}/*** 还是用深度优先搜索,结合以上两种规则,值进行一次遍历得到* */public void findArt(Vertex_v1 v){v.setKnow(true);v.setNum(counter++);v.setLow(v.getNum());for (Vertex_v1 w : v.getVertexList()) {if(!w.isKnow()){w.setPath(v);findArt(w);if(w.getLow() >= v.getNum()){System.out.println(v.getNum() + " is an articulation point");}v.setLow(Math.min(v.getLow(), w.getLow()));}else if(v.getPath().compareTo(w) != 0){//此处不能写成: v.setLow(Math.min(v.getLow(), w.getLow()));因为此种情况是在最后二次范文边路径的时候得到,可能w的Low已经被修改成更小的值v.setLow(Math.min(v.getLow(), w.getNum()));}}}}
  • 以上算法中assignNum通过执行一次先序遍历计算Num,然后在后续遍历计算Low,第三次遍历可以用来检查哪些顶点满足割点的标准,但是执行了三次遍历比较浪费,我们将两个方法合成为findArt,在同一个递归中完成,得出算法。

上一篇:数据结构与算法–图论-最短路径算法应用
下一篇:数据结构与算法–贪婪算法:模拟调度问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310463.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET Core技术研究-主机

前一段时间&#xff0c;和大家分享了 ASP.NET Core技术研究-探秘Host主机启动过程但是没有深入说明主机的设计。今天整理了一下主机的一些知识&#xff0c;结合先前的博文&#xff0c;完整地介绍一下.NET Core的主机的设计和构建启动过程。一、什么是主机主机是一个封装了应用资…

数据结构与算法--贪婪算法

贪婪算法 贪婪算法分阶段地工作。在每个阶段&#xff0c;可以认为所做决定是最好的&#xff0c;而不考虑将来的后果。通常这意味着选择的是某个局部最优。这种“当前能获得的最优就拿”的策略是这类算法的名字来源。当算法终止时候&#xff0c;我们希望的到累积的局部最优解就…

[C++STL]C++ 实现map容器和set容器

代码如下: #pragma once #include <iostream> using namespace std;enum COLOR {BLACK, RED };template<class V>//迭代器声明&#xff0c;定义在后面 struct RBTreeIterator;template<typename V> struct RBTreeNode {RBTreeNode<V> * _parent;RBTre…

多角度让你彻底明白yield语法糖的用法和原理及在C#函数式编程中的作用

如果大家读过dapper源码&#xff0c;你会发现这内部有很多方法都用到了yield关键词&#xff0c;那yield到底是用来干嘛的&#xff0c;能不能拿掉&#xff0c;拿掉与不拿掉有多大的差别&#xff0c;首先上一段dapper中精简后的Query方法&#xff0c;先让大家眼见为实。private s…

C++泛型编程实现哈希表(闭散列---线性探测)

代码如下: #include <iostream> #include <vector> using namespace std;enum STATE {EXIST,DELETE,EMPTY };template<typename K,typename V> struct HashNode {pair<K, V> _kv;STATE _state EMPTY; };template<typename K,typename V> class…

哪种开源许可证最适合商业化?

选择最佳开源许可证是为新项目所做的最重要的决定之一。大多数开发者会选用 MIT、BSD 或 Apache 等流行的宽松许可证&#xff08;permissive license&#xff09;。对于商业项目而言&#xff0c;这种选择不错&#xff0c;因为这能减少用户对项目的抵触情绪。当应用于开源项目时…

C++泛型编程实现哈希表(开散列法)

代码如下: #include <iostream> #include <vector> using namespace std;template<typename K> struct HashNode {typedef HashNode<K> Node;K _val;Node * _next;HashNode(const K & val):_val(val),_next(nullptr){} };template<typename K&…

数据结构与算法--分治算法-最大子序列和问题

分治算法 用于设计算法的一种常用技巧–分治算法&#xff08;divide and conquer&#xff09;。分治算法由两部分组成&#xff1a; 分(divide)&#xff1a;递归然后借机较小的问题&#xff08;基础情况除外&#xff09;治(conquer)&#xff1a;然后从子问题的解构建原问题的解…

请把我不会,换成我可以学

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份有位读者跟我说起自己的烦恼&#xff1a;“我到公司已经接近四年了&#xff0c;领导经常让我做一些岗位职责以外的事情。这些东西我都不会&#xff0c;还非让我做。并且一直没有职位上的改变&#xff0c;我怎…

[C++STL]C++实现unordermap容器和unorderset容器

代码如下: #include <iostream> #include <vector> using namespace std;template<typename K,typename V,typename KeyOfValue> class HashTable;//声明template<typename V> struct HashNode {typedef HashNode<V> Node;V _val;Node * _next;…

还不会docker+k8s?2020年,就要面对现实了...

docker的前世今生2010年&#xff0c;几个年轻人&#xff0c;在美国旧金山成立了一家名叫“dotCloud”的公司。这家公司主要提供基于PaaS的云计算技术服务。具体来说&#xff0c;是和LXC有关的容器技术。后来&#xff0c;dotCloud公司将自己的容器技术进行了简化和标准化&#x…

数据结构与算法--重建二叉树

二叉树 树在实际编程中经常遇到地一种数据结构。上一篇中我们解释了二叉树及其原理&#xff0c;从中可以知道&#xff0c;树地操作会涉及到很多指针地操作&#xff0c;我们一般遇到地树相关地问题差不多都是二叉树。二叉树最重要地莫过于遍历&#xff0c;即按照某一顺序访问树…

3分钟掌握Quartz.net分布式定时任务的姿势

长话短说&#xff0c;今天聊一聊分布式定时任务&#xff0c;我的流水账笔记&#xff1a;ASP.NET CoreQuartz.Net实现web定时任务AspNetCore结合Redis实践消息队列细心朋友稍一分析&#xff0c;就知道还有问题&#xff1a;水平扩展后的WebApp的Quartz.net定时任务会多次触发&…

数据结构与算法--利用栈实现队列

利用栈实现队列 上一节中说明了栈的特点 后进先出&#xff0c;我们用数组的方式实现了栈的基本操作api&#xff0c;因此我们对栈的操作是不考虑排序的&#xff0c;每个api的操作基本都是O(1)的世界&#xff0c;因为不考虑顺序&#xff0c;所以找最大&#xff0c;最小值&#x…

ASP.NET Core 配置源:实时生效

在之前的文章 ASP.NET Core 自定义配置源 和 ASP.NET Core etcd 配置源 中主要是介绍如何实现自定义的配置源&#xff0c;但不论内置的和自定义的配置源&#xff0c;都会面临如何使配置修改后实时生效的问题&#xff08;修改配置后在不重启服务的情况下能马上生效&#xff09;。…

分布式事务理论模型

分布式事务 事务的概念&#xff0c;我们第一想到的应该是数据库的事务。所谓数据库事务就是只作为单个逻辑工作单元执行多个数据库操作的时候&#xff0c;数据库需要保证要么都成功&#xff0c;要么都失败&#xff0c;它必须满足ACID特性&#xff0c;即&#xff1a; 原子性&…

[MySQL基础]数据库的相关概念

DB: 数据库(database):存储数据的“仓库”&#xff0c;它保存了一系列有组织的数据。 DBMS: 数据库管理系统(Database Management System):数据库是通过DBMS创建和操作的容器。 SQL: 结构化查询语言(Structure Query Language):专门用来与数据库通信的语言。 SQL的优点: 1.几…

Linq下有一个非常实用的SelectMany方法,很多人却不会用

在平时开发中经常会看到有些朋友或者同事在写代码时会充斥着各种for&#xff0c;foreach&#xff0c;这种程式代码太多的话阅读性特别差&#xff0c;而且还显得特别累赘&#xff0c;其实在FCL中有很多帮助我们提高阅读感的方法&#xff0c;而现实中很多人不会用或者说不知道&am…

.NET Core前后端分离快速开发框架(Core.3.1+AntdVue)

引言时间真快&#xff0c;转眼今年又要过去了。回想今年&#xff0c;依次开源发布了Colder.Fx.Net.AdminLTE(254Star)、Colder.Fx.Core.AdminLTE(335Star)、DotNettySocket(82Star)、IdHelper(47Star)&#xff0c;这些框架及组件都是本着以实际出发&#xff0c;实事求是的态度&…