目录

  • 一、简介
  • 二、无向图
    • 1. 相关术语
    • 2. 存储结构
      • 1. 邻接矩阵
      • 2. 邻接表
    • 3. 无向图的实现
    • 4. 加权无向图
      • ① 加权无向图边的表示
      • ② 加权无向图的实现
  • 三、有向图
    • 1. 相关术语
    • 2. 有向图的实现
    • 3. 加权有向图
      • ① 加权有向图边的表示
      • ② 加权有向图的实现
  • 四、图的遍历
    • 1. 深度优先搜索
    • 2. 广度优先搜索
  • 五、路径查找

一、简介

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


二、无向图

若E是无向边(简称边)的有限集合时,则图G为无向图。边是顶点的无序对,记为(v, w)或(w,v),因为(v,w)=(w,v), 其中v,w是顶点。可以说顶点w和顶点v互为邻接点。边(v, w)依附于顶点w和v,或者说边(v, w)和顶点v, w相关联。

图所示的无向图G2可表示为

1. 相关术语

相邻顶点:

当两个顶点通过一条边相连时,我们称这两个顶点是相邻的,并且称这条边依附于这两个顶点

度:

某个顶点的度就是依附于该顶点的边的个数

子图:

是一幅图的所有边的子集(包含这些边依附的顶点)组成的图

路径:

是由边顺序连接的一系列的顶点组成

环:

是一条至少含有一条边且终点和起点相同的路径

连通图:

如果图中任意一个顶点都存在一条路径到达另外一个顶点,那么这幅图就称之为连通图

连通子图:

一个非连通图由若干连通的部分组成,每一个连通的部分都可以称为该图的连通子图

2. 存储结构

要表示一幅图,只需要表示清楚以下两部分内容即可:

  1. 图中所有的顶点

  2. 所有连接顶点的边

常见的图的存储结构有两种:邻接矩阵和邻接表

1. 邻接矩阵

  • 使用一个V * V的二维数组int[V][V] adj,把索引的值看做是顶点;

  • 如果顶点v和顶点w相连,我们只需要将adj[v][w]adj[w][v]的值设置为1,否则设置为0即可。

很明显,邻接矩阵这种存储方式的空间复杂度是V^2的,如果我们处理的问题规模比较大的话,内存空间极有可能不够用。

2. 邻接表

  • 使用一个大小为V的数组 Queue[V] adj,把索引看做是顶点;

  • 每个索引处adj[v]存储了一个队列,该队列中存储的是所有与该顶点相邻的其他顶点

很明显,邻接表的空间并不是是线性级别的,所以大多数情况下都采用邻接表这种存储形式来表示图。

3. 无向图的实现

无向图API设计

import java.util.LinkedList;public class Graph {// 顶点数目private final int V;// 边的数目private int E;// 邻接表private LinkedList<Integer>[] adj;public Graph(int V){// 初始化顶点数量this.V = V;// 初始化边的数量this.E = 0;// 初始化邻接表this.adj = new LinkedList[V];for (int i = 0; i < adj.length; i++) {adj[i] = new LinkedList<Integer>();}}// 获取顶点数目public int V(){return V;}// 获取边的数目public int E(){return E;}// 向图中添加一条边 v-wpublic void addEdge(int v, int w) {// 在无向图中,边是没有方向的,所以该边既可以说是从v到w的边,又可以说是从w到v的边,// 因此,需要让w出现在v的邻接表中,并且还要让v出现在w的邻接表中adj[v].add(w);adj[w].add(v);// 边的数量+1E ++;}// 获取和顶点v相邻的所有顶点public LinkedList<Integer> adj(int v){return adj[v];}
}

4. 加权无向图

加权无向图是一种为每条边关联一个权重值或是成本的图模型。这种图能够自然地表示许多应用。在一副航空图中,边表示航线,权值则可以表示距离或是费用。在一副电路图中,边表示导线,权值则可能表示导线的长度即成本,或是信号通过这条先所需的时间。

在下图中,从顶点0到顶点4有三条路径,分别为0-2-3-4,0-2-4,0-5-3-4,那我们如果要通过那条路径到达4顶点最好呢?此时就要考虑,那条路径的成本最低。

① 加权无向图边的表示

加权无向图中的边我们就不能简单的使用v-w两个顶点表示了,而必须要给边关联一个权重值,因此我们可以使用对象来描述一条边。

