无向图

2023-11-10

概念轰炸

  • 是由一组顶点和一组能够将两个顶点连接的组成的
  • x-y表示x到y的一条
  • 一条连接一个顶点和其自身的边称为自环
  • 连接同一对顶点的两条边称为平行边
  • 含有平行边的图称为多重图
  • 某个顶点的度数即为依附于它的边的总数
  • 当两个顶点通过一条边相连时,我们称这两个顶点是相邻的,并称这条边依附于这两个顶点
  • 子图是由一幅图的所有边的一个子集(以及它们所依附的所有顶点)组成的图
  • 如果从任何一个顶点都存在一条路径到达另一个任意顶点,我们称这幅图为连通图
  • 一幅非连通的图由若干连通的部分组成,它们都是它的极大连通子图
  • 二分图是一种能够将所有结点分为两部分的图,也就是说图中每条边连接的两个顶点属于不同的部分

这里写图片描述

无向图的表示

今天的主角是无向图,顾名思义,无向图就是边没有方向的图。每当一个概念拿到程序中,总是需要抽象出一个数据结构来表示这个概念。那么,图怎么表示呢?表示图的这个数据结构叫做邻接表
这里写图片描述
这个邻接表表示的就是下面这个图
这里写图片描述

首先,邻接表使用了一个数组来存放各个顶点,各个顶点又都指向了一个链表,链表里存放了与这个顶点相邻的顶点。1与2、5相邻,于是数组下标为1的元素指向的链表结点中含有2和5,同样数组下标为2和5的元素指向的链表中也一定含有1。当我们对一个图进行操作的时候,其实就是对这个邻接表进行操作。同时我们也可以看到,如果要访问与顶点3相邻的顶点,我们势必会先访问到2,然后是5,最后是9。但是对与顶点3来说,和它相邻的任何一个顶点低位都是相同的,但这个先后顺序却是确定的。所以构造这个图的时候,也就是构造这个邻接表的时候就已经决定了我们操作图中结点时的某些顺序。

对与领接表数组中的元素,本身是一个链表,为了方便操作,我们用一个Bag类来实现这个链表。

public class Bag<Item> implements Iterable<Item>{
    private Node first;
    private class Node{
        Item item;
        Node next;
    }
    public void add(Item item){
        Node oldfirst = first;
        first = new Node();
        first.item=item;
        first.next=oldfirst;
    }
    public Iterator<Item> iterator(){
        return new ListIterator();
    }
    private class ListIterator implements Iterator<Item>{
        private Node current = first;
        public boolean hasNext(){
            return current!=null;
        }
        public Item next(){
            Item item = current.item;
            current=current.next;
            return item;
        }
    }
}

从而我们就可以用这个Bag来构造我们的无向图

public class Graph {
    private final int V;//顶点数
    private int E;      //边数
    private Bag<Integer>[] adj;//邻接表
    public Graph(int V){
        this.V=V;
        this.E=0;
        adj = (Bag<Integer>[]) new Bag[V];
        for (int i=0;i<V;i++){
            adj[i]=new Bag<Integer>();
        }
    }
    public Graph(In in){
        this(in.readInt());
        this.E=in.readInt();
        for (int i=0;i<V;i++){
            int w = in.readInt();
            int v = in.readInt();

        }

    }
    public int V(){
        return V;
    }
    public int E(){
        return E;
    }
    public void addEdge(int v,int w){
        adj[v].add(w);
        adj[w].add(v);
        E++;
    }
    public Iterable<Integer> adj(int v){
        return adj[v];
    }
}

今天主要说一下图的两种搜索方法,深度优先搜索和广度优先搜索。

深度优先搜索(递归)

深度优先搜索就是:一条路走到黑,装了南墙就返回,再找一条路往黑了走。还以上边的图为例,我们从1开始进行深度优先搜索,首先他会先访问1,然后访问1的相邻顶点,于是找到了2(为什么不是5?因为构造邻接表时,2排在了5前边),然后再去找2的相邻顶点,当它开始访问2的相邻顶点的时候,1的相邻顶点其实还没有访问完,这就体现了深度优先,访问过程是一直深入的,直到碰了南墙才会返回。这里的碰了南墙其实就是访问的顶点已经被访问过。

