暴力法求解“微信群覆盖”?

开发 开发工具 前端
假设微信有M个群(M为亿级别),每个群内平均有N个用户(N为十级别),下面,我们设计算法,求群的覆盖,并说明算法时间与空间复杂度。

题目:求微信群覆盖

微信有很多群,现进行如下抽象:

  • 每个微信群由一个***的gid标识;
  • 微信群内每个用户由一个***的uid标识;
  • 一个用户可以加入多个群;
  • 群可以抽象成一个由不重复uid组成的集合,例如:
  1. g1{u1, u2, u3} 
  2. g2{u1, u4, u5} 

可以看到,用户u1加入了g1与g2两个群。

[[249955]]

画外音,注意:

  • gid和uid都是uint64;
  • 集合内没有重复元素;

假设微信有M个群(M为亿级别),每个群内平均有N个用户(N为十级别).

现在要进行如下操作:

(1) 如果两个微信群中有相同的用户,则将两个微信群合并,并生成一个新微信群;

例如,上面的g1和g2就会合并成新的群:

  1. g3{u1, u2, u3, u4, u5}; 

画外音:集合g1中包含u1,集合g2中包含u1,合并后的微信群g3也只包含一个u1。

(2) 不断的进行上述操作,直到剩下所有的微信群都不含相同的用户为止;

将上述操作称:求群的覆盖。

设计算法,求群的覆盖,并说明算法时间与空间复杂度。

画外音:58同城2013年校招笔试题。

对于一个复杂的问题,思路肯定是“先解决,再优化”,大部分人不是神,很难一步到位。先用一种比较“笨”的方法解决,再看“笨方法”有什么痛点,优化各个痛点,不断升级方案。

拿到这个问题,很容易想到的思路是:

  • 先初始化M个集合,用集合来表示微信群gid与用户uid的关系;
  • 找到哪两个(哪些)集合需要合并;
  • 接着,进行集合的合并;
  • 迭代步骤二和步骤三,直至所有集合都没有相同元素,算法结束;

***步,如何初始化集合?

set这种数据结构,大家用得很多,来表示集合:

  • 新建M个set来表示M个微信群gid
  • 每个set插入N个元素来表示微信群中的用户uid

set有两种最常见的实现方式,一种是树型set,一种是哈希型set。

假设有集合:

  1. s={7, 2, 0, 14, 4, 12} 

树型set的实现如下:

其特点是:

  • 插入和查找的平均时间复杂度是O(lg(n))
  • 能实现有序查找
  • 省空间

哈希型set实现如下:

其特点是:

  • 插入和查找的平均时间复杂度是O(1)
  • 不能实现有序查找

画外音:求群覆盖,哈希型实现的初始化更快,复杂度是O(M*N)。

第二步,如何判断两个(多个)集合要不要合并?

集合对set(i)和set(j),判断里面有没有重复元素,如果有,就需要合并,判重的伪代码是:

  1. // 对set(i)和set(j)进行元素判断并合并 
  2. (1)    foreach (element in set(i)) 
  3. (2)    if (element in set(j)) 
  4.          merge(set(i), set(j)); 

***行(1)遍历***个集合set(i)中的所有元素element;

画外音:这一步的时间复杂度是O(N)。

第二行(2)判断element是否在第二个集合set(j)中;

画外音:如果使用哈希型set,第二行(2)的平均时间复杂度是O(1)。

这一步的时间复杂度至少是O(N)*O(1)=O(N)。

第三步,如何合并集合?

集合对set(i)和set(j)如果需要合并,只要把一个集合中的元素插入到另一个集合中即可:

  1. // 对set(i)和set(j)进行集合合并 
  2. merge(set(i), set(j)){ 
  3. (1)    foreach (element in set(i)) 
  4. (2)    set(j).insert(element); 

***行(1)遍历***个集合set(i)中的所有元素element;

画外音:这一步的时间复杂度是O(N)。

第二行(2)把element插入到集合set(j)中;

画外音:如果使用哈希型set,第二行(2)的平均时间复杂度是O(1)。

这一步的时间复杂度至少是O(N)*O(1)=O(N)。

第四步:迭代第二步与第三步,直至结束

对于M个集合,暴力针对所有集合对,进行重复元素判断并合并,用两个for循环可以暴力解决:

  1. (1)for(i = 1 to M) 
  2. (2)    for(ji+1 to M) 
  3.          //对set(i)和set(j)进行元素判断并合并 
  4.          foreach (element in set(i)) 
  5.          if (element in set(j)) 
  6.          merge(set(i), set(j)); 

递归调用,两个for循环,复杂度是O(M*M)。

综上,如果这么解决群覆盖的问题,时间复杂度至少是:

  1. O(M*N) // 集合初始化的过程 
  2. O(M*M) // 两重for循环递归 
  3. O(N) // 判重 
  4. O(N) // 合并 

画外音:实际复杂度要高于这个,随着集合的合并,集合元素会越来越多,判重和合并的成本会越来越高。

基于“先解决,再优化”的思想,很多优化方向的问题,自然而然的从脑中蹦出:

(1) 能不能快速通过元素定位集合?

画外音:

  • 通过集合查元素,哈希型set时间复杂度是O(1);
  • 通过元素查集合(句柄),如何来实现呢?

(2) 能不能快速进行集合合并?

(3) 能不能一次合并多个集合?

经典数据结构,分离集合(disjoint set),它有三类操作:

  • Make-set(a):生成一个只有一个元素a的集合;
  • Union(X, Y):合并两个集合X和Y;
  • Find-set(a):查找元素a所在集合,即通过元素找集合;

特别适合用来解决这类集合合并与查找的问题,又称为并查集。

如何利用并查集来解决求“微信群覆盖”问题,是后文将要介绍的内容。

画外音:先介绍“并查集”这一种方案,后续再介绍其他方案。

知道并查集的思路和原理,比知道什么是并查集更重要。

算法,其实还是挺有意思的。

【本文为51CTO专栏作者“58沈剑”原创稿件,转载请联系原作者】

戳这里,看该作者更多好文

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2021-12-09 15:02:21

算法微信群覆盖开发

2021-04-20 08:30:23

微信微信输入法张小龙

2017-03-27 13:20:36

2021-04-26 05:39:03

微信输入法腾讯

2019-12-16 17:25:04

Python微信群同步直播

2013-08-08 10:13:25

微信

2019-10-14 11:26:05

开源技术 软件

2015-10-19 15:20:14

有鱼

2020-03-17 15:01:19

微信医保电子凭证

2021-04-27 13:43:42

微信iOS输入法

2019-11-26 10:08:00

微信医院挂号

2019-12-20 09:22:12

垃圾分类微信城市服务

2020-02-05 13:15:03

微信移动应用

2020-01-08 06:40:12

微信微信群移动应用

2021-09-30 05:39:05

微信Android 8.0腾讯

2013-10-24 11:00:30

马云微信

2017-01-11 17:01:20

飞鱼星

2020-07-27 15:06:14

微信张小龙焦虑

2015-02-13 10:20:15

微信

2013-08-26 15:21:41

微博微信易信
点赞
收藏

51CTO技术栈公众号