图----无向图

news2024/9/29 5:31:02

1.定义

图的定义:图是由一组顶点和一组能够将两个顶点相连的边组成

边:edge

顶点:vertex

 连通图:如果从任意一个顶点都存在一条路径到达另外一个任意顶点,我们称这幅图是连通图。

非连通图:由若干连通的部分组成,他们都是其极大连通子图。

自环:即一条连接一个顶点和其自身的边

平行边:连接同一顶点的两条边称为平行边

 图的密度:是指已经连接的顶点对占所有可能被连接的顶点对的比例。在稀疏图中,被连接的顶点对很少;而在稠密图中,只有少部分顶点对之间没有边连接。

二分图:是一种能够将所有结点分为两部分的图,其中图的每条边所连接的两个顶点都分别属于不同的部分。

2.图的不同表示方法

2.1邻接表数组表示

用一个以顶点为索引的列表数组,其中的每个元素都是和该顶点相邻的定点列表

 2.2邻接矩阵 表示

用一个V乘以V的布尔矩阵。当顶点V和顶点W之间有相连的边时,定义V行W列的元素值为true,否则为false。如果含有上百万个顶点,V的平方个布尔值所需要的空间会非常大。

3.相关API及代码

package com.sid.graph;


public class Vertex {
    String value;//顶点的值
    Edge firstEdge;//第一条边
    int index;//在外层数组的下标


    public Vertex(String value, Edge firstEdge,int index) {
        super();
        this.value = value;
        this.firstEdge = firstEdge;
        this.index = index;
    }
}
package com.sid.graph;

public class Edge {
    Vertex vertex;//该边对应的顶点
    int weight;//权重,无向图,有向图权重为0
    Edge next;//下一个边
    /**
     * 构建一条边 weight未0表示无向图或者有向图 不为0表示网
     * @param vertex
     * @param weight
     * @param next
     */
    public Edge(Vertex vertex, int weight, Edge next) {
        super();
        this.vertex = vertex;
        this.weight = weight;
        this.next = next;
    }
}
package com.sid.graph;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Graph {
    /**
     * 顶点数
     */
    private final int V;

    /**
     * 边数
     */
    private int E;

    /**
     * 邻接表
     */
    private List<Vertex> adj;

    public Graph(int V) {
        this.V = V;
        this.E = 0;
        adj = new ArrayList<Vertex>(V);  /** 创建邻接表 */

    }

    public int V() {
        return V;
    }

    public int E() {
        return E;
    }

    /**
     * 插入顶点
     */
    public Vertex addVertex(String value) {
        int size = adj.size();
        Vertex vertex = new Vertex(value, null,size);
        adj.add(vertex);
        return vertex;
    }

    /**
     * 获取顶点
     */
    public Vertex getVertexByValue(String value) {
        for (int i = 0; i < V; i++) {
            if (adj.get(i).value.equals(value)) {
                return adj.get(i);
            }
        }
        return null;
    }

    /**
     * 获取顶点
     */
    public Vertex getVertexByIndex(int index) {
        return adj.get(index);
    }

    /**
     * 添加无向图的边
     * @param vertex1 第一个顶点
     * @param vertex2 第二个顶点
     */
    public void addUndigraphEdge(Vertex vertex1,Vertex vertex2) {
        //因为是无向图,所以就直接加入
        addEdgeByVertex(vertex1,vertex2,0);
        addEdgeByVertex(vertex2,vertex1,0);
    }
    /**
     * 添加有向图的边
     * @param start 开始节点
     * @param end 结束节点
     */
    public void addDigraphEdge(Vertex start,Vertex end) {
        //因为是有向图,所以只有一条边
        addEdgeByVertex(start,end,0);
    }

    /**
     * 添加一条由start-->end的边
     *
     * @param start
     * @param end
     * @param weight 权重未0表示无向图或者有向图,部位0表示网
     */
    private void addEdgeByVertex(Vertex start, Vertex end, int weight) {
        //1、找到第一个顶点
        Vertex v1 = this.getVertexByValue(start.value);
        if(v1 == null){
            v1 = addVertex(start.value);
        }

        //2、检查这条边是否已经存在,已经存在就直接报错
        for (Vertex w : adj(v1)) {
            if (end.value.equals(w.value)) {
                System.out.println("边" + start.value + "-->" + end.value + "已经加入,不可以再加入");
                return;
            }
        }

        //3.添加
        Edge firstEdge = v1.firstEdge;
        if (firstEdge == null) {
            firstEdge = new Edge(end, weight, null);
            v1.firstEdge = firstEdge;
        } else {
            //当前节点变为第一个节点
            Edge nowEdge = new Edge(end, weight, null);
            v1.firstEdge = nowEdge;
            nowEdge.next = firstEdge;
        }
        E++;
    }

    /**
     * 计算V节点的度数
     */
    public static int degree(Graph g, int v) {
        int degree = 0;
        Edge firstEdge = g.getVertexByIndex(v).firstEdge;
        while (firstEdge != null) {
            degree++;
            firstEdge = firstEdge.next;
        }
        return degree;
    }

    /**
     * 计算所有顶点的最大度数
     */
    public static int maxDegree(Graph G) {
        int max = 0;

        for (int v = 0; v < G.V(); v++) {
            if (degree(G, v) > max) {
                max = degree(G, v);
            }
        }
        return max;
    }

    /**
     * 计算所有顶点的平均度数
     */
    public static int avgDegree(Graph G) {
        return 2 * G.E() / G.V();
    }

    /**
     * 计算自环的的个数
     */
    public static int numberOfSelfLoops(Graph g) {
        int count = 0;
        for (int v = 0; v < g.V(); v++) {
            for(int w : g.adj(v)){
                if(v == w){
                    count++;
                }
            }
        }
        return count / 2;   //每条边都被记过两次
    }

    /**
     * 得到跟V节点相邻的所有节点
     */
    public static List<Vertex> adj(Vertex vertex) {
        List result = new LinkedList();
        Edge firstEdge = vertex.firstEdge;
        while (firstEdge != null) {
            result.add(firstEdge.vertex);
            firstEdge = firstEdge.next;
        }
        return result;
    }

    /**
     * 得到跟V节点相邻的所有节点下标  入参是节点的数组下标
     */
    public List<Integer> adj(int index) {
        List result = new ArrayList();
        Edge firstEdge = this.getVertexByIndex(index).firstEdge;
        while (firstEdge != null) {
            result.add(firstEdge.vertex.index);
            firstEdge = firstEdge.next;
        }
        return result;
    }

    /**
     * 图的邻接表的字符串表示
     */
    public String toString() {
        String s = V + " vertices, " + E + " edges\n";
        for (int v = 0; v < V; v++) {
            Vertex vertex = this.getVertexByIndex(v);
            s += vertex.value + ": ";
            for (Vertex w : adj(vertex)) {
                s += w.value + "";
            }
            s += "\n";
        }
        return s;
    }
}

