排行榜算法設計實現比較 排序樹 平衡二叉樹

 小結:html

一、mysql

 

https://mp.weixin.qq.com/s/kLepoYkXg9xQRbPvTJ6wowc++

 

排行榜算法設計實現比較

之前在音樂作過一些實時投票,積分排名;單曲、專輯等排行榜;遊戲中也有相似的戰鬥力排行;sns的遊戲又有好友排行等,對於此類的排行算法在此作個總結。redis

 

需求背景:算法

查看前top N的排名用戶sql

查看本身的排名數據庫

用戶積分變動後,排名及時更新數組

 

方案一:緩存

利用mysql來實現,存放一張用戶積分表user_score,結構以下:數據結構

取前top N,本身的排名均可以經過簡單的sql語句搞定。

算法簡單,利用sql的功能,不須要其餘複雜邏輯,對於數據量比較少、性能要求不高,可使用。可是對於海量數據,性能是沒法接受的。

 

方案二:

積分排名數組實現,若有1百萬用戶進行排名,就用一個大小爲1,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)。

 

方案三:

用GCC的pb_ds庫中有assoc_container來進行實現。

參考以下:

http://www.opensource.apple.com/source/llvmgcc42/llvmgcc42-2336.9/libstdc++-v3/testsuite/ext/pb_ds/example/tree_order_statistics.cc

存取效率均可以達到O(log(n)),不足就是程序重啓後數據會丟失。

 

方案四:

本身實現排序樹,大體實現思路以下:

咱們能夠把[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),依此類推,最終咱們會獲得1,000,000個21級區間[0,1), [1,2) … [999,999, 1,000,000)。這其實是把區間組織成了一種平衡二叉樹結構,根結點表明一級區間,每一個非葉子結點有兩個子結點,左子結點表明低分區間,右子結點表明高分區間。樹形分區結構須要在更新時保持一種不變量,非葉子結點的count值老是等於其左右子結點的count值之和。

之後,每次用戶積分有變化所須要更新的區間數量和積分變化量有關係,積分變化越小更新的區間層次越低。整體上,每次所須要更新的區間數量是用戶積分變量的log(n)級別的,也就是說若是用戶積分一次變化在百萬級,更新區間的數量在二十這個級別。在這種樹形分區積分表的輔助下查詢積分爲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),整個累加過程完成,得出排名!

雖然,本算法的更新和查詢都涉及到若干個操做,但若是咱們爲區間的from_score和to_score創建索引,這些操做都是基於鍵的查詢和更新,不會產生表掃描,所以效率更高。另外,本算法並不依賴於關係數據模型和SQL運算,能夠輕易地改造爲NoSQL等其餘存儲方式,而基於鍵的操做也很容易引入緩存機制進一步優化性能。進一步,咱們能夠估算一下樹形區間的數目大約爲2,000,000,考慮每一個結點的大小,整個結構只佔用幾十M空間。因此,咱們徹底能夠在內存創建區間樹結構,並經過user_score表在O(n)的時間內初始化區間樹,而後排名的查詢和更新操做均可以在內存進行。通常來說,一樣的算法,從數據庫到內存算法的性能提高經常能夠達到10^5以上;所以,本算法能夠達到很是高的性能。

算法特色

優勢:結構穩定,不受積分分佈影響;每次查詢或更新的複雜度爲積分最大值的O(log(n))級別,且與用戶規模無關,能夠應對海量規模;不依賴於SQL,容易改造爲NoSQL或內存數據結構。

缺點:算法相對更復雜。

 

方案五:skiplist的實現

實現方案四的時候,發現代碼比較複雜,調試起來特別不方便。遊戲這邊有個同事也實現了個,代碼地址:http://km.oa.com/articles/show/158740

因而就想到的跳錶,發現用這個實現起來比較簡單;用hashmap來存儲具體的對象;用skiplist用來排序。也能夠簡單的用一個map和set來實現。Map內面存具體對象,set用來排序。

關於skip list這裏簡單介紹下:skip list是鏈表的一種特殊形式,對鏈表的一種優化;保證INSERT和REMOVE操做是O(logn),而通用鏈表的複雜度爲O(n);

 

優勢:實現較簡單,效率基本上O(log(N))

缺點:當達到億級別時的數據時,性能會急劇降低

 

方案六:基於redis的 sort set的實現

後來看redis發現redis的zset天生是用來作排行榜的、好友列表, 去重, 歷史記錄等業務需求。接口使用很是簡單。接口很是豐富,基本上須要的實現都能知足,說明以下:

 

 

ZAdd/ZRem是O(log(N)),ZRangeByScore/ZRemRangeByScore是O(log(N)+M),N是Set大小,M是結果/操做元素的個數。

ZSET的實現用到了兩個數據結構:hash table 和 skip list(跳躍表),其中hash table是具體使用redis中的dict來實現的,主要是爲了保證查詢效率爲O(1) ,而skip list(跳躍表)主要是保證元素有序並可以保證INSERT和REMOVE操做是O(logn)的複雜度。

音樂如今的通用投票排名系統就是基於redis來實現的,運行還不錯。

優勢:基於redis開發,速度快;使用redis相關特性

缺點:當達到億級別時的數據時,性能會急劇降低

 

 

來實現排行榜的方法不少,能夠根據本身的具體需求,參考選用。

 

 

參考文章:

http://yaronspace.cn/blog/archives/1259

http://haili.me/archives/750.html

http://km.oa.com/articles/show/158740

相關文章
相關標籤/搜索