数据结构与算法之图(一)图的构建与遍历(邻接矩阵法)

引言

图是一种比线性表和树更复杂的数据结构,在图中,结点之间的关系是任意的,任意两个数据元素之间都可能相关。图是一种多对多的数据结构。它包含顶点集合和边集合两部分,边反映了顶点之间的关系。具体定义如下:
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。图可以没有边,但是必须有顶点。根据边是否存在方向,我们将图分为有向图和无向图,结构如下:



有关图的其他术语如边的权重、顶点的度等等概念请字形查阅,这里不再赘述。

图的存储结构

图的常用存储结构有:邻接矩阵法和邻接表法:
1.邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称邻接矩阵)存储图中的边或弧的信息。结构图如下:


邻接矩阵

下面就是具体的例子:


有向图邻接矩阵例子

二维数组元素adj[i][j]的下标及对应的权重反映了顶点i与j的关系:adj[i][j]权重有效时(大于0且不是无穷),表示从i顶点可以到达j顶点;本节点相对本节点的权重为0;权重为无穷表示顶点没有单向没有连接。可以看出,两个数组几乎反映了图的所有信息,这种方法结构简单,易于操作,但是对于稀疏图,会造成空间巨大浪费。
2.邻接表是一种将数组与链表相结合的存储方法。每一个数组元素存放一个单链表,单链表里面存的是与头结点相邻的顶点。结构图如下:
图的邻接表描述

邻接表引入单链表,操作相对复杂,查找效率相对较低,适合稀疏图。本文先学习相对简单的邻接矩阵法作为入门,来熟悉图的基本操作。

图的邻接矩阵描述

/**
 * Created by chenming on 2018/6/15
 */
public class MatrixGraph {
    private int[][] weightEdges;//带权重边二维数组,weightEdges[i][j]表示节点i到j的权重
    private int[] vertexes;//顶点数组
    public static int NO_WEIGHT_VALUE = Integer.MAX_VALUE;

    public MatrixGraph(int[] vertexes, int[][] edges) {
        if (vertexes == null || edges == null || vertexes.length != edges.length) {
            return;
        }
        this.vertexes = vertexes;
        this.weightEdges = edges;
    }
    ...
}

二维数组weightEdges存放边信息,反映顶点之间的连接情况,vertexes为顶点数组,简单起见,这里用整数代替泛型。

图的遍历

从图的某个顶点出发,遍历图中其余顶点,且使每个顶点仅被访问一次,这个过程叫做图的遍历。类似于我们搜索房间,它的遍历通常有两种方法:深度优先遍历和广度优先遍历。

图的深度优先遍历

类似于树的前序遍历,图的深度遍历为纵向遍历,步骤如下:
1.先访问顶点V,在访问它的相邻顶点W,然后在访问W的相邻顶点X..直到最深的相邻顶点被访问过或者到达路径底部,递归过程结束。
2.若此时图中仍有顶点未被访问,则另选一个未曾访问的顶点作为起点,重复上述步骤,直到图中所有顶点都被访问到为止。
下图是一个深度优先遍历的实例:


图的深度优先遍历.png

红色数字代表遍历的先后顺序,它的深度优先遍历的顶点访问序列为:V0,V1,V2,V5,V4,V6,V3,V7,V8。
实现代码如下:

 /**
     * 深度优先遍历
     */
    public void transverseDfs() {
        boolean[] isAccessedTable = new boolean[vertexes.length];
        //初始化访问标记数组
        for (int i = 0; i < vertexes.length; i++) {
            isAccessedTable[i] = false;
        }

        for (int i = 0; i < vertexes.length; i++) {
            if (!isAccessedTable[i]) {
                //递归深度优先遍历
                dfsVertexes(i, isAccessedTable);
            }
        }
    }

    /**
     * 递归深度遍历顶点
     *
     * @param index           顶点索引
     * @param isAccessedTable
     */
    private void dfsVertexes(int index, boolean[] isAccessedTable) {
        isAccessedTable[index] = true;//访问节点
        System.out.println("深度优先遍历:" + vertexes[index]);//访问节点
        for (int i = 0; i < vertexes.length; i++) {
            //没有访问,且为连接顶点则递归遍历
            int temp = weightEdges[index][i];
            if (!isAccessedTable[i] && temp > 0 && temp < Integer.MAX_VALUE) {
                dfsVertexes(i, isAccessedTable);
            }
        }
    }

图的广度优先遍历

广度优先遍历类似于二叉树的层序遍历,它的基本思路是:
1.先访问出发点V[i],标记V[i]访问过;
2.访问V[i]的未被访问过的相邻顶点,并标记它们;
3.对于V[i]的相邻顶点,重复执行2步骤。
示意图如下:


