12-图(Graph)

图(Graph)

在讨论图这种数据结构之前,先来回顾一下前面介绍的几种数据结构

线性结构

  • 数组
  • 链表
  • 队列
  • 哈希表

树形结构

  • 二叉树
  • B树
  • Trie
  • 哈夫曼树
  • 并查集

接下来就是将要讨论到的图这种树形结构

通过观察,可以发现,图这种数据结构确实和前面的线性结构,树形结构很不一样,看起来更加复杂。不用担心,这里将一步一步的对图进行研究。首先,先来了解图的一些基本概念。

图的基本概念
  1. 图[下图]由顶点(vertex)和边(edge)组成,通常表示为G= (V,E)
    • G表示一个图,V的顶点集,E是边集
    • 顶点集V有穷且非空(下图1有6个顶点,下图2有6个顶点,下图3有6个顶点)
    • 任意两个顶点之间都可以用边来表示它们之间的关系,边集E可以是空的(下图1有6条边,下图2有2条边,下图3有0条边)
图的应用

图结构的应用极其广泛

  1. 社交网络


  2. 地图导航



  3. 游戏开发


其他应用场景不一一举例

有向图(Directed Graph)

有向图的边是右明确方向的,例如下图的就表示一个有向图,每一条边都是有明确方向的

有向无环图(Directed Acyclic Graph,简称DAG)

  • 如果有一个有向图,从任一顶点出发无法经过若干条边回到该顶点,那么它就是一个有向无环图

所以上图为一个有向无环图,下图则不是一个有向无环图

出度、入度

出度、入度使用于有向图

出度(Out-degree)

  • 一个顶点的出度为x,是指有x条边以该顶点为起点(以当前节点,出发的边的数量,就表示出度值)

    根据这条结论,可以知道,顶点11的出度为3

入度(In-degree)

  • 一个顶点的入度为x,是指有x条边以该顶点为终点(以当前节点为重点的边,就表示入度)

    根据这条结论,可以知道,顶点11的入度为2

无向图(Undirected Graph)

无向图的边,是没有方向的,如下图所示

这种有向图,类似于下面这种有向图

混合图(Mixed Graph)

缓和图的边可能是无向的,也有可能是有向的,如下图所示

平行边

  • 在无向图中,关联一对顶点的无向边如果多于一条,则称这些边为平行边
  • 在有向图中,关联一对顶点的有向边如果多于一条,并且它们的方向相同,则称这些边为平行边
多重图(Multigraph)

有平行边或者有自环的图(可以理解为比较复杂的图)

为什么上图是多重图呢?因为该图有平行边(红色线条),有自环边(蓝色线条)

简单图(Simple Graph)

既没有平行边,也没有自环的图,成为简单图。从定义可以知道,简单图与多重图是相对的,所以在多重图之前,介绍的所有图,其实都是简单图

本节内容,也将主要对简单图进行讨论

无向完全图(Undirected Complete Graph)

无向完全图的任意两个顶点之间都存在边

所以,n个顶点的无向完全图有n(n - 1) / 2条边,即(n - 1) + (n - 2) + ( n - 3) + ... + 3 + 2 + 1

有向完全图(Directed Complete Graph)

有向完全图的任意两个顶点之间,都存在方向相反的两条边

所以,n个顶点的有向完全图有n(n - 1)条边

稠密图(Dense Graph)

如果边数接近于或者等于完全图,则称该图为稠密图(上图也是一个稠密图)

稀疏图(Sparse Graph)

如果边的数量远远少于完全图,就成为稀疏图

有权图(Weighted Graph)

有权图的边可以拥有权值(Weight),例如下图的有向图,每一条边可以带上一个权值,权值代表一定的含义。

下图表示两个地点之间来往,所消耗的成本

当前,权值不仅仅可以是整数,还可以是小数,负数。根据情况而定,甚至还可以是自定义对象

连通图(Connected Graph)
  1. 如果顶点x和y之间存在可相互抵达的路径(直接或间接的路径),则称x和y是连通的。
  2. 如果无向图G中任意两个顶点都是连通的,则称G为连通图[如下图所示]
连通分量(Connected Component)

