图的基本操作

简介: 图的基本操作

图的定义



图是一种非线性数据结构, 由【顶点Vertex】 和 【边Edge】组成。我们可以将图G抽象地表示为一组顶点V 和一组边 E 地集合。


如下图就是图地网络关系 和 树 以及链表地区别

image-20230422111116753.png


图与其他数据结构之间的关系我们可以抽象为


把顶点看作节点, 将边看作各个节点地指针。, 可以将图看作是一种从链表拓展而来的数据结构。它的复杂度 和 自由度更高。


图的常见类型



根据边是否具有方向,可分为「无向图 Undirected Graph」和「有向图 Directed Graph」


image-20230422112607922.png


根据所有顶点是否联通,可分为「连通图 Connected Graph」和「非连通图 Disconnected Graph」


连通图 : 从一个顶点出发可以到达其余任意顶点。


非连通图 :从一个顶点出发 ,至少有一个顶点无法到达。


image-20230422112820432.png


还可以为图添加权重变量, 从未得到有权图[Weighted Graph]


image-20230422112948547.png


图的常用术语



图是由节点(vertices)和边(edges)组成的一种数据结构,常用术语包括:


  1. 有向图(Directed Graph):每条边都有一个方向,从一个节点指向另一个节点。
  2. 无向图(Undirected Graph):每条边没有方向,连接两个节点。
  3. 加权图(Weighted Graph):每条边都有一个权重值,表示两个节点之间的距离、代价等。
  4. 连通图(Connected Graph):图中的任意两个节点都可以通过路径相连。
  5. 子图(Subgraph):一个图的一部分,包含一些节点和它们之间的边。
  6. 点的度数(Degree):指与该节点相连的边的数目。
  7. 路径(Path):连接两个节点的一系列边构成的序列。
  8. 环(Cycle):路径的起点和终点相同的路径。
  9. 连通分量(Connected Component):无向图中的极大连通子图。
  10. 强连通分量(Strongly Connected Component):有向图中的极大强连通子图。
  11. 度(Degree): 表示一个顶点所拥有的边数,对于有向图, 那么描述变数就需要使用下面的两个出入度。
  12. 入度(In-degree):有向图中指向一个节点的边的数目。
  13. 出度(Out-degree):有向图中从一个节点出发的边的数目。
  14. 邻居(Neighbor)/邻接Adjacency:指与一个节点相连的其他节点。
  15. 序列(Sequence):一个节点序列,其中每个节点都与相邻的节点相连。
  16. 生成树(Spanning Tree):一个连通无向图的生成树是一个无环连通子图,包含所有节点,且仅有n-1条边。


图的表示方法



邻接矩阵:


设图的顶点数量为 n ,「邻接矩阵 Adjacency Matrix」使用一个 n×n 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 1 或 0 表示两个顶点之间是否存在边。


如下图所示,设邻接矩阵为 M 、顶点列表为 N ,那么矩阵元素M[i][j]=1 表示顶点 V[i]到顶点 V[j] 之间存在边,反之M[i][j]= 0 表示两顶点之间无边。


image-20230422114024968.png


  • 对角线无意义。

  • 如果将矩阵中的数字换成其他数字, 那么就相当于权重

  • 对于邻接矩阵表示图时, 它的curd操作时间复杂度非常低, 都是O(1)。但是空间复杂度非常高,因为要构造邻接矩阵 ,所以未O(n2)



邻接表 :


使用邻接表法和 hash表有异曲同工之妙 。都是通过链表来实现。


「邻接表 Adjacency List」使用 n 个链表来表示图,链表节点表示顶点。第 i条链表对应顶点 i ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。


image-20230422114657889.png


它相较于邻接矩阵最大的优点就是他存储的内容都是有用的, 而不是像邻接矩阵那样都存储。


同时,邻接表我们可以进行优化, 将链表过长的部分像hash表那样转换为红黑树。从而可以将时间效率从O(n)—-> O(log n)


基于邻接矩阵的实现


package tu;
import java.util.ArrayList;
import java.util.List;
/**
 * 基于邻接矩阵实现图
 */