public class DeepFirstSearch {
    private boolean[] marked;
    private int count;//被访问过的结点的个数
    public DeepFirstSearch(Graph g,int s){
        marked = new boolean[g.V()];
        dfs(g,s);
    }
    private void dfs(Graph g,int s){
        marked[s] = true;
        count++;
        for (int v:g.adj(s)
             ) {
            if (!marked[v])dfs(g,v);
        }
    }
    public boolean marked(int v){
        return marked[v];
    }
    public int count(){
        return count;
    }
}

marked这个boolean数组用来记录哪些顶点被访问过,以完成撞南墙检测。深度优先搜索可以用来解决单点路径问题。如:从1到9是否存在一条路径?如果有,找出这条路径。
其实我们只需要改动一点点上边的代码就可以解决这个问题

public class DeepFirstPath {
    private final int s;
    private boolean[] marked;
    private int[] edgeTo;
    public DeepFirstPath(Graph g,int s){
        this.s=s;
        marked = new boolean[g.V()];
        edgeTo = new int[g.V()];

    }
    private void dfs(Graph g,int v){
        marked[v] = true;
        for (int w :
                g.adj(v)) {
            if (!marked[v]){
                edgeTo[w] = v;
                dfs(g,w);
            }
        }
    }
    public boolean hasPathTo(int v){
        return marked[v];
    }
    public Iterable<Integer> pathTo(int v){
        if (!hasPathTo(v))return null;
        Stack<Integer> path = new Stack<>();
        for (int i = v;i!=s;i=edgeTo[i]){
            path.push(i);
        }
        path.push(s);
        return path;
    }
}

我们只是加了一个Int数组edgeTo,这个数组记录了路径的信息。edgeTo[2]=1,表示1-2是第一次访问2时经过的边。通过edgeTo这个数组我们就可以还原出一个路径。除此之外,深度优先搜索还可以找出图中的所有连通分量。

public class CC {
    private boolean[] marked;
    private int[] id;
    private int count;
    public CC(Graph graph){
        marked = new boolean[graph.V()];
        id = new int[graph.V()];
        for (int s = 0;s> graph.V();s++){
            if (!marked[s]){
                dfs(graph,s);
                count++;
            }
        }

    }
    public void dfs(Graph g,int v){
        marked[v] = true;
        id[v] = count;
        for (int w :
                g.adj(v)) {
            if (!marked[w]) dfs(g, w);
        }
    }
    public boolean connected(int v,int w){
        return id[v]==id[w];
    }
    public int id(int v){
        return id[v];
    }
    public int count(){
        return count;
    }
}

在原来一篇文中我们通过union-find算法寻找了连通分量,今天这个深度优先算法一样可以用来寻找连通分量。其中的id数组起到的就是这个作用,如若x和y两个顶点属于一个连通分量,那么id[x]=id[y]。

广度优先搜索

刚才说到深度优先搜索可以找到两个顶点之间的一个路径,但当两个顶点之间有多个路径的时候,我们需要找出最短的那一条时,深度优先搜索就束手无策了。此刻只能我们广度优先搜索出来亮亮相了。广度优先搜索会先搜索最近的顶点(也就是邻接表中的顶点),然后再去搜索最近的顶点的相邻顶点,就像是一个扩散的过程。所以第一次访问到的顶点所经过的边构成的路径一定是最短的路径。广度优先搜索的实现没有使用递归,而是用了一个队列来保存已经被访问过但其领接表还没有被访问的顶点。然后重复以下两步:①取队列中的下一个顶点并标记为已访问。②将和这个顶点相邻的所有未访问的顶点加入队列

