数据结构 - 图(邻接矩阵、邻接表)

目录

1、图的概念

2、图的表示方式

1)、邻接矩阵

2)、邻接表

    图是除了树之外的另一大类非线性表数据结构,应对到现实生活中有大量的应用,比如微博、微信、QQ等怎么表示其好友关系,关注、被关注(粉丝),亲密度等;地图出行,怎么查询到一个地方的最近距离,这些问题抽象算法都依赖底层的数据结构图。现在除了非关系型数据库、关系型数据库,还有图数据库抽象存储更复杂的现实模型。上面都没有图来的直接,先上图吧.

《数据结构 - 图(邻接矩阵、邻接表)》

1、图的概念

    树种的节点类比到图中叫顶点(Vertex),如上图中的A;顶点之间的连线叫作边(Edge),比如链接A、B的线。连接每个顶点的边数叫作度(Degree),比如上面的无向图中顶点A的度为3。

    图按照是否有方向、是否有权重分为上图中的无向图有向图带权图有向带权图四类。所以有方向的箭头将度分为:入度(In-Degree)出度(Out-Degree)

    怎么表示好友关系,只需要一根不带箭头的边怎么表示一个人有多少个好友,也就是与该节点连接的边的个数,也就是该节点的度。怎么表示A关注了B(即B是A的粉丝),而B没有关注A,使用一根带箭头的边。怎么表示QQ好友中的亲密度,一根不带箭头的有权重的边,权重越大亲密度越高。怎么表示地图上A到B有多少公里,只需要在带方向的箭头上面添加权重。

 

2、图的表示方式

1)、邻接矩阵

《数据结构 - 图(邻接矩阵、邻接表)》

    邻接矩阵第一次听说时觉得非常高大上和拗口,不过梳理完之后还是觉得比较形象的,使用一个二维数组的矩阵来诠释每两相邻的顶点的关系。 首先使用一个数组存储顶点信息(比如上面的A、B、C、D),存储完成后每个顶点都有自己的数组下标了。再用一个二维数组表示这些关系,那么就是 N*N的矩阵,那么每一个子数组就对应顶点数组的每一个下标,与其他元素的关系,比如上面的 int[0]就是存储的节点A与所有其他顶点的关系,所以上面画黄先的对应二维数组的 int[0][0],int[1][1],int[2][2],int[2][3],分别对应了A,B,C,D自己与自己的关系,所以斜线存储的都是0.

    邻接矩阵如果想查询如上,A到C的边,那么直接 int[0][2]的位置,之前在数组一节分析过数组通过下标随机访问的时间复杂度是O(1),现在是二维数组也是一样的,只是计算下标的公式变一下。但是如上面的存储了自己与自己的关系,当数组顶点个数为十万时,相当于浪费了 十万 * int类型长度的空间。如果本身是稀疏图(Sparse Matrix)时,整个数组会存储大量的 0占位符,浪费空间。 所以邻接矩阵本身是使用空间换时间的思想,只适合小数据量的图表达存储。

使用 Java类表示为:

/**
 * 邻接矩阵表示图
 * 邻接矩阵法表示图只适应于数据量比较小,并且不是稀疏图(Sparse Matrix)的情况,否则会浪费大量的空间,相比较邻接表的图查询效率高,是空间换时间的思想
 * 如果查询第一个顶点和第三个顶点是否有关系: relation[1][3] > 0 ?
 *
 *  {@link #data} 表示图的顶点; {@link #relation} 表示图的边; relation[0]中大于0的个数 表示第一个顶点的度
 *
 * @author kevin
 * @date 2021/3/9 13:31
 * @since 1.0.0
 */
public class AdjacencyMatrixGraph {
    /** 图的顶点 */
    private String[] data;
    /** 图的边 */
    private int[][] relation;
}

 

2)、邻接表

《数据结构 - 图(邻接矩阵、邻接表)》

    图的存储往往会是大数据量的标识,比如地图中的位置及每个位置间可能要存储距离,上亿的微信好友关系,一个有上亿粉丝的账号等。并且像微信好友一般都是稀疏图,并没有人与其他所有人都是好友(需要用一个边连接),大多数用户都是局部的边,大多用户的好友不超过500人。如果存储的关系不仅要查询用户关注了哪些人,还要查询自己的粉丝列表,则像上面的有向图,不仅要存储邻接表,还要存储逆邻接表。

    邻接表本身是使用时间换空间的方式存储数据,如上图,特别像拉链式散列表的样子。如上面的有向图,邻接表中1的顶点的链表中存储了2,3,4三个顶点,那么我们要查询顶点1是否指向了顶点3,此时需要遍历整个双向链表(LinkedList),那么是否复杂度为O(N),那么怎么才能降低链表的查询时间复杂度问题呢? 红黑树、跳表读写的时间复杂度都是O(logN),有序数组,使用散列表读写的时间复杂度近似O(1),怎么选择?根据业务: 如果存储的是粉丝列表,并且一般按照字母排序(有序),则使用跳表比较合适。如果关系不经常变动可以使用有序数组(二分查找时间复杂度也是O(logN))。

    数据量打到超过单机限制时,可以使用散列表的思想将顶点数据分散到多态服务器内存中进行存储。 如果大到内存不能存储时,可以使用关系型数据库等,建一张表(两个字段都创建索引),我们知道Mysql数据库使用B+树存储,读写的时间复杂度也是O(logN)。

使用 Java类表示为:

/**
 *  邻接表方式的无向图
 *  邻接表存储比较节省空间,只是查询两个节点是否有边时,需要查询链表,时间复杂度较低,是时间换空间的思想
 *  只是当数据量比较大是,只能用邻接表存储, 如果链表过长导致查询性能下降,可以选择读写性能都比较高的数据结构
 *  即链表可以更换为 跳表、红黑树(或其他的平衡二叉查找树)等
 *
 * @author kevin
 * @date 2021/3/9 13:41
 * @since 1.0.0
 */
public class AdjacencyTableGraph {
    /** 记录顶点的个数 */
    private int vertex;
    /** 邻接表 */
    private LinkedList<Integer>[] data;

    public AdjacencyTableGraph(int vertex) {
        this.vertex = vertex;
        data = new LinkedList[vertex];
        for (int i = 0; i < data.length; i++) {
            data[i] = new LinkedList<>();
        }
    }

    /** 无向图存储一个边,要存储两个地方 */
    public void addEdge(int start, int end) {
        data[start].add(end);
        data[end].add(start);
    }
}

 

    原文作者:it_lihongmin
    原文地址: https://blog.csdn.net/it_lihongmin/article/details/114586495
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