public class GraphAdjMat {
    //定义邻接矩阵
    List<List<Integer>> AdjMat;
    //定义顶点列表
    List<Integer> vertices;
    /**
     * 构造器,对邻接矩阵进行初始化
     * @param edges edges 元素代表顶点索引,即对应 vertices 元素索引
     * @param Vertices 传入的顶点列表
     */
    public GraphAdjMat(int[][] edges , int[] Vertices){
        //1. 先new矩阵 和 列表 对象 (初始化空间)
        AdjMat = new ArrayList<>();
        vertices = new ArrayList<>();
        //2. 对顶点列表进行赋值,同时扩展矩阵
        for (int i : Vertices){
            addAdjMat(i);
        }
        //3. 添加边 .其中的edges中的元素为矩阵中为
        for (int[] e : edges) {
            addEdge(e[0], e[1]);
        }
    }
    //todo 实现元素顶点列的元素添加, 同时对矩阵进行扩展
    public void addAdjMat(int val){
        int size = vertices.size();
        vertices.add(val);
        //在矩阵中添加一行
        List<Integer> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            list.add(0);
        }
        AdjMat.add(list);
        //在矩阵中添加一列
        for (List<Integer> row : AdjMat){
            row.add(0);
        }
    }
    //todo 对矩阵进行赋值 ,其中 m、n 代表vertices的索引
    public void addEdge(int m , int n){
        //需要对数组下表越界 以及对角线的情况进行处理
        if(m < 0 || n < 0 || m > vertices.size() || n > vertices.size() || m == n){
            throw new IndexOutOfBoundsException("数组下标越界");
        }
        AdjMat.get(m).set(n,1);
        AdjMat.get(n).set(m,1);
    }
    /**
     * 删除边 , 就是删除个顶点之间的边
     * @param m 该顶点对应vertices中元素的索引
     * @param n 另一个顶点对应vertices中元素的索引
     */
    public void removeAdjMat(int m ,int n){
        //还是需要考虑数组下标越界情况
        if(m < 0 || n < 0 || m > vertices.size() || n > vertices.size() || m == n){
            throw new IndexOutOfBoundsException("数组下标越界");
        }
        //修改两个顶点的连线
        AdjMat.get(m).set(n,0);
        AdjMat.get(n).set(m,0);
    }
    /**
     * 删除对应的顶点 ,需要同时删除矩阵中顶点对应的row and lie
     * @param index 元素在vertices中的索引
     */
    public void removeVertices(int index){
        //判断下标是否越界
        if (index > vertices.size()){
            throw new IndexOutOfBoundsException("数组下标越界, 无法删除");
        }
        vertices.remove(index);
        //删除矩阵中对应的row and lie
        AdjMat.remove(index);
        for (List<Integer> row : AdjMat){
            row.remove(index);
        }
    }
    //todo 打印矩阵
    public void list(){
        System.out.println("顶点列表....");
        vertices.forEach(System.out::print);
        System.out.println();
        System.out.println("矩阵....");
        for (int i = 0; i < AdjMat.size(); i++) {
            for (int j = 0; j < AdjMat.get(i).size(); j++) {
                System.out.print(AdjMat.get(i).get(j) + "\t");
            }
            System.out.println();
        }
    }
    public static void main(String[] args) {
        int[] vertices = new int[]{1,3,2,5,4};
        int[][] edges = new int[][]{
                {0,1},
                {0,3},
                {1,0},
                {2,1},
                {2,3},
                {2,4},
                {3,0},
                {3,2},
                {3,4},
                {4,2},
                {4,3}
        };
        GraphAdjMat graphAdjMat = new GraphAdjMat(edges,vertices);
        graphAdjMat.list();
    }
}

image-20230422130043149.png


基于邻接表的实现


package tu;
import org.omg.CORBA.MARSHAL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * 基于邻接表实现图
 * 将顶点用顶点类Vertex 进行封装
 */
