定义
图中的元素叫做顶点(vertex),图中的一个顶点可以与任意其他顶点建立连接的关系叫做边(edge),一个顶点有多少条边叫做“度”。
无向图(例:微信好友)
有向图(例:微博关注)
在有向图中,度分为入度(In-degree)和出度(Out-degree)。
带权图(例:qq好友亲密度)
每条边都有一个权重(weight)
如何表示这种数据结构
邻接矩阵存储方法
邻接矩阵的底层依赖一个二维数组
- 缺陷
- 在无向图的二维数组中,如果将其用对角线划分为上下两部分,那只需要利用上面或者下面这样一半的空间就足够了,另外一半白白浪费掉了
- 如果存储的是稀疏图(Sparse Matrix),也就是说,顶点很多,但每个顶点的边并不多,那邻接矩阵的存储方法就更加浪费空间了
- 优点
- 邻接矩阵的存储方式简单、直接
- 因为是基于数据,所以访问两顶点的关系时非常高效
- 用邻接矩阵的方式存储图,可以将很多图的运算转换成矩阵之间的运算,便于计算
邻接表(Adjacency List)存储方法
- 有向图
每个顶点对应一条链表,链表中存储的是与这个顶点相连接的其他顶点。
邻接矩阵存储起来比较浪费空间,但是使用起来比较节省时间。相反,邻接表存储起来比较节省空间,但是使用起来就比较耗时间。要判断是否存在一条从顶点 2 到顶点 4 的边,那我们就要遍历顶点 2 对应的那条链表,看链表中是否存在顶点 4。
可以将邻接表中的链表改成平衡二叉查找树。实际开发中,可以选择用红黑树。这样,就可以更加快速地查找两个顶点之间是否存在边了。这里的二叉查找树可以换成其他动态数据结构,比如跳表、散列表等。除此之外,还可以将链表改成有序动态数组,可以通过二分查找的方法来快速定位两个顶点之间否是存在边。
- 无向图 也可以使用邻接表的方式存储每个人所对应的好友列表。为了支持快速查找,好友列表可以使用红黑树存储。
应用
我们需要支持下面这样几个操作:
- 判断用户 A 是否关注了用户 B;
- 判断用户 A 是否被用户B关注;
- 根据用户名称的首字母排序,分页获取用户的粉丝列表;
- 根据用户名称的首字母排序,分页获取用户的关注列表;
-
因为社交网络是一张稀疏图,使用邻接矩阵存储比较浪费存储空间。所以,这里采用邻接表来存储。
-
用一个邻接表来存储这种有向图是不够的。去查找某个用户关注了哪些用户非常容易,但是如果要想知道某个用户都被哪些用户关注了,也就是用户的粉丝列表,是非常困难的。基于此,需要一个逆邻接表。邻接表中存储了用户的关注关系,逆邻接表中存储的是用户的被关注关系。
-
因为需要按照用户名称的首字母排序,分页来获取用户的粉丝列表或者关注列表,用跳表这种结构再合适不过了。这是因为,跳表插入、删除、查找都非常高效,时间复杂度是 O(logn),空间复杂度上稍高,是 O(n)。最重要的一点,跳表中存储的数据本来就是有序的了,分页获取粉丝列表或关注列表,就非常高效。
-
因为数据可能过大,可以通过哈希算法等数据分片方式,将邻接表存储在不同的机器上。你可以看下面这幅图,我们在机器 1 上存储顶点 1,2,3 的邻接表,在机器 2 上,存储顶点 4,5 的邻接表。逆邻接表的处理方式也一样。当要查询顶点与顶点关系的时候,就利用同样的哈希算法,先定位顶点所在的机器,然后再在相应的机器上查找。