4.深度优先搜索

4.1 找到以起点s连通的所有顶点,和个数 

实现:

用一个递归方法来遍历所有顶点,在访问一个顶点时:

将它标记为已访问

递归地访问它的所有没有被标记过的邻居顶点

package com.sid.graph;

public class DepthFirstSearch {
    private boolean[] marked;
    private int count;

    public DepthFirstSearch(Graph G,int s){
        marked = new boolean[G.V()];
        dfs(G,s);
    }

    private void dfs(Graph G, int index) {
        marked[index] = true;
        count++;
        for(int w : G.adj(index)){
            if(!marked[w]){
                dfs(G,w);
            }
        }
    }

    public boolean marked(int w ){
        return marked[w];
    }

    public int count(){
        return count;
    }
}

4.2深度优先搜索查找图中路径

注意这不一定是最短路径,比如说下面这个例子,查出来的0到1的路径,不是0----1,而是0----2----1 

         

package com.sid.graph;

import java.util.Stack;

public class DepthFirstPaths {
    private boolean[] marked;  //这个顶点上调用过dfs()了吗
    private int[] edgeTo;       //从起点到一个顶点的已知路径上的最后一个顶点  比如S---A---C 则 edgeTo[C]=A  edgeTo[C]其实表示的是谁直接指向C点
    private final int s;       //起点

    public DepthFirstPaths(Graph G, int s) {
        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];
        this.s = s;
        dfs(G, s);
    }

    private void dfs(Graph G, int index) {
        marked[index] = true;
        for (int w : G.adj(index)) {
            if (!marked[w]) {
                edgeTo[w] = index;
                dfs(G, w);
            }
        }
    }

    public boolean hasPathTo(int v){  //起点S是否有路径到V
        return marked[v];
    }

    public Iterable<Integer> pathTo(int v){
        if(!hasPathTo(v)){
            return null;
        }
        Stack<Integer> path = new Stack<Integer>();
        for(int x = v ; x != s ; x = edgeTo[x]){
            path.push(x);
        }
        path.push(s);
        return path;
    }
}

