• 图的实现(邻接矩阵)及DFS、BFS


    @author QYX

    写作时间:2013/0302 最近准备noi比赛,加油!!!

    因为近期学习任务太多太紧,所以我主要维护Github,博客园可能会停更几天。----2020年2月9日

     图(graph)是用线连接在一起的顶点或节点的集合,即两个要素:边和顶点。每一条边连接个两个顶点,用(i,j)表示顶点为 i 和 j 的边。

    ​ 如果用图示来表示一个图,一般用圆圈表示顶点,线段表示边。有方向的边称为有向边,对应的图成为有向图,没有方向的边称为无向边,对应的图叫无向图。对于无向图,边(i, j)和(j,i)是一样的,称顶点 i 和 j 是邻接的,边(i,j)关联于顶点 i 和 j ;对于有向图,边(i,j)表示由顶点 i 指向顶点 j 的边,即称顶点 i 邻接至顶点 j ,顶点 i 邻接于顶点 j ,边(i,j)关联至顶点 j 而关联于顶点 i 。

    ​ 对于很多的实际问题,不同顶点之间的边的权值(长度、重量、成本、价值等实际意义)是不一样的,所以这样的图被称为加权图,反之边没有权值的图称为无权图。所以,图分为四种:加权有向图,加权无向图,无权有向图,无权无向图。

    图的表现有很多种,邻接表法,临接矩阵等。

    图经常是以这种形式出现的[weight,from,to]的n*3维数组出现的,见名知意,三个元素分别为边的权重,从哪儿来,到哪儿去。

     如上图所示,由一条边连接在一起的顶点称为相邻顶点,A和B是相邻顶点,A和D是相邻顶点,A和C是相邻顶点......A和E是不相邻顶点。一个顶点的是其相邻顶点的数量,A和其它三个顶点相连,所以A的度为3,E和其它两个顶点相连,所以E的度为2......路径是一组相邻顶点的连续序列,如上图中包含路径ABEI、路径ACDG、路径ABE、路径ACDH等。简单路径要求路径中不包含有重复的顶点,如果将的最后一个顶点去掉,它也是一个简单路径。例如路径ADCA是一个环,它不是一个简单路径,如果将路径中的最后一个顶点A去掉,那么它就是一个简单路径。如果图中不存在环,则称该图是无环的。如果图中任何两个顶点间都存在路径,则该图是连通的,如上图就是一个连通图。如果图的边没有方向,则该图是无向图,上图所示为无向图,反之则称为有向图,下图所示为有向图:

     在有向图中,如果两个顶点间在双向上都存在路径,则称这两个顶点是强连通的,如上图中C和D是强连通的,而A和B是非强连通的。如果有向图中的任何两个顶点间在双向上都存在路径,则该有向图是强连通的,非强连通的图也称为稀疏图

      此外,图还可以是加权的。前面我们看到的图都是未加权的,下图为一个加权的图:

    可以想象一下,前面我们介绍的链表也属于图的一种特殊形式。图在计算机科学中的应用十分广泛,例如我们可以搜索图中的一个特定顶点或一条特定的边,或者寻找两个顶点间的路径以及最短路径,检测图中是否存在环等等。

      存在多种不同的方式来实现图的数据结构,下面介绍几种常用的方式。

    邻接矩阵

      在邻接矩阵中,我们用一个二维数组来表示图中顶点之间的连接,如果两个顶点之间存在连接,则这两个顶点对应的二维数组下标的元素的值为1,否则为0。下图是用邻接矩阵方式表示的图:

     如果是加权的图,我们可以将邻接矩阵中二维数组里的值1改成对应的加权数。邻接矩阵方式存在一个缺点,如果图是非强连通的,则二维数组中会有很多的0,这表示我们使用了很多的存储空间来表示根本不存在的边。另一个缺点就是当图的顶点发生改变时,对于二维数组的修改会变得不太灵活。

    邻接表

      图的另外一种实现方式是邻接表,它是对邻接矩阵的一种改进。邻接表由图中每个顶点的相邻顶点列表所组成。如下图所示,我们可以用数组、链表、字典或散列表来表示邻接表。

    关联矩阵

      我们还可以用关联矩阵来表示图。在关联矩阵中,矩阵的行表示顶点,列表示边。关联矩阵通常用于边的数量比顶点多的情况下,以节省存储空间。如下图所示为关联矩阵方式表示的图:

    深度优先搜索#

    深度优先搜索,我们以无向图为例。

    图的深度优先搜索(Depth First Search),和树的先序遍历比较类似。

    它的思想:假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。 若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

    显然,深度优先搜索是一个递归的过程。

    广度优先搜索#

    广度优先搜索,我们以有向图为例。

    广度优先搜索算法(Breadth First Search),又称为”宽度优先搜索”或”横向优先搜索”,简称BFS。

    它的思想是:从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

    换句话说,广度优先搜索遍历图的过程是以v为起点,由近至远,依次访问和v有路径相通且路径长度为1,2…的顶点。

    package com.qyx;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.LinkedList;
    //使用邻接矩阵来实现图
    public class Graph {
        private ArrayList<String> vertexList; //存储顶点的集合
        private int[][] edges;//存储图对应的邻接矩阵
        private int numOfEdges;//表示边的数目
        private boolean isVisited[];
        public static void main(String[] args) {
            int n=5;//节点的个数
            String vertex[]={"A","B","C","D","E"};
            //创建图对象
            Graph graph=new Graph(n);
            for (String s:vertex)
            {
                graph.vertexList.add(s);
            }
            //添加边
            graph.insertEdge(0,1,1);
            graph.insertEdge(0,2,1);
            graph.insertEdge(1,2,1);
            graph.insertEdge(1,3,1);
            graph.insertEdge(1,4,1);
            //显示邻接矩阵
            graph.showGraph();
            //graph.dfs();
            graph.bfs();
        }
        //构造器
        public Graph(int n)
        {
            //初始化邻接矩阵和vertexList
            edges=new int[n][n];
            vertexList=new ArrayList<String>();
            numOfEdges=0;
            isVisited=new boolean[5];
        }
        //插入顶点
        public void insertVertex(String vertex)
        {
            vertexList.add(vertex);
        }
    
        /**
         *
         * @param v1 表示点的下标 即是第几个顶点
         * @param v2 第二个顶点对应的下标
         * @param weight 表示是否连接 0不相连 1相连
         */
        //添加边
        public void insertEdge(int v1,int v2,int weight)
        {
            edges[v1][v2]=weight;
            edges[v2][v1]=weight;
            numOfEdges++;
        }
        //图中常用的方法
        //返回节点的个数
        public int getNumOfVertex()
        {
            return vertexList.size();
        }
        //得到边的数目
        public int getNumOfEdges()
        {
            return numOfEdges;
        }
        //返回节点i对应的值 0-A 1-B 2-C
        public String getValue(int index)
        {
            return vertexList.get(index);
        }
        //返回v1和v2的权值
        public int getWeight(int v1,int v2)
        {
            return edges[v1][v2];
        }
        //显示图对应的邻接矩阵
        public void showGraph()
        {
            for (int[] arrs:edges)
            {
                System.out.println(Arrays.toString(arrs));
            }
        }
    
        /**
         *
         * @param index
         * @return 如果存在返回对应的下标,否则返回-1
         */
        //得到第一个邻接节点的下标
        public int getFirstNeighbor(int index)
        {
            for (int j=index;j<vertexList.size();j++)
            {
                if (edges[index][j]>0)
                {
                    return j;
                }
            }
            return -1;
        }
        //根据前一个邻接节点的下标来获取下一个邻接节点
        public int getNextNeighbor(int v1,int v2) {
            for (int i = v2 + 1; i < vertexList.size(); i++) {
                    if (edges[v1][i] > 0) {
                        return i;
                    }
            }
            return -1;
        }
            //深度优先遍历算法
            //i 第一次就是0
            private void dfs ( boolean[] isVisited, int i)
            {
                //首先我们访问该节点
                System.out.print(getValue(i) + "->");
                //将该邻接节点设置为已访问
                isVisited[i] = true;
                int w = getFirstNeighbor(i);//查找节点w的第一个邻接节点w
                while (w != -1) {
                    if (!isVisited[w]) {
                        dfs(isVisited,w);
                    }
                    //如果已经被访问过
                    w=getNextNeighbor(i,w);
                }
            }
        //对dfs进行重载,遍历所有的节点并进行dfs
        public void dfs()
        {
            //遍历所有的节点进行dfs
            for (int i=0;i<getNumOfVertex();i++)
            {
                if (!isVisited[i])
                {
                    dfs(isVisited,i);
                }
            }
        }
        //对一个节点进行深度优先遍历的方法
        public void bfs(boolean[] isVisited,int i)
        {
            int u;//表示队列的头结点对应下标
            int w;//邻接节点
            //队列,记录节点访问的顺序
            LinkedList queue = new LinkedList();
            //访问节点
            System.out.print(getValue(i)+"->");
            //标记为已访问
            isVisited[i]=true;
            //将节点加入队列
            queue.addLast(i);
            while (!queue.isEmpty())
            {
                //取出队列的头结点下标
                u =(Integer)queue.removeFirst();
                //得到第一个邻接节点的下标w
                w =getFirstNeighbor(u);
                while (w!=-1)
                {
                    //找到
                    //是否访问
                    if (!isVisited[w])
                    {
                        System.out.print(getValue(w)+"->");
                        //入队
                        queue.addLast(w);
                        isVisited[w]=true;
                    }
                    //找w后面的下一个邻接节点
                    w=getNextNeighbor(u,w); //体现出广度优先
    
                }
            }
        }
        //遍历所有的节点都进行广度优先搜索
        public void bfs()
        {
            for (int i=0;i<getNumOfVertex();i++)
            {
                if (!isVisited[i])
                {
                    bfs(isVisited,i);
                }
            }
        }
    }

     2020年2月9日对原有博客进行了修改,参考博客:

    https://www.cnblogs.com/jaxu/p/11338294.html

     https://www.cnblogs.com/DarrenChan/p/9547869.html

    感谢帮助!

  • 相关阅读:
    bzoj1648:奶牛野餐
    bzoj1650:跳石子
    bzoj1643:贝西的秘密草坪
    bzoj1639:月度开支
    bzoj1636:Balanced Lineup
    bzoj1634:护花
    .
    bzoj1620:时间管理
    bzoj1611:流星雨
    bzoj1609:麻烦的聚餐
  • 原文地址:https://www.cnblogs.com/qyx66/p/12289482.html
Copyright © 2020-2023  润新知