图的实现(邻接矩阵)及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

感谢帮助!

posted @ 2020-02-09 23:54  计算机的探索者  阅读(2000)  评论(0编辑  收藏  举报