关于《海量用户积分算法探讨》的读后总结和扩展

周末在看《码农 第一期 算法》的时候看到了这样一篇文章:《海量用户积分算法探讨》如下是原文地址:算法

http://www.ituring.com.cn/article/62896数组

下面是个人一些总结和讨论数据结构

须要解决的问题是:

某海量用户网站,用户拥有积分,积分可能会在使用过程当中随时更新。如今要为该网站设计一种算法,在每次用户登陆时显示其当前积分排名。用户最大规模为2亿;积分为非负整数,且小于 100万。
优化


问题就能够总结为:网站

1.更改积分spa

2.查询排名设计

设计算法的时候也就要着重考虑查询和更改的时间复杂度orm


算法

文章中提到了四种算法:
1. 简单的查询语句实现,直接进行全表的遍历扫描,得出结果。

2.均匀分区,如:[0, 1000), [1000, 2000), …, [999 000, 1 000 000)这样划分。每个分区有count个用户。在分区积分表的辅助下查询积分为s的用户的排名,能够首先肯定其所属区间,把高于s的积分区间的count值累加,而后再查询出该用户在本区间内的排名,两者相加便可得到用户的排名。

3.采用线段二叉树的方法,假设最多有1 000 000积分,咱们能够把[0, 1 000 000)做为一级区间;再把一级区间分为两个2级区间[0, 500 000), [500 000, 1 000 000),而后把二级区间二分为4个3级区间[0, 250 000), [250 000, 500 000), [500 000, 750 000), [750 000, 1 000 000)。这样咱们就将获得一棵平衡二叉树。以区间范围为节点,每一个节点存储着这个区间范围用户的总数,count。
该平衡二叉树的非叶子节点的count恒等于子节点count之和,且左子结点表明低分区间,右子结点表明高分区间。
在这种树形分区积分表的辅助下查询积分为s的用户排名,其实是一个在区间树上由上至下、由粗到细一步步明确s所在位置的过程。好比,对于积分499 000,咱们用一个初值为0的排名变量来作累加;首先,它属于1级区间的左子树[0, 500 000),那么该用户排名应该在右子树[500 000, 1 000 000)的用户数count以后,咱们把该count值累加到该用户排名变量,进入下一级区间;其次,它属于3级区间的[250 000, 500 000),这是2级区间的右子树,因此不用累加count到排名变量,直接进入下一级区间;再次,它属于4级区间的……如此往复,直到最后咱们把用户积分精肯定位在21级区间[499 000, 499 001),整个累加过程完成,得出排名!

4.使用积分排名数组的方法。因为:用户的积分从s变为s+n,积分小于s或者大于等于s+n的其余用户排名实际上并不会受到影响,只有积分在[ss+n)区间内的用户排名会降低1位。咱们能够用一个大小为100 000 000 的数组表示积分和排名的对应关系,其中rank[s]表示积分s所对应的排名。初始化时,rank数组能够由user_score表在O(n)的复杂度内计算而来。用户排名的查询和更新基于这个数组来进行。查询积分s所对应的排名直接返回rank[s]便可,复杂度为O(1);当用户积分从s变为s+n,只须要把rank[s]到rank[s+n-1]这n个元素的值增长1便可,复杂度为O(n)。

算法分析

第1种算法无疑除了实现简单以外,没有任何优势,全表遍历时间,不管更新仍是查找复杂度都很大。
第2种算法作了些许优化,不用全表遍历了,这样好比搜索积分为1 000的用户排名,就只须要在对应的积分区间里面遍历。可是根据二八定律,80%的用户集中在了20%的低分区,因此可能查询积分为1000的用户的时候,对应的积分区间仍是有海量的用户,遍历的表仍是很大。均匀分区的方法就被pass了,可是不均匀分区能够吗?将低分区划分的密集一些,将高分区划分的稀疏。可是这种分发过于随性,且一种分法确定会因为系统的使用而变得不适用。
第3种方法是标准的线段二叉树,因为是平衡二叉树,因此查询和更新的时间复杂度都是O(Log(n)),这样就高效了不少,可是有一个问题是,当n很小的时候,这种算法的优点并不明显。并且做为一个积分排名系统,查询的频率远大于更新的频率,因此就有了算法4.
第4种算法在初始化和更改排名的时候复杂度是O(n),可是因为能够直接经过数组下标(即积分的值)查询排名,因此查询的复杂度是O(n)。

那么第3和第4孰优孰劣呢?的确在,n较小且重视查询的时候,算法4比算法3有优点,且实现起来简单不少。可是当数据量很是大的时候,算法4的更新的时间复杂度O(n)仍是照O(Log(n))差了太远。因此仍是要看数据量的大小,分状况来决定孰优孰劣。

个人拓展

原文博主说了,这是开放问题,那么我就说出个人解决方案。
算法三、4的确已经将问题较为优化的解决了。可是这其中其实仍是有问题的。 咱们一直假设整个数据结构存储在计算机的主存中。但是,假设数据量极大,主存装不下,那么就意味着必须把数据结构放到磁盘上,大O假设全部操做耗时相等,那么此时大O模型再也不适用。为了节省一次磁盘访问时间,咱们愿意进行大量的计算。不平衡的二叉树这时候就是一个灾难,最坏情形下它具备线性的深度,平衡二叉树会好不少,1千万次磁盘访问,也就是须要log(10000000)≈25.
解决办法很简单,若是有更多的分支,就会有更小的深度,也就是说咱们能够用b树来实现算法。具体的算法与算法3相似,仍然以积分区间为节点,节点储存用户数为数据。
这种算法在处理更大规模的数据时,较算法三、4又有了必定优化,可是仍然会在遇到小数据量的时候,凸显出查询效率较低,实现复杂等问题。