API设计

public class Edge implements Comparable<Edge> {// 顶点一private final int v;// 顶点二private final int w;// 当前边的权重private final double weight;// 通过顶点v和w,以及权重weight值构造一个边对象public Edge(int v, int w, double weight) {this.v = v;this.w = w;this.weight = weight;}// 获取边的权重值public double weight(){return weight;}// 获取边上的一个点public int either(){return v;}// 获取边上除了顶点vertex外的另外一个顶点public int other(int vertex){if (vertex == v){return w;}else{return v;}}@Overridepublic int compareTo(Edge that) {/*如果当前边的权重值大 return 1如果当前边的权重值小 return -1如果权重值一样大    return 0*/return Double.compare(this.weight, that.weight);}
}

② 加权无向图的实现

之前我们已经完成了无向图,在无向图的基础上,我们只需要把边的表示切换成Edge对象即可。

API设计

import java.util.LinkedList;
import java.util.Queue;public class EdgeWeightedGraph {// 顶点总数private final int V;// 边的总数private int E;// 邻接表private Queue<Edge>[] adj;// 创建一个含有V个顶点的空加权无向图public EdgeWeightedGraph(int V) {// 初始化顶点数量this.V = V;// 初始化边的数量this.E = 0;// 初始化邻接表this.adj = new LinkedList[V];for (int i = 0; i < adj.length; i++) {adj[i] = new LinkedList<Edge>();}}// 获取图中顶点的数量public int V() {return V;}// 获取图中边的数量public int E() {return E;}// 向加权无向图中添加一条边epublic void addEdge(Edge e) {// 需要让边e同时出现在e这个边的两个顶点的邻接表中int v = e.either();int w = e.other(v);adj[v].add(e);adj[w].add(e);// 边的数量+1E++;}// 获取和顶点v关联的所有边public Queue<Edge> adj(int v) {return adj[v];}// 获取加权无向图的所有边public Queue<Edge> edges() {// 创建一个队列对象,存储所有的边Queue<Edge> allEdges = new LinkedList<>();/* 遍历图中的每一个顶点,找到该顶点的邻接表,邻接表中存储了该顶点关联的每一条边.*/for(int v = 0; v < V; v++){// 遍历v顶点的邻接表,找到每一条和v关联的边for (Edge e : adj(v)) {/*因为这是无向图,所以同一条边同时出现在了它关联的两个顶点的邻接表中,需要让一条边只记录一次;*/if (e.other(v) < v){allEdges.add(e);}}}return allEdges;}
}

三、有向图

若E是有向边(也称弧)的有限集合时,则图G为有向图。弧是顶点的有序对,记为<v, w>,其中v,w是顶点,v称为弧尾,w称为弧头,<v,w>称为从顶点v到顶点w的弧,也称v邻接到w,或w邻接自v。

图所示的有向图 G1可表示为

1. 相关术语

定义:

有向图是一副具有方向性的图,是由一组顶点和一组有方向的边组成的,每条方向的边都连着一对有序的顶点

出度:

由某个顶点指出的边的个数称为该顶点的出度

入度:

指向某个顶点的边的个数称为该顶点的入度

有向路径:

由一系列顶点组成,对于其中的每个顶点都存在一条有向边,从它指向序列中的下一个顶点

有向环:

一条至少含有一条边,且起点和终点相同的有向路径。

一副有向图中两个顶点v和w可能存在以下四种关系:

  1. 没有边相连;

  2. 存在从v到w的边v—>w;

  3. 存在从w到v的边w—>v;

  4. 既存在w到v的边,也存在v到w的边,即双向连接;

2. 有向图的实现

有向图API设计


import java.util.LinkedList;public class Digraph {// 顶点数目private final int V;// 边的数目private int E;// 邻接表private LinkedList<Integer>[] adj;public Digraph(int V){// 初始化顶点数量this.V = V;// 初始化边的数量this.E = 0;// 初始化邻接表this.adj = new LinkedList[V];for (int i = 0; i < adj.length; i++) {adj[i] = new LinkedList<Integer>();}}// 获取顶点数目public int V(){return V;}// 获取边的数目public int E(){return E;}// 向有向图中添加一条边 v->wpublic void addEdge(int v, int w) {/*只需要让顶点w出现在顶点v的邻接表中,因为边是有方向的,最终,顶点v的邻接表中存储的相邻顶点的含义是:  v -> 其他顶点*/adj[v].add(w);E ++;}// 获取由v指出的边所连接的所有顶点public LinkedList<Integer> adj(int v){return adj[v];}// 该图的反向图private Digraph reverse(){// 创建有向图对象Digraph r = new Digraph(V);for (int v = 0; v < V; v++){// 获取由该顶点v指出的所有边// 原图中表示的是由顶点v->w的边for (Integer w : adj[v]) {// 转换指向为 w->vr.addEdge(w, v);}}return r;}
}

