[Java]图论详解(内附详细代码)

news2024/11/17 14:34:59


专栏简介 :MySql数据库从入门到进阶.

题目来源:leetcode,牛客,剑指offer.

创作目标:记录学习MySql学习历程

希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长.

学历代表过去,能力代表现在,学习能力代表未来! 


目录 

1.图的基本概念

2.图的存储结构

2.1 邻接矩阵

2.2 邻接表

3. 图的遍历

3.1 图的广度优先遍历

3.2 图的深度优先遍历


前言

本文旨在言简意赅的介绍图论基本知识 , 尽量避免冗杂的知识方便大家快速入门 , 进阶算法后续更新. 


 1.图的基本概念

图是由顶点集合以及顶点间的关系组成的一种数据结构:G = {V,E}.(顶点:vertex , 边:edge)

V是顶点集合 , V = {x|x属于某个对象集}.

E是集 , E = {(x,y)|x , y 属于V}或者E = {<x,y>|x , y 属于V}.

Tips: (x,y)表示x,y 之间的双向通路 , 即(x,y)是无方向的.<x,y>表示x,y之间的有向通路 . 即<x,y>是有向的.

  • 完全图

假设有n个顶点 , 每个顶点之间有且仅有1条边.完全无向图有n*(n-1)/2条边 , 完全有向图有n*(n-1)条边 , 即每个顶点之间有且仅有两条方向相反的边.

  • 领接顶点

两个顶点 v1 , v2 之间有边相连 , 则称 v1是v2的领接顶点或v2是v1的领接顶点.

  • 顶点的度

顶点的度指的是它关联边的条数.有向图中顶点的度=入度(指出顶点的边)与出度(指入顶点的边)之和.

  •  简单路径与回路

若路径上 v1,v2...vm均不重复 , 称这样的路径为简单路径. 若路径上第一个顶点 v1与最后一个顶点 vm 重合 , 则称这样的路径为回路或者环.

  • 连通图

无向图中 , 如果图中任意顶点都是连通的 , 则称此图为连通图.

  • 强连通图

有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到 vi的路
径,则称此图是强连通图

  • 生成树

一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边


2.图的存储结构

因为图中既有节点又有(节点与节点之间的关系) , 因此在图的存储中 , 我们可以使用一段连续的数组来存储节点 , 但边的关系存储较为复杂 , 通常有以下两种方式~~

2.1 邻接矩阵

因为节点与节点之间的关系就是是否连通 , 即为0 或 1 , 因此可以使用一个二维数组(领接矩阵)来保存节点与节点之间的关系.

Tips:

  • 无向图的矩阵是对称的 , 第 i 行(列)元素之和就是顶点 i 的度. 有向图的领接矩阵不一定是对称的 , 第 i 行(列)元素之和就是顶点i的出度(入度).
  • 如果边带权值 , 并且两个顶点之间是连通的 , 上图中的边的关系就用权值代替 ,  如果两个节点不通 , 则用无穷大代替.
  • 用领结矩阵存储图的优点是能够快速知道两个节点之间是否连通 , 缺陷是如果顶点较多 , 边较少(领接矩阵较为稀疏) , 矩阵中存储了大量的0 , 比较浪费空间 , 并且要求两个顶点之间的路径不是很好求.

代码实现:

package Review;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;

public class GraphOfMatrix {
    private char[] arrayV;//节点数组
    private int[][] Matrix;//领接矩阵
    private boolean isDirect;//是否是有向图
    HashMap<Character, Integer> map;//优化版的写法 , 目的是建立节点数组与其下标之间的映射关系

    //构造节点数组和领接矩阵 size表示当前节点的个数
    public GraphOfMatrix(int size, boolean isDirect) {
        arrayV = new char[size];
        Matrix = new int[size][size];
        //将领接矩阵的每一位都初始化为无穷大
        for (int i = 0; i < size; i++) {
            Arrays.fill(Matrix[i], Integer.MIN_VALUE);
        }
        this.isDirect = isDirect;
    }

    /**
     * 初始化节点数组
     *
     * @param array
     */
    public void initArray(char[] array) {

        for (int i = 0; i < array.length; i++) {
            //要么初始化节点数组 , 要么建立映射关系.二选一
            map.put(array[i], i);
//            arrayV[i] = array[i];
        }
    }

