学习总结(数据结构:图论)

转载请标明出处,谢谢!
//www.greatytc.com/p/cf03e51a3ca2

关联文章
冒泡、选择排序 //www.greatytc.com/p/176b0b892591
栈和队列 //www.greatytc.com/p/8cb602ef4e21
顺序表、单双链表 //www.greatytc.com/p/3aeb5998e79e
二叉树 //www.greatytc.com/p/de829eab944c
图论 //www.greatytc.com/p/cf03e51a3ca2

定义:图(graph)是由一些点(vertex)和这些点之间的连线(edge)所组成的;其中,点通常被成为”顶点(vertex)”,而点与点之间的连线则被成为”边或弧”(edege)。通常记为,G=(V,E)。

无向图

image.png

有向图

image.png

邻接点
一条边上的两个顶点叫做邻接点。 例如无向图中的A点的B点就是邻接点

在有向图中,除了邻接点之外;还有”入边”和”出边”的概念。
顶点的入边,是指以该顶点为终点的边。而顶点的出边,则是指以该顶点为起点的边。


在无向图中,某个顶点的度是邻接到该顶点的边(或弧)的数目。
例如,上面无向图中顶点A的度是3。

在有向图中,度还有”入度”和”出度”之分。
某个顶点的入度,是指以该顶点为终点的边的数目。而顶点的出度,则是指以该顶点为起点的边的数目。
顶点的度=入度+出度。
例如,上面有向图G2中,顶点B的入度是2,出度是2;顶点B的度=2+2=4。

路径和回路
路径:如果顶点(Vm)到顶点(Vn)之间存在一个顶点序列。则表示Vm到Vn是一条路径。
路径长度:路径中”边的数量”。
简单路径:若一条路径上顶点不重复出现,则是简单路径。
回路:若路径的第一个顶点和最后一个顶点相同,则是回路。
简单回路:第一个顶点和最后一个顶点相同,其它各顶点都不重复的回路则是简单回路。

连通图和连通分量
连通图:对无向图而言,任意两个顶点之间都存在一条无向路径,则称该无向图为连通图。 对有向图而言,若图中任意两个顶点之间都存在一条有向路径,则称该有向图为强连通图。

连通分量:非连通图中的各个连通子图称为该图的连通分量。


image.png

在某些图中,边具有与之相关的数值,称为权

邻接矩阵
用一个一维数组存放图中所有顶点数据;用一个二维数组存放顶点间关系(边或弧)的数据,这个二维数组称为邻接矩阵。
无向图

image.png

有向图


image.png

带权有向图


image.png

邻接表
邻接表存储的基本思想:对于图的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表(对于有向图则称为出边表),所有边表的头指针和存储顶点信息的一维数组构成了顶点表。

将节点存入数组中,并对节点的孩子进行链式存储,不管有多少孩子,也不会存在空间按浪费的问题,这个思路同样适用于图的存储。我们把这种数组与链表相结合的存储方法称为邻接表

无向图

image.png

有向图

image.png

带权

image.png

