zoukankan      html  css  js  c++  java
  • 存图的三种方式

    合适的存图方式往往能事半功倍,这里介绍三种方式:邻接、邻接表、链式前向星。

    邻接矩阵

    1)存图思想

    用一个矩阵来记录一个图,矩阵第 i 行第 j 列的值就表示顶点 i 到顶点 j 的权值

    2  代码实现

     1 #include<stdio.h>
     2 #include<string>
     3 const int V = 1000;  //最大顶点数
     4 int mat[V][V];
     5 
     6 int main()
     7 {
     8     //初始化操作
     9     //假设权值为0表示没有该边
    10     memset(mat, 0, sizeof(mat));  
    11 
    12     //增加边
    13     //增加顶点i到顶点j的权值为w的边
    14     mat[i][j] = w;
    15 
    16     //删除边
    17     mat[i][j] = 0;
    18 
    19     //查询边
    20     mat[i][j]
    21 }

    3)优点

    a.简单易学

    b.对已确定的边进行操作效率高:对已确定的边(两顶点已知),进行增加、删除(或更改权值)、查询,都是O(1)的时间复杂度

    4)缺点

    a.过高的空间复杂度:这几乎是一个致命的缺点,对于顶点数V,邻接矩阵存图的空间复杂度高达O(V * V),顶点数高于10000就不用考虑了。对于稀疏矩阵,太浪费内存了

    邻接表

    1)存图思想

    对每个顶点,使用不定长的链表来存储从该点出发的所有边的情况,第 i 个链表的第 j 个值就是顶点 i 到顶点 j 的权值。

    2)代码实现

     1 #include<stdio.h>
     2 #include<vector>
     3 #include<string>
     4 using namespace std;
     5 
     6 const int V = 100000;
     7 vector<int>e[V];  //定义了V个链表
     8 
     9 int main()
    10 {
    11     int i,j,k;
    12     //邻接链表的初始化
    13     //将起点为"i "的链表清空
    14     e[i].clear();
    15 
    16     //增加边
    17     //增加顶点"i"到顶点"j"的边
    18     e[i].push_back(j);
    19 
    20     //查询边值
    21     //查询以"i"为起点的第"j"条边
    22     e[i][j];
    23 
    24     //寻找权值为k的边
    25     for (int j = 0; j < (int)e[i].size(); j++)
    26     {
    27         if (e[i][j] == k)
    28         {
    29             //do something
    30         }
    31     }
    32 
    33 }

    3)优点

    a.较为简单易学:数组转链表,定长转不定长

    b.内存利用率较高:对于顶点数V、边数E,空间复杂度为O(V + E),能较好的处理稀疏图

    c.对不确定的边操作比较方便:比如,要遍历从某点出发的某条边,不会像邻接矩阵一样可能遍历到不存在的边

    4)缺点

    a.重边不好处理:判重比较麻烦,还要遍历已有的边,不能直接判断;一般情况下使用邻接表存图是会存储重边的,不会做重边的判断

    b.对确定边效率不高:比如对于给定i->j的边要进行查询或修改等操作只有通过遍历这种方式找到

    链式前向星

    1)存图思想

    主要的数据结构是边数组(存储边的数组),当然想要完美表示图,光有一个边集数组还不够,还要有一个数组每一个点的第一条边。同时,每一条边都需要存储接下来一条边的“指针”

    2)代码实现

     1 #include<stdio.h>
     2 #include<string>
     3 const int V = 100000;
     4 const int E = 100000;
     5 
     6 //边结构体的定义
     7 struct Edge
     8 {
     9     int to;        //该边的另一个顶点
    10     int w;        //该边的权值
    11     int next;  //下一条边的数组下标
    12 };
    13 
    14 //head[i]表示顶点i的第一条边的数组下标,"-1"表示顶点i没有边
    15 int head[V];
    16 Edge edge[E];
    17 
    18 
    19 //增加边
    20 //新增加边"a -> b",该边的权值为w,数组下标为"id"
    21 inline void AddEdge(int a, int b, int w, int id)
    22 {
    23     edge[id].to = b;
    24     edge[id].w = w;
    25     edge[id].next = head[a];  // 新增的边要成为顶点`a`的第一条边,而不是最后一条边
    26     head[a] = id;              //类似于链表的头插法,最后一条边指向"-1"
    27     return;
    28 }
    29 int main()
    30 {
    31     int a,b,w;
    32     //链式前向星的初始化
    33     //只需要初始化顶点数组就可以了
    34     memset(head, -1, sizeof(head));
    35 
    36     //遍历从a出发的所有边
    37     for (int i = head[a]; i != -1; i = edge[i].next)
    38     {
    39         //do something
    40         if(edge[i].to == b)  //找到边a -> b
    41         if(edge[i].w == w)   //找到权值为w的边
    42     }
    43 }

    3)优点

    a.内存利用率高:相比vector实现的邻接表而言,可以准确开辟最多边数的内存,不像vector实现的邻接表有爆内存的风险

    b..对不确定的边操作比较方便:和邻接表一样不会遍历到不存在的边

    4)缺点

    a.操作复杂一点:要求比较熟练,不熟练写起来比较慢

    b.重边不好处理:这点与邻接表一样,只有通过遍历判重

    c.对确定边操作效率不高:也与邻接表一样,不能通过两点马上确定边,只能遍历查找

    总结

    对于邻接矩阵,由于内存消耗局限性的,几乎只能用于简单的图论题

    邻接表存图是最为常见的一种方法,绝大部分采用C++STL中的vector实现,一般情况下大部分图论题目都能使用该存图方式。

    而链式前向星是一种较好的用来代替邻接表的数据结构,在邻接表存图不能使用时可以使用,几乎可以用于全部图论题目。如果熟练掌握,可以作为主要的存图方法。

    参考链接:izqt.github.io/2015/07/21/ACM图论之存图方式

  • 相关阅读:
    TListView点击表头进行排序和显示图标的方法
    绘制空心圆
    Delphi中设置默认打印机
    远程桌面(摘自百度)
    添加作业到ORACLE(定时执行的计划)
    字节数转换为KB或MB或GB
    C语言绘制表格
    AcroPDF.ocx退出窗体报错的解决方法
    ADOQuery的批处理方式
    杀掉进程
  • 原文地址:https://www.cnblogs.com/lfri/p/9316529.html
Copyright © 2011-2022 走看看