zoukankan      html  css  js  c++  java
  • 数据结构和算法学习笔记五:图的基本概念

    一.概念

      1.图(Graph)是由顶点(Vertex)的有穷非空集合和顶点(Edge)之间边的集合组成,通常表示为:G(V,E)。其中G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

      2.图中的数据元素我们称为顶点,顶点之间的逻辑关系我们称为边。

      3.各种图的定义:

        1)无向边和无向图:没有方向的边称为无向边,用无序偶对(vi,vj)表示。如果图中所有边都是无向边,则该图为无向图(Undirected graphs)。

        2)有向边和有向图:有方向的边称为有向边,也称为弧(Arc),用有序偶<vi,vj>来表示从顶点Vj到顶点Vi的有向边,其中Vi称为弧尾(Tail),Vj称为弧头(Head)。如果图中所有边都是有向边,则该图是有向图(Directed graphs)。注意这里弧的方向。

        3)无向完全图和有向完全图:图中任意两个点之间都是无向边,则称图为无向完全图。图中任意两个顶点之间都存在方向相反的两条弧,则称该图是有向完全图。

        4)对于拥有n个顶点的图,如果它是无向完全图,则边数为,如果它是有向完全图,则边数为

        5)稀疏图和稠密图:边或弧很少的图我们称为稀疏图,反之称为稠密图。稀疏图和稠密图的概念比较模糊,一般地,对于一个n个顶点的图,我们可以采用作为稀疏和稠密的分界线,但是这个分解标准不是绝对的。

        6)网:图的边或者弧带有权重,则该图可以称为网(NetWork)。

        7)对于两个图,如果一个图的顶点和边的集合都是另一个图的顶点和边集合的子集,这个图就称为另一个图的子图。

        8)无向图中,一个边的两个顶点称为邻接点或称这两个顶点相邻接;边依附于它的两个顶点或称边与它的两个顶点相关联;和顶点相关联的边的数目称为顶点的度,顶点v的度记为TD(v)。有向图中,也有顶点之间相邻接、弧和顶点相关联的概念,但是注意有向图的方向性:对于弧<vi,vj>,我们可以说顶点vi邻接自顶点vj,以顶点vi为头的弧数目称为vi的入度(InDegree),顶点v的入度记为ID(v),以顶点vi为尾的弧数目称为vi的出度(OutDegree),顶点v的出度记为OD(v)。入度和出度其实就是从顶点指出的箭头数和指向顶点的箭头数。

        9)从一个顶点到另一个顶点经过的顶点的序列称为路径。在无向图中,路径没有方向,有向图中,路径是有方向的。路径的长度是路径上的边或弧的数目。第一个顶点和最后一个顶点相同的路径称为环或回路(cycle);序列中顶点没有重复的路径称为简单路径;第一个顶点和最后一个顶点中间的顶点没有重复的环称为简单环或简单回路。

        10)在无向图中,从一个顶点到另一个顶点有路径,称这两个顶点是连通的;任意两个顶点之间都有路径的图称为连通图;无向图中的极大连通子图称为连通分量。有向图中,两个顶点V和W,从W到V存在路径,从V到W也存在路径,则称这两个顶点是连通的;同样地,任意两个顶点都连通的有向图称为强连通图;有向图中的极大强连通子图称为强连通分量。

    二.图的存储结构

      图的存储可以直接想到的就是多重链表,但是这种存储结构对空间浪费严重,因此我们并不会推荐使用。

      1.邻接矩阵

        考虑到图是由顶点和边或弧组成,因此我们可以使用一个矩阵来存储图。首先我们可以将一个图抽象成如下的一张表:

        图中黄色部分为顶点,中间白色部分为4X4矩阵,矩阵中1代表有弧,0代表没有弧。横向的顶点数和纵向的顶点数一定是相同的。我们规定竖排的顶点作为弧头,横排的顶点作为弧尾,如矩阵中第二排(v1排)第一列(v0列)为1,代表存在一条弧(v0,v1),而第三排(v2排)第二列(v1列)为0,代表弧(v1,v2)不存在。显然,对于任意的图,我们都可以通过这种将边或弧抽象成矩阵的方式来表示,抽象出来的矩阵也有一些规律:1)抽象出的矩阵的主对角线一定都是0(任意顶点到自身都不能连接出边或弧);2)无向图抽象出的矩阵一定是一个关于主对角线对称的对称矩阵;3)对于有向图,一个横排的1的数量就是这个顶点的出度,一个竖排1的数量就是这个顶点的入度;对于无向图,无须考虑方向,找顶点的度直接找横排或者竖排的1的数量均可(相等的)。

        将图抽象成这样一个矩阵之后,我们就可以使用一个数组存储顶点数据结构,使用一个二维数组存储矩阵结构,这样一个图就存储好了。当然,当前存储的图中边或弧是没有权重的,如果是一个有权重的图,将矩阵中的0和1替换为权值即可。

        对于有n个顶点和e条边得无向图网,创建的时间复杂度为O(n+n2+e)。

      2.邻接表

        考虑到一些特殊的图,可能边数会非常少,比如可能只有一条边,这时使用邻接矩阵的方式存储图,矩阵的空间也会浪费比较多。我们可以才去一种类似于树中孩子表示法的存储方式存储图,也就是首先使用数组的结构存储存储顶点,数组中的顶点数据结构分为数据域和指针域两部分,数据域存储顶点的数据,指针域存储顶点的边表(一个存储顶点的所有关联的边的链表)的第一个结点的指针,这样的存储方式我们称为邻接表。对于带权值得图,在边表中得节点中再增加一个权重域存储权重即可。
        以1中的矩阵图为例,首先声明一个数组存储顶点v0、v1、v2、v3的数据信息,在每个数组元素中除了存储顶点数据信息外,还有一个指针域存储节点信息。对于一个有向图来说,数组元素的指针域默认存储的是入指针域头节点(以当前顶点为弧头的弧组成的链表头节点),如v3的指针域就指向一个<v3,v0>和<v3,v1>依次组成的链表。在有向图中,如果指针域链表存储的是以当前顶点为弧尾的弧组成的链表,那么这个图就称为逆邻接表。

        对于有n个顶点和e条边的图,创建的时间复杂度为O(n+e)。

      3.十字链表

        对于有向图来说,如果使用邻接表存储,在边表中只能存储当前节点作为弧头的所有边或者作为弧尾的所有弧,也就是边表中节点个数代表当前节点的出度或者入度;可不可以同时将这个节点作为弧头的弧和作为弧尾的所有弧都存储起来呢?十字链表就可以解决同时存储的问题:

        回看1中的矩阵图形,将它当作一个有向图,我们会发现矩阵中横向的一排弧都是以同一个节点为头节点的弧,竖向的一排弧都是以同一个节点为尾节点的弧,因此如果我们将这两排弧以链表的形式存储到顶点的指针域中,这样的表就称之为十字链表。在十字链表的顶点数据结构中,指针域包含两部分,一部分是存储入度链表的头节点指针,一部分存储出度链表的头节点指针,而不论是入度链表还是出度链表,其中表示弧的节点数据域存储弧头和弧尾,指针域和顶点的指针域类似,一部分存储入度链表的下一个节点指针,一部分存储出度链表的下一个节点指针。下面看图解:

        在上图中,可以看到一个十字链表结构分为节点集合(一般使用数组存储)和弧集合(使用链表存储)两部分,节点集合中的元素是顶点的存储结构,存储了顶点数据和入度链表、出度链表的指针,弧集合中的元素是弧的存储结构,由4个指针组成,分别是弧头、弧尾、下一个入度链表节点、下一个出度链表节点。对于入度链表和出度链表节点的指针我用蓝色的数字进行了编号,接下来跟着数字从1开始逐个看过去就好。首先看1号位,这是v1的出度链表头指针,指向了一个弧集合的链表节点,这个链表节点对应弧的弧尾是v1,然后它的出度指针2又指向了下一个弧尾为v1的节点,这个节点的出度指针3指向下一个弧尾为v1的节点,最后这个节点的出度指针位置(4号位)为空,v1的出度链表有3个节点;同理,v2的出度链表头指针是5号位,然后找到6号位......。接下来看11号位,这是v1的入度链表的头指针,指向了弧头为v1的一个节点,这个节点的入度指针12指向了下一个以v1为弧头的节点,这个节点的13号为为空,v1的入度链表有2个节点......在十字链表中,入度指针域和弧尾是对应的,可以根据弧节点中的弧尾值判断这个节点在哪个顶点的入度链表中,同理,出度指针和弧头是对应的,可以根据弧节点中弧头的值判断这个节点在哪个顶点的出度链表中。查找一个顶点的入度链表或出度链表的操作一定分开来,不要混淆起来,那么十字链表还是不难理解的。

      4.邻接多重表

        对于有向图来说,使用十字链表相对来说比较方便,但是对于无向图,十字链表就很没有必要了,一般使用邻接表即可。对于一个无向图,邻接表的优势在于查找方便,但是在增删边时却会显得麻烦一些,如删除某一边时我们需要遍历这个边的两个依附顶点的边表节点并找到这两条边进行删除操作,这也是有一点麻烦的,因此我们使用邻接多重表来优化邻接表用于表示无向图。接下来同样看图说话:

        上图中,左侧的一列1、2、3、4、5、6是我们的顶点集合的元素,右侧的结构是边集合的元素。节点集合中的黑色数字代表顶点中存储的数据,边集合中的黑色数字是指向顶点集合中元素的指针,对应了这个边的两个依附点;所有的红色数字都是我给的编号,红色数字所在的空间存储指向边表元素的指针。节点集合中的元素有数据域和指针域组成,指针域指向边表的第一个节点;边集合中的元素也就是边表中的元素中包含4个指针,分别指向边的两个依附点和当前节点从属于此依附点的下一个节点位置。接下来我们可以跟着红色数字的顺序遍历这个邻接多重表。首先红色1号位置存储顶点1的边表的头节点指针,这个指针指向的边表节点中存储了其两个依附点的指针(顶点1和顶点2),找到顶点1指针,后面的红色2号位置存储了这个边节点在1号顶点边表中时的下一个节点指针,顺着这个指针就可以找到红色3号位置......以此类推,知道找到红色5号位置指针位空,然后数一数顶点1有4条关联边;之后顶点2的关联边的找法从红色6号位置开始到红色9号位置如法炮制,顶点2有3条关联边......

      5.边集数组

        在邻接多重表中,我们使用链表的形式存储了边集中的元素,但是使用数组形式也是可以的,只需要将指针存储的地址改为存储数组下标即可,这种存储图的形式就是边集数组。

  • 相关阅读:
    java 多线程 CountDownLatch用法
    android的消息处理机制(图+源码分析)——Looper,Handler,Message
    Handler 总结
    Android常用UI编程_TextView实现跑马灯效果
    Android常用UI编程_TextView实现Activity转变
    Android常用UI编程_TextView显示图片和文字(包含超链接)
    Http编程 ___ 1
    Iterator用法
    增强型for循环
    Android_文件下载
  • 原文地址:https://www.cnblogs.com/movin2333/p/14800867.html
Copyright © 2011-2022 走看看