连通分量:无向图的几大连通子图(尽可能多的连通子图,子图:图中的图)

  • 连通图只有一个连通分量,即其自身;非连通的无向图有多个连通分量

根据上面的定义,可以知道,下面的无向图有3个连通分量

强连通图(Strongly Connected Graph)

如果有向图G中任意两个顶点都是连通的,则称G为强连通图[如下图所示]

强连通分量(Strongly Connected Component)

强连通分量:有向图的极大强连通子图

  • 强连通图只有一个连通分量,即其自身,非强连通的有向图有多个强连通分量

以上,就是关于图的一些基本概念,内容比较多,相信如果是第一次学习相关知识的话,很难记得住,不过没有关系,只要有一个印象即可,后面会在使用中慢慢熟悉。

图的实现方案

前面介绍了一大堆与图相关的概念。那如果现在需要用代码来表达一个图,实现方案是怎么样的呢?图一般来讲,有2中实现方案,分别为

  • 邻接矩阵(Adjacency Matrix)
  • 邻接表(Adjacency List)

接下来就来了解这两种方案有什么区别

邻接矩阵(Adjacency Matrix)

邻接矩阵的存储方式

  • 用一维数组存放顶点信息
  • 用二维数组存放边信息
存储无向图

例如现在有如下图所示的无向图

首先,有一个存放顶点的一维数组,存放着上图中的4个顶点

然后,边用二维数组来进行表达,由于现在每一条边没有权值,所以两个顶点之间的值,如果为1,就边数存在边,为0就不存在边,所以上面的图可以通过下图的二维边数组进行表示。所以,一个顶点到另外一个顶点,有没有边,通过矩阵就可以表示清楚了

可以发现,如果是这种无向图,通过邻接矩阵的方式来存储的话,数据有一点冗余,例如V0到V2,V2到V0都存储了一次,这两次存储其实是有一点重复的

存储有向图

如果用邻接矩阵来表示有向图的话[下图]

首先,这个有向图与无向图的顶点是一样的,所以顶点数组是一样的,存放着所有的顶点

边仍然是使用二维数组来进行存储,但是存储的内容却有所不同,首先仍然使用0表示两个顶点时间没有边,1表示两个顶点之间有边,由于是有向图,所以边数组中的存储的每一条边都有一条唯一的边进行对应,所以数据不冗余

所以,邻接矩阵比较适合稠密图。因为邻接矩阵可以表示大量的边信息,否则就会造成内存的浪费,因为如果存储的是稀疏图,则会在矩阵中存储大量的0,导致内存浪费。

存储有权图

如果现在有如下所示的一个有权图

那在邻接矩阵中应该如何进行存储呢?

首先存储顶点与前面的存储方式一致,利用一个一维数组存放顶点

存储边时,就有一些区别了,由于是有权值的图,所以在存储边时,需要存储两个顶点对应边的权值,如果两个顶点之间没有边,则使用无穷大来表示,所以最终表示的结果如下

可以发现,利用邻接矩阵来表示图中顶点与边之间的关系,还是比较简单的,但是在某些时候,可能会导致内存的浪费。所以接下来研究邻接表是如何实现的。

邻接表(Adjacency List)

邻接表,利用一个一维数组就可以存储了,数组中的每一个元素是一个链表对象。存储方式如下

存储无向图

现在有如下的一个无向图

如果使用无向图来进行存储的话,最终存储的结果如下

表达的意思是这样的,

  1. 首先从图中可以知道顶点V0可以到达V1,V2,V3,所以在顶点V0后面,就跟着1,2,3这3个节点,就代表V0能通往这三个节点
  2. V1可以到达V0,V2,所以在顶点V1后面,跟着0,2这两个节点,就代表V1可以通往这两个节点
  3. 以此类推。。。

可以发现,使用邻接表,在表示图时,只存储了当前顶点可以到达的顶点,不会存储其他无法到达的顶点,这样的话, 就不会导致内存浪费

存储有向图

所以,如果你用邻接表存储如下的有向图

在邻接表中存储的结果如下,由于是有向图,所以邻接表与逆邻接表来进行表示,其中邻接表表示当前节点可以到达的节点,如下所示

