美文网首页数据结构和算法分析
图的基本概念与图的表示

图的基本概念与图的表示

作者: Ice_spring | 来源:发表于2020-03-27 09:32 被阅读0次

    内容较基础,从简记录。

    图的一些概念

    有向图,无向图,有权图,无权图,简单图(无自环边和平行边),多重图,稠密图,稀疏图(一般|E|<|V||logV|时称);
    顶点的度,入度,出度,边的权值;
    子图,连通图,强联通图,弱连通图,连通分量,强连通分量,弱连通分量;
    路径,简单路径,回路,简单回路,距离,欧拉路径,哈密顿路径,欧拉回路,哈密顿回路;
    生成树,生成森林,最小生成树。
    ......
    更多图相关的概念将在后续笔记介绍具体图论算法时给出。

    图的表示概述

    图的表示最基本的有邻接矩阵和邻接表。
    邻接矩阵:适用于稠密图
    邻接表:适用于稀疏图

    图的表示时空复杂度分析

    (以无向无权图为例)
    要做的一点说明是,邻接表方式中所谓"表"只是邻接顶点的组织形式,可以使用普通链表,也可以使用红黑树,哈希表。
    各种图的表示对算法的影响如下:

    数据结构类型 空间复杂度 建立图的时间复杂度 判断两顶点相邻与否时间复杂度 查找某顶点所有临边时间复杂度
    邻接矩阵 O(V^2) O(E) O(1) O(V)
    邻接表(普通链表) O(V+E) O(E)(如果查重O(V*E)) O(degree(V)) O(degree(V))
    邻接表(红黑树) O(V+E) O(ElogV) O(logV) O(degree(V))
    邻接表(哈希表) O(V+E+?) O(E) O(1) O(degree(V))

    上面列举的图的操作是图论领域很多经典算法的基础,所以选择适合的数据结构研究图论问题是必要的。
    在邻接表的各种实现中,虽然哈希表性能最优,但是顶点之间的顺序性失去了,这在一些图论算法的调试和模拟中非常不方便,而红黑树可以保持节点间的顺序,且红黑树相比哈希表只是在部分操作多了一个logV数量级,还是可以接受的,故本文及以后的图论问题讨论邻接表都采用红黑树数据结构。当然如果在竞赛和刷题中追求效率可以用哈希表。

    邻接矩阵表示(基于无向无权图)

    /*Ice_spring 2020/3/26*/
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Scanner;
    // 图的邻接矩阵表示,无向无权图
    public class AdjMatrix {
        private int V; // 顶点数
        private int E; // 边数
        private int[][] adj; // 邻接矩阵
    
        public AdjMatrix(String filename){
            File file = new File(filename);
            try(Scanner scanner = new Scanner(file)){
    
                V = scanner.nextInt();
                if(V < 0) throw new IllegalArgumentException("V must be a non-neg number!");
                adj = new int[V][V];
    
                E = scanner.nextInt();
                if(E < 0) throw new IllegalArgumentException("E must be a non-neg number!");
                for(int i=0; i < E; i ++){
                    int a = scanner.nextInt();
                    validateVertex(a);
                    int b = scanner.nextInt();
                    validateVertex(b);
                    // 本代码只处理简单图
                    if(a == b) throw new IllegalArgumentException("检测到self-loop边!");
                    if(adj[a][b] == 1) throw new IllegalArgumentException("Parallel Edges are detected!");
                    adj[a][b] = 1;
                    adj[b][a] = 1;
                }
            }
            catch(IOException e){
                e.printStackTrace();//打印异常信息
            }
        }
        private void validateVertex(int v){
            // 判断顶点v是否合法
            if(v < 0 ||v >= V)
                throw new IllegalArgumentException("vertex " + v + "is invalid!");
        }
        public int V(){ // 返回顶点数
            return V;
        }
        public int E(){
            return E;
        }
        public boolean hasEdge(int v, int w){
            // 顶点 v 到 w 是存在边
            validateVertex(v);
            validateVertex(w);
            return adj[v][w] == 1;
        }
        public ArrayList<Integer> adj(int v){
            // 返回和顶点 v 相邻的顶点
            validateVertex(v);
            ArrayList<Integer> res = new ArrayList<>();
            for(int i = 0; i < V; i ++)
                if(adj[v][i] == 1)
                    res.add(i);
            return res;
        }
        public int degree(int v){
            // 返回节点 v 的度,即与该顶点相邻的顶点个数
            return adj(v).size(); // adj 函数
        }
        @Override
        public String toString(){
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("V = %d, E = %d\n",V, E));
            for(int i = 0; i < V; i ++){
                for(int j = 0; j < V; j ++){
                    sb.append(String.format("%d ", adj[i][j]));
                }
                sb.append('\n');
            }
            return sb.toString();
        }
        public static void main(String args[]){
            AdjMatrix adjMatrix = new AdjMatrix("g.txt");
            System.out.println(adjMatrix);
        }
    }
    
    

    邻接表表示(基于无向无权图,表数据结构使用红黑树)

    /*Ice_spring 2020/3/26*/
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.Scanner;
    import java.util.TreeSet;
    
    // 图的邻接表表示,无向无权图
    public class AdjSet {
        private int V; // 顶点数
        private int E; // 边数
        private TreeSet<Integer>[] adj; // 邻接矩阵
    
        public AdjSet(String filename){
            File file = new File(filename);
            try(Scanner scanner = new Scanner(file)){
    
                V = scanner.nextInt();
                if(V < 0) throw new IllegalArgumentException("V must be a non-neg number!");
                adj = new TreeSet[V];
    
                for(int i = 0; i < V; i ++)
                    adj[i] = new TreeSet<>();
                E = scanner.nextInt();
                if(E < 0) throw new IllegalArgumentException("E must be a non-neg number!");
                for(int i=0; i < E; i ++){
                    int a = scanner.nextInt();
                    validateVertex(a);
                    int b = scanner.nextInt();
                    validateVertex(b);
                    // 本代码只处理简单图
                    if(a == b) throw new IllegalArgumentException("检测到self-loop边!");
                    if(adj[a].contains(b)) throw new IllegalArgumentException("Parallel Edges are detected!");
                    adj[a].add(b);
                    adj[b].add(a);
                }
            }
            catch(IOException e){
                e.printStackTrace();//打印异常信息
            }
        }
        private void validateVertex(int v){
            // 判断顶点v是否合法
            if(v < 0 ||v >= V)
                throw new IllegalArgumentException("vertex " + v + "is invalid!");
        }
        public int V(){ // 返回顶点数
            return V;
        }
        public int E(){
            return E;
        }
        public boolean hasEdge(int v, int w){
            // 顶点 v 到 w 是存在边
            validateVertex(v);
            validateVertex(w);
            return adj[v].contains(w);
        }
        public Iterable<Integer> adj(int v){
            // 返回值可以是TreeSet,不过用 Iterable 接口更能体现面向对象
            // 返回和顶点 v 相邻的所有顶点
            validateVertex(v);
            return adj[v];
        }
        public int degree(int v){
            // 返回节点 v 的度,即与该顶点相邻的顶点个数
            validateVertex(v);
            return adj[v].size(); //
        }
        @Override
        public String toString(){
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("V = %d, E = %d\n",V, E));
            for(int v = 0; v < V; v ++){
                // 编程好习惯 i,j,k 作索引, v,w 作顶点
                sb.append(String.format("%d : ", v));
    
                for(int w: adj[v])
                    sb.append(String.format("%d ", w));
    
                sb.append('\n');
            }
            return sb.toString();
        }
        public static void main(String args[]){
            AdjSet adjList = new AdjSet("g.txt");
            System.out.println(adjList);
        }
    }
    

    最后,在上面的时空复杂度分析中可以看到,综合来看,相比于邻接矩阵邻接表(红黑树)性能是更好的,所以以后对图论各个算法的实现均采用邻接表(红黑树),这样更多的注意力可以集中在算法本身。

    相关文章

      网友评论

        本文标题:图的基本概念与图的表示

        本文链接:https://www.haomeiwen.com/subject/nnfkuhtx.html