之前我们介绍过图的邻接矩阵存储法,它的空间和时间复杂度都是N2,现在我来介绍另外一种存储图的方法:邻接表,这样空间和时间复杂度就都是M。对于稀疏图来说,M要远远小于N2。先上数据,如下。

1
2
3
4
5
6
4 5
1 4 9
4 3 8
1 2 5
2 4 6
1 3 7

第一行两个整数n m。n表示顶点个数(顶点编号为1~n),m表示边的条数。接下来m行表示,每行有3个数x y z,表示顶点x到顶点y的边的权值为z。下图就是一种使用链表来实现邻接表的方法。

上面这种实现方法为图中的每一个顶点(左边部分)都建立了一个单链表(右边部分)。这样我们就可以通过遍历每个顶点的链表,从而得到该顶点所有的边了。使用链表来实现邻接表对于痛恨指针的的朋友来说,这简直就是噩梦。这里我将为大家介绍另一种使用数组来实现的邻接表,这是一种在实际应用中非常容易实现的方法。这种方法为每个顶点i(i从1~n)也都保存了一个类似“链表”的东西,里面保存的是从顶点i出发的所有的边,具体如下。

首先我们按照读入的顺序为每一条边进行编号(1~m)。比如第一条边“1 4 9”的编号就是1,“1 3 7”这条边的编号是5。

这里用u、v和w三个数组用来记录每条边的具体信息,即u[i]、v[i]和w[i]表示第i条边是从第u[i]号顶点到v[i]号顶点(u[i]àv[i]),且权值为w[i]。

再用一个first数组来存储每个顶点其中一条边的编号。以便待会我们来枚举每个顶点所有的边(你可能会问:存储其中一条边的编号就可以了?不可能吧,每个顶点都需要存储其所有边的编号才行吧!甭着急,继续往下看)。比如1号顶点有一条边是 “1 4 9”(该条边的编号是1),那么就将first[1]的值设为1。如果某个顶点i没有以该顶点为起始点的边,则将first[i]的值设为-1。现在我们来看看具体如何操作,初始状态如下。

咦?上图中怎么多了一个next数组,有什么作用呢?不着急,待会再解释,现在先读入第一条边“1 4 9”。

读入第1条边(1 4 9),将这条边的信息存储到u[1]、v[1]和w[1]中。同时为这条边赋予一个编号,因为这条边是最先读入的,存储在u、v和w数组下标为1的单元格中,因此编号就是1。这条边的起始点是1号顶点,因此将first[1]的值设为1。

另外这条“编号为1的边”是以1号顶点(即u[1])为起始点的第一条边,所以要将next[1]的值设为-1。也就是说,如果当前这条“编号为i的边”,是我们发现的以u[i]为起始点的第一条边,就将next[i]的值设为-1(貌似的这个next数组很神秘啊⊙_⊙)。

读入第2条边(4 3 8),将这条边的信息存储到u[2]、v[2]和w[2]中,这条边的编号为2。这条边的起始顶点是4号顶点,因此将first[4]的值设为2。另外这条“编号为2的边”是我们发现以4号顶点为起始点的第一条边,所以将next[2]的值设为-1。

读入第3条边(1 2 5),将这条边的信息存储到u[3]、v[3]和w[3]中,这条边的编号为3,起始顶点是1号顶点。我们发现1号顶点已经有一条“编号为1 的边”了,如果此时将first[1]的值设为3,那“编号为1的边”岂不是就丢失了?我有办法,此时只需将next[3]的值设为1即可。现在你知道next数组是用来做什么的吧。next[i]存储的是“编号为i的边”的“前一条边”的编号。

读入第4条边(2 4 6),将这条边的信息存储到u[4]、v[4]和w[4]中,这条边的编号为4,起始顶点是2号顶点,因此将first[2]的值设为4。另外这条“编号为4的边”是我们发现以2号顶点为起始点的第一条边,所以将next[4]的值设为-1。

读入第5条边(1 3 7),将这条边的信息存储到u[5]、v[5]和w[5]中,这条边的编号为5,起始顶点又是1号顶点。此时需要将first[1]的值设为5,并将next[5]的值改为3。

此时,如果我们想遍历1号顶点的每一条边就很简单了。1号顶点的其中一条边的编号存储在first[1]中。其余的边则可以通过next数组寻找到。请看下图。

细心的同学会发现,此时遍历边某个顶点边的时候的遍历顺序正好与读入时候的顺序相反。因为在为每个顶点插入边的时候都直接插入“链表”的首部而不是尾部。不过这并不会产生任何问题,这正是这种方法的其妙之处。

创建邻接表的代码如下

 1 int n,m,i;
 2 //u、v和w的数组大小要根据实际情况来设置,要比m的最大值要大1
 3 int u[6],v[6],w[6];
 4 //first和next的数组大小要根据实际情况来设置,要比n的最大值要大1
 5 int first[5],next[5];
 6 scanf("%d %d",&n,&m);
 7 //初始化first数组下标1~n的值为-1,表示1~n顶点暂时都没有边
 8 for(i=1;i<=n;i++)
 9     first[i]=-1;
10 for(i=1;i<=m;i++)
11 {
12     scanf("%d %d %d",&u[i],&v[i],&w[i]);//读入每一条边
13     //下面两句是关键啦
14     next[i]=first[u[i]];
15     first[u[i]]=i;
16 }

接下来如何遍历每一条边呢?我们之前说过其实first数组存储的就是每个顶点i(i从1~n)的第一条边。比如1号顶点的第一条边是编号为5的边(1 3 7),2号顶点的第一条边是编号为4的边(2 4 6),3号顶点没有出向边,4号顶点的第一条边是编号为2的边(2 4 6)。那么如何遍历1号顶点的每一条边呢?也很简单。请看下图:

