当前位置:网站首页>由排行榜实时更新想到的数状数值

由排行榜实时更新想到的数状数值

2022-07-08 00:01:00 lixia0417mul2

排行榜常规实现
1.单机:使用普通数值存储每个用户的分数,更新某个用户的分数的时间复杂度是O(1),查询用户分数排行榜的时间复杂度是O(NlogN),如果总共有100000个用户,有1000个用户在同时更新,那么时间复杂度是O(1000) + 1000 * O(100000 * log100000) = 1800000100=1800w次操作
2.分布式: 分布式一般是基于redis的zset有序集合实现,每个用户的分数时每个有序集合元素的score的值,redis的zset集合的底层数据结构是跳表,更新分数操作的时间复杂度是O(logN),所以使用跳表结构来实现排行榜,10w用户同时有1000用户在更新时,时间复杂度只是(1000 * log100000) = 180000=18w次操作.

由此可见,如果是单机或者分布式要实现实时更新排行榜的功能,使用有序数组(比如基于跳表结构实现的有序数组)是可以满足要求的,时间复杂度都是O(logN),那还有没有一种结构也可以满足要求,并且空间复杂度仅仅是O(N)呢,答案是有

  1. 树状数值首先是一个数组的结构,数组中的元素有可能存放着前面几个元素的前缀和,也有可能仅仅存放着自身的元素值,至于序号为x的元素存放的值是起前面多少个元素的前缀和由lowbit(x)函数决定,lowbit(x) = x & (-x)也即x的二进制为1的最低位位数决定,比如lowbit(6) = lowbit(110) = 2, lowbit(3)=lowbit(011)=1等
  2. 对于更新操作来说,比如更新x序号位置的分数值,他不仅仅只是更新x位置的元素值,他还要更新那些包含了x这个前缀值的后面的那些序号的值,假设总共有16个数组元素,现在要更新索引位置为6的元素值,除了更新5这个位置的元素值外,还要更新0b110 + lowbit(110) = 0b1000=8这个位置的值,此外还要继续更新0b1000+lowbit(0b1000) = 0x10000=16这个位置的值(不过这个位置的值大于了最大数组长度,不需要更新),相当于要一直加上lowbit的值向后更新对应索引的元素值
  3. 对于获取前缀和或者区间和的操作,由于当前序号的元素值有可能并没有包括他前面所有的元素的元素值,所以需要把前面的值加上,比如要获取一直到索引=6的前缀和,需要加上索引6的元素值,再加上0b110 - lowbit(0b110) = 0b100索引的元素值,还要继续加上0b100-lowbit(0b100) = 0索引的元素值,这样才是完整的前缀和
    对于树状数组来说,更新和求任意的区间和操作的时间复杂度都是O(logN),性能相当理想,排行榜的场景也可以转成使用树状数组实现,不过存放的含义要改变以下,数组中存放的分数等于某个索引值的用户数,此时当我从改变了分数的时候比如从oldScore变成了newScore,相当于索引为oldScore的用户数减去1,走前面的更新流程,然后索引为newScore的用户数加上1,也走前面的更新流程,最后我计算出从索引oldScore到newScore的区间和,这个区间和就是这个用户的排行榜名次变动的值
原网站

版权声明
本文为[lixia0417mul2]所创,转载请带上原文链接,感谢
https://blog.csdn.net/lixia0417mul2/article/details/125650682

随机推荐