    /**
     * 添加边
     *
     * @param src    起始节点
     * @param dest   终止节点
     * @param weight 权值
     */
    public void addEdg(char src, char dest, int weight) {
        //首先要确定起始节点和终止节点在矩阵中的位置
        int srcIndex = getIndexOfV(src);
        int destIndex = getIndexOfV(dest);
        //将节点和节点之间的关系存储在矩阵中
        Matrix[srcIndex][destIndex] = weight;
        //如果是无向图 , 矩阵对称的位置同样需要赋值
        if (!isDirect) {
            Matrix[destIndex][srcIndex] = weight;
        }
    }

    /**
     * 获取节点数组的下标
     *
     * @param v
     * @return
     */
    public int getIndexOfV(char v) {
        //同样两种写法二选一
        return map.get(v);
//        for (int i = 0; i < arrayV.length; i++) {
//            if (arrayV[i]==v){
//                return i;
//            }
//        }
//        return -1;
    }

    /**
     * 获取顶点的度
     *
     * @param v 有向图 = 入度+出度
     * @return
     */
    public int getDevOfV(char v) {
        int count = 0;
        int srcIndex = getIndexOfV(v);
        for (int i = 0; i < Matrix.length; i++) {
            if (Matrix[srcIndex][i] != Integer.MIN_VALUE) {
                count++;
            }
        }
        //计算有向图的出度
        if (isDirect) {
            for (int i = 0; i < Matrix[0].length; i++) {
                if (Matrix[i][srcIndex] != Integer.MIN_VALUE) {
                    count++;
                }
            }
        }
        return count;
    }