遍历1号顶点所有边的代码如下

1 k=first[1];// 1号顶点其中的一条边的编号(其实也是最后读入的边)
2 while(k!=-1) //其余的边都可以在next数组中依次找到
3 {
4     printf("%d %d %d\n",u[k],v[k],w[k]);
5     k=next[k];
6 }

遍历每个顶点的所有边的代码如下

1 for(i=1;i<=n;i++)
2 {
3     k=first[i];
4     while(k!=-1)
5     {
6         printf("%d %d %d\n",u[k],v[k],w[k]);
7         k=next[k];
8     }
9 }

可以发现使用邻接表来存储图的时间空间复杂度是O(M),遍历每一条边的时间复杂度是也是O(M)。如果一个图是稀疏图的话,M要远小于N2。因此稀疏图选用邻接表来存储要比邻接矩阵来存储要好很多。

图的存储结构之邻接表(详解)相关推荐

  1. 数据结构之图的存储结构:邻接表法

    图的存储结构:邻接表法 产生条件: 邻接表法的定义: 邻接表法的特点: 邻接表法的代码定义: 邻接表法与邻接矩阵法的对比: 产生条件: 当用邻接矩阵存储时:空间复杂度为O(|v|^2),太大 邻接表法 ...

  2. 图的存储结构之邻接表

    什么是邻接表? 邻接表(Adjacency List)是图的一种顺序存储与链式存储结合的存储方法. 对于图G中的每个顶点Vi,将所有邻接于Vi的顶点Vj链成一个单链表,这个单链表就称为顶点Vi的邻接表 ...

  3. 数据结构之图的存储结构:邻接多重表

    图的存储结构:邻接多重表 产生条件: 邻接多重表的定义: 邻接多重表的代码定义: 删除: 性能分析: 十字链表与邻接多重表的对比 产生条件: 当用邻接矩阵法存储时:空间复杂度为O(|V|^2),太大 ...

  4. 图之邻接表详解(C语言版)

    文章目录 一.定义 二.结构 三.常用操作 四.测试 结语 附录 一.定义 图的邻接表是一种顺序与链式存储相结合的存储方式.下面给出一个示例,以便大家能够理解邻接表这种存储方式:         无向 ...

  5. 图 - 存储结构之邻接表

    对于图来说,邻接矩阵是不错的一种图存储结构,但是我们也发现,对于边数相对顶点较少的图,这种结构是存在对存储空间的极大浪费的.因此我们考虑另外一种存储结构方式:邻接表(Adjacency List),即 ...

  6. 数据结构之图的存储结构:邻接矩阵法

    图的存储结构:邻接矩阵法 邻接矩阵法: 邻接矩阵的定义: 邻接矩阵存储无向图: 邻接矩阵存储有向图: 邻接矩阵存储网: 邻接矩阵法的性质: 邻接矩阵法的代码实现: 矩阵运算A的n次幂的含义: 性能分析 ...

  7. 图的存储-邻接矩阵和邻接表之间的相互转化

    邻接矩阵和邻接表之间的相互转化,输出邻接矩阵和邻接表算法实现. 将文件保存为GraphBasicOperation.cpp文件,具体实现如下: #include <stdio.h> #in ...

  8. [转]数据结构:图的存储结构之邻接多重表

    1.引言: 若要删除左边的(V0,V2)这条边,需要对图下表的阴影两个结点进行删除操作. 2.邻接多重表的存储结构: iVex和jVex:是与某条边依附的两个顶点在顶点表中的下标. iLink:指向依 ...

  9. 图的存储--邻接矩阵和邻接表(链表实现和用vector实现)

    邻接矩阵: #include<iostream> #include<vector> #include<iomanip> #include<string> ...

最新文章

  1. HashMap内部结构深入剖析
  2. 从 NavMesh 网格寻路回归到 Grid 网格寻路。
  3. sleep函数_MySQL中的sleep函数介绍
  4. NYOJ 5767 装背包
  5. 【计算机网络复习 数据链路层】3.6.2 以太网
  6. curl命令详解_命令行学习(一)基础命令
  7. 洛希极限 (10 分)
  8. 多模态简述(情感分析)
  9. 查看 Python 内置函数的方法
  10. Co-occurrence网络图绘制教程(附详细代码)
  11. 机器人 铁血兵团 魂斗罗_【魂斗罗铁血兵团中文版】魂斗罗铁血兵团中文版下载-街机中国...
  12. linux系统top命令:virt,res,shr详解
  13. 重庆万豪行政公寓:经典焕新,传奇永续
  14. appium-desktop Capability
  15. 多年锤炼,迈向Kata 3.0 !走进开箱即用的安全容器体验之旅
  16. 搜索已配对蓝牙 java,java-查找和配对蓝牙设备
  17. 祛湿不能迷信红豆薏米水!
  18. 哨向 Mika Lelush 1
  19. Linux共享库编程方法,Linux共享库c
  20. {2018.5.8}荀(gou)彧(huo)的贪心初步小结

热门文章

  1. JavaSpring框架有哪些优势?
  2. 开课吧课堂:Kubernetes集群环境常见问题解决
  3. Gse v0.40.0 发布,Go 高性能分词,增加更多常用 API
  4. Skype for Business 2015全新部署_10.边缘服务器安装01
  5. IJCAI最佳论文公布 华人斩获最佳学生论文奖!
  6. 受够了碎片化,Salesforce决定只支持部分安卓设备
  7. HttpClient使用具体解释
  8. Redis源码分析系列三:initServerConfig下半部分
  9. 【暴力枚举】LeetCode 90. Subsets II
  10. Rust小试牛刀之猜猜看游戏