数据结构-图的基本知识和表示

  图是一种基本的非线性数据结构,其相关基本概念介绍太多了,这里就不再重复说了。为了让自己能够复习的印象深刻一些,今天专门总结一些图的基本知识。

一、基本的知识点

  图包含两个核心要素:顶点和边,一个图可以没有边,但不能没有顶点。

  图分可以分为无向图和有向图,有向图就是在这个图中每一条边都有一个方向,表示从一条边出发到另一条边结束。而无向图的边则没有方向。

  图的顶点存在“度”这个概念,对于无向图而言,顶点的度即是这个顶点连接的边的条数就是当前顶点的"度";对于有向图而言,顶点的度又分为出度和入度,出度就是从当前顶点出发的边的条数,入度就是以当前顶点为结尾的边的条数。

  平行边:两个顶点之间可通过两条或者多条边相连,这些边称之为平行边。

  环:一条将顶点连接到它自身的边。

  简单图:无环、无平行边。

  连通图:图中任意两个顶点之间皆存在路径。

  对于一个图G,它的子图的顶点集合是G中顶点的子集,子图的边的集合也是G中边的子集。

  对于无向连通图:

    回路:始于一个顶点,终于同一个顶点的封闭路径。

    无回路的连通图是一棵树。

    连通图的生成树:它是图的一个子图,包含了原图的所有顶点,但是包含了保持连通的最少边数。

二、图的表示

  表示图就是在程序中存储它的顶点和边,一般使用数组或者线性表存储图的数据结构。

  1、表示顶点

  顶点可以存储在数组或者线性表中,其中顶点可以是任意类型的对象,也可以是基本数据类型。

  2、表示边

  边的表示方法有很多,可以是边数组、边对象、邻接矩阵、邻接线性表等。

  (1)边数组

    就是一个二维数组,第一维的大小是图中边的总数,第二位的大小是2,即边的两个顶点。数组是int型的,存储的边的两个顶点就是顶点数组中对应这两个顶点的索引。

  (2)边对象

    定义一个边对象,包含两个成员变量u、v,表示边的两个顶点,然后重写equals方法,用于判断两个边对象是否相同。

public class Edge {
    int u;
    int v;

    @Override
    public boolean equals(Object obj) {
        return (u == ((Edge)obj).u) && (v == ((Edge)obj).v);
    }

    public int getU() {
        return u;
    }

    public void setU(int u) {
        this.u = u;
    }

    public int getV() {
        return v;
    }

    public void setV(int v) {
        this.v = v;
    }
}

  (3)邻接矩阵

    邻接矩阵也是一个二维数组,但是不同于边数组的是,它的大小是n*n,n是顶点个数,即如果顶点i到顶点j之间有边,那么array[i][j]=1,否则等于0。对于无向图而言邻接矩阵是对称的,所以为了节省空间,可以采用锯齿矩阵进行存储。对于有向图,因为边是有方向的,所以不存在对称这一概念。

  (4)邻接线性表

    邻接线性表分为邻接顶点线性表和邻接边线性表。邻接顶点线性表包含了与一个顶点相连的所有顶点。邻接边线性表包含了与一个顶点相连的所有的边。邻接线性表具体的数据结构是,有一个大小为n的线性表数组,这个线性表数组的每一个元素也是一个线性表。

    邻接顶点线性表可以定义为:

ArrayList<ArrayList<Integer>> neighbor = new ArrayList<>();

    邻接边线性表可以定义为:

ArrayList<ArrayList<Edge>> neighbor = new ArrayList<>();

  邻接矩阵和邻接线性表都可以表示一个图,但是二者在不同的应用场景下有不同的优点。如果图比较稠密,即含有的边比较多,那么采用邻接矩阵会更好一些,这样的查询效率高,只需常数时间复杂度就可以查询两个点之间是否存在边,并且存储带权重的边也非常方便,但是如果图很稀疏,那么使用邻接矩阵就会很不划算,浪费很多空间,这时使用邻接线性表会更好,能够有效地节省空间。