竞技场往往是游戏中不可缺少的系统,根据不同的需求类型,竞技场排行榜的类型可以有如下划分:
1.按照排名更新的方式划分:依靠积分进行排行的竞技场,当玩家完成挑战后根据一定的规则进行双方积分的增减,依据新的积分进行重新排名;不依靠积分进行排行的竞技场,只有当排名较低玩家战胜了排名较高玩家时,会交换双方排名。前者一次积分变化会导致较多的玩家排名发生变动,后者一次最多只会影响两名玩家的排名,但要考虑相同玩家同时被挑战的情况。
2.按照排行是否严格划分:并列排行榜允许积分相同的玩家处于同一名次,严格排行榜需要处理玩家相同积分情况下决定排名的规则。
此处我们只讨论积分排行榜。
积分排行榜的关键在于对于一个有序数据结构的维护更新,而对于该数据结构的实现主要有两种思路:
1.自己使用语言实现有序的数据结构
2.借助现成的工具,如MySQL,Redis实现有序排序
1.有序列表
按照积分大小将列表进行排序。
查询TopN:只需获取列表前TopN个元素,时间复杂度O(1)。
查询指定玩家排名:计算积分大于该玩家的人数,时间复杂度O(n)。
更新排名:玩家积分发生变化,需要将旧元素删除,把积分变化后的新元素插入列表中并进行重新排序,若使用快速排序,则时间复杂度为O(n²)。不过大多数情况下列表是基本有序的,比起对单纯打乱的列表进行排序会好很多。
排行榜较小,对效率没有极端要求的时候可以使用这种方法。
2.数组
构建大小为M的数值rank[],此时积分s对应的排名为rank[s]。
查询TopN:获取列表rank[1:N]的元素即可。
查询玩家自己排名:查询积分s对应的排名直接查询rank[s]即可,时间复杂度O(1)。
更新:若用户积分从s变为s+n,则把rank[s]到rank[s+n-1]这n个元素值加1即可,时间复杂度为O(n)。
很明显,游戏中排行榜积分相同的情况并不少见,引入桶可以解决积分相同的情况,但积分更新和查询玩家自身排行的策略需要改变,改变之后的策略可能并不比有序列表效率高。
3.平衡二叉树(AVL)
查询TopN:获取树的前N个元素即可,时间复杂度O(1)。
查询自己的排名:需要对数进行遍历,找出积分高于自己的玩家数量,时间复杂度O(n)。
更新:需要将原元素删除,将新元素插入树中,时间复杂度O(logn)。
4.跳表(skiplist)
跳表本质是可以实现二分查找的有序链表。
个人了解不深,直接给出参考文章:
https://yuerblog.cc/2019/02/13/skiplist-rank/
感觉在计算个人排名的时候效率会比平衡二叉树高,其他地方与平衡二叉树相差不大。
5.树形分区算法
详见
https://cloud.tencent.com/developer/article/1621522
本质是平衡二叉树,但与上面单纯使用平衡二叉树的方法不同,作者预先构建了一棵包含所有积分范围的树,树的每一层会根据范围划分,再将每一个需要统计排名的积分通过范围的比较放入树中,更新树的时候会更新每一层所包含的积分数量。当需要获取积分排名的时候,只需要获取每一层大于该积分的积分数量相加即可,时间复杂度可达到O(logn)。具体的实现方法建议直接进入文章查看。
1.使用SQL查询。
当数据量不大的时候可以直接使用数据库的查询。使用INSERT,UPDATE插入与更新玩家,使用SELECT COUNT查询分数高于某个玩家的人数,即是该玩家的排名。
好处是数据操作的细节被数据库隐藏了,在数量较少的时候能够提供方便的操作,缺点是当数据量大的时候,相较于操作放置于内存的数据结构,其性能会非常低下。
2.使用Redis的sorted set
其内部使用了Hash table和skip list,用来做排行榜有着天生的优势:提供了丰富的排行榜查询接口,例如zrank()用于查询指定元素的排名(下标),zrevrange()用于获取指定区间的元素。
3.使用Erlang有序异键形式(sorted set)的ETS
也有许多团队选择使用Erlang语言进行游戏开发,此时Erlang自带的ETS也是不错的选择。ETS的有序异键表是用平衡二叉树实现的。
排行榜可以定时更新,还是需要同步更新?
排行榜是严格排行榜是可以存在并列排名的排行榜?
如果是同步更新的严格排行榜,则需要自己制定在分数相同情况下的排行规则,一些 有着默认排行规则的排行榜用不了。
除此之外,需要进行排行的玩家数量也是很关键的因素,如果玩家数量不多,或者对于排行过低的玩家排名的准确性(甚至是是否展示排名)不做太强制的要求,除却上面的方法可能还有更适合的实现方式。
备忘参考:
海量用户积分排名算法探讨
https://www.cnblogs.com/weidagang2046/archive/2012/03/01/massive-user-ranking.html
游戏排行榜 – 基于skiplist计算rank排名
https://yuerblog.cc/2019/02/13/skiplist-rank/
在C#中使用二叉树实时计算海量用户积分排名的实现
https://cloud.tencent.com/developer/article/1621522
简单高效的排行榜算法——树状数组
https://www.owenzhang.net/blog/325.html
谈谈陌陌争霸在数据库方面踩过的坑(排行榜篇)
https://blog.codingnow.com/2014/03/mmzb_db_2.html
排行榜算法设计实现比较
https://gameinstitute.qq.com/community/detail/101951
使用Redis Zset来处理活动常用排行榜(精确排行)
https://segmentfault.com/a/1190000011737336
如何对游戏服务器全服玩家进行排名?
https://www.zhihu.com/question/27933771
海量用户的积分排序问题算法的分析
https://segmentfault.com/a/1190000004268386