图(1)

作者: YLiuY | 来源:发表于2019-04-06 17:34 被阅读0次

    一. 基本概念

    图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
    在图中不允许没有顶点,可以没有边。

    • 无向边:若顶点Vi和Vj之间的边没有方向,称这条边为无向边(Edge),用(Vi,Vj)来表示
    • 有向边:若从顶点Vi到Vj的边有方向,称这条边为有向边,也称为弧(Arc),用<Vi, Vj>来表示,其中Vi称为弧尾(Tail),Vj称为弧头(Head)。
    • 稀疏图;有很少条边或弧的图称为稀疏图,反之称为稠密图。
    • 权(Weight):表示从图中一个顶点到另一个顶点的距离或耗费。
    • 网:带有权重的图
    • 度:与特定顶点相连接的边数;
    • 连通图:任意两个顶点都相互连通的图;
    • 最小生成树:此生成树的边的权重之和是所有生成树中最小的;
    • 生成树:n个顶点,n-1条边,并且保证n个顶点相互连通(不存在环);

    二. 储存结构

    1. 邻接矩阵

    图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称邻接矩阵)存储图中的边或弧的信息。

    • 优点:结构简单,操作方便
    • 缺点:对于稀疏图,这种实现方式将浪费大量的空间。
      无向图
      无向图由于边不区分方向,所以其邻接矩阵是一个对称矩阵。邻接矩阵中的0表示边不存在,主对角线全为0表示图中不存在自环。 image.png
      带权的有向图
      在带权有向图的邻接矩阵中,数字表示权值weight,「无穷」表示弧不存在。由于权值可能为0,所以不能像在无向图的邻接矩阵中那样使用0来表示弧不存在。 image.png
    /**
     * 有向图的邻接矩阵实现
     */
    public class Digraph {
        private int vertexsNum;   //顶点数目
        private int edgesNum;
        private int[][] arc;
    
       //data为M*2的矩阵,表示有m条边,起点为data[i][0],终点data[i][1]
        public Digraph(int[][] data, int vertexsNum) {
            this.vertexsNum = vertexsNum;
            this.edgesNum = data.length;
            arc = new int[vertexsNum][vertexsNum];
            for (int i = 0; i < vertexsNum; i++) {
                for (int j = 0; j < vertexsNum; j++) {
                    arc[i][j] = Integer.MAX_VALUE;
                }
            }
            
            for (int i = 0; i < data.length; i++) {
                int tail = data[i][0];
                int head = data[i][1];
                arc[tail][head] = 1;        //将可以导通的路,设为1
            }
        }
        
        //用于测试,返回一个顶点的邻接点
        public Iterable<Integer> adj(int vertex) {
            Set<Integer> set = new HashSet<>();
            for (int i = 0; i < vertexsNum; i++) {
                if (arc[vertex][i] != Integer.MAX_VALUE)
                    set.add(i);
            }
            return set;
        }
        
        public static void main(String[] args) {
            int[][] data = {
                    {0,3},
                    {1,0},
                    {1,2},
                    {2,0},
                    {2,1},
            };
            Digraph wd = new Digraph(data,4);
            for(int i :wd.adj(1)) {
                System.out.println(i);
            }   
        }
    }
    

    2. 邻接表

    邻接表是一种将数组与链表相结合的存储方法。其具体实现为:将图中顶点用一个一维数组存储,每个顶点Vi的所有邻接点用一个单链表来存储。这种方式和树结构中孩子表示法一样。
    有向图
    有向图的邻接表是以顶点为弧尾来存储边表的,这样很容易求一个顶点的出度(顶点对应单链表的长度),但若求一个顶点的入度,则需遍历整个图才行。这时可以建立一个有向图的逆邻接表即对每个顶点v都建立一个弧头尾v的单链表。
    本算法的时间复杂度为 O(N + E),其中N、E分别为顶点数和边数,邻接表实现比较适合表示稀疏图。

    image.png
    /**
     * 有向图的邻接表实现
     *
     */
    public class AdjListDigraph {
        private class EdgeNode {
            int index;
            EdgeNode next;
            EdgeNode(int index, EdgeNode next){
                this.index = index;
                this.next = next;
            }
        }
        
        private class VertexNode {
            int id;
            EdgeNode headNode;
        }
        
        private VertexNode[] vertexs;
        private int vertexsNum;
        private int edgesNum;
        
        public AdjListDigraph(int[][] data, int vertexsNum) {
            this.vertexsNum = vertexsNum;
            this.edgesNum = data.length;
            vertexs = new VertexNode[vertexsNum];
            for (int i = 0; i < vertexs.length; i++) {
                vertexs[i] = new VertexNode();
                vertexs[i].id = i;        //
            }
            
            for (int i = 0; i < data.length; i++) {
                int index = data[i][1];
                EdgeNode next = vertexs[data[i][0]].headNode;
                EdgeNode eNode = new EdgeNode(index,next);
                vertexs[data[i][0]].headNode = eNode; //头插法
            }
            
        }
        
        //用于测试,返回一个顶点的邻接点
        public Iterable<Integer> adj(int index) {
            Set<Integer> set = new HashSet<>();
            EdgeNode current = vertexs[index].headNode;
            while(current != null) {
                VertexNode node = vertexs[current.index];
                set.add(node.id);
                current = current.next;
            }
            return set;
        }
        
        public static void main(String[] args) {
            int[][] data = {
                    {0,3},
                    {1,0},
                    {1,2},
                    {2,0},
                    {2,1},
            };
            AdjListDigraph ald = new AdjListDigraph(data,4);
            for(int i :ald.adj(1)) {
                System.out.println(i);
            }   
        }
    }
    

    3.十字链表

    十字链表(Orthogonal List)是将邻接表和逆邻接表相结合的存储方法,它解决了邻接表(或逆邻接表)的缺陷,即求入度(或出度)时必须遍历整个图。

    • firstIn表示入边表(即是逆邻接表中的单链表)头指针,firstOut表示出边表(即是邻接表中的单链表)头指针,data表示顶点数据。
    • tailVex表示边的起点在顶点数组中的下标,tailNext值出边表指针域,指向起点相同的下一条边。
    • headVex表示边的终点在顶点数组中的下标,headNext指入边表指针域,指向终点相同的下一条边。


      image.png

      代码实现:

    /**
     * 有向图的十字链表实现
     *
     */
    public class OrthogonalList {
        
        private class EdgeNode {
            int tailVex;
            int headVex;
            EdgeNode headNext;
            EdgeNode tailNext;
            
            public EdgeNode(int tailVex, int headVex, EdgeNode headNext, EdgeNode tailNext) {
                super();
                this.tailVex = tailVex;
                this.headVex = headVex;
                this.headNext = headNext;
                this.tailNext = tailNext;
            }
            
        }
        
        private class VertexNode {
            int data;
            EdgeNode firstIn;
            EdgeNode firstOut;
        }
        
        private VertexNode[] vertexs;
        private int vertexsNum;
        private int edgesNum;
        
        public OrthogonalList(int[][] data, int vertexsNum) {
            this.vertexsNum = vertexsNum;
            this.edgesNum = data.length;
            vertexs = new VertexNode[vertexsNum];
            for (int i = 0; i < vertexs.length; i++) {
                vertexs[i] = new VertexNode();
                vertexs[i].data = i;        //
            }
            
            //关键
            for (int i = 0; i < data.length; i++) {
                int tail = data[i][0];
                int head = data[i][1];
                EdgeNode out = vertexs[tail].firstOut;
                EdgeNode in = vertexs[head].firstIn;
                EdgeNode eNode = new EdgeNode(tail,head,in,out);
                vertexs[tail].firstOut = eNode;
                vertexs[head].firstIn = eNode;
            }
            
        }
        
        //返回一个顶点的出度
        public int outDegree(int index) {
            int result = 0;
            EdgeNode current = vertexs[index].firstOut;
            while(current != null) {
                current = current.tailNext;
                result++;
            }
            return result;
        }
        
        //返回一个顶点的入度
        public int inDegree(int index) {
            int result = 0;
            EdgeNode current = vertexs[index].firstIn;
            while(current != null) {
                current = current.headNext;
                result++;
            }
            return result;
        }
        
        public static void main(String[] args) {
            int[][] data = {
                    {0,3},
                    {1,0},
                    {1,2},
                    {2,0},
                    {2,1},
            };
            OrthogonalList orth = new OrthogonalList(data,4);
            System.out.println("顶点1的出度为" + orth.outDegree(1));
            System.out.println("顶点1的入度为" + orth.inDegree(1));
                
        }
    }
    

    相关文章

      网友评论

          本文标题:图(1)

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