3. 加权有向图

上述提到的加权无向图中,边是没有方向的,并且同一条边会同时出现在该边的两个顶点的邻接表中,为了能够处理含有方向性的图的问题,我们需要使用加权有向图。

① 加权有向图边的表示

public class DirectedEdge {// 起点private final int v;// 终点private final int w;// 当前边的权重private final double weight;// 通过顶点v和w,以及权重weight值构造一个边对象public DirectedEdge(int v, int w, double weight) {this.v = v;this.w = w;this.weight = weight;}//获取边的权重值public double weight(){return weight;}// 获取有向边的起点public int from(){return v;}// 获取有向边的终点public int to(){return w;}
}

② 加权有向图的实现

之前我们已经完成了有向图,在有向图的基础上,我们只需要把边的表示切换成DirectedEdge对象即可。

API设计

import java.util.LinkedList;
import java.util.Queue;public class EdgeWeightedDigraph {// 顶点总数private final int V;// 边的总数private int E;// 邻接表private Queue<DirectedEdge>[] adj;// 创建一个含有V个顶点的空加权有向图public EdgeWeightedDigraph(int V) {// 初始化顶点数量this.V = V;// 初始化边的数量this.E = 0;// 初始化邻接表this.adj = new LinkedList[V];for (int i = 0; i < adj.length; i++) {adj[i] = new LinkedList<DirectedEdge>();}}// 获取图中顶点的数量public int V() {return V;}// 获取图中边的数量public int E() {return E;}// 向加权有向图中添加一条边epublic void addEdge(DirectedEdge e) {// 边e是有方向的,所以只需要让e出现在起点的邻接表中即可int v = e.from();adj[v].add(e);E++;}// 获取由顶点v指出的所有的边public Queue<DirectedEdge> adj(int v) {return adj[v];}// 获取加权有向图的所有边public Queue<DirectedEdge> edges() {/*遍历图中的每一个顶点,得到该顶点的邻接表,遍历得到每一条边,添加到队列中返回即可*/Queue<DirectedEdge> allEdges = new LinkedList<>();for (int v = 0;v<V;v++){for (DirectedEdge edge : adj[v]) {allEdges.add(edge);}}return allEdges;}
}

四、图的遍历

在很多情况下,我们需要遍历图,得到图的一些性质,例如,找出图中与指定的顶点相连的所有顶点,或者判定某个顶点与指定顶点是否相通,是非常常见的需求。有关图的搜索,最经典的算法有深度优先搜索和广度优先搜索,接下来我们分别讲解这两种搜索算法。

1. 深度优先搜索

所谓的深度优先搜索,指的是在搜索时,如果遇到一个结点既有子结点,又有兄弟结点,那么先找子结点,然后找兄弟结点。


API设计

public class DepthFirstSearch {// 索引代表顶点,值表示当前顶点是否已经被搜索private boolean[] marked;// 记录有多少个顶点与 s 顶点相通private int count;// 构造深度优先搜索对象,使用深度优先搜索找出 G 图中 s 顶点的所有相邻顶点public DepthFirstSearch(Graph G, int s){// 初始化marked数组this.marked = new boolean[G.V()];// 初始化跟顶点s相通的顶点的数量this.count = 0;dfs(G,s);}// 使用深度优先搜索找出G图中v顶点的所有相通顶点private void dfs(Graph G, int v){// 把v顶点标识为已搜索marked[v] = true;for (Integer w : G.adj(v)) {/*判断当前w顶点有没有被搜索过,如果没有被搜索过,则递归调用dfs方法进行深度搜索*/if (!marked[w]){dfs(G,w);}}// 相通顶点数量+1count++;}// 判断w顶点与s顶点是否相通public boolean marked(int w){return marked[w];}// 获取与顶点s相通的所有顶点的总数public int count(){return count;}
}

