有这么一个问题,有一个10万条记录的文件,每条记录有两列,分别是用户一次访问在两个服务器上留下的两条cookie记录,可以记作cookie A 和 cookie B。同一个用户的多次访问记录,cookie可能相同,也可能不同(cookie有时效性)。任意两条数据i,j ,如果cookie A相同或者cookie B相同,则认为i, j 是同一个用户留下的记录。同一个用户留下的访问记录可以合并,输出每个用户的所有cookie。例如有文件记录如下(用小写字母表示cookie):
a, b
a, d
c, e
f, d
c, g
则输出为:
{a, f}, {b, d}
{c}, {e, g}
在面试的时候被问到这个问题,当时没有什么思路。回到宿舍,问了宿舍的冰神,冰神告诉我应该用并查集。我才恍然大悟,对于不相交集合的数据就应该用并查集,就是算法导论中的 disjoint-set data structure.
并查集
对并查集介绍摘自CYJB博客。
并查集(Union-find Set)是冰神心中完美的数据结构——简单却有非常大的威力。它主要用于处理一些不相交集合的合并问题。一些常见的用途有求连通子图、最小生成树Kruskal算法、求最近公共祖先(Least Common Ancestors, LCA)等。
每个集合有一个代表元素,对于给定的元素,可以很快的找到这个元素所在集合的代表元素。
并查集有三个基本操作:
1.makeSet(s): 建立一个新的并查集.
2.unionSet(x, y): 把元素x和元素y所在的集合合并。
3.find(x): 找到元素x所在集合的代表,该操作也可以用于判断两个元素是否在同一个集合。
该问题的解法
1.如果有n条记录,给每条记录一个编号0~n-1.
2.分别按照cookie A 和 cookie B排序,分别遍历两个排序后的结果,如果一条记录和它前一个记录cookie相同,合并之。
3.并查集就像一个森林,建立一个字典,key是森林中每个树的根编号(即是每个用户),value是该用户对应的所有cookie。扫描一次整个文件就可以完成Step 3.
相当于对数据做两次排序,三次线性扫描,时间复杂度是O(n).
1 | def init_set(n): |
如果输入cookie就是上文举的例子:1
2
3
4
5
6
7
8
9if __name__ == '__main__':
cookie = [
(0, 'a', 'b'),
(1, 'a', 'd'),
(2, 'c', 'e'),
(3, 'f', 'd'),
(4, 'c', 'g'),
]
print find_user_cookie(cookie)
则运行结果为:1
{0: (set(['a', 'f']), set(['b', 'd'])), 2: (set(['c']), set(['e', 'g']))}