逆邻接表表示哪些节点可以到达当前节点,例如V0可以通过V1,V2节点到达,所以在逆邻接表中,V0后面,就跟着1,2,其他顶点以此类推即可

存储有权图

如下所示的有权图

利用邻接表进行存储如下

与前面的无向图/有向图存储方式相似,唯一的区别是在存储当前顶点可以到达的顶点时,需要存储两个顶点之间的权值

结合前面的分析,现在可以着手实现一个图了。

图的实现

前面分析知道,图是由顶点和边组成的,所以肯定会定义两个对应的类与顶点和边进行对应。

  • 顶点类中存储的数据
    • 顶点的值
    • 以顶点出去的边
    • 以顶点进来的边

所以可以通过这种方式,定义一个顶点类

private static class Vertex {
    V value;
    Set> inEdges = new HashSet<>();
    Set> outEdges = new HashSet<>();
}
  • 边类中存储的数据
    • 出发到当前顶点的顶点
    • 当前顶点出发到的顶点
    • 权值

所以,边的定义如下

private static class Edge {
    Vertex from;
    Vertex to;
    E weight;
}

好的,顶点和边定义好以后,就可以添加顶点和边了。

添加顶点
public void addVertex(V v) {
    if (vertices.containsKey(v)) return;
    vertices.put(v,new Vertex<>(v));
}
添加边
public void addEdge(V from, V to, E weight) {
    //判断 from to顶点是否存在
    Vertex fromVertex = vertices.get(from);
    if (fromVertex == null) {
        fromVertex = new Vertex(from);
        vertices.put(from,fromVertex);
    }
    Vertex toVertex = vertices.get(to);
    if (toVertex == null) {
        toVertex = new Vertex(to);
        vertices.put(to,toVertex);
    }
    Edge edge = new Edge(fromVertex,toVertex);
    edge.weight = weight;
    if (fromVertex.outEdges.remove(edge)){
        toVertex.inEdges.remove(edge);
        edges.remove(edge);
    }
    fromVertex.outEdges.add(edge);
    toVertex.inEdges.add(edge);
    edges.add(edge);
}
删除边
public void removeEdge(V from, V to) {
    Vertex fromVertex = vertices.get(from);
    if (fromVertex == null) return;
    Vertex toVertex = vertices.get(to);
    if (toVertex == null) return;

    Edge edge = new Edge(fromVertex,toVertex);
    if (fromVertex.outEdges.remove(edge)){
        toVertex.inEdges.remove(edge);
        edges.remove(edge);
    }
}
删除顶点
public void removeVertex(V v) {
    Vertex vertex = vertices.remove(v);
    if (vertex == null) return;
    //删除顶点相关联的边
    //使用迭代器进行删除
    for (Iterator> iterator = vertex.outEdges.iterator(); iterator.hasNext() ;)   {
        Edge edge = iterator.next();
        edge.to.inEdges.remove(edge);
        iterator.remove();//将当前遍历到的元素edge从集合vertex.outEdges中删掉
        edges.remove(edge);
    }

    for (Iterator> iterator = vertex.inEdges.iterator(); iterator.hasNext() ;)    {
        Edge edge = iterator.next();
        edge.from.outEdges.remove(edge);
        iterator.remove();//将当前遍历到的元素edge从集合vertex.outEdges中删掉
        edges.remove(edge);
    }
}

所以到这里,图的添加顶点,添加边,删除顶点,删除边都已经实现了。接下来研究一下如何对图进行遍历

遍历

图的遍历

  • 从图中某一顶点出发访问图中其余顶点,且每个顶点仅被访问一次

图有2中常见的遍历方式(有向图,无向图都适用)

  • 广度优先搜索(Breadth First Search,BFD),又称为宽度优先搜索,横向优先搜索
  • 深度优先搜索(Depth First Search,DFS)

发明“深度优先搜索”算法的2为科学家在1986年共同获得计算机领域的最高奖:图灵奖

关于这两种搜索遍历方式,将会在后面的章节中继续介绍。

demo下载地址

完!

你可能感兴趣的:(12-图(Graph))