public class BreadthFirstPaths {
    private boolean[] marked;
    private final int s;
    private int[] edgeTo;
    public BreadthFirstPaths(Graph g,int s){
        this.s=s;
        marked = new boolean[g.V()];
        edgeTo = new int[g.V()];
    }
    private void bfs(Graph g,int v){
        Queue<Integer> queue = new LinkedBlockingQueue<Integer>();
        marked[v]=true;
        queue.add(v);
        while (!queue.isEmpty()){
            int w = queue.remove();
            for (int ss :
                    g.adj(w)) {
                if (!marked[ss]) {
                edgeTo[ss]=w;
                queue.add(ss);
                }
        }
    }
    }
    public boolean hasPathTo(int v){
        return marked[v];
    }
    public Iterable<Integer> pathTo(int v){
        if (!marked[v]) return null;
        Stack<Integer> path = new Stack<>();
        for (int i=v;i!=s;i=edgeTo[i]){
            path.push(i);
        }
        path.push(s);
        return path;
    }

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

无向图 的相关文章

  • eterm系统服务器地址,Eterm管理系统

    Eterm管理系统提供用户管理 PID管理 分组管理 角色管理 指令管理等功能 可以帮助用户管理自己的设备 适合企业使用 企业可以在软件添加多种设备到软件管理 可以分组管理不同的网络设备 可以为管理员设置账户 可以在软件编辑控制设备的指令

随机推荐

  • C语言代码静态检查学习笔记(结尾有惊喜)

    静态检查 静态分析的概念 定义 程序静态分析是指在不运行代码的方式下 通过各种分析工具对程序代码进行扫描并做出评估的过程 特点 不实际执行程序 只是通过对代码的静态扫描对程序进行分析 执行速度快 效率高 静态分析技术 类型检查 风格检查 风
  • QT QChart使用基本常识

    效果图 准备工作 ui添加QGraphicsView 提升为QChartView 命名graphicsView 接口和变量声明 QChart chart QXYSeries series1 QXYSeries series2 QXYSeri
  • 分层测试:什么是分层测试?(详解)

    1 什么是分层测试 分层测试是通过对质量问题分类 分层来保证整体系统质量的测试体系 模块内通过接口测试保证模块质量 多模块之间通过集成测试保证通信路径和模块间交互质量 整体系统通过端到端用例对核心业务场景进行验证 用户体验通过手工测试确保无
  • pyecharts 安装报错 ModuleNotFoundError: No module named ‘pyecharts_snapshot‘

    1 出错原因 因为用下面语句安装pyecharts时 默认会安装最新版本的pyecharts python解释器版本更新的速度慢很多 现在的python解释器默认的是与0 1 9 4版本的pyecharts配合 你安装最新的 python解
  • MyBatis笔记(2):CRUD操作及配置解析/狂神说

    目录 0 写在前面 1 Select 1 1 需求 根据id查询用户 1 2 课堂练习 根据 密码 和 名字 查询用户 2 insert 3 Update 4 delete 5 模糊查询like语句该怎么写 6 配置解析 6 1 envir
  • django报错 No module named 'MySQLdb'

    环境 anaconda3 python3 7 django2 2 mysql5 7 在运行python manage py makemigrations appxxx时报错 No module named MySQLdb 网上有方案说改源码
  • Java压缩包制作遗留问题解决

    本文衔接上篇JDK压缩包制作环境配置 在环境配置好后 在DOS命令窗口会发现Java依旧无法运行 提示安装没成功 报错 Error occurred during initialization of VM java lang NoClass
  • 什么是集线器

    集线器 英文名又称Hub 在OSI模型中属于数据链路层 价格便宜是它最大的优势 但由于集线器属于共享型设备 导致了在繁重的网络中 效率变得十分低下 所以我们在中 大型的网络中看不到集线器的身影 如今的集线器普遍采用全双工模式 市场上常见的集
  • 哄女朋友玩的c语言编程,哄女朋友开心的小套路 逗女朋友开心的话套路

