数据结构与算法--图

图的概念

(graph)和树比起来,是一种更加复杂的非线性表结构。

顶点&边

树中的元素成为节点,图中的元素叫做顶点(vertex)。图一个顶点可以与任意其他顶点建立连接关系,这种建立的关系叫(edge)。

在微信中,可以把每个用户看作一个顶点,两个用户之间互加好友,那就在两者之间建立一条边。所以,整个微信的好友关系就可以用一张图来表示。其中,每个用户有多少好友,对应到图中,就叫做顶点的(degree),就是跟顶点相连的边的条数。

有向图&无向图

微博允许单向关注,用户A关注了用户B,但用户B可以不关注用户A。如果用户A关注了用户B,就在图中画一条从
A到B的带箭头的边,来表示边的方向。如果用户A和用户B互相关注了,那我们就画一条从A指向B的边,再画一条从B指向A的边。

这种边有方向的图叫做“有向图”。边没有方向的图就叫做“无向图”。

无向图总的“度”表示一个顶点有多少条边,在有向图中度分为入度(In-degree)和出度(Out-degree)。

顶点的入度,表示有多少条边指向这个顶点;顶点的出度,表示有多少条边是以这个顶点为起点指向其他顶点。

对应到微博中的例子,入度就表示有多少粉丝,出度就表示关注了多少人。

带权图

QQ还记录了两个用户之间的亲密度,如果两个用户经常往来,那亲密度就比较高;如果不经常往来,亲密度就比较低。在带权图(weighted graph)中,每条边都有一个权重(weight),可以通过这个权重来表示QQ好友间的亲密度。

图的存储

邻接矩阵存储方法

邻接矩阵(Adjacency Matrix)的底层依赖一个二维数组。

对于无向图来说,如果顶点i与顶点j之间有边,就将A[i][j]和A[j][i]标记为1;

对于有向图来说,如果顶点i指向顶点j就将A[i][j]标记为1。同理,如果顶点j指向顶点i就将A[j][i]标记为1。对于带权图,数组中就存储相应的权重。

邻接矩阵的底层依赖一个二维数组。对于无向图来说,如果顶点i与顶点j之间有边,我们就将A[i][j]和A[j][i]标记为1;对于有向图来说,如果顶点i和顶点j之间,有一条箭头从顶点i指向顶点j的边,那我们就将A[i][j]标记为1。同理,如果有一条箭头从顶点j指向顶点i的边,我们就将A[j][i]标记为1。对于带权图,数组中就存储相应的权重。

用邻接矩阵来表示一个图,虽然简单、直观,但是比较浪费存储空间。为什么这么说呢?

对于无向图来说,如果A[i][j]等于1,那A[j][i]也肯定等于1,只需要存储上三角矩阵或下三角矩阵可以节省一半的空间。

稀疏图(Sparse Matrix)是指每个顶点的边不多的图,用邻接矩阵的存储方法会非常浪费空间。

比如微信有好几亿的用户,对应到图上有好几亿的顶点,但每个用户的好友一般也就三五百个。如果用邻接矩阵来存储,那绝大部分的存储空间都被浪费了。

但邻接矩阵的存储方式简单、直接,因为基于数组,所以在获取两个顶点的关系时,就非常高效。其次用邻接矩阵的方式存储图,可以将很多图的运算转换成矩阵之间的运算。比如求最短路径Floy-Waeshall算法,就是利用矩阵循环相乘若干次得到的结果。

邻接表存储方法

邻接表(Adjacency List)的每个顶点对应一条链表,链表中存储的是与这个顶点相连接的其他顶点。

无向图可以看作每条边都是双方向的有向图。

邻接矩阵存储起来比较浪费空间,但是使用起来比较节省时间。相反,邻接表存储起来比较节省空间,但是使用起来就比较耗时间。这是时间、空间复杂度互换的设计思想,前者是空间换时间,后者是时间换空间。

上图邻接表的例子中,如果要确定是否存在一条从顶点2到顶点4的边,就要遍历顶点2对应的那条链表,看链表中是否存储顶点4。当然如果链表过长,可以将链表转换成红黑树、跳表、散列表等来提高查询效率,还可以将链表改成有序动态数组,通过二分查找的方法来快速定位两个顶点之间是否存在边。

如果存储微博、微信等社交网络中好友的关系?

假设需要支持下面这几个操作:

  • 判断用户A是否关注了用户B;
  • 判断用户A是否是用户B的粉丝;
  • 用户A关注用户B;
  • 用户A取消关注用户B;
  • 根据用户名称的首字母排序,分页获取用户的粉丝列表;
  • 根据用户名称的首字母排序,分页获取用户的关注列表;

用两个邻接表来存储,一个邻接表存储某个用户关注了哪些用户,即用户的关注关系;另一个邻接表称为逆邻接表存储某个用户被哪些用户关注,即用户的粉丝列表。

如果要查找某个用户关注了哪些用户,在邻接表中查找即可;如果要查找某个用户被哪些用户关注了,从逆邻接表表查找即可。

因为需要按照用户名称的首字母排序,分页来获取用户的粉丝列表或者关注列表,用跳表替换邻接表的链表比较合适。

跳表插入、删除、查找时间复杂度都是O(logn),空间复杂度上是O(n)。最重要的一点,跳表中存储的数据本身就是有序的,分页获取粉丝列表或关注列表,就非常高效。

如果对于小规模的数据,可以将整个社交关系存储在内存中。

如果对于大规模的数据,可以通过哈希算法等数据分片方式,将邻接表存储在不同的机器上。

例如:

当要查询顶点和顶点关系的时候,就利用同样的哈希算法,先定位顶点所在的机器再在相应的机器上查找。

另外一种解决思路,就是利用外部存储(比如硬盘),数据库是经常用来持久化存储关系数据的。

用下面这张图存储这样一个图。为了高效地支持前面定义的操作,可以在表上对这两列都建立索引。

user_id follower_id
1 4
2 1
2 4
3 2
3 5
4 2
4 5
5 2
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335