    //打印领接表
    public void printGraph() {
        for (int i = 0; i < Matrix.length; i++) {
            for (int j = 0; j < Matrix[0].length; j++) {
                if (Matrix[i][j] != Integer.MIN_VALUE) {
                    System.out.print(Matrix[i][j] + " ");
                } else {
                    System.out.print("∞ ");
                }
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        char[] chars = {'A', 'B', 'C', 'D',};
        graph.GraphOfMatrix graph = new graph.GraphOfMatrix(chars.length, true);
        graph.initArray(chars);

        graph.addEdge('A', 'B', 1);
        graph.addEdge('A', 'D', 1);
        graph.addEdge('B', 'A', 1);
        graph.addEdge('B', 'C', 1);
        graph.addEdge('C', 'B', 1);
        graph.addEdge('C', 'D', 1);
        graph.addEdge('D', 'A', 1);
        graph.addEdge('D', 'C', 1);

        graph.printGraph();
        System.out.print("输入节点的度为: ");
        System.out.println(graph.getDevOfV('A'));
        System.out.println("=============");
    }

}


2.2 邻接表

使用数组表示节点的集合 , 使用链表表示边的关系 , 每个链表的节点中即存放边的关系也存放权重.

1. 无向图临接表存储

 Tips:

无向图中同一条边在邻接表中出现了两次 , 如果想知道某一个节点的度 , 直接计算链表集合中节点的个数即可.

2. 有向图领接表存储

Tips:

有向图中每条边在领接表中只出现一次 , 节点对应的领接表所含顶点的个数称为出度 , 该领接表也叫出度表.入度表的获取方式是查看连向目标节点的节点个数 , 最后总度=入度+出度.

代码示例: 

package Review;

import java.util.ArrayList;

public class GraphByNode {
    //构造存储边的链表
    static class Node {
        public int src;//起始位置

        public int dest;//目标位置

        public int weight;//权值

        public Node next;

        public Node(int src, int dest, int weight) {
            this.src = src;
            this.dest = dest;
            this.weight = weight;
        }
    }

    //存储节点的数组
    public char[] arrayV;
    //存在链表的集合
    public ArrayList<Node> edgList;
    //判断是否为有向图
    public boolean isDirect;

    //构造领接表
    public GraphByNode(int size, boolean isDirect) {
        arrayV = new char[size];
        edgList = new ArrayList<>(size);
        this.isDirect = isDirect;
    }

    /**
     * 初始化顶点数组
     *
     * @param array
     */
    public void initArray(char[] array) {
        for (int i = 0; i < arrayV.length; i++) {
            arrayV[i] = array[i];
        }
    }

    /**
     * 添加边
     *
     * @param src    起点
     * @param dest   终点
     * @param weight 权重
     */
    public void addEdge(char src, char dest, int weight) {
        int srcIndex = getIndexOfV(src);
        int destIndex = getIndexOfV(dest);
        addEdgeChild(srcIndex, destIndex, weight);
    }

    public void addEdgeChild(int srcIndex, int destIndex, int weight) {
        //获取链表的头结点
        Node cur = edgList.get(srcIndex);
        //遍历整个链表查看之前是否已存在该边
        while (cur != null) {
            if (cur.dest == destIndex) {
                //之前存过这条边直接返回
                return;
            }
            cur = cur.next;
        }
        //之前没有存储过这条边 , 头插法插入链表
        Node node = new Node(srcIndex, destIndex, weight);
        node.next = edgList.get(srcIndex);
        edgList.set(srcIndex, node);
    }

    /**
     * 获取 顶点下标
     *
     * @param v
     * @return
     */
    public int getIndexOfV(char v) {
        for (int i = 0; i < arrayV.length; i++) {
            if (arrayV[i] == v) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 获取顶点的度
     *
     * @param v
     * @return
     */
    public int getDevOfV(char v) {
        int count = 0;
        int srcIndex = getIndexOfV(v);
        Node cur = edgList.get(srcIndex);
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        //以上仅仅计算了出度 , 还需计算入度
        //遍历除了自身外 , 每一个顶点对应的链表中节点是否有指向srcIndex的.
        if (isDirect) {
            //将srcIndex当做目的下标.
            int destIndex = srcIndex;
            for (int i = 0; i < arrayV.length; i++) {
                //出去自身
                if (destIndex == i) {
                    continue;
                }
                Node pCur = edgList.get(i);
                while (pCur != null) {
                    if (pCur.dest == destIndex) {
                        count++;
                    }
                    pCur = pCur.next;
                }
            }
        }
        return count;
    }

    /**
     * 打印领接表
     */
    public void printGraph() {
        for (int i = 0; i < arrayV.length; i++) {
            System.out.println(arrayV[i] + "->");
            Node cur = edgList.get(i);
            while (cur != null) {
                System.out.println(arrayV[cur.dest] + "->");
                cur = cur.next;
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        graph.GraphByNode graph = new graph.GraphByNode(4, false);
        char[] array = {'A', 'B', 'C', 'D',};
        graph.initArray(array);

        graph.addEdge('A', 'B', 1);
        graph.addEdge('A', 'D', 1);
        graph.addEdge('B', 'A', 1);
        graph.addEdge('B', 'C', 1);
        graph.addEdge('C', 'B', 1);
        graph.addEdge('C', 'D', 1);
        graph.addEdge('D', 'A', 1);
        graph.addEdge('D', 'C', 1);

        graph.printGraph();
        System.out.println(graph.getDevOfV('A'));
        System.out.println("=============");
    }
}


3. 图的遍历

3.1 图的广度优先遍历

广度优先遍历类似于二叉树的层序遍历 , 由于二叉树的层序遍历借助队列 , 那么图的广度优先遍历也要借助队列.广度优先遍历每次都访问起始节点相邻的所有节点 , 下图中的访问顺序就是B->A->C->D.

图示过程: 

Tips:

注意入队和出队都要将visited数组下标置为true , 否则会出现多次打印最后一个元素的情况. 

示例代码: 

 /**
     * 广度优先搜索
     * @param v
     */
    public void bfs(char v) {
        //获取起始节点的下标
        int srcIndex = getIndexOfV(v);
        //调用队列
        Queue<Integer> queue = new LinkedList<>();
        //使用visited数组记录节点是否被访问
        boolean[] visited = new boolean[arrayV.length];
        queue.offer(srcIndex);
        while (!queue.isEmpty()) {
            int top = queue.poll();
            visited[top] = true;//每弹出一个元素visited数组相应下标就置为true
            System.out.println(arrayV[top] + "->");
            for (int i = 0; i < arrayV.length; i++) {//搜索领接矩阵中起始节点行的每一个元素
                if (Matrix[top][i] != Integer.MAX_VALUE && visited[i] != true) {
                    queue.offer(i);
                    visited[i] = true;//每存入一个元素visited数组相应下标就置为true
                }
            }
        }
    }

3.2 图的深度优先遍历

图的深度优先优先遍历类型与二叉树的前序遍历 , 需要递归实现.从起始位置一条路走到底 , 再返回寻找下一条路.返回时需要一个visited数组记录元素使用遍历过.

图示过程: 

代码示例: 

/**
     * 深度优先遍历
     *
     * @param v 起始元素
     */
    public void dfs(char v) {
        int srcIndex = getIndexOfV(v);
        boolean[] visited = new boolean[arrayV.length];
        dfsChild(srcIndex, visited);
    }

    public void dfsChild(int srcIndex, boolean[] visited) {
        System.out.println(arrayV[srcIndex] + "->");
        visited[srcIndex] = true;
        for (int i = 0; i < Matrix[srcIndex].length; i++) {
            if (Matrix[srcIndex][i] != Integer.MAX_VALUE) {
                dfsChild(i, visited);
            }
        }
    }

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

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

相关文章

[Linux]------线程控制与互斥

文章目录前言一、进程VS线程空间共享二、线程控制POSIX线程库创建线程获取线程IDpthread_join线程异常第二个参数线程的局部存储线程的分离exit()三、线程的互斥进城线程间的互斥相关背景概念互斥量mutex模拟抢票逻辑解决问题互斥量实现原理探究基于RAII机制锁的模拟实现四、可…

Linux 线程控制 —— 线程清理 pthread_cleanup_push

主线程可以通道 pthread_cancel 主动终止子线程&#xff0c;但是子线程中可能还有未被释放的资源&#xff0c;比如malloc开辟的空间。如果不清理&#xff0c;很有可能会造成内存泄漏。 // 子线程回调函数 void* thread_run(void* args) {int* p (int*)malloc(100); // 动…

中小企业选择ERP系统时应关注的10个关键功能

现代ERP系统是帮助企业实现提高生产力、增加盈利能力和提高竞争力的目标的好帮手。该类软件旨在满足中小企业不断增长的业务需求&#xff0c;可确保整个企业的健康发展。 每天都有新的ERP功能和应用程序进入市场&#xff0c;如何明智选择至关重要。以下是中小企业在选择现代ERP…

Elasticsearch_第2章_ elasticsearch基础

Elasticsearch_第2章_ elasticsearch基础 文章目录Elasticsearch_第2章_ elasticsearch基础0.学习目标1.DSL查询文档1.1.DSL查询分类1.2.全文检索查询1.2.1.使用场景1.2.2.基本语法1.2.3.示例1.2.4.总结1.3.精准查询1.3.1.term查询1.3.2.range查询1.3.3.总结1.4.地理坐标查询1.…

SSM+JSP实现《吃货联盟外卖系统》

&#x1f345;程序员小王的博客&#xff1a;程序员小王的博客 &#x1f345;程序员小王的资源博客&#xff1a;http://wanghj.online/ &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 如有编辑错误联系作者&#xff0c;如果有比较好的文章欢迎…

上海亚商投顾:沪指冲高回落 纺织服装股午后集体走强

上海亚商投顾前言&#xff1a;无惧大盘大跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪沪指今日缩量震荡&#xff0c;以中字头为首的权重股走低&#xff0c;上证50午后一度跌近1%&#xff0c;创业板指较为…

秋招|阿里测试开发岗面经(一共七次面试)

三月份的时候投了阿里的实习&#xff0c;然后基本上是一周面一次&#xff0c;前前后后一个月。实习通过了&#xff0c;但是后面因为有事&#xff0c;所以没能去成暑期实习&#xff0c;部门leader人很好&#xff0c;说是可以在秋招的时候再补上终面&#xff0c;于是就有了一共七…

jQuery easyui源码赏析

引子 jQuery未过时&#xff0c;在一些中小网站中&#xff0c;jQuery还是发挥着瑞士军刀的作用。我们不能为了前后端分离而分离&#xff0c;一些很简单的需求&#xff0c;很简单的页面&#xff0c;freemarkerjQuerybootstrap就能搞掂&#xff0c;奈何一定要搬动vue和react这些大…

[附源码]计算机毕业设计影评网站系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【雷达回波】基于matlab天气观测极化雷达回波仿真【含Matlab源码 2252期】

⛄一、天气观测极化雷达回波仿真简介 本示例展示了如何模拟满足天气观测要求的极化多普勒雷达回波。雷达在天气观测、灾害检测、降水分类和量化以及预报方面发挥着关键作用。此外&#xff0c;极化雷达以前所未有的质量和信息提供多参数测量。此示例演示如何模拟扫描分布式天气…

Java项目:ssm+jsp实现手机WAP版外卖订餐系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 这是一款由jspssm框架&#xff08;spring、springMVC、mybaits&#xff09;实现的手机WAP版外卖订餐系统。 本系统前台页面是手机端的&#xf…

如何发布一个 npm 包

前言 npm&#xff08;node package manager&#xff09;作为 Node.js 的包管理工具&#xff0c;让世界各地的 JavaScript 开发者方便复用、分享代码以及造轮子&#xff1b;本文将介绍如何发布一个 npm 包&#xff0c;以及使用工具来自动化管理发布 npm 包&#xff1b;本文总览…

Java代码审计——WebGoat CSRF (上)

目录 前言&#xff1a; &#xff08;一&#xff09;CSRF 0x01、简单介绍 0x02、实际案例 1&#xff0e;对 Referer 过滤不严导致的 CSRF 漏洞 2&#xff0e;token 可重用导致 CSRF 漏洞 3、webGoat中的CSRF 0x03 防御 3.1 STP 3.2 检查 Referer 字段 3.3 检查 Referer…

GMO Research 2022年旅游调查:旅游业有望强劲增长

GMO Research (TOKYO: 3695)最近进行的一项旅行调查显示&#xff0c;随着边境再次开放&#xff0c;亚洲正在逐渐恢复正常的旅行模式。尽管该地区仍没有达到疫情前水平&#xff0c;旅行者仍持谨慎态度&#xff0c;但他们对海外旅行的兴趣显著增加。 为了解旅行模式和旅行意愿&a…

Intel OpenVINO 安装显卡驱动

背景&#xff1a; 使用集合诚KMDA-3301 OpenVINO CPU和GPU 算法加速&#xff0c;用GPU加速时&#xff0c;调动不起来。写下解决过程&#xff0c;以备后用。 过程&#xff1a; 调动GPU 报错&#xff1a; terminate called after throwing an instance of InferenceEngine::G…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java高校师生党建管理系统4d8du

最近大四学弟学妹们开始准备设计了&#xff0c;有一些问题问我&#xff0c;比如设计怎么做&#xff0c;有没有模板等等吧&#xff0c;大家都没有去学校&#xff0c;老师都是通过远程指导的&#xff0c;答辩也是远程答辩&#xff0c;这种情况下同学们不在一起&#xff0c;可能碰…

day13【代码随想录】环形链表II、环形链表、快乐数、各位相加、丑数、丑数||

文章目录一、环形链表 II&#xff08;力扣142&#xff09;二、环形链表&#xff08;力扣141&#xff09;三、快乐数&#xff08;力扣202&#xff09;四、各位相加&#xff08;力扣258&#xff09;五、丑数&#xff08;力扣263&#xff09;六、丑数||&#xff08;力扣264&#x…

JavaScript:File API和Blob API

web应用的痛点就是不能操作计算机上的文件。File API和Blob API可以安全访问到客户端上的文件。 File类型 现在我们可以在html表单中直接访问文件&#xff0c;比如&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"U…

PaddleOCR服务部署-并通过Java进行调用

上一篇讲了PaddleOCR的简单使用&#xff0c;但是最终的目的肯定是要将它进行服务部署方便我们调用的&#xff0c;这里介绍一下他的服务部署方式 选择部署方式 官方推荐有以下几种&#xff1a; Python 推理 C 推理 Serving 服务化部署&#xff08;Python/C&#xff09; Paddle…

电影寒冬之下,票房靠“主旋律”能撑住场吗?《扫黑行动》仍在重播

春节将近&#xff0c;各大院线陆陆续续公布了春节档将要上映的影片档期&#xff0c;小伙伴们是不是也对近期热门的影片有了兴趣&#xff0c;想要一饱眼福了呢。下面是小编根据网络公布的数据进行报表数据处理分析后得到的数据可视化图&#xff0c;展示了近期一些热门影片的情况…