图的遍历
[参考:](https://blog.csdn.net/zhangxiangdavaid/article/details/38323633
深度遍历
 从图的某个顶点出发,访问图中的所有顶点且使每个顶点仅被访问一次。这一过程叫做图的遍历。
深度优先搜索的思想:
①访问顶点v;
②依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
③若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。

比如:

image

在这里为了区分已经访问过的节点和没有访问过的节点,我们引入一个一维数组bool visited[MaxVnum]用来表示与下标对应的顶点是否被访问过,

流程:
☐ 首先输出 V1,标记V1的flag=true;
☐ 获得V1的邻接边 [V2 V3],取出V2,标记V2的flag=true;
☐ 获得V2的邻接边[V1 V4 V5],过滤掉已经flag的,取出V4,标记V4的flag=true;
☐ 获得V4的邻接边[V2 V8],过滤掉已经flag的,取出V8,标记V8的flag=true;
☐ 获得V8的邻接边[V4 V5],过滤掉已经flag的,取出V5,标记V5的flag=true;
☐ 此时发现V5的所有邻接边都已经被flag了,所以需要回溯。(左边黑色虚线,回溯到V1,回溯就是下层递归结束往回返)

image

☐ 回溯到V1,在前面取出的是V2,现在取出V3,标记V3的flag=true;
☐ 获得V3的邻接边[V1 V6 V7],过滤掉已经flag的,取出V6,标记V6的flag=true;
☐ 获得V6的邻接边[V3 V7],过滤掉已经flag的,取出V7,标记V7的flag=true;
☐ 此时发现V7的所有邻接边都已经被flag了,所以需要回溯。(右边黑色虚线,回溯到V1,回溯就是下层递归结束往回返)

图例:


image.png

代码:

 public void depthTraverse() {
        for (int i = 0; i < verticesSize; i++) {
            if (!isVisited[i]) {
                /*如果顶点没被访问过先打印*/
                System.out.println("打印顶点" + vertices[i]);
                traverse(i);
            }

        }
    }

    public void traverse(int i) {
        isVisited[i] = true;
        int v = getFirstBor(i);
        while (v!=-1){
            if(!isVisited[v]){
                System.out.println("visit ="+vertices[v]);
                traverse(v);
            }
            /**
             * 以下代码会先压入栈中,在递归完成后会以先进后出的
             * 形式执行,达到从下往上把各个顶点的邻接点打印的效果
             *           0
             *          / \
             *         1   4
             *        / \   \
             *       2   5   6
             *      /
             *     3
             */
            v = getNextBor(i,v);
        }
    }

结果:


image.png

广度优先遍历
所谓广度,就是一层一层的,向下遍历,层层堵截,还是这幅图,我们如果要是广度优先遍历的话,我们的结果是V1 V2 V3 V4 V5 V6 V7 V8。

image

广度优先搜索的思想:

① 访问顶点vi ;

② 访问vi 的所有未被访问的邻接点w1 ,w2 , …wk ;

③ 依次从这些邻接点(在步骤②中访问的顶点)出发,访问它们的所有未被访问的邻接点; 依此类推,直到图中所有访问过的顶点的邻接点都被访问;

说明:

为实现③,需要保存在步骤②中访问的顶点,而且访问这些顶点的邻接点的顺序为:先保存的顶点,其邻接点先被访问。 这里我们就想到了用标准模板库中的queue队列来实现这种先进现出的服务。

老规矩我们还是走一边流程:

说明:

☐将V1加入队列,取出V1,并标记为true(即已经访问),将其邻接点加进入队列,则 <—[V2 V3]

☐取出V2,并标记为true(即已经访问),将其未访问过的邻接点加进入队列,则 <—[V3 V4 V5]

☐取出V3,并标记为true(即已经访问),将其未访问过的邻接点加进入队列,则 <—[V4 V5 V6 V7]

☐取出V4,并标记为true(即已经访问),将其未访问过的邻接点加进入队列,则 <—[V5 V6 V7 V8]

☐取出V5,并标记为true(即已经访问),因为其邻接点已经加入队列,则 <—[V6 V7 V8]

☐取出V6,并标记为true(即已经访问),将其未访问过的邻接点加进入队列,则 <—[V7 V8]

☐取出V7,并标记为true(即已经访问),将其未访问过的邻接点加进入队列,则 <—[V8]

☐取出V8,并标记为true(即已经访问),将其未访问过的邻接点加进入队列,则 <—[]

图例


image.png
 /**
     * 广度优先
     */
    public void bfs(){
        for (int i = 0; i < verticesSize; i++) {
            isVisited[i]=false;
        }
        for (int i = 0; i < verticesSize; i++) {
            if(!isVisited[i]){
                isVisited[i]=true;
                System.out.println("visited vertice:"+ vertices[i]);
                bfs(i);
            }
        }
    }

    public void bfs() {
        for (int i = 0; i < verticesSize; i++) {
            if (!isVisited[i]) {
                /*如果顶点没被访问过先打印*/
                System.out.println("打印顶点" + vertices[i]);
                isVisited[i] = true;
                bfs(i);
            }

        }
    }

    public void bfs(int i) {
        /*自定义的队列*/
        Queue queue = new Queue();
        int first = getFirstBor(i);
        /*如果存在且没有被访问过*/
        if (first != -1 && !isVisited[first]) {
            isVisited[first] = true;
            System.out.println("first " + vertices[first]);
            queue.enQueue(queue, first);

        }
        int next = getNextBor(i, first);
        while (next != -1) {
            if (!isVisited[next]) {
                isVisited[next] = true;
                System.out.println("next " + vertices[next]);
                queue.enQueue(queue, next);
            }

            next = getNextBor(i, next);
        }
        /*重复以上操作*/
        while (queue.front != queue.rear) {
            int temp = queue.array[queue.front];
            bfs(temp);
            queue.front = (queue.front + 1) % queue.MAX_LENGTH;
        }


    }

结果:


image.png

两种算法的复杂度分析
深度优先
数组表示:查找所有顶点的所有邻接点所需时间为O(n2),n为顶点数,算法时间复杂度为O(n2)   
广度优先
数组表示:查找每个顶点的邻接点所需时间为O(n2),n为顶点数,算法的时间复杂度为O(n2)
代码

public class MyGraph {
    /*顶点集*/
    public String[] vertices;
    /*图的边的信息*/
    public int[][] matrix;
    /*矩阵大小*/
    public int verticesSize;
    /*被访问过的顶点集合*/
    public boolean[] isVisited;

    public static final int MAX_WEIGHT = Integer.MAX_VALUE;

    public MyGraph(int verticesSize) {
        this.verticesSize = verticesSize;
        vertices = new String[verticesSize];
        matrix = new int[verticesSize][verticesSize];
        isVisited = new boolean[verticesSize];

        for (int i = 0; i < verticesSize; i++) {
            vertices[i] = "v" + i;
        }
    }

    /*获取第一个邻接点*/
    public int getFirstBor(int v) {
        for (int i = 0; i < verticesSize; i++) {
            if (matrix[v][i] > 0 && matrix[v][i] < MAX_WEIGHT) {
                // System.out.println(v + " first " + i);
                return i;
            }
        }

        return -1;
    }

    /**
     * 获取到顶点v的邻接点index的下一个邻接点
     */
    public int getNextBor(int v, int index) {
        for (int i = index + 1; i < verticesSize; i++) {
//            System.out.println(v + "---" + i + "---" + matrix[v][i]);
            if (matrix[v][i] > 0 && matrix[v][i] < MAX_WEIGHT) {
                //   System.out.println(v + " next " + i);
                return i;
            }
        }
        return -1;
    }

    public void depthTraverse() {
        for (int i = 0; i < verticesSize; i++) {
            if (!isVisited[i]) {
                /*如果顶点没被访问过先打印*/
                System.out.println("打印顶点" + vertices[i]);
                traverse(i);
            }

        }
    }

    public void traverse(int i) {
        isVisited[i] = true;
        int v = getFirstBor(i);
        while (v != -1) {
            if (!isVisited[v]) {
                System.out.println("visit =" + vertices[v]);
                traverse(v);
            }
            /**
             * 以下代码会先压入栈中,在递归完成后会以先进后出的
             * 形式执行,达到从下往上把各个顶点的邻接点打印的效果
             *           0
             *          / \
             *         1   4
             *        / \   \
             *       2   5   6
             *      /
             *     3
             */
            v = getNextBor(i, v);
        }
    }

    public void dfs(int i) {
        isVisited[i] = true;
        int v = getFirstBor(i);
        if (v != -1) {
            if (!isVisited[v]) {
                System.out.println("visit =" + vertices[v]);

                traverse(v);
            }
            /**
             * 以下代码会先压入栈中,在递归完成后会以先进后出的
             * 形式执行,达到从下往上把各个顶点的邻接点打印的效果
             *           0
             *          / \
             *         1   4
             *        / \   \
             *       2   5   6
             *      /
             *     3
             */
            //   v = getNextBor(i,v);
        }
    }

    public void bfs() {
        for (int i = 0; i < verticesSize; i++) {
            if (!isVisited[i]) {
                /*如果顶点没被访问过先打印*/
                System.out.println("打印顶点" + vertices[i]);
                isVisited[i] = true;
                bfs(i);
            }

        }
    }

    public void bfs(int i) {
        /*自定义的队列*/
        Queue queue = new Queue();
        int first = getFirstBor(i);
        /*如果存在且没有被访问过*/
        if (first != -1 && !isVisited[first]) {
            isVisited[first] = true;
            System.out.println("first " + vertices[first]);
            queue.enQueue(queue, first);

        }
        int next = getNextBor(i, first);
        while (next != -1) {
            if (!isVisited[next]) {
                isVisited[next] = true;
                System.out.println("next " + vertices[next]);
                queue.enQueue(queue, next);
            }

            next = getNextBor(i, next);
        }
        /*重复以上操作*/
        while (queue.front != queue.rear) {
            int temp = queue.array[queue.front];
            bfs(temp);
            queue.front = (queue.front + 1) % queue.MAX_LENGTH;
        }


    }

}

未完待续!!!

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

推荐阅读更多精彩内容

  • 弗洛伊德算法适用于为图中每一个顶点求最短路径,思路如下 检查图中任何一个 到 任何另一个点能否通过第一个点降低最短...
    RichardW阅读 949评论 0 1
  • 1. 图的定义和基本术语 线性结构中,元素仅有线性关系,每个元素只有一个直接前驱和直接后继;树形结构中,数据元素(...
    yinxmm阅读 5,434评论 0 3
  • 1)这本书为什么值得看: Python语言描述,如果学的Python用这本书学数据结构更合适 2016年出版,内容...
    孙怀阔阅读 12,456评论 0 15
  • 生活是一首歌,你永远不知道下一秒会发生什么。 以前看电视,看电影,都觉得演员演得太假,编得太不真实。因为生活没那...
    一生两世阅读 278评论 0 1
  • 公司新来的保洁阿姨不多话,很没有存在感。 不像先前那一位,总是笑嘻嘻让人不要把垃圾袋弄湿,躲在厕所玩手机,喊她过来...
    虽一然阅读 164评论 0 0