ADT: Graph 图

文章目录

  • ADT: Graph 图
    • 简介
    • 参考
  • 正文
    • 名词解释
    • 图的定义和表示
    • 抽象接口
    • 两种存储实现
      • 邻接矩阵(Adjacent-Matrix)
      • 邻接链表(Adjacent-List)
    • Java 实现
      • 接口
      • 邻接矩阵实现
      • 邻接表实现
      • 测试
  • 结语

简介

图(Graph)数据结构是一个较为复杂,但是却能很好描述现实世界的各种信息,利用节点(Vertex)表示实体(entity),以边(Edge)描述实体间的联系。例如:以人为节点,人的交友关系为边;以网页为节点,以链接为边等。透过建立图数据结构,我们可以进行一系列的图算法来对图进行搜索、迭代、排序等。

与图相关的基础算法可以分成几类(参考:算法导论)

  • 搜索算法:深度优先搜索 DFS、广度优先搜索 BFS
  • 生成树算法:最小生成树算法
  • 路径搜索算法:单源最短路径算法、所有节点对间最短路径
  • 流网络:计算最大流

本篇主要介绍图数据结构的定义和两种存储实现:邻接矩阵(Adjacent-Matrix) & 邻接表(Adjacent-List)

参考

正文

名词解释

  • 顶点 Vertex:图中顶点,通常代表某个实体

    • 键 Key:能唯一表示顶点的标识
    • 卫星数据 Data:顶点上附带的额外信息
  • 边 Edge:图中的边,表示实体间的联系
  • 有向边:具有方向的边,v1 指向 v2 的边可表示为 v1 -> v2
  • 无向边:边不具有方向性,也可以看作两个方向同时存在的边
  • 出边:从 v1 指向 v2 的边定义为 v1 的一个出边
  • 入边:从 v1 指向 v2 的边定义为 v2 的一个入边

图的定义和表示

图数据结构的定义如下:

图 G = (V, E)
V 为顶点(Vertex)的集合
E 为边(Edge)的集合

每个节点需要有一个键(key)来唯一表示,同时可选附带卫星数据;每个边则是描述一个顶点到另一个顶点的有向或无向边,同时也能附带卫星数据(常见附带一个权值 weight)

  • 无权值无向图

  • 带权值有向图

我们可以从示例中看出,图其实就是一种更自由的树结构,图结构不必须存在一个根节点,子节点也能够自由指向父节点。

抽象接口

由于后续章节会介绍一些图算法,同时又不想因为实现方式的差异而污染算法本身的思想,故这边先抽象出图数据结构几个对外开放的接口,以下为接口伪代码:

Vertex 顶点(v)KEY()获取顶点的关键字DATA()获取顶点的卫星数据Edge 边(e)FROM()获取边来自的顶点TO()获取边去往的顶点WEIGHT()获取边上的权值(或是卫星数据)Graph 图(G = (V, E))VERTICES()获取图中所有的顶点ADJ-VERTICES(v)根据给定节点,返回所有相邻的顶点(有出边指向的顶点)EDGES()获取图中所有的边ADJ-EDGES(v)根据给定节点,返回顶点所有的出边ADD-VERTEX(key)给定键值添加顶点ADD-EDGE(v1, v2)增加一条由 v1 指向 v2 的边

两种存储实现

定义好了公共接口之后,我们来看看两种存储实现方式的结构差异以及实现需要的要素

我们以下图的图结构实例来讲解两种实现方式的差异:

邻接矩阵(Adjacent-Matrix)

使用邻接矩阵存储我们首先需要用一个数组保存所有顶点(包括键值和卫星数据),然后我们再使用一个矩阵保存所有边信息(权重或是卫星数据)(这里先不考虑多重边),保存结构如下图

由于使用矩阵保存边信息,我们很容易发现使用邻接矩阵表示稀疏图(|E| << |V|<sup>2</sup>)的时候,会造成大量的空间浪费。

邻接链表(Adjacent-List)

与邻接矩阵相似的是我们依旧需要先用一个数组保存所有顶点信息,不过边信息我们为了节省空间(稀疏图时效果更为明显),我们使用一个链表的数组来记录边的信息,第 i 串链表对应了第 i 个节点的所有出边,如下图