    不会玩小编为大家收集整理了哄女朋友开心的小套路 以及逗女朋友开心的话套路如果觉得不错就请收藏一下 下面咱们一起来看一下吧 1 你属什么 虎 不 你属于我 2 想让你爸妈开心吗 想啊 想就带我回家 3 我觉得所有的门都应该让你敲 为什么这么说
  • 通过配置浏览器方式解决跨域问题

    复制桌面上的谷歌浏览器快捷方式 名称改为 Google Debug 浏览器快捷图标 鼠标右键 属性 目标项的最后面 空格 然后加入下面配置 user data dir c ChromeDebug test type disable web
  • java接口回调

    接口回调 我们可以先定义一个接口 比如接口叫usb 然后再定义接口的实现者 如U盘 鼠标 风扇 接口的使用者 如电脑 测试类 Java是一门面向对象语言 一切皆对象 因此在Java中不存在回调函数这一说法的 由于Java的一切皆对象性质 从
  • FPGA时钟电路PCBlayout设计原则

    1 时钟晶振源应该尽可能放在与其连接的FPGA时钟专用引脚的临近位置 2 时钟线尽可能走直线 如果无法避免转弯走线 则使用45度线 尽量避免T型走线和直角走线 3 不要同时在多个信号层走时钟线 4 时钟走线不要使用过孔 因为过孔会导致阻抗变
  • 前端web3入门脚本二:初探dex,在dex完成一笔swap

    前言 现在市面上大多数去中心化交易所 简称dex 都是fork的uniswap的代码 名气比较大的如eth上的sushi 以及 bsc上的pancake 博主这里说的都是V2 uniswapV3在这里不做讨论 那么知道了他们的代码都是来自同
  • 出租车GPS数据处理

    提取出租车订单的OD 从大量的GPS信息中提取出每个出租车订单的起点和终点 数据是出租车GPS的散点时空数据 散点时间间隔大概在15s 取决于GPS的采样频率 因此要提取出乘客出行的OD信息 首先要定义乘客的上车时点 下车时点选取标准 然后
  • JAVA学习之路以及第一次项目实战心得

    JAVA学习之路以及第一次项目实战心得 前言 今天是2023年4月24日 突发奇想想写一篇学习心得 因为以前光顾着一直赶进度学习java 没有总结 也就不知道自己的哪些地方还有缺陷 还需要提高 如何接触到java和学习过程 我是在2021年
  • 江苏省人力资源社会保障厅 省职称办 关于做好2021年度职称评审工作的通知

    各设区市人力资源社会保障局 昆山市 泰兴市 沭阳县人力资源和社会保障局 省各有关厅局人事 职称 部门 各有关企事业单位 社会组织 根据中央和省关于深化职称制度和人才评价机制改革精神 按照 职称评审管理暂行规定 人力资源和社会保障部令第40号
  • uniapp使用中出现的问题

    1 真机调试时 运行到手机 手机显示 本应用无法独立运行 需与HBuilderX搭配使用 我这里是window系统电脑连接到安卓手机 如下图 以上两个图片分别是手机和电脑显示的信息 手机和电脑就一直这样显示 就没然后了 处理方法 升级最新H
  • HTTPS 原理详解

    转自 https baijiahao baidu com s id 1570143475599137 wfr spider for pc 前言 HTTPS 全称 HyperText Transfer Protocol over Secure
  • 计算机中cat是什么命令,cat(操作系统命令)_百度百科

    本词条缺少概述图 补充相关内容使词条更完整 还能快速升级 赶紧来编辑吧 cat是操作系统命令的名称 cat命令在Unix和类Unix系统中是英语单词concatenate 意思都是连接 的缩写 作用是显示或连接多个文本文件 在Apple P
  • 无向图

    概念轰炸 图是由一组顶点和一组能够将两个顶点连接的边组成的 x y表示x到y的一条边 一条连接一个顶点和其自身的边称为自环 连接同一对顶点的两条边称为平行边 含有平行边的图称为多重图 某个顶点的度数即为依附于它的边的总数 当两个顶点通过一条