public class Graph {
    // 邻接表,key: 顶点,value:该顶点的所有邻接顶点
    Map<Vertex, List<Vertex>> adjList;
    /**
     * 初始化邻接表
     * @param edges 两顶点之间的边的关系, edges中的数代表顶点上的数
     */
    public Graph(Vertex[][] edges){
        this.adjList = new HashMap<>();
        //添加顶点
        for (Vertex[] edge  : edges){
            addVertex(edge[0]);
            addVertex(edge[1]);
            //添加边
            addAdjMat(edge[0],edge[1]);
        }
    }
    /**
     * 添加两个顶点之间的关系, 也就是添加边的关系
     * @param item0 顶点0
     * @param item1 顶点1
     */
    private void addAdjMat(Vertex item0, Vertex item1) {
        //首先判断两个顶点是否存在, 如果不存在就不能添加边的关系
        if (!adjList.containsKey(item0) || !adjList.containsKey(item1) || item0 == item1){
            throw new IndexOutOfBoundsException("其中一个顶点不存在!");
        }
        if (!adjList.get(item0).contains(item1)){
            adjList.get(item0).add(item1);
        }
           if (!adjList.get(item1).contains(item0)){
            adjList.get(item1).add(item0);
        }
    }
    /**
     * 添加点 ,并且需要将扩展顶点
     * @param item 顶点val
     */
    private void addVertex(Vertex item) {
        //先判断顶点是否存在
        if(adjList.containsKey(item)){
            return;
        }
        adjList.put(item,new ArrayList<>());
    }
    /**
     * 删除顶点 ,同时需要删除所有有关它的边的关系
     * @param val 顶点的所有边的关系
     */
    public void removeVertex(Vertex val){
        //先判断顶点是否存在
        if(!adjList.containsKey(val)){
            throw new IndexOutOfBoundsException("顶点不存在");
        }
        adjList.remove(val);
        //先遍历每个顶点,然后看他里面是否存在val顶点, 存在就删除
        for (Map.Entry<Vertex,List<Vertex>> item : adjList.entrySet()){
           item.getValue().remove(val);
        }
    }
    //todo 打印邻接表
    public void list(){
        for (Map.Entry<Vertex,List<Vertex>> item : adjList.entrySet()){
            System.out.print("key : " + item.getKey() + " value : ");
            List<Vertex> value = item.getValue();
            for (Vertex vertex  : value){
                System.out.print(vertex + "—>");
            }
            System.out.println();
        }
    }
    public static void main(String[] args) {
        Vertex vertex1 = new Vertex(1);
        Vertex vertex3 = new Vertex(3);
        Vertex vertex2 = new Vertex(2);
        Vertex vertex5 = new Vertex(5);
        Vertex vertex4 = new Vertex(4);
        Vertex[][] vertices = new Vertex[][]{
                {vertex1,vertex3},
                {vertex3,vertex1},
                {vertex2,vertex3},
                {vertex5,vertex1},
                {vertex4,vertex2},
        };
        Graph graph = new Graph(vertices);
        graph.addAdjMat(vertex1, vertex5);
        graph.addAdjMat(vertex3, vertex2);
        graph.addAdjMat(vertex2, vertex5);
        graph.addAdjMat(vertex2, vertex4);
        graph.addAdjMat(vertex5, vertex2);
        graph.addAdjMat(vertex5, vertex4);
        graph.addAdjMat(vertex4, vertex5);
        graph.list();
    }
}

image-20230422140458483.png



两者效率比较:


设图中共有 m 个顶点和 n 条边,下表为邻接矩阵和邻接表的时间和空间效率对比。


image-20230422140636011.png


观察上表,似乎邻接表(哈希表)的时间与空间效率最优。但实际上,在邻接矩阵中操作边的效率更高,只需要一次数组访问或赋值操作即可。综合来看,邻接矩阵体现了“以空间换时间”的原则,而邻接表体现了“以时间换空间”的原则。



目录
相关文章
|
29天前
|
JSON Shell API
3.基本操作
3.基本操作
|
7天前
|
存储
数据结构学习记录——如何建立图(邻接矩阵、邻接表-图节点的结构、创建并初始化、插入变、完整图的建立)
数据结构学习记录——如何建立图(邻接矩阵、邻接表-图节点的结构、创建并初始化、插入变、完整图的建立)
11 0
|
29天前
|
网络架构
|
29天前
|
存储 人工智能
数据结构——图详解及代码实现
数据结构——图详解及代码实现
|
7月前
|
SQL
数据的基本操作
数据的基本操作。
25 1
|
11月前
|
存储 算法
【数据结构】图邻接矩阵的创建完整代码
【数据结构】图邻接矩阵的创建完整代码
238 0
|
11月前
|
Python Windows
JupyterNotebook基本操作
JupyterNotebook基本操作
|
12月前
|
存储 算法 C语言
图的基础操作详解
本文主要了解图的各种基本操作,为后面打下基础!
115 1
|
数据库
Bartender基本操作
本教程使用的是Bartender10,其他版本的Bartender使用上差不多。
|
机器学习/深度学习 存储 算法
十、图(基本知识,存储结构,遍历方法)2
十、图(基本知识,存储结构,遍历方法)
十、图(基本知识,存储结构,遍历方法)2