2. 广度优先搜索

所谓的深度优先搜索,指的是在搜索时,如果遇到一个结点既有子结点,又有兄弟结点,那么先找兄弟结点,然后找子结点。

import java.util.LinkedList;public class BreadthFirstSearch {// 索引代表顶点,值表示当前顶点是否已经被搜索private boolean[] marked;// 记录有多少个顶点与s顶点相通private int count;// 用来存储待搜索邻接表的点private LinkedList<Integer> waitSearch;// 构造广度优先搜索对象,使用广度优先搜索找出 G 图中 s 顶点的所有相邻顶点public BreadthFirstSearch(Graph G, int s) {this.marked = new boolean[G.V()];this.count = 0;this.waitSearch = new LinkedList<Integer>();bfs(G,s);}// 使用广度优先搜索找出G图中v顶点的所有相邻顶点private void bfs(Graph G, int v) {// 把当前顶点v标识为已搜索marked[v] = true;// 让顶点 v 的相邻点都进入队列waitSearch.addAll(G.adj(v));// 通过循环,如果队列不为空,则从队列中弹出一个待搜索的顶点进行搜索while(!waitSearch.isEmpty()){// 弹出一个待搜索的顶点Integer wait = waitSearch.poll();if (!marked[wait]){bfs(G, wait);}}// 让相通的顶点+1;count++;}// 判断w顶点与s顶点是否相通public boolean marked(int w) {return marked[w];}// 获取与顶点s相通的所有顶点的总数public int count() {return count;}
}

五、路径查找

在实际生活中,地图是我们经常使用的一种工具,通常我们会用它进行导航,输入一个出发城市,输入一个目的地城市,就可以把路线规划好,而在规划好的这个路线上,会路过很多中间的城市。这类问题翻译成专业问题就是:从s顶点到v顶点是否存在一条路径?如果存在,请找出这条路径。


例如在上图上查找顶点0到顶点4的路径用红色标识出来,那么我们可以把该路径表示为 0-2-3-4。

public class DepthFirstPaths {// 索引代表顶点,值表示当前顶点是否已经被搜索private boolean[] marked;// 起点private int s;// 索引代表顶点,值代表从起点s到当前顶点路径上的最后一个顶点private int[] edgeTo;// 构造深度优先搜索对象,使用深度优先搜索找出G图中起点为s的所有路径public DepthFirstPaths(Graph G, int s){// 初始化marked数组this.marked = new boolean[G.V()];// 初始化起点this.s = s;// 初始化edgeTo数组this.edgeTo = new int[G.V()];dfs(G,s);}// 使用深度优先搜索找出 G 图中 v 顶点的所有相邻顶点private void dfs(Graph G, int v){// 把v表示为已搜索marked[v] = true;// 遍历顶点v的邻接表,拿到每一个相邻的顶点,继续递归搜索for (Integer w : G.adj(v)) {// 如果顶点w没有被搜索,则继续递归搜索if (!marked[w]){edgeTo[w] = v;//到达顶点w的路径上的最后一个顶点是vdfs(G,w);}}}// 判断w顶点与s顶点是否存在路径public boolean hasPathTo(int v){return marked[v];}// 找出从起点s到顶点v的路径(就是该路径经过的顶点)public ArrayList<Integer> pathTo(int v){if (!hasPathTo(v)){return null;}// 创建对象,保存路径中的所有顶点ArrayList<Integer> path = new ArrayList<>();// 通过循环,从顶点v开始,一直往前找,到找到起点为止for (int x = v; x != s; x = edgeTo[x]){path.add(x);}// 把起点s放到栈中path.add(s);Collections.reverse(path);return path;}
}