5.广度优先搜索

深度优先搜索得到的路径不仅取决于图的结构,还取决于图的表示和递归调用的性质。

“从S到给定目标的顶点V是否存在一条路径?如果有,找出其中最短的那条”,则需要用广度优先搜索。

实现:

1.使用一个队列来保存所有已经被标记过,但其邻接表还未被检查过的顶点

2.先将起点加入队列,然后重复以下步骤直到队列为空

        取队列中的下一个顶点v并标记它

        将与v相邻的所有未被标记过的顶点加入队列

package com.sid.graph;

import java.util.*;

public class BreadthFirstPaths {
    private boolean[] marked;  //这个顶点上调用过dfs()了吗
    private int[] edgeTo;       //从起点到一个顶点的已知路径上的最后一个顶点  比如S---A---C 则 edgeTo[C]=A  edgeTo[C]其实表示的是谁直接指向C点
    private final int s;       //起点

    public BreadthFirstPaths(Graph G, int s) {
        marked = new boolean[G.V()];
        edgeTo = new int[G.V()];
        this.s = s;
        bfs(G, s);
    }
    private void bfs(Graph G, int s) {
        Queue<Integer> queue = new PriorityQueue<>();
        marked[s] = true;
        queue.add(s);
        while (!queue.isEmpty()){
            int v = queue.poll();
            for(int w : G.adj(v)){
                if(!marked[w]){
                 edgeTo[w] = v;
                 marked[w] = true;
                 queue.add(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<Integer>();
        for(int x = v ; x != s ; x = edgeTo[x]){
            path.push(x);
        }
        path.push(s);
        return path;
    }
}

6.连通分量

无向图G的极大连通子图称为G的连通分量( Connected Component)。

任何连通图的连通分量只有一个,即是其自身,非连通的无向图有多个连通分量。 

实现

使用marked[]数组来寻找一个顶点作为每个连通分量中深度优先搜索的起点。

递归的深度优先搜索第一次调用的参数是顶点0,它会标记所有与0连通的顶点。

然后构造函数中for循环会查找每个没有被标记的顶点,并且递归调用dfs()来标记和它相邻的所有顶点。

使用一个以顶点作为索引的数组id[],将同一个连通分量的顶点和连通分量的标识符关联起来(int 值)。这个数组是的connected()方法的实现变得十分简单。

标识符0会被赋予第一个连通分量中的所有顶点,1会被赋予第二个连通分量中的所有顶点,以此类推。这样所有的标识符都会如API中指定的那样在0到count()-1之间。这个约定使得以子图作为索引的数组成为可能。

例子

最后

id[0]= 0

id[1]= 0

id[2]= 0

id[3]= 0

id[4]= 0

id[5]= 0

id[6]= 0

id[7]= 1

id[8]= 1

id[9]= 2

id[10]= 2

id[11]= 2

id[12]= 2

也就是说count[]的值相同的是一个连通分量,count[]的值不同的节点之间是走不通的。

代码

package com.sid.graph;

public class CC {
    private boolean[] marked;
    private int[] id;
    private int count;

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

    private 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;
    }
}

7.G是无环图吗

使用深度优先处理

如果不存在自环和平行边,就是无环图

package com.sid.graph;

public class Cycle {
    private boolean[] marked;
    private boolean hasCycle;

    public Cycle(Graph G){
        marked = new boolean[G.V()];
        for(int s = 0 ; s < G.V(); s++){
            if(!marked[s]){
                dfs(G,s,s);
            }
        }
    }

    private void dfs(Graph G, int v , int u){
        marked[v] = true;
        for(int w : G.adj(v)){
            if(!marked[w]){
                dfs(G,w,v);
            }
            else if(w != u){  //A---B 与B相邻的节点肯定有A,我理解的是无向图中他们属于同一条边,不是环。
                hasCycle = true;
            }
        }
    }

    public boolean hasCycle(){
        return hasCycle;
    }
}

8.G是二分图吗(双色问题)

package com.sid.graph;

public class TwoColor {
    private boolean[] marked;
    private boolean[] color;
    private boolean isTowColorable = true;

    public TwoColor(Graph G){
        marked = new boolean[G.V()];
        color = new boolean[G.V()];
        for(int s = 0 ; s < G.V(); s++){
            if(!marked[s]){
                dfs(G,s);
            }
        }
    }

    private void dfs(Graph G, int v) {
        marked[v] = true;
        for (int w : G.adj(v)){
            if(!marked[w]){
                color[w] = !color[v];
            }else if(color[w] == color[v]){
                isTowColorable = false;
                return;
            }
        }
    }

    public boolean isBipartite(){
        return isTowColorable;
    }
}

9.符号图

节点里面装的不是数字,而是其他的,比如字符串

 

 

 

 

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

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

相关文章

【Python】tkinter messagebox练习笔记

我一好友在朋友圈看到人家用代码花式秀恩爱&#xff0c;让我也做一个&#xff0c;我就用我学习半年python的功力&#xff0c;做了这一个东西。&#x1f64f;窗口主页面&#xff08;图一&#xff09;为了让我这个盆友有颜面&#xff0c;特意做了一个问答问他帅不帅&#xff0c;以…

Active Directory 02 - Windows Kerberos Authentication(Kerberos 协议鉴权)

写在最前 如果你是信息安全爱好者&#xff0c;如果你想考一些证书来提升自己的能力&#xff0c;那么欢迎大家来我的 Discord 频道 Northern Bay。邀请链接在这里&#xff1a; https://discord.gg/9XvvuFq9Wb我会提供备考过程中尽可能多的帮助&#xff0c;并分享学习和实践过程…

从0到1一步一步玩转openEuler--11 openEuler基础配置-设置磁盘调度算法

11 openEuler基础配置-设置磁盘调度算法 文章目录11 openEuler基础配置-设置磁盘调度算法11.1 设置磁盘调度算法11.1.1 临时修改调度策略11.1.2 永久设置调度策略11.1 设置磁盘调度算法 本节介绍如何设置磁盘调度算法。 11.1.1 临时修改调度策略 例如将所有IO调度算法修改为…

js逆向-某头条_signature参数

前言 头条的加密参数_signature其实可以通过搜索来直接定位到关键位置&#xff0c;我们换种定位的方法 定位 先查看下堆栈&#xff0c;直接在第一个XMLHttpRequest.send的位置下上断点&#xff0c;然后下拉触发断点 这个位置还有其他请求&#xff0c;这里只看/api/pc/list…

2023 AIME 答案与解析 - 第二期(完结)

原题目 Find the number of cubic polynomials where and are integers in such that there is a unique integer with 绿树教育中心独家解析 是一个有两个整数根的三次方程&#xff0c;因此它有三个整数根。所以&#xff0c; 或 &#xff0c;其中 。 「Case 1」 &#xff0c;则…

关于spring bean的生命周期的个人理解(根据官方文档学习)

首先说一下Servlet的生命周期&#xff1a;实例化&#xff0c;初始init&#xff0c;接受service&#xff0c;销毁destroy&#xff1b; spring上下文中的Bean 生命周期也是类似&#xff0c;如下&#xff1a; &#xff08;1&#xff09;实例化Bean 对于Bean Factory容器&#xf…

elasticsearch更新和删除

文档更新文档的更新经历三个步骤&#xff0c;检索、修改、重新索引部分更新在原有文档已经存在的情况下&#xff0c;可以对原有的文档部分字段更新&#xff0c;使用POST请求&#xff0c;发送到/_update如果文档是不存在的&#xff0c;更新操作是失败的存在则更新&#xff0c;不…

Qt C++ 自定义仪表盘控件02

简介仪表盘是工控领域不可缺少的一类软件UI元素&#xff0c;通常出现在各类电子看板软件上&#xff0c;以及一些高级的上位机软件界面上&#xff0c;目的是将繁杂的数据转化为可视化的图表能大幅提高后台管理效率。本文分享了几个经典常用的仪表盘控件&#xff0c;在项目中可以…

利用升序定时器链表处理非活动连接

参考自游双《Linux高性能服务器编程》 背景 服务器同常需要定期处理非活动连接&#xff1a;给客户发一个重连请求&#xff0c;或关闭该连接&#xff0c;或者其他。我们可以通过使用升序定时器链表处理非活动连接&#xff0c;下面的代码利用alarm函数周期性的触发SIGALRM信号&a…

半人半妖时代来啦

未来是半人半妖时代&#xff01;&#xff01;&#xff01; 碳基生命与硅基生命结合 趣讲大白话&#xff1a;人和机器结合是大趋势 *********** 人工智能就是宗&#xff5e;教 科技宗&#xff5e;教的一支最强势的教派 日常使用智能机器的人就是信众 维护机器的人就是牧师 创造这…

【mock】手把手带你用mock写自定义接口+mock常用语法

mock自定义接口完整流程 官网语法规范:https://github.com/nuysoft/Mock/wiki/Syntax-Specification 首先: 要有一个项目,我这里是vue3项目,以下从vue3项目搭建开始,已搭建好的请直接看2 1.空目录下新建vue3项目 运行创建项目命令&#xff1a; 在bash中:(文件路径处输入cm…

【计组】内存和总线--《深入浅出计算机组成原理》(十)

课程链接&#xff1a;深入浅出计算机组成原理_组成原理_计算机基础-极客时间 一、虚拟内存和内存保护 日常使用的操作系统下&#xff0c;程序不能直接访问物理内存。内存需要被分成固定大小的页&#xff08;Page&#xff09;&#xff0c;再通过虚拟内存地址&#xff08;Virtu…

卡通形象人物2 写代码-睡觉 丝滑如德芙

目录 本次实现效果 目录结构 index static/css/style.css static/js/script.js 结语&#xff1a; 前期回顾 【 css动画 】—— 把你喜欢css动画嵌入到浏览器中_0.活在风浪里的博客-CSDN博客常用酷炫动画999合集&#xff0c;代码直接复制可用&#xff0c;总用你想找的…

【Java】 JAVA Notes

JAVA语言帮助笔记Java的安装与JDKJava命名规范JAVA的数据类型自动类型转换强制类型转换JAVA的运算符取余运算结果的符号逻辑运算的短路运算三元运算符运算符优先级JAVA的流程控制分支结构JAVA类Scanner类Math 类random方法获取随机数Java的安装与JDK JDK安装网站&#xff1a;h…

AXI 总线协议学习笔记(4)

引言 前面两篇博文从简单介绍的角度说明了 AXI协议规范。 AXI 总线协议学习笔记&#xff08;2&#xff09; AXI 总线协议学习笔记&#xff08;3&#xff09; 从本篇开始&#xff0c;详细翻译并学习AXI协议的官方发布规范。 文档中的时序图说明&#xff1a; AXI指&#xff1…

基础面试题:堆和栈的区别

面试题&#xff1a;堆和栈的区别&#xff08;往往讲的是内存zha&#xff09; 为什么说访问栈栈比访问堆快些&#xff1f; 目录 一、数据结构中的堆栈 1、数据结构中的堆 1&#xff09;堆的定义 2&#xff09;堆的效率 2、 数据结构中的栈 二、内存中的堆栈 1、内存堆的定义…

Stm32 for arduino STM32G071GBU6 I2C and SERIAL

文件目录: C:\Users\Administrator\AppData\Local\Arduino15\packages\STMicroelectronics\hardware\stm32\2.3.0\variants\STM32G0xx\G071G(6-8-B)U_G081GBU boards_entry.txt Generic G071GBUx GenG0.menu.pnum.GENERIC_G071GBUXGeneric G071GBUx GenG0.menu.pnum.GENERIC…

SpringMVC:统一异常处理(11)

统一异常处理1. 说明2. 问题描述3. 异常处理器使用3.1 创建异常处理器类3.2 让程序抛出异常3.3 测试4. 项目异常处理方案4.1 异常分类4.2 异常解决方案4.3 异常解决方案的具体实现4.4 测试5. 总结1. 说明 \quad本篇文章是在文章SpringMVC&#xff1a;SSM整合&#xff08;Spring…

【Vuex 源码学习】第六篇 - Vuex 的模块收集

一&#xff0c;前言 上一篇&#xff0c;主要介绍了 Vuex 中 Mutations 和 Actions 的实现&#xff0c;主要涉及以下几个点&#xff1a; 将 options 选项中定义的 mutation 方法绑定到 store 实例的 mutations 对象&#xff1b;创建并实现 commit 方法&#xff08;同步&#x…

最近挺火的人工智能chatGPT注册

文章目录1.前提预备1.1 短信接收平台1.2 ip加速&#xff0c;不做说明2.注册chatGPT步骤2.1 进入chat.openai.com网址后&#xff0c;点击sign up2.2 可以使用qq邮箱注册2.3 填写好邮箱&#xff0c;然后点击Continue,然后再填写密码2.4 之后在qq邮箱进行验证注册(注意&#xff1a…