zoukankan      html  css  js  c++  java
  • 图的存储结构

    废话不多说,上来撸干货。
    我们知道,实现图共有两种常用的方法:邻接矩阵、邻接表法。接下来我们就来一一介绍这两种方法。
    实际上,图的存储结构有些复杂,为了方便读者理解,也为了方便笔者的写作,这部分的篇幅会长一些,稍有些啰嗦,还望见谅。

    一、邻接矩阵法


    显然,图是由顶点(vex)和边(arc)构成的。在这种情况下,如果我们想直接将这两部分合在一起存储,不说别的,单单思维上就很混乱。因为边本身就是由两个顶点连接组成的,所以说,合在一起很困难。
    于是,我们就想到了分开存储。而顶点中所包含的数据一般是相同的,所以,利用一维数组来存储顶点数据是很好的办法。那么对于边来说呢?
    边是由两个顶点共同构成的,显然一维数组是无法解决的,那也好办,用二维数组试试。一看,二维数组显然是满足我们的需求的嘛,我们正好可以利用二维数组的两个下标,比如说a[i][j],我们可以把i想象成顶点表中的第i个顶点,j是第j个顶点,然后用特殊的标识来确定两点之间是否有边,是不是很简单呢?
    这种特殊的标识,就要用到我们在离散数学中学到的矩阵了。我们可以用1表示存在边,0表示不存在边。如图:
    这里写图片描述
    这样,就构成了一个二维数组。此外,注意观察:
    这里写图片描述
    以矩阵的对角线为中心,两边的数据是对称相等的,这样你联想到了什么呢?没错,就是我们之前学过的对称矩阵。根据之前我们讲的,1和0分别代表有边和无边,所以,通过这个图,我们很容易就能够画出这个无向图了。
    此外,通过一行或者是一列中“1”的个数,就能知道某个点的度。若是求结点的邻接点,只需要将该行(列)中“1”对应的数字符号即可。

    下面介绍有向图。直接上邻接矩阵。
    这里写图片描述
    从这个矩阵上。根据上面无向图的分析,“0”和“1”分别代表的是有边和没有边,所以可以看出各点之间的关系。对于有向图来说,虽然矩阵仍然以主对角线“0”为分界线,但可以看出,对角线的两面并不是对称的。
    一般来讲,有向图研究的是入度出度,在这幅图上,横向代表的是入度,纵向代表的是出度。所以,v1的入度是1,出度是2。
    由此可见,要判断有向图或者无向图两点间有没有边,只需查看v[i][j]是否不为零即可(如果是有向网或无向网,边的权值一般不为1,有可能为无穷大,表示不存在边)。

    下面看如何用代码实现这个过程:

    结构定义

    typedef char VerTexType;                    //假设顶点的数据类型为字符型 
    typedef int ArcType;                        //假设边的权值类型为整型 
    
    //- - - - -图的邻接矩阵存储表示- - - - -
    typedef struct{ 
        char vexs[MVNum];                   //顶点表 
        int arcs[MVNum][MVNum];             //邻接矩阵 
        int vexnum,arcnum;                      //图的当前点数和边数 
    }AMGraph;
    
    int LocateVex(AMGraph G , VerTexType v){
        //确定点v在G中的位置
        for(int i = 0; i < G.vexnum; ++i)
            if(G.vexs[i] == v)
                return i;
       return -1;
    }//LocateVex
    

    有了结构定义,我们就可以进行图的创建,实质上就是向结构中输入数据。

    int CreateUDN(AMGraph &G){ 
        //采用邻接矩阵表示法,创建无向网G 
        int i , j , k;
        cout <<"请输入总顶点数,总边数,以空格隔开:";
        cin >> G.vexnum >> G.arcnum;                            //输入总顶点数,总边数
        cout << endl;
        cout << "输入点的名称,如a" << endl;
        for(i = 0; i < G.vexnum; ++i){   
            cout << "请输入第" << (i+1) << "个点的名称:";
            cin >> G.vexs[i];                                   //依次输入点的信息 
        }
        cout << endl;
        for(i = 0; i < G.vexnum; ++i)                           //初始化邻接矩阵,边的权值均置为极大值MaxInt 
            for(j = 0; j < G.vexnum; ++j)   
                G.arcs[i][j] = MaxInt;  
        cout << "输入边依附的顶点及权值,如 a b 5" << endl;
        for(k = 0; k < G.arcnum;++k){                           //构造邻接矩阵 
            VerTexType v1 , v2;
            ArcType w;
            cout << "请输入第" << (k + 1) << "条边依附的顶点及权值:";
            cin >> v1 >> v2 >> w;                               //输入一条边依附的顶点及权值
            i = LocateVex(G, v1);  j = LocateVex(G, v2);        //确定v1和v2在G中的位置,即顶点数组的下标 
            G.arcs[i][j] = w;                                   //边<v1, v2>的权值置为w 
            G.arcs[j][i] = G.arcs[i][j];                        //置<v1, v2>的对称边<v2, v1>的权值为w 
        }//for  
        return OK; 
    }//CreateUDN 

    时间复杂度
    从代码中可知,如果创建n个顶点e条边的邻接矩阵,时间复杂度为O(n+n^2+e),对邻接矩阵的初始化耗费了O(n^2)的时间。

    二、邻接表法

    对于邻接矩阵,我们会发现,当图的边数较少的时候,这种存储方法是非常浪费存储空间的(如图所示)。
    稀疏图
    我们在学习链表的时候知道,由于顺序表存储会浪费空间,所以我们引出了链式表的概念。
    显然,我们也能通过链式表来避免这种空间的浪费。
    首先,图中的顶点和邻接矩阵中的处理方式相同,用一维数组来存储。(链表也可以,不过操作起来不是很方便)
    其次,图中的每个顶点vi的所有邻接点构成一个线性表。由于邻接点的个数不确定,所以用单链表来存储。无向图称为边表,有向图称为顶点vi作为弧尾的出边表。
    这里写图片描述
    从图中可以得知,顶点表的各点由data域和指针域firstedge(指向边表的第一个邻接点)。而边表结点由adjvex域(邻接点域,存储某顶点的邻接点在顶点表中的下标)和next指针域(存储边表下一个结点)组成,如图所示,对于无向图,顶点的度通过边表顶点个数可知,若要判断两点间是否存在边,只需看某顶点的边表中是否存在另一个顶点的下标即可。
    不过对于有向图,若要确定出度,只需要看顶点vi作为弧尾的出边表中的顶点个数,而出度就需要另外想办法了。
    这里写图片描述
    根据“顶点vi作为弧尾的出边表”这个名字可知出度,那么我们是不是可以做出“顶点vi作为弧头的入边表”呢?答案是肯定的。
    由此,我们来引出“逆邻接表”的概念。
    即以顶点Vi作为弧头建立边表,此时邻接点的个数就是入度了。
    这里写图片描述
    所以,可以看出v0的入度是2……

    接下来就是代码实现了:

    结构定义

    //- - - - -图的邻接表存储表示- - - - - 
    typedef struct ArcNode{                     //边结点 
        int adjvex;                             //该边所指向的顶点的位置 
        struct ArcNode *nextarc;                //指向下一条边的指针 
        OtherInfo info;                         //和边相关的信息 
    }ArcNode; 
    
    typedef struct VNode{ 
        VerTexType data;                        //顶点信息 
        ArcNode *firstarc;                      //指向第一条依附该顶点的边的指针 
    }VNode, AdjList[MVNum];                     //AdjList表示邻接表类型 
    
    typedef struct{ 
        AdjList vertices;                       //邻接表 
        int vexnum, arcnum;                     //图的当前顶点数和边数 
    }ALGraph;
    

    创建过程

    int LocateVex(ALGraph G , VerTexType v){
        //确定点v在G中的位置
        for(int i = 0; i < G.vexnum; ++i)
            if(G.vertices[i].data == v)
                return i;
       return -1;
    }//LocateVex
    
    int CreateUDG(ALGraph &G){ 
        //采用邻接表表示法,创建无向图G
        int i , k;
    
        cout <<"请输入总顶点数,总边数中间以空格隔开:";
        cin >> G.vexnum >> G.arcnum;                //输入总顶点数,总边数 
        cout << endl;
    
        cout << "输入点的名称,如 a " <<endl;
        for(i = 0; i < G.vexnum; ++i){              //输入各点,构造表头结点表
            cout << "请输入第" << (i+1) << "个点的名称:";
            cin >> G.vertices[i].data;              //输入顶点值 
            G.vertices[i].firstarc=NULL;            //初始化表头结点的指针域为NULL 
        }//for
        cout << endl;
    
        cout << "请输入一条边依附的顶点,如 a b" << endl;
        for(k = 0; k < G.arcnum;++k){               //输入各边,构造邻接表
            VerTexType v1 , v2;
            int i , j;
            cout << "请输入第" << (k + 1) << "条边依附的顶点:";
            cin >> v1 >> v2;                        //输入一条边依附的两个顶点
            i = LocateVex(G, v1);  j = LocateVex(G, v2);
            //确定v1和v2在G中位置,即顶点在G.vertices中的序号 
    
            ArcNode *p1=new ArcNode;                //生成一个新的边结点*p1 
            p1->adjvex=j;                           //邻接点序号为j 
            p1->nextarc= G.vertices[i].firstarc;  G.vertices[i].firstarc=p1;  
            //将新结点*p1插入顶点vi的边表头部
    
            ArcNode *p2=new ArcNode;                //生成另一个对称的新的边结点*p2 
            p2->adjvex=i;                           //邻接点序号为i 
            p2->nextarc= G.vertices[j].firstarc;  G.vertices[j].firstarc=p2;  
            //将新结点*p2插入顶点vj的边表头部 
        }//for 
        return OK; 
    }//CreateUDG
    

    时间复杂度
    O(n+e)

    欢迎小伙伴们留言!

    结语

    感谢您的阅读,欢迎指正博客中存在的问题,也可以跟我联系,一起进步,一起交流!

    微信公众号:进击的程序狗
    邮箱:roobtyan@outlook.com
    个人博客:https://roobtyan.github.io

  • 相关阅读:
    ASP.NET 4.0尚未在 Web 服务器上注册 解决方法
    Node.js
    AC多模式匹配算法
    红黑树的实现原理
    OAuth2.0 用户验证授权标准 理解
    SNMP 和 NetBios协议理解
    使用RSA非对称密钥算法实现硬件设备授权
    常用
    目录列表
    Memcache的 简介
  • 原文地址:https://www.cnblogs.com/roobtyan/p/9576744.html
Copyright © 2011-2022 走看看