Java数据结构-图相关推荐

  1. Java数据结构—图(Graph)

    图(Graph)是由节点(Vertex)和边(Edge)构成的一种数据结构.节点表示图中的元素,边表示节点之间的关系.图可以用于描述许多现实世界中的问题,例如社交网络.路线规划等. 在Java中,可以 ...

  2. java队列_如何彻底搞懂 Java 数据结构?CSDN 博文精选

    作者 | 张振华.Jack 责编 | 郭芮 出品 | CSDN 博客 本文和大家一起来重温<Java数据结构>经典之作. Java数据结构 要理解Java数据结构,必须能清楚何为数据结构? ...

  3. java数据结构 队列_Java数据结构与算法[原创]——队列

    声明:码字不易,转载请注明出处,欢迎文章下方讨论交流. 前言:Java数据结构与算法专题会不定时更新,欢迎各位读者监督.本文介绍数据结构中的队列(queue)的概念.存储结构.队列的特点,文末给出ja ...

  4. 数据结构 python的书推荐-java数据结构书一般推荐看什么好?

    想要学习java的各种数据结构,一本良好的书籍会让你受益匪浅,本文就来推荐一些学习java数据结构适合看的书. 一.入门推荐 因为是入门,所以我们先不要求实现,阅读一些通过图片,打比方等通俗易懂的方法 ...

  5. java数据结构实验一顺序表,java数据结构实验代码之升序顺序表

    java数据结构实验代码之升序顺序表 数据结构实验报告 学院:管理学院 班级:13电子商务(1)班 姓名:廖秋君 学号:3213004779 2014年 10月 23 日 目录 一.需求分析----- ...

  6. JAVA数据结构与算法【简单介绍】

    前几天去面一个大厂,面试官特别好,面试官说到,我们的学习不能本末倒置,数据结构和算法是程序的基础,如果数据结构你没有学好,你真正意义上不算会写代码.你的代码是各处粘贴,杂乱无章的. 由于现在大多用JA ...

  7. java 固定长度队列_如何彻底搞懂 Java 数据结构?|CSDN 博文精选

    作者 | 张振华.Jack 责编 | 郭芮 出品 | CSDN 博客 本文和大家一起来重温<Java数据结构>经典之作. Java数据结构 要理解Java数据结构,必须能清楚何为数据结构? ...

  8. Java数据结构的知识体系

    Java数据结构的知识体系主要包括线性表,树,图,数组,集合,矩阵,排序,查询,哈希表,并将java的设计思想,方法及一些常用的算法,设计模式贯穿其中. 其中线性表,链表和哈希表示最为常用的数据结构, ...

  9. 如何彻底搞懂 Java 数据结构?|CSDN 博文精选

    作者 | 张振华.Jack 责编 | 郭芮 出品 | CSDN 博客 本文和大家一起来重温<Java数据结构>经典之作. Java数据结构 要理解Java数据结构,必须能清楚何为数据结构? ...

最新文章

  1. 安装Python2.7出现configure: error: no acceptable C compiler found in $PATH错误
  2. Citrix VDI实战攻略之八:测试验收
  3. Redis的Expire与Setex
  4. Python基础教程:list列表、tuple元组、range常用方法总结
  5. JZOJ 3600. 【CQOI2014】通配符匹配
  6. java的css的块_CSS块宽度不大于前一个块
  7. UVA 694-The Collatz Sequence
  8. C++ 11 Lambda表达式
  9. 塔菲克蓝牙适配器驱动_TAFIQ蓝牙适配器驱动(TAFIQ蓝牙设备驱动程序)V4.1 正式版...
  10. cfree 上面工具栏消失解决办法(不用重下!!!!!)
  11. 几个鲜为人知但很有用的 HTML 属性
  12. AMD/CMD/CommonJs到底是什么?它们有什么区别?
  13. 计算车号Java,汽车VIN码校验算法 java版
  14. 全国计算机软考中级哪个科目最简单,软考中级哪个科目比较容易考?
  15. Synaptics FP Sensors(WBF)(PID=0011)无法录入Windows Hello问题记录
  16. linux下无论什么命令都command not fount
  17. Shell脚本一键安装软件
  18. python2的n次方代码_关于python:*与**的2次幂运算
  19. 学习笔记5-MPU6050模块
  20. 此电脑右键管理提示windows找不到文件的解决方法

热门文章

  1. MEM/MBA数学强化(04)方程 函数 不等式
  2. emacs 中文设置与输入法安装
  3. 切削力matlab数据处理,基于MATLAB的切削力测量实验数据的处理及分析
  4. 冻库正常低压力是多少_冰箱系统压力正常是多少-冰柜r134运行低压压力
  5. 计算机企业照片墙设计效果图,怎么用自己的照片做电脑墙纸
  6. C++ vector 容器的全排列算法 next_permutation
  7. 16进制转2进制代码
  8. halcon 纹理检测_Halcon 纹理缺陷检测 apply_texture_inspection_model
  9. python导入库的简便方法
  10. git commit 提交报错 husky > pre-commit 问题