使用链表的优点在于消去稀疏图使用矩阵保存时的空间浪费,但是在查找各个顶点的的出边时的代价也相应的提升了(数组随机访问 -> 链表按序查找)

Java 实现

最后附上我自己捣鼓出来的图数据结构存储方式的实现,这边给出的是有向图的两种实现,无向图可以在增加边的时候同时添加双向的边即可(考虑的不周到的部分欢迎在下面留言指出

接口

  • Vertex.java
package adt.graph;/*** 图节点* @param <T>*/
public interface Vertex<K, T> {/*** 取得节点 id* @return*/K getKey();/*** 取得节点卫星数据* @return*/T getData();/*** 重新赋值卫星数据* @param t* @return*/boolean setData(T t);
}
  • Edge.java
package adt.graph;/*** 图边*/
public interface Edge<K, T, W> {Object nil = new Object();/*** 边离开的节点* @return*/Vertex<K, T> from();/*** 边进入的节点* @return*/Vertex<K, T> to();/*** 获取边权重/卫星数据* @return*/W getWeight();
}
  • Graph.java
package adt.graph;import java.util.List;/*** 图** @param <T>*/
public interface Graph<K, T, W> {class DefaultVertex<K, T> implements Vertex<K, T> {private K key;private T data;public DefaultVertex(K key, T data) {this.key = key;this.data = data;}@Overridepublic K getKey() {return key;}@Overridepublic T getData() {return data;}@Overridepublic boolean setData(T t) {if (t == null) return false;data = t;return true;}@Overridepublic String toString() {if (data == null) {return String.valueOf(key);}return "(key=" + key + ", data=" + data + ')';}}class DefaultEdge<K, T, W> implements Edge<K, T, W> {private Vertex<K, T> from;private Vertex<K, T> to;private W weight;public DefaultEdge(Vertex<K, T> from, Vertex<K, T> to) {this.from = from;this.to = to;this.weight = (W)Edge.nil;}public DefaultEdge(Vertex<K, T> from, Vertex<K, T> to, W weight) {this.from = from;this.to = to;this.weight = weight;}@Overridepublic Vertex<K, T> from() {return from;}@Overridepublic Vertex<K, T> to() {return to;}@Overridepublic W getWeight() {return weight;}@Overridepublic String toString() {if (weight == Edge.nil) {return "" + '{' + from + " -> " + to + '}';}return "{" + from + " -> " + to + ", weight=" + weight + '}';}}/*** 获取所有节点** @return*/List<Vertex<K, T>> getVertices();/*** 获取所有邻接节点** @param key* @return*/List<Vertex<K, T>> getAdjVertices(K key);/*** 添加节点** @param key* @return*/default boolean addVertex(K key) {return addVertex(key, null);}/*** 添加节点和卫星数据** @param key* @param data* @return*/boolean addVertex(K key, T data);default boolean[] addVertices(K[] keys) {int n = keys.length;boolean[] res = new boolean[n];for (int i = 0; i < n; i++) res[i] = addVertex(keys[i], null);return res;}default boolean[] addVertices(K[] keys, T[] dataList) {int n = keys.length;boolean[] res = new boolean[n];for (int i = 0; i < n; i++) res[i] = addVertex(keys[i], dataList[i]);return res;}/*** 获取所有边** @return*/List<Edge<K, T, W>> getEdges();/*** 获取所有出边** @param key* @return*/List<Edge<K, T, W>> getAdjEdges(K key);/*** 添加边* @param fromKey* @param toKey* @return*/default boolean addEdge(K fromKey, K toKey) {return addEdge(fromKey, toKey, (W)Edge.nil);}/*** 添加边** @param fromKey* @param toKey* @param weight* @return*/boolean addEdge(K fromKey, K toKey, W weight);default boolean[] addEdges(K[] fromKeys, K[] toKeys) {int n = fromKeys.length;boolean[] res = new boolean[n];for (int i = 0; i < n; i++) res[i] = addEdge(fromKeys[i], toKeys[i]);return res;}default boolean[] addEdges(K[] fromKeys, K[] toKeys, W[] weights) {int n = fromKeys.length;boolean[] res = new boolean[n];for (int i = 0; i < n; i++) res[i] = addEdge(fromKeys[i], toKeys[i], weights[i]);return res;}
}

邻接矩阵实现

  • DirectedGraphWithAdjMatrix.java
package adt.graph;import java.util.ArrayList;
import java.util.List;/*** 有向图的邻接矩阵实现** @param <K>* @param <T>* @param <W>*/
public class DirectedGraphWithAdjMatrix<K, T, W> implements Graph<K, T, W> {private K[] vertexKeyList; // 保存顶点的键private T[] vertexDataList; // 保存顶点的卫星数据private W[][] edgeMatrix; //记录边信息的矩阵private int capacity; // 当前数组容量private int size; // 已加入的节点数public DirectedGraphWithAdjMatrix(int capacity) {this.capacity = capacity;vertexKeyList = (K[]) new Object[capacity];vertexDataList = (T[]) new Object[capacity];edgeMatrix = (W[][]) new Object[capacity][capacity];}/*** 由 key 查找 id** @param key* @return*/private int getId(K key) {for (int i = 0; i < size; i++) {if (vertexKeyList[i].equals(key)) return i;}return -1;}/*** 根据偏移量生成节点对象** @param id* @return*/private Vertex<K, T> getVertex(int id) {return new DefaultVertex<>(vertexKeyList[id], vertexDataList[id]);}@Overridepublic List<Vertex<K, T>> getVertices() {List<Vertex<K, T>> vertices = new ArrayList<>();for (int i = 0; i < size; i++) vertices.add(getVertex(i));return vertices;}@Overridepublic List<Vertex<K, T>> getAdjVertices(K key) {List<Vertex<K, T>> vertices = new ArrayList<>();int i = getId(key);if (i < 0) return vertices;for (int j = 0; j < size; j++) {if (edgeMatrix[i][j] != null) {Vertex<K, T> v = getVertex(j);vertices.add(v);}}return vertices;}@Overridepublic boolean addVertex(K key, T data) {for (int i = 0; i < size; i++) {if (vertexKeyList[i].equals(key)) return false;}vertexKeyList[size] = key;vertexDataList[size] = data;size++;return true;}private void extend() {int c = capacity * 2;K[] newVertexKeyList = (K[]) new Object[c];T[] newVertexDataList = (T[]) new Object[c];W[][] newEdgeMatrix = (W[][]) new Object[c][c];for (int i = 0; i < size; i++) {newVertexKeyList[i] = vertexKeyList[i];newVertexDataList[i] = vertexDataList[i];for (int j = 0; j < size; j++) {newEdgeMatrix[i][j] = edgeMatrix[i][j];}}vertexKeyList = newVertexKeyList;vertexDataList = newVertexDataList;edgeMatrix = newEdgeMatrix;capacity = c;}@Overridepublic List<Edge<K, T, W>> getEdges() {List<Edge<K, T, W>> edges = new ArrayList<>();for (int i = 0; i < size; i++) {for (int j = 0; j < size; j++) {if (edgeMatrix[i][j] != null) {Vertex<K, T> from = getVertex(i), to = getVertex(j);Edge<K, T, W> edge = new DefaultEdge<>(from, to, edgeMatrix[i][j]);edges.add(edge);}}}return edges;}@Overridepublic List<Edge<K, T, W>> getAdjEdges(K key) {List<Edge<K, T, W>> edges = new ArrayList<>();int i = getId(key);if (i < 0) return edges;Vertex<K, T> from = getVertex(i);for (int j = 0; j < size; j++) {if (edgeMatrix[i][j] != null) {Vertex<K, T> to = getVertex(j);Edge<K, T, W> edge = new DefaultEdge<>(from, to, edgeMatrix[i][j]);edges.add(edge);}}return edges;}@Overridepublic boolean addEdge(K fromKey, K toKey, W weight) {int i = getId(fromKey), j = getId(toKey);if (i < 0 || j < 0) return false;edgeMatrix[i][j] = weight;return true;}
}

邻接表实现

  • DirectedGraphWithAdjList.java
package adt.graph;import java.util.ArrayList;
import java.util.List;/*** 有向图的邻接表实现** @param <K>* @param <T>* @param <W>*/
public class DirectedGraphWithAdjList<K, T, W> implements Graph<K, T, W> {/*** 出边信息链表节点类* @param <W>*/private static class EdgeListNode<W> {int adjId;W weight;EdgeListNode<W> next;public EdgeListNode(int adjId) {this.adjId = adjId;this.weight = (W) Edge.nil;}public EdgeListNode(int adjId, W weight) {this.adjId = adjId;this.weight = weight;}}private K[] vertexKeyList; // 保存顶点键private T[] vertexDataList; // 保存顶点卫星数据private EdgeListNode<W>[] edgeList; // 顶点链表数组private int capacity; // 当前数组容量private int size; // 已加入顶点数public DirectedGraphWithAdjList(int capacity) {this.capacity = capacity;vertexKeyList = (K[]) new Object[capacity];vertexDataList = (T[]) new Object[capacity];edgeList = new EdgeListNode[capacity];}/*** 由 key 查找 id** @param key* @return*/private int getId(K key) {for (int i = 0; i < size; i++) {if (vertexKeyList[i].equals(key)) return i;}return -1;}/*** 根据偏移量生成节点对象** @param id* @return*/private Vertex<K, T> getVertex(int id) {return new DefaultVertex<>(vertexKeyList[id], vertexDataList[id]);}@Overridepublic List<Vertex<K, T>> getVertices() {List<Vertex<K, T>> res = new ArrayList<>();for (int i = 0; i < size; i++) res.add(getVertex(i));return res;}@Overridepublic List<Vertex<K, T>> getAdjVertices(K key) {List<Vertex<K, T>> res = new ArrayList<>();int i = getId(key);if (i < 0) return res;EdgeListNode node = edgeList[i];while (node != null) {res.add(getVertex(node.adjId));node = node.next;}return res;}@Overridepublic boolean addVertex(K key, T data) {for (int i = 0; i < size; i++) {if (vertexKeyList[i].equals(key)) return false;}vertexKeyList[size] = key;vertexDataList[size] = data;size++;return true;}@Overridepublic List<Edge<K, T, W>> getEdges() {List<Edge<K, T, W>> res = new ArrayList<>();for (int i = 0; i < size; i++) {Vertex<K, T> from = getVertex(i);EdgeListNode<W> node = edgeList[i];while (node != null) {Vertex<K, T> to = getVertex(node.adjId);res.add(new DefaultEdge<>(from, to, (W) node.weight));node = node.next;}}return res;}@Overridepublic List<Edge<K, T, W>> getAdjEdges(K key) {List<Edge<K, T, W>> res = new ArrayList<>();int i = getId(key);if (i < 0) return res;EdgeListNode<W> node = edgeList[i];Vertex<K, T> from = getVertex(i);while (node != null) {Vertex<K, T> to = getVertex(node.adjId);res.add(new DefaultEdge<>(from, to, (W) node.weight));node = node.next;}return res;}@Overridepublic boolean addEdge(K fromKey, K toKey, W weight) {int i = getId(fromKey), j = getId(toKey);if (i < 0 || j < 0) return false;EdgeListNode<W> node = new EdgeListNode<>(j, weight);if (edgeList[i] == null) {edgeList[i] = node;} else {EdgeListNode<W> cur = edgeList[i];while (cur.next != null) cur = cur.next;cur.next = node;}return true;}
}

测试

  • 测试图1

  • 测试图2

  • GraphTest.java
package adt.graph;import org.junit.Test;import java.util.Arrays;import static org.junit.Assert.*;public class GraphTest {private Graph<Integer, Integer, Integer> graph;private boolean[] buildSuccessSeq(int size) {boolean[] res = new boolean[size];Arrays.fill(res, true);return res;}@Testpublic void test_directed_graph_with_adj_matrix() {test_1(new DirectedGraphWithAdjMatrix<>(4));test_2(new DirectedGraphWithAdjMatrix<>(6));}@Testpublic void test_directed_graph_with_adj_list() {test_1(new DirectedGraphWithAdjList<>(4));test_2(new DirectedGraphWithAdjList<>(6));}/*** 第一张图测试** @param graph*/private void test_1(Graph<Integer, Integer, Integer> graph) {System.out.println("test_1: type<Integer, Integer, Integer>");Integer[] keys = new Integer[]{0, 2, 4, 6};boolean[] addVerticesRes = buildSuccessSeq(keys.length);assertArrayEquals(addVerticesRes, graph.addVertices(keys));Integer[] fromKeys = new Integer[]{0, 0, 0, 2, 2, 4};Integer[] toKeys = new Integer[]{2, 4, 6, 4, 6, 6};boolean[] addEdgesRes = buildSuccessSeq(fromKeys.length);assertArrayEquals(addEdgesRes, graph.addEdges(fromKeys, toKeys));System.out.println("all vertices: " + graph.getVertices());System.out.println("0 to : " + graph.getAdjVertices(0));System.out.println("2 to : " + graph.getAdjVertices(2));System.out.println("4 to : " + graph.getAdjVertices(4));System.out.println("6 to : " + graph.getAdjVertices(6));System.out.println("all edges" + graph.getEdges());System.out.println("0 edges: " + graph.getAdjEdges(0));System.out.println("2 edges: " + graph.getAdjEdges(2));System.out.println("4 edges: " + graph.getAdjEdges(4));System.out.println("6 edges: " + graph.getAdjEdges(6));System.out.println();}/*** 第二张图测试** @param graph*/private void test_2(Graph<String, Integer, Integer> graph) {System.out.println("test_2: type<String, Integer, Integer>");graph.addVertex("s", null);graph.addVertex("v1", null);graph.addVertex("v2", null);graph.addVertex("v3", null);graph.addVertex("v4", null);graph.addVertex("t", null);graph.addEdge("s", "v1", 16);graph.addEdge("s", "v2", 13);graph.addEdge("v1", "v3", 12);graph.addEdge("v2", "v1", 4);graph.addEdge("v2", "v4", 14);graph.addEdge("v3", "v2", 9);graph.addEdge("v3", "t", 20);graph.addEdge("v4", "v3", 7);graph.addEdge("v4", "t", 4);System.out.println("all vertices: " + graph.getVertices());System.out.println("s to: " + graph.getAdjVertices("s"));System.out.println("v1 to: " + graph.getAdjVertices("v1"));System.out.println("v2 to: " + graph.getAdjVertices("v2"));System.out.println("v3 to: " + graph.getAdjVertices("v3"));System.out.println("v4 to: " + graph.getAdjVertices("v4"));System.out.println("t to: " + graph.getAdjVertices("t"));System.out.println(graph.getEdges());System.out.println("s edges: " + graph.getAdjEdges("s"));System.out.println("v1 edges: " + graph.getAdjEdges("v1"));System.out.println("v2 edges: " + graph.getAdjEdges("v2"));System.out.println("v3 edges: " + graph.getAdjEdges("v3"));System.out.println("v4 edges: " + graph.getAdjEdges("v4"));System.out.println("t edges: " + graph.getAdjEdges("t"));System.out.println();}
}

结语

图数据结构相较于之前介绍过的线性表、树等更为复杂,不过应用面也更广泛。应用图数据结构的算法难点在于:

  1. 将数据抽象成图结构
  2. 将问题转化为图结构算法
  3. 降低图结构算法使之在有效时间内完成计算

由于当前存在许多图算法都属于 NP 完全问题,也就是并不能在多项式的时间内完成,因此当面对大规模数据的计算的时候就可能需要采用求近似结果或是适当的贪心算法来加快算法的执行。本篇提供的图存储实现将作为后续图算法实现的基础,虽然在效率和空间复杂度上有待加强,但是便于理解和后续图算法的朴素实现。

ADT: Graph 图相关推荐

  1. C语言实现Graph图的算法(附完整源码)

    C语言实现Graph图的算法 C语言实现Graph图的算法完整源码(定义,实现) C语言实现Graph图的算法完整源码(定义,实现) #ifndef _GRAPH_H #define _GRAPH_H ...

  2. 腾讯 Angel Graph 图计算框架在智能风控中的应用

    本文约6800字,建议阅读15+分钟 本文为你分享腾讯的姜亚松老师的图计算框架Angel Graph. [ 导读 ] 图计算在智能风控场景有着广泛的应用,但是图的规模和计算的复杂度往往会制约落地的使用 ...

  3. 图神经网络系列-Graph图基本介绍、度中心性、特征向量中心性、中介中心性、连接中心性

    图神经网络系列-Graph图基本介绍.度中心性.特征向量中心性.中介中心性.连接中心性 目录 图的定义 图的类型 空图形 简单图 多重图 有向图 无向图 连通与断开图 正则图 完全图 循环图 二部图 ...

  4. AntV G6(二)Graph 图

    AntV G6(二)Graph 图 文章目录 AntV G6(二)Graph 图 一.什么是 Graph 二.使用步骤 1.初始化 2.加载数据 3.渲染 三.Graph配置 1.必要配置项 2.常用 ...

  5. 如何使用echart的Graph图实现一个流程控制图

    前言: 最近接到一个新需求,需要写一个新模块,这个模块主要是用于查看当前每个生产计划的完成情况,然后所有的生产计划都列在了表格里,而流程控制图用于直观展示选中的生产计划的完成进度 1.首先看一下这个生 ...

  6. 图(Graph)-图的存储

    1.图的存储类型 顺序存储-邻接矩阵 链式存储-邻接表 9:19 2.用邻接矩阵存储的方式表示无向图的结构 2.1 邻接矩阵 图的邻接矩阵存储方式是用两个数组来表示图. 一个数组存储图中的顶点信息: ...

  7. CUDA Graph图详解

    CUDA图 CUDA Graphs 为 CUDA 中的工作提交提供了一种新模型.图是一系列操作,例如内核启动,由依赖关系连接,独立于其执行定义.这允许一个图被定义一次,然后重复启动.将图的定义与其执行 ...

  8. C语言实现图形ADT(Graph ADT)接口COMP2521(附完整源码)

    C语言实现图形ADT接口COMP2521 GraphRep结构体定义 Edge(边)定义 实现以下6个接口 完整头文件 完整源文件 GraphRep结构体定义 typedef struct Graph ...

  9. 图(Graph)-图的相关概念

    目录 1 图的定义与术语 2 图的分类 2.1 无向图的定义与术语 2.2 有向图的定义与术语 3 图的其他术语 3.1 权 3.2 网 3.3 稀疏图 3.4 稠密图 3.5 子图 3.6 度 3. ...

最新文章

  1. c++中const与函数一起用的时候需要注意什么?
  2. Pytorch view()、squeeze()、unsqueeze()、torch.max()
  3. tf.assign()函数简单解释
  4. php 新闻列表,php原生开发新闻站之新闻列表(二)
  5. 拉美光伏新兴市场热潮将至
  6. 搜集侠采集织梦系统模板
  7. python优先级排序_用Python实现优先级队列的3种方法
  8. android 屏蔽焦点,android – 如何在视图失去焦点时屏蔽EditText中的文本.
  9. stc单片机c语言编程软件,stc isp官方下载-STC单片机ISP下载编程软件下载v6.85i 官方最新版-西西软件下载...
  10. 【手撕算法】PatchMatch图像修复算法C++实现
  11. CF 379F: New Year Tree
  12. iOS 通过数字拼音快速搜索股票
  13. UNIX环境高级编程 - UNIX基础知识
  14. 斑马打印机ZDesigner GK888t (EPL)型号的java集成
  15. 狂神css视频笔记1-15课
  16. 《UnityAPI.Application应用程序》(Unity+SteamVR+云技术+5G+AI+VR云游戏+API+dataPath+OpenURL+LoadLevel+立钻哥哥++OK++)
  17. ERP系统实施对企业内部控制的利弊分析
  18. RF使用技巧--导入自定义的库文件
  19. 优思学院|10个品质管理的原则
  20. 测试支持硬件的软件,硬件检测工具AIDA64 v5.97版发布:支持Win10春季创意者更新...

热门文章

  1. UE4 蓝图常用节点汇总及意译(一)
  2. mysql中week()函数的用法
  3. 第一章 MySQL数据库的简介
  4. windows平台下静态库(.lib)和动态库(.dll)使用
  5. 编程语言居然是魔法咒语!
  6. 百度之星2009程序设计大赛 初赛第一场试题
  7. r5 4650g和r5 5600g选哪个好
  8. 在Google 上搜书的方法
  9. word-spacing、word-break、letter-spacing和white-space
  10. chmod 命令详细用法