图的广度优先遍历.png

采用广度优先搜索遍历以V0为出发点的顶点序列为:V0,V1,V3,V4,V2,V6,V8,V5,V7。
这里我们和二叉树的层序遍历思想一样,引入队列存放下一层将要遍历的顶点,每一次迭代,都从队列头取顶点V[i],然后将V[i]的相邻未访问节点V[i][x]集合加入到队列,直到队列为空,这样就保证的层的访问顺序。具体实现代码如下:

    /**
     * 广度遍历和二次树的层序遍历相似,引入队列,存放当前未访问的连接节点
     */
    public void transverseBfs() {
        boolean[] isAccessedTable = new boolean[vertexes.length];
        for (int i = 0; i < vertexes.length; i++) {
            isAccessedTable[i] = false;
        }
        Integer headIndex;
        Queue<Integer> queue = new Queue<>();//也可以用LinkList代替
        for (int i = 0; i < vertexes.length; i++) {//各顶点作为入口
            if (!isAccessedTable[i]) {
                headIndex = i;//层序遍历起点
                while (headIndex != null) {//一次层序遍历,直到遍历完最后一层,队列为空
                    isAccessedTable[headIndex] = true;//遍历当前节点
                    System.out.println("广度优先遍历:" + vertexes[headIndex]);
                    //查找未访问的连接点入队
                    for (int j = 0; j < vertexes.length; j++) {
                        int tmp = weightEdges[headIndex][j];
                        if (!isAccessedTable[j] && tmp > 0 && tmp < Integer.MAX_VALUE && !queue.contains(j)) {
                            //未访问的连接点j入队
                            queue.enquene(j);
                        }
                    }
                    headIndex = queue.dequeue();//取未访问的节点
                }
            }
        }
    }

测试代码:

    @Test
    public void testGraph() {
        /**
         * 图的邻接矩阵描述
         */
        int[] vertexes = new int[]{
                0, 1, 2, 3, 4

        };

        int[][] edges = new int[][]{
                new int[]{0, MatrixGraph.NO_WEIGHT_VALUE, MatrixGraph.NO_WEIGHT_VALUE, MatrixGraph.NO_WEIGHT_VALUE, 6},
                new int[]{9, 0, 3, MatrixGraph.NO_WEIGHT_VALUE, MatrixGraph.NO_WEIGHT_VALUE},
                new int[]{2, MatrixGraph.NO_WEIGHT_VALUE, 0, 5, MatrixGraph.NO_WEIGHT_VALUE},
                new int[]{MatrixGraph.NO_WEIGHT_VALUE, MatrixGraph.NO_WEIGHT_VALUE, MatrixGraph.NO_WEIGHT_VALUE, 0, 1},
                new int[]{MatrixGraph.NO_WEIGHT_VALUE, MatrixGraph.NO_WEIGHT_VALUE, MatrixGraph.NO_WEIGHT_VALUE, MatrixGraph.NO_WEIGHT_VALUE, 0},
        };
        MatrixGraph graph = new MatrixGraph(vertexes, edges);
        System.out.println("邻接矩阵深度优先遍历:");
        graph.transverseDfs();
        System.out.println("邻接矩阵广度优先遍历:");
        graph.transverseBfs();
    }

测试结果:

邻接矩阵深度优先遍历:
深度优先遍历:0
深度优先遍历:4
深度优先遍历:1
深度优先遍历:2
深度优先遍历:3
邻接矩阵广度优先遍历:
广度优先遍历:0
广度优先遍历:4
广度优先遍历:1
广度优先遍历:2
广度优先遍历:3

本篇介绍了图的邻接矩阵构建及遍历方法,它的应用主要有:最短路径规划、最小生成树和拓扑排序,后面再学习它们的实现。
完整代码地址:数据结构与算法学习JAVA描述GayHub地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355

推荐阅读更多精彩内容

  • 图是一种比线性表和树更复杂的数据结构,在图中,结点之间的关系是任意的,任意两个数据元素之间都可能相关。图是一种多对...
    Alent阅读 2,307评论 1 22
  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 5,775评论 0 19
  • 内容整理于鱼c工作室教程 1. 图的基本概念 1.1 图的概念 图(Graph)是由顶点的有穷非空集合和顶点之间边...
    阿阿阿阿毛阅读 3,183评论 0 2
  • https://zh.visualgo.net/graphds 浅谈图形结构https://zh.visualgo...
    狼之独步阅读 4,156评论 0 0
  • 亲爱的妈妈: 我❤️爱你。五一回老家看你了,怎么最近看着有些消瘦,有些心疼。我们还没回去你就忙着张罗饭菜,提...
    珠珠米阅读 224评论 0 1